[
  {
    "path": ".github/FUNDING.yml",
    "content": "ko_fi: elvissteinjr\n"
  },
  {
    "path": ".github/workflows/nightly.yml",
    "content": "name: Nightly Build\n\non:\n  push:\n    paths-ignore:\n      - 'README.md'\n      - 'CONTRIBUTING'\n      - 'LICENSE'\n      - 'docs/**'\n\nenv:\n  SOLUTION_FILE_PATH: ./src/DesktopPlus.sln\n  OUTPUT_PATH: ./src/x64/Release\n  BUILD_CONFIGURATION: Release\n\npermissions:\n  contents: read\n\njobs:\n  build:\n    runs-on: windows-latest\n\n    steps:\n    - name: Checkout\n      uses: actions/checkout@v4\n\n    - name: Setup MSBuild\n      uses: microsoft/setup-msbuild@v2\n\n    - name: Restore NuGet Packages\n      working-directory: ${{env.GITHUB_WORKSPACE}}\n      run: nuget restore ${{env.SOLUTION_FILE_PATH}}\n\n    - name: Build\n      working-directory: ${{env.GITHUB_WORKSPACE}}\n      run: msbuild /m /p:Configuration=${{env.BUILD_CONFIGURATION}} /p:DPLUS_SHA='\"${{github.sha}}\"' ${{env.SOLUTION_FILE_PATH}}\n\n    - name: Rename Output Folder for Archive\n      working-directory: ${{env.GITHUB_WORKSPACE}}\n      run: ren ${{env.OUTPUT_PATH}} DesktopPlus\n\n    - name: Upload Artifact\n      uses: actions/upload-artifact@v4\n      with:\n        name: DesktopPlus-${{github.sha}}\n        path: |\n          ./src/x64\n          !./**/*.h\n          !./**/*.lib\n          !./**/*.exp\n"
  },
  {
    "path": "CONTRIBUTING",
    "content": "Short version:\nAny contribution is welcomed with open arms. In the rare case something would be off, it'll be taken care of.\nIf you contribute code, you allow me to use it in a Steam build that may contain additional code not present in this repo.\n\nLong version:\nI want to preface this with stating that the Desktop+ code is no way trying to be professional. Just read the comments.\nIt's provided as-is, in hope somebody finds a use in it. \nThe code grew with time, overall structure may not be up to best practice. If you want to re-organize it to be \"better\", feel free.\n\nIn terms of actually adding code, there are no strict style guidelines. Just don't make the code look foreign in the middle of the existing mess.\nIf you plan to add something big, consider opening an issue for it first.\nI take some needless pride in the low memory footprint, both runtime and size on disk. Try to not add bloat without reason.\nIt's fine for code to be a tiny bit awkward in order to use system libraries instead of pulling megabytes of additional dependencies.\nPerhaps also avoid stacks of abstraction. The code may run on typically beefy machines, but it's running in the background most of time. Make sane decisions.\n\nJust felt like I should mention this stuff somewhere. Not like anyone reads this anyways.\n\nIf you contribute code to this repository, you are also granting me (GitHub user elvissteinjr) permission to distribute it on other platforms such as Steam in binary form, possibly linking to non-free third-party libraries such as the Steamworks API library. Such a build may also contain additional code not present in this repo to support these libraries.\nPlease do not contribute if you do not agree to this."
  },
  {
    "path": "LICENSE",
    "content": "                    GNU GENERAL PUBLIC LICENSE\n                       Version 3, 29 June 2007\n\n Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>\n Everyone is permitted to copy and distribute verbatim copies\n of this license document, but changing it is not allowed.\n\n                            Preamble\n\n  The GNU General Public License is a free, copyleft license for\nsoftware and other kinds of works.\n\n  The licenses for most software and other practical works are designed\nto take away your freedom to share and change the works.  By contrast,\nthe GNU General Public License is intended to guarantee your freedom to\nshare and change all versions of a program--to make sure it remains free\nsoftware for all its users.  We, the Free Software Foundation, use the\nGNU General Public License for most of our software; it applies also to\nany other work released this way by its authors.  You can apply it to\nyour programs, too.\n\n  When we speak of free software, we are referring to freedom, not\nprice.  Our General Public Licenses are designed to make sure that you\nhave the freedom to distribute copies of free software (and charge for\nthem if you wish), that you receive source code or can get it if you\nwant it, that you can change the software or use pieces of it in new\nfree programs, and that you know you can do these things.\n\n  To protect your rights, we need to prevent others from denying you\nthese rights or asking you to surrender the rights.  Therefore, you have\ncertain responsibilities if you distribute copies of the software, or if\nyou modify it: responsibilities to respect the freedom of others.\n\n  For example, if you distribute copies of such a program, whether\ngratis or for a fee, you must pass on to the recipients the same\nfreedoms that you received.  You must make sure that they, too, receive\nor can get the source code.  And you must show them these terms so they\nknow their rights.\n\n  Developers that use the GNU GPL protect your rights with two steps:\n(1) assert copyright on the software, and (2) offer you this License\ngiving you legal permission to copy, distribute and/or modify it.\n\n  For the developers' and authors' protection, the GPL clearly explains\nthat there is no warranty for this free software.  For both users' and\nauthors' sake, the GPL requires that modified versions be marked as\nchanged, so that their problems will not be attributed erroneously to\nauthors of previous versions.\n\n  Some devices are designed to deny users access to install or run\nmodified versions of the software inside them, although the manufacturer\ncan do so.  This is fundamentally incompatible with the aim of\nprotecting users' freedom to change the software.  The systematic\npattern of such abuse occurs in the area of products for individuals to\nuse, which is precisely where it is most unacceptable.  Therefore, we\nhave designed this version of the GPL to prohibit the practice for those\nproducts.  If such problems arise substantially in other domains, we\nstand ready to extend this provision to those domains in future versions\nof the GPL, as needed to protect the freedom of users.\n\n  Finally, every program is threatened constantly by software patents.\nStates should not allow patents to restrict development and use of\nsoftware on general-purpose computers, but in those that do, we wish to\navoid the special danger that patents applied to a free program could\nmake it effectively proprietary.  To prevent this, the GPL assures that\npatents cannot be used to render the program non-free.\n\n  The precise terms and conditions for copying, distribution and\nmodification follow.\n\n                       TERMS AND CONDITIONS\n\n  0. Definitions.\n\n  \"This License\" refers to version 3 of the GNU General Public License.\n\n  \"Copyright\" also means copyright-like laws that apply to other kinds of\nworks, such as semiconductor masks.\n\n  \"The Program\" refers to any copyrightable work licensed under this\nLicense.  Each licensee is addressed as \"you\".  \"Licensees\" and\n\"recipients\" may be individuals or organizations.\n\n  To \"modify\" a work means to copy from or adapt all or part of the work\nin a fashion requiring copyright permission, other than the making of an\nexact copy.  The resulting work is called a \"modified version\" of the\nearlier work or a work \"based on\" the earlier work.\n\n  A \"covered work\" means either the unmodified Program or a work based\non the Program.\n\n  To \"propagate\" a work means to do anything with it that, without\npermission, would make you directly or secondarily liable for\ninfringement under applicable copyright law, except executing it on a\ncomputer or modifying a private copy.  Propagation includes copying,\ndistribution (with or without modification), making available to the\npublic, and in some countries other activities as well.\n\n  To \"convey\" a work means any kind of propagation that enables other\nparties to make or receive copies.  Mere interaction with a user through\na computer network, with no transfer of a copy, is not conveying.\n\n  An interactive user interface displays \"Appropriate Legal Notices\"\nto the extent that it includes a convenient and prominently visible\nfeature that (1) displays an appropriate copyright notice, and (2)\ntells the user that there is no warranty for the work (except to the\nextent that warranties are provided), that licensees may convey the\nwork under this License, and how to view a copy of this License.  If\nthe interface presents a list of user commands or options, such as a\nmenu, a prominent item in the list meets this criterion.\n\n  1. Source Code.\n\n  The \"source code\" for a work means the preferred form of the work\nfor making modifications to it.  \"Object code\" means any non-source\nform of a work.\n\n  A \"Standard Interface\" means an interface that either is an official\nstandard defined by a recognized standards body, or, in the case of\ninterfaces specified for a particular programming language, one that\nis widely used among developers working in that language.\n\n  The \"System Libraries\" of an executable work include anything, other\nthan the work as a whole, that (a) is included in the normal form of\npackaging a Major Component, but which is not part of that Major\nComponent, and (b) serves only to enable use of the work with that\nMajor Component, or to implement a Standard Interface for which an\nimplementation is available to the public in source code form.  A\n\"Major Component\", in this context, means a major essential component\n(kernel, window system, and so on) of the specific operating system\n(if any) on which the executable work runs, or a compiler used to\nproduce the work, or an object code interpreter used to run it.\n\n  The \"Corresponding Source\" for a work in object code form means all\nthe source code needed to generate, install, and (for an executable\nwork) run the object code and to modify the work, including scripts to\ncontrol those activities.  However, it does not include the work's\nSystem Libraries, or general-purpose tools or generally available free\nprograms which are used unmodified in performing those activities but\nwhich are not part of the work.  For example, Corresponding Source\nincludes interface definition files associated with source files for\nthe work, and the source code for shared libraries and dynamically\nlinked subprograms that the work is specifically designed to require,\nsuch as by intimate data communication or control flow between those\nsubprograms and other parts of the work.\n\n  The Corresponding Source need not include anything that users\ncan regenerate automatically from other parts of the Corresponding\nSource.\n\n  The Corresponding Source for a work in source code form is that\nsame work.\n\n  2. Basic Permissions.\n\n  All rights granted under this License are granted for the term of\ncopyright on the Program, and are irrevocable provided the stated\nconditions are met.  This License explicitly affirms your unlimited\npermission to run the unmodified Program.  The output from running a\ncovered work is covered by this License only if the output, given its\ncontent, constitutes a covered work.  This License acknowledges your\nrights of fair use or other equivalent, as provided by copyright law.\n\n  You may make, run and propagate covered works that you do not\nconvey, without conditions so long as your license otherwise remains\nin force.  You may convey covered works to others for the sole purpose\nof having them make modifications exclusively for you, or provide you\nwith facilities for running those works, provided that you comply with\nthe terms of this License in conveying all material for which you do\nnot control copyright.  Those thus making or running the covered works\nfor you must do so exclusively on your behalf, under your direction\nand control, on terms that prohibit them from making any copies of\nyour copyrighted material outside their relationship with you.\n\n  Conveying under any other circumstances is permitted solely under\nthe conditions stated below.  Sublicensing is not allowed; section 10\nmakes it unnecessary.\n\n  3. Protecting Users' Legal Rights From Anti-Circumvention Law.\n\n  No covered work shall be deemed part of an effective technological\nmeasure under any applicable law fulfilling obligations under article\n11 of the WIPO copyright treaty adopted on 20 December 1996, or\nsimilar laws prohibiting or restricting circumvention of such\nmeasures.\n\n  When you convey a covered work, you waive any legal power to forbid\ncircumvention of technological measures to the extent such circumvention\nis effected by exercising rights under this License with respect to\nthe covered work, and you disclaim any intention to limit operation or\nmodification of the work as a means of enforcing, against the work's\nusers, your or third parties' legal rights to forbid circumvention of\ntechnological measures.\n\n  4. Conveying Verbatim Copies.\n\n  You may convey verbatim copies of the Program's source code as you\nreceive it, in any medium, provided that you conspicuously and\nappropriately publish on each copy an appropriate copyright notice;\nkeep intact all notices stating that this License and any\nnon-permissive terms added in accord with section 7 apply to the code;\nkeep intact all notices of the absence of any warranty; and give all\nrecipients a copy of this License along with the Program.\n\n  You may charge any price or no price for each copy that you convey,\nand you may offer support or warranty protection for a fee.\n\n  5. Conveying Modified Source Versions.\n\n  You may convey a work based on the Program, or the modifications to\nproduce it from the Program, in the form of source code under the\nterms of section 4, provided that you also meet all of these conditions:\n\n    a) The work must carry prominent notices stating that you modified\n    it, and giving a relevant date.\n\n    b) The work must carry prominent notices stating that it is\n    released under this License and any conditions added under section\n    7.  This requirement modifies the requirement in section 4 to\n    \"keep intact all notices\".\n\n    c) You must license the entire work, as a whole, under this\n    License to anyone who comes into possession of a copy.  This\n    License will therefore apply, along with any applicable section 7\n    additional terms, to the whole of the work, and all its parts,\n    regardless of how they are packaged.  This License gives no\n    permission to license the work in any other way, but it does not\n    invalidate such permission if you have separately received it.\n\n    d) If the work has interactive user interfaces, each must display\n    Appropriate Legal Notices; however, if the Program has interactive\n    interfaces that do not display Appropriate Legal Notices, your\n    work need not make them do so.\n\n  A compilation of a covered work with other separate and independent\nworks, which are not by their nature extensions of the covered work,\nand which are not combined with it such as to form a larger program,\nin or on a volume of a storage or distribution medium, is called an\n\"aggregate\" if the compilation and its resulting copyright are not\nused to limit the access or legal rights of the compilation's users\nbeyond what the individual works permit.  Inclusion of a covered work\nin an aggregate does not cause this License to apply to the other\nparts of the aggregate.\n\n  6. Conveying Non-Source Forms.\n\n  You may convey a covered work in object code form under the terms\nof sections 4 and 5, provided that you also convey the\nmachine-readable Corresponding Source under the terms of this License,\nin one of these ways:\n\n    a) Convey the object code in, or embodied in, a physical product\n    (including a physical distribution medium), accompanied by the\n    Corresponding Source fixed on a durable physical medium\n    customarily used for software interchange.\n\n    b) Convey the object code in, or embodied in, a physical product\n    (including a physical distribution medium), accompanied by a\n    written offer, valid for at least three years and valid for as\n    long as you offer spare parts or customer support for that product\n    model, to give anyone who possesses the object code either (1) a\n    copy of the Corresponding Source for all the software in the\n    product that is covered by this License, on a durable physical\n    medium customarily used for software interchange, for a price no\n    more than your reasonable cost of physically performing this\n    conveying of source, or (2) access to copy the\n    Corresponding Source from a network server at no charge.\n\n    c) Convey individual copies of the object code with a copy of the\n    written offer to provide the Corresponding Source.  This\n    alternative is allowed only occasionally and noncommercially, and\n    only if you received the object code with such an offer, in accord\n    with subsection 6b.\n\n    d) Convey the object code by offering access from a designated\n    place (gratis or for a charge), and offer equivalent access to the\n    Corresponding Source in the same way through the same place at no\n    further charge.  You need not require recipients to copy the\n    Corresponding Source along with the object code.  If the place to\n    copy the object code is a network server, the Corresponding Source\n    may be on a different server (operated by you or a third party)\n    that supports equivalent copying facilities, provided you maintain\n    clear directions next to the object code saying where to find the\n    Corresponding Source.  Regardless of what server hosts the\n    Corresponding Source, you remain obligated to ensure that it is\n    available for as long as needed to satisfy these requirements.\n\n    e) Convey the object code using peer-to-peer transmission, provided\n    you inform other peers where the object code and Corresponding\n    Source of the work are being offered to the general public at no\n    charge under subsection 6d.\n\n  A separable portion of the object code, whose source code is excluded\nfrom the Corresponding Source as a System Library, need not be\nincluded in conveying the object code work.\n\n  A \"User Product\" is either (1) a \"consumer product\", which means any\ntangible personal property which is normally used for personal, family,\nor household purposes, or (2) anything designed or sold for incorporation\ninto a dwelling.  In determining whether a product is a consumer product,\ndoubtful cases shall be resolved in favor of coverage.  For a particular\nproduct received by a particular user, \"normally used\" refers to a\ntypical or common use of that class of product, regardless of the status\nof the particular user or of the way in which the particular user\nactually uses, or expects or is expected to use, the product.  A product\nis a consumer product regardless of whether the product has substantial\ncommercial, industrial or non-consumer uses, unless such uses represent\nthe only significant mode of use of the product.\n\n  \"Installation Information\" for a User Product means any methods,\nprocedures, authorization keys, or other information required to install\nand execute modified versions of a covered work in that User Product from\na modified version of its Corresponding Source.  The information must\nsuffice to ensure that the continued functioning of the modified object\ncode is in no case prevented or interfered with solely because\nmodification has been made.\n\n  If you convey an object code work under this section in, or with, or\nspecifically for use in, a User Product, and the conveying occurs as\npart of a transaction in which the right of possession and use of the\nUser Product is transferred to the recipient in perpetuity or for a\nfixed term (regardless of how the transaction is characterized), the\nCorresponding Source conveyed under this section must be accompanied\nby the Installation Information.  But this requirement does not apply\nif neither you nor any third party retains the ability to install\nmodified object code on the User Product (for example, the work has\nbeen installed in ROM).\n\n  The requirement to provide Installation Information does not include a\nrequirement to continue to provide support service, warranty, or updates\nfor a work that has been modified or installed by the recipient, or for\nthe User Product in which it has been modified or installed.  Access to a\nnetwork may be denied when the modification itself materially and\nadversely affects the operation of the network or violates the rules and\nprotocols for communication across the network.\n\n  Corresponding Source conveyed, and Installation Information provided,\nin accord with this section must be in a format that is publicly\ndocumented (and with an implementation available to the public in\nsource code form), and must require no special password or key for\nunpacking, reading or copying.\n\n  7. Additional Terms.\n\n  \"Additional permissions\" are terms that supplement the terms of this\nLicense by making exceptions from one or more of its conditions.\nAdditional permissions that are applicable to the entire Program shall\nbe treated as though they were included in this License, to the extent\nthat they are valid under applicable law.  If additional permissions\napply only to part of the Program, that part may be used separately\nunder those permissions, but the entire Program remains governed by\nthis License without regard to the additional permissions.\n\n  When you convey a copy of a covered work, you may at your option\nremove any additional permissions from that copy, or from any part of\nit.  (Additional permissions may be written to require their own\nremoval in certain cases when you modify the work.)  You may place\nadditional permissions on material, added by you to a covered work,\nfor which you have or can give appropriate copyright permission.\n\n  Notwithstanding any other provision of this License, for material you\nadd to a covered work, you may (if authorized by the copyright holders of\nthat material) supplement the terms of this License with terms:\n\n    a) Disclaiming warranty or limiting liability differently from the\n    terms of sections 15 and 16 of this License; or\n\n    b) Requiring preservation of specified reasonable legal notices or\n    author attributions in that material or in the Appropriate Legal\n    Notices displayed by works containing it; or\n\n    c) Prohibiting misrepresentation of the origin of that material, or\n    requiring that modified versions of such material be marked in\n    reasonable ways as different from the original version; or\n\n    d) Limiting the use for publicity purposes of names of licensors or\n    authors of the material; or\n\n    e) Declining to grant rights under trademark law for use of some\n    trade names, trademarks, or service marks; or\n\n    f) Requiring indemnification of licensors and authors of that\n    material by anyone who conveys the material (or modified versions of\n    it) with contractual assumptions of liability to the recipient, for\n    any liability that these contractual assumptions directly impose on\n    those licensors and authors.\n\n  All other non-permissive additional terms are considered \"further\nrestrictions\" within the meaning of section 10.  If the Program as you\nreceived it, or any part of it, contains a notice stating that it is\ngoverned by this License along with a term that is a further\nrestriction, you may remove that term.  If a license document contains\na further restriction but permits relicensing or conveying under this\nLicense, you may add to a covered work material governed by the terms\nof that license document, provided that the further restriction does\nnot survive such relicensing or conveying.\n\n  If you add terms to a covered work in accord with this section, you\nmust place, in the relevant source files, a statement of the\nadditional terms that apply to those files, or a notice indicating\nwhere to find the applicable terms.\n\n  Additional terms, permissive or non-permissive, may be stated in the\nform of a separately written license, or stated as exceptions;\nthe above requirements apply either way.\n\n  8. Termination.\n\n  You may not propagate or modify a covered work except as expressly\nprovided under this License.  Any attempt otherwise to propagate or\nmodify it is void, and will automatically terminate your rights under\nthis License (including any patent licenses granted under the third\nparagraph of section 11).\n\n  However, if you cease all violation of this License, then your\nlicense from a particular copyright holder is reinstated (a)\nprovisionally, unless and until the copyright holder explicitly and\nfinally terminates your license, and (b) permanently, if the copyright\nholder fails to notify you of the violation by some reasonable means\nprior to 60 days after the cessation.\n\n  Moreover, your license from a particular copyright holder is\nreinstated permanently if the copyright holder notifies you of the\nviolation by some reasonable means, this is the first time you have\nreceived notice of violation of this License (for any work) from that\ncopyright holder, and you cure the violation prior to 30 days after\nyour receipt of the notice.\n\n  Termination of your rights under this section does not terminate the\nlicenses of parties who have received copies or rights from you under\nthis License.  If your rights have been terminated and not permanently\nreinstated, you do not qualify to receive new licenses for the same\nmaterial under section 10.\n\n  9. Acceptance Not Required for Having Copies.\n\n  You are not required to accept this License in order to receive or\nrun a copy of the Program.  Ancillary propagation of a covered work\noccurring solely as a consequence of using peer-to-peer transmission\nto receive a copy likewise does not require acceptance.  However,\nnothing other than this License grants you permission to propagate or\nmodify any covered work.  These actions infringe copyright if you do\nnot accept this License.  Therefore, by modifying or propagating a\ncovered work, you indicate your acceptance of this License to do so.\n\n  10. Automatic Licensing of Downstream Recipients.\n\n  Each time you convey a covered work, the recipient automatically\nreceives a license from the original licensors, to run, modify and\npropagate that work, subject to this License.  You are not responsible\nfor enforcing compliance by third parties with this License.\n\n  An \"entity transaction\" is a transaction transferring control of an\norganization, or substantially all assets of one, or subdividing an\norganization, or merging organizations.  If propagation of a covered\nwork results from an entity transaction, each party to that\ntransaction who receives a copy of the work also receives whatever\nlicenses to the work the party's predecessor in interest had or could\ngive under the previous paragraph, plus a right to possession of the\nCorresponding Source of the work from the predecessor in interest, if\nthe predecessor has it or can get it with reasonable efforts.\n\n  You may not impose any further restrictions on the exercise of the\nrights granted or affirmed under this License.  For example, you may\nnot impose a license fee, royalty, or other charge for exercise of\nrights granted under this License, and you may not initiate litigation\n(including a cross-claim or counterclaim in a lawsuit) alleging that\nany patent claim is infringed by making, using, selling, offering for\nsale, or importing the Program or any portion of it.\n\n  11. Patents.\n\n  A \"contributor\" is a copyright holder who authorizes use under this\nLicense of the Program or a work on which the Program is based.  The\nwork thus licensed is called the contributor's \"contributor version\".\n\n  A contributor's \"essential patent claims\" are all patent claims\nowned or controlled by the contributor, whether already acquired or\nhereafter acquired, that would be infringed by some manner, permitted\nby this License, of making, using, or selling its contributor version,\nbut do not include claims that would be infringed only as a\nconsequence of further modification of the contributor version.  For\npurposes of this definition, \"control\" includes the right to grant\npatent sublicenses in a manner consistent with the requirements of\nthis License.\n\n  Each contributor grants you a non-exclusive, worldwide, royalty-free\npatent license under the contributor's essential patent claims, to\nmake, use, sell, offer for sale, import and otherwise run, modify and\npropagate the contents of its contributor version.\n\n  In the following three paragraphs, a \"patent license\" is any express\nagreement or commitment, however denominated, not to enforce a patent\n(such as an express permission to practice a patent or covenant not to\nsue for patent infringement).  To \"grant\" such a patent license to a\nparty means to make such an agreement or commitment not to enforce a\npatent against the party.\n\n  If you convey a covered work, knowingly relying on a patent license,\nand the Corresponding Source of the work is not available for anyone\nto copy, free of charge and under the terms of this License, through a\npublicly available network server or other readily accessible means,\nthen you must either (1) cause the Corresponding Source to be so\navailable, or (2) arrange to deprive yourself of the benefit of the\npatent license for this particular work, or (3) arrange, in a manner\nconsistent with the requirements of this License, to extend the patent\nlicense to downstream recipients.  \"Knowingly relying\" means you have\nactual knowledge that, but for the patent license, your conveying the\ncovered work in a country, or your recipient's use of the covered work\nin a country, would infringe one or more identifiable patents in that\ncountry that you have reason to believe are valid.\n\n  If, pursuant to or in connection with a single transaction or\narrangement, you convey, or propagate by procuring conveyance of, a\ncovered work, and grant a patent license to some of the parties\nreceiving the covered work authorizing them to use, propagate, modify\nor convey a specific copy of the covered work, then the patent license\nyou grant is automatically extended to all recipients of the covered\nwork and works based on it.\n\n  A patent license is \"discriminatory\" if it does not include within\nthe scope of its coverage, prohibits the exercise of, or is\nconditioned on the non-exercise of one or more of the rights that are\nspecifically granted under this License.  You may not convey a covered\nwork if you are a party to an arrangement with a third party that is\nin the business of distributing software, under which you make payment\nto the third party based on the extent of your activity of conveying\nthe work, and under which the third party grants, to any of the\nparties who would receive the covered work from you, a discriminatory\npatent license (a) in connection with copies of the covered work\nconveyed by you (or copies made from those copies), or (b) primarily\nfor and in connection with specific products or compilations that\ncontain the covered work, unless you entered into that arrangement,\nor that patent license was granted, prior to 28 March 2007.\n\n  Nothing in this License shall be construed as excluding or limiting\nany implied license or other defenses to infringement that may\notherwise be available to you under applicable patent law.\n\n  12. No Surrender of Others' Freedom.\n\n  If conditions are imposed on you (whether by court order, agreement or\notherwise) that contradict the conditions of this License, they do not\nexcuse you from the conditions of this License.  If you cannot convey a\ncovered work so as to satisfy simultaneously your obligations under this\nLicense and any other pertinent obligations, then as a consequence you may\nnot convey it at all.  For example, if you agree to terms that obligate you\nto collect a royalty for further conveying from those to whom you convey\nthe Program, the only way you could satisfy both those terms and this\nLicense would be to refrain entirely from conveying the Program.\n\n  13. Use with the GNU Affero General Public License.\n\n  Notwithstanding any other provision of this License, you have\npermission to link or combine any covered work with a work licensed\nunder version 3 of the GNU Affero General Public License into a single\ncombined work, and to convey the resulting work.  The terms of this\nLicense will continue to apply to the part which is the covered work,\nbut the special requirements of the GNU Affero General Public License,\nsection 13, concerning interaction through a network will apply to the\ncombination as such.\n\n  14. Revised Versions of this License.\n\n  The Free Software Foundation may publish revised and/or new versions of\nthe GNU General Public License from time to time.  Such new versions will\nbe similar in spirit to the present version, but may differ in detail to\naddress new problems or concerns.\n\n  Each version is given a distinguishing version number.  If the\nProgram specifies that a certain numbered version of the GNU General\nPublic License \"or any later version\" applies to it, you have the\noption of following the terms and conditions either of that numbered\nversion or of any later version published by the Free Software\nFoundation.  If the Program does not specify a version number of the\nGNU General Public License, you may choose any version ever published\nby the Free Software Foundation.\n\n  If the Program specifies that a proxy can decide which future\nversions of the GNU General Public License can be used, that proxy's\npublic statement of acceptance of a version permanently authorizes you\nto choose that version for the Program.\n\n  Later license versions may give you additional or different\npermissions.  However, no additional obligations are imposed on any\nauthor or copyright holder as a result of your choosing to follow a\nlater version.\n\n  15. Disclaimer of Warranty.\n\n  THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY\nAPPLICABLE LAW.  EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT\nHOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM \"AS IS\" WITHOUT WARRANTY\nOF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,\nTHE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\nPURPOSE.  THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM\nIS WITH YOU.  SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF\nALL NECESSARY SERVICING, REPAIR OR CORRECTION.\n\n  16. Limitation of Liability.\n\n  IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING\nWILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS\nTHE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY\nGENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE\nUSE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF\nDATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD\nPARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),\nEVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF\nSUCH DAMAGES.\n\n  17. Interpretation of Sections 15 and 16.\n\n  If the disclaimer of warranty and limitation of liability provided\nabove cannot be given local legal effect according to their terms,\nreviewing courts shall apply local law that most closely approximates\nan absolute waiver of all civil liability in connection with the\nProgram, unless a warranty or assumption of liability accompanies a\ncopy of the Program in return for a fee.\n\n                     END OF TERMS AND CONDITIONS\n\n            How to Apply These Terms to Your New Programs\n\n  If you develop a new program, and you want it to be of the greatest\npossible use to the public, the best way to achieve this is to make it\nfree software which everyone can redistribute and change under these terms.\n\n  To do so, attach the following notices to the program.  It is safest\nto attach them to the start of each source file to most effectively\nstate the exclusion of warranty; and each file should have at least\nthe \"copyright\" line and a pointer to where the full notice is found.\n\n    <one line to give the program's name and a brief idea of what it does.>\n    Copyright (C) <year>  <name of author>\n\n    This program is free software: you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation, either version 3 of the License, or\n    (at your option) any later version.\n\n    This program is distributed in the hope that it will be useful,\n    but WITHOUT ANY WARRANTY; without even the implied warranty of\n    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n    GNU General Public License for more details.\n\n    You should have received a copy of the GNU General Public License\n    along with this program.  If not, see <http://www.gnu.org/licenses/>.\n\nAlso add information on how to contact you by electronic and paper mail.\n\n  If the program does terminal interaction, make it output a short\nnotice like this when it starts in an interactive mode:\n\n    <program>  Copyright (C) <year>  <name of author>\n    This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.\n    This is free software, and you are welcome to redistribute it\n    under certain conditions; type `show c' for details.\n\nThe hypothetical commands `show w' and `show c' should show the appropriate\nparts of the General Public License.  Of course, your program's commands\nmight be different; for a GUI interface, you would use an \"about box\".\n\n  You should also get your employer (if you work as a programmer) or school,\nif any, to sign a \"copyright disclaimer\" for the program, if necessary.\nFor more information on this, and how to apply and follow the GNU GPL, see\n<http://www.gnu.org/licenses/>.\n\n  The GNU General Public License does not permit incorporating your program\ninto proprietary programs.  If your program is a subroutine library, you\nmay consider it more useful to permit linking proprietary applications with\nthe library.  If this is what you want to do, use the GNU Lesser General\nPublic License instead of this License.  But first, please read\n<http://www.gnu.org/philosophy/why-not-lgpl.html>.\n"
  },
  {
    "path": "README.md",
    "content": "# Desktop+ VR Overlay\nAdvanced desktop access for OpenVR.\n\n![VR Interface](docs/screenshot.jpg)\n\n## Features\n\n- User interface with real-time adjustments accessible in VR or on desktop\n- Smooth, low-latency mirroring of desktops and windows\n- Low memory footprint and performance impact\n- Support for creating as many overlays as SteamVR allows at once\n- Customizable overlay settings (width, position, curvature, opacity, cropping), with switchable profiles\n- 3D support (SBS, HSBS, OU, HOU)\n- Overlay visibility and origin settings: Display a desktop/window during gameplay or attach it to a different origin (play space, dashboard, HMD, controllers, tracker)\n- Actions: User-definable functions (input simulation, running applications) which can be bound to controller inputs, hotkeys or UI buttons\n- Custom laser pointer implementation, allowing for non-blocking overlay interaction during gameplay\n- Configurable PC-style VR keyboard with various layouts and a keyboard layout editor for deeper customization, appearing automatically when detecting text input widget focus\n- Elevated access toggle, making it possible to deal with UAC prompts and other UIP-restricted UI in VR without using full admin-access at all times\n- Gaze Fade: Fade-out overlay when not looking at it\n- Window management: Change window focus depending on overlay/dashboard state or drag overlays when dragging the title bar of a mirrored window\n- Performance Monitor: View system performance in real time\n- Application profiles: Automatically switch overlay profiles when a specific VR application is being run\n- Browser overlays: View web pages independent from your desktop (CEF-based)\n\n## Usage\n\n### Steam\n\nInstall Desktop+ from its [Steam store page](https://store.steampowered.com/app/1494460) and run it.\n\n### Release Archive\n\nDownload and extract the latest archive from the [releases page](https://github.com/elvissteinjr/DesktopPlus/releases). Follow instructions in the included [readme file](assets/readme.txt).  \nMake sure to also download the [Desktop+ Browser component](https://github.com/elvissteinjr/DesktopPlusBrowser/releases) if you want browser overlay support.\n\n### Nightly Build\n\nAn automated build based on the latest code changes can be downloaded from [here](https://nightly.link/elvissteinjr/DesktopPlus/workflows/nightly/master).  \nKeep in mind that nightly builds are unstable and mostly untested. Prefer the latest release if possible.\n\n### Building from Source\n\nThe Visual Studio 2019 Solution builds out of the box with no further external dependencies.  \nBuilding with Graphics Capture support requires Windows SDK 10.0.19041 or newer, and will download C++/WinRT packages automatically.\nGraphics Capture support can be disabled entirely if desired. Windows 8 SDK or newer is sufficient in that case. See DesktopPlusWinRT.h for details.\n\nSee the [Desktop+ Browser repository](https://github.com/elvissteinjr/DesktopPlusBrowser) for building the browser component.\n\nOther compilers likely work as well, but are neither tested nor have a build configuration. Building for 32-bit is not supported.\n\n## Demonstration\n\nThe [Steam announcements](https://store.steampowered.com/news/app/1494460) for typically feature short video clips showing off new additions.  \nThe trailer on the [Steam store page](https://store.steampowered.com/app/1494460) also shows off some functionality.\n\n## Documentation\n\nFor basic usage, installation and troubleshooting see the included [readme file](assets/readme.txt).  \nFor more detailed information on each setting, step-by-step examples for a few common usage scenarios and more check out the [User Guide](docs/user_guide.md).\n\n## Notes\n\nDesktop+ only runs on Windows 8.1 or newer, as it uses the DXGI Desktop Duplication API which is not available on older versions of Windows.  \nWindow mirroring through Graphics Capture requires at least Windows 10 1803 for basic support, Windows 10 2004 or newer for full support (some additional non-essential features require Windows 11 or Windows 11 24H2). \n\n## License\n\nThis software is licensed under the GPL 3.0.  \nDesktop+ includes work of third-party projects. For their licenses, see [here](assets/third-party_licenses.txt).\n"
  },
  {
    "path": "assets/action_manifest.json",
    "content": "{\n  \"actions\": [\n    {\n      \"name\": \"/actions/shortcuts/in/EnableGlobalLaserPointer\",\n      \"requirement\": \"optional\",\n      \"type\": \"boolean\"\n    },\n    {\n      \"name\": \"/actions/shortcuts/in/GlobalShortcut01\",\n      \"requirement\": \"optional\",\n      \"type\": \"boolean\"\n    },\n    {\n      \"name\": \"/actions/shortcuts/in/GlobalShortcut02\",\n      \"requirement\": \"optional\",\n      \"type\": \"boolean\"\n    },\n    {\n      \"name\": \"/actions/shortcuts/in/GlobalShortcut03\",\n      \"requirement\": \"optional\",\n      \"type\": \"boolean\"\n    },\n    {\n      \"name\": \"/actions/shortcuts/in/GlobalShortcut04\",\n      \"requirement\": \"optional\",\n      \"type\": \"boolean\"\n    },\n    {\n      \"name\": \"/actions/shortcuts/in/GlobalShortcut05\",\n      \"requirement\": \"optional\",\n      \"type\": \"boolean\"\n    },\n    {\n      \"name\": \"/actions/shortcuts/in/GlobalShortcut06\",\n      \"requirement\": \"optional\",\n      \"type\": \"boolean\"\n    },\n    {\n      \"name\": \"/actions/shortcuts/in/GlobalShortcut07\",\n      \"requirement\": \"optional\",\n      \"type\": \"boolean\"\n    },\n    {\n      \"name\": \"/actions/shortcuts/in/GlobalShortcut08\",\n      \"requirement\": \"optional\",\n      \"type\": \"boolean\"\n    },\n    {\n      \"name\": \"/actions/shortcuts/in/GlobalShortcut09\",\n      \"requirement\": \"optional\",\n      \"type\": \"boolean\"\n    },\n    {\n      \"name\": \"/actions/shortcuts/in/GlobalShortcut10\",\n      \"requirement\": \"optional\",\n      \"type\": \"boolean\"\n    },\n    {\n      \"name\": \"/actions/shortcuts/in/GlobalShortcut11\",\n      \"requirement\": \"optional\",\n      \"type\": \"boolean\"\n    },\n    {\n      \"name\": \"/actions/shortcuts/in/GlobalShortcut12\",\n      \"requirement\": \"optional\",\n      \"type\": \"boolean\"\n    },\n    {\n      \"name\": \"/actions/shortcuts/in/GlobalShortcut13\",\n      \"requirement\": \"optional\",\n      \"type\": \"boolean\"\n    },\n    {\n      \"name\": \"/actions/shortcuts/in/GlobalShortcut14\",\n      \"requirement\": \"optional\",\n      \"type\": \"boolean\"\n    },\n    {\n      \"name\": \"/actions/shortcuts/in/GlobalShortcut15\",\n      \"requirement\": \"optional\",\n      \"type\": \"boolean\"\n    },\n    {\n      \"name\": \"/actions/shortcuts/in/GlobalShortcut16\",\n      \"requirement\": \"optional\",\n      \"type\": \"boolean\"\n    },\n    {\n      \"name\": \"/actions/shortcuts/in/GlobalShortcut17\",\n      \"requirement\": \"optional\",\n      \"type\": \"boolean\"\n    },\n    {\n      \"name\": \"/actions/shortcuts/in/GlobalShortcut18\",\n      \"requirement\": \"optional\",\n      \"type\": \"boolean\"\n    },\n    {\n      \"name\": \"/actions/shortcuts/in/GlobalShortcut19\",\n      \"requirement\": \"optional\",\n      \"type\": \"boolean\"\n    },\n    {\n      \"name\": \"/actions/shortcuts/in/GlobalShortcut20\",\n      \"requirement\": \"optional\",\n      \"type\": \"boolean\"\n    },\n    {\n      \"name\": \"/actions/laserpointer/in/LeftClick\",\n      \"requirement\": \"suggested\",\n      \"type\": \"boolean\"\n    },\n    {\n      \"name\": \"/actions/laserpointer/in/RightClick\",\n      \"requirement\": \"optional\",\n      \"type\": \"boolean\"\n    },\n    {\n      \"name\": \"/actions/laserpointer/in/MiddleClick\",\n      \"requirement\": \"optional\",\n      \"type\": \"boolean\"\n    },\n    {\n      \"name\": \"/actions/laserpointer/in/Aux01Click\",\n      \"requirement\": \"optional\",\n      \"type\": \"boolean\"\n    },\n    {\n      \"name\": \"/actions/laserpointer/in/Aux02Click\",\n      \"requirement\": \"optional\",\n      \"type\": \"boolean\"\n    },\n    {\n      \"name\": \"/actions/laserpointer/in/Drag\",\n      \"requirement\": \"optional\",\n      \"type\": \"boolean\"\n    },\n    {\n      \"name\": \"/actions/scroll_discrete/in/ScrollDiscrete\",\n      \"requirement\": \"suggested\",\n      \"type\": \"vector2\"\n    },\n    {\n      \"name\": \"/actions/scroll_smooth/in/ScrollSmooth\",\n      \"requirement\": \"suggested\",\n      \"type\": \"vector2\"\n    },\n    {\n      \"name\": \"/actions/laserpointer/out/Haptic\",\n      \"requirement\": \"suggested\",\n      \"type\": \"vibration\"\n    }\n  ],\n  \"action_sets\": [\n    {\n      \"name\": \"/actions/shortcuts\",\n      \"usage\": \"leftright\"\n    },\n    {\n      \"name\": \"/actions/laserpointer\",\n      \"usage\": \"single\"\n    },\n    {\n      \"name\": \"/actions/scroll_discrete\",\n      \"display_with\": \"/actions/laserpointer\",\n      \"usage\": \"single\"\n    },\n    {\n      \"name\": \"/actions/scroll_smooth\",\n      \"display_with\": \"/actions/laserpointer\",\n      \"usage\": \"single\"\n    }\n  ],\n  \"localization_files\": {\n    \"en_US\" : \"input/action_manifest_en.json\",\n    \"de_DE\" : \"input/action_manifest_de.json\",\n    \"ja_JP\" : \"input/action_manifest_ja.json\",\n    \"ko_KR\" : \"input/action_manifest_ko.json\",\n    \"zh_CN\" : \"input/action_manifest_zh_CN.json\"\n  },\n  \"default_bindings\": [\n    {\n      \"controller_type\": \"frame_controller\",\n      \"binding_url\": \"input/action_bindings_frame_controller.json\"\n    },\n    {\n      \"controller_type\": \"knuckles\",\n      \"binding_url\": \"input/action_bindings_knuckles.json\"\n    },\n    {\n      \"controller_type\": \"playstation_vr2_sense\",\n      \"binding_url\": \"input/action_bindings_playstation_vr2_sense_controller.json\"\n    },\n    {\n      \"controller_type\": \"hpmotioncontroller\",\n      \"binding_url\": \"input/action_bindings_hpmotioncontroller.json\"\n    },\n    {\n      \"controller_type\": \"holographic\",\n      \"binding_url\": \"input/action_bindings_holographic.json\"\n    },\n\t{\n      \"controller_type\": \"oculus_touch\",\n      \"binding_url\": \"input/action_bindings_touch.json\"\n    },\n    {\n      \"controller_type\": \"vive_cosmos_controller\",\n      \"binding_url\": \"input/action_bindings_vive_cosmos.json\"\n    },\n\t{\n      \"controller_type\": \"knuckles_ev1\",\n      \"binding_url\": \"input/action_bindings_knuckles_ev1.json\"\n    },\n    {\n      \"controller_type\": \"vive_controller\",\n      \"binding_url\": \"input/action_bindings_vive_controller.json\"\n    },\n    {\n      \"controller_type\": \"svl_hand_interaction_augmented\",\n      \"binding_url\": \"input/action_bindings_svl_hand_interaction_augmented.json\"\n    },\n    {\n      \"controller_type\": \"gamepad\",\n      \"binding_url\": \"input/action_bindings_gamepad.json\"\n    },\n    {\n      \"controller_type\": \"generic\",\n      \"binding_url\": \"input/action_bindings_generic.json\"\n    }\n  ],\n  \"version\": 5,\n  \"minimum_required_version\": 2\n}\n"
  },
  {
    "path": "assets/actions_default.ini",
    "content": "[1]\nName=tstr_DefActionShowKeyboard\nCommandCount=1\nCommand0Type=ShowKeyboard\nCommand0UIntArg=0\nIconFilename=keyboard.png\n\n[2]\nName=tstr_DefActionActiveWindowCrop\nLabel=tstr_DefActionActiveWindowCropLabel\nCommandCount=1\nCommand0Type=CropActiveWindow\n\n[3]\nName=tstr_DefActionSwitchTask\nCommandCount=1\nCommand0Type=SwitchTask\nIconFilename=task_switch.png\n\n[4]\nName=tstr_DefActionToggleOverlays\nLabel=tstr_DefActionToggleOverlaysLabel\nCommandCount=1\nCommand0Type=ShowOverlay\nTargetUseTags=true\nTargetTags=Ovrl_All\n\n[5]\nName=tstr_DefActionMiddleMouse\nLabel=tstr_DefActionMiddleMouseLabel\nCommandCount=1\nCommand0Type=Key\nCommand0UIntID=4\nCommand0UIntArg=0\n\n[6]\nName=tstr_DefActionBackMouse\nLabel=tstr_DefActionBackMouseLabel\nCommandCount=1\nCommand0Type=Key\nCommand0UIntID=5\nCommand0UIntArg=0\n\n[7]\nName=tstr_DefActionReadMe\nLabel=tstr_DefActionReadMeLabel\nCommandCount=1\nCommand0Type=LaunchApp\nCommand0StrMain=readme.txt\n\n[8]\nName=tstr_DefActionDashboardToggle\nLabel=tstr_DefActionDashboardToggleLabel\nCommandCount=1\nCommand0Type=LaunchApp\nCommand0StrMain=vrmonitor://debugcommands/system_dashboard_toggle"
  },
  {
    "path": "assets/config_default.ini",
    "content": "﻿;Lines starting with ; are comments\n;Boolean settings allow use of \"true\"/\"false\" or 1/0\n;Do not include the quotation marks for string values\n;Only change the values after the =\n;\n;It shouldn't be necessary to mess with most of these by hand, but feel free to do so\n;Hidden settings are preceded by a comment line\n;Save changes to config.ini, not config_default.ini, and make sure Desktop+ is not running when doing so\n[Overlay0]\nName=\nNameIsCustom=false\nEnabled=true\nDesktopID=0\nCaptureSource=0\nWinRTDesktopID=-2\nWinRTLastWindowTitle=\nWinRTLastWindowClassName=\nWinRTLastWindowExeName=\nBrowserURL=\nBrowserURLUserLast=\nBrowserTitle=\nBrowserAllowTransparency=false\nWidth=165\nCurvature=17\nOpacity=100\nBrightness=100\nOffsetRight=0\nOffsetUp=0\nOffsetForward=0\nDisplayMode=3\nOrigin=Dashboard\nOriginHMDFloorTurning=false\nOriginSmoothingLevel=0\nTransformLocked=true\nCroppingEnabled=false\nCroppingX=0\nCroppingY=0\nCroppingWidth=-1\nCroppingHeight=-1\nShowBackside=true\n3DEnabled=false\n3DMode=0\n3DSwapped=false\nGazeFade=false\nGazeFadeDistance=0\nGazeFadeRate=100\nGazeFadeOpacity=0\nUpdateLimitModeOverride=0\nUpdateLimitMS=0\nUpdateLimitFPS=7\nInputEnabled=true\nInputDPlusLPEnabled=true\nUpdateInvisible=false\nShowFloatingUI=true\nShowDesktopButtons=true\nShowActionBar=true\nShowExtraButtons=true\nActionBarOrderUseGlobal=true\nTransform=[2.12766 0 0 0 0 2.12766 0 0 0 0 2.12766 0 0 0 0 1]\n\n[Interface]\n;Don't want DesktopPlusUI.exe to run automatically? Set this to true\nNoUIAutoLaunch=false\n;Don't want the notification/tray icon? Set this to true\nNoNotificationIcon=false\n;Want to override desktop mode DPI? Set your custom scale here (100 is 100%)\nDesktopUIScaleOverride=0\n;LanguageFile is not included here to trigger auto-detection\nShowAdvancedSettings=false\nDisplaySizeLarge=false\nOverlayCurrentID=0\nDesktopButtonCyclingMode=1\nDesktopButtonIncludeAll=false\nEnvironmentBackgroundColor=00000080\nEnvironmentBackgroundColorDisplayMode=0\nDimUI=false\nBlankSpaceDragEnabled=true\nLastVRUIScale=100\nWarningCompositorResolutionHidden=false\nWarningCompositorQualityHidden=false\nWarningProcessElevationHidden=false\nWarningElevatedModeHidden=false\nWarningBrowserMissingHidden=false\nWarningBrowserVersionMismatchHidden=false\nWarningAppProfileActiveHidden=false\nActionOrder=1;2;3;4;5;6;7;8;\nActionOrderBarDefault=1;3;7;\nWindowSettingsRestoreState=false\nWindowSettingsRoomVisible=false\nWindowSettingsRoomPinned=false\nWindowSettingsRoomSize=100\nWindowSettingsRoomTransform=[0.965926 0 0.258819 0 0 1 0 0 -0.258819 0 0.965926 0 0.846609 0.7 0.38214 1]\nWindowSettingsDashboardTabVisible=false\nWindowSettingsDashboardTabPinned=false\nWindowSettingsDashboardTabSize=100\nWindowSettingsDashboardTabTransform=[0.965926 0 0.258819 0 0 1 0 0 -0.258819 0 0.965926 0 0.846609 0.7 0.38214 1]\nWindowPropertiesRestoreState=false\nWindowPropertiesRoomVisible=false\nWindowPropertiesRoomPinned=false\nWindowPropertiesRoomSize=100\nWindowPropertiesRoomTransform=[0.965926 0 -0.258819 0 0 1 0 0 0.258819 0 0.965926 0 -0.846609 0.7 0.38214 1]\nWindowPropertiesDashboardTabVisible=false\nWindowPropertiesDashboardTabPinned=false\nWindowPropertiesDashboardTabSize=100\nWindowPropertiesDashboardTabTransform=[0.965926 0 -0.258819 0 0 1 0 0 0.258819 0 0.965926 0 -0.846609 0.7 0.38214 1]\nWindowPropertiesLastOverlayID=-1\nWindowPropertiesRestoreState=false\nWindowPropertiesRoomVisible=false\nWindowPropertiesDashboardTabVisible=false\nWindowKeyboardRestoreState=true\nWindowKeyboardRoomVisible=false\nWindowKeyboardRoomPinned=false\nWindowKeyboardRoomSize=100\nWindowKeyboardRoomTransform=[1 0 0 0 0 0.707107 -0.707107 0 0 0.707107 0.707107 0 0 -0.782608 0.782608 1]\nWindowKeyboardDashboardTabVisible=false\nWindowKeyboardDashboardTabPinned=false\nWindowKeyboardDashboardTabSize=100\nWindowKeyboardDashboardTabTransform=[1 0 0 0 0 0.707107 -0.707107 0 0 0.707107 0.707107 0 0 -0.767465 0.767466 1]\nWindowKeyboardLastAssignedOverlayID=-1\nQuickStartGuideHidden=false\n\n[Input]\nGoHomeButtonActionUID=6\nGoBackButtonActionUID=5\n;You can change this to allow for more global shortcuts, but the action manifest needs to adjusted as well and the app can not be installed in Steam (its internal manifest has priority)\nGlobalShortcutsMaxCount=20\nGlobalShortcut01ActionUID=0\nGlobalHotkey01Modifiers=0\nGlobalHotkey01KeyCode=0\nGlobalHotkey01ActionUID=0\nDetachedInteractionMaxDistance=200\nLaserPointerBlockInput=false\nGlobalHMDPointer=false\nLaserPointerHMDKeyCodeToggle=0\nLaserPointerHMDKeyCodeLeft=0\nLaserPointerHMDKeyCodeRight=0\nLaserPointerHMDKeyCodeMiddle=0\nLaserPointerHMDKeyCodeDrag=0\nDragAutoDocking=true\nDragFixedDistance=false\nDragFixedDistanceCM=200\nDragFixedDistanceShape=0\nDragFixedDistanceAutoCurve=true\nDragFixedDistanceAutoTilt=true\nDragSnapPosition=false\nDragSnapPositionSize=10\nDragSnapRotation=false\nDragSnapRotationX=true\nDragSnapRotationY=true\nDragSnapRotationZ=true\nDragSnapRotationAngle=45\n\n[Mouse]\nRenderCursor=true\nRenderIntersectionBlob=false\nScrollSmooth=false\nSimulatePenInput=false\nAllowPointerOverride=true\nDoubleClickAssistDuration=-1\nInputSmoothingLevel=0\n\n[Keyboard]\nLayoutFile=qwerty_usa.ini\nLayoutClusterFunction=true\nLayoutClusterNavigation=true\nLayoutClusterNumpad=false\nLayoutClusterExtra=false\nStickyModifiers=true\nKeyRepeat=true\n\n[Windows]\nAutoFocusSceneAppDashboard=false\nWinRTAutoFocus=true\nWinRTKeepOnScreen=true\nWinRTAutoSizeOverlay=false\nWinRTAutoFocusSceneApp=false\nWinRTWindowMatchingStrict=false\nWinRTDraggingMode=2\nWinRTOnCaptureLost=1\n\n[Browser]\n;These arguments are passed to the Desktop+ Browser executable and parsed by CEF. Things may break, use at your own risk.\nCommandLineArguments=\nBrowserMaxFPS=60\nBrowserContentBlocker=false\n\n[Performance]\nUpdateLimitMode=0\nUpdateLimitMS=0\nUpdateLimitFPS=7\nRapidLaserPointerUpdates=false\nSingleDesktopMirroring=false\nAlternativeCursorRendering=false\nShowFPS=false\n;Experience issues with the UI getting stuck? Set this to false to let it render unconditionally\nUIAutoThrottle=true\n;Want to lower the render rate of the UI? Set this to the amount of VR frame syncs to skip for each rendered UI frame\nUIFrameSkip=0\nPerformanceMonitorStyleMinimal=false\nPerformanceMonitorStyleLarge=true\nPerformanceMonitorStyleShowWindow=true\nPerformanceMonitorStyleShowTextOutline=false\nPerformanceMonitorStyleMinimalShowMore=false\nPerformanceMonitorShowGraphs=true\nPerformanceMonitorShowTime=false\nPerformanceMonitorShowCPU=true\nPerformanceMonitorShowGPU=true\nPerformanceMonitorShowFPS=true\nPerformanceMonitorShowBattery=true\nPerformanceMonitorShowTrackers=true\nPerformanceMonitorShowViveWireless=false\n;Disables display of GPU load % and VRAM usage. This prevents GPU hardware monitoring related stutter with certain older NVIDIA drivers\nPerformanceMonitorDisableGPUCounters=false\n\n[Misc]\nNoSteam=false\nUIAccessWasEnabled=false\n;Need to force a specific GPU to be used? Set the DeviceID here (get the IDs from logs)\nForceGPUDeviceID=-1\n;Same as above but for the GPU SteamVR is using if different from the desktops. Normally SteamVR reports it to the application with no need to change anything here\nForceGPUVRDeviceID=-1\n"
  },
  {
    "path": "assets/input/action_bindings_frame_controller.json",
    "content": "{\n   \"action_manifest_version\" : 5,\n   \"alias_info\" : {},\n   \"app_key\" : \"steam.overlay.1494460\",\n   \"bindings\" : {\n      \"/actions/laserpointer\" : {\n         \"haptics\" : [\n            {\n               \"output\" : \"/actions/laserpointer/out/Haptic\",\n               \"path\" : \"/user/hand/left/output/haptic\"\n            },\n            {\n               \"output\" : \"/actions/laserpointer/out/Haptic\",\n               \"path\" : \"/user/hand/right/output/haptic\"\n            }\n         ],\n         \"sources\" : [\n            {\n               \"inputs\" : {\n                  \"click\" : {\n                     \"output\" : \"/actions/laserpointer/in/LeftClick\"\n                  }\n               },\n               \"mode\" : \"button\",\n               \"parameters\" : {\n                  \"click_activate_threshold\" : \"0.65\",\n                  \"click_deactivate_threshold\" : \"0.6\",\n                  \"haptic_amplitude\" : \"0\"\n               },\n               \"path\" : \"/user/hand/right/input/trigger\"\n            },\n            {\n               \"inputs\" : {\n                  \"click\" : {\n                     \"output\" : \"/actions/laserpointer/in/LeftClick\"\n                  }\n               },\n               \"mode\" : \"button\",\n               \"parameters\" : {\n                  \"click_activate_threshold\" : \"0.65\",\n                  \"click_deactivate_threshold\" : \"0.6\",\n                  \"haptic_amplitude\" : \"0\"\n               },\n               \"path\" : \"/user/hand/left/input/trigger\"\n            },\n            {\n               \"inputs\" : {\n                  \"click\" : {\n                     \"output\" : \"/actions/laserpointer/in/RightClick\"\n                  }\n               },\n               \"mode\" : \"button\",\n               \"path\" : \"/user/hand/left/input/bumper\"\n            },\n            {\n               \"inputs\" : {\n                  \"click\" : {\n                     \"output\" : \"/actions/laserpointer/in/RightClick\"\n                  }\n               },\n               \"mode\" : \"button\",\n               \"path\" : \"/user/hand/right/input/bumper\"\n            },\n            {\n               \"inputs\" : {\n                  \"click\" : {\n                     \"output\" : \"/actions/laserpointer/in/Aux02Click\"\n                  }\n               },\n               \"mode\" : \"button\",\n               \"path\" : \"/user/hand/left/input/dpad_down\"\n            },\n            {\n               \"inputs\" : {\n                  \"click\" : {\n                     \"output\" : \"/actions/laserpointer/in/Aux02Click\"\n                  }\n               },\n               \"mode\" : \"button\",\n               \"path\" : \"/user/hand/right/input/b\"\n            }\n         ]\n      },\n      \"/actions/scroll_discrete\" : {\n         \"sources\" : [\n            {\n               \"inputs\" : {\n                  \"scroll\" : {\n                     \"output\" : \"/actions/scroll_discrete/in/ScrollDiscrete\"\n                  }\n               },\n               \"mode\" : \"scroll\",\n               \"path\" : \"/user/hand/left/input/thumbstick\"\n            },\n            {\n               \"inputs\" : {\n                  \"scroll\" : {\n                     \"output\" : \"/actions/scroll_discrete/in/ScrollDiscrete\"\n                  }\n               },\n               \"mode\" : \"scroll\",\n               \"path\" : \"/user/hand/right/input/thumbstick\"\n            }\n         ]\n      },\n\t  \"/actions/scroll_smooth\" : {\n         \"sources\" : [\n            {\n               \"inputs\" : {\n                  \"scroll\" : {\n                     \"output\" : \"/actions/scroll_smooth/in/ScrollSmooth\"\n                  }\n               },\n               \"mode\" : \"scroll\",\n               \"parameters\" : {\n                  \"scroll_mode\" : \"smooth\"\n               },\n               \"path\" : \"/user/hand/left/input/thumbstick\"\n            },\n            {\n               \"inputs\" : {\n                  \"scroll\" : {\n                     \"output\" : \"/actions/scroll_smooth/in/ScrollSmooth\"\n                  }\n               },\n               \"mode\" : \"scroll\",\n               \"parameters\" : {\n                  \"scroll_mode\" : \"smooth\"\n               },\n               \"path\" : \"/user/hand/right/input/thumbstick\"\n            }\n         ]\n      }\n   },\n   \"category\" : \"steamvr_input\",\n   \"controller_type\" : \"frame_controller\",\n   \"description\" : \"\",\n   \"name\" : \"Default Desktop+ bindings for Steam Frame Controllers\",\n   \"options\" : {},\n   \"simulated_actions\" : []\n}\n"
  },
  {
    "path": "assets/input/action_bindings_gamepad.json",
    "content": "{\n   \"action_manifest_version\" : 5,\n   \"alias_info\" : {},\n   \"app_key\" : \"steam.overlay.1494460\",\n   \"bindings\" : {\n      \"/actions/laserpointer\" : {\n         \"sources\" : [\n            {\n               \"inputs\" : {\n                  \"click\" : {\n                     \"output\" : \"/actions/laserpointer/in/LeftClick\"\n                  }\n               },\n               \"mode\" : \"button\",\n               \"path\" : \"/user/gamepad/input/trigger_right\"\n            },\n            {\n               \"inputs\" : {\n                  \"east\" : {\n                     \"output\" : \"/actions/laserpointer/in/RightClick\"\n                  }\n               },\n               \"mode\" : \"button\",\n               \"path\" : \"/user/gamepad/input/trigger_left\"\n            },\n\t\t\t{\n               \"inputs\" : {\n                  \"click\" : {\n                     \"output\" : \"/actions/laserpointer/in/Aux01Click\"\n                  }\n               },\n               \"mode\" : \"button\",\n               \"path\" : \"/user/gamepad/input/b\"\n            },\n            {\n               \"inputs\" : {\n                  \"click\" : {\n                     \"output\" : \"/actions/laserpointer/in/Aux02Click\"\n                  }\n               },\n               \"mode\" : \"button\",\n               \"path\" : \"/user/gamepad/input/x\"\n            }\n         ]\n      },\n\t  \"/actions/scroll_discrete\" : {\n         \"sources\" : [\n            {\n               \"inputs\" : {\n                  \"scroll\" : {\n                     \"output\" : \"/actions/scroll_discrete/in/ScrollDiscrete\"\n                  }\n               },\n               \"mode\" : \"scroll\",\n               \"path\" : \"/user/gamepad/input/joystick_right\"\n            }\n         ]\n      },\n\t  \"/actions/scroll_smooth\" : {\n         \"sources\" : [\n            {\n               \"inputs\" : {\n                  \"scroll\" : {\n                     \"output\" : \"/actions/scroll_smooth/in/ScrollSmooth\"\n                  }\n               },\n               \"mode\" : \"scroll\",\n               \"parameters\" : {\n                  \"scroll_mode\" : \"smooth\"\n               },\n               \"path\" : \"/user/gamepad/input/joystick_right\"\n            }\n         ]\n      }\n   },\n   \"category\" : \"steamvr_input\",\n   \"controller_type\" : \"gamepad\",\n   \"description\" : \"\",\n   \"name\" : \"Default Desktop+ bindings for Gamepads\",\n   \"options\" : {},\n   \"simulated_actions\" : []\n}\n"
  },
  {
    "path": "assets/input/action_bindings_generic.json",
    "content": "{\n   \"action_manifest_version\" : 5,\n   \"alias_info\" : {},\n   \"app_key\" : \"steam.overlay.1494460\",\n   \"bindings\" : {\n      \"/actions/laserpointer\" : {\n         \"haptics\" : [\n            {\n               \"output\" : \"/actions/laserpointer/out/Haptic\",\n               \"path\" : \"/user/hand/left/output/haptic\"\n            },\n            {\n               \"output\" : \"/actions/laserpointer/out/Haptic\",\n               \"path\" : \"/user/hand/right/output/haptic\"\n            }\n         ],\n         \"sources\" : [\n            {\n               \"inputs\" : {\n                  \"click\" : {\n                     \"output\" : \"/actions/laserpointer/in/LeftClick\"\n                  }\n               },\n               \"mode\" : \"button\",\n               \"path\" : \"/user/hand/right/input/trigger\"\n            },\n            {\n               \"inputs\" : {\n                  \"click\" : {\n                     \"output\" : \"/actions/laserpointer/in/LeftClick\"\n                  }\n               },\n               \"mode\" : \"button\",\n               \"path\" : \"/user/hand/left/input/trigger\"\n            },\n            {\n               \"inputs\" : {\n                  \"east\" : {\n                     \"output\" : \"/actions/laserpointer/in/RightClick\"\n                  }\n               },\n               \"mode\" : \"dpad_click\",\n               \"path\" : \"/user/hand/left/input/trackpad\"\n            },\n            {\n               \"inputs\" : {\n                  \"east\" : {\n                     \"output\" : \"/actions/laserpointer/in/RightClick\"\n                  }\n               },\n               \"mode\" : \"dpad_click\",\n               \"path\" : \"/user/hand/right/input/trackpad\"\n            },\n\t\t\t{\n               \"inputs\" : {\n                  \"click\" : {\n                     \"output\" : \"/actions/laserpointer/in/Aux01Click\"\n                  }\n               },\n               \"mode\" : \"button\",\n               \"path\" : \"/user/hand/left/input/grip\"\n            },\n\t\t\t{\n               \"inputs\" : {\n                  \"click\" : {\n                     \"output\" : \"/actions/laserpointer/in/Aux01Click\"\n                  }\n               },\n               \"mode\" : \"button\",\n               \"path\" : \"/user/hand/right/input/grip\"\n            },\n            {\n               \"inputs\" : {\n                  \"click\" : {\n                     \"output\" : \"/actions/laserpointer/in/Aux02Click\"\n                  }\n               },\n               \"mode\" : \"button\",\n               \"path\" : \"/user/hand/left/input/application_menu\"\n            },\n            {\n               \"inputs\" : {\n                  \"click\" : {\n                     \"output\" : \"/actions/laserpointer/in/Aux02Click\"\n                  }\n               },\n               \"mode\" : \"button\",\n               \"path\" : \"/user/hand/right/input/application_menu\"\n            }\n         ]\n      },\n\t  \"/actions/scroll_discrete\" : {\n         \"sources\" : [\n            {\n               \"inputs\" : {\n                  \"scroll\" : {\n                     \"output\" : \"/actions/scroll_discrete/in/ScrollDiscrete\"\n                  }\n               },\n               \"mode\" : \"scroll\",\n               \"parameters\" : {\n                  \"discrete_scroll_trackpad_accumthreshold_onmove\" : \"0.224\",\n                  \"discrete_scroll_trackpad_accumthreshold_onreversal\" : \"0.672\",\n                  \"discrete_scroll_trackpad_accumthreshold_ontouch\" : \"0.624\",\n                  \"discrete_scroll_trackpad_noisethreshold_onmove\" : \"0.008\",\n                  \"discrete_scroll_trackpad_noisethreshold_onreversal\" : \"0.095\",\n                  \"discrete_scroll_trackpad_noisethreshold_ontouch\" : \"0.16\",\n                  \"discrete_scroll_trackpad_slideandhold_borderbottom\" : \"-0.60\",\n                  \"discrete_scroll_trackpad_slideandhold_bordertop\" : \"0.65\",\n                  \"scroll_mode\" : \"discrete\"\n               },\n               \"path\" : \"/user/hand/left/input/trackpad\"\n            },\n            {\n               \"inputs\" : {\n                  \"scroll\" : {\n                     \"output\" : \"/actions/scroll_discrete/in/ScrollDiscrete\"\n                  }\n               },\n               \"mode\" : \"scroll\",\n               \"parameters\" : {\n                  \"discrete_scroll_trackpad_accumthreshold_onmove\" : \"0.224\",\n                  \"discrete_scroll_trackpad_accumthreshold_onreversal\" : \"0.672\",\n                  \"discrete_scroll_trackpad_accumthreshold_ontouch\" : \"0.624\",\n                  \"discrete_scroll_trackpad_noisethreshold_onmove\" : \"0.008\",\n                  \"discrete_scroll_trackpad_noisethreshold_onreversal\" : \"0.095\",\n                  \"discrete_scroll_trackpad_noisethreshold_ontouch\" : \"0.16\",\n                  \"discrete_scroll_trackpad_slideandhold_borderbottom\" : \"-0.60\",\n                  \"discrete_scroll_trackpad_slideandhold_bordertop\" : \"0.65\",\n                  \"scroll_mode\" : \"discrete\"\n               },\n               \"path\" : \"/user/hand/right/input/trackpad\"\n            }\n         ]\n      },\n\t  \"/actions/scroll_smooth\" : {\n         \"sources\" : [\n            {\n               \"inputs\" : {\n                  \"scroll\" : {\n                     \"output\" : \"/actions/scroll_smooth/in/ScrollSmooth\"\n                  }\n               },\n               \"mode\" : \"scroll\",\n               \"parameters\" : {\n                  \"scroll_mode\" : \"smooth\"\n               },\n               \"path\" : \"/user/hand/left/input/trackpad\"\n            },\n            {\n               \"inputs\" : {\n                  \"scroll\" : {\n                     \"output\" : \"/actions/scroll_smooth/in/ScrollSmooth\"\n                  }\n               },\n               \"mode\" : \"scroll\",\n               \"parameters\" : {\n                  \"scroll_mode\" : \"smooth\"\n               },\n               \"path\" : \"/user/hand/right/input/trackpad\"\n            }\n         ]\n      }\n   },\n   \"category\" : \"steamvr_input\",\n   \"controller_type\" : \"generic\",\n   \"description\" : \"\",\n   \"name\" : \"Default Desktop+ bindings for Generic Controllers\",\n   \"options\" : {},\n   \"simulated_actions\" : []\n}\n"
  },
  {
    "path": "assets/input/action_bindings_holographic.json",
    "content": "{\n   \"action_manifest_version\" : 5,\n   \"alias_info\" : {},\n   \"app_key\" : \"steam.overlay.1494460\",\n   \"bindings\" : {\n      \"/actions/laserpointer\" : {\n         \"haptics\" : [\n            {\n               \"output\" : \"/actions/laserpointer/out/Haptic\",\n               \"path\" : \"/user/hand/left/output/haptic\"\n            },\n            {\n               \"output\" : \"/actions/laserpointer/out/Haptic\",\n               \"path\" : \"/user/hand/right/output/haptic\"\n            }\n         ],\n         \"sources\" : [\n            {\n               \"inputs\" : {\n                  \"click\" : {\n                     \"output\" : \"/actions/laserpointer/in/LeftClick\"\n                  }\n               },\n               \"mode\" : \"button\",\n               \"path\" : \"/user/hand/right/input/trigger\"\n            },\n            {\n               \"inputs\" : {\n                  \"click\" : {\n                     \"output\" : \"/actions/laserpointer/in/LeftClick\"\n                  }\n               },\n               \"mode\" : \"button\",\n               \"path\" : \"/user/hand/left/input/trigger\"\n            },\n            {\n               \"inputs\" : {\n                  \"east\" : {\n                     \"output\" : \"/actions/laserpointer/in/RightClick\"\n                  }\n               },\n               \"mode\" : \"dpad_click\",\n               \"path\" : \"/user/hand/left/input/trackpad\"\n            },\n            {\n               \"inputs\" : {\n                  \"east\" : {\n                     \"output\" : \"/actions/laserpointer/in/RightClick\"\n                  }\n               },\n               \"mode\" : \"dpad_click\",\n               \"path\" : \"/user/hand/right/input/trackpad\"\n            },\n\t\t\t{\n               \"inputs\" : {\n                  \"click\" : {\n                     \"output\" : \"/actions/laserpointer/in/Aux01Click\"\n                  }\n               },\n               \"mode\" : \"button\",\n               \"path\" : \"/user/hand/left/input/grip\"\n            },\n\t\t\t{\n               \"inputs\" : {\n                  \"click\" : {\n                     \"output\" : \"/actions/laserpointer/in/Aux01Click\"\n                  }\n               },\n               \"mode\" : \"button\",\n               \"path\" : \"/user/hand/right/input/grip\"\n            },\n            {\n               \"inputs\" : {\n                  \"click\" : {\n                     \"output\" : \"/actions/laserpointer/in/Aux02Click\"\n                  }\n               },\n               \"mode\" : \"button\",\n               \"path\" : \"/user/hand/left/input/application_menu\"\n            },\n            {\n               \"inputs\" : {\n                  \"click\" : {\n                     \"output\" : \"/actions/laserpointer/in/Aux02Click\"\n                  }\n               },\n               \"mode\" : \"button\",\n               \"path\" : \"/user/hand/right/input/application_menu\"\n            }\n         ]\n      },\n\t  \"/actions/scroll_discrete\" : {\n         \"sources\" : [\n            {\n               \"inputs\" : {\n                  \"scroll\" : {\n                     \"output\" : \"/actions/scroll_discrete/in/ScrollDiscrete\"\n                  }\n               },\n               \"mode\" : \"scroll\",\n               \"parameters\" : {\n                  \"scroll_mode\" : \"discrete\"\n               },\n               \"path\" : \"/user/hand/left/input/joystick\"\n            },\n            {\n               \"inputs\" : {\n                  \"scroll\" : {\n                     \"output\" : \"/actions/scroll_discrete/in/ScrollDiscrete\"\n                  }\n               },\n               \"mode\" : \"scroll\",\n               \"parameters\" : {\n                  \"scroll_mode\" : \"discrete\"\n               },\n               \"path\" : \"/user/hand/right/input/joystick\"\n            },\n            {\n               \"inputs\" : {\n                  \"scroll\" : {\n                     \"output\" : \"/actions/scroll_discrete/in/ScrollDiscrete\"\n                  }\n               },\n               \"mode\" : \"scroll\",\n               \"path\" : \"/user/hand/left/input/trackpad\"\n            },\n            {\n               \"inputs\" : {\n                  \"scroll\" : {\n                     \"output\" : \"/actions/scroll_discrete/in/ScrollDiscrete\"\n                  }\n               },\n               \"mode\" : \"scroll\",\n               \"path\" : \"/user/hand/right/input/trackpad\"\n            }\n         ]\n      },\n\t  \"/actions/scroll_smooth\" : {\n         \"sources\" : [\n            {\n               \"inputs\" : {\n                  \"scroll\" : {\n                     \"output\" : \"/actions/scroll_smooth/in/ScrollSmooth\"\n                  }\n               },\n               \"mode\" : \"scroll\",\n               \"parameters\": {\n                  \"scroll_mode\": \"smooth\",\n                  \"smooth_scroll_multiplier\": \"11.8\"\n               },\n               \"path\" : \"/user/hand/left/input/trackpad\"\n            },\n            {\n               \"inputs\" : {\n                  \"scroll\" : {\n                     \"output\" : \"/actions/scroll_smooth/in/ScrollSmooth\"\n                  }\n               },\n               \"mode\" : \"scroll\",\n               \"parameters\": {\n                  \"scroll_mode\": \"smooth\",\n                  \"smooth_scroll_multiplier\": \"11.8\"\n               },\n               \"path\" : \"/user/hand/right/input/trackpad\"\n            },\n            {\n               \"inputs\" : {\n                  \"scroll\" : {\n                     \"output\" : \"/actions/scroll_smooth/in/ScrollSmooth\"\n                  }\n               },\n               \"mode\" : \"scroll\",\n               \"parameters\": {\n                  \"scroll_mode\": \"smooth\",\n                  \"smooth_scroll_joystick_min_input_magnitude\": \"0.3\"\n               },\n               \"path\" : \"/user/hand/left/input/joystick\"\n            },\n            {\n               \"inputs\" : {\n                  \"scroll\" : {\n                     \"output\" : \"/actions/scroll_smooth/in/ScrollSmooth\"\n                  }\n               },\n               \"mode\" : \"scroll\",\n               \"parameters\": {\n                  \"scroll_mode\": \"smooth\",\n                  \"smooth_scroll_joystick_min_input_magnitude\": \"0.3\"\n               },\n               \"path\" : \"/user/hand/right/input/joystick\"\n            }\n         ]\n      }\n   },\n   \"category\" : \"steamvr_input\",\n   \"controller_type\" : \"holographic_controller\",\n   \"description\" : \"\",\n   \"name\" : \"Default Desktop+ bindings for Windows Mixed Reality Controllers\",\n   \"options\" : {},\n   \"simulated_actions\" : []\n}\n"
  },
  {
    "path": "assets/input/action_bindings_hpmotioncontroller.json",
    "content": "{\n   \"action_manifest_version\" : 5,\n   \"alias_info\" : {},\n   \"app_key\" : \"steam.overlay.1494460\",\n   \"bindings\" : {\n      \"/actions/laserpointer\" : {\n         \"haptics\" : [\n            {\n               \"output\" : \"/actions/laserpointer/out/Haptic\",\n               \"path\" : \"/user/hand/left/output/haptic\"\n            },\n            {\n               \"output\" : \"/actions/laserpointer/out/Haptic\",\n               \"path\" : \"/user/hand/right/output/haptic\"\n            }\n         ],\n         \"sources\" : [\n            {\n               \"inputs\" : {\n                  \"click\" : {\n                     \"output\" : \"/actions/laserpointer/in/LeftClick\"\n                  }\n               },\n               \"mode\" : \"button\",\n               \"path\" : \"/user/hand/right/input/trigger\"\n            },\n            {\n               \"inputs\" : {\n                  \"click\" : {\n                     \"output\" : \"/actions/laserpointer/in/LeftClick\"\n                  }\n               },\n               \"mode\" : \"button\",\n               \"path\" : \"/user/hand/left/input/trigger\"\n            },\n            {\n               \"inputs\" : {\n                  \"click\" : {\n                     \"output\" : \"/actions/laserpointer/in/RightClick\"\n                  }\n               },\n               \"mode\" : \"button\",\n               \"path\" : \"/user/hand/left/input/x\"\n            },\n            {\n               \"inputs\" : {\n                  \"click\" : {\n                     \"output\" : \"/actions/laserpointer/in/RightClick\"\n                  }\n               },\n               \"mode\" : \"button\",\n               \"path\" : \"/user/hand/right/input/a\"\n            },\n\t\t\t{\n               \"inputs\" : {\n                  \"click\" : {\n                     \"output\" : \"/actions/laserpointer/in/Aux01Click\"\n                  }\n               },\n               \"mode\" : \"button\",\n               \"path\" : \"/user/hand/left/input/grip\"\n            },\n\t\t\t{\n               \"inputs\" : {\n                  \"click\" : {\n                     \"output\" : \"/actions/laserpointer/in/Aux01Click\"\n                  }\n               },\n               \"mode\" : \"button\",\n               \"path\" : \"/user/hand/right/input/grip\"\n            },\n            {\n               \"inputs\" : {\n                  \"click\" : {\n                     \"output\" : \"/actions/laserpointer/in/Aux02Click\"\n                  }\n               },\n               \"mode\" : \"button\",\n               \"path\" : \"/user/hand/left/input/y\"\n            },\n            {\n               \"inputs\" : {\n                  \"click\" : {\n                     \"output\" : \"/actions/laserpointer/in/Aux02Click\"\n                  }\n               },\n               \"mode\" : \"button\",\n               \"path\" : \"/user/hand/right/input/b\"\n            }\n         ]\n      },\n\t  \"/actions/scroll_discrete\" : {\n         \"sources\" : [\n            {\n               \"inputs\" : {\n                  \"scroll\" : {\n                     \"output\" : \"/actions/scroll_discrete/in/ScrollDiscrete\"\n                  }\n               },\n               \"mode\" : \"scroll\",\n               \"path\" : \"/user/hand/left/input/joystick\"\n            },\n            {\n               \"inputs\" : {\n                  \"scroll\" : {\n                     \"output\" : \"/actions/scroll_discrete/in/ScrollDiscrete\"\n                  }\n               },\n               \"mode\" : \"scroll\",\n               \"path\" : \"/user/hand/right/input/joystick\"\n            }\n         ]\n      },\n\t  \"/actions/scroll_smooth\" : {\n         \"sources\" : [\n            {\n               \"inputs\" : {\n                  \"scroll\" : {\n                     \"output\" : \"/actions/scroll_smooth/in/ScrollSmooth\"\n                  }\n               },\n               \"mode\" : \"scroll\",\n               \"path\" : \"/user/hand/left/input/joystick\"\n            },\n            {\n               \"inputs\" : {\n                  \"scroll\" : {\n                     \"output\" : \"/actions/scroll_smooth/in/ScrollSmooth\"\n                  }\n               },\n               \"mode\" : \"scroll\",\n               \"path\" : \"/user/hand/right/input/joystick\"\n            }\n         ]\n      }\n   },\n   \"category\" : \"steamvr_input\",\n   \"controller_type\" : \"hpmotioncontroller\",\n   \"description\" : \"\",\n   \"name\" : \"Default Desktop+ bindings for HP Motion Controllers\",\n   \"options\" : {},\n   \"simulated_actions\" : []\n}\n"
  },
  {
    "path": "assets/input/action_bindings_knuckles.json",
    "content": "{\n   \"action_manifest_version\" : 5,\n   \"alias_info\" : {},\n   \"app_key\" : \"steam.overlay.1494460\",\n   \"bindings\" : {\n      \"/actions/laserpointer\" : {\n         \"haptics\" : [\n            {\n               \"output\" : \"/actions/laserpointer/out/Haptic\",\n               \"path\" : \"/user/hand/left/output/haptic\"\n            },\n            {\n               \"output\" : \"/actions/laserpointer/out/Haptic\",\n               \"path\" : \"/user/hand/right/output/haptic\"\n            }\n         ],\n         \"sources\" : [\n            {\n               \"inputs\" : {\n                  \"click\" : {\n                     \"output\" : \"/actions/laserpointer/in/LeftClick\"\n                  }\n               },\n               \"mode\" : \"button\",\n               \"parameters\" : {\n                  \"click_activate_threshold\" : \"0.65\",\n                  \"click_deactivate_threshold\" : \"0.6\"\n               },\n               \"path\" : \"/user/hand/right/input/trigger\"\n            },\n            {\n               \"inputs\" : {\n                  \"click\" : {\n                     \"output\" : \"/actions/laserpointer/in/LeftClick\"\n                  }\n               },\n               \"mode\" : \"button\",\n               \"parameters\" : {\n                  \"click_activate_threshold\" : \"0.65\",\n                  \"click_deactivate_threshold\" : \"0.6\"\n               },\n               \"path\" : \"/user/hand/left/input/trigger\"\n            },\n            {\n               \"inputs\" : {\n                  \"click\" : {\n                     \"output\" : \"/actions/laserpointer/in/RightClick\"\n                  }\n               },\n               \"mode\" : \"button\",\n               \"path\" : \"/user/hand/left/input/trackpad\"\n            },\n            {\n               \"inputs\" : {\n                  \"click\" : {\n                     \"output\" : \"/actions/laserpointer/in/RightClick\"\n                  }\n               },\n               \"mode\" : \"button\",\n               \"path\" : \"/user/hand/right/input/trackpad\"\n            },\n            {\n               \"inputs\" : {\n                  \"click\" : {\n                     \"output\" : \"/actions/laserpointer/in/MiddleClick\"\n                  }\n               },\n               \"mode\" : \"joystick\",\n               \"path\" : \"/user/hand/left/input/thumbstick\"\n            },\n            {\n               \"inputs\" : {\n                  \"click\" : {\n                     \"output\" : \"/actions/laserpointer/in/MiddleClick\"\n                  }\n               },\n               \"mode\" : \"joystick\",\n               \"path\" : \"/user/hand/right/input/thumbstick\"\n            },\n            {\n               \"inputs\" : {\n                  \"click\" : {\n                     \"output\" : \"/actions/laserpointer/in/Aux01Click\"\n                  }\n               },\n               \"mode\" : \"button\",\n               \"path\" : \"/user/hand/left/input/b\"\n            },\n            {\n               \"inputs\" : {\n                  \"click\" : {\n                     \"output\" : \"/actions/laserpointer/in/Aux01Click\"\n                  }\n               },\n               \"mode\" : \"button\",\n               \"path\" : \"/user/hand/right/input/b\"\n            },\n            {\n               \"inputs\" : {\n                  \"click\" : {\n                     \"output\" : \"/actions/laserpointer/in/Aux02Click\"\n                  }\n               },\n               \"mode\" : \"button\",\n               \"path\" : \"/user/hand/left/input/a\"\n            },\n            {\n               \"inputs\" : {\n                  \"click\" : {\n                     \"output\" : \"/actions/laserpointer/in/Aux02Click\"\n                  }\n               },\n               \"mode\" : \"button\",\n               \"path\" : \"/user/hand/right/input/a\"\n            }\n         ]\n      },\n      \"/actions/scroll_discrete\" : {\n         \"sources\" : [\n            {\n               \"inputs\" : {\n                  \"scroll\" : {\n                     \"output\" : \"/actions/scroll_discrete/in/ScrollDiscrete\"\n                  }\n               },\n               \"mode\" : \"scroll\",\n               \"parameters\" : {\n                  \"scroll_mode\" : \"discrete\"\n               },\n               \"path\" : \"/user/hand/left/input/thumbstick\"\n            },\n            {\n               \"inputs\" : {\n                  \"scroll\" : {\n                     \"output\" : \"/actions/scroll_discrete/in/ScrollDiscrete\"\n                  }\n               },\n               \"mode\" : \"scroll\",\n               \"parameters\" : {\n                  \"scroll_mode\" : \"discrete\"\n               },\n               \"path\" : \"/user/hand/right/input/thumbstick\"\n            },\n            {\n               \"inputs\" : {\n                  \"scroll\" : {\n                     \"output\" : \"/actions/scroll_discrete/in/ScrollDiscrete\"\n                  }\n               },\n               \"mode\" : \"scroll\",\n               \"parameters\" : {\n                  \"discrete_scroll_trackpad_accumthreshold_onmove\" : \"0.28\",\n                  \"discrete_scroll_trackpad_accumthreshold_onreversal\" : \"0.84\",\n                  \"discrete_scroll_trackpad_accumthreshold_ontouch\" : \"0.78\",\n                  \"discrete_scroll_trackpad_noisethreshold_onmove\" : \"0.01\",\n                  \"discrete_scroll_trackpad_noisethreshold_onreversal\" : \"0.045\",\n                  \"discrete_scroll_trackpad_noisethreshold_ontouch\" : \"0.15\",\n                  \"discrete_scroll_trackpad_slideandhold_borderbottom\" : \"-0.65\",\n                  \"discrete_scroll_trackpad_slideandhold_bordertop\" : \"0.55\",\n                  \"scroll_mode\" : \"discrete\"\n               },\n               \"path\" : \"/user/hand/left/input/trackpad\"\n            },\n            {\n               \"inputs\" : {\n                  \"scroll\" : {\n                     \"output\" : \"/actions/scroll_discrete/in/ScrollDiscrete\"\n                  }\n               },\n               \"mode\" : \"scroll\",\n               \"parameters\" : {\n                  \"discrete_scroll_trackpad_accumthreshold_onmove\" : \"0.28\",\n                  \"discrete_scroll_trackpad_accumthreshold_onreversal\" : \"0.84\",\n                  \"discrete_scroll_trackpad_accumthreshold_ontouch\" : \"0.78\",\n                  \"discrete_scroll_trackpad_noisethreshold_onmove\" : \"0.01\",\n                  \"discrete_scroll_trackpad_noisethreshold_onreversal\" : \"0.045\",\n                  \"discrete_scroll_trackpad_noisethreshold_ontouch\" : \"0.15\",\n                  \"discrete_scroll_trackpad_slideandhold_borderbottom\" : \"-0.65\",\n                  \"discrete_scroll_trackpad_slideandhold_bordertop\" : \"0.55\",\n                  \"scroll_mode\" : \"discrete\"\n               },\n               \"path\" : \"/user/hand/right/input/trackpad\"\n            }\n         ]\n      },\n\t  \"/actions/scroll_smooth\" : {\n         \"sources\" : [\n            {\n               \"inputs\" : {\n                  \"scroll\" : {\n                     \"output\" : \"/actions/scroll_smooth/in/ScrollSmooth\"\n                  }\n               },\n               \"mode\" : \"scroll\",\n               \"parameters\" : {\n                  \"scroll_mode\" : \"smooth\",\n                  \"smooth_scroll_edge_min_swipe\" : \"0.75\",\n                  \"smooth_scroll_edge_scroll_threshold\" : \"0.55\",\n                  \"smooth_scroll_edge_scroll_threshold_vertical_bias\" : \"0.1\",\n                  \"smooth_scroll_trackpad_aspect_ratio\" : \"0.5\"\n               },\n               \"path\" : \"/user/hand/left/input/trackpad\"\n            },\n            {\n               \"inputs\" : {\n                  \"scroll\" : {\n                     \"output\" : \"/actions/scroll_smooth/in/ScrollSmooth\"\n                  }\n               },\n               \"mode\" : \"scroll\",\n               \"parameters\" : {\n                  \"scroll_mode\" : \"smooth\",\n                  \"smooth_scroll_edge_min_swipe\" : \"0.75\",\n                  \"smooth_scroll_edge_scroll_threshold\" : \"0.55\",\n                  \"smooth_scroll_edge_scroll_threshold_vertical_bias\" : \"0.1\",\n                  \"smooth_scroll_trackpad_aspect_ratio\" : \"0.5\"\n               },\n               \"path\" : \"/user/hand/right/input/trackpad\"\n            },\n            {\n               \"inputs\" : {\n                  \"scroll\" : {\n                     \"output\" : \"/actions/scroll_smooth/in/ScrollSmooth\"\n                  }\n               },\n               \"mode\" : \"scroll\",\n               \"parameters\" : {\n                  \"scroll_mode\" : \"smooth\"\n               },\n               \"path\" : \"/user/hand/left/input/thumbstick\"\n            },\n            {\n               \"inputs\" : {\n                  \"scroll\" : {\n                     \"output\" : \"/actions/scroll_smooth/in/ScrollSmooth\"\n                  }\n               },\n               \"mode\" : \"scroll\",\n               \"parameters\" : {\n                  \"scroll_mode\" : \"smooth\"\n               },\n               \"path\" : \"/user/hand/right/input/thumbstick\"\n            }\n         ]\n      }\n   },\n   \"category\" : \"steamvr_input\",\n   \"controller_type\" : \"knuckles\",\n   \"description\" : \"\",\n   \"name\" : \"Default Desktop+ bindings for Index Controllers\",\n   \"options\" : {},\n   \"simulated_actions\" : []\n}\n"
  },
  {
    "path": "assets/input/action_bindings_knuckles_ev1.json",
    "content": "{\n   \"action_manifest_version\" : 5,\n   \"alias_info\" : {},\n   \"app_key\" : \"steam.overlay.1494460\",\n   \"bindings\" : {\n      \"/actions/laserpointer\" : {\n         \"haptics\" : [\n            {\n               \"output\" : \"/actions/laserpointer/out/Haptic\",\n               \"path\" : \"/user/hand/left/output/haptic\"\n            },\n            {\n               \"output\" : \"/actions/laserpointer/out/Haptic\",\n               \"path\" : \"/user/hand/right/output/haptic\"\n            }\n         ],\n         \"sources\" : [\n            {\n               \"inputs\" : {\n                  \"click\" : {\n                     \"output\" : \"/actions/laserpointer/in/LeftClick\"\n                  }\n               },\n               \"mode\" : \"button\",\n               \"path\" : \"/user/hand/right/input/trigger\"\n            },\n            {\n               \"inputs\" : {\n                  \"click\" : {\n                     \"output\" : \"/actions/laserpointer/in/LeftClick\"\n                  }\n               },\n               \"mode\" : \"button\",\n               \"path\" : \"/user/hand/left/input/trigger\"\n            },\n            {\n               \"inputs\" : {\n                  \"east\" : {\n                     \"output\" : \"/actions/laserpointer/in/RightClick\"\n                  }\n               },\n               \"mode\" : \"dpad_click\",\n               \"path\" : \"/user/hand/left/input/trackpad\"\n            },\n            {\n               \"inputs\" : {\n                  \"east\" : {\n                     \"output\" : \"/actions/laserpointer/in/RightClick\"\n                  }\n               },\n               \"mode\" : \"dpad_click\",\n               \"path\" : \"/user/hand/right/input/trackpad\"\n            },\n\t\t\t{\n               \"inputs\" : {\n                  \"click\" : {\n                     \"output\" : \"/actions/laserpointer/in/Aux01Click\"\n                  }\n               },\n               \"mode\" : \"button\",\n               \"path\" : \"/user/hand/left/input/b\"\n            },\n\t\t\t{\n               \"inputs\" : {\n                  \"click\" : {\n                     \"output\" : \"/actions/laserpointer/in/Aux01Click\"\n                  }\n               },\n               \"mode\" : \"button\",\n               \"path\" : \"/user/hand/right/input/a\"\n            },\n            {\n               \"inputs\" : {\n                  \"click\" : {\n                     \"output\" : \"/actions/laserpointer/in/Aux02Click\"\n                  }\n               },\n               \"mode\" : \"button\",\n               \"path\" : \"/user/hand/left/input/a\"\n            },\n            {\n               \"inputs\" : {\n                  \"click\" : {\n                     \"output\" : \"/actions/laserpointer/in/Aux02Click\"\n                  }\n               },\n               \"mode\" : \"button\",\n               \"path\" : \"/user/hand/right/input/b\"\n            }\n         ]\n      },\n\t  \"/actions/scroll_discrete\" : {\n         \"sources\" : [\n            {\n               \"inputs\" : {\n                  \"scroll\" : {\n                     \"output\" : \"/actions/scroll_discrete/in/ScrollDiscrete\"\n                  }\n               },\n               \"mode\" : \"scroll\",\n               \"parameters\" : {\n                  \"discrete_scroll_trackpad_accumthreshold_onmove\" : \"0.224\",\n                  \"discrete_scroll_trackpad_accumthreshold_onreversal\" : \"0.672\",\n                  \"discrete_scroll_trackpad_accumthreshold_ontouch\" : \"0.624\",\n                  \"discrete_scroll_trackpad_noisethreshold_onmove\" : \"0.008\",\n                  \"discrete_scroll_trackpad_noisethreshold_onreversal\" : \"0.095\",\n                  \"discrete_scroll_trackpad_noisethreshold_ontouch\" : \"0.16\",\n                  \"discrete_scroll_trackpad_slideandhold_borderbottom\" : \"-0.60\",\n                  \"discrete_scroll_trackpad_slideandhold_bordertop\" : \"0.65\",\n                  \"scroll_mode\" : \"discrete\"\n               },\n               \"path\" : \"/user/hand/left/input/trackpad\"\n            },\n            {\n               \"inputs\" : {\n                  \"scroll\" : {\n                     \"output\" : \"/actions/scroll_discrete/in/ScrollDiscrete\"\n                  }\n               },\n               \"mode\" : \"scroll\",\n               \"parameters\" : {\n                  \"discrete_scroll_trackpad_accumthreshold_onmove\" : \"0.224\",\n                  \"discrete_scroll_trackpad_accumthreshold_onreversal\" : \"0.672\",\n                  \"discrete_scroll_trackpad_accumthreshold_ontouch\" : \"0.624\",\n                  \"discrete_scroll_trackpad_noisethreshold_onmove\" : \"0.008\",\n                  \"discrete_scroll_trackpad_noisethreshold_onreversal\" : \"0.095\",\n                  \"discrete_scroll_trackpad_noisethreshold_ontouch\" : \"0.16\",\n                  \"discrete_scroll_trackpad_slideandhold_borderbottom\" : \"-0.60\",\n                  \"discrete_scroll_trackpad_slideandhold_bordertop\" : \"0.65\",\n                  \"scroll_mode\" : \"discrete\"\n               },\n               \"path\" : \"/user/hand/right/input/trackpad\"\n            }\n         ]\n      },\n\t  \"/actions/scroll_smooth\" : {\n         \"sources\" : [\n            {\n               \"inputs\" : {\n                  \"scroll\" : {\n                     \"output\" : \"/actions/scroll_smooth/in/ScrollSmooth\"\n                  }\n               },\n               \"mode\" : \"scroll\",\n               \"parameters\" : {\n                  \"scroll_mode\" : \"smooth\"\n               },\n               \"path\" : \"/user/hand/left/input/trackpad\"\n            },\n            {\n               \"inputs\" : {\n                  \"scroll\" : {\n                     \"output\" : \"/actions/scroll_smooth/in/ScrollSmooth\"\n                  }\n               },\n               \"mode\" : \"scroll\",\n               \"parameters\" : {\n                  \"scroll_mode\" : \"smooth\"\n               },\n               \"path\" : \"/user/hand/right/input/trackpad\"\n            }\n         ]\n      }\n   },\n   \"category\" : \"steamvr_input\",\n   \"controller_type\" : \"knuckles_ev1\",\n   \"description\" : \"\",\n   \"name\" : \"Default Desktop+ bindings for Knuckles EV1 Controllers\",\n   \"options\" : {},\n   \"simulated_actions\" : []\n}\n"
  },
  {
    "path": "assets/input/action_bindings_playstation_vr2_sense_controller.json",
    "content": "{\n   \"action_manifest_version\" : 5,\n   \"alias_info\" : {},\n   \"app_key\" : \"steam.overlay.1494460\",\n   \"bindings\" : {\n      \"/actions/laserpointer\" : {\n         \"haptics\" : [\n            {\n               \"output\" : \"/actions/laserpointer/out/Haptic\",\n               \"path\" : \"/user/hand/left/output/haptic\"\n            },\n            {\n               \"output\" : \"/actions/laserpointer/out/Haptic\",\n               \"path\" : \"/user/hand/right/output/haptic\"\n            }\n         ],\n         \"sources\" : [\n            {\n               \"inputs\" : {\n                  \"click\" : {\n                     \"output\" : \"/actions/laserpointer/in/LeftClick\"\n                  }\n               },\n               \"mode\" : \"button\",\n               \"parameters\" : {\n                  \"click_activate_threshold\" : \"0.65\",\n                  \"click_deactivate_threshold\" : \"0.6\"\n               },\n               \"path\" : \"/user/hand/left/input/l2\"\n            },\n            {\n               \"inputs\" : {\n                  \"click\" : {\n                     \"output\" : \"/actions/laserpointer/in/LeftClick\"\n                  }\n               },\n               \"mode\" : \"button\",\n               \"parameters\" : {\n                  \"click_activate_threshold\" : \"0.65\",\n                  \"click_deactivate_threshold\" : \"0.6\"\n               },\n               \"path\" : \"/user/hand/right/input/r2\"\n            },\n            {\n               \"inputs\" : {\n                  \"click\" : {\n                     \"output\" : \"/actions/laserpointer/in/RightClick\"\n                  }\n               },\n               \"mode\" : \"button\",\n               \"path\" : \"/user/hand/left/input/square\"\n            },\n            {\n               \"inputs\" : {\n                  \"click\" : {\n                     \"output\" : \"/actions/laserpointer/in/RightClick\"\n                  }\n               },\n               \"mode\" : \"button\",\n               \"path\" : \"/user/hand/right/input/cross\"\n            },\n            {\n               \"inputs\" : {\n                  \"click\" : {\n                     \"output\" : \"/actions/laserpointer/in/Aux02Click\"\n                  }\n               },\n               \"mode\" : \"button\",\n               \"path\" : \"/user/hand/left/input/triangle\"\n            },\n            {\n               \"inputs\" : {\n                  \"click\" : {\n                     \"output\" : \"/actions/laserpointer/in/Aux02Click\"\n                  }\n               },\n               \"mode\" : \"button\",\n               \"path\" : \"/user/hand/right/input/circle\"\n            }\n         ]\n      },\n      \"/actions/scroll_discrete\" : {\n         \"sources\" : [\n            {\n               \"inputs\" : {\n                  \"scroll\" : {\n                     \"output\" : \"/actions/scroll_discrete/in/ScrollDiscrete\"\n                  }\n               },\n               \"mode\" : \"scroll\",\n               \"path\" : \"/user/hand/left/input/left_stick\"\n            },\n            {\n               \"inputs\" : {\n                  \"scroll\" : {\n                     \"output\" : \"/actions/scroll_discrete/in/ScrollDiscrete\"\n                  }\n               },\n               \"mode\" : \"scroll\",\n               \"path\" : \"/user/hand/right/input/right_stick\"\n            }\n         ]\n      },\n\t  \"/actions/scroll_smooth\" : {\n         \"sources\" : [\n            {\n               \"inputs\" : {\n                  \"scroll\" : {\n                     \"output\" : \"/actions/scroll_smooth/in/ScrollSmooth\"\n                  }\n               },\n               \"mode\" : \"scroll\",\n               \"parameters\" : {\n                  \"scroll_mode\" : \"smooth\"\n               },\n               \"path\" : \"/user/hand/left/input/left_stick\"\n            },\n            {\n               \"inputs\" : {\n                  \"scroll\" : {\n                     \"output\" : \"/actions/scroll_smooth/in/ScrollSmooth\"\n                  }\n               },\n               \"mode\" : \"scroll\",\n               \"parameters\" : {\n                  \"scroll_mode\" : \"smooth\"\n               },\n               \"path\" : \"/user/hand/right/input/right_stick\"\n            }\n         ]\n      }\n   },\n   \"category\" : \"steamvr_input\",\n   \"controller_type\" : \"playstation_vr2_sense\",\n   \"description\" : \"\",\n   \"name\" : \"Default Desktop+ bindings for PS VR2 Sense Controllers\",\n   \"options\" : {},\n   \"simulated_actions\" : []\n}\n"
  },
  {
    "path": "assets/input/action_bindings_svl_hand_interaction_augmented.json",
    "content": "{\n   \"action_manifest_version\" : 5,\n   \"alias_info\" : {},\n   \"app_key\" : \"steam.overlay.1494460\",\n   \"bindings\" : {\n      \"/actions/laserpointer\" : {\n         \"sources\" : [\n            {\n               \"inputs\" : {\n                  \"click\" : {\n                     \"output\" : \"/actions/laserpointer/in/LeftClick\"\n                  }\n               },\n               \"mode\" : \"button\",\n               \"parameters\" : {\n                  \"click_activate_threshold\" : \"0.65\",\n                  \"click_deactivate_threshold\" : \"0.6\"\n               },\n               \"path\" : \"/user/hand/right/input/trigger\"\n            },\n            {\n               \"inputs\" : {\n                  \"click\" : {\n                     \"output\" : \"/actions/laserpointer/in/LeftClick\"\n                  }\n               },\n               \"mode\" : \"button\",\n               \"parameters\" : {\n                  \"click_activate_threshold\" : \"0.65\",\n                  \"click_deactivate_threshold\" : \"0.6\"\n               },\n               \"path\" : \"/user/hand/left/input/trigger\"\n            },\n            {\n               \"inputs\" : {\n                  \"click\" : {\n                     \"output\" : \"/actions/laserpointer/in/LeftClick\"\n                  }\n               },\n               \"mode\" : \"button\",\n               \"path\" : \"/user/hand/right/input/index_pinch\"\n            },\n            {\n               \"inputs\" : {\n                  \"click\" : {\n                     \"output\" : \"/actions/laserpointer/in/LeftClick\"\n                  }\n               },\n               \"mode\" : \"button\",\n               \"path\" : \"/user/hand/left/input/index_pinch\"\n            },\n            {\n               \"inputs\" : {\n                  \"click\" : {\n                     \"output\" : \"/actions/laserpointer/in/RightClick\"\n                  }\n               },\n               \"mode\" : \"button\",\n               \"path\" : \"/user/hand/left/input/pinky_pinch\"\n            },\n            {\n               \"inputs\" : {\n                  \"click\" : {\n                     \"output\" : \"/actions/laserpointer/in/RightClick\"\n                  }\n               },\n               \"mode\" : \"button\",\n               \"path\" : \"/user/hand/right/input/pinky_pinch\"\n            }\n         ]\n      }\n   },\n   \"category\" : \"steamvr_input\",\n   \"controller_type\": \"svl_hand_interaction_augmented\",\n   \"description\": \"\",\n   \"name\": \"Default Desktop+ bindings for Steam Link Hand Tracking\",\n   \"options\" : {},\n   \"simulated_actions\" : []\n}\n"
  },
  {
    "path": "assets/input/action_bindings_touch.json",
    "content": "{\n   \"action_manifest_version\" : 5,\n   \"alias_info\" : {},\n   \"app_key\" : \"steam.overlay.1494460\",\n   \"bindings\" : {\n      \"/actions/laserpointer\" : {\n         \"haptics\" : [\n            {\n               \"output\" : \"/actions/laserpointer/out/Haptic\",\n               \"path\" : \"/user/hand/left/output/haptic\"\n            },\n            {\n               \"output\" : \"/actions/laserpointer/out/Haptic\",\n               \"path\" : \"/user/hand/right/output/haptic\"\n            }\n         ],\n         \"sources\" : [\n            {\n               \"inputs\" : {\n                  \"click\" : {\n                     \"output\" : \"/actions/laserpointer/in/LeftClick\"\n                  }\n               },\n               \"mode\" : \"button\",\n               \"parameters\" : {\n                  \"click_activate_threshold\" : \"0.65\",\n                  \"click_deactivate_threshold\" : \"0.6\"\n               },\n               \"path\" : \"/user/hand/right/input/trigger\"\n            },\n            {\n               \"inputs\" : {\n                  \"click\" : {\n                     \"output\" : \"/actions/laserpointer/in/LeftClick\"\n                  }\n               },\n               \"mode\" : \"button\",\n               \"parameters\" : {\n                  \"click_activate_threshold\" : \"0.65\",\n                  \"click_deactivate_threshold\" : \"0.6\"\n               },\n               \"path\" : \"/user/hand/left/input/trigger\"\n            },\n            {\n               \"inputs\" : {\n                  \"click\" : {\n                     \"output\" : \"/actions/laserpointer/in/RightClick\"\n                  }\n               },\n               \"mode\" : \"button\",\n               \"path\" : \"/user/hand/left/input/x\"\n            },\n            {\n               \"inputs\" : {\n                  \"click\" : {\n                     \"output\" : \"/actions/laserpointer/in/RightClick\"\n                  }\n               },\n               \"mode\" : \"button\",\n               \"path\" : \"/user/hand/right/input/a\"\n            },\n            {\n               \"inputs\" : {\n                  \"click\" : {\n                     \"output\" : \"/actions/laserpointer/in/MiddleClick\"\n                  }\n               },\n               \"mode\" : \"button\",\n               \"path\" : \"/user/hand/left/input/grip\"\n            },\n            {\n               \"inputs\" : {\n                  \"click\" : {\n                     \"output\" : \"/actions/laserpointer/in/MiddleClick\"\n                  }\n               },\n               \"mode\" : \"button\",\n               \"path\" : \"/user/hand/right/input/grip\"\n            },\n            {\n               \"inputs\" : {\n                  \"click\" : {\n                     \"output\" : \"/actions/laserpointer/in/Aux02Click\"\n                  }\n               },\n               \"mode\" : \"button\",\n               \"path\" : \"/user/hand/left/input/y\"\n            },\n            {\n               \"inputs\" : {\n                  \"click\" : {\n                     \"output\" : \"/actions/laserpointer/in/Aux02Click\"\n                  }\n               },\n               \"mode\" : \"button\",\n               \"path\" : \"/user/hand/right/input/b\"\n            }\n         ]\n      },\n      \"/actions/scroll_discrete\" : {\n         \"sources\" : [\n            {\n               \"inputs\" : {\n                  \"scroll\" : {\n                     \"output\" : \"/actions/scroll_discrete/in/ScrollDiscrete\"\n                  }\n               },\n               \"mode\" : \"scroll\",\n               \"path\" : \"/user/hand/left/input/joystick\"\n            },\n            {\n               \"inputs\" : {\n                  \"scroll\" : {\n                     \"output\" : \"/actions/scroll_discrete/in/ScrollDiscrete\"\n                  }\n               },\n               \"mode\" : \"scroll\",\n               \"path\" : \"/user/hand/right/input/joystick\"\n            }\n         ]\n      },\n      \"/actions/scroll_smooth\" : {\n         \"sources\" : [\n            {\n               \"inputs\" : {\n                  \"scroll\" : {\n                     \"output\" : \"/actions/scroll_smooth/in/ScrollSmooth\"\n                  }\n               },\n               \"mode\" : \"scroll\",\n               \"parameters\" : {\n                  \"scroll_mode\" : \"smooth\"\n               },\n               \"path\" : \"/user/hand/left/input/joystick\"\n            },\n            {\n               \"inputs\" : {\n                  \"scroll\" : {\n                     \"output\" : \"/actions/scroll_smooth/in/ScrollSmooth\"\n                  }\n               },\n               \"mode\" : \"scroll\",\n               \"parameters\" : {\n                  \"scroll_mode\" : \"smooth\"\n               },\n               \"path\" : \"/user/hand/right/input/joystick\"\n            }\n         ]\n      }\n   },\n   \"category\" : \"steamvr_input\",\n   \"controller_type\" : \"oculus_touch\",\n   \"description\" : \"\",\n   \"name\" : \"Default Desktop+ bindings for Oculus Touch\",\n   \"options\" : {},\n   \"simulated_actions\" : []\n}\n"
  },
  {
    "path": "assets/input/action_bindings_vive_controller.json",
    "content": "{\n   \"action_manifest_version\" : 5,\n   \"alias_info\" : {},\n   \"app_key\" : \"steam.overlay.1494460\",\n   \"bindings\" : {\n      \"/actions/laserpointer\" : {\n         \"haptics\" : [\n            {\n               \"output\" : \"/actions/laserpointer/out/Haptic\",\n               \"path\" : \"/user/hand/left/output/haptic\"\n            },\n            {\n               \"output\" : \"/actions/laserpointer/out/Haptic\",\n               \"path\" : \"/user/hand/right/output/haptic\"\n            }\n         ],\n         \"sources\" : [\n            {\n               \"inputs\" : {\n                  \"click\" : {\n                     \"output\" : \"/actions/laserpointer/in/LeftClick\"\n                  }\n               },\n               \"mode\" : \"button\",\n               \"parameters\" : {\n                  \"click_activate_threshold\" : \"0.65\",\n                  \"click_deactivate_threshold\" : \"0.6\"\n               },\n               \"path\" : \"/user/hand/right/input/trigger\"\n            },\n            {\n               \"inputs\" : {\n                  \"click\" : {\n                     \"output\" : \"/actions/laserpointer/in/LeftClick\"\n                  }\n               },\n               \"mode\" : \"button\",\n               \"parameters\" : {\n                  \"click_activate_threshold\" : \"0.65\",\n                  \"click_deactivate_threshold\" : \"0.6\"\n               },\n               \"path\" : \"/user/hand/left/input/trigger\"\n            },\n            {\n               \"inputs\" : {\n                  \"east\" : {\n                     \"output\" : \"/actions/laserpointer/in/RightClick\"\n                  }\n               },\n               \"mode\" : \"dpad_click\",\n               \"path\" : \"/user/hand/left/input/trackpad\"\n            },\n            {\n               \"inputs\" : {\n                  \"east\" : {\n                     \"output\" : \"/actions/laserpointer/in/RightClick\"\n                  }\n               },\n               \"mode\" : \"dpad_click\",\n               \"path\" : \"/user/hand/right/input/trackpad\"\n            },\n\t\t\t{\n               \"inputs\" : {\n                  \"click\" : {\n                     \"output\" : \"/actions/laserpointer/in/Aux01Click\"\n                  }\n               },\n               \"mode\" : \"button\",\n               \"path\" : \"/user/hand/left/input/grip\"\n            },\n\t\t\t{\n               \"inputs\" : {\n                  \"click\" : {\n                     \"output\" : \"/actions/laserpointer/in/Aux01Click\"\n                  }\n               },\n               \"mode\" : \"button\",\n               \"path\" : \"/user/hand/right/input/grip\"\n            },\n            {\n               \"inputs\" : {\n                  \"click\" : {\n                     \"output\" : \"/actions/laserpointer/in/Aux02Click\"\n                  }\n               },\n               \"mode\" : \"button\",\n               \"path\" : \"/user/hand/left/input/application_menu\"\n            },\n            {\n               \"inputs\" : {\n                  \"click\" : {\n                     \"output\" : \"/actions/laserpointer/in/Aux02Click\"\n                  }\n               },\n               \"mode\" : \"button\",\n               \"path\" : \"/user/hand/right/input/application_menu\"\n            }\n         ]\n      },\n\t  \"/actions/scroll_discrete\" : {\n         \"sources\" : [\n            {\n               \"inputs\" : {\n                  \"scroll\" : {\n                     \"output\" : \"/actions/scroll_discrete/in/ScrollDiscrete\"\n                  }\n               },\n               \"mode\" : \"scroll\",\n               \"parameters\" : {\n                  \"discrete_scroll_trackpad_accumthreshold_onmove\" : \"0.224\",\n                  \"discrete_scroll_trackpad_accumthreshold_onreversal\" : \"0.672\",\n                  \"discrete_scroll_trackpad_accumthreshold_ontouch\" : \"0.624\",\n                  \"discrete_scroll_trackpad_noisethreshold_onmove\" : \"0.008\",\n                  \"discrete_scroll_trackpad_noisethreshold_onreversal\" : \"0.095\",\n                  \"discrete_scroll_trackpad_noisethreshold_ontouch\" : \"0.16\",\n                  \"discrete_scroll_trackpad_slideandhold_borderbottom\" : \"-0.60\",\n                  \"discrete_scroll_trackpad_slideandhold_bordertop\" : \"0.65\",\n                  \"scroll_mode\" : \"discrete\"\n               },\n               \"path\" : \"/user/hand/left/input/trackpad\"\n            },\n            {\n               \"inputs\" : {\n                  \"scroll\" : {\n                     \"output\" : \"/actions/scroll_discrete/in/ScrollDiscrete\"\n                  }\n               },\n               \"mode\" : \"scroll\",\n               \"parameters\" : {\n                  \"discrete_scroll_trackpad_accumthreshold_onmove\" : \"0.224\",\n                  \"discrete_scroll_trackpad_accumthreshold_onreversal\" : \"0.672\",\n                  \"discrete_scroll_trackpad_accumthreshold_ontouch\" : \"0.624\",\n                  \"discrete_scroll_trackpad_noisethreshold_onmove\" : \"0.008\",\n                  \"discrete_scroll_trackpad_noisethreshold_onreversal\" : \"0.095\",\n                  \"discrete_scroll_trackpad_noisethreshold_ontouch\" : \"0.16\",\n                  \"discrete_scroll_trackpad_slideandhold_borderbottom\" : \"-0.60\",\n                  \"discrete_scroll_trackpad_slideandhold_bordertop\" : \"0.65\",\n                  \"scroll_mode\" : \"discrete\"\n               },\n               \"path\" : \"/user/hand/right/input/trackpad\"\n            }\n         ]\n      },\n\t  \"/actions/scroll_smooth\" : {\n         \"sources\" : [\n            {\n               \"inputs\" : {\n                  \"scroll\" : {\n                     \"output\" : \"/actions/scroll_smooth/in/ScrollSmooth\"\n                  }\n               },\n               \"mode\" : \"scroll\",\n               \"parameters\" : {\n                  \"scroll_mode\" : \"smooth\"\n               },\n               \"path\" : \"/user/hand/left/input/trackpad\"\n            },\n            {\n               \"inputs\" : {\n                  \"scroll\" : {\n                     \"output\" : \"/actions/scroll_smooth/in/ScrollSmooth\"\n                  }\n               },\n               \"mode\" : \"scroll\",\n               \"parameters\" : {\n                  \"scroll_mode\" : \"smooth\"\n               },\n               \"path\" : \"/user/hand/right/input/trackpad\"\n            }\n         ]\n      }\n   },\n   \"category\" : \"steamvr_input\",\n   \"controller_type\" : \"vive_controller\",\n   \"description\" : \"\",\n   \"name\" : \"Default Desktop+ bindings for Vive Controllers\",\n   \"options\" : {},\n   \"simulated_actions\" : []\n}\n"
  },
  {
    "path": "assets/input/action_bindings_vive_cosmos.json",
    "content": "{\n   \"action_manifest_version\" : 5,\n   \"alias_info\" : {},\n   \"app_key\" : \"steam.overlay.1494460\",\n   \"bindings\" : {\n      \"/actions/laserpointer\" : {\n         \"haptics\" : [\n            {\n               \"output\" : \"/actions/laserpointer/out/Haptic\",\n               \"path\" : \"/user/hand/left/output/haptic\"\n            },\n            {\n               \"output\" : \"/actions/laserpointer/out/Haptic\",\n               \"path\" : \"/user/hand/right/output/haptic\"\n            }\n         ],\n         \"sources\" : [\n            {\n               \"inputs\" : {\n                  \"click\" : {\n                     \"output\" : \"/actions/laserpointer/in/LeftClick\"\n                  }\n               },\n               \"mode\" : \"button\",\n\t\t\t   \"parameters\" : {\n                  \"click_activate_threshold\" : \"0.65\",\n                  \"click_deactivate_threshold\" : \"0.6\"\n               },\n               \"path\" : \"/user/hand/right/input/trigger\"\n            },\n            {\n               \"inputs\" : {\n                  \"click\" : {\n                     \"output\" : \"/actions/laserpointer/in/LeftClick\"\n                  }\n               },\n               \"mode\" : \"button\",\n\t\t\t   \"parameters\" : {\n                  \"click_activate_threshold\" : \"0.65\",\n                  \"click_deactivate_threshold\" : \"0.6\"\n               },\n               \"path\" : \"/user/hand/left/input/trigger\"\n            },\n            {\n               \"inputs\" : {\n                  \"click\" : {\n                     \"output\" : \"/actions/laserpointer/in/RightClick\"\n                  }\n               },\n               \"mode\" : \"button\",\n               \"path\" : \"/user/hand/left/input/x\"\n            },\n            {\n               \"inputs\" : {\n                  \"click\" : {\n                     \"output\" : \"/actions/laserpointer/in/RightClick\"\n                  }\n               },\n               \"mode\" : \"button\",\n               \"path\" : \"/user/hand/right/input/a\"\n            },\n            {\n               \"inputs\" : {\n                  \"click\" : {\n                     \"output\" : \"/actions/laserpointer/in/Aux02Click\"\n                  }\n               },\n               \"mode\" : \"button\",\n               \"path\" : \"/user/hand/left/input/y\"\n            },\n            {\n               \"inputs\" : {\n                  \"click\" : {\n                     \"output\" : \"/actions/laserpointer/in/Aux02Click\"\n                  }\n               },\n               \"mode\" : \"button\",\n               \"path\" : \"/user/hand/right/input/b\"\n            }\n         ]\n      },\n\t  \"/actions/scroll_discrete\" : {\n         \"sources\" : [\n            {\n               \"inputs\" : {\n                  \"scroll\" : {\n                     \"output\" : \"/actions/scroll_discrete/in/ScrollDiscrete\"\n                  }\n               },\n               \"mode\" : \"scroll\",\n               \"path\" : \"/user/hand/left/input/joystick\"\n            },\n            {\n               \"inputs\" : {\n                  \"scroll\" : {\n                     \"output\" : \"/actions/scroll_discrete/in/ScrollDiscrete\"\n                  }\n               },\n               \"mode\" : \"scroll\",\n               \"path\" : \"/user/hand/right/input/joystick\"\n            }\n         ]\n      },\n\t  \"/actions/scroll_smooth\" : {\n         \"sources\" : [\n            {\n               \"inputs\" : {\n                  \"scroll\" : {\n                     \"output\" : \"/actions/scroll_smooth/in/ScrollSmooth\"\n                  }\n               },\n               \"mode\" : \"scroll\",\n\t\t\t   \"parameters\" : {\n                  \"scroll_mode\" : \"smooth\"\n               },\n               \"path\" : \"/user/hand/left/input/joystick\"\n            },\n            {\n               \"inputs\" : {\n                  \"scroll\" : {\n                     \"output\" : \"/actions/scroll_smooth/in/ScrollSmooth\"\n                  }\n               },\n               \"mode\" : \"scroll\",\n\t\t\t   \"parameters\" : {\n                  \"scroll_mode\" : \"smooth\"\n               },\n               \"path\" : \"/user/hand/right/input/joystick\"\n            }\n         ]\n      }\n   },\n   \"category\" : \"steamvr_input\",\n   \"controller_type\" : \"vive_cosmos_controller\",\n   \"description\" : \"\",\n   \"name\" : \"Default Desktop+ bindings for Vive Cosmos Controllers\",\n   \"options\" : {},\n   \"simulated_actions\" : []\n}\n"
  },
  {
    "path": "assets/input/action_manifest_de.json",
    "content": "{\n    \"language_tag\": \"de_DE\",\n\n    \"/actions/shortcuts\" : \"Globale Kürzel\",\n\n    \"/actions/shortcuts/in/EnableGlobalLaserPointer\" : \"Aktiviere globalen Laser-Pointer\",\n    \"/actions/shortcuts/in/GlobalShortcut01\" : \"Globales Kürzel #1 ausführen\",\n    \"/actions/shortcuts/in/GlobalShortcut02\" : \"Globales Kürzel #2 ausführen\",\n    \"/actions/shortcuts/in/GlobalShortcut03\" : \"Globales Kürzel #3 ausführen\",\n    \"/actions/shortcuts/in/GlobalShortcut04\" : \"Globales Kürzel #4 ausführen\",\n    \"/actions/shortcuts/in/GlobalShortcut05\" : \"Globales Kürzel #5 ausführen\",\n    \"/actions/shortcuts/in/GlobalShortcut06\" : \"Globales Kürzel #6 ausführen\",\n    \"/actions/shortcuts/in/GlobalShortcut07\" : \"Globales Kürzel #7 ausführen\",\n    \"/actions/shortcuts/in/GlobalShortcut08\" : \"Globales Kürzel #8 ausführen\",\n    \"/actions/shortcuts/in/GlobalShortcut09\" : \"Globales Kürzel #9 ausführen\",\n    \"/actions/shortcuts/in/GlobalShortcut10\" : \"Globales Kürzel #10 ausführen\",\n    \"/actions/shortcuts/in/GlobalShortcut11\" : \"Globales Kürzel #11 ausführen\",\n    \"/actions/shortcuts/in/GlobalShortcut12\" : \"Globales Kürzel #12 ausführen\",\n    \"/actions/shortcuts/in/GlobalShortcut13\" : \"Globales Kürzel #13 ausführen\",\n    \"/actions/shortcuts/in/GlobalShortcut14\" : \"Globales Kürzel #14 ausführen\",\n    \"/actions/shortcuts/in/GlobalShortcut15\" : \"Globales Kürzel #15 ausführen\",\n    \"/actions/shortcuts/in/GlobalShortcut16\" : \"Globales Kürzel #16 ausführen\",\n    \"/actions/shortcuts/in/GlobalShortcut17\" : \"Globales Kürzel #17 ausführen\",\n    \"/actions/shortcuts/in/GlobalShortcut18\" : \"Globales Kürzel #18 ausführen\",\n    \"/actions/shortcuts/in/GlobalShortcut19\" : \"Globales Kürzel #19 ausführen\",\n    \"/actions/shortcuts/in/GlobalShortcut20\" : \"Globales Kürzel #20 ausführen\",\n\n    \"/actions/laserpointer\" : \"Laserpointer\",\n\n    \"/actions/laserpointer/in/LeftClick\"   : \"Linksklick\",\n    \"/actions/laserpointer/in/RightClick\"  : \"Rechtsklick\",\n    \"/actions/laserpointer/in/MiddleClick\" : \"Mittelklick\",\n    \"/actions/laserpointer/in/Aux01Click\"  : \"Extra 1-Klick\",\n    \"/actions/laserpointer/in/Aux02Click\"  : \"Extra 2-Klick\",\n    \"/actions/laserpointer/in/Drag\"        : \"Overlay ziehen\",\n\t\n\t\"/actions/scroll_discrete\" : \"Mausrad-Bildlauf\",\n\t\n    \"/actions/scroll_discrete/in/ScrollDiscrete\" : \"Overlay scrollen\",\n\t\n\t\"/actions/scroll_smooth\" : \"Weicher Bildlauf\",\n\t\n    \"/actions/scroll_smooth/in/ScrollSmooth\"   : \"Overlay scrollen\"\n}\n"
  },
  {
    "path": "assets/input/action_manifest_en.json",
    "content": "{\n    \"language_tag\": \"en_US\",\n\n    \"/actions/shortcuts\" : \"Global Shortcuts\",\n\n    \"/actions/shortcuts/in/EnableGlobalLaserPointer\" : \"Enable Global Laser Pointer\",\n    \"/actions/shortcuts/in/GlobalShortcut01\" : \"Do Global Shortcut #1\",\n    \"/actions/shortcuts/in/GlobalShortcut02\" : \"Do Global Shortcut #2\",\n    \"/actions/shortcuts/in/GlobalShortcut03\" : \"Do Global Shortcut #3\",\n    \"/actions/shortcuts/in/GlobalShortcut04\" : \"Do Global Shortcut #4\",\n    \"/actions/shortcuts/in/GlobalShortcut05\" : \"Do Global Shortcut #5\",\n    \"/actions/shortcuts/in/GlobalShortcut06\" : \"Do Global Shortcut #6\",\n    \"/actions/shortcuts/in/GlobalShortcut07\" : \"Do Global Shortcut #7\",\n    \"/actions/shortcuts/in/GlobalShortcut08\" : \"Do Global Shortcut #8\",\n    \"/actions/shortcuts/in/GlobalShortcut09\" : \"Do Global Shortcut #9\",\n    \"/actions/shortcuts/in/GlobalShortcut10\" : \"Do Global Shortcut #10\",\n    \"/actions/shortcuts/in/GlobalShortcut11\" : \"Do Global Shortcut #11\",\n    \"/actions/shortcuts/in/GlobalShortcut12\" : \"Do Global Shortcut #12\",\n    \"/actions/shortcuts/in/GlobalShortcut13\" : \"Do Global Shortcut #13\",\n    \"/actions/shortcuts/in/GlobalShortcut14\" : \"Do Global Shortcut #14\",\n    \"/actions/shortcuts/in/GlobalShortcut15\" : \"Do Global Shortcut #15\",\n    \"/actions/shortcuts/in/GlobalShortcut16\" : \"Do Global Shortcut #16\",\n    \"/actions/shortcuts/in/GlobalShortcut17\" : \"Do Global Shortcut #17\",\n    \"/actions/shortcuts/in/GlobalShortcut18\" : \"Do Global Shortcut #18\",\n    \"/actions/shortcuts/in/GlobalShortcut19\" : \"Do Global Shortcut #19\",\n    \"/actions/shortcuts/in/GlobalShortcut20\" : \"Do Global Shortcut #20\",\n\n    \"/actions/laserpointer\" : \"Laser Pointer\",\n\n    \"/actions/laserpointer/in/LeftClick\"   : \"Left Click\",\n    \"/actions/laserpointer/in/RightClick\"  : \"Right Click\",\n    \"/actions/laserpointer/in/MiddleClick\" : \"Middle Click\",\n    \"/actions/laserpointer/in/Aux01Click\"  : \"Auxiliary 1 Click\",\n    \"/actions/laserpointer/in/Aux02Click\"  : \"Auxiliary 2 Click\",\n    \"/actions/laserpointer/in/Drag\"        : \"Drag Overlay\",\n\t\n\t\"/actions/scroll_discrete\" : \"Mousewheel Scrolling\",\n\t\n    \"/actions/scroll_discrete/in/ScrollDiscrete\" : \"Scroll Overlay\",\n\t\n\t\"/actions/scroll_smooth\" : \"Smooth Scrolling\",\n\t\n    \"/actions/scroll_smooth/in/ScrollSmooth\" : \"Scroll Overlay\"\n}\n"
  },
  {
    "path": "assets/input/action_manifest_ja.json",
    "content": "{\n    \"language_tag\": \"ja_JP\",\n\n    \"/actions/shortcuts\" : \"グローバルショートカット\",\n\n    \"/actions/shortcuts/in/EnableGlobalLaserPointer\" : \"グローバルレーザーポインターを有効化\",\n    \"/actions/shortcuts/in/GlobalShortcut01\" : \"グローバルショートカットを実行 #1\",\n    \"/actions/shortcuts/in/GlobalShortcut02\" : \"グローバルショートカットを実行 #2\",\n    \"/actions/shortcuts/in/GlobalShortcut03\" : \"グローバルショートカットを実行 #3\",\n    \"/actions/shortcuts/in/GlobalShortcut04\" : \"グローバルショートカットを実行 #4\",\n    \"/actions/shortcuts/in/GlobalShortcut05\" : \"グローバルショートカットを実行 #5\",\n    \"/actions/shortcuts/in/GlobalShortcut06\" : \"グローバルショートカットを実行 #6\",\n    \"/actions/shortcuts/in/GlobalShortcut07\" : \"グローバルショートカットを実行 #7\",\n    \"/actions/shortcuts/in/GlobalShortcut08\" : \"グローバルショートカットを実行 #8\",\n    \"/actions/shortcuts/in/GlobalShortcut09\" : \"グローバルショートカットを実行 #9\",\n    \"/actions/shortcuts/in/GlobalShortcut10\" : \"グローバルショートカットを実行 #10\",\n    \"/actions/shortcuts/in/GlobalShortcut11\" : \"グローバルショートカットを実行 #11\",\n    \"/actions/shortcuts/in/GlobalShortcut12\" : \"グローバルショートカットを実行 #12\",\n    \"/actions/shortcuts/in/GlobalShortcut13\" : \"グローバルショートカットを実行 #13\",\n    \"/actions/shortcuts/in/GlobalShortcut14\" : \"グローバルショートカットを実行 #14\",\n    \"/actions/shortcuts/in/GlobalShortcut15\" : \"グローバルショートカットを実行 #15\",\n    \"/actions/shortcuts/in/GlobalShortcut16\" : \"グローバルショートカットを実行 #16\",\n    \"/actions/shortcuts/in/GlobalShortcut17\" : \"グローバルショートカットを実行 #17\",\n    \"/actions/shortcuts/in/GlobalShortcut18\" : \"グローバルショートカットを実行 #18\",\n    \"/actions/shortcuts/in/GlobalShortcut19\" : \"グローバルショートカットを実行 #19\",\n    \"/actions/shortcuts/in/GlobalShortcut20\" : \"グローバルショートカットを実行 #20\",\n\n    \"/actions/laserpointer\" : \"レーザーポインター\",\n\n    \"/actions/laserpointer/in/LeftClick\"   : \"左クリック\",\n    \"/actions/laserpointer/in/RightClick\"  : \"右クリック\",\n    \"/actions/laserpointer/in/MiddleClick\" : \"ホイールクリック\",\n    \"/actions/laserpointer/in/Aux01Click\"  : \"補助 1 クリック\",\n    \"/actions/laserpointer/in/Aux02Click\"  : \"補助 2 クリック\",\n\t\n\t\"/actions/scroll_discrete\" : \"マウスホイール スクロール\",\n\t\n    \"/actions/scroll_discrete/in/ScrollDiscrete\" : \"スクロールオーバーレイ\",\n\t\n\t\"/actions/scroll_smooth\" : \"スムーズ スクロール\",\n\t\n    \"/actions/scroll_smooth/in/ScrollSmooth\" : \"スクロールオーバーレイ\"\n}\n"
  },
  {
    "path": "assets/input/action_manifest_ko.json",
    "content": "{\n    \"language_tag\": \"ko_KR\",\n\n    \"/actions/shortcuts\" : \"전역 단축키\",\n\n    \"/actions/shortcuts/in/EnableGlobalLaserPointer\" : \"전역 레이저 포인터 활성화\",\n    \"/actions/shortcuts/in/GlobalShortcut01\" : \"전역 단축키 실행 #1\",\n    \"/actions/shortcuts/in/GlobalShortcut02\" : \"전역 단축키 실행 #2\",\n    \"/actions/shortcuts/in/GlobalShortcut03\" : \"전역 단축키 실행 #3\",\n    \"/actions/shortcuts/in/GlobalShortcut04\" : \"전역 단축키 실행 #4\",\n    \"/actions/shortcuts/in/GlobalShortcut05\" : \"전역 단축키 실행 #5\",\n    \"/actions/shortcuts/in/GlobalShortcut06\" : \"전역 단축키 실행 #6\",\n    \"/actions/shortcuts/in/GlobalShortcut07\" : \"전역 단축키 실행 #7\",\n    \"/actions/shortcuts/in/GlobalShortcut08\" : \"전역 단축키 실행 #8\",\n    \"/actions/shortcuts/in/GlobalShortcut09\" : \"전역 단축키 실행 #9\",\n    \"/actions/shortcuts/in/GlobalShortcut10\" : \"전역 단축키 실행 #10\",\n    \"/actions/shortcuts/in/GlobalShortcut11\" : \"전역 단축키 실행 #11\",\n    \"/actions/shortcuts/in/GlobalShortcut12\" : \"전역 단축키 실행 #12\",\n    \"/actions/shortcuts/in/GlobalShortcut13\" : \"전역 단축키 실행 #13\",\n    \"/actions/shortcuts/in/GlobalShortcut14\" : \"전역 단축키 실행 #14\",\n    \"/actions/shortcuts/in/GlobalShortcut15\" : \"전역 단축키 실행 #15\",\n    \"/actions/shortcuts/in/GlobalShortcut16\" : \"전역 단축키 실행 #16\",\n    \"/actions/shortcuts/in/GlobalShortcut17\" : \"전역 단축키 실행 #17\",\n    \"/actions/shortcuts/in/GlobalShortcut18\" : \"전역 단축키 실행 #18\",\n    \"/actions/shortcuts/in/GlobalShortcut19\" : \"전역 단축키 실행 #19\",\n    \"/actions/shortcuts/in/GlobalShortcut20\" : \"전역 단축키 실행 #20\",\n\n    \"/actions/laserpointer\" : \"레이저 포인터\",\n\n    \"/actions/laserpointer/in/LeftClick\"   : \"왼쪽 클릭\",\n    \"/actions/laserpointer/in/RightClick\"  : \"오른쪽 클릭\",\n    \"/actions/laserpointer/in/MiddleClick\" : \"휠 클릭\",\n    \"/actions/laserpointer/in/Aux01Click\"  : \"측면 1 클릭\",\n    \"/actions/laserpointer/in/Aux02Click\"  : \"측면 2 클릭\",\n    \"/actions/laserpointer/in/Drag\"        : \"오버레이 드래그\",\n\t\n\t\"/actions/scroll_discrete\" : \"마우스 휠 스크롤\",\n\t\n    \"/actions/scroll_discrete/in/ScrollDiscrete\" : \"오버레이 스크롤\",\n\t\n\t\"/actions/scroll_smooth\" : \"부드러운 스크롤\",\n\t\n    \"/actions/scroll_smooth/in/ScrollSmooth\" : \"오버레이 스크롤\"\n}\n"
  },
  {
    "path": "assets/input/action_manifest_zh_CN.json",
    "content": "{\n    \"language_tag\": \"zh_CN\",\n\n    \"/actions/shortcuts\" : \"全局快捷键\",\n\n    \"/actions/shortcuts/in/EnableGlobalLaserPointer\" : \"启用全局激光指针\",\n    \"/actions/shortcuts/in/GlobalShortcut01\" : \"执行全局快捷键 #1\",\n    \"/actions/shortcuts/in/GlobalShortcut02\" : \"执行全局快捷键 #2\",\n    \"/actions/shortcuts/in/GlobalShortcut03\" : \"执行全局快捷键 #3\",\n    \"/actions/shortcuts/in/GlobalShortcut04\" : \"执行全局快捷键 #4\",\n    \"/actions/shortcuts/in/GlobalShortcut05\" : \"执行全局快捷键 #5\",\n    \"/actions/shortcuts/in/GlobalShortcut06\" : \"执行全局快捷键 #6\",\n    \"/actions/shortcuts/in/GlobalShortcut07\" : \"执行全局快捷键 #7\",\n    \"/actions/shortcuts/in/GlobalShortcut08\" : \"执行全局快捷键 #8\",\n    \"/actions/shortcuts/in/GlobalShortcut09\" : \"执行全局快捷键 #9\",\n    \"/actions/shortcuts/in/GlobalShortcut10\" : \"执行全局快捷键 #10\",\n    \"/actions/shortcuts/in/GlobalShortcut11\" : \"执行全局快捷键 #11\",\n    \"/actions/shortcuts/in/GlobalShortcut12\" : \"执行全局快捷键 #12\",\n    \"/actions/shortcuts/in/GlobalShortcut13\" : \"执行全局快捷键 #13\",\n    \"/actions/shortcuts/in/GlobalShortcut14\" : \"执行全局快捷键 #14\",\n    \"/actions/shortcuts/in/GlobalShortcut15\" : \"执行全局快捷键 #15\",\n    \"/actions/shortcuts/in/GlobalShortcut16\" : \"执行全局快捷键 #16\",\n    \"/actions/shortcuts/in/GlobalShortcut17\" : \"执行全局快捷键 #17\",\n    \"/actions/shortcuts/in/GlobalShortcut18\" : \"执行全局快捷键 #18\",\n    \"/actions/shortcuts/in/GlobalShortcut19\" : \"执行全局快捷键 #19\",\n    \"/actions/shortcuts/in/GlobalShortcut20\" : \"执行全局快捷键 #20\",\n\n    \"/actions/laserpointer\" : \"激光指针\",\n\n    \"/actions/laserpointer/in/LeftClick\"   : \"左键点击\",\n    \"/actions/laserpointer/in/RightClick\"  : \"右键点击\",\n    \"/actions/laserpointer/in/MiddleClick\" : \"中键点击\",\n    \"/actions/laserpointer/in/Aux01Click\"  : \"辅助1点击\",\n    \"/actions/laserpointer/in/Aux02Click\"  : \"辅助2点击\",\n    \"/actions/laserpointer/in/Drag\"        : \"拖动叠加\",\n\t\n\t\"/actions/scroll_discrete\" : \"鼠标滚轮滚动\",\n\t\n    \"/actions/scroll_discrete/in/ScrollDiscrete\" : \"滚动叠加\",\n\t\n\t\"/actions/scroll_smooth\" : \"平滑滚动\",\n\t\n    \"/actions/scroll_smooth/in/ScrollSmooth\" : \"滚动叠加\"\n}\n"
  },
  {
    "path": "assets/keyboards/azerty_be.ini",
    "content": "[LayoutInfo]\nName=AZERTY (Belgium)\nAuthor=\nHasAltGr=true\nHasClusterFunction=true\nHasClusterNavigation=true\nHasClusterNumpad=true\nHasClusterExtra=true\n\n[Key_Base_Row_0_ID_0]\nType=VirtualKey\nLabel=Esc\nCluster=Function\nKeyCode=27\nNoRepeat=true\n\n[Key_Base_Row_0_ID_1]\nType=Blank\nCluster=Function\n\n[Key_Base_Row_0_ID_2]\nType=VirtualKey\nLabel=F1\nCluster=Function\nKeyCode=112\n\n[Key_Base_Row_0_ID_3]\nType=VirtualKey\nLabel=F2\nCluster=Function\nKeyCode=113\n\n[Key_Base_Row_0_ID_4]\nType=VirtualKey\nLabel=F3\nCluster=Function\nKeyCode=114\n\n[Key_Base_Row_0_ID_5]\nType=VirtualKey\nLabel=F4\nCluster=Function\nKeyCode=115\n\n[Key_Base_Row_0_ID_6]\nType=Blank\nWidth=50\nCluster=Function\n\n[Key_Base_Row_0_ID_7]\nType=VirtualKey\nLabel=F5\nCluster=Function\nKeyCode=116\n\n[Key_Base_Row_0_ID_8]\nType=VirtualKey\nLabel=F6\nCluster=Function\nKeyCode=117\n\n[Key_Base_Row_0_ID_9]\nType=VirtualKey\nLabel=F7\nCluster=Function\nKeyCode=118\n\n[Key_Base_Row_0_ID_10]\nType=VirtualKey\nLabel=F8\nCluster=Function\nKeyCode=119\n\n[Key_Base_Row_0_ID_11]\nType=Blank\nWidth=50\nCluster=Function\n\n[Key_Base_Row_0_ID_12]\nType=VirtualKey\nLabel=F9\nCluster=Function\nKeyCode=120\n\n[Key_Base_Row_0_ID_13]\nType=VirtualKey\nLabel=F10\nCluster=Function\nKeyCode=121\n\n[Key_Base_Row_0_ID_14]\nType=VirtualKey\nLabel=F11\nCluster=Function\nKeyCode=122\n\n[Key_Base_Row_0_ID_15]\nType=VirtualKey\nLabel=F12\nCluster=Function\nKeyCode=123\n\n[Key_Base_Row_0_ID_16]\nType=Blank\nWidth=25\nCluster=Function\n\n[Key_Base_Row_0_ID_17]\nType=VirtualKey\nLabel=Print\\nScreen\nCluster=Function\nKeyCode=44\nNoRepeat=true\n\n[Key_Base_Row_0_ID_18]\nType=VirtualKey\nLabel=Scroll\\nLock\nCluster=Function\nKeyCode=145\nNoRepeat=true\n\n[Key_Base_Row_0_ID_19]\nType=VirtualKey\nLabel=Pause\nCluster=Function\nKeyCode=19\nNoRepeat=true\n\n[Key_Base_Row_0_ID_20]\nType=Blank\nWidth=25\nCluster=Extra\n\n[Key_Base_Row_0_ID_21]\nType=VirtualKey\nLabel=⏯\nCluster=Extra\nKeyCode=179\nNoRepeat=true\n\n[Key_Base_Row_0_ID_22]\nType=VirtualKey\nLabel=◼\nCluster=Extra\nKeyCode=178\nNoRepeat=true\n\n[Key_Base_Row_0_ID_23]\nType=VirtualKey\nLabel=⏮\nCluster=Extra\nKeyCode=177\nNoRepeat=true\n\n[Key_Base_Row_0_ID_24]\nType=VirtualKey\nLabel=⏭\nCluster=Extra\nKeyCode=176\nNoRepeat=true\n\n[Key_Base_Row_1_ID_0]\nType=Blank\nHeight=25\nCluster=Function\n\n[Key_Base_Row_1_ID_1]\nType=Blank\nHeight=25\nCluster=Extra\n\n[Key_Base_Row_2_ID_0]\nType=String\nLabel=²\nString=²\n\n[Key_Base_Row_2_ID_1]\nType=String\nLabel=&\nString=&\n\n[Key_Base_Row_2_ID_2]\nType=String\nLabel=é\nString=é\n\n[Key_Base_Row_2_ID_3]\nType=String\nLabel=\"\nString=\"\n\n[Key_Base_Row_2_ID_4]\nType=String\nLabel='\nString='\n\n[Key_Base_Row_2_ID_5]\nType=String\nLabel=(\nString=(\n\n[Key_Base_Row_2_ID_6]\nType=String\nLabel=§\nString=§\n\n[Key_Base_Row_2_ID_7]\nType=String\nLabel=è\nString=è\n\n[Key_Base_Row_2_ID_8]\nType=String\nLabel=!\nString=!\n\n[Key_Base_Row_2_ID_9]\nType=String\nLabel=ç\nString=ç\n\n[Key_Base_Row_2_ID_10]\nType=String\nLabel=à\nString=à\n\n[Key_Base_Row_2_ID_11]\nType=String\nLabel=)\nString=)\n\n[Key_Base_Row_2_ID_12]\nType=String\nLabel=-\nString=-\n\n[Key_Base_Row_2_ID_13]\nType=VirtualKey\nWidth=200\nLabel=⟵\nKeyCode=8\n\n[Key_Base_Row_2_ID_14]\nType=Blank\nWidth=25\nCluster=Navigation\n\n[Key_Base_Row_2_ID_15]\nType=VirtualKey\nLabel=Insert\nCluster=Navigation\nKeyCode=45\n\n[Key_Base_Row_2_ID_16]\nType=VirtualKey\nLabel=Home\nCluster=Navigation\nKeyCode=36\n\n[Key_Base_Row_2_ID_17]\nType=VirtualKey\nLabel=PgUp\nCluster=Navigation\nKeyCode=33\n\n[Key_Base_Row_2_ID_18]\nType=Blank\nWidth=25\nCluster=Numpad\n\n[Key_Base_Row_2_ID_19]\nType=VirtualKey\nLabel=Num\\nLock\nCluster=Numpad\nKeyCode=144\nNoRepeat=true\n\n[Key_Base_Row_2_ID_20]\nType=VirtualKey\nLabel=/\nCluster=Numpad\nKeyCode=111\n\n[Key_Base_Row_2_ID_21]\nType=VirtualKey\nLabel=*\nCluster=Numpad\nKeyCode=106\n\n[Key_Base_Row_2_ID_22]\nType=VirtualKey\nLabel=-\nCluster=Numpad\nKeyCode=109\n\n[Key_Base_Row_3_ID_0]\nType=VirtualKey\nWidth=150\nLabel=⭾\nKeyCode=9\n\n[Key_Base_Row_3_ID_1]\nType=VirtualKey\nLabel=a\nKeyCode=65\n\n[Key_Base_Row_3_ID_2]\nType=VirtualKey\nLabel=z\nKeyCode=90\n\n[Key_Base_Row_3_ID_3]\nType=VirtualKey\nLabel=e\nKeyCode=69\n\n[Key_Base_Row_3_ID_4]\nType=VirtualKey\nLabel=r\nKeyCode=82\n\n[Key_Base_Row_3_ID_5]\nType=VirtualKey\nLabel=t\nKeyCode=84\n\n[Key_Base_Row_3_ID_6]\nType=VirtualKey\nLabel=y\nKeyCode=89\n\n[Key_Base_Row_3_ID_7]\nType=VirtualKey\nLabel=u\nKeyCode=85\n\n[Key_Base_Row_3_ID_8]\nType=VirtualKey\nLabel=i\nKeyCode=73\n\n[Key_Base_Row_3_ID_9]\nType=VirtualKey\nLabel=o\nKeyCode=79\n\n[Key_Base_Row_3_ID_10]\nType=VirtualKey\nLabel=p\nKeyCode=80\n\n[Key_Base_Row_3_ID_11]\nType=String\nLabel=^\nString=^\n\n[Key_Base_Row_3_ID_12]\nType=String\nLabel=$\nString=$\n\n[Key_Base_Row_3_ID_13]\nType=VirtualKeyIsoEnter\nWidth=150\nLabel=\nKeyCode=13\n\n[Key_Base_Row_3_ID_14]\nType=Blank\nWidth=25\nCluster=Navigation\n\n[Key_Base_Row_3_ID_15]\nType=VirtualKey\nLabel=Delete\nCluster=Navigation\nKeyCode=46\n\n[Key_Base_Row_3_ID_16]\nType=VirtualKey\nLabel=End\nCluster=Navigation\nKeyCode=35\n\n[Key_Base_Row_3_ID_17]\nType=VirtualKey\nLabel=PgDn\nCluster=Navigation\nKeyCode=34\n\n[Key_Base_Row_3_ID_18]\nType=Blank\nWidth=25\nCluster=Numpad\n\n[Key_Base_Row_3_ID_19]\nType=VirtualKey\nLabel=7\nCluster=Numpad\nKeyCode=103\n\n[Key_Base_Row_3_ID_20]\nType=VirtualKey\nLabel=8\nCluster=Numpad\nKeyCode=104\n\n[Key_Base_Row_3_ID_21]\nType=VirtualKey\nLabel=9\nCluster=Numpad\nKeyCode=105\n\n[Key_Base_Row_3_ID_22]\nType=VirtualKey\nHeight=200\nLabel=+\nCluster=Numpad\nKeyCode=107\n\n[Key_Base_Row_4_ID_0]\nType=VirtualKey\nWidth=175\nLabel=🡇\nKeyCode=20\nNoRepeat=true\n\n[Key_Base_Row_4_ID_1]\nType=VirtualKey\nLabel=q\nKeyCode=81\n\n[Key_Base_Row_4_ID_2]\nType=VirtualKey\nLabel=s\nKeyCode=83\n\n[Key_Base_Row_4_ID_3]\nType=VirtualKey\nLabel=d\nKeyCode=68\n\n[Key_Base_Row_4_ID_4]\nType=VirtualKey\nLabel=f\nKeyCode=70\n\n[Key_Base_Row_4_ID_5]\nType=VirtualKey\nLabel=g\nKeyCode=71\n\n[Key_Base_Row_4_ID_6]\nType=VirtualKey\nLabel=h\nKeyCode=72\n\n[Key_Base_Row_4_ID_7]\nType=VirtualKey\nLabel=j\nKeyCode=74\n\n[Key_Base_Row_4_ID_8]\nType=VirtualKey\nLabel=k\nKeyCode=75\n\n[Key_Base_Row_4_ID_9]\nType=VirtualKey\nLabel=l\nKeyCode=76\n\n[Key_Base_Row_4_ID_10]\nType=VirtualKey\nLabel=m\nKeyCode=77\n\n[Key_Base_Row_4_ID_11]\nType=String\nLabel=ù\nString=ù\n\n[Key_Base_Row_4_ID_12]\nType=String\nLabel=µ\nString=µ\n\n[Key_Base_Row_4_ID_13]\nType=VirtualKeyIsoEnter\nWidth=125\nLabel=↵\nKeyCode=13\n\n[Key_Base_Row_4_ID_14]\nType=Blank\nWidth=325\nCluster=Navigation\n\n[Key_Base_Row_4_ID_15]\nType=Blank\nWidth=25\nCluster=Numpad\n\n[Key_Base_Row_4_ID_16]\nType=VirtualKey\nLabel=4\nCluster=Numpad\nKeyCode=100\n\n[Key_Base_Row_4_ID_17]\nType=VirtualKey\nLabel=5\nCluster=Numpad\nKeyCode=101\n\n[Key_Base_Row_4_ID_18]\nType=VirtualKey\nLabel=6\nCluster=Numpad\nKeyCode=102\n\n[Key_Base_Row_5_ID_0]\nType=VirtualKeyToggle\nWidth=125\nLabel=🡅\nKeyCode=160\nNoRepeat=true\n\n[Key_Base_Row_5_ID_1]\nType=String\nLabel=<\nString=<\n\n[Key_Base_Row_5_ID_2]\nType=VirtualKey\nLabel=w\nKeyCode=87\n\n[Key_Base_Row_5_ID_3]\nType=VirtualKey\nLabel=x\nKeyCode=88\n\n[Key_Base_Row_5_ID_4]\nType=VirtualKey\nLabel=c\nKeyCode=67\n\n[Key_Base_Row_5_ID_5]\nType=VirtualKey\nLabel=v\nKeyCode=86\n\n[Key_Base_Row_5_ID_6]\nType=VirtualKey\nLabel=b\nKeyCode=66\n\n[Key_Base_Row_5_ID_7]\nType=VirtualKey\nLabel=n\nKeyCode=78\n\n[Key_Base_Row_5_ID_8]\nType=String\nLabel=,\nString=,\n\n[Key_Base_Row_5_ID_9]\nType=String\nLabel=;\nString=;\n\n[Key_Base_Row_5_ID_10]\nType=String\nLabel=:\nString=:\n\n[Key_Base_Row_5_ID_11]\nType=String\nLabel==\nString==\n\n[Key_Base_Row_5_ID_12]\nType=VirtualKeyToggle\nWidth=275\nLabel=🡅\nKeyCode=161\nNoRepeat=true\n\n[Key_Base_Row_5_ID_13]\nType=Blank\nWidth=125\nCluster=Navigation\n\n[Key_Base_Row_5_ID_14]\nType=VirtualKey\nLabel=🠅\nCluster=Navigation\nKeyCode=38\n\n[Key_Base_Row_5_ID_15]\nType=Blank\nCluster=Navigation\n\n[Key_Base_Row_5_ID_16]\nType=Blank\nWidth=25\nCluster=Numpad\n\n[Key_Base_Row_5_ID_17]\nType=VirtualKey\nLabel=1\nCluster=Numpad\nKeyCode=97\n\n[Key_Base_Row_5_ID_18]\nType=VirtualKey\nLabel=2\nCluster=Numpad\nKeyCode=98\n\n[Key_Base_Row_5_ID_19]\nType=VirtualKey\nLabel=3\nCluster=Numpad\nKeyCode=99\n\n[Key_Base_Row_5_ID_20]\nType=VirtualKey\nHeight=200\nLabel=Enter\nCluster=Numpad\nKeyCode=13\n\n[Key_Base_Row_6_ID_0]\nType=VirtualKeyToggle\nWidth=125\nLabel=Ctrl\nKeyCode=162\nNoRepeat=true\n\n[Key_Base_Row_6_ID_1]\nType=VirtualKeyToggle\nWidth=125\nLabel=Win\nKeyCode=91\nNoRepeat=true\n\n[Key_Base_Row_6_ID_2]\nType=VirtualKeyToggle\nWidth=125\nLabel=Alt\nKeyCode=164\nNoRepeat=true\n\n[Key_Base_Row_6_ID_3]\nType=VirtualKey\nWidth=625\nLabel=\nKeyCode=32\n\n[Key_Base_Row_6_ID_4]\nType=VirtualKeyToggle\nWidth=125\nLabel=AltGr\nKeyCode=165\nNoRepeat=true\n\n[Key_Base_Row_6_ID_5]\nType=VirtualKeyToggle\nWidth=125\nLabel=Win\nKeyCode=92\nNoRepeat=true\n\n[Key_Base_Row_6_ID_6]\nType=VirtualKey\nWidth=125\nLabel=Menu\nKeyCode=93\nNoRepeat=true\n\n[Key_Base_Row_6_ID_7]\nType=VirtualKeyToggle\nWidth=125\nLabel=Ctrl\nKeyCode=163\nNoRepeat=true\n\n[Key_Base_Row_6_ID_8]\nType=Blank\nWidth=25\nCluster=Navigation\n\n[Key_Base_Row_6_ID_9]\nType=VirtualKey\nLabel=🠄\nCluster=Navigation\nKeyCode=37\n\n[Key_Base_Row_6_ID_10]\nType=VirtualKey\nLabel=🠇\nCluster=Navigation\nKeyCode=40\n\n[Key_Base_Row_6_ID_11]\nType=VirtualKey\nLabel=🠆\nCluster=Navigation\nKeyCode=39\n\n[Key_Base_Row_6_ID_12]\nType=Blank\nWidth=25\nCluster=Numpad\n\n[Key_Base_Row_6_ID_13]\nType=VirtualKey\nWidth=200\nLabel=0\nCluster=Numpad\nKeyCode=96\n\n[Key_Base_Row_6_ID_14]\nType=VirtualKey\nLabel=.\nCluster=Numpad\nKeyCode=110\n\n[Key_Shift_Row_0_ID_0]\nType=VirtualKey\nLabel=Esc\nCluster=Function\nKeyCode=27\nNoRepeat=true\n\n[Key_Shift_Row_0_ID_1]\nType=Blank\nCluster=Function\n\n[Key_Shift_Row_0_ID_2]\nType=VirtualKey\nLabel=F1\nCluster=Function\nKeyCode=112\n\n[Key_Shift_Row_0_ID_3]\nType=VirtualKey\nLabel=F2\nCluster=Function\nKeyCode=113\n\n[Key_Shift_Row_0_ID_4]\nType=VirtualKey\nLabel=F3\nCluster=Function\nKeyCode=114\n\n[Key_Shift_Row_0_ID_5]\nType=VirtualKey\nLabel=F4\nCluster=Function\nKeyCode=115\n\n[Key_Shift_Row_0_ID_6]\nType=Blank\nWidth=50\nCluster=Function\n\n[Key_Shift_Row_0_ID_7]\nType=VirtualKey\nLabel=F5\nCluster=Function\nKeyCode=116\n\n[Key_Shift_Row_0_ID_8]\nType=VirtualKey\nLabel=F6\nCluster=Function\nKeyCode=117\n\n[Key_Shift_Row_0_ID_9]\nType=VirtualKey\nLabel=F7\nCluster=Function\nKeyCode=118\n\n[Key_Shift_Row_0_ID_10]\nType=VirtualKey\nLabel=F8\nCluster=Function\nKeyCode=119\n\n[Key_Shift_Row_0_ID_11]\nType=Blank\nWidth=50\nCluster=Function\n\n[Key_Shift_Row_0_ID_12]\nType=VirtualKey\nLabel=F9\nCluster=Function\nKeyCode=120\n\n[Key_Shift_Row_0_ID_13]\nType=VirtualKey\nLabel=F10\nCluster=Function\nKeyCode=121\n\n[Key_Shift_Row_0_ID_14]\nType=VirtualKey\nLabel=F11\nCluster=Function\nKeyCode=122\n\n[Key_Shift_Row_0_ID_15]\nType=VirtualKey\nLabel=F12\nCluster=Function\nKeyCode=123\n\n[Key_Shift_Row_0_ID_16]\nType=Blank\nWidth=25\nCluster=Function\n\n[Key_Shift_Row_0_ID_17]\nType=VirtualKey\nLabel=Print\\nScreen\nCluster=Function\nKeyCode=44\nNoRepeat=true\n\n[Key_Shift_Row_0_ID_18]\nType=VirtualKey\nLabel=Scroll\\nLock\nCluster=Function\nKeyCode=145\nNoRepeat=true\n\n[Key_Shift_Row_0_ID_19]\nType=VirtualKey\nLabel=Pause\nCluster=Function\nKeyCode=19\nNoRepeat=true\n\n[Key_Shift_Row_0_ID_20]\nType=Blank\nWidth=25\nCluster=Extra\n\n[Key_Shift_Row_0_ID_21]\nType=VirtualKey\nLabel=🡰\nCluster=Extra\nKeyCode=166\nNoRepeat=true\n\n[Key_Shift_Row_0_ID_22]\nType=VirtualKey\nLabel=🡲\nCluster=Extra\nKeyCode=167\nNoRepeat=true\n\n[Key_Shift_Row_0_ID_23]\nType=VirtualKey\nLabel=🔇\nCluster=Extra\nKeyCode=173\nNoRepeat=true\n\n[Key_Shift_Row_1_ID_0]\nType=Blank\nHeight=25\nCluster=Function\n\n[Key_Shift_Row_1_ID_1]\nType=Blank\nHeight=25\nCluster=Extra\n\n[Key_Shift_Row_2_ID_0]\nType=String\nLabel=³\nString=³\nNoRepeat=true\n\n[Key_Shift_Row_2_ID_1]\nType=VirtualKey\nLabel=1\nKeyCode=49\n\n[Key_Shift_Row_2_ID_2]\nType=VirtualKey\nLabel=2\nKeyCode=50\n\n[Key_Shift_Row_2_ID_3]\nType=VirtualKey\nLabel=3\nKeyCode=51\n\n[Key_Shift_Row_2_ID_4]\nType=VirtualKey\nLabel=4\nKeyCode=52\n\n[Key_Shift_Row_2_ID_5]\nType=VirtualKey\nLabel=5\nKeyCode=53\n\n[Key_Shift_Row_2_ID_6]\nType=VirtualKey\nLabel=6\nKeyCode=54\n\n[Key_Shift_Row_2_ID_7]\nType=VirtualKey\nLabel=7\nKeyCode=55\n\n[Key_Shift_Row_2_ID_8]\nType=VirtualKey\nLabel=8\nKeyCode=56\n\n[Key_Shift_Row_2_ID_9]\nType=VirtualKey\nLabel=9\nKeyCode=57\n\n[Key_Shift_Row_2_ID_10]\nType=VirtualKey\nLabel=0\nKeyCode=48\n\n[Key_Shift_Row_2_ID_11]\nType=String\nLabel=°\nString=°\n\n[Key_Shift_Row_2_ID_12]\nType=String\nLabel=_\nString=_\n\n[Key_Shift_Row_2_ID_13]\nType=VirtualKey\nWidth=200\nLabel=⟵\nKeyCode=8\n\n[Key_Shift_Row_2_ID_14]\nType=Blank\nWidth=25\nCluster=Navigation\n\n[Key_Shift_Row_2_ID_15]\nType=VirtualKey\nLabel=Insert\nCluster=Navigation\nKeyCode=45\n\n[Key_Shift_Row_2_ID_16]\nType=VirtualKey\nLabel=Home\nCluster=Navigation\nKeyCode=36\n\n[Key_Shift_Row_2_ID_17]\nType=VirtualKey\nLabel=PgUp\nCluster=Navigation\nKeyCode=33\n\n[Key_Shift_Row_2_ID_18]\nType=Blank\nWidth=25\nCluster=Numpad\n\n[Key_Shift_Row_2_ID_19]\nType=VirtualKey\nLabel=Num\\nLock\nCluster=Numpad\nKeyCode=144\nNoRepeat=true\n\n[Key_Shift_Row_2_ID_20]\nType=VirtualKey\nLabel=/\nCluster=Numpad\nKeyCode=111\n\n[Key_Shift_Row_2_ID_21]\nType=VirtualKey\nLabel=*\nCluster=Numpad\nKeyCode=106\n\n[Key_Shift_Row_2_ID_22]\nType=VirtualKey\nLabel=-\nCluster=Numpad\nKeyCode=109\n\n[Key_Shift_Row_3_ID_0]\nType=VirtualKey\nWidth=150\nLabel=⭾\nKeyCode=9\n\n[Key_Shift_Row_3_ID_1]\nType=VirtualKey\nLabel=A\nKeyCode=65\n\n[Key_Shift_Row_3_ID_2]\nType=VirtualKey\nLabel=Z\nKeyCode=90\n\n[Key_Shift_Row_3_ID_3]\nType=VirtualKey\nLabel=E\nKeyCode=69\n\n[Key_Shift_Row_3_ID_4]\nType=VirtualKey\nLabel=R\nKeyCode=82\n\n[Key_Shift_Row_3_ID_5]\nType=VirtualKey\nLabel=T\nKeyCode=84\n\n[Key_Shift_Row_3_ID_6]\nType=VirtualKey\nLabel=Y\nKeyCode=89\n\n[Key_Shift_Row_3_ID_7]\nType=VirtualKey\nLabel=U\nKeyCode=85\n\n[Key_Shift_Row_3_ID_8]\nType=VirtualKey\nLabel=I\nKeyCode=73\n\n[Key_Shift_Row_3_ID_9]\nType=VirtualKey\nLabel=O\nKeyCode=79\n\n[Key_Shift_Row_3_ID_10]\nType=VirtualKey\nLabel=P\nKeyCode=80\n\n[Key_Shift_Row_3_ID_11]\nType=String\nLabel=¨\nString=¨\n\n[Key_Shift_Row_3_ID_12]\nType=String\nLabel=*\nString=*\n\n[Key_Shift_Row_3_ID_13]\nType=VirtualKeyIsoEnter\nWidth=150\nLabel=\nKeyCode=13\n\n[Key_Shift_Row_3_ID_14]\nType=Blank\nWidth=25\nCluster=Navigation\n\n[Key_Shift_Row_3_ID_15]\nType=VirtualKey\nLabel=Delete\nCluster=Navigation\nKeyCode=46\n\n[Key_Shift_Row_3_ID_16]\nType=VirtualKey\nLabel=End\nCluster=Navigation\nKeyCode=35\n\n[Key_Shift_Row_3_ID_17]\nType=VirtualKey\nLabel=PgDn\nCluster=Navigation\nKeyCode=34\n\n[Key_Shift_Row_3_ID_18]\nType=Blank\nWidth=25\nCluster=Numpad\n\n[Key_Shift_Row_3_ID_19]\nType=VirtualKey\nLabel=7\nCluster=Numpad\nKeyCode=103\n\n[Key_Shift_Row_3_ID_20]\nType=VirtualKey\nLabel=8\nCluster=Numpad\nKeyCode=104\n\n[Key_Shift_Row_3_ID_21]\nType=VirtualKey\nLabel=9\nCluster=Numpad\nKeyCode=105\n\n[Key_Shift_Row_3_ID_22]\nType=VirtualKey\nHeight=200\nLabel=+\nCluster=Numpad\nKeyCode=107\n\n[Key_Shift_Row_4_ID_0]\nType=VirtualKey\nWidth=175\nLabel=🡇\nKeyCode=20\nNoRepeat=true\n\n[Key_Shift_Row_4_ID_1]\nType=VirtualKey\nLabel=Q\nKeyCode=81\n\n[Key_Shift_Row_4_ID_2]\nType=VirtualKey\nLabel=S\nKeyCode=83\n\n[Key_Shift_Row_4_ID_3]\nType=VirtualKey\nLabel=D\nKeyCode=68\n\n[Key_Shift_Row_4_ID_4]\nType=VirtualKey\nLabel=F\nKeyCode=70\n\n[Key_Shift_Row_4_ID_5]\nType=VirtualKey\nLabel=G\nKeyCode=71\n\n[Key_Shift_Row_4_ID_6]\nType=VirtualKey\nLabel=H\nKeyCode=72\n\n[Key_Shift_Row_4_ID_7]\nType=VirtualKey\nLabel=J\nKeyCode=74\n\n[Key_Shift_Row_4_ID_8]\nType=VirtualKey\nLabel=K\nKeyCode=75\n\n[Key_Shift_Row_4_ID_9]\nType=VirtualKey\nLabel=L\nKeyCode=76\n\n[Key_Shift_Row_4_ID_10]\nType=VirtualKey\nLabel=M\nKeyCode=77\n\n[Key_Shift_Row_4_ID_11]\nType=String\nLabel=%\nString=%\n\n[Key_Shift_Row_4_ID_12]\nType=String\nLabel=£\nString=£\n\n[Key_Shift_Row_4_ID_13]\nType=VirtualKeyIsoEnter\nWidth=125\nLabel=↵\nKeyCode=13\n\n[Key_Shift_Row_4_ID_14]\nType=Blank\nWidth=325\nCluster=Navigation\n\n[Key_Shift_Row_4_ID_15]\nType=Blank\nWidth=25\nCluster=Numpad\n\n[Key_Shift_Row_4_ID_16]\nType=VirtualKey\nLabel=4\nCluster=Numpad\nKeyCode=100\n\n[Key_Shift_Row_4_ID_17]\nType=VirtualKey\nLabel=5\nCluster=Numpad\nKeyCode=101\n\n[Key_Shift_Row_4_ID_18]\nType=VirtualKey\nLabel=6\nCluster=Numpad\nKeyCode=102\n\n[Key_Shift_Row_5_ID_0]\nType=VirtualKeyToggle\nWidth=125\nLabel=🡅\nKeyCode=160\nNoRepeat=true\n\n[Key_Shift_Row_5_ID_1]\nType=String\nLabel=>\nString=>\n\n[Key_Shift_Row_5_ID_2]\nType=VirtualKey\nLabel=W\nKeyCode=87\n\n[Key_Shift_Row_5_ID_3]\nType=VirtualKey\nLabel=X\nKeyCode=88\n\n[Key_Shift_Row_5_ID_4]\nType=VirtualKey\nLabel=C\nKeyCode=67\n\n[Key_Shift_Row_5_ID_5]\nType=VirtualKey\nLabel=V\nKeyCode=86\n\n[Key_Shift_Row_5_ID_6]\nType=VirtualKey\nLabel=B\nKeyCode=66\n\n[Key_Shift_Row_5_ID_7]\nType=VirtualKey\nLabel=N\nKeyCode=78\n\n[Key_Shift_Row_5_ID_8]\nType=String\nLabel=?\nString=?\n\n[Key_Shift_Row_5_ID_9]\nType=String\nLabel=.\nString=.\n\n[Key_Shift_Row_5_ID_10]\nType=String\nLabel=/\nString=/\n\n[Key_Shift_Row_5_ID_11]\nType=String\nLabel=+\nString=+\n\n[Key_Shift_Row_5_ID_12]\nType=VirtualKeyToggle\nWidth=275\nLabel=🡅\nKeyCode=161\nNoRepeat=true\n\n[Key_Shift_Row_5_ID_13]\nType=Blank\nWidth=125\nCluster=Navigation\n\n[Key_Shift_Row_5_ID_14]\nType=VirtualKey\nLabel=🠅\nCluster=Navigation\nKeyCode=38\n\n[Key_Shift_Row_5_ID_15]\nType=Blank\nCluster=Navigation\n\n[Key_Shift_Row_5_ID_16]\nType=Blank\nWidth=25\nCluster=Numpad\n\n[Key_Shift_Row_5_ID_17]\nType=VirtualKey\nLabel=1\nCluster=Numpad\nKeyCode=97\n\n[Key_Shift_Row_5_ID_18]\nType=VirtualKey\nLabel=2\nCluster=Numpad\nKeyCode=98\n\n[Key_Shift_Row_5_ID_19]\nType=VirtualKey\nLabel=3\nCluster=Numpad\nKeyCode=99\n\n[Key_Shift_Row_5_ID_20]\nType=VirtualKey\nHeight=200\nLabel=Enter\nCluster=Numpad\nKeyCode=13\n\n[Key_Shift_Row_6_ID_0]\nType=VirtualKeyToggle\nWidth=125\nLabel=Ctrl\nKeyCode=162\nNoRepeat=true\n\n[Key_Shift_Row_6_ID_1]\nType=VirtualKeyToggle\nWidth=125\nLabel=Win\nKeyCode=91\nNoRepeat=true\n\n[Key_Shift_Row_6_ID_2]\nType=VirtualKeyToggle\nWidth=125\nLabel=Alt\nKeyCode=164\nNoRepeat=true\n\n[Key_Shift_Row_6_ID_3]\nType=VirtualKey\nWidth=625\nLabel=\nKeyCode=32\n\n[Key_Shift_Row_6_ID_4]\nType=VirtualKeyToggle\nWidth=125\nLabel=AltGr\nKeyCode=165\nNoRepeat=true\n\n[Key_Shift_Row_6_ID_5]\nType=VirtualKeyToggle\nWidth=125\nLabel=Win\nKeyCode=92\nNoRepeat=true\n\n[Key_Shift_Row_6_ID_6]\nType=VirtualKey\nWidth=125\nLabel=Menu\nKeyCode=93\nNoRepeat=true\n\n[Key_Shift_Row_6_ID_7]\nType=VirtualKeyToggle\nWidth=125\nLabel=Ctrl\nKeyCode=163\nNoRepeat=true\n\n[Key_Shift_Row_6_ID_8]\nType=Blank\nWidth=25\nCluster=Navigation\n\n[Key_Shift_Row_6_ID_9]\nType=VirtualKey\nLabel=🠄\nCluster=Navigation\nKeyCode=37\n\n[Key_Shift_Row_6_ID_10]\nType=VirtualKey\nLabel=🠇\nCluster=Navigation\nKeyCode=40\n\n[Key_Shift_Row_6_ID_11]\nType=VirtualKey\nLabel=🠆\nCluster=Navigation\nKeyCode=39\n\n[Key_Shift_Row_6_ID_12]\nType=Blank\nWidth=25\nCluster=Numpad\n\n[Key_Shift_Row_6_ID_13]\nType=VirtualKey\nWidth=200\nLabel=0\nCluster=Numpad\nKeyCode=96\n\n[Key_Shift_Row_6_ID_14]\nType=VirtualKey\nLabel=.\nCluster=Numpad\nKeyCode=110\n\n[Key_AltGr_Row_0_ID_0]\nType=VirtualKey\nLabel=Esc\nCluster=Function\nKeyCode=27\nNoRepeat=true\n\n[Key_AltGr_Row_0_ID_1]\nType=Blank\nCluster=Function\n\n[Key_AltGr_Row_0_ID_2]\nType=VirtualKey\nLabel=F1\nCluster=Function\nKeyCode=112\n\n[Key_AltGr_Row_0_ID_3]\nType=VirtualKey\nLabel=F2\nCluster=Function\nKeyCode=113\n\n[Key_AltGr_Row_0_ID_4]\nType=VirtualKey\nLabel=F3\nCluster=Function\nKeyCode=114\n\n[Key_AltGr_Row_0_ID_5]\nType=VirtualKey\nLabel=F4\nCluster=Function\nKeyCode=115\n\n[Key_AltGr_Row_0_ID_6]\nType=Blank\nWidth=50\nCluster=Function\n\n[Key_AltGr_Row_0_ID_7]\nType=VirtualKey\nLabel=F5\nCluster=Function\nKeyCode=116\n\n[Key_AltGr_Row_0_ID_8]\nType=VirtualKey\nLabel=F6\nCluster=Function\nKeyCode=117\n\n[Key_AltGr_Row_0_ID_9]\nType=VirtualKey\nLabel=F7\nCluster=Function\nKeyCode=118\n\n[Key_AltGr_Row_0_ID_10]\nType=VirtualKey\nLabel=F8\nCluster=Function\nKeyCode=119\n\n[Key_AltGr_Row_0_ID_11]\nType=Blank\nWidth=50\nCluster=Function\n\n[Key_AltGr_Row_0_ID_12]\nType=VirtualKey\nLabel=F9\nCluster=Function\nKeyCode=120\n\n[Key_AltGr_Row_0_ID_13]\nType=VirtualKey\nLabel=F10\nCluster=Function\nKeyCode=121\n\n[Key_AltGr_Row_0_ID_14]\nType=VirtualKey\nLabel=F11\nCluster=Function\nKeyCode=122\n\n[Key_AltGr_Row_0_ID_15]\nType=VirtualKey\nLabel=F12\nCluster=Function\nKeyCode=123\n\n[Key_AltGr_Row_0_ID_16]\nType=Blank\nWidth=25\nCluster=Function\n\n[Key_AltGr_Row_0_ID_17]\nType=VirtualKey\nLabel=Print\\nScreen\nCluster=Function\nKeyCode=44\nNoRepeat=true\n\n[Key_AltGr_Row_0_ID_18]\nType=VirtualKey\nLabel=Scroll\\nLock\nCluster=Function\nKeyCode=145\nNoRepeat=true\n\n[Key_AltGr_Row_0_ID_19]\nType=VirtualKey\nLabel=Pause\nCluster=Function\nKeyCode=19\nNoRepeat=true\n\n[Key_AltGr_Row_0_ID_20]\nType=Blank\nWidth=25\nCluster=Extra\n\n[Key_AltGr_Row_0_ID_21]\nType=VirtualKey\nLabel=⏯\nCluster=Extra\nKeyCode=179\nNoRepeat=true\n\n[Key_AltGr_Row_0_ID_22]\nType=VirtualKey\nLabel=◼\nCluster=Extra\nKeyCode=178\nNoRepeat=true\n\n[Key_AltGr_Row_0_ID_23]\nType=VirtualKey\nLabel=⏮\nCluster=Extra\nKeyCode=177\nNoRepeat=true\n\n[Key_AltGr_Row_0_ID_24]\nType=VirtualKey\nLabel=⏭\nCluster=Extra\nKeyCode=176\nNoRepeat=true\n\n[Key_AltGr_Row_1_ID_0]\nType=Blank\nHeight=25\nCluster=Function\n\n[Key_AltGr_Row_1_ID_1]\nType=Blank\nHeight=25\nCluster=Extra\n\n[Key_AltGr_Row_2_ID_0]\nType=Blank\n\n[Key_AltGr_Row_2_ID_1]\nType=String\nLabel=|\nString=|\n\n[Key_AltGr_Row_2_ID_2]\nType=String\nLabel=@\nString=@\n\n[Key_AltGr_Row_2_ID_3]\nType=String\nLabel=#\nString=#\n\n[Key_AltGr_Row_2_ID_4]\nType=Blank\nWidth=200\n\n[Key_AltGr_Row_2_ID_5]\nType=String\nLabel=^\nString=^\n\n[Key_AltGr_Row_2_ID_6]\nType=Blank\nWidth=200\n\n[Key_AltGr_Row_2_ID_7]\nType=String\nLabel={\nString={\n\n[Key_AltGr_Row_2_ID_8]\nType=String\nLabel=}\nString=}\n\n[Key_AltGr_Row_2_ID_9]\nType=Blank\nWidth=200\n\n[Key_AltGr_Row_2_ID_10]\nType=VirtualKey\nWidth=200\nLabel=⟵\nKeyCode=8\n\n[Key_AltGr_Row_2_ID_11]\nType=Blank\nWidth=25\nCluster=Navigation\n\n[Key_AltGr_Row_2_ID_12]\nType=VirtualKey\nLabel=Insert\nCluster=Navigation\nKeyCode=45\n\n[Key_AltGr_Row_2_ID_13]\nType=VirtualKey\nLabel=Home\nCluster=Navigation\nKeyCode=36\n\n[Key_AltGr_Row_2_ID_14]\nType=VirtualKey\nLabel=PgUp\nCluster=Navigation\nKeyCode=33\n\n[Key_AltGr_Row_2_ID_15]\nType=Blank\nWidth=25\nCluster=Numpad\n\n[Key_AltGr_Row_2_ID_16]\nType=VirtualKey\nLabel=Num\\nLock\nCluster=Numpad\nKeyCode=144\nNoRepeat=true\n\n[Key_AltGr_Row_2_ID_17]\nType=VirtualKey\nLabel=/\nCluster=Numpad\nKeyCode=111\n\n[Key_AltGr_Row_2_ID_18]\nType=VirtualKey\nLabel=*\nCluster=Numpad\nKeyCode=106\n\n[Key_AltGr_Row_2_ID_19]\nType=VirtualKey\nLabel=-\nCluster=Numpad\nKeyCode=109\n\n[Key_AltGr_Row_3_ID_0]\nType=VirtualKey\nWidth=150\nLabel=⭾\nKeyCode=9\n\n[Key_AltGr_Row_3_ID_1]\nType=Blank\nWidth=200\n\n[Key_AltGr_Row_3_ID_2]\nType=String\nLabel=€\nString=€\n\n[Key_AltGr_Row_3_ID_3]\nType=Blank\nWidth=700\n\n[Key_AltGr_Row_3_ID_4]\nType=String\nLabel=[\nString=[\n\n[Key_AltGr_Row_3_ID_5]\nType=String\nLabel=]\nString=]\n\n[Key_AltGr_Row_3_ID_6]\nType=VirtualKeyIsoEnter\nWidth=150\nLabel=\nKeyCode=13\n\n[Key_AltGr_Row_3_ID_7]\nType=Blank\nWidth=25\nCluster=Navigation\n\n[Key_AltGr_Row_3_ID_8]\nType=VirtualKey\nLabel=Delete\nCluster=Navigation\nKeyCode=46\n\n[Key_AltGr_Row_3_ID_9]\nType=VirtualKey\nLabel=End\nCluster=Navigation\nKeyCode=35\n\n[Key_AltGr_Row_3_ID_10]\nType=VirtualKey\nLabel=PgDn\nCluster=Navigation\nKeyCode=34\n\n[Key_AltGr_Row_3_ID_11]\nType=Blank\nWidth=25\nCluster=Numpad\n\n[Key_AltGr_Row_3_ID_12]\nType=VirtualKey\nLabel=7\nCluster=Numpad\nKeyCode=103\n\n[Key_AltGr_Row_3_ID_13]\nType=VirtualKey\nLabel=8\nCluster=Numpad\nKeyCode=104\n\n[Key_AltGr_Row_3_ID_14]\nType=VirtualKey\nLabel=9\nCluster=Numpad\nKeyCode=105\n\n[Key_AltGr_Row_3_ID_15]\nType=VirtualKey\nHeight=200\nLabel=+\nCluster=Numpad\nKeyCode=107\n\n[Key_AltGr_Row_4_ID_0]\nType=VirtualKey\nWidth=175\nLabel=🡇\nKeyCode=20\nNoRepeat=true\n\n[Key_AltGr_Row_4_ID_1]\nType=Blank\nWidth=1000\n\n[Key_AltGr_Row_4_ID_2]\nType=String\nLabel=´\nString=´\n\n[Key_AltGr_Row_4_ID_3]\nType=String\nLabel=`\nString=`\n\n[Key_AltGr_Row_4_ID_4]\nType=VirtualKeyIsoEnter\nWidth=125\nLabel=↵\nKeyCode=13\n\n[Key_AltGr_Row_4_ID_5]\nType=Blank\nWidth=325\nCluster=Navigation\n\n[Key_AltGr_Row_4_ID_6]\nType=Blank\nWidth=25\nCluster=Numpad\n\n[Key_AltGr_Row_4_ID_7]\nType=VirtualKey\nLabel=4\nCluster=Numpad\nKeyCode=100\n\n[Key_AltGr_Row_4_ID_8]\nType=VirtualKey\nLabel=5\nCluster=Numpad\nKeyCode=101\n\n[Key_AltGr_Row_4_ID_9]\nType=VirtualKey\nLabel=6\nCluster=Numpad\nKeyCode=102\n\n[Key_AltGr_Row_5_ID_0]\nType=VirtualKeyToggle\nWidth=125\nLabel=🡅\nKeyCode=160\nNoRepeat=true\n\n[Key_AltGr_Row_5_ID_1]\nType=String\nLabel=\\\nString=\\\n\n[Key_AltGr_Row_5_ID_2]\nType=Blank\nWidth=900\n\n[Key_AltGr_Row_5_ID_3]\nType=String\nLabel=~\nString=~\n\n[Key_AltGr_Row_5_ID_4]\nType=VirtualKeyToggle\nWidth=275\nLabel=🡅\nKeyCode=161\nNoRepeat=true\n\n[Key_AltGr_Row_5_ID_5]\nType=Blank\nWidth=125\nCluster=Navigation\n\n[Key_AltGr_Row_5_ID_6]\nType=VirtualKey\nLabel=🠅\nCluster=Navigation\nKeyCode=38\n\n[Key_AltGr_Row_5_ID_7]\nType=Blank\nCluster=Navigation\n\n[Key_AltGr_Row_5_ID_8]\nType=Blank\nWidth=25\nCluster=Numpad\n\n[Key_AltGr_Row_5_ID_9]\nType=VirtualKey\nLabel=1\nCluster=Numpad\nKeyCode=97\n\n[Key_AltGr_Row_5_ID_10]\nType=VirtualKey\nLabel=2\nCluster=Numpad\nKeyCode=98\n\n[Key_AltGr_Row_5_ID_11]\nType=VirtualKey\nLabel=3\nCluster=Numpad\nKeyCode=99\n\n[Key_AltGr_Row_5_ID_12]\nType=VirtualKey\nHeight=200\nLabel=Enter\nCluster=Numpad\nKeyCode=13\n\n[Key_AltGr_Row_6_ID_0]\nType=VirtualKeyToggle\nWidth=125\nLabel=Ctrl\nKeyCode=162\nNoRepeat=true\n\n[Key_AltGr_Row_6_ID_1]\nType=VirtualKeyToggle\nWidth=125\nLabel=Win\nKeyCode=91\nNoRepeat=true\n\n[Key_AltGr_Row_6_ID_2]\nType=VirtualKeyToggle\nWidth=125\nLabel=Alt\nKeyCode=164\nNoRepeat=true\n\n[Key_AltGr_Row_6_ID_3]\nType=VirtualKey\nWidth=625\nLabel=\nKeyCode=32\n\n[Key_AltGr_Row_6_ID_4]\nType=VirtualKeyToggle\nWidth=125\nLabel=AltGr\nKeyCode=165\nNoRepeat=true\n\n[Key_AltGr_Row_6_ID_5]\nType=VirtualKeyToggle\nWidth=125\nLabel=Win\nKeyCode=92\nNoRepeat=true\n\n[Key_AltGr_Row_6_ID_6]\nType=VirtualKey\nWidth=125\nLabel=Menu\nKeyCode=93\nNoRepeat=true\n\n[Key_AltGr_Row_6_ID_7]\nType=VirtualKeyToggle\nWidth=125\nLabel=Ctrl\nKeyCode=163\nNoRepeat=true\n\n[Key_AltGr_Row_6_ID_8]\nType=Blank\nWidth=25\nCluster=Navigation\n\n[Key_AltGr_Row_6_ID_9]\nType=VirtualKey\nLabel=🠄\nCluster=Navigation\nKeyCode=37\n\n[Key_AltGr_Row_6_ID_10]\nType=VirtualKey\nLabel=🠇\nCluster=Navigation\nKeyCode=40\n\n[Key_AltGr_Row_6_ID_11]\nType=VirtualKey\nLabel=🠆\nCluster=Navigation\nKeyCode=39\n\n[Key_AltGr_Row_6_ID_12]\nType=Blank\nWidth=25\nCluster=Numpad\n\n[Key_AltGr_Row_6_ID_13]\nType=VirtualKey\nWidth=200\nLabel=0\nCluster=Numpad\nKeyCode=96\n\n[Key_AltGr_Row_6_ID_14]\nType=VirtualKey\nLabel=.\nCluster=Numpad\nKeyCode=110\n\n"
  },
  {
    "path": "assets/keyboards/azerty_fr.ini",
    "content": "[LayoutInfo]\nName=AZERTY (France)\nAuthor=\nHasAltGr=true\nHasClusterFunction=true\nHasClusterNavigation=true\nHasClusterNumpad=true\nHasClusterExtra=true\n\n[Key_Base_Row_0_ID_0]\nType=VirtualKey\nLabel=Échap\nCluster=Function\nKeyCode=27\nNoRepeat=true\n\n[Key_Base_Row_0_ID_1]\nType=Blank\nCluster=Function\n\n[Key_Base_Row_0_ID_2]\nType=VirtualKey\nLabel=F1\nCluster=Function\nKeyCode=112\n\n[Key_Base_Row_0_ID_3]\nType=VirtualKey\nLabel=F2\nCluster=Function\nKeyCode=113\n\n[Key_Base_Row_0_ID_4]\nType=VirtualKey\nLabel=F3\nCluster=Function\nKeyCode=114\n\n[Key_Base_Row_0_ID_5]\nType=VirtualKey\nLabel=F4\nCluster=Function\nKeyCode=115\n\n[Key_Base_Row_0_ID_6]\nType=Blank\nWidth=50\nCluster=Function\n\n[Key_Base_Row_0_ID_7]\nType=VirtualKey\nLabel=F5\nCluster=Function\nKeyCode=116\n\n[Key_Base_Row_0_ID_8]\nType=VirtualKey\nLabel=F6\nCluster=Function\nKeyCode=117\n\n[Key_Base_Row_0_ID_9]\nType=VirtualKey\nLabel=F7\nCluster=Function\nKeyCode=118\n\n[Key_Base_Row_0_ID_10]\nType=VirtualKey\nLabel=F8\nCluster=Function\nKeyCode=119\n\n[Key_Base_Row_0_ID_11]\nType=Blank\nWidth=50\nCluster=Function\n\n[Key_Base_Row_0_ID_12]\nType=VirtualKey\nLabel=F9\nCluster=Function\nKeyCode=120\n\n[Key_Base_Row_0_ID_13]\nType=VirtualKey\nLabel=F10\nCluster=Function\nKeyCode=121\n\n[Key_Base_Row_0_ID_14]\nType=VirtualKey\nLabel=F11\nCluster=Function\nKeyCode=122\n\n[Key_Base_Row_0_ID_15]\nType=VirtualKey\nLabel=F12\nCluster=Function\nKeyCode=123\n\n[Key_Base_Row_0_ID_16]\nType=Blank\nWidth=25\nCluster=Function\n\n[Key_Base_Row_0_ID_17]\nType=VirtualKey\nLabel=Impr\nCluster=Function\nKeyCode=44\nNoRepeat=true\n\n[Key_Base_Row_0_ID_18]\nType=VirtualKey\nLabel=Défil\nCluster=Function\nKeyCode=145\nNoRepeat=true\n\n[Key_Base_Row_0_ID_19]\nType=VirtualKey\nLabel=Pause\nCluster=Function\nKeyCode=19\nNoRepeat=true\n\n[Key_Base_Row_0_ID_20]\nType=Blank\nWidth=25\nCluster=Extra\n\n[Key_Base_Row_0_ID_21]\nType=VirtualKey\nLabel=⏯\nCluster=Extra\nKeyCode=179\nNoRepeat=true\n\n[Key_Base_Row_0_ID_22]\nType=VirtualKey\nLabel=◼\nCluster=Extra\nKeyCode=178\nNoRepeat=true\n\n[Key_Base_Row_0_ID_23]\nType=VirtualKey\nLabel=⏮\nCluster=Extra\nKeyCode=177\nNoRepeat=true\n\n[Key_Base_Row_0_ID_24]\nType=VirtualKey\nLabel=⏭\nCluster=Extra\nKeyCode=176\nNoRepeat=true\n\n[Key_Base_Row_1_ID_0]\nType=Blank\nHeight=25\nCluster=Function\n\n[Key_Base_Row_1_ID_1]\nType=Blank\nHeight=25\nCluster=Extra\n\n[Key_Base_Row_2_ID_0]\nType=String\nLabel=²\nString=²\n\n[Key_Base_Row_2_ID_1]\nType=String\nLabel=&\nString=&\n\n[Key_Base_Row_2_ID_2]\nType=String\nLabel=é\nString=é\n\n[Key_Base_Row_2_ID_3]\nType=String\nLabel=\"\nString=\"\n\n[Key_Base_Row_2_ID_4]\nType=String\nLabel='\nString='\n\n[Key_Base_Row_2_ID_5]\nType=String\nLabel=(\nString=(\n\n[Key_Base_Row_2_ID_6]\nType=String\nLabel=-\nString=-\n\n[Key_Base_Row_2_ID_7]\nType=String\nLabel=è\nString=è\n\n[Key_Base_Row_2_ID_8]\nType=String\nLabel=_\nString=_\n\n[Key_Base_Row_2_ID_9]\nType=String\nLabel=ç\nString=ç\n\n[Key_Base_Row_2_ID_10]\nType=String\nLabel=à\nString=à\n\n[Key_Base_Row_2_ID_11]\nType=String\nLabel=)\nString=)\n\n[Key_Base_Row_2_ID_12]\nType=String\nLabel==\nString==\n\n[Key_Base_Row_2_ID_13]\nType=VirtualKey\nWidth=200\nLabel=⟵\nKeyCode=8\n\n[Key_Base_Row_2_ID_14]\nType=Blank\nWidth=25\nCluster=Navigation\n\n[Key_Base_Row_2_ID_15]\nType=VirtualKey\nLabel=Inser\nCluster=Navigation\nKeyCode=45\n\n[Key_Base_Row_2_ID_16]\nType=VirtualKey\nLabel=Début\nCluster=Navigation\nKeyCode=36\n\n[Key_Base_Row_2_ID_17]\nType=VirtualKey\nLabel=Page🠹\nCluster=Navigation\nKeyCode=33\n\n[Key_Base_Row_2_ID_18]\nType=Blank\nWidth=25\nCluster=Numpad\n\n[Key_Base_Row_2_ID_19]\nType=VirtualKey\nLabel=Verr\\nNum\nCluster=Numpad\nKeyCode=144\nNoRepeat=true\n\n[Key_Base_Row_2_ID_20]\nType=VirtualKey\nLabel=/\nCluster=Numpad\nKeyCode=111\n\n[Key_Base_Row_2_ID_21]\nType=VirtualKey\nLabel=*\nCluster=Numpad\nKeyCode=106\n\n[Key_Base_Row_2_ID_22]\nType=VirtualKey\nLabel=-\nCluster=Numpad\nKeyCode=109\n\n[Key_Base_Row_3_ID_0]\nType=VirtualKey\nWidth=150\nLabel=⭾\nKeyCode=9\n\n[Key_Base_Row_3_ID_1]\nType=VirtualKey\nLabel=a\nKeyCode=65\n\n[Key_Base_Row_3_ID_2]\nType=VirtualKey\nLabel=z\nKeyCode=90\n\n[Key_Base_Row_3_ID_3]\nType=VirtualKey\nLabel=e\nKeyCode=69\n\n[Key_Base_Row_3_ID_4]\nType=VirtualKey\nLabel=r\nKeyCode=82\n\n[Key_Base_Row_3_ID_5]\nType=VirtualKey\nLabel=t\nKeyCode=84\n\n[Key_Base_Row_3_ID_6]\nType=VirtualKey\nLabel=y\nKeyCode=89\n\n[Key_Base_Row_3_ID_7]\nType=VirtualKey\nLabel=u\nKeyCode=85\n\n[Key_Base_Row_3_ID_8]\nType=VirtualKey\nLabel=i\nKeyCode=73\n\n[Key_Base_Row_3_ID_9]\nType=VirtualKey\nLabel=o\nKeyCode=79\n\n[Key_Base_Row_3_ID_10]\nType=VirtualKey\nLabel=p\nKeyCode=80\n\n[Key_Base_Row_3_ID_11]\nType=String\nLabel=^\nString=^\n\n[Key_Base_Row_3_ID_12]\nType=String\nLabel=$\nString=$\n\n[Key_Base_Row_3_ID_13]\nType=VirtualKeyIsoEnter\nWidth=150\nLabel=\nKeyCode=13\n\n[Key_Base_Row_3_ID_14]\nType=Blank\nWidth=25\nCluster=Navigation\n\n[Key_Base_Row_3_ID_15]\nType=VirtualKey\nLabel=Suppr\nCluster=Navigation\nKeyCode=46\n\n[Key_Base_Row_3_ID_16]\nType=VirtualKey\nLabel=Fin\nCluster=Navigation\nKeyCode=35\n\n[Key_Base_Row_3_ID_17]\nType=VirtualKey\nLabel=Page🠻\nCluster=Navigation\nKeyCode=34\n\n[Key_Base_Row_3_ID_18]\nType=Blank\nWidth=25\nCluster=Numpad\n\n[Key_Base_Row_3_ID_19]\nType=VirtualKey\nLabel=7\nCluster=Numpad\nKeyCode=103\n\n[Key_Base_Row_3_ID_20]\nType=VirtualKey\nLabel=8\nCluster=Numpad\nKeyCode=104\n\n[Key_Base_Row_3_ID_21]\nType=VirtualKey\nLabel=9\nCluster=Numpad\nKeyCode=105\n\n[Key_Base_Row_3_ID_22]\nType=VirtualKey\nHeight=200\nLabel=+\nCluster=Numpad\nKeyCode=107\n\n[Key_Base_Row_4_ID_0]\nType=VirtualKey\nWidth=175\nLabel=🡇\nKeyCode=20\nNoRepeat=true\n\n[Key_Base_Row_4_ID_1]\nType=VirtualKey\nLabel=q\nKeyCode=81\n\n[Key_Base_Row_4_ID_2]\nType=VirtualKey\nLabel=s\nKeyCode=83\n\n[Key_Base_Row_4_ID_3]\nType=VirtualKey\nLabel=d\nKeyCode=68\n\n[Key_Base_Row_4_ID_4]\nType=VirtualKey\nLabel=f\nKeyCode=70\n\n[Key_Base_Row_4_ID_5]\nType=VirtualKey\nLabel=g\nKeyCode=71\n\n[Key_Base_Row_4_ID_6]\nType=VirtualKey\nLabel=h\nKeyCode=72\n\n[Key_Base_Row_4_ID_7]\nType=VirtualKey\nLabel=j\nKeyCode=74\n\n[Key_Base_Row_4_ID_8]\nType=VirtualKey\nLabel=k\nKeyCode=75\n\n[Key_Base_Row_4_ID_9]\nType=VirtualKey\nLabel=l\nKeyCode=76\n\n[Key_Base_Row_4_ID_10]\nType=VirtualKey\nLabel=m\nKeyCode=77\n\n[Key_Base_Row_4_ID_11]\nType=String\nLabel=ù\nString=ù\n\n[Key_Base_Row_4_ID_12]\nType=String\nLabel=*\nString=*\n\n[Key_Base_Row_4_ID_13]\nType=VirtualKeyIsoEnter\nWidth=125\nLabel=↵\nKeyCode=13\n\n[Key_Base_Row_4_ID_14]\nType=Blank\nWidth=325\nCluster=Navigation\n\n[Key_Base_Row_4_ID_15]\nType=Blank\nWidth=25\nCluster=Numpad\n\n[Key_Base_Row_4_ID_16]\nType=VirtualKey\nLabel=4\nCluster=Numpad\nKeyCode=100\n\n[Key_Base_Row_4_ID_17]\nType=VirtualKey\nLabel=5\nCluster=Numpad\nKeyCode=101\n\n[Key_Base_Row_4_ID_18]\nType=VirtualKey\nLabel=6\nCluster=Numpad\nKeyCode=102\n\n[Key_Base_Row_5_ID_0]\nType=VirtualKeyToggle\nWidth=125\nLabel=🡅\nKeyCode=160\nNoRepeat=true\n\n[Key_Base_Row_5_ID_1]\nType=String\nLabel=<\nString=<\n\n[Key_Base_Row_5_ID_2]\nType=VirtualKey\nLabel=w\nKeyCode=87\n\n[Key_Base_Row_5_ID_3]\nType=VirtualKey\nLabel=x\nKeyCode=88\n\n[Key_Base_Row_5_ID_4]\nType=VirtualKey\nLabel=c\nKeyCode=67\n\n[Key_Base_Row_5_ID_5]\nType=VirtualKey\nLabel=v\nKeyCode=86\n\n[Key_Base_Row_5_ID_6]\nType=VirtualKey\nLabel=b\nKeyCode=66\n\n[Key_Base_Row_5_ID_7]\nType=VirtualKey\nLabel=n\nKeyCode=78\n\n[Key_Base_Row_5_ID_8]\nType=String\nLabel=,\nString=,\n\n[Key_Base_Row_5_ID_9]\nType=String\nLabel=;\nString=;\n\n[Key_Base_Row_5_ID_10]\nType=String\nLabel=:\nString=:\n\n[Key_Base_Row_5_ID_11]\nType=String\nLabel=!\nString=!\n\n[Key_Base_Row_5_ID_12]\nType=VirtualKeyToggle\nWidth=275\nLabel=🡅\nKeyCode=161\nNoRepeat=true\n\n[Key_Base_Row_5_ID_13]\nType=Blank\nWidth=125\nCluster=Navigation\n\n[Key_Base_Row_5_ID_14]\nType=VirtualKey\nLabel=🠅\nCluster=Navigation\nKeyCode=38\n\n[Key_Base_Row_5_ID_15]\nType=Blank\nCluster=Navigation\n\n[Key_Base_Row_5_ID_16]\nType=Blank\nWidth=25\nCluster=Numpad\n\n[Key_Base_Row_5_ID_17]\nType=VirtualKey\nLabel=1\nCluster=Numpad\nKeyCode=97\n\n[Key_Base_Row_5_ID_18]\nType=VirtualKey\nLabel=2\nCluster=Numpad\nKeyCode=98\n\n[Key_Base_Row_5_ID_19]\nType=VirtualKey\nLabel=3\nCluster=Numpad\nKeyCode=99\n\n[Key_Base_Row_5_ID_20]\nType=VirtualKey\nHeight=200\nLabel=Entrée\nCluster=Numpad\nKeyCode=13\n\n[Key_Base_Row_6_ID_0]\nType=VirtualKeyToggle\nWidth=125\nLabel=Ctrl\nKeyCode=162\nNoRepeat=true\n\n[Key_Base_Row_6_ID_1]\nType=VirtualKeyToggle\nWidth=125\nLabel=Win\nKeyCode=91\nNoRepeat=true\n\n[Key_Base_Row_6_ID_2]\nType=VirtualKeyToggle\nWidth=125\nLabel=Alt\nKeyCode=164\nNoRepeat=true\n\n[Key_Base_Row_6_ID_3]\nType=VirtualKey\nWidth=625\nLabel=\nKeyCode=32\n\n[Key_Base_Row_6_ID_4]\nType=VirtualKeyToggle\nWidth=125\nLabel=AltGr\nKeyCode=165\nNoRepeat=true\n\n[Key_Base_Row_6_ID_5]\nType=VirtualKeyToggle\nWidth=125\nLabel=Win\nKeyCode=92\nNoRepeat=true\n\n[Key_Base_Row_6_ID_6]\nType=VirtualKey\nWidth=125\nLabel=Menu\nKeyCode=93\nNoRepeat=true\n\n[Key_Base_Row_6_ID_7]\nType=VirtualKeyToggle\nWidth=125\nLabel=Ctrl\nKeyCode=163\nNoRepeat=true\n\n[Key_Base_Row_6_ID_8]\nType=Blank\nWidth=25\nCluster=Navigation\n\n[Key_Base_Row_6_ID_9]\nType=VirtualKey\nLabel=🠄\nCluster=Navigation\nKeyCode=37\n\n[Key_Base_Row_6_ID_10]\nType=VirtualKey\nLabel=🠇\nCluster=Navigation\nKeyCode=40\n\n[Key_Base_Row_6_ID_11]\nType=VirtualKey\nLabel=🠆\nCluster=Navigation\nKeyCode=39\n\n[Key_Base_Row_6_ID_12]\nType=Blank\nWidth=25\nCluster=Numpad\n\n[Key_Base_Row_6_ID_13]\nType=VirtualKey\nWidth=200\nLabel=0\nCluster=Numpad\nKeyCode=96\n\n[Key_Base_Row_6_ID_14]\nType=VirtualKey\nLabel=.\nCluster=Numpad\nKeyCode=110\n\n[Key_Shift_Row_0_ID_0]\nType=VirtualKey\nLabel=Échap\nCluster=Function\nKeyCode=27\nNoRepeat=true\n\n[Key_Shift_Row_0_ID_1]\nType=Blank\nCluster=Function\n\n[Key_Shift_Row_0_ID_2]\nType=VirtualKey\nLabel=F1\nCluster=Function\nKeyCode=112\n\n[Key_Shift_Row_0_ID_3]\nType=VirtualKey\nLabel=F2\nCluster=Function\nKeyCode=113\n\n[Key_Shift_Row_0_ID_4]\nType=VirtualKey\nLabel=F3\nCluster=Function\nKeyCode=114\n\n[Key_Shift_Row_0_ID_5]\nType=VirtualKey\nLabel=F4\nCluster=Function\nKeyCode=115\n\n[Key_Shift_Row_0_ID_6]\nType=Blank\nWidth=50\nCluster=Function\n\n[Key_Shift_Row_0_ID_7]\nType=VirtualKey\nLabel=F5\nCluster=Function\nKeyCode=116\n\n[Key_Shift_Row_0_ID_8]\nType=VirtualKey\nLabel=F6\nCluster=Function\nKeyCode=117\n\n[Key_Shift_Row_0_ID_9]\nType=VirtualKey\nLabel=F7\nCluster=Function\nKeyCode=118\n\n[Key_Shift_Row_0_ID_10]\nType=VirtualKey\nLabel=F8\nCluster=Function\nKeyCode=119\n\n[Key_Shift_Row_0_ID_11]\nType=Blank\nWidth=50\nCluster=Function\n\n[Key_Shift_Row_0_ID_12]\nType=VirtualKey\nLabel=F9\nCluster=Function\nKeyCode=120\n\n[Key_Shift_Row_0_ID_13]\nType=VirtualKey\nLabel=F10\nCluster=Function\nKeyCode=121\n\n[Key_Shift_Row_0_ID_14]\nType=VirtualKey\nLabel=F11\nCluster=Function\nKeyCode=122\n\n[Key_Shift_Row_0_ID_15]\nType=VirtualKey\nLabel=F12\nCluster=Function\nKeyCode=123\n\n[Key_Shift_Row_0_ID_16]\nType=Blank\nWidth=25\nCluster=Function\n\n[Key_Shift_Row_0_ID_17]\nType=VirtualKey\nLabel=Impr\nCluster=Function\nKeyCode=44\nNoRepeat=true\n\n[Key_Shift_Row_0_ID_18]\nType=VirtualKey\nLabel=Défil\nCluster=Function\nKeyCode=145\nNoRepeat=true\n\n[Key_Shift_Row_0_ID_19]\nType=VirtualKey\nLabel=Pause\nCluster=Function\nKeyCode=19\nNoRepeat=true\n\n[Key_Shift_Row_0_ID_20]\nType=Blank\nWidth=25\nCluster=Extra\n\n[Key_Shift_Row_0_ID_21]\nType=VirtualKey\nLabel=🡰\nCluster=Extra\nKeyCode=166\nNoRepeat=true\n\n[Key_Shift_Row_0_ID_22]\nType=VirtualKey\nLabel=🡲\nCluster=Extra\nKeyCode=167\nNoRepeat=true\n\n[Key_Shift_Row_0_ID_23]\nType=VirtualKey\nLabel=🔇\nCluster=Extra\nKeyCode=173\nNoRepeat=true\n\n[Key_Shift_Row_1_ID_0]\nType=Blank\nHeight=25\nCluster=Function\n\n[Key_Shift_Row_1_ID_1]\nType=Blank\nHeight=25\nCluster=Extra\n\n[Key_Shift_Row_2_ID_0]\nType=String\nLabel=²\nString=²\n\n[Key_Shift_Row_2_ID_1]\nType=VirtualKey\nLabel=1\nKeyCode=49\n\n[Key_Shift_Row_2_ID_2]\nType=VirtualKey\nLabel=2\nKeyCode=50\n\n[Key_Shift_Row_2_ID_3]\nType=VirtualKey\nLabel=3\nKeyCode=51\n\n[Key_Shift_Row_2_ID_4]\nType=VirtualKey\nLabel=4\nKeyCode=52\n\n[Key_Shift_Row_2_ID_5]\nType=VirtualKey\nLabel=5\nKeyCode=53\n\n[Key_Shift_Row_2_ID_6]\nType=VirtualKey\nLabel=6\nKeyCode=54\n\n[Key_Shift_Row_2_ID_7]\nType=VirtualKey\nLabel=7\nKeyCode=55\n\n[Key_Shift_Row_2_ID_8]\nType=VirtualKey\nLabel=8\nKeyCode=56\n\n[Key_Shift_Row_2_ID_9]\nType=VirtualKey\nLabel=9\nKeyCode=57\n\n[Key_Shift_Row_2_ID_10]\nType=VirtualKey\nLabel=0\nKeyCode=48\n\n[Key_Shift_Row_2_ID_11]\nType=String\nLabel=°\nString=°\n\n[Key_Shift_Row_2_ID_12]\nType=String\nLabel=+\nString=+\n\n[Key_Shift_Row_2_ID_13]\nType=VirtualKey\nWidth=200\nLabel=⟵\nKeyCode=8\n\n[Key_Shift_Row_2_ID_14]\nType=Blank\nWidth=25\nCluster=Navigation\n\n[Key_Shift_Row_2_ID_15]\nType=VirtualKey\nLabel=Inser\nCluster=Navigation\nKeyCode=45\n\n[Key_Shift_Row_2_ID_16]\nType=VirtualKey\nLabel=Début\nCluster=Navigation\nKeyCode=36\n\n[Key_Shift_Row_2_ID_17]\nType=VirtualKey\nLabel=Page🠹\nCluster=Navigation\nKeyCode=33\n\n[Key_Shift_Row_2_ID_18]\nType=Blank\nWidth=25\nCluster=Numpad\n\n[Key_Shift_Row_2_ID_19]\nType=VirtualKey\nLabel=Verr\\nNum\nCluster=Numpad\nKeyCode=144\nNoRepeat=true\n\n[Key_Shift_Row_2_ID_20]\nType=VirtualKey\nLabel=/\nCluster=Numpad\nKeyCode=111\n\n[Key_Shift_Row_2_ID_21]\nType=VirtualKey\nLabel=*\nCluster=Numpad\nKeyCode=106\n\n[Key_Shift_Row_2_ID_22]\nType=VirtualKey\nLabel=-\nCluster=Numpad\nKeyCode=109\n\n[Key_Shift_Row_3_ID_0]\nType=VirtualKey\nWidth=150\nLabel=⭾\nKeyCode=9\n\n[Key_Shift_Row_3_ID_1]\nType=VirtualKey\nLabel=A\nKeyCode=65\n\n[Key_Shift_Row_3_ID_2]\nType=VirtualKey\nLabel=Z\nKeyCode=90\n\n[Key_Shift_Row_3_ID_3]\nType=VirtualKey\nLabel=E\nKeyCode=69\n\n[Key_Shift_Row_3_ID_4]\nType=VirtualKey\nLabel=R\nKeyCode=82\n\n[Key_Shift_Row_3_ID_5]\nType=VirtualKey\nLabel=T\nKeyCode=84\n\n[Key_Shift_Row_3_ID_6]\nType=VirtualKey\nLabel=Y\nKeyCode=89\n\n[Key_Shift_Row_3_ID_7]\nType=VirtualKey\nLabel=U\nKeyCode=85\n\n[Key_Shift_Row_3_ID_8]\nType=VirtualKey\nLabel=I\nKeyCode=73\n\n[Key_Shift_Row_3_ID_9]\nType=VirtualKey\nLabel=O\nKeyCode=79\n\n[Key_Shift_Row_3_ID_10]\nType=VirtualKey\nLabel=P\nKeyCode=80\n\n[Key_Shift_Row_3_ID_11]\nType=String\nLabel=¨\nString=¨\n\n[Key_Shift_Row_3_ID_12]\nType=String\nLabel=£\nString=£\n\n[Key_Shift_Row_3_ID_13]\nType=VirtualKeyIsoEnter\nWidth=150\nLabel=\nKeyCode=13\n\n[Key_Shift_Row_3_ID_14]\nType=Blank\nWidth=25\nCluster=Navigation\n\n[Key_Shift_Row_3_ID_15]\nType=VirtualKey\nLabel=Suppr\nCluster=Navigation\nKeyCode=46\n\n[Key_Shift_Row_3_ID_16]\nType=VirtualKey\nLabel=Fin\nCluster=Navigation\nKeyCode=35\n\n[Key_Shift_Row_3_ID_17]\nType=VirtualKey\nLabel=Page🠻\nCluster=Navigation\nKeyCode=34\n\n[Key_Shift_Row_3_ID_18]\nType=Blank\nWidth=25\nCluster=Numpad\n\n[Key_Shift_Row_3_ID_19]\nType=VirtualKey\nLabel=7\nCluster=Numpad\nKeyCode=103\n\n[Key_Shift_Row_3_ID_20]\nType=VirtualKey\nLabel=8\nCluster=Numpad\nKeyCode=104\n\n[Key_Shift_Row_3_ID_21]\nType=VirtualKey\nLabel=9\nCluster=Numpad\nKeyCode=105\n\n[Key_Shift_Row_3_ID_22]\nType=VirtualKey\nHeight=200\nLabel=+\nCluster=Numpad\nKeyCode=107\n\n[Key_Shift_Row_4_ID_0]\nType=VirtualKey\nWidth=175\nLabel=🡇\nKeyCode=20\nNoRepeat=true\n\n[Key_Shift_Row_4_ID_1]\nType=VirtualKey\nLabel=Q\nKeyCode=81\n\n[Key_Shift_Row_4_ID_2]\nType=VirtualKey\nLabel=S\nKeyCode=83\n\n[Key_Shift_Row_4_ID_3]\nType=VirtualKey\nLabel=D\nKeyCode=68\n\n[Key_Shift_Row_4_ID_4]\nType=VirtualKey\nLabel=F\nKeyCode=70\n\n[Key_Shift_Row_4_ID_5]\nType=VirtualKey\nLabel=G\nKeyCode=71\n\n[Key_Shift_Row_4_ID_6]\nType=VirtualKey\nLabel=H\nKeyCode=72\n\n[Key_Shift_Row_4_ID_7]\nType=VirtualKey\nLabel=J\nKeyCode=74\n\n[Key_Shift_Row_4_ID_8]\nType=VirtualKey\nLabel=K\nKeyCode=75\n\n[Key_Shift_Row_4_ID_9]\nType=VirtualKey\nLabel=L\nKeyCode=76\n\n[Key_Shift_Row_4_ID_10]\nType=VirtualKey\nLabel=M\nKeyCode=77\n\n[Key_Shift_Row_4_ID_11]\nType=String\nLabel=%\nString=%\n\n[Key_Shift_Row_4_ID_12]\nType=String\nLabel=µ\nString=µ\n\n[Key_Shift_Row_4_ID_13]\nType=VirtualKeyIsoEnter\nWidth=125\nLabel=↵\nKeyCode=13\n\n[Key_Shift_Row_4_ID_14]\nType=Blank\nWidth=325\nCluster=Navigation\n\n[Key_Shift_Row_4_ID_15]\nType=Blank\nWidth=25\nCluster=Numpad\n\n[Key_Shift_Row_4_ID_16]\nType=VirtualKey\nLabel=4\nCluster=Numpad\nKeyCode=100\n\n[Key_Shift_Row_4_ID_17]\nType=VirtualKey\nLabel=5\nCluster=Numpad\nKeyCode=101\n\n[Key_Shift_Row_4_ID_18]\nType=VirtualKey\nLabel=6\nCluster=Numpad\nKeyCode=102\n\n[Key_Shift_Row_5_ID_0]\nType=VirtualKeyToggle\nWidth=125\nLabel=🡅\nKeyCode=160\nNoRepeat=true\n\n[Key_Shift_Row_5_ID_1]\nType=String\nLabel=>\nString=>\n\n[Key_Shift_Row_5_ID_2]\nType=VirtualKey\nLabel=W\nKeyCode=87\n\n[Key_Shift_Row_5_ID_3]\nType=VirtualKey\nLabel=X\nKeyCode=88\n\n[Key_Shift_Row_5_ID_4]\nType=VirtualKey\nLabel=C\nKeyCode=67\n\n[Key_Shift_Row_5_ID_5]\nType=VirtualKey\nLabel=V\nKeyCode=86\n\n[Key_Shift_Row_5_ID_6]\nType=VirtualKey\nLabel=B\nKeyCode=66\n\n[Key_Shift_Row_5_ID_7]\nType=VirtualKey\nLabel=N\nKeyCode=78\n\n[Key_Shift_Row_5_ID_8]\nType=String\nLabel=?\nString=?\n\n[Key_Shift_Row_5_ID_9]\nType=String\nLabel=.\nString=.\n\n[Key_Shift_Row_5_ID_10]\nType=String\nLabel=/\nString=/\n\n[Key_Shift_Row_5_ID_11]\nType=String\nLabel=§\nString=§\n\n[Key_Shift_Row_5_ID_12]\nType=VirtualKeyToggle\nWidth=275\nLabel=🡅\nKeyCode=161\nNoRepeat=true\n\n[Key_Shift_Row_5_ID_13]\nType=Blank\nWidth=125\nCluster=Navigation\n\n[Key_Shift_Row_5_ID_14]\nType=VirtualKey\nLabel=🠅\nCluster=Navigation\nKeyCode=38\n\n[Key_Shift_Row_5_ID_15]\nType=Blank\nCluster=Navigation\n\n[Key_Shift_Row_5_ID_16]\nType=Blank\nWidth=25\nCluster=Numpad\n\n[Key_Shift_Row_5_ID_17]\nType=VirtualKey\nLabel=1\nCluster=Numpad\nKeyCode=97\n\n[Key_Shift_Row_5_ID_18]\nType=VirtualKey\nLabel=2\nCluster=Numpad\nKeyCode=98\n\n[Key_Shift_Row_5_ID_19]\nType=VirtualKey\nLabel=3\nCluster=Numpad\nKeyCode=99\n\n[Key_Shift_Row_5_ID_20]\nType=VirtualKey\nHeight=200\nLabel=Entrée\nCluster=Numpad\nKeyCode=13\n\n[Key_Shift_Row_6_ID_0]\nType=VirtualKeyToggle\nWidth=125\nLabel=Ctrl\nKeyCode=162\nNoRepeat=true\n\n[Key_Shift_Row_6_ID_1]\nType=VirtualKeyToggle\nWidth=125\nLabel=Win\nKeyCode=91\nNoRepeat=true\n\n[Key_Shift_Row_6_ID_2]\nType=VirtualKeyToggle\nWidth=125\nLabel=Alt\nKeyCode=164\nNoRepeat=true\n\n[Key_Shift_Row_6_ID_3]\nType=VirtualKey\nWidth=625\nLabel=\nKeyCode=32\n\n[Key_Shift_Row_6_ID_4]\nType=VirtualKeyToggle\nWidth=125\nLabel=AltGr\nKeyCode=165\nNoRepeat=true\n\n[Key_Shift_Row_6_ID_5]\nType=VirtualKeyToggle\nWidth=125\nLabel=Win\nKeyCode=92\nNoRepeat=true\n\n[Key_Shift_Row_6_ID_6]\nType=VirtualKey\nWidth=125\nLabel=Menu\nKeyCode=93\nNoRepeat=true\n\n[Key_Shift_Row_6_ID_7]\nType=VirtualKeyToggle\nWidth=125\nLabel=Ctrl\nKeyCode=163\nNoRepeat=true\n\n[Key_Shift_Row_6_ID_8]\nType=Blank\nWidth=25\nCluster=Navigation\n\n[Key_Shift_Row_6_ID_9]\nType=VirtualKey\nLabel=🠄\nCluster=Navigation\nKeyCode=37\n\n[Key_Shift_Row_6_ID_10]\nType=VirtualKey\nLabel=🠇\nCluster=Navigation\nKeyCode=40\n\n[Key_Shift_Row_6_ID_11]\nType=VirtualKey\nLabel=🠆\nCluster=Navigation\nKeyCode=39\n\n[Key_Shift_Row_6_ID_12]\nType=Blank\nWidth=25\nCluster=Numpad\n\n[Key_Shift_Row_6_ID_13]\nType=VirtualKey\nWidth=200\nLabel=0\nCluster=Numpad\nKeyCode=96\n\n[Key_Shift_Row_6_ID_14]\nType=VirtualKey\nLabel=.\nCluster=Numpad\nKeyCode=110\n\n[Key_AltGr_Row_0_ID_0]\nType=VirtualKey\nLabel=Échap\nCluster=Function\nKeyCode=27\nNoRepeat=true\n\n[Key_AltGr_Row_0_ID_1]\nType=Blank\nCluster=Function\n\n[Key_AltGr_Row_0_ID_2]\nType=VirtualKey\nLabel=F1\nCluster=Function\nKeyCode=112\n\n[Key_AltGr_Row_0_ID_3]\nType=VirtualKey\nLabel=F2\nCluster=Function\nKeyCode=113\n\n[Key_AltGr_Row_0_ID_4]\nType=VirtualKey\nLabel=F3\nCluster=Function\nKeyCode=114\n\n[Key_AltGr_Row_0_ID_5]\nType=VirtualKey\nLabel=F4\nCluster=Function\nKeyCode=115\n\n[Key_AltGr_Row_0_ID_6]\nType=Blank\nWidth=50\nCluster=Function\n\n[Key_AltGr_Row_0_ID_7]\nType=VirtualKey\nLabel=F5\nCluster=Function\nKeyCode=116\n\n[Key_AltGr_Row_0_ID_8]\nType=VirtualKey\nLabel=F6\nCluster=Function\nKeyCode=117\n\n[Key_AltGr_Row_0_ID_9]\nType=VirtualKey\nLabel=F7\nCluster=Function\nKeyCode=118\n\n[Key_AltGr_Row_0_ID_10]\nType=VirtualKey\nLabel=F8\nCluster=Function\nKeyCode=119\n\n[Key_AltGr_Row_0_ID_11]\nType=Blank\nWidth=50\nCluster=Function\n\n[Key_AltGr_Row_0_ID_12]\nType=VirtualKey\nLabel=F9\nCluster=Function\nKeyCode=120\n\n[Key_AltGr_Row_0_ID_13]\nType=VirtualKey\nLabel=F10\nCluster=Function\nKeyCode=121\n\n[Key_AltGr_Row_0_ID_14]\nType=VirtualKey\nLabel=F11\nCluster=Function\nKeyCode=122\n\n[Key_AltGr_Row_0_ID_15]\nType=VirtualKey\nLabel=F12\nCluster=Function\nKeyCode=123\n\n[Key_AltGr_Row_0_ID_16]\nType=Blank\nWidth=25\nCluster=Function\n\n[Key_AltGr_Row_0_ID_17]\nType=VirtualKey\nLabel=Impr\nCluster=Function\nKeyCode=44\nNoRepeat=true\n\n[Key_AltGr_Row_0_ID_18]\nType=VirtualKey\nLabel=Défil\nCluster=Function\nKeyCode=145\nNoRepeat=true\n\n[Key_AltGr_Row_0_ID_19]\nType=VirtualKey\nLabel=Pause\nCluster=Function\nKeyCode=19\nNoRepeat=true\n\n[Key_AltGr_Row_0_ID_20]\nType=Blank\nWidth=25\nCluster=Extra\n\n[Key_AltGr_Row_0_ID_21]\nType=VirtualKey\nLabel=⏯\nCluster=Extra\nKeyCode=179\nNoRepeat=true\n\n[Key_AltGr_Row_0_ID_22]\nType=VirtualKey\nLabel=◼\nCluster=Extra\nKeyCode=178\nNoRepeat=true\n\n[Key_AltGr_Row_0_ID_23]\nType=VirtualKey\nLabel=⏮\nCluster=Extra\nKeyCode=177\nNoRepeat=true\n\n[Key_AltGr_Row_0_ID_24]\nType=VirtualKey\nLabel=⏭\nCluster=Extra\nKeyCode=176\nNoRepeat=true\n\n[Key_AltGr_Row_1_ID_0]\nType=Blank\nHeight=25\nCluster=Function\n\n[Key_AltGr_Row_1_ID_1]\nType=Blank\nHeight=25\nCluster=Extra\n\n[Key_AltGr_Row_2_ID_0]\nType=Blank\nWidth=200\n\n[Key_AltGr_Row_2_ID_1]\nType=String\nLabel=~\nString=~\n\n[Key_AltGr_Row_2_ID_2]\nType=String\nLabel=#\nString=#\n\n[Key_AltGr_Row_2_ID_3]\nType=String\nLabel={\nString={\n\n[Key_AltGr_Row_2_ID_4]\nType=String\nLabel=[\nString=[\n\n[Key_AltGr_Row_2_ID_5]\nType=String\nLabel=|\nString=|\n\n[Key_AltGr_Row_2_ID_6]\nType=String\nLabel=`\nString=`\n\n[Key_AltGr_Row_2_ID_7]\nType=String\nLabel=\\\nString=\\\n\n[Key_AltGr_Row_2_ID_8]\nType=String\nLabel=^\nString=^\n\n[Key_AltGr_Row_2_ID_9]\nType=String\nLabel=@\nString=@\n\n[Key_AltGr_Row_2_ID_10]\nType=String\nLabel=]\nString=]\n\n[Key_AltGr_Row_2_ID_11]\nType=String\nLabel=}\nString=}\n\n[Key_AltGr_Row_2_ID_12]\nType=VirtualKey\nWidth=200\nLabel=⟵\nKeyCode=8\n\n[Key_AltGr_Row_2_ID_13]\nType=Blank\nWidth=25\nCluster=Navigation\n\n[Key_AltGr_Row_2_ID_14]\nType=VirtualKey\nLabel=Inser\nCluster=Navigation\nKeyCode=45\n\n[Key_AltGr_Row_2_ID_15]\nType=VirtualKey\nLabel=Début\nCluster=Navigation\nKeyCode=36\n\n[Key_AltGr_Row_2_ID_16]\nType=VirtualKey\nLabel=Page🠹\nCluster=Navigation\nKeyCode=33\n\n[Key_AltGr_Row_2_ID_17]\nType=Blank\nWidth=25\nCluster=Numpad\n\n[Key_AltGr_Row_2_ID_18]\nType=VirtualKey\nLabel=Verr\\nNum\nCluster=Numpad\nKeyCode=144\nNoRepeat=true\n\n[Key_AltGr_Row_2_ID_19]\nType=VirtualKey\nLabel=/\nCluster=Numpad\nKeyCode=111\n\n[Key_AltGr_Row_2_ID_20]\nType=VirtualKey\nLabel=*\nCluster=Numpad\nKeyCode=106\n\n[Key_AltGr_Row_2_ID_21]\nType=VirtualKey\nLabel=-\nCluster=Numpad\nKeyCode=109\n\n[Key_AltGr_Row_3_ID_0]\nType=VirtualKey\nWidth=150\nLabel=⭾\nKeyCode=9\n\n[Key_AltGr_Row_3_ID_1]\nType=Blank\nWidth=200\n\n[Key_AltGr_Row_3_ID_2]\nType=String\nLabel=€\nString=€\n\n[Key_AltGr_Row_3_ID_3]\nType=Blank\nWidth=800\n\n[Key_AltGr_Row_3_ID_4]\nType=String\nLabel=¤\nString=¤\n\n[Key_AltGr_Row_3_ID_5]\nType=VirtualKeyIsoEnter\nWidth=150\nLabel=\nKeyCode=13\n\n[Key_AltGr_Row_3_ID_6]\nType=Blank\nWidth=25\nCluster=Navigation\n\n[Key_AltGr_Row_3_ID_7]\nType=VirtualKey\nLabel=Suppr\nCluster=Navigation\nKeyCode=46\n\n[Key_AltGr_Row_3_ID_8]\nType=VirtualKey\nLabel=Fin\nCluster=Navigation\nKeyCode=35\n\n[Key_AltGr_Row_3_ID_9]\nType=VirtualKey\nLabel=Page🠻\nCluster=Navigation\nKeyCode=34\n\n[Key_AltGr_Row_3_ID_10]\nType=Blank\nWidth=25\nCluster=Numpad\n\n[Key_AltGr_Row_3_ID_11]\nType=VirtualKey\nLabel=7\nCluster=Numpad\nKeyCode=103\n\n[Key_AltGr_Row_3_ID_12]\nType=VirtualKey\nLabel=8\nCluster=Numpad\nKeyCode=104\n\n[Key_AltGr_Row_3_ID_13]\nType=VirtualKey\nLabel=9\nCluster=Numpad\nKeyCode=105\n\n[Key_AltGr_Row_3_ID_14]\nType=VirtualKey\nHeight=200\nLabel=+\nCluster=Numpad\nKeyCode=107\n\n[Key_AltGr_Row_4_ID_0]\nType=VirtualKey\nWidth=175\nLabel=🡇\nKeyCode=20\nNoRepeat=true\n\n[Key_AltGr_Row_4_ID_1]\nType=Blank\nWidth=1200\n\n[Key_AltGr_Row_4_ID_2]\nType=VirtualKeyIsoEnter\nWidth=125\nLabel=↵\nKeyCode=13\n\n[Key_AltGr_Row_4_ID_3]\nType=Blank\nWidth=325\nCluster=Navigation\n\n[Key_AltGr_Row_4_ID_4]\nType=Blank\nWidth=25\nCluster=Numpad\n\n[Key_AltGr_Row_4_ID_5]\nType=VirtualKey\nLabel=4\nCluster=Numpad\nKeyCode=100\n\n[Key_AltGr_Row_4_ID_6]\nType=VirtualKey\nLabel=5\nCluster=Numpad\nKeyCode=101\n\n[Key_AltGr_Row_4_ID_7]\nType=VirtualKey\nLabel=6\nCluster=Numpad\nKeyCode=102\n\n[Key_AltGr_Row_5_ID_0]\nType=VirtualKeyToggle\nWidth=125\nLabel=🡅\nKeyCode=160\nNoRepeat=true\n\n[Key_AltGr_Row_5_ID_1]\nType=Blank\nWidth=1100\n\n[Key_AltGr_Row_5_ID_2]\nType=VirtualKeyToggle\nWidth=275\nLabel=🡅\nKeyCode=161\nNoRepeat=true\n\n[Key_AltGr_Row_5_ID_3]\nType=Blank\nWidth=125\nCluster=Navigation\n\n[Key_AltGr_Row_5_ID_4]\nType=VirtualKey\nLabel=🠅\nCluster=Navigation\nKeyCode=38\n\n[Key_AltGr_Row_5_ID_5]\nType=Blank\nCluster=Navigation\n\n[Key_AltGr_Row_5_ID_6]\nType=Blank\nWidth=25\nCluster=Numpad\n\n[Key_AltGr_Row_5_ID_7]\nType=VirtualKey\nLabel=1\nCluster=Numpad\nKeyCode=97\n\n[Key_AltGr_Row_5_ID_8]\nType=VirtualKey\nLabel=2\nCluster=Numpad\nKeyCode=98\n\n[Key_AltGr_Row_5_ID_9]\nType=VirtualKey\nLabel=3\nCluster=Numpad\nKeyCode=99\n\n[Key_AltGr_Row_5_ID_10]\nType=VirtualKey\nHeight=200\nLabel=Entrée\nCluster=Numpad\nKeyCode=13\n\n[Key_AltGr_Row_6_ID_0]\nType=VirtualKeyToggle\nWidth=125\nLabel=Ctrl\nKeyCode=162\nNoRepeat=true\n\n[Key_AltGr_Row_6_ID_1]\nType=VirtualKeyToggle\nWidth=125\nLabel=Win\nKeyCode=91\nNoRepeat=true\n\n[Key_AltGr_Row_6_ID_2]\nType=VirtualKeyToggle\nWidth=125\nLabel=Alt\nKeyCode=164\nNoRepeat=true\n\n[Key_AltGr_Row_6_ID_3]\nType=VirtualKey\nWidth=625\nLabel=\nKeyCode=32\n\n[Key_AltGr_Row_6_ID_4]\nType=VirtualKeyToggle\nWidth=125\nLabel=AltGr\nKeyCode=165\nNoRepeat=true\n\n[Key_AltGr_Row_6_ID_5]\nType=VirtualKeyToggle\nWidth=125\nLabel=Win\nKeyCode=92\nNoRepeat=true\n\n[Key_AltGr_Row_6_ID_6]\nType=VirtualKey\nWidth=125\nLabel=Menu\nKeyCode=93\nNoRepeat=true\n\n[Key_AltGr_Row_6_ID_7]\nType=VirtualKeyToggle\nWidth=125\nLabel=Ctrl\nKeyCode=163\nNoRepeat=true\n\n[Key_AltGr_Row_6_ID_8]\nType=Blank\nWidth=25\nCluster=Navigation\n\n[Key_AltGr_Row_6_ID_9]\nType=VirtualKey\nLabel=🠄\nCluster=Navigation\nKeyCode=37\n\n[Key_AltGr_Row_6_ID_10]\nType=VirtualKey\nLabel=🠇\nCluster=Navigation\nKeyCode=40\n\n[Key_AltGr_Row_6_ID_11]\nType=VirtualKey\nLabel=🠆\nCluster=Navigation\nKeyCode=39\n\n[Key_AltGr_Row_6_ID_12]\nType=Blank\nWidth=25\nCluster=Numpad\n\n[Key_AltGr_Row_6_ID_13]\nType=VirtualKey\nWidth=200\nLabel=0\nCluster=Numpad\nKeyCode=96\n\n[Key_AltGr_Row_6_ID_14]\nType=VirtualKey\nLabel=.\nCluster=Numpad\nKeyCode=110\n\n"
  },
  {
    "path": "assets/keyboards/qwerty_dk.ini",
    "content": "[LayoutInfo]\nName=QWERTY (Denmark)\nAuthor=BOTAlex\nHasAltGr=true\nHasClusterFunction=true\nHasClusterNavigation=true\nHasClusterNumpad=true\nHasClusterExtra=true\n\n[Key_Base_Row_0_ID_0]\nType=VirtualKey\nLabel=Esc\nCluster=Function\nKeyCode=27\nNoRepeat=true\n\n[Key_Base_Row_0_ID_1]\nType=Blank\nCluster=Function\n\n[Key_Base_Row_0_ID_2]\nType=VirtualKey\nLabel=F1\nCluster=Function\nKeyCode=112\n\n[Key_Base_Row_0_ID_3]\nType=VirtualKey\nLabel=F2\nCluster=Function\nKeyCode=113\n\n[Key_Base_Row_0_ID_4]\nType=VirtualKey\nLabel=F3\nCluster=Function\nKeyCode=114\n\n[Key_Base_Row_0_ID_5]\nType=VirtualKey\nLabel=F4\nCluster=Function\nKeyCode=115\n\n[Key_Base_Row_0_ID_6]\nType=Blank\nWidth=50\nCluster=Function\n\n[Key_Base_Row_0_ID_7]\nType=VirtualKey\nLabel=F5\nCluster=Function\nKeyCode=116\n\n[Key_Base_Row_0_ID_8]\nType=VirtualKey\nLabel=F6\nCluster=Function\nKeyCode=117\n\n[Key_Base_Row_0_ID_9]\nType=VirtualKey\nLabel=F7\nCluster=Function\nKeyCode=118\n\n[Key_Base_Row_0_ID_10]\nType=VirtualKey\nLabel=F8\nCluster=Function\nKeyCode=119\n\n[Key_Base_Row_0_ID_11]\nType=Blank\nWidth=50\nCluster=Function\n\n[Key_Base_Row_0_ID_12]\nType=VirtualKey\nLabel=F9\nCluster=Function\nKeyCode=120\n\n[Key_Base_Row_0_ID_13]\nType=VirtualKey\nLabel=F10\nCluster=Function\nKeyCode=121\n\n[Key_Base_Row_0_ID_14]\nType=VirtualKey\nLabel=F11\nCluster=Function\nKeyCode=122\n\n[Key_Base_Row_0_ID_15]\nType=VirtualKey\nLabel=F12\nCluster=Function\nKeyCode=123\n\n[Key_Base_Row_0_ID_16]\nType=Blank\nWidth=25\nCluster=Function\n\n[Key_Base_Row_0_ID_17]\nType=VirtualKey\nLabel=Print\\nScreen\nCluster=Function\nKeyCode=44\nNoRepeat=true\n\n[Key_Base_Row_0_ID_18]\nType=VirtualKey\nLabel=Scroll\\nLock\nCluster=Function\nKeyCode=145\nNoRepeat=true\n\n[Key_Base_Row_0_ID_19]\nType=VirtualKey\nLabel=Pause\nCluster=Function\nKeyCode=19\nNoRepeat=true\n\n[Key_Base_Row_0_ID_20]\nType=Blank\nWidth=25\nCluster=Extra\n\n[Key_Base_Row_0_ID_21]\nType=VirtualKey\nLabel=⏯\nCluster=Extra\nKeyCode=179\nNoRepeat=true\n\n[Key_Base_Row_0_ID_22]\nType=VirtualKey\nLabel=◼\nCluster=Extra\nKeyCode=178\nNoRepeat=true\n\n[Key_Base_Row_0_ID_23]\nType=VirtualKey\nLabel=⏮\nCluster=Extra\nKeyCode=177\nNoRepeat=true\n\n[Key_Base_Row_0_ID_24]\nType=VirtualKey\nLabel=⏭\nCluster=Extra\nKeyCode=176\nNoRepeat=true\n\n[Key_Base_Row_1_ID_0]\nType=Blank\nWidth=1500\nHeight=25\nCluster=Function\n\n[Key_Base_Row_2_ID_0]\nType=String\nLabel=½\nString=½\n\n[Key_Base_Row_2_ID_1]\nType=VirtualKey\nLabel=1\nKeyCode=49\n\n[Key_Base_Row_2_ID_2]\nType=VirtualKey\nLabel=2\nKeyCode=50\n\n[Key_Base_Row_2_ID_3]\nType=VirtualKey\nLabel=3\nKeyCode=51\n\n[Key_Base_Row_2_ID_4]\nType=VirtualKey\nLabel=4\nKeyCode=52\n\n[Key_Base_Row_2_ID_5]\nType=VirtualKey\nLabel=5\nKeyCode=53\n\n[Key_Base_Row_2_ID_6]\nType=VirtualKey\nLabel=6\nKeyCode=54\n\n[Key_Base_Row_2_ID_7]\nType=VirtualKey\nLabel=7\nKeyCode=55\n\n[Key_Base_Row_2_ID_8]\nType=VirtualKey\nLabel=8\nKeyCode=56\n\n[Key_Base_Row_2_ID_9]\nType=VirtualKey\nLabel=9\nKeyCode=57\n\n[Key_Base_Row_2_ID_10]\nType=VirtualKey\nLabel=0\nKeyCode=48\n\n[Key_Base_Row_2_ID_11]\nType=VirtualKey\nLabel=+\nKeyCode=187\n\n[Key_Base_Row_2_ID_12]\nType=String\nLabel=´\nString=´\n\n[Key_Base_Row_2_ID_13]\nType=VirtualKey\nWidth=200\nLabel=Backspace\nKeyCode=8\n\n[Key_Base_Row_2_ID_14]\nType=Blank\nWidth=25\nCluster=Navigation\n\n[Key_Base_Row_2_ID_15]\nType=VirtualKey\nLabel=Insert\nCluster=Navigation\nKeyCode=45\n\n[Key_Base_Row_2_ID_16]\nType=VirtualKey\nLabel=Home\nCluster=Navigation\nKeyCode=36\n\n[Key_Base_Row_2_ID_17]\nType=VirtualKey\nLabel=PgUp\nCluster=Navigation\nKeyCode=33\n\n[Key_Base_Row_2_ID_18]\nType=Blank\nWidth=25\nCluster=Numpad\n\n[Key_Base_Row_2_ID_19]\nType=VirtualKey\nLabel=Num\\nLock\nCluster=Numpad\nKeyCode=144\nNoRepeat=true\n\n[Key_Base_Row_2_ID_20]\nType=VirtualKey\nLabel=/\nCluster=Numpad\nKeyCode=111\n\n[Key_Base_Row_2_ID_21]\nType=VirtualKey\nLabel=*\nCluster=Numpad\nKeyCode=106\n\n[Key_Base_Row_2_ID_22]\nType=VirtualKey\nLabel=-\nCluster=Numpad\nKeyCode=109\n\n[Key_Base_Row_3_ID_0]\nType=VirtualKey\nWidth=150\nLabel=Tab\nKeyCode=9\n\n[Key_Base_Row_3_ID_1]\nType=VirtualKey\nLabel=q\nKeyCode=81\n\n[Key_Base_Row_3_ID_2]\nType=VirtualKey\nLabel=w\nKeyCode=87\n\n[Key_Base_Row_3_ID_3]\nType=VirtualKey\nLabel=e\nKeyCode=69\n\n[Key_Base_Row_3_ID_4]\nType=VirtualKey\nLabel=r\nKeyCode=82\n\n[Key_Base_Row_3_ID_5]\nType=VirtualKey\nLabel=t\nKeyCode=84\n\n[Key_Base_Row_3_ID_6]\nType=VirtualKey\nLabel=y\nKeyCode=89\n\n[Key_Base_Row_3_ID_7]\nType=VirtualKey\nLabel=u\nKeyCode=85\n\n[Key_Base_Row_3_ID_8]\nType=VirtualKey\nLabel=i\nKeyCode=73\n\n[Key_Base_Row_3_ID_9]\nType=VirtualKey\nLabel=o\nKeyCode=79\n\n[Key_Base_Row_3_ID_10]\nType=VirtualKey\nLabel=p\nKeyCode=80\n\n[Key_Base_Row_3_ID_11]\nType=String\nLabel=å\nString=å\n\n[Key_Base_Row_3_ID_12]\nType=String\nLabel=¨\nString=¨\n\n[Key_Base_Row_3_ID_13]\nType=VirtualKeyIsoEnter\nWidth=150\nLabel=Enter\nKeyCode=13\n\n[Key_Base_Row_3_ID_14]\nType=Blank\nWidth=25\nCluster=Navigation\n\n[Key_Base_Row_3_ID_15]\nType=VirtualKey\nLabel=Delete\nCluster=Navigation\nKeyCode=46\n\n[Key_Base_Row_3_ID_16]\nType=VirtualKey\nLabel=End\nCluster=Navigation\nKeyCode=35\n\n[Key_Base_Row_3_ID_17]\nType=VirtualKey\nLabel=PgDn\nCluster=Navigation\nKeyCode=34\n\n[Key_Base_Row_3_ID_18]\nType=Blank\nWidth=25\nCluster=Numpad\n\n[Key_Base_Row_3_ID_19]\nType=VirtualKey\nLabel=7\nCluster=Numpad\nKeyCode=103\n\n[Key_Base_Row_3_ID_20]\nType=VirtualKey\nLabel=8\nCluster=Numpad\nKeyCode=104\n\n[Key_Base_Row_3_ID_21]\nType=VirtualKey\nLabel=9\nCluster=Numpad\nKeyCode=105\n\n[Key_Base_Row_3_ID_22]\nType=VirtualKey\nHeight=200\nLabel=+\nCluster=Numpad\nKeyCode=107\n\n[Key_Base_Row_4_ID_0]\nType=VirtualKey\nWidth=175\nLabel=Caps Lock\nKeyCode=20\nNoRepeat=true\n\n[Key_Base_Row_4_ID_1]\nType=VirtualKey\nLabel=a\nKeyCode=65\n\n[Key_Base_Row_4_ID_2]\nType=VirtualKey\nLabel=s\nKeyCode=83\n\n[Key_Base_Row_4_ID_3]\nType=VirtualKey\nLabel=d\nKeyCode=68\n\n[Key_Base_Row_4_ID_4]\nType=VirtualKey\nLabel=f\nKeyCode=70\n\n[Key_Base_Row_4_ID_5]\nType=VirtualKey\nLabel=g\nKeyCode=71\n\n[Key_Base_Row_4_ID_6]\nType=VirtualKey\nLabel=h\nKeyCode=72\n\n[Key_Base_Row_4_ID_7]\nType=VirtualKey\nLabel=j\nKeyCode=74\n\n[Key_Base_Row_4_ID_8]\nType=VirtualKey\nLabel=k\nKeyCode=75\n\n[Key_Base_Row_4_ID_9]\nType=VirtualKey\nLabel=l\nKeyCode=76\n\n[Key_Base_Row_4_ID_10]\nType=String\nLabel=æ\nString=æ\n\n[Key_Base_Row_4_ID_11]\nType=String\nLabel=ø\nString=ø\n\n[Key_Base_Row_4_ID_12]\nType=String\nLabel='\nString='\n\n[Key_Base_Row_4_ID_13]\nType=VirtualKeyIsoEnter\nWidth=125\nLabel=Enter\nKeyCode=13\n\n[Key_Base_Row_4_ID_14]\nType=Blank\nWidth=25\nCluster=Navigation\n\n[Key_Base_Row_4_ID_15]\nType=Blank\nWidth=300\nCluster=Navigation\n\n[Key_Base_Row_4_ID_16]\nType=Blank\nWidth=25\nCluster=Numpad\n\n[Key_Base_Row_4_ID_17]\nType=VirtualKey\nLabel=4\nCluster=Numpad\nKeyCode=100\n\n[Key_Base_Row_4_ID_18]\nType=VirtualKey\nLabel=5\nCluster=Numpad\nKeyCode=101\n\n[Key_Base_Row_4_ID_19]\nType=VirtualKey\nLabel=6\nCluster=Numpad\nKeyCode=102\n\n[Key_Base_Row_5_ID_0]\nType=VirtualKeyToggle\nWidth=125\nLabel=Shift\nKeyCode=160\nNoRepeat=true\n\n[Key_Base_Row_5_ID_1]\nType=VirtualKey\nLabel=<\nKeyCode=226\n\n[Key_Base_Row_5_ID_2]\nType=VirtualKey\nLabel=z\nKeyCode=90\n\n[Key_Base_Row_5_ID_3]\nType=VirtualKey\nLabel=x\nKeyCode=88\n\n[Key_Base_Row_5_ID_4]\nType=VirtualKey\nLabel=c\nKeyCode=67\n\n[Key_Base_Row_5_ID_5]\nType=VirtualKey\nLabel=v\nKeyCode=86\n\n[Key_Base_Row_5_ID_6]\nType=VirtualKey\nLabel=b\nKeyCode=66\n\n[Key_Base_Row_5_ID_7]\nType=VirtualKey\nLabel=n\nKeyCode=78\n\n[Key_Base_Row_5_ID_8]\nType=VirtualKey\nLabel=m\nKeyCode=77\n\n[Key_Base_Row_5_ID_9]\nType=String\nLabel=,\nString=,\n\n[Key_Base_Row_5_ID_10]\nType=String\nLabel=.\nString=.\n\n[Key_Base_Row_5_ID_11]\nType=String\nLabel=-\nString=-\n\n[Key_Base_Row_5_ID_12]\nType=VirtualKeyToggle\nWidth=275\nLabel=Shift\nKeyCode=161\nNoRepeat=true\n\n[Key_Base_Row_5_ID_13]\nType=Blank\nWidth=125\nCluster=Navigation\n\n[Key_Base_Row_5_ID_14]\nType=VirtualKey\nLabel=🠅\nCluster=Navigation\nKeyCode=38\n\n[Key_Base_Row_5_ID_15]\nType=Blank\nCluster=Navigation\n\n[Key_Base_Row_5_ID_16]\nType=Blank\nWidth=25\nCluster=Numpad\n\n[Key_Base_Row_5_ID_17]\nType=VirtualKey\nLabel=1\nCluster=Numpad\nKeyCode=97\n\n[Key_Base_Row_5_ID_18]\nType=VirtualKey\nLabel=2\nCluster=Numpad\nKeyCode=98\n\n[Key_Base_Row_5_ID_19]\nType=VirtualKey\nLabel=3\nCluster=Numpad\nKeyCode=99\n\n[Key_Base_Row_5_ID_20]\nType=VirtualKey\nHeight=200\nLabel=Enter\nCluster=Numpad\nKeyCode=13\n\n[Key_Base_Row_6_ID_0]\nType=VirtualKeyToggle\nWidth=125\nLabel=Ctrl\nKeyCode=162\nNoRepeat=true\n\n[Key_Base_Row_6_ID_1]\nType=VirtualKeyToggle\nWidth=125\nLabel=Win\nKeyCode=91\nNoRepeat=true\n\n[Key_Base_Row_6_ID_2]\nType=VirtualKeyToggle\nWidth=125\nLabel=Alt\nKeyCode=164\nNoRepeat=true\n\n[Key_Base_Row_6_ID_3]\nType=VirtualKey\nWidth=625\nLabel=\nKeyCode=32\n\n[Key_Base_Row_6_ID_4]\nType=VirtualKeyToggle\nWidth=125\nLabel=AltGr\nKeyCode=165\nNoRepeat=true\n\n[Key_Base_Row_6_ID_5]\nType=VirtualKeyToggle\nWidth=125\nLabel=Win\nKeyCode=92\nNoRepeat=true\n\n[Key_Base_Row_6_ID_6]\nType=VirtualKey\nWidth=125\nLabel=Menu\nKeyCode=93\nNoRepeat=true\n\n[Key_Base_Row_6_ID_7]\nType=VirtualKeyToggle\nWidth=125\nLabel=Ctrl\nKeyCode=163\nNoRepeat=true\n\n[Key_Base_Row_6_ID_8]\nType=Blank\nWidth=25\nCluster=Navigation\n\n[Key_Base_Row_6_ID_9]\nType=VirtualKey\nLabel=🠄\nCluster=Navigation\nKeyCode=37\n\n[Key_Base_Row_6_ID_10]\nType=VirtualKey\nLabel=🠇\nCluster=Navigation\nKeyCode=40\n\n[Key_Base_Row_6_ID_11]\nType=VirtualKey\nLabel=🠆\nCluster=Navigation\nKeyCode=39\n\n[Key_Base_Row_6_ID_12]\nType=Blank\nWidth=25\nCluster=Numpad\n\n[Key_Base_Row_6_ID_13]\nType=VirtualKey\nWidth=200\nLabel=0\nCluster=Numpad\nKeyCode=96\n\n[Key_Base_Row_6_ID_14]\nType=VirtualKey\nLabel=,\nCluster=Numpad\nKeyCode=110\n\n[Key_Shift_Row_0_ID_0]\nType=VirtualKey\nLabel=Esc\nCluster=Function\nKeyCode=27\nNoRepeat=true\n\n[Key_Shift_Row_0_ID_1]\nType=Blank\nCluster=Function\n\n[Key_Shift_Row_0_ID_2]\nType=VirtualKey\nLabel=F1\nCluster=Function\nKeyCode=112\n\n[Key_Shift_Row_0_ID_3]\nType=VirtualKey\nLabel=F2\nCluster=Function\nKeyCode=113\n\n[Key_Shift_Row_0_ID_4]\nType=VirtualKey\nLabel=F3\nCluster=Function\nKeyCode=114\n\n[Key_Shift_Row_0_ID_5]\nType=VirtualKey\nLabel=F4\nCluster=Function\nKeyCode=115\n\n[Key_Shift_Row_0_ID_6]\nType=Blank\nWidth=50\nCluster=Function\n\n[Key_Shift_Row_0_ID_7]\nType=VirtualKey\nLabel=F5\nCluster=Function\nKeyCode=116\n\n[Key_Shift_Row_0_ID_8]\nType=VirtualKey\nLabel=F6\nCluster=Function\nKeyCode=117\n\n[Key_Shift_Row_0_ID_9]\nType=VirtualKey\nLabel=F7\nCluster=Function\nKeyCode=118\n\n[Key_Shift_Row_0_ID_10]\nType=VirtualKey\nLabel=F8\nCluster=Function\nKeyCode=119\n\n[Key_Shift_Row_0_ID_11]\nType=Blank\nWidth=50\nCluster=Function\n\n[Key_Shift_Row_0_ID_12]\nType=VirtualKey\nLabel=F9\nCluster=Function\nKeyCode=120\n\n[Key_Shift_Row_0_ID_13]\nType=VirtualKey\nLabel=F10\nCluster=Function\nKeyCode=121\n\n[Key_Shift_Row_0_ID_14]\nType=VirtualKey\nLabel=F11\nCluster=Function\nKeyCode=122\n\n[Key_Shift_Row_0_ID_15]\nType=VirtualKey\nLabel=F12\nCluster=Function\nKeyCode=123\n\n[Key_Shift_Row_0_ID_16]\nType=Blank\nWidth=25\nCluster=Function\n\n[Key_Shift_Row_0_ID_17]\nType=VirtualKey\nLabel=Print\\nScreen\nCluster=Function\nKeyCode=44\nNoRepeat=true\n\n[Key_Shift_Row_0_ID_18]\nType=VirtualKey\nLabel=Scroll\\nLock\nCluster=Function\nKeyCode=145\nNoRepeat=true\n\n[Key_Shift_Row_0_ID_19]\nType=VirtualKey\nLabel=Pause\nCluster=Function\nKeyCode=19\nNoRepeat=true\n\n[Key_Shift_Row_0_ID_20]\nType=Blank\nWidth=25\nCluster=Extra\n\n[Key_Shift_Row_0_ID_21]\nType=VirtualKey\nLabel=🡰\nCluster=Extra\nKeyCode=166\nNoRepeat=true\n\n[Key_Shift_Row_0_ID_22]\nType=VirtualKey\nLabel=🡲\nCluster=Extra\nKeyCode=167\nNoRepeat=true\n\n[Key_Shift_Row_0_ID_23]\nType=VirtualKey\nLabel=🔇\nCluster=Extra\nKeyCode=173\nNoRepeat=true\n\n[Key_Shift_Row_1_ID_0]\nType=Blank\nHeight=25\nCluster=Function\n\n[Key_Shift_Row_1_ID_1]\nType=Blank\nHeight=25\nCluster=Extra\n\n[Key_Shift_Row_2_ID_0]\nType=String\nLabel=§\nString=§\n\n[Key_Shift_Row_2_ID_1]\nType=String\nLabel=!\nString=!\n\n[Key_Shift_Row_2_ID_2]\nType=String\nLabel=\"\nString=\"\n\n[Key_Shift_Row_2_ID_3]\nType=String\nLabel=#\nString=#\n\n[Key_Shift_Row_2_ID_4]\nType=String\nLabel=¤\nString=¤\n\n[Key_Shift_Row_2_ID_5]\nType=String\nLabel=%\nString=%\n\n[Key_Shift_Row_2_ID_6]\nType=String\nLabel=&\nString=&\n\n[Key_Shift_Row_2_ID_7]\nType=String\nLabel=/\nString=/\n\n[Key_Shift_Row_2_ID_8]\nType=String\nLabel=(\nString=(\n\n[Key_Shift_Row_2_ID_9]\nType=String\nLabel=)\nString=)\n\n[Key_Shift_Row_2_ID_10]\nType=String\nLabel==\nString==\n\n[Key_Shift_Row_2_ID_11]\nType=String\nLabel=?\nString=?\n\n[Key_Shift_Row_2_ID_12]\nType=String\nLabel=`\nString=`\n\n[Key_Shift_Row_2_ID_13]\nType=VirtualKey\nWidth=200\nLabel=Backspace\nKeyCode=8\n\n[Key_Shift_Row_2_ID_14]\nType=Blank\nWidth=25\nCluster=Navigation\n\n[Key_Shift_Row_2_ID_15]\nType=VirtualKey\nLabel=Insert\nCluster=Navigation\nKeyCode=45\n\n[Key_Shift_Row_2_ID_16]\nType=VirtualKey\nLabel=Home\nCluster=Navigation\nKeyCode=36\n\n[Key_Shift_Row_2_ID_17]\nType=VirtualKey\nLabel=PgUp\nCluster=Navigation\nKeyCode=33\n\n[Key_Shift_Row_2_ID_18]\nType=Blank\nWidth=25\nCluster=Numpad\n\n[Key_Shift_Row_2_ID_19]\nType=VirtualKey\nLabel=Num\\nLock\nCluster=Numpad\nKeyCode=144\nNoRepeat=true\n\n[Key_Shift_Row_2_ID_20]\nType=VirtualKey\nLabel=/\nCluster=Numpad\nKeyCode=111\n\n[Key_Shift_Row_2_ID_21]\nType=VirtualKey\nLabel=*\nCluster=Numpad\nKeyCode=106\n\n[Key_Shift_Row_2_ID_22]\nType=VirtualKey\nLabel=-\nCluster=Numpad\nKeyCode=109\n\n[Key_Shift_Row_3_ID_0]\nType=VirtualKey\nWidth=150\nLabel=Tab\nKeyCode=9\n\n[Key_Shift_Row_3_ID_1]\nType=VirtualKey\nLabel=Q\nKeyCode=81\n\n[Key_Shift_Row_3_ID_2]\nType=VirtualKey\nLabel=W\nKeyCode=87\n\n[Key_Shift_Row_3_ID_3]\nType=VirtualKey\nLabel=E\nKeyCode=69\n\n[Key_Shift_Row_3_ID_4]\nType=VirtualKey\nLabel=R\nKeyCode=82\n\n[Key_Shift_Row_3_ID_5]\nType=VirtualKey\nLabel=T\nKeyCode=84\n\n[Key_Shift_Row_3_ID_6]\nType=VirtualKey\nLabel=Y\nKeyCode=89\n\n[Key_Shift_Row_3_ID_7]\nType=VirtualKey\nLabel=U\nKeyCode=85\n\n[Key_Shift_Row_3_ID_8]\nType=VirtualKey\nLabel=I\nKeyCode=73\n\n[Key_Shift_Row_3_ID_9]\nType=VirtualKey\nLabel=O\nKeyCode=79\n\n[Key_Shift_Row_3_ID_10]\nType=VirtualKey\nLabel=P\nKeyCode=80\n\n[Key_Shift_Row_3_ID_11]\nType=String\nLabel=Å\nString=Å\n\n[Key_Shift_Row_3_ID_12]\nType=String\nLabel=^\nString=^\n\n[Key_Shift_Row_3_ID_13]\nType=VirtualKeyIsoEnter\nWidth=150\nLabel=Enter\nKeyCode=13\n\n[Key_Shift_Row_3_ID_14]\nType=Blank\nWidth=25\nCluster=Navigation\n\n[Key_Shift_Row_3_ID_15]\nType=VirtualKey\nLabel=Delete\nCluster=Navigation\nKeyCode=46\n\n[Key_Shift_Row_3_ID_16]\nType=VirtualKey\nLabel=End\nCluster=Navigation\nKeyCode=35\n\n[Key_Shift_Row_3_ID_17]\nType=VirtualKey\nLabel=PgDn\nCluster=Navigation\nKeyCode=34\n\n[Key_Shift_Row_3_ID_18]\nType=Blank\nWidth=25\nCluster=Numpad\n\n[Key_Shift_Row_3_ID_19]\nType=VirtualKey\nLabel=7\nCluster=Numpad\nKeyCode=103\n\n[Key_Shift_Row_3_ID_20]\nType=VirtualKey\nLabel=8\nCluster=Numpad\nKeyCode=104\n\n[Key_Shift_Row_3_ID_21]\nType=VirtualKey\nLabel=9\nCluster=Numpad\nKeyCode=105\n\n[Key_Shift_Row_3_ID_22]\nType=VirtualKey\nHeight=200\nLabel=+\nCluster=Numpad\nKeyCode=107\n\n[Key_Shift_Row_4_ID_0]\nType=VirtualKey\nWidth=175\nLabel=Caps Lock\nKeyCode=20\nNoRepeat=true\n\n[Key_Shift_Row_4_ID_1]\nType=VirtualKey\nLabel=A\nKeyCode=65\n\n[Key_Shift_Row_4_ID_2]\nType=VirtualKey\nLabel=S\nKeyCode=83\n\n[Key_Shift_Row_4_ID_3]\nType=VirtualKey\nLabel=D\nKeyCode=68\n\n[Key_Shift_Row_4_ID_4]\nType=VirtualKey\nLabel=F\nKeyCode=70\n\n[Key_Shift_Row_4_ID_5]\nType=VirtualKey\nLabel=G\nKeyCode=71\n\n[Key_Shift_Row_4_ID_6]\nType=VirtualKey\nLabel=H\nKeyCode=72\n\n[Key_Shift_Row_4_ID_7]\nType=VirtualKey\nLabel=J\nKeyCode=74\n\n[Key_Shift_Row_4_ID_8]\nType=VirtualKey\nLabel=K\nKeyCode=75\n\n[Key_Shift_Row_4_ID_9]\nType=VirtualKey\nLabel=L\nKeyCode=76\n\n[Key_Shift_Row_4_ID_10]\nType=String\nLabel=Æ\nString=Æ\n\n[Key_Shift_Row_4_ID_11]\nType=String\nLabel=Ø\nString=Ø\n\n[Key_Shift_Row_4_ID_12]\nType=String\nLabel=*\nString=*\n\n[Key_Shift_Row_4_ID_13]\nType=VirtualKeyIsoEnter\nWidth=125\nLabel=Enter\nKeyCode=13\n\n[Key_Shift_Row_4_ID_14]\nType=Blank\nWidth=325\nCluster=Navigation\n\n[Key_Shift_Row_4_ID_15]\nType=Blank\nWidth=25\nCluster=Numpad\n\n[Key_Shift_Row_4_ID_16]\nType=VirtualKey\nLabel=4\nCluster=Numpad\nKeyCode=100\n\n[Key_Shift_Row_4_ID_17]\nType=VirtualKey\nLabel=5\nCluster=Numpad\nKeyCode=101\n\n[Key_Shift_Row_4_ID_18]\nType=VirtualKey\nLabel=6\nCluster=Numpad\nKeyCode=102\n\n[Key_Shift_Row_5_ID_0]\nType=VirtualKeyToggle\nWidth=125\nLabel=Shift\nKeyCode=160\nNoRepeat=true\n\n[Key_Shift_Row_5_ID_1]\nType=VirtualKey\nLabel=>\nKeyCode=226\n\n[Key_Shift_Row_5_ID_2]\nType=VirtualKey\nLabel=Z\nKeyCode=90\n\n[Key_Shift_Row_5_ID_3]\nType=VirtualKey\nLabel=X\nKeyCode=88\n\n[Key_Shift_Row_5_ID_4]\nType=VirtualKey\nLabel=C\nKeyCode=67\n\n[Key_Shift_Row_5_ID_5]\nType=VirtualKey\nLabel=V\nKeyCode=86\n\n[Key_Shift_Row_5_ID_6]\nType=VirtualKey\nLabel=B\nKeyCode=66\n\n[Key_Shift_Row_5_ID_7]\nType=VirtualKey\nLabel=N\nKeyCode=78\n\n[Key_Shift_Row_5_ID_8]\nType=VirtualKey\nLabel=M\nKeyCode=77\n\n[Key_Shift_Row_5_ID_9]\nType=String\nLabel=;\nString=;\n\n[Key_Shift_Row_5_ID_10]\nType=String\nLabel=:\nString=:\n\n[Key_Shift_Row_5_ID_11]\nType=String\nLabel=_\nString=_\n\n[Key_Shift_Row_5_ID_12]\nType=VirtualKeyToggle\nWidth=275\nLabel=Shift\nKeyCode=161\nNoRepeat=true\n\n[Key_Shift_Row_5_ID_13]\nType=Blank\nWidth=125\nCluster=Navigation\n\n[Key_Shift_Row_5_ID_14]\nType=VirtualKey\nLabel=🠅\nCluster=Navigation\nKeyCode=38\n\n[Key_Shift_Row_5_ID_15]\nType=Blank\nCluster=Navigation\n\n[Key_Shift_Row_5_ID_16]\nType=Blank\nWidth=25\nCluster=Numpad\n\n[Key_Shift_Row_5_ID_17]\nType=VirtualKey\nLabel=1\nCluster=Numpad\nKeyCode=97\n\n[Key_Shift_Row_5_ID_18]\nType=VirtualKey\nLabel=2\nCluster=Numpad\nKeyCode=98\n\n[Key_Shift_Row_5_ID_19]\nType=VirtualKey\nLabel=3\nCluster=Numpad\nKeyCode=99\n\n[Key_Shift_Row_5_ID_20]\nType=VirtualKey\nHeight=200\nLabel=Enter\nCluster=Numpad\nKeyCode=13\n\n[Key_Shift_Row_6_ID_0]\nType=VirtualKeyToggle\nWidth=125\nLabel=Ctrl\nKeyCode=162\nNoRepeat=true\n\n[Key_Shift_Row_6_ID_1]\nType=VirtualKeyToggle\nWidth=125\nLabel=Win\nKeyCode=91\nNoRepeat=true\n\n[Key_Shift_Row_6_ID_2]\nType=VirtualKeyToggle\nWidth=125\nLabel=Alt\nKeyCode=164\nNoRepeat=true\n\n[Key_Shift_Row_6_ID_3]\nType=VirtualKey\nWidth=625\nLabel=\nKeyCode=32\n\n[Key_Shift_Row_6_ID_4]\nType=VirtualKeyToggle\nWidth=125\nLabel=AltGr\nKeyCode=165\nNoRepeat=true\n\n[Key_Shift_Row_6_ID_5]\nType=VirtualKeyToggle\nWidth=125\nLabel=Win\nKeyCode=92\nNoRepeat=true\n\n[Key_Shift_Row_6_ID_6]\nType=VirtualKey\nWidth=125\nLabel=Menu\nKeyCode=93\nNoRepeat=true\n\n[Key_Shift_Row_6_ID_7]\nType=VirtualKeyToggle\nWidth=125\nLabel=Ctrl\nKeyCode=163\nNoRepeat=true\n\n[Key_Shift_Row_6_ID_8]\nType=Blank\nWidth=25\nCluster=Navigation\n\n[Key_Shift_Row_6_ID_9]\nType=VirtualKey\nLabel=🠄\nCluster=Navigation\nKeyCode=37\n\n[Key_Shift_Row_6_ID_10]\nType=VirtualKey\nLabel=🠇\nCluster=Navigation\nKeyCode=40\n\n[Key_Shift_Row_6_ID_11]\nType=VirtualKey\nLabel=🠆\nCluster=Navigation\nKeyCode=39\n\n[Key_Shift_Row_6_ID_12]\nType=Blank\nWidth=25\nCluster=Numpad\n\n[Key_Shift_Row_6_ID_13]\nType=VirtualKey\nWidth=200\nLabel=0\nCluster=Numpad\nKeyCode=96\n\n[Key_Shift_Row_6_ID_14]\nType=VirtualKey\nLabel=,\nCluster=Numpad\nKeyCode=110\n\n[Key_AltGr_Row_0_ID_0]\nType=VirtualKey\nLabel=Esc\nCluster=Function\nKeyCode=27\nNoRepeat=true\n\n[Key_AltGr_Row_0_ID_1]\nType=Blank\nCluster=Function\n\n[Key_AltGr_Row_0_ID_2]\nType=VirtualKey\nLabel=F1\nCluster=Function\nKeyCode=112\n\n[Key_AltGr_Row_0_ID_3]\nType=VirtualKey\nLabel=F2\nCluster=Function\nKeyCode=113\n\n[Key_AltGr_Row_0_ID_4]\nType=VirtualKey\nLabel=F3\nCluster=Function\nKeyCode=114\n\n[Key_AltGr_Row_0_ID_5]\nType=VirtualKey\nLabel=F4\nCluster=Function\nKeyCode=115\n\n[Key_AltGr_Row_0_ID_6]\nType=Blank\nWidth=50\nCluster=Function\n\n[Key_AltGr_Row_0_ID_7]\nType=VirtualKey\nLabel=F5\nCluster=Function\nKeyCode=116\n\n[Key_AltGr_Row_0_ID_8]\nType=VirtualKey\nLabel=F6\nCluster=Function\nKeyCode=117\n\n[Key_AltGr_Row_0_ID_9]\nType=VirtualKey\nLabel=F7\nCluster=Function\nKeyCode=118\n\n[Key_AltGr_Row_0_ID_10]\nType=VirtualKey\nLabel=F8\nCluster=Function\nKeyCode=119\n\n[Key_AltGr_Row_0_ID_11]\nType=Blank\nWidth=50\nCluster=Function\n\n[Key_AltGr_Row_0_ID_12]\nType=VirtualKey\nLabel=F9\nCluster=Function\nKeyCode=120\n\n[Key_AltGr_Row_0_ID_13]\nType=VirtualKey\nLabel=F10\nCluster=Function\nKeyCode=121\n\n[Key_AltGr_Row_0_ID_14]\nType=VirtualKey\nLabel=F11\nCluster=Function\nKeyCode=122\n\n[Key_AltGr_Row_0_ID_15]\nType=VirtualKey\nLabel=F12\nCluster=Function\nKeyCode=123\n\n[Key_AltGr_Row_0_ID_16]\nType=Blank\nWidth=25\nCluster=Function\n\n[Key_AltGr_Row_0_ID_17]\nType=VirtualKey\nLabel=Print\\nScreen\nCluster=Function\nKeyCode=44\nNoRepeat=true\n\n[Key_AltGr_Row_0_ID_18]\nType=VirtualKey\nLabel=Scroll\\nLock\nCluster=Function\nKeyCode=145\nNoRepeat=true\n\n[Key_AltGr_Row_0_ID_19]\nType=VirtualKey\nLabel=Pause\nCluster=Function\nKeyCode=19\nNoRepeat=true\n\n[Key_AltGr_Row_0_ID_20]\nType=Blank\nWidth=25\nCluster=Extra\n\n[Key_AltGr_Row_0_ID_21]\nType=VirtualKey\nLabel=⏯\nCluster=Extra\nKeyCode=179\nNoRepeat=true\n\n[Key_AltGr_Row_0_ID_22]\nType=VirtualKey\nLabel=◼\nCluster=Extra\nKeyCode=178\nNoRepeat=true\n\n[Key_AltGr_Row_0_ID_23]\nType=VirtualKey\nLabel=⏮\nCluster=Extra\nKeyCode=177\nNoRepeat=true\n\n[Key_AltGr_Row_0_ID_24]\nType=VirtualKey\nLabel=⏭\nCluster=Extra\nKeyCode=176\nNoRepeat=true\n\n[Key_AltGr_Row_1_ID_0]\nType=Blank\nHeight=25\nCluster=Function\n\n[Key_AltGr_Row_1_ID_1]\nType=Blank\nHeight=25\nCluster=Extra\n\n[Key_AltGr_Row_2_ID_0]\nType=String\nLabel=\nString=\n\n[Key_AltGr_Row_2_ID_1]\nType=VirtualKey\nLabel=\nKeyCode=0\n\n[Key_AltGr_Row_2_ID_2]\nType=String\nLabel=@\nString=@\n\n[Key_AltGr_Row_2_ID_3]\nType=String\nLabel=£\nString=£\n\n[Key_AltGr_Row_2_ID_4]\nType=String\nLabel=$\nString=$\n\n[Key_AltGr_Row_2_ID_5]\nType=String\nLabel=€\nString=€\n\n[Key_AltGr_Row_2_ID_6]\nType=String\nLabel=\nString=\n\n[Key_AltGr_Row_2_ID_7]\nType=String\nLabel={\nString={\n\n[Key_AltGr_Row_2_ID_8]\nType=String\nLabel=[\nString=[\n\n[Key_AltGr_Row_2_ID_9]\nType=String\nLabel=]\nString=]\n\n[Key_AltGr_Row_2_ID_10]\nType=String\nLabel=}\nString=}\n\n[Key_AltGr_Row_2_ID_11]\nType=String\nLabel=\nString=\n\n[Key_AltGr_Row_2_ID_12]\nType=String\nLabel=|\nString=|\n\n[Key_AltGr_Row_2_ID_13]\nType=VirtualKey\nWidth=200\nLabel=Backspace\nKeyCode=8\n\n[Key_AltGr_Row_2_ID_14]\nType=Blank\nWidth=25\nCluster=Navigation\n\n[Key_AltGr_Row_2_ID_15]\nType=VirtualKey\nLabel=Insert\nCluster=Navigation\nKeyCode=45\n\n[Key_AltGr_Row_2_ID_16]\nType=VirtualKey\nLabel=Home\nCluster=Navigation\nKeyCode=36\n\n[Key_AltGr_Row_2_ID_17]\nType=VirtualKey\nLabel=PgUp\nCluster=Navigation\nKeyCode=33\n\n[Key_AltGr_Row_2_ID_18]\nType=Blank\nWidth=25\nCluster=Numpad\n\n[Key_AltGr_Row_2_ID_19]\nType=VirtualKey\nLabel=Num\\nLock\nCluster=Numpad\nKeyCode=144\nNoRepeat=true\n\n[Key_AltGr_Row_2_ID_20]\nType=VirtualKey\nLabel=/\nCluster=Numpad\nKeyCode=111\n\n[Key_AltGr_Row_2_ID_21]\nType=VirtualKey\nLabel=*\nCluster=Numpad\nKeyCode=106\n\n[Key_AltGr_Row_2_ID_22]\nType=VirtualKey\nLabel=-\nCluster=Numpad\nKeyCode=109\n\n[Key_AltGr_Row_3_ID_0]\nType=VirtualKey\nWidth=150\nLabel=Tab\nKeyCode=9\n\n[Key_AltGr_Row_3_ID_1]\nType=VirtualKey\nLabel=\nKeyCode=0\n\n[Key_AltGr_Row_3_ID_2]\nType=VirtualKey\nLabel=\nKeyCode=0\n\n[Key_AltGr_Row_3_ID_3]\nType=VirtualKey\nLabel=\nKeyCode=0\n\n[Key_AltGr_Row_3_ID_4]\nType=VirtualKey\nLabel=\nKeyCode=0\n\n[Key_AltGr_Row_3_ID_5]\nType=VirtualKey\nLabel=\nKeyCode=0\n\n[Key_AltGr_Row_3_ID_6]\nType=VirtualKey\nLabel=\nKeyCode=0\n\n[Key_AltGr_Row_3_ID_7]\nType=VirtualKey\nLabel=\nKeyCode=0\n\n[Key_AltGr_Row_3_ID_8]\nType=VirtualKey\nLabel=\nKeyCode=0\n\n[Key_AltGr_Row_3_ID_9]\nType=VirtualKey\nLabel=\nKeyCode=0\n\n[Key_AltGr_Row_3_ID_10]\nType=VirtualKey\nLabel=\nKeyCode=0\n\n[Key_AltGr_Row_3_ID_11]\nType=VirtualKey\nLabel=\nKeyCode=0\n\n[Key_AltGr_Row_3_ID_12]\nType=String\nLabel=~\nString=~\n\n[Key_AltGr_Row_3_ID_13]\nType=VirtualKeyIsoEnter\nWidth=150\nLabel=Enter\nKeyCode=13\n\n[Key_AltGr_Row_3_ID_14]\nType=Blank\nWidth=25\nCluster=Navigation\n\n[Key_AltGr_Row_3_ID_15]\nType=VirtualKey\nLabel=Delete\nCluster=Navigation\nKeyCode=46\n\n[Key_AltGr_Row_3_ID_16]\nType=VirtualKey\nLabel=End\nCluster=Navigation\nKeyCode=35\n\n[Key_AltGr_Row_3_ID_17]\nType=VirtualKey\nLabel=PgDn\nCluster=Navigation\nKeyCode=34\n\n[Key_AltGr_Row_3_ID_18]\nType=Blank\nWidth=25\nCluster=Numpad\n\n[Key_AltGr_Row_3_ID_19]\nType=VirtualKey\nLabel=7\nCluster=Numpad\nKeyCode=103\n\n[Key_AltGr_Row_3_ID_20]\nType=VirtualKey\nLabel=8\nCluster=Numpad\nKeyCode=56\n\n[Key_AltGr_Row_3_ID_21]\nType=VirtualKey\nLabel=9\nCluster=Numpad\nKeyCode=105\n\n[Key_AltGr_Row_3_ID_22]\nType=VirtualKey\nHeight=200\nLabel=+\nCluster=Numpad\nKeyCode=107\n\n[Key_AltGr_Row_4_ID_0]\nType=VirtualKey\nWidth=175\nLabel=Caps Lock\nKeyCode=20\nNoRepeat=true\n\n[Key_AltGr_Row_4_ID_1]\nType=VirtualKey\nLabel=\nKeyCode=0\n\n[Key_AltGr_Row_4_ID_2]\nType=VirtualKey\nLabel=\nKeyCode=0\n\n[Key_AltGr_Row_4_ID_3]\nType=VirtualKey\nLabel=\nKeyCode=0\n\n[Key_AltGr_Row_4_ID_4]\nType=VirtualKey\nLabel=\nKeyCode=0\n\n[Key_AltGr_Row_4_ID_5]\nType=VirtualKey\nLabel=\nKeyCode=0\n\n[Key_AltGr_Row_4_ID_6]\nType=VirtualKey\nLabel=\nKeyCode=0\n\n[Key_AltGr_Row_4_ID_7]\nType=VirtualKey\nLabel=\nKeyCode=0\n\n[Key_AltGr_Row_4_ID_8]\nType=VirtualKey\nLabel=\nKeyCode=0\n\n[Key_AltGr_Row_4_ID_9]\nType=VirtualKey\nLabel=\nKeyCode=0\n\n[Key_AltGr_Row_4_ID_10]\nType=VirtualKey\nLabel=\nKeyCode=0\n\n[Key_AltGr_Row_4_ID_11]\nType=VirtualKey\nLabel=\nKeyCode=0\n\n[Key_AltGr_Row_4_ID_12]\nType=String\nLabel=\nString=\n\n[Key_AltGr_Row_4_ID_13]\nType=VirtualKeyIsoEnter\nWidth=125\nLabel=Enter\nKeyCode=13\n\n[Key_AltGr_Row_4_ID_14]\nType=Blank\nWidth=325\nCluster=Navigation\n\n[Key_AltGr_Row_4_ID_15]\nType=Blank\nWidth=25\nCluster=Numpad\n\n[Key_AltGr_Row_4_ID_16]\nType=VirtualKey\nLabel=4\nCluster=Numpad\nKeyCode=100\n\n[Key_AltGr_Row_4_ID_17]\nType=VirtualKey\nLabel=5\nCluster=Numpad\nKeyCode=101\n\n[Key_AltGr_Row_4_ID_18]\nType=VirtualKey\nLabel=6\nCluster=Numpad\nKeyCode=102\n\n[Key_AltGr_Row_5_ID_0]\nType=VirtualKeyToggle\nWidth=125\nLabel=Shift\nKeyCode=160\nNoRepeat=true\n\n[Key_AltGr_Row_5_ID_1]\nType=String\nLabel=\\\nString=\\\n\n[Key_AltGr_Row_5_ID_2]\nType=VirtualKey\nLabel=\nKeyCode=0\n\n[Key_AltGr_Row_5_ID_3]\nType=VirtualKey\nLabel=\nKeyCode=0\n\n[Key_AltGr_Row_5_ID_4]\nType=VirtualKey\nLabel=\nKeyCode=0\n\n[Key_AltGr_Row_5_ID_5]\nType=VirtualKey\nLabel=\nKeyCode=0\n\n[Key_AltGr_Row_5_ID_6]\nType=VirtualKey\nLabel=\nKeyCode=0\n\n[Key_AltGr_Row_5_ID_7]\nType=VirtualKey\nLabel=\nKeyCode=0\n\n[Key_AltGr_Row_5_ID_8]\nType=VirtualKey\nLabel=\nKeyCode=0\n\n[Key_AltGr_Row_5_ID_9]\nType=String\nLabel=\nString=\n\n[Key_AltGr_Row_5_ID_10]\nType=String\nLabel=\nString=\n\n[Key_AltGr_Row_5_ID_11]\nType=String\nLabel=\nString=\n\n[Key_AltGr_Row_5_ID_12]\nType=VirtualKeyToggle\nWidth=275\nLabel=Shift\nKeyCode=161\nNoRepeat=true\n\n[Key_AltGr_Row_5_ID_13]\nType=Blank\nWidth=125\nCluster=Navigation\n\n[Key_AltGr_Row_5_ID_14]\nType=VirtualKey\nLabel=🠅\nCluster=Navigation\nKeyCode=38\n\n[Key_AltGr_Row_5_ID_15]\nType=Blank\nCluster=Navigation\n\n[Key_AltGr_Row_5_ID_16]\nType=Blank\nWidth=25\nCluster=Numpad\n\n[Key_AltGr_Row_5_ID_17]\nType=VirtualKey\nLabel=1\nCluster=Numpad\nKeyCode=97\n\n[Key_AltGr_Row_5_ID_18]\nType=VirtualKey\nLabel=2\nCluster=Numpad\nKeyCode=98\n\n[Key_AltGr_Row_5_ID_19]\nType=VirtualKey\nLabel=3\nCluster=Numpad\nKeyCode=99\n\n[Key_AltGr_Row_5_ID_20]\nType=VirtualKey\nHeight=200\nLabel=Enter\nCluster=Numpad\nKeyCode=13\n\n[Key_AltGr_Row_6_ID_0]\nType=VirtualKeyToggle\nWidth=125\nLabel=Ctrl\nKeyCode=162\nNoRepeat=true\n\n[Key_AltGr_Row_6_ID_1]\nType=VirtualKeyToggle\nWidth=125\nLabel=Win\nKeyCode=91\nNoRepeat=true\n\n[Key_AltGr_Row_6_ID_2]\nType=VirtualKeyToggle\nWidth=125\nLabel=Alt\nKeyCode=164\nNoRepeat=true\n\n[Key_AltGr_Row_6_ID_3]\nType=VirtualKey\nWidth=625\nLabel=\nKeyCode=32\n\n[Key_AltGr_Row_6_ID_4]\nType=VirtualKeyToggle\nWidth=125\nLabel=AltGr\nKeyCode=165\nNoRepeat=true\n\n[Key_AltGr_Row_6_ID_5]\nType=VirtualKeyToggle\nWidth=125\nLabel=Win\nKeyCode=92\nNoRepeat=true\n\n[Key_AltGr_Row_6_ID_6]\nType=VirtualKey\nWidth=125\nLabel=Menu\nKeyCode=93\nNoRepeat=true\n\n[Key_AltGr_Row_6_ID_7]\nType=VirtualKeyToggle\nWidth=125\nLabel=Ctrl\nKeyCode=163\nNoRepeat=true\n\n[Key_AltGr_Row_6_ID_8]\nType=Blank\nWidth=25\nCluster=Navigation\n\n[Key_AltGr_Row_6_ID_9]\nType=VirtualKey\nLabel=🠄\nCluster=Navigation\nKeyCode=37\n\n[Key_AltGr_Row_6_ID_10]\nType=VirtualKey\nLabel=🠇\nCluster=Navigation\nKeyCode=40\n\n[Key_AltGr_Row_6_ID_11]\nType=VirtualKey\nLabel=🠆\nCluster=Navigation\nKeyCode=39\n\n[Key_AltGr_Row_6_ID_12]\nType=Blank\nWidth=25\nCluster=Numpad\n\n[Key_AltGr_Row_6_ID_13]\nType=VirtualKey\nWidth=200\nLabel=0\nCluster=Numpad\nKeyCode=96\n\n[Key_AltGr_Row_6_ID_14]\nType=VirtualKey\nLabel=.\nCluster=Numpad\nKeyCode=110\n\n"
  },
  {
    "path": "assets/keyboards/qwerty_es.ini",
    "content": "[LayoutInfo]\nName=QWERTY (Spain)\nAuthor=\nHasAltGr=true\nHasClusterFunction=true\nHasClusterNavigation=true\nHasClusterNumpad=true\nHasClusterExtra=true\n\n[Key_Base_Row_0_ID_0]\nType=VirtualKey\nLabel=Esc\nCluster=Function\nKeyCode=27\nNoRepeat=true\n\n[Key_Base_Row_0_ID_1]\nType=Blank\nCluster=Function\n\n[Key_Base_Row_0_ID_2]\nType=VirtualKey\nLabel=F1\nCluster=Function\nKeyCode=112\n\n[Key_Base_Row_0_ID_3]\nType=VirtualKey\nLabel=F2\nCluster=Function\nKeyCode=113\n\n[Key_Base_Row_0_ID_4]\nType=VirtualKey\nLabel=F3\nCluster=Function\nKeyCode=114\n\n[Key_Base_Row_0_ID_5]\nType=VirtualKey\nLabel=F4\nCluster=Function\nKeyCode=115\n\n[Key_Base_Row_0_ID_6]\nType=Blank\nWidth=50\nCluster=Function\n\n[Key_Base_Row_0_ID_7]\nType=VirtualKey\nLabel=F5\nCluster=Function\nKeyCode=116\n\n[Key_Base_Row_0_ID_8]\nType=VirtualKey\nLabel=F6\nCluster=Function\nKeyCode=117\n\n[Key_Base_Row_0_ID_9]\nType=VirtualKey\nLabel=F7\nCluster=Function\nKeyCode=118\n\n[Key_Base_Row_0_ID_10]\nType=VirtualKey\nLabel=F8\nCluster=Function\nKeyCode=119\n\n[Key_Base_Row_0_ID_11]\nType=Blank\nWidth=50\nCluster=Function\n\n[Key_Base_Row_0_ID_12]\nType=VirtualKey\nLabel=F9\nCluster=Function\nKeyCode=120\n\n[Key_Base_Row_0_ID_13]\nType=VirtualKey\nLabel=F10\nCluster=Function\nKeyCode=121\n\n[Key_Base_Row_0_ID_14]\nType=VirtualKey\nLabel=F11\nCluster=Function\nKeyCode=122\n\n[Key_Base_Row_0_ID_15]\nType=VirtualKey\nLabel=F12\nCluster=Function\nKeyCode=123\n\n[Key_Base_Row_0_ID_16]\nType=Blank\nWidth=25\nCluster=Function\n\n[Key_Base_Row_0_ID_17]\nType=VirtualKey\nLabel=Impr\\nPant\nCluster=Function\nKeyCode=44\nNoRepeat=true\n\n[Key_Base_Row_0_ID_18]\nType=VirtualKey\nLabel=Bloq\\nDespl\nCluster=Function\nKeyCode=145\nNoRepeat=true\n\n[Key_Base_Row_0_ID_19]\nType=VirtualKey\nLabel=Pausa\nCluster=Function\nKeyCode=19\nNoRepeat=true\n\n[Key_Base_Row_0_ID_20]\nType=Blank\nWidth=25\nCluster=Extra\n\n[Key_Base_Row_0_ID_21]\nType=VirtualKey\nLabel=⏯\nCluster=Extra\nKeyCode=179\nNoRepeat=true\n\n[Key_Base_Row_0_ID_22]\nType=VirtualKey\nLabel=◼\nCluster=Extra\nKeyCode=178\nNoRepeat=true\n\n[Key_Base_Row_0_ID_23]\nType=VirtualKey\nLabel=⏮\nCluster=Extra\nKeyCode=177\nNoRepeat=true\n\n[Key_Base_Row_0_ID_24]\nType=VirtualKey\nLabel=⏭\nCluster=Extra\nKeyCode=176\nNoRepeat=true\n\n[Key_Base_Row_1_ID_0]\nType=Blank\nHeight=25\nCluster=Function\n\n[Key_Base_Row_1_ID_1]\nType=Blank\nHeight=25\nCluster=Extra\n\n[Key_Base_Row_2_ID_0]\nType=String\nLabel=º\nString=º\n\n[Key_Base_Row_2_ID_1]\nType=VirtualKey\nLabel=1\nKeyCode=49\n\n[Key_Base_Row_2_ID_2]\nType=VirtualKey\nLabel=2\nKeyCode=50\n\n[Key_Base_Row_2_ID_3]\nType=VirtualKey\nLabel=3\nKeyCode=51\n\n[Key_Base_Row_2_ID_4]\nType=VirtualKey\nLabel=4\nKeyCode=52\n\n[Key_Base_Row_2_ID_5]\nType=VirtualKey\nLabel=5\nKeyCode=53\n\n[Key_Base_Row_2_ID_6]\nType=VirtualKey\nLabel=6\nKeyCode=54\n\n[Key_Base_Row_2_ID_7]\nType=VirtualKey\nLabel=7\nKeyCode=55\n\n[Key_Base_Row_2_ID_8]\nType=VirtualKey\nLabel=8\nKeyCode=56\n\n[Key_Base_Row_2_ID_9]\nType=VirtualKey\nLabel=9\nKeyCode=57\n\n[Key_Base_Row_2_ID_10]\nType=VirtualKey\nLabel=0\nKeyCode=48\n\n[Key_Base_Row_2_ID_11]\nType=String\nLabel='\nString='\n\n[Key_Base_Row_2_ID_12]\nType=String\nLabel=¡\nString=¡\n\n[Key_Base_Row_2_ID_13]\nType=VirtualKey\nWidth=200\nLabel=⟵\nKeyCode=8\n\n[Key_Base_Row_2_ID_14]\nType=Blank\nWidth=25\nCluster=Navigation\n\n[Key_Base_Row_2_ID_15]\nType=VirtualKey\nLabel=Insert\nCluster=Navigation\nKeyCode=45\n\n[Key_Base_Row_2_ID_16]\nType=VirtualKey\nLabel=Incio\nCluster=Navigation\nKeyCode=36\n\n[Key_Base_Row_2_ID_17]\nType=VirtualKey\nLabel=RePág\nCluster=Navigation\nKeyCode=33\n\n[Key_Base_Row_2_ID_18]\nType=Blank\nWidth=25\nCluster=Numpad\n\n[Key_Base_Row_2_ID_19]\nType=VirtualKey\nLabel=Bloq\\nNum\nCluster=Numpad\nKeyCode=144\nNoRepeat=true\n\n[Key_Base_Row_2_ID_20]\nType=VirtualKey\nLabel=/\nCluster=Numpad\nKeyCode=111\n\n[Key_Base_Row_2_ID_21]\nType=VirtualKey\nLabel=*\nCluster=Numpad\nKeyCode=106\n\n[Key_Base_Row_2_ID_22]\nType=VirtualKey\nLabel=-\nCluster=Numpad\nKeyCode=109\n\n[Key_Base_Row_3_ID_0]\nType=VirtualKey\nWidth=150\nLabel=⭾\nKeyCode=9\n\n[Key_Base_Row_3_ID_1]\nType=VirtualKey\nLabel=q\nKeyCode=81\n\n[Key_Base_Row_3_ID_2]\nType=VirtualKey\nLabel=w\nKeyCode=87\n\n[Key_Base_Row_3_ID_3]\nType=VirtualKey\nLabel=e\nKeyCode=69\n\n[Key_Base_Row_3_ID_4]\nType=VirtualKey\nLabel=r\nKeyCode=82\n\n[Key_Base_Row_3_ID_5]\nType=VirtualKey\nLabel=t\nKeyCode=84\n\n[Key_Base_Row_3_ID_6]\nType=VirtualKey\nLabel=y\nKeyCode=89\n\n[Key_Base_Row_3_ID_7]\nType=VirtualKey\nLabel=u\nKeyCode=85\n\n[Key_Base_Row_3_ID_8]\nType=VirtualKey\nLabel=i\nKeyCode=73\n\n[Key_Base_Row_3_ID_9]\nType=VirtualKey\nLabel=o\nKeyCode=79\n\n[Key_Base_Row_3_ID_10]\nType=VirtualKey\nLabel=p\nKeyCode=80\n\n[Key_Base_Row_3_ID_11]\nType=String\nLabel=`\nString=`\n\n[Key_Base_Row_3_ID_12]\nType=String\nLabel=+\nString=+\n\n[Key_Base_Row_3_ID_13]\nType=VirtualKeyIsoEnter\nWidth=150\nLabel=\nKeyCode=13\n\n[Key_Base_Row_3_ID_14]\nType=Blank\nWidth=25\nCluster=Navigation\n\n[Key_Base_Row_3_ID_15]\nType=VirtualKey\nLabel=Supr\nCluster=Navigation\nKeyCode=46\n\n[Key_Base_Row_3_ID_16]\nType=VirtualKey\nLabel=Fin\nCluster=Navigation\nKeyCode=35\n\n[Key_Base_Row_3_ID_17]\nType=VirtualKey\nLabel=AvPág\nCluster=Navigation\nKeyCode=34\n\n[Key_Base_Row_3_ID_18]\nType=Blank\nWidth=25\nCluster=Numpad\n\n[Key_Base_Row_3_ID_19]\nType=VirtualKey\nLabel=7\nCluster=Numpad\nKeyCode=103\n\n[Key_Base_Row_3_ID_20]\nType=VirtualKey\nLabel=8\nCluster=Numpad\nKeyCode=104\n\n[Key_Base_Row_3_ID_21]\nType=VirtualKey\nLabel=9\nCluster=Numpad\nKeyCode=105\n\n[Key_Base_Row_3_ID_22]\nType=VirtualKey\nHeight=200\nLabel=+\nCluster=Numpad\nKeyCode=107\n\n[Key_Base_Row_4_ID_0]\nType=VirtualKey\nWidth=175\nLabel=Bloq Mayús\nKeyCode=20\nNoRepeat=true\n\n[Key_Base_Row_4_ID_1]\nType=VirtualKey\nLabel=a\nKeyCode=65\n\n[Key_Base_Row_4_ID_2]\nType=VirtualKey\nLabel=s\nKeyCode=83\n\n[Key_Base_Row_4_ID_3]\nType=VirtualKey\nLabel=d\nKeyCode=68\n\n[Key_Base_Row_4_ID_4]\nType=VirtualKey\nLabel=f\nKeyCode=70\n\n[Key_Base_Row_4_ID_5]\nType=VirtualKey\nLabel=g\nKeyCode=71\n\n[Key_Base_Row_4_ID_6]\nType=VirtualKey\nLabel=h\nKeyCode=72\n\n[Key_Base_Row_4_ID_7]\nType=VirtualKey\nLabel=j\nKeyCode=74\n\n[Key_Base_Row_4_ID_8]\nType=VirtualKey\nLabel=k\nKeyCode=75\n\n[Key_Base_Row_4_ID_9]\nType=VirtualKey\nLabel=l\nKeyCode=76\n\n[Key_Base_Row_4_ID_10]\nType=String\nLabel=ñ\nString=ñ\n\n[Key_Base_Row_4_ID_11]\nType=String\nLabel=´\nString=´\n\n[Key_Base_Row_4_ID_12]\nType=String\nLabel=ç\nString=ç\n\n[Key_Base_Row_4_ID_13]\nType=VirtualKeyIsoEnter\nWidth=125\nLabel=↵\nKeyCode=13\n\n[Key_Base_Row_4_ID_14]\nType=Blank\nWidth=325\nCluster=Navigation\n\n[Key_Base_Row_4_ID_15]\nType=Blank\nWidth=25\nCluster=Numpad\n\n[Key_Base_Row_4_ID_16]\nType=VirtualKey\nLabel=4\nCluster=Numpad\nKeyCode=100\n\n[Key_Base_Row_4_ID_17]\nType=VirtualKey\nLabel=5\nCluster=Numpad\nKeyCode=101\n\n[Key_Base_Row_4_ID_18]\nType=VirtualKey\nLabel=6\nCluster=Numpad\nKeyCode=102\n\n[Key_Base_Row_5_ID_0]\nType=VirtualKeyToggle\nWidth=125\nLabel=🡅\nKeyCode=160\nNoRepeat=true\n\n[Key_Base_Row_5_ID_1]\nType=String\nLabel=<\nString=<\n\n[Key_Base_Row_5_ID_2]\nType=VirtualKey\nLabel=z\nKeyCode=90\n\n[Key_Base_Row_5_ID_3]\nType=VirtualKey\nLabel=x\nKeyCode=88\n\n[Key_Base_Row_5_ID_4]\nType=VirtualKey\nLabel=c\nKeyCode=67\n\n[Key_Base_Row_5_ID_5]\nType=VirtualKey\nLabel=v\nKeyCode=86\n\n[Key_Base_Row_5_ID_6]\nType=VirtualKey\nLabel=b\nKeyCode=66\n\n[Key_Base_Row_5_ID_7]\nType=VirtualKey\nLabel=n\nKeyCode=78\n\n[Key_Base_Row_5_ID_8]\nType=VirtualKey\nLabel=m\nKeyCode=77\n\n[Key_Base_Row_5_ID_9]\nType=String\nLabel=,\nString=,\n\n[Key_Base_Row_5_ID_10]\nType=String\nLabel=.\nString=.\n\n[Key_Base_Row_5_ID_11]\nType=String\nLabel=-\nString=-\n\n[Key_Base_Row_5_ID_12]\nType=VirtualKeyToggle\nWidth=275\nLabel=🡅\nKeyCode=161\nNoRepeat=true\n\n[Key_Base_Row_5_ID_13]\nType=Blank\nWidth=125\nCluster=Navigation\n\n[Key_Base_Row_5_ID_14]\nType=VirtualKey\nLabel=🠅\nCluster=Navigation\nKeyCode=38\n\n[Key_Base_Row_5_ID_15]\nType=Blank\nCluster=Navigation\n\n[Key_Base_Row_5_ID_16]\nType=Blank\nWidth=25\nCluster=Numpad\n\n[Key_Base_Row_5_ID_17]\nType=VirtualKey\nLabel=1\nCluster=Numpad\nKeyCode=97\n\n[Key_Base_Row_5_ID_18]\nType=VirtualKey\nLabel=2\nCluster=Numpad\nKeyCode=98\n\n[Key_Base_Row_5_ID_19]\nType=VirtualKey\nLabel=3\nCluster=Numpad\nKeyCode=99\n\n[Key_Base_Row_5_ID_20]\nType=VirtualKey\nHeight=200\nLabel=Intro\nCluster=Numpad\nKeyCode=13\n\n[Key_Base_Row_6_ID_0]\nType=VirtualKeyToggle\nWidth=125\nLabel=Ctrl\nKeyCode=162\nNoRepeat=true\n\n[Key_Base_Row_6_ID_1]\nType=VirtualKeyToggle\nWidth=125\nLabel=Win\nKeyCode=91\nNoRepeat=true\n\n[Key_Base_Row_6_ID_2]\nType=VirtualKeyToggle\nWidth=125\nLabel=Alt\nKeyCode=164\nNoRepeat=true\n\n[Key_Base_Row_6_ID_3]\nType=VirtualKey\nWidth=625\nLabel=\nKeyCode=32\n\n[Key_Base_Row_6_ID_4]\nType=VirtualKeyToggle\nWidth=125\nLabel=AltGr\nKeyCode=165\nNoRepeat=true\n\n[Key_Base_Row_6_ID_5]\nType=VirtualKeyToggle\nWidth=125\nLabel=Win\nKeyCode=92\nNoRepeat=true\n\n[Key_Base_Row_6_ID_6]\nType=VirtualKey\nWidth=125\nLabel=Menu\nKeyCode=93\nNoRepeat=true\n\n[Key_Base_Row_6_ID_7]\nType=VirtualKeyToggle\nWidth=125\nLabel=Ctrl\nKeyCode=163\nNoRepeat=true\n\n[Key_Base_Row_6_ID_8]\nType=Blank\nWidth=25\nCluster=Navigation\n\n[Key_Base_Row_6_ID_9]\nType=VirtualKey\nLabel=🠄\nCluster=Navigation\nKeyCode=37\n\n[Key_Base_Row_6_ID_10]\nType=VirtualKey\nLabel=🠇\nCluster=Navigation\nKeyCode=40\n\n[Key_Base_Row_6_ID_11]\nType=VirtualKey\nLabel=🠆\nCluster=Navigation\nKeyCode=39\n\n[Key_Base_Row_6_ID_12]\nType=Blank\nWidth=25\nCluster=Numpad\n\n[Key_Base_Row_6_ID_13]\nType=VirtualKey\nWidth=200\nLabel=0\nCluster=Numpad\nKeyCode=96\n\n[Key_Base_Row_6_ID_14]\nType=VirtualKey\nLabel=.\nCluster=Numpad\nKeyCode=110\n\n[Key_Shift_Row_0_ID_0]\nType=VirtualKey\nLabel=Esc\nCluster=Function\nKeyCode=27\nNoRepeat=true\n\n[Key_Shift_Row_0_ID_1]\nType=Blank\nCluster=Function\n\n[Key_Shift_Row_0_ID_2]\nType=VirtualKey\nLabel=F1\nCluster=Function\nKeyCode=112\n\n[Key_Shift_Row_0_ID_3]\nType=VirtualKey\nLabel=F2\nCluster=Function\nKeyCode=113\n\n[Key_Shift_Row_0_ID_4]\nType=VirtualKey\nLabel=F3\nCluster=Function\nKeyCode=114\n\n[Key_Shift_Row_0_ID_5]\nType=VirtualKey\nLabel=F4\nCluster=Function\nKeyCode=115\n\n[Key_Shift_Row_0_ID_6]\nType=Blank\nWidth=50\nCluster=Function\n\n[Key_Shift_Row_0_ID_7]\nType=VirtualKey\nLabel=F5\nCluster=Function\nKeyCode=116\n\n[Key_Shift_Row_0_ID_8]\nType=VirtualKey\nLabel=F6\nCluster=Function\nKeyCode=117\n\n[Key_Shift_Row_0_ID_9]\nType=VirtualKey\nLabel=F7\nCluster=Function\nKeyCode=118\n\n[Key_Shift_Row_0_ID_10]\nType=VirtualKey\nLabel=F8\nCluster=Function\nKeyCode=119\n\n[Key_Shift_Row_0_ID_11]\nType=Blank\nWidth=50\nCluster=Function\n\n[Key_Shift_Row_0_ID_12]\nType=VirtualKey\nLabel=F9\nCluster=Function\nKeyCode=120\n\n[Key_Shift_Row_0_ID_13]\nType=VirtualKey\nLabel=F10\nCluster=Function\nKeyCode=121\n\n[Key_Shift_Row_0_ID_14]\nType=VirtualKey\nLabel=F11\nCluster=Function\nKeyCode=122\n\n[Key_Shift_Row_0_ID_15]\nType=VirtualKey\nLabel=F12\nCluster=Function\nKeyCode=123\n\n[Key_Shift_Row_0_ID_16]\nType=Blank\nWidth=25\nCluster=Function\n\n[Key_Shift_Row_0_ID_17]\nType=VirtualKey\nLabel=Impr\\nPant\nCluster=Function\nKeyCode=44\nNoRepeat=true\n\n[Key_Shift_Row_0_ID_18]\nType=VirtualKey\nLabel=Bloq\\nDespl\nCluster=Function\nKeyCode=145\nNoRepeat=true\n\n[Key_Shift_Row_0_ID_19]\nType=VirtualKey\nLabel=Pausa\nCluster=Function\nKeyCode=19\nNoRepeat=true\n\n[Key_Shift_Row_0_ID_20]\nType=Blank\nWidth=25\nCluster=Extra\n\n[Key_Shift_Row_0_ID_21]\nType=VirtualKey\nLabel=🡰\nCluster=Extra\nKeyCode=166\nNoRepeat=true\n\n[Key_Shift_Row_0_ID_22]\nType=VirtualKey\nLabel=🡲\nCluster=Extra\nKeyCode=167\nNoRepeat=true\n\n[Key_Shift_Row_0_ID_23]\nType=VirtualKey\nLabel=🔇\nCluster=Extra\nKeyCode=173\nNoRepeat=true\n\n[Key_Shift_Row_1_ID_0]\nType=Blank\nHeight=25\nCluster=Function\n\n[Key_Shift_Row_1_ID_1]\nType=Blank\nHeight=25\nCluster=Extra\n\n[Key_Shift_Row_2_ID_0]\nType=String\nLabel=ª\nString=ª\n\n[Key_Shift_Row_2_ID_1]\nType=String\nLabel=!\nString=!\n\n[Key_Shift_Row_2_ID_2]\nType=String\nLabel=\"\nString=\"\n\n[Key_Shift_Row_2_ID_3]\nType=String\nLabel=·\nString=·\n\n[Key_Shift_Row_2_ID_4]\nType=String\nLabel=$\nString=$\n\n[Key_Shift_Row_2_ID_5]\nType=String\nLabel=%\nString=%\n\n[Key_Shift_Row_2_ID_6]\nType=String\nLabel=&\nString=&\n\n[Key_Shift_Row_2_ID_7]\nType=String\nLabel=/\nString=/\n\n[Key_Shift_Row_2_ID_8]\nType=String\nLabel=(\nString=(\n\n[Key_Shift_Row_2_ID_9]\nType=String\nLabel=)\nString=)\n\n[Key_Shift_Row_2_ID_10]\nType=String\nLabel==\nString==\n\n[Key_Shift_Row_2_ID_11]\nType=String\nLabel=?\nString=?\n\n[Key_Shift_Row_2_ID_12]\nType=String\nLabel=¿\nString=¿\n\n[Key_Shift_Row_2_ID_13]\nType=VirtualKey\nWidth=200\nLabel=⟵\nKeyCode=8\n\n[Key_Shift_Row_2_ID_14]\nType=Blank\nWidth=25\nCluster=Navigation\n\n[Key_Shift_Row_2_ID_15]\nType=VirtualKey\nLabel=Insert\nCluster=Navigation\nKeyCode=45\n\n[Key_Shift_Row_2_ID_16]\nType=VirtualKey\nLabel=Incio\nCluster=Navigation\nKeyCode=36\n\n[Key_Shift_Row_2_ID_17]\nType=VirtualKey\nLabel=RePág\nCluster=Navigation\nKeyCode=33\n\n[Key_Shift_Row_2_ID_18]\nType=Blank\nWidth=25\nCluster=Numpad\n\n[Key_Shift_Row_2_ID_19]\nType=VirtualKey\nLabel=Bloq\\nNum\nCluster=Numpad\nKeyCode=144\nNoRepeat=true\n\n[Key_Shift_Row_2_ID_20]\nType=VirtualKey\nLabel=/\nCluster=Numpad\nKeyCode=111\n\n[Key_Shift_Row_2_ID_21]\nType=VirtualKey\nLabel=*\nCluster=Numpad\nKeyCode=106\n\n[Key_Shift_Row_2_ID_22]\nType=VirtualKey\nLabel=-\nCluster=Numpad\nKeyCode=109\n\n[Key_Shift_Row_3_ID_0]\nType=VirtualKey\nWidth=150\nLabel=⭾\nKeyCode=9\n\n[Key_Shift_Row_3_ID_1]\nType=VirtualKey\nLabel=Q\nKeyCode=81\n\n[Key_Shift_Row_3_ID_2]\nType=VirtualKey\nLabel=W\nKeyCode=87\n\n[Key_Shift_Row_3_ID_3]\nType=VirtualKey\nLabel=E\nKeyCode=69\n\n[Key_Shift_Row_3_ID_4]\nType=VirtualKey\nLabel=R\nKeyCode=82\n\n[Key_Shift_Row_3_ID_5]\nType=VirtualKey\nLabel=T\nKeyCode=84\n\n[Key_Shift_Row_3_ID_6]\nType=VirtualKey\nLabel=Y\nKeyCode=89\n\n[Key_Shift_Row_3_ID_7]\nType=VirtualKey\nLabel=U\nKeyCode=85\n\n[Key_Shift_Row_3_ID_8]\nType=VirtualKey\nLabel=I\nKeyCode=73\n\n[Key_Shift_Row_3_ID_9]\nType=VirtualKey\nLabel=O\nKeyCode=79\n\n[Key_Shift_Row_3_ID_10]\nType=VirtualKey\nLabel=P\nKeyCode=80\n\n[Key_Shift_Row_3_ID_11]\nType=String\nLabel=^\nString=^\n\n[Key_Shift_Row_3_ID_12]\nType=String\nLabel=*\nString=*\n\n[Key_Shift_Row_3_ID_13]\nType=VirtualKeyIsoEnter\nWidth=150\nLabel=\nKeyCode=13\n\n[Key_Shift_Row_3_ID_14]\nType=Blank\nWidth=25\nCluster=Navigation\n\n[Key_Shift_Row_3_ID_15]\nType=VirtualKey\nLabel=Supr\nCluster=Navigation\nKeyCode=46\n\n[Key_Shift_Row_3_ID_16]\nType=VirtualKey\nLabel=Fin\nCluster=Navigation\nKeyCode=35\n\n[Key_Shift_Row_3_ID_17]\nType=VirtualKey\nLabel=AvPág\nCluster=Navigation\nKeyCode=34\n\n[Key_Shift_Row_3_ID_18]\nType=Blank\nWidth=25\nCluster=Numpad\n\n[Key_Shift_Row_3_ID_19]\nType=VirtualKey\nLabel=7\nCluster=Numpad\nKeyCode=103\n\n[Key_Shift_Row_3_ID_20]\nType=VirtualKey\nLabel=8\nCluster=Numpad\nKeyCode=104\n\n[Key_Shift_Row_3_ID_21]\nType=VirtualKey\nLabel=9\nCluster=Numpad\nKeyCode=105\n\n[Key_Shift_Row_3_ID_22]\nType=VirtualKey\nHeight=200\nLabel=+\nCluster=Numpad\nKeyCode=107\n\n[Key_Shift_Row_4_ID_0]\nType=VirtualKey\nWidth=175\nLabel=Bloq Mayús\nKeyCode=20\nNoRepeat=true\n\n[Key_Shift_Row_4_ID_1]\nType=VirtualKey\nLabel=A\nKeyCode=65\n\n[Key_Shift_Row_4_ID_2]\nType=VirtualKey\nLabel=S\nKeyCode=83\n\n[Key_Shift_Row_4_ID_3]\nType=VirtualKey\nLabel=D\nKeyCode=68\n\n[Key_Shift_Row_4_ID_4]\nType=VirtualKey\nLabel=F\nKeyCode=70\n\n[Key_Shift_Row_4_ID_5]\nType=VirtualKey\nLabel=G\nKeyCode=71\n\n[Key_Shift_Row_4_ID_6]\nType=VirtualKey\nLabel=H\nKeyCode=72\n\n[Key_Shift_Row_4_ID_7]\nType=VirtualKey\nLabel=J\nKeyCode=74\n\n[Key_Shift_Row_4_ID_8]\nType=VirtualKey\nLabel=K\nKeyCode=75\n\n[Key_Shift_Row_4_ID_9]\nType=VirtualKey\nLabel=L\nKeyCode=76\n\n[Key_Shift_Row_4_ID_10]\nType=String\nLabel=Ñ\nString=Ñ\n\n[Key_Shift_Row_4_ID_11]\nType=String\nLabel=¨\nString=¨\n\n[Key_Shift_Row_4_ID_12]\nType=String\nLabel=Ç\nString=Ç\n\n[Key_Shift_Row_4_ID_13]\nType=VirtualKeyIsoEnter\nWidth=125\nLabel=↵\nKeyCode=13\n\n[Key_Shift_Row_4_ID_14]\nType=Blank\nWidth=325\nCluster=Navigation\n\n[Key_Shift_Row_4_ID_15]\nType=Blank\nWidth=25\nCluster=Numpad\n\n[Key_Shift_Row_4_ID_16]\nType=VirtualKey\nLabel=4\nCluster=Numpad\nKeyCode=100\n\n[Key_Shift_Row_4_ID_17]\nType=VirtualKey\nLabel=5\nCluster=Numpad\nKeyCode=101\n\n[Key_Shift_Row_4_ID_18]\nType=VirtualKey\nLabel=6\nCluster=Numpad\nKeyCode=102\n\n[Key_Shift_Row_5_ID_0]\nType=VirtualKeyToggle\nWidth=125\nLabel=🡅\nKeyCode=160\nNoRepeat=true\n\n[Key_Shift_Row_5_ID_1]\nType=String\nLabel=>\nString=>\n\n[Key_Shift_Row_5_ID_2]\nType=VirtualKey\nLabel=Z\nKeyCode=90\n\n[Key_Shift_Row_5_ID_3]\nType=VirtualKey\nLabel=X\nKeyCode=88\n\n[Key_Shift_Row_5_ID_4]\nType=VirtualKey\nLabel=C\nKeyCode=67\n\n[Key_Shift_Row_5_ID_5]\nType=VirtualKey\nLabel=V\nKeyCode=86\n\n[Key_Shift_Row_5_ID_6]\nType=VirtualKey\nLabel=B\nKeyCode=66\n\n[Key_Shift_Row_5_ID_7]\nType=VirtualKey\nLabel=N\nKeyCode=78\n\n[Key_Shift_Row_5_ID_8]\nType=VirtualKey\nLabel=M\nKeyCode=77\n\n[Key_Shift_Row_5_ID_9]\nType=String\nLabel=;\nString=;\n\n[Key_Shift_Row_5_ID_10]\nType=String\nLabel=:\nString=:\n\n[Key_Shift_Row_5_ID_11]\nType=String\nLabel=_\nString=_\n\n[Key_Shift_Row_5_ID_12]\nType=VirtualKeyToggle\nWidth=275\nLabel=🡅\nKeyCode=161\nNoRepeat=true\n\n[Key_Shift_Row_5_ID_13]\nType=Blank\nWidth=125\nCluster=Navigation\n\n[Key_Shift_Row_5_ID_14]\nType=VirtualKey\nLabel=🠅\nCluster=Navigation\nKeyCode=38\n\n[Key_Shift_Row_5_ID_15]\nType=Blank\nCluster=Navigation\n\n[Key_Shift_Row_5_ID_16]\nType=Blank\nWidth=25\nCluster=Numpad\n\n[Key_Shift_Row_5_ID_17]\nType=VirtualKey\nLabel=1\nCluster=Numpad\nKeyCode=97\n\n[Key_Shift_Row_5_ID_18]\nType=VirtualKey\nLabel=2\nCluster=Numpad\nKeyCode=98\n\n[Key_Shift_Row_5_ID_19]\nType=VirtualKey\nLabel=3\nCluster=Numpad\nKeyCode=99\n\n[Key_Shift_Row_5_ID_20]\nType=VirtualKey\nHeight=200\nLabel=Intro\nCluster=Numpad\nKeyCode=13\n\n[Key_Shift_Row_6_ID_0]\nType=VirtualKeyToggle\nWidth=125\nLabel=Ctrl\nKeyCode=162\nNoRepeat=true\n\n[Key_Shift_Row_6_ID_1]\nType=VirtualKeyToggle\nWidth=125\nLabel=Win\nKeyCode=91\nNoRepeat=true\n\n[Key_Shift_Row_6_ID_2]\nType=VirtualKeyToggle\nWidth=125\nLabel=Alt\nKeyCode=164\nNoRepeat=true\n\n[Key_Shift_Row_6_ID_3]\nType=VirtualKey\nWidth=625\nLabel=\nKeyCode=32\n\n[Key_Shift_Row_6_ID_4]\nType=VirtualKeyToggle\nWidth=125\nLabel=AltGr\nKeyCode=165\nNoRepeat=true\n\n[Key_Shift_Row_6_ID_5]\nType=VirtualKeyToggle\nWidth=125\nLabel=Win\nKeyCode=92\nNoRepeat=true\n\n[Key_Shift_Row_6_ID_6]\nType=VirtualKey\nWidth=125\nLabel=Menu\nKeyCode=93\nNoRepeat=true\n\n[Key_Shift_Row_6_ID_7]\nType=VirtualKeyToggle\nWidth=125\nLabel=Ctrl\nKeyCode=163\nNoRepeat=true\n\n[Key_Shift_Row_6_ID_8]\nType=Blank\nWidth=25\nCluster=Navigation\n\n[Key_Shift_Row_6_ID_9]\nType=VirtualKey\nLabel=🠄\nCluster=Navigation\nKeyCode=37\n\n[Key_Shift_Row_6_ID_10]\nType=VirtualKey\nLabel=🠇\nCluster=Navigation\nKeyCode=40\n\n[Key_Shift_Row_6_ID_11]\nType=VirtualKey\nLabel=🠆\nCluster=Navigation\nKeyCode=39\n\n[Key_Shift_Row_6_ID_12]\nType=Blank\nWidth=25\nCluster=Numpad\n\n[Key_Shift_Row_6_ID_13]\nType=VirtualKey\nWidth=200\nLabel=0\nCluster=Numpad\nKeyCode=96\n\n[Key_Shift_Row_6_ID_14]\nType=VirtualKey\nLabel=.\nCluster=Numpad\nKeyCode=110\n\n[Key_AltGr_Row_0_ID_0]\nType=VirtualKey\nLabel=Esc\nCluster=Function\nKeyCode=27\nNoRepeat=true\n\n[Key_AltGr_Row_0_ID_1]\nType=Blank\nCluster=Function\n\n[Key_AltGr_Row_0_ID_2]\nType=VirtualKey\nLabel=F1\nCluster=Function\nKeyCode=112\n\n[Key_AltGr_Row_0_ID_3]\nType=VirtualKey\nLabel=F2\nCluster=Function\nKeyCode=113\n\n[Key_AltGr_Row_0_ID_4]\nType=VirtualKey\nLabel=F3\nCluster=Function\nKeyCode=114\n\n[Key_AltGr_Row_0_ID_5]\nType=VirtualKey\nLabel=F4\nCluster=Function\nKeyCode=115\n\n[Key_AltGr_Row_0_ID_6]\nType=Blank\nWidth=50\nCluster=Function\n\n[Key_AltGr_Row_0_ID_7]\nType=VirtualKey\nLabel=F5\nCluster=Function\nKeyCode=116\n\n[Key_AltGr_Row_0_ID_8]\nType=VirtualKey\nLabel=F6\nCluster=Function\nKeyCode=117\n\n[Key_AltGr_Row_0_ID_9]\nType=VirtualKey\nLabel=F7\nCluster=Function\nKeyCode=118\n\n[Key_AltGr_Row_0_ID_10]\nType=VirtualKey\nLabel=F8\nCluster=Function\nKeyCode=119\n\n[Key_AltGr_Row_0_ID_11]\nType=Blank\nWidth=50\nCluster=Function\n\n[Key_AltGr_Row_0_ID_12]\nType=VirtualKey\nLabel=F9\nCluster=Function\nKeyCode=120\n\n[Key_AltGr_Row_0_ID_13]\nType=VirtualKey\nLabel=F10\nCluster=Function\nKeyCode=121\n\n[Key_AltGr_Row_0_ID_14]\nType=VirtualKey\nLabel=F11\nCluster=Function\nKeyCode=122\n\n[Key_AltGr_Row_0_ID_15]\nType=VirtualKey\nLabel=F12\nCluster=Function\nKeyCode=123\n\n[Key_AltGr_Row_0_ID_16]\nType=Blank\nWidth=25\nCluster=Function\n\n[Key_AltGr_Row_0_ID_17]\nType=VirtualKey\nLabel=Impr\\nPant\nCluster=Function\nKeyCode=44\nNoRepeat=true\n\n[Key_AltGr_Row_0_ID_18]\nType=VirtualKey\nLabel=Bloq\\nDespl\nCluster=Function\nKeyCode=145\nNoRepeat=true\n\n[Key_AltGr_Row_0_ID_19]\nType=VirtualKey\nLabel=Pausa\nCluster=Function\nKeyCode=19\nNoRepeat=true\n\n[Key_AltGr_Row_0_ID_20]\nType=Blank\nWidth=25\nCluster=Extra\n\n[Key_AltGr_Row_0_ID_21]\nType=VirtualKey\nLabel=⏯\nCluster=Extra\nKeyCode=179\nNoRepeat=true\n\n[Key_AltGr_Row_0_ID_22]\nType=VirtualKey\nLabel=◼\nCluster=Extra\nKeyCode=178\nNoRepeat=true\n\n[Key_AltGr_Row_0_ID_23]\nType=VirtualKey\nLabel=⏮\nCluster=Extra\nKeyCode=177\nNoRepeat=true\n\n[Key_AltGr_Row_0_ID_24]\nType=VirtualKey\nLabel=⏭\nCluster=Extra\nKeyCode=176\nNoRepeat=true\n\n[Key_AltGr_Row_1_ID_0]\nType=Blank\nHeight=25\nCluster=Function\n\n[Key_AltGr_Row_1_ID_1]\nType=Blank\nHeight=25\nCluster=Extra\n\n[Key_AltGr_Row_2_ID_0]\nType=String\nLabel=\\\nString=\\\n\n[Key_AltGr_Row_2_ID_1]\nType=String\nLabel=|\nString=|\n\n[Key_AltGr_Row_2_ID_2]\nType=String\nLabel=@\nString=@\n\n[Key_AltGr_Row_2_ID_3]\nType=String\nLabel=#\nString=#\n\n[Key_AltGr_Row_2_ID_4]\nType=String\nLabel=~\nString=~\n\n[Key_AltGr_Row_2_ID_5]\nType=Blank\n\n[Key_AltGr_Row_2_ID_6]\nType=String\nLabel=¬\nString=¬\n\n[Key_AltGr_Row_2_ID_7]\nType=Blank\nWidth=600\n\n[Key_AltGr_Row_2_ID_8]\nType=VirtualKey\nWidth=200\nLabel=⟵\nKeyCode=8\n\n[Key_AltGr_Row_2_ID_9]\nType=Blank\nWidth=25\nCluster=Navigation\n\n[Key_AltGr_Row_2_ID_10]\nType=VirtualKey\nLabel=Insert\nCluster=Navigation\nKeyCode=45\n\n[Key_AltGr_Row_2_ID_11]\nType=VirtualKey\nLabel=Incio\nCluster=Navigation\nKeyCode=36\n\n[Key_AltGr_Row_2_ID_12]\nType=VirtualKey\nLabel=RePág\nCluster=Navigation\nKeyCode=33\n\n[Key_AltGr_Row_2_ID_13]\nType=Blank\nWidth=25\nCluster=Numpad\n\n[Key_AltGr_Row_2_ID_14]\nType=VirtualKey\nLabel=Bloq\\nNum\nCluster=Numpad\nKeyCode=144\nNoRepeat=true\n\n[Key_AltGr_Row_2_ID_15]\nType=VirtualKey\nLabel=/\nCluster=Numpad\nKeyCode=111\n\n[Key_AltGr_Row_2_ID_16]\nType=VirtualKey\nLabel=*\nCluster=Numpad\nKeyCode=106\n\n[Key_AltGr_Row_2_ID_17]\nType=VirtualKey\nLabel=-\nCluster=Numpad\nKeyCode=109\n\n[Key_AltGr_Row_3_ID_0]\nType=VirtualKey\nWidth=150\nLabel=⭾\nKeyCode=9\n\n[Key_AltGr_Row_3_ID_1]\nType=Blank\nWidth=200\n\n[Key_AltGr_Row_3_ID_2]\nType=String\nLabel=€\nString=€\n\n[Key_AltGr_Row_3_ID_3]\nType=Blank\nWidth=700\n\n[Key_AltGr_Row_3_ID_4]\nType=String\nLabel=[\nString=[\n\n[Key_AltGr_Row_3_ID_5]\nType=String\nLabel=]\nString=]\n\n[Key_AltGr_Row_3_ID_6]\nType=VirtualKeyIsoEnter\nWidth=150\nLabel=\nKeyCode=13\n\n[Key_AltGr_Row_3_ID_7]\nType=Blank\nWidth=25\nCluster=Navigation\n\n[Key_AltGr_Row_3_ID_8]\nType=VirtualKey\nLabel=Supr\nCluster=Navigation\nKeyCode=46\n\n[Key_AltGr_Row_3_ID_9]\nType=VirtualKey\nLabel=Fin\nCluster=Navigation\nKeyCode=35\n\n[Key_AltGr_Row_3_ID_10]\nType=VirtualKey\nLabel=AvPág\nCluster=Navigation\nKeyCode=34\n\n[Key_AltGr_Row_3_ID_11]\nType=Blank\nWidth=25\nCluster=Numpad\n\n[Key_AltGr_Row_3_ID_12]\nType=VirtualKey\nLabel=7\nCluster=Numpad\nKeyCode=103\n\n[Key_AltGr_Row_3_ID_13]\nType=VirtualKey\nLabel=8\nCluster=Numpad\nKeyCode=104\n\n[Key_AltGr_Row_3_ID_14]\nType=VirtualKey\nLabel=9\nCluster=Numpad\nKeyCode=105\n\n[Key_AltGr_Row_3_ID_15]\nType=VirtualKey\nHeight=200\nLabel=+\nCluster=Numpad\nKeyCode=107\n\n[Key_AltGr_Row_4_ID_0]\nType=VirtualKey\nWidth=175\nLabel=Bloq Mayús\nKeyCode=20\nNoRepeat=true\n\n[Key_AltGr_Row_4_ID_1]\nType=Blank\nWidth=1000\n\n[Key_AltGr_Row_4_ID_2]\nType=String\nLabel={\nString={\n\n[Key_AltGr_Row_4_ID_3]\nType=String\nLabel=}\nString=}\n\n[Key_AltGr_Row_4_ID_4]\nType=VirtualKeyIsoEnter\nWidth=125\nLabel=↵\nKeyCode=13\n\n[Key_AltGr_Row_4_ID_5]\nType=Blank\nWidth=325\nCluster=Navigation\n\n[Key_AltGr_Row_4_ID_6]\nType=Blank\nWidth=25\nCluster=Numpad\n\n[Key_AltGr_Row_4_ID_7]\nType=VirtualKey\nLabel=4\nCluster=Numpad\nKeyCode=100\n\n[Key_AltGr_Row_4_ID_8]\nType=VirtualKey\nLabel=5\nCluster=Numpad\nKeyCode=101\n\n[Key_AltGr_Row_4_ID_9]\nType=VirtualKey\nLabel=6\nCluster=Numpad\nKeyCode=102\n\n[Key_AltGr_Row_5_ID_0]\nType=VirtualKeyToggle\nWidth=125\nLabel=🡅\nKeyCode=160\nNoRepeat=true\n\n[Key_AltGr_Row_5_ID_1]\nType=Blank\nWidth=1100\n\n[Key_AltGr_Row_5_ID_2]\nType=VirtualKeyToggle\nWidth=275\nLabel=🡅\nKeyCode=161\nNoRepeat=true\n\n[Key_AltGr_Row_5_ID_3]\nType=Blank\nWidth=125\nCluster=Navigation\n\n[Key_AltGr_Row_5_ID_4]\nType=VirtualKey\nLabel=🠅\nCluster=Navigation\nKeyCode=38\n\n[Key_AltGr_Row_5_ID_5]\nType=Blank\nCluster=Navigation\n\n[Key_AltGr_Row_5_ID_6]\nType=Blank\nWidth=25\nCluster=Numpad\n\n[Key_AltGr_Row_5_ID_7]\nType=VirtualKey\nLabel=1\nCluster=Numpad\nKeyCode=97\n\n[Key_AltGr_Row_5_ID_8]\nType=VirtualKey\nLabel=2\nCluster=Numpad\nKeyCode=98\n\n[Key_AltGr_Row_5_ID_9]\nType=VirtualKey\nLabel=3\nCluster=Numpad\nKeyCode=99\n\n[Key_AltGr_Row_5_ID_10]\nType=VirtualKey\nHeight=200\nLabel=Intro\nCluster=Numpad\nKeyCode=13\n\n[Key_AltGr_Row_6_ID_0]\nType=VirtualKeyToggle\nWidth=125\nLabel=Ctrl\nKeyCode=162\nNoRepeat=true\n\n[Key_AltGr_Row_6_ID_1]\nType=VirtualKeyToggle\nWidth=125\nLabel=Win\nKeyCode=91\nNoRepeat=true\n\n[Key_AltGr_Row_6_ID_2]\nType=VirtualKeyToggle\nWidth=125\nLabel=Alt\nKeyCode=164\nNoRepeat=true\n\n[Key_AltGr_Row_6_ID_3]\nType=VirtualKey\nWidth=625\nLabel=\nKeyCode=32\n\n[Key_AltGr_Row_6_ID_4]\nType=VirtualKeyToggle\nWidth=125\nLabel=AltGr\nKeyCode=165\nNoRepeat=true\n\n[Key_AltGr_Row_6_ID_5]\nType=VirtualKeyToggle\nWidth=125\nLabel=Win\nKeyCode=92\nNoRepeat=true\n\n[Key_AltGr_Row_6_ID_6]\nType=VirtualKey\nWidth=125\nLabel=Menu\nKeyCode=93\nNoRepeat=true\n\n[Key_AltGr_Row_6_ID_7]\nType=VirtualKeyToggle\nWidth=125\nLabel=Ctrl\nKeyCode=163\nNoRepeat=true\n\n[Key_AltGr_Row_6_ID_8]\nType=Blank\nWidth=25\nCluster=Navigation\n\n[Key_AltGr_Row_6_ID_9]\nType=VirtualKey\nLabel=🠄\nCluster=Navigation\nKeyCode=37\n\n[Key_AltGr_Row_6_ID_10]\nType=VirtualKey\nLabel=🠇\nCluster=Navigation\nKeyCode=40\n\n[Key_AltGr_Row_6_ID_11]\nType=VirtualKey\nLabel=🠆\nCluster=Navigation\nKeyCode=39\n\n[Key_AltGr_Row_6_ID_12]\nType=Blank\nWidth=25\nCluster=Numpad\n\n[Key_AltGr_Row_6_ID_13]\nType=VirtualKey\nWidth=200\nLabel=0\nCluster=Numpad\nKeyCode=96\n\n[Key_AltGr_Row_6_ID_14]\nType=VirtualKey\nLabel=.\nCluster=Numpad\nKeyCode=110\n\n"
  },
  {
    "path": "assets/keyboards/qwerty_ja.ini",
    "content": "[LayoutInfo]\r\nName=QWERTY (Japanese JIS-set)\r\nAuthor=Shimotuki Rieru\r\nHasAltGr=false\r\nHasClusterFunction=true\r\nHasClusterNavigation=true\r\nHasClusterNumpad=true\r\nHasClusterExtra=true\r\n\r\n[Key_Base_Row_0_ID_0]\r\nType=VirtualKey\r\nLabel=Esc\r\nCluster=Function\r\nKeyCode=27\r\nNoRepeat=true\r\n\r\n[Key_Base_Row_0_ID_1]\r\nType=Blank\r\nCluster=Function\r\n\r\n[Key_Base_Row_0_ID_2]\r\nType=VirtualKey\r\nLabel=F1\r\nCluster=Function\r\nKeyCode=112\r\n\r\n[Key_Base_Row_0_ID_3]\r\nType=VirtualKey\r\nLabel=F2\r\nCluster=Function\r\nKeyCode=113\r\n\r\n[Key_Base_Row_0_ID_4]\r\nType=VirtualKey\r\nLabel=F3\r\nCluster=Function\r\nKeyCode=114\r\n\r\n[Key_Base_Row_0_ID_5]\r\nType=VirtualKey\r\nLabel=F4\r\nCluster=Function\r\nKeyCode=115\r\n\r\n[Key_Base_Row_0_ID_6]\r\nType=Blank\r\nWidth=50\r\nCluster=Function\r\n\r\n[Key_Base_Row_0_ID_7]\r\nType=VirtualKey\r\nLabel=F5\r\nCluster=Function\r\nKeyCode=116\r\n\r\n[Key_Base_Row_0_ID_8]\r\nType=VirtualKey\r\nLabel=F6\r\nCluster=Function\r\nKeyCode=117\r\n\r\n[Key_Base_Row_0_ID_9]\r\nType=VirtualKey\r\nLabel=F7\r\nCluster=Function\r\nKeyCode=118\r\n\r\n[Key_Base_Row_0_ID_10]\r\nType=VirtualKey\r\nLabel=F8\r\nCluster=Function\r\nKeyCode=119\r\n\r\n[Key_Base_Row_0_ID_11]\r\nType=Blank\r\nWidth=50\r\nCluster=Function\r\n\r\n[Key_Base_Row_0_ID_12]\r\nType=VirtualKey\r\nLabel=F9\r\nCluster=Function\r\nKeyCode=120\r\n\r\n[Key_Base_Row_0_ID_13]\r\nType=VirtualKey\r\nLabel=F10\r\nCluster=Function\r\nKeyCode=121\r\n\r\n[Key_Base_Row_0_ID_14]\r\nType=VirtualKey\r\nLabel=F11\r\nCluster=Function\r\nKeyCode=122\r\n\r\n[Key_Base_Row_0_ID_15]\r\nType=VirtualKey\r\nLabel=F12\r\nCluster=Function\r\nKeyCode=123\r\n\r\n[Key_Base_Row_0_ID_16]\r\nType=Blank\r\nWidth=25\r\nCluster=Function\r\n\r\n[Key_Base_Row_0_ID_17]\r\nType=VirtualKey\r\nLabel=Print\\nScreen\r\nCluster=Function\r\nKeyCode=44\r\nNoRepeat=true\r\n\r\n[Key_Base_Row_0_ID_18]\r\nType=VirtualKey\r\nLabel=Scroll\\nLock\r\nCluster=Function\r\nKeyCode=145\r\nNoRepeat=true\r\n\r\n[Key_Base_Row_0_ID_19]\r\nType=VirtualKey\r\nLabel=Pause\r\nCluster=Function\r\nKeyCode=19\r\nNoRepeat=true\r\n\r\n[Key_Base_Row_0_ID_20]\r\nType=Blank\r\nWidth=25\r\nCluster=Extra\r\n\r\n[Key_Base_Row_0_ID_21]\r\nType=VirtualKey\r\nLabel=⏯\r\nCluster=Extra\r\nKeyCode=179\r\nNoRepeat=true\r\n\r\n[Key_Base_Row_0_ID_22]\r\nType=VirtualKey\r\nLabel=◼\r\nCluster=Extra\r\nKeyCode=178\r\nNoRepeat=true\r\n\r\n[Key_Base_Row_0_ID_23]\r\nType=VirtualKey\r\nLabel=⏮\r\nCluster=Extra\r\nKeyCode=177\r\nNoRepeat=true\r\n\r\n[Key_Base_Row_0_ID_24]\r\nType=VirtualKey\r\nLabel=⏭\r\nCluster=Extra\r\nKeyCode=176\r\nNoRepeat=true\r\n\r\n[Key_Base_Row_1_ID_0]\r\nType=Blank\r\nHeight=25\r\nCluster=Function\r\n\r\n[Key_Base_Row_1_ID_1]\r\nType=Blank\r\nHeight=25\r\nCluster=Extra\r\n\r\n[Key_Base_Row_2_ID_0]\r\nType=VirtualKey\r\nLabel=半角/##L\\n全角##L\\n漢字##L\r\nKeyCode=25\r\n\r\n[Key_Base_Row_2_ID_1]\r\nType=VirtualKey\r\nLabel=1##L\\nぬ##R\r\nKeyCode=49\r\n\r\n[Key_Base_Row_2_ID_2]\r\nType=VirtualKey\r\nLabel=2##L\\nふ##R\r\nKeyCode=50\r\n\r\n[Key_Base_Row_2_ID_3]\r\nType=VirtualKey\r\nLabel=3##L\\nあ##R\r\nKeyCode=51\r\n\r\n[Key_Base_Row_2_ID_4]\r\nType=VirtualKey\r\nLabel=4##L\\nう##R\r\nKeyCode=52\r\n\r\n[Key_Base_Row_2_ID_5]\r\nType=VirtualKey\r\nLabel=5##L\\nえ##R\r\nKeyCode=53\r\n\r\n[Key_Base_Row_2_ID_6]\r\nType=VirtualKey\r\nLabel=6##L\\nお##R\r\nKeyCode=54\r\n\r\n[Key_Base_Row_2_ID_7]\r\nType=VirtualKey\r\nLabel=7##L\\nや##R\r\nKeyCode=55\r\n\r\n[Key_Base_Row_2_ID_8]\r\nType=VirtualKey\r\nLabel=8##L\\nゆ##R\r\nKeyCode=56\r\n\r\n[Key_Base_Row_2_ID_9]\r\nType=VirtualKey\r\nLabel=9##L\\nよ##R\r\nKeyCode=57\r\n\r\n[Key_Base_Row_2_ID_10]\r\nType=VirtualKey\r\nLabel=0##L\\nわ##R\r\nKeyCode=48\r\n\r\n[Key_Base_Row_2_ID_11]\r\nType=String\r\nLabel=-##L\\nほ##R\r\nString=-\r\n\r\n[Key_Base_Row_2_ID_12]\r\nType=String\r\nLabel=^##L\\nへ##R\r\nString=^\r\n\r\n[Key_Base_Row_2_ID_13]\r\nType=VirtualKey\r\nLabel=￥##L\\n\r\nKeyCode=220\r\n\r\n[Key_Base_Row_2_ID_14]\r\nType=VirtualKey\r\nLabel=Back\\nspace\r\nKeyCode=8\r\n\r\n[Key_Base_Row_2_ID_15]\r\nType=Blank\r\nWidth=25\r\nCluster=Navigation\r\n\r\n[Key_Base_Row_2_ID_16]\r\nType=VirtualKey\r\nLabel=Insert\r\nCluster=Navigation\r\nKeyCode=45\r\n\r\n[Key_Base_Row_2_ID_17]\r\nType=VirtualKey\r\nLabel=Home\r\nCluster=Navigation\r\nKeyCode=36\r\n\r\n[Key_Base_Row_2_ID_18]\r\nType=VirtualKey\r\nLabel=PgUp\r\nCluster=Navigation\r\nKeyCode=33\r\n\r\n[Key_Base_Row_2_ID_19]\r\nType=Blank\r\nWidth=25\r\nCluster=Numpad\r\n\r\n[Key_Base_Row_2_ID_20]\r\nType=VirtualKey\r\nLabel=Num\\nLock\r\nCluster=Numpad\r\nKeyCode=144\r\nNoRepeat=true\r\n\r\n[Key_Base_Row_2_ID_21]\r\nType=VirtualKey\r\nLabel=/\r\nCluster=Numpad\r\nKeyCode=111\r\n\r\n[Key_Base_Row_2_ID_22]\r\nType=VirtualKey\r\nLabel=*\r\nCluster=Numpad\r\nKeyCode=106\r\n\r\n[Key_Base_Row_2_ID_23]\r\nType=VirtualKey\r\nLabel=-\r\nCluster=Numpad\r\nKeyCode=109\r\n\r\n[Key_Base_Row_3_ID_0]\r\nType=VirtualKey\r\nWidth=150\r\nLabel=Tab\r\nKeyCode=9\r\n\r\n[Key_Base_Row_3_ID_1]\r\nType=VirtualKey\r\nLabel=q##L\\nた##R\r\nKeyCode=81\r\n\r\n[Key_Base_Row_3_ID_2]\r\nType=VirtualKey\r\nLabel=w##L\\nて##R\r\nKeyCode=87\r\n\r\n[Key_Base_Row_3_ID_3]\r\nType=VirtualKey\r\nLabel=e##L\\nい##R\r\nKeyCode=69\r\n\r\n[Key_Base_Row_3_ID_4]\r\nType=VirtualKey\r\nLabel=r##L\\nす##R\r\nKeyCode=82\r\n\r\n[Key_Base_Row_3_ID_5]\r\nType=VirtualKey\r\nLabel=t##L\\nか##R\r\nKeyCode=84\r\n\r\n[Key_Base_Row_3_ID_6]\r\nType=VirtualKey\r\nLabel=y##L\\nん##R\r\nKeyCode=89\r\n\r\n[Key_Base_Row_3_ID_7]\r\nType=VirtualKey\r\nLabel=u##L\\nな##R\r\nKeyCode=85\r\n\r\n[Key_Base_Row_3_ID_8]\r\nType=VirtualKey\r\nLabel=i##L\\nに##R\r\nKeyCode=73\r\n\r\n[Key_Base_Row_3_ID_9]\r\nType=VirtualKey\r\nLabel=o##L\\nら##R\r\nKeyCode=79\r\n\r\n[Key_Base_Row_3_ID_10]\r\nType=VirtualKey\r\nLabel=p##L\\nせ##R\r\nKeyCode=80\r\n\r\n[Key_Base_Row_3_ID_11]\r\nType=String\r\nLabel=@##L\\n\r\nString=@\r\n\r\n[Key_Base_Row_3_ID_12]\r\nType=String\r\nLabel=[  「##L\\n\r\nString=[\r\n\r\n[Key_Base_Row_3_ID_13]\r\nType=VirtualKeyIsoEnter\r\nWidth=150\r\nLabel=\r\nKeyCode=13\r\n\r\n[Key_Base_Row_3_ID_14]\r\nType=Blank\r\nWidth=25\r\nCluster=Navigation\r\n\r\n[Key_Base_Row_3_ID_15]\r\nType=VirtualKey\r\nLabel=Delete\r\nCluster=Navigation\r\nKeyCode=46\r\n\r\n[Key_Base_Row_3_ID_16]\r\nType=VirtualKey\r\nLabel=End\r\nCluster=Navigation\r\nKeyCode=35\r\n\r\n[Key_Base_Row_3_ID_17]\r\nType=VirtualKey\r\nLabel=PgDn\r\nCluster=Navigation\r\nKeyCode=34\r\n\r\n[Key_Base_Row_3_ID_18]\r\nType=Blank\r\nWidth=25\r\nCluster=Numpad\r\n\r\n[Key_Base_Row_3_ID_19]\r\nType=VirtualKey\r\nLabel=7\\nHome\r\nCluster=Numpad\r\nKeyCode=103\r\n\r\n[Key_Base_Row_3_ID_20]\r\nType=VirtualKey\r\nLabel=8\\n↑\r\nCluster=Numpad\r\nKeyCode=104\r\n\r\n[Key_Base_Row_3_ID_21]\r\nType=VirtualKey\r\nLabel=9\\nPgUp\r\nCluster=Numpad\r\nKeyCode=105\r\n\r\n[Key_Base_Row_3_ID_22]\r\nType=VirtualKey\r\nHeight=200\r\nLabel=+\r\nCluster=Numpad\r\nKeyCode=107\r\n\r\n[Key_Base_Row_4_ID_0]\r\nType=VirtualKey\r\nWidth=175\r\nLabel=Caps Lock\r\nKeyCode=20\r\nNoRepeat=true\r\n\r\n[Key_Base_Row_4_ID_1]\r\nType=VirtualKey\r\nLabel=a##L\\nち##R\r\nKeyCode=65\r\n\r\n[Key_Base_Row_4_ID_2]\r\nType=VirtualKey\r\nLabel=s##L\\nと##R\r\nKeyCode=83\r\n\r\n[Key_Base_Row_4_ID_3]\r\nType=VirtualKey\r\nLabel=d##L\\nし##R\r\nKeyCode=68\r\n\r\n[Key_Base_Row_4_ID_4]\r\nType=VirtualKey\r\nLabel=f##L\\nは##R\r\nKeyCode=70\r\n\r\n[Key_Base_Row_4_ID_5]\r\nType=VirtualKey\r\nLabel=g##L\\nき##R\r\nKeyCode=71\r\n\r\n[Key_Base_Row_4_ID_6]\r\nType=VirtualKey\r\nLabel=h##L\\nく##R\r\nKeyCode=72\r\n\r\n[Key_Base_Row_4_ID_7]\r\nType=VirtualKey\r\nLabel=j##L\\nま##R\r\nKeyCode=74\r\n\r\n[Key_Base_Row_4_ID_8]\r\nType=VirtualKey\r\nLabel=k##L\\nの##R\r\nKeyCode=75\r\n\r\n[Key_Base_Row_4_ID_9]\r\nType=VirtualKey\r\nLabel=l##L\\nり##R\r\nKeyCode=76\r\n\r\n[Key_Base_Row_4_ID_10]\r\nType=String\r\nLabel=;##L\\nれ##R\r\nString=;\r\n\r\n[Key_Base_Row_4_ID_11]\r\nType=String\r\nLabel=:##L\\nけ##R\r\nString=:\r\n\r\n[Key_Base_Row_4_ID_12]\r\nType=String\r\nLabel=]  」##L\\nむ##R\r\nString=]\r\n\r\n[Key_Base_Row_4_ID_13]\r\nType=VirtualKeyIsoEnter\r\nWidth=125\r\nLabel=Enter\r\nKeyCode=13\r\n\r\n[Key_Base_Row_4_ID_14]\r\nType=Blank\r\nWidth=325\r\nCluster=Navigation\r\n\r\n[Key_Base_Row_4_ID_15]\r\nType=Blank\r\nWidth=25\r\nCluster=Numpad\r\n\r\n[Key_Base_Row_4_ID_16]\r\nType=VirtualKey\r\nLabel=4\\n←\r\nCluster=Numpad\r\nKeyCode=100\r\n\r\n[Key_Base_Row_4_ID_17]\r\nType=VirtualKey\r\nLabel=5\\n\r\nCluster=Numpad\r\nKeyCode=101\r\n\r\n[Key_Base_Row_4_ID_18]\r\nType=VirtualKey\r\nLabel=6\\n→\r\nCluster=Numpad\r\nKeyCode=102\r\n\r\n[Key_Base_Row_5_ID_0]\r\nType=VirtualKeyToggle\r\nWidth=225\r\nLabel=Shift\r\nKeyCode=160\r\nNoRepeat=true\r\n\r\n[Key_Base_Row_5_ID_1]\r\nType=VirtualKey\r\nLabel=z##L\\nつ##R\r\nKeyCode=90\r\n\r\n[Key_Base_Row_5_ID_2]\r\nType=VirtualKey\r\nLabel=x##L\\nさ##R\r\nKeyCode=88\r\n\r\n[Key_Base_Row_5_ID_3]\r\nType=VirtualKey\r\nLabel=c##L\\nそ##R\r\nKeyCode=67\r\n\r\n[Key_Base_Row_5_ID_4]\r\nType=VirtualKey\r\nLabel=v##L\\nひ##R\r\nKeyCode=86\r\n\r\n[Key_Base_Row_5_ID_5]\r\nType=VirtualKey\r\nLabel=b##L\\nこ##R\r\nKeyCode=66\r\n\r\n[Key_Base_Row_5_ID_6]\r\nType=VirtualKey\r\nLabel=n##L\\nみ##R\r\nKeyCode=78\r\n\r\n[Key_Base_Row_5_ID_7]\r\nType=VirtualKey\r\nLabel=m##L\\nも##R\r\nKeyCode=77\r\n\r\n[Key_Base_Row_5_ID_8]\r\nType=String\r\nLabel=,  、##L\\nね##R\r\nString=,\r\n\r\n[Key_Base_Row_5_ID_9]\r\nType=String\r\nLabel=.  。##L\\nる##R\r\nString=.\r\n\r\n[Key_Base_Row_5_ID_10]\r\nType=String\r\nLabel=/  ・##L\\nめ##R\r\nString=/\r\n\r\n[Key_Base_Row_5_ID_11]\r\nType=String\r\nLabel=\\##L\\nろ##R\r\nString=\\\r\n\r\n[Key_Base_Row_5_ID_12]\r\nType=VirtualKeyToggle\r\nWidth=175\r\nLabel=Shift\r\nKeyCode=161\r\nNoRepeat=true\r\n\r\n[Key_Base_Row_5_ID_13]\r\nType=Blank\r\nWidth=125\r\nCluster=Navigation\r\n\r\n[Key_Base_Row_5_ID_14]\r\nType=VirtualKey\r\nLabel=🠅\r\nCluster=Navigation\r\nKeyCode=38\r\n\r\n[Key_Base_Row_5_ID_15]\r\nType=Blank\r\nCluster=Navigation\r\n\r\n[Key_Base_Row_5_ID_16]\r\nType=Blank\r\nWidth=25\r\nCluster=Numpad\r\n\r\n[Key_Base_Row_5_ID_17]\r\nType=VirtualKey\r\nLabel=1\\nEnd\r\nCluster=Numpad\r\nKeyCode=97\r\n\r\n[Key_Base_Row_5_ID_18]\r\nType=VirtualKey\r\nLabel=2\\n↓\r\nCluster=Numpad\r\nKeyCode=98\r\n\r\n[Key_Base_Row_5_ID_19]\r\nType=VirtualKey\r\nLabel=3\\nPgDn\r\nCluster=Numpad\r\nKeyCode=99\r\n\r\n[Key_Base_Row_5_ID_20]\r\nType=VirtualKey\r\nHeight=200\r\nLabel=Enter\r\nCluster=Numpad\r\nKeyCode=13\r\n\r\n[Key_Base_Row_6_ID_0]\r\nType=VirtualKeyToggle\r\nWidth=125\r\nLabel=Ctrl\r\nKeyCode=162\r\nNoRepeat=true\r\n\r\n[Key_Base_Row_6_ID_1]\r\nType=VirtualKeyToggle\r\nWidth=125\r\nLabel=Win\r\nKeyCode=91\r\nNoRepeat=true\r\n\r\n[Key_Base_Row_6_ID_2]\r\nType=VirtualKeyToggle\r\nWidth=125\r\nLabel=Alt\r\nKeyCode=164\r\nNoRepeat=true\r\n\r\n[Key_Base_Row_6_ID_3]\r\nType=VirtualKey\r\nLabel=無変換\r\nKeyCode=29\r\n\r\n[Key_Base_Row_6_ID_4]\r\nType=VirtualKey\r\nWidth=300\r\nLabel=\r\nKeyCode=32\r\n\r\n[Key_Base_Row_6_ID_5]\r\nType=VirtualKey\r\nLabel=変換\r\nKeyCode=28\r\n\r\n[Key_Base_Row_6_ID_6]\r\nType=VirtualKey\r\nWidth=125\r\nLabel=カタカナ\\nひらがな\r\nKeyCode=240\r\n\r\n[Key_Base_Row_6_ID_7]\r\nType=VirtualKeyToggle\r\nWidth=125\r\nLabel=Alt\r\nKeyCode=165\r\nNoRepeat=true\r\n\r\n[Key_Base_Row_6_ID_8]\r\nType=VirtualKeyToggle\r\nWidth=125\r\nLabel=Win\r\nKeyCode=92\r\nNoRepeat=true\r\n\r\n[Key_Base_Row_6_ID_9]\r\nType=VirtualKey\r\nWidth=125\r\nLabel=Menu\r\nKeyCode=93\r\nNoRepeat=true\r\n\r\n[Key_Base_Row_6_ID_10]\r\nType=VirtualKeyToggle\r\nWidth=125\r\nLabel=Ctrl\r\nKeyCode=163\r\nNoRepeat=true\r\n\r\n[Key_Base_Row_6_ID_11]\r\nType=Blank\r\nWidth=25\r\nCluster=Navigation\r\n\r\n[Key_Base_Row_6_ID_12]\r\nType=VirtualKey\r\nLabel=🠄\r\nCluster=Navigation\r\nKeyCode=37\r\n\r\n[Key_Base_Row_6_ID_13]\r\nType=VirtualKey\r\nLabel=🠇\r\nCluster=Navigation\r\nKeyCode=40\r\n\r\n[Key_Base_Row_6_ID_14]\r\nType=VirtualKey\r\nLabel=🠆\r\nCluster=Navigation\r\nKeyCode=39\r\n\r\n[Key_Base_Row_6_ID_15]\r\nType=Blank\r\nWidth=25\r\nCluster=Numpad\r\n\r\n[Key_Base_Row_6_ID_16]\r\nType=VirtualKey\r\nWidth=200\r\nLabel=0\\nInsert\r\nCluster=Numpad\r\nKeyCode=96\r\n\r\n[Key_Base_Row_6_ID_17]\r\nType=VirtualKey\r\nLabel=.\\nDel\r\nCluster=Numpad\r\nKeyCode=110\r\n\r\n[Key_Shift_Row_0_ID_0]\r\nType=VirtualKey\r\nLabel=Esc\r\nCluster=Function\r\nKeyCode=27\r\nNoRepeat=true\r\n\r\n[Key_Shift_Row_0_ID_1]\r\nType=Blank\r\nCluster=Function\r\n\r\n[Key_Shift_Row_0_ID_2]\r\nType=VirtualKey\r\nLabel=F1\r\nCluster=Function\r\nKeyCode=112\r\n\r\n[Key_Shift_Row_0_ID_3]\r\nType=VirtualKey\r\nLabel=F2\r\nCluster=Function\r\nKeyCode=113\r\n\r\n[Key_Shift_Row_0_ID_4]\r\nType=VirtualKey\r\nLabel=F3\r\nCluster=Function\r\nKeyCode=114\r\n\r\n[Key_Shift_Row_0_ID_5]\r\nType=VirtualKey\r\nLabel=F4\r\nCluster=Function\r\nKeyCode=115\r\n\r\n[Key_Shift_Row_0_ID_6]\r\nType=Blank\r\nWidth=50\r\nCluster=Function\r\n\r\n[Key_Shift_Row_0_ID_7]\r\nType=VirtualKey\r\nLabel=F5\r\nCluster=Function\r\nKeyCode=116\r\n\r\n[Key_Shift_Row_0_ID_8]\r\nType=VirtualKey\r\nLabel=F6\r\nCluster=Function\r\nKeyCode=117\r\n\r\n[Key_Shift_Row_0_ID_9]\r\nType=VirtualKey\r\nLabel=F7\r\nCluster=Function\r\nKeyCode=118\r\n\r\n[Key_Shift_Row_0_ID_10]\r\nType=VirtualKey\r\nLabel=F8\r\nCluster=Function\r\nKeyCode=119\r\n\r\n[Key_Shift_Row_0_ID_11]\r\nType=Blank\r\nWidth=50\r\nCluster=Function\r\n\r\n[Key_Shift_Row_0_ID_12]\r\nType=VirtualKey\r\nLabel=F9\r\nCluster=Function\r\nKeyCode=120\r\n\r\n[Key_Shift_Row_0_ID_13]\r\nType=VirtualKey\r\nLabel=F10\r\nCluster=Function\r\nKeyCode=121\r\n\r\n[Key_Shift_Row_0_ID_14]\r\nType=VirtualKey\r\nLabel=F11\r\nCluster=Function\r\nKeyCode=122\r\n\r\n[Key_Shift_Row_0_ID_15]\r\nType=VirtualKey\r\nLabel=F12\r\nCluster=Function\r\nKeyCode=123\r\n\r\n[Key_Shift_Row_0_ID_16]\r\nType=Blank\r\nWidth=25\r\nCluster=Function\r\n\r\n[Key_Shift_Row_0_ID_17]\r\nType=VirtualKey\r\nLabel=Print\\nScreen\r\nCluster=Function\r\nKeyCode=44\r\nNoRepeat=true\r\n\r\n[Key_Shift_Row_0_ID_18]\r\nType=VirtualKey\r\nLabel=Scroll\\nLock\r\nCluster=Function\r\nKeyCode=145\r\nNoRepeat=true\r\n\r\n[Key_Shift_Row_0_ID_19]\r\nType=VirtualKey\r\nLabel=Pause\r\nCluster=Function\r\nKeyCode=19\r\nNoRepeat=true\r\n\r\n[Key_Shift_Row_0_ID_20]\r\nType=Blank\r\nWidth=25\r\nCluster=Extra\r\n\r\n[Key_Shift_Row_0_ID_21]\r\nType=VirtualKey\r\nLabel=🡰\r\nCluster=Extra\r\nKeyCode=166\r\nNoRepeat=true\r\n\r\n[Key_Shift_Row_0_ID_22]\r\nType=VirtualKey\r\nLabel=🡲\r\nCluster=Extra\r\nKeyCode=167\r\nNoRepeat=true\r\n\r\n[Key_Shift_Row_0_ID_23]\r\nType=VirtualKey\r\nLabel=🔇\r\nCluster=Extra\r\nKeyCode=173\r\nNoRepeat=true\r\n\r\n[Key_Shift_Row_1_ID_0]\r\nType=Blank\r\nHeight=25\r\nCluster=Function\r\n\r\n[Key_Shift_Row_1_ID_1]\r\nType=Blank\r\nHeight=25\r\nCluster=Extra\r\n\r\n[Key_Shift_Row_2_ID_0]\r\nType=VirtualKey\r\nLabel=半角/##L\\n全角##L\\n漢字##L\r\nKeyCode=25\r\n\r\n[Key_Shift_Row_2_ID_1]\r\nType=String\r\nLabel=!##L\\nぬ##R\r\nString=!\r\n\r\n[Key_Shift_Row_2_ID_2]\r\nType=String\r\nLabel=\"##L\\nふ##R\r\nString=\"\r\n\r\n[Key_Shift_Row_2_ID_3]\r\nType=String\r\nLabel=# ##L\\nあ##R\r\nString=#\r\n\r\n[Key_Shift_Row_2_ID_4]\r\nType=String\r\nLabel=$##L\\nう##R\r\nString=$\r\n\r\n[Key_Shift_Row_2_ID_5]\r\nType=String\r\nLabel=%##L\\nえ##R\r\nString=%\r\n\r\n[Key_Shift_Row_2_ID_6]\r\nType=String\r\nLabel=&##L\\nお##R\r\nString=&\r\n\r\n[Key_Shift_Row_2_ID_7]\r\nType=String\r\nLabel='##L\\nや##R\r\nString='\r\n\r\n[Key_Shift_Row_2_ID_8]\r\nType=String\r\nLabel=(##L\\nゆ##R\r\nString=(\r\n\r\n[Key_Shift_Row_2_ID_9]\r\nType=String\r\nLabel=)##L\\nよ##R\r\nString=)\r\n\r\n[Key_Shift_Row_2_ID_10]\r\nType=VirtualKey\r\nLabel=##L\\nわ##R\r\nKeyCode=48\r\n\r\n[Key_Shift_Row_2_ID_11]\r\nType=String\r\nLabel==##L\\nほ##R\r\nString==\r\n\r\n[Key_Shift_Row_2_ID_12]\r\nType=String\r\nLabel=~##L\\nへ##R\r\nString=~\r\n\r\n[Key_Shift_Row_2_ID_13]\r\nType=String\r\nLabel=|##L\\n\r\nString=|\r\n\r\n[Key_Shift_Row_2_ID_14]\r\nType=VirtualKey\r\nLabel=Back\\nspace\r\nKeyCode=8\r\n\r\n[Key_Shift_Row_2_ID_15]\r\nType=Blank\r\nWidth=25\r\nCluster=Navigation\r\n\r\n[Key_Shift_Row_2_ID_16]\r\nType=VirtualKey\r\nLabel=Insert\r\nCluster=Navigation\r\nKeyCode=45\r\n\r\n[Key_Shift_Row_2_ID_17]\r\nType=VirtualKey\r\nLabel=Home\r\nCluster=Navigation\r\nKeyCode=36\r\n\r\n[Key_Shift_Row_2_ID_18]\r\nType=VirtualKey\r\nLabel=PgUp\r\nCluster=Navigation\r\nKeyCode=33\r\n\r\n[Key_Shift_Row_2_ID_19]\r\nType=Blank\r\nWidth=25\r\nCluster=Numpad\r\n\r\n[Key_Shift_Row_2_ID_20]\r\nType=VirtualKey\r\nLabel=Num\\nLock\r\nCluster=Numpad\r\nKeyCode=144\r\nNoRepeat=true\r\n\r\n[Key_Shift_Row_2_ID_21]\r\nType=VirtualKey\r\nLabel=/\r\nCluster=Numpad\r\nKeyCode=111\r\n\r\n[Key_Shift_Row_2_ID_22]\r\nType=VirtualKey\r\nLabel=*\r\nCluster=Numpad\r\nKeyCode=106\r\n\r\n[Key_Shift_Row_2_ID_23]\r\nType=VirtualKey\r\nLabel=-\r\nCluster=Numpad\r\nKeyCode=109\r\n\r\n[Key_Shift_Row_3_ID_0]\r\nType=VirtualKey\r\nWidth=150\r\nLabel=Tab\r\nKeyCode=9\r\n\r\n[Key_Shift_Row_3_ID_1]\r\nType=VirtualKey\r\nLabel=Q##L\\nた##R\r\nKeyCode=81\r\n\r\n[Key_Shift_Row_3_ID_2]\r\nType=VirtualKey\r\nLabel=W##L\\nて##R\r\nKeyCode=87\r\n\r\n[Key_Shift_Row_3_ID_3]\r\nType=VirtualKey\r\nLabel=E##L\\nい##R\r\nKeyCode=69\r\n\r\n[Key_Shift_Row_3_ID_4]\r\nType=VirtualKey\r\nLabel=R##L\\nす##R\r\nKeyCode=82\r\n\r\n[Key_Shift_Row_3_ID_5]\r\nType=VirtualKey\r\nLabel=T##L\\nか##R\r\nKeyCode=84\r\n\r\n[Key_Shift_Row_3_ID_6]\r\nType=VirtualKey\r\nLabel=Y##L\\nん##R\r\nKeyCode=89\r\n\r\n[Key_Shift_Row_3_ID_7]\r\nType=VirtualKey\r\nLabel=U##L\\nな##R\r\nKeyCode=85\r\n\r\n[Key_Shift_Row_3_ID_8]\r\nType=VirtualKey\r\nLabel=I##L\\nに##R\r\nKeyCode=73\r\n\r\n[Key_Shift_Row_3_ID_9]\r\nType=VirtualKey\r\nLabel=O##L\\nら##R\r\nKeyCode=79\r\n\r\n[Key_Shift_Row_3_ID_10]\r\nType=VirtualKey\r\nLabel=P##L\\nせ##R\r\nKeyCode=80\r\n\r\n[Key_Shift_Row_3_ID_11]\r\nType=String\r\nLabel=`##L\\n\r\nString=`\r\n\r\n[Key_Shift_Row_3_ID_12]\r\nType=String\r\nLabel={##L\\n\r\nString={\r\n\r\n[Key_Shift_Row_3_ID_13]\r\nType=VirtualKeyIsoEnter\r\nWidth=150\r\nLabel=\r\nKeyCode=13\r\n\r\n[Key_Shift_Row_3_ID_14]\r\nType=Blank\r\nWidth=25\r\nCluster=Navigation\r\n\r\n[Key_Shift_Row_3_ID_15]\r\nType=VirtualKey\r\nLabel=Delete\r\nCluster=Navigation\r\nKeyCode=46\r\n\r\n[Key_Shift_Row_3_ID_16]\r\nType=VirtualKey\r\nLabel=End\r\nCluster=Navigation\r\nKeyCode=35\r\n\r\n[Key_Shift_Row_3_ID_17]\r\nType=VirtualKey\r\nLabel=PgDn\r\nCluster=Navigation\r\nKeyCode=34\r\n\r\n[Key_Shift_Row_3_ID_18]\r\nType=Blank\r\nWidth=25\r\nCluster=Numpad\r\n\r\n[Key_Shift_Row_3_ID_19]\r\nType=VirtualKey\r\nLabel=7\\nHome\r\nCluster=Numpad\r\nKeyCode=103\r\n\r\n[Key_Shift_Row_3_ID_20]\r\nType=VirtualKey\r\nLabel=8\\n↑\r\nCluster=Numpad\r\nKeyCode=104\r\n\r\n[Key_Shift_Row_3_ID_21]\r\nType=VirtualKey\r\nLabel=9\\nPgUp\r\nCluster=Numpad\r\nKeyCode=105\r\n\r\n[Key_Shift_Row_3_ID_22]\r\nType=VirtualKey\r\nHeight=200\r\nLabel=+\r\nCluster=Numpad\r\nKeyCode=107\r\n\r\n[Key_Shift_Row_4_ID_0]\r\nType=VirtualKey\r\nWidth=175\r\nLabel=Caps Lock\r\nKeyCode=20\r\nNoRepeat=true\r\n\r\n[Key_Shift_Row_4_ID_1]\r\nType=VirtualKey\r\nLabel=A##L\\nち##R\r\nKeyCode=65\r\n\r\n[Key_Shift_Row_4_ID_2]\r\nType=VirtualKey\r\nLabel=S##L\\nと##R\r\nKeyCode=83\r\n\r\n[Key_Shift_Row_4_ID_3]\r\nType=VirtualKey\r\nLabel=D##L\\nし##R\r\nKeyCode=68\r\n\r\n[Key_Shift_Row_4_ID_4]\r\nType=VirtualKey\r\nLabel=F##L\\nは##R\r\nKeyCode=70\r\n\r\n[Key_Shift_Row_4_ID_5]\r\nType=VirtualKey\r\nLabel=G##L\\nき##R\r\nKeyCode=71\r\n\r\n[Key_Shift_Row_4_ID_6]\r\nType=VirtualKey\r\nLabel=H##L\\nく##R\r\nKeyCode=72\r\n\r\n[Key_Shift_Row_4_ID_7]\r\nType=VirtualKey\r\nLabel=J##L\\nま##R\r\nKeyCode=74\r\n\r\n[Key_Shift_Row_4_ID_8]\r\nType=VirtualKey\r\nLabel=K##L\\nの##R\r\nKeyCode=75\r\n\r\n[Key_Shift_Row_4_ID_9]\r\nType=VirtualKey\r\nLabel=L##L\\nり##R\r\nKeyCode=76\r\n\r\n[Key_Shift_Row_4_ID_10]\r\nType=String\r\nLabel=+##L\\nれ##R\r\nString=+\r\n\r\n[Key_Shift_Row_4_ID_11]\r\nType=String\r\nLabel=*##L\\nけ##R\r\nString=*\r\n\r\n[Key_Shift_Row_4_ID_12]\r\nType=String\r\nLabel=}##L\\nむ##R\r\nString=}\r\n\r\n[Key_Shift_Row_4_ID_13]\r\nType=VirtualKeyIsoEnter\r\nWidth=125\r\nLabel=Enter\r\nKeyCode=13\r\n\r\n[Key_Shift_Row_4_ID_14]\r\nType=Blank\r\nWidth=325\r\nCluster=Navigation\r\n\r\n[Key_Shift_Row_4_ID_15]\r\nType=Blank\r\nWidth=25\r\nCluster=Numpad\r\n\r\n[Key_Shift_Row_4_ID_16]\r\nType=VirtualKey\r\nLabel=4\\n←\r\nCluster=Numpad\r\nKeyCode=100\r\n\r\n[Key_Shift_Row_4_ID_17]\r\nType=VirtualKey\r\nLabel=5\\n\r\nCluster=Numpad\r\nKeyCode=101\r\n\r\n[Key_Shift_Row_4_ID_18]\r\nType=VirtualKey\r\nLabel=6\\n→\r\nCluster=Numpad\r\nKeyCode=102\r\n\r\n[Key_Shift_Row_5_ID_0]\r\nType=VirtualKeyToggle\r\nWidth=225\r\nLabel=Shift\r\nKeyCode=160\r\nNoRepeat=true\r\n\r\n[Key_Shift_Row_5_ID_1]\r\nType=VirtualKey\r\nLabel=Z##L\\nつ##R\r\nKeyCode=90\r\n\r\n[Key_Shift_Row_5_ID_2]\r\nType=VirtualKey\r\nLabel=X##L\\nさ##R\r\nKeyCode=88\r\n\r\n[Key_Shift_Row_5_ID_3]\r\nType=VirtualKey\r\nLabel=C##L\\nそ##R\r\nKeyCode=67\r\n\r\n[Key_Shift_Row_5_ID_4]\r\nType=VirtualKey\r\nLabel=V##L\\nひ##R\r\nKeyCode=86\r\n\r\n[Key_Shift_Row_5_ID_5]\r\nType=VirtualKey\r\nLabel=B##L\\nこ##R\r\nKeyCode=66\r\n\r\n[Key_Shift_Row_5_ID_6]\r\nType=VirtualKey\r\nLabel=N##L\\nみ##R\r\nKeyCode=78\r\n\r\n[Key_Shift_Row_5_ID_7]\r\nType=VirtualKey\r\nLabel=M##L\\nも##R\r\nKeyCode=77\r\n\r\n[Key_Shift_Row_5_ID_8]\r\nType=String\r\nLabel=<##L\\nね##R\r\nString=<\r\n\r\n[Key_Shift_Row_5_ID_9]\r\nType=String\r\nLabel=>##L\\nる##R\r\nString=>\r\n\r\n[Key_Shift_Row_5_ID_10]\r\nType=String\r\nLabel=?##L\\nめ##R\r\nString=?\r\n\r\n[Key_Shift_Row_5_ID_11]\r\nType=String\r\nLabel=_##L\\nろ##R\r\nString=_\r\n\r\n[Key_Shift_Row_5_ID_12]\r\nType=VirtualKeyToggle\r\nWidth=175\r\nLabel=Shift\r\nKeyCode=161\r\nNoRepeat=true\r\n\r\n[Key_Shift_Row_5_ID_13]\r\nType=Blank\r\nWidth=125\r\nCluster=Navigation\r\n\r\n[Key_Shift_Row_5_ID_14]\r\nType=VirtualKey\r\nLabel=🠅\r\nCluster=Navigation\r\nKeyCode=38\r\n\r\n[Key_Shift_Row_5_ID_15]\r\nType=Blank\r\nCluster=Navigation\r\n\r\n[Key_Shift_Row_5_ID_16]\r\nType=Blank\r\nWidth=25\r\nCluster=Numpad\r\n\r\n[Key_Shift_Row_5_ID_17]\r\nType=VirtualKey\r\nLabel=1\\nEnd\r\nCluster=Numpad\r\nKeyCode=97\r\n\r\n[Key_Shift_Row_5_ID_18]\r\nType=VirtualKey\r\nLabel=2\\n↓\r\nCluster=Numpad\r\nKeyCode=98\r\n\r\n[Key_Shift_Row_5_ID_19]\r\nType=VirtualKey\r\nLabel=3\\nPgDn\r\nCluster=Numpad\r\nKeyCode=99\r\n\r\n[Key_Shift_Row_5_ID_20]\r\nType=VirtualKey\r\nHeight=200\r\nLabel=Enter\r\nCluster=Numpad\r\nKeyCode=13\r\n\r\n[Key_Shift_Row_6_ID_0]\r\nType=VirtualKeyToggle\r\nWidth=125\r\nLabel=Ctrl\r\nKeyCode=162\r\nNoRepeat=true\r\n\r\n[Key_Shift_Row_6_ID_1]\r\nType=VirtualKeyToggle\r\nWidth=125\r\nLabel=Win\r\nKeyCode=91\r\nNoRepeat=true\r\n\r\n[Key_Shift_Row_6_ID_2]\r\nType=VirtualKeyToggle\r\nWidth=125\r\nLabel=Alt\r\nKeyCode=164\r\nNoRepeat=true\r\n\r\n[Key_Shift_Row_6_ID_3]\r\nType=VirtualKey\r\nLabel=無変換\r\nKeyCode=29\r\n\r\n[Key_Shift_Row_6_ID_4]\r\nType=VirtualKey\r\nWidth=300\r\nLabel=\r\nKeyCode=32\r\n\r\n[Key_Shift_Row_6_ID_5]\r\nType=VirtualKey\r\nLabel=変換\r\nKeyCode=28\r\n\r\n[Key_Shift_Row_6_ID_6]\r\nType=VirtualKey\r\nWidth=125\r\nLabel=カタカナ\\nひらがな\r\nKeyCode=21\r\n\r\n[Key_Shift_Row_6_ID_7]\r\nType=VirtualKeyToggle\r\nWidth=125\r\nLabel=Alt\r\nKeyCode=165\r\nNoRepeat=true\r\n\r\n[Key_Shift_Row_6_ID_8]\r\nType=VirtualKeyToggle\r\nWidth=125\r\nLabel=Win\r\nKeyCode=92\r\nNoRepeat=true\r\n\r\n[Key_Shift_Row_6_ID_9]\r\nType=VirtualKey\r\nWidth=125\r\nLabel=Menu\r\nKeyCode=93\r\nNoRepeat=true\r\n\r\n[Key_Shift_Row_6_ID_10]\r\nType=VirtualKeyToggle\r\nWidth=125\r\nLabel=Ctrl\r\nKeyCode=163\r\nNoRepeat=true\r\n\r\n[Key_Shift_Row_6_ID_11]\r\nType=Blank\r\nWidth=25\r\nCluster=Navigation\r\n\r\n[Key_Shift_Row_6_ID_12]\r\nType=VirtualKey\r\nLabel=🠄\r\nCluster=Navigation\r\nKeyCode=37\r\n\r\n[Key_Shift_Row_6_ID_13]\r\nType=VirtualKey\r\nLabel=🠇\r\nCluster=Navigation\r\nKeyCode=40\r\n\r\n[Key_Shift_Row_6_ID_14]\r\nType=VirtualKey\r\nLabel=🠆\r\nCluster=Navigation\r\nKeyCode=39\r\n\r\n[Key_Shift_Row_6_ID_15]\r\nType=Blank\r\nWidth=25\r\nCluster=Numpad\r\n\r\n[Key_Shift_Row_6_ID_16]\r\nType=VirtualKey\r\nWidth=200\r\nLabel=0\\nInsert\r\nCluster=Numpad\r\nKeyCode=96\r\n\r\n[Key_Shift_Row_6_ID_17]\r\nType=VirtualKey\r\nLabel=.\\nDel\r\nCluster=Numpad\r\nKeyCode=110\r\n\r\n\r\n"
  },
  {
    "path": "assets/keyboards/qwerty_ko_2-set.ini",
    "content": "[LayoutInfo]\nName=QWERTY (Korean 2-set)\nAuthor=HisaCat\nHasAltGr=false\nHasClusterFunction=true\nHasClusterNavigation=true\nHasClusterNumpad=true\nHasClusterExtra=true\n\n[Key_Base_Row_0_ID_0]\nType=VirtualKey\nLabel=Esc\nCluster=Function\nKeyCode=27\nNoRepeat=true\n\n[Key_Base_Row_0_ID_1]\nType=Blank\nCluster=Function\n\n[Key_Base_Row_0_ID_2]\nType=VirtualKey\nLabel=F1\nCluster=Function\nKeyCode=112\n\n[Key_Base_Row_0_ID_3]\nType=VirtualKey\nLabel=F2\nCluster=Function\nKeyCode=113\n\n[Key_Base_Row_0_ID_4]\nType=VirtualKey\nLabel=F3\nCluster=Function\nKeyCode=114\n\n[Key_Base_Row_0_ID_5]\nType=VirtualKey\nLabel=F4\nCluster=Function\nKeyCode=115\n\n[Key_Base_Row_0_ID_6]\nType=Blank\nWidth=50\nCluster=Function\n\n[Key_Base_Row_0_ID_7]\nType=VirtualKey\nLabel=F5\nCluster=Function\nKeyCode=116\n\n[Key_Base_Row_0_ID_8]\nType=VirtualKey\nLabel=F6\nCluster=Function\nKeyCode=117\n\n[Key_Base_Row_0_ID_9]\nType=VirtualKey\nLabel=F7\nCluster=Function\nKeyCode=118\n\n[Key_Base_Row_0_ID_10]\nType=VirtualKey\nLabel=F8\nCluster=Function\nKeyCode=119\n\n[Key_Base_Row_0_ID_11]\nType=Blank\nWidth=50\nCluster=Function\n\n[Key_Base_Row_0_ID_12]\nType=VirtualKey\nLabel=F9\nCluster=Function\nKeyCode=120\n\n[Key_Base_Row_0_ID_13]\nType=VirtualKey\nLabel=F10\nCluster=Function\nKeyCode=121\n\n[Key_Base_Row_0_ID_14]\nType=VirtualKey\nLabel=F11\nCluster=Function\nKeyCode=122\n\n[Key_Base_Row_0_ID_15]\nType=VirtualKey\nLabel=F12\nCluster=Function\nKeyCode=123\n\n[Key_Base_Row_0_ID_16]\nType=Blank\nWidth=25\nCluster=Function\n\n[Key_Base_Row_0_ID_17]\nType=VirtualKey\nLabel=Print\\nScreen\nCluster=Function\nKeyCode=44\nNoRepeat=true\n\n[Key_Base_Row_0_ID_18]\nType=VirtualKey\nLabel=Scroll\\nLock\nCluster=Function\nKeyCode=145\nNoRepeat=true\n\n[Key_Base_Row_0_ID_19]\nType=VirtualKey\nLabel=Pause\nCluster=Function\nKeyCode=19\nNoRepeat=true\n\n[Key_Base_Row_0_ID_20]\nType=Blank\nWidth=25\nCluster=Extra\n\n[Key_Base_Row_0_ID_21]\nType=VirtualKey\nLabel=⏯\nCluster=Extra\nKeyCode=179\nNoRepeat=true\n\n[Key_Base_Row_0_ID_22]\nType=VirtualKey\nLabel=◼\nCluster=Extra\nKeyCode=178\nNoRepeat=true\n\n[Key_Base_Row_0_ID_23]\nType=VirtualKey\nLabel=⏮\nCluster=Extra\nKeyCode=177\nNoRepeat=true\n\n[Key_Base_Row_0_ID_24]\nType=VirtualKey\nLabel=⏭\nCluster=Extra\nKeyCode=176\nNoRepeat=true\n\n[Key_Base_Row_1_ID_0]\nType=Blank\nHeight=25\nCluster=Function\n\n[Key_Base_Row_1_ID_1]\nType=Blank\nHeight=25\nCluster=Extra\n\n[Key_Base_Row_2_ID_0]\nType=VirtualKey\nLabel=`##L\\n\nKeyCode=192\n\n[Key_Base_Row_2_ID_1]\nType=VirtualKey\nLabel=1##L\\n\nKeyCode=49\n\n[Key_Base_Row_2_ID_2]\nType=VirtualKey\nLabel=2##L\\n\nKeyCode=50\n\n[Key_Base_Row_2_ID_3]\nType=VirtualKey\nLabel=3##L\\n\nKeyCode=51\n\n[Key_Base_Row_2_ID_4]\nType=VirtualKey\nLabel=4##L\\n\nKeyCode=52\n\n[Key_Base_Row_2_ID_5]\nType=VirtualKey\nLabel=5##L\\n\nKeyCode=53\n\n[Key_Base_Row_2_ID_6]\nType=VirtualKey\nLabel=6##L\\n\nKeyCode=54\n\n[Key_Base_Row_2_ID_7]\nType=VirtualKey\nLabel=7##L\\n\nKeyCode=55\n\n[Key_Base_Row_2_ID_8]\nType=VirtualKey\nLabel=8##L\\n\nKeyCode=56\n\n[Key_Base_Row_2_ID_9]\nType=VirtualKey\nLabel=9##L\\n\nKeyCode=57\n\n[Key_Base_Row_2_ID_10]\nType=VirtualKey\nLabel=0##L\\n\nKeyCode=48\n\n[Key_Base_Row_2_ID_11]\nType=VirtualKey\nLabel=-##L\\n\nKeyCode=189\n\n[Key_Base_Row_2_ID_12]\nType=VirtualKey\nLabel==##L\\n\nKeyCode=187\n\n[Key_Base_Row_2_ID_13]\nType=VirtualKey\nWidth=200\nLabel=Backspace\nKeyCode=8\n\n[Key_Base_Row_2_ID_14]\nType=Blank\nWidth=25\nCluster=Navigation\n\n[Key_Base_Row_2_ID_15]\nType=VirtualKey\nLabel=Insert\nCluster=Navigation\nKeyCode=45\n\n[Key_Base_Row_2_ID_16]\nType=VirtualKey\nLabel=Home\nCluster=Navigation\nKeyCode=36\n\n[Key_Base_Row_2_ID_17]\nType=VirtualKey\nLabel=PgUp\nCluster=Navigation\nKeyCode=33\n\n[Key_Base_Row_2_ID_18]\nType=Blank\nWidth=25\nCluster=Numpad\n\n[Key_Base_Row_2_ID_19]\nType=VirtualKey\nLabel=Num##L\\nLock##L\nCluster=Numpad\nKeyCode=144\nNoRepeat=true\n\n[Key_Base_Row_2_ID_20]\nType=VirtualKey\nLabel=/##L\\n\nCluster=Numpad\nKeyCode=111\n\n[Key_Base_Row_2_ID_21]\nType=VirtualKey\nLabel=*##L\\n\nCluster=Numpad\nKeyCode=106\n\n[Key_Base_Row_2_ID_22]\nType=VirtualKey\nLabel=-##L\\n\nCluster=Numpad\nKeyCode=109\n\n[Key_Base_Row_3_ID_0]\nType=VirtualKey\nWidth=150\nLabel=Tab\nKeyCode=9\n\n[Key_Base_Row_3_ID_1]\nType=VirtualKey\nLabel=q##L\\nㅂ##R\nKeyCode=81\n\n[Key_Base_Row_3_ID_2]\nType=VirtualKey\nLabel=w ##L\\nㅈ##R\nKeyCode=87\n\n[Key_Base_Row_3_ID_3]\nType=VirtualKey\nLabel=e##L\\nㄷ##R\nKeyCode=69\n\n[Key_Base_Row_3_ID_4]\nType=VirtualKey\nLabel=r##L\\nㄱ##R\nKeyCode=82\n\n[Key_Base_Row_3_ID_5]\nType=VirtualKey\nLabel=t##L\\nㅅ##R\nKeyCode=84\n\n[Key_Base_Row_3_ID_6]\nType=VirtualKey\nLabel=y##L\\nㅛ##R\nKeyCode=89\n\n[Key_Base_Row_3_ID_7]\nType=VirtualKey\nLabel=u##L\\nㅕ##R\nKeyCode=85\n\n[Key_Base_Row_3_ID_8]\nType=VirtualKey\nLabel=i##L\\nㅑ##R\nKeyCode=73\n\n[Key_Base_Row_3_ID_9]\nType=VirtualKey\nLabel=o##L\\nㅐ##R\nKeyCode=79\n\n[Key_Base_Row_3_ID_10]\nType=VirtualKey\nLabel=p##L\\nㅔ##R\nKeyCode=80\n\n[Key_Base_Row_3_ID_11]\nType=VirtualKey\nLabel=[##L\\n\nKeyCode=219\n\n[Key_Base_Row_3_ID_12]\nType=VirtualKey\nLabel=]##L\\n\nKeyCode=221\n\n[Key_Base_Row_3_ID_13]\nType=VirtualKey\nWidth=150\nLabel=₩##L\\n\nKeyCode=220\n\n[Key_Base_Row_3_ID_14]\nType=Blank\nWidth=25\nCluster=Navigation\n\n[Key_Base_Row_3_ID_15]\nType=VirtualKey\nLabel=Delete\nCluster=Navigation\nKeyCode=46\n\n[Key_Base_Row_3_ID_16]\nType=VirtualKey\nLabel=End\nCluster=Navigation\nKeyCode=35\n\n[Key_Base_Row_3_ID_17]\nType=VirtualKey\nLabel=PgDn\nCluster=Navigation\nKeyCode=34\n\n[Key_Base_Row_3_ID_18]\nType=Blank\nWidth=25\nCluster=Numpad\n\n[Key_Base_Row_3_ID_19]\nType=VirtualKey\nLabel=7##L\\nHome##L\nCluster=Numpad\nKeyCode=103\n\n[Key_Base_Row_3_ID_20]\nType=VirtualKey\nLabel=8##L\\n↑##L\nCluster=Numpad\nKeyCode=104\n\n[Key_Base_Row_3_ID_21]\nType=VirtualKey\nLabel=9##L\\nPgUp##L\nCluster=Numpad\nKeyCode=105\n\n[Key_Base_Row_3_ID_22]\nType=VirtualKey\nHeight=200\nLabel=+\nCluster=Numpad\nKeyCode=107\n\n[Key_Base_Row_4_ID_0]\nType=VirtualKey\nWidth=175\nLabel=Caps Lock\nKeyCode=20\nNoRepeat=true\n\n[Key_Base_Row_4_ID_1]\nType=VirtualKey\nLabel=a##L\\nㅁ##R\nKeyCode=65\n\n[Key_Base_Row_4_ID_2]\nType=VirtualKey\nLabel=s##L\\nㄴ##R\nKeyCode=83\n\n[Key_Base_Row_4_ID_3]\nType=VirtualKey\nLabel=d##L\\nㅇ##R\nKeyCode=68\n\n[Key_Base_Row_4_ID_4]\nType=VirtualKey\nLabel=f##L\\nㄹ##R\nKeyCode=70\n\n[Key_Base_Row_4_ID_5]\nType=VirtualKey\nLabel=g##L\\nㅎ##R\nKeyCode=71\n\n[Key_Base_Row_4_ID_6]\nType=VirtualKey\nLabel=h##L\\nㅗ##R\nKeyCode=72\n\n[Key_Base_Row_4_ID_7]\nType=VirtualKey\nLabel=j##L\\nㅓ##R\nKeyCode=74\n\n[Key_Base_Row_4_ID_8]\nType=VirtualKey\nLabel=k##L\\nㅏ##R\nKeyCode=75\n\n[Key_Base_Row_4_ID_9]\nType=VirtualKey\nLabel=l##L\\nㅣ##R\nKeyCode=76\n\n[Key_Base_Row_4_ID_10]\nType=VirtualKey\nLabel=;##L\\n\nKeyCode=186\n\n[Key_Base_Row_4_ID_11]\nType=VirtualKey\nLabel='##L\\n\nKeyCode=222\n\n[Key_Base_Row_4_ID_12]\nType=VirtualKey\nWidth=225\nLabel=Enter\nKeyCode=13\n\n[Key_Base_Row_4_ID_13]\nType=Blank\nWidth=325\nCluster=Navigation\n\n[Key_Base_Row_4_ID_14]\nType=Blank\nWidth=25\nCluster=Numpad\n\n[Key_Base_Row_4_ID_15]\nType=VirtualKey\nLabel=4##L\\n←##L\nCluster=Numpad\nKeyCode=100\n\n[Key_Base_Row_4_ID_16]\nType=VirtualKey\nLabel=5##L\\n\nCluster=Numpad\nKeyCode=101\n\n[Key_Base_Row_4_ID_17]\nType=VirtualKey\nLabel=6##L\\n→##L\nCluster=Numpad\nKeyCode=102\n\n[Key_Base_Row_5_ID_0]\nType=VirtualKeyToggle\nWidth=225\nLabel=Shift\nKeyCode=160\nNoRepeat=true\n\n[Key_Base_Row_5_ID_1]\nType=VirtualKey\nLabel=z##L\\nㅋ##R\nKeyCode=90\n\n[Key_Base_Row_5_ID_2]\nType=VirtualKey\nLabel=x##L\\nㅌ##R\nKeyCode=88\n\n[Key_Base_Row_5_ID_3]\nType=VirtualKey\nLabel=c##L\\nㅊ##R\nKeyCode=67\n\n[Key_Base_Row_5_ID_4]\nType=VirtualKey\nLabel=v##L\\nㅍ##R\nKeyCode=86\n\n[Key_Base_Row_5_ID_5]\nType=VirtualKey\nLabel=b##L\\nㅠ##R\nKeyCode=66\n\n[Key_Base_Row_5_ID_6]\nType=VirtualKey\nLabel=n##L\\nㅜ##R\nKeyCode=78\n\n[Key_Base_Row_5_ID_7]\nType=VirtualKey\nLabel=m##L\\nㅡ##R\nKeyCode=77\n\n[Key_Base_Row_5_ID_8]\nType=VirtualKey\nLabel=,##L\\n\nKeyCode=188\n\n[Key_Base_Row_5_ID_9]\nType=VirtualKey\nLabel=.##L\\n\nKeyCode=190\n\n[Key_Base_Row_5_ID_10]\nType=VirtualKey\nLabel=/##L\\n\nKeyCode=191\n\n[Key_Base_Row_5_ID_11]\nType=VirtualKeyToggle\nWidth=275\nLabel=Shift\nKeyCode=161\nNoRepeat=true\n\n[Key_Base_Row_5_ID_12]\nType=Blank\nWidth=125\nCluster=Navigation\n\n[Key_Base_Row_5_ID_13]\nType=VirtualKey\nLabel=🠅\nCluster=Navigation\nKeyCode=38\n\n[Key_Base_Row_5_ID_14]\nType=Blank\nCluster=Navigation\n\n[Key_Base_Row_5_ID_15]\nType=Blank\nWidth=25\nCluster=Numpad\n\n[Key_Base_Row_5_ID_16]\nType=VirtualKey\nLabel=1##L\\nEnd##L\nCluster=Numpad\nKeyCode=97\n\n[Key_Base_Row_5_ID_17]\nType=VirtualKey\nLabel=2##L\\n↓##L\nCluster=Numpad\nKeyCode=98\n\n[Key_Base_Row_5_ID_18]\nType=VirtualKey\nLabel=3##L\\nPgDn##L\nCluster=Numpad\nKeyCode=99\n\n[Key_Base_Row_5_ID_19]\nType=VirtualKey\nHeight=200\nLabel=Enter\nCluster=Numpad\nKeyCode=13\n\n[Key_Base_Row_6_ID_0]\nType=VirtualKeyToggle\nWidth=125\nLabel=Ctrl\nKeyCode=162\nNoRepeat=true\n\n[Key_Base_Row_6_ID_1]\nType=VirtualKeyToggle\nWidth=125\nLabel=Win\nKeyCode=91\nNoRepeat=true\n\n[Key_Base_Row_6_ID_2]\nType=VirtualKeyToggle\nWidth=125\nLabel=Alt\nKeyCode=164\nNoRepeat=true\n\n[Key_Base_Row_6_ID_3]\nType=VirtualKey\nWidth=625\nLabel=\nKeyCode=32\n\n[Key_Base_Row_6_ID_4]\nType=VirtualKey\nWidth=125\nLabel=Alt##L\\n한/영##L\nKeyCode=21\nNoRepeat=true\n\n[Key_Base_Row_6_ID_5]\nType=VirtualKeyToggle\nWidth=125\nLabel=Win\nKeyCode=92\nNoRepeat=true\n\n[Key_Base_Row_6_ID_6]\nType=VirtualKey\nWidth=125\nLabel=Menu\nKeyCode=93\nNoRepeat=true\n\n[Key_Base_Row_6_ID_7]\nType=VirtualKey\nWidth=125\nLabel=Ctrl##L\\n한자##L\nKeyCode=25\nNoRepeat=true\n\n[Key_Base_Row_6_ID_8]\nType=Blank\nWidth=25\nCluster=Navigation\n\n[Key_Base_Row_6_ID_9]\nType=VirtualKey\nLabel=🠄\nCluster=Navigation\nKeyCode=37\n\n[Key_Base_Row_6_ID_10]\nType=VirtualKey\nLabel=🠇\nCluster=Navigation\nKeyCode=40\n\n[Key_Base_Row_6_ID_11]\nType=VirtualKey\nLabel=🠆\nCluster=Navigation\nKeyCode=39\n\n[Key_Base_Row_6_ID_12]\nType=Blank\nWidth=25\nCluster=Numpad\n\n[Key_Base_Row_6_ID_13]\nType=VirtualKey\nWidth=200\nLabel=0##L\\nInsert##L\nCluster=Numpad\nKeyCode=96\n\n[Key_Base_Row_6_ID_14]\nType=VirtualKey\nLabel=.##L\\nDel##L\nCluster=Numpad\nKeyCode=110\n\n[Key_Shift_Row_0_ID_0]\nType=VirtualKey\nLabel=Esc\nCluster=Function\nKeyCode=27\nNoRepeat=true\n\n[Key_Shift_Row_0_ID_1]\nType=Blank\nCluster=Function\n\n[Key_Shift_Row_0_ID_2]\nType=VirtualKey\nLabel=F1\nCluster=Function\nKeyCode=112\n\n[Key_Shift_Row_0_ID_3]\nType=VirtualKey\nLabel=F2\nCluster=Function\nKeyCode=113\n\n[Key_Shift_Row_0_ID_4]\nType=VirtualKey\nLabel=F3\nCluster=Function\nKeyCode=114\n\n[Key_Shift_Row_0_ID_5]\nType=VirtualKey\nLabel=F4\nCluster=Function\nKeyCode=115\n\n[Key_Shift_Row_0_ID_6]\nType=Blank\nWidth=50\nCluster=Function\n\n[Key_Shift_Row_0_ID_7]\nType=VirtualKey\nLabel=F5\nCluster=Function\nKeyCode=116\n\n[Key_Shift_Row_0_ID_8]\nType=VirtualKey\nLabel=F6\nCluster=Function\nKeyCode=117\n\n[Key_Shift_Row_0_ID_9]\nType=VirtualKey\nLabel=F7\nCluster=Function\nKeyCode=118\n\n[Key_Shift_Row_0_ID_10]\nType=VirtualKey\nLabel=F8\nCluster=Function\nKeyCode=119\n\n[Key_Shift_Row_0_ID_11]\nType=Blank\nWidth=50\nCluster=Function\n\n[Key_Shift_Row_0_ID_12]\nType=VirtualKey\nLabel=F9\nCluster=Function\nKeyCode=120\n\n[Key_Shift_Row_0_ID_13]\nType=VirtualKey\nLabel=F10\nCluster=Function\nKeyCode=121\n\n[Key_Shift_Row_0_ID_14]\nType=VirtualKey\nLabel=F11\nCluster=Function\nKeyCode=122\n\n[Key_Shift_Row_0_ID_15]\nType=VirtualKey\nLabel=F12\nCluster=Function\nKeyCode=123\n\n[Key_Shift_Row_0_ID_16]\nType=Blank\nWidth=25\nCluster=Function\n\n[Key_Shift_Row_0_ID_17]\nType=VirtualKey\nLabel=Print\\nScreen\nCluster=Function\nKeyCode=44\nNoRepeat=true\n\n[Key_Shift_Row_0_ID_18]\nType=VirtualKey\nLabel=Scroll\\nLock\nCluster=Function\nKeyCode=145\nNoRepeat=true\n\n[Key_Shift_Row_0_ID_19]\nType=VirtualKey\nLabel=Pause\nCluster=Function\nKeyCode=19\nNoRepeat=true\n\n[Key_Shift_Row_0_ID_20]\nType=Blank\nWidth=25\nCluster=Extra\n\n[Key_Shift_Row_0_ID_21]\nType=VirtualKey\nLabel=⏯\nCluster=Extra\nKeyCode=179\nNoRepeat=true\n\n[Key_Shift_Row_0_ID_22]\nType=VirtualKey\nLabel=◼\nCluster=Extra\nKeyCode=178\nNoRepeat=true\n\n[Key_Shift_Row_0_ID_23]\nType=VirtualKey\nLabel=⏮\nCluster=Extra\nKeyCode=177\nNoRepeat=true\n\n[Key_Shift_Row_0_ID_24]\nType=VirtualKey\nLabel=⏭\nCluster=Extra\nKeyCode=176\nNoRepeat=true\n\n[Key_Shift_Row_1_ID_0]\nType=Blank\nHeight=25\nCluster=Function\n\n[Key_Shift_Row_1_ID_1]\nType=Blank\nHeight=25\nCluster=Extra\n\n[Key_Shift_Row_2_ID_0]\nType=VirtualKey\nLabel=~##L\\n\nKeyCode=192\n\n[Key_Shift_Row_2_ID_1]\nType=VirtualKey\nLabel=!##L\\n\nKeyCode=49\n\n[Key_Shift_Row_2_ID_2]\nType=VirtualKey\nLabel=@##L\\n\nKeyCode=50\n\n[Key_Shift_Row_2_ID_3]\nType=VirtualKey\nLabel=# ##L\\n\nKeyCode=51\n\n[Key_Shift_Row_2_ID_4]\nType=VirtualKey\nLabel=$##L\\n\nKeyCode=52\n\n[Key_Shift_Row_2_ID_5]\nType=VirtualKey\nLabel=%##L\\n\nKeyCode=53\n\n[Key_Shift_Row_2_ID_6]\nType=VirtualKey\nLabel=^##L\\n\nKeyCode=54\n\n[Key_Shift_Row_2_ID_7]\nType=VirtualKey\nLabel=&##L\\n\nKeyCode=55\n\n[Key_Shift_Row_2_ID_8]\nType=VirtualKey\nLabel=*##L\\n\nKeyCode=56\n\n[Key_Shift_Row_2_ID_9]\nType=VirtualKey\nLabel=(##L\\n\nKeyCode=57\n\n[Key_Shift_Row_2_ID_10]\nType=VirtualKey\nLabel=)##L\\n\nKeyCode=48\n\n[Key_Shift_Row_2_ID_11]\nType=VirtualKey\nLabel=_##L\\n\nKeyCode=189\n\n[Key_Shift_Row_2_ID_12]\nType=VirtualKey\nLabel=+##L\\n\nKeyCode=187\n\n[Key_Shift_Row_2_ID_13]\nType=VirtualKey\nWidth=200\nLabel=Backspace\nKeyCode=8\n\n[Key_Shift_Row_2_ID_14]\nType=Blank\nWidth=25\nCluster=Navigation\n\n[Key_Shift_Row_2_ID_15]\nType=VirtualKey\nLabel=Insert\nCluster=Navigation\nKeyCode=45\n\n[Key_Shift_Row_2_ID_16]\nType=VirtualKey\nLabel=Home\nCluster=Navigation\nKeyCode=36\n\n[Key_Shift_Row_2_ID_17]\nType=VirtualKey\nLabel=PgUp\nCluster=Navigation\nKeyCode=33\n\n[Key_Shift_Row_2_ID_18]\nType=Blank\nWidth=25\nCluster=Numpad\n\n[Key_Shift_Row_2_ID_19]\nType=VirtualKey\nLabel=Num##L\\nLock##L\nCluster=Numpad\nKeyCode=144\nNoRepeat=true\n\n[Key_Shift_Row_2_ID_20]\nType=VirtualKey\nLabel=/##L\\n\nCluster=Numpad\nKeyCode=111\n\n[Key_Shift_Row_2_ID_21]\nType=VirtualKey\nLabel=*##L\\n\nCluster=Numpad\nKeyCode=106\n\n[Key_Shift_Row_2_ID_22]\nType=VirtualKey\nLabel=-##L\\n\nCluster=Numpad\nKeyCode=109\n\n[Key_Shift_Row_3_ID_0]\nType=VirtualKey\nWidth=150\nLabel=Tab\nKeyCode=9\n\n[Key_Shift_Row_3_ID_1]\nType=VirtualKey\nLabel=Q##L\\nㅃ##R\nKeyCode=81\n\n[Key_Shift_Row_3_ID_2]\nType=VirtualKey\nLabel=W##L\\nㅉ##R\nKeyCode=87\n\n[Key_Shift_Row_3_ID_3]\nType=VirtualKey\nLabel=E##L\\nㄸ##R\nKeyCode=69\n\n[Key_Shift_Row_3_ID_4]\nType=VirtualKey\nLabel=R##L\\nㄲ##R\nKeyCode=82\n\n[Key_Shift_Row_3_ID_5]\nType=VirtualKey\nLabel=T##L\\nㅆ##R\nKeyCode=84\n\n[Key_Shift_Row_3_ID_6]\nType=VirtualKey\nLabel=Y##L\\nㅛ##R\nKeyCode=89\n\n[Key_Shift_Row_3_ID_7]\nType=VirtualKey\nLabel=U##L\\nㅕ##R\nKeyCode=85\n\n[Key_Shift_Row_3_ID_8]\nType=VirtualKey\nLabel=I##L\\nㅑ##R\nKeyCode=73\n\n[Key_Shift_Row_3_ID_9]\nType=VirtualKey\nLabel=O##L\\nㅒ##R\nKeyCode=79\n\n[Key_Shift_Row_3_ID_10]\nType=VirtualKey\nLabel=P##L\\nㅖ##R\nKeyCode=80\n\n[Key_Shift_Row_3_ID_11]\nType=VirtualKey\nLabel={##L\\n\nKeyCode=219\n\n[Key_Shift_Row_3_ID_12]\nType=VirtualKey\nLabel=}##L\\n\nKeyCode=221\n\n[Key_Shift_Row_3_ID_13]\nType=VirtualKey\nWidth=150\nLabel=|##L\\n\nKeyCode=220\n\n[Key_Shift_Row_3_ID_14]\nType=Blank\nWidth=25\nCluster=Navigation\n\n[Key_Shift_Row_3_ID_15]\nType=VirtualKey\nLabel=Delete\nCluster=Navigation\nKeyCode=46\n\n[Key_Shift_Row_3_ID_16]\nType=VirtualKey\nLabel=End\nCluster=Navigation\nKeyCode=35\n\n[Key_Shift_Row_3_ID_17]\nType=VirtualKey\nLabel=PgDn\nCluster=Navigation\nKeyCode=34\n\n[Key_Shift_Row_3_ID_18]\nType=Blank\nWidth=25\nCluster=Numpad\n\n[Key_Shift_Row_3_ID_19]\nType=VirtualKey\nLabel=7##L\\nHome##L\nCluster=Numpad\nKeyCode=103\n\n[Key_Shift_Row_3_ID_20]\nType=VirtualKey\nLabel=8##L\\n↑##L\nCluster=Numpad\nKeyCode=104\n\n[Key_Shift_Row_3_ID_21]\nType=VirtualKey\nLabel=9##L\\nPgUp##L\nCluster=Numpad\nKeyCode=105\n\n[Key_Shift_Row_3_ID_22]\nType=VirtualKey\nHeight=200\nLabel=+\nCluster=Numpad\nKeyCode=107\n\n[Key_Shift_Row_4_ID_0]\nType=VirtualKey\nWidth=175\nLabel=Caps Lock\nKeyCode=20\nNoRepeat=true\n\n[Key_Shift_Row_4_ID_1]\nType=VirtualKey\nLabel=A##L\\nㅁ##R\nKeyCode=65\n\n[Key_Shift_Row_4_ID_2]\nType=VirtualKey\nLabel=S##L\\nㄴ##R\nKeyCode=83\n\n[Key_Shift_Row_4_ID_3]\nType=VirtualKey\nLabel=D##L\\nㅇ##R\nKeyCode=68\n\n[Key_Shift_Row_4_ID_4]\nType=VirtualKey\nLabel=F##L\\nㄹ##R\nKeyCode=70\n\n[Key_Shift_Row_4_ID_5]\nType=VirtualKey\nLabel=G##L\\nㅎ##R\nKeyCode=71\n\n[Key_Shift_Row_4_ID_6]\nType=VirtualKey\nLabel=H##L\\nㅗ##R\nKeyCode=72\n\n[Key_Shift_Row_4_ID_7]\nType=VirtualKey\nLabel=J##L\\nㅓ##R\nKeyCode=74\n\n[Key_Shift_Row_4_ID_8]\nType=VirtualKey\nLabel=K##L\\nㅏ##R\nKeyCode=75\n\n[Key_Shift_Row_4_ID_9]\nType=VirtualKey\nLabel=L##L\\nㅣ##R\nKeyCode=76\n\n[Key_Shift_Row_4_ID_10]\nType=VirtualKey\nLabel=:##L\\n\nKeyCode=186\n\n[Key_Shift_Row_4_ID_11]\nType=VirtualKey\nLabel=\"##L\\n\nKeyCode=222\n\n[Key_Shift_Row_4_ID_12]\nType=VirtualKey\nWidth=225\nLabel=Enter\nKeyCode=13\n\n[Key_Shift_Row_4_ID_13]\nType=Blank\nWidth=325\nCluster=Navigation\n\n[Key_Shift_Row_4_ID_14]\nType=Blank\nWidth=25\nCluster=Numpad\n\n[Key_Shift_Row_4_ID_15]\nType=VirtualKey\nLabel=4##L\\n←##L\nCluster=Numpad\nKeyCode=100\n\n[Key_Shift_Row_4_ID_16]\nType=VirtualKey\nLabel=5##L\\n\nCluster=Numpad\nKeyCode=101\n\n[Key_Shift_Row_4_ID_17]\nType=VirtualKey\nLabel=6##L\\n→##L\nCluster=Numpad\nKeyCode=102\n\n[Key_Shift_Row_5_ID_0]\nType=VirtualKeyToggle\nWidth=225\nLabel=Shift\nKeyCode=160\nNoRepeat=true\n\n[Key_Shift_Row_5_ID_1]\nType=VirtualKey\nLabel=Z##L\\nㅋ##R\nKeyCode=90\n\n[Key_Shift_Row_5_ID_2]\nType=VirtualKey\nLabel=X##L\\nㅌ##R\nKeyCode=88\n\n[Key_Shift_Row_5_ID_3]\nType=VirtualKey\nLabel=C##L\\nㅊ##R\nKeyCode=67\n\n[Key_Shift_Row_5_ID_4]\nType=VirtualKey\nLabel=V##L\\nㅍ##R\nKeyCode=86\n\n[Key_Shift_Row_5_ID_5]\nType=VirtualKey\nLabel=B##L\\nㅠ##R\nKeyCode=66\n\n[Key_Shift_Row_5_ID_6]\nType=VirtualKey\nLabel=N##L\\nㅜ##R\nKeyCode=78\n\n[Key_Shift_Row_5_ID_7]\nType=VirtualKey\nLabel=M##L\\nㅡ##R\nKeyCode=77\n\n[Key_Shift_Row_5_ID_8]\nType=VirtualKey\nLabel=<##L\\n\nKeyCode=188\n\n[Key_Shift_Row_5_ID_9]\nType=VirtualKey\nLabel=>##L\\n\nKeyCode=190\n\n[Key_Shift_Row_5_ID_10]\nType=VirtualKey\nLabel=?##L\\n\nKeyCode=191\n\n[Key_Shift_Row_5_ID_11]\nType=VirtualKeyToggle\nWidth=275\nLabel=Shift\nKeyCode=161\nNoRepeat=true\n\n[Key_Shift_Row_5_ID_12]\nType=Blank\nWidth=125\nCluster=Navigation\n\n[Key_Shift_Row_5_ID_13]\nType=VirtualKey\nLabel=🠅\nCluster=Navigation\nKeyCode=38\n\n[Key_Shift_Row_5_ID_14]\nType=Blank\nCluster=Navigation\n\n[Key_Shift_Row_5_ID_15]\nType=Blank\nWidth=25\nCluster=Numpad\n\n[Key_Shift_Row_5_ID_16]\nType=VirtualKey\nLabel=1##L\\nEnd##L\nCluster=Numpad\nKeyCode=97\n\n[Key_Shift_Row_5_ID_17]\nType=VirtualKey\nLabel=2##L\\n↓##L\nCluster=Numpad\nKeyCode=98\n\n[Key_Shift_Row_5_ID_18]\nType=VirtualKey\nLabel=3##L\\nPgDn##L\nCluster=Numpad\nKeyCode=99\n\n[Key_Shift_Row_5_ID_19]\nType=VirtualKey\nHeight=200\nLabel=Enter\nCluster=Numpad\nKeyCode=13\n\n[Key_Shift_Row_6_ID_0]\nType=VirtualKeyToggle\nWidth=125\nLabel=Ctrl\nKeyCode=162\nNoRepeat=true\n\n[Key_Shift_Row_6_ID_1]\nType=VirtualKeyToggle\nWidth=125\nLabel=Win\nKeyCode=91\nNoRepeat=true\n\n[Key_Shift_Row_6_ID_2]\nType=VirtualKeyToggle\nWidth=125\nLabel=Alt\nKeyCode=164\nNoRepeat=true\n\n[Key_Shift_Row_6_ID_3]\nType=VirtualKey\nWidth=625\nLabel=\nKeyCode=32\n\n[Key_Shift_Row_6_ID_4]\nType=VirtualKey\nWidth=125\nLabel=Alt##L\\n한/영##L\nKeyCode=21\nNoRepeat=true\n\n[Key_Shift_Row_6_ID_5]\nType=VirtualKeyToggle\nWidth=125\nLabel=Win\nKeyCode=92\nNoRepeat=true\n\n[Key_Shift_Row_6_ID_6]\nType=VirtualKey\nWidth=125\nLabel=Menu\nKeyCode=93\nNoRepeat=true\n\n[Key_Shift_Row_6_ID_7]\nType=VirtualKey\nWidth=125\nLabel=Ctrl##L\\n한자##L\nKeyCode=25\nNoRepeat=true\n\n[Key_Shift_Row_6_ID_8]\nType=Blank\nWidth=25\nCluster=Navigation\n\n[Key_Shift_Row_6_ID_9]\nType=VirtualKey\nLabel=🠄\nCluster=Navigation\nKeyCode=37\n\n[Key_Shift_Row_6_ID_10]\nType=VirtualKey\nLabel=🠇\nCluster=Navigation\nKeyCode=40\n\n[Key_Shift_Row_6_ID_11]\nType=VirtualKey\nLabel=🠆\nCluster=Navigation\nKeyCode=39\n\n[Key_Shift_Row_6_ID_12]\nType=Blank\nWidth=25\nCluster=Numpad\n\n[Key_Shift_Row_6_ID_13]\nType=VirtualKey\nWidth=200\nLabel=0##L\\nInsert##L\nCluster=Numpad\nKeyCode=96\n\n[Key_Shift_Row_6_ID_14]\nType=VirtualKey\nLabel=.##L\\nDel##L\nCluster=Numpad\nKeyCode=110\n\n"
  },
  {
    "path": "assets/keyboards/qwerty_latam.ini",
    "content": "[LayoutInfo]\nName=QWERTY (Latin America)\nAuthor=\nHasAltGr=true\nHasClusterFunction=true\nHasClusterNavigation=true\nHasClusterNumpad=true\nHasClusterExtra=true\n\n[Key_Base_Row_0_ID_0]\nType=VirtualKey\nLabel=Esc\nCluster=Function\nKeyCode=27\nNoRepeat=true\n\n[Key_Base_Row_0_ID_1]\nType=Blank\nCluster=Function\n\n[Key_Base_Row_0_ID_2]\nType=VirtualKey\nLabel=F1\nCluster=Function\nKeyCode=112\n\n[Key_Base_Row_0_ID_3]\nType=VirtualKey\nLabel=F2\nCluster=Function\nKeyCode=113\n\n[Key_Base_Row_0_ID_4]\nType=VirtualKey\nLabel=F3\nCluster=Function\nKeyCode=114\n\n[Key_Base_Row_0_ID_5]\nType=VirtualKey\nLabel=F4\nCluster=Function\nKeyCode=115\n\n[Key_Base_Row_0_ID_6]\nType=Blank\nWidth=50\nCluster=Function\n\n[Key_Base_Row_0_ID_7]\nType=VirtualKey\nLabel=F5\nCluster=Function\nKeyCode=116\n\n[Key_Base_Row_0_ID_8]\nType=VirtualKey\nLabel=F6\nCluster=Function\nKeyCode=117\n\n[Key_Base_Row_0_ID_9]\nType=VirtualKey\nLabel=F7\nCluster=Function\nKeyCode=118\n\n[Key_Base_Row_0_ID_10]\nType=VirtualKey\nLabel=F8\nCluster=Function\nKeyCode=119\n\n[Key_Base_Row_0_ID_11]\nType=Blank\nWidth=50\nCluster=Function\n\n[Key_Base_Row_0_ID_12]\nType=VirtualKey\nLabel=F9\nCluster=Function\nKeyCode=120\n\n[Key_Base_Row_0_ID_13]\nType=VirtualKey\nLabel=F10\nCluster=Function\nKeyCode=121\n\n[Key_Base_Row_0_ID_14]\nType=VirtualKey\nLabel=F11\nCluster=Function\nKeyCode=122\n\n[Key_Base_Row_0_ID_15]\nType=VirtualKey\nLabel=F12\nCluster=Function\nKeyCode=123\n\n[Key_Base_Row_0_ID_16]\nType=Blank\nWidth=25\nCluster=Function\n\n[Key_Base_Row_0_ID_17]\nType=VirtualKey\nLabel=Impr\\nPant\nCluster=Function\nKeyCode=44\nNoRepeat=true\n\n[Key_Base_Row_0_ID_18]\nType=VirtualKey\nLabel=Bloq\\nDespl\nCluster=Function\nKeyCode=145\nNoRepeat=true\n\n[Key_Base_Row_0_ID_19]\nType=VirtualKey\nLabel=Pausa\nCluster=Function\nKeyCode=19\nNoRepeat=true\n\n[Key_Base_Row_0_ID_20]\nType=Blank\nWidth=25\nCluster=Extra\n\n[Key_Base_Row_0_ID_21]\nType=VirtualKey\nLabel=⏯\nCluster=Extra\nKeyCode=179\nNoRepeat=true\n\n[Key_Base_Row_0_ID_22]\nType=VirtualKey\nLabel=◼\nCluster=Extra\nKeyCode=178\nNoRepeat=true\n\n[Key_Base_Row_0_ID_23]\nType=VirtualKey\nLabel=⏮\nCluster=Extra\nKeyCode=177\nNoRepeat=true\n\n[Key_Base_Row_0_ID_24]\nType=VirtualKey\nLabel=⏭\nCluster=Extra\nKeyCode=176\nNoRepeat=true\n\n[Key_Base_Row_1_ID_0]\nType=Blank\nHeight=25\nCluster=Function\n\n[Key_Base_Row_1_ID_1]\nType=Blank\nHeight=25\nCluster=Extra\n\n[Key_Base_Row_2_ID_0]\nType=String\nLabel=|\nString=|\n\n[Key_Base_Row_2_ID_1]\nType=VirtualKey\nLabel=1\nKeyCode=49\n\n[Key_Base_Row_2_ID_2]\nType=VirtualKey\nLabel=2\nKeyCode=50\n\n[Key_Base_Row_2_ID_3]\nType=VirtualKey\nLabel=3\nKeyCode=51\n\n[Key_Base_Row_2_ID_4]\nType=VirtualKey\nLabel=4\nKeyCode=52\n\n[Key_Base_Row_2_ID_5]\nType=VirtualKey\nLabel=5\nKeyCode=53\n\n[Key_Base_Row_2_ID_6]\nType=VirtualKey\nLabel=6\nKeyCode=54\n\n[Key_Base_Row_2_ID_7]\nType=VirtualKey\nLabel=7\nKeyCode=55\n\n[Key_Base_Row_2_ID_8]\nType=VirtualKey\nLabel=8\nKeyCode=56\n\n[Key_Base_Row_2_ID_9]\nType=VirtualKey\nLabel=9\nKeyCode=57\n\n[Key_Base_Row_2_ID_10]\nType=VirtualKey\nLabel=0\nKeyCode=48\n\n[Key_Base_Row_2_ID_11]\nType=String\nLabel='\nString='\n\n[Key_Base_Row_2_ID_12]\nType=String\nLabel=¿\nString=¿\n\n[Key_Base_Row_2_ID_13]\nType=VirtualKey\nWidth=200\nLabel=⟵\nKeyCode=8\n\n[Key_Base_Row_2_ID_14]\nType=Blank\nWidth=25\nCluster=Navigation\n\n[Key_Base_Row_2_ID_15]\nType=VirtualKey\nLabel=Insert\nCluster=Navigation\nKeyCode=45\n\n[Key_Base_Row_2_ID_16]\nType=VirtualKey\nLabel=Incio\nCluster=Navigation\nKeyCode=36\n\n[Key_Base_Row_2_ID_17]\nType=VirtualKey\nLabel=RePág\nCluster=Navigation\nKeyCode=33\n\n[Key_Base_Row_2_ID_18]\nType=Blank\nWidth=25\nCluster=Numpad\n\n[Key_Base_Row_2_ID_19]\nType=VirtualKey\nLabel=Bloq\\nNum\nCluster=Numpad\nKeyCode=144\nNoRepeat=true\n\n[Key_Base_Row_2_ID_20]\nType=VirtualKey\nLabel=/\nCluster=Numpad\nKeyCode=111\n\n[Key_Base_Row_2_ID_21]\nType=VirtualKey\nLabel=*\nCluster=Numpad\nKeyCode=106\n\n[Key_Base_Row_2_ID_22]\nType=VirtualKey\nLabel=-\nCluster=Numpad\nKeyCode=109\n\n[Key_Base_Row_3_ID_0]\nType=VirtualKey\nWidth=150\nLabel=⭾\nKeyCode=9\n\n[Key_Base_Row_3_ID_1]\nType=VirtualKey\nLabel=q\nKeyCode=81\n\n[Key_Base_Row_3_ID_2]\nType=VirtualKey\nLabel=w\nKeyCode=87\n\n[Key_Base_Row_3_ID_3]\nType=VirtualKey\nLabel=e\nKeyCode=69\n\n[Key_Base_Row_3_ID_4]\nType=VirtualKey\nLabel=r\nKeyCode=82\n\n[Key_Base_Row_3_ID_5]\nType=VirtualKey\nLabel=t\nKeyCode=84\n\n[Key_Base_Row_3_ID_6]\nType=VirtualKey\nLabel=y\nKeyCode=89\n\n[Key_Base_Row_3_ID_7]\nType=VirtualKey\nLabel=u\nKeyCode=85\n\n[Key_Base_Row_3_ID_8]\nType=VirtualKey\nLabel=i\nKeyCode=73\n\n[Key_Base_Row_3_ID_9]\nType=VirtualKey\nLabel=o\nKeyCode=79\n\n[Key_Base_Row_3_ID_10]\nType=VirtualKey\nLabel=p\nKeyCode=80\n\n[Key_Base_Row_3_ID_11]\nType=String\nLabel=´\nString=´\n\n[Key_Base_Row_3_ID_12]\nType=String\nLabel=+\nString=+\n\n[Key_Base_Row_3_ID_13]\nType=VirtualKeyIsoEnter\nWidth=150\nLabel=\nKeyCode=13\n\n[Key_Base_Row_3_ID_14]\nType=Blank\nWidth=25\nCluster=Navigation\n\n[Key_Base_Row_3_ID_15]\nType=VirtualKey\nLabel=Supr\nCluster=Navigation\nKeyCode=46\n\n[Key_Base_Row_3_ID_16]\nType=VirtualKey\nLabel=Fin\nCluster=Navigation\nKeyCode=35\n\n[Key_Base_Row_3_ID_17]\nType=VirtualKey\nLabel=AvPág\nCluster=Navigation\nKeyCode=34\n\n[Key_Base_Row_3_ID_18]\nType=Blank\nWidth=25\nCluster=Numpad\n\n[Key_Base_Row_3_ID_19]\nType=VirtualKey\nLabel=7\nCluster=Numpad\nKeyCode=103\n\n[Key_Base_Row_3_ID_20]\nType=VirtualKey\nLabel=8\nCluster=Numpad\nKeyCode=104\n\n[Key_Base_Row_3_ID_21]\nType=VirtualKey\nLabel=9\nCluster=Numpad\nKeyCode=105\n\n[Key_Base_Row_3_ID_22]\nType=VirtualKey\nHeight=200\nLabel=+\nCluster=Numpad\nKeyCode=107\n\n[Key_Base_Row_4_ID_0]\nType=VirtualKey\nWidth=175\nLabel=Bloq Mayús\nKeyCode=20\nNoRepeat=true\n\n[Key_Base_Row_4_ID_1]\nType=VirtualKey\nLabel=a\nKeyCode=65\n\n[Key_Base_Row_4_ID_2]\nType=VirtualKey\nLabel=s\nKeyCode=83\n\n[Key_Base_Row_4_ID_3]\nType=VirtualKey\nLabel=d\nKeyCode=68\n\n[Key_Base_Row_4_ID_4]\nType=VirtualKey\nLabel=f\nKeyCode=70\n\n[Key_Base_Row_4_ID_5]\nType=VirtualKey\nLabel=g\nKeyCode=71\n\n[Key_Base_Row_4_ID_6]\nType=VirtualKey\nLabel=h\nKeyCode=72\n\n[Key_Base_Row_4_ID_7]\nType=VirtualKey\nLabel=j\nKeyCode=74\n\n[Key_Base_Row_4_ID_8]\nType=VirtualKey\nLabel=k\nKeyCode=75\n\n[Key_Base_Row_4_ID_9]\nType=VirtualKey\nLabel=l\nKeyCode=76\n\n[Key_Base_Row_4_ID_10]\nType=String\nLabel=ñ\nString=ñ\n\n[Key_Base_Row_4_ID_11]\nType=String\nLabel={\nString={\n\n[Key_Base_Row_4_ID_12]\nType=String\nLabel=}\nString=}\n\n[Key_Base_Row_4_ID_13]\nType=VirtualKeyIsoEnter\nWidth=125\nLabel=↵\nKeyCode=13\n\n[Key_Base_Row_4_ID_14]\nType=Blank\nWidth=325\nCluster=Navigation\n\n[Key_Base_Row_4_ID_15]\nType=Blank\nWidth=25\nCluster=Numpad\n\n[Key_Base_Row_4_ID_16]\nType=VirtualKey\nLabel=4\nCluster=Numpad\nKeyCode=100\n\n[Key_Base_Row_4_ID_17]\nType=VirtualKey\nLabel=5\nCluster=Numpad\nKeyCode=101\n\n[Key_Base_Row_4_ID_18]\nType=VirtualKey\nLabel=6\nCluster=Numpad\nKeyCode=102\n\n[Key_Base_Row_5_ID_0]\nType=VirtualKeyToggle\nWidth=125\nLabel=🡅\nKeyCode=160\nNoRepeat=true\n\n[Key_Base_Row_5_ID_1]\nType=String\nLabel=<\nString=<\n\n[Key_Base_Row_5_ID_2]\nType=VirtualKey\nLabel=z\nKeyCode=90\n\n[Key_Base_Row_5_ID_3]\nType=VirtualKey\nLabel=x\nKeyCode=88\n\n[Key_Base_Row_5_ID_4]\nType=VirtualKey\nLabel=c\nKeyCode=67\n\n[Key_Base_Row_5_ID_5]\nType=VirtualKey\nLabel=v\nKeyCode=86\n\n[Key_Base_Row_5_ID_6]\nType=VirtualKey\nLabel=b\nKeyCode=66\n\n[Key_Base_Row_5_ID_7]\nType=VirtualKey\nLabel=n\nKeyCode=78\n\n[Key_Base_Row_5_ID_8]\nType=VirtualKey\nLabel=m\nKeyCode=77\n\n[Key_Base_Row_5_ID_9]\nType=String\nLabel=,\nString=,\n\n[Key_Base_Row_5_ID_10]\nType=String\nLabel=.\nString=.\n\n[Key_Base_Row_5_ID_11]\nType=String\nLabel=-\nString=-\n\n[Key_Base_Row_5_ID_12]\nType=VirtualKeyToggle\nWidth=275\nLabel=🡅\nKeyCode=161\nNoRepeat=true\n\n[Key_Base_Row_5_ID_13]\nType=Blank\nWidth=125\nCluster=Navigation\n\n[Key_Base_Row_5_ID_14]\nType=VirtualKey\nLabel=🠅\nCluster=Navigation\nKeyCode=38\n\n[Key_Base_Row_5_ID_15]\nType=Blank\nCluster=Navigation\n\n[Key_Base_Row_5_ID_16]\nType=Blank\nWidth=25\nCluster=Numpad\n\n[Key_Base_Row_5_ID_17]\nType=VirtualKey\nLabel=1\nCluster=Numpad\nKeyCode=97\n\n[Key_Base_Row_5_ID_18]\nType=VirtualKey\nLabel=2\nCluster=Numpad\nKeyCode=98\n\n[Key_Base_Row_5_ID_19]\nType=VirtualKey\nLabel=3\nCluster=Numpad\nKeyCode=99\n\n[Key_Base_Row_5_ID_20]\nType=VirtualKey\nHeight=200\nLabel=Intro\nCluster=Numpad\nKeyCode=13\n\n[Key_Base_Row_6_ID_0]\nType=VirtualKeyToggle\nWidth=125\nLabel=Ctrl\nKeyCode=162\nNoRepeat=true\n\n[Key_Base_Row_6_ID_1]\nType=VirtualKeyToggle\nWidth=125\nLabel=Win\nKeyCode=91\nNoRepeat=true\n\n[Key_Base_Row_6_ID_2]\nType=VirtualKeyToggle\nWidth=125\nLabel=Alt\nKeyCode=164\nNoRepeat=true\n\n[Key_Base_Row_6_ID_3]\nType=VirtualKey\nWidth=625\nLabel=\nKeyCode=32\n\n[Key_Base_Row_6_ID_4]\nType=VirtualKeyToggle\nWidth=125\nLabel=AltGr\nKeyCode=165\nNoRepeat=true\n\n[Key_Base_Row_6_ID_5]\nType=VirtualKeyToggle\nWidth=125\nLabel=Win\nKeyCode=92\nNoRepeat=true\n\n[Key_Base_Row_6_ID_6]\nType=VirtualKey\nWidth=125\nLabel=Menu\nKeyCode=93\nNoRepeat=true\n\n[Key_Base_Row_6_ID_7]\nType=VirtualKeyToggle\nWidth=125\nLabel=Ctrl\nKeyCode=163\nNoRepeat=true\n\n[Key_Base_Row_6_ID_8]\nType=Blank\nWidth=25\nCluster=Navigation\n\n[Key_Base_Row_6_ID_9]\nType=VirtualKey\nLabel=🠄\nCluster=Navigation\nKeyCode=37\n\n[Key_Base_Row_6_ID_10]\nType=VirtualKey\nLabel=🠇\nCluster=Navigation\nKeyCode=40\n\n[Key_Base_Row_6_ID_11]\nType=VirtualKey\nLabel=🠆\nCluster=Navigation\nKeyCode=39\n\n[Key_Base_Row_6_ID_12]\nType=Blank\nWidth=25\nCluster=Numpad\n\n[Key_Base_Row_6_ID_13]\nType=VirtualKey\nWidth=200\nLabel=0\nCluster=Numpad\nKeyCode=96\n\n[Key_Base_Row_6_ID_14]\nType=VirtualKey\nLabel=.\nCluster=Numpad\nKeyCode=110\n\n[Key_Shift_Row_0_ID_0]\nType=VirtualKey\nLabel=Esc\nCluster=Function\nKeyCode=27\nNoRepeat=true\n\n[Key_Shift_Row_0_ID_1]\nType=Blank\nCluster=Function\n\n[Key_Shift_Row_0_ID_2]\nType=VirtualKey\nLabel=F1\nCluster=Function\nKeyCode=112\n\n[Key_Shift_Row_0_ID_3]\nType=VirtualKey\nLabel=F2\nCluster=Function\nKeyCode=113\n\n[Key_Shift_Row_0_ID_4]\nType=VirtualKey\nLabel=F3\nCluster=Function\nKeyCode=114\n\n[Key_Shift_Row_0_ID_5]\nType=VirtualKey\nLabel=F4\nCluster=Function\nKeyCode=115\n\n[Key_Shift_Row_0_ID_6]\nType=Blank\nWidth=50\nCluster=Function\n\n[Key_Shift_Row_0_ID_7]\nType=VirtualKey\nLabel=F5\nCluster=Function\nKeyCode=116\n\n[Key_Shift_Row_0_ID_8]\nType=VirtualKey\nLabel=F6\nCluster=Function\nKeyCode=117\n\n[Key_Shift_Row_0_ID_9]\nType=VirtualKey\nLabel=F7\nCluster=Function\nKeyCode=118\n\n[Key_Shift_Row_0_ID_10]\nType=VirtualKey\nLabel=F8\nCluster=Function\nKeyCode=119\n\n[Key_Shift_Row_0_ID_11]\nType=Blank\nWidth=50\nCluster=Function\n\n[Key_Shift_Row_0_ID_12]\nType=VirtualKey\nLabel=F9\nCluster=Function\nKeyCode=120\n\n[Key_Shift_Row_0_ID_13]\nType=VirtualKey\nLabel=F10\nCluster=Function\nKeyCode=121\n\n[Key_Shift_Row_0_ID_14]\nType=VirtualKey\nLabel=F11\nCluster=Function\nKeyCode=122\n\n[Key_Shift_Row_0_ID_15]\nType=VirtualKey\nLabel=F12\nCluster=Function\nKeyCode=123\n\n[Key_Shift_Row_0_ID_16]\nType=Blank\nWidth=25\nCluster=Function\n\n[Key_Shift_Row_0_ID_17]\nType=VirtualKey\nLabel=Impr\\nPant\nCluster=Function\nKeyCode=44\nNoRepeat=true\n\n[Key_Shift_Row_0_ID_18]\nType=VirtualKey\nLabel=Bloq\\nDespl\nCluster=Function\nKeyCode=145\nNoRepeat=true\n\n[Key_Shift_Row_0_ID_19]\nType=VirtualKey\nLabel=Pausa\nCluster=Function\nKeyCode=19\nNoRepeat=true\n\n[Key_Shift_Row_0_ID_20]\nType=Blank\nWidth=25\nCluster=Extra\n\n[Key_Shift_Row_0_ID_21]\nType=VirtualKey\nLabel=🡰\nCluster=Extra\nKeyCode=166\nNoRepeat=true\n\n[Key_Shift_Row_0_ID_22]\nType=VirtualKey\nLabel=🡲\nCluster=Extra\nKeyCode=167\nNoRepeat=true\n\n[Key_Shift_Row_0_ID_23]\nType=VirtualKey\nLabel=🔇\nCluster=Extra\nKeyCode=173\nNoRepeat=true\n\n[Key_Shift_Row_1_ID_0]\nType=Blank\nHeight=25\nCluster=Function\n\n[Key_Shift_Row_1_ID_1]\nType=Blank\nHeight=25\nCluster=Extra\n\n[Key_Shift_Row_2_ID_0]\nType=String\nLabel=°\nString=°\n\n[Key_Shift_Row_2_ID_1]\nType=String\nLabel=!\nString=!\n\n[Key_Shift_Row_2_ID_2]\nType=String\nLabel=\"\nString=\"\n\n[Key_Shift_Row_2_ID_3]\nType=String\nLabel=#\nString=#\n\n[Key_Shift_Row_2_ID_4]\nType=String\nLabel=$\nString=$\n\n[Key_Shift_Row_2_ID_5]\nType=String\nLabel=%\nString=%\n\n[Key_Shift_Row_2_ID_6]\nType=String\nLabel=&\nString=&\n\n[Key_Shift_Row_2_ID_7]\nType=String\nLabel=/\nString=/\n\n[Key_Shift_Row_2_ID_8]\nType=String\nLabel=(\nString=(\n\n[Key_Shift_Row_2_ID_9]\nType=String\nLabel=)\nString=)\n\n[Key_Shift_Row_2_ID_10]\nType=String\nLabel==\nString==\n\n[Key_Shift_Row_2_ID_11]\nType=String\nLabel=?\nString=?\n\n[Key_Shift_Row_2_ID_12]\nType=String\nLabel=¡\nString=¡\n\n[Key_Shift_Row_2_ID_13]\nType=VirtualKey\nWidth=200\nLabel=⟵\nKeyCode=8\n\n[Key_Shift_Row_2_ID_14]\nType=Blank\nWidth=25\nCluster=Navigation\n\n[Key_Shift_Row_2_ID_15]\nType=VirtualKey\nLabel=Insert\nCluster=Navigation\nKeyCode=45\n\n[Key_Shift_Row_2_ID_16]\nType=VirtualKey\nLabel=Incio\nCluster=Navigation\nKeyCode=36\n\n[Key_Shift_Row_2_ID_17]\nType=VirtualKey\nLabel=RePág\nCluster=Navigation\nKeyCode=33\n\n[Key_Shift_Row_2_ID_18]\nType=Blank\nWidth=25\nCluster=Numpad\n\n[Key_Shift_Row_2_ID_19]\nType=VirtualKey\nLabel=Bloq\\nNum\nCluster=Numpad\nKeyCode=144\nNoRepeat=true\n\n[Key_Shift_Row_2_ID_20]\nType=VirtualKey\nLabel=/\nCluster=Numpad\nKeyCode=111\n\n[Key_Shift_Row_2_ID_21]\nType=VirtualKey\nLabel=*\nCluster=Numpad\nKeyCode=106\n\n[Key_Shift_Row_2_ID_22]\nType=VirtualKey\nLabel=-\nCluster=Numpad\nKeyCode=109\n\n[Key_Shift_Row_3_ID_0]\nType=VirtualKey\nWidth=150\nLabel=⭾\nKeyCode=9\n\n[Key_Shift_Row_3_ID_1]\nType=VirtualKey\nLabel=Q\nKeyCode=81\n\n[Key_Shift_Row_3_ID_2]\nType=VirtualKey\nLabel=W\nKeyCode=87\n\n[Key_Shift_Row_3_ID_3]\nType=VirtualKey\nLabel=E\nKeyCode=69\n\n[Key_Shift_Row_3_ID_4]\nType=VirtualKey\nLabel=R\nKeyCode=82\n\n[Key_Shift_Row_3_ID_5]\nType=VirtualKey\nLabel=T\nKeyCode=84\n\n[Key_Shift_Row_3_ID_6]\nType=VirtualKey\nLabel=Y\nKeyCode=89\n\n[Key_Shift_Row_3_ID_7]\nType=VirtualKey\nLabel=U\nKeyCode=85\n\n[Key_Shift_Row_3_ID_8]\nType=VirtualKey\nLabel=I\nKeyCode=73\n\n[Key_Shift_Row_3_ID_9]\nType=VirtualKey\nLabel=O\nKeyCode=79\n\n[Key_Shift_Row_3_ID_10]\nType=VirtualKey\nLabel=P\nKeyCode=80\n\n[Key_Shift_Row_3_ID_11]\nType=String\nLabel=¨\nString=¨\n\n[Key_Shift_Row_3_ID_12]\nType=String\nLabel=*\nString=*\n\n[Key_Shift_Row_3_ID_13]\nType=VirtualKeyIsoEnter\nWidth=150\nLabel=\nKeyCode=13\n\n[Key_Shift_Row_3_ID_14]\nType=Blank\nWidth=25\nCluster=Navigation\n\n[Key_Shift_Row_3_ID_15]\nType=VirtualKey\nLabel=Supr\nCluster=Navigation\nKeyCode=46\n\n[Key_Shift_Row_3_ID_16]\nType=VirtualKey\nLabel=Fin\nCluster=Navigation\nKeyCode=35\n\n[Key_Shift_Row_3_ID_17]\nType=VirtualKey\nLabel=AvPág\nCluster=Navigation\nKeyCode=34\n\n[Key_Shift_Row_3_ID_18]\nType=Blank\nWidth=25\nCluster=Numpad\n\n[Key_Shift_Row_3_ID_19]\nType=VirtualKey\nLabel=7\nCluster=Numpad\nKeyCode=103\n\n[Key_Shift_Row_3_ID_20]\nType=VirtualKey\nLabel=8\nCluster=Numpad\nKeyCode=104\n\n[Key_Shift_Row_3_ID_21]\nType=VirtualKey\nLabel=9\nCluster=Numpad\nKeyCode=105\n\n[Key_Shift_Row_3_ID_22]\nType=VirtualKey\nHeight=200\nLabel=+\nCluster=Numpad\nKeyCode=107\n\n[Key_Shift_Row_4_ID_0]\nType=VirtualKey\nWidth=175\nLabel=Bloq Mayús\nKeyCode=20\nNoRepeat=true\n\n[Key_Shift_Row_4_ID_1]\nType=VirtualKey\nLabel=A\nKeyCode=65\n\n[Key_Shift_Row_4_ID_2]\nType=VirtualKey\nLabel=S\nKeyCode=83\n\n[Key_Shift_Row_4_ID_3]\nType=VirtualKey\nLabel=D\nKeyCode=68\n\n[Key_Shift_Row_4_ID_4]\nType=VirtualKey\nLabel=F\nKeyCode=70\n\n[Key_Shift_Row_4_ID_5]\nType=VirtualKey\nLabel=G\nKeyCode=71\n\n[Key_Shift_Row_4_ID_6]\nType=VirtualKey\nLabel=H\nKeyCode=72\n\n[Key_Shift_Row_4_ID_7]\nType=VirtualKey\nLabel=J\nKeyCode=74\n\n[Key_Shift_Row_4_ID_8]\nType=VirtualKey\nLabel=K\nKeyCode=75\n\n[Key_Shift_Row_4_ID_9]\nType=VirtualKey\nLabel=L\nKeyCode=76\n\n[Key_Shift_Row_4_ID_10]\nType=String\nLabel=Ñ\nString=Ñ\n\n[Key_Shift_Row_4_ID_11]\nType=String\nLabel=[\nString=[\n\n[Key_Shift_Row_4_ID_12]\nType=String\nLabel=]\nString=]\n\n[Key_Shift_Row_4_ID_13]\nType=VirtualKeyIsoEnter\nWidth=125\nLabel=↵\nKeyCode=13\n\n[Key_Shift_Row_4_ID_14]\nType=Blank\nWidth=325\nCluster=Navigation\n\n[Key_Shift_Row_4_ID_15]\nType=Blank\nWidth=25\nCluster=Numpad\n\n[Key_Shift_Row_4_ID_16]\nType=VirtualKey\nLabel=4\nCluster=Numpad\nKeyCode=100\n\n[Key_Shift_Row_4_ID_17]\nType=VirtualKey\nLabel=5\nCluster=Numpad\nKeyCode=101\n\n[Key_Shift_Row_4_ID_18]\nType=VirtualKey\nLabel=6\nCluster=Numpad\nKeyCode=102\n\n[Key_Shift_Row_5_ID_0]\nType=VirtualKeyToggle\nWidth=125\nLabel=🡅\nKeyCode=160\nNoRepeat=true\n\n[Key_Shift_Row_5_ID_1]\nType=String\nLabel=>\nString=>\n\n[Key_Shift_Row_5_ID_2]\nType=VirtualKey\nLabel=Z\nKeyCode=90\n\n[Key_Shift_Row_5_ID_3]\nType=VirtualKey\nLabel=X\nKeyCode=88\n\n[Key_Shift_Row_5_ID_4]\nType=VirtualKey\nLabel=C\nKeyCode=67\n\n[Key_Shift_Row_5_ID_5]\nType=VirtualKey\nLabel=V\nKeyCode=86\n\n[Key_Shift_Row_5_ID_6]\nType=VirtualKey\nLabel=B\nKeyCode=66\n\n[Key_Shift_Row_5_ID_7]\nType=VirtualKey\nLabel=N\nKeyCode=78\n\n[Key_Shift_Row_5_ID_8]\nType=VirtualKey\nLabel=M\nKeyCode=77\n\n[Key_Shift_Row_5_ID_9]\nType=String\nLabel=;\nString=;\n\n[Key_Shift_Row_5_ID_10]\nType=String\nLabel=:\nString=:\n\n[Key_Shift_Row_5_ID_11]\nType=String\nLabel=_\nString=_\n\n[Key_Shift_Row_5_ID_12]\nType=VirtualKeyToggle\nWidth=275\nLabel=🡅\nKeyCode=161\nNoRepeat=true\n\n[Key_Shift_Row_5_ID_13]\nType=Blank\nWidth=125\nCluster=Navigation\n\n[Key_Shift_Row_5_ID_14]\nType=VirtualKey\nLabel=🠅\nCluster=Navigation\nKeyCode=38\n\n[Key_Shift_Row_5_ID_15]\nType=Blank\nCluster=Navigation\n\n[Key_Shift_Row_5_ID_16]\nType=Blank\nWidth=25\nCluster=Numpad\n\n[Key_Shift_Row_5_ID_17]\nType=VirtualKey\nLabel=1\nCluster=Numpad\nKeyCode=97\n\n[Key_Shift_Row_5_ID_18]\nType=VirtualKey\nLabel=2\nCluster=Numpad\nKeyCode=98\n\n[Key_Shift_Row_5_ID_19]\nType=VirtualKey\nLabel=3\nCluster=Numpad\nKeyCode=99\n\n[Key_Shift_Row_5_ID_20]\nType=VirtualKey\nHeight=200\nLabel=Intro\nCluster=Numpad\nKeyCode=13\n\n[Key_Shift_Row_6_ID_0]\nType=VirtualKeyToggle\nWidth=125\nLabel=Ctrl\nKeyCode=162\nNoRepeat=true\n\n[Key_Shift_Row_6_ID_1]\nType=VirtualKeyToggle\nWidth=125\nLabel=Win\nKeyCode=91\nNoRepeat=true\n\n[Key_Shift_Row_6_ID_2]\nType=VirtualKeyToggle\nWidth=125\nLabel=Alt\nKeyCode=164\nNoRepeat=true\n\n[Key_Shift_Row_6_ID_3]\nType=VirtualKey\nWidth=625\nLabel=\nKeyCode=32\n\n[Key_Shift_Row_6_ID_4]\nType=VirtualKeyToggle\nWidth=125\nLabel=AltGr\nKeyCode=165\nNoRepeat=true\n\n[Key_Shift_Row_6_ID_5]\nType=VirtualKeyToggle\nWidth=125\nLabel=Win\nKeyCode=92\nNoRepeat=true\n\n[Key_Shift_Row_6_ID_6]\nType=VirtualKey\nWidth=125\nLabel=Menu\nKeyCode=93\nNoRepeat=true\n\n[Key_Shift_Row_6_ID_7]\nType=VirtualKeyToggle\nWidth=125\nLabel=Ctrl\nKeyCode=163\nNoRepeat=true\n\n[Key_Shift_Row_6_ID_8]\nType=Blank\nWidth=25\nCluster=Navigation\n\n[Key_Shift_Row_6_ID_9]\nType=VirtualKey\nLabel=🠄\nCluster=Navigation\nKeyCode=37\n\n[Key_Shift_Row_6_ID_10]\nType=VirtualKey\nLabel=🠇\nCluster=Navigation\nKeyCode=40\n\n[Key_Shift_Row_6_ID_11]\nType=VirtualKey\nLabel=🠆\nCluster=Navigation\nKeyCode=39\n\n[Key_Shift_Row_6_ID_12]\nType=Blank\nWidth=25\nCluster=Numpad\n\n[Key_Shift_Row_6_ID_13]\nType=VirtualKey\nWidth=200\nLabel=0\nCluster=Numpad\nKeyCode=96\n\n[Key_Shift_Row_6_ID_14]\nType=VirtualKey\nLabel=.\nCluster=Numpad\nKeyCode=110\n\n[Key_AltGr_Row_0_ID_0]\nType=VirtualKey\nLabel=Esc\nCluster=Function\nKeyCode=27\nNoRepeat=true\n\n[Key_AltGr_Row_0_ID_1]\nType=Blank\nCluster=Function\n\n[Key_AltGr_Row_0_ID_2]\nType=VirtualKey\nLabel=F1\nCluster=Function\nKeyCode=112\n\n[Key_AltGr_Row_0_ID_3]\nType=VirtualKey\nLabel=F2\nCluster=Function\nKeyCode=113\n\n[Key_AltGr_Row_0_ID_4]\nType=VirtualKey\nLabel=F3\nCluster=Function\nKeyCode=114\n\n[Key_AltGr_Row_0_ID_5]\nType=VirtualKey\nLabel=F4\nCluster=Function\nKeyCode=115\n\n[Key_AltGr_Row_0_ID_6]\nType=Blank\nWidth=50\nCluster=Function\n\n[Key_AltGr_Row_0_ID_7]\nType=VirtualKey\nLabel=F5\nCluster=Function\nKeyCode=116\n\n[Key_AltGr_Row_0_ID_8]\nType=VirtualKey\nLabel=F6\nCluster=Function\nKeyCode=117\n\n[Key_AltGr_Row_0_ID_9]\nType=VirtualKey\nLabel=F7\nCluster=Function\nKeyCode=118\n\n[Key_AltGr_Row_0_ID_10]\nType=VirtualKey\nLabel=F8\nCluster=Function\nKeyCode=119\n\n[Key_AltGr_Row_0_ID_11]\nType=Blank\nWidth=50\nCluster=Function\n\n[Key_AltGr_Row_0_ID_12]\nType=VirtualKey\nLabel=F9\nCluster=Function\nKeyCode=120\n\n[Key_AltGr_Row_0_ID_13]\nType=VirtualKey\nLabel=F10\nCluster=Function\nKeyCode=121\n\n[Key_AltGr_Row_0_ID_14]\nType=VirtualKey\nLabel=F11\nCluster=Function\nKeyCode=122\n\n[Key_AltGr_Row_0_ID_15]\nType=VirtualKey\nLabel=F12\nCluster=Function\nKeyCode=123\n\n[Key_AltGr_Row_0_ID_16]\nType=Blank\nWidth=25\nCluster=Function\n\n[Key_AltGr_Row_0_ID_17]\nType=VirtualKey\nLabel=Impr\\nPant\nCluster=Function\nKeyCode=44\nNoRepeat=true\n\n[Key_AltGr_Row_0_ID_18]\nType=VirtualKey\nLabel=Bloq\\nDespl\nCluster=Function\nKeyCode=145\nNoRepeat=true\n\n[Key_AltGr_Row_0_ID_19]\nType=VirtualKey\nLabel=Pausa\nCluster=Function\nKeyCode=19\nNoRepeat=true\n\n[Key_AltGr_Row_0_ID_20]\nType=Blank\nWidth=25\nCluster=Extra\n\n[Key_AltGr_Row_0_ID_21]\nType=VirtualKey\nLabel=⏯\nCluster=Extra\nKeyCode=179\nNoRepeat=true\n\n[Key_AltGr_Row_0_ID_22]\nType=VirtualKey\nLabel=◼\nCluster=Extra\nKeyCode=178\nNoRepeat=true\n\n[Key_AltGr_Row_0_ID_23]\nType=VirtualKey\nLabel=⏮\nCluster=Extra\nKeyCode=177\nNoRepeat=true\n\n[Key_AltGr_Row_0_ID_24]\nType=VirtualKey\nLabel=⏭\nCluster=Extra\nKeyCode=176\nNoRepeat=true\n\n[Key_AltGr_Row_1_ID_0]\nType=Blank\nHeight=25\nCluster=Function\n\n[Key_AltGr_Row_1_ID_1]\nType=Blank\nHeight=25\nCluster=Extra\n\n[Key_AltGr_Row_2_ID_0]\nType=String\nLabel=¬\nString=¬\n\n[Key_AltGr_Row_2_ID_1]\nType=Blank\nWidth=1000\n\n[Key_AltGr_Row_2_ID_2]\nType=String\nLabel=\\\nString=\\\n\n[Key_AltGr_Row_2_ID_3]\nType=Blank\n\n[Key_AltGr_Row_2_ID_4]\nType=VirtualKey\nWidth=200\nLabel=⟵\nKeyCode=8\n\n[Key_AltGr_Row_2_ID_5]\nType=Blank\nWidth=25\nCluster=Navigation\n\n[Key_AltGr_Row_2_ID_6]\nType=VirtualKey\nLabel=Insert\nCluster=Navigation\nKeyCode=45\n\n[Key_AltGr_Row_2_ID_7]\nType=VirtualKey\nLabel=Incio\nCluster=Navigation\nKeyCode=36\n\n[Key_AltGr_Row_2_ID_8]\nType=VirtualKey\nLabel=RePág\nCluster=Navigation\nKeyCode=33\n\n[Key_AltGr_Row_2_ID_9]\nType=Blank\nWidth=25\nCluster=Numpad\n\n[Key_AltGr_Row_2_ID_10]\nType=VirtualKey\nLabel=Bloq\\nNum\nCluster=Numpad\nKeyCode=144\nNoRepeat=true\n\n[Key_AltGr_Row_2_ID_11]\nType=VirtualKey\nLabel=/\nCluster=Numpad\nKeyCode=111\n\n[Key_AltGr_Row_2_ID_12]\nType=VirtualKey\nLabel=*\nCluster=Numpad\nKeyCode=106\n\n[Key_AltGr_Row_2_ID_13]\nType=VirtualKey\nLabel=-\nCluster=Numpad\nKeyCode=109\n\n[Key_AltGr_Row_3_ID_0]\nType=VirtualKey\nWidth=150\nLabel=⭾\nKeyCode=9\n\n[Key_AltGr_Row_3_ID_1]\nType=String\nLabel=@\nString=@\n\n[Key_AltGr_Row_3_ID_2]\nType=Blank\nWidth=1000\n\n[Key_AltGr_Row_3_ID_3]\nType=String\nLabel=~\nString=~\n\n[Key_AltGr_Row_3_ID_4]\nType=VirtualKeyIsoEnter\nWidth=150\nLabel=\nKeyCode=13\n\n[Key_AltGr_Row_3_ID_5]\nType=Blank\nWidth=25\nCluster=Navigation\n\n[Key_AltGr_Row_3_ID_6]\nType=VirtualKey\nLabel=Supr\nCluster=Navigation\nKeyCode=46\n\n[Key_AltGr_Row_3_ID_7]\nType=VirtualKey\nLabel=Fin\nCluster=Navigation\nKeyCode=35\n\n[Key_AltGr_Row_3_ID_8]\nType=VirtualKey\nLabel=AvPág\nCluster=Navigation\nKeyCode=34\n\n[Key_AltGr_Row_3_ID_9]\nType=Blank\nWidth=25\nCluster=Numpad\n\n[Key_AltGr_Row_3_ID_10]\nType=VirtualKey\nLabel=7\nCluster=Numpad\nKeyCode=103\n\n[Key_AltGr_Row_3_ID_11]\nType=VirtualKey\nLabel=8\nCluster=Numpad\nKeyCode=104\n\n[Key_AltGr_Row_3_ID_12]\nType=VirtualKey\nLabel=9\nCluster=Numpad\nKeyCode=105\n\n[Key_AltGr_Row_3_ID_13]\nType=VirtualKey\nHeight=200\nLabel=+\nCluster=Numpad\nKeyCode=107\n\n[Key_AltGr_Row_4_ID_0]\nType=VirtualKey\nWidth=175\nLabel=Bloq Mayús\nKeyCode=20\nNoRepeat=true\n\n[Key_AltGr_Row_4_ID_1]\nType=Blank\nWidth=1000\n\n[Key_AltGr_Row_4_ID_2]\nType=String\nLabel=^\nString=^\n\n[Key_AltGr_Row_4_ID_3]\nType=String\nLabel=`\nString=`\n\n[Key_AltGr_Row_4_ID_4]\nType=VirtualKeyIsoEnter\nWidth=125\nLabel=↵\nKeyCode=13\n\n[Key_AltGr_Row_4_ID_5]\nType=Blank\nWidth=325\nCluster=Navigation\n\n[Key_AltGr_Row_4_ID_6]\nType=Blank\nWidth=25\nCluster=Numpad\n\n[Key_AltGr_Row_4_ID_7]\nType=VirtualKey\nLabel=4\nCluster=Numpad\nKeyCode=100\n\n[Key_AltGr_Row_4_ID_8]\nType=VirtualKey\nLabel=5\nCluster=Numpad\nKeyCode=101\n\n[Key_AltGr_Row_4_ID_9]\nType=VirtualKey\nLabel=6\nCluster=Numpad\nKeyCode=102\n\n[Key_AltGr_Row_5_ID_0]\nType=VirtualKeyToggle\nWidth=125\nLabel=🡅\nKeyCode=160\nNoRepeat=true\n\n[Key_AltGr_Row_5_ID_1]\nType=Blank\nWidth=1100\n\n[Key_AltGr_Row_5_ID_2]\nType=VirtualKeyToggle\nWidth=275\nLabel=🡅\nKeyCode=161\nNoRepeat=true\n\n[Key_AltGr_Row_5_ID_3]\nType=Blank\nWidth=125\nCluster=Navigation\n\n[Key_AltGr_Row_5_ID_4]\nType=VirtualKey\nLabel=🠅\nCluster=Navigation\nKeyCode=38\n\n[Key_AltGr_Row_5_ID_5]\nType=Blank\nCluster=Navigation\n\n[Key_AltGr_Row_5_ID_6]\nType=Blank\nWidth=25\nCluster=Numpad\n\n[Key_AltGr_Row_5_ID_7]\nType=VirtualKey\nLabel=1\nCluster=Numpad\nKeyCode=97\n\n[Key_AltGr_Row_5_ID_8]\nType=VirtualKey\nLabel=2\nCluster=Numpad\nKeyCode=98\n\n[Key_AltGr_Row_5_ID_9]\nType=VirtualKey\nLabel=3\nCluster=Numpad\nKeyCode=99\n\n[Key_AltGr_Row_5_ID_10]\nType=VirtualKey\nHeight=200\nLabel=Intro\nCluster=Numpad\nKeyCode=13\n\n[Key_AltGr_Row_6_ID_0]\nType=VirtualKeyToggle\nWidth=125\nLabel=Ctrl\nKeyCode=162\nNoRepeat=true\n\n[Key_AltGr_Row_6_ID_1]\nType=VirtualKeyToggle\nWidth=125\nLabel=Win\nKeyCode=91\nNoRepeat=true\n\n[Key_AltGr_Row_6_ID_2]\nType=VirtualKeyToggle\nWidth=125\nLabel=Alt\nKeyCode=164\nNoRepeat=true\n\n[Key_AltGr_Row_6_ID_3]\nType=VirtualKey\nWidth=625\nLabel=\nKeyCode=32\n\n[Key_AltGr_Row_6_ID_4]\nType=VirtualKeyToggle\nWidth=125\nLabel=AltGr\nKeyCode=165\nNoRepeat=true\n\n[Key_AltGr_Row_6_ID_5]\nType=VirtualKeyToggle\nWidth=125\nLabel=Win\nKeyCode=92\nNoRepeat=true\n\n[Key_AltGr_Row_6_ID_6]\nType=VirtualKey\nWidth=125\nLabel=Menu\nKeyCode=93\nNoRepeat=true\n\n[Key_AltGr_Row_6_ID_7]\nType=VirtualKeyToggle\nWidth=125\nLabel=Ctrl\nKeyCode=163\nNoRepeat=true\n\n[Key_AltGr_Row_6_ID_8]\nType=Blank\nWidth=25\nCluster=Navigation\n\n[Key_AltGr_Row_6_ID_9]\nType=VirtualKey\nLabel=🠄\nCluster=Navigation\nKeyCode=37\n\n[Key_AltGr_Row_6_ID_10]\nType=VirtualKey\nLabel=🠇\nCluster=Navigation\nKeyCode=40\n\n[Key_AltGr_Row_6_ID_11]\nType=VirtualKey\nLabel=🠆\nCluster=Navigation\nKeyCode=39\n\n[Key_AltGr_Row_6_ID_12]\nType=Blank\nWidth=25\nCluster=Numpad\n\n[Key_AltGr_Row_6_ID_13]\nType=VirtualKey\nWidth=200\nLabel=0\nCluster=Numpad\nKeyCode=96\n\n[Key_AltGr_Row_6_ID_14]\nType=VirtualKey\nLabel=.\nCluster=Numpad\nKeyCode=110\n\n"
  },
  {
    "path": "assets/keyboards/qwerty_th_kedmanee.ini",
    "content": "[LayoutInfo]\nName=QWERTY (Thai Kedmanee)\nAuthor=TangMo\nHasAltGr=false\nHasClusterFunction=true\nHasClusterNavigation=true\nHasClusterNumpad=true\nHasClusterExtra=true\n\n[Key_Base_Row_0_ID_0]\nType=VirtualKey\nLabel=Esc\nCluster=Function\nKeyCode=27\nNoRepeat=true\n\n[Key_Base_Row_0_ID_1]\nType=Blank\nCluster=Function\n\n[Key_Base_Row_0_ID_2]\nType=VirtualKey\nLabel=F1\nCluster=Function\nKeyCode=112\n\n[Key_Base_Row_0_ID_3]\nType=VirtualKey\nLabel=F2\nCluster=Function\nKeyCode=113\n\n[Key_Base_Row_0_ID_4]\nType=VirtualKey\nLabel=F3\nCluster=Function\nKeyCode=114\n\n[Key_Base_Row_0_ID_5]\nType=VirtualKey\nLabel=F4\nCluster=Function\nKeyCode=115\n\n[Key_Base_Row_0_ID_6]\nType=Blank\nWidth=50\nCluster=Function\n\n[Key_Base_Row_0_ID_7]\nType=VirtualKey\nLabel=F5\nCluster=Function\nKeyCode=116\n\n[Key_Base_Row_0_ID_8]\nType=VirtualKey\nLabel=F6\nCluster=Function\nKeyCode=117\n\n[Key_Base_Row_0_ID_9]\nType=VirtualKey\nLabel=F7\nCluster=Function\nKeyCode=118\n\n[Key_Base_Row_0_ID_10]\nType=VirtualKey\nLabel=F8\nCluster=Function\nKeyCode=119\n\n[Key_Base_Row_0_ID_11]\nType=Blank\nWidth=50\nCluster=Function\n\n[Key_Base_Row_0_ID_12]\nType=VirtualKey\nLabel=F9\nCluster=Function\nKeyCode=120\n\n[Key_Base_Row_0_ID_13]\nType=VirtualKey\nLabel=F10\nCluster=Function\nKeyCode=121\n\n[Key_Base_Row_0_ID_14]\nType=VirtualKey\nLabel=F11\nCluster=Function\nKeyCode=122\n\n[Key_Base_Row_0_ID_15]\nType=VirtualKey\nLabel=F12\nCluster=Function\nKeyCode=123\n\n[Key_Base_Row_0_ID_16]\nType=Blank\nWidth=25\nCluster=Function\n\n[Key_Base_Row_0_ID_17]\nType=VirtualKey\nLabel=Print\\nScreen\nCluster=Function\nKeyCode=44\nNoRepeat=true\n\n[Key_Base_Row_0_ID_18]\nType=VirtualKey\nLabel=Scroll\\nLock\nCluster=Function\nKeyCode=145\nNoRepeat=true\n\n[Key_Base_Row_0_ID_19]\nType=VirtualKey\nLabel=Pause\nCluster=Function\nKeyCode=19\nNoRepeat=true\n\n[Key_Base_Row_0_ID_20]\nType=Blank\nWidth=25\nCluster=Extra\n\n[Key_Base_Row_0_ID_21]\nType=VirtualKey\nLabel=⏯\nCluster=Extra\nKeyCode=179\nNoRepeat=true\n\n[Key_Base_Row_0_ID_22]\nType=VirtualKey\nLabel=◼\nCluster=Extra\nKeyCode=178\nNoRepeat=true\n\n[Key_Base_Row_0_ID_23]\nType=VirtualKey\nLabel=⏮\nCluster=Extra\nKeyCode=177\nNoRepeat=true\n\n[Key_Base_Row_0_ID_24]\nType=VirtualKey\nLabel=⏭\nCluster=Extra\nKeyCode=176\nNoRepeat=true\n\n[Key_Base_Row_1_ID_0]\nType=Blank\nHeight=25\nCluster=Function\n\n[Key_Base_Row_1_ID_1]\nType=Blank\nHeight=25\nCluster=Extra\n\n[Key_Base_Row_2_ID_0]\nType=VirtualKey\nLabel=`##L\\n_##R\nKeyCode=192\n\n[Key_Base_Row_2_ID_1]\nType=VirtualKey\nLabel=1##L\\nๅ##R\nKeyCode=49\n\n[Key_Base_Row_2_ID_2]\nType=VirtualKey\nLabel=2##L\\n/##R\nKeyCode=50\n\n[Key_Base_Row_2_ID_3]\nType=VirtualKey\nLabel=3##L\\n-##R\nKeyCode=51\n\n[Key_Base_Row_2_ID_4]\nType=VirtualKey\nLabel=4##L\\nภ##R\nKeyCode=52\n\n[Key_Base_Row_2_ID_5]\nType=VirtualKey\nLabel=5##L\\nถ##R\nKeyCode=53\n\n[Key_Base_Row_2_ID_6]\nType=VirtualKey\nLabel=6##L\\nุ##R\nKeyCode=54\n\n[Key_Base_Row_2_ID_7]\nType=VirtualKey\nLabel=7##L\\nึ##R\nKeyCode=55\n\n[Key_Base_Row_2_ID_8]\nType=VirtualKey\nLabel=8##L\\nค##R\nKeyCode=56\n\n[Key_Base_Row_2_ID_9]\nType=VirtualKey\nLabel=9##L\\nต##R\nKeyCode=57\n\n[Key_Base_Row_2_ID_10]\nType=VirtualKey\nLabel=0##L\\nจ##R\nKeyCode=48\n\n[Key_Base_Row_2_ID_11]\nType=VirtualKey\nLabel=-##L\\nข##R\nKeyCode=189\n\n[Key_Base_Row_2_ID_12]\nType=VirtualKey\nLabel==##L\\nช##R\nKeyCode=187\n\n[Key_Base_Row_2_ID_13]\nType=VirtualKey\nWidth=200\nLabel=Backspace\nKeyCode=8\n\n[Key_Base_Row_2_ID_14]\nType=Blank\nWidth=25\nCluster=Navigation\n\n[Key_Base_Row_2_ID_15]\nType=VirtualKey\nLabel=Insert\nCluster=Navigation\nKeyCode=45\n\n[Key_Base_Row_2_ID_16]\nType=VirtualKey\nLabel=Home\nCluster=Navigation\nKeyCode=36\n\n[Key_Base_Row_2_ID_17]\nType=VirtualKey\nLabel=PgUp\nCluster=Navigation\nKeyCode=33\n\n[Key_Base_Row_2_ID_18]\nType=Blank\nWidth=25\nCluster=Numpad\n\n[Key_Base_Row_2_ID_19]\nType=VirtualKey\nLabel=Num##L\\nLock##L\nCluster=Numpad\nKeyCode=144\nNoRepeat=true\n\n[Key_Base_Row_2_ID_20]\nType=VirtualKey\nLabel=/##L\\n\nCluster=Numpad\nKeyCode=111\n\n[Key_Base_Row_2_ID_21]\nType=VirtualKey\nLabel=*##L\\n\nCluster=Numpad\nKeyCode=106\n\n[Key_Base_Row_2_ID_22]\nType=VirtualKey\nLabel=-##L\\n\nCluster=Numpad\nKeyCode=109\n\n[Key_Base_Row_3_ID_0]\nType=VirtualKey\nWidth=150\nLabel=Tab\nKeyCode=9\n\n[Key_Base_Row_3_ID_1]\nType=VirtualKey\nLabel=q##L\\nๆ##R\nKeyCode=81\n\n[Key_Base_Row_3_ID_2]\nType=VirtualKey\nLabel=w ##L\\nไ##R\nKeyCode=87\n\n[Key_Base_Row_3_ID_3]\nType=VirtualKey\nLabel=e##L\\nำ##R\nKeyCode=69\n\n[Key_Base_Row_3_ID_4]\nType=VirtualKey\nLabel=r##L\\nพ##R\nKeyCode=82\n\n[Key_Base_Row_3_ID_5]\nType=VirtualKey\nLabel=t##L\\nะ##R\nKeyCode=84\n\n[Key_Base_Row_3_ID_6]\nType=VirtualKey\nLabel=y##L\\nั##R\nKeyCode=89\n\n[Key_Base_Row_3_ID_7]\nType=VirtualKey\nLabel=u##L\\nี##R\nKeyCode=85\n\n[Key_Base_Row_3_ID_8]\nType=VirtualKey\nLabel=i##L\\nร##R\nKeyCode=73\n\n[Key_Base_Row_3_ID_9]\nType=VirtualKey\nLabel=o##L\\nน##R\nKeyCode=79\n\n[Key_Base_Row_3_ID_10]\nType=VirtualKey\nLabel=p##L\\nย##R\nKeyCode=80\n\n[Key_Base_Row_3_ID_11]\nType=VirtualKey\nLabel=[##L\\nบ##R\nKeyCode=219\n\n[Key_Base_Row_3_ID_12]\nType=VirtualKey\nLabel=]##L\\nล##R\nKeyCode=221\n\n[Key_Base_Row_3_ID_13]\nType=VirtualKey\nWidth=150\nLabel=\\##L\\nฃ##R\nKeyCode=220\n\n[Key_Base_Row_3_ID_14]\nType=Blank\nWidth=25\nCluster=Navigation\n\n[Key_Base_Row_3_ID_15]\nType=VirtualKey\nLabel=Delete\nCluster=Navigation\nKeyCode=46\n\n[Key_Base_Row_3_ID_16]\nType=VirtualKey\nLabel=End\nCluster=Navigation\nKeyCode=35\n\n[Key_Base_Row_3_ID_17]\nType=VirtualKey\nLabel=PgDn\nCluster=Navigation\nKeyCode=34\n\n[Key_Base_Row_3_ID_18]\nType=Blank\nWidth=25\nCluster=Numpad\n\n[Key_Base_Row_3_ID_19]\nType=VirtualKey\nLabel=7##L\\nHome##L\nCluster=Numpad\nKeyCode=103\n\n[Key_Base_Row_3_ID_20]\nType=VirtualKey\nLabel=8##L\\n↑##L\nCluster=Numpad\nKeyCode=104\n\n[Key_Base_Row_3_ID_21]\nType=VirtualKey\nLabel=9##L\\nPgUp##L\nCluster=Numpad\nKeyCode=105\n\n[Key_Base_Row_3_ID_22]\nType=VirtualKey\nHeight=200\nLabel=+\nCluster=Numpad\nKeyCode=107\n\n[Key_Base_Row_4_ID_0]\nType=VirtualKey\nWidth=175\nLabel=Caps Lock\nKeyCode=20\nNoRepeat=true\n\n[Key_Base_Row_4_ID_1]\nType=VirtualKey\nLabel=a##L\\nฟ##R\nKeyCode=65\n\n[Key_Base_Row_4_ID_2]\nType=VirtualKey\nLabel=s##L\\nห##R\nKeyCode=83\n\n[Key_Base_Row_4_ID_3]\nType=VirtualKey\nLabel=d##L\\nก##R\nKeyCode=68\n\n[Key_Base_Row_4_ID_4]\nType=VirtualKey\nLabel=f##L\\nด##R\nKeyCode=70\n\n[Key_Base_Row_4_ID_5]\nType=VirtualKey\nLabel=g##L\\nเ##R\nKeyCode=71\n\n[Key_Base_Row_4_ID_6]\nType=VirtualKey\nLabel=h##L\\n้##R\nKeyCode=72\n\n[Key_Base_Row_4_ID_7]\nType=VirtualKey\nLabel=j##L\\n่##R\nKeyCode=74\n\n[Key_Base_Row_4_ID_8]\nType=VirtualKey\nLabel=k##L\\nา##R\nKeyCode=75\n\n[Key_Base_Row_4_ID_9]\nType=VirtualKey\nLabel=l##L\\nส##R\nKeyCode=76\n\n[Key_Base_Row_4_ID_10]\nType=VirtualKey\nLabel=;##L\\nว##R\nKeyCode=186\n\n[Key_Base_Row_4_ID_11]\nType=VirtualKey\nLabel='##L\\nง##R\nKeyCode=222\n\n[Key_Base_Row_4_ID_12]\nType=VirtualKey\nWidth=225\nLabel=Enter\nKeyCode=13\n\n[Key_Base_Row_4_ID_13]\nType=Blank\nWidth=325\nCluster=Navigation\n\n[Key_Base_Row_4_ID_14]\nType=Blank\nWidth=25\nCluster=Numpad\n\n[Key_Base_Row_4_ID_15]\nType=VirtualKey\nLabel=4##L\\n←##L\nCluster=Numpad\nKeyCode=100\n\n[Key_Base_Row_4_ID_16]\nType=VirtualKey\nLabel=5##L\\n\nCluster=Numpad\nKeyCode=101\n\n[Key_Base_Row_4_ID_17]\nType=VirtualKey\nLabel=6##L\\n→##L\nCluster=Numpad\nKeyCode=102\n\n[Key_Base_Row_5_ID_0]\nType=VirtualKeyToggle\nWidth=225\nLabel=Shift\nKeyCode=160\nNoRepeat=true\n\n[Key_Base_Row_5_ID_1]\nType=VirtualKey\nLabel=z##L\\nผ##R\nKeyCode=90\n\n[Key_Base_Row_5_ID_2]\nType=VirtualKey\nLabel=x##L\\nป##R\nKeyCode=88\n\n[Key_Base_Row_5_ID_3]\nType=VirtualKey\nLabel=c##L\\nแ##R\nKeyCode=67\n\n[Key_Base_Row_5_ID_4]\nType=VirtualKey\nLabel=v##L\\nอ##R\nKeyCode=86\n\n[Key_Base_Row_5_ID_5]\nType=VirtualKey\nLabel=b##L\\nิ##R\nKeyCode=66\n\n[Key_Base_Row_5_ID_6]\nType=VirtualKey\nLabel=n##L\\nื##R\nKeyCode=78\n\n[Key_Base_Row_5_ID_7]\nType=VirtualKey\nLabel=m##L\\nท##R\nKeyCode=77\n\n[Key_Base_Row_5_ID_8]\nType=VirtualKey\nLabel=,##L\\nม##R\nKeyCode=188\n\n[Key_Base_Row_5_ID_9]\nType=VirtualKey\nLabel=.##L\\nใ##R\nKeyCode=190\n\n[Key_Base_Row_5_ID_10]\nType=VirtualKey\nLabel=/##L\\nฝ##R\nKeyCode=191\n\n[Key_Base_Row_5_ID_11]\nType=VirtualKeyToggle\nWidth=275\nLabel=Shift\nKeyCode=161\nNoRepeat=true\n\n[Key_Base_Row_5_ID_12]\nType=Blank\nWidth=125\nCluster=Navigation\n\n[Key_Base_Row_5_ID_13]\nType=VirtualKey\nLabel=🠅\nCluster=Navigation\nKeyCode=38\n\n[Key_Base_Row_5_ID_14]\nType=Blank\nCluster=Navigation\n\n[Key_Base_Row_5_ID_15]\nType=Blank\nWidth=25\nCluster=Numpad\n\n[Key_Base_Row_5_ID_16]\nType=VirtualKey\nLabel=1##L\\nEnd##L\nCluster=Numpad\nKeyCode=97\n\n[Key_Base_Row_5_ID_17]\nType=VirtualKey\nLabel=2##L\\n↓##L\nCluster=Numpad\nKeyCode=98\n\n[Key_Base_Row_5_ID_18]\nType=VirtualKey\nLabel=3##L\\nPgDn##L\nCluster=Numpad\nKeyCode=99\n\n[Key_Base_Row_5_ID_19]\nType=VirtualKey\nHeight=200\nLabel=Enter\nCluster=Numpad\nKeyCode=13\n\n[Key_Base_Row_6_ID_0]\nType=VirtualKeyToggle\nWidth=125\nLabel=Ctrl\nKeyCode=162\nNoRepeat=true\n\n[Key_Base_Row_6_ID_1]\nType=VirtualKeyToggle\nWidth=125\nLabel=Win\nKeyCode=91\nNoRepeat=true\n\n[Key_Base_Row_6_ID_2]\nType=VirtualKeyToggle\nWidth=125\nLabel=Alt\nKeyCode=164\nNoRepeat=true\n\n[Key_Base_Row_6_ID_3]\nType=VirtualKey\nWidth=625\nLabel=\nKeyCode=32\n\n[Key_Base_Row_6_ID_4]\nType=VirtualKeyToggle\nWidth=125\nLabel=Alt\nKeyCode=165\nNoRepeat=true\n\n[Key_Base_Row_6_ID_5]\nType=VirtualKeyToggle\nWidth=125\nLabel=Win\nKeyCode=92\nNoRepeat=true\n\n[Key_Base_Row_6_ID_6]\nType=VirtualKey\nWidth=125\nLabel=Menu\nKeyCode=93\nNoRepeat=true\n\n[Key_Base_Row_6_ID_7]\nType=VirtualKeyToggle\nWidth=125\nLabel=Ctrl\nKeyCode=163\nNoRepeat=true\n\n[Key_Base_Row_6_ID_8]\nType=Blank\nWidth=25\nCluster=Navigation\n\n[Key_Base_Row_6_ID_9]\nType=VirtualKey\nLabel=🠄\nCluster=Navigation\nKeyCode=37\n\n[Key_Base_Row_6_ID_10]\nType=VirtualKey\nLabel=🠇\nCluster=Navigation\nKeyCode=40\n\n[Key_Base_Row_6_ID_11]\nType=VirtualKey\nLabel=🠆\nCluster=Navigation\nKeyCode=39\n\n[Key_Base_Row_6_ID_12]\nType=Blank\nWidth=25\nCluster=Numpad\n\n[Key_Base_Row_6_ID_13]\nType=VirtualKey\nWidth=200\nLabel=0##L\\nInsert##L\nCluster=Numpad\nKeyCode=96\n\n[Key_Base_Row_6_ID_14]\nType=VirtualKey\nLabel=.##L\\nDel##L\nCluster=Numpad\nKeyCode=110\n\n[Key_Shift_Row_0_ID_0]\nType=VirtualKey\nLabel=Esc\nCluster=Function\nKeyCode=27\nNoRepeat=true\n\n[Key_Shift_Row_0_ID_1]\nType=Blank\nCluster=Function\n\n[Key_Shift_Row_0_ID_2]\nType=VirtualKey\nLabel=F1\nCluster=Function\nKeyCode=112\n\n[Key_Shift_Row_0_ID_3]\nType=VirtualKey\nLabel=F2\nCluster=Function\nKeyCode=113\n\n[Key_Shift_Row_0_ID_4]\nType=VirtualKey\nLabel=F3\nCluster=Function\nKeyCode=114\n\n[Key_Shift_Row_0_ID_5]\nType=VirtualKey\nLabel=F4\nCluster=Function\nKeyCode=115\n\n[Key_Shift_Row_0_ID_6]\nType=Blank\nWidth=50\nCluster=Function\n\n[Key_Shift_Row_0_ID_7]\nType=VirtualKey\nLabel=F5\nCluster=Function\nKeyCode=116\n\n[Key_Shift_Row_0_ID_8]\nType=VirtualKey\nLabel=F6\nCluster=Function\nKeyCode=117\n\n[Key_Shift_Row_0_ID_9]\nType=VirtualKey\nLabel=F7\nCluster=Function\nKeyCode=118\n\n[Key_Shift_Row_0_ID_10]\nType=VirtualKey\nLabel=F8\nCluster=Function\nKeyCode=119\n\n[Key_Shift_Row_0_ID_11]\nType=Blank\nWidth=50\nCluster=Function\n\n[Key_Shift_Row_0_ID_12]\nType=VirtualKey\nLabel=F9\nCluster=Function\nKeyCode=120\n\n[Key_Shift_Row_0_ID_13]\nType=VirtualKey\nLabel=F10\nCluster=Function\nKeyCode=121\n\n[Key_Shift_Row_0_ID_14]\nType=VirtualKey\nLabel=F11\nCluster=Function\nKeyCode=122\n\n[Key_Shift_Row_0_ID_15]\nType=VirtualKey\nLabel=F12\nCluster=Function\nKeyCode=123\n\n[Key_Shift_Row_0_ID_16]\nType=Blank\nWidth=25\nCluster=Function\n\n[Key_Shift_Row_0_ID_17]\nType=VirtualKey\nLabel=Print\\nScreen\nCluster=Function\nKeyCode=44\nNoRepeat=true\n\n[Key_Shift_Row_0_ID_18]\nType=VirtualKey\nLabel=Scroll\\nLock\nCluster=Function\nKeyCode=145\nNoRepeat=true\n\n[Key_Shift_Row_0_ID_19]\nType=VirtualKey\nLabel=Pause\nCluster=Function\nKeyCode=19\nNoRepeat=true\n\n[Key_Shift_Row_0_ID_20]\nType=Blank\nWidth=25\nCluster=Extra\n\n[Key_Shift_Row_0_ID_21]\nType=VirtualKey\nLabel=🡰\nCluster=Extra\nKeyCode=166\nNoRepeat=true\n\n[Key_Shift_Row_0_ID_22]\nType=VirtualKey\nLabel=🡲\nCluster=Extra\nKeyCode=167\nNoRepeat=true\n\n[Key_Shift_Row_0_ID_23]\nType=VirtualKey\nLabel=🔇\nCluster=Extra\nKeyCode=173\nNoRepeat=true\n\n[Key_Shift_Row_1_ID_0]\nType=Blank\nHeight=25\nCluster=Function\n\n[Key_Shift_Row_1_ID_1]\nType=Blank\nHeight=25\nCluster=Extra\n\n[Key_Shift_Row_2_ID_0]\nType=VirtualKey\nLabel=~##L\\n%##R\nKeyCode=192\n\n[Key_Shift_Row_2_ID_1]\nType=VirtualKey\nLabel=!##L\\n+##R\nKeyCode=49\n\n[Key_Shift_Row_2_ID_2]\nType=VirtualKey\nLabel=@##L\\n๑##R\nKeyCode=50\n\n[Key_Shift_Row_2_ID_3]\nType=VirtualKey\nLabel=# ##L\\n๒##R\nKeyCode=51\n\n[Key_Shift_Row_2_ID_4]\nType=VirtualKey\nLabel=$##L\\n๓##R\nKeyCode=52\n\n[Key_Shift_Row_2_ID_5]\nType=VirtualKey\nLabel=%##L\\n๔##R\nKeyCode=53\n\n[Key_Shift_Row_2_ID_6]\nType=VirtualKey\nLabel=^##L\\nู##R\nKeyCode=54\n\n[Key_Shift_Row_2_ID_7]\nType=VirtualKey\nLabel=&##L\\n฿##R\nKeyCode=55\n\n[Key_Shift_Row_2_ID_8]\nType=VirtualKey\nLabel=*##L\\n๕##R\nKeyCode=56\n\n[Key_Shift_Row_2_ID_9]\nType=VirtualKey\nLabel=(##L\\n๖##R\nKeyCode=57\n\n[Key_Shift_Row_2_ID_10]\nType=VirtualKey\nLabel=)##L\\n๗##R\nKeyCode=48\n\n[Key_Shift_Row_2_ID_11]\nType=VirtualKey\nLabel=_##L\\n๘##R\nKeyCode=189\n\n[Key_Shift_Row_2_ID_12]\nType=VirtualKey\nLabel=+##L\\n๙##R\nKeyCode=187\n\n[Key_Shift_Row_2_ID_13]\nType=VirtualKey\nWidth=200\nLabel=Backspace\nKeyCode=8\n\n[Key_Shift_Row_2_ID_14]\nType=Blank\nWidth=25\nCluster=Navigation\n\n[Key_Shift_Row_2_ID_15]\nType=VirtualKey\nLabel=Insert\nCluster=Navigation\nKeyCode=45\n\n[Key_Shift_Row_2_ID_16]\nType=VirtualKey\nLabel=Home\nCluster=Navigation\nKeyCode=36\n\n[Key_Shift_Row_2_ID_17]\nType=VirtualKey\nLabel=PgUp\nCluster=Navigation\nKeyCode=33\n\n[Key_Shift_Row_2_ID_18]\nType=Blank\nWidth=25\nCluster=Numpad\n\n[Key_Shift_Row_2_ID_19]\nType=VirtualKey\nLabel=Num##L\\nLock##L\nCluster=Numpad\nKeyCode=144\nNoRepeat=true\n\n[Key_Shift_Row_2_ID_20]\nType=VirtualKey\nLabel=/##L\\n\nCluster=Numpad\nKeyCode=111\n\n[Key_Shift_Row_2_ID_21]\nType=VirtualKey\nLabel=*##L\\n\nCluster=Numpad\nKeyCode=106\n\n[Key_Shift_Row_2_ID_22]\nType=VirtualKey\nLabel=-##L\\n\nCluster=Numpad\nKeyCode=109\n\n[Key_Shift_Row_3_ID_0]\nType=VirtualKey\nWidth=150\nLabel=Tab\nKeyCode=9\n\n[Key_Shift_Row_3_ID_1]\nType=VirtualKey\nLabel=Q##L\\n๐##R\nKeyCode=81\n\n[Key_Shift_Row_3_ID_2]\nType=VirtualKey\nLabel=W##L\\n\"##R\nKeyCode=87\n\n[Key_Shift_Row_3_ID_3]\nType=VirtualKey\nLabel=E##L\\nฎ##R\nKeyCode=69\n\n[Key_Shift_Row_3_ID_4]\nType=VirtualKey\nLabel=R##L\\nฑ##R\nKeyCode=82\n\n[Key_Shift_Row_3_ID_5]\nType=VirtualKey\nLabel=T##L\\nธ##R\nKeyCode=84\n\n[Key_Shift_Row_3_ID_6]\nType=VirtualKey\nLabel=Y##L\\nํ##R\nKeyCode=89\n\n[Key_Shift_Row_3_ID_7]\nType=VirtualKey\nLabel=U##L\\n๊##R\nKeyCode=85\n\n[Key_Shift_Row_3_ID_8]\nType=VirtualKey\nLabel=I##L\\nณ##R\nKeyCode=73\n\n[Key_Shift_Row_3_ID_9]\nType=VirtualKey\nLabel=O##L\\nฯ##R\nKeyCode=79\n\n[Key_Shift_Row_3_ID_10]\nType=VirtualKey\nLabel=P##L\\nญ##R\nKeyCode=80\n\n[Key_Shift_Row_3_ID_11]\nType=VirtualKey\nLabel={##L\\nฐ##R\nKeyCode=219\n\n[Key_Shift_Row_3_ID_12]\nType=VirtualKey\nLabel=}##L\\n,##R\nKeyCode=221\n\n[Key_Shift_Row_3_ID_13]\nType=VirtualKey\nWidth=150\nLabel=|##L\\nฅ##R\nKeyCode=220\n\n[Key_Shift_Row_3_ID_14]\nType=Blank\nWidth=25\nCluster=Navigation\n\n[Key_Shift_Row_3_ID_15]\nType=VirtualKey\nLabel=Delete\nCluster=Navigation\nKeyCode=46\n\n[Key_Shift_Row_3_ID_16]\nType=VirtualKey\nLabel=End\nCluster=Navigation\nKeyCode=35\n\n[Key_Shift_Row_3_ID_17]\nType=VirtualKey\nLabel=PgDn\nCluster=Navigation\nKeyCode=34\n\n[Key_Shift_Row_3_ID_18]\nType=Blank\nWidth=25\nCluster=Numpad\n\n[Key_Shift_Row_3_ID_19]\nType=VirtualKey\nLabel=7##L\\nHome##L\nCluster=Numpad\nKeyCode=103\n\n[Key_Shift_Row_3_ID_20]\nType=VirtualKey\nLabel=8##L\\n↑##L\nCluster=Numpad\nKeyCode=104\n\n[Key_Shift_Row_3_ID_21]\nType=VirtualKey\nLabel=9##L\\nPgUp##L\nCluster=Numpad\nKeyCode=105\n\n[Key_Shift_Row_3_ID_22]\nType=VirtualKey\nHeight=200\nLabel=+\nCluster=Numpad\nKeyCode=107\n\n[Key_Shift_Row_4_ID_0]\nType=VirtualKey\nWidth=175\nLabel=Caps Lock\nKeyCode=20\nNoRepeat=true\n\n[Key_Shift_Row_4_ID_1]\nType=VirtualKey\nLabel=A##L\\nฤ##R\nKeyCode=65\n\n[Key_Shift_Row_4_ID_2]\nType=VirtualKey\nLabel=S##L\\nฆ##R\nKeyCode=83\n\n[Key_Shift_Row_4_ID_3]\nType=VirtualKey\nLabel=D##L\\nฏ##R\nKeyCode=68\n\n[Key_Shift_Row_4_ID_4]\nType=VirtualKey\nLabel=F##L\\nโ##R\nKeyCode=70\n\n[Key_Shift_Row_4_ID_5]\nType=VirtualKey\nLabel=G##L\\nฌ##R\nKeyCode=71\n\n[Key_Shift_Row_4_ID_6]\nType=VirtualKey\nLabel=H##L\\n็##R\nKeyCode=72\n\n[Key_Shift_Row_4_ID_7]\nType=VirtualKey\nLabel=J##L\\n๋##R\nKeyCode=74\n\n[Key_Shift_Row_4_ID_8]\nType=VirtualKey\nLabel=K##L\\nษ##R\nKeyCode=75\n\n[Key_Shift_Row_4_ID_9]\nType=VirtualKey\nLabel=L##L\\nศ##R\nKeyCode=76\n\n[Key_Shift_Row_4_ID_10]\nType=VirtualKey\nLabel=:##L\\nซ##R\nKeyCode=186\n\n[Key_Shift_Row_4_ID_11]\nType=VirtualKey\nLabel=\"##L\\n.##R\nKeyCode=222\n\n[Key_Shift_Row_4_ID_12]\nType=VirtualKey\nWidth=225\nLabel=Enter\nKeyCode=13\n\n[Key_Shift_Row_4_ID_13]\nType=Blank\nWidth=325\nCluster=Navigation\n\n[Key_Shift_Row_4_ID_14]\nType=Blank\nWidth=25\nCluster=Numpad\n\n[Key_Shift_Row_4_ID_15]\nType=VirtualKey\nLabel=4##L\\n←##L\nCluster=Numpad\nKeyCode=100\n\n[Key_Shift_Row_4_ID_16]\nType=VirtualKey\nLabel=5##L\\n\nCluster=Numpad\nKeyCode=101\n\n[Key_Shift_Row_4_ID_17]\nType=VirtualKey\nLabel=6##L\\n→##L\nCluster=Numpad\nKeyCode=102\n\n[Key_Shift_Row_5_ID_0]\nType=VirtualKeyToggle\nWidth=225\nLabel=Shift\nKeyCode=160\nNoRepeat=true\n\n[Key_Shift_Row_5_ID_1]\nType=VirtualKey\nLabel=Z##L\\n(##R\nKeyCode=90\n\n[Key_Shift_Row_5_ID_2]\nType=VirtualKey\nLabel=X##L\\n)##R\nKeyCode=88\n\n[Key_Shift_Row_5_ID_3]\nType=VirtualKey\nLabel=C##L\\nฉ##R\nKeyCode=67\n\n[Key_Shift_Row_5_ID_4]\nType=VirtualKey\nLabel=V##L\\nฮ##R\nKeyCode=86\n\n[Key_Shift_Row_5_ID_5]\nType=VirtualKey\nLabel=B##L\\nฺ##R\nKeyCode=66\n\n[Key_Shift_Row_5_ID_6]\nType=VirtualKey\nLabel=N##L\\n์##R\nKeyCode=78\n\n[Key_Shift_Row_5_ID_7]\nType=VirtualKey\nLabel=M##L\\n?##R\nKeyCode=77\n\n[Key_Shift_Row_5_ID_8]\nType=VirtualKey\nLabel=<##L\\nฒ##R\nKeyCode=188\n\n[Key_Shift_Row_5_ID_9]\nType=VirtualKey\nLabel=>##L\\nฬ##R\nKeyCode=190\n\n[Key_Shift_Row_5_ID_10]\nType=VirtualKey\nLabel=?##L\\nฦ##R\nKeyCode=191\n\n[Key_Shift_Row_5_ID_11]\nType=VirtualKeyToggle\nWidth=275\nLabel=Shift\nKeyCode=161\nNoRepeat=true\n\n[Key_Shift_Row_5_ID_12]\nType=Blank\nWidth=125\nCluster=Navigation\n\n[Key_Shift_Row_5_ID_13]\nType=VirtualKey\nLabel=🠅\nCluster=Navigation\nKeyCode=38\n\n[Key_Shift_Row_5_ID_14]\nType=Blank\nCluster=Navigation\n\n[Key_Shift_Row_5_ID_15]\nType=Blank\nWidth=25\nCluster=Numpad\n\n[Key_Shift_Row_5_ID_16]\nType=VirtualKey\nLabel=1##L\\nEnd##L\nCluster=Numpad\nKeyCode=97\n\n[Key_Shift_Row_5_ID_17]\nType=VirtualKey\nLabel=2##L\\n↓##L\nCluster=Numpad\nKeyCode=98\n\n[Key_Shift_Row_5_ID_18]\nType=VirtualKey\nLabel=3##L\\nPgDn##L\nCluster=Numpad\nKeyCode=99\n\n[Key_Shift_Row_5_ID_19]\nType=VirtualKey\nHeight=200\nLabel=Enter\nCluster=Numpad\nKeyCode=13\n\n[Key_Shift_Row_6_ID_0]\nType=VirtualKeyToggle\nWidth=125\nLabel=Ctrl\nKeyCode=162\nNoRepeat=true\n\n[Key_Shift_Row_6_ID_1]\nType=VirtualKeyToggle\nWidth=125\nLabel=Win\nKeyCode=91\nNoRepeat=true\n\n[Key_Shift_Row_6_ID_2]\nType=VirtualKeyToggle\nWidth=125\nLabel=Alt\nKeyCode=164\nNoRepeat=true\n\n[Key_Shift_Row_6_ID_3]\nType=VirtualKey\nWidth=625\nLabel=\nKeyCode=32\n\n[Key_Shift_Row_6_ID_4]\nType=VirtualKeyToggle\nWidth=125\nLabel=Alt\nKeyCode=165\nNoRepeat=true\n\n[Key_Shift_Row_6_ID_5]\nType=VirtualKeyToggle\nWidth=125\nLabel=Win\nKeyCode=92\nNoRepeat=true\n\n[Key_Shift_Row_6_ID_6]\nType=VirtualKey\nWidth=125\nLabel=Menu\nKeyCode=93\nNoRepeat=true\n\n[Key_Shift_Row_6_ID_7]\nType=VirtualKeyToggle\nWidth=125\nLabel=Ctrl\nKeyCode=163\nNoRepeat=true\n\n[Key_Shift_Row_6_ID_8]\nType=Blank\nWidth=25\nCluster=Navigation\n\n[Key_Shift_Row_6_ID_9]\nType=VirtualKey\nLabel=🠄\nCluster=Navigation\nKeyCode=37\n\n[Key_Shift_Row_6_ID_10]\nType=VirtualKey\nLabel=🠇\nCluster=Navigation\nKeyCode=40\n\n[Key_Shift_Row_6_ID_11]\nType=VirtualKey\nLabel=🠆\nCluster=Navigation\nKeyCode=39\n\n[Key_Shift_Row_6_ID_12]\nType=Blank\nWidth=25\nCluster=Numpad\n\n[Key_Shift_Row_6_ID_13]\nType=VirtualKey\nWidth=200\nLabel=0##L\\nInsert##L\nCluster=Numpad\nKeyCode=96\n\n[Key_Shift_Row_6_ID_14]\nType=VirtualKey\nLabel=.##L\\nDel##L\nCluster=Numpad\nKeyCode=110\n\n"
  },
  {
    "path": "assets/keyboards/qwerty_uk.ini",
    "content": "[LayoutInfo]\nName=QWERTY (United Kingdom)\nAuthor=\nHasAltGr=true\nHasClusterFunction=true\nHasClusterNavigation=true\nHasClusterNumpad=true\nHasClusterExtra=true\n\n[Key_Base_Row_0_ID_0]\nType=VirtualKey\nLabel=Esc\nCluster=Function\nKeyCode=27\nNoRepeat=true\n\n[Key_Base_Row_0_ID_1]\nType=Blank\nCluster=Function\n\n[Key_Base_Row_0_ID_2]\nType=VirtualKey\nLabel=F1\nCluster=Function\nKeyCode=112\n\n[Key_Base_Row_0_ID_3]\nType=VirtualKey\nLabel=F2\nCluster=Function\nKeyCode=113\n\n[Key_Base_Row_0_ID_4]\nType=VirtualKey\nLabel=F3\nCluster=Function\nKeyCode=114\n\n[Key_Base_Row_0_ID_5]\nType=VirtualKey\nLabel=F4\nCluster=Function\nKeyCode=115\n\n[Key_Base_Row_0_ID_6]\nType=Blank\nWidth=50\nCluster=Function\n\n[Key_Base_Row_0_ID_7]\nType=VirtualKey\nLabel=F5\nCluster=Function\nKeyCode=116\n\n[Key_Base_Row_0_ID_8]\nType=VirtualKey\nLabel=F6\nCluster=Function\nKeyCode=117\n\n[Key_Base_Row_0_ID_9]\nType=VirtualKey\nLabel=F7\nCluster=Function\nKeyCode=118\n\n[Key_Base_Row_0_ID_10]\nType=VirtualKey\nLabel=F8\nCluster=Function\nKeyCode=119\n\n[Key_Base_Row_0_ID_11]\nType=Blank\nWidth=50\nCluster=Function\n\n[Key_Base_Row_0_ID_12]\nType=VirtualKey\nLabel=F9\nCluster=Function\nKeyCode=120\n\n[Key_Base_Row_0_ID_13]\nType=VirtualKey\nLabel=F10\nCluster=Function\nKeyCode=121\n\n[Key_Base_Row_0_ID_14]\nType=VirtualKey\nLabel=F11\nCluster=Function\nKeyCode=122\n\n[Key_Base_Row_0_ID_15]\nType=VirtualKey\nLabel=F12\nCluster=Function\nKeyCode=123\n\n[Key_Base_Row_0_ID_16]\nType=Blank\nWidth=25\nCluster=Function\n\n[Key_Base_Row_0_ID_17]\nType=VirtualKey\nLabel=Print\\nScreen\nCluster=Function\nKeyCode=44\nNoRepeat=true\n\n[Key_Base_Row_0_ID_18]\nType=VirtualKey\nLabel=Scroll\\nLock\nCluster=Function\nKeyCode=145\nNoRepeat=true\n\n[Key_Base_Row_0_ID_19]\nType=VirtualKey\nLabel=Pause\nCluster=Function\nKeyCode=19\nNoRepeat=true\n\n[Key_Base_Row_0_ID_20]\nType=Blank\nWidth=25\nCluster=Extra\n\n[Key_Base_Row_0_ID_21]\nType=VirtualKey\nLabel=⏯\nCluster=Extra\nKeyCode=179\nNoRepeat=true\n\n[Key_Base_Row_0_ID_22]\nType=VirtualKey\nLabel=◼\nCluster=Extra\nKeyCode=178\nNoRepeat=true\n\n[Key_Base_Row_0_ID_23]\nType=VirtualKey\nLabel=⏮\nCluster=Extra\nKeyCode=177\nNoRepeat=true\n\n[Key_Base_Row_0_ID_24]\nType=VirtualKey\nLabel=⏭\nCluster=Extra\nKeyCode=176\nNoRepeat=true\n\n[Key_Base_Row_1_ID_0]\nType=Blank\nHeight=25\nCluster=Function\n\n[Key_Base_Row_1_ID_1]\nType=Blank\nHeight=25\nCluster=Extra\n\n[Key_Base_Row_2_ID_0]\nType=String\nLabel=`\nString=`\n\n[Key_Base_Row_2_ID_1]\nType=VirtualKey\nLabel=1\nKeyCode=49\n\n[Key_Base_Row_2_ID_2]\nType=VirtualKey\nLabel=2\nKeyCode=50\n\n[Key_Base_Row_2_ID_3]\nType=VirtualKey\nLabel=3\nKeyCode=51\n\n[Key_Base_Row_2_ID_4]\nType=VirtualKey\nLabel=4\nKeyCode=52\n\n[Key_Base_Row_2_ID_5]\nType=VirtualKey\nLabel=5\nKeyCode=53\n\n[Key_Base_Row_2_ID_6]\nType=VirtualKey\nLabel=6\nKeyCode=54\n\n[Key_Base_Row_2_ID_7]\nType=VirtualKey\nLabel=7\nKeyCode=55\n\n[Key_Base_Row_2_ID_8]\nType=VirtualKey\nLabel=8\nKeyCode=56\n\n[Key_Base_Row_2_ID_9]\nType=VirtualKey\nLabel=9\nKeyCode=57\n\n[Key_Base_Row_2_ID_10]\nType=VirtualKey\nLabel=0\nKeyCode=48\n\n[Key_Base_Row_2_ID_11]\nType=String\nLabel=-\nString=-\n\n[Key_Base_Row_2_ID_12]\nType=String\nLabel==\nString==\n\n[Key_Base_Row_2_ID_13]\nType=VirtualKey\nWidth=200\nLabel=⟵\nKeyCode=8\n\n[Key_Base_Row_2_ID_14]\nType=Blank\nWidth=25\nCluster=Navigation\n\n[Key_Base_Row_2_ID_15]\nType=VirtualKey\nLabel=Insert\nCluster=Navigation\nKeyCode=45\n\n[Key_Base_Row_2_ID_16]\nType=VirtualKey\nLabel=Home\nCluster=Navigation\nKeyCode=36\n\n[Key_Base_Row_2_ID_17]\nType=VirtualKey\nLabel=PgUp\nCluster=Navigation\nKeyCode=33\n\n[Key_Base_Row_2_ID_18]\nType=Blank\nWidth=25\nCluster=Numpad\n\n[Key_Base_Row_2_ID_19]\nType=VirtualKey\nLabel=Num\\nLock\nCluster=Numpad\nKeyCode=144\nNoRepeat=true\n\n[Key_Base_Row_2_ID_20]\nType=VirtualKey\nLabel=/\nCluster=Numpad\nKeyCode=111\n\n[Key_Base_Row_2_ID_21]\nType=VirtualKey\nLabel=*\nCluster=Numpad\nKeyCode=106\n\n[Key_Base_Row_2_ID_22]\nType=VirtualKey\nLabel=-\nCluster=Numpad\nKeyCode=109\n\n[Key_Base_Row_3_ID_0]\nType=VirtualKey\nWidth=150\nLabel=⭾\nKeyCode=9\n\n[Key_Base_Row_3_ID_1]\nType=VirtualKey\nLabel=q\nKeyCode=81\n\n[Key_Base_Row_3_ID_2]\nType=VirtualKey\nLabel=w\nKeyCode=87\n\n[Key_Base_Row_3_ID_3]\nType=VirtualKey\nLabel=e\nKeyCode=69\n\n[Key_Base_Row_3_ID_4]\nType=VirtualKey\nLabel=r\nKeyCode=82\n\n[Key_Base_Row_3_ID_5]\nType=VirtualKey\nLabel=t\nKeyCode=84\n\n[Key_Base_Row_3_ID_6]\nType=VirtualKey\nLabel=y\nKeyCode=89\n\n[Key_Base_Row_3_ID_7]\nType=VirtualKey\nLabel=u\nKeyCode=85\n\n[Key_Base_Row_3_ID_8]\nType=VirtualKey\nLabel=i\nKeyCode=73\n\n[Key_Base_Row_3_ID_9]\nType=VirtualKey\nLabel=o\nKeyCode=79\n\n[Key_Base_Row_3_ID_10]\nType=VirtualKey\nLabel=p\nKeyCode=80\n\n[Key_Base_Row_3_ID_11]\nType=String\nLabel=[\nString=[\n\n[Key_Base_Row_3_ID_12]\nType=String\nLabel=]\nString=]\n\n[Key_Base_Row_3_ID_13]\nType=VirtualKeyIsoEnter\nWidth=150\nLabel=\nKeyCode=13\n\n[Key_Base_Row_3_ID_14]\nType=Blank\nWidth=25\nCluster=Navigation\n\n[Key_Base_Row_3_ID_15]\nType=VirtualKey\nLabel=Delete\nCluster=Navigation\nKeyCode=46\n\n[Key_Base_Row_3_ID_16]\nType=VirtualKey\nLabel=End\nCluster=Navigation\nKeyCode=35\n\n[Key_Base_Row_3_ID_17]\nType=VirtualKey\nLabel=PgDn\nCluster=Navigation\nKeyCode=34\n\n[Key_Base_Row_3_ID_18]\nType=Blank\nWidth=25\nCluster=Numpad\n\n[Key_Base_Row_3_ID_19]\nType=VirtualKey\nLabel=7\nCluster=Numpad\nKeyCode=103\n\n[Key_Base_Row_3_ID_20]\nType=VirtualKey\nLabel=8\nCluster=Numpad\nKeyCode=104\n\n[Key_Base_Row_3_ID_21]\nType=VirtualKey\nLabel=9\nCluster=Numpad\nKeyCode=105\n\n[Key_Base_Row_3_ID_22]\nType=VirtualKey\nHeight=200\nLabel=+\nCluster=Numpad\nKeyCode=107\n\n[Key_Base_Row_4_ID_0]\nType=VirtualKey\nWidth=175\nLabel=🡇\nKeyCode=20\nNoRepeat=true\n\n[Key_Base_Row_4_ID_1]\nType=VirtualKey\nLabel=a\nKeyCode=65\n\n[Key_Base_Row_4_ID_2]\nType=VirtualKey\nLabel=s\nKeyCode=83\n\n[Key_Base_Row_4_ID_3]\nType=VirtualKey\nLabel=d\nKeyCode=68\n\n[Key_Base_Row_4_ID_4]\nType=VirtualKey\nLabel=f\nKeyCode=70\n\n[Key_Base_Row_4_ID_5]\nType=VirtualKey\nLabel=g\nKeyCode=71\n\n[Key_Base_Row_4_ID_6]\nType=VirtualKey\nLabel=h\nKeyCode=72\n\n[Key_Base_Row_4_ID_7]\nType=VirtualKey\nLabel=j\nKeyCode=74\n\n[Key_Base_Row_4_ID_8]\nType=VirtualKey\nLabel=k\nKeyCode=75\n\n[Key_Base_Row_4_ID_9]\nType=VirtualKey\nLabel=l\nKeyCode=76\n\n[Key_Base_Row_4_ID_10]\nType=String\nLabel=;\nString=;\n\n[Key_Base_Row_4_ID_11]\nType=String\nLabel='\nString='\n\n[Key_Base_Row_4_ID_12]\nType=String\nLabel=#\nString=#\n\n[Key_Base_Row_4_ID_13]\nType=VirtualKeyIsoEnter\nWidth=125\nLabel=↵\nKeyCode=13\n\n[Key_Base_Row_4_ID_14]\nType=Blank\nWidth=325\nCluster=Navigation\n\n[Key_Base_Row_4_ID_15]\nType=Blank\nWidth=25\nCluster=Numpad\n\n[Key_Base_Row_4_ID_16]\nType=VirtualKey\nLabel=4\nCluster=Numpad\nKeyCode=100\n\n[Key_Base_Row_4_ID_17]\nType=VirtualKey\nLabel=5\nCluster=Numpad\nKeyCode=101\n\n[Key_Base_Row_4_ID_18]\nType=VirtualKey\nLabel=6\nCluster=Numpad\nKeyCode=102\n\n[Key_Base_Row_5_ID_0]\nType=VirtualKeyToggle\nWidth=125\nLabel=🡅\nKeyCode=160\nNoRepeat=true\n\n[Key_Base_Row_5_ID_1]\nType=String\nLabel=\\\nString=\\\n\n[Key_Base_Row_5_ID_2]\nType=VirtualKey\nLabel=z\nKeyCode=90\n\n[Key_Base_Row_5_ID_3]\nType=VirtualKey\nLabel=x\nKeyCode=88\n\n[Key_Base_Row_5_ID_4]\nType=VirtualKey\nLabel=c\nKeyCode=67\n\n[Key_Base_Row_5_ID_5]\nType=VirtualKey\nLabel=v\nKeyCode=86\n\n[Key_Base_Row_5_ID_6]\nType=VirtualKey\nLabel=b\nKeyCode=66\n\n[Key_Base_Row_5_ID_7]\nType=VirtualKey\nLabel=n\nKeyCode=78\n\n[Key_Base_Row_5_ID_8]\nType=VirtualKey\nLabel=m\nKeyCode=77\n\n[Key_Base_Row_5_ID_9]\nType=String\nLabel=,\nString=,\n\n[Key_Base_Row_5_ID_10]\nType=String\nLabel=.\nString=.\n\n[Key_Base_Row_5_ID_11]\nType=String\nLabel=/\nString=/\n\n[Key_Base_Row_5_ID_12]\nType=VirtualKeyToggle\nWidth=275\nLabel=🡅\nKeyCode=161\nNoRepeat=true\n\n[Key_Base_Row_5_ID_13]\nType=Blank\nWidth=125\nCluster=Navigation\n\n[Key_Base_Row_5_ID_14]\nType=VirtualKey\nLabel=🠅\nCluster=Navigation\nKeyCode=38\n\n[Key_Base_Row_5_ID_15]\nType=Blank\nCluster=Navigation\n\n[Key_Base_Row_5_ID_16]\nType=Blank\nWidth=25\nCluster=Numpad\n\n[Key_Base_Row_5_ID_17]\nType=VirtualKey\nLabel=1\nCluster=Numpad\nKeyCode=97\n\n[Key_Base_Row_5_ID_18]\nType=VirtualKey\nLabel=2\nCluster=Numpad\nKeyCode=98\n\n[Key_Base_Row_5_ID_19]\nType=VirtualKey\nLabel=3\nCluster=Numpad\nKeyCode=99\n\n[Key_Base_Row_5_ID_20]\nType=VirtualKey\nHeight=200\nLabel=Enter\nCluster=Numpad\nKeyCode=13\n\n[Key_Base_Row_6_ID_0]\nType=VirtualKeyToggle\nWidth=125\nLabel=Ctrl\nKeyCode=162\nNoRepeat=true\n\n[Key_Base_Row_6_ID_1]\nType=VirtualKeyToggle\nWidth=125\nLabel=Win\nKeyCode=91\nNoRepeat=true\n\n[Key_Base_Row_6_ID_2]\nType=VirtualKeyToggle\nWidth=125\nLabel=Alt\nKeyCode=164\nNoRepeat=true\n\n[Key_Base_Row_6_ID_3]\nType=VirtualKey\nWidth=625\nLabel=\nKeyCode=32\n\n[Key_Base_Row_6_ID_4]\nType=VirtualKeyToggle\nWidth=125\nLabel=AltGr\nKeyCode=165\nNoRepeat=true\n\n[Key_Base_Row_6_ID_5]\nType=VirtualKeyToggle\nWidth=125\nLabel=Win\nKeyCode=92\nNoRepeat=true\n\n[Key_Base_Row_6_ID_6]\nType=VirtualKey\nWidth=125\nLabel=Menu\nKeyCode=93\nNoRepeat=true\n\n[Key_Base_Row_6_ID_7]\nType=VirtualKeyToggle\nWidth=125\nLabel=Ctrl\nKeyCode=163\nNoRepeat=true\n\n[Key_Base_Row_6_ID_8]\nType=Blank\nWidth=25\nCluster=Navigation\n\n[Key_Base_Row_6_ID_9]\nType=VirtualKey\nLabel=🠄\nCluster=Navigation\nKeyCode=37\n\n[Key_Base_Row_6_ID_10]\nType=VirtualKey\nLabel=🠇\nCluster=Navigation\nKeyCode=40\n\n[Key_Base_Row_6_ID_11]\nType=VirtualKey\nLabel=🠆\nCluster=Navigation\nKeyCode=39\n\n[Key_Base_Row_6_ID_12]\nType=Blank\nWidth=25\nCluster=Numpad\n\n[Key_Base_Row_6_ID_13]\nType=VirtualKey\nWidth=200\nLabel=0\nCluster=Numpad\nKeyCode=96\n\n[Key_Base_Row_6_ID_14]\nType=VirtualKey\nLabel=.\nCluster=Numpad\nKeyCode=110\n\n[Key_Shift_Row_0_ID_0]\nType=VirtualKey\nLabel=Esc\nCluster=Function\nKeyCode=27\nNoRepeat=true\n\n[Key_Shift_Row_0_ID_1]\nType=Blank\nCluster=Function\n\n[Key_Shift_Row_0_ID_2]\nType=VirtualKey\nLabel=F1\nCluster=Function\nKeyCode=112\n\n[Key_Shift_Row_0_ID_3]\nType=VirtualKey\nLabel=F2\nCluster=Function\nKeyCode=113\n\n[Key_Shift_Row_0_ID_4]\nType=VirtualKey\nLabel=F3\nCluster=Function\nKeyCode=114\n\n[Key_Shift_Row_0_ID_5]\nType=VirtualKey\nLabel=F4\nCluster=Function\nKeyCode=115\n\n[Key_Shift_Row_0_ID_6]\nType=Blank\nWidth=50\nCluster=Function\n\n[Key_Shift_Row_0_ID_7]\nType=VirtualKey\nLabel=F5\nCluster=Function\nKeyCode=116\n\n[Key_Shift_Row_0_ID_8]\nType=VirtualKey\nLabel=F6\nCluster=Function\nKeyCode=117\n\n[Key_Shift_Row_0_ID_9]\nType=VirtualKey\nLabel=F7\nCluster=Function\nKeyCode=118\n\n[Key_Shift_Row_0_ID_10]\nType=VirtualKey\nLabel=F8\nCluster=Function\nKeyCode=119\n\n[Key_Shift_Row_0_ID_11]\nType=Blank\nWidth=50\nCluster=Function\n\n[Key_Shift_Row_0_ID_12]\nType=VirtualKey\nLabel=F9\nCluster=Function\nKeyCode=120\n\n[Key_Shift_Row_0_ID_13]\nType=VirtualKey\nLabel=F10\nCluster=Function\nKeyCode=121\n\n[Key_Shift_Row_0_ID_14]\nType=VirtualKey\nLabel=F11\nCluster=Function\nKeyCode=122\n\n[Key_Shift_Row_0_ID_15]\nType=VirtualKey\nLabel=F12\nCluster=Function\nKeyCode=123\n\n[Key_Shift_Row_0_ID_16]\nType=Blank\nWidth=25\nCluster=Function\n\n[Key_Shift_Row_0_ID_17]\nType=VirtualKey\nLabel=Print\\nScreen\nCluster=Function\nKeyCode=44\nNoRepeat=true\n\n[Key_Shift_Row_0_ID_18]\nType=VirtualKey\nLabel=Scroll\\nLock\nCluster=Function\nKeyCode=145\nNoRepeat=true\n\n[Key_Shift_Row_0_ID_19]\nType=VirtualKey\nLabel=Pause\nCluster=Function\nKeyCode=19\nNoRepeat=true\n\n[Key_Shift_Row_0_ID_20]\nType=Blank\nWidth=25\nCluster=Extra\n\n[Key_Shift_Row_0_ID_21]\nType=VirtualKey\nLabel=🡰\nCluster=Extra\nKeyCode=166\nNoRepeat=true\n\n[Key_Shift_Row_0_ID_22]\nType=VirtualKey\nLabel=🡲\nCluster=Extra\nKeyCode=167\nNoRepeat=true\n\n[Key_Shift_Row_0_ID_23]\nType=VirtualKey\nLabel=🔇\nCluster=Extra\nKeyCode=173\nNoRepeat=true\n\n[Key_Shift_Row_1_ID_0]\nType=Blank\nHeight=25\nCluster=Function\n\n[Key_Shift_Row_1_ID_1]\nType=Blank\nHeight=25\nCluster=Extra\n\n[Key_Shift_Row_2_ID_0]\nType=String\nLabel=¬\nString=¬\n\n[Key_Shift_Row_2_ID_1]\nType=String\nLabel=!\nString=!\n\n[Key_Shift_Row_2_ID_2]\nType=String\nLabel=\"\nString=\"\n\n[Key_Shift_Row_2_ID_3]\nType=String\nLabel=£\nString=£\n\n[Key_Shift_Row_2_ID_4]\nType=String\nLabel=$\nString=$\n\n[Key_Shift_Row_2_ID_5]\nType=String\nLabel=%\nString=%\n\n[Key_Shift_Row_2_ID_6]\nType=String\nLabel=^\nString=^\n\n[Key_Shift_Row_2_ID_7]\nType=String\nLabel=&\nString=&\n\n[Key_Shift_Row_2_ID_8]\nType=String\nLabel=*\nString=*\n\n[Key_Shift_Row_2_ID_9]\nType=String\nLabel=(\nString=(\n\n[Key_Shift_Row_2_ID_10]\nType=String\nLabel=)\nString=)\n\n[Key_Shift_Row_2_ID_11]\nType=String\nLabel=_\nString=_\n\n[Key_Shift_Row_2_ID_12]\nType=String\nLabel=+\nString=+\n\n[Key_Shift_Row_2_ID_13]\nType=VirtualKey\nWidth=200\nLabel=⟵\nKeyCode=8\n\n[Key_Shift_Row_2_ID_14]\nType=Blank\nWidth=25\nCluster=Navigation\n\n[Key_Shift_Row_2_ID_15]\nType=VirtualKey\nLabel=Insert\nCluster=Navigation\nKeyCode=45\n\n[Key_Shift_Row_2_ID_16]\nType=VirtualKey\nLabel=Home\nCluster=Navigation\nKeyCode=36\n\n[Key_Shift_Row_2_ID_17]\nType=VirtualKey\nLabel=PgUp\nCluster=Navigation\nKeyCode=33\n\n[Key_Shift_Row_2_ID_18]\nType=Blank\nWidth=25\nCluster=Numpad\n\n[Key_Shift_Row_2_ID_19]\nType=VirtualKey\nLabel=Num\\nLock\nCluster=Numpad\nKeyCode=144\nNoRepeat=true\n\n[Key_Shift_Row_2_ID_20]\nType=VirtualKey\nLabel=/\nCluster=Numpad\nKeyCode=111\n\n[Key_Shift_Row_2_ID_21]\nType=VirtualKey\nLabel=*\nCluster=Numpad\nKeyCode=106\n\n[Key_Shift_Row_2_ID_22]\nType=VirtualKey\nLabel=-\nCluster=Numpad\nKeyCode=109\n\n[Key_Shift_Row_3_ID_0]\nType=VirtualKey\nWidth=150\nLabel=⭾\nKeyCode=9\n\n[Key_Shift_Row_3_ID_1]\nType=VirtualKey\nLabel=Q\nKeyCode=81\n\n[Key_Shift_Row_3_ID_2]\nType=VirtualKey\nLabel=W\nKeyCode=87\n\n[Key_Shift_Row_3_ID_3]\nType=VirtualKey\nLabel=E\nKeyCode=69\n\n[Key_Shift_Row_3_ID_4]\nType=VirtualKey\nLabel=R\nKeyCode=82\n\n[Key_Shift_Row_3_ID_5]\nType=VirtualKey\nLabel=T\nKeyCode=84\n\n[Key_Shift_Row_3_ID_6]\nType=VirtualKey\nLabel=Y\nKeyCode=89\n\n[Key_Shift_Row_3_ID_7]\nType=VirtualKey\nLabel=U\nKeyCode=85\n\n[Key_Shift_Row_3_ID_8]\nType=VirtualKey\nLabel=I\nKeyCode=73\n\n[Key_Shift_Row_3_ID_9]\nType=VirtualKey\nLabel=O\nKeyCode=79\n\n[Key_Shift_Row_3_ID_10]\nType=VirtualKey\nLabel=P\nKeyCode=80\n\n[Key_Shift_Row_3_ID_11]\nType=String\nLabel={\nString={\n\n[Key_Shift_Row_3_ID_12]\nType=String\nLabel=}\nString=}\n\n[Key_Shift_Row_3_ID_13]\nType=VirtualKeyIsoEnter\nWidth=150\nLabel=\nKeyCode=13\n\n[Key_Shift_Row_3_ID_14]\nType=Blank\nWidth=25\nCluster=Navigation\n\n[Key_Shift_Row_3_ID_15]\nType=VirtualKey\nLabel=Delete\nCluster=Navigation\nKeyCode=46\n\n[Key_Shift_Row_3_ID_16]\nType=VirtualKey\nLabel=End\nCluster=Navigation\nKeyCode=35\n\n[Key_Shift_Row_3_ID_17]\nType=VirtualKey\nLabel=PgDn\nCluster=Navigation\nKeyCode=34\n\n[Key_Shift_Row_3_ID_18]\nType=Blank\nWidth=25\nCluster=Numpad\n\n[Key_Shift_Row_3_ID_19]\nType=VirtualKey\nLabel=7\nCluster=Numpad\nKeyCode=103\n\n[Key_Shift_Row_3_ID_20]\nType=VirtualKey\nLabel=8\nCluster=Numpad\nKeyCode=104\n\n[Key_Shift_Row_3_ID_21]\nType=VirtualKey\nLabel=9\nCluster=Numpad\nKeyCode=105\n\n[Key_Shift_Row_3_ID_22]\nType=VirtualKey\nHeight=200\nLabel=+\nCluster=Numpad\nKeyCode=107\n\n[Key_Shift_Row_4_ID_0]\nType=VirtualKey\nWidth=175\nLabel=🡇\nKeyCode=20\nNoRepeat=true\n\n[Key_Shift_Row_4_ID_1]\nType=VirtualKey\nLabel=A\nKeyCode=65\n\n[Key_Shift_Row_4_ID_2]\nType=VirtualKey\nLabel=S\nKeyCode=83\n\n[Key_Shift_Row_4_ID_3]\nType=VirtualKey\nLabel=D\nKeyCode=68\n\n[Key_Shift_Row_4_ID_4]\nType=VirtualKey\nLabel=F\nKeyCode=70\n\n[Key_Shift_Row_4_ID_5]\nType=VirtualKey\nLabel=G\nKeyCode=71\n\n[Key_Shift_Row_4_ID_6]\nType=VirtualKey\nLabel=H\nKeyCode=72\n\n[Key_Shift_Row_4_ID_7]\nType=VirtualKey\nLabel=J\nKeyCode=74\n\n[Key_Shift_Row_4_ID_8]\nType=VirtualKey\nLabel=K\nKeyCode=75\n\n[Key_Shift_Row_4_ID_9]\nType=VirtualKey\nLabel=L\nKeyCode=76\n\n[Key_Shift_Row_4_ID_10]\nType=String\nLabel=:\nString=:\n\n[Key_Shift_Row_4_ID_11]\nType=String\nLabel=@\nString=@\n\n[Key_Shift_Row_4_ID_12]\nType=String\nLabel=~\nString=~\n\n[Key_Shift_Row_4_ID_13]\nType=VirtualKeyIsoEnter\nWidth=125\nLabel=↵\nKeyCode=13\n\n[Key_Shift_Row_4_ID_14]\nType=Blank\nWidth=325\nCluster=Navigation\n\n[Key_Shift_Row_4_ID_15]\nType=Blank\nWidth=25\nCluster=Numpad\n\n[Key_Shift_Row_4_ID_16]\nType=VirtualKey\nLabel=4\nCluster=Numpad\nKeyCode=100\n\n[Key_Shift_Row_4_ID_17]\nType=VirtualKey\nLabel=5\nCluster=Numpad\nKeyCode=101\n\n[Key_Shift_Row_4_ID_18]\nType=VirtualKey\nLabel=6\nCluster=Numpad\nKeyCode=102\n\n[Key_Shift_Row_5_ID_0]\nType=VirtualKeyToggle\nWidth=125\nLabel=🡅\nKeyCode=160\nNoRepeat=true\n\n[Key_Shift_Row_5_ID_1]\nType=String\nLabel=|\nString=|\n\n[Key_Shift_Row_5_ID_2]\nType=VirtualKey\nLabel=Z\nKeyCode=90\n\n[Key_Shift_Row_5_ID_3]\nType=VirtualKey\nLabel=X\nKeyCode=88\n\n[Key_Shift_Row_5_ID_4]\nType=VirtualKey\nLabel=C\nKeyCode=67\n\n[Key_Shift_Row_5_ID_5]\nType=VirtualKey\nLabel=V\nKeyCode=86\n\n[Key_Shift_Row_5_ID_6]\nType=VirtualKey\nLabel=B\nKeyCode=66\n\n[Key_Shift_Row_5_ID_7]\nType=VirtualKey\nLabel=N\nKeyCode=78\n\n[Key_Shift_Row_5_ID_8]\nType=VirtualKey\nLabel=M\nKeyCode=77\n\n[Key_Shift_Row_5_ID_9]\nType=String\nLabel=<\nString=<\n\n[Key_Shift_Row_5_ID_10]\nType=String\nLabel=>\nString=>\n\n[Key_Shift_Row_5_ID_11]\nType=String\nLabel=?\nString=?\n\n[Key_Shift_Row_5_ID_12]\nType=VirtualKeyToggle\nWidth=275\nLabel=🡅\nKeyCode=161\nNoRepeat=true\n\n[Key_Shift_Row_5_ID_13]\nType=Blank\nWidth=125\nCluster=Navigation\n\n[Key_Shift_Row_5_ID_14]\nType=VirtualKey\nLabel=🠅\nCluster=Navigation\nKeyCode=38\n\n[Key_Shift_Row_5_ID_15]\nType=Blank\nCluster=Navigation\n\n[Key_Shift_Row_5_ID_16]\nType=Blank\nWidth=25\nCluster=Numpad\n\n[Key_Shift_Row_5_ID_17]\nType=VirtualKey\nLabel=1\nCluster=Numpad\nKeyCode=97\n\n[Key_Shift_Row_5_ID_18]\nType=VirtualKey\nLabel=2\nCluster=Numpad\nKeyCode=98\n\n[Key_Shift_Row_5_ID_19]\nType=VirtualKey\nLabel=3\nCluster=Numpad\nKeyCode=99\n\n[Key_Shift_Row_5_ID_20]\nType=VirtualKey\nHeight=200\nLabel=Enter\nCluster=Numpad\nKeyCode=13\n\n[Key_Shift_Row_6_ID_0]\nType=VirtualKeyToggle\nWidth=125\nLabel=Ctrl\nKeyCode=162\nNoRepeat=true\n\n[Key_Shift_Row_6_ID_1]\nType=VirtualKeyToggle\nWidth=125\nLabel=Win\nKeyCode=91\nNoRepeat=true\n\n[Key_Shift_Row_6_ID_2]\nType=VirtualKeyToggle\nWidth=125\nLabel=Alt\nKeyCode=164\nNoRepeat=true\n\n[Key_Shift_Row_6_ID_3]\nType=VirtualKey\nWidth=625\nLabel=\nKeyCode=32\n\n[Key_Shift_Row_6_ID_4]\nType=VirtualKeyToggle\nWidth=125\nLabel=AltGr\nKeyCode=165\nNoRepeat=true\n\n[Key_Shift_Row_6_ID_5]\nType=VirtualKeyToggle\nWidth=125\nLabel=Win\nKeyCode=92\nNoRepeat=true\n\n[Key_Shift_Row_6_ID_6]\nType=VirtualKey\nWidth=125\nLabel=Menu\nKeyCode=93\nNoRepeat=true\n\n[Key_Shift_Row_6_ID_7]\nType=VirtualKeyToggle\nWidth=125\nLabel=Ctrl\nKeyCode=163\nNoRepeat=true\n\n[Key_Shift_Row_6_ID_8]\nType=Blank\nWidth=25\nCluster=Navigation\n\n[Key_Shift_Row_6_ID_9]\nType=VirtualKey\nLabel=🠄\nCluster=Navigation\nKeyCode=37\n\n[Key_Shift_Row_6_ID_10]\nType=VirtualKey\nLabel=🠇\nCluster=Navigation\nKeyCode=40\n\n[Key_Shift_Row_6_ID_11]\nType=VirtualKey\nLabel=🠆\nCluster=Navigation\nKeyCode=39\n\n[Key_Shift_Row_6_ID_12]\nType=Blank\nWidth=25\nCluster=Numpad\n\n[Key_Shift_Row_6_ID_13]\nType=VirtualKey\nWidth=200\nLabel=0\nCluster=Numpad\nKeyCode=96\n\n[Key_Shift_Row_6_ID_14]\nType=VirtualKey\nLabel=.\nCluster=Numpad\nKeyCode=110\n\n[Key_AltGr_Row_0_ID_0]\nType=VirtualKey\nLabel=Esc\nCluster=Function\nKeyCode=27\nNoRepeat=true\n\n[Key_AltGr_Row_0_ID_1]\nType=Blank\nCluster=Function\n\n[Key_AltGr_Row_0_ID_2]\nType=VirtualKey\nLabel=F1\nCluster=Function\nKeyCode=112\n\n[Key_AltGr_Row_0_ID_3]\nType=VirtualKey\nLabel=F2\nCluster=Function\nKeyCode=113\n\n[Key_AltGr_Row_0_ID_4]\nType=VirtualKey\nLabel=F3\nCluster=Function\nKeyCode=114\n\n[Key_AltGr_Row_0_ID_5]\nType=VirtualKey\nLabel=F4\nCluster=Function\nKeyCode=115\n\n[Key_AltGr_Row_0_ID_6]\nType=Blank\nWidth=50\nCluster=Function\n\n[Key_AltGr_Row_0_ID_7]\nType=VirtualKey\nLabel=F5\nCluster=Function\nKeyCode=116\n\n[Key_AltGr_Row_0_ID_8]\nType=VirtualKey\nLabel=F6\nCluster=Function\nKeyCode=117\n\n[Key_AltGr_Row_0_ID_9]\nType=VirtualKey\nLabel=F7\nCluster=Function\nKeyCode=118\n\n[Key_AltGr_Row_0_ID_10]\nType=VirtualKey\nLabel=F8\nCluster=Function\nKeyCode=119\n\n[Key_AltGr_Row_0_ID_11]\nType=Blank\nWidth=50\nCluster=Function\n\n[Key_AltGr_Row_0_ID_12]\nType=VirtualKey\nLabel=F9\nCluster=Function\nKeyCode=120\n\n[Key_AltGr_Row_0_ID_13]\nType=VirtualKey\nLabel=F10\nCluster=Function\nKeyCode=121\n\n[Key_AltGr_Row_0_ID_14]\nType=VirtualKey\nLabel=F11\nCluster=Function\nKeyCode=122\n\n[Key_AltGr_Row_0_ID_15]\nType=VirtualKey\nLabel=F12\nCluster=Function\nKeyCode=123\n\n[Key_AltGr_Row_0_ID_16]\nType=Blank\nWidth=25\nCluster=Function\n\n[Key_AltGr_Row_0_ID_17]\nType=VirtualKey\nLabel=Print\\nScreen\nCluster=Function\nKeyCode=44\nNoRepeat=true\n\n[Key_AltGr_Row_0_ID_18]\nType=VirtualKey\nLabel=Scroll\\nLock\nCluster=Function\nKeyCode=145\nNoRepeat=true\n\n[Key_AltGr_Row_0_ID_19]\nType=VirtualKey\nLabel=Pause\nCluster=Function\nKeyCode=19\nNoRepeat=true\n\n[Key_AltGr_Row_0_ID_20]\nType=Blank\nWidth=25\nCluster=Extra\n\n[Key_AltGr_Row_0_ID_21]\nType=VirtualKey\nLabel=⏯\nCluster=Extra\nKeyCode=179\nNoRepeat=true\n\n[Key_AltGr_Row_0_ID_22]\nType=VirtualKey\nLabel=◼\nCluster=Extra\nKeyCode=178\nNoRepeat=true\n\n[Key_AltGr_Row_0_ID_23]\nType=VirtualKey\nLabel=⏮\nCluster=Extra\nKeyCode=177\nNoRepeat=true\n\n[Key_AltGr_Row_0_ID_24]\nType=VirtualKey\nLabel=⏭\nCluster=Extra\nKeyCode=176\nNoRepeat=true\n\n[Key_AltGr_Row_1_ID_0]\nType=Blank\nHeight=25\nCluster=Function\n\n[Key_AltGr_Row_1_ID_1]\nType=Blank\nHeight=25\nCluster=Extra\n\n[Key_AltGr_Row_2_ID_0]\nType=String\nLabel=¦\nString=¦\n\n[Key_AltGr_Row_2_ID_1]\nType=Blank\nWidth=300\n\n[Key_AltGr_Row_2_ID_2]\nType=String\nLabel=€\nString=€\n\n[Key_AltGr_Row_2_ID_3]\nType=Blank\nWidth=800\n\n[Key_AltGr_Row_2_ID_4]\nType=VirtualKey\nWidth=200\nLabel=⟵\nKeyCode=8\n\n[Key_AltGr_Row_2_ID_5]\nType=Blank\nWidth=25\nCluster=Navigation\n\n[Key_AltGr_Row_2_ID_6]\nType=VirtualKey\nLabel=Insert\nCluster=Navigation\nKeyCode=45\n\n[Key_AltGr_Row_2_ID_7]\nType=VirtualKey\nLabel=Home\nCluster=Navigation\nKeyCode=36\n\n[Key_AltGr_Row_2_ID_8]\nType=VirtualKey\nLabel=PgUp\nCluster=Navigation\nKeyCode=33\n\n[Key_AltGr_Row_2_ID_9]\nType=Blank\nWidth=25\nCluster=Numpad\n\n[Key_AltGr_Row_2_ID_10]\nType=VirtualKey\nLabel=Num\\nLock\nCluster=Numpad\nKeyCode=144\nNoRepeat=true\n\n[Key_AltGr_Row_2_ID_11]\nType=VirtualKey\nLabel=/\nCluster=Numpad\nKeyCode=111\n\n[Key_AltGr_Row_2_ID_12]\nType=VirtualKey\nLabel=*\nCluster=Numpad\nKeyCode=106\n\n[Key_AltGr_Row_2_ID_13]\nType=VirtualKey\nLabel=-\nCluster=Numpad\nKeyCode=109\n\n[Key_AltGr_Row_3_ID_0]\nType=VirtualKey\nWidth=150\nLabel=⭾\nKeyCode=9\n\n[Key_AltGr_Row_3_ID_1]\nType=Blank\nWidth=1200\n\n[Key_AltGr_Row_3_ID_2]\nType=VirtualKeyIsoEnter\nWidth=150\nLabel=\nKeyCode=13\n\n[Key_AltGr_Row_3_ID_3]\nType=Blank\nWidth=25\nCluster=Navigation\n\n[Key_AltGr_Row_3_ID_4]\nType=VirtualKey\nLabel=Delete\nCluster=Navigation\nKeyCode=46\n\n[Key_AltGr_Row_3_ID_5]\nType=VirtualKey\nLabel=End\nCluster=Navigation\nKeyCode=35\n\n[Key_AltGr_Row_3_ID_6]\nType=VirtualKey\nLabel=PgDn\nCluster=Navigation\nKeyCode=34\n\n[Key_AltGr_Row_3_ID_7]\nType=Blank\nWidth=25\nCluster=Numpad\n\n[Key_AltGr_Row_3_ID_8]\nType=VirtualKey\nLabel=7\nCluster=Numpad\nKeyCode=103\n\n[Key_AltGr_Row_3_ID_9]\nType=VirtualKey\nLabel=8\nCluster=Numpad\nKeyCode=104\n\n[Key_AltGr_Row_3_ID_10]\nType=VirtualKey\nLabel=9\nCluster=Numpad\nKeyCode=105\n\n[Key_AltGr_Row_3_ID_11]\nType=VirtualKey\nHeight=200\nLabel=+\nCluster=Numpad\nKeyCode=107\n\n[Key_AltGr_Row_4_ID_0]\nType=VirtualKey\nWidth=175\nLabel=🡇\nKeyCode=20\nNoRepeat=true\n\n[Key_AltGr_Row_4_ID_1]\nType=Blank\nWidth=1200\n\n[Key_AltGr_Row_4_ID_2]\nType=VirtualKeyIsoEnter\nWidth=125\nLabel=↵\nKeyCode=13\n\n[Key_AltGr_Row_4_ID_3]\nType=Blank\nWidth=325\nCluster=Navigation\n\n[Key_AltGr_Row_4_ID_4]\nType=Blank\nWidth=25\nCluster=Numpad\n\n[Key_AltGr_Row_4_ID_5]\nType=VirtualKey\nLabel=4\nCluster=Numpad\nKeyCode=100\n\n[Key_AltGr_Row_4_ID_6]\nType=VirtualKey\nLabel=5\nCluster=Numpad\nKeyCode=101\n\n[Key_AltGr_Row_4_ID_7]\nType=VirtualKey\nLabel=6\nCluster=Numpad\nKeyCode=102\n\n[Key_AltGr_Row_5_ID_0]\nType=VirtualKeyToggle\nWidth=125\nLabel=🡅\nKeyCode=160\nNoRepeat=true\n\n[Key_AltGr_Row_5_ID_1]\nType=Blank\nWidth=1100\n\n[Key_AltGr_Row_5_ID_2]\nType=VirtualKeyToggle\nWidth=275\nLabel=🡅\nKeyCode=161\nNoRepeat=true\n\n[Key_AltGr_Row_5_ID_3]\nType=Blank\nWidth=125\nCluster=Navigation\n\n[Key_AltGr_Row_5_ID_4]\nType=VirtualKey\nLabel=🠅\nCluster=Navigation\nKeyCode=38\n\n[Key_AltGr_Row_5_ID_5]\nType=Blank\nCluster=Navigation\n\n[Key_AltGr_Row_5_ID_6]\nType=Blank\nWidth=25\nCluster=Numpad\n\n[Key_AltGr_Row_5_ID_7]\nType=VirtualKey\nLabel=1\nCluster=Numpad\nKeyCode=97\n\n[Key_AltGr_Row_5_ID_8]\nType=VirtualKey\nLabel=2\nCluster=Numpad\nKeyCode=98\n\n[Key_AltGr_Row_5_ID_9]\nType=VirtualKey\nLabel=3\nCluster=Numpad\nKeyCode=99\n\n[Key_AltGr_Row_5_ID_10]\nType=VirtualKey\nHeight=200\nLabel=Enter\nCluster=Numpad\nKeyCode=13\n\n[Key_AltGr_Row_6_ID_0]\nType=VirtualKeyToggle\nWidth=125\nLabel=Ctrl\nKeyCode=162\nNoRepeat=true\n\n[Key_AltGr_Row_6_ID_1]\nType=VirtualKeyToggle\nWidth=125\nLabel=Win\nKeyCode=91\nNoRepeat=true\n\n[Key_AltGr_Row_6_ID_2]\nType=VirtualKeyToggle\nWidth=125\nLabel=Alt\nKeyCode=164\nNoRepeat=true\n\n[Key_AltGr_Row_6_ID_3]\nType=VirtualKey\nWidth=625\nLabel=\nKeyCode=32\n\n[Key_AltGr_Row_6_ID_4]\nType=VirtualKeyToggle\nWidth=125\nLabel=AltGr\nKeyCode=165\nNoRepeat=true\n\n[Key_AltGr_Row_6_ID_5]\nType=VirtualKeyToggle\nWidth=125\nLabel=Win\nKeyCode=92\nNoRepeat=true\n\n[Key_AltGr_Row_6_ID_6]\nType=VirtualKey\nWidth=125\nLabel=Menu\nKeyCode=93\nNoRepeat=true\n\n[Key_AltGr_Row_6_ID_7]\nType=VirtualKeyToggle\nWidth=125\nLabel=Ctrl\nKeyCode=163\nNoRepeat=true\n\n[Key_AltGr_Row_6_ID_8]\nType=Blank\nWidth=25\nCluster=Navigation\n\n[Key_AltGr_Row_6_ID_9]\nType=VirtualKey\nLabel=🠄\nCluster=Navigation\nKeyCode=37\n\n[Key_AltGr_Row_6_ID_10]\nType=VirtualKey\nLabel=🠇\nCluster=Navigation\nKeyCode=40\n\n[Key_AltGr_Row_6_ID_11]\nType=VirtualKey\nLabel=🠆\nCluster=Navigation\nKeyCode=39\n\n[Key_AltGr_Row_6_ID_12]\nType=Blank\nWidth=25\nCluster=Numpad\n\n[Key_AltGr_Row_6_ID_13]\nType=VirtualKey\nWidth=200\nLabel=0\nCluster=Numpad\nKeyCode=96\n\n[Key_AltGr_Row_6_ID_14]\nType=VirtualKey\nLabel=.\nCluster=Numpad\nKeyCode=110\n\n"
  },
  {
    "path": "assets/keyboards/qwerty_usa.ini",
    "content": "[LayoutInfo]\nName=QWERTY (USA)\nHasAltGr=false\nHasClusterFunction=true\nHasClusterNavigation=true\nHasClusterNumpad=true\nHasClusterExtra=true\n\n;[Key_{SubLayout}_Row_{Row}_ID_{ID}]\n;Type=Blank / VirtualKey / VirtualKeyToggle / VirtualKeyIsoEnter / String / SubLayoutToggle / Action\n;Width=Value in %                                       | Optional, defaults to 100\n;Height=Value in %                                      | Optional, defaults to 100\n;Label=Key Label                                        | Not used with type Blank, use \\n for a second line\n;Cluster=Base / Function / Navigation / Numpad / Extra  | Optional, defaults to Base\n;KeyCode=Virtual key code (decimal)                     | Use with type VirtualKey / VirtualKeyToggle / VirtualKeyIsoEnter\n;BlockModifiers=true/false                              | Optional, defaults to false. Use with type VirtualKey\n;NoRepeat=true/false                                    | Optional, defaults to false\n;String=String typed by the key (UTF-8)                 | Use with type String\n;SubLayout=SubLayout Name                               | Use with type SubLayoutToggle\n;ActionID=Global Action ID                              | Use with type Action\n;\n;Possible sublayouts: Base / Shift / AltGr / Aux\n;\n;Use String type for non-basic character keys. This allows Desktop+ to figure out the correct key combination on the real keyboard layout for maximum application compatibility.\n;Use two VirtualKeyIsoEnter in adjacent rows to build an ISO-Enter shaped key. One max per sublayout.\n;Use BlockModifiers to have all modifier keys be released while the key is pressed. Not recommended in general, but can come handy when having auxiliary keys in modifier sublayouts.\n;Use NoRepeat to block key repeat from holding down even when key repeat is enabled. Recommend for keys that typically do not repeat on real keyboards.\n;\n;Cluster assignment is used to selectively disable loading of keys. Function cluster should also include the keys next to the actual function keys so the whole row can be toggled.\n;Surrounding blank space is not removed. Carefully assign blank space type keys to clusters in order to have them toggle correctly... or don't use clusters at all.\n;\n;Global Action ID's are not really displayed anywhere, but basically anything below ID 1000 is reserved for built-in actions and custom action are their ID + 1000.\n;Refer to the source code or values of ActionBarOrderCustom in config.ini for built-in action IDs for now.\n\n;----------------------Row 0\n[Key_Base_Row_0_ID_0]\nType=VirtualKey\nLabel=Esc\nCluster=Function\nKeyCode=27\nNoRepeat=true\n\n[Key_Base_Row_0_ID_1]\nType=Blank\nCluster=Function\n\n[Key_Base_Row_0_ID_2]\nType=VirtualKey\nLabel=F1\nCluster=Function\nKeyCode=112\n\n[Key_Base_Row_0_ID_3]\nType=VirtualKey\nLabel=F2\nCluster=Function\nKeyCode=113\n\n[Key_Base_Row_0_ID_4]\nType=VirtualKey\nLabel=F3\nCluster=Function\nKeyCode=114\n\n[Key_Base_Row_0_ID_5]\nType=VirtualKey\nLabel=F4\nCluster=Function\nKeyCode=115\n\n[Key_Base_Row_0_ID_6]\nType=Blank\nWidth=50\nCluster=Function\n\n[Key_Base_Row_0_ID_7]\nType=VirtualKey\nLabel=F5\nCluster=Function\nKeyCode=116\n\n[Key_Base_Row_0_ID_8]\nType=VirtualKey\nLabel=F6\nCluster=Function\nKeyCode=117\n\n[Key_Base_Row_0_ID_9]\nType=VirtualKey\nLabel=F7\nCluster=Function\nKeyCode=118\n\n[Key_Base_Row_0_ID_10]\nType=VirtualKey\nLabel=F8\nCluster=Function\nKeyCode=119\n\n[Key_Base_Row_0_ID_11]\nType=Blank\nWidth=50\nCluster=Function\n\n[Key_Base_Row_0_ID_12]\nType=VirtualKey\nLabel=F9\nCluster=Function\nKeyCode=120\n\n[Key_Base_Row_0_ID_13]\nType=VirtualKey\nLabel=F10\nCluster=Function\nKeyCode=121\n\n[Key_Base_Row_0_ID_14]\nType=VirtualKey\nLabel=F11\nCluster=Function\nKeyCode=122\n\n[Key_Base_Row_0_ID_15]\nType=VirtualKey\nLabel=F12\nCluster=Function\nKeyCode=123\n\n[Key_Base_Row_0_ID_16]\nType=Blank\nWidth=25\nCluster=Function\n\n[Key_Base_Row_0_ID_17]\nType=VirtualKey\nLabel=Print\\nScreen\nCluster=Function\nKeyCode=44\nNoRepeat=true\n\n[Key_Base_Row_0_ID_18]\nType=VirtualKey\nLabel=Scroll\\nLock\nCluster=Function\nKeyCode=145\nNoRepeat=true\n\n[Key_Base_Row_0_ID_19]\nType=VirtualKey\nLabel=Pause\nCluster=Function\nKeyCode=19\nNoRepeat=true\n\n[Key_Base_Row_0_ID_20]\nType=Blank\nWidth=25\nCluster=Extra\n\n[Key_Base_Row_0_ID_21]\nType=VirtualKey\nLabel=⏯\nCluster=Extra\nKeyCode=179\nNoRepeat=true\n\n[Key_Base_Row_0_ID_22]\nType=VirtualKey\nLabel=◼\nCluster=Extra\nKeyCode=178\nNoRepeat=true\n\n[Key_Base_Row_0_ID_23]\nType=VirtualKey\nLabel=⏮\nCluster=Extra\nKeyCode=177\nNoRepeat=true\n\n[Key_Base_Row_0_ID_24]\nType=VirtualKey\nLabel=⏭\nCluster=Extra\nKeyCode=176\nNoRepeat=true\n\n;----------------------Row 1 (25% height blank space)\n[Key_Base_Row_1_ID_0]\nType=Blank\nHeight=25\nCluster=Function\n\n[Key_Base_Row_1_ID_1]\nType=Blank\nHeight=25\nCluster=Extra\n\n;----------------------Row 2\n[Key_Base_Row_2_ID_0]\nType=String\nLabel=`\nString=`\n\n[Key_Base_Row_2_ID_1]\nType=VirtualKey\nLabel=1\nKeyCode=49\n\n[Key_Base_Row_2_ID_2]\nType=VirtualKey\nLabel=2\nKeyCode=50\n\n[Key_Base_Row_2_ID_3]\nType=VirtualKey\nLabel=3\nKeyCode=51\n\n[Key_Base_Row_2_ID_4]\nType=VirtualKey\nLabel=4\nKeyCode=52\n\n[Key_Base_Row_2_ID_5]\nType=VirtualKey\nLabel=5\nKeyCode=53\n\n[Key_Base_Row_2_ID_6]\nType=VirtualKey\nLabel=6\nKeyCode=54\n\n[Key_Base_Row_2_ID_7]\nType=VirtualKey\nLabel=7\nKeyCode=55\n\n[Key_Base_Row_2_ID_8]\nType=VirtualKey\nLabel=8\nKeyCode=56\n\n[Key_Base_Row_2_ID_9]\nType=VirtualKey\nLabel=9\nKeyCode=57\n\n[Key_Base_Row_2_ID_10]\nType=VirtualKey\nLabel=0\nKeyCode=48\n\n[Key_Base_Row_2_ID_11]\nType=String\nLabel=-\nString=-\n\n[Key_Base_Row_2_ID_12]\nType=String\nLabel==\nString==\n\n[Key_Base_Row_2_ID_13]\nType=VirtualKey\nWidth=200\nLabel=Backspace\nKeyCode=8\n\n[Key_Base_Row_2_ID_14]\nType=Blank\nWidth=25\nCluster=Navigation\n\n[Key_Base_Row_2_ID_15]\nType=VirtualKey\nLabel=Insert\nCluster=Navigation\nKeyCode=45\n\n[Key_Base_Row_2_ID_16]\nType=VirtualKey\nLabel=Home\nCluster=Navigation\nKeyCode=36\n\n[Key_Base_Row_2_ID_17]\nType=VirtualKey\nLabel=PgUp\nCluster=Navigation\nKeyCode=33\n\n[Key_Base_Row_2_ID_18]\nType=Blank\nWidth=25\nCluster=Numpad\n\n[Key_Base_Row_2_ID_19]\nType=VirtualKey\nLabel=Num\\nLock\nCluster=Numpad\nKeyCode=144\nNoRepeat=true\n\n[Key_Base_Row_2_ID_20]\nType=VirtualKey\nLabel=/\nCluster=Numpad\nKeyCode=111\n\n[Key_Base_Row_2_ID_21]\nType=VirtualKey\nLabel=*\nCluster=Numpad\nKeyCode=106\n\n[Key_Base_Row_2_ID_22]\nType=VirtualKey\nLabel=-\nCluster=Numpad\nKeyCode=109\n\n;----------------------Row 3\n[Key_Base_Row_3_ID_0]\nType=VirtualKey\nWidth=150\nLabel=Tab\nKeyCode=9\n\n[Key_Base_Row_3_ID_1]\nType=VirtualKey\nLabel=q\nKeyCode=81\n\n[Key_Base_Row_3_ID_2]\nType=VirtualKey\nLabel=w\nKeyCode=87\n\n[Key_Base_Row_3_ID_3]\nType=VirtualKey\nLabel=e\nKeyCode=69\n\n[Key_Base_Row_3_ID_4]\nType=VirtualKey\nLabel=r\nKeyCode=82\n\n[Key_Base_Row_3_ID_5]\nType=VirtualKey\nLabel=t\nKeyCode=84\n\n[Key_Base_Row_3_ID_6]\nType=VirtualKey\nLabel=y\nKeyCode=89\n\n[Key_Base_Row_3_ID_7]\nType=VirtualKey\nLabel=u\nKeyCode=85\n\n[Key_Base_Row_3_ID_8]\nType=VirtualKey\nLabel=i\nKeyCode=73\n\n[Key_Base_Row_3_ID_9]\nType=VirtualKey\nLabel=o\nKeyCode=79\n\n[Key_Base_Row_3_ID_10]\nType=VirtualKey\nLabel=p\nKeyCode=80\n\n[Key_Base_Row_3_ID_11]\nType=String\nLabel=[\nString=[\n\n[Key_Base_Row_3_ID_12]\nType=String\nLabel=]\nString=]\n\n[Key_Base_Row_3_ID_13]\nType=String\nWidth=150\nLabel=\\\nString=\\\n\n[Key_Base_Row_3_ID_14]\nType=Blank\nWidth=25\nCluster=Navigation\n\n[Key_Base_Row_3_ID_15]\nType=VirtualKey\nLabel=Delete\nCluster=Navigation\nKeyCode=46\n\n[Key_Base_Row_3_ID_16]\nType=VirtualKey\nLabel=End\nCluster=Navigation\nKeyCode=35\n\n[Key_Base_Row_3_ID_17]\nType=VirtualKey\nLabel=PgDn\nCluster=Navigation\nKeyCode=34\n\n[Key_Base_Row_3_ID_18]\nType=Blank\nWidth=25\nCluster=Numpad\n\n[Key_Base_Row_3_ID_19]\nType=VirtualKey\nLabel=7\nCluster=Numpad\nKeyCode=103\n\n[Key_Base_Row_3_ID_20]\nType=VirtualKey\nLabel=8\nCluster=Numpad\nKeyCode=104\n\n[Key_Base_Row_3_ID_21]\nType=VirtualKey\nLabel=9\nCluster=Numpad\nKeyCode=105\n\n[Key_Base_Row_3_ID_22]\nType=VirtualKey\nHeight=200\nLabel=+\nCluster=Numpad\nKeyCode=107\n\n;----------------------Row 4\n[Key_Base_Row_4_ID_0]\nType=VirtualKey\nWidth=175\nLabel=Caps Lock\nKeyCode=20\nNoRepeat=true\n\n[Key_Base_Row_4_ID_1]\nType=VirtualKey\nLabel=a\nKeyCode=65\n\n[Key_Base_Row_4_ID_2]\nType=VirtualKey\nLabel=s\nKeyCode=83\n\n[Key_Base_Row_4_ID_3]\nType=VirtualKey\nLabel=d\nKeyCode=68\n\n[Key_Base_Row_4_ID_4]\nType=VirtualKey\nLabel=f\nKeyCode=70\n\n[Key_Base_Row_4_ID_5]\nType=VirtualKey\nLabel=g\nKeyCode=71\n\n[Key_Base_Row_4_ID_6]\nType=VirtualKey\nLabel=h\nKeyCode=72\n\n[Key_Base_Row_4_ID_7]\nType=VirtualKey\nLabel=j\nKeyCode=74\n\n[Key_Base_Row_4_ID_8]\nType=VirtualKey\nLabel=k\nKeyCode=75\n\n[Key_Base_Row_4_ID_9]\nType=VirtualKey\nLabel=l\nKeyCode=76\n\n[Key_Base_Row_4_ID_10]\nType=String\nLabel=;\nString=;\n\n[Key_Base_Row_4_ID_11]\nType=String\nLabel='\nString='\n\n[Key_Base_Row_4_ID_12]\nType=VirtualKey\nWidth=225\nLabel=Enter\nKeyCode=13\n\n[Key_Base_Row_4_ID_13]\nType=Blank\nWidth=325\nCluster=Navigation\n\n[Key_Base_Row_4_ID_14]\nType=Blank\nWidth=25\nCluster=Numpad\n\n[Key_Base_Row_4_ID_15]\nType=VirtualKey\nLabel=4\nCluster=Numpad\nKeyCode=100\n\n[Key_Base_Row_4_ID_16]\nType=VirtualKey\nLabel=5\nCluster=Numpad\nKeyCode=101\n\n[Key_Base_Row_4_ID_17]\nType=VirtualKey\nLabel=6\nCluster=Numpad\nKeyCode=102\n\n;----------------------Row 5\n[Key_Base_Row_5_ID_0]\nType=VirtualKeyToggle\nWidth=225\nLabel=Shift\nKeyCode=160\nNoRepeat=true\n\n[Key_Base_Row_5_ID_1]\nType=VirtualKey\nLabel=z\nKeyCode=90\n\n[Key_Base_Row_5_ID_2]\nType=VirtualKey\nLabel=x\nKeyCode=88\n\n[Key_Base_Row_5_ID_3]\nType=VirtualKey\nLabel=c\nKeyCode=67\n\n[Key_Base_Row_5_ID_4]\nType=VirtualKey\nLabel=v\nKeyCode=86\n\n[Key_Base_Row_5_ID_5]\nType=VirtualKey\nLabel=b\nKeyCode=66\n\n[Key_Base_Row_5_ID_6]\nType=VirtualKey\nLabel=n\nKeyCode=78\n\n[Key_Base_Row_5_ID_7]\nType=VirtualKey\nLabel=m\nKeyCode=77\n\n[Key_Base_Row_5_ID_8]\nType=String\nLabel=,\nString=,\n\n[Key_Base_Row_5_ID_9]\nType=String\nLabel=.\nString=.\n\n[Key_Base_Row_5_ID_10]\nType=String\nLabel=/\nString=/\n\n[Key_Base_Row_5_ID_11]\nType=VirtualKeyToggle\nWidth=275\nLabel=Shift\nKeyCode=161\nNoRepeat=true\n\n[Key_Base_Row_5_ID_12]\nType=Blank\nWidth=125\nCluster=Navigation\n\n[Key_Base_Row_5_ID_13]\nType=VirtualKey\nLabel=🠅\nCluster=Navigation\nKeyCode=38\n\n[Key_Base_Row_5_ID_14]\nType=Blank\nWidth=100\nCluster=Navigation\n\n[Key_Base_Row_5_ID_15]\nType=Blank\nWidth=25\nCluster=Numpad\n\n[Key_Base_Row_5_ID_16]\nType=VirtualKey\nLabel=1\nCluster=Numpad\nKeyCode=97\n\n[Key_Base_Row_5_ID_17]\nType=VirtualKey\nLabel=2\nCluster=Numpad\nKeyCode=98\n\n[Key_Base_Row_5_ID_18]\nType=VirtualKey\nLabel=3\nCluster=Numpad\nKeyCode=99\n\n[Key_Base_Row_5_ID_19]\nType=VirtualKey\nHeight=200\nLabel=Enter\nCluster=Numpad\nKeyCode=13\n\n;----------------------Row 6\n[Key_Base_Row_6_ID_0]\nType=VirtualKeyToggle\nWidth=125\nLabel=Ctrl\nKeyCode=162\nNoRepeat=true\n\n[Key_Base_Row_6_ID_1]\nType=VirtualKeyToggle\nWidth=125\nLabel=Win\nKeyCode=91\nNoRepeat=true\n\n[Key_Base_Row_6_ID_2]\nType=VirtualKeyToggle\nWidth=125\nLabel=Alt\nKeyCode=164\nNoRepeat=true\n\n[Key_Base_Row_6_ID_3]\n;Space Bar\nType=VirtualKey\nWidth=625\nLabel=\nKeyCode=32\n\n[Key_Base_Row_6_ID_4]\nType=VirtualKeyToggle\nWidth=125\nLabel=Alt\nKeyCode=165\nNoRepeat=true\n\n[Key_Base_Row_6_ID_5]\nType=VirtualKeyToggle\nWidth=125\nLabel=Win\nKeyCode=92\nNoRepeat=true\n\n[Key_Base_Row_6_ID_6]\nType=VirtualKey\nWidth=125\nLabel=Menu\nKeyCode=93\nNoRepeat=true\n\n[Key_Base_Row_6_ID_7]\nType=VirtualKeyToggle\nWidth=125\nLabel=Ctrl\nKeyCode=163\nNoRepeat=true\n\n[Key_Base_Row_6_ID_8]\nType=Blank\nWidth=25\nCluster=Navigation\n\n[Key_Base_Row_6_ID_9]\nType=VirtualKey\nLabel=🠄\nCluster=Navigation\nKeyCode=37\n\n[Key_Base_Row_6_ID_10]\nType=VirtualKey\nLabel=🠇\nCluster=Navigation\nKeyCode=40\n\n[Key_Base_Row_6_ID_11]\nType=VirtualKey\nLabel=🠆\nCluster=Navigation\nKeyCode=39\n\n[Key_Base_Row_6_ID_12]\nType=Blank\nWidth=25\nCluster=Numpad\n\n[Key_Base_Row_6_ID_13]\nType=VirtualKey\nWidth=200\nLabel=0\nCluster=Numpad\nKeyCode=96\n\n[Key_Base_Row_6_ID_14]\nType=VirtualKey\nLabel=,\nCluster=Numpad\nKeyCode=110\n\n;----------------------Shift SubLayout\n;\n;----------------------Row 0\n[Key_Shift_Row_0_ID_0]\nType=VirtualKey\nLabel=Esc\nCluster=Function\nKeyCode=27\nNoRepeat=true\n\n[Key_Shift_Row_0_ID_1]\nType=Blank\nCluster=Function\n\n[Key_Shift_Row_0_ID_2]\nType=VirtualKey\nLabel=F1\nCluster=Function\nKeyCode=112\n\n[Key_Shift_Row_0_ID_3]\nType=VirtualKey\nLabel=F2\nCluster=Function\nKeyCode=113\n\n[Key_Shift_Row_0_ID_4]\nType=VirtualKey\nLabel=F3\nCluster=Function\nKeyCode=114\n\n[Key_Shift_Row_0_ID_5]\nType=VirtualKey\nLabel=F4\nCluster=Function\nKeyCode=115\n\n[Key_Shift_Row_0_ID_6]\nType=Blank\nWidth=50\nCluster=Function\n\n[Key_Shift_Row_0_ID_7]\nType=VirtualKey\nLabel=F5\nCluster=Function\nKeyCode=116\n\n[Key_Shift_Row_0_ID_8]\nType=VirtualKey\nLabel=F6\nCluster=Function\nKeyCode=117\n\n[Key_Shift_Row_0_ID_9]\nType=VirtualKey\nLabel=F7\nCluster=Function\nKeyCode=118\n\n[Key_Shift_Row_0_ID_10]\nType=VirtualKey\nLabel=F8\nCluster=Function\nKeyCode=119\n\n[Key_Shift_Row_0_ID_11]\nType=Blank\nWidth=50\nCluster=Function\n\n[Key_Shift_Row_0_ID_12]\nType=VirtualKey\nLabel=F9\nCluster=Function\nKeyCode=120\n\n[Key_Shift_Row_0_ID_13]\nType=VirtualKey\nLabel=F10\nCluster=Function\nKeyCode=121\n\n[Key_Shift_Row_0_ID_14]\nType=VirtualKey\nLabel=F11\nCluster=Function\nKeyCode=122\n\n[Key_Shift_Row_0_ID_15]\nType=VirtualKey\nLabel=F12\nCluster=Function\nKeyCode=123\n\n[Key_Shift_Row_0_ID_16]\nType=Blank\nWidth=25\nCluster=Function\n\n[Key_Shift_Row_0_ID_17]\nType=VirtualKey\nLabel=Print\\nScreen\nCluster=Function\nKeyCode=44\nNoRepeat=true\n\n[Key_Shift_Row_0_ID_18]\nType=VirtualKey\nLabel=Scroll\\nLock\nCluster=Function\nKeyCode=145\nNoRepeat=true\n\n[Key_Shift_Row_0_ID_19]\nType=VirtualKey\nLabel=Pause\nCluster=Function\nKeyCode=19\nNoRepeat=true\n\n[Key_Shift_Row_0_ID_20]\nType=Blank\nWidth=25\nCluster=Extra\n\n[Key_Shift_Row_0_ID_21]\nType=VirtualKey\nLabel=🡰\nCluster=Extra\nKeyCode=166\nBlockModifiers=true\nNoRepeat=true\n\n[Key_Shift_Row_0_ID_22]\nType=VirtualKey\nLabel=🡲\nCluster=Extra\nKeyCode=167\nBlockModifiers=true\nNoRepeat=true\n\n[Key_Shift_Row_0_ID_23]\nType=VirtualKey\nLabel=🔇\nCluster=Extra\nKeyCode=173\nNoRepeat=true\n\n;----------------------Row 1 (25% height blank space)\n[Key_Shift_Row_1_ID_0]\nType=Blank\nHeight=25\nCluster=Function\n\n[Key_Shift_Row_1_ID_1]\nType=Blank\nHeight=25\nCluster=Extra\n\n;----------------------Row 2\n[Key_Shift_Row_2_ID_0]\nType=String\nLabel=~\nString=~\n\n[Key_Shift_Row_2_ID_1]\nType=String\nLabel=!\nString=!\n\n[Key_Shift_Row_2_ID_2]\nType=String\nLabel=@\nString=@\n\n[Key_Shift_Row_2_ID_3]\nType=String\nLabel=#\nString=#\n\n[Key_Shift_Row_2_ID_4]\nType=String\nLabel=$\nString=$\n\n[Key_Shift_Row_2_ID_5]\nType=String\nLabel=%\nString=%\n\n[Key_Shift_Row_2_ID_6]\nType=String\nLabel=^\nString=^\n\n[Key_Shift_Row_2_ID_7]\nType=String\nLabel=&\nString=&\n\n[Key_Shift_Row_2_ID_8]\nType=String\nLabel=*\nString=*\n\n[Key_Shift_Row_2_ID_9]\nType=String\nLabel=(\nString=(\n\n[Key_Shift_Row_2_ID_10]\nType=String\nLabel=)\nString=)\n\n[Key_Shift_Row_2_ID_11]\nType=String\nLabel=_\nString=_\n\n[Key_Shift_Row_2_ID_12]\nType=String\nLabel=+\nString=+\n\n[Key_Shift_Row_2_ID_13]\nType=VirtualKey\nWidth=200\nLabel=Backspace\nKeyCode=8\n\n[Key_Shift_Row_2_ID_14]\nType=Blank\nWidth=25\nCluster=Navigation\n\n[Key_Shift_Row_2_ID_15]\nType=VirtualKey\nLabel=Insert\nCluster=Navigation\nKeyCode=45\n\n[Key_Shift_Row_2_ID_16]\nType=VirtualKey\nLabel=Home\nCluster=Navigation\nKeyCode=36\n\n[Key_Shift_Row_2_ID_17]\nType=VirtualKey\nLabel=PgUp\nCluster=Navigation\nKeyCode=33\n\n[Key_Shift_Row_2_ID_18]\nType=Blank\nWidth=25\nCluster=Numpad\n\n[Key_Shift_Row_2_ID_19]\nType=VirtualKey\nLabel=Num\\nLock\nCluster=Numpad\nKeyCode=144\nNoRepeat=true\n\n[Key_Shift_Row_2_ID_20]\nType=VirtualKey\nLabel=/\nCluster=Numpad\nKeyCode=111\n\n[Key_Shift_Row_2_ID_21]\nType=VirtualKey\nLabel=*\nCluster=Numpad\nKeyCode=106\n\n[Key_Shift_Row_2_ID_22]\nType=VirtualKey\nLabel=-\nCluster=Numpad\nKeyCode=109\n\n;----------------------Row 3\n[Key_Shift_Row_3_ID_0]\nType=VirtualKey\nWidth=150\nLabel=Tab\nKeyCode=9\n\n[Key_Shift_Row_3_ID_1]\nType=VirtualKey\nLabel=Q\nKeyCode=81\n\n[Key_Shift_Row_3_ID_2]\nType=VirtualKey\nLabel=W\nKeyCode=87\n\n[Key_Shift_Row_3_ID_3]\nType=VirtualKey\nLabel=E\nKeyCode=69\n\n[Key_Shift_Row_3_ID_4]\nType=VirtualKey\nLabel=R\nKeyCode=82\n\n[Key_Shift_Row_3_ID_5]\nType=VirtualKey\nLabel=T\nKeyCode=84\n\n[Key_Shift_Row_3_ID_6]\nType=VirtualKey\nLabel=Y\nKeyCode=89\n\n[Key_Shift_Row_3_ID_7]\nType=VirtualKey\nLabel=U\nKeyCode=85\n\n[Key_Shift_Row_3_ID_8]\nType=VirtualKey\nLabel=I\nKeyCode=73\n\n[Key_Shift_Row_3_ID_9]\nType=VirtualKey\nLabel=O\nKeyCode=79\n\n[Key_Shift_Row_3_ID_10]\nType=VirtualKey\nLabel=P\nKeyCode=80\n\n[Key_Shift_Row_3_ID_11]\nType=String\nLabel={\nString={\n\n[Key_Shift_Row_3_ID_12]\nType=String\nLabel=}\nString=}\n\n[Key_Shift_Row_3_ID_13]\nType=String\nWidth=150\nLabel=|\nString=|\n\n[Key_Shift_Row_3_ID_14]\nType=Blank\nWidth=25\nCluster=Navigation\n\n[Key_Shift_Row_3_ID_15]\nType=VirtualKey\nLabel=Delete\nCluster=Navigation\nKeyCode=46\n\n[Key_Shift_Row_3_ID_16]\nType=VirtualKey\nLabel=End\nCluster=Navigation\nKeyCode=35\n\n[Key_Shift_Row_3_ID_17]\nType=VirtualKey\nLabel=PgDn\nCluster=Navigation\nKeyCode=34\n\n[Key_Shift_Row_3_ID_18]\nType=Blank\nWidth=25\nCluster=Numpad\n\n[Key_Shift_Row_3_ID_19]\nType=VirtualKey\nLabel=7\nCluster=Numpad\nKeyCode=103\n\n[Key_Shift_Row_3_ID_20]\nType=VirtualKey\nLabel=8\nCluster=Numpad\nKeyCode=104\n\n[Key_Shift_Row_3_ID_21]\nType=VirtualKey\nLabel=9\nCluster=Numpad\nKeyCode=105\n\n[Key_Shift_Row_3_ID_22]\nType=VirtualKey\nHeight=200\nLabel=+\nCluster=Numpad\nKeyCode=107\n\n;----------------------Row 4\n[Key_Shift_Row_4_ID_0]\nType=VirtualKey\nWidth=175\nLabel=Caps Lock\nKeyCode=20\nNoRepeat=true\n\n[Key_Shift_Row_4_ID_1]\nType=VirtualKey\nLabel=A\nKeyCode=65\n\n[Key_Shift_Row_4_ID_2]\nType=VirtualKey\nLabel=S\nKeyCode=83\n\n[Key_Shift_Row_4_ID_3]\nType=VirtualKey\nLabel=D\nKeyCode=68\n\n[Key_Shift_Row_4_ID_4]\nType=VirtualKey\nLabel=F\nKeyCode=70\n\n[Key_Shift_Row_4_ID_5]\nType=VirtualKey\nLabel=G\nKeyCode=71\n\n[Key_Shift_Row_4_ID_6]\nType=VirtualKey\nLabel=H\nKeyCode=72\n\n[Key_Shift_Row_4_ID_7]\nType=VirtualKey\nLabel=J\nKeyCode=74\n\n[Key_Shift_Row_4_ID_8]\nType=VirtualKey\nLabel=K\nKeyCode=75\n\n[Key_Shift_Row_4_ID_9]\nType=VirtualKey\nLabel=L\nKeyCode=76\n\n[Key_Shift_Row_4_ID_10]\nType=String\nLabel=:\nString=:\n\n[Key_Shift_Row_4_ID_11]\nType=String\nLabel=\"\nString=\"\n\n[Key_Shift_Row_4_ID_12]\nType=VirtualKey\nWidth=225\nLabel=Enter\nKeyCode=13\n\n[Key_Shift_Row_4_ID_13]\nType=Blank\nWidth=325\nCluster=Navigation\n\n[Key_Shift_Row_4_ID_14]\nType=Blank\nWidth=25\nCluster=Numpad\n\n[Key_Shift_Row_4_ID_15]\nType=VirtualKey\nLabel=4\nCluster=Numpad\nKeyCode=100\n\n[Key_Shift_Row_4_ID_16]\nType=VirtualKey\nLabel=5\nCluster=Numpad\nKeyCode=101\n\n[Key_Shift_Row_4_ID_17]\nType=VirtualKey\nLabel=6\nCluster=Numpad\nKeyCode=102\n\n;----------------------Row 5\n[Key_Shift_Row_5_ID_0]\nType=VirtualKeyToggle\nWidth=225\nLabel=Shift\nKeyCode=160\nNoRepeat=true\n\n[Key_Shift_Row_5_ID_1]\nType=VirtualKey\nLabel=Z\nKeyCode=90\n\n[Key_Shift_Row_5_ID_2]\nType=VirtualKey\nLabel=X\nKeyCode=88\n\n[Key_Shift_Row_5_ID_3]\nType=VirtualKey\nLabel=C\nKeyCode=67\n\n[Key_Shift_Row_5_ID_4]\nType=VirtualKey\nLabel=V\nKeyCode=86\n\n[Key_Shift_Row_5_ID_5]\nType=VirtualKey\nLabel=B\nKeyCode=66\n\n[Key_Shift_Row_5_ID_6]\nType=VirtualKey\nLabel=N\nKeyCode=78\n\n[Key_Shift_Row_5_ID_7]\nType=VirtualKey\nLabel=M\nKeyCode=77\n\n[Key_Shift_Row_5_ID_8]\nType=String\nLabel=<\nString=<\n\n[Key_Shift_Row_5_ID_9]\nType=String\nLabel=>\nString=>\n\n[Key_Shift_Row_5_ID_10]\nType=String\nLabel=?\nString=?\n\n[Key_Shift_Row_5_ID_11]\nType=VirtualKeyToggle\nWidth=275\nLabel=Shift\nKeyCode=161\nNoRepeat=true\n\n[Key_Shift_Row_5_ID_12]\nType=Blank\nWidth=125\nCluster=Navigation\n\n[Key_Shift_Row_5_ID_13]\nType=VirtualKey\nLabel=🠅\nCluster=Navigation\nKeyCode=38\n\n[Key_Shift_Row_5_ID_14]\nType=Blank\nWidth=100\nCluster=Navigation\n\n[Key_Shift_Row_5_ID_15]\nType=Blank\nWidth=25\nCluster=Numpad\n\n[Key_Shift_Row_5_ID_16]\nType=VirtualKey\nLabel=1\nCluster=Numpad\nKeyCode=97\n\n[Key_Shift_Row_5_ID_17]\nType=VirtualKey\nLabel=2\nCluster=Numpad\nKeyCode=98\n\n[Key_Shift_Row_5_ID_18]\nType=VirtualKey\nLabel=3\nCluster=Numpad\nKeyCode=99\n\n[Key_Shift_Row_5_ID_19]\nType=VirtualKey\nHeight=200\nLabel=Enter\nCluster=Numpad\nKeyCode=13\n\n;----------------------Row 6\n[Key_Shift_Row_6_ID_0]\nType=VirtualKeyToggle\nWidth=125\nLabel=Ctrl\nKeyCode=162\nNoRepeat=true\n\n[Key_Shift_Row_6_ID_1]\nType=VirtualKeyToggle\nWidth=125\nLabel=Win\nKeyCode=91\nNoRepeat=true\n\n[Key_Shift_Row_6_ID_2]\nType=VirtualKeyToggle\nWidth=125\nLabel=Alt\nKeyCode=164\nNoRepeat=true\n\n[Key_Shift_Row_6_ID_3]\n;Space Bar\nType=VirtualKey\nWidth=625\nLabel=\nKeyCode=32\n\n[Key_Shift_Row_6_ID_4]\nType=VirtualKeyToggle\nWidth=125\nLabel=Alt\nKeyCode=165\nNoRepeat=true\n\n[Key_Shift_Row_6_ID_5]\nType=VirtualKeyToggle\nWidth=125\nLabel=Win\nKeyCode=92\nNoRepeat=true\n\n[Key_Shift_Row_6_ID_6]\nType=VirtualKey\nWidth=125\nLabel=Menu\nKeyCode=93\nNoRepeat=true\n\n[Key_Shift_Row_6_ID_7]\nType=VirtualKeyToggle\nWidth=125\nLabel=Ctrl\nKeyCode=163\nNoRepeat=true\n\n[Key_Shift_Row_6_ID_8]\nType=Blank\nWidth=25\nCluster=Navigation\n\n[Key_Shift_Row_6_ID_9]\nType=VirtualKey\nLabel=🠄\nCluster=Navigation\nKeyCode=37\n\n[Key_Shift_Row_6_ID_10]\nType=VirtualKey\nLabel=🠇\nCluster=Navigation\nKeyCode=40\n\n[Key_Shift_Row_6_ID_11]\nType=VirtualKey\nLabel=🠆\nCluster=Navigation\nKeyCode=39\n\n[Key_Shift_Row_6_ID_12]\nType=Blank\nWidth=25\nCluster=Numpad\n\n[Key_Shift_Row_6_ID_13]\nType=VirtualKey\nWidth=200\nLabel=0\nCluster=Numpad\nKeyCode=96\n\n[Key_Shift_Row_6_ID_14]\nType=VirtualKey\nLabel=,\nCluster=Numpad\nKeyCode=110"
  },
  {
    "path": "assets/keyboards/qwertz_ger.ini",
    "content": "[LayoutInfo]\nName=QWERTZ (Germany)\nHasAltGr=true\nHasClusterFunction=true\nHasClusterNavigation=true\nHasClusterNumpad=true\nHasClusterExtra=true\n\n;See qwerty_usa.ini for format documentation\n\n;----------------------Row 0\n[Key_Base_Row_0_ID_0]\nType=VirtualKey\nLabel=Esc\nCluster=Function\nKeyCode=27\nNoRepeat=true\n\n[Key_Base_Row_0_ID_1]\nType=Blank\nCluster=Function\n\n[Key_Base_Row_0_ID_2]\nType=VirtualKey\nLabel=F1\nCluster=Function\nKeyCode=112\n\n[Key_Base_Row_0_ID_3]\nType=VirtualKey\nLabel=F2\nCluster=Function\nKeyCode=113\n\n[Key_Base_Row_0_ID_4]\nType=VirtualKey\nLabel=F3\nCluster=Function\nKeyCode=114\n\n[Key_Base_Row_0_ID_5]\nType=VirtualKey\nLabel=F4\nCluster=Function\nKeyCode=115\n\n[Key_Base_Row_0_ID_6]\nType=Blank\nWidth=50\nCluster=Function\n\n[Key_Base_Row_0_ID_7]\nType=VirtualKey\nLabel=F5\nCluster=Function\nKeyCode=116\n\n[Key_Base_Row_0_ID_8]\nType=VirtualKey\nLabel=F6\nCluster=Function\nKeyCode=117\n\n[Key_Base_Row_0_ID_9]\nType=VirtualKey\nLabel=F7\nCluster=Function\nKeyCode=118\n\n[Key_Base_Row_0_ID_10]\nType=VirtualKey\nLabel=F8\nCluster=Function\nKeyCode=119\n\n[Key_Base_Row_0_ID_11]\nType=Blank\nWidth=50\nCluster=Function\n\n[Key_Base_Row_0_ID_12]\nType=VirtualKey\nLabel=F9\nCluster=Function\nKeyCode=120\n\n[Key_Base_Row_0_ID_13]\nType=VirtualKey\nLabel=F10\nCluster=Function\nKeyCode=121\n\n[Key_Base_Row_0_ID_14]\nType=VirtualKey\nLabel=F11\nCluster=Function\nKeyCode=122\n\n[Key_Base_Row_0_ID_15]\nType=VirtualKey\nLabel=F12\nCluster=Function\nKeyCode=123\n\n[Key_Base_Row_0_ID_16]\nType=Blank\nWidth=25\nCluster=Function\n\n[Key_Base_Row_0_ID_17]\nType=VirtualKey\nLabel=Druck\nCluster=Function\nKeyCode=44\nNoRepeat=true\n\n[Key_Base_Row_0_ID_18]\nType=VirtualKey\nLabel=Rollen\nCluster=Function\nKeyCode=145\nNoRepeat=true\n\n[Key_Base_Row_0_ID_19]\nType=VirtualKey\nLabel=Pause\nCluster=Function\nKeyCode=19\nNoRepeat=true\n\n[Key_Base_Row_0_ID_20]\nType=Blank\nWidth=25\nCluster=Extra\n\n[Key_Base_Row_0_ID_21]\nType=VirtualKey\nLabel=⏯\nCluster=Extra\nKeyCode=179\nNoRepeat=true\n\n[Key_Base_Row_0_ID_22]\nType=VirtualKey\nLabel=◼\nCluster=Extra\nKeyCode=178\nNoRepeat=true\n\n[Key_Base_Row_0_ID_23]\nType=VirtualKey\nLabel=⏮\nCluster=Extra\nKeyCode=177\nNoRepeat=true\n\n[Key_Base_Row_0_ID_24]\nType=VirtualKey\nLabel=⏭\nCluster=Extra\nKeyCode=176\nNoRepeat=true\n\n;----------------------Row 1 (25% height blank space)\n[Key_Base_Row_1_ID_0]\nType=Blank\nHeight=25\nCluster=Function\n\n[Key_Base_Row_1_ID_1]\nType=Blank\nHeight=25\nCluster=Extra\n\n;----------------------Row 2\n[Key_Base_Row_2_ID_0]\nType=String\nLabel=^\nString=^\n\n[Key_Base_Row_2_ID_1]\nType=VirtualKey\nLabel=1\nKeyCode=49\n\n[Key_Base_Row_2_ID_2]\nType=VirtualKey\nLabel=2\nKeyCode=50\n\n[Key_Base_Row_2_ID_3]\nType=VirtualKey\nLabel=3\nKeyCode=51\n\n[Key_Base_Row_2_ID_4]\nType=VirtualKey\nLabel=4\nKeyCode=52\n\n[Key_Base_Row_2_ID_5]\nType=VirtualKey\nLabel=5\nKeyCode=53\n\n[Key_Base_Row_2_ID_6]\nType=VirtualKey\nLabel=6\nKeyCode=54\n\n[Key_Base_Row_2_ID_7]\nType=VirtualKey\nLabel=7\nKeyCode=55\n\n[Key_Base_Row_2_ID_8]\nType=VirtualKey\nLabel=8\nKeyCode=56\n\n[Key_Base_Row_2_ID_9]\nType=VirtualKey\nLabel=9\nKeyCode=57\n\n[Key_Base_Row_2_ID_10]\nType=VirtualKey\nLabel=0\nKeyCode=48\n\n[Key_Base_Row_2_ID_11]\nType=String\nLabel=ß\nString=ß\n\n[Key_Base_Row_2_ID_12]\nType=String\nLabel=´\nString=´\n\n[Key_Base_Row_2_ID_13]\nType=VirtualKey\nWidth=200\nLabel=⟵\nKeyCode=8\n\n[Key_Base_Row_2_ID_14]\nType=Blank\nWidth=25\nCluster=Navigation\n\n[Key_Base_Row_2_ID_15]\nType=VirtualKey\nLabel=Einfg\nCluster=Navigation\nKeyCode=45\n\n[Key_Base_Row_2_ID_16]\nType=VirtualKey\nLabel=Pos1\nCluster=Navigation\nKeyCode=36\n\n[Key_Base_Row_2_ID_17]\nType=VirtualKey\nLabel=Bild🠹\nCluster=Navigation\nKeyCode=33\n\n[Key_Base_Row_2_ID_18]\nType=Blank\nWidth=25\nCluster=Numpad\n\n[Key_Base_Row_2_ID_19]\nType=VirtualKey\nLabel=Num\nCluster=Numpad\nKeyCode=144\nNoRepeat=true\n\n[Key_Base_Row_2_ID_20]\nType=VirtualKey\nLabel=/\nCluster=Numpad\nKeyCode=111\n\n[Key_Base_Row_2_ID_21]\nType=VirtualKey\nLabel=*\nCluster=Numpad\nKeyCode=106\n\n[Key_Base_Row_2_ID_22]\nType=VirtualKey\nLabel=-\nCluster=Numpad\nKeyCode=109\n\n;----------------------Row 3\n[Key_Base_Row_3_ID_0]\nType=VirtualKey\nWidth=150\nLabel=⭾\nKeyCode=9\n\n[Key_Base_Row_3_ID_1]\nType=VirtualKey\nLabel=q\nKeyCode=81\n\n[Key_Base_Row_3_ID_2]\nType=VirtualKey\nLabel=w\nKeyCode=87\n\n[Key_Base_Row_3_ID_3]\nType=VirtualKey\nLabel=e\nKeyCode=69\n\n[Key_Base_Row_3_ID_4]\nType=VirtualKey\nLabel=r\nKeyCode=82\n\n[Key_Base_Row_3_ID_5]\nType=VirtualKey\nLabel=t\nKeyCode=84\n\n[Key_Base_Row_3_ID_6]\nType=VirtualKey\nLabel=z\nKeyCode=90\n\n[Key_Base_Row_3_ID_7]\nType=VirtualKey\nLabel=u\nKeyCode=85\n\n[Key_Base_Row_3_ID_8]\nType=VirtualKey\nLabel=i\nKeyCode=73\n\n[Key_Base_Row_3_ID_9]\nType=VirtualKey\nLabel=o\nKeyCode=79\n\n[Key_Base_Row_3_ID_10]\nType=VirtualKey\nLabel=p\nKeyCode=80\n\n[Key_Base_Row_3_ID_11]\nType=String\nLabel=ü\nString=ü\n\n[Key_Base_Row_3_ID_12]\nType=String\nLabel=+\nString=+\n\n[Key_Base_Row_3_ID_13]\nType=VirtualKeyIsoEnter\nWidth=150\nLabel=\nKeyCode=13\n\n[Key_Base_Row_3_ID_14]\nType=Blank\nWidth=25\nCluster=Navigation\n\n[Key_Base_Row_3_ID_15]\nType=VirtualKey\nLabel=Entf\nCluster=Navigation\nKeyCode=46\n\n[Key_Base_Row_3_ID_16]\nType=VirtualKey\nLabel=Ende\nCluster=Navigation\nKeyCode=35\n\n[Key_Base_Row_3_ID_17]\nType=VirtualKey\nLabel=Bild🠻\nCluster=Navigation\nKeyCode=34\n\n[Key_Base_Row_3_ID_18]\nType=Blank\nWidth=25\nCluster=Numpad\n\n[Key_Base_Row_3_ID_19]\nType=VirtualKey\nLabel=7\nCluster=Numpad\nKeyCode=103\n\n[Key_Base_Row_3_ID_20]\nType=VirtualKey\nLabel=8\nCluster=Numpad\nKeyCode=104\n\n[Key_Base_Row_3_ID_21]\nType=VirtualKey\nLabel=9\nCluster=Numpad\nKeyCode=105\n\n[Key_Base_Row_3_ID_22]\nType=VirtualKey\nHeight=200\nLabel=+\nCluster=Numpad\nKeyCode=107\n\n;----------------------Row 4\n[Key_Base_Row_4_ID_0]\nType=VirtualKey\nWidth=175\nLabel=🡇\nKeyCode=20\nNoRepeat=true\n\n[Key_Base_Row_4_ID_1]\nType=VirtualKey\nLabel=a\nKeyCode=65\n\n[Key_Base_Row_4_ID_2]\nType=VirtualKey\nLabel=s\nKeyCode=83\n\n[Key_Base_Row_4_ID_3]\nType=VirtualKey\nLabel=d\nKeyCode=68\n\n[Key_Base_Row_4_ID_4]\nType=VirtualKey\nLabel=f\nKeyCode=70\n\n[Key_Base_Row_4_ID_5]\nType=VirtualKey\nLabel=g\nKeyCode=71\n\n[Key_Base_Row_4_ID_6]\nType=VirtualKey\nLabel=h\nKeyCode=72\n\n[Key_Base_Row_4_ID_7]\nType=VirtualKey\nLabel=j\nKeyCode=74\n\n[Key_Base_Row_4_ID_8]\nType=VirtualKey\nLabel=k\nKeyCode=75\n\n[Key_Base_Row_4_ID_9]\nType=VirtualKey\nLabel=l\nKeyCode=76\n\n[Key_Base_Row_4_ID_10]\nType=String\nLabel=ö\nString=ö\n\n[Key_Base_Row_4_ID_11]\nType=String\nLabel=ä\nString=ä\n\n[Key_Base_Row_4_ID_12]\nType=String\nLabel=#\nString=#\n\n[Key_Base_Row_4_ID_13]\nType=VirtualKeyIsoEnter\nWidth=125\nLabel=↵\nKeyCode=13\n\n[Key_Base_Row_4_ID_14]\nType=Blank\nWidth=325\nCluster=Navigation\n\n[Key_Base_Row_4_ID_15]\nType=Blank\nWidth=25\nCluster=Numpad\n\n[Key_Base_Row_4_ID_16]\nType=VirtualKey\nLabel=4\nCluster=Numpad\nKeyCode=100\n\n[Key_Base_Row_4_ID_17]\nType=VirtualKey\nLabel=5\nCluster=Numpad\nKeyCode=101\n\n[Key_Base_Row_4_ID_18]\nType=VirtualKey\nLabel=6\nCluster=Numpad\nKeyCode=102\n\n;----------------------Row 5\n[Key_Base_Row_5_ID_0]\nType=VirtualKeyToggle\nWidth=125\nLabel=🡅\nKeyCode=160\nNoRepeat=true\n\n[Key_Base_Row_5_ID_1]\nType=String\nLabel=<\nString=<\n\n[Key_Base_Row_5_ID_2]\nType=VirtualKey\nLabel=y\nKeyCode=89\n\n[Key_Base_Row_5_ID_3]\nType=VirtualKey\nLabel=x\nKeyCode=88\n\n[Key_Base_Row_5_ID_4]\nType=VirtualKey\nLabel=c\nKeyCode=67\n\n[Key_Base_Row_5_ID_5]\nType=VirtualKey\nLabel=v\nKeyCode=86\n\n[Key_Base_Row_5_ID_6]\nType=VirtualKey\nLabel=b\nKeyCode=66\n\n[Key_Base_Row_5_ID_7]\nType=VirtualKey\nLabel=n\nKeyCode=78\n\n[Key_Base_Row_5_ID_8]\nType=VirtualKey\nLabel=m\nKeyCode=77\n\n[Key_Base_Row_5_ID_9]\nType=String\nLabel=,\nString=,\n\n[Key_Base_Row_5_ID_10]\nType=String\nLabel=.\nString=.\n\n[Key_Base_Row_5_ID_11]\nType=String\nLabel=-\nString=-\n\n[Key_Base_Row_5_ID_12]\nType=VirtualKeyToggle\nWidth=275\nLabel=🡅\nKeyCode=161\nNoRepeat=true\n\n[Key_Base_Row_5_ID_13]\nType=Blank\nWidth=125\nCluster=Navigation\n\n[Key_Base_Row_5_ID_14]\nType=VirtualKey\nLabel=🠅\nCluster=Navigation\nKeyCode=38\n\n[Key_Base_Row_5_ID_15]\nType=Blank\nWidth=100\nCluster=Navigation\n\n[Key_Base_Row_5_ID_16]\nType=Blank\nWidth=25\nCluster=Numpad\n\n[Key_Base_Row_5_ID_17]\nType=VirtualKey\nLabel=1\nCluster=Numpad\nKeyCode=97\n\n[Key_Base_Row_5_ID_18]\nType=VirtualKey\nLabel=2\nCluster=Numpad\nKeyCode=98\n\n[Key_Base_Row_5_ID_19]\nType=VirtualKey\nLabel=3\nCluster=Numpad\nKeyCode=99\n\n[Key_Base_Row_5_ID_20]\nType=VirtualKey\nHeight=200\nLabel=Enter\nCluster=Numpad\nKeyCode=13\n\n;----------------------Row 6\n[Key_Base_Row_6_ID_0]\nType=VirtualKeyToggle\nWidth=125\nLabel=Strg\nKeyCode=162\nNoRepeat=true\n\n[Key_Base_Row_6_ID_1]\nType=VirtualKeyToggle\nWidth=125\nLabel=Win\nKeyCode=91\nNoRepeat=true\n\n[Key_Base_Row_6_ID_2]\nType=VirtualKeyToggle\nWidth=125\nLabel=Alt\nKeyCode=164\nNoRepeat=true\n\n[Key_Base_Row_6_ID_3]\n;Space Bar\nType=VirtualKey\nWidth=625\nLabel=\nKeyCode=32\n\n[Key_Base_Row_6_ID_4]\nType=VirtualKeyToggle\nWidth=125\nLabel=AltGr\nKeyCode=165\nNoRepeat=true\n\n[Key_Base_Row_6_ID_5]\nType=VirtualKeyToggle\nWidth=125\nLabel=Win\nKeyCode=92\nNoRepeat=true\n\n[Key_Base_Row_6_ID_6]\nType=VirtualKey\nWidth=125\nLabel=Menü\nKeyCode=93\nNoRepeat=true\n\n[Key_Base_Row_6_ID_7]\nType=VirtualKeyToggle\nWidth=125\nLabel=Strg\nKeyCode=163\nNoRepeat=true\n\n[Key_Base_Row_6_ID_8]\nType=Blank\nWidth=25\nCluster=Navigation\n\n[Key_Base_Row_6_ID_9]\nType=VirtualKey\nLabel=🠄\nCluster=Navigation\nKeyCode=37\n\n[Key_Base_Row_6_ID_10]\nType=VirtualKey\nLabel=🠇\nCluster=Navigation\nKeyCode=40\n\n[Key_Base_Row_6_ID_11]\nType=VirtualKey\nLabel=🠆\nCluster=Navigation\nKeyCode=39\n\n[Key_Base_Row_6_ID_12]\nType=Blank\nWidth=25\nCluster=Numpad\n\n[Key_Base_Row_6_ID_13]\nType=VirtualKey\nWidth=200\nLabel=0\nCluster=Numpad\nKeyCode=96\n\n[Key_Base_Row_6_ID_14]\nType=VirtualKey\nLabel=,\nCluster=Numpad\nKeyCode=110\n\n;----------------------Shift SubLayout\n;\n;----------------------Row 0\n[Key_Shift_Row_0_ID_0]\nType=VirtualKey\nLabel=Esc\nCluster=Function\nKeyCode=27\nNoRepeat=true\n\n[Key_Shift_Row_0_ID_1]\nType=Blank\nCluster=Function\n\n[Key_Shift_Row_0_ID_2]\nType=VirtualKey\nLabel=F1\nCluster=Function\nKeyCode=112\n\n[Key_Shift_Row_0_ID_3]\nType=VirtualKey\nLabel=F2\nCluster=Function\nKeyCode=113\n\n[Key_Shift_Row_0_ID_4]\nType=VirtualKey\nLabel=F3\nCluster=Function\nKeyCode=114\n\n[Key_Shift_Row_0_ID_5]\nType=VirtualKey\nLabel=F4\nCluster=Function\nKeyCode=115\n\n[Key_Shift_Row_0_ID_6]\nType=Blank\nWidth=50\nCluster=Function\n\n[Key_Shift_Row_0_ID_7]\nType=VirtualKey\nLabel=F5\nCluster=Function\nKeyCode=116\n\n[Key_Shift_Row_0_ID_8]\nType=VirtualKey\nLabel=F6\nCluster=Function\nKeyCode=117\n\n[Key_Shift_Row_0_ID_9]\nType=VirtualKey\nLabel=F7\nCluster=Function\nKeyCode=118\n\n[Key_Shift_Row_0_ID_10]\nType=VirtualKey\nLabel=F8\nCluster=Function\nKeyCode=119\n\n[Key_Shift_Row_0_ID_11]\nType=Blank\nWidth=50\nCluster=Function\n\n[Key_Shift_Row_0_ID_12]\nType=VirtualKey\nLabel=F9\nCluster=Function\nKeyCode=120\n\n[Key_Shift_Row_0_ID_13]\nType=VirtualKey\nLabel=F10\nCluster=Function\nKeyCode=121\n\n[Key_Shift_Row_0_ID_14]\nType=VirtualKey\nLabel=F11\nCluster=Function\nKeyCode=122\n\n[Key_Shift_Row_0_ID_15]\nType=VirtualKey\nLabel=F12\nCluster=Function\nKeyCode=123\n\n[Key_Shift_Row_0_ID_16]\nType=Blank\nWidth=25\nCluster=Function\n\n[Key_Shift_Row_0_ID_17]\nType=VirtualKey\nLabel=Druck\nCluster=Function\nKeyCode=44\nNoRepeat=true\n\n[Key_Shift_Row_0_ID_18]\nType=VirtualKey\nLabel=Rollen\nCluster=Function\nKeyCode=145\nNoRepeat=true\n\n[Key_Shift_Row_0_ID_19]\nType=VirtualKey\nLabel=Pause\nCluster=Function\nKeyCode=19\nNoRepeat=true\n\n[Key_Shift_Row_0_ID_20]\nType=Blank\nWidth=25\nCluster=Extra\n\n[Key_Shift_Row_0_ID_21]\nType=VirtualKey\nLabel=🡰\nCluster=Extra\nKeyCode=166\nNoRepeat=true\n\n[Key_Shift_Row_0_ID_22]\nType=VirtualKey\nLabel=🡲\nCluster=Extra\nKeyCode=167\nNoRepeat=true\n\n[Key_Shift_Row_0_ID_23]\nType=VirtualKey\nLabel=🔇\nCluster=Extra\nKeyCode=173\nNoRepeat=true\n\n;----------------------Row 1 (25% height blank space)\n[Key_Shift_Row_1_ID_0]\nType=Blank\nHeight=25\nCluster=Function\n\n[Key_Shift_Row_1_ID_1]\nType=Blank\nHeight=25\nCluster=Extra\n\n;----------------------Row 2\n[Key_Shift_Row_2_ID_0]\nType=String\nLabel=°\nString=°\n\n[Key_Shift_Row_2_ID_1]\nType=String\nLabel=!\nString=!\n\n[Key_Shift_Row_2_ID_2]\nType=String\nLabel=\"\nString=\"\n\n[Key_Shift_Row_2_ID_3]\nType=String\nLabel=§\nString=§\n\n[Key_Shift_Row_2_ID_4]\nType=String\nLabel=$\nString=$\n\n[Key_Shift_Row_2_ID_5]\nType=String\nLabel=%\nString=%\n\n[Key_Shift_Row_2_ID_6]\nType=String\nLabel=&\nString=&\n\n[Key_Shift_Row_2_ID_7]\nType=String\nLabel=/\nString=/\n\n[Key_Shift_Row_2_ID_8]\nType=String\nLabel=(\nString=(\n\n[Key_Shift_Row_2_ID_9]\nType=String\nLabel=)\nString=)\n\n[Key_Shift_Row_2_ID_10]\nType=String\nLabel==\nString==\n\n[Key_Shift_Row_2_ID_11]\nType=String\nLabel=?\nString=?\n\n[Key_Shift_Row_2_ID_12]\nType=String\nLabel=`\nString=`\n\n[Key_Shift_Row_2_ID_13]\nType=VirtualKey\nWidth=200\nLabel=⟵\nKeyCode=8\n\n[Key_Shift_Row_2_ID_14]\nType=Blank\nWidth=25\nCluster=Navigation\n\n[Key_Shift_Row_2_ID_15]\nType=VirtualKey\nLabel=Einfg\nCluster=Navigation\nKeyCode=45\n\n[Key_Shift_Row_2_ID_16]\nType=VirtualKey\nLabel=Pos1\nCluster=Navigation\nKeyCode=36\n\n[Key_Shift_Row_2_ID_17]\nType=VirtualKey\nLabel=Bild🠹\nCluster=Navigation\nKeyCode=33\n\n[Key_Shift_Row_2_ID_18]\nType=Blank\nWidth=25\nCluster=Numpad\n\n[Key_Shift_Row_2_ID_19]\nType=VirtualKey\nLabel=Num\nCluster=Numpad\nKeyCode=144\nNoRepeat=true\n\n[Key_Shift_Row_2_ID_20]\nType=VirtualKey\nLabel=/\nCluster=Numpad\nKeyCode=111\n\n[Key_Shift_Row_2_ID_21]\nType=VirtualKey\nLabel=*\nCluster=Numpad\nKeyCode=106\n\n[Key_Shift_Row_2_ID_22]\nType=VirtualKey\nLabel=-\nCluster=Numpad\nKeyCode=109\n\n;----------------------Row 3\n[Key_Shift_Row_3_ID_0]\nType=VirtualKey\nWidth=150\nLabel=⭾\nKeyCode=9\n\n[Key_Shift_Row_3_ID_1]\nType=VirtualKey\nLabel=Q\nKeyCode=81\n\n[Key_Shift_Row_3_ID_2]\nType=VirtualKey\nLabel=W\nKeyCode=87\n\n[Key_Shift_Row_3_ID_3]\nType=VirtualKey\nLabel=E\nKeyCode=69\n\n[Key_Shift_Row_3_ID_4]\nType=VirtualKey\nLabel=R\nKeyCode=82\n\n[Key_Shift_Row_3_ID_5]\nType=VirtualKey\nLabel=T\nKeyCode=84\n\n[Key_Shift_Row_3_ID_6]\nType=VirtualKey\nLabel=Z\nKeyCode=90\n\n[Key_Shift_Row_3_ID_7]\nType=VirtualKey\nLabel=U\nKeyCode=85\n\n[Key_Shift_Row_3_ID_8]\nType=VirtualKey\nLabel=I\nKeyCode=73\n\n[Key_Shift_Row_3_ID_9]\nType=VirtualKey\nLabel=O\nKeyCode=79\n\n[Key_Shift_Row_3_ID_10]\nType=VirtualKey\nLabel=P\nKeyCode=80\n\n[Key_Shift_Row_3_ID_11]\nType=String\nLabel=Ü\nString=Ü\n\n[Key_Shift_Row_3_ID_12]\nType=String\nLabel=*\nString=*\n\n[Key_Shift_Row_3_ID_13]\nType=VirtualKeyIsoEnter\nWidth=150\nLabel=\nKeyCode=13\n\n[Key_Shift_Row_3_ID_14]\nType=Blank\nWidth=25\nCluster=Navigation\n\n[Key_Shift_Row_3_ID_15]\nType=VirtualKey\nLabel=Entf\nCluster=Navigation\nKeyCode=46\n\n[Key_Shift_Row_3_ID_16]\nType=VirtualKey\nLabel=Ende\nCluster=Navigation\nKeyCode=35\n\n[Key_Shift_Row_3_ID_17]\nType=VirtualKey\nLabel=Bild🠻\nCluster=Navigation\nKeyCode=34\n\n[Key_Shift_Row_3_ID_18]\nType=Blank\nWidth=25\nCluster=Numpad\n\n[Key_Shift_Row_3_ID_19]\nType=VirtualKey\nLabel=7\nCluster=Numpad\nKeyCode=103\n\n[Key_Shift_Row_3_ID_20]\nType=VirtualKey\nLabel=8\nCluster=Numpad\nKeyCode=104\n\n[Key_Shift_Row_3_ID_21]\nType=VirtualKey\nLabel=9\nCluster=Numpad\nKeyCode=105\n\n[Key_Shift_Row_3_ID_22]\nType=VirtualKey\nHeight=200\nLabel=+\nCluster=Numpad\nKeyCode=107\n\n;----------------------Row 4\n[Key_Shift_Row_4_ID_0]\nType=VirtualKey\nWidth=175\nLabel=🡇\nKeyCode=20\nNoRepeat=true\n\n[Key_Shift_Row_4_ID_1]\nType=VirtualKey\nLabel=A\nKeyCode=65\n\n[Key_Shift_Row_4_ID_2]\nType=VirtualKey\nLabel=S\nKeyCode=83\n\n[Key_Shift_Row_4_ID_3]\nType=VirtualKey\nLabel=D\nKeyCode=68\n\n[Key_Shift_Row_4_ID_4]\nType=VirtualKey\nLabel=F\nKeyCode=70\n\n[Key_Shift_Row_4_ID_5]\nType=VirtualKey\nLabel=G\nKeyCode=71\n\n[Key_Shift_Row_4_ID_6]\nType=VirtualKey\nLabel=H\nKeyCode=72\n\n[Key_Shift_Row_4_ID_7]\nType=VirtualKey\nLabel=J\nKeyCode=74\n\n[Key_Shift_Row_4_ID_8]\nType=VirtualKey\nLabel=K\nKeyCode=75\n\n[Key_Shift_Row_4_ID_9]\nType=VirtualKey\nLabel=L\nKeyCode=76\n\n[Key_Shift_Row_4_ID_10]\nType=String\nLabel=Ö\nString=Ö\n\n[Key_Shift_Row_4_ID_11]\nType=String\nLabel=Ä\nString=Ä\n\n[Key_Shift_Row_4_ID_12]\nType=String\nLabel='\nString='\n\n[Key_Shift_Row_4_ID_13]\nType=VirtualKeyIsoEnter\nWidth=125\nLabel=↵\nKeyCode=13\n\n[Key_Shift_Row_4_ID_14]\nType=Blank\nWidth=325\nCluster=Navigation\n\n[Key_Shift_Row_4_ID_15]\nType=Blank\nWidth=25\nCluster=Numpad\n\n[Key_Shift_Row_4_ID_16]\nType=VirtualKey\nLabel=4\nCluster=Numpad\nKeyCode=100\n\n[Key_Shift_Row_4_ID_17]\nType=VirtualKey\nLabel=5\nCluster=Numpad\nKeyCode=101\n\n[Key_Shift_Row_4_ID_18]\nType=VirtualKey\nLabel=6\nCluster=Numpad\nKeyCode=102\n\n;----------------------Row 5\n[Key_Shift_Row_5_ID_0]\nType=VirtualKeyToggle\nWidth=125\nLabel=🡅\nKeyCode=160\nNoRepeat=true\n\n[Key_Shift_Row_5_ID_1]\nType=String\nLabel=>\nString=>\n\n[Key_Shift_Row_5_ID_2]\nType=VirtualKey\nLabel=Y\nKeyCode=89\n\n[Key_Shift_Row_5_ID_3]\nType=VirtualKey\nLabel=X\nKeyCode=88\n\n[Key_Shift_Row_5_ID_4]\nType=VirtualKey\nLabel=C\nKeyCode=67\n\n[Key_Shift_Row_5_ID_5]\nType=VirtualKey\nLabel=V\nKeyCode=86\n\n[Key_Shift_Row_5_ID_6]\nType=VirtualKey\nLabel=B\nKeyCode=66\n\n[Key_Shift_Row_5_ID_7]\nType=VirtualKey\nLabel=N\nKeyCode=78\n\n[Key_Shift_Row_5_ID_8]\nType=VirtualKey\nLabel=M\nKeyCode=77\n\n[Key_Shift_Row_5_ID_9]\nType=String\nLabel=;\nString=;\n\n[Key_Shift_Row_5_ID_10]\nType=String\nLabel=:\nString=:\n\n[Key_Shift_Row_5_ID_11]\nType=String\nLabel=_\nString=_\n\n[Key_Shift_Row_5_ID_12]\nType=VirtualKeyToggle\nWidth=275\nLabel=🡅\nKeyCode=161\nNoRepeat=true\n\n[Key_Shift_Row_5_ID_13]\nType=Blank\nWidth=125\nCluster=Navigation\n\n[Key_Shift_Row_5_ID_14]\nType=VirtualKey\nLabel=🠅\nCluster=Navigation\nKeyCode=38\n\n[Key_Shift_Row_5_ID_15]\nType=Blank\nWidth=100\nCluster=Navigation\n\n[Key_Shift_Row_5_ID_16]\nType=Blank\nWidth=25\nCluster=Numpad\n\n[Key_Shift_Row_5_ID_17]\nType=VirtualKey\nLabel=1\nCluster=Numpad\nKeyCode=97\n\n[Key_Shift_Row_5_ID_18]\nType=VirtualKey\nLabel=2\nCluster=Numpad\nKeyCode=98\n\n[Key_Shift_Row_5_ID_19]\nType=VirtualKey\nLabel=3\nCluster=Numpad\nKeyCode=99\n\n[Key_Shift_Row_5_ID_20]\nType=VirtualKey\nHeight=200\nLabel=Enter\nCluster=Numpad\nKeyCode=13\n\n;----------------------Row 6\n[Key_Shift_Row_6_ID_0]\nType=VirtualKeyToggle\nWidth=125\nLabel=Strg\nKeyCode=162\nNoRepeat=true\n\n[Key_Shift_Row_6_ID_1]\nType=VirtualKeyToggle\nWidth=125\nLabel=Win\nKeyCode=91\nNoRepeat=true\n\n[Key_Shift_Row_6_ID_2]\nType=VirtualKeyToggle\nWidth=125\nLabel=Alt\nKeyCode=164\nNoRepeat=true\n\n[Key_Shift_Row_6_ID_3]\n;Space Bar\nType=VirtualKey\nWidth=625\nLabel=\nKeyCode=32\n\n[Key_Shift_Row_6_ID_4]\nType=VirtualKeyToggle\nWidth=125\nLabel=AltGr\nKeyCode=165\nNoRepeat=true\n\n[Key_Shift_Row_6_ID_5]\nType=VirtualKeyToggle\nWidth=125\nLabel=Win\nKeyCode=92\nNoRepeat=true\n\n[Key_Shift_Row_6_ID_6]\nType=VirtualKey\nWidth=125\nLabel=Menü\nKeyCode=93\nNoRepeat=true\n\n[Key_Shift_Row_6_ID_7]\nType=VirtualKeyToggle\nWidth=125\nLabel=Strg\nKeyCode=163\nNoRepeat=true\n\n[Key_Shift_Row_6_ID_8]\nType=Blank\nWidth=25\nCluster=Navigation\n\n[Key_Shift_Row_6_ID_9]\nType=VirtualKey\nLabel=🠄\nCluster=Navigation\nKeyCode=37\n\n[Key_Shift_Row_6_ID_10]\nType=VirtualKey\nLabel=🠇\nCluster=Navigation\nKeyCode=40\n\n[Key_Shift_Row_6_ID_11]\nType=VirtualKey\nLabel=🠆\nCluster=Navigation\nKeyCode=39\n\n[Key_Shift_Row_6_ID_12]\nType=Blank\nWidth=25\nCluster=Numpad\n\n[Key_Shift_Row_6_ID_13]\nType=VirtualKey\nWidth=200\nLabel=0\nCluster=Numpad\nKeyCode=96\n\n[Key_Shift_Row_6_ID_14]\nType=VirtualKey\nLabel=,\nCluster=Numpad\nKeyCode=110\n\n;----------------------AltGr SubLayout\n;\n;----------------------Row 0\n[Key_AltGr_Row_0_ID_0]\nType=VirtualKey\nLabel=Esc\nCluster=Function\nKeyCode=27\nNoRepeat=true\n\n[Key_AltGr_Row_0_ID_1]\nType=Blank\nCluster=Function\n\n[Key_AltGr_Row_0_ID_2]\nType=VirtualKey\nLabel=F1\nCluster=Function\nKeyCode=112\n\n[Key_AltGr_Row_0_ID_3]\nType=VirtualKey\nLabel=F2\nCluster=Function\nKeyCode=113\n\n[Key_AltGr_Row_0_ID_4]\nType=VirtualKey\nLabel=F3\nCluster=Function\nKeyCode=114\n\n[Key_AltGr_Row_0_ID_5]\nType=VirtualKey\nLabel=F4\nCluster=Function\nKeyCode=115\n\n[Key_AltGr_Row_0_ID_6]\nType=Blank\nWidth=50\nCluster=Function\n\n[Key_AltGr_Row_0_ID_7]\nType=VirtualKey\nLabel=F5\nCluster=Function\nKeyCode=116\n\n[Key_AltGr_Row_0_ID_8]\nType=VirtualKey\nLabel=F6\nCluster=Function\nKeyCode=117\n\n[Key_AltGr_Row_0_ID_9]\nType=VirtualKey\nLabel=F7\nCluster=Function\nKeyCode=118\n\n[Key_AltGr_Row_0_ID_10]\nType=VirtualKey\nLabel=F8\nCluster=Function\nKeyCode=119\n\n[Key_AltGr_Row_0_ID_11]\nType=Blank\nWidth=50\nCluster=Function\n\n[Key_AltGr_Row_0_ID_12]\nType=VirtualKey\nLabel=F9\nCluster=Function\nKeyCode=120\n\n[Key_AltGr_Row_0_ID_13]\nType=VirtualKey\nLabel=F10\nCluster=Function\nKeyCode=121\n\n[Key_AltGr_Row_0_ID_14]\nType=VirtualKey\nLabel=F11\nCluster=Function\nKeyCode=122\n\n[Key_AltGr_Row_0_ID_15]\nType=VirtualKey\nLabel=F12\nCluster=Function\nKeyCode=123\n\n[Key_AltGr_Row_0_ID_16]\nType=Blank\nWidth=25\nCluster=Function\n\n[Key_AltGr_Row_0_ID_17]\nType=VirtualKey\nLabel=Druck\nCluster=Function\nKeyCode=44\nNoRepeat=true\n\n[Key_AltGr_Row_0_ID_18]\nType=VirtualKey\nLabel=Rollen\nCluster=Function\nKeyCode=145\nNoRepeat=true\n\n[Key_AltGr_Row_0_ID_19]\nType=VirtualKey\nLabel=Pause\nCluster=Function\nKeyCode=19\nNoRepeat=true\n\n[Key_AltGr_Row_0_ID_20]\nType=Blank\nWidth=25\nCluster=Extra\n\n[Key_AltGr_Row_0_ID_21]\nType=VirtualKey\nLabel=⏯\nCluster=Extra\nKeyCode=179\nNoRepeat=true\n\n[Key_AltGr_Row_0_ID_22]\nType=VirtualKey\nLabel=◼\nCluster=Extra\nKeyCode=178\nNoRepeat=true\n\n[Key_AltGr_Row_0_ID_23]\nType=VirtualKey\nLabel=⏮\nCluster=Extra\nKeyCode=177\nNoRepeat=true\n\n[Key_AltGr_Row_0_ID_24]\nType=VirtualKey\nLabel=⏭\nCluster=Extra\nKeyCode=176\nNoRepeat=true\n\n;----------------------Row 1 (25% height blank space)\n[Key_AltGr_Row_1_ID_0]\nType=Blank\nHeight=25\nCluster=Function\n\n[Key_AltGr_Row_1_ID_1]\nType=Blank\nHeight=25\nCluster=Extra\n\n;----------------------Row 2\n[Key_AltGr_Row_2_ID_0]\nType=Blank\nWidth=700\n\n[Key_AltGr_Row_2_ID_1]\nType=String\nLabel={\nString={\n\n[Key_AltGr_Row_2_ID_2]\nType=String\nLabel=[\nString=[\n\n[Key_AltGr_Row_2_ID_3]\nType=String\nLabel=]\nString=]\n\n[Key_AltGr_Row_2_ID_4]\nType=String\nLabel=}\nString=}\n\n[Key_AltGr_Row_2_ID_5]\nType=String\nLabel=\\\nString=\\\n\n[Key_AltGr_Row_2_ID_6]\nType=Blank\n\n[Key_AltGr_Row_2_ID_7]\nType=VirtualKey\nWidth=200\nLabel=⟵\nKeyCode=8\n\n[Key_AltGr_Row_2_ID_8]\nType=Blank\nWidth=25\nCluster=Navigation\n\n[Key_AltGr_Row_2_ID_9]\nType=VirtualKey\nLabel=Einfg\nCluster=Navigation\nKeyCode=45\n\n[Key_AltGr_Row_2_ID_10]\nType=VirtualKey\nLabel=Pos1\nCluster=Navigation\nKeyCode=36\n\n[Key_AltGr_Row_2_ID_11]\nType=VirtualKey\nLabel=Bild🠹\nCluster=Navigation\nKeyCode=33\n\n[Key_AltGr_Row_2_ID_12]\nType=Blank\nWidth=25\nCluster=Numpad\n\n[Key_AltGr_Row_2_ID_13]\nType=VirtualKey\nLabel=Num\nCluster=Numpad\nKeyCode=144\nNoRepeat=true\n\n[Key_AltGr_Row_2_ID_14]\nType=VirtualKey\nLabel=/\nCluster=Numpad\nKeyCode=111\n\n[Key_AltGr_Row_2_ID_15]\nType=VirtualKey\nLabel=*\nCluster=Numpad\nKeyCode=106\n\n[Key_AltGr_Row_2_ID_16]\nType=VirtualKey\nLabel=-\nCluster=Numpad\nKeyCode=109\n\n;----------------------Row 3\n[Key_AltGr_Row_3_ID_0]\nType=VirtualKey\nWidth=150\nLabel=⭾\nKeyCode=9\n\n[Key_AltGr_Row_3_ID_1]\nType=String\nLabel=@\nString=@\n\n[Key_AltGr_Row_3_ID_2]\nType=Blank\n\n[Key_AltGr_Row_3_ID_3]\nType=String\nLabel=€\nString=€\n\n[Key_AltGr_Row_3_ID_4]\nType=Blank\nWidth=800\n\n[Key_AltGr_Row_3_ID_5]\nType=String\nLabel=~\nString=~\n\n[Key_AltGr_Row_3_ID_6]\nType=VirtualKeyIsoEnter\nWidth=150\nLabel=\nKeyCode=13\n\n[Key_AltGr_Row_3_ID_7]\nType=Blank\nWidth=25\nCluster=Navigation\n\n[Key_AltGr_Row_3_ID_8]\nType=VirtualKey\nLabel=Entf\nCluster=Navigation\nKeyCode=46\n\n[Key_AltGr_Row_3_ID_9]\nType=VirtualKey\nLabel=Ende\nCluster=Navigation\nKeyCode=35\n\n[Key_AltGr_Row_3_ID_10]\nType=VirtualKey\nLabel=Bild🠻\nCluster=Navigation\nKeyCode=34\n\n[Key_AltGr_Row_3_ID_11]\nType=Blank\nWidth=25\nCluster=Numpad\n\n[Key_AltGr_Row_3_ID_12]\nType=VirtualKey\nLabel=7\nCluster=Numpad\nKeyCode=103\n\n[Key_AltGr_Row_3_ID_13]\nType=VirtualKey\nLabel=8\nCluster=Numpad\nKeyCode=104\n\n[Key_AltGr_Row_3_ID_14]\nType=VirtualKey\nLabel=9\nCluster=Numpad\nKeyCode=105\n\n[Key_AltGr_Row_3_ID_15]\nType=VirtualKey\nHeight=200\nLabel=+\nCluster=Numpad\nKeyCode=107\n\n;----------------------Row 4\n[Key_AltGr_Row_4_ID_0]\nType=VirtualKey\nWidth=175\nLabel=🡇\nKeyCode=20\nNoRepeat=true\n\n[Key_AltGr_Row_4_ID_1]\nType=Blank\nWidth=1200\n\n[Key_AltGr_Row_4_ID_2]\nType=VirtualKeyIsoEnter\nWidth=125\nLabel=↵\nKeyCode=13\n\n[Key_AltGr_Row_4_ID_3]\nType=Blank\nWidth=325\nCluster=Navigation\n\n[Key_AltGr_Row_4_ID_4]\nType=Blank\nWidth=25\nCluster=Numpad\n\n[Key_AltGr_Row_4_ID_5]\nType=VirtualKey\nLabel=4\nCluster=Numpad\nKeyCode=100\n\n[Key_AltGr_Row_4_ID_6]\nType=VirtualKey\nLabel=5\nCluster=Numpad\nKeyCode=101\n\n[Key_AltGr_Row_4_ID_7]\nType=VirtualKey\nLabel=6\nCluster=Numpad\nKeyCode=102\n\n;----------------------Row 5\n[Key_AltGr_Row_5_ID_0]\nType=VirtualKeyToggle\nWidth=125\nLabel=🡅\nKeyCode=160\nNoRepeat=true\n\n[Key_AltGr_Row_5_ID_1]\nType=String\nLabel=|\nString=|\n\n[Key_AltGr_Row_5_ID_2]\nType=Blank\nWidth=600\n\n[Key_AltGr_Row_5_ID_3]\nType=String\nLabel=µ\nString=µ\n\n[Key_AltGr_Row_5_ID_4]\nType=Blank\nWidth=300\n\n[Key_AltGr_Row_5_ID_5]\nType=VirtualKeyToggle\nWidth=275\nLabel=🡅\nKeyCode=161\nNoRepeat=true\n\n[Key_AltGr_Row_5_ID_6]\nType=Blank\nWidth=125\nCluster=Navigation\n\n[Key_AltGr_Row_5_ID_7]\nType=VirtualKey\nLabel=🠅\nKeyCode=38\nCluster=Navigation\n\n[Key_AltGr_Row_5_ID_8]\nType=Blank\nWidth=100\nCluster=Navigation\n\n[Key_AltGr_Row_5_ID_9]\nType=Blank\nWidth=25\nCluster=Numpad\n\n[Key_AltGr_Row_5_ID_10]\nType=VirtualKey\nLabel=1\nCluster=Numpad\nKeyCode=97\n\n[Key_AltGr_Row_5_ID_11]\nType=VirtualKey\nLabel=2\nCluster=Numpad\nKeyCode=98\n\n[Key_AltGr_Row_5_ID_12]\nType=VirtualKey\nLabel=3\nCluster=Numpad\nKeyCode=99\n\n[Key_AltGr_Row_5_ID_13]\nType=VirtualKey\nHeight=200\nLabel=Enter\nCluster=Numpad\nKeyCode=13\n\n;----------------------Row 6\n[Key_AltGr_Row_6_ID_0]\nType=VirtualKeyToggle\nWidth=125\nLabel=Strg\nKeyCode=162\nNoRepeat=true\n\n[Key_AltGr_Row_6_ID_1]\nType=VirtualKeyToggle\nWidth=125\nLabel=Win\nKeyCode=91\nNoRepeat=true\n\n[Key_AltGr_Row_6_ID_2]\nType=VirtualKeyToggle\nWidth=125\nLabel=Alt\nKeyCode=164\nNoRepeat=true\n\n[Key_AltGr_Row_6_ID_3]\n;Space Bar\nType=VirtualKey\nWidth=625\nLabel=\nKeyCode=32\n\n[Key_AltGr_Row_6_ID_4]\nType=VirtualKeyToggle\nWidth=125\nLabel=AltGr\nKeyCode=165\nNoRepeat=true\n\n[Key_AltGr_Row_6_ID_5]\nType=VirtualKeyToggle\nWidth=125\nLabel=Win\nKeyCode=92\nNoRepeat=true\n\n[Key_AltGr_Row_6_ID_6]\nType=VirtualKey\nWidth=125\nLabel=Menü\nKeyCode=93\nNoRepeat=true\n\n[Key_AltGr_Row_6_ID_7]\nType=VirtualKeyToggle\nWidth=125\nLabel=Strg\nKeyCode=163\nNoRepeat=true\n\n[Key_AltGr_Row_6_ID_8]\nType=Blank\nWidth=25\nCluster=Navigation\n\n[Key_AltGr_Row_6_ID_9]\nType=VirtualKey\nLabel=🠄\nCluster=Navigation\nKeyCode=37\n\n[Key_AltGr_Row_6_ID_10]\nType=VirtualKey\nLabel=🠇\nCluster=Navigation\nKeyCode=40\n\n[Key_AltGr_Row_6_ID_11]\nType=VirtualKey\nLabel=🠆\nCluster=Navigation\nKeyCode=39\n\n[Key_AltGr_Row_6_ID_12]\nType=Blank\nWidth=25\nCluster=Numpad\n\n[Key_AltGr_Row_6_ID_13]\nType=VirtualKey\nWidth=200\nLabel=0\nCluster=Numpad\nKeyCode=96\n\n[Key_AltGr_Row_6_ID_14]\nType=VirtualKey\nLabel=,\nCluster=Numpad\nKeyCode=110"
  },
  {
    "path": "assets/keyboards/qwertz_hu.ini",
    "content": "[LayoutInfo]\r\nName=QWERTZ (Hungary)\r\nAuthor=Lenr\r\nHasAltGr=true\r\nHasClusterFunction=true\r\nHasClusterNavigation=true\r\nHasClusterNumpad=true\r\nHasClusterExtra=true\r\n\r\n[Key_Base_Row_0_ID_0]\r\nType=VirtualKey\r\nLabel=Esc\r\nCluster=Function\r\nKeyCode=27\r\nNoRepeat=true\r\n\r\n[Key_Base_Row_0_ID_1]\r\nType=Blank\r\nCluster=Function\r\n\r\n[Key_Base_Row_0_ID_2]\r\nType=VirtualKey\r\nLabel=F1\r\nCluster=Function\r\nKeyCode=112\r\n\r\n[Key_Base_Row_0_ID_3]\r\nType=VirtualKey\r\nLabel=F2\r\nCluster=Function\r\nKeyCode=113\r\n\r\n[Key_Base_Row_0_ID_4]\r\nType=VirtualKey\r\nLabel=F3\r\nCluster=Function\r\nKeyCode=114\r\n\r\n[Key_Base_Row_0_ID_5]\r\nType=VirtualKey\r\nLabel=F4\r\nCluster=Function\r\nKeyCode=115\r\n\r\n[Key_Base_Row_0_ID_6]\r\nType=Blank\r\nWidth=50\r\nCluster=Function\r\n\r\n[Key_Base_Row_0_ID_7]\r\nType=VirtualKey\r\nLabel=F5\r\nCluster=Function\r\nKeyCode=116\r\n\r\n[Key_Base_Row_0_ID_8]\r\nType=VirtualKey\r\nLabel=F6\r\nCluster=Function\r\nKeyCode=117\r\n\r\n[Key_Base_Row_0_ID_9]\r\nType=VirtualKey\r\nLabel=F7\r\nCluster=Function\r\nKeyCode=118\r\n\r\n[Key_Base_Row_0_ID_10]\r\nType=VirtualKey\r\nLabel=F8\r\nCluster=Function\r\nKeyCode=119\r\n\r\n[Key_Base_Row_0_ID_11]\r\nType=Blank\r\nWidth=50\r\nCluster=Function\r\n\r\n[Key_Base_Row_0_ID_12]\r\nType=VirtualKey\r\nLabel=F9\r\nCluster=Function\r\nKeyCode=120\r\n\r\n[Key_Base_Row_0_ID_13]\r\nType=VirtualKey\r\nLabel=F10\r\nCluster=Function\r\nKeyCode=121\r\n\r\n[Key_Base_Row_0_ID_14]\r\nType=VirtualKey\r\nLabel=F11\r\nCluster=Function\r\nKeyCode=122\r\n\r\n[Key_Base_Row_0_ID_15]\r\nType=VirtualKey\r\nLabel=F12\r\nCluster=Function\r\nKeyCode=123\r\n\r\n[Key_Base_Row_0_ID_16]\r\nType=Blank\r\nWidth=25\r\nCluster=Function\r\n\r\n[Key_Base_Row_0_ID_17]\r\nType=VirtualKey\r\nLabel=PrtSc\r\nCluster=Function\r\nKeyCode=44\r\nNoRepeat=true\r\n\r\n[Key_Base_Row_0_ID_18]\r\nType=VirtualKey\r\nLabel=ScrLk\r\nCluster=Function\r\nKeyCode=145\r\nNoRepeat=true\r\n\r\n[Key_Base_Row_0_ID_19]\r\nType=VirtualKey\r\nLabel=Pause\r\nCluster=Function\r\nKeyCode=19\r\nNoRepeat=true\r\n\r\n[Key_Base_Row_0_ID_20]\r\nType=Blank\r\nWidth=25\r\nCluster=Extra\r\n\r\n[Key_Base_Row_0_ID_21]\r\nType=VirtualKey\r\nLabel=⏵/⏸\r\nCluster=Extra\r\nKeyCode=179\r\nNoRepeat=true\r\n\r\n[Key_Base_Row_0_ID_22]\r\nType=VirtualKey\r\nLabel=◼\r\nCluster=Extra\r\nKeyCode=178\r\nNoRepeat=true\r\n\r\n[Key_Base_Row_0_ID_23]\r\nType=VirtualKey\r\nLabel=⏮\r\nCluster=Extra\r\nKeyCode=177\r\nNoRepeat=true\r\n\r\n[Key_Base_Row_0_ID_24]\r\nType=VirtualKey\r\nLabel=⏭\r\nCluster=Extra\r\nKeyCode=176\r\nNoRepeat=true\r\n\r\n[Key_Base_Row_1_ID_0]\r\nType=Blank\r\nHeight=25\r\nCluster=Function\r\n\r\n[Key_Base_Row_1_ID_1]\r\nType=Blank\r\nHeight=25\r\nCluster=Extra\r\n\r\n[Key_Base_Row_2_ID_0]\r\nType=String\r\nLabel=0\r\nString=0\r\n\r\n[Key_Base_Row_2_ID_1]\r\nType=VirtualKey\r\nLabel=1\r\nKeyCode=49\r\n\r\n[Key_Base_Row_2_ID_2]\r\nType=VirtualKey\r\nLabel=2\r\nKeyCode=50\r\n\r\n[Key_Base_Row_2_ID_3]\r\nType=VirtualKey\r\nLabel=3\r\nKeyCode=51\r\n\r\n[Key_Base_Row_2_ID_4]\r\nType=VirtualKey\r\nLabel=4\r\nKeyCode=52\r\n\r\n[Key_Base_Row_2_ID_5]\r\nType=VirtualKey\r\nLabel=5\r\nKeyCode=53\r\n\r\n[Key_Base_Row_2_ID_6]\r\nType=VirtualKey\r\nLabel=6\r\nKeyCode=54\r\n\r\n[Key_Base_Row_2_ID_7]\r\nType=VirtualKey\r\nLabel=7\r\nKeyCode=55\r\n\r\n[Key_Base_Row_2_ID_8]\r\nType=VirtualKey\r\nLabel=8\r\nKeyCode=56\r\n\r\n[Key_Base_Row_2_ID_9]\r\nType=VirtualKey\r\nLabel=9\r\nKeyCode=57\r\n\r\n[Key_Base_Row_2_ID_10]\r\nType=VirtualKey\r\nLabel=ö\r\nKeyCode=192\r\n\r\n[Key_Base_Row_2_ID_11]\r\nType=String\r\nLabel=ü\r\nString=ü\r\n\r\n[Key_Base_Row_2_ID_12]\r\nType=String\r\nLabel=ó\r\nString=ó\r\n\r\n[Key_Base_Row_2_ID_13]\r\nType=VirtualKey\r\nWidth=200\r\nLabel=⟵\r\nKeyCode=8\r\n\r\n[Key_Base_Row_2_ID_14]\r\nType=Blank\r\nWidth=25\r\nCluster=Navigation\r\n\r\n[Key_Base_Row_2_ID_15]\r\nType=VirtualKey\r\nLabel=Insert\r\nCluster=Navigation\r\nKeyCode=45\r\n\r\n[Key_Base_Row_2_ID_16]\r\nType=VirtualKey\r\nLabel=Home\r\nCluster=Navigation\r\nKeyCode=36\r\n\r\n[Key_Base_Row_2_ID_17]\r\nType=VirtualKey\r\nLabel=PgUp\r\nCluster=Navigation\r\nKeyCode=33\r\n\r\n[Key_Base_Row_2_ID_18]\r\nType=Blank\r\nWidth=25\r\nCluster=Numpad\r\n\r\n[Key_Base_Row_2_ID_19]\r\nType=VirtualKey\r\nLabel=Num\\nLock\r\nCluster=Numpad\r\nKeyCode=144\r\nNoRepeat=true\r\n\r\n[Key_Base_Row_2_ID_20]\r\nType=VirtualKey\r\nLabel=/\r\nCluster=Numpad\r\nKeyCode=111\r\n\r\n[Key_Base_Row_2_ID_21]\r\nType=VirtualKey\r\nLabel=*\r\nCluster=Numpad\r\nKeyCode=106\r\n\r\n[Key_Base_Row_2_ID_22]\r\nType=VirtualKey\r\nLabel=-\r\nCluster=Numpad\r\nKeyCode=109\r\n\r\n[Key_Base_Row_3_ID_0]\r\nType=VirtualKey\r\nWidth=150\r\nLabel=⭾\r\nKeyCode=9\r\n\r\n[Key_Base_Row_3_ID_1]\r\nType=VirtualKey\r\nLabel=q\r\nKeyCode=81\r\n\r\n[Key_Base_Row_3_ID_2]\r\nType=VirtualKey\r\nLabel=w\r\nKeyCode=87\r\n\r\n[Key_Base_Row_3_ID_3]\r\nType=VirtualKey\r\nLabel=e\r\nKeyCode=69\r\n\r\n[Key_Base_Row_3_ID_4]\r\nType=VirtualKey\r\nLabel=r\r\nKeyCode=82\r\n\r\n[Key_Base_Row_3_ID_5]\r\nType=VirtualKey\r\nLabel=t\r\nKeyCode=84\r\n\r\n[Key_Base_Row_3_ID_6]\r\nType=VirtualKey\r\nLabel=z\r\nKeyCode=90\r\n\r\n[Key_Base_Row_3_ID_7]\r\nType=VirtualKey\r\nLabel=u\r\nKeyCode=85\r\n\r\n[Key_Base_Row_3_ID_8]\r\nType=VirtualKey\r\nLabel=i\r\nKeyCode=73\r\n\r\n[Key_Base_Row_3_ID_9]\r\nType=VirtualKey\r\nLabel=o\r\nKeyCode=79\r\n\r\n[Key_Base_Row_3_ID_10]\r\nType=VirtualKey\r\nLabel=p\r\nKeyCode=80\r\n\r\n[Key_Base_Row_3_ID_11]\r\nType=String\r\nLabel=ő\r\nString=ő\r\n\r\n[Key_Base_Row_3_ID_12]\r\nType=String\r\nLabel=ú\r\nString=ú\r\n\r\n[Key_Base_Row_3_ID_13]\r\nType=VirtualKeyIsoEnter\r\nWidth=150\r\nLabel=\r\nKeyCode=13\r\n\r\n[Key_Base_Row_3_ID_14]\r\nType=Blank\r\nWidth=25\r\nCluster=Navigation\r\n\r\n[Key_Base_Row_3_ID_15]\r\nType=VirtualKey\r\nLabel=Delete\r\nCluster=Navigation\r\nKeyCode=46\r\n\r\n[Key_Base_Row_3_ID_16]\r\nType=VirtualKey\r\nLabel=End\r\nCluster=Navigation\r\nKeyCode=35\r\n\r\n[Key_Base_Row_3_ID_17]\r\nType=VirtualKey\r\nLabel=PgDn\r\nCluster=Navigation\r\nKeyCode=34\r\n\r\n[Key_Base_Row_3_ID_18]\r\nType=Blank\r\nWidth=25\r\nCluster=Numpad\r\n\r\n[Key_Base_Row_3_ID_19]\r\nType=VirtualKey\r\nLabel=7\\nHome\r\nCluster=Numpad\r\nKeyCode=103\r\n\r\n[Key_Base_Row_3_ID_20]\r\nType=VirtualKey\r\nLabel=8\\n🠅\r\nCluster=Numpad\r\nKeyCode=104\r\n\r\n[Key_Base_Row_3_ID_21]\r\nType=VirtualKey\r\nLabel=9\\nPgUp\r\nCluster=Numpad\r\nKeyCode=105\r\n\r\n[Key_Base_Row_3_ID_22]\r\nType=VirtualKey\r\nHeight=200\r\nLabel=+\r\nCluster=Numpad\r\nKeyCode=107\r\n\r\n[Key_Base_Row_4_ID_0]\r\nType=VirtualKey\r\nWidth=175\r\nLabel=🡇\r\nKeyCode=20\r\nNoRepeat=true\r\n\r\n[Key_Base_Row_4_ID_1]\r\nType=VirtualKey\r\nLabel=a\r\nKeyCode=65\r\n\r\n[Key_Base_Row_4_ID_2]\r\nType=VirtualKey\r\nLabel=s\r\nKeyCode=83\r\n\r\n[Key_Base_Row_4_ID_3]\r\nType=VirtualKey\r\nLabel=d\r\nKeyCode=68\r\n\r\n[Key_Base_Row_4_ID_4]\r\nType=VirtualKey\r\nLabel=f\r\nKeyCode=70\r\n\r\n[Key_Base_Row_4_ID_5]\r\nType=VirtualKey\r\nLabel=g\r\nKeyCode=71\r\n\r\n[Key_Base_Row_4_ID_6]\r\nType=VirtualKey\r\nLabel=h\r\nKeyCode=72\r\n\r\n[Key_Base_Row_4_ID_7]\r\nType=VirtualKey\r\nLabel=j\r\nKeyCode=74\r\n\r\n[Key_Base_Row_4_ID_8]\r\nType=VirtualKey\r\nLabel=k\r\nKeyCode=75\r\n\r\n[Key_Base_Row_4_ID_9]\r\nType=VirtualKey\r\nLabel=l\r\nKeyCode=76\r\n\r\n[Key_Base_Row_4_ID_10]\r\nType=String\r\nLabel=é\r\nString=é\r\n\r\n[Key_Base_Row_4_ID_11]\r\nType=String\r\nLabel=á\r\nString=á\r\n\r\n[Key_Base_Row_4_ID_12]\r\nType=String\r\nLabel=ű\r\nString=ű\r\n\r\n[Key_Base_Row_4_ID_13]\r\nType=VirtualKeyIsoEnter\r\nWidth=125\r\nLabel=↵\r\nKeyCode=13\r\n\r\n[Key_Base_Row_4_ID_14]\r\nType=Blank\r\nWidth=325\r\nCluster=Navigation\r\n\r\n[Key_Base_Row_4_ID_15]\r\nType=Blank\r\nWidth=25\r\nCluster=Numpad\r\n\r\n[Key_Base_Row_4_ID_16]\r\nType=VirtualKey\r\nLabel=4\\n🠄\r\nCluster=Numpad\r\nKeyCode=100\r\n\r\n[Key_Base_Row_4_ID_17]\r\nType=VirtualKey\r\nLabel=5\\n \r\nCluster=Numpad\r\nKeyCode=101\r\n\r\n[Key_Base_Row_4_ID_18]\r\nType=VirtualKey\r\nLabel=6\\n🠆\r\nCluster=Numpad\r\nKeyCode=102\r\n\r\n[Key_Base_Row_5_ID_0]\r\nType=VirtualKeyToggle\r\nWidth=125\r\nLabel=🡅\r\nKeyCode=160\r\nNoRepeat=true\r\n\r\n[Key_Base_Row_5_ID_1]\r\nType=String\r\nLabel=í\r\nString=í\r\n\r\n[Key_Base_Row_5_ID_2]\r\nType=VirtualKey\r\nLabel=y\r\nKeyCode=89\r\n\r\n[Key_Base_Row_5_ID_3]\r\nType=VirtualKey\r\nLabel=x\r\nKeyCode=88\r\n\r\n[Key_Base_Row_5_ID_4]\r\nType=VirtualKey\r\nLabel=c\r\nKeyCode=67\r\n\r\n[Key_Base_Row_5_ID_5]\r\nType=VirtualKey\r\nLabel=v\r\nKeyCode=86\r\n\r\n[Key_Base_Row_5_ID_6]\r\nType=VirtualKey\r\nLabel=b\r\nKeyCode=66\r\n\r\n[Key_Base_Row_5_ID_7]\r\nType=VirtualKey\r\nLabel=n\r\nKeyCode=78\r\n\r\n[Key_Base_Row_5_ID_8]\r\nType=VirtualKey\r\nLabel=m\r\nKeyCode=77\r\n\r\n[Key_Base_Row_5_ID_9]\r\nType=String\r\nLabel=,\r\nString=,\r\n\r\n[Key_Base_Row_5_ID_10]\r\nType=String\r\nLabel=.\r\nString=.\r\n\r\n[Key_Base_Row_5_ID_11]\r\nType=String\r\nLabel=-\r\nString=-\r\n\r\n[Key_Base_Row_5_ID_12]\r\nType=VirtualKeyToggle\r\nWidth=275\r\nLabel=🡅\r\nKeyCode=161\r\nNoRepeat=true\r\n\r\n[Key_Base_Row_5_ID_13]\r\nType=Blank\r\nWidth=125\r\nCluster=Navigation\r\n\r\n[Key_Base_Row_5_ID_14]\r\nType=VirtualKey\r\nLabel=🠅\r\nCluster=Navigation\r\nKeyCode=38\r\n\r\n[Key_Base_Row_5_ID_15]\r\nType=Blank\r\nCluster=Navigation\r\n\r\n[Key_Base_Row_5_ID_16]\r\nType=Blank\r\nWidth=25\r\nCluster=Numpad\r\n\r\n[Key_Base_Row_5_ID_17]\r\nType=VirtualKey\r\nLabel=1\\nEnd\r\nCluster=Numpad\r\nKeyCode=97\r\n\r\n[Key_Base_Row_5_ID_18]\r\nType=VirtualKey\r\nLabel=2\\n🠇\r\nCluster=Numpad\r\nKeyCode=98\r\n\r\n[Key_Base_Row_5_ID_19]\r\nType=VirtualKey\r\nLabel=3\\nPgDn\r\nCluster=Numpad\r\nKeyCode=99\r\n\r\n[Key_Base_Row_5_ID_20]\r\nType=VirtualKey\r\nHeight=200\r\nLabel=Enter\r\nCluster=Numpad\r\nKeyCode=13\r\n\r\n[Key_Base_Row_6_ID_0]\r\nType=VirtualKeyToggle\r\nWidth=125\r\nLabel=Ctrl\r\nKeyCode=162\r\nNoRepeat=true\r\n\r\n[Key_Base_Row_6_ID_1]\r\nType=VirtualKeyToggle\r\nWidth=125\r\nLabel=Win\r\nKeyCode=91\r\nNoRepeat=true\r\n\r\n[Key_Base_Row_6_ID_2]\r\nType=VirtualKeyToggle\r\nWidth=125\r\nLabel=Alt\r\nKeyCode=164\r\nNoRepeat=true\r\n\r\n[Key_Base_Row_6_ID_3]\r\nType=VirtualKey\r\nWidth=625\r\nLabel=\r\nKeyCode=32\r\n\r\n[Key_Base_Row_6_ID_4]\r\nType=VirtualKeyToggle\r\nWidth=125\r\nLabel=AltGr\r\nKeyCode=165\r\nNoRepeat=true\r\n\r\n[Key_Base_Row_6_ID_5]\r\nType=VirtualKeyToggle\r\nWidth=125\r\nLabel=Win\r\nKeyCode=92\r\nNoRepeat=true\r\n\r\n[Key_Base_Row_6_ID_6]\r\nType=VirtualKey\r\nWidth=125\r\nLabel=Menü\r\nKeyCode=93\r\nNoRepeat=true\r\n\r\n[Key_Base_Row_6_ID_7]\r\nType=VirtualKeyToggle\r\nWidth=125\r\nLabel=Ctrl\r\nKeyCode=163\r\nNoRepeat=true\r\n\r\n[Key_Base_Row_6_ID_8]\r\nType=Blank\r\nWidth=25\r\nCluster=Navigation\r\n\r\n[Key_Base_Row_6_ID_9]\r\nType=VirtualKey\r\nLabel=🠄\r\nCluster=Navigation\r\nKeyCode=37\r\n\r\n[Key_Base_Row_6_ID_10]\r\nType=VirtualKey\r\nLabel=🠇\r\nCluster=Navigation\r\nKeyCode=40\r\n\r\n[Key_Base_Row_6_ID_11]\r\nType=VirtualKey\r\nLabel=🠆\r\nCluster=Navigation\r\nKeyCode=39\r\n\r\n[Key_Base_Row_6_ID_12]\r\nType=Blank\r\nWidth=25\r\nCluster=Numpad\r\n\r\n[Key_Base_Row_6_ID_13]\r\nType=VirtualKey\r\nWidth=200\r\nLabel=0\\nInsert\r\nCluster=Numpad\r\nKeyCode=96\r\n\r\n[Key_Base_Row_6_ID_14]\r\nType=VirtualKey\r\nLabel=,\\nDelete\r\nCluster=Numpad\r\nKeyCode=110\r\n\r\n[Key_Shift_Row_0_ID_0]\r\nType=VirtualKey\r\nLabel=Esc\r\nCluster=Function\r\nKeyCode=27\r\nNoRepeat=true\r\n\r\n[Key_Shift_Row_0_ID_1]\r\nType=Blank\r\nCluster=Function\r\n\r\n[Key_Shift_Row_0_ID_2]\r\nType=VirtualKey\r\nLabel=F1\r\nCluster=Function\r\nKeyCode=112\r\n\r\n[Key_Shift_Row_0_ID_3]\r\nType=VirtualKey\r\nLabel=F2\r\nCluster=Function\r\nKeyCode=113\r\n\r\n[Key_Shift_Row_0_ID_4]\r\nType=VirtualKey\r\nLabel=F3\r\nCluster=Function\r\nKeyCode=114\r\n\r\n[Key_Shift_Row_0_ID_5]\r\nType=VirtualKey\r\nLabel=F4\r\nCluster=Function\r\nKeyCode=115\r\n\r\n[Key_Shift_Row_0_ID_6]\r\nType=Blank\r\nWidth=50\r\nCluster=Function\r\n\r\n[Key_Shift_Row_0_ID_7]\r\nType=VirtualKey\r\nLabel=F5\r\nCluster=Function\r\nKeyCode=116\r\n\r\n[Key_Shift_Row_0_ID_8]\r\nType=VirtualKey\r\nLabel=F6\r\nCluster=Function\r\nKeyCode=117\r\n\r\n[Key_Shift_Row_0_ID_9]\r\nType=VirtualKey\r\nLabel=F7\r\nCluster=Function\r\nKeyCode=118\r\n\r\n[Key_Shift_Row_0_ID_10]\r\nType=VirtualKey\r\nLabel=F8\r\nCluster=Function\r\nKeyCode=119\r\n\r\n[Key_Shift_Row_0_ID_11]\r\nType=Blank\r\nWidth=50\r\nCluster=Function\r\n\r\n[Key_Shift_Row_0_ID_12]\r\nType=VirtualKey\r\nLabel=F9\r\nCluster=Function\r\nKeyCode=120\r\n\r\n[Key_Shift_Row_0_ID_13]\r\nType=VirtualKey\r\nLabel=F10\r\nCluster=Function\r\nKeyCode=121\r\n\r\n[Key_Shift_Row_0_ID_14]\r\nType=VirtualKey\r\nLabel=F11\r\nCluster=Function\r\nKeyCode=122\r\n\r\n[Key_Shift_Row_0_ID_15]\r\nType=VirtualKey\r\nLabel=F12\r\nCluster=Function\r\nKeyCode=123\r\n\r\n[Key_Shift_Row_0_ID_16]\r\nType=Blank\r\nWidth=25\r\nCluster=Function\r\n\r\n[Key_Shift_Row_0_ID_17]\r\nType=VirtualKey\r\nLabel=PrtSc\r\nCluster=Function\r\nKeyCode=44\r\nNoRepeat=true\r\n\r\n[Key_Shift_Row_0_ID_18]\r\nType=VirtualKey\r\nLabel=ScrLk\r\nCluster=Function\r\nKeyCode=145\r\nNoRepeat=true\r\n\r\n[Key_Shift_Row_0_ID_19]\r\nType=VirtualKey\r\nLabel=Pause\r\nCluster=Function\r\nKeyCode=19\r\nNoRepeat=true\r\n\r\n[Key_Shift_Row_0_ID_20]\r\nType=Blank\r\nWidth=25\r\nCluster=Extra\r\n\r\n[Key_Shift_Row_0_ID_21]\r\nType=VirtualKey\r\nLabel=🡰\r\nCluster=Extra\r\nKeyCode=166\r\nNoRepeat=true\r\n\r\n[Key_Shift_Row_0_ID_22]\r\nType=VirtualKey\r\nLabel=🡲\r\nCluster=Extra\r\nKeyCode=167\r\nNoRepeat=true\r\n\r\n[Key_Shift_Row_0_ID_23]\r\nType=VirtualKey\r\nLabel=🔇\r\nCluster=Extra\r\nKeyCode=173\r\nNoRepeat=true\r\n\r\n[Key_Shift_Row_1_ID_0]\r\nType=Blank\r\nHeight=25\r\nCluster=Function\r\n\r\n[Key_Shift_Row_1_ID_1]\r\nType=Blank\r\nHeight=25\r\nCluster=Extra\r\n\r\n[Key_Shift_Row_2_ID_0]\r\nType=String\r\nLabel=§\r\nString=§\r\n\r\n[Key_Shift_Row_2_ID_1]\r\nType=String\r\nLabel='\r\nString='\r\n\r\n[Key_Shift_Row_2_ID_2]\r\nType=String\r\nLabel=\"\r\nString=\"\r\n\r\n[Key_Shift_Row_2_ID_3]\r\nType=String\r\nLabel=+\r\nString=+\r\n\r\n[Key_Shift_Row_2_ID_4]\r\nType=String\r\nLabel=!\r\nString=!\r\n\r\n[Key_Shift_Row_2_ID_5]\r\nType=String\r\nLabel=%\r\nString=%\r\n\r\n[Key_Shift_Row_2_ID_6]\r\nType=String\r\nLabel=/\r\nString=/\r\n\r\n[Key_Shift_Row_2_ID_7]\r\nType=String\r\nLabel==\r\nString==\r\n\r\n[Key_Shift_Row_2_ID_8]\r\nType=String\r\nLabel=(\r\nString=(\r\n\r\n[Key_Shift_Row_2_ID_9]\r\nType=String\r\nLabel=)\r\nString=)\r\n\r\n[Key_Shift_Row_2_ID_10]\r\nType=String\r\nLabel=Ö\r\nString=Ö\r\n\r\n[Key_Shift_Row_2_ID_11]\r\nType=String\r\nLabel=Ü\r\nString=Ü\r\n\r\n[Key_Shift_Row_2_ID_12]\r\nType=String\r\nLabel=Ó\r\nString=Ó\r\n\r\n[Key_Shift_Row_2_ID_13]\r\nType=VirtualKey\r\nWidth=200\r\nLabel=⟵\r\nKeyCode=8\r\n\r\n[Key_Shift_Row_2_ID_14]\r\nType=Blank\r\nWidth=25\r\nCluster=Navigation\r\n\r\n[Key_Shift_Row_2_ID_15]\r\nType=VirtualKey\r\nLabel=Insert\r\nCluster=Navigation\r\nKeyCode=45\r\n\r\n[Key_Shift_Row_2_ID_16]\r\nType=VirtualKey\r\nLabel=Home\r\nCluster=Navigation\r\nKeyCode=36\r\n\r\n[Key_Shift_Row_2_ID_17]\r\nType=VirtualKey\r\nLabel=PgUp\r\nCluster=Navigation\r\nKeyCode=33\r\n\r\n[Key_Shift_Row_2_ID_18]\r\nType=Blank\r\nWidth=25\r\nCluster=Numpad\r\n\r\n[Key_Shift_Row_2_ID_19]\r\nType=VirtualKey\r\nLabel=Num\\nLock\r\nCluster=Numpad\r\nKeyCode=144\r\nNoRepeat=true\r\n\r\n[Key_Shift_Row_2_ID_20]\r\nType=VirtualKey\r\nLabel=/\r\nCluster=Numpad\r\nKeyCode=111\r\n\r\n[Key_Shift_Row_2_ID_21]\r\nType=VirtualKey\r\nLabel=*\r\nCluster=Numpad\r\nKeyCode=106\r\n\r\n[Key_Shift_Row_2_ID_22]\r\nType=VirtualKey\r\nLabel=-\r\nCluster=Numpad\r\nKeyCode=109\r\n\r\n[Key_Shift_Row_3_ID_0]\r\nType=VirtualKey\r\nWidth=150\r\nLabel=⭾\r\nKeyCode=9\r\n\r\n[Key_Shift_Row_3_ID_1]\r\nType=VirtualKey\r\nLabel=Q\r\nKeyCode=81\r\n\r\n[Key_Shift_Row_3_ID_2]\r\nType=VirtualKey\r\nLabel=W\r\nKeyCode=87\r\n\r\n[Key_Shift_Row_3_ID_3]\r\nType=VirtualKey\r\nLabel=E\r\nKeyCode=69\r\n\r\n[Key_Shift_Row_3_ID_4]\r\nType=VirtualKey\r\nLabel=R\r\nKeyCode=82\r\n\r\n[Key_Shift_Row_3_ID_5]\r\nType=VirtualKey\r\nLabel=T\r\nKeyCode=84\r\n\r\n[Key_Shift_Row_3_ID_6]\r\nType=VirtualKey\r\nLabel=Z\r\nKeyCode=90\r\n\r\n[Key_Shift_Row_3_ID_7]\r\nType=VirtualKey\r\nLabel=U\r\nKeyCode=85\r\n\r\n[Key_Shift_Row_3_ID_8]\r\nType=VirtualKey\r\nLabel=I\r\nKeyCode=73\r\n\r\n[Key_Shift_Row_3_ID_9]\r\nType=VirtualKey\r\nLabel=O\r\nKeyCode=79\r\n\r\n[Key_Shift_Row_3_ID_10]\r\nType=VirtualKey\r\nLabel=P\r\nKeyCode=80\r\n\r\n[Key_Shift_Row_3_ID_11]\r\nType=String\r\nLabel=Ő\r\nString=Ő\r\n\r\n[Key_Shift_Row_3_ID_12]\r\nType=String\r\nLabel=Ú\r\nString=Ú\r\n\r\n[Key_Shift_Row_3_ID_13]\r\nType=VirtualKeyIsoEnter\r\nWidth=150\r\nLabel=\r\nKeyCode=13\r\n\r\n[Key_Shift_Row_3_ID_14]\r\nType=Blank\r\nWidth=25\r\nCluster=Navigation\r\n\r\n[Key_Shift_Row_3_ID_15]\r\nType=VirtualKey\r\nLabel=Delete\r\nCluster=Navigation\r\nKeyCode=46\r\n\r\n[Key_Shift_Row_3_ID_16]\r\nType=VirtualKey\r\nLabel=End\r\nCluster=Navigation\r\nKeyCode=35\r\n\r\n[Key_Shift_Row_3_ID_17]\r\nType=VirtualKey\r\nLabel=PgDn\r\nCluster=Navigation\r\nKeyCode=34\r\n\r\n[Key_Shift_Row_3_ID_18]\r\nType=Blank\r\nWidth=25\r\nCluster=Numpad\r\n\r\n[Key_Shift_Row_3_ID_19]\r\nType=VirtualKey\r\nLabel=Home\r\nCluster=Numpad\r\nKeyCode=103\r\n\r\n[Key_Shift_Row_3_ID_20]\r\nType=VirtualKey\r\nLabel=🠅\r\nCluster=Numpad\r\nKeyCode=104\r\n\r\n[Key_Shift_Row_3_ID_21]\r\nType=VirtualKey\r\nLabel=PgUp\r\nCluster=Numpad\r\nKeyCode=105\r\n\r\n[Key_Shift_Row_3_ID_22]\r\nType=VirtualKey\r\nHeight=200\r\nLabel=+\r\nCluster=Numpad\r\nKeyCode=107\r\n\r\n[Key_Shift_Row_4_ID_0]\r\nType=VirtualKey\r\nWidth=175\r\nLabel=🡇\r\nKeyCode=20\r\nNoRepeat=true\r\n\r\n[Key_Shift_Row_4_ID_1]\r\nType=VirtualKey\r\nLabel=A\r\nKeyCode=65\r\n\r\n[Key_Shift_Row_4_ID_2]\r\nType=VirtualKey\r\nLabel=S\r\nKeyCode=83\r\n\r\n[Key_Shift_Row_4_ID_3]\r\nType=VirtualKey\r\nLabel=D\r\nKeyCode=68\r\n\r\n[Key_Shift_Row_4_ID_4]\r\nType=VirtualKey\r\nLabel=F\r\nKeyCode=70\r\n\r\n[Key_Shift_Row_4_ID_5]\r\nType=VirtualKey\r\nLabel=G\r\nKeyCode=71\r\n\r\n[Key_Shift_Row_4_ID_6]\r\nType=VirtualKey\r\nLabel=H\r\nKeyCode=72\r\n\r\n[Key_Shift_Row_4_ID_7]\r\nType=VirtualKey\r\nLabel=J\r\nKeyCode=74\r\n\r\n[Key_Shift_Row_4_ID_8]\r\nType=VirtualKey\r\nLabel=K\r\nKeyCode=75\r\n\r\n[Key_Shift_Row_4_ID_9]\r\nType=VirtualKey\r\nLabel=L\r\nKeyCode=76\r\n\r\n[Key_Shift_Row_4_ID_10]\r\nType=String\r\nLabel=É\r\nString=É\r\n\r\n[Key_Shift_Row_4_ID_11]\r\nType=String\r\nLabel=Á\r\nString=Á\r\n\r\n[Key_Shift_Row_4_ID_12]\r\nType=String\r\nLabel=Ű\r\nString=Ű\r\n\r\n[Key_Shift_Row_4_ID_13]\r\nType=VirtualKeyIsoEnter\r\nWidth=125\r\nLabel=↵\r\nKeyCode=13\r\n\r\n[Key_Shift_Row_4_ID_14]\r\nType=Blank\r\nWidth=325\r\nCluster=Navigation\r\n\r\n[Key_Shift_Row_4_ID_15]\r\nType=Blank\r\nWidth=25\r\nCluster=Numpad\r\n\r\n[Key_Shift_Row_4_ID_16]\r\nType=VirtualKey\r\nLabel=🠄\r\nCluster=Numpad\r\nKeyCode=100\r\n\r\n[Key_Shift_Row_4_ID_17]\r\nType=VirtualKey\r\nLabel=5\r\nCluster=Numpad\r\nKeyCode=101\r\n\r\n[Key_Shift_Row_4_ID_18]\r\nType=VirtualKey\r\nLabel=🠆\r\nCluster=Numpad\r\nKeyCode=102\r\n\r\n[Key_Shift_Row_5_ID_0]\r\nType=VirtualKeyToggle\r\nWidth=125\r\nLabel=🡅\r\nKeyCode=160\r\nNoRepeat=true\r\n\r\n[Key_Shift_Row_5_ID_1]\r\nType=String\r\nLabel=Í\r\nString=Í\r\n\r\n[Key_Shift_Row_5_ID_2]\r\nType=VirtualKey\r\nLabel=Y\r\nKeyCode=89\r\n\r\n[Key_Shift_Row_5_ID_3]\r\nType=VirtualKey\r\nLabel=X\r\nKeyCode=88\r\n\r\n[Key_Shift_Row_5_ID_4]\r\nType=VirtualKey\r\nLabel=C\r\nKeyCode=67\r\n\r\n[Key_Shift_Row_5_ID_5]\r\nType=VirtualKey\r\nLabel=V\r\nKeyCode=86\r\n\r\n[Key_Shift_Row_5_ID_6]\r\nType=VirtualKey\r\nLabel=B\r\nKeyCode=66\r\n\r\n[Key_Shift_Row_5_ID_7]\r\nType=VirtualKey\r\nLabel=N\r\nKeyCode=78\r\n\r\n[Key_Shift_Row_5_ID_8]\r\nType=VirtualKey\r\nLabel=M\r\nKeyCode=77\r\n\r\n[Key_Shift_Row_5_ID_9]\r\nType=String\r\nLabel=?\r\nString=?\r\n\r\n[Key_Shift_Row_5_ID_10]\r\nType=String\r\nLabel=:\r\nString=:\r\n\r\n[Key_Shift_Row_5_ID_11]\r\nType=String\r\nLabel=_\r\nString=_\r\n\r\n[Key_Shift_Row_5_ID_12]\r\nType=VirtualKeyToggle\r\nWidth=275\r\nLabel=🡅\r\nKeyCode=161\r\nNoRepeat=true\r\n\r\n[Key_Shift_Row_5_ID_13]\r\nType=Blank\r\nWidth=125\r\nCluster=Navigation\r\n\r\n[Key_Shift_Row_5_ID_14]\r\nType=VirtualKey\r\nLabel=🠅\r\nCluster=Navigation\r\nKeyCode=38\r\n\r\n[Key_Shift_Row_5_ID_15]\r\nType=Blank\r\nCluster=Navigation\r\n\r\n[Key_Shift_Row_5_ID_16]\r\nType=Blank\r\nWidth=25\r\nCluster=Numpad\r\n\r\n[Key_Shift_Row_5_ID_17]\r\nType=VirtualKey\r\nLabel=End\r\nCluster=Numpad\r\nKeyCode=97\r\n\r\n[Key_Shift_Row_5_ID_18]\r\nType=VirtualKey\r\nLabel=🠇\r\nCluster=Numpad\r\nKeyCode=98\r\n\r\n[Key_Shift_Row_5_ID_19]\r\nType=VirtualKey\r\nLabel=PgDn\r\nCluster=Numpad\r\nKeyCode=99\r\n\r\n[Key_Shift_Row_5_ID_20]\r\nType=VirtualKey\r\nHeight=200\r\nLabel=Enter\r\nCluster=Numpad\r\nKeyCode=13\r\n\r\n[Key_Shift_Row_6_ID_0]\r\nType=VirtualKeyToggle\r\nWidth=125\r\nLabel=Ctrl\r\nKeyCode=162\r\nNoRepeat=true\r\n\r\n[Key_Shift_Row_6_ID_1]\r\nType=VirtualKeyToggle\r\nWidth=125\r\nLabel=Win\r\nKeyCode=91\r\nNoRepeat=true\r\n\r\n[Key_Shift_Row_6_ID_2]\r\nType=VirtualKeyToggle\r\nWidth=125\r\nLabel=Alt\r\nKeyCode=164\r\nNoRepeat=true\r\n\r\n[Key_Shift_Row_6_ID_3]\r\nType=VirtualKey\r\nWidth=625\r\nLabel=\r\nKeyCode=32\r\n\r\n[Key_Shift_Row_6_ID_4]\r\nType=VirtualKeyToggle\r\nWidth=125\r\nLabel=AltGr\r\nKeyCode=165\r\nNoRepeat=true\r\n\r\n[Key_Shift_Row_6_ID_5]\r\nType=VirtualKeyToggle\r\nWidth=125\r\nLabel=Win\r\nKeyCode=92\r\nNoRepeat=true\r\n\r\n[Key_Shift_Row_6_ID_6]\r\nType=VirtualKey\r\nWidth=125\r\nLabel=Menü\r\nKeyCode=93\r\nNoRepeat=true\r\n\r\n[Key_Shift_Row_6_ID_7]\r\nType=VirtualKeyToggle\r\nWidth=125\r\nLabel=Ctrl\r\nKeyCode=163\r\nNoRepeat=true\r\n\r\n[Key_Shift_Row_6_ID_8]\r\nType=Blank\r\nWidth=25\r\nCluster=Navigation\r\n\r\n[Key_Shift_Row_6_ID_9]\r\nType=VirtualKey\r\nLabel=🠄\r\nCluster=Navigation\r\nKeyCode=37\r\n\r\n[Key_Shift_Row_6_ID_10]\r\nType=VirtualKey\r\nLabel=🠇\r\nCluster=Navigation\r\nKeyCode=40\r\n\r\n[Key_Shift_Row_6_ID_11]\r\nType=VirtualKey\r\nLabel=🠆\r\nCluster=Navigation\r\nKeyCode=39\r\n\r\n[Key_Shift_Row_6_ID_12]\r\nType=Blank\r\nWidth=25\r\nCluster=Numpad\r\n\r\n[Key_Shift_Row_6_ID_13]\r\nType=VirtualKey\r\nWidth=200\r\nLabel=Insert\r\nCluster=Numpad\r\nKeyCode=96\r\n\r\n[Key_Shift_Row_6_ID_14]\r\nType=VirtualKey\r\nLabel=Delete\r\nCluster=Numpad\r\nKeyCode=110\r\n\r\n[Key_AltGr_Row_0_ID_0]\r\nType=VirtualKey\r\nLabel=Esc\r\nCluster=Function\r\nKeyCode=27\r\nNoRepeat=true\r\n\r\n[Key_AltGr_Row_0_ID_1]\r\nType=Blank\r\nCluster=Function\r\n\r\n[Key_AltGr_Row_0_ID_2]\r\nType=VirtualKey\r\nLabel=F1\r\nCluster=Function\r\nKeyCode=112\r\n\r\n[Key_AltGr_Row_0_ID_3]\r\nType=VirtualKey\r\nLabel=F2\r\nCluster=Function\r\nKeyCode=113\r\n\r\n[Key_AltGr_Row_0_ID_4]\r\nType=VirtualKey\r\nLabel=F3\r\nCluster=Function\r\nKeyCode=114\r\n\r\n[Key_AltGr_Row_0_ID_5]\r\nType=VirtualKey\r\nLabel=F4\r\nCluster=Function\r\nKeyCode=115\r\n\r\n[Key_AltGr_Row_0_ID_6]\r\nType=Blank\r\nWidth=50\r\nCluster=Function\r\n\r\n[Key_AltGr_Row_0_ID_7]\r\nType=VirtualKey\r\nLabel=F5\r\nCluster=Function\r\nKeyCode=116\r\n\r\n[Key_AltGr_Row_0_ID_8]\r\nType=VirtualKey\r\nLabel=F6\r\nCluster=Function\r\nKeyCode=117\r\n\r\n[Key_AltGr_Row_0_ID_9]\r\nType=VirtualKey\r\nLabel=F7\r\nCluster=Function\r\nKeyCode=118\r\n\r\n[Key_AltGr_Row_0_ID_10]\r\nType=VirtualKey\r\nLabel=F8\r\nCluster=Function\r\nKeyCode=119\r\n\r\n[Key_AltGr_Row_0_ID_11]\r\nType=Blank\r\nWidth=50\r\nCluster=Function\r\n\r\n[Key_AltGr_Row_0_ID_12]\r\nType=VirtualKey\r\nLabel=F9\r\nCluster=Function\r\nKeyCode=120\r\n\r\n[Key_AltGr_Row_0_ID_13]\r\nType=VirtualKey\r\nLabel=F10\r\nCluster=Function\r\nKeyCode=121\r\n\r\n[Key_AltGr_Row_0_ID_14]\r\nType=VirtualKey\r\nLabel=F11\r\nCluster=Function\r\nKeyCode=122\r\n\r\n[Key_AltGr_Row_0_ID_15]\r\nType=VirtualKey\r\nLabel=F12\r\nCluster=Function\r\nKeyCode=123\r\n\r\n[Key_AltGr_Row_0_ID_16]\r\nType=Blank\r\nWidth=25\r\nCluster=Function\r\n\r\n[Key_AltGr_Row_0_ID_17]\r\nType=VirtualKey\r\nLabel=PrtSc\r\nCluster=Function\r\nKeyCode=44\r\nNoRepeat=true\r\n\r\n[Key_AltGr_Row_0_ID_18]\r\nType=VirtualKey\r\nLabel=ScrLk\r\nCluster=Function\r\nKeyCode=145\r\nNoRepeat=true\r\n\r\n[Key_AltGr_Row_0_ID_19]\r\nType=VirtualKey\r\nLabel=Pause\r\nCluster=Function\r\nKeyCode=19\r\nNoRepeat=true\r\n\r\n[Key_AltGr_Row_0_ID_20]\r\nType=Blank\r\nWidth=25\r\nCluster=Extra\r\n\r\n[Key_AltGr_Row_0_ID_21]\r\nType=VirtualKey\r\nLabel=⏵/⏸\r\nCluster=Extra\r\nKeyCode=179\r\nNoRepeat=true\r\n\r\n[Key_AltGr_Row_0_ID_22]\r\nType=VirtualKey\r\nLabel=◼\r\nCluster=Extra\r\nKeyCode=178\r\nNoRepeat=true\r\n\r\n[Key_AltGr_Row_0_ID_23]\r\nType=VirtualKey\r\nLabel=⏮\r\nCluster=Extra\r\nKeyCode=177\r\nNoRepeat=true\r\n\r\n[Key_AltGr_Row_0_ID_24]\r\nType=VirtualKey\r\nLabel=⏭\r\nCluster=Extra\r\nKeyCode=176\r\nNoRepeat=true\r\n\r\n[Key_AltGr_Row_1_ID_0]\r\nType=Blank\r\nHeight=25\r\nCluster=Function\r\n\r\n[Key_AltGr_Row_1_ID_1]\r\nType=Blank\r\nHeight=25\r\nCluster=Extra\r\n\r\n[Key_AltGr_Row_2_ID_0]\r\nType=Blank\r\n\r\n[Key_AltGr_Row_2_ID_1]\r\nType=String\r\nLabel=~\r\nString=~\r\n\r\n[Key_AltGr_Row_2_ID_2]\r\nType=String\r\nLabel=ˇ\r\nString=ˇ\r\n\r\n[Key_AltGr_Row_2_ID_3]\r\nType=String\r\nLabel=^\r\nString=^\r\n\r\n[Key_AltGr_Row_2_ID_4]\r\nType=String\r\nLabel=˘\r\nString=˘\r\n\r\n[Key_AltGr_Row_2_ID_5]\r\nType=String\r\nLabel=°\r\nString=°\r\n\r\n[Key_AltGr_Row_2_ID_6]\r\nType=String\r\nLabel=˛\r\nString=˛\r\n\r\n[Key_AltGr_Row_2_ID_7]\r\nType=String\r\nLabel=`\r\nString=`\r\n\r\n[Key_AltGr_Row_2_ID_8]\r\nType=String\r\nLabel=˙\r\nString=˙\r\n\r\n[Key_AltGr_Row_2_ID_9]\r\nType=String\r\nLabel=´\r\nString=´\r\n\r\n[Key_AltGr_Row_2_ID_10]\r\nType=String\r\nLabel=˝\r\nString=˝\r\n\r\n[Key_AltGr_Row_2_ID_11]\r\nType=String\r\nLabel=¨\r\nString=¨\r\n\r\n[Key_AltGr_Row_2_ID_12]\r\nType=String\r\nLabel=¸\r\nString=¸\r\n\r\n[Key_AltGr_Row_2_ID_13]\r\nType=VirtualKey\r\nWidth=200\r\nLabel=⟵\r\nKeyCode=8\r\n\r\n[Key_AltGr_Row_2_ID_14]\r\nType=Blank\r\nWidth=25\r\nCluster=Navigation\r\n\r\n[Key_AltGr_Row_2_ID_15]\r\nType=VirtualKey\r\nLabel=⏵/⏸\r\nCluster=Navigation\r\nKeyCode=179\r\n\r\n[Key_AltGr_Row_2_ID_16]\r\nType=VirtualKey\r\nLabel=⏮\r\nCluster=Navigation\r\nKeyCode=177\r\n\r\n[Key_AltGr_Row_2_ID_17]\r\nType=VirtualKey\r\nLabel=⏭\r\nCluster=Navigation\r\nKeyCode=176\r\n\r\n[Key_AltGr_Row_2_ID_18]\r\nType=Blank\r\nWidth=25\r\nCluster=Numpad\r\n\r\n[Key_AltGr_Row_2_ID_19]\r\nType=VirtualKey\r\nLabel=Num\\nLock\r\nCluster=Numpad\r\nKeyCode=144\r\nNoRepeat=true\r\n\r\n[Key_AltGr_Row_2_ID_20]\r\nType=VirtualKey\r\nLabel=/\r\nCluster=Numpad\r\nKeyCode=111\r\n\r\n[Key_AltGr_Row_2_ID_21]\r\nType=VirtualKey\r\nLabel=*\r\nCluster=Numpad\r\nKeyCode=106\r\n\r\n[Key_AltGr_Row_2_ID_22]\r\nType=VirtualKey\r\nLabel=-\r\nCluster=Numpad\r\nKeyCode=109\r\n\r\n[Key_AltGr_Row_3_ID_0]\r\nType=VirtualKey\r\nWidth=150\r\nLabel=⭾\r\nKeyCode=9\r\n\r\n[Key_AltGr_Row_3_ID_1]\r\nType=String\r\nLabel=\\\r\nString=\\\r\n\r\n[Key_AltGr_Row_3_ID_2]\r\nType=String\r\nLabel=|\r\nString=|\r\n\r\n[Key_AltGr_Row_3_ID_3]\r\nType=String\r\nLabel=Ä\r\nString=Ä\r\n\r\n[Key_AltGr_Row_3_ID_4]\r\nType=Blank\r\n\r\n[Key_AltGr_Row_3_ID_5]\r\nType=Blank\r\n\r\n[Key_AltGr_Row_3_ID_6]\r\nType=Blank\r\n\r\n[Key_AltGr_Row_3_ID_7]\r\nType=String\r\nLabel=€\r\nString=€\r\n\r\n[Key_AltGr_Row_3_ID_8]\r\nType=String\r\nLabel=Í\r\nString=Í\r\n\r\n[Key_AltGr_Row_3_ID_9]\r\nType=Blank\r\n\r\n[Key_AltGr_Row_3_ID_10]\r\nType=Blank\r\n\r\n[Key_AltGr_Row_3_ID_11]\r\nType=String\r\nLabel=÷\r\nString=÷\r\n\r\n[Key_AltGr_Row_3_ID_12]\r\nType=String\r\nLabel=×\r\nString=×\r\n\r\n[Key_AltGr_Row_3_ID_13]\r\nType=VirtualKeyIsoEnter\r\nWidth=150\r\nLabel=\r\nKeyCode=13\r\n\r\n[Key_AltGr_Row_3_ID_14]\r\nType=Blank\r\nWidth=25\r\nCluster=Navigation\r\n\r\n[Key_AltGr_Row_3_ID_15]\r\nType=VirtualKey\r\nLabel=🔇 X\r\nCluster=Navigation\r\nKeyCode=173\r\n\r\n[Key_AltGr_Row_3_ID_16]\r\nType=VirtualKey\r\nLabel=🔈--\r\nCluster=Navigation\r\nKeyCode=174\r\n\r\n[Key_AltGr_Row_3_ID_17]\r\nType=VirtualKey\r\nLabel=🔈++\r\nCluster=Navigation\r\nKeyCode=175\r\n\r\n[Key_AltGr_Row_3_ID_18]\r\nType=Blank\r\nWidth=25\r\nCluster=Numpad\r\n\r\n[Key_AltGr_Row_3_ID_19]\r\nType=VirtualKey\r\nLabel=7\\nHome\r\nCluster=Numpad\r\nKeyCode=103\r\n\r\n[Key_AltGr_Row_3_ID_20]\r\nType=VirtualKey\r\nLabel=8\\n🠅\r\nCluster=Numpad\r\nKeyCode=104\r\n\r\n[Key_AltGr_Row_3_ID_21]\r\nType=VirtualKey\r\nLabel=9\\nPgUp\r\nCluster=Numpad\r\nKeyCode=105\r\n\r\n[Key_AltGr_Row_3_ID_22]\r\nType=VirtualKey\r\nHeight=200\r\nLabel=+\r\nCluster=Numpad\r\nKeyCode=107\r\n\r\n[Key_AltGr_Row_4_ID_0]\r\nType=VirtualKey\r\nWidth=175\r\nLabel=🡇\r\nKeyCode=20\r\nNoRepeat=true\r\n\r\n[Key_AltGr_Row_4_ID_1]\r\nType=String\r\nLabel=ä\r\nString=ä\r\n\r\n[Key_AltGr_Row_4_ID_2]\r\nType=String\r\nLabel=đ\r\nString=đ\r\n\r\n[Key_AltGr_Row_4_ID_3]\r\nType=String\r\nLabel=Đ\r\nString=Đ\r\n\r\n[Key_AltGr_Row_4_ID_4]\r\nType=String\r\nLabel=[\r\nString=[\r\n\r\n[Key_AltGr_Row_4_ID_5]\r\nType=String\r\nLabel=]\r\nString=]\r\n\r\n[Key_AltGr_Row_4_ID_6]\r\nType=Blank\r\n\r\n[Key_AltGr_Row_4_ID_7]\r\nType=String\r\nLabel=í\r\nString=í\r\n\r\n[Key_AltGr_Row_4_ID_8]\r\nType=String\r\nLabel=ł\r\nString=ł\r\n\r\n[Key_AltGr_Row_4_ID_9]\r\nType=String\r\nLabel=Ł\r\nString=Ł\r\n\r\n[Key_AltGr_Row_4_ID_10]\r\nType=String\r\nLabel=$\r\nString=$\r\n\r\n[Key_AltGr_Row_4_ID_11]\r\nType=String\r\nLabel=ß\r\nString=ß\r\n\r\n[Key_AltGr_Row_4_ID_12]\r\nType=String\r\nLabel=¤\r\nString=¤\r\n\r\n[Key_AltGr_Row_4_ID_13]\r\nType=VirtualKeyIsoEnter\r\nWidth=125\r\nLabel=↵\r\nKeyCode=13\r\n\r\n[Key_AltGr_Row_4_ID_14]\r\nType=Blank\r\nWidth=325\r\nCluster=Navigation\r\n\r\n[Key_AltGr_Row_4_ID_15]\r\nType=Blank\r\nWidth=25\r\nCluster=Numpad\r\n\r\n[Key_AltGr_Row_4_ID_16]\r\nType=VirtualKey\r\nLabel=4\\n🠄\r\nCluster=Numpad\r\nKeyCode=100\r\n\r\n[Key_AltGr_Row_4_ID_17]\r\nType=VirtualKey\r\nLabel=5\\n \r\nCluster=Numpad\r\nKeyCode=101\r\n\r\n[Key_AltGr_Row_4_ID_18]\r\nType=VirtualKey\r\nLabel=6\\n🠆\r\nCluster=Numpad\r\nKeyCode=102\r\n\r\n[Key_AltGr_Row_5_ID_0]\r\nType=VirtualKeyToggle\r\nWidth=125\r\nLabel=🡅\r\nKeyCode=160\r\nNoRepeat=true\r\n\r\n[Key_AltGr_Row_5_ID_1]\r\nType=String\r\nLabel=<\r\nString=<\r\n\r\n[Key_AltGr_Row_5_ID_2]\r\nType=String\r\nLabel=>\r\nString=>\r\n\r\n[Key_AltGr_Row_5_ID_3]\r\nType=String\r\nLabel=#\r\nString=#\r\n\r\n[Key_AltGr_Row_5_ID_4]\r\nType=String\r\nLabel=&\r\nString=&\r\n\r\n[Key_AltGr_Row_5_ID_5]\r\nType=String\r\nLabel=@\r\nString=@\r\n\r\n[Key_AltGr_Row_5_ID_6]\r\nType=String\r\nLabel={\r\nString={\r\n\r\n[Key_AltGr_Row_5_ID_7]\r\nType=String\r\nLabel=}\r\nString=}\r\n\r\n[Key_AltGr_Row_5_ID_8]\r\nType=String\r\nLabel=<\r\nString=<\r\n\r\n[Key_AltGr_Row_5_ID_9]\r\nType=String\r\nLabel=;\r\nString=;\r\n\r\n[Key_AltGr_Row_5_ID_10]\r\nType=String\r\nLabel=>\r\nString=>\r\n\r\n[Key_AltGr_Row_5_ID_11]\r\nType=String\r\nLabel=*\r\nString=*\r\n\r\n[Key_AltGr_Row_5_ID_12]\r\nType=VirtualKeyToggle\r\nWidth=275\r\nLabel=🡅\r\nKeyCode=161\r\nNoRepeat=true\r\n\r\n[Key_AltGr_Row_5_ID_13]\r\nType=Blank\r\nWidth=125\r\nCluster=Navigation\r\n\r\n[Key_AltGr_Row_5_ID_14]\r\nType=VirtualKey\r\nLabel=🠅\r\nCluster=Navigation\r\nKeyCode=38\r\n\r\n[Key_AltGr_Row_5_ID_15]\r\nType=Blank\r\nCluster=Navigation\r\n\r\n[Key_AltGr_Row_5_ID_16]\r\nType=Blank\r\nWidth=25\r\nCluster=Numpad\r\n\r\n[Key_AltGr_Row_5_ID_17]\r\nType=VirtualKey\r\nLabel=1\\nEnd\r\nCluster=Numpad\r\nKeyCode=97\r\n\r\n[Key_AltGr_Row_5_ID_18]\r\nType=VirtualKey\r\nLabel=2\\n🠇\r\nCluster=Numpad\r\nKeyCode=98\r\n\r\n[Key_AltGr_Row_5_ID_19]\r\nType=VirtualKey\r\nLabel=3\\nPgDn\r\nCluster=Numpad\r\nKeyCode=99\r\n\r\n[Key_AltGr_Row_5_ID_20]\r\nType=VirtualKey\r\nHeight=200\r\nLabel=Enter\r\nCluster=Numpad\r\nKeyCode=13\r\n\r\n[Key_AltGr_Row_6_ID_0]\r\nType=VirtualKeyToggle\r\nWidth=125\r\nLabel=Ctrl\r\nKeyCode=162\r\nNoRepeat=true\r\n\r\n[Key_AltGr_Row_6_ID_1]\r\nType=VirtualKeyToggle\r\nWidth=125\r\nLabel=Win\r\nKeyCode=91\r\nNoRepeat=true\r\n\r\n[Key_AltGr_Row_6_ID_2]\r\nType=VirtualKeyToggle\r\nWidth=125\r\nLabel=Alt\r\nKeyCode=164\r\nNoRepeat=true\r\n\r\n[Key_AltGr_Row_6_ID_3]\r\nType=VirtualKey\r\nWidth=625\r\nLabel=\r\nKeyCode=32\r\n\r\n[Key_AltGr_Row_6_ID_4]\r\nType=VirtualKeyToggle\r\nWidth=125\r\nLabel=AltGr\r\nKeyCode=165\r\nNoRepeat=true\r\n\r\n[Key_AltGr_Row_6_ID_5]\r\nType=VirtualKeyToggle\r\nWidth=125\r\nLabel=Win\r\nKeyCode=92\r\nNoRepeat=true\r\n\r\n[Key_AltGr_Row_6_ID_6]\r\nType=VirtualKey\r\nWidth=125\r\nLabel=Menü\r\nKeyCode=93\r\nNoRepeat=true\r\n\r\n[Key_AltGr_Row_6_ID_7]\r\nType=VirtualKeyToggle\r\nWidth=125\r\nLabel=Ctrl\r\nKeyCode=163\r\nNoRepeat=true\r\n\r\n[Key_AltGr_Row_6_ID_8]\r\nType=Blank\r\nWidth=25\r\nCluster=Navigation\r\n\r\n[Key_AltGr_Row_6_ID_9]\r\nType=VirtualKey\r\nLabel=🠄\r\nCluster=Navigation\r\nKeyCode=37\r\n\r\n[Key_AltGr_Row_6_ID_10]\r\nType=VirtualKey\r\nLabel=🠇\r\nCluster=Navigation\r\nKeyCode=40\r\n\r\n[Key_AltGr_Row_6_ID_11]\r\nType=VirtualKey\r\nLabel=🠆\r\nCluster=Navigation\r\nKeyCode=39\r\n\r\n[Key_AltGr_Row_6_ID_12]\r\nType=Blank\r\nWidth=25\r\nCluster=Numpad\r\n\r\n[Key_AltGr_Row_6_ID_13]\r\nType=VirtualKey\r\nWidth=200\r\nLabel=0\\nInsert\r\nCluster=Numpad\r\nKeyCode=96\r\n\r\n[Key_AltGr_Row_6_ID_14]\r\nType=VirtualKey\r\nLabel=,\\nDelete\r\nCluster=Numpad\r\nKeyCode=110\r\n\r\n"
  },
  {
    "path": "assets/lang/de.ini",
    "content": "[TranslationInfo]\nName=Deutsch (German)\nLocale=de-DE\n\n[Strings]\n;Settings Window\ntstr_SettingsWindowTitle=Desktop+ Einstellungen\n\ntstr_SettingsCatInterface=Oberfläche\ntstr_SettingsCatEnvironment=Umgebung\ntstr_SettingsCatProfiles=Profile\ntstr_SettingsCatActions=Aktionen\ntstr_SettingsCatKeyboard=Tastatur\ntstr_SettingsCatMouse=Maus\ntstr_SettingsCatLaserPointer=Laserpointer\ntstr_SettingsCatWindowOverlays=Fenster-Overlays\ntstr_SettingsCatBrowser=Browser\ntstr_SettingsCatPerformance=Leistung\ntstr_SettingsCatVersionInfo=Versionsdetails\ntstr_SettingsCatWarnings=Warnungen\ntstr_SettingsCatStartup=Startvorgang\ntstr_SettingsCatTroubleshooting=Problembehandlung\n\ntstr_SettingsWarningPrefix=Warnung:\ntstr_SettingsWarningCompositorResolution=Auflösung des SteamVR-Compositors ist unter 100%! Dies beeinträchtigt Overlay-Renderqualität.\ntstr_SettingsWarningCompositorQuality=SteamVR Overlay-Renderqualität ist nicht auf hoch eingestellt!\ntstr_SettingsWarningProcessElevated=Desktop+ wird mit administrativen Rechten ausgeführt!\ntstr_SettingsWarningElevatedMode=Erhöhter Modus ist aktiv!\ntstr_SettingsWarningElevatedProcessFocus=Ein Prozess mit erhöhten Rechten hat Fokus!\\nDesktop+ ist derzeit nicht in der Lage Eingaben zu simulieren.\ntstr_SettingsWarningBrowserMissing=Es werden Browser-Overlays verwendet, aber die Desktop+ Browser-Komponente ist derzeit nicht verfügbar.\ntstr_SettingsWarningBrowserMismatch=Die installierte Desktop+ Browser-Komponente ist nicht mit dieser Version von Desktop+ kompatibel!\ntstr_SettingsWarningUIAccessLost=Desktop+ wird nicht mehr mit UIAccess Rechten ausgeführt!\ntstr_SettingsWarningOverlayCreationErrorLimit=Eine Overlayerstellung ist fehlgeschlagen! (Maximales Overlaylimit wurde überschritten)\ntstr_SettingsWarningOverlayCreationErrorOther=Eine Overlayerstellung ist fehlgeschlagen! (%ERRORNAME%)\ntstr_SettingsWarningGraphicsCaptureError=Ein unerwarteter Fehler ist in einem Graphics Capture-Thread aufgetreten! (%ERRORCODE%)\ntstr_SettingsWarningAppProfileActive=Das Anwendungs-Profil für %APPNAME% hat das bestehende Overlaylayout überschrieben.\\nÄnderungen an den Overlays werden nicht automatisch gespeichert während es aktiv ist.\ntstr_SettingsWarningConfigMigrated=Einstellungen und Profile der vorherigen Desktop+ Version wurden in neue Formate migriert.\\nDie ursprünglichen Dateien wurden nicht gelöscht und können weiterhin mit einer älteren Version der Anwendung verwendet werden.\\nUm stattdessen von Null zu beginnen, siehe [Auf Standardeinstellungen zurücksetzen].\ntstr_SettingsWarningMenuDontShowAgain=Nicht mehr zeigen\ntstr_SettingsWarningMenuDismiss=Ausblenden\n\ntstr_SettingsInterfaceLanguage=Sprache\ntstr_SettingsInterfaceLanguageCommunity=Community-Übersetzung von %AUTHOR%\ntstr_SettingsInterfaceLanguageIncompleteWarning=Übersetzung ist möglicherweise nicht vollständig\ntstr_SettingsInterfaceAdvancedSettings=Zeige erweiterte Einstellungen\ntstr_SettingsInterfaceAdvancedSettingsTip=Zeige erweiterte und seltener verwendete Einstellungen\ntstr_SettingsInterfaceBlankSpaceDrag=Verschiebe Fenster, wenn auf leeren Bereich geklickt wird\ntstr_SettingsInterfacePersistentUI=Persistente UI\ntstr_SettingsInterfacePersistentUIManage=Verwalten\ntstr_SettingsInterfaceDesktopButtons=Desktop-Schaltflächenstil\ntstr_SettingsInterfaceDesktopButtonsNone=Keine\ntstr_SettingsInterfaceDesktopButtonsIndividual=Individuelle Desktops\ntstr_SettingsInterfaceDesktopButtonsCycle=Zyklische Schaltflächen\ntstr_SettingsInterfaceDesktopButtonsAddCombined=Gesamten Desktop hinzufügen\n\ntstr_SettingsInterfacePersistentUIHelp=Desktop+ merkt sich die Zustände der UI-Fenster getrennt für die allgemeine Nutzung (Global) und der Nutzung innerhalb des Desktop+ SteamVR Dashboard-Tabs (Desktop+ Tab).\ntstr_SettingsInterfacePersistentUIHelp2=Die folgenden Bedienelemente können zur direkten Manipulation der Fensterzustände verwendet werden.\\nBeachte, dass einige Zustände das Zurücksetzen der Position erfordern um das Fenster an eine sichtbare Stelle zu bringen.\ntstr_SettingsInterfacePersistentUIWindowsHeader=Fenster\ntstr_SettingsInterfacePersistentUIWindowsSettings=Einstellungen\ntstr_SettingsInterfacePersistentUIWindowsProperties=Overlay-Eigenschaften\ntstr_SettingsInterfacePersistentUIWindowsKeyboard=Desktop+ Tastatur\ntstr_SettingsInterfacePersistentUIWindowsStateGlobal=Global\ntstr_SettingsInterfacePersistentUIWindowsStateDashboardTab=Desktop+ Tab\ntstr_SettingsInterfacePersistentUIWindowsStateVisible=Sichtbar\ntstr_SettingsInterfacePersistentUIWindowsStatePinned=Angepinnt\ntstr_SettingsInterfacePersistentUIWindowsStatePosition=Position\ntstr_SettingsInterfacePersistentUIWindowsStatePositionReset=Zurücksetzen\ntstr_SettingsInterfacePersistentUIWindowsStateSize=Größe\ntstr_SettingsInterfacePersistentUIWindowsStateLaunchRestore=Zustand bei Desktop+-Start wiederherstellen\n\ntstr_SettingsEnvironmentBackgroundColor=Hintergrundfarbe\ntstr_SettingsEnvironmentBackgroundColorDispModeNever=Nie sichtbar\ntstr_SettingsEnvironmentBackgroundColorDispModeDPlusTab=Nur im Desktop+ Tab sichtbar\ntstr_SettingsEnvironmentBackgroundColorDispModeAlways=Immer sichtbar\ntstr_SettingsEnvironmentDimInterface=Oberfläche dimmen\ntstr_SettingsEnvironmentDimInterfaceTip=Dimmt das SteamVR Dashboard und die Desktop+ Oberfläche während der Desktop+ Dashboard-Tab offen ist\n\ntstr_SettingsProfilesOverlays=Overlay-Profile\ntstr_SettingsProfilesApps=Anwendungs-Profile\ntstr_SettingsProfilesManage=Verwalten\n\ntstr_SettingsProfilesOverlaysHeader=Overlay-Profile verwalten\ntstr_SettingsProfilesOverlaysNameDefault=Standardprofil\ntstr_SettingsProfilesOverlaysNameNew=[Neues Profil]\ntstr_SettingsProfilesOverlaysNameNewBase=Profil %ID%\ntstr_SettingsProfilesOverlaysProfileLoad=Profil laden\ntstr_SettingsProfilesOverlaysProfileAdd=Aus Profil hinzufügen\ntstr_SettingsProfilesOverlaysProfileSave=Aktuelle Overlays speichern\ntstr_SettingsProfilesOverlaysProfileDelete=Profil löschen\ntstr_SettingsProfilesOverlaysProfileDeleteConfirm=Wirklich?\ntstr_SettingsProfilesOverlaysProfileFailedLoad=Laden des Profils fehlgeschlagen\ntstr_SettingsProfilesOverlaysProfileFailedDelete=Löschen des Profils fehlgeschlagen\ntstr_SettingsProfilesOverlaysProfileAddSelectHeader=Profil zum Hinzufügen auswählen\ntstr_SettingsProfilesOverlaysProfileAddSelectEmpty=Dieses Profil enthält keine Overlays\ntstr_SettingsProfilesOverlaysProfileAddSelectDo=Ausgewählte Overlays hinzufügen\ntstr_SettingsProfilesOverlaysProfileAddSelectAll=Alle auswählen\ntstr_SettingsProfilesOverlaysProfileAddSelectNone=Keine auswählen\ntstr_SettingsProfilesOverlaysProfileSaveSelectHeader=Aktuelle Overlays speichern\ntstr_SettingsProfilesOverlaysProfileSaveSelectName=Profilname\ntstr_SettingsProfilesOverlaysProfileSaveSelectNameErrorBlank=Name darf nicht leer sein\ntstr_SettingsProfilesOverlaysProfileSaveSelectNameErrorTaken=Name ist bereits in Verwendung\ntstr_SettingsProfilesOverlaysProfileSaveSelectHeaderList=Overlays zum Speichern im Profil auswählen\ntstr_SettingsProfilesOverlaysProfileSaveSelectDo=Ausgewählte Overlays speichern\ntstr_SettingsProfilesOverlaysProfileSaveSelectDoFailed=Speichern des Profils fehlgeschlagen\n\ntstr_SettingsProfilesAppsHeader=Anwendungs-Profile verwalten\ntstr_SettingsProfilesAppsHeaderNoVRTip=Während Desktop+ nicht läuft werden nur existierende Anwendungs-Profile gelistet\ntstr_SettingsProfilesAppsListEmpty=Keine Anwendungen verfügbar\ntstr_SettingsProfilesAppsProfileHeaderActive=(derzeit aktiv)\ntstr_SettingsProfilesAppsProfileEnabled=Aktiviere, wenn Anwendung läuft\ntstr_SettingsProfilesAppsProfileOverlayProfile=Overlay-Profil\ntstr_SettingsProfilesAppsProfileActionEnter=Start-Aktion\ntstr_SettingsProfilesAppsProfileActionLeave=End-Aktion\n\ntstr_SettingsActionsManage=Aktionen\ntstr_SettingsActionsManageButton=Verwalten\ntstr_SettingsActionsButtonsOrderDefault=Aktions-Schaltflächen (Standard)\ntstr_SettingsActionsButtonsOrderOverlayBar=Aktions-Schaltflächen (Overlay-Bar)\ntstr_SettingsActionsShowBindings=Zeige Controllerzuordnungen\ntstr_SettingsActionsActiveShortcuts=Aktive Controllertasten\ntstr_SettingsActionsActiveShortcutsTip=Controllerzuordnungen während auf ein Overlay gezeigt wird.\\nBearbeite die VR Dashboard Controllerzuordnungen um zu ändern welche Tasten diese sind.\ntstr_SettingsActionsActiveShortuctsHome=\"Zur Startseite\"\ntstr_SettingsActionsActiveShortuctsBack=\"Zurück\"\ntstr_SettingsActionsGlobalShortcuts=Globale Controllertasten\ntstr_SettingsActionsGlobalShortcutsTip=Controllerzuordnungen während das Dashboard geschlossen ist und auf kein Overlay gezeigt wird.\\nBearbeite die Desktop+ Controllerzuordnungnen um zu ändern welche Tasten diese sind.\ntstr_SettingsActionsGlobalShortcutsEntry=Globales Kürzel #%ID%\ntstr_SettingsActionsGlobalShortcutsAdd=Kürzel hinzufügen\ntstr_SettingsActionsGlobalShortcutsRemove=Letztes Kürzel entfernen\ntstr_SettingsActionsHotkeys=Hotkeys\ntstr_SettingsActionsHotkeysTip=Systemweite Tastenkürzel.\\nHotkey-Eingaben werden von anderen Anwendungen nicht erfasst und funktionieren möglicherweise nicht falls die selbe Tastenkombination schon woanders registriert wurde.\ntstr_SettingsActionsHotkeysAdd=Hotkey hinzufügen\ntstr_SettingsActionsHotkeysRemove=Entfernen\ntstr_SettingsActionsTableHeaderAction=Aktion\ntstr_SettingsActionsTableHeaderShortcut=Kürzel\ntstr_SettingsActionsTableHeaderHotkey=Hotkey\n\ntstr_SettingsActionsManageHeader=Aktionen verwalten\ntstr_SettingsActionsManageCopyUID=UID kopieren\ntstr_SettingsActionsManageNew=Neue Aktion...\ntstr_SettingsActionsManageEdit=Aktion bearbeiten\ntstr_SettingsActionsManageDuplicate=Aktion duplizieren\ntstr_SettingsActionsManageDelete=Aktion löschen\ntstr_SettingsActionsManageDeleteConfirm=Wirklich?\ntstr_SettingsActionsManageDuplicatedName=%NAME% (Kopie)\n\ntstr_SettingsActionsEditHeader=Aktion bearbeiten\ntstr_SettingsActionsEditName=Name\ntstr_SettingsActionsEditNameTranslatedTip=Diese Aktion verwendet momentan eine Übersetzungstext-ID als Name, um sich automatisch der gewählten Sprache anzupassen\ntstr_SettingsActionsEditTarget=Ziel\ntstr_SettingsActionsEditTargetDefault=Standard\ntstr_SettingsActionsEditTargetDefaultTip=Verwendet das Overlay als Ziel, auf dem die Aktion ausgelöst wurde.\\nFalls letzteres nicht existiert, wird das fokussierte Overlay verwendet.\ntstr_SettingsActionsEditTargetUseTags=Verwende Tags\ntstr_SettingsActionsEditTargetActionTarget=Aktions-Ziel\ntstr_SettingsActionsEditHeaderAppearance=Schaltflächen-Erscheinungsbild\ntstr_SettingsActionsEditIcon=Icon\ntstr_SettingsActionsEditLabel=Beschriftung\ntstr_SettingsActionsEditLabelTranslatedTip=Diese Aktion verwendet momentan eine Übersetzungstext-ID als Beschriftung, um sich automatisch der gewählten Sprache anzupassen\ntstr_SettingsActionsEditHeaderCommands=Befehle\ntstr_SettingsActionsEditNameNew=Neue Aktion\ntstr_SettingsActionsEditCommandAdd=Befehl hinzufügen\ntstr_SettingsActionsEditCommandDelete=Befehl löschen\ntstr_SettingsActionsEditCommandDeleteConfirm=Wirklich?\ntstr_SettingsActionsEditCommandType=Befehlstyp\ntstr_SettingsActionsEditCommandTypeNone=Kein\ntstr_SettingsActionsEditCommandTypeKey=Drücke Taste\ntstr_SettingsActionsEditCommandTypeMousePos=Setze Mausposition\ntstr_SettingsActionsEditCommandTypeString=Tippe Text\ntstr_SettingsActionsEditCommandTypeLaunchApp=Starte Anwendung\ntstr_SettingsActionsEditCommandTypeShowKeyboard=Zeige Tastatur\ntstr_SettingsActionsEditCommandTypeCropActiveWindow=Schneide zum aktiven Fenster zu\ntstr_SettingsActionsEditCommandTypeShowOverlay=Zeige Overlay\ntstr_SettingsActionsEditCommandTypeSwitchTask=Wechsle Task\ntstr_SettingsActionsEditCommandTypeLoadOverlayProfile=Lade Overlay-Profil\ntstr_SettingsActionsEditCommandTypeUnknown=Unbekannt\ntstr_SettingsActionsEditCommandVisibilityToggle=Umschalten\ntstr_SettingsActionsEditCommandVisibilityShow=Immer zeigen\ntstr_SettingsActionsEditCommandVisibilityHide=Immer verstecken\ntstr_SettingsActionsEditCommandUndo=Bei Loslassen umkehren\ntstr_SettingsActionsEditCommandKeyCode=Tastencode\ntstr_SettingsActionsEditCommandKeyToggle=Taste umschalten\ntstr_SettingsActionsEditCommandMouseX=X\ntstr_SettingsActionsEditCommandMouseY=Y\ntstr_SettingsActionsEditCommandMouseUseCurrent=Verwende momentane Mausposition\ntstr_SettingsActionsEditCommandString=Text\ntstr_SettingsActionsEditCommandPath=Anwendungspfad\ntstr_SettingsActionsEditCommandPathTip=Dies kann auch eine normale Datei oder URL sein\ntstr_SettingsActionsEditCommandArgs=Anwendungsparameter\ntstr_SettingsActionsEditCommandArgsTip=Diese werden zur Anwendung weitergeleitet.\\nFalls unsicher, leer lassen.\ntstr_SettingsActionsEditCommandVisibility=Sichtbarkeit\ntstr_SettingsActionsEditCommandSwitchingMethod=Wechselmethode\ntstr_SettingsActionsEditCommandSwitchingMethodSwitcher=Zeige Task-Wechsler\ntstr_SettingsActionsEditCommandSwitchingMethodFocus=Fokussiere Fenster\ntstr_SettingsActionsEditCommandWindow=Fenster\ntstr_SettingsActionsEditCommandWindowNone=[Kein Fenster]\ntstr_SettingsActionsEditCommandWindowStrictMatchingTip=Erlaube nur exakte Fenstertitel-Übereinstimmungen, wenn ein Fenster zum fokussieren gesucht wird\ntstr_SettingsActionsEditCommandCursorWarp=Bewege Mauszeiger in das Fenster\ntstr_SettingsActionsEditCommandProfile=Profil\ntstr_SettingsActionsEditCommandProfileClear=Entferne bestehende Overlays\ntstr_SettingsActionsEditCommandDescNone=Kein Befehl\ntstr_SettingsActionsEditCommandDescKey=Drücke Taste \"%KEYNAME%\"\ntstr_SettingsActionsEditCommandDescKeyToggle=Schalte Taste \"%KEYNAME%\" um\ntstr_SettingsActionsEditCommandDescMousePos=Setze Mausposition zu %X%, %Y%\ntstr_SettingsActionsEditCommandDescString=Tippe Text \"%STRING%\"\ntstr_SettingsActionsEditCommandDescLaunchApp=Starte Anwendung \"%APP%\" %ARGSOPT%\ntstr_SettingsActionsEditCommandDescLaunchAppArgsOpt=\"%ARGS%\"\ntstr_SettingsActionsEditCommandDescKeyboardToggle=Schalte Tastatur um\ntstr_SettingsActionsEditCommandDescKeyboardShow=Zeige Tastatur\ntstr_SettingsActionsEditCommandDescKeyboardHide=Verstecke Tastatur\ntstr_SettingsActionsEditCommandDescCropWindow=Schneide zum aktiven Fenster zu\ntstr_SettingsActionsEditCommandDescOverlayToggle=Schalte Overlay um: %TAGS%\ntstr_SettingsActionsEditCommandDescOverlayShow=Zeige Overlay: %TAGS%\ntstr_SettingsActionsEditCommandDescOverlayHide=Verstecke Overlay: %TAGS%\ntstr_SettingsActionsEditCommandDescOverlayTargetDefault=[Aktions-Ziel]\ntstr_SettingsActionsEditCommandDescSwitchTask=Wechsle Task\ntstr_SettingsActionsEditCommandDescSwitchTaskWindow=Wechsle Task zu \"%WINDOW%\"\ntstr_SettingsActionsEditCommandDescLoadOverlayProfile=Lade Overlay-Profil \"%PROFILE%\"\ntstr_SettingsActionsEditCommandDescLoadOverlayProfileAdd=Füge Overlay-Profil \"%PROFILE%\" hinzu\ntstr_SettingsActionsEditCommandDescUnknown=Unbekannter Befehl\n\ntstr_SettingsActionsOrderHeader=Ändere Aktions-Anordnung\ntstr_SettingsActionsOrderButtonLabel=%COUNT% Aktionen ausgewählt\ntstr_SettingsActionsOrderButtonLabelSingular=%COUNT% Aktion ausgewählt\ntstr_SettingsActionsOrderNoActions=Keine Aktionen ausgewählt\ntstr_SettingsActionsOrderAdd=Aktionen hinzufügen...\ntstr_SettingsActionsOrderRemove=Aktion entfernen\n\ntstr_SettingsActionsAddSelectorHeader=Wähle Aktionen zum Hinzufügen\ntstr_SettingsActionsAddSelectorAdd=Ausgewählte Aktionen hinzufügen\n\ntstr_SettingsKeyboardLayout=Tastaturlayout\ntstr_SettingsKeyboardSize=Größe\ntstr_SettingsKeyboardBehavior=Verhalten\ntstr_SettingsKeyboardStickyMod=Umschalttasten einrasten\ntstr_SettingsKeyboardKeyRepeat=Tastenwiederholung\ntstr_SettingsKeyboardAutoShow=Automatisch einblenden\ntstr_SettingsKeyboardAutoShowDesktopOnly=Automatisch einblenden\ntstr_SettingsKeyboardAutoShowDesktop=Für Desktop- & Fenster-Overlays\ntstr_SettingsKeyboardAutoShowDesktopTip=Experimentell. Funktioniert nicht mit allen Anwendungen.\ntstr_SettingsKeyboardAutoShowBrowser=Für Browser-Overlays\ntstr_SettingsKeyboardLayoutAuthor=Erstellt von %AUTHOR%\ntstr_SettingsKeyboardKeyClusters=Tastengruppen\ntstr_SettingsKeyboardKeyClusterBase=Basis\ntstr_SettingsKeyboardKeyClusterFunction=Funktion\ntstr_SettingsKeyboardKeyClusterNavigation=Navigation\ntstr_SettingsKeyboardKeyClusterNumpad=Ziffernblock\ntstr_SettingsKeyboardKeyClusterExtra=Extra\ntstr_SettingsKeyboardSwitchToEditor=Zu Tastaturlayout-Editor wechseln\n\ntstr_SettingsMouseShowCursor=Zeige Mauszeiger\ntstr_SettingsMouseShowCursorGCUnsupported=Das Deaktivieren des Mauszeigers für Graphics Capture-Overlays wird von diesem System nicht unterstützt\ntstr_SettingsMouseShowCursorGCActiveWarning=Aktive Graphics Capture-Bilderfassungen verhindern möglicherweise, dass der Mauszeiger in Desktop Duplication-Overlays ausgeblendet werden kann\ntstr_SettingsMouseScrollSmooth=Verwende weiches Scrollen\ntstr_SettingsMouseSimulatePen=Simuliere als Stifteingabe\ntstr_SettingsMouseSimulatePenUnsupported=Simulation von Stifteingaben wird von diesem System nicht unterstützt\ntstr_SettingsMouseAllowLaserPointerOverride=Erlaube Laserpointer-Überbrückung\ntstr_SettingsMouseAllowLaserPointerOverrideTip=Deaktiviert den Laserpointer, wenn die physische Maus ruckartig bewegt wird.\\nKlicke auf ein Overlay um den Laserpointer wieder zu aktivieren.\ntstr_SettingsMouseDoubleClickAssist=Doppelklick-Assistent\ntstr_SettingsMouseDoubleClickAssistTip=Friert den Mauszeiger für die angegebene Zeit ein, um Eingaben von Doppelklicks zu erleichtern.\ntstr_SettingsMouseDoubleClickAssistTipValueOff=Aus\ntstr_SettingsMouseDoubleClickAssistTipValueAuto=Auto\ntstr_SettingsMouseSmoothing=Eingabeglättung\ntstr_SettingsMouseSmoothingLevelNone=Keine\ntstr_SettingsMouseSmoothingLevelVeryLow=Sehr Niedrig\ntstr_SettingsMouseSmoothingLevelLow=Niedrig\ntstr_SettingsMouseSmoothingLevelMedium=Mittel\ntstr_SettingsMouseSmoothingLevelHigh=Hoch\ntstr_SettingsMouseSmoothingLevelVeryHigh=Sehr Hoch\n\ntstr_SettingsLaserPointerTip=Diese Einstellungen gelten für Desktop+'s Laserpointer, welcher verwendet wird, wenn das SteamVR Dashboard geschlossen ist\ntstr_SettingsLaserPointerBlockInput=Blockiere Spiel-Eingabe, wenn aktiv\ntstr_SettingsLaserPointerAutoToggleDistance=Maximale Auto-Aktivierungsdistanz\ntstr_SettingsLaserPointerAutoToggleDistanceValueOff=Aus\ntstr_SettingsLaserPointerHMDPointer=Blick-basierter HMD-Pointer\ntstr_SettingsLaserPointerHMDPointerTableHeaderInputAction=Eingabeaktion\ntstr_SettingsLaserPointerHMDPointerTableHeaderBinding=Tastatureingabe\ntstr_SettingsLaserPointerHMDPointerTableBindingToggle=Laserpointer umschalten\ntstr_SettingsLaserPointerHMDPointerTableBindingLeft=Linke Maustaste\ntstr_SettingsLaserPointerHMDPointerTableBindingRight=Rechte Maustaste\ntstr_SettingsLaserPointerHMDPointerTableBindingMiddle=Mittlere Maustaste\ntstr_SettingsLaserPointerHMDPointerTableBindingDrag=Overlay ziehen\n\ntstr_SettingsWindowOverlaysAutoFocus=Fokussiere Fenster, wenn auf ein Overlay gezeigt wird\ntstr_SettingsWindowOverlaysKeepOnScreen=Halte Fenster auf dem Bildschirm\ntstr_SettingsWindowOverlaysKeepOnScreenTip=Bewegt das Quellfenster in den Arbeitsbereich des Bildschirms, wenn es außerhalb dessen liegt\ntstr_SettingsWindowOverlaysAutoSizeOverlay=Passe Overlaygröße an, wenn die Fenstergröße sich ändert\ntstr_SettingsWindowOverlaysFocusSceneApp=Fokussiere Spiel, wenn der Laserpointer das Overlay verlässt\ntstr_SettingsWindowOverlaysFocusSceneAppDashboard=Fokussiere Spiel, nachdem das Dashboard geschlossen wurde\ntstr_SettingsWindowOverlaysOnWindowDrag=Bei Fenster-Verschiebung\ntstr_SettingsWindowOverlaysOnWindowDragDoNothing=Nichts tun\ntstr_SettingsWindowOverlaysOnWindowDragBlock=Blockiere Verschiebung\ntstr_SettingsWindowOverlaysOnWindowDragOverlay=Verschiebe Overlay\ntstr_SettingsWindowOverlaysOnCaptureLoss=Bei Bilderfassungs-Verlust\ntstr_SettingsWindowOverlaysOnCaptureLossTip=Verhalten, wenn eine Fenster-Bilderfassung endet, in der Regel aufgrund vom Schließen des Zielfensters.\\n\"Verstecke Overlay\" zeigt das Overlay auch wieder, wenn die Erfassung wiederhergestellt wird.\ntstr_SettingsWindowOverlaysOnCaptureLossDoNothing=Nichts tun\ntstr_SettingsWindowOverlaysOnCaptureLossHide=Verstecke Overlay\ntstr_SettingsWindowOverlaysOnCaptureLossRemove=Entferne Overlay\n\ntstr_SettingsBrowserMaxFrameRate=Maximale Frame-Rate\ntstr_SettingsBrowserMaxFrameRateOverrideOff=Globale Einstellung\ntstr_SettingsBrowserContentBlocker=Inhaltsblockierung\ntstr_SettingsBrowserContentBlockerTip=Kopiere Blocklisten im Adblock Plus-Syntax in das \"DesktopPlusBrowser\\content_block\" Verzeichnis.\\nEs werden alle Listen im Verzeichnis geladen.\ntstr_SettingsBrowserContentBlockerListCount=(%LISTCOUNT% Listen aktiv)\ntstr_SettingsBrowserContentBlockerListCountSingular=(%LISTCOUNT% Liste aktiv)\n\ntstr_SettingsPerformanceUpdateLimiter=Limitiere Updates\ntstr_SettingsPerformanceUpdateLimiterMode=Updatelimiter-Modus\ntstr_SettingsPerformanceUpdateLimiterModeOff=Aus\ntstr_SettingsPerformanceUpdateLimiterModeMS=Frame-Zeit\ntstr_SettingsPerformanceUpdateLimiterModeFPS=Frame-Rate\ntstr_SettingsPerformanceUpdateLimiterModeOffOverride=Verwende globale Einstellung\ntstr_SettingsPerformanceUpdateLimiterModeMSTip=\"Frame-Zeit\" erzwingt ein Mindestintervall zwischen Overlayupdates\ntstr_SettingsPerformanceUpdateLimiterFPSValue=%FPS% fps\ntstr_SettingsPerformanceUpdateLimiterOverride=Überschreibe Updatelimit\ntstr_SettingsPerformanceUpdateLimiterOverrideTip=Wenn mehrere Überschreibungen aktiv sind, wird diejenige verwendet, die in der höchsten Update-Rate resultiert\ntstr_SettingsPerformanceUpdateLimiterModeOverride=Überschreibe Updatelimiter\ntstr_SettingsPerformanceRapidUpdates=Verringere Laserpointer-Latenz\ntstr_SettingsPerformanceRapidUpdatesTip=Verringere die Latenz von Laserpointer-Eingaben auf Kosten zusätzlicher CPU-Last, solange auf ein Overlay gezeigt wird\ntstr_SettingsPerformanceSingleDesktopMirror=Einzelne Desktopspiegelung\ntstr_SettingsPerformanceSingleDesktopMirrorTip=Spiegele individuelle Desktops, wenn zu ihnen gewechselt wird, anstatt vom gesamten Desktop zuzuschneiden.\\nSolange diese Einstellung aktiv ist, werden alle Overlays den selben Desktop zeigen.\ntstr_SettingsPerformanceAlternativeCursorRendering=Alternative Mauszeigerdarstellung\ntstr_SettingsPerformanceAlternativeCursorRenderingTip=Verwende eine andere Methode zum Erfassen und Darstellen des Mauszeigers.\\nKann helfen, wenn mit der Standardmethode Probleme bei der Darstellung des Hardware-Mauszeigers auftreten.\ntstr_SettingsPerformanceUseHDR=HDR-Spiegelung\ntstr_SettingsPerformanceUseHDRTip=Verwende beim Spiegeln von Desktops und Fenstern Texturen mit höherer Bittiefe, zur HDR-Unterstützung. Experimentell.\\nKann die Leistung beeinträchtigen, falls nicht erforderlich, und erhöht VRAM-Verbrauch.\ntstr_SettingsPerformanceShowFPS=Zeige FPS in Floating UI\n\ntstr_SettingsWarningsHidden=Versteckte Warnungen:\ntstr_SettingsWarningsReset=Versteckte Warnungen zurücksetzen\n\ntstr_SettingsStartupAutoLaunch=Starte automatisch bei SteamVR-Start\ntstr_SettingsStartupSteamDisable=Deaktiviere Steam-Integration\ntstr_SettingsStartupSteamDisableTip=Startet Desktop+ ohne Steam neu, wenn es davon gestartet wurde.\\nDies deaktiviert den permanenten In-App-Status, Nutzungszeit-Statistik und andere Steam-Funktionen.\n\ntstr_SettingsTroubleshootingRestart=Neustarten\ntstr_SettingsTroubleshootingRestartSteam=Mit Steam neu starten\ntstr_SettingsTroubleshootingRestartDesktop=Im Desktopmodus neu starten\ntstr_SettingsTroubleshootingElevatedModeEnter=Starte erhöhten Modus\ntstr_SettingsTroubleshootingElevatedModeLeave=Stoppe erhöhten Modus\ntstr_SettingsTroubleshootingSettingsReset=Auf Standardeinstellungen zurücksetzen\ntstr_SettingsTroubleshootingSettingsResetConfirmDescription=Die ausgewählte Elemente werden auf ihre Standardwerte zurückgesetzt.\\nDieser Vorgang kann nicht rückgängig gemacht werden.\\n\\nSetze folgendes zurück:\ntstr_SettingsTroubleshootingSettingsResetConfirmButton=Ausgewählte Elemente zurücksetzen\ntstr_SettingsTroubleshootingSettingsResetConfirmElementOverlays=Aktive Overlaykonfiguration\ntstr_SettingsTroubleshootingSettingsResetConfirmElementLegacyFiles=Lösche alte unbenutzte Konfigurations- und Profildateien\ntstr_SettingsTroubleshootingSettingsResetShowQuickStart=Kurzanleitung anzeigen\n\n;Keyboard Window\ntstr_KeyboardWindowTitle=Desktop+ Tastatur\ntstr_KeyboardWindowTitleSettings=Desktop+ Tastatur (Einstellungen)\ntstr_KeyboardWindowTitleOverlay=Desktop+ Tastatur (%OVERLAYNAME%)\ntstr_KeyboardWindowTitleOverlayUnknown=[Unbekanntes Overlay]\n\n;Keyboard Shortcuts Window\ntstr_KeyboardShortcutsCut=Ausschneiden\ntstr_KeyboardShortcutsCopy=Kopieren\ntstr_KeyboardShortcutsPaste=Einfügen\n\n;Overlay Properties Window\ntstr_OvrlPropsCatPosition=Position\ntstr_OvrlPropsCatAppearance=Aussehen\ntstr_OvrlPropsCatCapture=Bilderfassung\ntstr_OvrlPropsCatPerformanceMonitor=Leistungsanzeige\ntstr_OvrlPropsCatBrowser=Browser\ntstr_OvrlPropsCatAdvanced=Erweitert\ntstr_OvrlPropsCatPerformance=Leistung\ntstr_OvrlPropsCatInterface=Oberfläche\n\ntstr_OvrlPropsPositionOrigin=Ursprung\ntstr_OvrlPropsPositionOriginRoom=Spielbereich\ntstr_OvrlPropsPositionOriginHMDXY=HMD-Bodenposition\ntstr_OvrlPropsPositionOriginDashboard=Dashboard\ntstr_OvrlPropsPositionOriginHMD=HMD\ntstr_OvrlPropsPositionOriginSeatedSpace=Sitzposition\ntstr_OvrlPropsPositionOriginControllerR=Rechter Controller\ntstr_OvrlPropsPositionOriginControllerL=Linker Controller\ntstr_OvrlPropsPositionOriginTheaterScreen=Kinoleinwand\ntstr_OvrlPropsPositionOriginTracker1=Tracker #1\ntstr_OvrlPropsPositionOriginConfigHMDXYTurning=Mit HMD drehen\ntstr_OvrlPropsPositionOriginConfigTheaterScreenEnter=Starte Kinomodus\ntstr_OvrlPropsPositionOriginConfigTheaterScreenLeave=Beende Kinomodus\ntstr_OvrlPropsPositionOriginConfigSmoothing=Ursprungsglättung\ntstr_OvrlPropsPositionOriginTheaterScreenTip=Einige Eigenschaften werden von der SteamVR Kinoleinwand gesteuert\ntstr_OvrlPropsPositionDispMode=Anzeigemodus\ntstr_OvrlPropsPositionDispModeAlways=Immer\ntstr_OvrlPropsPositionDispModeDashboard=Nur im Dashboard\ntstr_OvrlPropsPositionDispModeScene=Nur im Spiel\ntstr_OvrlPropsPositionDispModeDPlus=Nur im Desktop+ Tab\ntstr_OvrlPropsPositionPos=Position\ntstr_OvrlPropsPositionPosTip=Die Position kann nur verändert oder zurückgesetzt werden wenn Desktop+ läuft\ntstr_OvrlPropsPositionChange=Ändern\ntstr_OvrlPropsPositionReset=Zurücksetzen\ntstr_OvrlPropsPositionLock=Sperren\n\ntstr_OvrlPropsPositionChangeHeader=Overlayposition ändern\ntstr_OvrlPropsPositionChangeHelp=Verschiebe ein beliebiges Overlay, um dessen Position zu ändern.\\nHalte Rechtsklick gedrückt für eine zweihändige Gestenmanipulation.\ntstr_OvrlPropsPositionChangeHelpDesktop=Halte die Zieh-Schaltflächen (\"Z\") gedrückt um das Overlay mit der Maus zu verschieben oder drehen.\ntstr_OvrlPropsPositionChangeManualAdjustment=Manuelle Anpassung\ntstr_OvrlPropsPositionChangeMove=Bewegen\ntstr_OvrlPropsPositionChangeRotate=Drehen\ntstr_OvrlPropsPositionChangeForward=Vorwärts\ntstr_OvrlPropsPositionChangeBackward=Rückwärts\ntstr_OvrlPropsPositionChangeRollCW=Rolle ⟳\ntstr_OvrlPropsPositionChangeRollCCW=Rolle ⟲\ntstr_OvrlPropsPositionChangeLookAt=Zum HMD\ntstr_OvrlPropsPositionChangeDragButton=Z\ntstr_OvrlPropsPositionChangeOffset=Zusätzliches Offset\ntstr_OvrlPropsPositionChangeOffsetUpDown=Hoch/Runter-Offset\ntstr_OvrlPropsPositionChangeOffsetRightLeft=Rechts/Links-Offset\ntstr_OvrlPropsPositionChangeOffsetForwardBackward=Vorwärts/Rückwärts-Offset\ntstr_OvrlPropsPositionChangeDragSettings=Zieh-Einstellungen\ntstr_OvrlPropsPositionChangeDragSettingsAutoDocking=Docke an Controller, wenn in Nähe\ntstr_OvrlPropsPositionChangeDragSettingsForceUpright=Erzwinge aufrechte Ausrichtung\ntstr_OvrlPropsPositionChangeDragSettingsForceDistance=Erzwinge festen Abstand\ntstr_OvrlPropsPositionChangeDragSettingsForceDistanceShape=Form\ntstr_OvrlPropsPositionChangeDragSettingsForceDistanceShapeSphere=Kugel\ntstr_OvrlPropsPositionChangeDragSettingsForceDistanceShapeCylinder=Zylinder\ntstr_OvrlPropsPositionChangeDragSettingsForceDistanceAutoCurve=Auto-Krümmung\ntstr_OvrlPropsPositionChangeDragSettingsForceDistanceAutoTilt=Auto-Neigung\ntstr_OvrlPropsPositionChangeDragSettingsSnapPosition=Position einrasten\ntstr_OvrlPropsPositionChangeDragSettingsSnapRotation=Drehung einrasten\ntstr_OvrlPropsPositionChangeDragSettingsSnapRotationPitch=Nickachse\ntstr_OvrlPropsPositionChangeDragSettingsSnapRotationYaw=Gierachse\ntstr_OvrlPropsPositionChangeDragSettingsSnapRotationRoll=Rollachse\n\ntstr_OvrlPropsAppearanceWidth=Breite\ntstr_OvrlPropsAppearanceCurve=Krümmung\ntstr_OvrlPropsAppearanceOpacity=Deckkraft\ntstr_OvrlPropsAppearanceBrightness=Helligkeit\ntstr_OvrlPropsAppearanceCrop=Zuschneidung\ntstr_OvrlPropsAppearanceCropValueMax=Max\ntstr_OvrlPropsAppearanceShowBackside=Zeige Rückseite\n\ntstr_OvrlPropsCrop=Zuschneide-Bereich\ntstr_OvrlPropsCropHelp=Ziehe das Rechteck um die Zuschneidung zu verändern. Verwende Scroll-Eingaben oder ziehe die Seiten um die Größe des Zuschneidungs-Rechtecks anzupassen.\ntstr_OvrlPropsCropManualAdjust=Manuelle Anpassung\ntstr_OvrlPropsCropInvalidTip=Das aktuelle Zuschneide-Rechteck ist ungültig. Das Overlay ist möglicherweise nicht sichtbar.\ntstr_OvrlPropsCropX=X\ntstr_OvrlPropsCropY=Y\ntstr_OvrlPropsCropWidth=Breite\ntstr_OvrlPropsCropHeight=Höhe\ntstr_OvrlPropsCropToWindow=Zum aktiven Fenster zuschneiden\n\ntstr_OvrlPropsCaptureMethod=Erfassungsmethode\ntstr_OvrlPropsCaptureMethodDup=Desktop Duplication\ntstr_OvrlPropsCaptureMethodGC=Graphics Capture\ntstr_OvrlPropsCaptureMethodGCUnsupportedTip=Graphics Capture wird von diesem System nicht unterstützt\ntstr_OvrlPropsCaptureMethodGCUnsupportedPartialTip=Einige Graphics Capture-Funktionen werden von diesem System nicht unterstützt\ntstr_OvrlPropsCaptureSource=Quelle\ntstr_OvrlPropsCaptureGCSource=Graphics Capture-Quelle\ntstr_OvrlPropsCaptureSourceUnknownWarning=Dieses Overlay verwendet eine unbekannte Erfassungsmethode und funktioniert möglicherweise nicht korrekt\ntstr_OvrlPropsCaptureGCStrictMatching=Verwende strikten Fensterabgleich\ntstr_OvrlPropsCaptureGCStrictMatchingTip=Erlaube nur exakte Fenstertitel-Übereinstimmungen, wenn dieses Overlay wiederhergestellt wird\n\ntstr_OvrlPropsPerfMonDesktopModeTip=Leistungsanzeige-Overlays werden im Desktopmodus nicht aktualisiert\ntstr_OvrlPropsPerfMonGlobalTip=Diese Einstellungen gelten für alle Leistungsanzeige-Overlays\ntstr_OvrlPropsPerfMonStyle=Stil\ntstr_OvrlPropsPerfMonItems=Elemente\ntstr_OvrlPropsPerfMonStyleMinimal=Minimal\ntstr_OvrlPropsPerfMonStyleCompact=Kompakt\ntstr_OvrlPropsPerfMonStyleLarge=Groß\ntstr_OvrlPropsPerfMonStyleShowWindow=Zeige Fenster\ntstr_OvrlPropsPerfMonStyleShowTextOutline=Zeige Textkontur\ntstr_OvrlPropsPerfMonStyleMinimalShowMore=Zeige mehr Elemente\ntstr_OvrlPropsPerfMonItemCPU=CPU Statistiken\ntstr_OvrlPropsPerfMonItemGPU=GPU Statistiken\ntstr_OvrlPropsPerfMonItemGraphs=Kurven\ntstr_OvrlPropsPerfMonItemFrameStats=Frame-Statistiken\ntstr_OvrlPropsPerfMonItemTime=Zeit\ntstr_OvrlPropsPerfMonItemBattery=Akku-Statistiken\ntstr_OvrlPropsPerfMonItemTrackerBattery=Tracker-Akkulevel\ntstr_OvrlPropsPerfMonItemViveWirelessTemp=Vive Wireless-Temperatur\ntstr_OvrlPropsPerfMonResetValues=Kumulative Werte zurücksetzen\n\ntstr_OvrlPropsBrowserNotAvailableTip=Die Desktop+ Browser-Komponente ist nicht installiert\ntstr_OvrlPropsBrowserCloned=Geklonte Ausgabe\ntstr_OvrlPropsBrowserClonedTip=Dieses Overlay ist ein Klon von \"%OVERLAYNAME%\".\\nÄnderungen an den Browser-Eigenschaften werden auf das Original und alle Overlays welche es klonen angewendet.\ntstr_OvrlPropsBrowserClonedConvert=Konvertiere zu eigenständigen Overlay\ntstr_OvrlPropsBrowserURL=URL\ntstr_OvrlPropsBrowserURLHint=Gebe eine Adresse ein\ntstr_OvrlPropsBrowserGo=Los\ntstr_OvrlPropsBrowserRestore=Letzte Eingabe wiederherstellen\ntstr_OvrlPropsBrowserWidth=Breite\ntstr_OvrlPropsBrowserHeight=Höhe\ntstr_OvrlPropsBrowserZoom=Zoom\ntstr_OvrlPropsBrowserAllowTransparency=Erlaube Transparenz\ntstr_OvrlPropsBrowserAllowTransparencyTip=Erlaubt Webseiten Transparenz zu nutzen.\\nSeiten, die keine Hintergrundfarbe definieren, werden möglicherweise nicht korrekt dargestellt.\ntstr_OvrlPropsBrowserRecreateContext=Erneuere Browser-Kontext\ntstr_OvrlPropsBrowserRecreateContextTip=Der Browser-Kontext muss neu erstellt werden um die Änderung anzuwenden.\\nDadurch wird die Seite neu geladen und der Navigationsverlauf zurückgesetzt.\n\ntstr_OvrlPropsAdvanced3D=3D\ntstr_OvrlPropsAdvancedHSBS=Halbes Nebeneinander (HSBS)\ntstr_OvrlPropsAdvancedSBS=Nebeneinander (SBS)\ntstr_OvrlPropsAdvancedHOU=Halbes Übereinander (HOU)\ntstr_OvrlPropsAdvancedOU=Übereinander (OU)\ntstr_OvrlPropsAdvanced3DSwap=Tausche Augen\ntstr_OvrlPropsAdvancedGazeFade=Gaze Fade\ntstr_OvrlPropsAdvancedGazeFadeAuto=Automatisch einstellen\ntstr_OvrlPropsAdvancedGazeFadeDistance=Distanz\ntstr_OvrlPropsAdvancedGazeFadeDistanceValueInf=Unendlich\ntstr_OvrlPropsAdvancedGazeFadeSensitivity=Empfindlichkeit\ntstr_OvrlPropsAdvancedGazeFadeOpacity=Ziel-Deckkraft\ntstr_OvrlPropsAdvancedInput=Laserpointer-Eingabe\ntstr_OvrlPropsAdvancedInputInGame=Aktiviere im Spiel\ntstr_OvrlPropsAdvancedInputFloatingUI=Zeige Floating UI\ntstr_OvrlPropsAdvancedOverlayTags=Overlay-Tags\ntstr_OvrlPropsAdvancedOverlayTagsTip=Overlay-Tags können verwendet werden um Overlays als Ziel in Aktionen festzulegen\n\ntstr_OvrlPropsPerformanceInvisibleUpdate=Aktualisiere wenn unsichtbar\ntstr_OvrlPropsPerformanceInvisibleUpdateTip=Aktualisiert das Overlay, auch wenn es aufgrund von Deckkrafts- oder Gaze Fade-Einstellung nicht sichtbar ist.\\nHilft Anwendungen von Drittanbietern auf den Overlayinhalt zuzugreifen.\\nIn anderen Fällen nicht empfohlen.\\nWenn das Overlay manuell oder durch den Anzeigemodus versteckt ist, sind Overlayupdates dennoch pausiert.\n\ntstr_OvrlPropsInterfaceOverlayName=Overlayname\ntstr_OvrlPropsInterfaceOverlayNameAuto=[Automatisch benennen]\ntstr_OvrlPropsInterfaceActionOrderCustom=Eigene Aktions-Schaltflächen\ntstr_OvrlPropsInterfaceDesktopButtons=Zeige Desktop-Schaltflächen\ntstr_OvrlPropsInterfaceExtraButtons=Zeige Zusatz-Schaltflächen\n\n;Overlay Bar\ntstr_OverlayBarOvrlHide=Verstecken\ntstr_OverlayBarOvrlShow=Zeigen\ntstr_OverlayBarOvrlClone=Klonen\ntstr_OverlayBarOvrlRemove=Entfernen\ntstr_OverlayBarOvrlRemoveConfirm=Wirklich?\ntstr_OverlayBarOvrlProperties=Eigenschaften...\ntstr_OverlayBarOvrlAddWindow=Fenster...\ntstr_OverlayBarTooltipOvrlAdd=Overlay hinzufügen\ntstr_OverlayBarTooltipSettings=Einstellungen\ntstr_OverlayBarTooltipResetHold=Halte um Fensterposition zurückzusetzen...\n\n;Floating UI\ntstr_FloatingUIHideOverlayTip=Verstecke Overlay\ntstr_FloatingUIHideOverlayHoldTip=Halte um Overlay zu entfernen...\ntstr_FloatingUIDragModeEnableTip=Aktiviere Verschiebemodus\ntstr_FloatingUIDragModeDisableTip=Deaktiviere Verschiebemodus\ntstr_FloatingUIDragModeHoldLockTip=Halte um Overlayposition zu sperren...\ntstr_FloatingUIDragModeHoldUnlockTip=Halte um Overlayposition zu entsperren...\ntstr_FloatingUIWindowAddTip=Füge aktives Fenster als Overlay hinzu\ntstr_FloatingUIActionBarShowTip=Zeige Aktions-Leiste\ntstr_FloatingUIActionBarHideTip=Verstecke Aktions-Leiste\ntstr_FloatingUIBrowserGoBackTip=Zurück zur vorherigen Seite\ntstr_FloatingUIBrowserGoForwardTip=Weiter zur nächsten Seite\ntstr_FloatingUIBrowserRefreshTip=Seite aktualisieren\ntstr_FloatingUIBrowserStopTip=Stoppe Seiten-Ladevorgang\ntstr_FloatingUIActionBarDesktopNext=Nächster Desktop\ntstr_FloatingUIActionBarDesktopPrev=Vorheriger Desktop\ntstr_FloatingUIActionBarEmpty=Keine Aktionen ausgewählt\n\n;Special Actions\ntstr_ActionNone=[Keine Aktion]\ntstr_ActionKeyboardShow=Zeige Tastatur\ntstr_ActionKeyboardHide=Verstecke Tastatur\n\n;Default Actions (translation IDs are matched to Action name string)\ntstr_DefActionShowKeyboard=Zeige Tastatur\ntstr_DefActionActiveWindowCrop=Zum aktiven Fenster zuschneiden\ntstr_DefActionActiveWindowCropLabel=Zum aktiven\\nFenster\\nzuschneiden\ntstr_DefActionSwitchTask=Wechsle Task\ntstr_DefActionToggleOverlays=Overlays umschalten\ntstr_DefActionToggleOverlaysLabel=Overlays\\numschalten\ntstr_DefActionMiddleMouse=Mittlere Maustaste\ntstr_DefActionMiddleMouseLabel=Mittlere\\nMaustaste\ntstr_DefActionBackMouse=Maus Zurücktaste\ntstr_DefActionBackMouseLabel=Maus\\nZurücktaste\ntstr_DefActionReadMe=Öffne LiesMich\ntstr_DefActionReadMeLabel=Öffne\\nLiesMich\ntstr_DefActionDashboardToggle=SteamVR Dashboard umschalten (Debug Befehl)\ntstr_DefActionDashboardToggleLabel=Dashboard\\numschalten\n\n;Performance Monitor (text space is very limited here, so keep it short or untranslated)\ntstr_PerformanceMonitorCPU=CPU\ntstr_PerformanceMonitorGPU=GPU\ntstr_PerformanceMonitorRAM=RAM:\ntstr_PerformanceMonitorVRAM=VRAM:\ntstr_PerformanceMonitorFrameTime=Frame-Zeit:\ntstr_PerformanceMonitorLoad=Auslastung:\ntstr_PerformanceMonitorFPS=FPS:\ntstr_PerformanceMonitorFPSAverage=Durchschnittliche FPS:\ntstr_PerformanceMonitorReprojectionRatio=Reprojektions-Ratio:\ntstr_PerformanceMonitorDroppedFrames=Verworfene Frames:\ntstr_PerformanceMonitorBatteryLeft=Linker Controller:\ntstr_PerformanceMonitorBatteryRight=Rechter Controller:\ntstr_PerformanceMonitorBatteryHMD=Headset:\ntstr_PerformanceMonitorBatteryTracker=Tracker %ID%:\ntstr_PerformanceMonitorBatteryDisconnected=N/V\ntstr_PerformanceMonitorViveWirelessTempNotAvailable=N/V\ntstr_PerformanceMonitorCompactCPU=CPU\ntstr_PerformanceMonitorCompactGPU=GPU\ntstr_PerformanceMonitorCompactFPS=FPS\ntstr_PerformanceMonitorCompactFPSAverage=AVG\ntstr_PerformanceMonitorCompactReprojectionRatio=% RPR\ntstr_PerformanceMonitorCompactDroppedFrames=VRW\ntstr_PerformanceMonitorCompactBattery=BAT\ntstr_PerformanceMonitorCompactBatteryLeft=L\ntstr_PerformanceMonitorCompactBatteryRight=R\ntstr_PerformanceMonitorCompactBatteryHMD=H\ntstr_PerformanceMonitorCompactBatteryTracker=T%ID%\ntstr_PerformanceMonitorCompactBatteryDisconnected=N/V\ntstr_PerformanceMonitorCompactViveWirelessTempNotAvailable=N/V\ntstr_PerformanceMonitorEmpty=Es sind keine Leistungsanzeige-Elemente aktiviert.\n\n;Aux UI\ntstr_AuxUIDragHintDocking=Loslassen um anzudocken\ntstr_AuxUIDragHintUndocking=Loslassen um abzudocken\ntstr_AuxUIDragHintOvrlLocked=Entsperre die Overlayposition um dieses Overlay zu bewegen\ntstr_AuxUIDragHintOvrlTheaterScreenBlocked=Die Overlayposition wird von der SteamVR-Kinoleinwand gesteuert\ntstr_AuxUIGazeFadeAutoHint=Schaue in die Mitte des Overlays und warte für %SECONDS% Sekunden...\ntstr_AuxUIGazeFadeAutoHintSingular=Schaue in die Mitte des Overlays und warte für %SECONDS% Sekunde...\n\ntstr_AuxUIQuickStartWelcomeHeader=Willkommen zu Desktop+!\ntstr_AuxUIQuickStartWelcomeBody=Diese kurze Einführung wird dich mit den grundlegenden Funktionen der Anwendung bekannt machen.\\nAusführlichere Informationen kannst du in der LiesMich und im Benutzerhandbuch finden.\\n\\nFalls du das hier überspringen möchtest, drücke einfach [Schließen].\ntstr_AuxUIQuickStartOverlaysHeader=Overlays\ntstr_AuxUIQuickStartOverlaysBody=Desktop+ ermöglicht es dir sogenannte Overlays zu erstellen, welche deine Desktops, einzelne Fenster, und mehr spiegeln.\\nAlle von dir erstellen Overlays werden unten in der Overlay-Leiste gelistet.\\n\\nOverlay-Eigenschaften können geändert werden, indem du auf eines der Overlay-Icons klickst und [Eigenschaften...] auswählst.\ntstr_AuxUIQuickStartOverlaysBody2=Neue Overlays können durch Drücken von [+] und Wahl einer Quelle/Overlay-Art aus der Liste erstellt werden.\\nEinige Arten, wie zum Beispiel Webbrowser-Overlays, sind nur verfügbar wenn die entsprechende Anwendungskomponente installiert ist.\\n\\nEinzelne Overlays oder komplette Layouts können in Profilen gespeichert werden.\\nDie aktive Overlaykonfiguration wird automatisch aus der letzten Sitzung wiederhergestellt.\ntstr_AuxUIQuickStartOverlayPropertiesHeader=Overlay-Eigenschaften\ntstr_AuxUIQuickStartOverlayPropertiesBody=Overlays haben eine Reihe von Anpassungsmöglichkeiten.\\nUrsprung und Einzeigemodus bestimmen wo und wann ein Overlay angezeigt wird.\\nBehalte diese Eigenschaften im Auge falls du das Gefühl hast dass ein Overlay sichtbar sein sollte, aber du es nicht finden kannst.\\nFalls letzteres nicht ausreicht, könnte ein Zurücksetzen der Position helfen.\\n\\nDu kannst ein Overlay an einen Bewegungscontroller docken, indem du es in die Nähe des Controllers ziehst.\ntstr_AuxUIQuickStartOverlayPropertiesBody2=Einige Eigenschaften sind standardmäßig ausgeblendet.\\nAktiviere \"Zeige erweiterte Einstellungen\" im Einstellungsfenster um alle Optionen zu sehen.\\n\\nDie Positionen von UI-Fenstern können durch längeres Gedrückthalten der zugehörigen Schaltflächen zurückgesetzt werden.\\nDoppel- und Rechtsklick können auch auf Overlay-Schaltflächen angewendet werden um Kürzel zu aktivieren.\ntstr_AuxUIQuickStartSettingsHeader=Einstellungen\ntstr_AuxUIQuickStartSettingsBody=Das Einstellungsfenster enthält alle globalen Einstellungen für Desktop+.\\nEs kann durch Drücken der Zahnrad-Schaltfläche am rechten Ende der Overlay-Leiste geöffnet werden.\\n\\nWie auch bei Overlays werden Änderungen an den Einstellungen automatisch gespeichert.\ntstr_AuxUIQuickStartProfilesHeader=Profile\ntstr_AuxUIQuickStartProfilesBody=Es gibt zwei Arten von Profilen in Desktop+.\\n\\nOverlay-Profile:\\nSpeichern ein oder mehrere Overlays mitsamt ihren Eigenschaften.\\nSie können manuell oder durch Aktionen und Anwendungs-Profilen geladen werden.\\n\\nAnwendungs-Profile:\\nWeisen ein Overlay-Profil und/oder Aktionen zu einer SteamVR-Anwendung zu, welche automatisch geladen werden wenn sie gestartet wird.\ntstr_AuxUIQuickStartActionsHeader=Aktionen\ntstr_AuxUIQuickStartActionsBody=Aktionen in Desktop+ sind eine Aneinanderreihung von Befehlen, die Controllereingaben und Anwendungs-Profile zugewiesen oder zur Aktions-Leiste von Overlays hinzugefügt werden können.\ntstr_AuxUIQuickStartActionsBody2=Standardmäßig werden Aktionen auf das Overlay angewendet, auf dem sie aktiviert wurden (für das Overlay gezeigter Aktions-Schaltfläche, Controllereingabe auf dem Overlay), oder auf dem fokussierten Overlay, falls letzteres nicht anwendbar ist.\\n\\nDas fokussierte Overlay ist das zuletzt geklickte Overlay.\\nIn vielen Fällen ist das Ziel-Overlay nicht wichtig, beispielsweise für simulierte Desktop-Eingaben.\\nIn anderen Fällen kann es nützlich sein ein anderes Overlay oder auch mehrere als Ziel zu verwenden.\ntstr_AuxUIQuickStartOverlayTagsHeader=Overlay-Tags\ntstr_AuxUIQuickStartOverlayTagsBody=Hierfür können Overlay-Tags verwendet werden.\\nEs gibt automatische Tags (grün hervorgehoben) und benutzerdefinierte Tags.\\nAutomatische Tags werden automatisch basierend auf den Eigenschaften welche sie repräsentieren zugewiesen.\\nBenutzerdefinierte Tags können manuell Overlays in den Overlay-Eigenschaften zugewiesen werden.\\n\\nBeachte dass Aktionen nur indirekt Controllertasten zugewiesen werden können. Die nummerierten Kürzel müssen auch mit einer Controllereingabe in den SteamVR-Controllerzuordnungen verbunden werden.\ntstr_AuxUIQuickStartSettingsEndBody=Es gibt noch viele weitere Einstellungen.\\nHabe keine Angst mit ihnen herumzuexperimentieren.\\n\\nFalls du doch steckenbleiben solltest, kannst du [Auf Standardeinstellungen zurücksetzen] am unteren Ende des Fensters drücken.\\nDu hast die Wahl nur bestimmte Elemente zurückzusetzen.\ntstr_AuxUIQuickStartFloatingUIHeader=Floating UI\ntstr_AuxUIQuickStartFloatingUIBody=Die als \"Floating UI\" bezeichnete Oberfläche ist sichtbar wenn auf ein Overlay gezeigt wird.\\nIm Dashboard ist sie permanent sichtbar, solange auf kein anderes Overlay gezeigt wird.\\n\\nDie Floating UI enthält grundlegende Elemente zum Anpassen des Overlays, wie das Umschalten des Zieh-Modus um das Overlay zu bewegen, sowie einen anpassbaren Bereich für Aktions-Schaltflächen.\ntstr_AuxUIQuickStartDesktopModeHeader=Desktopmodus\ntstr_AuxUIQuickStartDesktopModeBody=Desktop+ kann auch in einem Fenster auf dem Desktop konfiguriert werden.\\nDies kann für Aufgaben welche häufige Tastatureingaben benötigen oder um Änderungen ohne verbundene Bewegungscontroller vorzunehmen nützlich sein.\\nWährend fast alle Funktionen in beiden Modi verfügbar sind, ist der Tastaturlayout-Editor, mit dem die Layouts für die Desktop+ VR-Tastatur anpassen werden können, nur im Desktopmodus zugänglich.\\n\\nDu kannst ihn im Problembehandlungsabschnitt des Einstellungsfensters oder über das Desktop+-Symbol im Systemtray/Benachrichtigungsbereich aufrufen.\ntstr_AuxUIQuickStartEndHeader=LiesMich & Benutzerhandbuch\ntstr_AuxUIQuickStartEndBody=Dies sollte schon ausreichen um mit Desktop+ loszulegen.\\n\\nFalls du auf Probleme stößt oder nicht weiter kommst, schaue dir auch die LiesMich an. Die Schaltfläche in der Aktions-Leiste wird sie für dich öffnen.\\nDetaillierte Erklärungen zu jeder Option und Schritt-für-Schritt-Anleitungen für häufige Nutzungsszenarien findest du im Benutzerhandbuch.\\n\\nUm diese Einführung erneut anzuzeigen, drücke auf [Kurzanleitung anzeigen] unten rechts auf der \"Auf Standardeinstellungen zurücksetzen\" Seite.\ntstr_AuxUIQuickStartButtonNext=Nächste Seite\ntstr_AuxUIQuickStartButtonPrev=Vorherige Seite\ntstr_AuxUIQuickStartButtonClose=Schließen\n\n;Desktop Mode\ntstr_DesktopModeCatTools=Werkzeuge\ntstr_DesktopModeCatOverlays=Overlays\ntstr_DesktopModeToolSettings=Einstellungen\ntstr_DesktopModeToolActions=Aktionen\ntstr_DesktopModeOverlayListAdd=Overlay hinzufügen\ntstr_DesktopModePageAddWindowOverlayTitle=Fenster-Overlay hinzufügen\ntstr_DesktopModePageAddWindowOverlayHeader=Wähle ein Fenster\n\n;Keyboard Editor\ntstr_KeyboardEditorKeyListTitle=Tastenliste\ntstr_KeyboardEditorKeyListTabContextReplace=Ersetze Inhalt mit...\ntstr_KeyboardEditorKeyListTabContextClear=Leere Unterlayout\ntstr_KeyboardEditorKeyListRow=Reihe %ID%\ntstr_KeyboardEditorKeyListSpacing=[Abstand]\ntstr_KeyboardEditorKeyListKeyAdd=Neu\ntstr_KeyboardEditorKeyListKeyDuplicate=Dupl.\ntstr_KeyboardEditorKeyListKeyRemove=Entf.\n\ntstr_KeyboardEditorKeyPropertiesTitle=Tasten-Eigenschaften\ntstr_KeyboardEditorKeyPropertiesNoSelection=Keine Taste ausgewählt\ntstr_KeyboardEditorKeyPropertiesType=Typ\ntstr_KeyboardEditorKeyPropertiesTypeBlank=Leere Fläche\ntstr_KeyboardEditorKeyPropertiesTypeVirtualKey=Virtuelle Taste\ntstr_KeyboardEditorKeyPropertiesTypeVirtualKeyToggle=Virtuelle Taste (Umschalten)\ntstr_KeyboardEditorKeyPropertiesTypeVirtualKeyIsoEnter=Virtuelle Taste (ISO-Enter)\ntstr_KeyboardEditorKeyPropertiesTypeString=Zeichenkette\ntstr_KeyboardEditorKeyPropertiesTypeSublayoutToggle=Unterlayout wechseln\ntstr_KeyboardEditorKeyPropertiesTypeAction=Aktion\ntstr_KeyboardEditorKeyPropertiesTypeVirtualKeyIsoEnterTip=Platziere zwei \"Virtuelle Taste (ISO-Enter)\"-Tasten in aufeinanderfolgenden Reihen um eine ISO-Enter geformte Taste zu erstellen.\\nNur eine kombinierte Taste pro Unterlayout.\ntstr_KeyboardEditorKeyPropertiesTypeStringTip=Verwende den Zeichenketten-Tastentyp für Sonderzeichen und nicht-grundlegende Buchstaben.\\nDies erlaubt Desktop+ die korrekte Tastenkombination auf dem echten Tastaturlayout herauszufinden und Anwendungskompatibilität zu erhöhen.\ntstr_KeyboardEditorKeyPropertiesSize=Größe\ntstr_KeyboardEditorKeyPropertiesLabel=Beschriftung\ntstr_KeyboardEditorKeyPropertiesKeyCode=Tastencode\ntstr_KeyboardEditorKeyPropertiesString=Zeichenkette\ntstr_KeyboardEditorKeyPropertiesSublayout=Unterlayout\ntstr_KeyboardEditorKeyPropertiesAction=Aktion\ntstr_KeyboardEditorKeyPropertiesCluster=Tastengruppe\ntstr_KeyboardEditorKeyPropertiesClusterTip=Tastengruppen-Zuweisung wird verwendet um ausgewählte Tasten vom Laden auszuschließen.\\nUmschließende Leerfläche wird nicht entfernt. Weise Leere Flächen-Tasten den passenden Tastengruppen hinzu um sie korrekt auszuschließen.\ntstr_KeyboardEditorKeyPropertiesBlockModifiers=Blockiere Modifikatoren\ntstr_KeyboardEditorKeyPropertiesBlockModifiersTip=Lässt alle Modifikatoren-Tasten los, während diese Taste gedrückt wird\ntstr_KeyboardEditorKeyPropertiesNoRepeat=Nie wiederholen\ntstr_KeyboardEditorKeyPropertiesNoRepeatTip=Blockiert Wiederholung von Tasteneingabe, selbst wenn Tastenwiederholung eingeschaltet ist\n\ntstr_KeyboardEditorMetadataTitle=Layout-Metadaten\ntstr_KeyboardEditorMetadataName=Name\ntstr_KeyboardEditorMetadataAuthor=Autor\ntstr_KeyboardEditorMetadataHasAltGr=Hat AltGr\ntstr_KeyboardEditorMetadataHasAltGrTip=Gedrückte rechte Alt-Taste wechselt zum AltGr Unterlayout\ntstr_KeyboardEditorMetadataClusterPreview=Tastengruppen in der Vorschau:\ntstr_KeyboardEditorMetadataSave=Speichern...\ntstr_KeyboardEditorMetadataLoad=Laden...\ntstr_KeyboardEditorMetadataSavePopupTitle=Speichere Tastaturlayout\ntstr_KeyboardEditorMetadataSavePopupFilename=Dateiname\ntstr_KeyboardEditorMetadataSavePopupFilenameBlankTip=Dateiname darf nicht leer sein\ntstr_KeyboardEditorMetadataSavePopupConfirm=Speichere Layout\ntstr_KeyboardEditorMetadataSavePopupConfirmError=Speichern des Layouts ist fehlgeschlagen\ntstr_KeyboardEditorMetadataLoadPopupTitle=Lade Tastaturlayout\ntstr_KeyboardEditorMetadataLoadPopupConfirm=Lade Layout\n\ntstr_KeyboardEditorPreviewTitle=Tastaturvorschau\n\ntstr_KeyboardEditorSublayoutBase=Basis\ntstr_KeyboardEditorSublayoutShift=Shift\ntstr_KeyboardEditorSublayoutAltGr=AltGr\ntstr_KeyboardEditorSublayoutAux=Zusatz\n\n;General Dialog Strings\ntstr_DialogOk=Ok\ntstr_DialogCancel=Abbrechen\ntstr_DialogDone=Fertig\ntstr_DialogUndo=Rückgängig\ntstr_DialogRedo=Wiederholen\ntstr_DialogColorPickerHeader=Wähle eine Farbe\ntstr_DialogColorPickerCurrent=Aktuell\ntstr_DialogColorPickerOriginal=Original\ntstr_DialogProfilePickerHeader=Wähle ein Profil\ntstr_DialogProfilePickerNone=[Kein Profil]\ntstr_DialogActionPickerHeader=Wähle eine Aktion\ntstr_DialogActionPickerEmpty=Keine Aktionen verfügbar\ntstr_DialogIconPickerHeader=Wähle ein Icon\ntstr_DialogIconPickerHeaderTip=Eigene Icons können als PNG-Dateien zum \"images\\icons\\\" Ordner hinzugefügt werden\ntstr_DialogIconPickerNone=[Kein Icon]\ntstr_DialogKeyCodePickerHeader=Wähle einen Tastencode\ntstr_DialogKeyCodePickerHeaderHotkey=Wähle einen Hotkey\ntstr_DialogKeyCodePickerModifiers=Modifikator\ntstr_DialogKeyCodePickerKeyCode=Tastencode\ntstr_DialogKeyCodePickerKeyCodeHint=Filtere Liste\ntstr_DialogKeyCodePickerKeyCodeNone=[Keine]\ntstr_DialogKeyCodePickerFromInput=Von Eingabe...\ntstr_DialogKeyCodePickerFromInputPopup=Drücke eine beliebige Taste (auch Maustasten)...\ntstr_DialogKeyCodePickerFromInputPopupNoMouse=Drücke eine beliebige Taste...\ntstr_DialogWindowPickerHeader=Wähle ein Fenster\ntstr_DialogInputTagsHint=Filtere oder füge neuen Tag hinzu\n\n;Source Strings\ntstr_SourceDesktopAll=Gesamter Desktop\ntstr_SourceDesktopID=Desktop %ID%\ntstr_SourceWinRTNone=[Kein Erfassungsziel]\ntstr_SourceWinRTUnknown=[Unbekanntes Fenster]\ntstr_SourceWinRTClosed=[Geschlossen]:\ntstr_SourcePerformanceMonitor=Leistungsanzeige\ntstr_SourceBrowser=Browser\ntstr_SourceBrowserNoPage=[Keine Seite geladen]\n\n;Notification Icon\ntstr_NotificationIconRestoreVR=VR-Oberfläche wiederherstellen\ntstr_NotificationIconOpenOnDesktop=Einstellungen auf dem Desktop öffnen\ntstr_NotificationIconQuit=Beenden\n\n;Notifications\ntstr_NotificationInitialStartupTitleVR=Ersteinrichtung\ntstr_NotificationInitialStartupTitleDesktop=Desktop+ Ersteinrichtung\ntstr_NotificationInitialStartupMessage=Desktop+ wurde zu SteamVR hinzugefügt und startet ab jetzt automatisch wenn SteamVR ausgeführt wird.\n\n;Browser\ntstr_BrowserErrorPageTitle=Seitenladefehler - Desktop+\ntstr_BrowserErrorPageHeading=Die Seite konnte nicht geladen werden\ntstr_BrowserErrorPageMessage=Beim Laden von %URL% ist ein Fehler aufgetreten: %ERROR%\n"
  },
  {
    "path": "assets/lang/en.ini",
    "content": "[TranslationInfo]\nName=English\nLocale=en-US\n\n;If this language has issues with the default fonts or their loading order, a different font can be defined here which is then loaded first\n;This should be the file name with extension, and is loaded either from the OS fonts folder or the \"lang\" folder if it's missing from the former\nPreferredFont=\n\n;Set file name to ISO 639-1 language code for auto-detection support, with ISO 3166-1 region code after an underscore if necessary\n;To translate the SteamVR input bindings, add a new localization file to the \"input\" folder and adjust action_manifest.json\n\n[Strings]\n;Settings Window\ntstr_SettingsWindowTitle=Desktop+ Settings\n\ntstr_SettingsCatInterface=Interface\ntstr_SettingsCatEnvironment=Environment\ntstr_SettingsCatProfiles=Profiles\ntstr_SettingsCatActions=Actions\ntstr_SettingsCatKeyboard=Keyboard\ntstr_SettingsCatMouse=Mouse\ntstr_SettingsCatLaserPointer=Laser Pointer\ntstr_SettingsCatWindowOverlays=Window Overlays\ntstr_SettingsCatBrowser=Browser\ntstr_SettingsCatPerformance=Performance\ntstr_SettingsCatVersionInfo=Version Info\ntstr_SettingsCatWarnings=Warnings\ntstr_SettingsCatStartup=Startup\ntstr_SettingsCatTroubleshooting=Troubleshooting\n\ntstr_SettingsWarningPrefix=Warning:\ntstr_SettingsWarningCompositorResolution=SteamVR Compositor resolution is below 100%! This affects overlay rendering quality.\ntstr_SettingsWarningCompositorQuality=SteamVR Overlay render quality is not set to high!\ntstr_SettingsWarningProcessElevated=Desktop+ is running with administrative privileges!\ntstr_SettingsWarningElevatedMode=Elevated mode is active!\ntstr_SettingsWarningBrowserMissing=Browser overlays are being used, but the Desktop+ Browser component is currently not available.\ntstr_SettingsWarningBrowserMismatch=The installed Desktop+ Browser component is incompatible with this version of Desktop+!\ntstr_SettingsWarningElevatedProcessFocus=An elevated process has focus!\\nDesktop+ is unable to simulate input right now.\ntstr_SettingsWarningUIAccessLost=Desktop+ is no longer running with UIAccess privileges!\ntstr_SettingsWarningOverlayCreationErrorLimit=An overlay creation failed! (Maximum Overlay limit exceeded)\ntstr_SettingsWarningOverlayCreationErrorOther=An overlay creation failed! (%ERRORNAME%)\ntstr_SettingsWarningGraphicsCaptureError=An unexpected error occurred in a Graphics Capture thread! (%ERRORCODE%)\ntstr_SettingsWarningAppProfileActive=The application profile for %APPNAME% has overridden the current overlay layout.\\nChanges made to overlays are not saved automatically while it is active.\ntstr_SettingsWarningConfigMigrated=Configuration and profiles from the previous version of Desktop+ have been migrated into new formats.\\nThe original files were not deleted and can still be used with an older version of the application.\\nSee [Restore Default Settings] if you wish to start fresh instead.\ntstr_SettingsWarningMenuDontShowAgain=Don't show this again\ntstr_SettingsWarningMenuDismiss=Dismiss\n\ntstr_SettingsInterfaceLanguage=Language\ntstr_SettingsInterfaceLanguageCommunity=Community Translation by %AUTHOR%\ntstr_SettingsInterfaceLanguageIncompleteWarning=Translation may be incomplete\ntstr_SettingsInterfaceAdvancedSettings=Show Advanced Settings\ntstr_SettingsInterfaceAdvancedSettingsTip=Show advanced and less commonly used settings\ntstr_SettingsInterfaceBlankSpaceDrag=Drag Windows when Clicking on Blank Space\ntstr_SettingsInterfacePersistentUI=Persistent UI\ntstr_SettingsInterfacePersistentUIManage=Manage\ntstr_SettingsInterfaceDesktopButtons=Desktop Buttons Listing Style\ntstr_SettingsInterfaceDesktopButtonsNone=None\ntstr_SettingsInterfaceDesktopButtonsIndividual=Individual Desktops\ntstr_SettingsInterfaceDesktopButtonsCycle=Cycle Buttons\ntstr_SettingsInterfaceDesktopButtonsAddCombined=Add Combined Desktop Button\n\ntstr_SettingsInterfacePersistentUIHelp=Desktop+ remembers the state of the interface windows separately for general use (Global) and use within the Desktop+ SteamVR dashboard tab (Desktop+ Tab).\ntstr_SettingsInterfacePersistentUIHelp2=The following controls can be used to directly manipulate the state of those windows.\\nKeep in mind some states may require resetting the position in order to bring the window into a visible spot.\ntstr_SettingsInterfacePersistentUIWindowsHeader=Windows\ntstr_SettingsInterfacePersistentUIWindowsSettings=Settings\ntstr_SettingsInterfacePersistentUIWindowsProperties=Overlay Properties\ntstr_SettingsInterfacePersistentUIWindowsKeyboard=Desktop+ Keyboard\ntstr_SettingsInterfacePersistentUIWindowsStateGlobal=Global\ntstr_SettingsInterfacePersistentUIWindowsStateDashboardTab=Desktop+ Tab\ntstr_SettingsInterfacePersistentUIWindowsStateVisible=Visible\ntstr_SettingsInterfacePersistentUIWindowsStatePinned=Pinned\ntstr_SettingsInterfacePersistentUIWindowsStatePosition=Position\ntstr_SettingsInterfacePersistentUIWindowsStatePositionReset=Reset\ntstr_SettingsInterfacePersistentUIWindowsStateSize=Size\ntstr_SettingsInterfacePersistentUIWindowsStateLaunchRestore=Restore State on Desktop+ Launch\n\ntstr_SettingsEnvironmentBackgroundColor=Background Color\ntstr_SettingsEnvironmentBackgroundColorDispModeNever=Never Visible\ntstr_SettingsEnvironmentBackgroundColorDispModeDPlusTab=Only Visible in Desktop+ Tab\ntstr_SettingsEnvironmentBackgroundColorDispModeAlways=Always Visible\ntstr_SettingsEnvironmentDimInterface=Dim Interface\ntstr_SettingsEnvironmentDimInterfaceTip=Dims the SteamVR dashboard and Desktop+ interface while the Desktop+ dashboard tab is open\n\ntstr_SettingsProfilesOverlays=Overlay Profiles\ntstr_SettingsProfilesApps=Application Profiles\ntstr_SettingsProfilesManage=Manage\n\ntstr_SettingsProfilesOverlaysHeader=Manage Overlay Profiles\ntstr_SettingsProfilesOverlaysNameDefault=Default\ntstr_SettingsProfilesOverlaysNameNew=[New Profile]\ntstr_SettingsProfilesOverlaysNameNewBase=Profile %ID%\ntstr_SettingsProfilesOverlaysProfileLoad=Load Profile\ntstr_SettingsProfilesOverlaysProfileAdd=Add Overlays from Profile\ntstr_SettingsProfilesOverlaysProfileSave=Save Current Overlays\ntstr_SettingsProfilesOverlaysProfileDelete=Delete Profile\ntstr_SettingsProfilesOverlaysProfileDeleteConfirm=Really?\ntstr_SettingsProfilesOverlaysProfileFailedLoad=Failed to Load Profile\ntstr_SettingsProfilesOverlaysProfileFailedDelete=Failed to Delete Profile\ntstr_SettingsProfilesOverlaysProfileAddSelectHeader=Choose Overlays to Add from Profile\ntstr_SettingsProfilesOverlaysProfileAddSelectEmpty=This profile contains no overlays.\ntstr_SettingsProfilesOverlaysProfileAddSelectDo=Add Selected Overlays\ntstr_SettingsProfilesOverlaysProfileAddSelectAll=Select All\ntstr_SettingsProfilesOverlaysProfileAddSelectNone=Select None\ntstr_SettingsProfilesOverlaysProfileSaveSelectHeader=Save Current Overlays\ntstr_SettingsProfilesOverlaysProfileSaveSelectName=Profile Name\ntstr_SettingsProfilesOverlaysProfileSaveSelectNameErrorBlank=Name cannot be blank\ntstr_SettingsProfilesOverlaysProfileSaveSelectNameErrorTaken=Name is already in use\ntstr_SettingsProfilesOverlaysProfileSaveSelectHeaderList=Choose Overlays to Save in Profile\ntstr_SettingsProfilesOverlaysProfileSaveSelectDo=Save Selected Overlays\ntstr_SettingsProfilesOverlaysProfileSaveSelectDoFailed=Failed to Save Profile\n\ntstr_SettingsProfilesAppsHeader=Manage App Profiles\ntstr_SettingsProfilesAppsHeaderNoVRTip=Only existing application profiles are listed when Desktop+ isn't running\ntstr_SettingsProfilesAppsListEmpty=No Applications available\ntstr_SettingsProfilesAppsProfileHeaderActive=(Currently Active)\ntstr_SettingsProfilesAppsProfileEnabled=Activate when Application is Running\ntstr_SettingsProfilesAppsProfileOverlayProfile=Overlay Profile\ntstr_SettingsProfilesAppsProfileActionEnter=Entry Action\ntstr_SettingsProfilesAppsProfileActionLeave=Exit Action\n\ntstr_SettingsActionsManage=Actions\ntstr_SettingsActionsManageButton=Manage\ntstr_SettingsActionsButtonsOrderDefault=Action Buttons (Default)\ntstr_SettingsActionsButtonsOrderOverlayBar=Action Buttons (Overlay Bar)\ntstr_SettingsActionsShowBindings=Show Controller Bindings\ntstr_SettingsActionsActiveShortcuts=Active Controller Buttons\ntstr_SettingsActionsActiveShortcutsTip=Controller bindings when pointing at an overlay.\\nConfigure the VR Dashboard controller bindings to change which buttons these are.\ntstr_SettingsActionsActiveShortuctsHome=\"Go Home\"\ntstr_SettingsActionsActiveShortuctsBack=\"Go Back\"\ntstr_SettingsActionsGlobalShortcuts=Global Controller Buttons\ntstr_SettingsActionsGlobalShortcutsTip=Controller bindings when the dashboard is closed and not pointing at an overlay.\\nConfigure the Desktop+ controller bindings to change which buttons these are.\ntstr_SettingsActionsGlobalShortcutsEntry=Global Shortcut #%ID%\ntstr_SettingsActionsGlobalShortcutsAdd=Add Shortcut\ntstr_SettingsActionsGlobalShortcutsRemove=Remove Last Shortcut\ntstr_SettingsActionsHotkeys=Hotkeys\ntstr_SettingsActionsHotkeysTip=System-wide keyboard shortcuts.\\nHotkeys block other applications from receiving that input and may not work if the same combination has already been registered elsewhere.\ntstr_SettingsActionsHotkeysAdd=Add Hotkey\ntstr_SettingsActionsHotkeysRemove=Remove\ntstr_SettingsActionsTableHeaderAction=Action\ntstr_SettingsActionsTableHeaderShortcut=Shortcut\ntstr_SettingsActionsTableHeaderHotkey=Hotkey\n\ntstr_SettingsActionsManageHeader=Manage Actions\ntstr_SettingsActionsManageCopyUID=Copy UID to Clipboard\ntstr_SettingsActionsManageNew=New Action...\ntstr_SettingsActionsManageEdit=Edit Action\ntstr_SettingsActionsManageDuplicate=Duplicate Action\ntstr_SettingsActionsManageDelete=Delete Action\ntstr_SettingsActionsManageDeleteConfirm=Really?\ntstr_SettingsActionsManageDuplicatedName=%NAME% (Copy)\n\ntstr_SettingsActionsEditHeader=Edit Action\ntstr_SettingsActionsEditName=Name\ntstr_SettingsActionsEditNameTranslatedTip=This action currently uses a translation string ID as a name to automatically match the chosen application language\ntstr_SettingsActionsEditTarget=Target\ntstr_SettingsActionsEditTargetDefault=Default\ntstr_SettingsActionsEditTargetDefaultTip=Targets the overlay the action was activated on or the focused overlay if the former is not applicable\ntstr_SettingsActionsEditTargetUseTags=Use Tags\ntstr_SettingsActionsEditTargetActionTarget=Action Target\ntstr_SettingsActionsEditHeaderAppearance=Button Appearance\ntstr_SettingsActionsEditIcon=Icon\ntstr_SettingsActionsEditLabel=Label\ntstr_SettingsActionsEditLabelTranslatedTip=This action currently uses a translation string ID as a label to automatically match the chosen application language\ntstr_SettingsActionsEditHeaderCommands=Commands\ntstr_SettingsActionsEditNameNew=New Action\ntstr_SettingsActionsEditCommandAdd=Add Command\ntstr_SettingsActionsEditCommandDelete=Delete Command\ntstr_SettingsActionsEditCommandDeleteConfirm=Really?\ntstr_SettingsActionsEditCommandType=Command Type\ntstr_SettingsActionsEditCommandTypeNone=None\ntstr_SettingsActionsEditCommandTypeKey=Press Key\ntstr_SettingsActionsEditCommandTypeMousePos=Set Mouse Position\ntstr_SettingsActionsEditCommandTypeString=Type String\ntstr_SettingsActionsEditCommandTypeLaunchApp=Launch Application\ntstr_SettingsActionsEditCommandTypeShowKeyboard=Show Keyboard\ntstr_SettingsActionsEditCommandTypeCropActiveWindow=Crop to Active Window\ntstr_SettingsActionsEditCommandTypeShowOverlay=Show Overlay\ntstr_SettingsActionsEditCommandTypeSwitchTask=Switch Task\ntstr_SettingsActionsEditCommandTypeLoadOverlayProfile=Load Overlay Profile\ntstr_SettingsActionsEditCommandTypeUnknown=Unknown\ntstr_SettingsActionsEditCommandVisibilityToggle=Toggle\ntstr_SettingsActionsEditCommandVisibilityShow=Always Show\ntstr_SettingsActionsEditCommandVisibilityHide=Always Hide\ntstr_SettingsActionsEditCommandUndo=Undo on Release\ntstr_SettingsActionsEditCommandKeyCode=Key Code\ntstr_SettingsActionsEditCommandKeyToggle=Toggle Key\ntstr_SettingsActionsEditCommandMouseX=X\ntstr_SettingsActionsEditCommandMouseY=Y\ntstr_SettingsActionsEditCommandMouseUseCurrent=Use Current Mouse Position\ntstr_SettingsActionsEditCommandString=String\ntstr_SettingsActionsEditCommandPath=Executable Path\ntstr_SettingsActionsEditCommandPathTip=This can also be a normal file or URL\ntstr_SettingsActionsEditCommandArgs=Application Arguments\ntstr_SettingsActionsEditCommandArgsTip=These are passed to the application.\\nIf unsure, leave this blank.\ntstr_SettingsActionsEditCommandVisibility=Visibility\ntstr_SettingsActionsEditCommandSwitchingMethod=Switching Method\ntstr_SettingsActionsEditCommandSwitchingMethodSwitcher=Show Task Switcher\ntstr_SettingsActionsEditCommandSwitchingMethodFocus=Focus Window\ntstr_SettingsActionsEditCommandWindow=Window\ntstr_SettingsActionsEditCommandWindowNone=[No Window]\ntstr_SettingsActionsEditCommandWindowStrictMatchingTip=Only allow exact window title matches when looking for a window to switch to\ntstr_SettingsActionsEditCommandCursorWarp=Warp Cursor into Window\ntstr_SettingsActionsEditCommandProfile=Profile\ntstr_SettingsActionsEditCommandProfileClear=Remove Existing Overlays\ntstr_SettingsActionsEditCommandDescNone=No Command\ntstr_SettingsActionsEditCommandDescKey=Press Key \"%KEYNAME%\"\ntstr_SettingsActionsEditCommandDescKeyToggle=Toggle Key \"%KEYNAME%\"\ntstr_SettingsActionsEditCommandDescMousePos=Set Mouse Position to %X%, %Y%\ntstr_SettingsActionsEditCommandDescString=Type String \"%STRING%\"\ntstr_SettingsActionsEditCommandDescLaunchApp=Launch Application \"%APP%\" %ARGSOPT%\ntstr_SettingsActionsEditCommandDescLaunchAppArgsOpt=\"%ARGS%\"\ntstr_SettingsActionsEditCommandDescKeyboardToggle=Toggle Keyboard\ntstr_SettingsActionsEditCommandDescKeyboardShow=Show Keyboard\ntstr_SettingsActionsEditCommandDescKeyboardHide=Hide Keyboard\ntstr_SettingsActionsEditCommandDescCropWindow=Crop to Active Window\ntstr_SettingsActionsEditCommandDescOverlayToggle=Toggle Overlay: %TAGS%\ntstr_SettingsActionsEditCommandDescOverlayShow=Show Overlay: %TAGS%\ntstr_SettingsActionsEditCommandDescOverlayHide=Hide Overlay: %TAGS%\ntstr_SettingsActionsEditCommandDescOverlayTargetDefault=[Action Target]\ntstr_SettingsActionsEditCommandDescSwitchTask=Switch Task\ntstr_SettingsActionsEditCommandDescSwitchTaskWindow=Switch Task to \"%WINDOW%\"\ntstr_SettingsActionsEditCommandDescLoadOverlayProfile=Load Overlay Profile \"%PROFILE%\"\ntstr_SettingsActionsEditCommandDescLoadOverlayProfileAdd=Add Overlay Profile \"%PROFILE%\"\ntstr_SettingsActionsEditCommandDescUnknown=Unknown Command\n\ntstr_SettingsActionsOrderHeader=Change Action Order\ntstr_SettingsActionsOrderButtonLabel=%COUNT% Actions Selected\ntstr_SettingsActionsOrderButtonLabelSingular=%COUNT% Action Selected\ntstr_SettingsActionsOrderNoActions=No Actions Selected\ntstr_SettingsActionsOrderAdd=Add Actions...\ntstr_SettingsActionsOrderRemove=Remove Action\n\ntstr_SettingsActionsAddSelectorHeader=Choose Actions to Add\ntstr_SettingsActionsAddSelectorAdd=Add Selected Actions\n\ntstr_SettingsKeyboardLayout=Keyboard Layout\ntstr_SettingsKeyboardSize=Size\ntstr_SettingsKeyboardBehavior=Behavior\ntstr_SettingsKeyboardStickyMod=Sticky Modifier Keys\ntstr_SettingsKeyboardKeyRepeat=Key Repeat\ntstr_SettingsKeyboardAutoShow=Show Automatically\ntstr_SettingsKeyboardAutoShowDesktopOnly=Show Automatically\ntstr_SettingsKeyboardAutoShowDesktop=For Desktop & Window Overlays\ntstr_SettingsKeyboardAutoShowDesktopTip=Experimental. Does not work with all applications.\ntstr_SettingsKeyboardAutoShowBrowser=For Browser Overlays\ntstr_SettingsKeyboardLayoutAuthor=Created by %AUTHOR%\ntstr_SettingsKeyboardKeyClusters=Key Clusters\ntstr_SettingsKeyboardKeyClusterBase=Base\ntstr_SettingsKeyboardKeyClusterFunction=Function\ntstr_SettingsKeyboardKeyClusterNavigation=Navigation\ntstr_SettingsKeyboardKeyClusterNumpad=Numpad\ntstr_SettingsKeyboardKeyClusterExtra=Extra\ntstr_SettingsKeyboardSwitchToEditor=Switch to Keyboard Layout Editor\n\ntstr_SettingsMouseShowCursor=Show Cursor\ntstr_SettingsMouseShowCursorGCUnsupported=Disabling the cursor for Graphics Capture overlays is not supported on this system\ntstr_SettingsMouseShowCursorGCActiveWarning=Active Graphics Capture mirrors may stop the cursor from being hidden in Desktop Duplication overlays\ntstr_SettingsMouseScrollSmooth=Use Smooth Scrolling\ntstr_SettingsMouseSimulatePen=Simulate as Pen Input\ntstr_SettingsMouseSimulatePenUnsupported=Pen input simulation is not supported on this system\ntstr_SettingsMouseAllowLaserPointerOverride=Allow Laser Pointer Override\ntstr_SettingsMouseAllowLaserPointerOverrideTip=Disables the laser pointer when the physical mouse is moved rapidly.\\nClick on an overlay to get the laser pointer back.\ntstr_SettingsMouseDoubleClickAssist=Double-Click Assistant\ntstr_SettingsMouseDoubleClickAssistTip=Freezes the mouse cursor for the set duration to ease the input of double-clicks\ntstr_SettingsMouseDoubleClickAssistTipValueOff=Off\ntstr_SettingsMouseDoubleClickAssistTipValueAuto=Auto\ntstr_SettingsMouseSmoothing=Input Smoothing\ntstr_SettingsMouseSmoothingLevelNone=None\ntstr_SettingsMouseSmoothingLevelVeryLow=Very Low\ntstr_SettingsMouseSmoothingLevelLow=Low\ntstr_SettingsMouseSmoothingLevelMedium=Medium\ntstr_SettingsMouseSmoothingLevelHigh=High\ntstr_SettingsMouseSmoothingLevelVeryHigh=Very High\n\ntstr_SettingsLaserPointerTip=These settings apply to Desktop+'s laser pointer used when the SteamVR dashboard is closed\ntstr_SettingsLaserPointerBlockInput=Block Game Input while Active\ntstr_SettingsLaserPointerAutoToggleDistance=Auto-Activation Max Distance\ntstr_SettingsLaserPointerAutoToggleDistanceValueOff=Off\ntstr_SettingsLaserPointerHMDPointer=Gaze-based HMD Pointer\ntstr_SettingsLaserPointerHMDPointerTableHeaderInputAction=Input Action\ntstr_SettingsLaserPointerHMDPointerTableHeaderBinding=Keyboard Key\ntstr_SettingsLaserPointerHMDPointerTableBindingToggle=Toggle Laser Pointer\ntstr_SettingsLaserPointerHMDPointerTableBindingLeft=Left Mouse Button\ntstr_SettingsLaserPointerHMDPointerTableBindingRight=Right Mouse Button\ntstr_SettingsLaserPointerHMDPointerTableBindingMiddle=Middle Mouse Button\ntstr_SettingsLaserPointerHMDPointerTableBindingDrag=Drag Overlay\n\ntstr_SettingsWindowOverlaysAutoFocus=Focus Window when Pointing at Overlay\ntstr_SettingsWindowOverlaysKeepOnScreen=Keep Window on Screen\ntstr_SettingsWindowOverlaysKeepOnScreenTip=Automatically move source window inside the screen's work area if its bounds lie outside of it\ntstr_SettingsWindowOverlaysAutoSizeOverlay=Adjust Overlay Size when Window Resizes\ntstr_SettingsWindowOverlaysFocusSceneApp=Focus Game when Laser Pointer Leaves Overlay\ntstr_SettingsWindowOverlaysFocusSceneAppDashboard=Focus Game after Closing Dashboard\ntstr_SettingsWindowOverlaysOnWindowDrag=On Window Drag\ntstr_SettingsWindowOverlaysOnWindowDragDoNothing=Do Nothing\ntstr_SettingsWindowOverlaysOnWindowDragBlock=Block Drag\ntstr_SettingsWindowOverlaysOnWindowDragOverlay=Drag Overlay\ntstr_SettingsWindowOverlaysOnCaptureLoss=On Capture Loss\ntstr_SettingsWindowOverlaysOnCaptureLossTip=Behavior when a window capture gets lost, usually from the target window being closed.\\n\"Hide Overlay\" also shows the overlay again when the capture gets restored.\ntstr_SettingsWindowOverlaysOnCaptureLossDoNothing=Do Nothing\ntstr_SettingsWindowOverlaysOnCaptureLossHide=Hide Overlay\ntstr_SettingsWindowOverlaysOnCaptureLossRemove=Remove Overlay\n\ntstr_SettingsBrowserMaxFrameRate=Maximum Frame Rate\ntstr_SettingsBrowserMaxFrameRateOverrideOff=Global Setting\ntstr_SettingsBrowserContentBlocker=Content Blocker\ntstr_SettingsBrowserContentBlockerTip=Add block lists in the Adblock Plus syntax to the \"DesktopPlusBrowser\\content_block\" directory.\\nAll lists in the directory will be loaded.\ntstr_SettingsBrowserContentBlockerListCount=(%LISTCOUNT% Lists Active)\ntstr_SettingsBrowserContentBlockerListCountSingular=(%LISTCOUNT% List Active)\n\ntstr_SettingsPerformanceUpdateLimiter=Limit Updates\ntstr_SettingsPerformanceUpdateLimiterMode=Update Limiter Mode\ntstr_SettingsPerformanceUpdateLimiterModeOff=Off\ntstr_SettingsPerformanceUpdateLimiterModeMS=Frame Time\ntstr_SettingsPerformanceUpdateLimiterModeFPS=Frame Rate\ntstr_SettingsPerformanceUpdateLimiterModeOffOverride=Use Global Setting\ntstr_SettingsPerformanceUpdateLimiterModeMSTip=\"Frame Time\" enforces a minimum interval between overlay updates\ntstr_SettingsPerformanceUpdateLimiterFPSValue=%FPS% fps\ntstr_SettingsPerformanceUpdateLimiterOverride=Override Update Limit\ntstr_SettingsPerformanceUpdateLimiterOverrideTip=When multiple overrides are active, the one resulting in the highest update rate is used\ntstr_SettingsPerformanceUpdateLimiterModeOverride=Override Update Limiter Mode\ntstr_SettingsPerformanceRapidUpdates=Reduce Laser Pointer Latency\ntstr_SettingsPerformanceRapidUpdatesTip=Reduce latency of laser pointer input at the cost of additional CPU load while pointing at an overlay\ntstr_SettingsPerformanceSingleDesktopMirror=Single Desktop Mirroring\ntstr_SettingsPerformanceSingleDesktopMirrorTip=Mirror individual desktops when switching to them instead of cropping from the combined desktop.\\nWhen this is active, all overlays will be showing the same desktop.\ntstr_SettingsPerformanceAlternativeCursorRendering=Alternative Cursor Rendering\ntstr_SettingsPerformanceAlternativeCursorRenderingTip=Use a different method to capture and render the cursor.\\nThis can help if there are problems with the default method of capturing the hardware cursor.\ntstr_SettingsPerformanceUseHDR=HDR Mirroring\ntstr_SettingsPerformanceUseHDRTip=Mirror desktops and windows using higher bit-depth textures, supporting HDR output. Experimental.\\nMay negatively impact performance when not required and increases VRAM usage.\ntstr_SettingsPerformanceShowFPS=Show FPS in Floating UI\n\ntstr_SettingsWarningsHidden=Warnings Hidden:\ntstr_SettingsWarningsReset=Reset Hidden Warnings\n\ntstr_SettingsStartupAutoLaunch=Launch Automatically on SteamVR Startup\ntstr_SettingsStartupSteamDisable=Disable Steam Integration\ntstr_SettingsStartupSteamDisableTip=Restarts Desktop+ without Steam when it was launched by it.\\nThis disables the permanent in-app status, usage time statistics and other Steam features.\n\ntstr_SettingsTroubleshootingRestart=Restart\ntstr_SettingsTroubleshootingRestartSteam=Restart with Steam\ntstr_SettingsTroubleshootingRestartDesktop=Restart in Desktop Mode\ntstr_SettingsTroubleshootingElevatedModeEnter=Enter Elevated Mode\ntstr_SettingsTroubleshootingElevatedModeLeave=Leave Elevated Mode\ntstr_SettingsTroubleshootingSettingsReset=Restore Default Settings\ntstr_SettingsTroubleshootingSettingsResetConfirmDescription=Restoring default settings will reset the selected elements to their default values.\\nThis cannot be undone.\\n\\nReset the following:\ntstr_SettingsTroubleshootingSettingsResetConfirmButton=Reset Selected Elements\ntstr_SettingsTroubleshootingSettingsResetConfirmElementOverlays=Current Overlay Setup\ntstr_SettingsTroubleshootingSettingsResetConfirmElementLegacyFiles=Delete Unused Legacy Configuration & Profile Files\ntstr_SettingsTroubleshootingSettingsResetShowQuickStart=Show Quick-Start Guide\n\n;Keyboard Window\ntstr_KeyboardWindowTitle=Desktop+ Keyboard\ntstr_KeyboardWindowTitleSettings=Desktop+ Keyboard (Settings)\ntstr_KeyboardWindowTitleOverlay=Desktop+ Keyboard (%OVERLAYNAME%)\ntstr_KeyboardWindowTitleOverlayUnknown=[Unknown Overlay]\n\n;Keyboard Shortcuts Window\ntstr_KeyboardShortcutsCut=Cut\ntstr_KeyboardShortcutsCopy=Copy\ntstr_KeyboardShortcutsPaste=Paste\n\n;Overlay Properties Window\ntstr_OvrlPropsCatPosition=Position\ntstr_OvrlPropsCatAppearance=Appearance\ntstr_OvrlPropsCatCapture=Capture\ntstr_OvrlPropsCatPerformanceMonitor=Performance Monitor\ntstr_OvrlPropsCatBrowser=Browser\ntstr_OvrlPropsCatAdvanced=Advanced\ntstr_OvrlPropsCatPerformance=Performance\ntstr_OvrlPropsCatInterface=Interface\n\ntstr_OvrlPropsPositionOrigin=Origin\ntstr_OvrlPropsPositionOriginRoom=Play Area\ntstr_OvrlPropsPositionOriginHMDXY=HMD Floor Position\ntstr_OvrlPropsPositionOriginDashboard=Dashboard\ntstr_OvrlPropsPositionOriginHMD=HMD\ntstr_OvrlPropsPositionOriginSeatedSpace=Seated Position\ntstr_OvrlPropsPositionOriginControllerR=Right Controller\ntstr_OvrlPropsPositionOriginControllerL=Left Controller\ntstr_OvrlPropsPositionOriginTheaterScreen=Theater Screen\ntstr_OvrlPropsPositionOriginTracker1=Tracker #1\ntstr_OvrlPropsPositionOriginConfigHMDXYTurning=Turn with HMD\ntstr_OvrlPropsPositionOriginConfigTheaterScreenEnter=Enter Theater Mode\ntstr_OvrlPropsPositionOriginConfigTheaterScreenLeave=Leave Theater Mode\ntstr_OvrlPropsPositionOriginConfigSmoothing=Origin Smoothing\ntstr_OvrlPropsPositionOriginTheaterScreenTip=Some properties are controlled by the SteamVR Theater Screen\ntstr_OvrlPropsPositionDispMode=Display Mode\ntstr_OvrlPropsPositionDispModeAlways=Always\ntstr_OvrlPropsPositionDispModeDashboard=Only in Dashboard\ntstr_OvrlPropsPositionDispModeScene=Only In-Game\ntstr_OvrlPropsPositionDispModeDPlus=Only in Desktop+ Tab\ntstr_OvrlPropsPositionPos=Position\ntstr_OvrlPropsPositionPosTip=Position can only be changed or reset while Desktop+ is running\ntstr_OvrlPropsPositionChange=Change\ntstr_OvrlPropsPositionReset=Reset\ntstr_OvrlPropsPositionLock=Lock\n\ntstr_OvrlPropsPositionChangeHeader=Change Overlay Position\ntstr_OvrlPropsPositionChangeHelp=Drag any overlay around to change its position.\\nHold right-click for two-handed gesture transform.\ntstr_OvrlPropsPositionChangeHelpDesktop=Hold down the drag buttons (\"D\") to move or rotate the overlay with the mouse.\ntstr_OvrlPropsPositionChangeManualAdjustment=Manual Adjustment\ntstr_OvrlPropsPositionChangeMove=Move\ntstr_OvrlPropsPositionChangeRotate=Rotate\ntstr_OvrlPropsPositionChangeForward=Forward\ntstr_OvrlPropsPositionChangeBackward=Backward\ntstr_OvrlPropsPositionChangeRollCW=Roll ⟳\ntstr_OvrlPropsPositionChangeRollCCW=Roll ⟲\ntstr_OvrlPropsPositionChangeLookAt=To HMD\ntstr_OvrlPropsPositionChangeDragButton=D\ntstr_OvrlPropsPositionChangeOffset=Additional Offset\ntstr_OvrlPropsPositionChangeOffsetUpDown=Up/Down Offset\ntstr_OvrlPropsPositionChangeOffsetRightLeft=Right/Left Offset\ntstr_OvrlPropsPositionChangeOffsetForwardBackward=Forward/Backward Offset\ntstr_OvrlPropsPositionChangeDragSettings=Drag Settings\ntstr_OvrlPropsPositionChangeDragSettingsAutoDocking=Dock to Controller when Near\ntstr_OvrlPropsPositionChangeDragSettingsForceDistance=Force Fixed Distance\ntstr_OvrlPropsPositionChangeDragSettingsForceDistanceShape=Shape\ntstr_OvrlPropsPositionChangeDragSettingsForceDistanceShapeSphere=Sphere\ntstr_OvrlPropsPositionChangeDragSettingsForceDistanceShapeCylinder=Cylinder\ntstr_OvrlPropsPositionChangeDragSettingsForceDistanceAutoCurve=Auto-Curve\ntstr_OvrlPropsPositionChangeDragSettingsForceDistanceAutoTilt=Auto-Tilt\ntstr_OvrlPropsPositionChangeDragSettingsSnapPosition=Snap Position\ntstr_OvrlPropsPositionChangeDragSettingsSnapRotation=Snap Rotation\ntstr_OvrlPropsPositionChangeDragSettingsSnapRotationPitch=Pitch\ntstr_OvrlPropsPositionChangeDragSettingsSnapRotationYaw=Yaw\ntstr_OvrlPropsPositionChangeDragSettingsSnapRotationRoll=Roll\n\ntstr_OvrlPropsAppearanceWidth=Width\ntstr_OvrlPropsAppearanceCurve=Curvature\ntstr_OvrlPropsAppearanceOpacity=Opacity\ntstr_OvrlPropsAppearanceBrightness=Brightness\ntstr_OvrlPropsAppearanceCrop=Crop\ntstr_OvrlPropsAppearanceCropValueMax=Max\ntstr_OvrlPropsAppearanceShowBackside=Show Backside\n\ntstr_OvrlPropsCrop=Cropping Area\ntstr_OvrlPropsCropHelp=Drag the rectangle to change the crop.\\nUse scroll input or drag the edges to adjust the size of the cropping rectangle.\ntstr_OvrlPropsCropManualAdjust=Manual Adjustment\ntstr_OvrlPropsCropInvalidTip=The current cropping rectangle is invalid. The overlay may not be visible as a result.\ntstr_OvrlPropsCropX=X\ntstr_OvrlPropsCropY=Y\ntstr_OvrlPropsCropWidth=Width\ntstr_OvrlPropsCropHeight=Height\ntstr_OvrlPropsCropToWindow=Crop to Active Window\n\ntstr_OvrlPropsCaptureMethod=Capture Method\ntstr_OvrlPropsCaptureMethodDup=Desktop Duplication\ntstr_OvrlPropsCaptureMethodGC=Graphics Capture\ntstr_OvrlPropsCaptureMethodGCUnsupportedTip=Graphics Capture is not supported on this system\ntstr_OvrlPropsCaptureMethodGCUnsupportedPartialTip=Some Graphics Capture features are not supported on this system\ntstr_OvrlPropsCaptureSource=Source\ntstr_OvrlPropsCaptureGCSource=Graphics Capture Source\ntstr_OvrlPropsCaptureSourceUnknownWarning=This overlay uses an unknown capture source and may not work correctly\ntstr_OvrlPropsCaptureGCStrictMatching=Use Strict Window Matching\ntstr_OvrlPropsCaptureGCStrictMatchingTip=Only allow exact window title matches when restoring this overlay's capture\n\ntstr_OvrlPropsPerfMonDesktopModeTip=Performance Monitor overlays do not update in desktop mode\ntstr_OvrlPropsPerfMonGlobalTip=These settings apply to all Performance Monitor overlays\ntstr_OvrlPropsPerfMonStyle=Style\ntstr_OvrlPropsPerfMonItems=Items\ntstr_OvrlPropsPerfMonStyleMinimal=Minimal\ntstr_OvrlPropsPerfMonStyleCompact=Compact\ntstr_OvrlPropsPerfMonStyleLarge=Large\ntstr_OvrlPropsPerfMonStyleShowWindow=Show Window\ntstr_OvrlPropsPerfMonStyleShowTextOutline=Show Text Outline\ntstr_OvrlPropsPerfMonStyleMinimalShowMore=Show More Items\ntstr_OvrlPropsPerfMonItemCPU=CPU Stats\ntstr_OvrlPropsPerfMonItemGPU=GPU Stats\ntstr_OvrlPropsPerfMonItemGraphs=Graphs\ntstr_OvrlPropsPerfMonItemFrameStats=Frame Stats\ntstr_OvrlPropsPerfMonItemTime=Time\ntstr_OvrlPropsPerfMonItemBattery=Battery Stats\ntstr_OvrlPropsPerfMonItemTrackerBattery=Tracker Battery Levels\ntstr_OvrlPropsPerfMonItemViveWirelessTemp=Vive Wireless Temperature\ntstr_OvrlPropsPerfMonResetValues=Reset Cumulative Values\n\ntstr_OvrlPropsBrowserNotAvailableTip=Desktop+ Browser component is not installed\ntstr_OvrlPropsBrowserCloned=Cloned Output\ntstr_OvrlPropsBrowserClonedTip=This overlay is a clone of \"%OVERLAYNAME%\".\\nChanges done to the Browser properties will apply to the original and all overlays cloning from it.\ntstr_OvrlPropsBrowserClonedConvert=Convert to Standalone\ntstr_OvrlPropsBrowserURL=URL\ntstr_OvrlPropsBrowserURLHint=Enter an Address\ntstr_OvrlPropsBrowserGo=Go\ntstr_OvrlPropsBrowserRestore=Restore Last Input\ntstr_OvrlPropsBrowserWidth=Width\ntstr_OvrlPropsBrowserHeight=Height\ntstr_OvrlPropsBrowserZoom=Zoom\ntstr_OvrlPropsBrowserAllowTransparency=Allow Transparency\ntstr_OvrlPropsBrowserAllowTransparencyTip=Allows web pages to use transparency.\\nSites not defining any background color may not display correctly.\ntstr_OvrlPropsBrowserRecreateContext=Recreate Browser Context\ntstr_OvrlPropsBrowserRecreateContextTip=The browser context needs to be recreated to apply the change.\\nDoing so will reload the page and clear the navigation history.\n\ntstr_OvrlPropsAdvanced3D=3D\ntstr_OvrlPropsAdvancedHSBS=Half Side-by-Side\ntstr_OvrlPropsAdvancedSBS=Side-by-Side\ntstr_OvrlPropsAdvancedHOU=Half Over-Under\ntstr_OvrlPropsAdvancedOU=Over-Under\ntstr_OvrlPropsAdvanced3DSwap=Swap Eyes\ntstr_OvrlPropsAdvancedGazeFade=Gaze Fade\ntstr_OvrlPropsAdvancedGazeFadeAuto=Auto-Configure\ntstr_OvrlPropsAdvancedGazeFadeDistance=Distance\ntstr_OvrlPropsAdvancedGazeFadeDistanceValueInf=Infinite\ntstr_OvrlPropsAdvancedGazeFadeSensitivity=Sensitivity\ntstr_OvrlPropsAdvancedGazeFadeOpacity=Target Opacity\ntstr_OvrlPropsAdvancedInput=Laser Pointer Input\ntstr_OvrlPropsAdvancedInputInGame=Enable In-Game\ntstr_OvrlPropsAdvancedInputFloatingUI=Show Floating UI\ntstr_OvrlPropsAdvancedOverlayTags=Overlay Tags\ntstr_OvrlPropsAdvancedOverlayTagsTip=Overlay tags are used to target overlays in actions\n\ntstr_OvrlPropsPerformanceInvisibleUpdate=Update while Invisible\ntstr_OvrlPropsPerformanceInvisibleUpdateTip=Update overlay even while invisible from opacity setting or Gaze Fade.\\nHelps with third-party applications accessing the overlay's contents.\\nNot recommended otherwise.\\nUpdates are still suspended if the overlay is hidden manually or by display mode setting.\n\ntstr_OvrlPropsInterfaceOverlayName=Overlay Name\ntstr_OvrlPropsInterfaceOverlayNameAuto=[Name Automatically]\ntstr_OvrlPropsInterfaceActionOrderCustom=Override Action Buttons\ntstr_OvrlPropsInterfaceDesktopButtons=Show Desktop Buttons\ntstr_OvrlPropsInterfaceExtraButtons=Show Extra Buttons\n\n;Overlay Bar\ntstr_OverlayBarOvrlHide=Hide\ntstr_OverlayBarOvrlShow=Show\ntstr_OverlayBarOvrlClone=Clone\ntstr_OverlayBarOvrlRemove=Remove\ntstr_OverlayBarOvrlRemoveConfirm=Really?\ntstr_OverlayBarOvrlProperties=Properties...\ntstr_OverlayBarOvrlAddWindow=Window...\ntstr_OverlayBarTooltipOvrlAdd=Add Overlay\ntstr_OverlayBarTooltipSettings=Settings\ntstr_OverlayBarTooltipResetHold=Hold to reset window position...\n\n;Floating UI\ntstr_FloatingUIHideOverlayTip=Hide Overlay\ntstr_FloatingUIHideOverlayHoldTip=Hold to remove overlay...\ntstr_FloatingUIDragModeEnableTip=Enable Drag-Mode\ntstr_FloatingUIDragModeDisableTip=Disable Drag-Mode\ntstr_FloatingUIDragModeHoldLockTip=Hold to lock overlay position...\ntstr_FloatingUIDragModeHoldUnlockTip=Hold to unlock overlay position...\ntstr_FloatingUIWindowAddTip=Add Active Window as Overlay\ntstr_FloatingUIActionBarShowTip=Show Action-Bar\ntstr_FloatingUIActionBarHideTip=Hide Action-Bar\ntstr_FloatingUIBrowserGoBackTip=Go to Previous Page\ntstr_FloatingUIBrowserGoForwardTip=Go to Next Page\ntstr_FloatingUIBrowserRefreshTip=Refresh Current Page\ntstr_FloatingUIBrowserStopTip=Stop Page Load\ntstr_FloatingUIActionBarDesktopPrev=Previous Desktop\ntstr_FloatingUIActionBarDesktopNext=Next Desktop\ntstr_FloatingUIActionBarEmpty=No actions enabled\n\n;Special Actions\ntstr_ActionNone=[None]\ntstr_ActionKeyboardShow=Show Keyboard\ntstr_ActionKeyboardHide=Hide Keyboard\n\n;Default Actions (translation IDs are matched to Action name string)\ntstr_DefActionShowKeyboard=Show Keyboard\ntstr_DefActionActiveWindowCrop=Crop to Active Window\ntstr_DefActionActiveWindowCropLabel=Crop to\\nActive\\nWindow\ntstr_DefActionSwitchTask=Switch Task\ntstr_DefActionToggleOverlays=Toggle Overlays\ntstr_DefActionToggleOverlaysLabel=Toggle\\nOverlays\ntstr_DefActionMiddleMouse=Middle Mouse Button\ntstr_DefActionMiddleMouseLabel=Middle\\nMouse\\nButton\ntstr_DefActionBackMouse=Back Mouse Button\ntstr_DefActionBackMouseLabel=Back\\nMouse\\nButton\ntstr_DefActionReadMe=Open ReadMe\ntstr_DefActionReadMeLabel=Open\\nReadMe\ntstr_DefActionDashboardToggle=Toggle SteamVR Dashboard (Debug Command)\ntstr_DefActionDashboardToggleLabel=Toggle\\nDashboard\n\n;Performance Monitor (text space is very limited here, so keep it short or untranslated)\ntstr_PerformanceMonitorCPU=CPU\ntstr_PerformanceMonitorGPU=GPU\ntstr_PerformanceMonitorRAM=RAM:\ntstr_PerformanceMonitorVRAM=VRAM:\ntstr_PerformanceMonitorFrameTime=Frame Time:\ntstr_PerformanceMonitorLoad=Load:\ntstr_PerformanceMonitorFPS=FPS:\ntstr_PerformanceMonitorFPSAverage=Average FPS:\ntstr_PerformanceMonitorReprojectionRatio=Reprojection Ratio:\ntstr_PerformanceMonitorDroppedFrames=Dropped Frames:\ntstr_PerformanceMonitorBatteryLeft=Left Controller:\ntstr_PerformanceMonitorBatteryRight=Right Controller:\ntstr_PerformanceMonitorBatteryHMD=Headset:\ntstr_PerformanceMonitorBatteryTracker=Tracker %ID%:\ntstr_PerformanceMonitorBatteryDisconnected=N/A\ntstr_PerformanceMonitorViveWirelessTempNotAvailable=N/A\ntstr_PerformanceMonitorCompactCPU=CPU\ntstr_PerformanceMonitorCompactGPU=GPU\ntstr_PerformanceMonitorCompactFPS=FPS\ntstr_PerformanceMonitorCompactFPSAverage=AVG\ntstr_PerformanceMonitorCompactReprojectionRatio=% RPR\ntstr_PerformanceMonitorCompactDroppedFrames=DRP\ntstr_PerformanceMonitorCompactBattery=BAT\ntstr_PerformanceMonitorCompactBatteryLeft=L\ntstr_PerformanceMonitorCompactBatteryRight=R\ntstr_PerformanceMonitorCompactBatteryHMD=H\ntstr_PerformanceMonitorCompactBatteryTracker=T%ID%\ntstr_PerformanceMonitorCompactBatteryDisconnected=N/A\ntstr_PerformanceMonitorCompactViveWirelessTempNotAvailable=N/A\ntstr_PerformanceMonitorEmpty=No Performance Monitor Items enabled.\n\n;Aux UI\ntstr_AuxUIDragHintDocking=Release to dock\ntstr_AuxUIDragHintUndocking=Release to undock\ntstr_AuxUIDragHintOvrlLocked=Unlock overlay position to drag this overlay\ntstr_AuxUIDragHintOvrlTheaterScreenBlocked=The overlay's position is controlled by the SteamVR Theater Screen\ntstr_AuxUIGazeFadeAutoHint=Look at the center of the overlay and wait for %SECONDS% seconds...\ntstr_AuxUIGazeFadeAutoHintSingular=Look at the center of the overlay and wait for %SECONDS% second...\n\ntstr_AuxUIQuickStartWelcomeHeader=Welcome to Desktop+!\ntstr_AuxUIQuickStartWelcomeBody=This short guide will introduce you to the very basics of the application.\\nFor more detailed information see the ReadMe and User Guide.\\n\\nIf you wish to skip this, simply press [Close].\ntstr_AuxUIQuickStartOverlaysHeader=Overlays\ntstr_AuxUIQuickStartOverlaysBody=Desktop+ allows you to create overlays mirroring your desktops, individual windows and more.\\nAll overlays you've created will be listed in the Overlay Bar down below.\\n\\nOverlay properties can be modified by clicking on one of the overlay icons and choosing [Properties...].\ntstr_AuxUIQuickStartOverlaysBody2=New overlays can be created by pressing [+] and choosing a capture source/overlay type from the list.\\nSome types, such as web browser overlays, are only available if the respective application component is installed.\\n\\nIndividual overlays or complete layouts can be saved in profiles.\\nThe current overlay setup will automatically be remembered between sessions.\ntstr_AuxUIQuickStartOverlayPropertiesHeader=Overlay Properties\ntstr_AuxUIQuickStartOverlayPropertiesBody=Overlays have a variety of customization options.\\nOrigin and display mode set where and when an overlay is displayed.\\nRemember to review these properties when you feel an overlay should be visible but you can't find it.\\nResetting its position may also help if the former doesn't.\\n\\nYou can also dock an overlay to motion controllers by dragging it close to one.\ntstr_AuxUIQuickStartOverlayPropertiesBody2=Some properties are hidden by default.\\nToggle \"Show Advanced Settings\" in the Settings window to show all options.\\n\\nThe position of UI windows can be reset by long-pressing their respective button.\\nDouble- and right-click works on overlay buttons as well, as quick shortcut toggles.\ntstr_AuxUIQuickStartSettingsHeader=Settings\ntstr_AuxUIQuickStartSettingsBody=The Settings window contains all global settings for Desktop+.\\nIt can be opened by pressing the gear button at the right end of the Overlay Bar.\\n\\nJust like for overlays, changes to settings are applied and saved automatically.\ntstr_AuxUIQuickStartProfilesHeader=Profiles\ntstr_AuxUIQuickStartProfilesBody=There two kinds of profiles in Desktop+.\\n\\nOverlay Profiles:\\nStore one or multiple overlays with their properties.\\nThey can be loaded manually or as a result of actions and application profiles.\\n\\nApplication Profiles:\\nAssign an overlay profile and/or actions to be loaded automatically when a SteamVR application is launched.\ntstr_AuxUIQuickStartActionsHeader=Actions\ntstr_AuxUIQuickStartActionsBody=Actions in Desktop+ are a series of commands that can be assigned to controller inputs and application profiles, or added to the Action Bar of overlays.\\nAction commands range from simulating inputs on the desktop to making changes to the overlay layout.\\n\nExternal applications can also be launched for advanced use-cases.\\n\\nDesktop+ comes with a few example actions that you can check out.\ntstr_AuxUIQuickStartActionsBody2=Actions by default target the overlay that it was activated on (action button displayed for the overlay, controller input on the overlay) or the focused overlay if the former isn't applicable.\\n\\nThe focused overlay is the overlay that was last clicked into.\\nIn many cases the target overlay doesn't matter, such as input simulated on the desktop. In others it may be useful to specify a different target overlay or even multiple.\ntstr_AuxUIQuickStartOverlayTagsHeader=Overlay Tags\ntstr_AuxUIQuickStartOverlayTagsBody=Overlay tags can be used for this purpose.\\nThere are automatic tags (green label) and user-defined tags.\\nAutomatic tags are assigned automatically based on the property they represent.\\nUser-defined tags can be assigned manually to overlays in the Overlay Properties window.\\n\\nNote that actions are only indirectly bound to controller buttons for global shortcuts. The numbered global shortcut also has to be assigned to a controller input via the SteamVR's input binding interface.\ntstr_AuxUIQuickStartSettingsEndBody=There are many more settings available.\\nDon't be afraid to play with the toggles to check them out.\\n\\nIf you do get stuck, you can press [Restore Default Settings] at the bottom of the window.\\nYou get the option to only restore specific elements.\ntstr_AuxUIQuickStartFloatingUIHeader=Floating UI\ntstr_AuxUIQuickStartFloatingUIBody=The interface referred to as \"Floating UI\" is displayed whenever you point at an overlay.\\nIn the dashboard it is always visible if no other Overlay is being pointed at.\\n\\nThe Floating UI contains basic controls for modifying the overlay, such as toggling Drag Mode to allow moving it, as well as a customizable section for action buttons.\\n\\nDepending on the overlay type there may also be other controls.\\nDisplay of those can be toggled for each overlay in the respective overlay properties.\ntstr_AuxUIQuickStartDesktopModeHeader=Desktop Mode\ntstr_AuxUIQuickStartDesktopModeBody=Desktop+ can also be configured in a window displayed on the desktop.\\nThis can be useful for tasks that require frequent keyboard input or to make changes without motion controllers connected.\\n\\nWhile almost all functionality is the same in both modes, the Keyboard Layout Editor used to customize layouts for the Desktop+ VR keyboard is only accessible in desktop mode.\\n\\nYou can switch to it in the Troubleshooting section of the Settings window, or from the Desktop+ icon in the system tray/notification area.\ntstr_AuxUIQuickStartEndHeader=ReadMe & User Guide\ntstr_AuxUIQuickStartEndBody=This should already be enough to get you started with Desktop+.\\n\\nIf you run into any trouble or get stuck, make sure to check out the ReadMe as well. The button in the Action Bar will open it for you.\\nDetailed explanations of each option and step-by-step guides for common usage scenarios can be found the in the user guide.\\n\\nTo see this guide again, press [Show Quick-Start Guide] at the bottom right of the \"Restore Default Settings\" page.\ntstr_AuxUIQuickStartButtonNext=Next Page\ntstr_AuxUIQuickStartButtonPrev=Previous Page\ntstr_AuxUIQuickStartButtonClose=Close\n\n;Desktop Mode\ntstr_DesktopModeCatTools=Tools\ntstr_DesktopModeCatOverlays=Overlays\ntstr_DesktopModeToolSettings=Settings\ntstr_DesktopModeToolActions=Actions\ntstr_DesktopModeOverlayListAdd=Add Overlay\ntstr_DesktopModePageAddWindowOverlayTitle=Add Window Overlay\ntstr_DesktopModePageAddWindowOverlayHeader=Choose a Window\n\n;Keyboard Editor\ntstr_KeyboardEditorKeyListTitle=Key List\ntstr_KeyboardEditorKeyListTabContextReplace=Replace Contents With...\ntstr_KeyboardEditorKeyListTabContextClear=Clear Sub-Layout\ntstr_KeyboardEditorKeyListRow=Row %ID%\ntstr_KeyboardEditorKeyListSpacing=[Spacing]\ntstr_KeyboardEditorKeyListKeyAdd=Add\ntstr_KeyboardEditorKeyListKeyDuplicate=Duplicate\ntstr_KeyboardEditorKeyListKeyRemove=Remove\n\ntstr_KeyboardEditorKeyPropertiesTitle=Key Properties\ntstr_KeyboardEditorKeyPropertiesNoSelection=No Key Selected\ntstr_KeyboardEditorKeyPropertiesType=Type\ntstr_KeyboardEditorKeyPropertiesTypeBlank=Blank Space\ntstr_KeyboardEditorKeyPropertiesTypeVirtualKey=Virtual Key\ntstr_KeyboardEditorKeyPropertiesTypeVirtualKeyToggle=Virtual Key (Toggle)\ntstr_KeyboardEditorKeyPropertiesTypeVirtualKeyIsoEnter=Virtual Key (ISO-Enter)\ntstr_KeyboardEditorKeyPropertiesTypeString=String\ntstr_KeyboardEditorKeyPropertiesTypeSublayoutToggle=Toggle Sub-Layout\ntstr_KeyboardEditorKeyPropertiesTypeAction=Action\ntstr_KeyboardEditorKeyPropertiesTypeVirtualKeyIsoEnterTip=Use two \"Virtual Key (ISO-Enter)\" keys in adjacent rows to build an ISO-Enter shaped key.\\nOnly one combined key in each sublayout.\ntstr_KeyboardEditorKeyPropertiesTypeStringTip=Use string type for non-basic character keys.\\nThis allows Desktop+ to figure out the correct key combination on the real keyboard layout to increase application compatibility.\ntstr_KeyboardEditorKeyPropertiesSize=Size\ntstr_KeyboardEditorKeyPropertiesLabel=Label\ntstr_KeyboardEditorKeyPropertiesKeyCode=Key Code\ntstr_KeyboardEditorKeyPropertiesString=String\ntstr_KeyboardEditorKeyPropertiesSublayout=Sub-Layout\ntstr_KeyboardEditorKeyPropertiesAction=Action\ntstr_KeyboardEditorKeyPropertiesCluster=Cluster\ntstr_KeyboardEditorKeyPropertiesClusterTip=Cluster assignment is used to selectively disable loading of keys.\\nSurrounding blank space is not removed. Carefully assign blank space type keys to clusters in order to have them toggle correctly.\ntstr_KeyboardEditorKeyPropertiesBlockModifiers=Block Modifiers\ntstr_KeyboardEditorKeyPropertiesBlockModifiersTip=Releases all modifier keys while the key is pressed\ntstr_KeyboardEditorKeyPropertiesNoRepeat=Never Repeat\ntstr_KeyboardEditorKeyPropertiesNoRepeatTip=Block key input from repeating while holding down, even if key repeat is enabled\n\ntstr_KeyboardEditorMetadataTitle=Layout Metadata\ntstr_KeyboardEditorMetadataName=Name\ntstr_KeyboardEditorMetadataAuthor=Author\ntstr_KeyboardEditorMetadataHasAltGr=Has AltGr\ntstr_KeyboardEditorMetadataHasAltGrTip=Right alt key switches to AltGr sub-layout when down\ntstr_KeyboardEditorMetadataClusterPreview=Preview Clusters:\ntstr_KeyboardEditorMetadataSave=Save...\ntstr_KeyboardEditorMetadataLoad=Load...\ntstr_KeyboardEditorMetadataSavePopupTitle=Save Keyboard Layout\ntstr_KeyboardEditorMetadataSavePopupFilename=File Name\ntstr_KeyboardEditorMetadataSavePopupFilenameBlankTip=File name must not be blank\ntstr_KeyboardEditorMetadataSavePopupConfirm=Save Layout\ntstr_KeyboardEditorMetadataSavePopupConfirmError=Failed to save the layout\ntstr_KeyboardEditorMetadataLoadPopupTitle=Load Keyboard Layout\ntstr_KeyboardEditorMetadataLoadPopupConfirm=Load Layout\n\ntstr_KeyboardEditorPreviewTitle=Keyboard Preview\n\ntstr_KeyboardEditorSublayoutBase=Base\ntstr_KeyboardEditorSublayoutShift=Shift\ntstr_KeyboardEditorSublayoutAltGr=AltGr\ntstr_KeyboardEditorSublayoutAux=Aux\n\n;General Dialog Strings\ntstr_DialogOk=Ok\ntstr_DialogCancel=Cancel\ntstr_DialogDone=Done\ntstr_DialogUndo=Undo\ntstr_DialogRedo=Redo\ntstr_DialogColorPickerHeader=Pick a Color\ntstr_DialogColorPickerCurrent=Current\ntstr_DialogColorPickerOriginal=Original\ntstr_DialogProfilePickerHeader=Pick a Profile\ntstr_DialogProfilePickerNone=[None]\ntstr_DialogActionPickerHeader=Pick an Action\ntstr_DialogActionPickerEmpty=No actions available\ntstr_DialogIconPickerHeader=Pick an Icon\ntstr_DialogIconPickerHeaderTip=Custom icons can be added as PNG files in the \"images\\icons\\\" directory\ntstr_DialogIconPickerNone=[No Icon]\ntstr_DialogKeyCodePickerHeader=Pick a Key Code\ntstr_DialogKeyCodePickerHeaderHotkey=Pick a Hotkey\ntstr_DialogKeyCodePickerModifiers=Modifiers\ntstr_DialogKeyCodePickerKeyCode=Key Code\ntstr_DialogKeyCodePickerKeyCodeHint=Filter List\ntstr_DialogKeyCodePickerKeyCodeNone=[None]\ntstr_DialogKeyCodePickerFromInput=From Input...\ntstr_DialogKeyCodePickerFromInputPopup=Press any key or mouse button...\ntstr_DialogKeyCodePickerFromInputPopupNoMouse=Press any key...\ntstr_DialogWindowPickerHeader=Pick a Window\ntstr_DialogInputTagsHint=Filter or add new tag\n\n;Source Strings\ntstr_SourceDesktopAll=Combined Desktop\ntstr_SourceDesktopID=Desktop %ID%\ntstr_SourceWinRTNone=[No Capture Target]\ntstr_SourceWinRTUnknown=[Unknown Window]\ntstr_SourceWinRTClosed=[Closed]:\ntstr_SourcePerformanceMonitor=Performance Monitor\ntstr_SourceBrowser=Browser\ntstr_SourceBrowserNoPage=[No Page Loaded]\n\n;Notification Icon\ntstr_NotificationIconRestoreVR=Restore VR Interface\ntstr_NotificationIconOpenOnDesktop=Open Settings on Desktop\ntstr_NotificationIconQuit=Quit\n\n;Notifications\ntstr_NotificationInitialStartupTitleVR=Initial Setup\ntstr_NotificationInitialStartupTitleDesktop=Desktop+ Initial Setup\ntstr_NotificationInitialStartupMessage=Desktop+ has been successfully added to SteamVR and will now automatically launch when SteamVR is run.\n\n;Browser\ntstr_BrowserErrorPageTitle=Page Load Error - Desktop+\ntstr_BrowserErrorPageHeading=The page could not be loaded\ntstr_BrowserErrorPageMessage=There was an error trying to load %URL%: %ERROR%\n"
  },
  {
    "path": "assets/lang/ja.ini",
    "content": "[TranslationInfo]\nName=日本語 (Japanese)\nAuthor=まるまさ\nLocale=ja-JP\n\n[Strings]\n;Settings Window\ntstr_SettingsWindowTitle=Desktop+ 設定\n\ntstr_SettingsCatInterface=インターフェイス\ntstr_SettingsCatEnvironment=システム構成\ntstr_SettingsCatProfiles=プロファイル\ntstr_SettingsCatActions=アクション\ntstr_SettingsCatKeyboard=キーボード\ntstr_SettingsCatMouse=マウス\ntstr_SettingsCatLaserPointer=レーザーポインター\ntstr_SettingsCatWindowOverlays=画面のオーバーレイ\ntstr_SettingsCatBrowser=ブラウザ\ntstr_SettingsCatPerformance=パフォーマンス\ntstr_SettingsCatVersionInfo=バージョン情報\ntstr_SettingsCatWarnings=警告\ntstr_SettingsCatStartup=スタートアップ\ntstr_SettingsCatTroubleshooting=トラブルシューティング\n\ntstr_SettingsWarningPrefix=警告:\ntstr_SettingsWarningCompositorResolution=SteamVR の内部解像度が100%を下回っています! これはオーバーレイのレンダリング品質に影響します。\ntstr_SettingsWarningCompositorQuality=SteamVR オーバーレイのレンダリング品質が高くありません!\ntstr_SettingsWarningProcessElevated=Desktop+ は管理者権限で実行されています!\ntstr_SettingsWarningElevatedMode=管理者モードが有効です!\ntstr_SettingsWarningBrowserMissing=ブラウザオーバーレイが使用されていますが、Desktop+ ブラウザコンポーネントは現在使用できません。\ntstr_SettingsWarningBrowserMismatch=インストールされている Desktop+ ブラウザは、このバージョンの Desktop+ と互換性がありません!\ntstr_SettingsWarningElevatedProcessFocus=管理者プロセスがフォーカスされています!\\nDesktop+ は現在、入力のシミュレーションができません。\ntstr_SettingsWarningUIAccessLost=Desktop+ がUIアクセス権限で実行されなくなりました!\ntstr_SettingsWarningOverlayCreationErrorLimit=オーバーレイの作成に失敗しました! (オーバーレイの上限を超えました)\ntstr_SettingsWarningOverlayCreationErrorOther=オーバーレイの作成に失敗しました! (%ERRORNAME%)\ntstr_SettingsWarningGraphicsCaptureError=グラフィックキャプチャースレッドで予期しないエラーが発生しました! (%ERRORCODE%)\ntstr_SettingsWarningAppProfileActive=%APPNAME% のアプリケーションプロファイルが、現在のオーバーレイのレイアウトを上書きしました。\\nオーバーレイに加えられた内容は、それが無効になるまで自動では保存されません。\ntstr_SettingsWarningConfigMigrated=以前のバージョンの Desktop+ の設定とプロファイルは新しい形式に移行されました。\\n元のファイルは削除されていないため、アプリケーションの古いバージョンでも引き続き使用できます。\\n最初からやり直す場合は、[初期設定に戻す] を参照してください。\ntstr_SettingsWarningMenuDontShowAgain=次回から表示しない\ntstr_SettingsWarningMenuDismiss=閉じる\n\ntstr_SettingsInterfaceLanguage=言語\ntstr_SettingsInterfaceLanguageCommunity=%AUTHOR% によるコミュニティ翻訳\ntstr_SettingsInterfaceLanguageIncompleteWarning=翻訳が不正確な場合があります\ntstr_SettingsInterfaceAdvancedSettings=詳細設定を表示\ntstr_SettingsInterfaceAdvancedSettingsTip=高度な設定とあまり使用されない設定\ntstr_SettingsInterfaceBlankSpaceDrag=空白スペースをクリックするとウィンドウがドラッグ\ntstr_SettingsInterfacePersistentUI=永続的なUI\ntstr_SettingsInterfacePersistentUIManage=管理\ntstr_SettingsInterfaceDesktopButtons=デスクトップボタンのスタイル\ntstr_SettingsInterfaceDesktopButtonsNone=なし\ntstr_SettingsInterfaceDesktopButtonsIndividual=個別\ntstr_SettingsInterfaceDesktopButtonsCycle=サイクルボタン\ntstr_SettingsInterfaceDesktopButtonsAddCombined=複合デスクトップ追加ボタン\n\ntstr_SettingsInterfacePersistentUIHelp=Desktop+ は、一般用 (グローバル) と、Desktop+ SteamVR ダッシュボードタブ (Desktop+ タブ) 内での使用に分けて、インターフェイスのウィンドウの状態を記憶します。\ntstr_SettingsInterfacePersistentUIHelp2=コントローラーを使って、ウィンドウの状態を直接操作することができます。\\n場合によっては、ウィンドウを表示可能な場所まで移動させるために、位置をリセットする必要があることに注意してください。\ntstr_SettingsInterfacePersistentUIWindowsHeader=ウィンドウズ\ntstr_SettingsInterfacePersistentUIWindowsSettings=設定\ntstr_SettingsInterfacePersistentUIWindowsProperties=オーバーレイのプロパティ\ntstr_SettingsInterfacePersistentUIWindowsKeyboard=Desktop+ キーボード\ntstr_SettingsInterfacePersistentUIWindowsStateGlobal=グローバル\ntstr_SettingsInterfacePersistentUIWindowsStateDashboardTab=Desktop+ タブ\ntstr_SettingsInterfacePersistentUIWindowsStateVisible=表示\ntstr_SettingsInterfacePersistentUIWindowsStatePinned=ピン留め\ntstr_SettingsInterfacePersistentUIWindowsStatePosition=位置\ntstr_SettingsInterfacePersistentUIWindowsStatePositionReset=リセット\ntstr_SettingsInterfacePersistentUIWindowsStateSize=大きさ\ntstr_SettingsInterfacePersistentUIWindowsStateLaunchRestore=Desktop+ の起動時の状態を復元\n\ntstr_SettingsEnvironmentBackgroundColor=背景の色\ntstr_SettingsEnvironmentBackgroundColorDispModeNever=常に非表示\ntstr_SettingsEnvironmentBackgroundColorDispModeDPlusTab=Desktop+ タブでのみ表示\ntstr_SettingsEnvironmentBackgroundColorDispModeAlways=常に表示\ntstr_SettingsEnvironmentDimInterface=インターフェイスを暗くする\ntstr_SettingsEnvironmentDimInterfaceTip=Desktop+ のダッシュボードタブを開いている間、SteamVR のダッシュボードと Desktop+ のインターフェイスを暗くします。\n\ntstr_SettingsProfilesOverlays=オーバーレイのプロファイル\ntstr_SettingsProfilesApps=アプリケーションプロファイル\ntstr_SettingsProfilesManage=管理\n\ntstr_SettingsProfilesOverlaysHeader=オーバーレイのプロファイルの管理\ntstr_SettingsProfilesOverlaysNameDefault=初期設定\ntstr_SettingsProfilesOverlaysNameNew=[新規プロファイル]\ntstr_SettingsProfilesOverlaysNameNewBase=プロファイル %ID%\ntstr_SettingsProfilesOverlaysProfileLoad=プロファイル読み込み\ntstr_SettingsProfilesOverlaysProfileAdd=プロファイル追加\ntstr_SettingsProfilesOverlaysProfileSave=現在のオーバーレイを保存\ntstr_SettingsProfilesOverlaysProfileDelete=プロファイル削除\ntstr_SettingsProfilesOverlaysProfileDeleteConfirm=本当にいい?\ntstr_SettingsProfilesOverlaysProfileFailedLoad=プロファイルの読み込みに失敗しました\ntstr_SettingsProfilesOverlaysProfileFailedDelete=プロファイルの削除に失敗しました\ntstr_SettingsProfilesOverlaysProfileAddSelectHeader=プロファイル\ntstr_SettingsProfilesOverlaysProfileAddSelectEmpty=このプロファイルにはオーバーレイが含まれていません。\ntstr_SettingsProfilesOverlaysProfileAddSelectDo=選択したオーバーレイを追加する\ntstr_SettingsProfilesOverlaysProfileAddSelectAll=すべて選択\ntstr_SettingsProfilesOverlaysProfileAddSelectNone=選択解除\ntstr_SettingsProfilesOverlaysProfileSaveSelectHeader=現在のオーバーレイを保存する\ntstr_SettingsProfilesOverlaysProfileSaveSelectName=プロファイル名\ntstr_SettingsProfilesOverlaysProfileSaveSelectNameErrorBlank=名前を空白にすることはできません\ntstr_SettingsProfilesOverlaysProfileSaveSelectNameErrorTaken=その名前は既に使用されています\ntstr_SettingsProfilesOverlaysProfileSaveSelectHeaderList=プロファイルに保存するオーバーレイを選択してください\ntstr_SettingsProfilesOverlaysProfileSaveSelectDo=選択したオーバーレイを保存\ntstr_SettingsProfilesOverlaysProfileSaveSelectDoFailed=プロファイルを保存できませんでした\n\ntstr_SettingsProfilesAppsHeader=アプリケーションプロファイルの管理\ntstr_SettingsProfilesAppsHeaderNoVRTip=Desktop+ が実行されていない場合は、既存のアプリケーションプロファイルのみがリストされます。\ntstr_SettingsProfilesAppsListEmpty=利用可能なアプリケーションはありません\ntstr_SettingsProfilesAppsProfileHeaderActive=(実行中)\ntstr_SettingsProfilesAppsProfileEnabled=アプリケーションの実行中にアクティブ化する\ntstr_SettingsProfilesAppsProfileOverlayProfile=オーバーレイプロファイル\ntstr_SettingsProfilesAppsProfileActionEnter=アクションを開始\ntstr_SettingsProfilesAppsProfileActionLeave=アクションを終了\n\ntstr_SettingsActionsManage=アクション\ntstr_SettingsActionsManageButton=管理\ntstr_SettingsActionsButtonsOrderDefault=アクションボタン (初期設定)\ntstr_SettingsActionsButtonsOrderOverlayBar=アクションボタン (オーバーレイのバー)\ntstr_SettingsActionsShowBindings=コントローラーのボタン設定を表示\ntstr_SettingsActionsActiveShortcuts=アクティブコントローラーボタン\ntstr_SettingsActionsActiveShortcutsTip=オーバーレイを選択しているときのコントローラーバインディング。\\nコントローラーのバインドを設定して VR ダッシュボードで、どのボタンになるかを変更します。\ntstr_SettingsActionsActiveShortuctsHome=\"ホームに行く\"\ntstr_SettingsActionsActiveShortuctsBack=\"戻る\"\ntstr_SettingsActionsGlobalShortcuts=グローバル コントローラーボタン\ntstr_SettingsActionsGlobalShortcutsTip=ダッシュボードが閉じられ、オーバーレイを選択していないときのコントローラーバインディング。\\nコントローラーのバインドを設定して Desktop+ が、どのボタンになるかを変更します。\ntstr_SettingsActionsGlobalShortcutsEntry=グローバル ショートカット #%ID%\ntstr_SettingsActionsGlobalShortcutsAdd=ショートカット追加\ntstr_SettingsActionsGlobalShortcutsRemove=最後の ショートカット 削除\ntstr_SettingsActionsHotkeys=ホットキー\ntstr_SettingsActionsHotkeysTip=システム全体のキーボード ショートカット。\\nホットキーは、他のアプリケーションがその入力をブロックするため、同じ組み合わせがすでに別の場所に登録されている場合は機能しない場合があります。\ntstr_SettingsActionsHotkeysAdd=ホットキー追加\ntstr_SettingsActionsHotkeysRemove=削除\ntstr_SettingsActionsTableHeaderAction=アクション\ntstr_SettingsActionsTableHeaderShortcut=ショートカット\ntstr_SettingsActionsTableHeaderHotkey=ホットキー\n\ntstr_SettingsActionsManageHeader=アクションの管理\ntstr_SettingsActionsManageCopyUID=UIDをコピー\ntstr_SettingsActionsManageNew=新規アクション...\ntstr_SettingsActionsManageEdit=アクションを編集\ntstr_SettingsActionsManageDuplicate=アクションを複製\ntstr_SettingsActionsManageDelete=アクションを削除\ntstr_SettingsActionsManageDeleteConfirm=本当にいいですか?\ntstr_SettingsActionsManageDuplicatedName=%NAME% (コピー)\n\ntstr_SettingsActionsEditHeader=アクションを編集\ntstr_SettingsActionsEditName=名前\ntstr_SettingsActionsEditNameTranslatedTip=このアクションは現在、翻訳文字列IDを名前として使い、選択されたアプリケーション言語に自動的にマッチします。\ntstr_SettingsActionsEditTarget=ターゲット\ntstr_SettingsActionsEditTargetDefault=初期設定\ntstr_SettingsActionsEditTargetDefaultTip=アクションが有効化されたオーバレイ、または前者が適用されない場合はフォーカスされたオーバレイを対象とする\ntstr_SettingsActionsEditTargetUseTags=タグを使用する\ntstr_SettingsActionsEditTargetActionTarget=アクション ターゲット\ntstr_SettingsActionsEditHeaderAppearance=ボタンの外観\ntstr_SettingsActionsEditIcon=アイコン\ntstr_SettingsActionsEditLabel=ラベル\ntstr_SettingsActionsEditLabelTranslatedTip=このアクションは現在、翻訳文字列IDをラベルとして使用し、選択されたアプリケーション言語に自動的にマッチします。\ntstr_SettingsActionsEditHeaderCommands=コマンド\ntstr_SettingsActionsEditNameNew=新規アクション\ntstr_SettingsActionsEditCommandAdd=コマンド追加\ntstr_SettingsActionsEditCommandDelete=コマンド削除\ntstr_SettingsActionsEditCommandDeleteConfirm=本当にいいですか?\ntstr_SettingsActionsEditCommandType=コマンド タイプ\ntstr_SettingsActionsEditCommandTypeNone=なし\ntstr_SettingsActionsEditCommandTypeKey=キーを押す\ntstr_SettingsActionsEditCommandTypeMousePos=マウスの位置を設定\ntstr_SettingsActionsEditCommandTypeString=文字を入力\ntstr_SettingsActionsEditCommandTypeLaunchApp=アプリを起動\ntstr_SettingsActionsEditCommandTypeShowKeyboard=キーボード 表示\ntstr_SettingsActionsEditCommandTypeCropActiveWindow=アクティブなウィンドウをクロップ\ntstr_SettingsActionsEditCommandTypeShowOverlay=オーバーレイ 表示\ntstr_SettingsActionsEditCommandTypeSwitchTask=タスク切り替え\ntstr_SettingsActionsEditCommandTypeLoadOverlayProfile=オーバーレイプロファイルを読み込む\ntstr_SettingsActionsEditCommandTypeUnknown=不明\ntstr_SettingsActionsEditCommandVisibilityToggle=切り替え\ntstr_SettingsActionsEditCommandVisibilityShow=常に表示\ntstr_SettingsActionsEditCommandVisibilityHide=常に非表示\ntstr_SettingsActionsEditCommandUndo=元に戻す\ntstr_SettingsActionsEditCommandKeyCode=コピーボタン\ntstr_SettingsActionsEditCommandKeyToggle=キー 切り替え\ntstr_SettingsActionsEditCommandMouseX=X\ntstr_SettingsActionsEditCommandMouseY=Y\ntstr_SettingsActionsEditCommandMouseUseCurrent=マウスカーソルの位置を使う\ntstr_SettingsActionsEditCommandString=文字\ntstr_SettingsActionsEditCommandPath=実行可能なパス\ntstr_SettingsActionsEditCommandPathTip=これは通常のファイルやURLでも可能です。\ntstr_SettingsActionsEditCommandArgs=アプリケーションの引数\ntstr_SettingsActionsEditCommandArgsTip=これらの引数はアプリケーションに渡されます。\\n分からない場合は空欄のままにしてください。\ntstr_SettingsActionsEditCommandVisibility=表示する\ntstr_SettingsActionsEditCommandSwitchingMethod=スイッチング方式\ntstr_SettingsActionsEditCommandSwitchingMethodSwitcher=タスクスイッチャーを表示\ntstr_SettingsActionsEditCommandSwitchingMethodFocus=フォーカスウィンドウ\ntstr_SettingsActionsEditCommandWindow=ウィンドウ\ntstr_SettingsActionsEditCommandWindowNone=[ウィンドウなし]\ntstr_SettingsActionsEditCommandWindowStrictMatchingTip=切り替え先のウィンドウを探すときに、ウィンドウのタイトルの正確な一致のみを許可する\ntstr_SettingsActionsEditCommandCursorWarp=カーソルをウィンドウ内にワープ\ntstr_SettingsActionsEditCommandProfile=プロフィール\ntstr_SettingsActionsEditCommandProfileClear=既存のオーバーレイを削除する\ntstr_SettingsActionsEditCommandDescNone=No コマンド\ntstr_SettingsActionsEditCommandDescKey=キーを押す \"%KEYNAME%\"\ntstr_SettingsActionsEditCommandDescKeyToggle=キー 切り替え \"%KEYNAME%\"\ntstr_SettingsActionsEditCommandDescMousePos=マウスの位置を %X%, %Y% に設定\ntstr_SettingsActionsEditCommandDescString=文字を入力 \"%STRING%\"\ntstr_SettingsActionsEditCommandDescLaunchApp=アプリを起動 \"%APP%\" %ARGSOPT%\ntstr_SettingsActionsEditCommandDescLaunchAppArgsOpt=\"%ARGS%\"\ntstr_SettingsActionsEditCommandDescKeyboardToggle=切り替え キーボード\ntstr_SettingsActionsEditCommandDescKeyboardShow=キーボード表示\ntstr_SettingsActionsEditCommandDescKeyboardHide=キーボード非表示\ntstr_SettingsActionsEditCommandDescCropWindow=アクティブなウィンドウをクロップ\ntstr_SettingsActionsEditCommandDescOverlayToggle=オーバーレイ切り替え: %TAGS%\ntstr_SettingsActionsEditCommandDescOverlayShow=オーバーレイ表示: %TAGS%\ntstr_SettingsActionsEditCommandDescOverlayHide=オーバーレイ非表示: %TAGS%\ntstr_SettingsActionsEditCommandDescOverlayTargetDefault=[アクション ターゲット]\ntstr_SettingsActionsEditCommandDescSwitchTask=タスク切り替え\ntstr_SettingsActionsEditCommandDescSwitchTaskWindow=タスクを \"%WINDOW%\" に切り替える\ntstr_SettingsActionsEditCommandDescLoadOverlayProfile=オーバーレイプロファイル \"%PROFILE%\" を読み込む\ntstr_SettingsActionsEditCommandDescLoadOverlayProfileAdd=オーバーレイプロファイル \"%PROFILE%\" を追加する\ntstr_SettingsActionsEditCommandDescUnknown=コマンドが不明です\n\ntstr_SettingsActionsOrderHeader=アクション変更オーダー\ntstr_SettingsActionsOrderButtonLabel=%COUNT% アクション選択中\ntstr_SettingsActionsOrderButtonLabelSingular=%COUNT% 選択中\ntstr_SettingsActionsOrderNoActions=アクションが選択されていません\ntstr_SettingsActionsOrderAdd=アクション追加...\ntstr_SettingsActionsOrderRemove=アクション削除\n\ntstr_SettingsActionsAddSelectorHeader=追加するアクションを選んでください\ntstr_SettingsActionsAddSelectorAdd=選択したアクションを追加\n\ntstr_SettingsKeyboardLayout=キーボード レイアウト\ntstr_SettingsKeyboardSize=大きさ\ntstr_SettingsKeyboardBehavior=動作\ntstr_SettingsKeyboardStickyMod=スティキー修飾キー\ntstr_SettingsKeyboardKeyRepeat=キー置き換え\ntstr_SettingsKeyboardAutoShow=自動で表示\ntstr_SettingsKeyboardAutoShowDesktopOnly=デスクトップのみ自動で表示\ntstr_SettingsKeyboardAutoShowDesktop=デスクトップ と ウィンドウオーバーレイ\ntstr_SettingsKeyboardAutoShowDesktopTip=実験的。すべてのアプリケーションで動作するわけではありません。\ntstr_SettingsKeyboardAutoShowBrowser=ブラウザオーバーレイ\ntstr_SettingsKeyboardLayoutAuthor=%AUTHOR% によって作成されました\ntstr_SettingsKeyboardKeyClusters=キー\ntstr_SettingsKeyboardKeyClusterBase=Base\ntstr_SettingsKeyboardKeyClusterFunction=Function\ntstr_SettingsKeyboardKeyClusterNavigation=Navigation\ntstr_SettingsKeyboardKeyClusterNumpad=Numpad\ntstr_SettingsKeyboardKeyClusterExtra=Extra\ntstr_SettingsKeyboardSwitchToEditor=キーボードレイアウトエディターに切り替える\n\ntstr_SettingsMouseShowCursor=マウスカーソル表示\ntstr_SettingsMouseShowCursorGCUnsupported=このシステムでは、グラフィックスキャプチャのオーバーレイのカーソルを無効にすることはサポートされていません\ntstr_SettingsMouseShowCursorGCActiveWarning=アクティブ グラフィックス キャプチャ ミラーにより、デスクトップ複製オーバーレイでカーソルが非表示にならなくなる場合があります\ntstr_SettingsMouseScrollSmooth=スムーズスクロール有効\ntstr_SettingsMouseSimulatePen=ペン入力としてシミュレート\ntstr_SettingsMouseSimulatePenUnsupported=このシステムではペン入力シミュレーションはサポートされていません\ntstr_SettingsMouseAllowLaserPointerOverride=レーザーポインター オーバーライドを有効\ntstr_SettingsMouseAllowLaserPointerOverrideTip=オーバーレイをクリックするとレーザーポインターが戻ります。\ntstr_SettingsMouseDoubleClickAssist=ダブルクリック アシスタント\ntstr_SettingsMouseDoubleClickAssistTip=ダブルクリックの入力を容易にするために、マウスカーソルを設定した時間だけ停止させる。\ntstr_SettingsMouseDoubleClickAssistTipValueOff=オフ\ntstr_SettingsMouseDoubleClickAssistTipValueAuto=自動\ntstr_SettingsMouseSmoothing=スムーズな入力\ntstr_SettingsMouseSmoothingLevelNone=なし\ntstr_SettingsMouseSmoothingLevelVeryLow=最小\ntstr_SettingsMouseSmoothingLevelLow=小\ntstr_SettingsMouseSmoothingLevelMedium=中\ntstr_SettingsMouseSmoothingLevelHigh=高\ntstr_SettingsMouseSmoothingLevelVeryHigh=最高\n\ntstr_SettingsLaserPointerTip=これらの設定は、SteamVR ダッシュボードが閉じているときに使用される Desktop+ のレーザー ポインターに適用されます。\ntstr_SettingsLaserPointerBlockInput=アクティブ中にゲームの入力をブロックする\ntstr_SettingsLaserPointerAutoToggleDistance=自動アクティベーション 最大距離\ntstr_SettingsLaserPointerAutoToggleDistanceValueOff=オフ\ntstr_SettingsLaserPointerHMDPointer=視線ベースのHMDのポインター\ntstr_SettingsLaserPointerHMDPointerTableHeaderInputAction=入力アクション\ntstr_SettingsLaserPointerHMDPointerTableHeaderBinding=キーボードのキー\ntstr_SettingsLaserPointerHMDPointerTableBindingToggle=レーザーポインターを切り替える\ntstr_SettingsLaserPointerHMDPointerTableBindingLeft=左クリック\ntstr_SettingsLaserPointerHMDPointerTableBindingRight=右クリック\ntstr_SettingsLaserPointerHMDPointerTableBindingMiddle=ホイールクリック\n\ntstr_SettingsWindowOverlaysAutoFocus=オーバーレイをポイントしたときのフォーカスウィンドウ\ntstr_SettingsWindowOverlaysKeepOnScreen=ウィンドウをスクリーンのままにする\ntstr_SettingsWindowOverlaysKeepOnScreenTip=ソースウィンドウの境界が画面の描画領域の外にある場合、ソースウィンドウを自動的に移動します。\ntstr_SettingsWindowOverlaysAutoSizeOverlay=ウィンドウのサイズ変更時にオーバーレイのサイズを調整する\ntstr_SettingsWindowOverlaysFocusSceneApp=レーザーポインターがオーバーレイから離れると、ゲームに焦点を合わせる\ntstr_SettingsWindowOverlaysFocusSceneAppDashboard=ゲームを終了したらダッシュボードを閉じる\ntstr_SettingsWindowOverlaysOnWindowDrag=ウィンドウドラッグ時\ntstr_SettingsWindowOverlaysOnWindowDragDoNothing=何もしない\ntstr_SettingsWindowOverlaysOnWindowDragBlock=ドラッグをブロック\ntstr_SettingsWindowOverlaysOnWindowDragOverlay=ドラッグオーバーレイ\ntstr_SettingsWindowOverlaysOnCaptureLoss=キャプチャーのロスについて\ntstr_SettingsWindowOverlaysOnCaptureLossTip=ウィンドウキャプチャが失われたときの動作。通常はターゲットウィンドウが閉じられることによって失われる。\\nまた、\"オーバーレイ非表示\" は、キャプチャが見つかったときにオーバーレイを再び表示します。\ntstr_SettingsWindowOverlaysOnCaptureLossDoNothing=何もしない\ntstr_SettingsWindowOverlaysOnCaptureLossHide=オーバーレイ非表示\ntstr_SettingsWindowOverlaysOnCaptureLossRemove=オーバーレイ削除\n\ntstr_SettingsBrowserMaxFrameRate=最大フレームレート\ntstr_SettingsBrowserMaxFrameRateOverrideOff=グローバル設定\ntstr_SettingsBrowserContentBlocker=コンテンツブロッカー\ntstr_SettingsBrowserContentBlockerTip=Adblock Plus構文のブロックリストを \"DesktopPlusBrowser\\content_block\" ディレクトリに追加します。\\nディレクトリ内のすべてのリストが読み込まれます。\ntstr_SettingsBrowserContentBlockerListCount=(%LISTCOUNT% 実行中リスト)\ntstr_SettingsBrowserContentBlockerListCountSingular=(%LISTCOUNT% 実行中リスト)\n\ntstr_SettingsPerformanceUpdateLimiter=フレームリミッター\ntstr_SettingsPerformanceUpdateLimiterMode=フレームリミッターモード\ntstr_SettingsPerformanceUpdateLimiterModeOff=オフ\ntstr_SettingsPerformanceUpdateLimiterModeMS=フレームタイム\ntstr_SettingsPerformanceUpdateLimiterModeFPS=フレームレート\ntstr_SettingsPerformanceUpdateLimiterModeOffOverride=グローバル設定を使う\ntstr_SettingsPerformanceUpdateLimiterModeMSTip=強制的にオーバーレイの毎フレームの最小間隔を制御します\ntstr_SettingsPerformanceUpdateLimiterFPSValue=%FPS% fps\ntstr_SettingsPerformanceUpdateLimiterOverride=オーバーレイフレーム上限\ntstr_SettingsPerformanceUpdateLimiterOverrideTip=複数のオーバーライドが有効な場合、更新レートが最も高いものが使用されます。\ntstr_SettingsPerformanceUpdateLimiterModeOverride=オーバーレイ更新上限モード\ntstr_SettingsPerformanceRapidUpdates=レーザー ポインターの遅延を短縮する\ntstr_SettingsPerformanceRapidUpdatesTip=オーバーレイをポイントするときに CPU 負荷を上げて、レーザー ポインター入力の遅延を短縮します。\ntstr_SettingsPerformanceSingleDesktopMirror=単一デスクトップのミラーリング\ntstr_SettingsPerformanceSingleDesktopMirrorTip=デスクトップ切り替え時に、結合されたデスクトップから切り取るのではなく、別々にデスクトップをミラーリングします。\\nこれが有効な場合、すべてのオーバーレイは同じデスクトップを表示します。\ntstr_SettingsPerformanceShowFPS=フローティング UI で FPS を表示\n\ntstr_SettingsWarningsHidden=警告非表示:\ntstr_SettingsWarningsReset=警告非表示リセット\n\ntstr_SettingsStartupAutoLaunch=自動で起動する\ntstr_SettingsStartupSteamDisable=Steamの起動 無効\ntstr_SettingsStartupSteamDisableTip=Steamで起動した場合、SteamなしでDesktop+を起動する。\\nこれにより、アプリ内のステータス、使用時間の統計、その他のSteamの機能が無効になります。\n\ntstr_SettingsTroubleshootingRestart=再起動\ntstr_SettingsTroubleshootingRestartSteam=Steam再起動\ntstr_SettingsTroubleshootingRestartDesktop=デスクトップモードで再起動\ntstr_SettingsTroubleshootingElevatedModeEnter=管理者モード 有効\ntstr_SettingsTroubleshootingElevatedModeLeave=管理者モード 無効\ntstr_SettingsTroubleshootingSettingsReset=初期設定に戻す\ntstr_SettingsTroubleshootingSettingsResetConfirmDescription=デフォルト設定を復元すると、選択した要素がデフォルト値にリセットされます。\\nこれは、元に戻すことはできません。\\n\\nリセットする値:\ntstr_SettingsTroubleshootingSettingsResetConfirmButton=選択している項目をリセット\ntstr_SettingsTroubleshootingSettingsResetConfirmElementOverlays=現在のオーバーレイのセットアップ\ntstr_SettingsTroubleshootingSettingsResetConfirmElementLegacyFiles=使用されていない古い構成とプロファイルファイルを削除する\ntstr_SettingsTroubleshootingSettingsResetShowQuickStart=クイックスタートガイドを表示\n\n;Keyboard Window\ntstr_KeyboardWindowTitle=Desktop+ キーボード\ntstr_KeyboardWindowTitleSettings=Desktop+  キーボード (設定)\ntstr_KeyboardWindowTitleOverlay=Desktop+ キーボード (%OVERLAYNAME%)\ntstr_KeyboardWindowTitleOverlayUnknown=[不明なオーバーレイ]\n\n;Keyboard Shortcuts Window\ntstr_KeyboardShortcutsCut=切り取り\ntstr_KeyboardShortcutsCopy=コピー\ntstr_KeyboardShortcutsPaste=貼り付け\n\n;Overlay Properties Window\ntstr_OvrlPropsCatPosition=位置\ntstr_OvrlPropsCatAppearance=外観\ntstr_OvrlPropsCatCapture=キャプチャー\ntstr_OvrlPropsCatPerformanceMonitor=プリファレンス モニター\ntstr_OvrlPropsCatBrowser=ブラウザ\ntstr_OvrlPropsCatAdvanced=実績\ntstr_OvrlPropsCatPerformance=パフォーマンス\ntstr_OvrlPropsCatInterface=インターフェイス\n\ntstr_OvrlPropsPositionOrigin=原点\ntstr_OvrlPropsPositionOriginRoom=プレイエリア\ntstr_OvrlPropsPositionOriginHMDXY=HMDの床の位置\ntstr_OvrlPropsPositionOriginDashboard=ダッシュボード\ntstr_OvrlPropsPositionOriginHMD=HMD\ntstr_OvrlPropsPositionOriginSeatedSpace=座っている位置\ntstr_OvrlPropsPositionOriginControllerR=右コントローラー\ntstr_OvrlPropsPositionOriginControllerL=左コントローラー\ntstr_OvrlPropsPositionOriginTheaterScreen=シアタースクリーン\ntstr_OvrlPropsPositionOriginTracker1=トラッカー #1\ntstr_OvrlPropsPositionOriginConfigHMDXYTurning=HMDの向きに追従\ntstr_OvrlPropsPositionOriginConfigTheaterScreenEnter=シアターモード オン\ntstr_OvrlPropsPositionOriginConfigTheaterScreenLeave=シアターモード オフ\ntstr_OvrlPropsPositionOriginTheaterScreenTip=一部のプロパティは SteamVR シアタースクリーン によって制御されます\ntstr_OvrlPropsPositionDispMode=ディスプレイモード\ntstr_OvrlPropsPositionDispModeAlways=常に表示\ntstr_OvrlPropsPositionDispModeDashboard=ダッシュボード中のみ\ntstr_OvrlPropsPositionDispModeScene=ゲーム中のみ\ntstr_OvrlPropsPositionDispModeDPlus=Desktop+ タブ内のみ\ntstr_OvrlPropsPositionPos=位置\ntstr_OvrlPropsPositionPosTip=位置は、Desktop+ の実行中にのみ変更またはリセットできます。\ntstr_OvrlPropsPositionChange=変更\ntstr_OvrlPropsPositionReset=リセット\ntstr_OvrlPropsPositionLock=ロック\n\ntstr_OvrlPropsPositionChangeHeader=オーバーレイの位置を変更\ntstr_OvrlPropsPositionChangeHelp=任意のオーバーレイをドラッグして位置を変更します。\\n両手で位置を変更するには、右クリックを押したままにします。\ntstr_OvrlPropsPositionChangeHelpDesktop=ドラッグボタン（\"D\"）を押しながらマウスでオーバーレイを移動または回転させる。\ntstr_OvrlPropsPositionChangeManualAdjustment=手動で調整\ntstr_OvrlPropsPositionChangeMove=移動\ntstr_OvrlPropsPositionChangeRotate=回転\ntstr_OvrlPropsPositionChangeForward=手前に移動\ntstr_OvrlPropsPositionChangeBackward=奥に移動\ntstr_OvrlPropsPositionChangeRollCW=ロール ⟳\ntstr_OvrlPropsPositionChangeRollCCW=ロール ⟲\ntstr_OvrlPropsPositionChangeLookAt=HMDを向く\ntstr_OvrlPropsPositionChangeDragButton=D\ntstr_OvrlPropsPositionChangeOffset=追加のオフセット\ntstr_OvrlPropsPositionChangeOffsetUpDown=上/下 オフセット\ntstr_OvrlPropsPositionChangeOffsetRightLeft=右/左 オフセット\ntstr_OvrlPropsPositionChangeOffsetForwardBackward=前/後 オフセット\ntstr_OvrlPropsPositionChangeDragSettings=ドラッグ設定\ntstr_OvrlPropsPositionChangeDragSettingsAutoDocking=近くのコントローラーにドッキング\ntstr_OvrlPropsPositionChangeDragSettingsForceDistance=フォースの距離 固定\ntstr_OvrlPropsPositionChangeDragSettingsForceDistanceShape=シャープ\ntstr_OvrlPropsPositionChangeDragSettingsForceDistanceShapeSphere=球体\ntstr_OvrlPropsPositionChangeDragSettingsForceDistanceShapeCylinder=シリンダー\ntstr_OvrlPropsPositionChangeDragSettingsForceDistanceAutoCurve=自動-湾曲\ntstr_OvrlPropsPositionChangeDragSettingsForceDistanceAutoTilt=自動-角度\ntstr_OvrlPropsPositionChangeDragSettingsSnapPosition=スナップ位置\n\ntstr_OvrlPropsAppearanceWidth=横\ntstr_OvrlPropsAppearanceCurve=曲率\ntstr_OvrlPropsAppearanceOpacity=不透明度\ntstr_OvrlPropsAppearanceBrightness=明るさ\ntstr_OvrlPropsAppearanceCrop=クロップ\ntstr_OvrlPropsAppearanceCropValueMax=最大\n\ntstr_OvrlPropsCrop=エリアをクロップ中\ntstr_OvrlPropsCropHelp=長方形をドラッグしてクロップを変更します。スクロールするか、端をドラッグして、長方形のサイズを調整します。\ntstr_OvrlPropsCropManualAdjust=手動で調整\ntstr_OvrlPropsCropInvalidTip=現在の四角形のクロップは無効です。オーバーレイが見えなくなる場合があります。\ntstr_OvrlPropsCropX=X\ntstr_OvrlPropsCropY=Y\ntstr_OvrlPropsCropWidth=横\ntstr_OvrlPropsCropHeight=縦\ntstr_OvrlPropsCropToWindow=選択しているウィンドウをクロップ\n\ntstr_OvrlPropsCaptureMethod=キャプチャー方法\ntstr_OvrlPropsCaptureMethodDup=デスクトップ\ntstr_OvrlPropsCaptureMethodGC=グラフィック\ntstr_OvrlPropsCaptureMethodGCUnsupportedTip=このシステムではグラフィックキャプチャーはサポートされていません\ntstr_OvrlPropsCaptureMethodGCUnsupportedPartialTip=一部のグラフィックキャプチャー機能はこのシステムではサポートされていません\ntstr_OvrlPropsCaptureSource=ソース\ntstr_OvrlPropsCaptureGCSource=グラフィックキャプチャーソース\ntstr_OvrlPropsCaptureSourceUnknownWarning=このオーバーレイは不明なキャプチャ ソースを使用しているため、正しく動作しない可能性があります\ntstr_OvrlPropsCaptureGCStrictMatching=厳密なウィンドウ マッチングを使用する\ntstr_OvrlPropsCaptureGCStrictMatchingTip=このオーバーレイのキャプチャを復元するとき、ウィンドウのタイトル名が完全に一致したウィンドウのみキャプチャーします\n\ntstr_OvrlPropsPerfMonDesktopModeTip=パフォーマンス モニターのオーバーレイがデスクトップ モードでは更新されません\ntstr_OvrlPropsPerfMonGlobalTip=これらの設定は、すべてのパフォーマンス モニター オーバーレイに適用されます\ntstr_OvrlPropsPerfMonStyle=スタイル\ntstr_OvrlPropsPerfMonStyleCompact=コンパクト\ntstr_OvrlPropsPerfMonStyleLarge=ラージ\ntstr_OvrlPropsPerfMonShowCPU=CPUの状態を表示\ntstr_OvrlPropsPerfMonShowGPU=GPUの状態を表示\ntstr_OvrlPropsPerfMonShowGraphs=グラフィックを表示\ntstr_OvrlPropsPerfMonShowFrameStats=FPSを表示\ntstr_OvrlPropsPerfMonShowTime=時計を表示\ntstr_OvrlPropsPerfMonShowBattery=バッテリーの状態を表示\ntstr_OvrlPropsPerfMonShowTrackerBattery=トラッカーのバッテリーを表示\ntstr_OvrlPropsPerfMonShowViveWirelessTemp=Vive ワイヤレス温度を表示\ntstr_OvrlPropsPerfMonResetValues=累積値 復元\n\ntstr_OvrlPropsBrowserNotAvailableTip=Desktop+ ブラウザコンポーネントがインストールされていません\ntstr_OvrlPropsBrowserCloned=クローン化された出力\ntstr_OvrlPropsBrowserClonedTip=このオーバーレイは \"%OVERLAYNAME%\" のクローンです。\\nブラウザのプロパティの変更内容は、元のブラウザとそこから複製されたすべてのオーバーレイに適用されます。\ntstr_OvrlPropsBrowserClonedConvert=スタンドアローンに変換中\ntstr_OvrlPropsBrowserURL=URL\ntstr_OvrlPropsBrowserURLHint=URLを入力してください...\ntstr_OvrlPropsBrowserGo=決定\ntstr_OvrlPropsBrowserRestore=戻る\ntstr_OvrlPropsBrowserWidth=横\ntstr_OvrlPropsBrowserHeight=縦\ntstr_OvrlPropsBrowserZoom=ズーム\ntstr_OvrlPropsBrowserAllowTransparency=透過を有効にする\ntstr_OvrlPropsBrowserAllowTransparencyTip=Web ページで透明度の変更を許可します。\\n背景色が設定されていないサイトでは正しく表示されない可能性があります。\ntstr_OvrlPropsBrowserRecreateContext=ブラウザの環境を再生成する\ntstr_OvrlPropsBrowserRecreateContextTip=変更を適用するには、ブラウザの環境を再生成する必要があります。\\nこれを実行すると、ページがリロードされ、履歴がリセットされます。\n\ntstr_OvrlPropsAdvanced3D=3D\ntstr_OvrlPropsAdvancedHSBS=半分に並べる\ntstr_OvrlPropsAdvancedSBS=並べる\ntstr_OvrlPropsAdvancedHOU=半分以下\ntstr_OvrlPropsAdvancedOU=以下\ntstr_OvrlPropsAdvanced3DSwap=左右を交換\ntstr_OvrlPropsAdvancedGazeFade=注視フェード\ntstr_OvrlPropsAdvancedGazeFadeAuto=自動構成\ntstr_OvrlPropsAdvancedGazeFadeDistance=距離\ntstr_OvrlPropsAdvancedGazeFadeDistanceValueInf=無制限\ntstr_OvrlPropsAdvancedGazeFadeSensitivity=感度\ntstr_OvrlPropsAdvancedGazeFadeOpacity=ターゲットの不透明度\ntstr_OvrlPropsAdvancedInput=レーザーポインターの入力\ntstr_OvrlPropsAdvancedInputInGame=ゲーム中 有効\ntstr_OvrlPropsAdvancedInputFloatingUI=フローティング UI を表示中\ntstr_OvrlPropsAdvancedOverlayTags=オーバーレイタグ\ntstr_OvrlPropsAdvancedOverlayTagsTip=オーバーレイタグは、アクション内のオーバーレイをターゲットにするために使用されます\n\ntstr_OvrlPropsPerformanceInvisibleUpdate=常に更新\ntstr_OvrlPropsPerformanceInvisibleUpdateTip=不透明度設定や視線フェードで非表示になっている場合でも、オーバーレイを更新します。\\nサードパーティ製のアプリがオーバーレイの内容を取得するのに役立ちます。\\nそれ以外の場合は推奨しません。\\nオーバーレイが手動または表示モード設定で非表示の場合でも、更新は一時停止しています。\n\ntstr_OvrlPropsInterfaceOverlayName=オーバーレイの名前\ntstr_OvrlPropsInterfaceOverlayNameAuto=[自動]\ntstr_OvrlPropsInterfaceActionOrderCustom=アクションボタンを上書きする\ntstr_OvrlPropsInterfaceDesktopButtons=デスクトップボタン表示\ntstr_OvrlPropsInterfaceExtraButtons=追加ボタンを表示\n\n;Overlay Bar\ntstr_OverlayBarOvrlHide=非表示\ntstr_OverlayBarOvrlShow=表示\ntstr_OverlayBarOvrlClone=複製\ntstr_OverlayBarOvrlRemove=削除\ntstr_OverlayBarOvrlRemoveConfirm=本当にいい?\ntstr_OverlayBarOvrlProperties=プロパティ...\ntstr_OverlayBarOvrlAddWindow=ウィンドウ...\ntstr_OverlayBarTooltipOvrlAdd=オーバーレイ追加\ntstr_OverlayBarTooltipSettings=設定\ntstr_OverlayBarTooltipResetHold=ホールドしたウィンドウの位置をリセット...\n\n;Floating UI\ntstr_FloatingUIHideOverlayTip=オーバーレイ 非表示\ntstr_FloatingUIHideOverlayHoldTip=長押しでオーバーレイを削除...\ntstr_FloatingUIDragModeEnableTip=ドラッグモード 有効\ntstr_FloatingUIDragModeDisableTip=ドラッグモード 無効\ntstr_FloatingUIDragModeHoldLockTip=長押しで位置を固定する...\ntstr_FloatingUIDragModeHoldUnlockTip=長押しで位置の固定を解除する...\ntstr_FloatingUIWindowAddTip=アクティブウィンドウをオーバーレイに追加\ntstr_FloatingUIActionBarShowTip=アクションバー 表示\ntstr_FloatingUIActionBarHideTip=アクションバー 非表示\ntstr_FloatingUIBrowserGoBackTip=Previous ページに行く\ntstr_FloatingUIBrowserGoForwardTip=次のページに行く\ntstr_FloatingUIBrowserRefreshTip=現在のページをリフレッシュ\ntstr_FloatingUIBrowserStopTip=ページの読み込みを停止\ntstr_FloatingUIActionBarDesktopPrev=前のデスクトップ\ntstr_FloatingUIActionBarDesktopNext=次のデスクトップ\ntstr_FloatingUIActionBarEmpty=有効なアクションはありません\n\n;Special Actions\ntstr_ActionNone=[なし]\ntstr_ActionKeyboardShow=キーボード 表示\ntstr_ActionKeyboardHide=キーボード 非表示\n\n;Default Actions (translation IDs are matched to Action name string)\ntstr_DefActionShowKeyboard=キーボード 表示\ntstr_DefActionActiveWindowCrop=選択しているウィンドウをトリミング\ntstr_DefActionActiveWindowCropLabel=トリミング\\n選択している\\nウィンドウ\ntstr_DefActionSwitchTask=タスク切り替え\ntstr_DefActionToggleOverlays=オーバーレイ 切り替え\ntstr_DefActionToggleOverlaysLabel=オーバーレイ\\n切り替え\ntstr_DefActionMiddleMouse=マウスボタン 中心\ntstr_DefActionMiddleMouseLabel=中心\\nマウス\\nボタン\ntstr_DefActionBackMouse=マウスボタン 戻る\ntstr_DefActionBackMouseLabel=戻る\\nマウス\\nボタン\ntstr_DefActionReadMe=説明書を開く\ntstr_DefActionReadMeLabel=説明書\\n開く\ntstr_DefActionDashboardToggle=SteamVR ダッシュボードの切り替え (デバッグ コマンド)\ntstr_DefActionDashboardToggleLabel=ダッシュボード\\n切り替え\n\n;Performance Monitor (text space is very limited here, so keep it short or untranslated)\ntstr_PerformanceMonitorCPU=CPU\ntstr_PerformanceMonitorGPU=GPU\ntstr_PerformanceMonitorRAM=RAM:\ntstr_PerformanceMonitorVRAM=VRAM:\ntstr_PerformanceMonitorFrameTime=フレームタイム:\ntstr_PerformanceMonitorLoad=負荷:\ntstr_PerformanceMonitorFPS=FPS:\ntstr_PerformanceMonitorFPSAverage=平均 FPS:\ntstr_PerformanceMonitorReprojectionRatio=再投影率:\ntstr_PerformanceMonitorDroppedFrames=ドロップされたフレーム:\ntstr_PerformanceMonitorBatteryLeft=左コントローラー:\ntstr_PerformanceMonitorBatteryRight=右コントローラー:\ntstr_PerformanceMonitorBatteryHMD=ヘッドセット:\ntstr_PerformanceMonitorBatteryTracker=トラッカー %ID%:\ntstr_PerformanceMonitorBatteryDisconnected=N/A\ntstr_PerformanceMonitorViveWirelessTempNotAvailable=N/A\ntstr_PerformanceMonitorCompactCPU=CPU\ntstr_PerformanceMonitorCompactGPU=GPU\ntstr_PerformanceMonitorCompactFPS=FPS\ntstr_PerformanceMonitorCompactFPSAverage=AVG\ntstr_PerformanceMonitorCompactReprojectionRatio=% RPR\ntstr_PerformanceMonitorCompactDroppedFrames=DRP\ntstr_PerformanceMonitorCompactBattery=BAT\ntstr_PerformanceMonitorCompactBatteryLeft=L\ntstr_PerformanceMonitorCompactBatteryRight=R\ntstr_PerformanceMonitorCompactBatteryHMD=H\ntstr_PerformanceMonitorCompactBatteryTracker=T%ID%\ntstr_PerformanceMonitorCompactBatteryDisconnected=N/A\ntstr_PerformanceMonitorCompactViveWirelessTempNotAvailable=N/A\ntstr_PerformanceMonitorEmpty=パフォーマンスモニターの項目が空です。\n\n;Aux UI\ntstr_AuxUIDragHintDocking=追従させる\ntstr_AuxUIDragHintUndocking=追従を解除\ntstr_AuxUIDragHintOvrlLocked=オーバーレイの位置のロックを解除して、このオーバーレイをドラッグします\ntstr_AuxUIDragHintOvrlTheaterScreenBlocked=オーバーレイの位置は SteamVR シアタースクリーンによって制御されます\ntstr_AuxUIGazeFadeAutoHint=オーバーレイの中心を見て、%SECONDS% 秒間待ちます...\ntstr_AuxUIGazeFadeAutoHintSingular=オーバーレイの中心を見て、%SECONDS% 秒間待ちます...\n\ntstr_AuxUIQuickStartWelcomeHeader=Desktop+ へようこそ!\ntstr_AuxUIQuickStartWelcomeBody=この短いガイドでは、アプリケーションの基本について説明します。\\n詳細については、説明書及びユーザーガイドを参照してください。\\n\\nこれをスキップしたい場合は、[閉じる]を押してください。\ntstr_AuxUIQuickStartOverlaysHeader=オーバーレイ\ntstr_AuxUIQuickStartOverlaysBody=Desktop+ を使用すると、デスクトップやウィンドウなどをミラーリングするオーバーレイを作成できます。\\n作成したすべてのオーバーレイは、下のオーバーレイバーにリストされます。\\n\\nオーバーレイのプロパティは、オーバーレイ アイコンの 1つをクリックして [プロパティ...] を選択することで変更できます。\ntstr_AuxUIQuickStartOverlaysBody2=[+] を押して、リストからキャプチャ ソース/オーバーレイタイプを選択すると、新しいオーバーレイを作成できます。\\nWeb ブラウザ オーバーレイなどの一部のタイプは、それぞれのアプリケーション コンポーネントがインストールされている場合にのみ使用できます。\\n\\n個々のオーバーレイまたは完全なレイアウトをプロファイルに保存できます。\\n現在のオーバーレイ設定は、セッション間で自動的に記憶されます。\ntstr_AuxUIQuickStartOverlayPropertiesHeader=オーバーレイプロパティ\ntstr_AuxUIQuickStartOverlayPropertiesBody=オーバーレイにはさまざまなカスタマイズオプションがあります。\\n原点と表示モードは、オーバーレイが表示される場所とタイミングを設定します。\\nオーバーレイが表示されるはずなのに見つからない場合は、これらのプロパティを確認してください。\\nそれでも見つからない場合は、その位置をリセットすると役立つこともあります。\\n\\nまた、オーバーレイをモーションコントローラーの近くにドラッグして、モーションコントローラーにドッキングすることもできます。\ntstr_AuxUIQuickStartOverlayPropertiesBody2=一部のプロパティはデフォルトで非表示になっています。\\nすべてのオプションを表示するには、設定ウィンドウで[詳細設定を表示]を切り替えます。\\n\\nUIウィンドウの位置は、それぞれのボタンを長押しすることでリセットできます。\\nダブルクリックや右クリックは、クイックショートカットトグルと同様にオーバーレイボタンでも機能します。\ntstr_AuxUIQuickStartSettingsHeader=設定\ntstr_AuxUIQuickStartSettingsBody=設定ウィンドウには、Desktop+ のすべてのグローバル設定が含まれています。\\nオーバーレイバーの右端にある歯車のボタンから開けます。\\n\\nオーバーレイの場合と同様に、設定の変更は自動的に適用され、保存されます。\ntstr_AuxUIQuickStartProfilesHeader=プロファイル\ntstr_AuxUIQuickStartProfilesBody=Desktop+ には、2種類のプロファイルがあります。\\n\\nオーバーレイプロファイル:\\n1つまたは複数のオーバーレイをそのプロパティとともに保存します。\\nこれらは手動で読み込んだり、アクションやアプリケーション、プロファイルのイベントとして読み込むこともできます。\\n\\nアプリケーションのプロファイル:\\nSteamVR アプリケーションの起動時に自動的に読み込まれるオーバーレイプロファイルやアクションを割り当てます。\ntstr_AuxUIQuickStartActionsHeader=アクション\ntstr_AuxUIQuickStartActionsBody=Desktop+ のアクションは、コントローラー入力やアプリケーションプロファイルに割り当てたり、オーバーレイのアクションバーに追加したりできる一連のコマンドです。\\nアクションコマンドには、デスクトップ上の入力をシミュレートするものから、オーバーレイレイアウトを変更するものまであります。\\n使い方次第では、外部アプリケーションを起動することもできます。\\n\\nDesktop+ には、いくつかのアクション例が用意されています。\ntstr_AuxUIQuickStartActionsBody2=初期設定では、アクションは有効化されたオーバレイ(オーバレイに表示されたアクションボタン、オーバレイ上のコントローラ入力)、またはフォーカスされたオーバレイをターゲットにします。\\n\\nフォーカスされたオーバーレイは、最後にクリックされたオーバーレイです。\\n多くの場合、デスクトップ上でシミュレートされた入力のように、対象となるオーバーレイは重要ではありません。他のケースでは、異なる対象のオーバーレイを指定したり、複数のオーバーレイを指定することが有用な場合もあります。\ntstr_AuxUIQuickStartOverlayTagsHeader=オーバーレイタグ\ntstr_AuxUIQuickStartOverlayTagsBody=オーバーレイタグは以下の目的で使用されます。\\nオーバーレイタグには自動タグ(緑色のラベル)とユーザー定義タグがあります。\\n自動タグは、それが表すプロパティに基づいて自動的に割り当てられます。\\nユーザー定義タグは、オーバーレイのプロパティウィンドウでオーバーレイに手動で割り当てることができます。\\n\\nアクションは、グローバルショートカットのコントローラーボタンに間接的にのみバインドされることに注意してください。番号付けされたグローバルショートカットは、SteamVRの入力バインディングインターフェイスを介してコントローラの入力にも割り当てる必要があります。\ntstr_AuxUIQuickStartSettingsEndBody=他にも多くの設定ができます。\\nチェックボックスなど操作して試してみてください。\\n\\n行き詰まった場合は、ウィンドウの下部にある [初期設定に戻す] を押してください。\\n特定の要素のみを復元するオプションもあります。\ntstr_AuxUIQuickStartFloatingUIHeader=フローティングUI\ntstr_AuxUIQuickStartFloatingUIBody=[フローティングUI]と呼ばれるインターフェースは、オーバーレイをポイントするたびに表示されます。\\nダッシュボードでは、他のオーバーレイがポイントされていない場合に常に表示されます。\\n\\nフローティングUIには、ドラッグモードを切り替えてオーバーレイを移動できるようにするなど、オーバーレイを変更するための基本的なコントロールと、アクションボタンのカスタマイズ可能なセクションが含まれています。\\n\\nオーバーレイの種類によっては、他のコントロールも存在する場合があります。\\nそれらの表示は、それぞれのオーバーレイプロパティでオーバーレイごとに切り替えることができます。\ntstr_AuxUIQuickStartDesktopModeHeader=デスクトップモード\ntstr_AuxUIQuickStartDesktopModeBody=Desktop+ は、デスクトップに表示されるウィンドウで設定することもできます。\\nこれは、頻繁なキーボード入力が必要なタスクや、モーションコントローラーを接続せずにせってを変更したいときに便利です。\\n\\nほぼすべての機能は両方のモードで同じですが、Desktop+ VRキーボードのレイアウトをカスタマイズするために使用されるキーボードレイアウトエディターは、デスクトップモードでのみアクセスできます。\\n\\n[設定] ウィンドウの [トラブルシューティング] セクション、またはシステムトレイ/通知領域の Desktop+ アイコンから切り替えることができます。\ntstr_AuxUIQuickStartEndHeader=説明書 & ユーザーガイド\ntstr_AuxUIQuickStartEndBody=これだけで、Desktop+ を使い始めるのに十分なはずです。\\n\\n何か問題が起こったり、行き詰まったりした場合は、必ず説明書も確認してください。アクションバーのボタンをクリックすると、説明書を開けます。\\n各オプションの詳細な説明と一般的な使用シナリオのステップバイステップガイドは、ユーザーガイドに記載されています。\\n\\nこのガイドを再度表示するには、[初期設定に戻す]ページの右下にある [クイックスタートガイドを表示] を押します。\ntstr_AuxUIQuickStartButtonNext=次のページ\ntstr_AuxUIQuickStartButtonPrev=前のページ\ntstr_AuxUIQuickStartButtonClose=閉じる\n\n;Desktop Mode\ntstr_DesktopModeCatTools=ツール\ntstr_DesktopModeCatOverlays=オーバーレイ\ntstr_DesktopModeToolSettings=設定\ntstr_DesktopModeToolActions=アクション\ntstr_DesktopModeOverlayListAdd=オーバーレイ追加\ntstr_DesktopModePageAddWindowOverlayTitle=ウィンドウオーバーレイ追加\ntstr_DesktopModePageAddWindowOverlayHeader=ウィンドウを選択\n\n;Keyboard Editor\ntstr_KeyboardEditorKeyListTitle=キーリスト\ntstr_KeyboardEditorKeyListTabContextReplace=内容を置き換える...\ntstr_KeyboardEditorKeyListTabContextClear=サブレイアウトをクリア\ntstr_KeyboardEditorKeyListRow=行 %ID%\ntstr_KeyboardEditorKeyListSpacing=[間隔]\ntstr_KeyboardEditorKeyListKeyAdd=追加\ntstr_KeyboardEditorKeyListKeyDuplicate=重複\ntstr_KeyboardEditorKeyListKeyRemove=削除\n\ntstr_KeyboardEditorKeyPropertiesTitle=キーのプロパティ\ntstr_KeyboardEditorKeyPropertiesNoSelection=キーが選択されていません\ntstr_KeyboardEditorKeyPropertiesType=タイプ\ntstr_KeyboardEditorKeyPropertiesTypeBlank=空白スペース\ntstr_KeyboardEditorKeyPropertiesTypeVirtualKey=仮想キー\ntstr_KeyboardEditorKeyPropertiesTypeVirtualKeyToggle=仮想キー(トグル)\ntstr_KeyboardEditorKeyPropertiesTypeVirtualKeyIsoEnter=仮想キー(ISO 入力)\ntstr_KeyboardEditorKeyPropertiesTypeString=文字列\ntstr_KeyboardEditorKeyPropertiesTypeSublayoutToggle=サブレイアウトを切り替える\ntstr_KeyboardEditorKeyPropertiesTypeAction=アクション\ntstr_KeyboardEditorKeyPropertiesTypeVirtualKeyIsoEnterTip=隣接する行にある 2つの[仮想キー(ISO 入力)]キーを使用して、ISO 入力 形状のキーを構築します。\\n各サブレイアウトには結合キーが 1つだけあります。\ntstr_KeyboardEditorKeyPropertiesTypeStringTip=基本以外の文字キーには文字列型を使用します。\\nこれにより、Desktop+ は実際のキーボードレイアウト上の正しいキーの組み合わせを判断し、アプリケーションの互換性を高めることができます。\ntstr_KeyboardEditorKeyPropertiesSize=サイズ\ntstr_KeyboardEditorKeyPropertiesLabel=ラベル\ntstr_KeyboardEditorKeyPropertiesKeyCode=キーコード\ntstr_KeyboardEditorKeyPropertiesString=文字列\ntstr_KeyboardEditorKeyPropertiesSublayout=サブレイアウト\ntstr_KeyboardEditorKeyPropertiesAction=アクション\ntstr_KeyboardEditorKeyPropertiesCluster=クラスター\ntstr_KeyboardEditorKeyPropertiesClusterTip=クラスター割り当ては、選択したキーの読み込みを無効にするために使用されます。\\n周辺のスペースは削除されません。正常に切り替えられるように、空白スペースタイプのキーをクラスターに慎重に割り当ててください。\ntstr_KeyboardEditorKeyPropertiesBlockModifiers=修飾キーをブロック\ntstr_KeyboardEditorKeyPropertiesBlockModifiersTip=キーを押している間、すべての修飾キーを解除します。\ntstr_KeyboardEditorKeyPropertiesNoRepeat=繰り返さない\ntstr_KeyboardEditorKeyPropertiesNoRepeatTip=キーリピートが有効になっている場合でも、押​​し続けている間はキーが繰り返し入力されないようにします。\n\ntstr_KeyboardEditorMetadataTitle=レイアウトメタデータ\ntstr_KeyboardEditorMetadataName=名前\ntstr_KeyboardEditorMetadataAuthor=作成者\ntstr_KeyboardEditorMetadataHasAltGr=AltGrキー あり\ntstr_KeyboardEditorMetadataHasAltGrTip=右Altキーを押すと、AltGrサブレイアウトに切り替わります。\ntstr_KeyboardEditorMetadataClusterPreview=プレビュー クラスター:\ntstr_KeyboardEditorMetadataSave=保存中...\ntstr_KeyboardEditorMetadataLoad=読み込み中...\ntstr_KeyboardEditorMetadataSavePopupTitle=キーボードレイアウトを保存\ntstr_KeyboardEditorMetadataSavePopupFilename=ファイル名\ntstr_KeyboardEditorMetadataSavePopupFilenameBlankTip=ファイル名は空白にできません\ntstr_KeyboardEditorMetadataSavePopupConfirm=レイアウトを保存\ntstr_KeyboardEditorMetadataSavePopupConfirmError=レイアウトを保存できませんでした\ntstr_KeyboardEditorMetadataLoadPopupTitle=キーボードレイアウトを読み込む\ntstr_KeyboardEditorMetadataLoadPopupConfirm=レイアウトを読み込む\n\ntstr_KeyboardEditorPreviewTitle=キーボードプレビュー\n\ntstr_KeyboardEditorSublayoutBase=Base\ntstr_KeyboardEditorSublayoutShift=Shift\ntstr_KeyboardEditorSublayoutAltGr=AltGr\ntstr_KeyboardEditorSublayoutAux=Aux\n\n;General Dialog Strings\ntstr_DialogOk=適用\ntstr_DialogCancel=キャンセル\ntstr_DialogDone=閉じる\ntstr_DialogUndo=元に戻す\ntstr_DialogRedo=やり直し\ntstr_DialogColorPickerHeader=色を選択\ntstr_DialogColorPickerCurrent=現在\ntstr_DialogColorPickerOriginal=オリジナル\ntstr_DialogProfilePickerHeader=プロフィールを選択\ntstr_DialogProfilePickerNone=[なし]\ntstr_DialogActionPickerHeader=アクションを選択\ntstr_DialogActionPickerEmpty=利用可能なアクションはありません\ntstr_DialogIconPickerHeader=アイコンを選択\ntstr_DialogIconPickerHeaderTip=カスタムアイコンは \"images\\icons\\\" ディレクトリに PNG ファイルとして追加できます。\ntstr_DialogIconPickerNone=[アイコンがありません]\ntstr_DialogKeyCodePickerHeader=キーコードを選択\ntstr_DialogKeyCodePickerHeaderHotkey=ホットキーを選択\ntstr_DialogKeyCodePickerModifiers=修飾子\ntstr_DialogKeyCodePickerKeyCode=キーコード\ntstr_DialogKeyCodePickerKeyCodeHint=フィルターリスト\ntstr_DialogKeyCodePickerKeyCodeNone=[なし]\ntstr_DialogKeyCodePickerFromInput=入力から...\ntstr_DialogKeyCodePickerFromInputPopup=任意のキーまたはマウスのボタンを押します...\ntstr_DialogKeyCodePickerFromInputPopupNoMouse=どれかキーを押してください...\ntstr_DialogWindowPickerHeader=ウィンドウを選択\ntstr_DialogInputTagsHint=フィルターまたは新規タグ\n\n;Source Strings\ntstr_SourceDesktopAll=統合デスクトップ\ntstr_SourceDesktopID=デスクトップ %ID%\ntstr_SourceWinRTNone=[キャプチャー対象がありません]\ntstr_SourceWinRTUnknown=[不明なウィンドウ]\ntstr_SourceWinRTClosed=[閉じる]:\ntstr_SourcePerformanceMonitor=パフォーマンスモニター\ntstr_SourceBrowser=ブラウザ\ntstr_SourceBrowserNoPage=[ページが読み込めません]\n\n;Notification Icon\ntstr_NotificationIconRestoreVR=VRインターフェイスを復元\ntstr_NotificationIconOpenOnDesktop=デスクトップ設定を開く\ntstr_NotificationIconQuit=閉じる\n\n;Notifications\ntstr_NotificationInitialStartupTitleVR=初期設定\ntstr_NotificationInitialStartupTitleDesktop=Desktop+ セットアップ\ntstr_NotificationInitialStartupMessage=Desktop+ が SteamVR に正常に追加され、SteamVR の実行時に自動的に起動するようになりました。\n\n;Browser\ntstr_BrowserErrorPageTitle=ページ読み込みエラー - Desktop+\ntstr_BrowserErrorPageHeading=ページを読み込めませんでした\ntstr_BrowserErrorPageMessage=%URL% の読み込み中にエラーが発生しました: %ERROR%\n"
  },
  {
    "path": "assets/lang/ko.ini",
    "content": "[TranslationInfo]\nName=한국어 (Korean)\nAuthor=HisaCat\nLocale=ko-KR\n\n[Strings]\n;Settings Window\ntstr_SettingsWindowTitle=Desktop+ 설정\n\ntstr_SettingsCatInterface=인터페이스\ntstr_SettingsCatEnvironment=환경\ntstr_SettingsCatProfiles=프로필\ntstr_SettingsCatActions=액션\ntstr_SettingsCatKeyboard=키보드\ntstr_SettingsCatMouse=마우스\ntstr_SettingsCatLaserPointer=레이저 포인터\ntstr_SettingsCatWindowOverlays=창 오버레이\ntstr_SettingsCatBrowser=브라우저\ntstr_SettingsCatPerformance=성능\ntstr_SettingsCatVersionInfo=버전 정보\ntstr_SettingsCatWarnings=경고\ntstr_SettingsCatStartup=시작 설정\ntstr_SettingsCatTroubleshooting=문제 해결\n\ntstr_SettingsWarningPrefix=경고:\ntstr_SettingsWarningCompositorResolution=SteamVR의 내부 해상도가 100% 미만입니다! 이는 오버레이 렌더링 품질에 영향을 줍니다.\ntstr_SettingsWarningCompositorQuality=SteamVR 오버레이 렌더링 품질이 높음으로 설정되어 있지 않습니다!\ntstr_SettingsWarningProcessElevated=Desktop+가 관리자 권한으로 실행되고 있습니다!\ntstr_SettingsWarningElevatedMode=관리자 모드가 활성화되었습니다!\ntstr_SettingsWarningBrowserMissing=브라우저 오버레이가 사용 중이지만, Desktop+ 브라우저 구성 요소를 현재 사용할 수 없습니다.\ntstr_SettingsWarningBrowserMismatch=설치된 Desktop+ 브라우저 구성 요소가 현재 버전의 Desktop+와 호환되지 않습니다!\ntstr_SettingsWarningElevatedProcessFocus=관리자 권한 프로세스가 포커스를 가지고 있습니다!\\n현재 Desktop+는 입력 시뮬레이션을 할 수 없습니다.\ntstr_SettingsWarningUIAccessLost=Desktop+가 더 이상 UI 접근 권한(UIAccess)으로 실행되고 있지 않습니다!\ntstr_SettingsWarningOverlayCreationErrorLimit=오버레이 생성에 실패했습니다! (오버레이 최대 개수 초과)\ntstr_SettingsWarningOverlayCreationErrorOther=오버레이 생성에 실패했습니다! (%ERRORNAME%)\ntstr_SettingsWarningGraphicsCaptureError=그래픽 캡처 스레드에서 예기치 못한 오류가 발생했습니다! (%ERRORCODE%)\ntstr_SettingsWarningAppProfileActive=%APPNAME%의 애플리케이션 프로필이 현재 오버레이 레이아웃을 덮어썼습니다.\\n활성화된 동안에는 오버레이 변경 사항이 자동 저장되지 않습니다.\ntstr_SettingsWarningConfigMigrated=이전 버전의 Desktop+ 설정 및 프로필이 새로운 형식으로 이전되었습니다.\\n원본 파일은 삭제되지 않아, 이전 버전에서도 사용할 수 있습니다.\\n처음부터 다시 시작하려면 [기본 설정 복원]을 참고하세요.\ntstr_SettingsWarningMenuDontShowAgain=다시 표시하지 않음\ntstr_SettingsWarningMenuDismiss=닫기\n\ntstr_SettingsInterfaceLanguage=언어\ntstr_SettingsInterfaceLanguageCommunity=%AUTHOR%에 의한 커뮤니티 번역\ntstr_SettingsInterfaceLanguageIncompleteWarning=번역이 불완전할 수 있습니다\ntstr_SettingsInterfaceAdvancedSettings=고급 설정 표시\ntstr_SettingsInterfaceAdvancedSettingsTip=자주 사용되지 않는 고급 설정을 표시합니다\ntstr_SettingsInterfaceBlankSpaceDrag=빈 공간 클릭 시 창 드래그\ntstr_SettingsInterfacePersistentUI=UI 상태 유지\ntstr_SettingsInterfacePersistentUIManage=관리\ntstr_SettingsInterfaceDesktopButtons=데스크톱 버튼 표시 방식\ntstr_SettingsInterfaceDesktopButtonsNone=없음\ntstr_SettingsInterfaceDesktopButtonsIndividual=개별 데스크톱\ntstr_SettingsInterfaceDesktopButtonsCycle=순환 버튼\ntstr_SettingsInterfaceDesktopButtonsAddCombined=통합 데스크톱 추가 버튼\n\ntstr_SettingsInterfacePersistentUIHelp=Desktop+는 일반 모드(전역)와 SteamVR 대시보드의 Desktop+ 탭 내에서 각각 별도로 창 상태를 기억합니다.\ntstr_SettingsInterfacePersistentUIHelp2=아래 컨트롤을 사용해 해당 창의 상태를 직접 변경할 수 있습니다.\\n일부 상태는 창을 보이게 하려면 위치를 초기화해야 할 수도 있습니다.\ntstr_SettingsInterfacePersistentUIWindowsHeader=창 목록\ntstr_SettingsInterfacePersistentUIWindowsSettings=설정\ntstr_SettingsInterfacePersistentUIWindowsProperties=오버레이 속성\ntstr_SettingsInterfacePersistentUIWindowsKeyboard=Desktop+ 키보드\ntstr_SettingsInterfacePersistentUIWindowsStateGlobal=전역\ntstr_SettingsInterfacePersistentUIWindowsStateDashboardTab=Desktop+ 탭\ntstr_SettingsInterfacePersistentUIWindowsStateVisible=표시됨\ntstr_SettingsInterfacePersistentUIWindowsStatePinned=고정됨\ntstr_SettingsInterfacePersistentUIWindowsStatePosition=위치\ntstr_SettingsInterfacePersistentUIWindowsStatePositionReset=위치 초기화\ntstr_SettingsInterfacePersistentUIWindowsStateSize=크기\ntstr_SettingsInterfacePersistentUIWindowsStateLaunchRestore=Desktop+ 실행 시 상태 복원\n\ntstr_SettingsEnvironmentBackgroundColor=배경 색상\ntstr_SettingsEnvironmentBackgroundColorDispModeNever=항상 숨김\ntstr_SettingsEnvironmentBackgroundColorDispModeDPlusTab=Desktop+ 탭에서만 표시\ntstr_SettingsEnvironmentBackgroundColorDispModeAlways=항상 표시\ntstr_SettingsEnvironmentDimInterface=어두운 인터페이스\ntstr_SettingsEnvironmentDimInterfaceTip=Desktop+ 대시보드 탭이 열려 있는 동안 SteamVR 대시보드와 Desktop+ UI를 어둡게 표시합니다.\n\ntstr_SettingsProfilesOverlays=오버레이 프로필\ntstr_SettingsProfilesApps=앱 프로필\ntstr_SettingsProfilesManage=관리\n\ntstr_SettingsProfilesOverlaysHeader=오버레이 프로필 관리\ntstr_SettingsProfilesOverlaysNameDefault=기본값\ntstr_SettingsProfilesOverlaysNameNew=[새 프로필]\ntstr_SettingsProfilesOverlaysNameNewBase=프로필 %ID%\ntstr_SettingsProfilesOverlaysProfileLoad=프로필 불러오기\ntstr_SettingsProfilesOverlaysProfileAdd=프로필에서 오버레이 추가\ntstr_SettingsProfilesOverlaysProfileSave=현재 오버레이 저장\ntstr_SettingsProfilesOverlaysProfileDelete=프로필 삭제\ntstr_SettingsProfilesOverlaysProfileDeleteConfirm=정말 삭제하시겠습니까?\ntstr_SettingsProfilesOverlaysProfileFailedLoad=프로필 불러오기 실패\ntstr_SettingsProfilesOverlaysProfileFailedDelete=프로필 삭제 실패\ntstr_SettingsProfilesOverlaysProfileAddSelectHeader=프로필에서 추가할 오버레이 선택\ntstr_SettingsProfilesOverlaysProfileAddSelectEmpty=이 프로필에는 오버레이가 없습니다.\ntstr_SettingsProfilesOverlaysProfileAddSelectDo=선택한 오버레이 추가\ntstr_SettingsProfilesOverlaysProfileAddSelectAll=모두 선택\ntstr_SettingsProfilesOverlaysProfileAddSelectNone=선택 해제\ntstr_SettingsProfilesOverlaysProfileSaveSelectHeader=현재 오버레이 저장\ntstr_SettingsProfilesOverlaysProfileSaveSelectName=프로필 이름\ntstr_SettingsProfilesOverlaysProfileSaveSelectNameErrorBlank=이름은 비워둘 수 없습니다\ntstr_SettingsProfilesOverlaysProfileSaveSelectNameErrorTaken=이미 사용 중인 이름입니다\ntstr_SettingsProfilesOverlaysProfileSaveSelectHeaderList=프로필에 저장할 오버레이 선택\ntstr_SettingsProfilesOverlaysProfileSaveSelectDo=선택한 오버레이 저장\ntstr_SettingsProfilesOverlaysProfileSaveSelectDoFailed=프로필 저장 실패\n\ntstr_SettingsProfilesAppsHeader=앱 프로필 관리\ntstr_SettingsProfilesAppsHeaderNoVRTip=Desktop+가 실행 중이 아닐 때는 기존 앱 프로필만 표시됩니다\ntstr_SettingsProfilesAppsListEmpty=사용 가능한 앱이 없습니다\ntstr_SettingsProfilesAppsProfileHeaderActive=(현재 활성화됨)\ntstr_SettingsProfilesAppsProfileEnabled=앱 실행 시 활성화\ntstr_SettingsProfilesAppsProfileOverlayProfile=오버레이 프로필\ntstr_SettingsProfilesAppsProfileActionEnter=진입 시 실행할 액션\ntstr_SettingsProfilesAppsProfileActionLeave=종료 시 실행할 액션\n\ntstr_SettingsActionsManage=액션\ntstr_SettingsActionsManageButton=관리\ntstr_SettingsActionsButtonsOrderDefault=액션 버튼 (기본)\ntstr_SettingsActionsButtonsOrderOverlayBar=액션 버튼 (오버레이 바)\ntstr_SettingsActionsShowBindings=컨트롤러 바인딩 표시\ntstr_SettingsActionsActiveShortcuts=활성 컨트롤러 버튼\ntstr_SettingsActionsActiveShortcutsTip=오버레이를 가리킬 때의 컨트롤러 바인딩입니다.\\nVR 대시보드 컨트롤러 바인딩을 변경하여 어떤 버튼을 사용할지 설정할 수 있습니다.\ntstr_SettingsActionsActiveShortuctsHome=\"홈으로 이동\"\ntstr_SettingsActionsActiveShortuctsBack=\"뒤로 가기\"\ntstr_SettingsActionsGlobalShortcuts=전역 컨트롤러 버튼\ntstr_SettingsActionsGlobalShortcutsTip=대시보드가 닫혀 있고 오버레이를 가리키지 않을 때의 컨트롤러 바인딩입니다.\\nDesktop+ 컨트롤러 바인딩을 변경하여 어떤 버튼을 사용할지 설정할 수 있습니다.\ntstr_SettingsActionsGlobalShortcutsEntry=전역 단축키 #%ID%\ntstr_SettingsActionsGlobalShortcutsAdd=단축키 추가\ntstr_SettingsActionsGlobalShortcutsRemove=마지막 단축키 제거\ntstr_SettingsActionsHotkeys=핫키\ntstr_SettingsActionsHotkeysTip=시스템 전역 키보드 단축키입니다.\\n단축키를 사용하면 다른 프로그램이 해당 입력을 받지 못하며, 동일한 조합이 이미 다른 곳에 등록되어 있으면 작동하지 않을 수 있습니다.\ntstr_SettingsActionsHotkeysAdd=핫키 추가\ntstr_SettingsActionsHotkeysRemove=삭제\ntstr_SettingsActionsTableHeaderAction=액션\ntstr_SettingsActionsTableHeaderShortcut=단축키\ntstr_SettingsActionsTableHeaderHotkey=핫키\n\ntstr_SettingsActionsManageHeader=액션 관리\ntstr_SettingsActionsManageCopyUID=UID 복사\ntstr_SettingsActionsManageNew=새 액션...\ntstr_SettingsActionsManageEdit=액션 편집\ntstr_SettingsActionsManageDuplicate=액션 복제\ntstr_SettingsActionsManageDelete=액션 삭제\ntstr_SettingsActionsManageDeleteConfirm=정말 삭제하시겠습니까?\ntstr_SettingsActionsManageDuplicatedName=%NAME% (복사본)\n\ntstr_SettingsActionsEditHeader=액션 편집\ntstr_SettingsActionsEditName=이름\ntstr_SettingsActionsEditNameTranslatedTip=이 액션은 현재 번역 문자열 ID를 이름으로 사용하며, 선택된 앱 언어에 자동으로 맞춰집니다\ntstr_SettingsActionsEditTarget=대상\ntstr_SettingsActionsEditTargetDefault=기본값\ntstr_SettingsActionsEditTargetDefaultTip=액션이 실행된 오버레이를 대상으로 하며, 해당 조건이 없으면 현재 포커스된 오버레이를 대상으로 합니다\ntstr_SettingsActionsEditTargetUseTags=태그 사용\ntstr_SettingsActionsEditTargetActionTarget=액션 대상\ntstr_SettingsActionsEditHeaderAppearance=버튼 외형\ntstr_SettingsActionsEditIcon=아이콘\ntstr_SettingsActionsEditLabel=라벨\ntstr_SettingsActionsEditLabelTranslatedTip=이 액션은 현재 번역 문자열 ID를 라벨로 사용하며, 선택된 앱 언어에 자동으로 맞춰집니다\ntstr_SettingsActionsEditHeaderCommands=명령\ntstr_SettingsActionsEditNameNew=새 액션\ntstr_SettingsActionsEditCommandAdd=명령 추가\ntstr_SettingsActionsEditCommandDelete=명령 삭제\ntstr_SettingsActionsEditCommandDeleteConfirm=정말 삭제하시겠습니까?\ntstr_SettingsActionsEditCommandType=명령 유형\ntstr_SettingsActionsEditCommandTypeNone=없음\ntstr_SettingsActionsEditCommandTypeKey=키 누르기\ntstr_SettingsActionsEditCommandTypeMousePos=마우스 위치 설정\ntstr_SettingsActionsEditCommandTypeString=문자열 입력\ntstr_SettingsActionsEditCommandTypeLaunchApp=앱 실행\ntstr_SettingsActionsEditCommandTypeShowKeyboard=키보드 표시\ntstr_SettingsActionsEditCommandTypeCropActiveWindow=활성 창 표시 영역 지정\ntstr_SettingsActionsEditCommandTypeShowOverlay=오버레이 표시\ntstr_SettingsActionsEditCommandTypeSwitchTask=작업 전환\ntstr_SettingsActionsEditCommandTypeLoadOverlayProfile=오버레이 프로필 불러오기\ntstr_SettingsActionsEditCommandTypeUnknown=알 수 없음\ntstr_SettingsActionsEditCommandVisibilityToggle=토글\ntstr_SettingsActionsEditCommandVisibilityShow=항상 표시\ntstr_SettingsActionsEditCommandVisibilityHide=항상 숨김\ntstr_SettingsActionsEditCommandUndo=해제 시 실행 취소\ntstr_SettingsActionsEditCommandKeyCode=키 코드\ntstr_SettingsActionsEditCommandKeyToggle=키 토글\ntstr_SettingsActionsEditCommandMouseX=X\ntstr_SettingsActionsEditCommandMouseY=Y\ntstr_SettingsActionsEditCommandMouseUseCurrent=현재 마우스 위치 사용\ntstr_SettingsActionsEditCommandString=문자열\ntstr_SettingsActionsEditCommandPath=실행 파일 경로\ntstr_SettingsActionsEditCommandPathTip=일반 파일이나 URL도 가능합니다\ntstr_SettingsActionsEditCommandArgs=앱 인자\ntstr_SettingsActionsEditCommandArgsTip=앱에 전달할 인자입니다.\\n모를 경우 비워두세요.\ntstr_SettingsActionsEditCommandVisibility=표시 여부\ntstr_SettingsActionsEditCommandSwitchingMethod=전환 방식\ntstr_SettingsActionsEditCommandSwitchingMethodSwitcher=작업 전환기 표시\ntstr_SettingsActionsEditCommandSwitchingMethodFocus=창 포커스\ntstr_SettingsActionsEditCommandWindow=창\ntstr_SettingsActionsEditCommandWindowNone=[창 없음]\ntstr_SettingsActionsEditCommandWindowStrictMatchingTip=전환할 창을 찾을 때 제목이 완전히 일치하는 경우만 허용\ntstr_SettingsActionsEditCommandCursorWarp=커서를 창 안으로 이동\ntstr_SettingsActionsEditCommandProfile=프로필\ntstr_SettingsActionsEditCommandProfileClear=기존 오버레이 제거\ntstr_SettingsActionsEditCommandDescNone=명령 없음\ntstr_SettingsActionsEditCommandDescKey=키 누르기 \"%KEYNAME%\"\ntstr_SettingsActionsEditCommandDescKeyToggle=키 토글 \"%KEYNAME%\"\ntstr_SettingsActionsEditCommandDescMousePos=마우스 위치를 %X%, %Y%로 설정\ntstr_SettingsActionsEditCommandDescString=문자열 입력 \"%STRING%\"\ntstr_SettingsActionsEditCommandDescLaunchApp=앱 실행 \"%APP%\" %ARGSOPT%\ntstr_SettingsActionsEditCommandDescLaunchAppArgsOpt=\"%ARGS%\"\ntstr_SettingsActionsEditCommandDescKeyboardToggle=키보드 토글\ntstr_SettingsActionsEditCommandDescKeyboardShow=키보드 표시\ntstr_SettingsActionsEditCommandDescKeyboardHide=키보드 숨김\ntstr_SettingsActionsEditCommandDescCropWindow=활성 창 표시 영역 지정\ntstr_SettingsActionsEditCommandDescOverlayToggle=오버레이 토글: %TAGS%\ntstr_SettingsActionsEditCommandDescOverlayShow=오버레이 표시: %TAGS%\ntstr_SettingsActionsEditCommandDescOverlayHide=오버레이 숨김: %TAGS%\ntstr_SettingsActionsEditCommandDescOverlayTargetDefault=[액션 대상]\ntstr_SettingsActionsEditCommandDescSwitchTask=작업 전환\ntstr_SettingsActionsEditCommandDescSwitchTaskWindow=작업을 \"%WINDOW%\"(으)로 전환\ntstr_SettingsActionsEditCommandDescLoadOverlayProfile=오버레이 프로필 \"%PROFILE%\" 불러오기\ntstr_SettingsActionsEditCommandDescLoadOverlayProfileAdd=오버레이 프로필 \"%PROFILE%\" 추가\ntstr_SettingsActionsEditCommandDescUnknown=알 수 없는 명령\n\ntstr_SettingsActionsOrderHeader=액션 순서 변경\ntstr_SettingsActionsOrderButtonLabel=%COUNT%개의 액션 선택됨\ntstr_SettingsActionsOrderButtonLabelSingular=%COUNT%개의 액션 선택됨\ntstr_SettingsActionsOrderNoActions=선택된 액션이 없습니다\ntstr_SettingsActionsOrderAdd=액션 추가...\ntstr_SettingsActionsOrderRemove=액션 제거\n\ntstr_SettingsActionsAddSelectorHeader=추가할 액션 선택\ntstr_SettingsActionsAddSelectorAdd=선택한 액션 추가\n\ntstr_SettingsKeyboardLayout=키보드 레이아웃\ntstr_SettingsKeyboardSize=크기\ntstr_SettingsKeyboardBehavior=동작\ntstr_SettingsKeyboardStickyMod=조합키 고정\ntstr_SettingsKeyboardKeyRepeat=키 반복\ntstr_SettingsKeyboardAutoShow=자동 표시\ntstr_SettingsKeyboardAutoShowDesktopOnly=데스크톱에서만 자동 표시\ntstr_SettingsKeyboardAutoShowDesktop=데스크톱과 창 오버레이에서 자동 표시\ntstr_SettingsKeyboardAutoShowDesktopTip=실험적인 기능. 모든 앱에서 동작하지 않을 수 있습니다.\ntstr_SettingsKeyboardAutoShowBrowser=브라우저 오버레이에서 자동 표시\ntstr_SettingsKeyboardLayoutAuthor=%AUTHOR% 제작\ntstr_SettingsKeyboardKeyClusters=키 그룹\ntstr_SettingsKeyboardKeyClusterBase=일반\ntstr_SettingsKeyboardKeyClusterFunction=기능\ntstr_SettingsKeyboardKeyClusterNavigation=네비게이션\ntstr_SettingsKeyboardKeyClusterNumpad=숫자 키패드\ntstr_SettingsKeyboardKeyClusterExtra=기타\ntstr_SettingsKeyboardSwitchToEditor=키보드 레이아웃 편집기로 전환\n\ntstr_SettingsMouseShowCursor=커서 표시\ntstr_SettingsMouseShowCursorGCUnsupported=이 시스템에서는 그래픽 캡처 오버레이의 커서 비활성화가 지원되지 않습니다\ntstr_SettingsMouseShowCursorGCActiveWarning=활성 그래픽 캡처 미러로 인해 데스크톱 복제 오버레이에서 커서가 숨겨지지 않을 수 있습니다\ntstr_SettingsMouseScrollSmooth=부드러운 스크롤 사용\ntstr_SettingsMouseSimulatePen=펜 입력으로 시뮬레이션\ntstr_SettingsMouseSimulatePenUnsupported=이 시스템에서는 펜 입력 시뮬레이션이 지원되지 않습니다\ntstr_SettingsMouseAllowLaserPointerOverride=레이저 포인터 재정의 허용\ntstr_SettingsMouseAllowLaserPointerOverrideTip=물리 마우스를 빠르게 움직이면 레이저 포인터를 비활성화합니다.\\n오버레이를 클릭하면 레이저 포인터가 다시 활성화됩니다.\ntstr_SettingsMouseDoubleClickAssist=더블 클릭 보조\ntstr_SettingsMouseDoubleClickAssistTip=더블 클릭을 쉽게 하기 위해 마우스 커서를 설정된 시간 동안 고정합니다\ntstr_SettingsMouseDoubleClickAssistTipValueOff=끔\ntstr_SettingsMouseDoubleClickAssistTipValueAuto=자동\ntstr_SettingsMouseSmoothing=부드러운 이동\ntstr_SettingsMouseSmoothingLevelNone=없음\ntstr_SettingsMouseSmoothingLevelVeryLow=매우 낮음\ntstr_SettingsMouseSmoothingLevelLow=낮음\ntstr_SettingsMouseSmoothingLevelMedium=중간\ntstr_SettingsMouseSmoothingLevelHigh=높음\ntstr_SettingsMouseSmoothingLevelVeryHigh=매우 높음\n\ntstr_SettingsLaserPointerTip=이 설정은 SteamVR 대시보드가 닫혀 있을 때 사용되는 Desktop+ 레이저 포인터에 적용됩니다\ntstr_SettingsLaserPointerBlockInput=활성 상태에서 게임 입력 차단\ntstr_SettingsLaserPointerAutoToggleDistance=자동 활성화 최대 거리\ntstr_SettingsLaserPointerAutoToggleDistanceValueOff=끔\ntstr_SettingsLaserPointerHMDPointer=시선 기반 HMD 포인터\ntstr_SettingsLaserPointerHMDPointerTableHeaderInputAction=입력 동작\ntstr_SettingsLaserPointerHMDPointerTableHeaderBinding=키보드 키\ntstr_SettingsLaserPointerHMDPointerTableBindingToggle=레이저 포인터 토글\ntstr_SettingsLaserPointerHMDPointerTableBindingLeft=마우스 왼쪽 버튼\ntstr_SettingsLaserPointerHMDPointerTableBindingRight=마우스 오른쪽 버튼\ntstr_SettingsLaserPointerHMDPointerTableBindingMiddle=마우스 휠 클릭\ntstr_SettingsLaserPointerHMDPointerTableBindingDrag=오버레이 드래그\n\ntstr_SettingsWindowOverlaysAutoFocus=오버레이를 가리킬 때 해당 창에 포커스\ntstr_SettingsWindowOverlaysKeepOnScreen=창을 화면 안에 유지\ntstr_SettingsWindowOverlaysKeepOnScreenTip=소스 창의 경계가 화면 작업 영역 밖에 있으면 자동으로 화면 안으로 이동시킵니다\ntstr_SettingsWindowOverlaysAutoSizeOverlay=창 크기 변경 시 오버레이 크기 조정\ntstr_SettingsWindowOverlaysFocusSceneApp=레이저 포인터가 오버레이를 벗어나면 게임에 포커스\ntstr_SettingsWindowOverlaysFocusSceneAppDashboard=대시보드 닫은 후 게임에 포커스\ntstr_SettingsWindowOverlaysOnWindowDrag=창 드래그 시 동작\ntstr_SettingsWindowOverlaysOnWindowDragDoNothing=아무 것도 하지 않음\ntstr_SettingsWindowOverlaysOnWindowDragBlock=드래그 차단\ntstr_SettingsWindowOverlaysOnWindowDragOverlay=오버레이 드래그\ntstr_SettingsWindowOverlaysOnCaptureLoss=캡처 손실 시 동작\ntstr_SettingsWindowOverlaysOnCaptureLossTip=창 캡처가 사라졌을 때의 동작입니다. 보통 대상 창이 닫히면 발생합니다.\\n\"오버레이 숨김\"을 선택하면 캡처가 복원될 때 오버레이가 다시 표시됩니다.\ntstr_SettingsWindowOverlaysOnCaptureLossDoNothing=아무 것도 하지 않음\ntstr_SettingsWindowOverlaysOnCaptureLossHide=오버레이 숨김\ntstr_SettingsWindowOverlaysOnCaptureLossRemove=오버레이 제거\n\ntstr_SettingsBrowserMaxFrameRate=최대 프레임\ntstr_SettingsBrowserMaxFrameRateOverrideOff=전역 설정 사용\ntstr_SettingsBrowserContentBlocker=콘텐츠 차단기\ntstr_SettingsBrowserContentBlockerTip=Adblock Plus 구문을 사용한 차단 목록을 \"DesktopPlusBrowser\\content_block\" 디렉토리에 추가하세요.\\n디렉토리에 있는 모든 목록이 로드됩니다.\ntstr_SettingsBrowserContentBlockerListCount=(%LISTCOUNT%개의 목록 활성화됨)\ntstr_SettingsBrowserContentBlockerListCountSingular=(%LISTCOUNT%개의 목록 활성화됨)\n\ntstr_SettingsPerformanceUpdateLimiter=업데이트 제한\ntstr_SettingsPerformanceUpdateLimiterMode=업데이트 제한 모드\ntstr_SettingsPerformanceUpdateLimiterModeOff=끔\ntstr_SettingsPerformanceUpdateLimiterModeMS=프레임 시간\ntstr_SettingsPerformanceUpdateLimiterModeFPS=프레임 레이트\ntstr_SettingsPerformanceUpdateLimiterModeOffOverride=전역 설정 사용\ntstr_SettingsPerformanceUpdateLimiterModeMSTip=\"프레임 시간\"은 오버레이 업데이트 사이의 최소 간격을 강제합니다\ntstr_SettingsPerformanceUpdateLimiterFPSValue=%FPS% fps\ntstr_SettingsPerformanceUpdateLimiterOverride=업데이트 제한 재정의\ntstr_SettingsPerformanceUpdateLimiterOverrideTip=여러 재정의가 활성화되어 있을 경우, 가장 높은 업데이트 속도를 제공하는 설정이 사용됩니다\ntstr_SettingsPerformanceUpdateLimiterModeOverride=업데이트 제한 모드 재정의\ntstr_SettingsPerformanceRapidUpdates=레이저 포인터 지연 시간 단축\ntstr_SettingsPerformanceRapidUpdatesTip=오버레이를 가리킬 때 CPU 부하를 늘려 레이저 포인터 입력 지연을 줄입니다\ntstr_SettingsPerformanceSingleDesktopMirror=단일 데스크톱 미러링\ntstr_SettingsPerformanceSingleDesktopMirrorTip=결합된 데스크톱에서 잘라내는 대신, 전환 시 각 데스크톱을 개별적으로 미러링합니다.\\n이 기능이 활성화되면 모든 오버레이가 동일한 데스크톱을 표시합니다.\ntstr_SettingsPerformanceUseHDR=HDR 미러링\ntstr_SettingsPerformanceUseHDRTip=데스크톱과 창을 더 높은 비트 깊이의 텍스처로 미러링하여 HDR 출력을 지원합니다. (실험적인 기능)\\n필요하지 않은 경우 성능에 부정적인 영향을 줄 수 있으며 VRAM 사용량이 증가합니다.\ntstr_SettingsPerformanceShowFPS=플로팅 UI에 FPS 표시\n\ntstr_SettingsWarningsHidden=숨긴 경고:\ntstr_SettingsWarningsReset=숨긴 경고 초기화\n\ntstr_SettingsStartupAutoLaunch=SteamVR 시작 시 자동 실행\ntstr_SettingsStartupSteamDisable=Steam 연동 비활성화\ntstr_SettingsStartupSteamDisableTip=Steam을 통해 실행된 경우 Steam 없이 Desktop+를 재시작합니다.\\n이 기능을 사용하면 앱 내 상태, 사용 시간 통계, 기타 Steam 기능이 비활성화됩니다.\n\ntstr_SettingsTroubleshootingRestart=재시작\ntstr_SettingsTroubleshootingRestartSteam=Steam으로 재시작\ntstr_SettingsTroubleshootingRestartDesktop=데스크톱 모드로 재시작\ntstr_SettingsTroubleshootingElevatedModeEnter=관리자 모드 진입\ntstr_SettingsTroubleshootingElevatedModeLeave=관리자 모드 종료\ntstr_SettingsTroubleshootingSettingsReset=기본 설정 복원\ntstr_SettingsTroubleshootingSettingsResetConfirmDescription=기본 설정을 복원하면 선택된 항목이 기본값으로 초기화됩니다.\\n이 작업은 되돌릴 수 없습니다.\\n\\n다음 항목이 초기화됩니다:\ntstr_SettingsTroubleshootingSettingsResetConfirmButton=선택 항목 초기화\ntstr_SettingsTroubleshootingSettingsResetConfirmElementOverlays=현재 오버레이 구성\ntstr_SettingsTroubleshootingSettingsResetConfirmElementLegacyFiles=사용하지 않는 이전 설정 및 프로필 파일 삭제\ntstr_SettingsTroubleshootingSettingsResetShowQuickStart=퀵스타트 가이드 표시\n\n;Keyboard Window\n;Keyboard Window\ntstr_KeyboardWindowTitle=Desktop+ 키보드\ntstr_KeyboardWindowTitleSettings=Desktop+ 키보드 (설정)\ntstr_KeyboardWindowTitleOverlay=Desktop+ 키보드 (%OVERLAYNAME%)\ntstr_KeyboardWindowTitleOverlayUnknown=[알 수 없는 오버레이]\n\n;Keyboard Shortcuts Window\ntstr_KeyboardShortcutsCut=잘라내기\ntstr_KeyboardShortcutsCopy=복사\ntstr_KeyboardShortcutsPaste=붙여넣기\n\n;Overlay Properties Window\ntstr_OvrlPropsCatPosition=위치\ntstr_OvrlPropsCatAppearance=외형\ntstr_OvrlPropsCatCapture=캡처\ntstr_OvrlPropsCatPerformanceMonitor=성능 모니터\ntstr_OvrlPropsCatBrowser=브라우저\ntstr_OvrlPropsCatAdvanced=고급\ntstr_OvrlPropsCatPerformance=성능\ntstr_OvrlPropsCatInterface=인터페이스\n\ntstr_OvrlPropsPositionOrigin=기준점\ntstr_OvrlPropsPositionOriginRoom=플레이 구역\ntstr_OvrlPropsPositionOriginHMDXY=HMD 바닥 위치\ntstr_OvrlPropsPositionOriginDashboard=대시보드\ntstr_OvrlPropsPositionOriginHMD=HMD\ntstr_OvrlPropsPositionOriginSeatedSpace=앉은 위치\ntstr_OvrlPropsPositionOriginControllerR=오른쪽 컨트롤러\ntstr_OvrlPropsPositionOriginControllerL=왼쪽 컨트롤러\ntstr_OvrlPropsPositionOriginTheaterScreen=시어터 스크린\ntstr_OvrlPropsPositionOriginTracker1=트래커 #1\ntstr_OvrlPropsPositionOriginConfigHMDXYTurning=HMD 회전에 맞춰 회전\ntstr_OvrlPropsPositionOriginConfigTheaterScreenEnter=시어터 모드 진입\ntstr_OvrlPropsPositionOriginConfigTheaterScreenLeave=시어터 모드 종료\ntstr_OvrlPropsPositionOriginTheaterScreenTip=일부 속성은 SteamVR 시어터 스크린에 의해 제어됩니다\ntstr_OvrlPropsPositionDispMode=표시 모드\ntstr_OvrlPropsPositionDispModeAlways=항상 표시\ntstr_OvrlPropsPositionDispModeDashboard=대시보드에서만\ntstr_OvrlPropsPositionDispModeScene=게임 중에만\ntstr_OvrlPropsPositionDispModeDPlus=Desktop+ 탭에서만\ntstr_OvrlPropsPositionPos=위치\ntstr_OvrlPropsPositionPosTip=위치는 Desktop+ 실행 중에만 변경하거나 초기화할 수 있습니다\ntstr_OvrlPropsPositionChange=변경\ntstr_OvrlPropsPositionReset=초기화\ntstr_OvrlPropsPositionLock=잠금\n\ntstr_OvrlPropsPositionChangeHeader=오버레이 위치 변경\ntstr_OvrlPropsPositionChangeHelp=오버레이를 드래그하여 위치를 변경합니다.\\n두 손 제스처 변환은 마우스 오른쪽 버튼을 길게 누릅니다.\ntstr_OvrlPropsPositionChangeHelpDesktop=\"D\" 드래그 버튼을 누른 상태에서 마우스로 오버레이를 이동 또는 회전합니다\ntstr_OvrlPropsPositionChangeManualAdjustment=수동 조정\ntstr_OvrlPropsPositionChangeMove=이동\ntstr_OvrlPropsPositionChangeRotate=회전\ntstr_OvrlPropsPositionChangeForward=앞으로 이동\ntstr_OvrlPropsPositionChangeBackward=뒤로 이동\ntstr_OvrlPropsPositionChangeRollCW=Yaw ⟳\ntstr_OvrlPropsPositionChangeRollCCW=Yaw ⟲\ntstr_OvrlPropsPositionChangeLookAt=HMD 방향으로\ntstr_OvrlPropsPositionChangeDragButton=D\ntstr_OvrlPropsPositionChangeOffset=추가 오프셋\ntstr_OvrlPropsPositionChangeOffsetUpDown=상/하 오프셋\ntstr_OvrlPropsPositionChangeOffsetRightLeft=좌/우 오프셋\ntstr_OvrlPropsPositionChangeOffsetForwardBackward=앞/뒤 오프셋\ntstr_OvrlPropsPositionChangeDragSettings=드래그 설정\ntstr_OvrlPropsPositionChangeDragSettingsAutoDocking=가까운 컨트롤러에 도킹\ntstr_OvrlPropsPositionChangeDragSettingsForceDistance=고정 거리 강제\ntstr_OvrlPropsPositionChangeDragSettingsForceDistanceShape=형태\ntstr_OvrlPropsPositionChangeDragSettingsForceDistanceShapeSphere=구\ntstr_OvrlPropsPositionChangeDragSettingsForceDistanceShapeCylinder=실린더\ntstr_OvrlPropsPositionChangeDragSettingsForceDistanceAutoCurve=자동 곡률\ntstr_OvrlPropsPositionChangeDragSettingsForceDistanceAutoTilt=자동 기울기\ntstr_OvrlPropsPositionChangeDragSettingsSnapPosition=위치 스냅\ntstr_OvrlPropsPositionChangeDragSettingsSnapRotation=회전 스냅\ntstr_OvrlPropsPositionChangeDragSettingsSnapRotationPitch=Pitch\ntstr_OvrlPropsPositionChangeDragSettingsSnapRotationYaw=Yaw\ntstr_OvrlPropsPositionChangeDragSettingsSnapRotationRoll=Roll\n\n\ntstr_OvrlPropsAppearanceWidth=너비\ntstr_OvrlPropsAppearanceCurve=곡률\ntstr_OvrlPropsAppearanceOpacity=불투명도\ntstr_OvrlPropsAppearanceBrightness=밝기\ntstr_OvrlPropsAppearanceCrop=표시 영역 지정\ntstr_OvrlPropsAppearanceCropValueMax=최대\n\ntstr_OvrlPropsCrop=표시 영역\ntstr_OvrlPropsCropHelp=사각형을 드래그하여 표시 영역을 변경합니다.\\n스크롤 입력이나 모서리를 드래그해 크기를 조정할 수 있습니다.\ntstr_OvrlPropsCropManualAdjust=수동 조정\ntstr_OvrlPropsCropInvalidTip=현재 표시 영역 사각형이 유효하지 않습니다. 오버레이가 표시되지 않을 수 있습니다.\ntstr_OvrlPropsCropX=X\ntstr_OvrlPropsCropY=Y\ntstr_OvrlPropsCropWidth=너비\ntstr_OvrlPropsCropHeight=높이\ntstr_OvrlPropsCropToWindow=활성 창 표시 영역\n\ntstr_OvrlPropsCaptureMethod=캡처 방식\ntstr_OvrlPropsCaptureMethodDup=데스크톱 복제\ntstr_OvrlPropsCaptureMethodGC=그래픽 캡처\ntstr_OvrlPropsCaptureMethodGCUnsupportedTip=이 시스템에서는 그래픽 캡처를 지원하지 않습니다\ntstr_OvrlPropsCaptureMethodGCUnsupportedPartialTip=이 시스템에서는 일부 그래픽 캡처 기능이 지원되지 않습니다\ntstr_OvrlPropsCaptureSource=소스\ntstr_OvrlPropsCaptureGCSource=그래픽 캡처 소스\ntstr_OvrlPropsCaptureSourceUnknownWarning=이 오버레이는 알 수 없는 캡처 소스를 사용하고 있어 올바르게 동작하지 않을 수 있습니다\ntstr_OvrlPropsCaptureGCStrictMatching=엄격한 창 일치 사용\ntstr_OvrlPropsCaptureGCStrictMatchingTip=이 오버레이의 캡처를 복원할 때 창 제목이 완전히 일치하는 경우에만 허용합니다\n\ntstr_OvrlPropsPerfMonDesktopModeTip=데스크톱 모드에서는 성능 모니터 오버레이가 업데이트되지 않습니다\ntstr_OvrlPropsPerfMonGlobalTip=이 설정은 모든 성능 모니터 오버레이에 적용됩니다\ntstr_OvrlPropsPerfMonStyle=스타일\ntstr_OvrlPropsPerfMonStyleCompact=컴팩트\ntstr_OvrlPropsPerfMonStyleLarge=라지\ntstr_OvrlPropsPerfMonShowCPU=CPU 상태 표시\ntstr_OvrlPropsPerfMonShowGPU=GPU 상태 표시\ntstr_OvrlPropsPerfMonShowGraphs=그래프 표시\ntstr_OvrlPropsPerfMonShowFrameStats=프레임 통계 표시\ntstr_OvrlPropsPerfMonShowTime=시계 표시\ntstr_OvrlPropsPerfMonShowBattery=배터리 상태 표시\ntstr_OvrlPropsPerfMonShowTrackerBattery=트래커 배터리 잔량 표시\ntstr_OvrlPropsPerfMonShowViveWirelessTemp=Vive 무선 온도 표시\ntstr_OvrlPropsPerfMonResetValues=누적값 초기화\n\ntstr_OvrlPropsBrowserNotAvailableTip=Desktop+ 브라우저 구성 요소가 설치되지 않았습니다\ntstr_OvrlPropsBrowserCloned=복제 출력\ntstr_OvrlPropsBrowserClonedTip=이 오버레이는 \"%OVERLAYNAME%\"의 복제본입니다.\\n브라우저 속성 변경은 원본과 이를 복제한 모든 오버레이에 적용됩니다.\ntstr_OvrlPropsBrowserClonedConvert=독립형으로 변환\ntstr_OvrlPropsBrowserURL=URL\ntstr_OvrlPropsBrowserURLHint=주소를 입력하세요\ntstr_OvrlPropsBrowserGo=이동\ntstr_OvrlPropsBrowserRestore=마지막 입력 복원\ntstr_OvrlPropsBrowserWidth=너비\ntstr_OvrlPropsBrowserHeight=높이\ntstr_OvrlPropsBrowserZoom=줌\ntstr_OvrlPropsBrowserAllowTransparency=투명도 허용\ntstr_OvrlPropsBrowserAllowTransparencyTip=웹 페이지의 투명도 사용을 허용합니다.\\n배경색이 지정되지 않은 사이트는 올바르게 표시되지 않을 수 있습니다.\ntstr_OvrlPropsBrowserRecreateContext=브라우저 컨텍스트 재생성\ntstr_OvrlPropsBrowserRecreateContextTip=변경 사항을 적용하려면 브라우저 컨텍스트를 재생성해야 합니다.\\n이 작업을 실행하면 페이지가 새로고침되고 방문 기록이 초기화됩니다.\n\ntstr_OvrlPropsAdvanced3D=3D\ntstr_OvrlPropsAdvancedHSBS=Half Side-by-Side (좌우 절반 폭)\ntstr_OvrlPropsAdvancedSBS=Side-by-Side (좌우)\ntstr_OvrlPropsAdvancedHOU=Half Over-Under (상하 절반 폭)\ntstr_OvrlPropsAdvancedOU=Over-Under (상하)\ntstr_OvrlPropsAdvanced3DSwap=좌우 화면 바꾸기\ntstr_OvrlPropsAdvancedGazeFade=시선 페이드\ntstr_OvrlPropsAdvancedGazeFadeAuto=자동 구성\ntstr_OvrlPropsAdvancedGazeFadeDistance=거리\ntstr_OvrlPropsAdvancedGazeFadeDistanceValueInf=무한\ntstr_OvrlPropsAdvancedGazeFadeSensitivity=민감도\ntstr_OvrlPropsAdvancedGazeFadeOpacity=대상 불투명도\ntstr_OvrlPropsAdvancedInput=레이저 포인터 입력\ntstr_OvrlPropsAdvancedInputInGame=게임 중 활성화\ntstr_OvrlPropsAdvancedInputFloatingUI=플로팅 UI 표시\ntstr_OvrlPropsAdvancedOverlayTags=오버레이 태그\ntstr_OvrlPropsAdvancedOverlayTagsTip=오버레이 태그는 액션에서 오버레이를 대상으로 지정하는 데 사용됩니다\n\ntstr_OvrlPropsPerformanceInvisibleUpdate=숨겨져 있어도 업데이트\ntstr_OvrlPropsPerformanceInvisibleUpdateTip=불투명도 설정이나 시선 페이드로 숨겨져 있어도 오버레이를 업데이트합니다.\\n서드파티 애플리케이션이 오버레이 내용을 접근하는 데 유용합니다.\\n그 외 경우에는 권장하지 않습니다.\\n오버레이가 수동 또는 표시 모드 설정으로 숨겨진 경우에는 여전히 업데이트가 중단됩니다.\n\ntstr_OvrlPropsInterfaceOverlayName=오버레이 이름\ntstr_OvrlPropsInterfaceOverlayNameAuto=[자동 이름]\ntstr_OvrlPropsInterfaceActionOrderCustom=액션 버튼 재정의\ntstr_OvrlPropsInterfaceDesktopButtons=데스크톱 버튼 표시\ntstr_OvrlPropsInterfaceExtraButtons=추가 버튼 표시\n\n;Overlay Bar\ntstr_OverlayBarOvrlHide=숨기기\ntstr_OverlayBarOvrlShow=표시\ntstr_OverlayBarOvrlClone=복제\ntstr_OverlayBarOvrlRemove=제거\ntstr_OverlayBarOvrlRemoveConfirm=정말 제거하시겠습니까?\ntstr_OverlayBarOvrlProperties=속성...\ntstr_OverlayBarOvrlAddWindow=창 추가...\ntstr_OverlayBarTooltipOvrlAdd=오버레이 추가\ntstr_OverlayBarTooltipSettings=설정\ntstr_OverlayBarTooltipResetHold=길게 눌러 창 위치 초기화...\n\n;Floating UI\ntstr_FloatingUIHideOverlayTip=오버레이 숨기기\ntstr_FloatingUIHideOverlayHoldTip=길게 눌러 오버레이 제거...\ntstr_FloatingUIDragModeEnableTip=드래그 모드 활성화\ntstr_FloatingUIDragModeDisableTip=드래그 모드 비활성화\ntstr_FloatingUIDragModeHoldLockTip=길게 눌러 오버레이 위치 잠금...\ntstr_FloatingUIDragModeHoldUnlockTip=길게 눌러 오버레이 위치 잠금 해제...\ntstr_FloatingUIWindowAddTip=활성 창 추가\ntstr_FloatingUIActionBarShowTip=액션 바 표시\ntstr_FloatingUIActionBarHideTip=액션 바 숨기기\ntstr_FloatingUIBrowserGoBackTip=이전 페이지로 이동\ntstr_FloatingUIBrowserGoForwardTip=다음 페이지로 이동\ntstr_FloatingUIBrowserRefreshTip=현재 페이지 새로고침\ntstr_FloatingUIBrowserStopTip=페이지 로드 중지\ntstr_FloatingUIActionBarDesktopPrev=이전 데스크톱\ntstr_FloatingUIActionBarDesktopNext=다음 데스크톱\ntstr_FloatingUIActionBarEmpty=활성화된 액션 없음\n\n;Special Actions\ntstr_ActionNone=[없음]\ntstr_ActionKeyboardShow=키보드 표시\ntstr_ActionKeyboardHide=키보드 숨기기\n\n;Default Actions (translation IDs are matched to Action name string)\ntstr_DefActionShowKeyboard=키보드 표시\ntstr_DefActionActiveWindowCrop=활성 창 크롭\ntstr_DefActionActiveWindowCropLabel=활성\\n창\\n크롭\ntstr_DefActionSwitchTask=작업 전환\ntstr_DefActionToggleOverlays=오버레이 전환\ntstr_DefActionToggleOverlaysLabel=오버레이\\n전환\ntstr_DefActionMiddleMouse=마우스 휠 클릭\ntstr_DefActionMiddleMouseLabel=마우스\\n휠\\n클릭\ntstr_DefActionBackMouse=마우스 뒤로가기 버튼\ntstr_DefActionBackMouseLabel=마우스\\n뒤로가기\\n버튼\ntstr_DefActionReadMe=설명서 열기\ntstr_DefActionReadMeLabel=설명서\\n열기\ntstr_DefActionDashboardToggle=SteamVR 대시보드 전환 (디버그 명령)\ntstr_DefActionDashboardToggleLabel=대시보드\\n전환\n\n;Performance Monitor (text space is very limited here, so keep it short or untranslated)\ntstr_PerformanceMonitorCPU=CPU\ntstr_PerformanceMonitorGPU=GPU\ntstr_PerformanceMonitorRAM=RAM:\ntstr_PerformanceMonitorVRAM=VRAM:\ntstr_PerformanceMonitorFrameTime=프레임 시간:\ntstr_PerformanceMonitorLoad=부하:\ntstr_PerformanceMonitorFPS=FPS:\ntstr_PerformanceMonitorFPSAverage=평균 FPS:\ntstr_PerformanceMonitorReprojectionRatio=리프로젝션 비율:\ntstr_PerformanceMonitorDroppedFrames=드롭된 프레임:\ntstr_PerformanceMonitorBatteryLeft=왼쪽 컨트롤러:\ntstr_PerformanceMonitorBatteryRight=오른쪽 컨트롤러:\ntstr_PerformanceMonitorBatteryHMD=헤드셋:\ntstr_PerformanceMonitorBatteryTracker=트래커 %ID%:\ntstr_PerformanceMonitorBatteryDisconnected=N/A\ntstr_PerformanceMonitorViveWirelessTempNotAvailable=N/A\ntstr_PerformanceMonitorCompactCPU=CPU\ntstr_PerformanceMonitorCompactGPU=GPU\ntstr_PerformanceMonitorCompactFPS=FPS\ntstr_PerformanceMonitorCompactFPSAverage=AVG\ntstr_PerformanceMonitorCompactReprojectionRatio=% RPR\ntstr_PerformanceMonitorCompactDroppedFrames=DRP\ntstr_PerformanceMonitorCompactBattery=BAT\ntstr_PerformanceMonitorCompactBatteryLeft=L\ntstr_PerformanceMonitorCompactBatteryRight=R\ntstr_PerformanceMonitorCompactBatteryHMD=H\ntstr_PerformanceMonitorCompactBatteryTracker=T%ID%\ntstr_PerformanceMonitorCompactBatteryDisconnected=N/A\ntstr_PerformanceMonitorCompactViveWirelessTempNotAvailable=N/A\ntstr_PerformanceMonitorEmpty=활성화된 성능 모니터 항목 없음\n\n;Aux UI\ntstr_AuxUIDragHintDocking=놓아서 도킹\ntstr_AuxUIDragHintUndocking=놓아서 도킹 해제\ntstr_AuxUIDragHintOvrlLocked=이 오버레이를 드래그하려면 오버레이 위치 잠금을 해제하세요\ntstr_AuxUIDragHintOvrlTheaterScreenBlocked=오버레이 위치는 SteamVR 시어터 스크린에서 제어합니다\ntstr_AuxUIGazeFadeAutoHint=오버레이 중앙을 보고 %SECONDS%초간 기다리세요...\ntstr_AuxUIGazeFadeAutoHintSingular=오버레이 중앙을 보고 %SECONDS%초간 기다리세요...\n\ntstr_AuxUIQuickStartWelcomeHeader=Desktop+에 오신 것을 환영합니다!\ntstr_AuxUIQuickStartWelcomeBody=이 짧은 가이드는 프로그램의 기본 사항을 소개합니다.\\n자세한 내용은 설명서와 사용자 가이드를 참조하세요.\\n\\n이 단계를 건너뛰려면 [닫기]를 누르세요.\ntstr_AuxUIQuickStartOverlaysHeader=오버레이\ntstr_AuxUIQuickStartOverlaysBody=Desktop+를 사용하면 데스크톱, 개별 창 등을 미러링하는 오버레이를 만들 수 있습니다.\\n생성한 모든 오버레이는 아래 오버레이 바에 표시됩니다.\\n\\n오버레이 속성은 오버레이 아이콘을 클릭하고 [속성...]을 선택해 변경할 수 있습니다.\ntstr_AuxUIQuickStartOverlaysBody2=[+] 버튼을 눌러 목록에서 캡처 소스/오버레이 유형을 선택하면 새 오버레이를 만들 수 있습니다.\\n웹 브라우저 오버레이와 같은 일부 유형은 해당 구성 요소가 설치되어 있어야 사용할 수 있습니다.\\n\\n개별 오버레이 또는 전체 레이아웃을 프로필로 저장할 수 있으며, 현재 구성은 세션 간 자동으로 기억됩니다.\ntstr_AuxUIQuickStartOverlayPropertiesHeader=오버레이 속성\ntstr_AuxUIQuickStartOverlayPropertiesBody=오버레이에는 다양한 맞춤 설정 옵션이 있습니다.\\n기준점과 표시 모드는 오버레이가 표시되는 위치와 시점을 설정합니다.\\n표시되어야 할 오버레이가 보이지 않으면 이 설정을 확인하세요.\\n위치를 초기화하면 도움이 될 수 있습니다.\\n\\n또한 오버레이를 컨트롤러 근처로 드래그하면 도킹할 수 있습니다.\ntstr_AuxUIQuickStartOverlayPropertiesBody2=일부 속성은 기본적으로 숨겨져 있습니다.\\n모든 옵션을 보려면 설정 창에서 [고급 설정 표시]를 켜세요.\\n\\nUI 창 위치는 해당 버튼을 길게 눌러 초기화할 수 있습니다.\\n더블 클릭이나 우클릭은 오버레이 버튼에서도 단축 토글로 동작합니다.\ntstr_AuxUIQuickStartSettingsHeader=설정\ntstr_AuxUIQuickStartSettingsBody=설정 창에는 Desktop+의 전역 설정이 모두 포함되어 있습니다.\\n오버레이 바 오른쪽 끝의 톱니바퀴 버튼을 눌러 열 수 있습니다.\\n\\n오버레이와 마찬가지로 설정 변경 사항은 자동으로 적용·저장됩니다.\ntstr_AuxUIQuickStartProfilesHeader=프로필\ntstr_AuxUIQuickStartProfilesBody=Desktop+에는 두 가지 프로필이 있습니다.\\n\\n오버레이 프로필:\\n하나 이상의 오버레이와 해당 속성을 저장합니다.\\n수동으로 불러오거나, 액션 및 앱 프로필에 따라 자동 불러올 수 있습니다.\\n\\n앱 프로필:\\nSteamVR 앱 실행 시 자동으로 불러올 오버레이 프로필과 액션을 지정합니다.\ntstr_AuxUIQuickStartActionsHeader=액션\ntstr_AuxUIQuickStartActionsBody=액션은 컨트롤러 입력이나 앱 프로필에 할당하거나, 오버레이의 액션 바에 추가할 수 있는 일련의 명령입니다.\\n데스크톱 입력 시뮬레이션부터 오버레이 레이아웃 변경, 외부 프로그램 실행까지 가능합니다.\\n\\nDesktop+에는 몇 가지 예시 액션이 포함되어 있습니다.\ntstr_AuxUIQuickStartActionsBody2=기본적으로 액션은 실행된 오버레이(버튼 클릭·컨트롤러 입력) 또는 마지막으로 클릭된 포커스 오버레이를 대상으로 합니다.\\n대상 오버레이가 중요하지 않은 경우도 있지만, 경우에 따라 특정 오버레이나 여러 개를 지정하면 유용할 수 있습니다.\ntstr_AuxUIQuickStartOverlayTagsHeader=오버레이 태그\ntstr_AuxUIQuickStartOverlayTagsBody=오버레이 태그에는 자동 태그(녹색)와 사용자 정의 태그가 있습니다.\\n자동 태그는 해당 속성에 따라 자동 부여되며, 사용자 태그는 오버레이 속성 창에서 수동 지정할 수 있습니다.\\n\\n전역 단축키는 컨트롤러 버튼에 직접 바인딩되지 않으므로, SteamVR 입력 바인딩에서 매핑해야 합니다.\ntstr_AuxUIQuickStartSettingsEndBody=설정은 다양하니 자유롭게 시도해 보세요.\\n\\n문제가 생기면 [기본 설정 복원]을 눌러 특정 항목만 초기화할 수도 있습니다.\ntstr_AuxUIQuickStartFloatingUIHeader=플로팅 UI\ntstr_AuxUIQuickStartFloatingUIBody=플로팅 UI는 오버레이를 가리킬 때 나타납니다.\\n대시보드에서는 다른 오버레이를 가리키지 않을 때 항상 표시됩니다.\\n\\n기본 컨트롤, 액션 버튼 섹션, 기타 오버레이별 컨트롤이 포함되며, 각 오버레이 속성에서 표시 여부를 설정할 수 있습니다.\ntstr_AuxUIQuickStartDesktopModeHeader=데스크톱 모드\ntstr_AuxUIQuickStartDesktopModeBody=Desktop+는 데스크톱 창에서도 설정할 수 있어, 키보드 입력이 잦거나 컨트롤러 없이 변경할 때 유용합니다.\\n기능은 동일하지만 VR 키보드 레이아웃 편집기는 데스크톱 모드에서만 사용할 수 있습니다.\\n\\n설정 창의 [문제 해결] 섹션이나 시스템 트레이 아이콘에서 전환할 수 있습니다.\ntstr_AuxUIQuickStartEndHeader=설명서 & 사용자 가이드\ntstr_AuxUIQuickStartEndBody=이 정도면 Desktop+를 시작하기 충분합니다.\\n\\n문제가 있으면 설명서를 참고하세요. 액션 바 버튼으로 열 수 있습니다.\\n자세한 설명과 가이드는 사용자 가이드에 있습니다.\\n\\n이 가이드를 다시 보려면 [기본 설정 복원] 페이지 하단의 [퀵 스타트 가이드 표시]를 누르세요.\ntstr_AuxUIQuickStartButtonNext=다음 페이지\ntstr_AuxUIQuickStartButtonPrev=이전 페이지\ntstr_AuxUIQuickStartButtonClose=닫기\n\n;Desktop Mode\ntstr_DesktopModeCatTools=도구\ntstr_DesktopModeCatOverlays=오버레이\ntstr_DesktopModeToolSettings=설정\ntstr_DesktopModeToolActions=액션\ntstr_DesktopModeOverlayListAdd=오버레이 추가\ntstr_DesktopModePageAddWindowOverlayTitle=창 오버레이 추가\ntstr_DesktopModePageAddWindowOverlayHeader=창 선택\n\n;Keyboard Editor\ntstr_KeyboardEditorKeyListTitle=키 목록\ntstr_KeyboardEditorKeyListTabContextReplace=내용 교체...\ntstr_KeyboardEditorKeyListTabContextClear=서브 레이아웃 지우기\ntstr_KeyboardEditorKeyListRow=행 %ID%\ntstr_KeyboardEditorKeyListSpacing=[간격]\ntstr_KeyboardEditorKeyListKeyAdd=추가\ntstr_KeyboardEditorKeyListKeyDuplicate=복제\ntstr_KeyboardEditorKeyListKeyRemove=삭제\n\ntstr_KeyboardEditorKeyPropertiesTitle=키 속성\ntstr_KeyboardEditorKeyPropertiesNoSelection=선택된 키 없음\ntstr_KeyboardEditorKeyPropertiesType=유형\ntstr_KeyboardEditorKeyPropertiesTypeBlank=빈 공간\ntstr_KeyboardEditorKeyPropertiesTypeVirtualKey=가상 키\ntstr_KeyboardEditorKeyPropertiesTypeVirtualKeyToggle=가상 키 (토글)\ntstr_KeyboardEditorKeyPropertiesTypeVirtualKeyIsoEnter=가상 키 (ISO-Enter)\ntstr_KeyboardEditorKeyPropertiesTypeString=문자열\ntstr_KeyboardEditorKeyPropertiesTypeSublayoutToggle=서브 레이아웃 전환\ntstr_KeyboardEditorKeyPropertiesTypeAction=액션\ntstr_KeyboardEditorKeyPropertiesTypeVirtualKeyIsoEnterTip=인접한 두 행에 \"가상 키 (ISO-Enter)\"를 배치해 ISO-Enter 모양 키를 만듭니다.\\n각 서브 레이아웃당 하나만 허용됩니다.\ntstr_KeyboardEditorKeyPropertiesTypeStringTip=기본 문자가 아닌 키에는 문자열 유형을 사용해, 실제 키보드 레이아웃에 맞는 조합을 찾아 호환성을 높입니다.\ntstr_KeyboardEditorKeyPropertiesSize=크기\ntstr_KeyboardEditorKeyPropertiesLabel=레이블\ntstr_KeyboardEditorKeyPropertiesKeyCode=키 코드\ntstr_KeyboardEditorKeyPropertiesString=문자열\ntstr_KeyboardEditorKeyPropertiesSublayout=서브 레이아웃\ntstr_KeyboardEditorKeyPropertiesAction=액션\ntstr_KeyboardEditorKeyPropertiesCluster=그룹\ntstr_KeyboardEditorKeyPropertiesClusterTip=그룹은 특정 키 로드를 비활성화할 때 사용됩니다.\\n주변 빈 공간은 제거되지 않으니, 올바른 전환을 위해 빈 공간 키도 그룹에 배정하세요.\ntstr_KeyboardEditorKeyPropertiesBlockModifiers=조합 키 차단\ntstr_KeyboardEditorKeyPropertiesBlockModifiersTip=키를 누르는 동안 모든 조합 키를 해제합니다.\ntstr_KeyboardEditorKeyPropertiesNoRepeat=반복 안 함\ntstr_KeyboardEditorKeyPropertiesNoRepeatTip=키 반복이 켜져 있어도, 누르고 있는 동안 반복 입력을 차단합니다.\n\ntstr_KeyboardEditorMetadataTitle=레이아웃 메타데이터\ntstr_KeyboardEditorMetadataName=이름\ntstr_KeyboardEditorMetadataAuthor=작성자\ntstr_KeyboardEditorMetadataHasAltGr=AltGr 키 사용\ntstr_KeyboardEditorMetadataHasAltGrTip=오른쪽 Alt 키를 누르면 AltGr 서브 레이아웃으로 전환\ntstr_KeyboardEditorMetadataClusterPreview=그룹 미리보기:\ntstr_KeyboardEditorMetadataSave=저장...\ntstr_KeyboardEditorMetadataLoad=불러오기...\ntstr_KeyboardEditorMetadataSavePopupTitle=키보드 레이아웃 저장\ntstr_KeyboardEditorMetadataSavePopupFilename=파일 이름\ntstr_KeyboardEditorMetadataSavePopupFilenameBlankTip=파일 이름은 비워둘 수 없습니다\ntstr_KeyboardEditorMetadataSavePopupConfirm=레이아웃 저장\ntstr_KeyboardEditorMetadataSavePopupConfirmError=레이아웃 저장 실패\ntstr_KeyboardEditorMetadataLoadPopupTitle=키보드 레이아웃 불러오기\ntstr_KeyboardEditorMetadataLoadPopupConfirm=레이아웃 불러오기\n\ntstr_KeyboardEditorPreviewTitle=키보드 미리보기\n\ntstr_KeyboardEditorSublayoutBase=Base\ntstr_KeyboardEditorSublayoutShift=Shift\ntstr_KeyboardEditorSublayoutAltGr=AltGr\ntstr_KeyboardEditorSublayoutAux=Aux\n\n;General Dialog Strings\ntstr_DialogOk=확인\ntstr_DialogCancel=취소\ntstr_DialogDone=완료\ntstr_DialogUndo=실행 취소\ntstr_DialogRedo=다시 실행\ntstr_DialogColorPickerHeader=색 선택\ntstr_DialogColorPickerCurrent=현재\ntstr_DialogColorPickerOriginal=원본\ntstr_DialogProfilePickerHeader=프로필 선택\ntstr_DialogProfilePickerNone=[없음]\ntstr_DialogActionPickerHeader=액션 선택\ntstr_DialogActionPickerEmpty=사용 가능한 액션 없음\ntstr_DialogIconPickerHeader=아이콘 선택\ntstr_DialogIconPickerHeaderTip=커스텀 아이콘은 \"images\\icons\\\" 디렉토리에 PNG 파일로 추가하세요\ntstr_DialogIconPickerNone=[아이콘 없음]\ntstr_DialogKeyCodePickerHeader=키 코드 선택\ntstr_DialogKeyCodePickerHeaderHotkey=단축키 선택\ntstr_DialogKeyCodePickerModifiers=조합 키\ntstr_DialogKeyCodePickerKeyCode=키 코드\ntstr_DialogKeyCodePickerKeyCodeHint=목록 필터\ntstr_DialogKeyCodePickerKeyCodeNone=[없음]\ntstr_DialogKeyCodePickerFromInput=입력에서 가져오기...\ntstr_DialogKeyCodePickerFromInputPopup=아무 키나 마우스 버튼을 누르세요...\ntstr_DialogKeyCodePickerFromInputPopupNoMouse=아무 키나 누르세요...\ntstr_DialogWindowPickerHeader=창 선택\ntstr_DialogInputTagsHint=필터 또는 새 태그 추가\n\n;Source Strings\ntstr_SourceDesktopAll=통합 데스크톱\ntstr_SourceDesktopID=데스크톱 %ID%\ntstr_SourceWinRTNone=[캡처 대상 없음]\ntstr_SourceWinRTUnknown=[알 수 없는 창]\ntstr_SourceWinRTClosed=[닫힘]:\ntstr_SourcePerformanceMonitor=성능 모니터\ntstr_SourceBrowser=브라우저\ntstr_SourceBrowserNoPage=[페이지 없음]\n\n;Notification Icon\ntstr_NotificationIconRestoreVR=VR 인터페이스 복원\ntstr_NotificationIconOpenOnDesktop=데스크톱에서 설정 열기\ntstr_NotificationIconQuit=종료\n\n;Notifications\ntstr_NotificationInitialStartupTitleVR=초기 설정\ntstr_NotificationInitialStartupTitleDesktop=Desktop+ 초기 설정\ntstr_NotificationInitialStartupMessage=Desktop+가 SteamVR에 정상적으로 추가되어, 이제 SteamVR 실행 시 자동으로 실행됩니다.\n\n;Browser\ntstr_BrowserErrorPageTitle=페이지 로드 오류 - Desktop+\ntstr_BrowserErrorPageHeading=페이지를 불러올 수 없습니다\ntstr_BrowserErrorPageMessage=%URL% 로드 중 오류 발생: %ERROR%\n"
  },
  {
    "path": "assets/lang/zh_CN.ini",
    "content": "[TranslationInfo]\nName=简体中文 (Simplified Chinese)\nAuthor=Xuan25_瑄\nLocale=zh-CN\n\n;如果该语言在默认字体或其加载顺序上存在问题，则可以在此处定义一个不同的字体，该字体将首先加载\n;这应该是带有扩展名的文件名，会从操作系统的字体文件夹中加载，如果找不到，则从 \"lang\" 文件夹加载\nPreferredFont=msyh.ttc\n\n;将文件名设置为 ISO 639-1 语言代码以支持自动检测，如有必要，后面跟上带下划线的 ISO 3166-1 地区代码\n;要翻译 SteamVR 输入绑定，请在“input”文件夹中添加新的本地化文件，并在 action_manifest.json 中进行相应调整\n\n[Strings]\n;设置窗口\ntstr_SettingsWindowTitle=Desktop+ 设置\n\ntstr_SettingsCatInterface=界面\ntstr_SettingsCatEnvironment=环境\ntstr_SettingsCatProfiles=配置文件\ntstr_SettingsCatActions=操作\ntstr_SettingsCatKeyboard=键盘\ntstr_SettingsCatMouse=鼠标\ntstr_SettingsCatLaserPointer=激光指针\ntstr_SettingsCatWindowOverlays=窗口叠加\ntstr_SettingsCatBrowser=浏览器\ntstr_SettingsCatPerformance=性能\ntstr_SettingsCatVersionInfo=版本信息\ntstr_SettingsCatWarnings=警告\ntstr_SettingsCatStartup=启动\ntstr_SettingsCatTroubleshooting=故障排除\n\ntstr_SettingsWarningPrefix=警告：\ntstr_SettingsWarningCompositorResolution=SteamVR 合成器分辨率低于 100%！这会影响叠加的渲染质量。\ntstr_SettingsWarningCompositorQuality=SteamVR 叠加渲染质量未设置为高！\ntstr_SettingsWarningProcessElevated=Desktop+ 正在以管理员权限运行！\ntstr_SettingsWarningElevatedMode=管理员权限模式已激活！\ntstr_SettingsWarningBrowserMissing=正在使用浏览器叠加，但 Desktop+ 浏览器组件当前不可用。\ntstr_SettingsWarningBrowserMismatch=已安装的 Desktop+ 浏览器组件与此版本的 Desktop+ 不兼容！\ntstr_SettingsWarningElevatedProcessFocus=一个以管理员权限运行的进程当前拥有焦点！\\nDesktop+ 无法模拟输入。\ntstr_SettingsWarningUIAccessLost=Desktop+ 已不再以 UIAccess 权限运行！\ntstr_SettingsWarningOverlayCreationErrorLimit=创建叠加失败！（超过最大叠加数上限）\ntstr_SettingsWarningOverlayCreationErrorOther=创建叠加失败！（%ERRORNAME%）\ntstr_SettingsWarningGraphicsCaptureError=图形捕获线程出现意外错误！（%ERRORCODE%）\ntstr_SettingsWarningAppProfileActive=%APPNAME% 的应用程序配置文件已覆写了当前叠加布局。\\n在此激活期间对叠加所做的更改不会自动保存。\ntstr_SettingsWarningConfigMigrated=从旧版本 Desktop+ 的配置和配置文件已迁移到新的格式。\\n原文件未被删除，仍可与旧版本的应用程序一起使用。\\n如需从头开始，可在 [恢复默认设置] 中进行重置。\ntstr_SettingsWarningMenuDontShowAgain=不再显示\ntstr_SettingsWarningMenuDismiss=忽略\n\ntstr_SettingsInterfaceLanguage=语言\ntstr_SettingsInterfaceLanguageCommunity=社区翻译：%AUTHOR%\ntstr_SettingsInterfaceLanguageIncompleteWarning=翻译可能不完整\ntstr_SettingsInterfaceAdvancedSettings=显示高级设置\ntstr_SettingsInterfaceAdvancedSettingsTip=显示高级和不常用的设置\ntstr_SettingsInterfaceBlankSpaceDrag=单击空白区域时可拖动窗口\ntstr_SettingsInterfacePersistentUI=持久化界面\ntstr_SettingsInterfacePersistentUIManage=管理\ntstr_SettingsInterfaceDesktopButtons=桌面按钮显示样式\ntstr_SettingsInterfaceDesktopButtonsNone=无\ntstr_SettingsInterfaceDesktopButtonsIndividual=单独桌面\ntstr_SettingsInterfaceDesktopButtonsCycle=循环按钮\ntstr_SettingsInterfaceDesktopButtonsAddCombined=增加合并桌面按钮\n\ntstr_SettingsInterfacePersistentUIHelp=Desktop+ 会分别记住界面窗口在普通使用（全局）和在 Desktop+ 的 SteamVR 主面板标签页（Desktop+ Tab）中的状态。\ntstr_SettingsInterfacePersistentUIHelp2=以下控件可用于直接管理那些窗口的状态。\\n请注意，一些状态可能需要重置位置才能让窗口重回可见范围。\ntstr_SettingsInterfacePersistentUIWindowsHeader=窗口\ntstr_SettingsInterfacePersistentUIWindowsSettings=设置\ntstr_SettingsInterfacePersistentUIWindowsProperties=叠加属性\ntstr_SettingsInterfacePersistentUIWindowsKeyboard=Desktop+ 键盘\ntstr_SettingsInterfacePersistentUIWindowsStateGlobal=全局\ntstr_SettingsInterfacePersistentUIWindowsStateDashboardTab=Desktop+ 标签页\ntstr_SettingsInterfacePersistentUIWindowsStateVisible=可见\ntstr_SettingsInterfacePersistentUIWindowsStatePinned=固定\ntstr_SettingsInterfacePersistentUIWindowsStatePosition=位置\ntstr_SettingsInterfacePersistentUIWindowsStatePositionReset=重置\ntstr_SettingsInterfacePersistentUIWindowsStateSize=大小\ntstr_SettingsInterfacePersistentUIWindowsStateLaunchRestore=在 Desktop+ 启动时恢复状态\n\ntstr_SettingsEnvironmentBackgroundColor=背景颜色\ntstr_SettingsEnvironmentBackgroundColorDispModeNever=从不显示\ntstr_SettingsEnvironmentBackgroundColorDispModeDPlusTab=仅在 Desktop+ 标签页中显示\ntstr_SettingsEnvironmentBackgroundColorDispModeAlways=始终显示\ntstr_SettingsEnvironmentDimInterface=界面变暗\ntstr_SettingsEnvironmentDimInterfaceTip=在 Desktop+ 主面板标签页打开时，使 SteamVR 主面板和 Desktop+ 界面变暗\n\ntstr_SettingsProfilesOverlays=叠加配置文件\ntstr_SettingsProfilesApps=应用程序配置文件\ntstr_SettingsProfilesManage=管理\n\ntstr_SettingsProfilesOverlaysHeader=管理叠加配置文件\ntstr_SettingsProfilesOverlaysNameDefault=默认\ntstr_SettingsProfilesOverlaysNameNew=[新配置文件]\ntstr_SettingsProfilesOverlaysNameNewBase=配置文件 %ID%\ntstr_SettingsProfilesOverlaysProfileLoad=加载配置文件\ntstr_SettingsProfilesOverlaysProfileAdd=从配置文件添加叠加\ntstr_SettingsProfilesOverlaysProfileSave=保存当前叠加\ntstr_SettingsProfilesOverlaysProfileDelete=删除配置文件\ntstr_SettingsProfilesOverlaysProfileDeleteConfirm=确定删除？\ntstr_SettingsProfilesOverlaysProfileFailedLoad=加载配置文件失败\ntstr_SettingsProfilesOverlaysProfileFailedDelete=删除配置文件失败\ntstr_SettingsProfilesOverlaysProfileAddSelectHeader=从配置文件中选择要添加的叠加\ntstr_SettingsProfilesOverlaysProfileAddSelectEmpty=此配置文件中不包含任何叠加。\ntstr_SettingsProfilesOverlaysProfileAddSelectDo=添加所选叠加\ntstr_SettingsProfilesOverlaysProfileAddSelectAll=全选\ntstr_SettingsProfilesOverlaysProfileAddSelectNone=全不选\ntstr_SettingsProfilesOverlaysProfileSaveSelectHeader=保存当前叠加\ntstr_SettingsProfilesOverlaysProfileSaveSelectName=配置文件名称\ntstr_SettingsProfilesOverlaysProfileSaveSelectNameErrorBlank=名称不能为空\ntstr_SettingsProfilesOverlaysProfileSaveSelectNameErrorTaken=名称已被占用\ntstr_SettingsProfilesOverlaysProfileSaveSelectHeaderList=选择要保存到配置文件的叠加\ntstr_SettingsProfilesOverlaysProfileSaveSelectDo=保存所选叠加\ntstr_SettingsProfilesOverlaysProfileSaveSelectDoFailed=保存配置文件失败\n\ntstr_SettingsProfilesAppsHeader=管理应用程序配置文件\ntstr_SettingsProfilesAppsHeaderNoVRTip=Desktop+ 未在运行 VR 时，仅会列出已存在的应用程序配置文件\ntstr_SettingsProfilesAppsListEmpty=暂无应用程序\ntstr_SettingsProfilesAppsProfileHeaderActive=（当前激活）\ntstr_SettingsProfilesAppsProfileEnabled=当应用程序运行时激活\ntstr_SettingsProfilesAppsProfileOverlayProfile=叠加配置文件\ntstr_SettingsProfilesAppsProfileActionEnter=进入时操作\ntstr_SettingsProfilesAppsProfileActionLeave=退出时操作\n\ntstr_SettingsActionsManage=操作\ntstr_SettingsActionsManageButton=管理\ntstr_SettingsActionsButtonsOrderDefault=操作按钮（默认）\ntstr_SettingsActionsButtonsOrderOverlayBar=操作按钮（叠加栏）\ntstr_SettingsActionsShowBindings=显示控制器绑定\ntstr_SettingsActionsActiveShortcuts=激活的控制器按钮\ntstr_SettingsActionsActiveShortcutsTip=指向叠加时的控制器绑定。\\n可通过 VR 主面板的控制器绑定界面来更改使用哪些按键。\ntstr_SettingsActionsActiveShortuctsHome=“返回首页”\ntstr_SettingsActionsActiveShortuctsBack=“后退”\ntstr_SettingsActionsGlobalShortcuts=全局控制器按钮\ntstr_SettingsActionsGlobalShortcutsTip=在主面板关闭且未指向叠加时的控制器绑定。\\n可通过 Desktop+ 的控制器绑定来配置这些按键。\ntstr_SettingsActionsGlobalShortcutsEntry=全局快捷键 #%ID%\ntstr_SettingsActionsGlobalShortcutsAdd=添加快捷键\ntstr_SettingsActionsGlobalShortcutsRemove=移除最后一个快捷键\ntstr_SettingsActionsHotkeys=热键\ntstr_SettingsActionsHotkeysTip=全系统范围的键盘快捷键。\\n热键会拦截其他应用程序对该组合键的接收；如果该组合键已被其他地方注册，可能无法生效。\ntstr_SettingsActionsHotkeysAdd=添加热键\ntstr_SettingsActionsHotkeysRemove=移除\ntstr_SettingsActionsTableHeaderAction=操作\ntstr_SettingsActionsTableHeaderShortcut=快捷键\ntstr_SettingsActionsTableHeaderHotkey=热键\n\ntstr_SettingsActionsManageHeader=管理操作\ntstr_SettingsActionsManageCopyUID=复制操作 UID 到剪贴板\ntstr_SettingsActionsManageNew=新建操作...\ntstr_SettingsActionsManageEdit=编辑操作\ntstr_SettingsActionsManageDuplicate=复制操作\ntstr_SettingsActionsManageDelete=删除操作\ntstr_SettingsActionsManageDeleteConfirm=确定删除？\ntstr_SettingsActionsManageDuplicatedName=%NAME%（副本）\n\ntstr_SettingsActionsEditHeader=编辑操作\ntstr_SettingsActionsEditName=名称\ntstr_SettingsActionsEditNameTranslatedTip=此操作当前使用翻译字符串 ID 作为名称，会自动匹配所选应用语言\ntstr_SettingsActionsEditTarget=目标\ntstr_SettingsActionsEditTargetDefault=默认\ntstr_SettingsActionsEditTargetDefaultTip=目标为激活此操作的叠加，如果不适用，则目标为当前拥有焦点的叠加\ntstr_SettingsActionsEditTargetUseTags=使用标签\ntstr_SettingsActionsEditTargetActionTarget=操作目标\ntstr_SettingsActionsEditHeaderAppearance=按钮外观\ntstr_SettingsActionsEditIcon=图标\ntstr_SettingsActionsEditLabel=标签\ntstr_SettingsActionsEditLabelTranslatedTip=此操作当前使用翻译字符串 ID 作为标签，会自动匹配所选应用语言\ntstr_SettingsActionsEditHeaderCommands=命令\ntstr_SettingsActionsEditNameNew=新操作\ntstr_SettingsActionsEditCommandAdd=添加命令\ntstr_SettingsActionsEditCommandDelete=删除命令\ntstr_SettingsActionsEditCommandDeleteConfirm=确定删除？\ntstr_SettingsActionsEditCommandType=命令类型\ntstr_SettingsActionsEditCommandTypeNone=无\ntstr_SettingsActionsEditCommandTypeKey=按键\ntstr_SettingsActionsEditCommandTypeMousePos=设置鼠标位置\ntstr_SettingsActionsEditCommandTypeString=输入字符串\ntstr_SettingsActionsEditCommandTypeLaunchApp=启动应用程序\ntstr_SettingsActionsEditCommandTypeShowKeyboard=显示键盘\ntstr_SettingsActionsEditCommandTypeCropActiveWindow=裁剪至当前活动窗口\ntstr_SettingsActionsEditCommandTypeShowOverlay=显示叠加\ntstr_SettingsActionsEditCommandTypeSwitchTask=切换任务\ntstr_SettingsActionsEditCommandTypeLoadOverlayProfile=加载叠加配置文件\ntstr_SettingsActionsEditCommandTypeUnknown=未知\ntstr_SettingsActionsEditCommandVisibilityToggle=切换\ntstr_SettingsActionsEditCommandVisibilityShow=始终显示\ntstr_SettingsActionsEditCommandVisibilityHide=始终隐藏\ntstr_SettingsActionsEditCommandUndo=在松开时撤销\ntstr_SettingsActionsEditCommandKeyCode=按键码\ntstr_SettingsActionsEditCommandKeyToggle=按键切换\ntstr_SettingsActionsEditCommandMouseX=X\ntstr_SettingsActionsEditCommandMouseY=Y\ntstr_SettingsActionsEditCommandMouseUseCurrent=使用当前鼠标位置\ntstr_SettingsActionsEditCommandString=字符串\ntstr_SettingsActionsEditCommandPath=可执行文件路径\ntstr_SettingsActionsEditCommandPathTip=这也可以是一个普通文件或 URL\ntstr_SettingsActionsEditCommandArgs=应用程序参数\ntstr_SettingsActionsEditCommandArgsTip=这些会传递给应用程序。\\n如果不确定，可以留空。\ntstr_SettingsActionsEditCommandVisibility=可见性\ntstr_SettingsActionsEditCommandSwitchingMethod=切换方法\ntstr_SettingsActionsEditCommandSwitchingMethodSwitcher=显示任务切换器\ntstr_SettingsActionsEditCommandSwitchingMethodFocus=聚焦窗口\ntstr_SettingsActionsEditCommandWindow=窗口\ntstr_SettingsActionsEditCommandWindowNone=[无窗口]\ntstr_SettingsActionsEditCommandWindowStrictMatchingTip=在寻找要切换的窗口时，仅允许精确匹配窗口标题\ntstr_SettingsActionsEditCommandCursorWarp=将光标移至该窗口\ntstr_SettingsActionsEditCommandProfile=配置文件\ntstr_SettingsActionsEditCommandProfileClear=移除现有叠加\ntstr_SettingsActionsEditCommandDescNone=无命令\ntstr_SettingsActionsEditCommandDescKey=按下按键 “%KEYNAME%”\ntstr_SettingsActionsEditCommandDescKeyToggle=切换按键 “%KEYNAME%”\ntstr_SettingsActionsEditCommandDescMousePos=设置鼠标位置到 %X%, %Y%\ntstr_SettingsActionsEditCommandDescString=输入字符串 “%STRING%”\ntstr_SettingsActionsEditCommandDescLaunchApp=启动应用程序 “%APP%” %ARGSOPT%\ntstr_SettingsActionsEditCommandDescLaunchAppArgsOpt=“%ARGS%”\ntstr_SettingsActionsEditCommandDescKeyboardToggle=切换键盘\ntstr_SettingsActionsEditCommandDescKeyboardShow=显示键盘\ntstr_SettingsActionsEditCommandDescKeyboardHide=隐藏键盘\ntstr_SettingsActionsEditCommandDescCropWindow=裁剪至当前活动窗口\ntstr_SettingsActionsEditCommandDescOverlayToggle=切换叠加：%TAGS%\ntstr_SettingsActionsEditCommandDescOverlayShow=显示叠加：%TAGS%\ntstr_SettingsActionsEditCommandDescOverlayHide=隐藏叠加：%TAGS%\ntstr_SettingsActionsEditCommandDescOverlayTargetDefault=[操作目标]\ntstr_SettingsActionsEditCommandDescSwitchTask=切换任务\ntstr_SettingsActionsEditCommandDescSwitchTaskWindow=切换到窗口 “%WINDOW%”\ntstr_SettingsActionsEditCommandDescLoadOverlayProfile=加载叠加配置文件 “%PROFILE%”\ntstr_SettingsActionsEditCommandDescLoadOverlayProfileAdd=添加叠加配置文件 “%PROFILE%”\ntstr_SettingsActionsEditCommandDescUnknown=未知命令\n\ntstr_SettingsActionsOrderHeader=更改操作顺序\ntstr_SettingsActionsOrderButtonLabel=已选择 %COUNT% 个操作\ntstr_SettingsActionsOrderButtonLabelSingular=已选择 %COUNT% 个操作\ntstr_SettingsActionsOrderNoActions=未选择任何操作\ntstr_SettingsActionsOrderAdd=添加操作...\ntstr_SettingsActionsOrderRemove=移除操作\n\ntstr_SettingsActionsAddSelectorHeader=选择要添加的操作\ntstr_SettingsActionsAddSelectorAdd=添加所选操作\n\ntstr_SettingsKeyboardLayout=键盘布局\ntstr_SettingsKeyboardSize=大小\ntstr_SettingsKeyboardBehavior=行为\ntstr_SettingsKeyboardStickyMod=粘滞修饰键\ntstr_SettingsKeyboardKeyRepeat=按键重复\ntstr_SettingsKeyboardAutoShow=自动显示\ntstr_SettingsKeyboardAutoShowDesktopOnly=自动显示\ntstr_SettingsKeyboardAutoShowDesktop=针对桌面与窗口叠加\ntstr_SettingsKeyboardAutoShowDesktopTip=实验性功能。并非所有应用都支持。\ntstr_SettingsKeyboardAutoShowBrowser=针对浏览器叠加\ntstr_SettingsKeyboardLayoutAuthor=作者：%AUTHOR%\ntstr_SettingsKeyboardKeyClusters=键簇\ntstr_SettingsKeyboardKeyClusterBase=基本\ntstr_SettingsKeyboardKeyClusterFunction=功能\ntstr_SettingsKeyboardKeyClusterNavigation=导航\ntstr_SettingsKeyboardKeyClusterNumpad=数字小键盘\ntstr_SettingsKeyboardKeyClusterExtra=额外\ntstr_SettingsKeyboardSwitchToEditor=切换到键盘布局编辑器\n\ntstr_SettingsMouseShowCursor=显示鼠标指针\ntstr_SettingsMouseShowCursorGCUnsupported=此系统不支持在图形捕获（Graphics Capture）叠加中禁用光标\ntstr_SettingsMouseShowCursorGCActiveWarning=当前激活的图形捕获镜像可能会阻止桌面复制叠加隐藏光标\ntstr_SettingsMouseScrollSmooth=使用平滑滚动\ntstr_SettingsMouseSimulatePen=模拟为笔输入\ntstr_SettingsMouseSimulatePenUnsupported=此系统不支持笔输入模拟\ntstr_SettingsMouseAllowLaserPointerOverride=允许激光指针覆写\ntstr_SettingsMouseAllowLaserPointerOverrideTip=当物理鼠标快速移动时，自动禁用激光指针。\\n单击叠加可重新启用激光指针。\ntstr_SettingsMouseDoubleClickAssist=双击辅助\ntstr_SettingsMouseDoubleClickAssistTip=在设定时间内冻结鼠标位置，以便更容易进行双击\ntstr_SettingsMouseDoubleClickAssistTipValueOff=关闭\ntstr_SettingsMouseDoubleClickAssistTipValueAuto=自动\ntstr_SettingsMouseSmoothing=输入平滑\ntstr_SettingsMouseSmoothingLevelNone=无\ntstr_SettingsMouseSmoothingLevelVeryLow=极低\ntstr_SettingsMouseSmoothingLevelLow=低\ntstr_SettingsMouseSmoothingLevelMedium=中\ntstr_SettingsMouseSmoothingLevelHigh=高\ntstr_SettingsMouseSmoothingLevelVeryHigh=极高\n\ntstr_SettingsLaserPointerTip=以下设置适用于在关闭 SteamVR 主面板时 Desktop+ 的激光指针\ntstr_SettingsLaserPointerBlockInput=激光指针激活时阻止游戏输入\ntstr_SettingsLaserPointerAutoToggleDistance=自动激活的最大距离\ntstr_SettingsLaserPointerAutoToggleDistanceValueOff=关闭\ntstr_SettingsLaserPointerHMDPointer=基于视线的头显指针\ntstr_SettingsLaserPointerHMDPointerTableHeaderInputAction=输入操作\ntstr_SettingsLaserPointerHMDPointerTableHeaderBinding=键盘按键\ntstr_SettingsLaserPointerHMDPointerTableBindingToggle=切换激光指针\ntstr_SettingsLaserPointerHMDPointerTableBindingLeft=鼠标左键\ntstr_SettingsLaserPointerHMDPointerTableBindingRight=鼠标右键\ntstr_SettingsLaserPointerHMDPointerTableBindingMiddle=鼠标中键\ntstr_SettingsLaserPointerHMDPointerTableBindingDrag=拖动叠加\n\ntstr_SettingsWindowOverlaysAutoFocus=指向叠加时自动聚焦窗口\ntstr_SettingsWindowOverlaysKeepOnScreen=使窗口保持在屏幕内\ntstr_SettingsWindowOverlaysKeepOnScreenTip=若窗口超出屏幕工作区域，则自动移动它至可见范围内\ntstr_SettingsWindowOverlaysAutoSizeOverlay=当窗口调整大小时同步调整叠加大小\ntstr_SettingsWindowOverlaysFocusSceneApp=从叠加移开激光指针后聚焦游戏\ntstr_SettingsWindowOverlaysFocusSceneAppDashboard=关闭主面板后聚焦游戏\ntstr_SettingsWindowOverlaysOnWindowDrag=窗口拖动时\ntstr_SettingsWindowOverlaysOnWindowDragDoNothing=不执行任何操作\ntstr_SettingsWindowOverlaysOnWindowDragBlock=阻止拖动\ntstr_SettingsWindowOverlaysOnWindowDragOverlay=拖动叠加\ntstr_SettingsWindowOverlaysOnCaptureLoss=捕获丢失时\ntstr_SettingsWindowOverlaysOnCaptureLossTip=当窗口捕获丢失（通常是因为目标窗口已关闭）时的行为。\\n“隐藏叠加”模式在捕获恢复后会重新显示叠加。\ntstr_SettingsWindowOverlaysOnCaptureLossDoNothing=不执行任何操作\ntstr_SettingsWindowOverlaysOnCaptureLossHide=隐藏叠加\ntstr_SettingsWindowOverlaysOnCaptureLossRemove=移除叠加\n\ntstr_SettingsBrowserMaxFrameRate=最大帧率\ntstr_SettingsBrowserMaxFrameRateOverrideOff=使用全局设置\ntstr_SettingsBrowserContentBlocker=内容拦截器\ntstr_SettingsBrowserContentBlockerTip=在“DesktopPlusBrowser\\content_block”目录中添加采用 Adblock Plus 语法的屏蔽列表。\\n目录中的所有列表都会被加载。\ntstr_SettingsBrowserContentBlockerListCount=(已启用 %LISTCOUNT% 个列表)\ntstr_SettingsBrowserContentBlockerListCountSingular=(已启用 %LISTCOUNT% 个列表)\n\ntstr_SettingsPerformanceUpdateLimiter=限制更新\ntstr_SettingsPerformanceUpdateLimiterMode=更新限制模式\ntstr_SettingsPerformanceUpdateLimiterModeOff=关闭\ntstr_SettingsPerformanceUpdateLimiterModeMS=帧间隔\ntstr_SettingsPerformanceUpdateLimiterModeFPS=帧率\ntstr_SettingsPerformanceUpdateLimiterModeOffOverride=使用全局设置\ntstr_SettingsPerformanceUpdateLimiterModeMSTip=“帧间隔”会强制叠加更新之间保持最小时间间隔\ntstr_SettingsPerformanceUpdateLimiterFPSValue=%FPS% 帧/秒\ntstr_SettingsPerformanceUpdateLimiterOverride=覆写限制更新\ntstr_SettingsPerformanceUpdateLimiterOverrideTip=当多个覆写同时启用时，将使用可以提供最高更新率的覆写\ntstr_SettingsPerformanceUpdateLimiterModeOverride=覆写更新限制模式\ntstr_SettingsPerformanceRapidUpdates=降低激光指针延迟\ntstr_SettingsPerformanceRapidUpdatesTip=在指向叠加时，以增加 CPU 负载为代价减少激光指针输入延迟\ntstr_SettingsPerformanceSingleDesktopMirror=单独桌面镜像\ntstr_SettingsPerformanceSingleDesktopMirrorTip=切换桌面时只镜像单个桌面，而不是从合并桌面进行裁剪。\\n启用后，所有叠加都将显示相同的桌面。\ntstr_SettingsPerformanceUseHDR=HDR 镜像\ntstr_SettingsPerformanceUseHDRTip=使用更高位深纹理镜像桌面或窗口，以支持 HDR 输出（实验性）。\\n在不需要时可能会导致性能下降，并增加显存使用量。\ntstr_SettingsPerformanceShowFPS=在浮动 UI 上显示帧率\n\ntstr_SettingsWarningsHidden=已隐藏的警告：\ntstr_SettingsWarningsReset=重置隐藏的警告\n\ntstr_SettingsStartupAutoLaunch=在 SteamVR 启动时自动启动\ntstr_SettingsStartupSteamDisable=禁用 Steam 集成\ntstr_SettingsStartupSteamDisableTip=如果 Desktop+ 是由 Steam 启动的，则以不带 Steam 的方式重新启动。\\n这样会禁用应用内的永久在线状态、使用时长统计和其他 Steam 功能。\n\ntstr_SettingsTroubleshootingRestart=重启\ntstr_SettingsTroubleshootingRestartSteam=随 Steam 重启\ntstr_SettingsTroubleshootingRestartDesktop=以桌面模式重启\ntstr_SettingsTroubleshootingElevatedModeEnter=进入管理员权限模式\ntstr_SettingsTroubleshootingElevatedModeLeave=退出管理员权限模式\ntstr_SettingsTroubleshootingSettingsReset=恢复默认设置\ntstr_SettingsTroubleshootingSettingsResetConfirmDescription=恢复默认设置将把所选元素重置为默认值。\\n此操作无法撤销。\\n\\n重置以下项目：\ntstr_SettingsTroubleshootingSettingsResetConfirmButton=重置所选元素\ntstr_SettingsTroubleshootingSettingsResetConfirmElementOverlays=当前叠加设置\ntstr_SettingsTroubleshootingSettingsResetConfirmElementLegacyFiles=删除未使用的旧版配置和配置文件\ntstr_SettingsTroubleshootingSettingsResetShowQuickStart=显示快速入门指南\n\n;键盘窗口\ntstr_KeyboardWindowTitle=Desktop+ 键盘\ntstr_KeyboardWindowTitleSettings=Desktop+ 键盘（设置）\ntstr_KeyboardWindowTitleOverlay=Desktop+ 键盘（%OVERLAYNAME%）\ntstr_KeyboardWindowTitleOverlayUnknown=[未知叠加]\n\n;键盘快捷键窗口\ntstr_KeyboardShortcutsCut=剪切\ntstr_KeyboardShortcutsCopy=复制\ntstr_KeyboardShortcutsPaste=粘贴\n\n;叠加属性窗口\ntstr_OvrlPropsCatPosition=位置\ntstr_OvrlPropsCatAppearance=外观\ntstr_OvrlPropsCatCapture=捕获\ntstr_OvrlPropsCatPerformanceMonitor=性能监控\ntstr_OvrlPropsCatBrowser=浏览器\ntstr_OvrlPropsCatAdvanced=高级\ntstr_OvrlPropsCatPerformance=性能\ntstr_OvrlPropsCatInterface=界面\n\ntstr_OvrlPropsPositionOrigin=原点\ntstr_OvrlPropsPositionOriginRoom=游戏空间\ntstr_OvrlPropsPositionOriginHMDXY=头显底部位置\ntstr_OvrlPropsPositionOriginDashboard=主面板\ntstr_OvrlPropsPositionOriginHMD=头显\ntstr_OvrlPropsPositionOriginSeatedSpace=座椅位置\ntstr_OvrlPropsPositionOriginControllerR=右手柄\ntstr_OvrlPropsPositionOriginControllerL=左手柄\ntstr_OvrlPropsPositionOriginTheaterScreen=影院屏幕\ntstr_OvrlPropsPositionOriginTracker1=追踪器 #1\ntstr_OvrlPropsPositionOriginConfigHMDXYTurning=随头显一起转动\ntstr_OvrlPropsPositionOriginConfigTheaterScreenEnter=进入影院模式\ntstr_OvrlPropsPositionOriginConfigTheaterScreenLeave=退出影院模式\ntstr_OvrlPropsPositionOriginTheaterScreenTip=部分属性由 SteamVR 影院屏幕控制\ntstr_OvrlPropsPositionDispMode=显示模式\ntstr_OvrlPropsPositionDispModeAlways=始终\ntstr_OvrlPropsPositionDispModeDashboard=仅在主面板\ntstr_OvrlPropsPositionDispModeScene=仅在游戏中\ntstr_OvrlPropsPositionDispModeDPlus=仅在 Desktop+ 标签页中\ntstr_OvrlPropsPositionPos=位置\ntstr_OvrlPropsPositionPosTip=只有在 Desktop+ 正在运行时才能更改或重置位置\ntstr_OvrlPropsPositionChange=更改\ntstr_OvrlPropsPositionReset=重置\ntstr_OvrlPropsPositionLock=锁定\n\ntstr_OvrlPropsPositionChangeHeader=更改叠加位置\ntstr_OvrlPropsPositionChangeHelp=拖动任意叠加以更改其位置。\\n按住右键可进行双手手势变换。\ntstr_OvrlPropsPositionChangeHelpDesktop=按住拖动按钮（“D”）可使用鼠标移动或旋转叠加。\ntstr_OvrlPropsPositionChangeManualAdjustment=手动调整\ntstr_OvrlPropsPositionChangeMove=移动\ntstr_OvrlPropsPositionChangeRotate=旋转\ntstr_OvrlPropsPositionChangeForward=前移\ntstr_OvrlPropsPositionChangeBackward=后移\ntstr_OvrlPropsPositionChangeRollCW=滚动 ⟳\ntstr_OvrlPropsPositionChangeRollCCW=滚动 ⟲\ntstr_OvrlPropsPositionChangeLookAt=面向头显\ntstr_OvrlPropsPositionChangeDragButton=D\ntstr_OvrlPropsPositionChangeOffset=附加偏移\ntstr_OvrlPropsPositionChangeOffsetUpDown=上下偏移\ntstr_OvrlPropsPositionChangeOffsetRightLeft=左右偏移\ntstr_OvrlPropsPositionChangeOffsetForwardBackward=前后偏移\ntstr_OvrlPropsPositionChangeDragSettings=拖动设置\ntstr_OvrlPropsPositionChangeDragSettingsAutoDocking=接近手柄时自动停靠\ntstr_OvrlPropsPositionChangeDragSettingsForceDistance=强制固定距离\ntstr_OvrlPropsPositionChangeDragSettingsForceDistanceShape=形状\ntstr_OvrlPropsPositionChangeDragSettingsForceDistanceShapeSphere=球\ntstr_OvrlPropsPositionChangeDragSettingsForceDistanceShapeCylinder=圆柱\ntstr_OvrlPropsPositionChangeDragSettingsForceDistanceAutoCurve=自动曲面\ntstr_OvrlPropsPositionChangeDragSettingsForceDistanceAutoTilt=自动倾斜\ntstr_OvrlPropsPositionChangeDragSettingsSnapPosition=位置贴合\n\ntstr_OvrlPropsAppearanceWidth=宽度\ntstr_OvrlPropsAppearanceCurve=曲率\ntstr_OvrlPropsAppearanceOpacity=透明度\ntstr_OvrlPropsAppearanceBrightness=亮度\ntstr_OvrlPropsAppearanceCrop=裁剪\ntstr_OvrlPropsAppearanceCropValueMax=最大\n\ntstr_OvrlPropsCrop=裁剪区域\ntstr_OvrlPropsCropHelp=拖动矩形以调整裁剪范围。\\n使用滚轮或拖动边缘来调整裁剪矩形的大小。\ntstr_OvrlPropsCropManualAdjust=手动调节\ntstr_OvrlPropsCropInvalidTip=当前裁剪矩形无效，可能导致叠加不可见。\ntstr_OvrlPropsCropX=X\ntstr_OvrlPropsCropY=Y\ntstr_OvrlPropsCropWidth=宽度\ntstr_OvrlPropsCropHeight=高度\ntstr_OvrlPropsCropToWindow=裁剪到当前活动窗口\n\ntstr_OvrlPropsCaptureMethod=捕获方式\ntstr_OvrlPropsCaptureMethodDup=桌面复制\ntstr_OvrlPropsCaptureMethodGC=图形捕获\ntstr_OvrlPropsCaptureMethodGCUnsupportedTip=此系统不支持图形捕获\ntstr_OvrlPropsCaptureMethodGCUnsupportedPartialTip=此系统不支持部分图形捕获功能\ntstr_OvrlPropsCaptureSource=来源\ntstr_OvrlPropsCaptureGCSource=图形捕获来源\ntstr_OvrlPropsCaptureSourceUnknownWarning=此叠加使用了未知的捕获来源，可能无法正常工作\ntstr_OvrlPropsCaptureGCStrictMatching=使用严格窗口匹配\ntstr_OvrlPropsCaptureGCStrictMatchingTip=在恢复此叠加的捕获时，仅允许精确匹配窗口标题\n\ntstr_OvrlPropsPerfMonDesktopModeTip=性能监控叠加在桌面模式下不会更新\ntstr_OvrlPropsPerfMonGlobalTip=以下设置应用于所有性能监控叠加\ntstr_OvrlPropsPerfMonStyle=样式\ntstr_OvrlPropsPerfMonStyleCompact=紧凑\ntstr_OvrlPropsPerfMonStyleLarge=大型\ntstr_OvrlPropsPerfMonShowCPU=显示 CPU 状态\ntstr_OvrlPropsPerfMonShowGPU=显示 GPU 状态\ntstr_OvrlPropsPerfMonShowGraphs=显示图表\ntstr_OvrlPropsPerfMonShowFrameStats=显示帧信息\ntstr_OvrlPropsPerfMonShowTime=显示时间\ntstr_OvrlPropsPerfMonShowBattery=显示电池信息\ntstr_OvrlPropsPerfMonShowTrackerBattery=显示追踪器电量\ntstr_OvrlPropsPerfMonShowViveWirelessTemp=显示 Vive 无线温度\ntstr_OvrlPropsPerfMonResetValues=重置累计数值\n\ntstr_OvrlPropsBrowserNotAvailableTip=未安装 Desktop+ 浏览器组件\ntstr_OvrlPropsBrowserCloned=克隆输出\ntstr_OvrlPropsBrowserClonedTip=此叠加克隆自 \"%OVERLAYNAME%\"。\\n对浏览器属性所做的更改将同时应用到原叠加及所有从其克隆的叠加。\ntstr_OvrlPropsBrowserClonedConvert=转换为独立叠加\ntstr_OvrlPropsBrowserURL=URL\ntstr_OvrlPropsBrowserURLHint=输入地址\ntstr_OvrlPropsBrowserGo=前往\ntstr_OvrlPropsBrowserRestore=恢复上次输入\ntstr_OvrlPropsBrowserWidth=宽度\ntstr_OvrlPropsBrowserHeight=高度\ntstr_OvrlPropsBrowserZoom=缩放\ntstr_OvrlPropsBrowserAllowTransparency=允许透明\ntstr_OvrlPropsBrowserAllowTransparencyTip=允许网页使用透明背景。\\n如果网站未定义任何背景颜色，页面可能会显示异常。\ntstr_OvrlPropsBrowserRecreateContext=重新创建浏览器上下文\ntstr_OvrlPropsBrowserRecreateContextTip=需要重新创建浏览器上下文才能应用更改。\\n此操作会重新加载页面并清除导航历史记录。\n\ntstr_OvrlPropsAdvanced3D=3D\ntstr_OvrlPropsAdvancedHSBS=左右半宽\ntstr_OvrlPropsAdvancedSBS=左右并列\ntstr_OvrlPropsAdvancedHOU=上下半高\ntstr_OvrlPropsAdvancedOU=上下并列\ntstr_OvrlPropsAdvanced3DSwap=交换左右眼\ntstr_OvrlPropsAdvancedGazeFade=视线淡出\ntstr_OvrlPropsAdvancedGazeFadeAuto=自动配置\ntstr_OvrlPropsAdvancedGazeFadeDistance=距离\ntstr_OvrlPropsAdvancedGazeFadeDistanceValueInf=无限\ntstr_OvrlPropsAdvancedGazeFadeSensitivity=灵敏度\ntstr_OvrlPropsAdvancedGazeFadeOpacity=目标透明度\ntstr_OvrlPropsAdvancedInput=激光指针输入\ntstr_OvrlPropsAdvancedInputInGame=游戏中启用\ntstr_OvrlPropsAdvancedInputFloatingUI=显示浮动 UI\ntstr_OvrlPropsAdvancedOverlayTags=叠加标签\ntstr_OvrlPropsAdvancedOverlayTagsTip=叠加标签用于在操作中定位叠加\n\ntstr_OvrlPropsPerformanceInvisibleUpdate=在不可见时仍更新\ntstr_OvrlPropsPerformanceInvisibleUpdateTip=即使叠加因透明度设置或视线淡出而不可见时，也继续更新叠加。\\n有助于第三方应用程序访问叠加内容。\\n除特殊需求外，不建议启用。\\n如果叠加是被手动隐藏或因显示模式而被隐藏，更新仍会暂停。\n\ntstr_OvrlPropsInterfaceOverlayName=叠加名称\ntstr_OvrlPropsInterfaceOverlayNameAuto=[自动命名]\ntstr_OvrlPropsInterfaceActionOrderCustom=覆写操作按钮\ntstr_OvrlPropsInterfaceDesktopButtons=显示桌面按钮\ntstr_OvrlPropsInterfaceExtraButtons=显示额外按钮\n\n;叠加栏\ntstr_OverlayBarOvrlHide=隐藏\ntstr_OverlayBarOvrlShow=显示\ntstr_OverlayBarOvrlClone=克隆\ntstr_OverlayBarOvrlRemove=移除\ntstr_OverlayBarOvrlRemoveConfirm=确定删除？\ntstr_OverlayBarOvrlProperties=属性...\ntstr_OverlayBarOvrlAddWindow=窗口...\ntstr_OverlayBarTooltipOvrlAdd=添加叠加\ntstr_OverlayBarTooltipSettings=设置\ntstr_OverlayBarTooltipResetHold=长按以重置窗口位置...\n\n;浮动界面\ntstr_FloatingUIHideOverlayTip=隐藏叠加\ntstr_FloatingUIHideOverlayHoldTip=长按以移除叠加...\ntstr_FloatingUIDragModeEnableTip=启用拖动模式\ntstr_FloatingUIDragModeDisableTip=禁用拖动模式\ntstr_FloatingUIDragModeHoldLockTip=长按以锁定叠加位置...\ntstr_FloatingUIDragModeHoldUnlockTip=长按以解锁叠加位置...\ntstr_FloatingUIWindowAddTip=将当前活动窗口添加为叠加\ntstr_FloatingUIActionBarShowTip=显示操作栏\ntstr_FloatingUIActionBarHideTip=隐藏操作栏\ntstr_FloatingUIBrowserGoBackTip=前往上一页\ntstr_FloatingUIBrowserGoForwardTip=前往下一页\ntstr_FloatingUIBrowserRefreshTip=刷新当前页面\ntstr_FloatingUIBrowserStopTip=停止加载页面\ntstr_FloatingUIActionBarDesktopPrev=上一个桌面\ntstr_FloatingUIActionBarDesktopNext=下一个桌面\ntstr_FloatingUIActionBarEmpty=未启用任何操作\n\n;特殊操作\ntstr_ActionNone=[无]\ntstr_ActionKeyboardShow=显示键盘\ntstr_ActionKeyboardHide=隐藏键盘\n\n;默认操作（翻译 ID 与操作名称匹配）\ntstr_DefActionShowKeyboard=显示键盘\ntstr_DefActionActiveWindowCrop=裁剪至活动窗口\ntstr_DefActionActiveWindowCropLabel=裁剪至\\n活动\\n窗口\ntstr_DefActionSwitchTask=切换任务\ntstr_DefActionToggleOverlays=切换叠加\ntstr_DefActionToggleOverlaysLabel=切换\\n叠加\ntstr_DefActionMiddleMouse=鼠标中键\ntstr_DefActionMiddleMouseLabel=鼠标\\n中键\ntstr_DefActionBackMouse=鼠标后退键\ntstr_DefActionBackMouseLabel=鼠标\\n后退\\n键\ntstr_DefActionReadMe=打开 ReadMe\ntstr_DefActionReadMeLabel=打开\\nReadMe\ntstr_DefActionDashboardToggle=切换 SteamVR 主面板（调试命令）\ntstr_DefActionDashboardToggleLabel=切换\\n主面板\n\n;性能监控（界面可用空间很有限，尽量简短或不翻译）\ntstr_PerformanceMonitorCPU=CPU\ntstr_PerformanceMonitorGPU=GPU\ntstr_PerformanceMonitorRAM=内存:\ntstr_PerformanceMonitorVRAM=显存:\ntstr_PerformanceMonitorFrameTime=帧时间:\ntstr_PerformanceMonitorLoad=负载:\ntstr_PerformanceMonitorFPS=FPS:\ntstr_PerformanceMonitorFPSAverage=平均 FPS:\ntstr_PerformanceMonitorReprojectionRatio=重投比例:\ntstr_PerformanceMonitorDroppedFrames=丢帧:\ntstr_PerformanceMonitorBatteryLeft=左手柄:\ntstr_PerformanceMonitorBatteryRight=右手柄:\ntstr_PerformanceMonitorBatteryHMD=头显:\ntstr_PerformanceMonitorBatteryTracker=追踪器 %ID%:\ntstr_PerformanceMonitorBatteryDisconnected=N/A\ntstr_PerformanceMonitorViveWirelessTempNotAvailable=N/A\ntstr_PerformanceMonitorCompactCPU=CPU\ntstr_PerformanceMonitorCompactGPU=GPU\ntstr_PerformanceMonitorCompactFPS=FPS\ntstr_PerformanceMonitorCompactFPSAverage=AVG\ntstr_PerformanceMonitorCompactReprojectionRatio=% RPR\ntstr_PerformanceMonitorCompactDroppedFrames=DRP\ntstr_PerformanceMonitorCompactBattery=BAT\ntstr_PerformanceMonitorCompactBatteryLeft=L\ntstr_PerformanceMonitorCompactBatteryRight=R\ntstr_PerformanceMonitorCompactBatteryHMD=H\ntstr_PerformanceMonitorCompactBatteryTracker=T%ID%\ntstr_PerformanceMonitorCompactBatteryDisconnected=N/A\ntstr_PerformanceMonitorCompactViveWirelessTempNotAvailable=N/A\ntstr_PerformanceMonitorEmpty=未启用任何性能监控项目。\n\n;辅助 UI\ntstr_AuxUIDragHintDocking=松开以停靠\ntstr_AuxUIDragHintUndocking=松开以取消停靠\ntstr_AuxUIDragHintOvrlLocked=解锁叠加位置才能拖动此叠加\ntstr_AuxUIDragHintOvrlTheaterScreenBlocked=此叠加的位置由 SteamVR 影院屏幕控制\ntstr_AuxUIGazeFadeAutoHint=注视叠加中心并等待 %SECONDS% 秒...\ntstr_AuxUIGazeFadeAutoHintSingular=注视叠加中心并等待 %SECONDS% 秒...\n\ntstr_AuxUIQuickStartWelcomeHeader=欢迎使用 Desktop+！\ntstr_AuxUIQuickStartWelcomeBody=本简短指南将向您介绍应用程序的基础知识。\\n如需更多详细信息，请查看 ReadMe 和用户指南。\\n\\n如果您想跳过此指南，可直接点击 [关闭]。\ntstr_AuxUIQuickStartOverlaysHeader=叠加\ntstr_AuxUIQuickStartOverlaysBody=Desktop+ 允许您创建叠加来镜像您的各个桌面、独立窗口等内容。\\n所有创建的叠加都会显示在下方的叠加栏中。\\n\\n单击一个叠加图标并选择 [属性...] 可以修改叠加属性。\ntstr_AuxUIQuickStartOverlaysBody2=点击 [+] 并从列表中选择捕获来源或叠加类型可以创建新的叠加。\\n某些类型（例如浏览器叠加）仅在安装了相应组件后才可用。\\n\\n单个叠加或整个布局可以被保存到配置文件。\\n当前叠加设置会在不同会话之间自动记住。\ntstr_AuxUIQuickStartOverlayPropertiesHeader=叠加属性\ntstr_AuxUIQuickStartOverlayPropertiesBody=叠加具有多种自定义选项。\\n原点和显示模式决定了叠加显示的位置和时机。\\n如果您觉得叠加应该显示却找不到，请检查这些属性。\\n若前述方法无效，重置叠加位置也许能解决问题。\\n\\n您还可以通过将叠加拖拽至运动控制器附近，将其停靠到控制器上。\ntstr_AuxUIQuickStartOverlayPropertiesBody2=部分属性默认处于隐藏状态。\\n在设置窗口中切换“显示高级设置”即可显示所有选项。\\n\\n通过长按各自的按钮可以重置 UI 窗口的位置。\\n叠加按钮也支持双击和右键单击，作为快捷切换功能。\ntstr_AuxUIQuickStartSettingsHeader=设置\ntstr_AuxUIQuickStartSettingsBody=设置窗口包含 Desktop+ 的所有全局设置。\\n按下叠加栏右端的齿轮按钮即可打开。\\n\\n与叠加一样，设置的更改会自动应用并保存。\ntstr_AuxUIQuickStartProfilesHeader=配置文件\ntstr_AuxUIQuickStartProfilesBody=Desktop+ 中有两种配置文件类型。\\n\\n叠加配置文件：\\n存储一个或多个叠加及其属性。\\n它们可以被手动加载，也可以根据操作和应用程序配置文件加载。\\n\\n应用程序配置文件：\\n在启动 SteamVR 应用程序时，自动加载指定的叠加配置文件和/或操作。\ntstr_AuxUIQuickStartActionsHeader=操作\ntstr_AuxUIQuickStartActionsBody=Desktop+ 中的操作是一系列命令，其可以被分配给控制器输入和应用程序配置文件，或添加到叠加的操作栏中。\\n这些操作命令包括从模拟桌面输入到调整叠加布局等功能。\\n其还可以被用于启动外部应用程序以满足高级用例需求。\\n\\nDesktop+ 自带一些示例操作供您参考。\ntstr_AuxUIQuickStartActionsBody2=默认情况下，操作的目标是被激活的叠加（为叠加显示的操作按钮、叠加上的控制器输入），如果前者不适用，则目标是被聚焦的叠加层。\\n\\n当前聚焦的叠加是最后一次被点击的叠加。\\n在许多情况下，目标叠加并不重要，例如在桌面上模拟输入；但在其他情况下，指定一个不同的目标叠加甚至多个目标叠加可能会有用。\ntstr_AuxUIQuickStartOverlayTagsHeader=叠加标签\ntstr_AuxUIQuickStartOverlayTagsBody=叠加标签可用于此目的。\\n存在自动标签（绿色标签）和用户自定义标签。\\n自动标签是基于其所代表的属性自动分配的。\\n用户自定义标签可以在叠加属性窗口中手动分配给叠加。\\n\\n请注意，对于全局快捷键，操作仅与控制器按钮间接绑定。此外，编号的全局快捷键还必须通过 SteamVR 的输入绑定界面分配给控制器输入。\ntstr_AuxUIQuickStartSettingsEndBody=还有更多设置可供使用。\\n不要害怕尝试各种切换按钮来探索它们。\\n\\n如果遇到问题，您可以点击窗口底部的 [恢复默认设置] 按钮。\\n您还可以选择仅恢复特定的部分。\ntstr_AuxUIQuickStartFloatingUIHeader=浮动 UI\ntstr_AuxUIQuickStartFloatingUIBody=当您指向某个叠加时，被称为“浮动 UI”的界面会显示出来。\\n在主面板中，如果没有指向其他叠加，它将始终显示。\\n\\n浮动 UI 包含用于修改叠加的基本控件，例如切换拖动模式以允许移动叠加，\\n以及一个可自定义的操作按钮的部分。\\n\\n根据叠加类型，可能还会有其他控件。这些控件的显示可以在各个叠加的属性中进行切换。\ntstr_AuxUIQuickStartDesktopModeHeader=桌面模式\ntstr_AuxUIQuickStartDesktopModeBody=Desktop+ 也可以在一个桌面窗口上被配置。\\n这对于需要频繁使用键盘输入或在未连接运动控制器时进行修改的任务非常有用。\\n\\n虽然两种模式下几乎所有功能都相同，但用于自定义 Desktop+ VR 键盘布局的键盘布局编辑器仅在桌面模式下可用。\\n\\n您可以在设置窗口的故障排除部分切换到该模式，或者通过系统托盘/通知区域中的 Desktop+ 图标进入桌面模式。\ntstr_AuxUIQuickStartEndHeader=ReadMe 和用户指南\ntstr_AuxUIQuickStartEndBody=这应该足以让您开始使用 Desktop+。\\n\\n如果您遇到任何问题或困惑，请务必查看 ReadMe。\\n操作栏中的按钮将为您打开它。\\n每个选项的详细说明以及常见使用场景的逐步指南可以在用户指南中找到。\\n\\n若要再次查看此指南，请点击“恢复默认设置”页面右下角的 [显示快速入门指南] 按钮。\ntstr_AuxUIQuickStartButtonNext=下一页\ntstr_AuxUIQuickStartButtonPrev=上一页\ntstr_AuxUIQuickStartButtonClose=关闭\n\n;桌面模式\ntstr_DesktopModeCatTools=工具\ntstr_DesktopModeCatOverlays=叠加\ntstr_DesktopModeToolSettings=设置\ntstr_DesktopModeToolActions=操作\ntstr_DesktopModeOverlayListAdd=添加叠加\ntstr_DesktopModePageAddWindowOverlayTitle=添加窗口叠加\ntstr_DesktopModePageAddWindowOverlayHeader=选择一个窗口\n\n;键盘编辑器\ntstr_KeyboardEditorKeyListTitle=按键列表\ntstr_KeyboardEditorKeyListTabContextReplace=替换布局内容...\ntstr_KeyboardEditorKeyListTabContextClear=清空子布局\ntstr_KeyboardEditorKeyListRow=行 %ID%\ntstr_KeyboardEditorKeyListSpacing=[空格]\ntstr_KeyboardEditorKeyListKeyAdd=添加\ntstr_KeyboardEditorKeyListKeyDuplicate=复制\ntstr_KeyboardEditorKeyListKeyRemove=移除\n\ntstr_KeyboardEditorKeyPropertiesTitle=按键属性\ntstr_KeyboardEditorKeyPropertiesNoSelection=未选择按键\ntstr_KeyboardEditorKeyPropertiesType=类型\ntstr_KeyboardEditorKeyPropertiesTypeBlank=空白区域\ntstr_KeyboardEditorKeyPropertiesTypeVirtualKey=虚拟按键\ntstr_KeyboardEditorKeyPropertiesTypeVirtualKeyToggle=虚拟按键（切换）\ntstr_KeyboardEditorKeyPropertiesTypeVirtualKeyIsoEnter=虚拟按键（ISO-Enter）\ntstr_KeyboardEditorKeyPropertiesTypeString=字符串\ntstr_KeyboardEditorKeyPropertiesTypeSublayoutToggle=切换子布局\ntstr_KeyboardEditorKeyPropertiesTypeAction=操作\ntstr_KeyboardEditorKeyPropertiesTypeVirtualKeyIsoEnterTip=在相邻的两行里各使用一个“虚拟按键（ISO-Enter）”即可组合成一个 ISO-Enter 形状的键。\\n每个子布局中只能有一个这样的组合键。\ntstr_KeyboardEditorKeyPropertiesTypeStringTip=对于非基本字符键，请使用字符串类型。\\n这样 Desktop+ 能够基于实际键盘布局计算出正确的按键组合，从而提高应用兼容性。\ntstr_KeyboardEditorKeyPropertiesSize=大小\ntstr_KeyboardEditorKeyPropertiesLabel=标签\ntstr_KeyboardEditorKeyPropertiesKeyCode=按键码\ntstr_KeyboardEditorKeyPropertiesString=字符串\ntstr_KeyboardEditorKeyPropertiesSublayout=子布局\ntstr_KeyboardEditorKeyPropertiesAction=操作\ntstr_KeyboardEditorKeyPropertiesCluster=键簇\ntstr_KeyboardEditorKeyPropertiesClusterTip=键簇分配可用于有选择地禁用按键的加载。\\n周围的空白区域不会被移除。请仔细为空白区域类型的按键分配键簇，以确保它们能正确切换。\ntstr_KeyboardEditorKeyPropertiesBlockModifiers=阻止修饰键\ntstr_KeyboardEditorKeyPropertiesBlockModifiersTip=在此键按下时，释放所有修饰键\ntstr_KeyboardEditorKeyPropertiesNoRepeat=禁止重复\ntstr_KeyboardEditorKeyPropertiesNoRepeatTip=即使开启了按键重复，也阻止此键被按住时重复输入\n\ntstr_KeyboardEditorMetadataTitle=布局元数据\ntstr_KeyboardEditorMetadataName=名称\ntstr_KeyboardEditorMetadataAuthor=作者\ntstr_KeyboardEditorMetadataHasAltGr=是否有 AltGr\ntstr_KeyboardEditorMetadataHasAltGrTip=当按下右 Alt 键时，会切换到 AltGr 子布局\ntstr_KeyboardEditorMetadataClusterPreview=预览键簇：\ntstr_KeyboardEditorMetadataSave=保存...\ntstr_KeyboardEditorMetadataLoad=加载...\ntstr_KeyboardEditorMetadataSavePopupTitle=保存键盘布局\ntstr_KeyboardEditorMetadataSavePopupFilename=文件名\ntstr_KeyboardEditorMetadataSavePopupFilenameBlankTip=文件名不能为空\ntstr_KeyboardEditorMetadataSavePopupConfirm=保存布局\ntstr_KeyboardEditorMetadataSavePopupConfirmError=保存布局失败\ntstr_KeyboardEditorMetadataLoadPopupTitle=加载键盘布局\ntstr_KeyboardEditorMetadataLoadPopupConfirm=加载布局\n\ntstr_KeyboardEditorPreviewTitle=键盘预览\n\ntstr_KeyboardEditorSublayoutBase=基本\ntstr_KeyboardEditorSublayoutShift=Shift\ntstr_KeyboardEditorSublayoutAltGr=AltGr\ntstr_KeyboardEditorSublayoutAux=辅助\n\n;通用对话字符串\ntstr_DialogOk=确定\ntstr_DialogCancel=取消\ntstr_DialogDone=完成\ntstr_DialogUndo=撤销\ntstr_DialogRedo=重做\ntstr_DialogColorPickerHeader=选择颜色\ntstr_DialogColorPickerCurrent=当前\ntstr_DialogColorPickerOriginal=原始\ntstr_DialogProfilePickerHeader=选择配置文件\ntstr_DialogProfilePickerNone=[无]\ntstr_DialogActionPickerHeader=选择操作\ntstr_DialogActionPickerEmpty=暂无可用操作\ntstr_DialogIconPickerHeader=选择图标\ntstr_DialogIconPickerHeaderTip=可以在“images\\icons\\”目录中添加自定义 PNG 图标\ntstr_DialogIconPickerNone=[无图标]\ntstr_DialogKeyCodePickerHeader=选择按键码\ntstr_DialogKeyCodePickerHeaderHotkey=选择热键\ntstr_DialogKeyCodePickerModifiers=修饰键\ntstr_DialogKeyCodePickerKeyCode=按键码\ntstr_DialogKeyCodePickerKeyCodeHint=搜索列表\ntstr_DialogKeyCodePickerKeyCodeNone=[无]\ntstr_DialogKeyCodePickerFromInput=从输入获取...\ntstr_DialogKeyCodePickerFromInputPopup=请按任意键或鼠标按键...\ntstr_DialogKeyCodePickerFromInputPopupNoMouse=请按任意键...\ntstr_DialogWindowPickerHeader=选择窗口\ntstr_DialogInputTagsHint=搜索或添加新标签\n\n;来源字符串\ntstr_SourceDesktopAll=合并桌面\ntstr_SourceDesktopID=桌面 %ID%\ntstr_SourceWinRTNone=[无捕获目标]\ntstr_SourceWinRTUnknown=[未知窗口]\ntstr_SourceWinRTClosed=[已关闭]：\ntstr_SourcePerformanceMonitor=性能监控\ntstr_SourceBrowser=浏览器\ntstr_SourceBrowserNoPage=[未加载页面]\n\n;通知图标\ntstr_NotificationIconRestoreVR=恢复 VR 界面\ntstr_NotificationIconOpenOnDesktop=在桌面打开设置\ntstr_NotificationIconQuit=退出\n\n;通知\ntstr_NotificationInitialStartupTitleVR=初始设置\ntstr_NotificationInitialStartupTitleDesktop=Desktop+ 初始设置\ntstr_NotificationInitialStartupMessage=Desktop+ 已成功加入 SteamVR，并将在每次启动 SteamVR 时自动运行。\n\n;浏览器\ntstr_BrowserErrorPageTitle=页面加载错误 - Desktop+\ntstr_BrowserErrorPageHeading=无法加载此页面\ntstr_BrowserErrorPageMessage=尝试加载 %URL% 时发生错误：%ERROR%\n"
  },
  {
    "path": "assets/license.txt",
    "content": "                    GNU GENERAL PUBLIC LICENSE\n                       Version 3, 29 June 2007\n\n Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>\n Everyone is permitted to copy and distribute verbatim copies\n of this license document, but changing it is not allowed.\n\n                            Preamble\n\n  The GNU General Public License is a free, copyleft license for\nsoftware and other kinds of works.\n\n  The licenses for most software and other practical works are designed\nto take away your freedom to share and change the works.  By contrast,\nthe GNU General Public License is intended to guarantee your freedom to\nshare and change all versions of a program--to make sure it remains free\nsoftware for all its users.  We, the Free Software Foundation, use the\nGNU General Public License for most of our software; it applies also to\nany other work released this way by its authors.  You can apply it to\nyour programs, too.\n\n  When we speak of free software, we are referring to freedom, not\nprice.  Our General Public Licenses are designed to make sure that you\nhave the freedom to distribute copies of free software (and charge for\nthem if you wish), that you receive source code or can get it if you\nwant it, that you can change the software or use pieces of it in new\nfree programs, and that you know you can do these things.\n\n  To protect your rights, we need to prevent others from denying you\nthese rights or asking you to surrender the rights.  Therefore, you have\ncertain responsibilities if you distribute copies of the software, or if\nyou modify it: responsibilities to respect the freedom of others.\n\n  For example, if you distribute copies of such a program, whether\ngratis or for a fee, you must pass on to the recipients the same\nfreedoms that you received.  You must make sure that they, too, receive\nor can get the source code.  And you must show them these terms so they\nknow their rights.\n\n  Developers that use the GNU GPL protect your rights with two steps:\n(1) assert copyright on the software, and (2) offer you this License\ngiving you legal permission to copy, distribute and/or modify it.\n\n  For the developers' and authors' protection, the GPL clearly explains\nthat there is no warranty for this free software.  For both users' and\nauthors' sake, the GPL requires that modified versions be marked as\nchanged, so that their problems will not be attributed erroneously to\nauthors of previous versions.\n\n  Some devices are designed to deny users access to install or run\nmodified versions of the software inside them, although the manufacturer\ncan do so.  This is fundamentally incompatible with the aim of\nprotecting users' freedom to change the software.  The systematic\npattern of such abuse occurs in the area of products for individuals to\nuse, which is precisely where it is most unacceptable.  Therefore, we\nhave designed this version of the GPL to prohibit the practice for those\nproducts.  If such problems arise substantially in other domains, we\nstand ready to extend this provision to those domains in future versions\nof the GPL, as needed to protect the freedom of users.\n\n  Finally, every program is threatened constantly by software patents.\nStates should not allow patents to restrict development and use of\nsoftware on general-purpose computers, but in those that do, we wish to\navoid the special danger that patents applied to a free program could\nmake it effectively proprietary.  To prevent this, the GPL assures that\npatents cannot be used to render the program non-free.\n\n  The precise terms and conditions for copying, distribution and\nmodification follow.\n\n                       TERMS AND CONDITIONS\n\n  0. Definitions.\n\n  \"This License\" refers to version 3 of the GNU General Public License.\n\n  \"Copyright\" also means copyright-like laws that apply to other kinds of\nworks, such as semiconductor masks.\n\n  \"The Program\" refers to any copyrightable work licensed under this\nLicense.  Each licensee is addressed as \"you\".  \"Licensees\" and\n\"recipients\" may be individuals or organizations.\n\n  To \"modify\" a work means to copy from or adapt all or part of the work\nin a fashion requiring copyright permission, other than the making of an\nexact copy.  The resulting work is called a \"modified version\" of the\nearlier work or a work \"based on\" the earlier work.\n\n  A \"covered work\" means either the unmodified Program or a work based\non the Program.\n\n  To \"propagate\" a work means to do anything with it that, without\npermission, would make you directly or secondarily liable for\ninfringement under applicable copyright law, except executing it on a\ncomputer or modifying a private copy.  Propagation includes copying,\ndistribution (with or without modification), making available to the\npublic, and in some countries other activities as well.\n\n  To \"convey\" a work means any kind of propagation that enables other\nparties to make or receive copies.  Mere interaction with a user through\na computer network, with no transfer of a copy, is not conveying.\n\n  An interactive user interface displays \"Appropriate Legal Notices\"\nto the extent that it includes a convenient and prominently visible\nfeature that (1) displays an appropriate copyright notice, and (2)\ntells the user that there is no warranty for the work (except to the\nextent that warranties are provided), that licensees may convey the\nwork under this License, and how to view a copy of this License.  If\nthe interface presents a list of user commands or options, such as a\nmenu, a prominent item in the list meets this criterion.\n\n  1. Source Code.\n\n  The \"source code\" for a work means the preferred form of the work\nfor making modifications to it.  \"Object code\" means any non-source\nform of a work.\n\n  A \"Standard Interface\" means an interface that either is an official\nstandard defined by a recognized standards body, or, in the case of\ninterfaces specified for a particular programming language, one that\nis widely used among developers working in that language.\n\n  The \"System Libraries\" of an executable work include anything, other\nthan the work as a whole, that (a) is included in the normal form of\npackaging a Major Component, but which is not part of that Major\nComponent, and (b) serves only to enable use of the work with that\nMajor Component, or to implement a Standard Interface for which an\nimplementation is available to the public in source code form.  A\n\"Major Component\", in this context, means a major essential component\n(kernel, window system, and so on) of the specific operating system\n(if any) on which the executable work runs, or a compiler used to\nproduce the work, or an object code interpreter used to run it.\n\n  The \"Corresponding Source\" for a work in object code form means all\nthe source code needed to generate, install, and (for an executable\nwork) run the object code and to modify the work, including scripts to\ncontrol those activities.  However, it does not include the work's\nSystem Libraries, or general-purpose tools or generally available free\nprograms which are used unmodified in performing those activities but\nwhich are not part of the work.  For example, Corresponding Source\nincludes interface definition files associated with source files for\nthe work, and the source code for shared libraries and dynamically\nlinked subprograms that the work is specifically designed to require,\nsuch as by intimate data communication or control flow between those\nsubprograms and other parts of the work.\n\n  The Corresponding Source need not include anything that users\ncan regenerate automatically from other parts of the Corresponding\nSource.\n\n  The Corresponding Source for a work in source code form is that\nsame work.\n\n  2. Basic Permissions.\n\n  All rights granted under this License are granted for the term of\ncopyright on the Program, and are irrevocable provided the stated\nconditions are met.  This License explicitly affirms your unlimited\npermission to run the unmodified Program.  The output from running a\ncovered work is covered by this License only if the output, given its\ncontent, constitutes a covered work.  This License acknowledges your\nrights of fair use or other equivalent, as provided by copyright law.\n\n  You may make, run and propagate covered works that you do not\nconvey, without conditions so long as your license otherwise remains\nin force.  You may convey covered works to others for the sole purpose\nof having them make modifications exclusively for you, or provide you\nwith facilities for running those works, provided that you comply with\nthe terms of this License in conveying all material for which you do\nnot control copyright.  Those thus making or running the covered works\nfor you must do so exclusively on your behalf, under your direction\nand control, on terms that prohibit them from making any copies of\nyour copyrighted material outside their relationship with you.\n\n  Conveying under any other circumstances is permitted solely under\nthe conditions stated below.  Sublicensing is not allowed; section 10\nmakes it unnecessary.\n\n  3. Protecting Users' Legal Rights From Anti-Circumvention Law.\n\n  No covered work shall be deemed part of an effective technological\nmeasure under any applicable law fulfilling obligations under article\n11 of the WIPO copyright treaty adopted on 20 December 1996, or\nsimilar laws prohibiting or restricting circumvention of such\nmeasures.\n\n  When you convey a covered work, you waive any legal power to forbid\ncircumvention of technological measures to the extent such circumvention\nis effected by exercising rights under this License with respect to\nthe covered work, and you disclaim any intention to limit operation or\nmodification of the work as a means of enforcing, against the work's\nusers, your or third parties' legal rights to forbid circumvention of\ntechnological measures.\n\n  4. Conveying Verbatim Copies.\n\n  You may convey verbatim copies of the Program's source code as you\nreceive it, in any medium, provided that you conspicuously and\nappropriately publish on each copy an appropriate copyright notice;\nkeep intact all notices stating that this License and any\nnon-permissive terms added in accord with section 7 apply to the code;\nkeep intact all notices of the absence of any warranty; and give all\nrecipients a copy of this License along with the Program.\n\n  You may charge any price or no price for each copy that you convey,\nand you may offer support or warranty protection for a fee.\n\n  5. Conveying Modified Source Versions.\n\n  You may convey a work based on the Program, or the modifications to\nproduce it from the Program, in the form of source code under the\nterms of section 4, provided that you also meet all of these conditions:\n\n    a) The work must carry prominent notices stating that you modified\n    it, and giving a relevant date.\n\n    b) The work must carry prominent notices stating that it is\n    released under this License and any conditions added under section\n    7.  This requirement modifies the requirement in section 4 to\n    \"keep intact all notices\".\n\n    c) You must license the entire work, as a whole, under this\n    License to anyone who comes into possession of a copy.  This\n    License will therefore apply, along with any applicable section 7\n    additional terms, to the whole of the work, and all its parts,\n    regardless of how they are packaged.  This License gives no\n    permission to license the work in any other way, but it does not\n    invalidate such permission if you have separately received it.\n\n    d) If the work has interactive user interfaces, each must display\n    Appropriate Legal Notices; however, if the Program has interactive\n    interfaces that do not display Appropriate Legal Notices, your\n    work need not make them do so.\n\n  A compilation of a covered work with other separate and independent\nworks, which are not by their nature extensions of the covered work,\nand which are not combined with it such as to form a larger program,\nin or on a volume of a storage or distribution medium, is called an\n\"aggregate\" if the compilation and its resulting copyright are not\nused to limit the access or legal rights of the compilation's users\nbeyond what the individual works permit.  Inclusion of a covered work\nin an aggregate does not cause this License to apply to the other\nparts of the aggregate.\n\n  6. Conveying Non-Source Forms.\n\n  You may convey a covered work in object code form under the terms\nof sections 4 and 5, provided that you also convey the\nmachine-readable Corresponding Source under the terms of this License,\nin one of these ways:\n\n    a) Convey the object code in, or embodied in, a physical product\n    (including a physical distribution medium), accompanied by the\n    Corresponding Source fixed on a durable physical medium\n    customarily used for software interchange.\n\n    b) Convey the object code in, or embodied in, a physical product\n    (including a physical distribution medium), accompanied by a\n    written offer, valid for at least three years and valid for as\n    long as you offer spare parts or customer support for that product\n    model, to give anyone who possesses the object code either (1) a\n    copy of the Corresponding Source for all the software in the\n    product that is covered by this License, on a durable physical\n    medium customarily used for software interchange, for a price no\n    more than your reasonable cost of physically performing this\n    conveying of source, or (2) access to copy the\n    Corresponding Source from a network server at no charge.\n\n    c) Convey individual copies of the object code with a copy of the\n    written offer to provide the Corresponding Source.  This\n    alternative is allowed only occasionally and noncommercially, and\n    only if you received the object code with such an offer, in accord\n    with subsection 6b.\n\n    d) Convey the object code by offering access from a designated\n    place (gratis or for a charge), and offer equivalent access to the\n    Corresponding Source in the same way through the same place at no\n    further charge.  You need not require recipients to copy the\n    Corresponding Source along with the object code.  If the place to\n    copy the object code is a network server, the Corresponding Source\n    may be on a different server (operated by you or a third party)\n    that supports equivalent copying facilities, provided you maintain\n    clear directions next to the object code saying where to find the\n    Corresponding Source.  Regardless of what server hosts the\n    Corresponding Source, you remain obligated to ensure that it is\n    available for as long as needed to satisfy these requirements.\n\n    e) Convey the object code using peer-to-peer transmission, provided\n    you inform other peers where the object code and Corresponding\n    Source of the work are being offered to the general public at no\n    charge under subsection 6d.\n\n  A separable portion of the object code, whose source code is excluded\nfrom the Corresponding Source as a System Library, need not be\nincluded in conveying the object code work.\n\n  A \"User Product\" is either (1) a \"consumer product\", which means any\ntangible personal property which is normally used for personal, family,\nor household purposes, or (2) anything designed or sold for incorporation\ninto a dwelling.  In determining whether a product is a consumer product,\ndoubtful cases shall be resolved in favor of coverage.  For a particular\nproduct received by a particular user, \"normally used\" refers to a\ntypical or common use of that class of product, regardless of the status\nof the particular user or of the way in which the particular user\nactually uses, or expects or is expected to use, the product.  A product\nis a consumer product regardless of whether the product has substantial\ncommercial, industrial or non-consumer uses, unless such uses represent\nthe only significant mode of use of the product.\n\n  \"Installation Information\" for a User Product means any methods,\nprocedures, authorization keys, or other information required to install\nand execute modified versions of a covered work in that User Product from\na modified version of its Corresponding Source.  The information must\nsuffice to ensure that the continued functioning of the modified object\ncode is in no case prevented or interfered with solely because\nmodification has been made.\n\n  If you convey an object code work under this section in, or with, or\nspecifically for use in, a User Product, and the conveying occurs as\npart of a transaction in which the right of possession and use of the\nUser Product is transferred to the recipient in perpetuity or for a\nfixed term (regardless of how the transaction is characterized), the\nCorresponding Source conveyed under this section must be accompanied\nby the Installation Information.  But this requirement does not apply\nif neither you nor any third party retains the ability to install\nmodified object code on the User Product (for example, the work has\nbeen installed in ROM).\n\n  The requirement to provide Installation Information does not include a\nrequirement to continue to provide support service, warranty, or updates\nfor a work that has been modified or installed by the recipient, or for\nthe User Product in which it has been modified or installed.  Access to a\nnetwork may be denied when the modification itself materially and\nadversely affects the operation of the network or violates the rules and\nprotocols for communication across the network.\n\n  Corresponding Source conveyed, and Installation Information provided,\nin accord with this section must be in a format that is publicly\ndocumented (and with an implementation available to the public in\nsource code form), and must require no special password or key for\nunpacking, reading or copying.\n\n  7. Additional Terms.\n\n  \"Additional permissions\" are terms that supplement the terms of this\nLicense by making exceptions from one or more of its conditions.\nAdditional permissions that are applicable to the entire Program shall\nbe treated as though they were included in this License, to the extent\nthat they are valid under applicable law.  If additional permissions\napply only to part of the Program, that part may be used separately\nunder those permissions, but the entire Program remains governed by\nthis License without regard to the additional permissions.\n\n  When you convey a copy of a covered work, you may at your option\nremove any additional permissions from that copy, or from any part of\nit.  (Additional permissions may be written to require their own\nremoval in certain cases when you modify the work.)  You may place\nadditional permissions on material, added by you to a covered work,\nfor which you have or can give appropriate copyright permission.\n\n  Notwithstanding any other provision of this License, for material you\nadd to a covered work, you may (if authorized by the copyright holders of\nthat material) supplement the terms of this License with terms:\n\n    a) Disclaiming warranty or limiting liability differently from the\n    terms of sections 15 and 16 of this License; or\n\n    b) Requiring preservation of specified reasonable legal notices or\n    author attributions in that material or in the Appropriate Legal\n    Notices displayed by works containing it; or\n\n    c) Prohibiting misrepresentation of the origin of that material, or\n    requiring that modified versions of such material be marked in\n    reasonable ways as different from the original version; or\n\n    d) Limiting the use for publicity purposes of names of licensors or\n    authors of the material; or\n\n    e) Declining to grant rights under trademark law for use of some\n    trade names, trademarks, or service marks; or\n\n    f) Requiring indemnification of licensors and authors of that\n    material by anyone who conveys the material (or modified versions of\n    it) with contractual assumptions of liability to the recipient, for\n    any liability that these contractual assumptions directly impose on\n    those licensors and authors.\n\n  All other non-permissive additional terms are considered \"further\nrestrictions\" within the meaning of section 10.  If the Program as you\nreceived it, or any part of it, contains a notice stating that it is\ngoverned by this License along with a term that is a further\nrestriction, you may remove that term.  If a license document contains\na further restriction but permits relicensing or conveying under this\nLicense, you may add to a covered work material governed by the terms\nof that license document, provided that the further restriction does\nnot survive such relicensing or conveying.\n\n  If you add terms to a covered work in accord with this section, you\nmust place, in the relevant source files, a statement of the\nadditional terms that apply to those files, or a notice indicating\nwhere to find the applicable terms.\n\n  Additional terms, permissive or non-permissive, may be stated in the\nform of a separately written license, or stated as exceptions;\nthe above requirements apply either way.\n\n  8. Termination.\n\n  You may not propagate or modify a covered work except as expressly\nprovided under this License.  Any attempt otherwise to propagate or\nmodify it is void, and will automatically terminate your rights under\nthis License (including any patent licenses granted under the third\nparagraph of section 11).\n\n  However, if you cease all violation of this License, then your\nlicense from a particular copyright holder is reinstated (a)\nprovisionally, unless and until the copyright holder explicitly and\nfinally terminates your license, and (b) permanently, if the copyright\nholder fails to notify you of the violation by some reasonable means\nprior to 60 days after the cessation.\n\n  Moreover, your license from a particular copyright holder is\nreinstated permanently if the copyright holder notifies you of the\nviolation by some reasonable means, this is the first time you have\nreceived notice of violation of this License (for any work) from that\ncopyright holder, and you cure the violation prior to 30 days after\nyour receipt of the notice.\n\n  Termination of your rights under this section does not terminate the\nlicenses of parties who have received copies or rights from you under\nthis License.  If your rights have been terminated and not permanently\nreinstated, you do not qualify to receive new licenses for the same\nmaterial under section 10.\n\n  9. Acceptance Not Required for Having Copies.\n\n  You are not required to accept this License in order to receive or\nrun a copy of the Program.  Ancillary propagation of a covered work\noccurring solely as a consequence of using peer-to-peer transmission\nto receive a copy likewise does not require acceptance.  However,\nnothing other than this License grants you permission to propagate or\nmodify any covered work.  These actions infringe copyright if you do\nnot accept this License.  Therefore, by modifying or propagating a\ncovered work, you indicate your acceptance of this License to do so.\n\n  10. Automatic Licensing of Downstream Recipients.\n\n  Each time you convey a covered work, the recipient automatically\nreceives a license from the original licensors, to run, modify and\npropagate that work, subject to this License.  You are not responsible\nfor enforcing compliance by third parties with this License.\n\n  An \"entity transaction\" is a transaction transferring control of an\norganization, or substantially all assets of one, or subdividing an\norganization, or merging organizations.  If propagation of a covered\nwork results from an entity transaction, each party to that\ntransaction who receives a copy of the work also receives whatever\nlicenses to the work the party's predecessor in interest had or could\ngive under the previous paragraph, plus a right to possession of the\nCorresponding Source of the work from the predecessor in interest, if\nthe predecessor has it or can get it with reasonable efforts.\n\n  You may not impose any further restrictions on the exercise of the\nrights granted or affirmed under this License.  For example, you may\nnot impose a license fee, royalty, or other charge for exercise of\nrights granted under this License, and you may not initiate litigation\n(including a cross-claim or counterclaim in a lawsuit) alleging that\nany patent claim is infringed by making, using, selling, offering for\nsale, or importing the Program or any portion of it.\n\n  11. Patents.\n\n  A \"contributor\" is a copyright holder who authorizes use under this\nLicense of the Program or a work on which the Program is based.  The\nwork thus licensed is called the contributor's \"contributor version\".\n\n  A contributor's \"essential patent claims\" are all patent claims\nowned or controlled by the contributor, whether already acquired or\nhereafter acquired, that would be infringed by some manner, permitted\nby this License, of making, using, or selling its contributor version,\nbut do not include claims that would be infringed only as a\nconsequence of further modification of the contributor version.  For\npurposes of this definition, \"control\" includes the right to grant\npatent sublicenses in a manner consistent with the requirements of\nthis License.\n\n  Each contributor grants you a non-exclusive, worldwide, royalty-free\npatent license under the contributor's essential patent claims, to\nmake, use, sell, offer for sale, import and otherwise run, modify and\npropagate the contents of its contributor version.\n\n  In the following three paragraphs, a \"patent license\" is any express\nagreement or commitment, however denominated, not to enforce a patent\n(such as an express permission to practice a patent or covenant not to\nsue for patent infringement).  To \"grant\" such a patent license to a\nparty means to make such an agreement or commitment not to enforce a\npatent against the party.\n\n  If you convey a covered work, knowingly relying on a patent license,\nand the Corresponding Source of the work is not available for anyone\nto copy, free of charge and under the terms of this License, through a\npublicly available network server or other readily accessible means,\nthen you must either (1) cause the Corresponding Source to be so\navailable, or (2) arrange to deprive yourself of the benefit of the\npatent license for this particular work, or (3) arrange, in a manner\nconsistent with the requirements of this License, to extend the patent\nlicense to downstream recipients.  \"Knowingly relying\" means you have\nactual knowledge that, but for the patent license, your conveying the\ncovered work in a country, or your recipient's use of the covered work\nin a country, would infringe one or more identifiable patents in that\ncountry that you have reason to believe are valid.\n\n  If, pursuant to or in connection with a single transaction or\narrangement, you convey, or propagate by procuring conveyance of, a\ncovered work, and grant a patent license to some of the parties\nreceiving the covered work authorizing them to use, propagate, modify\nor convey a specific copy of the covered work, then the patent license\nyou grant is automatically extended to all recipients of the covered\nwork and works based on it.\n\n  A patent license is \"discriminatory\" if it does not include within\nthe scope of its coverage, prohibits the exercise of, or is\nconditioned on the non-exercise of one or more of the rights that are\nspecifically granted under this License.  You may not convey a covered\nwork if you are a party to an arrangement with a third party that is\nin the business of distributing software, under which you make payment\nto the third party based on the extent of your activity of conveying\nthe work, and under which the third party grants, to any of the\nparties who would receive the covered work from you, a discriminatory\npatent license (a) in connection with copies of the covered work\nconveyed by you (or copies made from those copies), or (b) primarily\nfor and in connection with specific products or compilations that\ncontain the covered work, unless you entered into that arrangement,\nor that patent license was granted, prior to 28 March 2007.\n\n  Nothing in this License shall be construed as excluding or limiting\nany implied license or other defenses to infringement that may\notherwise be available to you under applicable patent law.\n\n  12. No Surrender of Others' Freedom.\n\n  If conditions are imposed on you (whether by court order, agreement or\notherwise) that contradict the conditions of this License, they do not\nexcuse you from the conditions of this License.  If you cannot convey a\ncovered work so as to satisfy simultaneously your obligations under this\nLicense and any other pertinent obligations, then as a consequence you may\nnot convey it at all.  For example, if you agree to terms that obligate you\nto collect a royalty for further conveying from those to whom you convey\nthe Program, the only way you could satisfy both those terms and this\nLicense would be to refrain entirely from conveying the Program.\n\n  13. Use with the GNU Affero General Public License.\n\n  Notwithstanding any other provision of this License, you have\npermission to link or combine any covered work with a work licensed\nunder version 3 of the GNU Affero General Public License into a single\ncombined work, and to convey the resulting work.  The terms of this\nLicense will continue to apply to the part which is the covered work,\nbut the special requirements of the GNU Affero General Public License,\nsection 13, concerning interaction through a network will apply to the\ncombination as such.\n\n  14. Revised Versions of this License.\n\n  The Free Software Foundation may publish revised and/or new versions of\nthe GNU General Public License from time to time.  Such new versions will\nbe similar in spirit to the present version, but may differ in detail to\naddress new problems or concerns.\n\n  Each version is given a distinguishing version number.  If the\nProgram specifies that a certain numbered version of the GNU General\nPublic License \"or any later version\" applies to it, you have the\noption of following the terms and conditions either of that numbered\nversion or of any later version published by the Free Software\nFoundation.  If the Program does not specify a version number of the\nGNU General Public License, you may choose any version ever published\nby the Free Software Foundation.\n\n  If the Program specifies that a proxy can decide which future\nversions of the GNU General Public License can be used, that proxy's\npublic statement of acceptance of a version permanently authorizes you\nto choose that version for the Program.\n\n  Later license versions may give you additional or different\npermissions.  However, no additional obligations are imposed on any\nauthor or copyright holder as a result of your choosing to follow a\nlater version.\n\n  15. Disclaimer of Warranty.\n\n  THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY\nAPPLICABLE LAW.  EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT\nHOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM \"AS IS\" WITHOUT WARRANTY\nOF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,\nTHE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\nPURPOSE.  THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM\nIS WITH YOU.  SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF\nALL NECESSARY SERVICING, REPAIR OR CORRECTION.\n\n  16. Limitation of Liability.\n\n  IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING\nWILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS\nTHE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY\nGENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE\nUSE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF\nDATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD\nPARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),\nEVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF\nSUCH DAMAGES.\n\n  17. Interpretation of Sections 15 and 16.\n\n  If the disclaimer of warranty and limitation of liability provided\nabove cannot be given local legal effect according to their terms,\nreviewing courts shall apply local law that most closely approximates\nan absolute waiver of all civil liability in connection with the\nProgram, unless a warranty or assumption of liability accompanies a\ncopy of the Program in return for a fee.\n\n                     END OF TERMS AND CONDITIONS\n\n            How to Apply These Terms to Your New Programs\n\n  If you develop a new program, and you want it to be of the greatest\npossible use to the public, the best way to achieve this is to make it\nfree software which everyone can redistribute and change under these terms.\n\n  To do so, attach the following notices to the program.  It is safest\nto attach them to the start of each source file to most effectively\nstate the exclusion of warranty; and each file should have at least\nthe \"copyright\" line and a pointer to where the full notice is found.\n\n    <one line to give the program's name and a brief idea of what it does.>\n    Copyright (C) <year>  <name of author>\n\n    This program is free software: you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation, either version 3 of the License, or\n    (at your option) any later version.\n\n    This program is distributed in the hope that it will be useful,\n    but WITHOUT ANY WARRANTY; without even the implied warranty of\n    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n    GNU General Public License for more details.\n\n    You should have received a copy of the GNU General Public License\n    along with this program.  If not, see <http://www.gnu.org/licenses/>.\n\nAlso add information on how to contact you by electronic and paper mail.\n\n  If the program does terminal interaction, make it output a short\nnotice like this when it starts in an interactive mode:\n\n    <program>  Copyright (C) <year>  <name of author>\n    This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.\n    This is free software, and you are welcome to redistribute it\n    under certain conditions; type `show c' for details.\n\nThe hypothetical commands `show w' and `show c' should show the appropriate\nparts of the General Public License.  Of course, your program's commands\nmight be different; for a GUI interface, you would use an \"about box\".\n\n  You should also get your employer (if you work as a programmer) or school,\nif any, to sign a \"copyright disclaimer\" for the program, if necessary.\nFor more information on this, and how to apply and follow the GNU GPL, see\n<http://www.gnu.org/licenses/>.\n\n  The GNU General Public License does not permit incorporating your program\ninto proprietary programs.  If your program is a subroutine library, you\nmay consider it more useful to permit linking proprietary applications with\nthe library.  If this is what you want to do, use the GNU Lesser General\nPublic License instead of this License.  But first, please read\n<http://www.gnu.org/philosophy/why-not-lgpl.html>.\n"
  },
  {
    "path": "assets/manifest.vrmanifest",
    "content": "{\n\t\"source\": \"builtin\",\n\t\"applications\": [\n\t{\n\t\t\"app_key\": \"steam.overlay.1494460\",\n\t\t\n\t\t\"launch_type\": \"binary\",\n\t\t\"binary_path_windows\": \"DesktopPlus.exe\",\n\t\t\"is_dashboard_overlay\": true,\n\t\t\"action_manifest_path\" : \"action_manifest.json\",\n\t\t\"image_path\" : \"images/desktop_plus_capsule.png\",\n\n\t\t\"strings\": {\n\t\t\t\"en_us\": {\n\t\t\t\t\"name\": \"Desktop+\",\n\t\t\t\t\"description\": \"Desktop+ Overlay\"\n\t\t\t}\n\t\t}\n\t}, \n\t{\n\t\t\"app_key\": \"elvissteinjr.DesktopPlusTheaterScreen\",\n\t\t\n\t\t\"launch_type\": \"binary\",\n\t\t\"binary_path_windows\": \"DesktopPlus.exe\",\n\t\t\"starts_theater_mode\": true,\n\t\t\"is_hidden\": true,\n\t\t\"is_internal\": true,\n\n\t\t\"strings\": {\n\t\t\t\"en_us\": {\n\t\t\t\t\"name\": \"Desktop+ Theater Screen\",\n\t\t\t\t\"description\": \"Desktop+ Theater Screen\"\n\t\t\t}\n\t\t}\n\t}]\n}\n"
  },
  {
    "path": "assets/misc/!About this folder.txt",
    "content": "This folder contains scripts enabling Desktop+ to gain higher access rights.\nThe scripts are provided for convenience. Changes made by these scripts are inherently unsafe due to SteamVR running from an\nuser-writable location among other things.\n\n! Use your own judgment before running any of them. Don't use them if you don't know what you're doing.\n! The author of Desktop+ shall not be liable for any damage caused by the use of these scripts.\n\nThe .ps1 PowerShell scripts require to be run as administrator. The .bat files will try do that for you, so it's recommended to\nuse those instead.\nMake sure Desktop+ is not running before executing them.\n\n\nCreateElevatedTask.ps1:\n-\nCreates a scheduled task to enable use of Desktop+'s elevated mode.\nElevated mode allows inputs and actions to be executed with administrator privileges, getting around most restrictions of User\nInterface Privilege Isolation (UIPI), which it is subject to otherwise.\nAfter running this script, elevated mode can be accessed via [Misc|Troubleshooting|Desktop+: Enter Elevated Mode].\nIf you move Desktop+'s files, the scheduled task will break but the button remains. Re-run the script to fix this.\n\nNote that actions launching applications will run them with the same privileges as Desktop+, so be careful if you don't intend\nthem to have administrator rights.\nLeave elevated mode again as soon as possible.\n\n\nEnableUIAccess.ps1:\n-\nEnables UIAccess rights for Desktop+ to lift interaction restrictions with higher privileged applications and UAC prompts.\nThis is done by signing the DesktopPlus.exe executable with a self-signed certificate, using a different side-by-side manifest\nand changing group policies if needed.\n\nCompared to elevated mode, this enables Desktop+ to simulate input on any applications, including UAC prompts, at any time while\nnot running with administrator privileges.\nThis script needs to be run again after Desktop+ has been updated or otherwise modified.\n\nThe following group policy is changed by the script:\n- \"User Account Control: Only elevate UIAccess applications that are installed in secure locations\" (set to 0) (only if needed)\n\nIn order for Desktop+ to be able to mirror and interact with UAC prompts, the UAC level has to be lowered to not display the\nprompts on the secure desktop (\"do not dim my desktop\").\nThe elevated scheduled task has to be recreated after enabling UIAccess for the first time or entering elevated mode will fail.\n\n\nUndoSystemChanges.ps1:\n-\nReverts system-wide changes made by above scripts.\nThe script does not revert changes made to group policies as it may impact other applications."
  },
  {
    "path": "assets/misc/CreateElevatedTask.bat",
    "content": "@echo off\n\npushd %~dp0\necho Launching script with administrator privileges...\npowershell -command \"   Start-Process PowerShell -Verb RunAs \\\"\"-ExecutionPolicy Bypass -Command `\\\"\"cd '%cd%'; & '.\\CreateElevatedTask.ps1';`\\\"\"\\\"\"   \"\n"
  },
  {
    "path": "assets/misc/CreateElevatedTask.ps1",
    "content": "#Requires -RunAsAdministrator\n\n$msg = \"Desktop+ Elevated Scheduled Task Creation Script\n------------------------------------------------\nThis creates an elevated scheduled task for Desktop+ to allow entering elevated mode without UAC prompt to simulate inputs with administrator rights.\nDesktop+ will only use this task when the button in the UI is used to enter elevated mode.\nPlease keep the security implications of doing this in mind.\nAt the very least consider restricting write access to the Desktop+ executable file.\n\"\n\nWrite-Host $msg\n\n#Ask before continuing\n$opt_yes = New-Object System.Management.Automation.Host.ChoiceDescription \"&Yes\", \"Create scheduled task and enable elevated mode for Desktop+\"\n$opt_no  = New-Object System.Management.Automation.Host.ChoiceDescription \"&No\",  'Cancel'\n$options = [System.Management.Automation.Host.ChoiceDescription[]]($opt_yes, $opt_no)\n\n$result = $host.ui.PromptForChoice(\"\", \"Continue and create the scheduled task?\", $options, 1)\n\nif ($result -eq 1)\n{\n    exit\n}\n\n#Apply the changes\n\n#Command and arguments for the scheduled task\n$command = $PSScriptRoot + \"\\..\\DesktopPlus.exe\"\n$arg = \"-ElevatedMode\"\n\n#Check if UIAccess is currently enabled\n$sel = Select-String -Path \"..\\DesktopPlus.exe.manifest\" -Pattern \"uiAccess=`\"true`\"\"\n\n#String found means UIAccess is enabled\nif ($sel -ne $null)\n{\n\t#Change the command and arguments to use cmd as a workaround since the elevated scheduled task can't be run directly when UIAccess is enabled\n\t$command = \"cmd\"\n\t$arg = \"/c `\"start DesktopPlus.exe -ElevatedMode`\"\"\n}\n\n\n#We use an XML file with schtasks instead of the Powershell cmdlets as we can create the task from an elevated session without additional password input this way.\n$xml = '<?xml version=\"1.0\" encoding=\"UTF-16\"?>\n<Task version=\"1.2\" xmlns=\"http://schemas.microsoft.com/windows/2004/02/mit/task\">\n  <RegistrationInfo>\n    <Author>Desktop+</Author>\n    <Description>This scheduled task is run manually by Desktop+ in order to allow it to be run with adminstrator rights without UAC prompt. \nDesktop+ will only use it when the button in the settings is used to switch into elevated mode.</Description>\n  </RegistrationInfo>\n  <Triggers />\n  <Principals>\n    <Principal id=\"Author\">\n      <LogonType>InteractiveToken</LogonType>\n      <RunLevel>HighestAvailable</RunLevel>\n    </Principal>\n  </Principals>\n  <Settings>\n    <MultipleInstancesPolicy>Parallel</MultipleInstancesPolicy>\n    <DisallowStartIfOnBatteries>false</DisallowStartIfOnBatteries>\n    <StopIfGoingOnBatteries>false</StopIfGoingOnBatteries>\n    <AllowHardTerminate>false</AllowHardTerminate>\n    <StartWhenAvailable>false</StartWhenAvailable>\n    <RunOnlyIfNetworkAvailable>false</RunOnlyIfNetworkAvailable>\n    <IdleSettings>\n      <StopOnIdleEnd>false</StopOnIdleEnd>\n      <RestartOnIdle>false</RestartOnIdle>\n    </IdleSettings>\n    <AllowStartOnDemand>true</AllowStartOnDemand>\n    <Enabled>true</Enabled>\n    <Hidden>false</Hidden>\n    <RunOnlyIfIdle>false</RunOnlyIfIdle>\n    <WakeToRun>false</WakeToRun>\n    <ExecutionTimeLimit>PT0S</ExecutionTimeLimit>\n    <Priority>7</Priority>\n  </Settings>\n  <Actions Context=\"Author\">\n    <Exec>\n      <Command>' + $command + '</Command>\n\t  <Arguments>' + $arg + '</Arguments>\n\t  <WorkingDirectory>'+ $PSScriptRoot + '\\..</WorkingDirectory>\n    </Exec>\n  </Actions>\n</Task>'\n\n#Write XML to file\n$xml | Set-Content .\\DesktopPlusElevatedTask.xml\n\n#Create task\nschtasks /Create /TN \"DesktopPlus Elevated\" /XML \"DesktopPlusElevatedTask.xml\" /F\n\n#Delete the XML file after we're done\nRemove-Item DesktopPlusElevatedTask.xml\n\nWrite-Output \"`n\"\n\ncmd /c pause\n"
  },
  {
    "path": "assets/misc/EnableUIAccess.bat",
    "content": "@echo off\n\npushd %~dp0\necho Launching script with administrator privileges...\npowershell -command \"   Start-Process PowerShell -Verb RunAs \\\"\"-ExecutionPolicy Bypass -Command `\\\"\"cd '%cd%'; & '.\\EnableUIAccess.ps1';`\\\"\"\\\"\"   \"\n"
  },
  {
    "path": "assets/misc/EnableUIAccess.ps1",
    "content": "#Requires -RunAsAdministrator\n\n$msg = \"Desktop+ UIAccess Setup Script\n------------------------------\nThis enables UIAccess rights for Desktop+ to lift interaction restrictions with higher privileged applications and UAC prompts.\nThis is done by signing the DesktopPlus.exe executable with a self-signed certificate, using a different side-by-side manifest and changing group policies if needed.\nThe private key of the certificate created in this script is deleted afterwards and does not remain on the system.\nNevertheless it's recommended to inspect what this script does and use your own judgement before just running this.\nThe script needs to be run again each time Desktop+ has been updated or otherwise modified.\n\"\n\nWrite-Host $msg\n\n#Ask before continuing\n$opt_yes = New-Object System.Management.Automation.Host.ChoiceDescription \"&Yes\", \"Apply changes and enable UIAccess for Desktop+\"\n$opt_no  = New-Object System.Management.Automation.Host.ChoiceDescription \"&No\",  'Cancel'\n$options = [System.Management.Automation.Host.ChoiceDescription[]]($opt_yes, $opt_no)\n\n$result = $host.ui.PromptForChoice(\"\", \"Continue and apply these changes?\", $options, 1)\n\nif ($result -eq 1)\n{\n    exit\n}\n\n#Apply the changes\n$CertificateSubject = \"DesktopPlus UIAccess Script\"\n\n#Remove old certificates if they exist\nGet-ChildItem Cert:\\LocalMachine\\Root |\nWhere-Object { $_.Subject -match $CertificateSubject } |\nRemove-Item\n\nGet-ChildItem Cert:\\LocalMachine\\CA |\nWhere-Object { $_.Subject -match $CertificateSubject } |\nRemove-Item\n\n\n#Create the new self-signed certificate\n$cert = New-SelfSignedCertificate -CertStoreLocation Cert:\\LocalMachine\\My -Type CodeSigningCert -Subject $CertificateSubject -NotAfter (Get-Date).AddYears(5)\n\n#Export the certificate to import it again as a root certificate, but without the private key\nExport-Certificate -Cert $cert -FilePath \".\\DesktopPlusCert.cer\" | Out-Null\nImport-Certificate -FilePath \".\\DesktopPlusCert.cer\" -CertStoreLocation \"Cert:\\LocalMachine\\Root\" | Out-Null\n\n\n#Sign DesktopPlus.exe\nSet-AuthenticodeSignature -FilePath \"..\\DesktopPlus.exe\" -Certificate $cert | Out-Null\n\n\n#Backup normal side-by-side manifest (doesn't overwrite if already exists)\nMove-Item \"..\\DesktopPlus.exe.manifest\" -Destination \"EnableUIAccessDesktopPlusBackup.manifest\" -ErrorAction SilentlyContinue\n#Replace side-by-side manifest with the UIAccess enabled one\nCopy-Item \"EnableUIAccessDesktopPlus.manifest\" -Destination \"..\\DesktopPlus.exe.manifest\"\n\n\n#Disable \"User Account Control: Only elevate UIAccess applications that are installed in secure locations\" group policy if we are outside of the Program Files directory\nif (!$pwd.path.StartsWith($env:ProgramFiles)) \n{\n    Set-ItemProperty -Path \"HKLM:\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Policies\\System\" -Name \"EnableSecureUIAPaths\" -Value 0\n}\n\n\n#Cleanup\nRemove-Item \".\\DesktopPlusCert.cer\"\n\n#We do not leave the certificate with the private key on the system. Signing with it was a one-time thing.\nSet-Location \"Cert:\"\n$cert | Remove-Item -DeleteKey\n\nWrite-Host \"Done.\"\nWrite-Output \"`n\"\n\ncmd /c pause"
  },
  {
    "path": "assets/misc/EnableUIAccessDesktopPlus.manifest",
    "content": "﻿<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>\n<assembly xmlns=\"urn:schemas-microsoft-com:asm.v1\" manifestVersion=\"1.0\">\n\t<dependency>\n\t\t<dependentAssembly>\n\t\t\t<assemblyIdentity type=\"win32\" name=\"Microsoft.Windows.Common-Controls\" version=\"6.0.0.0\" processorArchitecture=\"*\" publicKeyToken=\"6595b64144ccf1df\" language=\"*\"/>\n\t\t</dependentAssembly>\n\t</dependency>\n\t<trustInfo xmlns=\"urn:schemas-microsoft-com:asm.v3\">\n\t\t<security>\n\t\t\t<requestedPrivileges>\n\t\t\t\t<requestedExecutionLevel level=\"asInvoker\" uiAccess=\"true\"/>\n\t\t\t</requestedPrivileges>\n\t\t</security>\n\t</trustInfo>\n\t<application xmlns=\"urn:schemas-microsoft-com:asm.v3\">\n\t\t<windowsSettings>\n\t\t\t<dpiAware xmlns=\"http://schemas.microsoft.com/SMI/2005/WindowsSettings\">True/PM</dpiAware>\n\t\t</windowsSettings>\n\t</application>\n</assembly>\n"
  },
  {
    "path": "assets/misc/UndoSystemChanges.bat",
    "content": "@echo off\n\npushd %~dp0\necho Launching script with administrator privileges...\npowershell -command \"   Start-Process PowerShell -Verb RunAs \\\"\"-ExecutionPolicy Bypass -Command `\\\"\"cd '%cd%'; & '.\\UndoSystemChanges.ps1';`\\\"\"\\\"\"   \"\n"
  },
  {
    "path": "assets/misc/UndoSystemChanges.ps1",
    "content": "#Requires -RunAsAdministrator\n\n$msg = \"Desktop+ Cleanup Script\n-----------------------\nThis reverts system-wide changes made by the scripts in the `\"misc`\" folder of Desktop+.\n\nIt removes the following:\n- `\"DesktopPlus Elevated`\" scheduled task\n- `\"DesktopPlus UIAccess Script`\" self-signed certificates\n- Desktop+ UIAccess side-by-side manifest\n\nNot reverted are changes which may impact other applications or may not have been different from the start, such as:\n- `\"User Account Control: Only elevate UIAccess applications that are installed in secure locations`\" group policy\n\"\n\nWrite-Host $msg\n\n#Ask before continuing\n$opt_yes = New-Object System.Management.Automation.Host.ChoiceDescription \"&Yes\", \"Undo changes made by Desktop+ scripts\"\n$opt_no  = New-Object System.Management.Automation.Host.ChoiceDescription \"&No\",  'Cancel'\n$options = [System.Management.Automation.Host.ChoiceDescription[]]($opt_yes, $opt_no)\n\n$result = $host.ui.PromptForChoice(\"\", \"Continue and undo these changes?\", $options, 1)\n\nif ($result -eq 1)\n{\n    exit\n}\n\n#Delete scheduled task if it exists\n\nschtasks /Query /TN \"DesktopPlus Elevated\" *>$null\nif ($LastExitCode -eq 0)\n{\n    schtasks /Delete /TN \"DesktopPlus Elevated\" /F\n}\n\n#Remove certificates\nGet-ChildItem Cert:\\LocalMachine\\Root |\nWhere-Object { $_.Subject -match $CertificateSubject } |\nRemove-Item\n\nGet-ChildItem Cert:\\LocalMachine\\CA |\nWhere-Object { $_.Subject -match $CertificateSubject } |\nRemove-Item\n\n#Restore previously backed-up side-by-side manifest if it exists\nif (Test-Path -Path \"EnableUIAccessDesktopPlusBackup.manifest\")\n{\n    Move-Item \"EnableUIAccessDesktopPlusBackup.manifest\" -Destination \"..\\DesktopPlus.exe.manifest\" -Force\n}\n\nWrite-Host \"Done.\"\nWrite-Output \"`n\"\n\ncmd /c pause"
  },
  {
    "path": "assets/profiles/Sample Profile.ini",
    "content": "[Overlay0]\nName=Dashboard\nNameIsCustom=true\nEnabled=true\nDesktopID=0\nCaptureSource=0\nWidth=165\nCurvature=17\nOpacity=100\nOffsetRight=0\nOffsetUp=0\nOffsetForward=0\nDisplayMode=3\nOrigin=Dashboard\nCroppingEnabled=false\nCroppingX=0\nCroppingY=0\nCroppingWidth=-1\nCroppingHeight=-1\n3DEnabled=false\n3DMode=0\n3DSwapped=false\nGazeFade=false\nGazeFadeDistance=0\nGazeFadeRate=100\nGazeFadeOpacity=0\nUpdateLimitModeOverride=0\nUpdateLimitMS=0\nUpdateLimitFPS=7\nInputEnabled=true\nInputDPlusLPEnabled=true\nGroupID=0\nUpdateInvisible=false\nShowFloatingUI=true\nShowDesktopButtons=true\nShowActionBar=true\nActionBarOrderUseGlobal=true\nTransform=[2.12766 0 0 0 0 2.12766 0 0 0 0 2.12766 0 0 0 0 1]\nWinRTLastWindowTitle=\nWinRTLastWindowClassName=\nWinRTLastWindowExeName=\nWinRTDesktopID=-2\nActionBarOrderCustom=1 1;2 0;1000 1;1001 0;1002 0;1003 1;3 0;4 0;5 0;\n\n[Overlay1]\nName=Dashboard 🡄\nNameIsCustom=true\nEnabled=true\nDesktopID=0\nCaptureSource=0\nWidth=100\nCurvature=0\nOpacity=100\nOffsetRight=0\nOffsetUp=0\nOffsetForward=0\nDisplayMode=3\nOrigin=Dashboard\nCroppingEnabled=false\nCroppingX=0\nCroppingY=0\nCroppingWidth=-1\nCroppingHeight=-1\n3DEnabled=false\n3DMode=0\n3DSwapped=false\nGazeFade=false\nGazeFadeDistance=0\nGazeFadeRate=100\nGazeFadeOpacity=0\nUpdateLimitModeOverride=0\nUpdateLimitMS=0\nUpdateLimitFPS=7\nInputEnabled=true\nInputDPlusLPEnabled=true\nGroupID=0\nUpdateInvisible=false\nShowFloatingUI=true\nShowDesktopButtons=true\nShowActionBar=false\nActionBarOrderUseGlobal=true\nTransform=[1.84261 0 -1.06383 0 0 2.12766 0 0 1.06383 0 1.84261 0 -2.66135 0.425532 0.922328 1]\nWinRTLastWindowTitle=\nWinRTLastWindowClassName=\nWinRTLastWindowExeName=\nWinRTDesktopID=-2\nActionBarOrderCustom=1 1;2 0;1000 1;1001 0;1002 0;1003 1;3 0;4 0;5 0;\n\n[Overlay2]\nName=Dashboard 🡆\nNameIsCustom=true\nEnabled=true\nDesktopID=0\nCaptureSource=0\nWidth=100\nCurvature=0\nOpacity=100\nOffsetRight=0\nOffsetUp=0\nOffsetForward=0\nDisplayMode=3\nOrigin=Dashboard\nCroppingEnabled=false\nCroppingX=0\nCroppingY=0\nCroppingWidth=-1\nCroppingHeight=-1\n3DEnabled=false\n3DMode=0\n3DSwapped=false\nGazeFade=false\nGazeFadeDistance=0\nGazeFadeRate=100\nGazeFadeOpacity=0\nUpdateLimitModeOverride=0\nUpdateLimitMS=0\nUpdateLimitFPS=7\nInputEnabled=true\nInputDPlusLPEnabled=true\nGroupID=0\nUpdateInvisible=false\nShowFloatingUI=true\nShowDesktopButtons=true\nShowActionBar=false\nActionBarOrderUseGlobal=true\nTransform=[1.84261 0 1.06383 0 0 2.12766 0 0 -1.06383 0 1.84261 0 2.66135 0.425532 0.922327 1]\nWinRTLastWindowTitle=\nWinRTLastWindowClassName=\nWinRTLastWindowExeName=\nWinRTDesktopID=-2\nActionBarOrderCustom=1 1;2 0;1000 1;1001 0;1002 0;1003 1;3 0;4 0;5 0;\n\n[Overlay3]\nName=Performance Monitor\nNameIsCustom=false\nEnabled=true\nDesktopID=0\nCaptureSource=2\nWidth=20\nCurvature=0\nOpacity=75\nOffsetRight=0\nOffsetUp=0\nOffsetForward=0\nDisplayMode=0\nOrigin=5\nCroppingEnabled=false\nCroppingX=0\nCroppingY=0\nCroppingWidth=-1\nCroppingHeight=-1\n3DEnabled=false\n3DMode=0\n3DSwapped=false\nGazeFade=true\nGazeFadeDistance=15\nGazeFadeRate=150\nGazeFadeOpacity=0\nUpdateLimitModeOverride=0\nUpdateLimitMS=0\nUpdateLimitFPS=7\nInputEnabled=false\nInputDPlusLPEnabled=true\nGroupID=0\nUpdateInvisible=false\nShowFloatingUI=true\nShowDesktopButtons=false\nShowActionBar=false\nActionBarOrderUseGlobal=true\nTransform=[-0.0495577 0.263826 -0.963298 0 -0.423386 -0.879087 -0.218981 0 -0.904595 0.396994 0.155265 0 -0.104406 0.00957629 0.138933 1]\nWinRTLastWindowTitle=\nWinRTLastWindowClassName=\nWinRTLastWindowExeName=\nWinRTDesktopID=-2\nActionBarOrderCustom=1 1;2 0;1000 1;1001 0;1002 0;1003 0;3 0;4 0;5 0;\n\n"
  },
  {
    "path": "assets/readme.txt",
    "content": "Desktop+, an advanced SteamVR Desktop Overlay, by elvissteinjr\n--------------------------------------------------------------\n\nInstallation (GitHub Version)\n-----------------------------\nExtract the complete archive if not done yet. Put the files in a location where they can stay.\nRun DesktopPlus.exe. This will also launch SteamVR if it's not already running. A VR-HMD must be connected.\nIf everything is fine, a message will come up to indicate successful first-time setup.\nDesktop+ will continue running in the background as a SteamVR overlay application afterwards.\nIf the message does not come up on the first launch, check the Troubleshooting section.\n\nDesktop+ will register itself as an overlay application to SteamVR and run automatically on following SteamVR launches.\nIf you move the files of this application, you'll have to repeat these steps.\n\n\nInstallation (Steam Version)\n----------------------------\nSimply install the application through Steam and launch it. This will also launch SteamVR if it's not already running.\nA VR-HMD must be connected. If everything is fine, a message will come up to indicate successful first-time setup.\nDesktop+ will continue running in the background as a SteamVR overlay application afterwards.\nIf the message does not come up on the first launch, check the \"Startup / Shutdown\" settings in SteamVR and the Troubleshooting\nsection.\n\n\nUpdates\n-------\nThe latest version of Desktop+ can be found on https://github.com/elvissteinjr/DesktopPlus/ and on Steam.\n\n\nUninstallation (GitHub Version)\n-------------------------------\nDelete all files that came with the archive. \nSteamVR will automatically remove the overlay application entry when the executable isn't present anymore.\nDesktop+ does not write to files outside its own directory. However, if you did set up elevated mode, its scheduled task will\nremain.\n\n\nUninstallation (Steam Version)\n------------------------------\nUninstall the application through Steam.\nSteam will not delete your configuration and profiles. They can be found in \"[Steam Library Path]\\SteamApps\\common\\DesktopPlus\"\nand be safely deleted if desired. These files are also synced to the Steam Cloud if that feature is enabled.\nAdditionally, if you did set up elevated mode, its scheduled task will remain.\n\n\nUser Guide\n----------\nA detailed user guide can be found on https://github.com/elvissteinjr/DesktopPlus/blob/master/docs/user_guide.md\nIt's recommended to finish reading this document beforehand, however, as the user guide does not cover some topics this readme\ncovers.\n\n\nConfiguration\n-------------\nDesktop+ can be fully configured from within VR. If desired, the settings interface can also be used from the desktop, however.\nEither run DesktopPlusUI.exe while SteamVR is not running, press [Restart in Desktop Mode] in the Troubleshooting section of the\nSettings window, or run DesktopPlusUI.exe with the \"--DesktopMode\" command line argument to do so.\nSome settings are only available while SteamVR is running.\n\nSettings are applied instantly and written to disk when the settings window is dismissed or Desktop+ closes.\nThe setting slider values can be edited directly by right-clicking the slider.\n\n\nOverlay Management\n------------------\nAll overlays in Desktop+ are listed in the Overlay Bar that appears in Desktop+'s SteamVR dashboard tab.\nThey can be rearranged by dragging the icons across the bar. Clicking on an overlay's icon opens a menu to toggle visibility,\nduplicate, remove or open the Overlay Properties window for the selected overlay.\n\nThe current overlay setup will be remembered automatically between sessions. Overlay profiles can be used to save and restore\nmultiple of such setups.\n\n\nActions\n-------\nDesktop+ offers so-called actions which can be bound to controller buttons, the Floating UI or just executed from the list.\nActions consist of a series of commands that can control the state of your overlays, simulate input and execute programs.\n\nCustom icons can be added by putting PNG files in the \"images/icons\" folder. Recommended size is 96x96 pixels.\n\n\nGlobal Shortcuts & Input Features\n---------------------------------\nActions can be bound to up to 20 different global shortcuts which can be activated by the SteamVR Input bindings.\nSteamVR doesn't list overlay applications in the regular application controller configuration list, but the Settings window in\nDesktop+ has buttons that lead directly into the input binding screen for Desktop+.\n\nApart from the global shortcuts, bindings for Desktop+ laser pointer interaction can also be found.\nThese mirror SteamVR's default laser pointer bindings for known devices, but can be adjusted as desired.\n\nThere's also \"Enable Global Laser Pointer\", which can be used to enable the laser pointer outside of the dashboard.\nHowever, laser pointer auto-activation is enabled by default, making this binding not strictly necessary to use.\n\nOutside of the dashboard/when SteamVR's system laser pointer is not active, Desktop+ uses its own laser pointer implementation.\nThis is allows for additional features that can be configured in the Laser Pointer section of the Settings window.\n\n\nElevated Mode / Enabling UIAccess\n---------------------------------\nAs Desktop+ is subject to User Interface Privilege Isolation (UIPI), it can't simulate input or move the cursor when a higher\nprivileged application (i.e. running as administrator) is in focus.\nDesktop+ offers multiple ways to deal with this, such as elevated mode or enabling UIAccess for the application.\nSee \"misc\\!About this folder.txt\" for details.\n\n\nKeyboard\n--------\nDesktop+ comes with a custom VR keyboard. The used keyboard layout and behavior can be configured in the \"Keyboard\" section of\nthe Settings window.\n\nKeys of the Desktop+ keyboard can be right-clicked to toggle their state. The application will not automatically release keys\nheld down this way while it's running, so keep that in mind.\nDesktop+ tries to map the VR keyboard keys to the keyboard layout chosen in the OS to maximize compatibility, before falling back\nto string inputs. It's recommended to select a keyboard layout in Desktop+ that matches the one in the OS.\n\nKeyboard layouts can be created or modified in the Keyboard Layout Editor. This editor can only be accessed in desktop mode.\nIn desktop mode, the editor can be found in the \"Keyboard\" section of the Settings window, after clicking on the Keyboard Layout\nbutton, [Switch to Keyboard Layout Editor] on the page that follows.\n\n\nTroubleshooting\n---------------\nDesktop+ runs as two processes (DesktopPlus.exe & DesktopPlusUI.exe), both of which write log files in the application's install\ndirectory (DesktopPlus.log & DesktopPlusUI.log).\n\nWhile most errors will be displayed in VR, it is helpful to check the contents of these log files when troubleshooting.\nMake sure to include them when seeking help as well.\n\nIn general, note that Desktop+ is using APIs which require Windows 8.1 or newer.\nUsing Graphics Capture overlays requires at least Windows 10 1903 for basic support, Windows 10 2004 or newer for full support,\nand Windows 11 to remove the yellow border around captures (only visible on real display).\nAdditionally, Windows 11 24H2 allows including secondary windows, such as context menus, in the capture.\n\n\nNo first-time setup message / Desktop+ not auto-launching (Steam version):\n-\nThe Steam version detects the need of first time setup by checking if an user configuration existed on launch.\nIf you previously had Desktop+ installed or the configuration was synced from another machine via Steam Cloud, you can enable\nauto-launch manually in the \"SteamVR\" section of the Settings window or use [Restore Default Settings] in the \"Troubleshooting\"\nsection to start fresh.\n\n\nBlack screen with question mark display icon instead of desktop mirror:\n-\nAn error occurred trying to duplicate the desktop. This may happen when displays were disconnected or are unavailable for another\nreason. You can try restarting Desktop+ or changing the capture method to Graphics Capture in the Overlay Properties window.\n\n\nShaky/Delayed laser pointer:\n-\nBy default, the laser pointed cursor may seem to lag behind a little bit, while other screen updates happen instantly.\nThis is in order to reduce the CPU load. Enable \"[x] Reduce Laser Pointer Latency\" in the \"Performance\" section of the Settings\nwindow to increase the accuracy of the laser pointer.\n\n\nHigh GPU load when overlay visible and cursor moving:\n-\nIn order to provide the lowest latency possible, all cursor updates are processed instantly, even if they occur more frequently\nthan the screen's vertical blanks.\nUsing the Frame Time limiter in \"Update Limiter Mode\" in the \"Performance\" section of either the Settings or Overlay Properties\nwindow with a low limit value can reduce the load from cursor movement while leaving other screen updates unaffected.\n\n\nUsing laser pointer after moving real mouse:\n-\nBy default the laser pointer will be deactivated after the physical mouse was moved. Click on the overlay to activate it again or\ndisable \"[x] Allow Laser Pointer Override\" in the \"Mouse\" section of the Settings window to turn this feature off.\n\n\nInput not working in certain applications:\n-\nInput simulated by Desktop+ is subject to User Interface Privilege Isolation (UIPI), see the Elevated Mode readme section for a\nworkaround.\n\n\nOverlay is no longer visible:\n-\nThere are several settings controlling overlay visibility and position. Check if they are not set to unexpected values.\nEspecially of interest are the cropping values. The cropping rectangle is preserved when switching between capture sources, but\nthat also means it could be invalid for the newly selected mirrored window or desktop.\nIf that's the case there will be a \"(!)\" warning next to the Cropping Rectangle section title. Simply reset it then.\n\nOculus/Meta headsets:\nIf all overlays, including the SteamVR dashboard disappear, this may be due to running a game that uses the headset's native APIs\nand as such bypasses SteamVR entirely.\nAs Desktop+ relies on SteamVR to function, the only solution to this is to find a way to run the game with SteamVR.\nPotential global workaround for this is using Steam Link for Meta Quest or enabling the \"Force Use SteamVR\" option available in \nOpenVR Advanced Settings.\nWorkarounds for some individual titles also exist, but are beyond the scope of this ReadMe.\n\n\nNo overlays visible on laptop:\n-\nOn laptops with hybrid-GPU solutions, the desktops are typically rendered on the power-saving integrated GPU. Make sure to have\nDesktopPlus.exe set to be running on integrated graphics so it can mirror them.\n\n\nNot all desktops from multiple GPUs available:\n-\nDesktop+ does not support Desktop Duplication with desktops distributed across multiple GPUs. It only supports copying one GPU's\nset of desktops over to the VR-rendering GPU when necessary.\nHowever, the missing desktops are typically still able to be mirrored by using Graphics Capture as the overlay's capture method.\n\n\nWarnings\n--------\nDesktop+ may display several warnings in the Settings window. They are mostly informational and can be safely ignored.\nClick on warnings to dismiss or not have them show up again.\n\n\"Compositor resolution is below 100%! This affects overlay rendering quality.\":\n-\nThe resolution of the VR compositor is based on the auto-resolution calculated by SteamVR, regardless of whether this resolution\nhas been chosen as the VR render resolution or not. There's no official way to change this. The auto-resolution can be increased\nby lowering the HMD's refresh rate or getting a faster GPU.\nUnofficially, there are tools such as SteamVR-ForceCompositorScale to combat this behavior.\n\n\n\"Overlay render quality is not set to high!\":\n-\nThe overlay render quality is a setting in SteamVR. It is recommended to set it to high to improve the visual clarity of the\noverlays.\n\n\n\"Desktop+ is running with administrative privileges!\":\n-\nThis message serves as a reminder about Desktop+ being elevated.\nUsing Desktop+'s elevated mode is recommended over running all of Desktop+ with administrative privileges.\n\n\n\"Elevated mode is active!\":\n-\nThis message serves as a reminder about Desktop+ being in elevated mode.\nPlease keep the security implications of that mode in mind and leave it once it's not needed anymore.\n\n\n\"The application profile for [application name] has overridden the current overlay layout. Changes made to overlays are not saved\nautomatically while it is active.\":\n-\nThis message serves as a reminder that an application profile is active. \nAutomatic saving of overlays is disabled while this is the case. Update the assigned overlay profile if you wish to make\npermanent changes that go with the application profile.\n\n\n\"An elevated process has focus! Desktop+ is unable to simulate input right now.\"\n-\nUser Interface Privilege Isolation (UIPI) prevents Desktop+ from simulating input when an elevated process has focus.\nThis warning persists until the a window with the same or lesser privilege level has gained focus again.\nClicking on this warning also offers the option to have Desktop+ try to open the task switcher to change the focus to another\nwindow or to enter elevated mode (only if the scheduled task is configured).\n\n\n\"An overlay creation failed!\":\n-\nThis message will typically appear with \"(Maximum Overlay limit exceeded)\" appended. It appears when the total overlay limit\nin SteamVR has been exceeded. This limit is not set by Desktop+ and other overlay applications can affect how many overlays can\nbe created by Desktop+.\nWhile the warning can be safely ignored, the overlays that were attempted to be created will be missing.\nIf this message appears with a different error status appended, it might be because of a bug in Desktop+ or SteamVR. Please\nreport it in that case.\n\n\n\"An unexpected error occurred in a Graphics Capture thread!\":\n-\nThis message appears when a Graphics Capture thread crashed. One or more overlays using Graphics Capture will not be updated\nanymore. While this shouldn't ever happen, this message can be safely ignored if it only appears once. The affected overlays will\nneed to have their source be set again from either changing it or reloading a profile.\n\n\n\"Desktop+ is no longer running with UIAccess privileges!\":\n-\nThis message appears when Desktop+ was previously configured to run with UIAccess privileges but no longer is.\nUIAccess is enabled by patching the DesktopPlus.exe executable and as such is not retained across application updates.\nSimply redo the process of enabling UIAccess to fix this.\n\n\n\"Browser overlays are being used, but the Desktop+ Browser component is currently not available.\":\n-\nBrowser overlays require the optional Desktop+ Browser application component.\nIt can be installed from https://github.com/elvissteinjr/DesktopPlusBrowser or Steam (as DLC for Desktop+).\n\n\n\"The installed Desktop+ Browser component is incompatible with this version of Desktop+!\":\n-\nThe installed version of Desktop+ Browser must be compatible with the running version of Desktop+. While not all updates require\na new version to be installed, it is generally a recommended to use the latest builds of both.\n\n\nLicense\n-------\nDesktop+ is licensed under the GPL 3.0.\n\nFor the third-party licenses, see third-party_licenses.txt."
  },
  {
    "path": "assets/third-party_licenses.txt",
    "content": "Desktop+ includes work of the following third-party projects:\n\nOpenVR, which is covered by the following license:\n-\nCopyright (c) 2015, Valve Corporation\nAll rights reserved.\n\nRedistribution and use in source and binary forms, with or without modification,\nare permitted provided that the following conditions are met:\n\n1. Redistributions of source code must retain the above copyright notice, this\nlist of conditions and the following disclaimer.\n\n2. Redistributions in binary form must reproduce the above copyright notice,\nthis list of conditions and the following disclaimer in the documentation and/or\nother materials provided with the distribution.\n\n3. Neither the name of the copyright holder nor the names of its contributors\nmay be used to endorse or promote products derived from this software without\nspecific prior written permission.\n\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\" AND\nANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED\nWARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE\nDISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR\nANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES\n(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;\nLOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON\nANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\nSOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n--------------------------------------------------------------------------------\n\nDear ImGui, which is covered by the following license:\n-\nThe MIT License (MIT)\n\nCopyright (c) 2014-2020 Omar Cornut\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n--------------------------------------------------------------------------------\n\nDXGI Desktop Duplication Sample, which is covered by the following license:\n-\nThe MIT License (MIT)\n\nCopyright (c) Microsoft Corporation\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\n of this software and associated documentation files (the \"Software\"), to deal\n in the Software without restriction, including without limitation the rights\n to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n copies of the Software, and to permit persons to whom the Software is\n furnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in\n all copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n THE SOFTWARE.\n\n--------------------------------------------------------------------------------\n\nWin32CaptureSample, which is covered by the following license:\n-\nMIT License\n\nCopyright (c) 2019 Robert Mikhayelyan\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n\n--------------------------------------------------------------------------------\n\nImPlot, which is covered by the following license:\n-\nMIT License\n\nCopyright (c) 2020 Evan Pezent\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n\n--------------------------------------------------------------------------------\n\nRadial Follow Smoothing (specifically RadialFollowCore.cs only), which is covered by the following license:\n-\nMozilla Public License, version 2.0\n\n1. Definitions\n\n  1.1. “Contributor” means each individual or legal entity that\n  creates, contributes to the creation of, or owns Covered Software.\n\n  1.2. “Contributor Version” means the combination of the\n  Contributions of others (if any) used by a Contributor and that\n  particular Contributor’s Contribution.\n\n  1.3. “Contribution” means Covered Software of a particular\n  Contributor.\n\n  1.4. “Covered Software” means Source Code Form to which the initial\n  Contributor has attached the notice in Exhibit A, the Executable\n  Form of such Source Code Form, and Modifications of such Source Code\n  Form, in each case including portions thereof.\n\n  1.5. “Incompatible With Secondary Licenses” means\n\n  that the initial Contributor has attached the notice described in\n  Exhibit B to the Covered Software; or\n\n  that the Covered Software was made available under the terms of\n  version 1.1 or earlier of the License, but not also under the terms\n  of a Secondary License.\n\n  1.6. “Executable Form” means any form of the work other than Source\n  Code Form.\n\n  1.7. “Larger Work” means a work that combines Covered Software with\n  other material, in a separate file or files, that is not Covered\n  Software.\n\n  1.8. “License” means this document.\n\n  1.9. “Licensable” means having the right to grant, to the maximum\n  extent possible, whether at the time of the initial grant or\n  subsequently, any and all of the rights conveyed by this License.\n\n  1.10. “Modifications” means any of the following:\n\n  any file in Source Code Form that results from an addition to,\n  deletion from, or modification of the contents of Covered Software;\n  or\n\n  any new file in Source Code Form that contains any Covered Software.\n\n  1.11. “Patent Claims” of a Contributor means any patent claim(s),\n  including without limitation, method, process, and apparatus claims,\n  in any patent Licensable by such Contributor that would be\n  infringed, but for the grant of the License, by the making, using,\n  selling, offering for sale, having made, import, or transfer of\n  either its Contributions or its Contributor Version.\n\n  1.12. “Secondary License” means either the GNU General Public\n  License, Version 2.0, the GNU Lesser General Public License, Version\n  2.1, the GNU Affero General Public License, Version 3.0, or any\n  later versions of those licenses.\n\n  1.13. “Source Code Form” means the form of the work preferred for\n  making modifications.\n\n  1.14. “You” (or “Your”) means an individual or a legal entity\n  exercising rights under this License. For legal entities, “You”\n  includes any entity that controls, is controlled by, or is under\n  common control with You. For purposes of this definition, “control”\n  means (a) the power, direct or indirect, to cause the direction or\n  management of such entity, whether by contract or otherwise, or (b)\n  ownership of more than fifty percent (50%) of the outstanding shares\n  or beneficial ownership of such entity.\n\n\n2. License Grants and Conditions\n\n  2.1. Grants Each Contributor hereby grants You a world-wide,\n  royalty-free, non-exclusive license:\n\n  under intellectual property rights (other than patent or trademark)\n  Licensable by such Contributor to use, reproduce, make available,\n  modify, display, perform, distribute, and otherwise exploit its\n  Contributions, either on an unmodified basis, with Modifications, or\n  as part of a Larger Work; and\n\n  under Patent Claims of such Contributor to make, use, sell, offer\n  for sale, have made, import, and otherwise transfer either its\n  Contributions or its Contributor Version.\n\n  2.2. Effective Date The licenses granted in Section 2.1 with respect\n  to any Contribution become effective for each Contribution on the\n  date the Contributor first distributes such Contribution.\n\n  2.3. Limitations on Grant Scope The licenses granted in this Section\n  2 are the only rights granted under this License. No additional\n  rights or licenses will be implied from the distribution or\n  licensing of Covered Software under this License. Notwithstanding\n  Section 2.1(b) above, no patent license is granted by a Contributor:\n\n  for any code that a Contributor has removed from Covered Software;\n  or\n\n  for infringements caused by: (i) Your and any other third party’s\n  modifications of Covered Software, or (ii) the combination of its\n  Contributions with other software (except as part of its Contributor\n  Version); or\n\n  under Patent Claims infringed by Covered Software in the absence of\n  its Contributions.\n\n  This License does not grant any rights in the trademarks, service\n  marks, or logos of any Contributor (except as may be necessary to\n  comply with the notice requirements in Section 3.4).\n\n  2.4. Subsequent Licenses No Contributor makes additional grants as a\n  result of Your choice to distribute the Covered Software under a\n  subsequent version of this License (see Section 10.2) or under the\n  terms of a Secondary License (if permitted under the terms of\n  Section 3.3).\n\n  2.5. Representation Each Contributor represents that the Contributor\n  believes its Contributions are its original creation(s) or it has\n  sufficient rights to grant the rights to its Contributions conveyed\n  by this License.\n\n  2.6. Fair Use This License is not intended to limit any rights You\n  have under applicable copyright doctrines of fair use, fair dealing,\n  or other equivalents.\n\n  2.7. Conditions Sections 3.1, 3.2, 3.3, and 3.4 are conditions of\n  the licenses granted in Section 2.1.\n\n\n3. Responsibilities\n\n  3.1. Distribution of Source Form All distribution of Covered\n  Software in Source Code Form, including any Modifications that You\n  create or to which You contribute, must be under the terms of this\n  License. You must inform recipients that the Source Code Form of the\n  Covered Software is governed by the terms of this License, and how\n  they can obtain a copy of this License. You may not attempt to alter\n  or restrict the recipients’ rights in the Source Code Form.\n\n  3.2. Distribution of Executable Form If You distribute Covered\n  Software in Executable Form then:\n\n  such Covered Software must also be made available in Source Code\n  Form, as described in Section 3.1, and You must inform recipients of\n  the Executable Form how they can obtain a copy of such Source Code\n  Form by reasonable means in a timely manner, at a charge no more\n  than the cost of distribution to the recipient; and\n\n  You may distribute such Executable Form under the terms of this\n  License, or sublicense it under different terms, provided that the\n  license for the Executable Form does not attempt to limit or alter\n  the recipients’ rights in the Source Code Form under this License.\n\n  3.3. Distribution of a Larger Work You may create and distribute a\n  Larger Work under terms of Your choice, provided that You also\n  comply with the requirements of this License for the Covered\n  Software. If the Larger Work is a combination of Covered Software\n  with a work governed by one or more Secondary Licenses, and the\n  Covered Software is not Incompatible With Secondary Licenses, this\n  License permits You to additionally distribute such Covered Software\n  under the terms of such Secondary License(s), so that the recipient\n  of the Larger Work may, at their option, further distribute the\n  Covered Software under the terms of either this License or such\n  Secondary License(s).\n\n  3.4. Notices You may not remove or alter the substance of any\n  license notices (including copyright notices, patent notices,\n  disclaimers of warranty, or limitations of liability) contained\n  within the Source Code Form of the Covered Software, except that You\n  may alter any license notices to the extent required to remedy known\n  factual inaccuracies.\n\n  3.5. Application of Additional Terms You may choose to offer, and to\n  charge a fee for, warranty, support, indemnity or liability\n  obligations to one or more recipients of Covered Software. However,\n  You may do so only on Your own behalf, and not on behalf of any\n  Contributor. You must make it absolutely clear that any such\n  warranty, support, indemnity, or liability obligation is offered by\n  You alone, and You hereby agree to indemnify every Contributor for\n  any liability incurred by such Contributor as a result of warranty,\n  support, indemnity or liability terms You offer. You may include\n  additional disclaimers of warranty and limitations of liability\n  specific to any jurisdiction.\n\n\n4. Inability to Comply Due to Statute or Regulation\n\n  If it is impossible for You to comply with any of the terms of this\n  License with respect to some or all of the Covered Software due to\n  statute, judicial order, or regulation then You must: (a) comply\n  with the terms of this License to the maximum extent possible; and\n  (b) describe the limitations and the code they affect. Such\n  description must be placed in a text file included with all\n  distributions of the Covered Software under this License. Except to\n  the extent prohibited by statute or regulation, such description\n  must be sufficiently detailed for a recipient of ordinary skill to\n  be able to understand it.\n\n\n5. Termination\n\n  5.1. The rights granted under this License will terminate\n  automatically if You fail to comply with any of its terms. However,\n  if You become compliant, then the rights granted under this License\n  from a particular Contributor are reinstated (a) provisionally,\n  unless and until such Contributor explicitly and finally terminates\n  Your grants, and (b) on an ongoing basis, if such Contributor fails\n  to notify You of the non-compliance by some reasonable means prior\n  to 60 days after You have come back into compliance. Moreover, Your\n  grants from a particular Contributor are reinstated on an ongoing\n  basis if such Contributor notifies You of the non-compliance by some\n  reasonable means, this is the first time You have received notice of\n  non-compliance with this License from such Contributor, and You\n  become compliant prior to 30 days after Your receipt of the notice.\n\n  5.2. If You initiate litigation against any entity by asserting a\n  patent infringement claim (excluding declaratory judgment actions,\n  counter-claims, and cross-claims) alleging that a Contributor\n  Version directly or indirectly infringes any patent, then the rights\n  granted to You by any and all Contributors for the Covered Software\n  under Section 2.1 of this License shall terminate.\n\n  5.3. In the event of termination under Sections 5.1 or 5.2 above,\n  all end user license agreements (excluding distributors and\n  resellers) which have been validly granted by You or Your\n  distributors under this License prior to termination shall survive\n  termination.\n\n6. Disclaimer of Warranty   Covered Software is provided under this\nLicense on an “as is” basis, without warranty of any kind, either\nexpressed, implied, or statutory, including, without limitation,\nwarranties that the Covered Software is free of defects, merchantable,\nfit for a particular purpose or non-infringing. The entire risk as to\nthe quality and performance of the Covered Software is with You.\nShould any Covered Software prove defective in any respect, You (not\nany Contributor) assume the cost of any necessary servicing, repair,\nor correction. This disclaimer of warranty constitutes an essential\npart of this License. No use of any Covered Software is authorized\nunder this License except under this disclaimer.\n\n7. Limitation of Liability   Under no circumstances and under no legal\ntheory, whether tort (including negligence), contract, or otherwise,\nshall any Contributor, or anyone who distributes Covered Software as\npermitted above, be liable to You for any direct, indirect, special,\nincidental, or consequential damages of any character including,\nwithout limitation, damages for lost profits, loss of goodwill, work\nstoppage, computer failure or malfunction, or any and all other\ncommercial damages or losses, even if such party shall have been\ninformed of the possibility of such damages. This limitation of\nliability shall not apply to liability for death or personal injury\nresulting from such party’s negligence to the extent applicable law\nprohibits such limitation. Some jurisdictions do not allow the\nexclusion or limitation of incidental or consequential damages, so\nthis exclusion and limitation may not apply to You.\n\n8. Litigation   Any litigation relating to this License may be brought\nonly in the courts of a jurisdiction where the defendant maintains its\nprincipal place of business and such litigation shall be governed by\nlaws of that jurisdiction, without reference to its conflict-of-law\nprovisions. Nothing in this Section shall prevent a party’s ability to\nbring cross-claims or counter-claims.\n\n9. Miscellaneous   This License represents the complete agreement\nconcerning the subject matter hereof. If any provision of this License\nis held to be unenforceable, such provision shall be reformed only to\nthe extent necessary to make it enforceable. Any law or regulation\nwhich provides that the language of a contract shall be construed\nagainst the drafter shall not be used to construe this License against\na Contributor.\n\n10. Versions of the License\n\n  10.1. New Versions Mozilla Foundation is the license steward. Except\n  as provided in Section 10.3, no one other than the license steward\n  has the right to modify or publish new versions of this License.\n  Each version will be given a distinguishing version number.\n\n  10.2. Effect of New Versions You may distribute the Covered Software\n  under the terms of the version of the License under which You\n  originally received the Covered Software, or under the terms of any\n  subsequent version published by the license steward.\n\n  10.3. Modified Versions If you create software not governed by this\n  License, and you want to create a new license for such software, you\n  may create and use a modified version of this License if you rename\n  the license and remove any references to the name of the license\n  steward (except to note that such modified license differs from this\n  License).\n\n  10.4. Distributing Source Code Form that is Incompatible With\n  Secondary Licenses If You choose to distribute Source Code Form that\n  is Incompatible With Secondary Licenses under the terms of this\n  version of the License, the notice described in Exhibit B of this\n  License must be attached.\n\nExhibit A - Source Code Form License Notice\n\n  This Source Code Form is subject to the terms of the Mozilla Public\n  License, v. 2.0. If a copy of the MPL was not distributed with this\n  file, You can obtain one at http://mozilla.org/MPL/2.0/.\n\n  If it is not possible or desirable to put the notice in a particular\n  file, then You may include the notice in a location (such as a\n  LICENSE file in a relevant directory) where a recipient would be\n  likely to look for such a notice.\n\n  You may add additional accurate notices of copyright ownership.\n\nExhibit B - “Incompatible With Secondary Licenses” Notice\n\n  This Source Code Form is “Incompatible With Secondary Licenses”, as\n  defined by the Mozilla Public License, v. 2.0.\n\n--------------------------------------------------------------------------------\n"
  },
  {
    "path": "docs/user_guide.md",
    "content": "# Desktop+ User Guide\n\nThis document explains the options in Desktop+ and possible usage scenarios in greater detail. It is generally not required to be read in order to get started with Desktop+, but can serve as a reference if one does get stuck.  \nThis guide does not touch upon all topics covered by the [readme file](../assets/readme.txt). It's recommended to read it beforehand.\n\n# Contents\n\n* [Glossary](#glossary)\n* [Interface](#interface)\n  * [Overlay Bar](#overlay-bar)\n\n  * [Floating UI](#floating-ui)\n    * [Main Bar](#main-bar)\n    * [Action Bar](#action-bar)\n\n  * [Settings Window](#settings-window)\n    * [Main Page](#main-page)\n      * [Interface](#interface)\n      * [Environment](#environment)\n      * [Profiles](#profiles)\n      * [Actions](#actions)\n      * [Keyboard](#keyboard)\n      * [Mouse](#mouse)\n      * [Laser Pointer](#laser-pointer)\n      * [Window Overlays](#window-overlays)\n      * [Browser](#browser)\n      * [Performance](#performance)\n      * [Version Info](#version-info)\n      * [Warnings](#warnings)\n      * [Startup](#startup)\n      * [Troubleshooting](#troubleshooting)\n    * [Persistent UI Page](#persistent-ui-page)\n      * [Windows](#windows)\n    * [Manage Overlay Profiles Page](#manage-overlay-profiles-page)\n    * [Manage Application Profiles Page](#manage-application-profiles-page)\n    * [Manage Actions Page](#manage-actions-page)\n    * [Edit Action Page](#manage-actions-page)\n      * [Button Appearance](#button-appearance)\n      * [Commands](#commands)\n    * [Change Action Order Page](#change-action-order-page)\n    * [Keyboard Layout Page](#keyboard-layout-page)\n      * [Key Clusters](#key-clusters)\n    * [Restore Default Settings Page](#restore-default-settings-page)\n\n  * [Overlay Properties](#overlay-properties)\n    * [Main Page](#main-page-1)\n      * [Position](#position)\n      * [Appearance](#appearance)\n      * [Capture](#capture)\n      * [Performance Monitor](#performance-monitor)\n      * [Browser](#browser-1)\n      * [Advanced](#advanced)\n      * [Performance](#performance-1)\n      * [Interface](#interface-2)\n    * [Change Overlay Position Page](#change-overlay-position-page)\n      * [Manual Adjustment](#manual-adjustment)\n      * [Additional Offset](#additional-offset)\n      * [Drag Settings](#drag-settings)\n    * [Cropping Area Page](#cropping-area-page)\n      * [Manual Adjustment](#manual-adjustment-1)\n\n  * [Desktop Mode](#desktop-mode)\n    * [Tools](#tools)\n    * [Overlays](#overlays)\n    * [Keyboard Controls](#keyboard-controls)\n\n  * [Desktop+ Keyboard](#desktop-keyboard)\n    * [Keyboard Layout Editor](#keyboard-layout-editor)\n      * [Key List](#key-list)\n      * [Key Properties](#key-properties)\n      * [Layout Metadata](#layout-metadata)\n      * [Keyboard Preview](#keyboard-preview)\n\n* [Performance Considerations](#performance-considerations)\n  * [Desktop Duplication](#desktop-duplication-1)\n  * [Graphics Capture](#graphics-capture-1)\n  * [Desktop Duplication vs. Graphics Capture](#desktop-duplication-vs-graphics-capture)\n    * [Desktop Duplication](#desktop-duplication-2)\n      * [Mirroring at Unconstrained Frame-Rates](#mirroring-at-unconstrained-frame-rates)\n    * [Graphics Capture](#graphics-capture-2)\n      * [Graphics Capture Feature Support](#graphics-capture-feature-support)\n\n* [VR Interactions](#vr-interactions)\n\n* [Usage Examples](#usage-examples)\n  * [Attach Overlay to a Motion-Controller, Wristwatch-Style](#attach-overlay-to-a-motion-controller-wristwatch-style)\n  * [Show Multiple Desktops or Windows in the Dashboard at Once](#show-multiple-desktops-or-windows-in-the-dashboard-at-once)\n  * [Simulate Keyboard Shortcut from Motion-Controller Input](#simulate-keyboard-shortcut-from-motion-controller-input)\n\n* [Advanced Features](#advanced-features)\n  * [Hidden Configuration Settings](#hidden-configuration-settings)\n  * [System-wide Modifications](#system-wide-modifications)\n  * [Command-Line Arguments](#command-line-arguments)\n    * [Desktop+ Browser](#desktop-browser)\n\n\n# Glossary\n\nThese are some of the terms used across the application and its documentation.\n\n### Overlay\n\nAn overlay in the context of SteamVR refers to a 2D surface displayed either in the SteamVR dashboard or somewhere in the VR space.  \nIn Desktop+, \"overlay\" usually only refers to overlays that belong to the application, unless it's explicitly spelled out as \"SteamVR overlay\".\n\n### Dashboard\n\nDashboard in the context of SteamVR refers to the menu that appears when using the system menu button in SteamVR.\n\n### Floating UI\n\nAn interface fading in when hovering an overlay, consisting of a Main Bar with quick access to disabling the overlay, toggling drag-mode, overlay-specific buttons and showing an additional Action Bar with customizable buttons.\n\n### Action\n\nAction in the context of Desktop+ refers to a series of commands that can control the ,state of your overlays simulate input and execute programs.\nActions can be user-created and bound to controller buttons, the Floating UI, or just executed from the action list.\n\n### Desktop Duplication\n\nOne of the capture methods available for capturing desktops for overlay display. Its name comes from the DXGI Desktop Duplication API it is using.\n\n### Graphics Capture\n\nOne of the capture methods available for capturing desktops and windows for overlay display. Its name comes from the Graphics Capture API it's using.\n\n### Gaze Fade\n\nA method to automatically adjust an overlay's opacity based on the user's gaze. The method in Desktop+ is distance-based, as opposed to other angle-based approaches.\n\n### Browser Overlay\n\nOverlays using the Desktop+ Browser component, a Chromium Embedded Framework-based internal web browser.  \nThis application component is optional and has to be installed separately, either from https://github.com/elvissteinjr/DesktopPlusBrowser or Steam (as DLC for Desktop+).\nOptions related to browser overlays are hidden if the application component is not installed.\n\n### Primary Dashboard Overlay\n\nThe first visible overlay with the dashboard origin. It is used a reference or fallback in some scenarios, but isn't strictly required to exist.\n\n# Interface\n\n## Overlay Bar\n\n![Overlay Bar, Below the SteamVR Dashboard](images/overlay_bar.png)\n\nThe Overlay Bar appears in the Desktop+ dashboard tab and allows managing all active overlays, as well as accessing the settings window.\n\n- **(Overlay Buttons)**:  \nClick on the button to pop up a menu with options to show/hide, duplicate, remove the overlay, or to change the overlay's properties.  \nThe Overlay Properties window can only be shown for one overlay at once. Trying to show it for another will make the existing window switch to that overlay.  \nThe overlay buttons can also be double-clicked to toggle visibility, right-clicked to quickly access the overlay properties, and long-pressed for a few seconds to reset the Overlay Properties window position.\n\nPointing at the SteamVR dashboard will result in the Overlay Bar to fade out temporarily. This is to allow using the dashboard's drag handle, as well as to make space for elements of the dashboard that may slide out on interaction.\n\n- **[+]**:  \nAdds a new overlay. A menu will pop up, listing the available overlay types/capture sources.  \nAfter choosing one, keep holding the trigger to drag the newly created overlay to the desired spot and then let it go. If the trigger was released immediately, it requires an additional click to let the overlay go.\n\n- **[⛭]**:  \nShows or hides the settings window.\n\n## Floating UI\n\n![Floating UI, Below the Hovered Desktop+ Overlay](images/floating_ui.png)\n\nThe Floating UI appears when pointing at an overlay and consists of a Main bar allow quick access to overlay functions, as well as an additional Action Bar with customizable buttons.  \nThe primary dashboard overlay always displays its Floating UI as long as no other overlay is being hovered.\n\n### Main Bar\n\n- **Toggle Action Bar ([...])**:  \nToggles visibility of the Action Bar. It appears right above the Main Bar.\n\n- **Enable/Disable Drag-Mode ([Arrows])**:  \nToggles the overlay drag-mode. While drag-mode is enabled, all laser pointer input on drag overlays around instead.  \nDuring drags, use vertical scroll input to change the distance to the overlay and horizontal scroll input to resize it.\nRight-click on overlays to perform a two-handed gesture drag instead, which allows to rotate and scale the overlay in place.  \n\n  This button can be long-pressed for a few seconds to lock the overlay's position instead.\n\n- **Hide Overlay ([X])**:  \nHides the overlay. This is the same as pressing \"Hide\" in the Overlay Bar.  \nThis button can be long-pressed for a few seconds to remove the overlay instead.\n\n- **Add Active Window as Overlay ([Window with Arrow])** (only visible for desktop overlays):  \nPress and hold this button to add a new overlay of the currently focused window. This is similar to adding a new overlay in the Overlay Bar.  \nThe new overlay will start as being dragged, allowing you to position it.\n\n  This button can be hidden via the [\"[x] Show Extra Buttons\"](#interface-2) overlay property.\n\n- **Reset Cumulative Values ([Performance Monitor Refresh Icon])** (only visible for Performance Monitor overlays):  \nResets most values of the Performance Monitor. This includes everything that represent an average or is counted up over time.\n\n  This button can be hidden via the [\"[x] Show Extra Buttons\"](#interface-2) overlay property.\n\n- **Go to Previous Page ([🡸])** (only visible for browser overlays):  \nGoes back in the browser's navigation history.\n\n  This button can be hidden via the [\"[x] Show Extra Buttons\"](#interface-2) overlay property.\n  \n- **Go to Next Page ([🡺])** (only visible for browser overlays):  \nGoes forward in the browser's navigation history.\n\n  This button can be hidden via the [\"[x] Show Extra Buttons\"](#interface-2) overlay property.\n  \n- **Refresh Current Page/Stop Page Load ([⟳/🚫])** (only visible for browser overlays):  \nRefreshes/reloads the current page or stops if it's currently loading.\n\n  This button can be hidden via the [\"[x] Show Extra Buttons\"](#interface-2) overlay property.\n  \n### Action Bar\n\nThe Action Bar contains a set of customizable action buttons which can be set either globally or for individual overlays.  \nPressing on action buttons will execute the associated action. Actions pressing keys will hold them as long as the button is held and release them once the button is released.\n\nThe *Show Keyboard* action button can be long-pressed for a few seconds to reset the [VR Keyboard's](#desktop-keyboard) position.\n\n## Settings Window\n\n### Main Page\n\n#### Interface\n\n![Settings Window, Main Page, Interface Section](images/settings_interface.png)\n\n- **Language**:  \nChange the application language. On first launch, Desktop+ tries to set this to the operating system's language if a matching translation is available.  \nSome of the languages listed are community translations, which may lag behind after updates and be incomplete.\n\n- **[x] Show Advanced Settings**:  \nSets if settings marked as advanced are being shown. Advanced settings are rarely-used settings and more in-depth options for other, always displayed settings.  \nWhen unticked, relevant settings are only hidden in the interface, not turned off.  \nAdvanced settings are marked with *(Adv.)* in this document.\n\n- **[x] Drag Windows When Clicking on Blank Space** (Adv.):  \nSets if Desktop+ interface windows can be dragged by pressing and holding anywhere on the window as long as there's no widget in the way.  \nWhen unticked, interface windows can only be dragged from the title bar.\n\n- **Persistent UI** (Adv.):  \nPress the button to manage the [Persistent UI settings](#persistent-ui-page).\n\n- **Desktop Buttons Listing Style**:  \nChanges the way the desktops are listed on the Action Bar or if at all.\n  - **[x] Add Combined Desktop**:  \n  Adds a button for the combined desktop to the Action Bar.\n\n#### Environment\n\n![Settings Window, Main Page, Environment Section](images/settings_environment.png)\n\n- **Background Color**:  \nAllows to partially or fully cover the VR scene in the specified color, either only while looking at the Desktop+ dashboard tab or at all times.  \nClick on the color button to switch to the color picker page and click on the drop-down list to choose when the background color appears.\n\n  On the color picker page, click on the \"Original\" preview box to restore the previous value. Right click the picker to switch between hue bar and hue wheel style pickers.\n  \n- **Dim Interface**:  \nDims the SteamVR dashboard and Desktop+ UI while the Desktop+ dashboard tab is open.\n\n#### Profiles\n\n![Settings Window, Main Page, Profiles Section](images/settings_profiles.png)\n\n- **Overlay Profiles**:  \nPress the button to manage overlay profiles. Overlay profiles allow saving and loading multiple overlay setups.\n- **Application Profiles**:  \nPress the button to manage application profiles. Application profiles allow to automatically load an overlay profile or execute actions when a SteamVR application is launched.\n  \n#### Actions\n\n![Settings Window, Main Page, Actions Section](images/settings_actions.png)\n\n- **Actions**:  \nPress [Manage] to go to the [Manage Actions page](#manage-actions-page).\n\n- **Action Buttons (Default)**:  \nPress [X Actions Selected] to change the default order of action buttons displayed in the Floating UI.\n\n- **Action Buttons (Overlay Bar)** (Adv.):  \nPress [X Actions Selected] to change the order of action buttons displayed in the Overlay Bar.\n\n- **Active Controller Buttons**  \nController bindings when pointing at a Desktop+ overlay. The actual buttons used for these are defined in the VR Dashboard SteamVR input bindings.\n  - **[Show Controller Bindings]** (only visible when SteamVR is running):  \n  Open the SteamVR Controller Bindings screen for the VR Dashboard.\n  - **(Table)**:  \n  Action triggered when pressing the \"Go Home\" or \"Go Back\" button. This is typically one of the face buttons on the controller by default. Click one of the Action column cells to change the action.\n  \n- **Global Controller Buttons**  \nController bindings when the dashboard is closed and not pointing at any overlay. The buttons used for these are defined in the Desktop+ SteamVR input bindings. These bindings will trigger globally as long as no laser pointer is active.\n  - **[Show Controller Bindings]** (only visible when SteamVR is running):  \n  Open the SteamVR Controller Bindings screen for Desktop+.\n  - **(Table)**:  \n  Action triggered when pressing the respective bound global shortcut input. Click one of the Action column cells to change the action.\n  - **[Add Shortcut]**:  \n  Add another shortcut. Up to 20 shortcuts can be added.\n  \n- **Hotkeys**:  \nSystem-wide keyboard shortcuts. Hotkeys block other applications from receiving that input and may not work if the same combination has already been registered elsewhere.  \nDesktop+ will not register a hotkey if no action is assigned to it. Most hotkeys without modifiers will not work while elevated applications have window focus.\n  - **(Hotkey Column)**:  \n  Keyboard shortcut that triggers the action. Click on the cell to change the hotkey.\n  - **(Action Column)**:  \n  Action triggered when pressing the respective hotkey. Click on the cell to change the action.\n  - **[Remove]**:  \n  Remove the hotkey in the hovered row.\n  - **[Add Hotkey]**:  \n  Add another hotkey. There's no upper limit on hotkeys.\n  \n#### Keyboard\n\n![Settings Window, Main Page, Keyboard Section](images/settings_keyboard.png)\n\n- **Keyboard Layout**:  \nPress the button to change the keyboard layout of the Desktop+ keyboard. In desktop mode, the Keyboard Layout Editor can be accessed from there as well.\n\n- **Size**:  \nSize of the Desktop+ keyboard. This slider adjusts the size for both inside and outside of the dashboard. Manage [Persistent UI settings](#persistent-ui-page) or resize the keyboard while dragging to adjust them individually.\n- **Behavior**:  \n  - **[x] Sticky Modifier Keys**:  \n  Sets if the modifier keys (ctrl, alt, shift, win) stay pressed until another non-modifier key is pressed.\n  - **[x] Key Repeat**:  \n  Sets if the key inputs are repeated when held down. Some keys do not repeat even with this enabled in order to mimic real keyboard behavior.\n\n- **Show Automatically**:  \nSets if the Desktop+ keyboard appears automatically when focusing a text input field and disappears again when unfocusing (unless it was manually brought up before).\n\n  This behavior is experimental for desktop and window overlays as there's no reliable way to detect all input fields.  \n  It works reasonably well for applications using native controls or sending the correct assistive events.\n\n  The keyboard is always shown automatically for Desktop+ UI elements.\n  - **[x] For Desktop & Window Overlays**:  \n  Sets if the keyboard is shown automatically for desktop & window overlays.\n  - **[x] For Browser Overlays** (only available when Desktop+ Browser is installed):  \n  Sets if the keyboard is shown automatically for browser overlays.\n  \n  \n#### Mouse\n\n![Settings Window, Main Page, Mouse Section](images/settings_mouse.png)\n\n- **[x] Show Cursor**:  \nSets if the cursor is rendered on top of the mirrored overlay. Graphics Capture overlays hide the cursor automatically when the source window is not in focus.\n\n- **[x] Use Smooth Scrolling**:  \nSets if smooth scrolling is used instead of discrete mouse wheel-like scrolling. Smooth scrolling also allows for horizontal and minute scrolling in applications that support it.\n\n- **[x] Simulate as Pen Input**:  \nSets if laser pointer input simulate a touch pen input device instead of a mouse.\nThis enables gestures meant for pen inputs, such as click drag scrolling in many applications and holding down left-click to perform a right click.\n\n  Some of the pen input behaviors are configurable on the OS-end, and may need to be enabled first.\n  \n- **[x] Allow Laser Pointer Override**:  \nDisables the laser pointer when the physical mouse is moved rapidly. Click the overlay to get the laser pointer back.  \nThis option is primarily useful when sitting in front of the desk using the real mouse and keyboard while wearing the headset to view the desktop.\n\n- **Double-Click Assistant**:  \nFreezes the mouse cursor for the set duration to ease the input of double-clicks. The \"Auto\" setting uses the double-click duration configured in Windows.  \nMoving larger distances from the frozen cursor position cancels the Double-Click Assistant.\n\n- **Input Smoothing**:  \nSets the level of smoothing/stabilization applied to mouse inputs.\n\n#### Laser Pointer\n\n![Settings Window, Main Page, Laser Pointer Section](images/settings_laser_pointer.png)\n\nThese settings apply to Desktop+'s laser pointer only.\n\n- **[x] Block Game Input while Active**:  \nSets if the laser pointer being active blocks the game from receiving inputs. Even if this is disabled, inputs bound for the laser pointer still take priority over the game's controller bindings and aren't sent to the game.\n\n- **Auto-Activation Max Distance**:  \nMaximum allowed distance between overlay and pointing controller to automatically toggle interaction mode while the dashboard is closed. This can be set to \"Off\" by dragging the slider into the left-most position.\n\n- **[x] Gaze-based HMD-Pointer** (Adv.):  \nEnables using HMD gaze to point at Desktop+ overlays. The Auto-Activation Max Distance value is used here as well, if no keyboard key is set for the toggle input action.\n  - **(Table)**:  \n  Input action triggered when pressing the respective keyboard key. Click one of the Keyboard Key column cells to change the keyboard key. The keyboard keys are optional can stay unset for gaze-only input.\n\n#### Window Overlays\n\n![Settings Window, Main Page, Window Overlays Section](images/settings_window_overlays.png)\n\n- **[x] Focus Game on Dashboard Deactivation** (Adv.):  \nTries to change the foreground window to the current VR game after the dashboard was closed. Some games may pause or decide to not process inputs when not being in focus on the desktop.  \n  Note that some applications such as SteamVR Home do not have a desktop window at all.\n  \n- **[x] Focus Window when Pointing at Overlay** (Adv.):  \nTries to change the foreground window to the mirrored window when pointing at the overlay. This generally recommended to be left enabled in order to prevent unintended inputs on other windows.  \nNote that always-on-top windows will still be in front of the mirrored window and could possibly take the mouse input instead unless the mirrored window is always-on-top as well.\n\n- **[x] Keep Window on Screen** (Adv.):  \nPrevents the mirrored window from being outside of the nearest desktop's work area (the space a maximized window takes up). The window is not resized if it does not fit into this space.\n\n- **[x] Adjust Overlay Size when Window Resizes**:  \nAutomatically adjusts the overlay size when the mirrored window's size changes. This directly affects the overlay's width property.\n\n- **[x] Focus Game when Laser Pointer leaves Overlay**:  \nTries to change the foreground window to the current VR game when the laser pointer leaves a Graphics Capture window overlay.\n\n- **On Window Drag** (Adv.):  \nChanges what happens when a window is being dragged. The selected option is only triggered on window drags initiated by Desktop+.\n\n- **On Capture Loss** (Adv.):  \nChanges what happens when a window capture is being lost. This usually happens after the target window was closed, but will trigger on any kind of capture loss.  \nThe \"Hide Overlay\" option also automatically shows the overlay again when the capture gets restored.\n\n#### Browser\n\n![Settings Window, Main Page, Browser Section](images/settings_browser.png)\n\n*This is only available if the Desktop+ Browser application component is installed*\n\n- **Maximum Frame Rate**:  \nThe maximum amount of frames per second browsers overlays will render at. Browser overlays render at variable frame-rates and will not update the overlay if there are no changes.\n\n- **[x] Content Blocker**:  \nSets if the content blocker is enabled. The content blocker built into Desktop+ Browser takes block lists in the AdblockPlus syntax, but doesn't implement cosmetic filters.  \nDesktop+ does not come with any block lists by default. They can be added by placing lists files in \"DesktopPlusBrowser\\content_block\". All lists present in the directory will be loaded and used.\n\n#### Performance\n\n![Settings Window, Main Page, Performance Section](images/settings_performance.png)\n\nThese settings allow tweaking the performance characteristics of Desktop+. However, changing any of them should not be required for good results. \n\n- **Update Limiter**\n  - **Limiter Mode** (Adv.):  \n  Sets the global limiter mode. May be overridden by overlay-specific update limiter settings.\n  - **(Limit Slider)**:  \n  Sets the limit in either ms or frames per second.\n\n- **Desktop Duplication** (Adv.)\n  - **[x] Reduce Laser Pointer Latency**:  \n  When this is active, the cursor position is updated instantly while pointing at the overlay. This causes high CPU load, but makes the laser pointer more responsive.\n  - **[x] Single Desktop Mirroring**:  \n  Mirrors individual desktops instead of cropping from the combined desktop. All Desktop Duplication overlays will be showing the same desktop when this is active.  \n  There can be a noticeable performance impact when multiple desktops update constantly. This setting can be used to mitigate it.\n  - **[x] Alternative Cursor Rendering**:  \n  Enables use of alternative methods to retrieve the software cursor image, instead of relying on the cursor returned by the Desktop Duplication API.  \n  This rendering method is slightly slower than the regular one and doesn't support cursor animation. It is intended for users experiencing issues with the hardware cursor capture using Desktop Duplication.  \n  If other means to retrieve the current cursor fail, it may additionally fall back to using the default cursor image or a built-in alternative.\n\n- **[x] HDR Mirroring**  \n  Enables mirroring of desktops and windows using higher bit-depth textures, supporting HDR output.  \n  While the full bit-depth is passed to SteamVR, there are no known HMDs capable of making use of HDR output.  \n  The primary use-case for this setting is to fix SDR content not being captured correctly while HDR is enabled in the OS.  \n  This setting is still considered experimental.\n- **[x] Show FPS in Floating UI**  \n  When this is active, the number of captured or rendered frames per second, at which the overlay is updated, is displayed in the Floating UI.  \n  This number will be the same for all Desktop Duplication overlays, since they share a single backing texture.\n\n#### Version Info\n\n![Settings Window, Main Page, Version Info Section](images/settings_version_info.png)\n\nDisplays version information of the running build of Desktop+.\n\n#### Warnings\n\n![Settings Window, Main Page, Warnings Section](images/settings_warnings.png)\n\n- **Warnings Hidden**:  \nShows how many warnings have been hidden by clicking on \"Don't show this again\".\n- **[Reset Hidden Warnings]**:  \nResets all hidden warnings, causing them to show up again when their condition is fulfilled.\n\n#### Startup\n\n![Settings Window, Main Page, Startup Section](images/settings_startup.png)\n\n- **[x] Launch Automatically on SteamVR Startup**:  \nSets if Desktop+ is launched automatically alongside SteamVR. This setting controls the SteamVR application configuration value directly and as such is saved and restored by SteamVR itself.\n\n- **[x] Disable Steam Integration** (only available if Desktop+ is being run by Steam):  \nRestarts Desktop+ without Steam when it was launched by it. This disables the permanent in-app status, usage time statistics and other Steam features.  \nThere will still be a brief in-app status and notification from Desktop+ being launched by Steam even if this is active. Steam Cloud functionality may not work as expected.\n\n  An alternative to this option is marking Desktop+ as private on the Steam profile.\n\n#### Troubleshooting\n\n![Settings Window, Main Page, Troubleshooting Section](images/settings_troubleshooting.png)\n\nThese buttons let you quickly restart components of Desktop+ in case it starts behaving unexpected in some way. \nSettings are saved before restarting and overlays are typically restored without loss afterwards. However, active sessions in browser overlays will be lost.\n\n- **Desktop+**:  \nThese buttons apply to the main Desktop+ process, responsible for non-UI overlays and most interaction with them.\n  - **[Restart]**:  \n  Restart Desktop+.\n  - **[Enter Elevated Mode]**:  \n  Enters elevated mode by launching a secondary Desktop+ process with administrator privileges using the \"DesktopPlus Elevated\" scheduled task. This button is only visible if [elevated mode](#system-wide-modifications) has been set up.\n\n- **Desktop+ UI**:  \nThese buttons apply to the Desktop+ UI process, responsible for displaying UI elements and modifying settings.\n  - **[Restart]**:  \n  Restart Desktop+ UI.\n  - **[Restart in Desktop Mode]**:  \n  Restart Desktop+ UI in desktop mode. The settings window will display on the desktop and all VR interface elements will be unavailable.\n  \n- **Restore Default Settings**:  \nPress the button to open the [Restore Default Settings page](#restore-default-settings-page), which allows further choices before making any changes.\n\n\n### Persistent UI Page\n\n![Settings Window, Persistent UI Page](images/settings_page_persistent_ui.png)\n\nThe *Persistent UI* page allows directly manipulating the state of Desktop+ interface windows.  \nSettings, Overlay Properties and Desktop+ Keyboard windows can be configured here.\n\n#### Windows\n\n- **[x] Visible**:  \nSets if the window is visible. This is the same as manually showing the window in other parts of the application.\n\n- **[x] Pinned**:  \nSets if the window is pinned. Pinning a window sets the absolute position for the global state to the current Desktop+ tab position. The Desktop+ keyboard can also be pinned in the Desktop+ tab.\n\n- **Position**:  \nPress [Reset] to restore the default position for the window. This position is relative to the dashboard for Desktop+ tab state and absolute for the global state.\n\n- **Size**:  \nChanges the size of the window. Interface windows can also be resized by using stick or touchpad inputs while dragging them.\n\n- **[x] Restore State on Desktop+ Launch**:  \nSets if the window state will persist between launches of Desktop+. Note that pinned windows may end up getting lost if left in a far away corner of the VR space, especially when facing away from the user.  \nHowever, a position reset can always be done in those cases.\n\n\n### Manage Overlay Profiles Page\n\n![Settings Window, Manage Overlay Profiles Page](images/settings_page_overlay_profiles.png)\n\nThe *Manage Overlay Profiles* page allows loading, saving and deleting overlay profiles.\nThe buttons below the list apply to the selected item.\n\nIf you want to create a new profile, choose \"[New Profile]\". The default profile can be loaded to quickly reset the overlay configuration.\n\n- **[Load Profile]**:  \nLoad the selected profile. This will replace all existing overlays.\n\n- **[Add Overlay from Profile]**:  \nAdds the overlays from the profile to the overlay list, without overriding any existing ones.\n\n- **[Save Current Overlays]**:  \nSaves the selected profile. If you selected \"[New Profile]\", you it will create a new profile.  \nDesktop+ will switch to the Save Current Overlays page before saving, letting you choose which overlays to save and enter a name in case of a new profile.\n\n- **[Delete Profile]**:  \nDeletes the selected profile. The button will change label to prompt for confirmation. Press it another time to really delete the profile.\n\n\n### Manage Application Profiles Page\n\n![Settings Window, Manage Application Profiles Page](images/settings_page_application_profiles.png)\n\nThe *Manage Application Profiles* page allows configuration of application profiles.\n\n- **(Application List)**:  \nThis is a list of all applications registered in SteamVR + applications a profile exists for.  \nMost non-Steam VR applications do not register a permanent manifest with SteamVR and as such will only be listed here while they're running, unless an application profile was already created for them.\n\n  While Desktop+ isn't running, only applications a profile exist for are listed.\n  \n  Inactive profiles are listed with a translucent text color. If an application profile is currently active, it is highlighted in the list with a green text color.\n  \n#### [Application Name]\n\nThe application profile configuration for the application selected in the list above.\n\n- **[x] Activate when Application is Running**:  \nToggles whether the application profile is applied or not.\n\n- **Overlay Profile**:  \nClick the button to pick an overlay profile that is loaded when the application is launched.  \nThe existing overlay configuration is restored when the application is no longer running.\n\n  Changes to the overlay configuration made while an overlay profile is loaded by an application profile are not saved automatically.\n\n- **Entry Action**:  \nClick the button to pick an action that is executed when the application is launched. \n\n- **Exit Action**:  \nClick the button to pick an action that is executed after the application has stopped running.\n\n- **Delete Profile**:  \nDeletes the selected profile. The button will change label to prompt for confirmation. Press it another time to really delete the profile.  \nAs the application list contains all registered applications regardless of a profile existing, this will not visibly remove any entry from the list, unless the profile is for an unregistered application.\n\n\n### Manage Actions Page\n\n![Settings Window, Manage Actions Page](images/settings_page_manage_actions.png)\n\nThe *Manage Actions* page allows creating, editing and deleting actions.\n\n- **(Action List)**:  \nThis is a list of all actions configured. The entries can be dragged up and down to change the order of them.  \nThe buttons below the list apply to the selected item.\n\n- **[Copy UID to Clipboard]** (Adv.):  \nCopies the action's unique identifier to the clipboard. This ID can be used in conjunction with the [command-line argument](#command-line-arguments) \"--DoAction [UID]\" to execute actions from external applications.  \nAction UIDs are 64-bit numbers derived from creation timestamp and a random component with a reasonably low chance of collisions. Default actions use special single digit UIDs.\n\n- **[New Action...]**:  \nSwitches to the [Edit Action page](#edit-action-page) to create a new action.\n\n- **[Edit Action]**:  \nSwitches to the [Edit Action page](#edit-action-page) to edit the selected action.\n\n- **[Duplicate Action]**:  \nCreates a copy of the selected action.\n\n- **[Delete Action]**:  \nDeletes the selected action. The button will change label to prompt for confirmation. Press it another time to really delete the action.  \nAnything referencing the action like controller buttons, hotkeys or application profiles will show their action be set to \"[None]\" afterwards.\n\n\n### Edit Action Page\n\n![Settings Window, Edit Action Page](images/settings_page_edit_action.png)\n\nThe *Edit Action* page allows creating and editing actions.\n\n- **Name**:  \nSets the name of the action. The default actions use special translation string ID names to automatically match the chosen application language.\n\n- **Target**:  \nSets the target of the action. Default targeting applies the commands to the overlay the action was activated on, or the focused overlay if the former isn't applicable.  \nThe focused overlay is the overlay that was clicked on with the laser pointer. Clicking on the Floating UI also changes the focused overlay.\n\n  Tag targeting uses a list of overlay tags to target one or multiple overlays. This list is inclusive, meaning adding multiple tags for targeting checks each tag individually for matching targets (i.e. does not require multiple tags on a target).  \n  The green tags are implicit auto-tags that match based on overlay properties.\n  \n#### Button Appearance\n\n- **(Preview Button)**:  \nButton showing a preview of the action button with the current settings. It can also be pressed to test the action.\n\n- **Icon**:  \nClick the button to pick an icon for the action. Icons are optional and rendered behind the label.  \nCustom icons can be added by placing PNG files in the \"images/icons\" folder. Recommended size is 96x96 pixels.\n\n- **Label**:  \nThe label of the button with up to 3 lines of text. The lines are always centered and squeezed to fit if they're too long.  \nThe default actions use special translation string ID labels to automatically match the chosen application language.\n\n#### Commands\n\nList of the commands the action runs when executed. Commands are run sequentially but with no delay between them.  \nConsider using the \"Launch Application\" command to run external scripts if you need more complex capabilities.\n\nClick the command headers to reveal their properties.\n\n- **[Delete Command]**:  \nDeletes the command. The button will change label to prompt for confirmation. Press it another time to really delete the command.\n\n- **[Add Command]**:  \nAdds another command to the command list.\n\n##### None\n\nNo-op command. Occupies an entry but does nothing.\n\n##### Press Key\n\nPresses or toggles a key.\n\n- **Key Code**:  \nClick the button to pick the virtual key code pressed by the command. Mouse buttons also have key codes that can be selected.\n\n- **[x] Toggle Key**:  \nToggles the state of the key instead of pressing and holding it until the action is stopped/released.  \nNote that key toggling is based on the global key state of the OS and held keys will remain in that state if not released otherwise. However, Desktop+ will release all held-down keys on exit.\n\n##### Set Mouse Position\n\nMoves the mouse cursor to the set position.\n\n- **X/Y**:  \nScreen coordinates the mouse cursor is moved to. Coordinates are relative to the top left corner of the primary desktop.\n\n- **[Use Current Mouse Position]**:  \nSets the X and Y values to the current mouse cursor position.\n\n##### Type String\n\nTypes the set string.  \nThis is done via text input simulation and does not send key down and up events for individual keys. As such, not every application will read this kind of input.\n\n- **String**:  \nThe string typed by the command.\n\n##### Launch Application\n\nLaunches external applications.  \n\n- **Executable Path**:  \nPath of the application launched. As this goes through the Windows shell, file paths and URLs for registered protocols can also be used.\n\n- **Application Arguments**:  \nArguments passed to the executable. This can usually be left blank.\n\n##### Show Keyboard\n\nChanges visibility of the [Desktop+ VR keyboard](#desktop-keyboard).\n\n- **Visibility**:  \nSets if the commands toggles, always shows, or always hides the keyboard.\n\n##### Crop to Active Window\n\nCrops the targeted overlays to the currently focused desktop window.  \nThis command acts as a toggle, turning overlay cropping off if the crop is already equal to the active window.\n\n##### Show Overlay\n\nChanges visibility of overlays.\n\n- **Visibility**:  \nSets if the commands toggles, always shows, or always hides the overlays.\n\n- **Target**:  \nSets if the command applies to the action's target overlays or overlays matching a specific set of tags.\n\n- **[x] Undo on Release**:  \nReverts the change made by the command when the action is stopped/released.  \nNote that some ways of executing actions start and stop the action immediately.\n\n##### Switch Task\n\nShows the Windows task switcher or changes the focus to a specific window.  \nShowing the task switcher usually works even when an elevated application has focus.\n\n- **Switching Method**:  \nSet if the command shows the task switcher or focuses a specific window.\n\n- **Window** (Focus Window method only):  \nPress the button to pick a window to focus.\n  - **[x] Use Strict Window Matching**:  \n  Only allow exact window title matches when searching the window to focus. By default Desktop+ uses a weak window title matching algorithm to account for document-style changing window titles.  \n  Use this option if you experience false positives with specific applications.\n  \n- **[x] Warp Cursor into Window** (Focus Window method only):  \nSets the mouse cursor position to the center of the window that is being focused.\n\n##### Load Overlay Profile\n\nLoads an overlay profile.\n\n- **Profile**:  \nClick on the drop-down list to select the profile the command will load.\n\n- **[x] Remove Existing Overlays**:  \nSets if all existing overlays are removed when loading the profile.  \nThis is equivalent to loading a profile instead of adding overlay from a profile in the [Manage Overlay Profiles page](#manage-overlay-profiles-page).\n\n\n### Change Action Order Page\n\n![Settings Window, Change Action Order Page](images/settings_page_change_action_order.png)\n\nThe *Change Action Order* page allows adding, removing and re-ordering actions to action order lists.\n\n- **(Action List)**:  \nList of actions in the action order list. The entries can be dragged up and down to change the order of them.\n\n- **[Add Actions...]**:  \nClick on the button to pick one or more actions to add to the list.\n\n- **[Remove Action]**:  \nRemoves the selected action from the action order list.\n\n\n### Keyboard Layout Page\n\n![Settings Window, Keyboard Layout Page](images/settings_page_keyboard_layout.png)\n\nThe *Keyboard Layout* page allows changing the layout of the Desktop+ VR keyboard.\n\n- **(Keyboard Layout List)**:  \nList of available keyboard layouts for the Desktop+ VR keyboard.\n\n- **[Switch to Keyboard Layout Editor]** (only available in desktop mode):  \nOpen the keyboard layout editor on the desktop to customize or create custom layouts for the VR keyboard.\n\n#### Key Clusters\n\n- **[x] Function/Navigation/Numpad/Extra**:  \nToggles specific sections of the keyboard layout. Some keyboard layouts may not include all clusters.\n\n\n### Restore Default Settings Page\n\n![Settings Window, Restore Default Settings Page](images/settings_page_restore_defaults.png)\n\nThe *Restore Default Settings* page allows choosing specific elements to reset to their default values.\nTick the desired elements to reset and press [Reset Selected Elements]. Desktop+ will restart after making the changes.  \nNote that active sessions in browser overlays will be lost when restarting.\n\nIt is recommended to use these controls over deleting the configuration files manually. For the Steam version, Steam Cloud would automatically restore the missing files on launch if enabled, undoing the deletion.\n\n\n## Overlay Properties\n\n### Main Page\n\n![Overlay Properties Window, Main Page, Position Section](images/overlay_properties_position.png)\n\n- **(Overlay Icon)**:  \nThe overlay icon can be double-clicked as an alternative way of toggling overlay visibility.\n\n#### Position\n\n- **Origin**:  \nClick on the drop-down list to select the position origin of the overlay. This can be used to attach an overlay to controllers or the HMD.\n  - **Play Area**:  \n  Attach overlay to the play area. This is absolute positioning relative to the standing play area.  \n  Overlays will not move unless the SteamVR room setup changes.\n  - **HMD Floor Position**:  \n  Attach overlay to the position of the HMD, without rotation.  \n  Choosing this origin will reveal the option \"[x] Turn with HMD\", which will spin the origin along without it tilting in any way.\n  - **Seated Position**:  \n  Attach overlay to the SteamVR seated position. This origin may have an unexpected position if the active VR game isn't using the seated space.  \n  Update the origin position by resetting the seated position in SteamVR. Games may also trigger updates to this position.\n  - **Dashboard**:  \n  Attach overlay to the SteamVR dashboard. If visibility isn't restricted to dashboard, the origin position updates when the dashboard is brought up.\n  - **HMD**:  \n  Attach overlay to the HMD. This origin is matches head movement 1:1 and essentially looks static in the headset view.\n  - **Left Controller**:  \n  Attach overlay to the left motion controller.\n  - **Right Controller**:  \n  Attach overlay to the right motion controller.\n  - **Theater Screen**:  \n  Attach overlay to the SteamVR Theater Screen. Choosing to this origin will hide the overlay and reveal the button [Enter Theater Mode] to display the SteamVR Theater Screen. This button does the same as showing the overlay.  \n  There can only be one Theater Screen origin overlay active at once.  \n  If another overlay of that origin is set to be visible, it'll hide every other one automatically. Theater Screen origin overlays will also be hidden if the SteamVR Theater Screen is taken over by an overlay of another application or when using the \"Close Overlay\" button below the screen.\n  - **Tracker #1** (only visible if a tracker is connected):  \n  Attach overlay to the first connected SteamVR tracker device. Using other trackers as the origin is not supported at this time.\n\n- **Origin Smoothing** (only visible for HMD Floor Position and HMD origins):  \nSets the level of smoothing/stabilization applied to the overlay origin.\n\n- **Display Mode**:  \nClick on the drop-down list to select a display mode. Display modes restricts under which condition the overlay is visible. For example, \"Only In-Game\" only shows the overlay while a game/scene application is active and hides it while the dashboard is visible.\n\n- **Position**:\n  - **[Change]**:  \n  Opens the Position Change page and activates overlay drag-mode. You can use the buttons to do manual position adjustments or simply drag the overlay in the VR space to reposition them.  \n  Using right-click to drag triggers a gesture drag, allowing you to rotate and scale overlays based on the motions of both hand-controllers.\n  - **[Reset]**:  \n  Reset the position of the overlay. Depending on the origin the new position will be near the zero point of it or next to the dashboard overlay.\n  - **[x] Lock**:  \n  Lock the position of the overlay. This option only prevents dragging the overlay. Position-locked overlays will still move along with their set origin.  \n  The lock state is temporarily toggled off for the respective overlay when using the Position Change page in the Overlay Properties window, but is restored when leaving the page again.\n\n#### Appearance\n\n![Overlay Properties Window, Main Page, Appearance Section](images/overlay_properties_appearance.png)\n\n- **Width**:  \nWidth of the overlay. The height is automatically determined by the aspect ratio of the mirrored content.\n\n- **Curvature**:  \nSets how much the overlay curves inwards.\n\n- **Opacity**:  \nSets how translucent the overlay appears. 100% is fully visible.\n\n- **Brightness**:  \nSets how bright the overlay appears. 100% is full brightness.\n\n- **[x] Crop**:  \nSets if the cropping area is applied.\n  - **[X, Y | Width x Height]** (Cropping Area button):  \n  Press this button to open the [Cropping Area page](#cropping-area-page) to change how the overlay is cropped.\n\n#### Capture\n\n![Overlay Properties Window, Main Page, Capture Section](images/overlay_properties_capture.png)\n\n*This is only shown for desktop and window overlays and if advanced settings are enabled*\n\n- **Capture Method**:  \nChanges the capture API used for this overlay. Graphics Capture additionally allows capturing windows directly. See [Desktop Duplication vs. Graphics Capture](#desktop-duplication-vs-graphics-capture) for a detailed comparison of both APIs.\n\n- **Source** (Desktop Duplication):  \nClick on the drop-down list to select a desktop to crop the combined desktop mirror to. This selection is also used when resetting the cropping rectangle.\n\n- **Source** (Graphics Capture):  \nClick on the button to select the capture source for the overlay. The list on the opening page contains available capturable desktops and windows.\n  - **[x] Use Strict Window Matching**:  \n  Only allow exact window title matches when restoring the overlay's capture. By default Desktop+ uses a weak window title matching algorithm to account for document-style changing window titles.  \n  Use this option if you experience false positives with specific applications.\n\n#### Performance Monitor\n\n![Overlay Properties Window, Main Page, Performance Monitor Section](images/overlay_properties_performance_monitor.png)\n\n*This is only shown for Performance Monitor overlays*\n\nThe Performance Monitor lets you quickly check the current state of your system from within VR in real time.  \nWhile it's possible to create as many overlays of it as desired, they will all be showing the same content. Performance Monitor properties are global.\n\n- **Style**:  \nSwitches between a large and compact style of the Performance Monitor.  \nThe compact style does not allow displaying the current time.\n- **[x] Show (Item)**:  \nAllows to customize which performance characteristics and states are displayed on the Performance Monitor.\n- **[x] Disable GPU Performance Counters** (Adv.):  \nDisables display of GPU load % and VRAM usage. This prevents GPU hardware monitoring related stutter with certain NVIDIA drivers.\n- **[Reset Cumulative Values]**:  \nResets statistics that build up over time, such as average FPS, dropped frames, and reprojection ratio.  \nThese values are also reset automatically when the game/scene application changes or SteamVR goes into standby mode.\n  \nNote that Performance Monitor overlays do not update or may be invisible while the Desktop+ UI is open in desktop mode.\n\n#### Browser\n\n![Overlay Properties Window, Main Page, Browser Section](images/overlay_properties_browser.png)\n\n*This is only shown for browser overlays*\n\n- **URL**:  \nAddress the browser overlay is displaying. This changes as browsing is done in the overlay. Local addresses can be used as well.\n  - **[Go]**:  \n  Start navigation. Pressing enter on the text input field works as well.\n  - **[Restore Last Input]**:  \n  Reset the text input field to the last manually entered URL. This is useful to go back to the initial location after navigating away from it.\n  \n- **Width/Height**:  \nRender resolution of the browser overlay. Changing this is akin to resizing a web browser window.\n- **Zoom**:  \nZoom level used in the browser overlay.\n\n- **[x] Allow Transparency**:  \nAllows web pages to use transparency in the page background. This allows for RGBA web rendering if the page is set up for it.  \nHowever, this results in the default background color being transparent. This means pages which don't define anything themselves and expect it to be opaque white will not look correct.\n\n  Changing this property requires a reset of the browser context. Such a reset will clear the browsing history and reload the page. Click [Recreate Browser Context] to proceed after changing the property.\n\n#### Advanced\n\n![Overlay Properties Window, Main Page, Advanced Section](images/overlay_properties_advanced.png)\n\n- **[x] 3D**:  \nSets if the 3D properties are applied.\n  - **3D Mode**:  \n  Use the drop-down list to change the way the overlay's image is split up between both eyes to create a 3D image.\n  - **[x] Swap Eyes**:  \n  Swaps which eye gets each split up part of the overlay image.\n\n- **[x] Gaze Fade**  \nSets if the Gaze Fade properties are applied.\nGaze Fade gradually fades the overlay to 0% opacity when not looking at it. When the overlay is at 0% opacity, it is considered inactive and will not react to laser pointer input.\n  - **[Auto-Configure]**:  \n  Configure distance and sensitivity values automatically from the current gaze and overlay settings. After clicking, a pop-up will appear asking to look at the overlay for 3 seconds.\n  - **Distance** (Adv.):  \n  Distance of the gaze point from the HMD. Put the slider to the leftmost value to set it to \"Infinite\".\n  - **Sensitivity** (Adv.):  \n  Rate at which the fading occurs. As Gaze Fade only takes the center point of the overlay in account, adjusting this value depending on the overlay size is recommended.\n  - **Target Opacity** (Adv.):  \n  Minimum opacity Gaze Fade will set when not looking at the overlay. If this value is higher than the overlay's opacity property, Gaze Fade will be reversed and fade the overlay in when not looking at it.\n  \n- **[x] Laser Pointer Input**  \nSets if laser pointer input is enabled for this overlay. The laser pointer will pass through the overlay if this is disabled.\n  - **[x] Enabled In-Game**:  \n  If this is disabled, the laser pointer will pass through the overlay outside of the dashboard.\n  - **[x] Show Floating UI**:  \n  Sets if the Floating UI is displayed when pointing at this overlay.\n\n- **Overlay Tags**  \nThe tags assigned to this overlay. Overlay tags are used by actions to target specific overlays. See [Overlay Tags](#overlay-tags) for more details.\n  \n#### Performance\n\n![Overlay Properties Window, Main Page, Performance Section](images/overlay_properties_performance.png)\n\n- **Override Update Limiter Mode** (only visible for desktop and window overlays):  \nSets the limiter mode for this overlay only. Overrides the global setting.  \nDue to the shared textures between overlays, the limit resulting in the most updates among all active overlays of the same capture source is used.\n\n- **Maximum Frame Rate** (only visible for browser overlays):  \nThe maximum amount of frames per second this browser overlay will render at. Overrides the global setting.\n\n- **[x] Update when Invisible** (Adv.):  \nUpdates the overlay even when invisible from Opacity setting or Gaze Fade.  \nThis helps with third-party applications accessing the overlay's contents but is not recommended otherwise. Updates are still suspended if the overlay is disabled or hidden by Display Mode setting.  \nAllowing overlays to update when not needed can have a significant performance impact.\n  \n#### Interface\n\n![Overlay Properties Window, Main Page, Interface Section](images/overlay_properties_interface.png)\n\n- **Overlay Name**:  \nSet a custom name for the overlay. Leave this blank to let Desktop+ choose a name automatically.\n\n- **[x] Override Action Buttons**:  \nSet if overlay-specific order and visibility of action buttons is used.\n  - **[x Actions Selected]**:  \n   Press this button to open the [Change Action Order page](#change-action-order-page). Actions can be re-ordered by dragging the entries up and down.\n\n- **[x] Show Desktop Buttons**:  \nAdds buttons for quickly switching desktops to the Action Bar of the Floating UI.\n\n- **[x] Show Extra Buttons**:  \nAdds overlay-type-specific buttons to the Main Bar of the Floating UI.  \nExamples for such buttons are \"Add Active Window as Overlay\" (desktop overlays only), \"Reset Cumulative Values\" (Performance Monitor only), and browser overlay controls.\n\n### Change Overlay Position Page\n\n![Overlay Properties Window, Change Overlay Position Page](images/overlay_properties_page_change_position.png)\n\nThe *Change Overlay Position* page allows making adjustments to the overlay's position and change laser pointer drag behavior.\n\n#### Manual Adjustment\n\nPress the buttons to make manual adjustments to the overlay's position and orientation. The changes are applied relative to the overlay's current position.  \n\n- **[D]** (only available in desktop mode):  \nClick and hold the drag buttons to move or rotate the overlay with the mouse. Scroll wheel input applies forward/backward movement and roll adjustments.\n\n#### Additional Offset\n\nThese offsets are applied on top of the overlay's position.\n\n- **Up/Down/Left/Right/Forward/Backward Offset** (Adv.):  \nAdditional offset in the specific direction in meters. The directions are relative to the overlay's orientation.\n\n#### Drag Settings\n\nChange these settings to adjust the laser pointer drag behavior. Drag settings are global and affect every overlay drag, except Desktop+ interface windows.\n\n- **[x] Dock to Controller when Near**:  \nSets if auto-docking is enabled. Auto-docking automatically changes the dragged overlay's origin to the left or right hand controller when the drag is released near them.\n\n- **[x] Force Fixed Distance**:  \nForce a fixed distance between the overlays and the user.  \nThe reference position for this is always based on the headset position, but is only updated when the dashboard is brought up or drag-mode is toggled on while the dashboard is not active. Small changes in position are ignored to allow for a more fixed reference position between adjustments.  \nUsing stick or touchpad to change the distance of an overlay during a drag will adjust the Fixed Distance value while this drag setting is enabled.\n  - **Shape**:  \n  Sets the shape the fixed distance is computed as. This can be set to sphere or a cylinder.\n    - **[x] Auto-Curve**:  \n    Automatically change the dragged overlay's curvature property to match the chosen shape.\n    - **[x] Auto-Tilt**:  \n    Automatically tilt the dragged overlay alongside the chosen shape.\n  \n- **[x] Snap Position**:  \nSnaps the position to a 3D grid of the given size.  \nNote that the overlay origin itself may still be moving and make it difficult to line up multiple overlays together.\n\n- **[x] Snap Rotation**:  \nSnaps the rotation to the gvien angle.  \nNote that the overlay origin itself may still be spinning and make it difficult to line up multiple overlays together.\n  - **[x] Yaw/Pitch/Roll**  \n  Toggles which axes the snapping is applied to.\n\n### Cropping Area Page\n\n![Overlay Properties Window, Cropping Area Page](images/overlay_properties_page_cropping_area.png)\n\nThe *Cropping Area* page allows changing the overlay's cropping area.\n\nThe cropping area is a rectangle the overlay content is cropped to. If the rectangle is larger than the overlay content, it's clamped to the maximum possible area.  \nCropping areas are preserved when switching between capture sources. This can lead to an invalid rectangle being set for the new capture source, making the overlay essentially invisible as a result.  \nIn that case, a \"(!)\" warning is displayed next to this crop setting on the main page and it's recommended to simply use the *[Reset]* button to fix it.\n\n- **(Draggable Cropping Rectangle**):  \nRepresents the overlay's cropping area in relation to its total content size.  \nDrag the rectangle to move it within the content bounds. Use scroll input or the edges to adjust the size of the cropping area.\n\n- **[Reset]**:  \nReset the cropping area to default values. If a desktop to crop to is set, it will restore the values for that. Otherwise, it resets to maximum possible area.\n\n#### Manual Adjustment\n\n- **X/Y/Width/Height**:  \n  Position and size of the cropping area. Width and height can be set to \"Max\" by dragging the slider into the right-most position. The cropping area will always assume the maximum possible size in that case.\n  \n- **[Crop to Active Window]** (Desktop Duplication):  \n  Change the cropping area to match the foreground window.\n  \n  \n## Desktop Mode\n\n![Desktop Mode Window](images/desktop_mode.png)\n\nDesktop mode displays the same interface for settings and overlay properties as in VR, but condensed into a single window on the desktop with its own main page to allow quick access and overlay management.\n\nTo access desktop mode, either run DesktopPlusUI.exe while SteamVR is not running, press [Restart in Desktop Mode] in the Troubleshooting section of the Settings window, or run DesktopPlusUI.exe with the \"--DesktopMode\" [command-line argument](#command-line-arguments).  \nNote that some settings are not available when running in desktop mode while SteamVR is not running, and the main application process will not be running either. The UI refers to this as \"Desktop+ isn't running\", even if the UI process might be seen as part of the application.\n\n- **[🡸]**:  \nNavigate back to the previous page.  \nOn pages that can have changes reverted by [Cancel], this button is equivalent to pressing it.\n\n### Tools\n\nQuickly access the settings and several key setting pages.  \nThe items listed are the same pages as accessible through the main settings page.\n\n### Overlays\n\nList of overlays, similar to the Overlay Bar.  \nOverlays can be re-ordered by dragging the entries up and down.\n\nRight-click the entries to access the context menu, which allows to show/hide, clone or remove the overlay.\n\n- **[Add Overlay]**:  \nAdd a new overlay.  \nPerformance Monitor overlays cannot be create while in desktop mode as their appearance relies on Desktop+ UI rendering in VR.\n\n### Keyboard Controls\n\nDesktop mode can be fully operated by keyboard alone and also has some mouse shortcuts. These are the controls:\n\n| **Mouse**                   |                                                       |\n| :-------------------------- | :---------------------------------------------------- |\n| Left Mouse Button           | Interact with Widgets                                 |\n| Right Mouse Button          | Context Menu on Overlay List/Edit Slider Value        |\n| Mouse Back Button/Backspace | Return to Previous Page                               |\n| Mouse Wheel                 | Scroll/Move or Rotate during Overlay Drag             |\n\n| **Keyboard**                | *(activate keyboard controls for these to work)*      |\n| :-------------------------- | :---------------------------------------------------- |\n| Arrow Keys                  | Navigate/Activate Keyboard Controls                   |\n| Spacebar                    | Interact with Widgets                                 |\n| Esc                         | Cancel Widget Selection                               |\n| Enter                       | Confirm Text Input                                    |\n| Menu Key                    | Context Menu on Overlay List                          |\n| Ctrl                        | Hold on List Entries while Navigating to Change Order |\n\n## Desktop+ Keyboard\n\n![Desktop+ VR Keyboard](images/vr_keyboard.png)\n\nDesktop+ features its own implementation of a customizable VR keyboard. Its behavior, layout, and visible clusters can be changed in the [Keyboard Layout Settings Page](#keyboard).  \nThe keyboard window can be operated with multiple laser pointers simultaneously. Due to API limitations, there are no haptics on the non-primary laser pointers while the dashboard is visible, however.\n\nIts input behavior is mostly straight forward and mimics a normal PC keyboard. Keys held on the VR keyboard are simulated to be held down until they are released on it, with key repeat for most keys if configured.  \nModifier keys can be sticky, meaning they will remain simulated as held down until they're pressed again. Every key on the VR keyboard can be made sticky by right-clicking them.  \nKeys remain pressed when hiding the keyboard, but all still held down keys are released on Desktop+ shutdown.\n\nWhile shown, the keyboard follows the overlay it was shown for, if any. Or the SteamVR dashboard, while it is visible. Changes to the keyboard position are relative to this, unless the keyboard window is pinned.  \nNote that input done on the keyboard always gets sent to the focused overlay (the overlay that was last clicked on with the laser pointer). For desktop/window overlays this does not make any difference, however.\n\n### Keyboard Layout Editor\n\n![Keyboard Layout Editor](images/keyboard_layout_editor.png)\n\nThe Keyboard Layout Editor allows to customize or create custom keyboard layouts for the Desktop+ VR Keyboard. It is only available in desktop mode.\n\n#### Key List\n\nList of all keys in the layout. Use the tabs to switch between sub-layouts.\n\n##### Sub-Layouts\n\n- **Base**:  \nNormal layout displayed by default.\n\n- **Shift**:  \nShifted layout displayed when any of the shift keys are held, or caps lock is active.  \nUsually features capital letters and special characters.\n\n- **AltGr** (only available if \"[x] Has AltGR\" is ticked):  \nAltGr layout displayed when AltGr is being held. Not present on US-style layouts.  \nUsually features third-level letters and special characters.\n\n- **Aux**:  \nAuxiliary layout. Only displayed if explicitly switched to via a \"Toggle Sub-Layout\" key.  \nThis can be anything or just remain unused.\n\n##### Sub-Layout Key List\n\nList of all keys in the sub-layout.  \nClick an entry to select the key or row and displays its properties in the Key Properties window.  \nEntries can be dragged up or down to re-arrange their order.\n\n##### Bottom Row\n\n- **[Undo]**:  \nUndo the last change. The changes history includes modifications made in any part of the Keyboard Layout Editor.  \nCtrl+Z can also be pressed to trigger this button.\n\n- **[Redo]**:  \nRedo the reverted last change.  \nCtrl+Y can also be pressed to trigger this button.\n\n- **[Add]**:  \nAdd a new key. The key is created next to the current selection.  \nTo add a new row, select an existing row before adding a key.  \nAdding too many keys in a row will cause them to be cut off. While there is no limit of rows enforced, a scroll bar will appear in the editor preview and VR if the window space is exhausted.\n\n- **[Duplicate]**:  \nCreates a copy of the currently selected key or row. This copy is created next to the original.\n\n- **[Remove]**:  \nRemoves the selected key or row. There is no confirmation step for this action, but it can always be undone.\n\n#### Key Properties\n\n- **Type**:  \nSets the type of the key. Some key types have their own specific properties, listed below.\n\n- **Size**:  \nSets the width and height of the key. 100% is the size of a standard key and compatible with common key size definitions.\n\n- **Label** (not available for \"Blank Space\"-type keys):  \nThe label displayed on the key. The text is horizontally centered per-line, and vertically centered on the key as a whole. 100% height keys typically fit two lines of text (three fit barely, but not recommended).  \nPer-line centering can be changed to left or right alignment by appending \"##L\" or \"##R\" to the end of a line.\n\n- **Cluster**:  \nAssign clusters to keys to allow selectively disabling them in the settings later. As \"Blank Space\"-type keys are also part of clusters, the assignments has to be chosen carefully to not break the layout when toggling them.\n\n##### Virtual Key / Virtual Key (Toggle) / Virtual Key (ISO-Enter)\n\nSimulates a key press or toggle of a virtual key code.  \nThe ISO-enter variant allows combining two keys in adjacent rows to build an ISO-enter shaped key.\n\n- **Key Code**:  \nClick the button to pick the virtual key code pressed by the key. Mouse buttons also have key codes that can be selected.\n\n- **[x] Block Modifiers** (not available for \"Virtual Key (Toggle)\"):  \nReleases all modifiers when this key is pressed. This is useful when the key's sub-layout differs from the required modifier state of the simulated input.\n\n- **[x] Never Repeat**:  \nDisable key repeat for this key, regardless of the global key repeat setting. This is useful for keys that don't repeat when held on a real keyboard.\n\n##### String\n\nIf the string is a single character, Desktop+ will try to figure out which keys to press on the real keyboard layout to simulate the input.  \nIf the string is multiple characters, input is done via text input simulation and not sending key down/up events for individual keys. Not every application will read this kind of input.\n\nIt is recommended to use this key type for non-basic character keys to increase application compatibility.\n\n- **String**:  \nThe string input simulated when pressing the key.\n\n##### Toggle Sub-Layout\n\nExplicitly switch to a different sub-layout. This is always a toggle. Place an identical key in the target sub-layout to allow switching back.  \nShift and AltGr sub-layouts are switched to automatically depending on the related modifier's state, making this only necessary to reach the Aux sub-layout.\n\n- **Sub-Layout**:  \nSets the sub-layout to toggle.\n\n##### Action\n\nExecute an action when pressing the key. This works the same as action buttons in the [Floating UI Action Bar](#action-bar).\n\n- **Action**:  \nClick the button to pick the action that is executed when the key is pressed. \n\n#### Layout Metadata\n\n- **Name**:  \nName of the keyboard layout. This is what it is listed as in the [Keyboard Layout Settings Page](#keyboard).\n\n- **Author**:  \nName of the keyboard layout's author. This is displayed in the [Keyboard Layout Settings Page](#keyboard).  \nOfficial layouts leave this field blank.\n\n- **[x] Has AltGr**:  \nSets if the AltGr sub-layout should be present. This is typically left unticked for US-style layouts.\n\n- **Preview Clusters**:  \nUntick clusters to check how the layout will look with different [key clusters settings](#key-clusters) set.  \nThe state of this does not affect the saved layout.\n\n- **[Save...]**:  \nClick this button to save the layout. A pop-up will open allowing you to choose the file name or to overwrite an existing layout.  \nKeyboard layouts are stored in the \"keyboards\" directory found in the application's install directory.\n\n- **[Load...]**:  \nClick this button to load a different layout. A pop-up will open allowing you to choose the next layout.  \nNote that there is no additional confirmation step and unsaved changes will be lost when loading a different layout.\n\n#### Keyboard Preview\n\nA preview of how the keyboard will look in VR.  \nLeft-click individual keys to select them in the key list or right-click a key to select the row it belongs to.\n\n\n# Performance Considerations\n\nDesktop+ was written to be lean in both computational load and memory footprint. However, functionality does not come without a price, so knowing the performance characteristics of the application can be helpful.\n\nIn general, configured, but inactive overlays are without noticeable performance penalty. Mirroring of the desktops or windows is paused completely while no overlays using the source are active.  \nAn overlay is considered inactive when it's not enabled or visible. This means display modes hiding an overlay and Gaze Fade setting its opacity to 0% are ways to set an overlay inactive.\nOverlays merely outside the view are *not* considered inactive, but may still have a lower GPU load from being culled from rendering by the VR Compositor.\n\nIf possible, avoid using Over-Under 3D, as it involves an extra conversion step and textures for each individual overlay. Textures are not shared for such overlays. Prefer Side-by-Side 3D instead.  \nOn certain multi-GPU setups, such as laptops with a hybrid GPU solution, textures have to be copied between GPUs since the desktops are typically rendered on the integrated GPU while the HMD is connected to the dedicated GPU. This comes with a significant performance penalty. If possible, consider deactivating the integrated GPU altogether.\n\n## Desktop Duplication\n\nAll Desktop Duplication overlays share a single texture, meaning additional overlays are fairly cheap. Mirroring is stopped when no Desktop Duplication overlays are active.  \nCursor movement is decoupled from the screen's refresh-rate and can cause GPU load spikes, especially when a mouse with a high polling rate is used. If this is undesirable, use the Update Limiter with a low frame time limit to mitigate this.\n\n## Graphics Capture\n\nAll Graphics Capture overlays using the same source are sharing a single texture, meaning additional overlays of the same source are fairly cheap.  \nGraphics Capture is generally a more efficient capture method than Desktop Duplication. However, keep in mind that many individual window overlays can easily end up performing worse than carefully cropped desktop overlays.\n\n## Desktop Duplication vs. Graphics Capture\n\nDesktop+ features two capture methods for overlays. Both have different strengths and weaknesses. Using Graphics Capture for desktop mirroring is usually only recommended on the latest Windows version.\n\n### Desktop Duplication\n\n- Supported on systems with Windows 8.1 and newer\n- Mirrors desktops\n- Decoupled cursor movement\n- Only updates on screen changes\n- Supports mirroring applications in exclusive fullscreen mode\n- Supports mirroring exclusive fullscreen applications at unconstrained frame-rates\n\n#### Mirroring at Unconstrained Frame-Rates\n\nDesktop Duplication supports mirroring exclusive fullscreen applications at unconstrained frame-rates. Actual support depends on how the application does its rendering, but it works for most.  \nFor this to work, the following conditions have to be met:\n\n- Application has to run in exclusive fullscreen mode\n- VSync has to be disabled\n- Fullscreen optimizations have to be disabled*\n\n<sub>* May not be required for certain games using OpenGL or Vulkan to render.</sub>\n\nFullscreen optimizations can be disabled on a per-application basis in the Compatibility tab of the executable's properties window on Windows 10 version 1803 or newer.\n\n### Graphics Capture\n\n- Supported on systems with Windows 10 version 1903 and newer*\n- Mirrors desktops or windows\n- Cursor movement at source monitor refresh rate\n- Always updates at monitor refresh rate (does not apply to Windows 11 24H2 and newer)\n- Supports direct mirroring of occluded windows\n- Additional Desktop+ features for window overlays\n- Draws yellow rectangle over mirrored sources (not visible in the capture, does not apply to Windows 11 and newer)\n- More efficient than Desktop Duplication (but the fixed refresh rate may nullify this with high refresh rate displays)\n\n<sub>* Windows 10 version 1903 is required for limited support, version 2004 for full support.</sub>\n\n#### Graphics Capture Feature Support\n\nWindows 10 version 1903 is the minimum supported version for Graphics Capture/window overlays.  \nVersion 2004 additionally enables selecting the combined desktop, as well as supporting disabling the cursor.  \nWith Windows 11 the yellow border around captures, which is only visible on real display, is removed.  \n24H2 additionally enables capturing secondary windows (e.g. pop-up menus), native update limiting, and only updates on content changes.\n\n\n# VR Interactions\n\nThe following interactions done with VR motion controllers trigger specific behavior in Desktop+:\n\n- Holding down left-click on a window's title bar in a desktop overlay and scrolling down: Create window overlay of this window and start dragging it.\n- Dragging a window in a window overlay: Trigger configured [window overlay drag behavior](#window-overlays)\n- Dragging an overlay near a motion controller: Trigger [overlay auto-docking](#drag-settings) if enabled\n\n\n# Usage Examples\n\nProvided are a few typical usage scenarios and how to configure Desktop+ for them.  \nIf unsure, load the \"Default\" overlay profile as a clean starting point before trying them out.\n\n## Attach Overlay to a Motion-Controller, Wristwatch-Style\n\n1. Click *[+]* in the [Overlay Bar](#overlay-bar) to bring up the overlay capture source pop-up menu.\n1. Use the opposite controller to press and hold left click/trigger on the desired capture source from the list.\n1. While still holding down, move and rotate the controller to move the newly added overlay towards the target controller until a pop-up saying \"Release to dock\" appears. ([Overlay auto-docking](#drag-settings) may be disabled if this doesn't work)\n1. Release left click/trigger to dock the controller.\n1. Optionally enable and adjust *Gaze Fade* in the *Overlay Properties* settings page's [Advanced](#advanced) section. See [Performance Considerations](#performance-considerations) for details on why this is recommended.\n\nThe size of the new overlay can be adjusted while dragging by moving the stick or sliding the touchpad left and right.\n\n## Show Multiple Desktops or Windows in the Dashboard at Once\n\n1. Click *[+]* in the [Overlay Bar](#overlay-bar) to bring up the overlay capture source pop-up menu.\n1. Use the opposite controller to press and hold left click/trigger on the desired capture source from the list.\n1. While still holding down, move and rotate the controller to move the newly added overlay to the desired spot.\n1. Release left click/trigger.\n1. Click on the icon of the newly added overlay in the [Overlay Bar](#overlay-bar) to bring up its pop-up menu.\n1. Click on \"Properties...\" to bring up the [Overlay Properties window](#overlay-properties) for the overlay.\n1. At the top of the window, click on the drop-down list next to *Origin* and choose \"Dashboard\".\n1. Optionally click on the drop-down list next to *Display Mode* and choose \"Only in Desktop+ Tab\" to limit visibility similar to the default Desktop+ dashboard overlay.\n1. Repeat these steps for any other overlays you wish to add. You may also choose to duplicate existing overlays from the overlay icon pop-up menu as a starting point instead.\n\n## Simulate Keyboard Shortcut from Motion-Controller Input\n\n1. Click on *[New Action...]* below the *action list* on the [Manage Actions Page](#manage-actions-page).\n1. Optionally give the action a fitting name and label.\n1. Click on *Add Command*.\n1. Click on the drop-down list next to *Command Type* and choose the \"Press Key\".\n1. Click on *[None]* next to *Key Code* and choose the desired keyboard key or mouse button to be simulated.\n1. Repeat step 3 to 5 to add more keys. All of them will be simulated as pressed simultaneously.\n1. Click on *[Ok]* at the bottom of the to save the new Action.\n1. Return to the settings window's main page.\n1. Click on *[None]* next to *Global Shortcut #1* in the *Global Controller Buttons* list found in the *Actions* section.\n1. Select the newly created action from the bottom of the list.\n1. Open the SteamVR controller bindings for Desktop+ by pressing *[Show Controller Bindings]*\n1. Configure the input bindings as desired. Desktop+ shortcut bindings are all boolean actions, meaning you'll find them when configuring digital input types such as *\"Button\"* or *\"Toggle Button\"*.\n1. To bind the action created in this example, select a controller input, use it as *\"Button\"* and select *\"Do Global Shortcut #1\"*.\n1. Click the check mark to save the changes.\n1. The keyboard shortcut should now be pressed whenever the configured input binding is triggered. The dashboard has to be closed, however.\n\nIf you wish to configure a keyboard shortcut to simulated when pressing a button while pointing at a Desktop+ overlay, simply select the action for one of the *Active Controller Buttons*.\n\nIf you need further help on how to use the SteamVR binding interface, [OpenVR Advanced Settings' SteamVR Input Guide](https://github.com/OpenVR-Advanced-Settings/OpenVR-AdvancedSettings/blob/master/docs/SteamVRInputGuide.md) is a good place to start.\n\nThe SteamVR controller bindings can also be accessed by either going into the SteamVR settings' *Controller* page and clicking *Show old Binding UI*, or loading http://localhost:27062/dashboard/controllerbinding.html with a web browser while SteamVR is running.  \nSteamVR will *not* list Desktop+ in the pop-up that appears by pressing the *Manage Controller Bindings* button, so make sure to not accidentally click that button instead.  \nSteamVR may not list Desktop+ right after its first launch. Try restarting SteamVR in that case.\n\n# Advanced Features\n\n## Hidden Configuration Settings\n\nThere are a handful of advanced configuration settings which aren't exposed in the settings interface.  \nThese config keys can be found in *config_default.ini* alongside comments about their functionality.\n\n## System-wide Modifications\n\nThe \"misc\" directory found in the application's install directory contains a collection of scripts that make system-wide changes to enable additional access privileges for Desktop+.\n\nPlease make sure to read \"!About this folder.txt\" in the same directory before running any of them to understand the consequences of doing so.\n\n## Command-Line Arguments\n\nThe following arguments can be passed to Desktop+ to change its behavior. Most of these are accessible in other ways via the UI and don't need to be used manually.\n\n| Executable        | Argument         | Description |\n| :---------------- | :--------------- | :---------- |\n| DesktopPlus.exe   | --ElevatedMode   | Launch in elevated mode. This is what the elevated task uses.<br>Elevated mode runs as a secondary process taking input from an existing DesktopPlus.exe process and will not work if one isn't already running. |\n| DesktopPlus.exe   | --DoAction [UID] | Execute the action with the specified UID. An action UID can be obtained by pressing [Copy UID to Clipboard] in the [Manage Actions page](#manage-actions-page).<br>This runs as a secondary process that passes the command on and then exits and will not work if the DesktopPlus.exe process isn't already running. |\n| DesktopPlusUI.exe | --DesktopMode    | Force launching in desktop mode. |\n| DesktopPlusUI.exe | --KeyboardEditor | Launch as Keyboard Editor. This argument also forces desktop mode. |\n| Both              | -v [verbosity]   | Set the verbosity level used for logging. Available levels are 0 (default), 1 (enables additional verbose logging), INFO, WARNING, ERROR and OFF. |\n\n### Desktop+ Browser\n\nCommand-line arguments for Chromium Embedded Framework can be set in config.ini via the *CommandLineArguments* key in the *[Browser]* section.\n"
  },
  {
    "path": "src/DesktopPlus/BackgroundOverlay.cpp",
    "content": "#include \"BackgroundOverlay.h\"\n\n#include \"ConfigManager.h\"\n#include \"OutputManager.h\"\n\nBackgroundOverlay::BackgroundOverlay() : m_OvrlHandle(vr::k_ulOverlayHandleInvalid)\n{\n    //Not calling Update() here since the OutputManager typically needs to load the config and OpenVR first\n}\n\nBackgroundOverlay::~BackgroundOverlay()\n{\n    if ((m_OvrlHandle != vr::k_ulOverlayHandleInvalid) && (vr::VROverlay() != nullptr))\n    {\n        vr::VROverlay()->DestroyOverlay(m_OvrlHandle);\n    }\n}\n\nvoid BackgroundOverlay::Update()\n{\n    InterfaceBGColorDisplayMode display_mode = (InterfaceBGColorDisplayMode)ConfigManager::GetValue(configid_int_interface_background_color_display_mode);\n    if (display_mode == ui_bgcolor_dispmode_never)\n    {\n        //Don't keep the overlay around if it's absolutely not needed (which is the case most of the time)\n        if (m_OvrlHandle != vr::k_ulOverlayHandleInvalid)\n        {\n            vr::VROverlay()->DestroyOverlay(m_OvrlHandle);\n            m_OvrlHandle = vr::k_ulOverlayHandleInvalid;\n        }\n    }\n    else\n    {\n        //Create overlay if it doesn't exist yet\n        if (m_OvrlHandle == vr::k_ulOverlayHandleInvalid)\n        {\n            vr::EVROverlayError ovrl_error = vr::VROverlay()->CreateOverlay(\"elvissteinjr.DesktopPlusBackground\", \"Desktop+ Background\", &m_OvrlHandle);\n\n            if (ovrl_error != vr::VROverlayError_None)\n                return;\n\n            //Fill overlay with some pixels\n            unsigned char bytes[2 * 2 * 4];\n            std::fill(std::begin(bytes), std::end(bytes), 255); //Full white RGBA\n\n            vr::VROverlay()->SetOverlayRaw(m_OvrlHandle, bytes, 2, 2, 4);\n\n            //The trick to this overlay is to set it as a panorama attached to the HMD\n            //Panorama overlays are weird in that they essentially poke a hole into the rendering in the area they'd cover normally\n            //By doing this we ensure to always cover the field of view. The actual panorama is rendered in front of scene content, but positioned behind all overlays\n            //IVRCompositor::FadeToColor() can achieve a similar effect, but the colors are washed out. Impossible to achieve full black with it.\n            vr::VROverlay()->SetOverlayFlag(m_OvrlHandle, vr::VROverlayFlags_Panorama, true);\n            vr::VROverlay()->SetOverlayWidthInMeters(m_OvrlHandle, 100.0f);\n\n            Matrix4 transform;\n            transform.setTranslation({0.0f, 0.0f, -10.0f});\n            vr::HmdMatrix34_t transform_openvr = transform.toOpenVR34();\n            vr::VROverlay()->SetOverlayTransformTrackedDeviceRelative(m_OvrlHandle, vr::k_unTrackedDeviceIndex_Hmd, &transform_openvr);\n        }\n\n        bool display_overlay = true; //ui_bgcolor_dispmode_always\n\n        if (display_mode == ui_bgcolor_dispmode_dplustab)\n        {\n            display_overlay = ((OutputManager::Get()) && (OutputManager::Get()->IsDashboardTabActive()));\n        }\n\n        if (display_overlay)\n        {\n            //Unpack color value\n            unsigned int rgba = pun_cast<unsigned int, int>(ConfigManager::GetValue(configid_int_interface_background_color));\n            float r =  (rgba & 0x000000FF)        / 255.0f;\n            float g = ((rgba & 0x0000FF00) >> 8)  / 255.0f;\n            float b = ((rgba & 0x00FF0000) >> 16) / 255.0f;\n            float a = ((rgba & 0xFF000000) >> 24) / 255.0f;\n\n            vr::VROverlay()->SetOverlayColor(m_OvrlHandle, r, g, b);\n            vr::VROverlay()->SetOverlayAlpha(m_OvrlHandle, a);\n\n            if (!vr::VROverlay()->IsOverlayVisible(m_OvrlHandle))\n            {\n                vr::VROverlay()->ShowOverlay(m_OvrlHandle);\n            }\n        }\n        else if (vr::VROverlay()->IsOverlayVisible(m_OvrlHandle))\n        {\n            vr::VROverlay()->HideOverlay(m_OvrlHandle);\n        }\n    }\n}\n"
  },
  {
    "path": "src/DesktopPlus/BackgroundOverlay.h",
    "content": "#pragma once\n\n#include \"openvr.h\"\n\nclass BackgroundOverlay\n{\n    private:\n        vr::VROverlayHandle_t m_OvrlHandle;\n\n    public:\n        BackgroundOverlay();\n        ~BackgroundOverlay();\n        void Update();\n};"
  },
  {
    "path": "src/DesktopPlus/CommonTypes.h",
    "content": "#ifndef _COMMONTYPES_H_\n#define _COMMONTYPES_H_\n\n\n//If NTDDI_WIN11_GA is available, use it (optional requirement by OutputManager)\n//This *could* blow up if one of the few files that include windows.h itself comes first somehow but it's fine for now\n#include <sdkddkver.h>\n#ifdef NTDDI_WIN11_GA\n    #undef NTDDI_VERSION\n    #define NTDDI_VERSION NTDDI_WIN11_GA\n#else\n    #pragma message(\"Using older Windows SDK! Not all Desktop+ features will be available in this build.\")\n#endif\n\n#define NOMINMAX\n#include <windows.h>\n#include <d3d11.h>\n#include <dxgi1_2.h>\n#include <sal.h>\n#include <new>\n#include <warning.h>\n#include <DirectXMath.h>\n#include <string>\n#include <vector>\n\n#include \"DPRect.h\"\n\n#include \"PixelShader.h\"\n#include \"PixelShaderCursor.h\"\n#include \"VertexShader.h\"\n\n#define NUMVERTICES 6\n#define BPP         4\n\n#define OCCLUSION_STATUS_MSG WM_USER\n\nextern HRESULT SystemTransitionsExpectedErrors[];\nextern HRESULT CreateDuplicationExpectedErrors[];\nextern HRESULT FrameInfoExpectedErrors[];\nextern HRESULT AcquireFrameExpectedError[];\nextern HRESULT EnumOutputsExpectedErrors[];\n\ntypedef _Return_type_success_(return == DUPL_RETURN_SUCCESS) enum\n{\n    DUPL_RETURN_SUCCESS             = 0,\n    DUPL_RETURN_ERROR_EXPECTED      = 1,\n    DUPL_RETURN_ERROR_UNEXPECTED    = 2\n}DUPL_RETURN;\n\n//\n// Used by OutputManager::Update(), maps to DUPL_RETURN values where applicable\n//\ntypedef _Return_type_success_(return == DUPL_RETURN_UPD_SUCCESS) enum\n{\n    DUPL_RETURN_UPD_SUCCESS = 0,\n    DUPL_RETURN_UPD_ERROR_EXPECTED = 1,\n    DUPL_RETURN_UPD_ERROR_UNEXPECTED = 2,\n    DUPL_RETURN_UPD_QUIT = 3,\n    DUPL_RETURN_UPD_RETRY = 4,\n    DUPL_RETURN_UPD_SUCCESS_REFRESHED_OVERLAY = 5\n}DUPL_RETURN_UPD;\n\n_Post_satisfies_(return != DUPL_RETURN_SUCCESS)\nDUPL_RETURN ProcessFailure(_In_opt_ ID3D11Device* device, _In_ LPCWSTR str, _In_ LPCWSTR title, HRESULT hr, _In_opt_z_ HRESULT* expected_errors = nullptr);\n\nvoid DisplayMsg(_In_ LPCWSTR str, _In_ LPCWSTR title, HRESULT hr);\n\n//\n// Holds info about the pointer/cursor\n//\ntypedef struct _PTR_INFO\n{\n    std::vector<BYTE> ShapeBuffer;\n    DXGI_OUTDUPL_POINTER_SHAPE_INFO ShapeInfo = {0};\n    POINT Position = {0, 0};\n    bool Visible = false;\n    UINT WhoUpdatedPositionLast = 0;\n    LARGE_INTEGER LastTimeStamp = {0};\n    bool CursorShapeChanged = false;\n} PTR_INFO;\n\n//\n// Structure that holds D3D resources not directly tied to any one thread\n//\ntypedef struct _DX_RESOURCES\n{\n    ID3D11Device* Device;\n    ID3D11DeviceContext* Context;\n    ID3D11VertexShader* VertexShader;\n    ID3D11PixelShader* PixelShader;\n    ID3D11InputLayout* InputLayout;\n    ID3D11SamplerState* Sampler;\n} DX_RESOURCES;\n\n//\n// Structure to pass to a new thread\n//\ntypedef struct _THREAD_DATA\n{\n    // Used to indicate abnormal error condition\n    HANDLE UnexpectedErrorEvent;\n\n    // Used to indicate a transition event occurred e.g. PnpStop, PnpStart, mode change, TDR, desktop switch and the application needs to recreate the duplication interface\n    HANDLE ExpectedErrorEvent;\n\n    HANDLE NewFrameProcessedEvent;\n    HANDLE PauseDuplicationEvent;\n    HANDLE ResumeDuplicationEvent;\n\n    // Used by WinProc to signal to threads to exit\n    HANDLE TerminateThreadsEvent;\n\n    HANDLE TexSharedHandle;\n    UINT Output;\n    INT OffsetX;\n    INT OffsetY;\n    PTR_INFO* PtrInfo;\n    DX_RESOURCES DxRes;\n    DPRect* DirtyRegionTotal;\n    bool WMRIgnoreVScreens;\n} THREAD_DATA;\n\n//\n// FRAME_DATA holds information about an acquired frame\n//\ntypedef struct _FRAME_DATA\n{\n    ID3D11Texture2D* Frame;\n    DXGI_OUTDUPL_FRAME_INFO FrameInfo;\n    _Field_size_bytes_((MoveCount * sizeof(DXGI_OUTDUPL_MOVE_RECT)) + (DirtyCount * sizeof(RECT))) BYTE* MetaData;\n    UINT DirtyCount;\n    UINT MoveCount;\n} FRAME_DATA;\n\n//\n// A vertex with a position and texture coordinate\n//\ntypedef struct _VERTEX\n{\n    DirectX::XMFLOAT3 Pos;\n    DirectX::XMFLOAT2 TexCoord;\n} VERTEX;\n\n#endif\n"
  },
  {
    "path": "src/DesktopPlus/DesktopPlus.cpp",
    "content": "//This code belongs to the Desktop+ OpenVR overlay application, licensed under GPL 3.0\n//\n//Desktop+ is heavily based on the DXGI Desktop Duplication sample code by Microsoft: https://github.com/microsoft/Windows-classic-samples/tree/master/Samples/DXGIDesktopDuplication\n//Much of the code is simply modified or expanded upon it, so structure and many comments are leftovers from that\n\n#include <limits.h>\n#include <fstream>\n#include <sstream>\n#include <system_error>\n\n#include <userenv.h>\n\n#include \"DesktopPlusWinRT.h\"\n#include \"DPBrowserAPIClient.h\"\n\n#include \"Util.h\"\n#include \"DisplayManager.h\"\n#include \"DuplicationManager.h\"\n#include \"OutputManager.h\"\n#include \"WindowManager.h\"\n#include \"ThreadManager.h\"\n#include \"InterprocessMessaging.h\"\n#include \"ElevatedMode.h\"\n#include \"Logging.h\"\n\n// Below are lists of errors expect from Dxgi API calls when a transition event like mode change, PnpStop, PnpStart\n// desktop switch, TDR or session disconnect/reconnect. In all these cases we want the application to clean up the threads that process\n// the desktop updates and attempt to recreate them.\n// If we get an error that is not on the appropriate list then we exit the application\n\n// These are the errors we expect from general Dxgi API due to a transition\nHRESULT SystemTransitionsExpectedErrors[] = {\n                                                DXGI_ERROR_DEVICE_REMOVED,\n                                                DXGI_ERROR_ACCESS_LOST,\n                                                static_cast<HRESULT>(WAIT_ABANDONED),\n                                                S_OK                                    // Terminate list with zero valued HRESULT\n                                            };\n\n// These are the errors we expect from IDXGIOutput1::DuplicateOutput due to a transition\nHRESULT CreateDuplicationExpectedErrors[] = {\n                                                DXGI_ERROR_DEVICE_REMOVED,\n                                                static_cast<HRESULT>(E_ACCESSDENIED),\n                                                DXGI_ERROR_UNSUPPORTED,\n                                                DXGI_ERROR_SESSION_DISCONNECTED,\n                                                S_OK                                    // Terminate list with zero valued HRESULT\n                                            };\n\n// These are the errors we expect from IDXGIOutputDuplication methods due to a transition\nHRESULT FrameInfoExpectedErrors[] = {\n                                        DXGI_ERROR_DEVICE_REMOVED,\n                                        DXGI_ERROR_ACCESS_LOST,\n                                        S_OK                                    // Terminate list with zero valued HRESULT\n                                    };\n\n// These are the errors we expect from IDXGIAdapter::EnumOutputs methods due to outputs becoming stale during a transition\nHRESULT EnumOutputsExpectedErrors[] = {\n                                          DXGI_ERROR_NOT_FOUND,\n                                          S_OK                                    // Terminate list with zero valued HRESULT\n                                      };\n\n\n//\n// Forward Declarations\n//\nDWORD WINAPI CaptureThreadEntry(_In_ void* Param);\nLRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam);\nbool SpawnProcessWithDefaultEnv(LPCWSTR application_name, LPWSTR commandline = nullptr);\nvoid ProcessCmdline(bool& use_elevated_mode, bool& cancel_startup);\nbool DisplayInitError(vr::EVRInitError vr_init_error, vr::EVROverlayError vr_overlay_error, bool vr_input_success);\n\n//\n// Class for progressive waits\n//\ntypedef struct\n{\n    UINT    WaitTime;\n    UINT    WaitCount;\n}WAIT_BAND;\n\n#define WAIT_BAND_COUNT 5\n#define WAIT_BAND_STOP 0\n\nclass DYNAMIC_WAIT\n{\n    public :\n        DYNAMIC_WAIT();\n        ~DYNAMIC_WAIT();\n\n        void Wait();\n\n    private :\n\n    static const WAIT_BAND   m_WaitBands[WAIT_BAND_COUNT];\n\n    // Period in seconds that a new wait call is considered part of the same wait sequence\n    static const UINT       m_WaitSequenceTimeInSeconds = 2;\n\n    UINT                    m_CurrentWaitBandIdx;\n    UINT                    m_WaitCountInCurrentBand;\n    LARGE_INTEGER           m_QPCFrequency;\n    LARGE_INTEGER           m_LastWakeUpTime;\n    BOOL                    m_QPCValid;\n};\nconst WAIT_BAND DYNAMIC_WAIT::m_WaitBands[WAIT_BAND_COUNT] = {\n                                                                 {10, 40},\n                                                                 {50, 20},\n                                                                 {250, 20},\n                                                                 {2000, 60},\n                                                                 {5000, WAIT_BAND_STOP}   // Never move past this band\n                                                             };\n\nDYNAMIC_WAIT::DYNAMIC_WAIT() : m_CurrentWaitBandIdx(0), m_WaitCountInCurrentBand(0)\n{\n    m_QPCValid = QueryPerformanceFrequency(&m_QPCFrequency);\n    m_LastWakeUpTime.QuadPart = 0L;\n}\n\nDYNAMIC_WAIT::~DYNAMIC_WAIT()\n{\n}\n\nvoid DYNAMIC_WAIT::Wait()\n{\n    LARGE_INTEGER CurrentQPC = {0};\n\n    // Is this wait being called with the period that we consider it to be part of the same wait sequence\n    QueryPerformanceCounter(&CurrentQPC);\n    if (m_QPCValid && (CurrentQPC.QuadPart <= (m_LastWakeUpTime.QuadPart + (m_QPCFrequency.QuadPart * m_WaitSequenceTimeInSeconds))))\n    {\n        // We are still in the same wait sequence, lets check if we should move to the next band\n        if ((m_WaitBands[m_CurrentWaitBandIdx].WaitCount != WAIT_BAND_STOP) && (m_WaitCountInCurrentBand > m_WaitBands[m_CurrentWaitBandIdx].WaitCount))\n        {\n            m_CurrentWaitBandIdx++;\n            m_WaitCountInCurrentBand = 0;\n        }\n    }\n    else\n    {\n        // Either we could not get the current time or we are starting a new wait sequence\n        m_WaitCountInCurrentBand = 0;\n        m_CurrentWaitBandIdx = 0;\n    }\n\n    // Sleep for the required period of time\n    Sleep(m_WaitBands[m_CurrentWaitBandIdx].WaitTime);\n\n    // Record the time we woke up so we can detect wait sequences\n    QueryPerformanceCounter(&m_LastWakeUpTime);\n    m_WaitCountInCurrentBand++;\n}\n\n\n//\n// Program entry point\n//\nint WINAPI WinMain(_In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPrevInstance, _In_ LPSTR lpCmdLine, _In_ INT nCmdShow)\n{\n    UNREFERENCED_PARAMETER(hPrevInstance);\n    UNREFERENCED_PARAMETER(lpCmdLine);\n\n    bool use_elevated_mode = false;\n    bool cancel_startup = false;\n    ProcessCmdline(use_elevated_mode, cancel_startup);\n\n    if (use_elevated_mode)\n    {\n        //Pass all control to eleveated mode and exit when we're done there\n        return ElevatedModeEnter(hInstance);\n    }\n    else if (cancel_startup)\n    {\n        //Command line contained a one-off command sent to existing instances, exit\n        return 0;\n    }\n\n    DPLog_Init(\"DesktopPlus\");\n\n    INT SingleOutput = 0;\n\n    // Synchronization\n    HANDLE UnexpectedErrorEvent   = nullptr;\n    HANDLE ExpectedErrorEvent     = nullptr;\n    HANDLE NewFrameProcessedEvent = nullptr;\n    HANDLE PauseDuplicationEvent  = nullptr;\n    HANDLE ResumeDuplicationEvent = nullptr;\n    HANDLE TerminateThreadsEvent  = nullptr;\n\n    // Window\n    HWND WindowHandle = nullptr;\n\n    //Make sure only one instance is running\n    StopProcessByWindowClass(g_WindowClassNameDashboardApp);\n\n    // Event used by the threads to signal an unexpected error and we want to quit the app\n    UnexpectedErrorEvent = ::CreateEvent(nullptr, TRUE, FALSE, nullptr);\n    if (!UnexpectedErrorEvent)\n    {\n        ProcessFailure(nullptr, L\"UnexpectedErrorEvent creation failed\", L\"Desktop+ Error\", E_UNEXPECTED);\n        return 0;\n    }\n\n    // Event for when a thread encounters an expected error\n    ExpectedErrorEvent = ::CreateEvent(nullptr, TRUE, FALSE, nullptr);\n    if (!ExpectedErrorEvent)\n    {\n        ProcessFailure(nullptr, L\"ExpectedErrorEvent creation failed\", L\"Desktop+ Error\", E_UNEXPECTED);\n        return 0;\n    }\n\n    // Event for when a thread succeeded in processing a frame\n    NewFrameProcessedEvent = ::CreateEvent(nullptr, TRUE, FALSE, nullptr);\n    if (!NewFrameProcessedEvent)\n    {\n        ProcessFailure(nullptr, L\"NewFrameProcessedEvent creation failed\", L\"Desktop+ Error\", E_UNEXPECTED);\n        return 0;\n    }\n\n    //Event to tell threads to pause duplication (signaled by default)\n    PauseDuplicationEvent = ::CreateEvent(nullptr, TRUE, TRUE, nullptr);\n    if (!PauseDuplicationEvent)\n    {\n        ProcessFailure(nullptr, L\"PauseDuplicationEvent creation failed\", L\"Desktop+ Error\", E_UNEXPECTED);\n        return 0;\n    }\n\n    //Event to tell threads to resume duplication\n    ResumeDuplicationEvent = ::CreateEvent(nullptr, TRUE, FALSE, nullptr);\n    if (!ResumeDuplicationEvent)\n    {\n        ProcessFailure(nullptr, L\"ResumeDuplicationEvent creation failed\", L\"Desktop+ Error\", E_UNEXPECTED);\n        return 0;\n    }\n\n    // Event to tell spawned threads to quit\n    TerminateThreadsEvent = ::CreateEvent(nullptr, TRUE, FALSE, nullptr);\n    if (!TerminateThreadsEvent)\n    {\n        ProcessFailure(nullptr, L\"TerminateThreadsEvent creation failed\", L\"Desktop+ Error\", E_UNEXPECTED);\n        return 0;\n    }\n\n    // Register class\n    WNDCLASSEXW Wc;\n    Wc.cbSize           = sizeof(WNDCLASSEXW);\n    Wc.style            = 0;\n    Wc.lpfnWndProc      = WndProc;\n    Wc.cbClsExtra       = 0;\n    Wc.cbWndExtra       = 0;\n    Wc.hInstance        = hInstance;\n    Wc.hIcon            = nullptr;\n    Wc.hCursor          = nullptr;\n    Wc.hbrBackground    = nullptr;\n    Wc.lpszMenuName     = nullptr;\n    Wc.lpszClassName    = g_WindowClassNameDashboardApp;\n    Wc.hIconSm          = nullptr;\n    if (!RegisterClassExW(&Wc))\n    {\n        ProcessFailure(nullptr, L\"Window class registration failed\", L\"Desktop+ Error\", E_UNEXPECTED);\n        return 0;\n    }\n\n    // Create window\n    WindowHandle = ::CreateWindowW(g_WindowClassNameDashboardApp, L\"Desktop+ Overlay\",\n                                   0,\n                                   0, 0,\n                                   1, 1,\n                                   HWND_DESKTOP, nullptr, hInstance, nullptr);\n    if (!WindowHandle)\n    {\n        ProcessFailure(nullptr, L\"Window creation failed\", L\"Desktop+ Error\", E_FAIL);\n        return 0;\n    }\n\n    //Init WinRT DLL\n    DPWinRT_Init();\n    DPLog_DPWinRT_SupportInfo();\n    LOG_F(INFO, \"Loaded WinRT library\");\n\n    //Init BrowserClientAPI (this doesn't start the browser process, only checks for presence)\n    DPBrowserAPIClient::Get().Init();\n\n    //Allow IPC messages even when elevated\n    IPCManager::Get().DisableUIPForRegisteredMessages(WindowHandle);\n\n    THREADMANAGER ThreadMgr;\n    OutputManager OutMgr(PauseDuplicationEvent, ResumeDuplicationEvent);\n    RECT DeskBounds;\n    UINT OutputCount;\n\n    //Start up UI process unless disabled or already running\n    if ( (!ConfigManager::GetValue(configid_bool_interface_no_ui)) && (!IPCManager::IsUIAppRunning()) )\n    {\n        std::wstring path = WStringConvertFromUTF8(ConfigManager::Get().GetApplicationPath().c_str()) + L\"DesktopPlusUI.exe\";\n        SpawnProcessWithDefaultEnv(path.c_str());\n\n        LOG_F(INFO, \"Launched Desktop+ UI process\");\n    }\n\n    //Message loop\n    MSG msg = {0};\n    DUPL_RETURN Ret = DUPL_RETURN_SUCCESS;\n    DUPL_RETURN_UPD RetUpdate = DUPL_RETURN_UPD_SUCCESS;\n    bool FirstTime = true;\n\n    DYNAMIC_WAIT DynamicWait;\n\n    LARGE_INTEGER UpdateLimiterStartingTime, UpdateLimiterEndingTime, UpdateLimiterElapsedMicroseconds;\n    LARGE_INTEGER UpdateLimiterFrequency;\n\n    bool IsNewFrame = false;\n    bool SkipFrame = false;\n\n    ::QueryPerformanceFrequency(&UpdateLimiterFrequency);\n    ::QueryPerformanceCounter(&UpdateLimiterStartingTime);\n\n    while (WM_QUIT != msg.message)\n    {\n        if ((!FirstTime) && (PeekMessage(&msg, nullptr, 0, 0, PM_REMOVE)))  //Wait for init before processing messages\n        {\n            // Process window messages\n            if (msg.message >= 0xC000)  //Custom message from UI process, handle in output manager (WM_COPYDATA is handled in WndProc())\n            {\n                if (OutMgr.HandleIPCMessage(msg))\n                {\n                    SetEvent(ExpectedErrorEvent);\n                }\n            }\n            else if (msg.message >= WM_DPLUSWINRT) //WinRT library messages\n            {\n                OutMgr.HandleWinRTMessage(msg);\n            }\n            else if (msg.message == WM_HOTKEY)\n            {\n                OutMgr.HandleHotkeyMessage(msg);\n            }\n            else\n            {\n                TranslateMessage(&msg);\n                DispatchMessage(&msg);\n            }\n        }\n        else if (WaitForSingleObjectEx(UnexpectedErrorEvent, 0, FALSE) == WAIT_OBJECT_0)\n        {\n            // Unexpected error occurred so exit the application\n            break;\n        }\n        else if ((FirstTime) || (WaitForSingleObjectEx(ExpectedErrorEvent, 0, FALSE) == WAIT_OBJECT_0))\n        {\n            if (!FirstTime)\n            {\n                LOG_F(INFO, \"System transition occured, reinitializing...\");\n\n                // Terminate other threads\n                ResetEvent(PauseDuplicationEvent);\n                SetEvent(ResumeDuplicationEvent);\n                SetEvent(TerminateThreadsEvent);\n                ThreadMgr.WaitForThreadTermination();\n                ResetEvent(TerminateThreadsEvent);\n                ResetEvent(ExpectedErrorEvent);\n                ResetEvent(NewFrameProcessedEvent);\n                ResetEvent(ResumeDuplicationEvent);\n\n                // Clean up\n                ThreadMgr.Clean();\n                OutMgr.CleanRefs();\n\n                // As we have encountered an error due to a system transition we wait before trying again, using this dynamic wait\n                // the wait periods will get progressively long to avoid wasting too much system resource if this state lasts a long time\n                DynamicWait.Wait();\n            }\n\n            // Re-initialize\n            vr::EVRInitError vr_init_error = vr::VRInitError_None;\n            if (FirstTime) //InitOutput() needs OpenVR to be initialized already\n            {\n                auto init_error = OutMgr.InitOverlay();\n\n                //Display error message if init error states indicates one\n                if (DisplayInitError(std::get<vr::EVRInitError>(init_error), std::get<vr::EVROverlayError>(init_error), std::get<bool>(init_error)))\n                {\n                    //An error message was displayed, abort\n                    Ret = DUPL_RETURN_ERROR_UNEXPECTED;\n                    break;\n                }\n\n                if ( (ConfigManager::GetValue(configid_bool_misc_no_steam)) && (ConfigManager::GetValue(configid_bool_state_misc_process_started_by_steam)) )\n                {\n                    //Was started by Steam but running through it was turned off, so restart without\n                    std::wstring path = WStringConvertFromUTF8(ConfigManager::Get().GetApplicationPath().c_str()) + L\"DesktopPlus.exe\";\n                    SpawnProcessWithDefaultEnv(path.c_str());\n\n                    break;\n                }\n            }\n\n            Ret = OutMgr.InitOutput(WindowHandle, SingleOutput, &OutputCount, &DeskBounds);\n            if (Ret == DUPL_RETURN_SUCCESS)\n            {\n                HANDLE SharedHandle = OutMgr.GetSharedHandle();\n                if (SharedHandle)\n                {\n                    Ret = ThreadMgr.Initialize(SingleOutput, OutputCount, UnexpectedErrorEvent, ExpectedErrorEvent, NewFrameProcessedEvent, PauseDuplicationEvent,\n                                               ResumeDuplicationEvent, TerminateThreadsEvent, SharedHandle, &DeskBounds, OutMgr.GetDXGIAdapter(), \n                                               (ConfigManager::GetValue(configid_int_interface_wmr_ignore_vscreens) == 1));\n                }\n                else\n                {\n                    DisplayMsg(L\"Failed to get handle of shared surface\", L\"Desktop+ Error\", E_FAIL);\n\n                    Ret = DUPL_RETURN_ERROR_UNEXPECTED;\n                }\n\n                if (FirstTime)\n                {\n                    // First time through the loop\n                    FirstTime = false;\n                }\n                else\n                {\n                    ForceScreenRefresh();\n                }\n            }\n            else if (Ret == DUPL_RETURN_ERROR_EXPECTED)\n            {\n                if (OutputCount == 0) //No outputs right now, oops\n                {\n                    OutMgr.SetOutputInvalid();\n                    Ret = DUPL_RETURN_SUCCESS; //Entered \"valid\" state now, prevent auto-retry to needlessly kick in\n                }\n\n                FirstTime = false;\n            }\n\n        }\n        else //Present frame or handle events as fast as needed\n        {\n            if (WaitForSingleObjectEx(NewFrameProcessedEvent, OutMgr.GetMaxRefreshDelay(), FALSE) == WAIT_OBJECT_0)   //New frame\n            {\n                ResetEvent(NewFrameProcessedEvent);\n                IsNewFrame = true;\n            }\n            else\n            {\n                IsNewFrame = (RetUpdate == DUPL_RETURN_UPD_RETRY); //Retry is treated as if it's new frame, otherwise false\n            }\n\n            //Update limiter/skipper\n            const LARGE_INTEGER& limiter_delay = OutMgr.GetUpdateLimiterDelay();\n            bool update_limiter_active = (limiter_delay.QuadPart != 0);\n            if (update_limiter_active)\n            {\n                QueryPerformanceCounter(&UpdateLimiterEndingTime);\n                UpdateLimiterElapsedMicroseconds.QuadPart = UpdateLimiterEndingTime.QuadPart - UpdateLimiterStartingTime.QuadPart;\n\n                UpdateLimiterElapsedMicroseconds.QuadPart *= 1000000;\n                UpdateLimiterElapsedMicroseconds.QuadPart /= UpdateLimiterFrequency.QuadPart;\n\n                SkipFrame = (UpdateLimiterElapsedMicroseconds.QuadPart < limiter_delay.QuadPart);\n            }\n            else\n            {\n                SkipFrame = false;\n            }\n\n            RetUpdate = OutMgr.Update(ThreadMgr.GetPointerInfo(), ThreadMgr.GetDirtyRegionTotal(), IsNewFrame, SkipFrame);\n\n            //Map return value to DUPL_RETRUN Ret\n            switch (RetUpdate)\n            {\n                case DUPL_RETURN_UPD_QUIT:                      Ret = DUPL_RETURN_ERROR_UNEXPECTED; break;\n                case DUPL_RETURN_UPD_RETRY:                     Ret = DUPL_RETURN_SUCCESS;          break;\n                case DUPL_RETURN_UPD_SUCCESS_REFRESHED_OVERLAY: Ret = DUPL_RETURN_SUCCESS;          break;\n                default:                                        Ret = (DUPL_RETURN)RetUpdate;\n            }\n\n            if ( (RetUpdate == DUPL_RETURN_UPD_SUCCESS_REFRESHED_OVERLAY) && (update_limiter_active) )\n            {\n                QueryPerformanceCounter(&UpdateLimiterStartingTime);\n            }\n\n            OutMgr.UpdatePerformanceStates();\n        }\n\n        // Check if for errors\n        if (Ret != DUPL_RETURN_SUCCESS)\n        {\n            if (Ret == DUPL_RETURN_ERROR_EXPECTED)\n            {\n                // Some type of system transition is occurring so retry\n                SetEvent(ExpectedErrorEvent);\n            }\n            else\n            {\n                // Unexpected error or exit event, so exit\n                break;\n            }\n        }\n    }\n\n    LOG_F(INFO, \"Shutting down...\");\n\n    //Remove all overlays since they may access things on destruction after we're shut down otherwise\n    OverlayManager::Get().RemoveAllOverlays();\n\n    //Quit Browser processes if they're running (we do this early since the browser doesn't poll for VREvent_Quit itself)\n    DPBrowserAPIClient::Get().Quit();\n\n    // Make sure all other threads have exited\n    if (SetEvent(TerminateThreadsEvent))\n    {\n        //Wake them up first if needed\n        ResetEvent(PauseDuplicationEvent);\n        SetEvent(ResumeDuplicationEvent);\n\n        ThreadMgr.WaitForThreadTermination();\n    }\n\n    // Clean up\n    CloseHandle(UnexpectedErrorEvent);\n    CloseHandle(ExpectedErrorEvent);\n    CloseHandle(NewFrameProcessedEvent);\n    CloseHandle(PauseDuplicationEvent);\n    CloseHandle(ResumeDuplicationEvent);\n    CloseHandle(TerminateThreadsEvent);\n\n    //Tell WindowManager to exit if it's still active\n    WindowManager::Get().ClearTempTopMostWindow();\n    WindowManager::Get().SetActive(false);\n\n    //Do other shutdown steps, like undimming the dashboard if needed\n    OutMgr.OnExit();\n\n    //Kindly ask elevated mode process to quit if it exists\n    if (HWND window = ::FindWindow(g_WindowClassNameElevatedMode, nullptr))\n    {\n        ::PostMessage(window, WM_QUIT, 0, 0);\n    }\n\n    if (msg.message == WM_QUIT)\n    {\n        // For a WM_QUIT message we should return the wParam value\n        return static_cast<INT>(msg.wParam);\n    }\n\n    return 0;\n}\n\n//\n// Window message processor\n//\nLRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)\n{\n    switch (message)\n    {\n        case WM_COPYDATA:\n        {\n            //Forward to output manager\n            if (OutputManager::Get())\n            {\n                bool mirror_reset_required = false;\n\n                MSG msg;\n                // Process all custom window messages posted before this\n                while (PeekMessage(&msg, nullptr, 0xC000, 0xFFFF, PM_REMOVE))\n                {\n                    //Skip mirror reset messages here to prevent an endless loop\n                    if ((IPCManager::Get().GetIPCMessageID(msg.message) == ipcmsg_action) && (msg.wParam == ipcact_mirror_reset))\n                    {\n                        continue;\n                    }\n\n                    if (OutputManager::Get()->HandleIPCMessage(msg))\n                    {\n                        mirror_reset_required = true;\n                    }\n                }\n\n                \n                msg.hwnd = hWnd;\n                msg.message = message;\n                msg.wParam = wParam;\n                msg.lParam = lParam;\n\n                if (OutputManager::Get()->HandleIPCMessage(msg))\n                {\n                    mirror_reset_required = true;\n                }\n\n                if (mirror_reset_required)\n                {\n                    IPCManager::Get().PostMessageToDashboardApp(ipcmsg_action, ipcact_mirror_reset);\n                }\n            }\n            break;\n        }\n        case WM_DISPLAYCHANGE:\n        {\n            //Update desktop count and rects\n            if (OutputManager::Get())\n            {\n                LOG_F(INFO, \"WM_DISPLAYCHANGE recieved, re-enumerating outputs...\");\n                OutputManager::Get()->EnumerateOutputs();\n            }\n            break;\n        }\n        case WM_DESTROY:\n        {\n            PostQuitMessage(0);\n            break;\n        }\n        default:\n            return DefWindowProc(hWnd, message, wParam, lParam);\n    }\n\n    return 0;\n}\n\nbool SpawnProcessWithDefaultEnv(LPCWSTR application_name, LPWSTR commandline)\n{\n    LPVOID env = nullptr;\n    STARTUPINFO si = {0};\n    PROCESS_INFORMATION pi = {0};\n    si.cb = sizeof(si);\n\n    //Use a new environment block since we don't want to copy the Steam environment variables if there are any\n    if (::CreateEnvironmentBlock(&env, ::GetCurrentProcessToken(), FALSE))\n    {\n        bool ret = ::CreateProcess(application_name, commandline, nullptr, nullptr, FALSE, CREATE_UNICODE_ENVIRONMENT, env, nullptr, &si, &pi);\n\n        //We don't care about these, so close right away\n        ::CloseHandle(pi.hProcess);\n        ::CloseHandle(pi.hThread);\n\n        ::DestroyEnvironmentBlock(env);\n\n        return ret;\n    }\n\n    return false;\n}\n\nvoid ProcessCmdline(bool& use_elevated_mode, bool& cancel_startup)\n{\n    //__argv and __argc are global vars set by system\n    for (UINT i = 0; i < static_cast<UINT>(__argc); ++i)\n    {\n        if ((strcmp(__argv[i], \"-ElevatedMode\")  == 0) ||\n            (strcmp(__argv[i], \"--ElevatedMode\") == 0) ||\n            (strcmp(__argv[i], \"/ElevatedMode\")  == 0))\n        {\n            use_elevated_mode = true;\n        }\n        else if ((strcmp(__argv[i], \"-DoAction\")  == 0) ||\n                 (strcmp(__argv[i], \"--DoAction\") == 0) ||\n                 (strcmp(__argv[i], \"/DoAction\")  == 0))\n        {\n            //Take the following argument and parse it as ActionUID to send it to the running dashboard app instance\n            if (__argc > i + 1)\n            {\n                IPCManager::Get().PostMessageToDashboardApp(ipcmsg_action, ipcact_action_do, std::strtoull(__argv[i+1], nullptr, 10));\n            }\n\n            cancel_startup = true;\n        }\n    }\n}\n\nbool DisplayInitError(vr::EVRInitError vr_init_error, vr::EVROverlayError vr_overlay_error, bool vr_input_success)\n{\n    if (vr_init_error != vr::VRInitError_None)\n    {\n        if ( (vr_init_error == vr::VRInitError_Init_HmdNotFound) || (vr_init_error == vr::VRInitError_Init_HmdNotFoundPresenceFailed) )\n        {\n            DisplayMsg(L\"Failed to init OpenVR: HMD not found.\\nRe-launch application with a HMD connected.\", L\"Desktop+ Error\", E_FAIL);\n        }\n        else if (vr_init_error == vr::VRInitError_Init_InvalidInterface)\n        {\n            DisplayMsg(L\"Failed to init OpenVR: Invalid Interface.\\nMake sure to have the latest version of SteamVR installed.\", L\"Desktop+ Error\", E_FAIL);\n        }\n        else if (vr_init_error != vr::VRInitError_Init_InitCanceledByUser) //Exclude canceled, supposed to be always silent exit\n        {\n            std::wstring error_str = L\"Failed to init OpenVR: \";\n            error_str += WStringConvertFromUTF8(vr::VR_GetVRInitErrorAsEnglishDescription(vr_init_error));\n            error_str += L\".\";\n\n            DisplayMsg(error_str.c_str(), L\"Desktop+ Error\", E_FAIL);\n        }\n\n        return true;\n    }\n\n    if (vr_overlay_error != vr::VROverlayError_None)\n    {\n        std::wstring error_str = L\"Failed to init overlay: \";\n        error_str += WStringConvertFromUTF8(vr::VROverlay()->GetOverlayErrorNameFromEnum(vr_overlay_error));\n        error_str += L\".\";\n\n        DisplayMsg(error_str.c_str(), L\"Desktop+ Error\", E_FAIL);\n\n        return true;\n    }\n\n    if (!vr_input_success)\n    {\n        //VRInput not working is bad, but doesn't stop us from running, so just log it\n        LOG_F(WARNING, \"Failed to load VRInput action manifest. Some input-related functionality will not be available\");\n    }\n\n    return false;\n}\n\n//\n// Entry point for new duplication threads\n//\nDWORD WINAPI CaptureThreadEntry(_In_ void* Param)\n{\n    // Classes\n    DISPLAYMANAGER DispMgr;\n    DUPLICATIONMANAGER DuplMgr;\n\n    // D3D objects\n    ID3D11Texture2D* SharedSurf = nullptr;\n    IDXGIKeyedMutex* KeyMutex = nullptr;\n\n    // Data passed in from thread creation\n    THREAD_DATA* TData = reinterpret_cast<THREAD_DATA*>(Param);\n\n    // Get desktop\n    DUPL_RETURN Ret;\n    HDESK CurrentDesktop = nullptr;\n    CurrentDesktop = OpenInputDesktop(0, FALSE, GENERIC_ALL);\n    if (!CurrentDesktop)\n    {\n        // We do not have access to the desktop so request a retry\n        SetEvent(TData->ExpectedErrorEvent);\n        Ret = DUPL_RETURN_ERROR_EXPECTED;\n        goto Exit;\n    }\n\n    // Attach desktop to this thread\n    bool DesktopAttached = SetThreadDesktop(CurrentDesktop) != 0;\n    CloseDesktop(CurrentDesktop);\n    CurrentDesktop = nullptr;\n    if (!DesktopAttached)\n    {\n        // We do not have access to the desktop so request a retry\n        Ret = DUPL_RETURN_ERROR_EXPECTED;\n        goto Exit;\n    }\n\n    // New display manager\n    DispMgr.InitD3D(&TData->DxRes);\n\n    // Obtain handle to sync shared Surface\n    HRESULT hr = TData->DxRes.Device->OpenSharedResource(TData->TexSharedHandle, __uuidof(ID3D11Texture2D), reinterpret_cast<void**>(&SharedSurf));\n    if (FAILED (hr))\n    {\n        Ret = ProcessFailure(TData->DxRes.Device, L\"Opening shared texture failed\", L\"Desktop+ Error\", hr, SystemTransitionsExpectedErrors);\n        goto Exit;\n    }\n\n    hr = SharedSurf->QueryInterface(__uuidof(IDXGIKeyedMutex), reinterpret_cast<void**>(&KeyMutex));\n    if (FAILED(hr))\n    {\n        Ret = ProcessFailure(nullptr, L\"Failed to get keyed mutex interface in spawned thread\", L\"Desktop+ Error\", hr);\n        goto Exit;\n    }\n\n    D3D11_TEXTURE2D_DESC SharedSurfDesc;\n    SharedSurf->GetDesc(&SharedSurfDesc);\n\n    // Make duplication manager\n    Ret = DuplMgr.InitDupl(TData->DxRes.Device, TData->Output, TData->WMRIgnoreVScreens, SharedSurfDesc.Format != DXGI_FORMAT_B8G8R8A8_UNORM);\n    if (Ret != DUPL_RETURN_SUCCESS)\n    {\n        goto Exit;\n    }\n\n    // Get output description\n    DXGI_OUTPUT_DESC DesktopDesc;\n    RtlZeroMemory(&DesktopDesc, sizeof(DXGI_OUTPUT_DESC));\n    DuplMgr.GetOutputDesc(&DesktopDesc);\n\n    // Main duplication loop\n    bool WaitToProcessCurrentFrame = false;\n    FRAME_DATA CurrentData;\n\n    while ((WaitForSingleObjectEx(TData->TerminateThreadsEvent, 0, FALSE) == WAIT_TIMEOUT))\n    {\n        //Wait if pause event was signaled\n        if ((WaitForSingleObjectEx(TData->PauseDuplicationEvent, 0, FALSE) == WAIT_OBJECT_0))\n        {\n            WaitForSingleObjectEx(TData->ResumeDuplicationEvent, INFINITE, FALSE); //Wait forever. Thread shutdown will also signal resume\n        }\n\n        if (!WaitToProcessCurrentFrame)\n        {\n            // Get new frame from desktop duplication\n            bool TimeOut;\n            Ret = DuplMgr.GetFrame(&CurrentData, &TimeOut);\n            if (Ret != DUPL_RETURN_SUCCESS)\n            {\n                // An error occurred getting the next frame drop out of loop which\n                // will check if it was expected or not\n                break;\n            }\n\n            // Check for timeout\n            if (TimeOut)\n            {\n                // No new frame at the moment\n                continue;\n            }\n        }\n\n        // We have a new frame so try and process it\n        // Try to acquire keyed mutex in order to access shared surface\n        hr = KeyMutex->AcquireSync(0, 1000);\n        if (hr == static_cast<HRESULT>(WAIT_TIMEOUT))\n        {\n            // Can't use shared surface right now, try again later\n            WaitToProcessCurrentFrame = true;\n            continue;\n        }\n        else if (FAILED(hr))\n        {\n            // Generic unknown failure\n            Ret = ProcessFailure(TData->DxRes.Device, L\"Unexpected error acquiring keyed mutex\", L\"Desktop+ Error\", hr, SystemTransitionsExpectedErrors);\n            DuplMgr.DoneWithFrame();\n            break;\n        }\n\n        // We can now process the current frame\n        WaitToProcessCurrentFrame = false;\n\n        // Get mouse info\n        Ret = DuplMgr.GetMouse(TData->PtrInfo, &(CurrentData.FrameInfo), TData->OffsetX, TData->OffsetY);\n        if (Ret != DUPL_RETURN_SUCCESS)\n        {\n            DuplMgr.DoneWithFrame();\n            KeyMutex->ReleaseSync(1);\n            break;\n        }\n\n        // Process new frame\n        Ret = DispMgr.ProcessFrame(&CurrentData, SharedSurf, TData->OffsetX, TData->OffsetY, &DesktopDesc, *TData->DirtyRegionTotal);\n        if (Ret != DUPL_RETURN_SUCCESS)\n        {\n            DuplMgr.DoneWithFrame();\n            KeyMutex->ReleaseSync(1);\n            SetEvent(TData->NewFrameProcessedEvent);\n            break;\n        }\n\n        // Release acquired keyed mutex\n        hr = KeyMutex->ReleaseSync(1);\n        if (FAILED(hr))\n        {\n            Ret = ProcessFailure(TData->DxRes.Device, L\"Unexpected error releasing the keyed mutex\", L\"Desktop+ Error\", hr, SystemTransitionsExpectedErrors);\n            DuplMgr.DoneWithFrame();\n            break;\n        }\n\n        // Release frame back to desktop duplication\n        Ret = DuplMgr.DoneWithFrame();\n        if (Ret != DUPL_RETURN_SUCCESS)\n        {\n            break;\n        }\n\n        SetEvent(TData->NewFrameProcessedEvent);\n    }\n\nExit:\n    if (Ret != DUPL_RETURN_SUCCESS)\n    {\n        if (Ret == DUPL_RETURN_ERROR_EXPECTED)\n        {\n            // The system is in a transition state so request the duplication be restarted\n            SetEvent(TData->ExpectedErrorEvent);\n        }\n        else\n        {\n            // Unexpected error so exit the application\n            SetEvent(TData->UnexpectedErrorEvent);\n        }\n    }\n\n    if (SharedSurf)\n    {\n        SharedSurf->Release();\n        SharedSurf = nullptr;\n    }\n\n    if (KeyMutex)\n    {\n        KeyMutex->Release();\n        KeyMutex = nullptr;\n    }\n\n    return 0;\n}\n\n_Post_satisfies_(return != DUPL_RETURN_SUCCESS)\nDUPL_RETURN ProcessFailure(_In_opt_ ID3D11Device* device, _In_ LPCWSTR str, _In_ LPCWSTR title, HRESULT hr, _In_opt_z_ HRESULT* expected_errors)\n{\n    HRESULT translated_hr;\n\n    // On an error check if the DX device is lost\n    if (device)\n    {\n        HRESULT device_removed_reason = device->GetDeviceRemovedReason();\n\n        switch (device_removed_reason)\n        {\n            case DXGI_ERROR_DEVICE_REMOVED:\n            case DXGI_ERROR_DEVICE_RESET:\n            case static_cast<HRESULT>(E_OUTOFMEMORY):\n            {\n                // Our device has been stopped due to an external event on the GPU so map them all to\n                // device removed and continue processing the condition\n                translated_hr = DXGI_ERROR_DEVICE_REMOVED;\n                break;\n            }\n            case S_OK:\n            {\n                // Device is not removed so use original error\n                translated_hr = hr;\n                break;\n            }\n            default:\n            {\n                // Device is removed but not a error we want to remap\n                translated_hr = device_removed_reason;\n            }\n        }\n    }\n    else\n    {\n        translated_hr = hr;\n    }\n\n    // Check if this error was expected or not\n    if (expected_errors)\n    {\n        HRESULT* current_result = expected_errors;\n\n        while (*current_result != S_OK)\n        {\n            if (*(current_result++) == translated_hr)\n            {\n                return DUPL_RETURN_ERROR_EXPECTED;\n            }\n        }\n    }\n\n    // Error was not expected so display the message box\n    DisplayMsg(str, title, translated_hr);\n\n    return DUPL_RETURN_ERROR_UNEXPECTED;\n}\n\n//\n// Displays a message\n//\nvoid DisplayMsg(_In_ LPCWSTR str, _In_ LPCWSTR title, HRESULT hr)\n{\n    //Generate a proper error message with description from the OS, unless it's E_FAIL which is unspecified anyways and we use it for our own errors passed to this function\n    std::wstringstream ss;\n\n    if (hr != E_FAIL)\n    {\n        std::error_code ec(hr, std::system_category());\n        ss << str << L\":\\n\" << WStringConvertFromLocalEncoding(ec.message().c_str()) << L\" (0x\" << std::hex << std::setfill(L'0') << std::setw(8) << hr << L\")\";\n    }\n    else\n    {\n        ss << str;\n    }\n\n    std::string str_u8 = StringConvertFromUTF16(ss.str().c_str());\n\n    //Try having the UI app display it if it's an error\n    if (!SUCCEEDED(hr))\n    {\n        HWND window = (OutputManager::Get() != nullptr) ? OutputManager::Get()->GetWindowHandle() : nullptr;\n        IPCManager::Get().SendStringToUIApp(configid_str_state_dashboard_error_string, str_u8.c_str(), window);\n    }\n\n    //Get rid of newlines before logging (system error strings comes with CRLF while \\n is treated as LF-only before writing to file...)\n    StringReplaceAll(str_u8, \"\\r\\n\", \" \");\n    StringReplaceAll(str_u8, \"\\n\",   \" \");\n\n    VLOG_F((SUCCEEDED(hr)) ? loguru::Verbosity_INFO : loguru::Verbosity_ERROR, str_u8.c_str());\n\n    //While we always try to send error messages to the UI app to have it display in VR, show the message on the desktop if UI is not running or VR isn't loaded\n    if ((!IPCManager::IsUIAppRunning()) || (vr::VROverlay() == nullptr))\n    {\n        ::MessageBoxW(nullptr, str, title, MB_OK);\n    }\n}\n"
  },
  {
    "path": "src/DesktopPlus/DesktopPlus.manifest",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>\n<assembly xmlns=\"urn:schemas-microsoft-com:asm.v1\" manifestVersion=\"1.0\">\n<dependency>\n    <dependentAssembly>\n        <assemblyIdentity\n            type=\"win32\"\n            name=\"Microsoft.Windows.Common-Controls\"\n            version=\"6.0.0.0\"\n            processorArchitecture=\"*\"\n            publicKeyToken=\"6595b64144ccf1df\"\n            language=\"*\"\n        />\n    </dependentAssembly>\n</dependency>\n</assembly>\n"
  },
  {
    "path": "src/DesktopPlus/DesktopPlus.ruleset",
    "content": "﻿<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<RuleSet Name=\"Rules for DesktopPlus\" Description=\"Code analysis rules for DesktopPlus.vcxproj.\" ToolsVersion=\"16.0\">\n  <Rules AnalyzerId=\"Microsoft.Analyzers.NativeCodeAnalysis\" RuleNamespace=\"Microsoft.Rules.Native\">\n    <Rule Id=\"C26449\" Action=\"Warning\" />\n    <Rule Id=\"C26450\" Action=\"Warning\" />\n    <Rule Id=\"C26452\" Action=\"Warning\" />\n    <Rule Id=\"C26453\" Action=\"Warning\" />\n    <Rule Id=\"C26454\" Action=\"Warning\" />\n    <Rule Id=\"C26495\" Action=\"Warning\" />\n    <Rule Id=\"C28021\" Action=\"Warning\" />\n    <Rule Id=\"C28182\" Action=\"Warning\" />\n    <Rule Id=\"C28202\" Action=\"Warning\" />\n    <Rule Id=\"C28203\" Action=\"Warning\" />\n    <Rule Id=\"C28205\" Action=\"Warning\" />\n    <Rule Id=\"C28206\" Action=\"Warning\" />\n    <Rule Id=\"C28207\" Action=\"Warning\" />\n    <Rule Id=\"C28210\" Action=\"Warning\" />\n    <Rule Id=\"C28211\" Action=\"Warning\" />\n    <Rule Id=\"C28212\" Action=\"Warning\" />\n    <Rule Id=\"C28213\" Action=\"Warning\" />\n    <Rule Id=\"C28214\" Action=\"Warning\" />\n    <Rule Id=\"C28215\" Action=\"Warning\" />\n    <Rule Id=\"C28216\" Action=\"Warning\" />\n    <Rule Id=\"C28217\" Action=\"Warning\" />\n    <Rule Id=\"C28218\" Action=\"Warning\" />\n    <Rule Id=\"C28219\" Action=\"Warning\" />\n    <Rule Id=\"C28220\" Action=\"Warning\" />\n    <Rule Id=\"C28221\" Action=\"Warning\" />\n    <Rule Id=\"C28222\" Action=\"Warning\" />\n    <Rule Id=\"C28223\" Action=\"Warning\" />\n    <Rule Id=\"C28224\" Action=\"Warning\" />\n    <Rule Id=\"C28225\" Action=\"Warning\" />\n    <Rule Id=\"C28226\" Action=\"Warning\" />\n    <Rule Id=\"C28227\" Action=\"Warning\" />\n    <Rule Id=\"C28228\" Action=\"Warning\" />\n    <Rule Id=\"C28229\" Action=\"Warning\" />\n    <Rule Id=\"C28230\" Action=\"Warning\" />\n    <Rule Id=\"C28231\" Action=\"Warning\" />\n    <Rule Id=\"C28232\" Action=\"Warning\" />\n    <Rule Id=\"C28233\" Action=\"Warning\" />\n    <Rule Id=\"C28234\" Action=\"Warning\" />\n    <Rule Id=\"C28235\" Action=\"Warning\" />\n    <Rule Id=\"C28236\" Action=\"Warning\" />\n    <Rule Id=\"C28237\" Action=\"Warning\" />\n    <Rule Id=\"C28238\" Action=\"Warning\" />\n    <Rule Id=\"C28239\" Action=\"Warning\" />\n    <Rule Id=\"C28240\" Action=\"Warning\" />\n    <Rule Id=\"C28241\" Action=\"Warning\" />\n    <Rule Id=\"C28243\" Action=\"Warning\" />\n    <Rule Id=\"C28245\" Action=\"Warning\" />\n    <Rule Id=\"C28246\" Action=\"Warning\" />\n    <Rule Id=\"C28250\" Action=\"Warning\" />\n    <Rule Id=\"C28251\" Action=\"Warning\" />\n    <Rule Id=\"C28252\" Action=\"Warning\" />\n    <Rule Id=\"C28253\" Action=\"Warning\" />\n    <Rule Id=\"C28254\" Action=\"Warning\" />\n    <Rule Id=\"C28262\" Action=\"Warning\" />\n    <Rule Id=\"C28263\" Action=\"Warning\" />\n    <Rule Id=\"C28267\" Action=\"Warning\" />\n    <Rule Id=\"C28272\" Action=\"Warning\" />\n    <Rule Id=\"C28273\" Action=\"Warning\" />\n    <Rule Id=\"C28275\" Action=\"Warning\" />\n    <Rule Id=\"C28279\" Action=\"Warning\" />\n    <Rule Id=\"C28280\" Action=\"Warning\" />\n    <Rule Id=\"C28282\" Action=\"Warning\" />\n    <Rule Id=\"C28285\" Action=\"Warning\" />\n    <Rule Id=\"C28286\" Action=\"Warning\" />\n    <Rule Id=\"C28287\" Action=\"Warning\" />\n    <Rule Id=\"C28288\" Action=\"Warning\" />\n    <Rule Id=\"C28289\" Action=\"Warning\" />\n    <Rule Id=\"C28290\" Action=\"Warning\" />\n    <Rule Id=\"C28291\" Action=\"Warning\" />\n    <Rule Id=\"C28300\" Action=\"Warning\" />\n    <Rule Id=\"C28301\" Action=\"Warning\" />\n    <Rule Id=\"C28302\" Action=\"Warning\" />\n    <Rule Id=\"C28303\" Action=\"Warning\" />\n    <Rule Id=\"C28304\" Action=\"Warning\" />\n    <Rule Id=\"C28305\" Action=\"Warning\" />\n    <Rule Id=\"C28308\" Action=\"Warning\" />\n    <Rule Id=\"C28309\" Action=\"Warning\" />\n    <Rule Id=\"C28350\" Action=\"Warning\" />\n    <Rule Id=\"C28351\" Action=\"Warning\" />\n    <Rule Id=\"C6001\" Action=\"Warning\" />\n    <Rule Id=\"C6011\" Action=\"Warning\" />\n    <Rule Id=\"C6029\" Action=\"Warning\" />\n    <Rule Id=\"C6053\" Action=\"Warning\" />\n    <Rule Id=\"C6059\" Action=\"Warning\" />\n    <Rule Id=\"C6063\" Action=\"Warning\" />\n    <Rule Id=\"C6064\" Action=\"Warning\" />\n    <Rule Id=\"C6066\" Action=\"Warning\" />\n    <Rule Id=\"C6067\" Action=\"Warning\" />\n    <Rule Id=\"C6101\" Action=\"Warning\" />\n    <Rule Id=\"C6200\" Action=\"Warning\" />\n    <Rule Id=\"C6201\" Action=\"Warning\" />\n    <Rule Id=\"C6270\" Action=\"Warning\" />\n    <Rule Id=\"C6271\" Action=\"Warning\" />\n    <Rule Id=\"C6272\" Action=\"Warning\" />\n    <Rule Id=\"C6273\" Action=\"Warning\" />\n    <Rule Id=\"C6274\" Action=\"Warning\" />\n    <Rule Id=\"C6276\" Action=\"Warning\" />\n    <Rule Id=\"C6277\" Action=\"Warning\" />\n    <Rule Id=\"C6284\" Action=\"Warning\" />\n    <Rule Id=\"C6290\" Action=\"Warning\" />\n    <Rule Id=\"C6291\" Action=\"Warning\" />\n    <Rule Id=\"C6302\" Action=\"Warning\" />\n    <Rule Id=\"C6303\" Action=\"Warning\" />\n    <Rule Id=\"C6305\" Action=\"Warning\" />\n    <Rule Id=\"C6306\" Action=\"Warning\" />\n    <Rule Id=\"C6328\" Action=\"Warning\" />\n    <Rule Id=\"C6385\" Action=\"Warning\" />\n    <Rule Id=\"C6386\" Action=\"Warning\" />\n    <Rule Id=\"C6387\" Action=\"Warning\" />\n    <Rule Id=\"C6500\" Action=\"Warning\" />\n    <Rule Id=\"C6501\" Action=\"Warning\" />\n    <Rule Id=\"C6503\" Action=\"Warning\" />\n    <Rule Id=\"C6504\" Action=\"Warning\" />\n    <Rule Id=\"C6505\" Action=\"Warning\" />\n    <Rule Id=\"C6506\" Action=\"Warning\" />\n    <Rule Id=\"C6508\" Action=\"Warning\" />\n    <Rule Id=\"C6509\" Action=\"Warning\" />\n    <Rule Id=\"C6510\" Action=\"Warning\" />\n    <Rule Id=\"C6511\" Action=\"Warning\" />\n    <Rule Id=\"C6513\" Action=\"Warning\" />\n    <Rule Id=\"C6514\" Action=\"Warning\" />\n    <Rule Id=\"C6515\" Action=\"Warning\" />\n    <Rule Id=\"C6516\" Action=\"Warning\" />\n    <Rule Id=\"C6517\" Action=\"Warning\" />\n    <Rule Id=\"C6518\" Action=\"Warning\" />\n    <Rule Id=\"C6522\" Action=\"Warning\" />\n    <Rule Id=\"C6525\" Action=\"Warning\" />\n    <Rule Id=\"C6527\" Action=\"Warning\" />\n    <Rule Id=\"C6530\" Action=\"Warning\" />\n    <Rule Id=\"C6540\" Action=\"Warning\" />\n    <Rule Id=\"C6551\" Action=\"Warning\" />\n    <Rule Id=\"C6552\" Action=\"Warning\" />\n    <Rule Id=\"C6701\" Action=\"Warning\" />\n    <Rule Id=\"C6702\" Action=\"Warning\" />\n    <Rule Id=\"C6703\" Action=\"Warning\" />\n    <Rule Id=\"C6704\" Action=\"Warning\" />\n    <Rule Id=\"C6705\" Action=\"Warning\" />\n    <Rule Id=\"C6706\" Action=\"Warning\" />\n  </Rules>\n</RuleSet>"
  },
  {
    "path": "src/DesktopPlus/DesktopPlus.vcxproj",
    "content": "﻿<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<Project DefaultTargets=\"Build\" ToolsVersion=\"14.0\" xmlns=\"http://schemas.microsoft.com/developer/msbuild/2003\">\n  <ItemGroup Label=\"ProjectConfigurations\">\n    <ProjectConfiguration Include=\"Debug|x64\">\n      <Configuration>Debug</Configuration>\n      <Platform>x64</Platform>\n    </ProjectConfiguration>\n    <ProjectConfiguration Include=\"Release|x64\">\n      <Configuration>Release</Configuration>\n      <Platform>x64</Platform>\n    </ProjectConfiguration>\n  </ItemGroup>\n  <PropertyGroup Label=\"Globals\">\n    <ProjectGuid>{05050918-71E9-AF87-0B3C-6F34D471A55A}</ProjectGuid>\n    <ProjectName>DesktopPlus</ProjectName>\n    <RootNamespace>DesktopPlus</RootNamespace>\n  </PropertyGroup>\n  <Import Project=\"$(VCTargetsPath)\\Microsoft.Cpp.Default.props\" />\n  <PropertyGroup Condition=\"'$(Configuration)|$(Platform)'=='Debug|x64'\" Label=\"Configuration\">\n    <ConfigurationType>Application</ConfigurationType>\n    <UseDebugLibraries>true</UseDebugLibraries>\n    <CharacterSet>Unicode</CharacterSet>\n    <PlatformToolset>v142</PlatformToolset>\n  </PropertyGroup>\n  <PropertyGroup Condition=\"'$(Configuration)|$(Platform)'=='Release|x64'\" Label=\"Configuration\">\n    <ConfigurationType>Application</ConfigurationType>\n    <UseDebugLibraries>false</UseDebugLibraries>\n    <CharacterSet>Unicode</CharacterSet>\n    <PlatformToolset>v142</PlatformToolset>\n    <WholeProgramOptimization>true</WholeProgramOptimization>\n  </PropertyGroup>\n  <Import Project=\"$(VCTargetsPath)\\Microsoft.Cpp.props\" />\n  <ImportGroup Label=\"ExtensionSettings\">\n  </ImportGroup>\n  <ImportGroup Condition=\"'$(Configuration)|$(Platform)'=='Debug|x64'\" Label=\"PropertySheets\">\n    <Import Project=\"$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props\" Condition=\"exists('$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props')\" Label=\"LocalAppDataPlatform\" />\n  </ImportGroup>\n  <ImportGroup Condition=\"'$(Configuration)|$(Platform)'=='Release|x64'\" Label=\"PropertySheets\">\n    <Import Project=\"$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props\" Condition=\"exists('$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props')\" Label=\"LocalAppDataPlatform\" />\n  </ImportGroup>\n  <PropertyGroup Label=\"UserMacros\" />\n  <PropertyGroup Condition=\"'$(Configuration)|$(Platform)'=='Debug|x64'\">\n    <LinkIncremental>true</LinkIncremental>\n    <CodeAnalysisRuleSet>DesktopPlus.ruleset</CodeAnalysisRuleSet>\n    <EmbedManifest>false</EmbedManifest>\n  </PropertyGroup>\n  <PropertyGroup Condition=\"'$(Configuration)|$(Platform)'=='Release|x64'\">\n    <LinkIncremental>false</LinkIncremental>\n    <CodeAnalysisRuleSet>DesktopPlus.ruleset</CodeAnalysisRuleSet>\n    <EmbedManifest>false</EmbedManifest>\n  </PropertyGroup>\n  <ItemDefinitionGroup Condition=\"'$(Configuration)|$(Platform)'=='Debug|x64'\">\n    <ClCompile>\n      <PreprocessorDefinitions>LOGURU_FILENAME_WIDTH=30;LOGURU_VERBOSE_SCOPE_ENDINGS=0;DPLUS_SHA=$(DPLUS_SHA);WIN32;_DEBUG;_WINDOWS;_WIN32_WINNT=_WIN32_WINNT_WIN8;%(PreprocessorDefinitions)</PreprocessorDefinitions>\n      <RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary>\n      <DebugInformationFormat>EditAndContinue</DebugInformationFormat>\n      <Optimization>Disabled</Optimization>\n      <AdditionalIncludeDirectories>$(OutDir);..\\Shared;..\\DesktopPlus;..\\DesktopPlusWinRT</AdditionalIncludeDirectories>\n      <WarningLevel>\n      </WarningLevel>\n      <MultiProcessorCompilation>true</MultiProcessorCompilation>\n      <DisableSpecificWarnings>%(DisableSpecificWarnings)</DisableSpecificWarnings>\n    </ClCompile>\n    <Link>\n      <GenerateDebugInformation>true</GenerateDebugInformation>\n      <SubSystem>Windows</SubSystem>\n      <AdditionalDependencies>d3d11.lib;dxgi.lib;openvr_api.lib;winmm.lib;dwmapi.lib;userenv.lib;DesktopPlusWinRT.lib;%(AdditionalDependencies)</AdditionalDependencies>\n      <AdditionalLibraryDirectories>$(SolutionDir)Shared;$(OutputPath)</AdditionalLibraryDirectories>\n    </Link>\n    <Manifest>\n      <EnableDpiAwareness>PerMonitorHighDPIAware</EnableDpiAwareness>\n      <AssemblyIdentity>\"DesktopPlus_$([System.DateTime]::Now.ToFileTime()), version=1\"</AssemblyIdentity>\n    </Manifest>\n  </ItemDefinitionGroup>\n  <ItemDefinitionGroup Condition=\"'$(Configuration)|$(Platform)'=='Release|x64'\">\n    <ClCompile>\n      <PreprocessorDefinitions>LOGURU_FILENAME_WIDTH=30;LOGURU_VERBOSE_SCOPE_ENDINGS=0;DPLUS_SHA=$(DPLUS_SHA);WIN32;NDEBUG;_WINDOWS;_WIN32_WINNT=_WIN32_WINNT_WIN8;%(PreprocessorDefinitions)</PreprocessorDefinitions>\n      <RuntimeLibrary>MultiThreaded</RuntimeLibrary>\n      <DebugInformationFormat>None</DebugInformationFormat>\n      <Optimization>Full</Optimization>\n      <AdditionalIncludeDirectories>$(OutDir);..\\Shared;..\\DesktopPlus;..\\DesktopPlusWinRT</AdditionalIncludeDirectories>\n      <WarningLevel>\n      </WarningLevel>\n      <MultiProcessorCompilation>true</MultiProcessorCompilation>\n      <DisableSpecificWarnings>%(DisableSpecificWarnings)</DisableSpecificWarnings>\n      <InlineFunctionExpansion>AnySuitable</InlineFunctionExpansion>\n      <IntrinsicFunctions>true</IntrinsicFunctions>\n      <FavorSizeOrSpeed>Speed</FavorSizeOrSpeed>\n      <OmitFramePointers>true</OmitFramePointers>\n      <WholeProgramOptimization>true</WholeProgramOptimization>\n      <FunctionLevelLinking>true</FunctionLevelLinking>\n    </ClCompile>\n    <Link>\n      <GenerateDebugInformation>false</GenerateDebugInformation>\n      <SubSystem>Windows</SubSystem>\n      <EnableCOMDATFolding>true</EnableCOMDATFolding>\n      <OptimizeReferences>true</OptimizeReferences>\n      <AdditionalDependencies>d3d11.lib;dxgi.lib;openvr_api.lib;winmm.lib;dwmapi.lib;userenv.lib;DesktopPlusWinRT.lib;%(AdditionalDependencies)</AdditionalDependencies>\n      <AdditionalLibraryDirectories>$(SolutionDir)Shared;$(OutputPath)</AdditionalLibraryDirectories>\n    </Link>\n    <Manifest>\n      <EnableDpiAwareness>PerMonitorHighDPIAware</EnableDpiAwareness>\n      <AssemblyIdentity>\"DesktopPlus_$([System.DateTime]::Now.ToFileTime()), version=1\"</AssemblyIdentity>\n    </Manifest>\n  </ItemDefinitionGroup>\n  <ItemGroup>\n    <ClCompile Include=\"..\\Shared\\Actions.cpp\" />\n    <ClCompile Include=\"..\\Shared\\AppProfiles.cpp\" />\n    <ClCompile Include=\"..\\Shared\\ConfigManager.cpp\" />\n    <ClCompile Include=\"..\\Shared\\DPBrowserAPIClient.cpp\" />\n    <ClCompile Include=\"..\\Shared\\Ini.cpp\" />\n    <ClCompile Include=\"..\\Shared\\InterprocessMessaging.cpp\" />\n    <ClCompile Include=\"..\\Shared\\Logging.cpp\" />\n    <ClCompile Include=\"..\\Shared\\loguru.cpp\" />\n    <ClCompile Include=\"..\\Shared\\Matrices.cpp\" />\n    <ClCompile Include=\"..\\Shared\\OpenVRExt.cpp\" />\n    <ClCompile Include=\"..\\Shared\\OUtoSBSConverter.cpp\" />\n    <ClCompile Include=\"..\\Shared\\OverlayDragger.cpp\" />\n    <ClCompile Include=\"..\\Shared\\OverlayManager.cpp\" />\n    <ClCompile Include=\"..\\Shared\\Util.cpp\" />\n    <ClCompile Include=\"..\\Shared\\WindowManager.cpp\" />\n    <ClCompile Include=\"BackgroundOverlay.cpp\" />\n    <ClCompile Include=\"DesktopPlus.cpp\">\n      <AdditionalIncludeDirectories Condition=\"'$(Configuration)|$(Platform)'=='Debug|x64'\">$(OutDir);%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>\n      <AdditionalIncludeDirectories Condition=\"'$(Configuration)|$(Platform)'=='Release|x64'\">$(OutDir);%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>\n    </ClCompile>\n    <ClCompile Include=\"DisplayManager.cpp\" />\n    <ClCompile Include=\"DuplicationManager.cpp\" />\n    <ClCompile Include=\"ElevatedMode.cpp\" />\n    <ClCompile Include=\"InputSimulator.cpp\" />\n    <ClCompile Include=\"LaserPointer.cpp\" />\n    <ClCompile Include=\"OutputManager.cpp\" />\n    <ClCompile Include=\"Overlays.cpp\" />\n    <ClCompile Include=\"RadialFollowSmoothing.cpp\" />\n    <ClCompile Include=\"SoftwareCursorGrabber.cpp\" />\n    <ClCompile Include=\"ThreadManager.cpp\" />\n    <ClCompile Include=\"VRInput.cpp\" />\n  </ItemGroup>\n  <ItemGroup>\n    <ClInclude Include=\"..\\Shared\\Actions.h\" />\n    <ClInclude Include=\"..\\Shared\\AppProfiles.h\" />\n    <ClInclude Include=\"..\\Shared\\ConfigManager.h\" />\n    <ClInclude Include=\"..\\Shared\\DPBrowserAPI.h\" />\n    <ClInclude Include=\"..\\Shared\\DPBrowserAPIClient.h\" />\n    <ClInclude Include=\"..\\Shared\\DPRect.h\" />\n    <ClInclude Include=\"..\\Shared\\Ini.h\" />\n    <ClInclude Include=\"..\\Shared\\InterprocessMessaging.h\" />\n    <ClInclude Include=\"..\\Shared\\Logging.h\" />\n    <ClInclude Include=\"..\\Shared\\loguru.hpp\" />\n    <ClInclude Include=\"..\\Shared\\Matrices.h\" />\n    <ClInclude Include=\"..\\Shared\\openvr.h\" />\n    <ClInclude Include=\"..\\Shared\\OpenVRExt.h\" />\n    <ClInclude Include=\"..\\Shared\\OUtoSBSConverter.h\" />\n    <ClInclude Include=\"..\\Shared\\OverlayDragger.h\" />\n    <ClInclude Include=\"..\\Shared\\OverlayManager.h\" />\n    <ClInclude Include=\"..\\Shared\\Util.h\" />\n    <ClInclude Include=\"..\\Shared\\Vectors.h\" />\n    <ClInclude Include=\"..\\Shared\\WindowManager.h\" />\n    <ClInclude Include=\"BackgroundOverlay.h\" />\n    <ClInclude Include=\"CommonTypes.h\" />\n    <ClInclude Include=\"DisplayManager.h\" />\n    <ClInclude Include=\"DuplicationManager.h\" />\n    <ClInclude Include=\"ElevatedMode.h\" />\n    <ClInclude Include=\"InputSimulator.h\" />\n    <ClInclude Include=\"LaserPointer.h\" />\n    <ClInclude Include=\"OutputManager.h\" />\n    <ClInclude Include=\"Overlays.h\" />\n    <ClInclude Include=\"RadialFollowSmoothing.h\" />\n    <ClInclude Include=\"resource.h\" />\n    <ClInclude Include=\"SoftwareCursorGrabber.h\" />\n    <ClInclude Include=\"ThreadManager.h\" />\n    <ClInclude Include=\"VRInput.h\" />\n  </ItemGroup>\n  <ItemGroup>\n    <FxCompile Include=\"PixelShader.hlsl\">\n      <EntryPointName Condition=\"'$(Configuration)|$(Platform)'=='Debug|x64'\">PS</EntryPointName>\n      <ShaderType Condition=\"'$(Configuration)|$(Platform)'=='Debug|x64'\">Pixel</ShaderType>\n      <ShaderModel Condition=\"'$(Configuration)|$(Platform)'=='Debug|x64'\">4.0_level_9_1</ShaderModel>\n      <EntryPointName Condition=\"'$(Configuration)|$(Platform)'=='Release|x64'\">PS</EntryPointName>\n      <ShaderModel Condition=\"'$(Configuration)|$(Platform)'=='Release|x64'\">4.0_level_9_1</ShaderModel>\n      <ShaderType Condition=\"'$(Configuration)|$(Platform)'=='Release|x64'\">Pixel</ShaderType>\n      <HeaderFileOutput Condition=\"'$(Configuration)|$(Platform)'=='Debug|x64'\">$(OutDir)%(Filename).h</HeaderFileOutput>\n      <ObjectFileOutput Condition=\"'$(Configuration)|$(Platform)'=='Debug|x64'\">\n      </ObjectFileOutput>\n      <HeaderFileOutput Condition=\"'$(Configuration)|$(Platform)'=='Release|x64'\">$(OutDir)%(Filename).h</HeaderFileOutput>\n      <ObjectFileOutput Condition=\"'$(Configuration)|$(Platform)'=='Release|x64'\">\n      </ObjectFileOutput>\n    </FxCompile>\n    <FxCompile Include=\"PixelShaderCursor.hlsl\">\n      <EntryPointName Condition=\"'$(Configuration)|$(Platform)'=='Release|x64'\">PSCURSOR</EntryPointName>\n      <ShaderType Condition=\"'$(Configuration)|$(Platform)'=='Release|x64'\">Pixel</ShaderType>\n      <HeaderFileOutput Condition=\"'$(Configuration)|$(Platform)'=='Release|x64'\">$(OutDir)%(Filename).h</HeaderFileOutput>\n      <ObjectFileOutput Condition=\"'$(Configuration)|$(Platform)'=='Release|x64'\">\n      </ObjectFileOutput>\n      <EntryPointName Condition=\"'$(Configuration)|$(Platform)'=='Debug|x64'\">PSCURSOR</EntryPointName>\n      <HeaderFileOutput Condition=\"'$(Configuration)|$(Platform)'=='Debug|x64'\">$(OutDir)%(Filename).h</HeaderFileOutput>\n      <ObjectFileOutput Condition=\"'$(Configuration)|$(Platform)'=='Debug|x64'\">\n      </ObjectFileOutput>\n      <ShaderType Condition=\"'$(Configuration)|$(Platform)'=='Debug|x64'\">Pixel</ShaderType>\n    </FxCompile>\n    <FxCompile Include=\"VertexShader.hlsl\">\n      <EntryPointName Condition=\"'$(Configuration)|$(Platform)'=='Debug|x64'\">VS</EntryPointName>\n      <ShaderModel Condition=\"'$(Configuration)|$(Platform)'=='Debug|x64'\">4.0_level_9_1</ShaderModel>\n      <EntryPointName Condition=\"'$(Configuration)|$(Platform)'=='Release|x64'\">VS</EntryPointName>\n      <ShaderModel Condition=\"'$(Configuration)|$(Platform)'=='Release|x64'\">4.0_level_9_1</ShaderModel>\n      <ShaderType Condition=\"'$(Configuration)|$(Platform)'=='Release|x64'\">Vertex</ShaderType>\n      <HeaderFileOutput Condition=\"'$(Configuration)|$(Platform)'=='Debug|x64'\">$(OutDir)%(Filename).h</HeaderFileOutput>\n      <HeaderFileOutput Condition=\"'$(Configuration)|$(Platform)'=='Release|x64'\">$(OutDir)%(Filename).h</HeaderFileOutput>\n      <ObjectFileOutput Condition=\"'$(Configuration)|$(Platform)'=='Debug|x64'\">\n      </ObjectFileOutput>\n      <ObjectFileOutput Condition=\"'$(Configuration)|$(Platform)'=='Release|x64'\">\n      </ObjectFileOutput>\n      <ShaderType Condition=\"'$(Configuration)|$(Platform)'=='Debug|x64'\">Vertex</ShaderType>\n    </FxCompile>\n  </ItemGroup>\n  <ItemGroup>\n    <ResourceCompile Include=\"DesktopPlus.rc\" />\n  </ItemGroup>\n  <ItemGroup>\n    <None Include=\"DesktopPlus.ruleset\" />\n  </ItemGroup>\n  <ItemGroup>\n    <Manifest Include=\"DesktopPlus.manifest\" />\n  </ItemGroup>\n  <ItemGroup>\n    <Image Include=\"..\\Shared\\icon_desktop.ico\" />\n  </ItemGroup>\n  <ItemGroup>\n    <Content Include=\"..\\..\\assets\\**\\*.*\">\n      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>\n      <DeploymentContent>true</DeploymentContent>\n      <Link>%(RecursiveDir)\\%(Filename)%(Extension)</Link>\n    </Content>\n  </ItemGroup>\n  <Import Project=\"$(VCTargetsPath)\\Microsoft.Cpp.targets\" />\n  <ImportGroup Label=\"ExtensionTargets\">\n  </ImportGroup>\n</Project>"
  },
  {
    "path": "src/DesktopPlus/DesktopPlus.vcxproj.filters",
    "content": "﻿<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<Project ToolsVersion=\"4.0\" xmlns=\"http://schemas.microsoft.com/developer/msbuild/2003\">\n  <ItemGroup>\n    <FxCompile Include=\"PixelShader.hlsl\" />\n    <FxCompile Include=\"PixelShaderCursor.hlsl\" />\n    <FxCompile Include=\"VertexShader.hlsl\" />\n  </ItemGroup>\n  <ItemGroup>\n    <ClCompile Include=\"DesktopPlus.cpp\" />\n    <ClCompile Include=\"DisplayManager.cpp\" />\n    <ClCompile Include=\"DuplicationManager.cpp\" />\n    <ClCompile Include=\"InputSimulator.cpp\" />\n    <ClCompile Include=\"OutputManager.cpp\" />\n    <ClCompile Include=\"ThreadManager.cpp\" />\n    <ClCompile Include=\"VRInput.cpp\" />\n    <ClCompile Include=\"..\\Shared\\Util.cpp\">\n      <Filter>Shared</Filter>\n    </ClCompile>\n    <ClCompile Include=\"..\\Shared\\InterprocessMessaging.cpp\">\n      <Filter>Shared</Filter>\n    </ClCompile>\n    <ClCompile Include=\"..\\Shared\\ConfigManager.cpp\">\n      <Filter>Shared</Filter>\n    </ClCompile>\n    <ClCompile Include=\"..\\Shared\\Matrices.cpp\">\n      <Filter>Shared</Filter>\n    </ClCompile>\n    <ClCompile Include=\"..\\Shared\\Actions.cpp\">\n      <Filter>Shared</Filter>\n    </ClCompile>\n    <ClCompile Include=\"..\\Shared\\Ini.cpp\">\n      <Filter>Shared</Filter>\n    </ClCompile>\n    <ClCompile Include=\"Overlays.cpp\" />\n    <ClCompile Include=\"..\\Shared\\OverlayManager.cpp\">\n      <Filter>Shared</Filter>\n    </ClCompile>\n    <ClCompile Include=\"..\\Shared\\OUtoSBSConverter.cpp\">\n      <Filter>Shared</Filter>\n    </ClCompile>\n    <ClCompile Include=\"ElevatedMode.cpp\" />\n    <ClCompile Include=\"BackgroundOverlay.cpp\" />\n    <ClCompile Include=\"..\\Shared\\OverlayDragger.cpp\">\n      <Filter>Shared</Filter>\n    </ClCompile>\n    <ClCompile Include=\"..\\Shared\\WindowManager.cpp\">\n      <Filter>Shared</Filter>\n    </ClCompile>\n    <ClCompile Include=\"LaserPointer.cpp\" />\n    <ClCompile Include=\"..\\Shared\\DPBrowserAPIClient.cpp\">\n      <Filter>Shared</Filter>\n    </ClCompile>\n    <ClCompile Include=\"..\\Shared\\AppProfiles.cpp\">\n      <Filter>Shared</Filter>\n    </ClCompile>\n    <ClCompile Include=\"..\\Shared\\loguru.cpp\">\n      <Filter>Shared</Filter>\n    </ClCompile>\n    <ClCompile Include=\"..\\Shared\\Logging.cpp\">\n      <Filter>Shared</Filter>\n    </ClCompile>\n    <ClCompile Include=\"..\\Shared\\OpenVRExt.cpp\">\n      <Filter>Shared</Filter>\n    </ClCompile>\n    <ClCompile Include=\"RadialFollowSmoothing.cpp\" />\n    <ClCompile Include=\"SoftwareCursorGrabber.cpp\" />\n  </ItemGroup>\n  <ItemGroup>\n    <ClInclude Include=\"CommonTypes.h\" />\n    <ClInclude Include=\"DisplayManager.h\" />\n    <ClInclude Include=\"DuplicationManager.h\" />\n    <ClInclude Include=\"InputSimulator.h\" />\n    <ClInclude Include=\"OutputManager.h\" />\n    <ClInclude Include=\"resource.h\" />\n    <ClInclude Include=\"ThreadManager.h\" />\n    <ClInclude Include=\"VRInput.h\" />\n    <ClInclude Include=\"..\\Shared\\Util.h\">\n      <Filter>Shared</Filter>\n    </ClInclude>\n    <ClInclude Include=\"..\\Shared\\InterprocessMessaging.h\">\n      <Filter>Shared</Filter>\n    </ClInclude>\n    <ClInclude Include=\"..\\Shared\\ConfigManager.h\">\n      <Filter>Shared</Filter>\n    </ClInclude>\n    <ClInclude Include=\"..\\Shared\\Ini.h\">\n      <Filter>Shared</Filter>\n    </ClInclude>\n    <ClInclude Include=\"..\\Shared\\Matrices.h\">\n      <Filter>Shared</Filter>\n    </ClInclude>\n    <ClInclude Include=\"..\\Shared\\openvr.h\">\n      <Filter>Shared</Filter>\n    </ClInclude>\n    <ClInclude Include=\"..\\Shared\\Vectors.h\">\n      <Filter>Shared</Filter>\n    </ClInclude>\n    <ClInclude Include=\"..\\Shared\\Actions.h\">\n      <Filter>Shared</Filter>\n    </ClInclude>\n    <ClInclude Include=\"..\\Shared\\DPRect.h\">\n      <Filter>Shared</Filter>\n    </ClInclude>\n    <ClInclude Include=\"Overlays.h\" />\n    <ClInclude Include=\"..\\Shared\\OverlayManager.h\">\n      <Filter>Shared</Filter>\n    </ClInclude>\n    <ClInclude Include=\"..\\Shared\\OUtoSBSConverter.h\">\n      <Filter>Shared</Filter>\n    </ClInclude>\n    <ClInclude Include=\"ElevatedMode.h\" />\n    <ClInclude Include=\"BackgroundOverlay.h\" />\n    <ClInclude Include=\"..\\Shared\\OverlayDragger.h\">\n      <Filter>Shared</Filter>\n    </ClInclude>\n    <ClInclude Include=\"..\\Shared\\WindowManager.h\">\n      <Filter>Shared</Filter>\n    </ClInclude>\n    <ClInclude Include=\"LaserPointer.h\" />\n    <ClInclude Include=\"..\\Shared\\DPBrowserAPI.h\">\n      <Filter>Shared</Filter>\n    </ClInclude>\n    <ClInclude Include=\"..\\Shared\\DPBrowserAPIClient.h\">\n      <Filter>Shared</Filter>\n    </ClInclude>\n    <ClInclude Include=\"..\\Shared\\AppProfiles.h\">\n      <Filter>Shared</Filter>\n    </ClInclude>\n    <ClInclude Include=\"..\\Shared\\Logging.h\">\n      <Filter>Shared</Filter>\n    </ClInclude>\n    <ClInclude Include=\"..\\Shared\\loguru.hpp\">\n      <Filter>Shared</Filter>\n    </ClInclude>\n    <ClInclude Include=\"..\\Shared\\OpenVRExt.h\">\n      <Filter>Shared</Filter>\n    </ClInclude>\n    <ClInclude Include=\"RadialFollowSmoothing.h\" />\n    <ClInclude Include=\"SoftwareCursorGrabber.h\" />\n  </ItemGroup>\n  <ItemGroup>\n    <ResourceCompile Include=\"DesktopPlus.rc\" />\n  </ItemGroup>\n  <ItemGroup>\n    <Filter Include=\"Shared\">\n      <UniqueIdentifier>{0572e516-12c3-49ec-a3ae-3696d6eb2490}</UniqueIdentifier>\n    </Filter>\n  </ItemGroup>\n  <ItemGroup>\n    <None Include=\"DesktopPlus.ruleset\" />\n  </ItemGroup>\n  <ItemGroup>\n    <Manifest Include=\"DesktopPlus.manifest\" />\n  </ItemGroup>\n  <ItemGroup>\n    <Image Include=\"..\\Shared\\icon_desktop.ico\" />\n  </ItemGroup>\n</Project>"
  },
  {
    "path": "src/DesktopPlus/DesktopPlus.vcxproj.user",
    "content": "﻿<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<Project ToolsVersion=\"14.0\" xmlns=\"http://schemas.microsoft.com/developer/msbuild/2003\">\n  <PropertyGroup Condition=\"'$(Configuration)|$(Platform)'=='Debug|x64'\">\n    <LocalDebuggerWorkingDirectory>$(OutputPath)</LocalDebuggerWorkingDirectory>\n    <DebuggerFlavor>WindowsLocalDebugger</DebuggerFlavor>\n  </PropertyGroup>\n  <PropertyGroup Condition=\"'$(Configuration)|$(Platform)'=='Release|x64'\">\n    <LocalDebuggerWorkingDirectory>$(OutputPath)</LocalDebuggerWorkingDirectory>\n    <DebuggerFlavor>WindowsLocalDebugger</DebuggerFlavor>\n  </PropertyGroup>\n</Project>"
  },
  {
    "path": "src/DesktopPlus/DisplayManager.cpp",
    "content": "#include \"DisplayManager.h\"\nusing namespace DirectX;\n\n#include \"DPRect.h\"\n\n//\n// Constructor NULLs out vars\n//\nDISPLAYMANAGER::DISPLAYMANAGER() : m_Device(nullptr),\n                                   m_DeviceContext(nullptr),\n                                   m_MoveSurf(nullptr),\n                                   m_VertexShader(nullptr),\n                                   m_PixelShader(nullptr),\n                                   m_InputLayout(nullptr),\n                                   m_RTV(nullptr),\n                                   m_SamplerLinear(nullptr),\n                                   m_DirtyVertexBufferAlloc(nullptr),\n                                   m_DirtyVertexBufferAllocSize(0)\n{\n}\n\n//\n// Destructor calls CleanRefs to destroy everything\n//\nDISPLAYMANAGER::~DISPLAYMANAGER()\n{\n    CleanRefs();\n\n    if (m_DirtyVertexBufferAlloc)\n    {\n        delete [] m_DirtyVertexBufferAlloc;\n        m_DirtyVertexBufferAlloc = nullptr;\n    }\n}\n\n//\n// Initialize D3D variables\n//\nvoid DISPLAYMANAGER::InitD3D(DX_RESOURCES* Data)\n{\n    m_Device = Data->Device;\n    m_DeviceContext = Data->Context;\n    m_VertexShader = Data->VertexShader;\n    m_PixelShader = Data->PixelShader;\n    m_InputLayout = Data->InputLayout;\n    m_SamplerLinear = Data->Sampler;\n\n    m_Device->AddRef();\n    m_DeviceContext->AddRef();\n    m_VertexShader->AddRef();\n    m_PixelShader->AddRef();\n    m_InputLayout->AddRef();\n    m_SamplerLinear->AddRef();\n}\n\n//\n// Process a given frame and its metadata\n//\nDUPL_RETURN DISPLAYMANAGER::ProcessFrame(_In_ FRAME_DATA* Data, _Inout_ ID3D11Texture2D* SharedSurf, INT OffsetX, INT OffsetY, _In_ DXGI_OUTPUT_DESC* DeskDesc, _Inout_ DPRect& DirtyRectTotal)\n{\n    DUPL_RETURN Ret = DUPL_RETURN_SUCCESS;\n\n    // Process dirties and moves\n    if (Data->FrameInfo.TotalMetadataBufferSize)\n    {\n        D3D11_TEXTURE2D_DESC Desc;\n        Data->Frame->GetDesc(&Desc);\n\n        if (Data->MoveCount)\n        {\n            Ret = CopyMove(SharedSurf, reinterpret_cast<DXGI_OUTDUPL_MOVE_RECT*>(Data->MetaData), Data->MoveCount, OffsetX, OffsetY, DeskDesc, Desc.Width, Desc.Height, DirtyRectTotal);\n            if (Ret != DUPL_RETURN_SUCCESS)\n            {\n                return Ret;\n            }\n        }\n\n        if (Data->DirtyCount)\n        {\n            Ret = CopyDirty(Data->Frame, SharedSurf, reinterpret_cast<RECT*>(Data->MetaData + (Data->MoveCount * sizeof(DXGI_OUTDUPL_MOVE_RECT))), Data->DirtyCount, OffsetX, OffsetY, DeskDesc, \n                            DirtyRectTotal);\n        }\n    }\n\n    return Ret;\n}\n\n//\n// Returns D3D device being used\n//\nID3D11Device* DISPLAYMANAGER::GetDevice()\n{\n    return m_Device;\n}\n\n//\n// Set appropriate source and destination rects for move rects\n//\nvoid DISPLAYMANAGER::SetMoveRect(_Out_ RECT* SrcRect, _Out_ RECT* DestRect, _In_ DXGI_OUTPUT_DESC* DeskDesc, _In_ DXGI_OUTDUPL_MOVE_RECT* MoveRect, INT TexWidth, INT TexHeight)\n{\n    switch (DeskDesc->Rotation)\n    {\n        case DXGI_MODE_ROTATION_UNSPECIFIED:\n        case DXGI_MODE_ROTATION_IDENTITY:\n        {\n            SrcRect->left = MoveRect->SourcePoint.x;\n            SrcRect->top = MoveRect->SourcePoint.y;\n            SrcRect->right = MoveRect->SourcePoint.x + MoveRect->DestinationRect.right - MoveRect->DestinationRect.left;\n            SrcRect->bottom = MoveRect->SourcePoint.y + MoveRect->DestinationRect.bottom - MoveRect->DestinationRect.top;\n\n            *DestRect = MoveRect->DestinationRect;\n            break;\n        }\n        case DXGI_MODE_ROTATION_ROTATE90:\n        {\n            SrcRect->left = TexHeight - (MoveRect->SourcePoint.y + MoveRect->DestinationRect.bottom - MoveRect->DestinationRect.top);\n            SrcRect->top = MoveRect->SourcePoint.x;\n            SrcRect->right = TexHeight - MoveRect->SourcePoint.y;\n            SrcRect->bottom = MoveRect->SourcePoint.x + MoveRect->DestinationRect.right - MoveRect->DestinationRect.left;\n\n            DestRect->left = TexHeight - MoveRect->DestinationRect.bottom;\n            DestRect->top = MoveRect->DestinationRect.left;\n            DestRect->right = TexHeight - MoveRect->DestinationRect.top;\n            DestRect->bottom = MoveRect->DestinationRect.right;\n            break;\n        }\n        case DXGI_MODE_ROTATION_ROTATE180:\n        {\n            SrcRect->left = TexWidth - (MoveRect->SourcePoint.x + MoveRect->DestinationRect.right - MoveRect->DestinationRect.left);\n            SrcRect->top = TexHeight - (MoveRect->SourcePoint.y + MoveRect->DestinationRect.bottom - MoveRect->DestinationRect.top);\n            SrcRect->right = TexWidth - MoveRect->SourcePoint.x;\n            SrcRect->bottom = TexHeight - MoveRect->SourcePoint.y;\n\n            DestRect->left = TexWidth - MoveRect->DestinationRect.right;\n            DestRect->top = TexHeight - MoveRect->DestinationRect.bottom;\n            DestRect->right = TexWidth - MoveRect->DestinationRect.left;\n            DestRect->bottom =  TexHeight - MoveRect->DestinationRect.top;\n            break;\n        }\n        case DXGI_MODE_ROTATION_ROTATE270:\n        {\n            SrcRect->left = MoveRect->SourcePoint.x;\n            SrcRect->top = TexWidth - (MoveRect->SourcePoint.x + MoveRect->DestinationRect.right - MoveRect->DestinationRect.left);\n            SrcRect->right = MoveRect->SourcePoint.y + MoveRect->DestinationRect.bottom - MoveRect->DestinationRect.top;\n            SrcRect->bottom = TexWidth - MoveRect->SourcePoint.x;\n\n            DestRect->left = MoveRect->DestinationRect.top;\n            DestRect->top = TexWidth - MoveRect->DestinationRect.right;\n            DestRect->right = MoveRect->DestinationRect.bottom;\n            DestRect->bottom =  TexWidth - MoveRect->DestinationRect.left;\n            break;\n        }\n        default:\n        {\n            RtlZeroMemory(DestRect, sizeof(RECT));\n            RtlZeroMemory(SrcRect, sizeof(RECT));\n            break;\n        }\n    }\n}\n\n//\n// Copy move rectangles\n//\nDUPL_RETURN DISPLAYMANAGER::CopyMove(_Inout_ ID3D11Texture2D* SharedSurf, _In_reads_(MoveCount) DXGI_OUTDUPL_MOVE_RECT* MoveBuffer, UINT MoveCount, INT OffsetX, INT OffsetY, _In_ DXGI_OUTPUT_DESC* DeskDesc,\n                                     INT TexWidth, INT TexHeight, _Inout_ DPRect& DirtyRectTotal)\n{\n    D3D11_TEXTURE2D_DESC FullDesc;\n    SharedSurf->GetDesc(&FullDesc);\n\n    // Make new intermediate surface to copy into for moving\n    if (!m_MoveSurf)\n    {\n        D3D11_TEXTURE2D_DESC MoveDesc;\n        MoveDesc = FullDesc;\n        MoveDesc.Width = DeskDesc->DesktopCoordinates.right - DeskDesc->DesktopCoordinates.left;\n        MoveDesc.Height = DeskDesc->DesktopCoordinates.bottom - DeskDesc->DesktopCoordinates.top;\n        MoveDesc.BindFlags = D3D11_BIND_RENDER_TARGET;\n        MoveDesc.MiscFlags = 0;\n        HRESULT hr = m_Device->CreateTexture2D(&MoveDesc, nullptr, &m_MoveSurf);\n        if (FAILED(hr))\n        {\n            return ProcessFailure(m_Device, L\"Failed to create staging texture for move rects\", L\"Desktop+ Error\", hr, SystemTransitionsExpectedErrors);\n        }\n    }\n\n    for (UINT i = 0; i < MoveCount; ++i)\n    {\n        RECT SrcRect;\n        RECT DestRect;\n\n        SetMoveRect(&SrcRect, &DestRect, DeskDesc, &(MoveBuffer[i]), TexWidth, TexHeight);\n\n        // Copy rect out of shared surface\n        D3D11_BOX Box;\n        Box.left = SrcRect.left + DeskDesc->DesktopCoordinates.left - OffsetX;\n        Box.top = SrcRect.top + DeskDesc->DesktopCoordinates.top - OffsetY;\n        Box.front = 0;\n        Box.right = SrcRect.right + DeskDesc->DesktopCoordinates.left - OffsetX;\n        Box.bottom = SrcRect.bottom + DeskDesc->DesktopCoordinates.top - OffsetY;\n        Box.back = 1;\n        m_DeviceContext->CopySubresourceRegion(m_MoveSurf, 0, SrcRect.left, SrcRect.top, 0, SharedSurf, 0, &Box);\n\n        // Copy back to shared surface\n        Box.left = SrcRect.left;\n        Box.top = SrcRect.top;\n        Box.front = 0;\n        Box.right = SrcRect.right;\n        Box.bottom = SrcRect.bottom;\n        Box.back = 1;\n\n        //Adjust by desktop and destination offsets\n        DestRect.left += DeskDesc->DesktopCoordinates.left - OffsetX;\n        DestRect.top  += DeskDesc->DesktopCoordinates.top  - OffsetY;\n\n        m_DeviceContext->CopySubresourceRegion(SharedSurf, 0, DestRect.left, DestRect.top, 0, m_MoveSurf, 0, &Box);\n    \n        //Add rect to total dirty region rect\n        DPRect drect(DestRect.left, DestRect.top, (int)Box.right, (int)Box.bottom);\n        (DirtyRectTotal.GetTL().x == -1) ? DirtyRectTotal = drect : DirtyRectTotal.Add(drect);\n        \n    }\n\n    return DUPL_RETURN_SUCCESS;\n}\n\n//\n// Sets up vertices for dirty rects for rotated desktops\n//\n#pragma warning(push)\n#pragma warning(disable:__WARNING_USING_UNINIT_VAR) // false positives in SetDirtyVert due to tool bug\n\nvoid DISPLAYMANAGER::SetDirtyVert(_Out_writes_(NUMVERTICES) VERTEX* Vertices, _In_ RECT* Dirty, INT OffsetX, INT OffsetY, _In_ DXGI_OUTPUT_DESC* DeskDesc, _In_ D3D11_TEXTURE2D_DESC* FullDesc,\n                                  _In_ D3D11_TEXTURE2D_DESC* ThisDesc, _Inout_ DPRect& DirtyRectTotal)\n{\n    FLOAT CenterX = FullDesc->Width  / 2.0f;\n    FLOAT CenterY = FullDesc->Height / 2.0f;\n\n    INT Width  = DeskDesc->DesktopCoordinates.right - DeskDesc->DesktopCoordinates.left;\n    INT Height = DeskDesc->DesktopCoordinates.bottom - DeskDesc->DesktopCoordinates.top;\n\n    // Rotation compensated destination rect\n    RECT DestDirty = *Dirty;\n\n    // Set appropriate coordinates compensated for rotation\n    switch (DeskDesc->Rotation)\n    {\n        case DXGI_MODE_ROTATION_ROTATE90:\n        {\n            DestDirty.left = Width - Dirty->bottom;\n            DestDirty.top = Dirty->left;\n            DestDirty.right = Width - Dirty->top;\n            DestDirty.bottom = Dirty->right;\n\n            Vertices[0].TexCoord = XMFLOAT2(Dirty->right / static_cast<FLOAT>(ThisDesc->Width), Dirty->bottom / static_cast<FLOAT>(ThisDesc->Height));\n            Vertices[1].TexCoord = XMFLOAT2(Dirty->left / static_cast<FLOAT>(ThisDesc->Width), Dirty->bottom / static_cast<FLOAT>(ThisDesc->Height));\n            Vertices[2].TexCoord = XMFLOAT2(Dirty->right / static_cast<FLOAT>(ThisDesc->Width), Dirty->top / static_cast<FLOAT>(ThisDesc->Height));\n            Vertices[5].TexCoord = XMFLOAT2(Dirty->left / static_cast<FLOAT>(ThisDesc->Width), Dirty->top / static_cast<FLOAT>(ThisDesc->Height));\n            break;\n        }\n        case DXGI_MODE_ROTATION_ROTATE180:\n        {\n            DestDirty.left = Width - Dirty->right;\n            DestDirty.top = Height - Dirty->bottom;\n            DestDirty.right = Width - Dirty->left;\n            DestDirty.bottom = Height - Dirty->top;\n\n            Vertices[0].TexCoord = XMFLOAT2(Dirty->right / static_cast<FLOAT>(ThisDesc->Width), Dirty->top / static_cast<FLOAT>(ThisDesc->Height));\n            Vertices[1].TexCoord = XMFLOAT2(Dirty->right / static_cast<FLOAT>(ThisDesc->Width), Dirty->bottom / static_cast<FLOAT>(ThisDesc->Height));\n            Vertices[2].TexCoord = XMFLOAT2(Dirty->left / static_cast<FLOAT>(ThisDesc->Width), Dirty->top / static_cast<FLOAT>(ThisDesc->Height));\n            Vertices[5].TexCoord = XMFLOAT2(Dirty->left / static_cast<FLOAT>(ThisDesc->Width), Dirty->bottom / static_cast<FLOAT>(ThisDesc->Height));\n            break;\n        }\n        case DXGI_MODE_ROTATION_ROTATE270:\n        {\n            DestDirty.left = Dirty->top;\n            DestDirty.top = Height - Dirty->right;\n            DestDirty.right = Dirty->bottom;\n            DestDirty.bottom = Height - Dirty->left;\n\n            Vertices[0].TexCoord = XMFLOAT2(Dirty->left / static_cast<FLOAT>(ThisDesc->Width), Dirty->top / static_cast<FLOAT>(ThisDesc->Height));\n            Vertices[1].TexCoord = XMFLOAT2(Dirty->right / static_cast<FLOAT>(ThisDesc->Width), Dirty->top / static_cast<FLOAT>(ThisDesc->Height));\n            Vertices[2].TexCoord = XMFLOAT2(Dirty->left / static_cast<FLOAT>(ThisDesc->Width), Dirty->bottom / static_cast<FLOAT>(ThisDesc->Height));\n            Vertices[5].TexCoord = XMFLOAT2(Dirty->right / static_cast<FLOAT>(ThisDesc->Width), Dirty->bottom / static_cast<FLOAT>(ThisDesc->Height));\n            break;\n        }\n        default:\n            assert(false); // drop through\n        case DXGI_MODE_ROTATION_UNSPECIFIED:\n        case DXGI_MODE_ROTATION_IDENTITY:\n        {\n            Vertices[0].TexCoord = XMFLOAT2(Dirty->left / static_cast<FLOAT>(ThisDesc->Width), Dirty->bottom / static_cast<FLOAT>(ThisDesc->Height));\n            Vertices[1].TexCoord = XMFLOAT2(Dirty->left / static_cast<FLOAT>(ThisDesc->Width), Dirty->top / static_cast<FLOAT>(ThisDesc->Height));\n            Vertices[2].TexCoord = XMFLOAT2(Dirty->right / static_cast<FLOAT>(ThisDesc->Width), Dirty->bottom / static_cast<FLOAT>(ThisDesc->Height));\n            Vertices[5].TexCoord = XMFLOAT2(Dirty->right / static_cast<FLOAT>(ThisDesc->Width), Dirty->top / static_cast<FLOAT>(ThisDesc->Height));\n            break;\n        }\n    }\n\n    // Set positions\n    Vertices[0].Pos = XMFLOAT3((DestDirty.left + DeskDesc->DesktopCoordinates.left - OffsetX - CenterX) / CenterX,\n                             -1 * (DestDirty.bottom + DeskDesc->DesktopCoordinates.top - OffsetY - CenterY) / CenterY,\n                             0.0f);\n    Vertices[1].Pos = XMFLOAT3((DestDirty.left + DeskDesc->DesktopCoordinates.left - OffsetX - CenterX) / CenterX,\n                             -1 * (DestDirty.top + DeskDesc->DesktopCoordinates.top - OffsetY - CenterY) / CenterY,\n                             0.0f);\n    Vertices[2].Pos = XMFLOAT3((DestDirty.right + DeskDesc->DesktopCoordinates.left - OffsetX - CenterX) / CenterX,\n                             -1 * (DestDirty.bottom + DeskDesc->DesktopCoordinates.top - OffsetY - CenterY) / CenterY,\n                             0.0f);\n    Vertices[3].Pos = Vertices[2].Pos;\n    Vertices[4].Pos = Vertices[1].Pos;\n    Vertices[5].Pos = XMFLOAT3((DestDirty.right + DeskDesc->DesktopCoordinates.left - OffsetX - CenterX) / CenterX,\n                             -1 * (DestDirty.top + DeskDesc->DesktopCoordinates.top - OffsetY - CenterY) / CenterY,\n                             0.0f);\n\n    Vertices[3].TexCoord = Vertices[2].TexCoord;\n    Vertices[4].TexCoord = Vertices[1].TexCoord;\n\n    //Add rect to total dirty region rect\n    DPRect drect(DestDirty.left, DestDirty.top, DestDirty.right, DestDirty.bottom);\n    drect.Translate({DeskDesc->DesktopCoordinates.left - OffsetX, DeskDesc->DesktopCoordinates.top - OffsetY});\n    (DirtyRectTotal.GetTL().x == -1) ? DirtyRectTotal = drect : DirtyRectTotal.Add(drect);\n}\n\n#pragma warning(pop) // re-enable __WARNING_USING_UNINIT_VAR\n\n//\n// Copies dirty rectangles\n//\nDUPL_RETURN DISPLAYMANAGER::CopyDirty(_In_ ID3D11Texture2D* SrcSurface, _Inout_ ID3D11Texture2D* SharedSurf, _In_reads_(DirtyCount) RECT* DirtyBuffer, UINT DirtyCount, INT OffsetX, INT OffsetY, \n                                      _In_ DXGI_OUTPUT_DESC* DeskDesc, _Inout_ DPRect& DirtyRectTotal)\n{\n    HRESULT hr;\n\n    D3D11_TEXTURE2D_DESC FullDesc;\n    SharedSurf->GetDesc(&FullDesc);\n\n    D3D11_TEXTURE2D_DESC ThisDesc;\n    SrcSurface->GetDesc(&ThisDesc);\n\n    if (!m_RTV)\n    {\n        hr = m_Device->CreateRenderTargetView(SharedSurf, nullptr, &m_RTV);\n        if (FAILED(hr))\n        {\n            return ProcessFailure(m_Device, L\"Failed to create render target view for dirty rects\", L\"Desktop+ Error\", hr, SystemTransitionsExpectedErrors);\n        }\n    }\n\n    D3D11_SHADER_RESOURCE_VIEW_DESC ShaderDesc;\n    ShaderDesc.Format = ThisDesc.Format;\n    ShaderDesc.ViewDimension = D3D11_SRV_DIMENSION_TEXTURE2D;\n    ShaderDesc.Texture2D.MostDetailedMip = ThisDesc.MipLevels - 1;\n    ShaderDesc.Texture2D.MipLevels = ThisDesc.MipLevels;\n\n    // Create new shader resource view\n    ID3D11ShaderResourceView* ShaderResource = nullptr;\n    hr = m_Device->CreateShaderResourceView(SrcSurface, &ShaderDesc, &ShaderResource);\n    if (FAILED(hr))\n    {\n        return ProcessFailure(m_Device, L\"Failed to create shader resource view for dirty rects\", L\"Desktop+ Error\", hr, SystemTransitionsExpectedErrors);\n    }\n\n    FLOAT BlendFactor[4] = {0.f, 0.f, 0.f, 0.f};\n    m_DeviceContext->OMSetBlendState(nullptr, BlendFactor, 0xFFFFFFFF);\n    m_DeviceContext->OMSetRenderTargets(1, &m_RTV, nullptr);\n    m_DeviceContext->VSSetShader(m_VertexShader, nullptr, 0);\n    m_DeviceContext->PSSetShader(m_PixelShader, nullptr, 0);\n    m_DeviceContext->PSSetShaderResources(0, 1, &ShaderResource);\n    m_DeviceContext->PSSetSamplers(0, 1, &m_SamplerLinear);\n    m_DeviceContext->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);\n\n    // Create space for vertices for the dirty rects if the current space isn't large enough\n    UINT BytesNeeded = sizeof(VERTEX) * NUMVERTICES * DirtyCount;\n    if (BytesNeeded > m_DirtyVertexBufferAllocSize)\n    {\n        if (m_DirtyVertexBufferAlloc)\n        {\n            delete [] m_DirtyVertexBufferAlloc;\n        }\n\n        m_DirtyVertexBufferAlloc = new (std::nothrow) BYTE[BytesNeeded];\n        if (!m_DirtyVertexBufferAlloc)\n        {\n            m_DirtyVertexBufferAllocSize = 0;\n            return ProcessFailure(nullptr, L\"Failed to allocate memory for dirty vertex buffer\", L\"Desktop+ Error\", E_OUTOFMEMORY);\n        }\n\n        m_DirtyVertexBufferAllocSize = BytesNeeded;\n    }\n\n    // Fill them in\n    VERTEX* DirtyVertex = reinterpret_cast<VERTEX*>(m_DirtyVertexBufferAlloc);\n    for (UINT i = 0; i < DirtyCount; ++i, DirtyVertex += NUMVERTICES)\n    {\n        SetDirtyVert(DirtyVertex, &(DirtyBuffer[i]), OffsetX, OffsetY, DeskDesc, &FullDesc, &ThisDesc, DirtyRectTotal);\n    }\n\n    // Create vertex buffer\n    D3D11_BUFFER_DESC BufferDesc;\n    RtlZeroMemory(&BufferDesc, sizeof(BufferDesc));\n    BufferDesc.Usage = D3D11_USAGE_DEFAULT;\n    BufferDesc.ByteWidth = BytesNeeded;\n    BufferDesc.BindFlags = D3D11_BIND_VERTEX_BUFFER;\n    BufferDesc.CPUAccessFlags = 0;\n    D3D11_SUBRESOURCE_DATA InitData;\n    RtlZeroMemory(&InitData, sizeof(InitData));\n    InitData.pSysMem = m_DirtyVertexBufferAlloc;\n\n    ID3D11Buffer* VertBuf = nullptr;\n    hr = m_Device->CreateBuffer(&BufferDesc, &InitData, &VertBuf);\n    if (FAILED(hr))\n    {\n        return ProcessFailure(m_Device, L\"Failed to create vertex buffer in dirty rect processing\", L\"Desktop+ Error\", hr, SystemTransitionsExpectedErrors);\n    }\n    UINT Stride = sizeof(VERTEX);\n    UINT Offset = 0;\n    m_DeviceContext->IASetVertexBuffers(0, 1, &VertBuf, &Stride, &Offset);\n\n    D3D11_VIEWPORT VP;\n    VP.Width = static_cast<FLOAT>(FullDesc.Width);\n    VP.Height = static_cast<FLOAT>(FullDesc.Height);\n    VP.MinDepth = 0.0f;\n    VP.MaxDepth = 1.0f;\n    VP.TopLeftX = 0.0f;\n    VP.TopLeftY = 0.0f;\n    m_DeviceContext->RSSetViewports(1, &VP);\n\n    m_DeviceContext->Draw(NUMVERTICES * DirtyCount, 0);\n\n    VertBuf->Release();\n    VertBuf = nullptr;\n\n    ShaderResource->Release();\n    ShaderResource = nullptr;\n\n    return DUPL_RETURN_SUCCESS;\n}\n\n//\n// Clean all references\n//\nvoid DISPLAYMANAGER::CleanRefs()\n{\n    if (m_DeviceContext)\n    {\n        m_DeviceContext->Release();\n        m_DeviceContext = nullptr;\n    }\n\n    if (m_Device)\n    {\n        m_Device->Release();\n        m_Device = nullptr;\n    }\n\n    if (m_MoveSurf)\n    {\n        m_MoveSurf->Release();\n        m_MoveSurf = nullptr;\n    }\n\n    if (m_VertexShader)\n    {\n        m_VertexShader->Release();\n        m_VertexShader = nullptr;\n    }\n\n    if (m_PixelShader)\n    {\n        m_PixelShader->Release();\n        m_PixelShader = nullptr;\n    }\n\n    if (m_InputLayout)\n    {\n        m_InputLayout->Release();\n        m_InputLayout = nullptr;\n    }\n\n    if (m_SamplerLinear)\n    {\n        m_SamplerLinear->Release();\n        m_SamplerLinear = nullptr;\n    }\n\n    if (m_RTV)\n    {\n        m_RTV->Release();\n        m_RTV = nullptr;\n    }\n}\n"
  },
  {
    "path": "src/DesktopPlus/DisplayManager.h",
    "content": "#ifndef _DISPLAYMANAGER_H_\n#define _DISPLAYMANAGER_H_\n\n#include \"CommonTypes.h\"\n\n//\n// Handles the task of processing frames\n//\nclass DISPLAYMANAGER\n{\n    public:\n        DISPLAYMANAGER();\n        ~DISPLAYMANAGER();\n        void InitD3D(DX_RESOURCES* Data);\n        ID3D11Device* GetDevice();\n        DUPL_RETURN ProcessFrame(_In_ FRAME_DATA* Data, _Inout_ ID3D11Texture2D* SharedSurf, INT OffsetX, INT OffsetY, _In_ DXGI_OUTPUT_DESC* DeskDesc, _Inout_ DPRect& DirtyRectTotal);\n        void CleanRefs();\n\n    private:\n    // methods\n        DUPL_RETURN CopyDirty(_In_ ID3D11Texture2D* SrcSurface, _Inout_ ID3D11Texture2D* SharedSurf, _In_reads_(DirtyCount) RECT* DirtyBuffer, UINT DirtyCount, INT OffsetX, INT OffsetY,\n                              _In_ DXGI_OUTPUT_DESC* DeskDesc, _Inout_ DPRect& DirtyRectTotal);\n        DUPL_RETURN CopyMove(_Inout_ ID3D11Texture2D* SharedSurf, _In_reads_(MoveCount) DXGI_OUTDUPL_MOVE_RECT* MoveBuffer, UINT MoveCount, INT OffsetX, INT OffsetY, _In_ DXGI_OUTPUT_DESC* DeskDesc,\n                             INT TexWidth, INT TexHeight, _Inout_ DPRect& DirtyRectTotal);\n        void SetDirtyVert(_Out_writes_(NUMVERTICES) VERTEX* Vertices, _In_ RECT* Dirty, INT OffsetX, INT OffsetY, _In_ DXGI_OUTPUT_DESC* DeskDesc, _In_ D3D11_TEXTURE2D_DESC* FullDesc, \n                          _In_ D3D11_TEXTURE2D_DESC* ThisDesc, _Inout_ DPRect& DirtyRectTotal);\n        void SetMoveRect(_Out_ RECT* SrcRect, _Out_ RECT* DestRect, _In_ DXGI_OUTPUT_DESC* DeskDesc, _In_ DXGI_OUTDUPL_MOVE_RECT* MoveRect, INT TexWidth, INT TexHeight);\n\n    // variables\n        ID3D11Device* m_Device;\n        ID3D11DeviceContext* m_DeviceContext;\n        ID3D11Texture2D* m_MoveSurf;\n        ID3D11VertexShader* m_VertexShader;\n        ID3D11PixelShader* m_PixelShader;\n        ID3D11InputLayout* m_InputLayout;\n        ID3D11RenderTargetView* m_RTV;\n        ID3D11SamplerState* m_SamplerLinear;\n        BYTE* m_DirtyVertexBufferAlloc;\n        UINT m_DirtyVertexBufferAllocSize;\n};\n\n#endif\n"
  },
  {
    "path": "src/DesktopPlus/DuplicationManager.cpp",
    "content": "#include \"DuplicationManager.h\"\n#include <wrl/client.h>\n\n#include <sdkddkver.h>\n\n//Keep building with 10.0.17763.0 / 1809 SDK optional\n#ifdef NTDDI_WIN10_RS5\n    #include <dxgi1_5.h>\n#else\n    #define DPLUS_DUP_NO_HDR\n#endif\n\n//\n// Constructor sets up references / variables\n//\nDUPLICATIONMANAGER::DUPLICATIONMANAGER() : m_DeskDupl(nullptr),\n                                           m_AcquiredDesktopImage(nullptr),\n                                           m_MetaDataBuffer(nullptr),\n                                           m_MetaDataSize(0),\n                                           m_OutputNumber(0),\n                                           m_Device(nullptr)\n{\n    RtlZeroMemory(&m_OutputDesc, sizeof(m_OutputDesc));\n}\n\n//\n// Destructor simply calls CleanRefs to destroy everything\n//\nDUPLICATIONMANAGER::~DUPLICATIONMANAGER()\n{\n    if (m_DeskDupl)\n    {\n        m_DeskDupl->Release();\n        m_DeskDupl = nullptr;\n    }\n\n    if (m_AcquiredDesktopImage)\n    {\n        m_AcquiredDesktopImage->Release();\n        m_AcquiredDesktopImage = nullptr;\n    }\n\n    if (m_MetaDataBuffer)\n    {\n        delete [] m_MetaDataBuffer;\n        m_MetaDataBuffer = nullptr;\n    }\n\n    if (m_Device)\n    {\n        m_Device->Release();\n        m_Device = nullptr;\n    }\n}\n\n//\n// Initialize duplication interfaces\n//\nDUPL_RETURN DUPLICATIONMANAGER::InitDupl(_In_ ID3D11Device* Device, UINT Output, bool WMRIgnoreVScreens, bool UseHDR)\n{\n    m_OutputNumber = Output;\n\n    #ifdef DPLUS_DUP_NO_HDR\n        UseHDR = false;\n    #endif\n\n    // Take a reference on the device\n    m_Device = Device;\n    m_Device->AddRef();\n\n    //Enumerate adapters the same way the main thread does so IDs match in multi-GPU situations\n    //Desktop Duplication of desktops spanned across multiple GPUs is not supported right now, however\n    //DuplicateOutput() will fail if the adapter for the output doesn't match Device\n    Microsoft::WRL::ComPtr<IDXGIFactory1> factory_ptr;\n    Microsoft::WRL::ComPtr<IDXGIAdapter> adapter_ptr_output;\n    int output_id_adapter = Output;           //Output ID on the adapter actually used. Only different from initial Output if there's desktops across multiple GPUs\n\n    HRESULT hr = CreateDXGIFactory1(__uuidof(IDXGIFactory1), (void**)&factory_ptr);\n    if (!FAILED(hr))\n    {\n        Microsoft::WRL::ComPtr<IDXGIAdapter> adapter_ptr;\n        UINT i = 0;\n        int output_count = 0;\n\n        while (factory_ptr->EnumAdapters(i, &adapter_ptr) != DXGI_ERROR_NOT_FOUND)\n        {\n            //Check if this a WMR virtual display adapter and skip it when the option is enabled\n            if (WMRIgnoreVScreens)\n            {\n                DXGI_ADAPTER_DESC adapter_desc;\n                adapter_ptr->GetDesc(&adapter_desc);\n\n                if (wcscmp(adapter_desc.Description, L\"Virtual Display Adapter\") == 0)\n                {\n                    ++i;\n                    continue;\n                }\n            }\n\n            //Count the available outputs\n            Microsoft::WRL::ComPtr<IDXGIOutput> output_ptr;\n            UINT output_index = 0;\n            while (adapter_ptr->EnumOutputs(output_index, &output_ptr) != DXGI_ERROR_NOT_FOUND)\n            {\n                //Check if this happens to be the output we're looking for\n                if ( (adapter_ptr_output == nullptr) && (Output == output_count) )\n                {\n                    adapter_ptr_output = adapter_ptr;\n                    output_id_adapter = output_index;\n                }\n\n                ++output_count;\n                ++output_index;\n            }\n\n            ++i;\n        }\n    }\n\n    if (adapter_ptr_output == nullptr)\n    {\n        return ProcessFailure(m_Device, L\"Failed to get the output's DXGI adapter\", L\"Desktop+ Error\", hr, SystemTransitionsExpectedErrors);\n    }\n\n    //Get output\n    Microsoft::WRL::ComPtr<IDXGIOutput> DxgiOutput;\n    hr = adapter_ptr_output->EnumOutputs(output_id_adapter, &DxgiOutput);\n    if (FAILED(hr))\n    {\n        return ProcessFailure(m_Device, L\"Failed to get specified DXGI output\", L\"Desktop+ Error\", hr, EnumOutputsExpectedErrors);\n    }\n\n    DxgiOutput->GetDesc(&m_OutputDesc);\n\n    //Create desktop duplication\n    if (!UseHDR)\n    {\n        Microsoft::WRL::ComPtr<IDXGIOutput1> DxgiOutput1;\n        hr = DxgiOutput.As(&DxgiOutput1);\n        if (FAILED(hr))\n        {\n            return ProcessFailure(nullptr, L\"Failed to get output as DxgiOutput1\", L\"Desktop+ Error\", hr);\n        }\n\n        hr = DxgiOutput1->DuplicateOutput(m_Device, &m_DeskDupl);\n        if (FAILED(hr))\n        {\n            if (hr == DXGI_ERROR_NOT_CURRENTLY_AVAILABLE)\n            {\n                ProcessFailure(m_Device, L\"There is already the maximum number of applications using the Desktop Duplication API running, please close one of those applications and then try again\", L\"Desktop+ Error\", hr);\n                return DUPL_RETURN_ERROR_UNEXPECTED;\n            }\n            return ProcessFailure(m_Device, L\"Failed to get duplicate output\", L\"Desktop+ Error\", hr, CreateDuplicationExpectedErrors);\n        }\n    }\n    else\n    {\n        #ifndef DPLUS_DUP_NO_HDR\n\n        Microsoft::WRL::ComPtr<IDXGIOutput5> DxgiOutput5;\n        hr = DxgiOutput.As(&DxgiOutput5);\n        if (FAILED(hr))\n        {\n            return ProcessFailure(nullptr, L\"Failed to get output as DxgiOutput5\", L\"Desktop+ Error\", hr);\n        }\n\n        const DXGI_FORMAT supported_formats[] = {DXGI_FORMAT_R16G16B16A16_FLOAT};\n        hr = DxgiOutput5->DuplicateOutput1(m_Device, 0, 1, supported_formats, &m_DeskDupl);\n        if (FAILED(hr))\n        {\n            if (hr == DXGI_ERROR_NOT_CURRENTLY_AVAILABLE)\n            {\n                ProcessFailure(m_Device, L\"There is already the maximum number of applications using the Desktop Duplication API running, please close one of those applications and then try again\", L\"Desktop+ Error\", hr);\n                return DUPL_RETURN_ERROR_UNEXPECTED;\n            }\n            return ProcessFailure(m_Device, L\"Failed to get duplicate output\", L\"Desktop+ Error\", hr, CreateDuplicationExpectedErrors);\n        }\n\n        #endif\n    }\n\n    return DUPL_RETURN_SUCCESS;\n}\n\n//\n// Retrieves mouse info and write it into PtrInfo\n//\nDUPL_RETURN DUPLICATIONMANAGER::GetMouse(_Inout_ PTR_INFO* PtrInfo, _In_ DXGI_OUTDUPL_FRAME_INFO* FrameInfo, INT OffsetX, INT OffsetY)\n{\n    // A non-zero mouse update timestamp indicates that there is a mouse position update and optionally a shape change\n    if (FrameInfo->LastMouseUpdateTime.QuadPart == 0)\n    {\n        return DUPL_RETURN_SUCCESS;\n    }\n\n    bool UpdatePosition = true;\n\n    // Make sure we don't update pointer position wrongly\n    // If pointer is invisible, make sure we did not get an update from another output that the last time that said pointer\n    // was visible, if so, don't set it to invisible or update.\n    if (!FrameInfo->PointerPosition.Visible && (PtrInfo->WhoUpdatedPositionLast != m_OutputNumber))\n    {\n        UpdatePosition = false;\n    }\n\n    // If two outputs both say they have a visible, only update if new update has newer timestamp\n    if (FrameInfo->PointerPosition.Visible && PtrInfo->Visible && (PtrInfo->WhoUpdatedPositionLast != m_OutputNumber) && (PtrInfo->LastTimeStamp.QuadPart > FrameInfo->LastMouseUpdateTime.QuadPart))\n    {\n        UpdatePosition = false;\n    }\n\n    // Update position\n    if (UpdatePosition)\n    {\n        PtrInfo->Position.x = FrameInfo->PointerPosition.Position.x + m_OutputDesc.DesktopCoordinates.left - OffsetX;\n        PtrInfo->Position.y = FrameInfo->PointerPosition.Position.y + m_OutputDesc.DesktopCoordinates.top - OffsetY;\n        PtrInfo->WhoUpdatedPositionLast = m_OutputNumber;\n        PtrInfo->LastTimeStamp = FrameInfo->LastMouseUpdateTime;\n        PtrInfo->Visible = FrameInfo->PointerPosition.Visible != 0;\n\n        //If pointer is not visible, set the hotspot to 0,0\n        if (!PtrInfo->Visible)\n        {\n            PtrInfo->ShapeInfo.HotSpot.x = 0;\n            PtrInfo->ShapeInfo.HotSpot.y = 0;\n        }\n    }\n\n    // No new shape\n    if (FrameInfo->PointerShapeBufferSize == 0)\n    {\n        PtrInfo->CursorShapeChanged = false;\n        return DUPL_RETURN_SUCCESS;\n    }\n\n    PtrInfo->CursorShapeChanged = true;\n\n    // Get shape\n    PtrInfo->ShapeBuffer.resize(FrameInfo->PointerShapeBufferSize, 0);\n    UINT BufferSizeRequired;\n    HRESULT hr = m_DeskDupl->GetFramePointerShape(FrameInfo->PointerShapeBufferSize, PtrInfo->ShapeBuffer.data(), &BufferSizeRequired, &(PtrInfo->ShapeInfo));\n    if (FAILED(hr))\n    {\n        PtrInfo->ShapeBuffer.clear();\n        return ProcessFailure(m_Device, L\"Failed to get frame pointer shape\", L\"Desktop+ Error\", hr, FrameInfoExpectedErrors);\n    }\n\n    return DUPL_RETURN_SUCCESS;\n}\n\n\n//\n// Get next frame and write it into Data\n//\n_Success_(*Timeout == false && return == DUPL_RETURN_SUCCESS)\nDUPL_RETURN DUPLICATIONMANAGER::GetFrame(_Out_ FRAME_DATA* Data, _Out_ bool* Timeout)\n{\n    IDXGIResource* DesktopResource = nullptr;\n    DXGI_OUTDUPL_FRAME_INFO FrameInfo;\n\n    // Get new frame\n    HRESULT hr = m_DeskDupl->AcquireNextFrame(100, &FrameInfo, &DesktopResource);\n    if (hr == DXGI_ERROR_WAIT_TIMEOUT)\n    {\n        *Timeout = true;\n        return DUPL_RETURN_SUCCESS;\n    }\n    *Timeout = false;\n\n    if (FAILED(hr))\n    {\n        return ProcessFailure(m_Device, L\"Failed to acquire next frame\", L\"Desktop+ Error\", hr, FrameInfoExpectedErrors);\n    }\n\n    // If still holding old frame, destroy it\n    if (m_AcquiredDesktopImage)\n    {\n        m_AcquiredDesktopImage->Release();\n        m_AcquiredDesktopImage = nullptr;\n    }\n\n    // QI for IDXGIResource\n    hr = DesktopResource->QueryInterface(__uuidof(ID3D11Texture2D), reinterpret_cast<void **>(&m_AcquiredDesktopImage));\n    DesktopResource->Release();\n    DesktopResource = nullptr;\n    if (FAILED(hr))\n    {\n        return ProcessFailure(nullptr, L\"Failed to QI for ID3D11Texture2D from acquired IDXGIResource\", L\"Desktop+ Error\", hr);\n    }\n\n    // Get metadata\n    if (FrameInfo.TotalMetadataBufferSize)\n    {\n        // Old buffer too small\n        if (FrameInfo.TotalMetadataBufferSize > m_MetaDataSize)\n        {\n            if (m_MetaDataBuffer)\n            {\n                delete [] m_MetaDataBuffer;\n                m_MetaDataBuffer = nullptr;\n            }\n            m_MetaDataBuffer = new (std::nothrow) BYTE[FrameInfo.TotalMetadataBufferSize];\n            if (!m_MetaDataBuffer)\n            {\n                m_MetaDataSize = 0;\n                Data->MoveCount = 0;\n                Data->DirtyCount = 0;\n                return ProcessFailure(nullptr, L\"Failed to allocate memory for metadata\", L\"Desktop+ Error\", E_OUTOFMEMORY);\n            }\n            m_MetaDataSize = FrameInfo.TotalMetadataBufferSize;\n        }\n\n        UINT BufSize = FrameInfo.TotalMetadataBufferSize;\n\n        // Get move rectangles\n        hr = m_DeskDupl->GetFrameMoveRects(BufSize, reinterpret_cast<DXGI_OUTDUPL_MOVE_RECT*>(m_MetaDataBuffer), &BufSize);\n        if (FAILED(hr))\n        {\n            Data->MoveCount = 0;\n            Data->DirtyCount = 0;\n            return ProcessFailure(nullptr, L\"Failed to get frame move rects\", L\"Desktop+ Error\", hr, FrameInfoExpectedErrors);\n        }\n        Data->MoveCount = BufSize / sizeof(DXGI_OUTDUPL_MOVE_RECT);\n\n        BYTE* DirtyRects = m_MetaDataBuffer + BufSize;\n        BufSize = FrameInfo.TotalMetadataBufferSize - BufSize;\n\n        // Get dirty rectangles\n        hr = m_DeskDupl->GetFrameDirtyRects(BufSize, reinterpret_cast<RECT*>(DirtyRects), &BufSize);\n        if (FAILED(hr))\n        {\n            Data->MoveCount = 0;\n            Data->DirtyCount = 0;\n            return ProcessFailure(nullptr, L\"Failed to get frame dirty rects\", L\"Desktop+ Error\", hr, FrameInfoExpectedErrors);\n        }\n        Data->DirtyCount = BufSize / sizeof(RECT);\n\n        Data->MetaData = m_MetaDataBuffer;\n    }\n\n    Data->Frame = m_AcquiredDesktopImage;\n    Data->FrameInfo = FrameInfo;\n\n    return DUPL_RETURN_SUCCESS;\n}\n\n//\n// Release frame\n//\nDUPL_RETURN DUPLICATIONMANAGER::DoneWithFrame()\n{\n    HRESULT hr = m_DeskDupl->ReleaseFrame();\n    if (FAILED(hr))\n    {\n        return ProcessFailure(m_Device, L\"Failed to release frame\", L\"Desktop+ Error\", hr, FrameInfoExpectedErrors);\n    }\n\n    if (m_AcquiredDesktopImage)\n    {\n        m_AcquiredDesktopImage->Release();\n        m_AcquiredDesktopImage = nullptr;\n    }\n\n    return DUPL_RETURN_SUCCESS;\n}\n\n//\n// Gets output desc into DescPtr\n//\nvoid DUPLICATIONMANAGER::GetOutputDesc(_Out_ DXGI_OUTPUT_DESC* DescPtr)\n{\n    *DescPtr = m_OutputDesc;\n}\n"
  },
  {
    "path": "src/DesktopPlus/DuplicationManager.h",
    "content": "#ifndef _DUPLICATIONMANAGER_H_\n#define _DUPLICATIONMANAGER_H_\n\n#include \"CommonTypes.h\"\n\n//\n// Handles the task of duplicating an output.\n//\nclass DUPLICATIONMANAGER\n{\n    public:\n        DUPLICATIONMANAGER();\n        ~DUPLICATIONMANAGER();\n        _Success_(*Timeout == false && return == DUPL_RETURN_SUCCESS) DUPL_RETURN GetFrame(_Out_ FRAME_DATA* Data, _Out_ bool* Timeout);\n        DUPL_RETURN DoneWithFrame();\n        DUPL_RETURN InitDupl(_In_ ID3D11Device* Device, UINT Output, bool WMRIgnoreVScreens, bool UseHDR);\n        DUPL_RETURN GetMouse(_Inout_ PTR_INFO* PtrInfo, _In_ DXGI_OUTDUPL_FRAME_INFO* FrameInfo, INT OffsetX, INT OffsetY);\n        void GetOutputDesc(_Out_ DXGI_OUTPUT_DESC* DescPtr);\n\n    private:\n\n    // vars\n        IDXGIOutputDuplication* m_DeskDupl;\n        ID3D11Texture2D* m_AcquiredDesktopImage;\n        _Field_size_bytes_(m_MetaDataSize) BYTE* m_MetaDataBuffer;\n        UINT m_MetaDataSize;\n        UINT m_OutputNumber;\n        DXGI_OUTPUT_DESC m_OutputDesc;\n        ID3D11Device* m_Device;\n};\n\n#endif\n"
  },
  {
    "path": "src/DesktopPlus/ElevatedMode.cpp",
    "content": "#include \"ElevatedMode.h\"\n\n#include <windowsx.h>\n\n#include \"InterprocessMessaging.h\"\n#include \"InputSimulator.h\"\n#include \"WindowManager.h\"\n#include \"Util.h\"\n#include \"Logging.h\"\n\nstatic bool g_ElevatedMode_ComInitDone = false;\n\nLRESULT CALLBACK WndProcElevated(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam);\nbool HandleIPCMessage(MSG msg);\n\nint ElevatedModeEnter(HINSTANCE hinstance)\n{\n    DPLog_Init(\"DesktopPlusElevatedMode\");\n\n    //Don't run if the dashboard app is not running or process isn't even elevated\n    if (!IPCManager::Get().IsDashboardAppRunning())\n    {\n        LOG_F(INFO, \"Started in elevated mode, but dashboard process is not running. Exiting...\");\n        return E_NOT_VALID_STATE;\n    }\n    else if (!IsProcessElevated())\n    {\n        LOG_F(INFO, \"Started in elevated mode, but process was not launched elevated. Exiting...\");\n        return E_NOT_VALID_STATE;\n    }\n\n    LOG_F(INFO, \"Desktop+ running in elevated mode\");\n\n    //Register class\n    WNDCLASSEXW wc;\n    wc.cbSize           = sizeof(WNDCLASSEXW);\n    wc.style            = 0;\n    wc.lpfnWndProc      = WndProcElevated;\n    wc.cbClsExtra       = 0;\n    wc.cbWndExtra       = 0;\n    wc.hInstance        = hinstance;\n    wc.hIcon            = nullptr;\n    wc.hCursor          = nullptr;\n    wc.hbrBackground    = nullptr;\n    wc.lpszMenuName     = nullptr;\n    wc.lpszClassName    = g_WindowClassNameElevatedMode;\n    wc.hIconSm          = nullptr;\n\n    if (!::RegisterClassExW(&wc))\n    {\n        return E_FAIL;\n    }\n\n    //Create window\n    HWND window_handle = ::CreateWindowW(g_WindowClassNameElevatedMode, L\"Desktop+ Elevated\",\n                                         0,\n                                         0, 0,\n                                         1, 1,\n                                         HWND_MESSAGE, nullptr, hinstance, nullptr);\n    if (!window_handle)\n    {\n        return E_FAIL;\n    }\n\n    //Allow IPC messages even when elevated\n    IPCManager::Get().DisableUIPForRegisteredMessages(window_handle);\n\n    //Send config update to dashboard and UI process to set elevated mode active\n    IPCManager::Get().PostConfigMessageToDashboardApp(configid_bool_state_misc_elevated_mode_active, true);\n    IPCManager::Get().PostConfigMessageToUIApp(configid_bool_state_misc_elevated_mode_active, true);\n\n    LOG_F(INFO, \"Finished startup\");\n\n    //Wait for callbacks, update or quit message\n    MSG msg;\n    while (::GetMessage(&msg, 0, 0, 0))\n    {\n        //Custom IPC messages\n        if (msg.message >= 0xC000)\n        {\n            HandleIPCMessage(msg);\n        }\n    }\n\n    LOG_F(INFO, \"Shutting down...\");\n\n    //Send config update to dashboard and UI process to disable it again\n    IPCManager::Get().PostConfigMessageToDashboardApp(configid_bool_state_misc_elevated_mode_active, false);\n    IPCManager::Get().PostConfigMessageToUIApp(configid_bool_state_misc_elevated_mode_active, false);\n\n    //Uninitialize COM if it was used\n    if (!g_ElevatedMode_ComInitDone)\n    {\n        ::CoUninitialize();\n        g_ElevatedMode_ComInitDone = false;\n    }\n\n    WindowManager::Get().ClearTempTopMostWindow();\n\n    return 0;\n}\n\nLRESULT CALLBACK WndProcElevated(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)\n{\n    switch (message)\n    {\n        case WM_COPYDATA:\n        {\n            MSG msg;\n            // Process all custom window messages posted before this\n            while (::PeekMessage(&msg, nullptr, 0xC000, 0xFFFF, PM_REMOVE))\n            {\n                HandleIPCMessage(msg);\n            }\n\n            msg.hwnd = hWnd;\n            msg.message = message;\n            msg.wParam = wParam;\n            msg.lParam = lParam;\n\n            HandleIPCMessage(msg);\n            break;\n        }\n        case WM_DESTROY:\n        {\n            ::PostQuitMessage(0);\n            break;\n        }\n        default:\n            return ::DefWindowProc(hWnd, message, wParam, lParam);\n    }\n\n    return 0;\n}\n\nbool HandleIPCMessage(MSG msg)\n{\n    static InputSimulator input_sim;\n\n    static std::string action_exe_path;\n    static std::string action_exe_arg;\n\n    //Input strings come as WM_COPYDATA\n    if (msg.message == WM_COPYDATA)\n    {\n        //At least check if the dashboard app is running and the wParam is its window\n        HWND dashboard_app = ::FindWindow(g_WindowClassNameDashboardApp, nullptr);\n        if ((dashboard_app == nullptr) || ((HWND)msg.wParam != dashboard_app))\n        {\n            return false;\n        }\n\n        COPYDATASTRUCT* pcds = (COPYDATASTRUCT*)msg.lParam;\n\n        //Arbitrary size limit to prevent some malicous applications from sending bad data\n        if (pcds->cbData <= 4096)\n        {\n            std::string copystr((char*)pcds->lpData, pcds->cbData); //We rely on the data length. The data is sent without the NUL byte\n\n            switch (pcds->dwData)\n            {\n                case ipcestrid_keyboard_text:\n                case ipcestrid_keyboard_text_force_unicode:\n                {\n                    //Pass string to InputSimulator\n                    input_sim.KeyboardText(copystr.c_str(), (pcds->dwData == ipcestrid_keyboard_text_force_unicode) );\n                    break;\n                }\n                case ipcestrid_launch_application_path:\n                {\n                    action_exe_path = copystr;\n                    action_exe_arg  = \"\";       //Also reset arg str since it's often not needed\n                    break;\n                }\n                case ipcestrid_launch_application_arg:\n                {\n                    action_exe_arg = copystr;\n                    break;\n                }\n            }\n            \n        }\n    }\n    else\n    {\n        IPCMsgID msgid = IPCManager::Get().GetIPCMessageID(msg.message);\n\n        if (msgid == ipcmsg_elevated_action)\n        {\n            switch (msg.wParam)\n            {\n                case ipceact_refresh:\n                {\n                    input_sim.RefreshScreenOffsets();\n                    break;\n                }\n                case ipceact_mouse_move:\n                {\n                    input_sim.MouseMove(GET_X_LPARAM(msg.lParam), GET_Y_LPARAM(msg.lParam));\n                    break;\n                };\n                case ipceact_mouse_hwheel:\n                {\n                    input_sim.MouseWheelHorizontal(pun_cast<float, LPARAM>(msg.lParam));\n                    break;\n                };\n                case ipceact_mouse_vwheel:\n                {\n                    input_sim.MouseWheelVertical(pun_cast<float, LPARAM>(msg.lParam));\n                    break;\n                };\n                case ipceact_pen_move:\n                {\n                    input_sim.PenMove(GET_X_LPARAM(msg.lParam), GET_Y_LPARAM(msg.lParam));\n                    break;\n                };\n                case ipceact_pen_button_down:\n                {\n                    (msg.lParam == 0) ? input_sim.PenSetPrimaryDown(true) : input_sim.PenSetSecondaryDown(true);\n                    break;\n                };\n                case ipceact_pen_button_up:\n                {\n                    (msg.lParam == 0) ? input_sim.PenSetPrimaryDown(false) : input_sim.PenSetSecondaryDown(false);\n                    break;\n                };\n                case ipceact_pen_leave:\n                {\n                    input_sim.PenLeave();\n                    break;\n                };\n                case ipceact_key_down:\n                {\n                    //Copy 3 keycodes from lparam\n                    unsigned char keycodes[3];\n                    memcpy(keycodes, &msg.lParam, 3);\n\n                    input_sim.KeyboardSetDown(keycodes);\n                    break;\n                };\n                case ipceact_key_up:\n                {\n                    //Copy 3 keycodes from lparam\n                    unsigned char keycodes[3];\n                    memcpy(keycodes, &msg.lParam, 3);\n\n                    input_sim.KeyboardSetUp(keycodes);\n                    break;\n                };\n                case ipceact_key_toggle:\n                {\n                    //Copy 3 keycodes from lparam\n                    unsigned char keycodes[3];\n                    memcpy(keycodes, &msg.lParam, 3);\n\n                    input_sim.KeyboardToggleState(keycodes);\n                    break;\n                };\n                case ipceact_key_press_and_release:\n                {\n                    input_sim.KeyboardPressAndRelease(msg.lParam);\n                    break;\n                };\n                case ipceact_key_togglekey_set:\n                {\n                    input_sim.KeyboardSetToggleKey(LOWORD(msg.lParam), HIWORD(msg.lParam));\n                    break;\n                }\n                case ipceact_keystate_w32_set:\n                {\n                    input_sim.KeyboardSetFromWin32KeyState(LOWORD(msg.lParam), HIWORD(msg.lParam));\n                    break;\n                }\n                case ipceact_keystate_set:\n                {\n                    input_sim.KeyboardSetKeyState((IPCKeyboardKeystateFlags)LOWORD(msg.lParam), HIWORD(msg.lParam));\n                    break;\n                }\n                case ipceact_keyboard_text_finish:\n                {\n                    input_sim.KeyboardTextFinish();\n                    break;\n                }\n                case ipceact_launch_application:\n                {\n                    //Init COM if necessary\n                    if (!g_ElevatedMode_ComInitDone)\n                    {\n                        if (::CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE) != RPC_E_CHANGED_MODE)\n                        {\n                            g_ElevatedMode_ComInitDone = true;\n                        }\n                    }\n\n                    //Convert path and arg to utf16\n                    std::wstring path_wstr = WStringConvertFromUTF8(action_exe_path.c_str());\n                    std::wstring arg_wstr  = WStringConvertFromUTF8(action_exe_arg.c_str());\n\n                    if (!path_wstr.empty())\n                    {   \n                        ::ShellExecute(nullptr, nullptr, path_wstr.c_str(), arg_wstr.c_str(), nullptr, SW_SHOWNORMAL);\n                    }\n                    break;\n                }\n                case ipceact_window_topmost_set:\n                {\n                    HWND window = (HWND)msg.lParam;\n                    (window != nullptr) ? WindowManager::Get().SetTempTopMostWindow(window) : WindowManager::Get().ClearTempTopMostWindow();\n                    break;\n                }\n            }\n        }\n    }\n\n    return true;\n}\n"
  },
  {
    "path": "src/DesktopPlus/ElevatedMode.h",
    "content": "#pragma once\n\n#define NOMINMAX\n#include <windows.h>\n\n//This runs the process in elevated input command mode, in which it only takes select window messages and passes them to InputSimulator.\n//\n//This isn't secure at all, I'm well aware.\n//There are certainly other more complex ways of implementation to make this seem more secure than the current one, but as far as I'm concerned we're fighting a battle that cannot be won.\n//As long as we're open source, not enforcing signed binaries and access rights to the application directory, there will always be an attack vector.\n//There's no way to actually trust the unelevated process requesting the inputs. Even if we isolated DesktopPlus.exe, we still couldn't trust SteamVR to not be compromised.\n//Both of these live in an environment where they are expected to be updated automatically by an unelevated process as well.\n//\n//The attack vector is at least smaller than when running Steam and everything related elevated as well.\n//Targeted attacks would be fairly easy if such code found the way of an user's machine, though.\n\nint ElevatedModeEnter(HINSTANCE hinstance);"
  },
  {
    "path": "src/DesktopPlus/InputSimulator.cpp",
    "content": "#include \"InputSimulator.h\"\n\n#include \"InterprocessMessaging.h\"\n#include \"OutputManager.h\"\n#include \"Util.h\"\n\nenum KeyboardWin32KeystateFlags\n{\n    kbd_w32keystate_flag_shift_down       = 1 << 0,\n    kbd_w32keystate_flag_ctrl_down        = 1 << 1,\n    kbd_w32keystate_flag_alt_down         = 1 << 2\n};\n\nfn_CreateSyntheticPointerDevice  InputSimulator::s_p_CreateSyntheticPointerDevice  = nullptr;\nfn_InjectSyntheticPointerInput   InputSimulator::s_p_InjectSyntheticPointerInput   = nullptr;\nfn_DestroySyntheticPointerDevice InputSimulator::s_p_DestroySyntheticPointerDevice = nullptr;\n\nInputSimulator::InputSimulator()\n{\n    RefreshScreenOffsets();\n\n    //Try to init pen functions if they're not loaded yet\n    if (!IsPenSimulationSupported())\n    {\n        LoadPenFunctions();\n    }\n\n    //Init pen state (rest can stay at default 0)\n    m_PenState.type = PT_PEN;\n    m_PenState.penInfo.pointerInfo.pointerType = PT_PEN;\n    m_PenState.penInfo.pressure = 1024;                     //Full pressure\n    m_PenState.penInfo.penMask = PEN_MASK_PRESSURE;         //Marked as optional, but without, applications expecting it may read 0 pressure still\n}\n\nInputSimulator::~InputSimulator()\n{\n    if (m_PenDevice != nullptr)\n    {\n        s_p_DestroySyntheticPointerDevice(m_PenDevice);\n    }\n}\n\nvoid InputSimulator::SetEventForMouseKeyCode(INPUT& input_event, unsigned char keycode, bool down)\n{\n    input_event.type = INPUT_MOUSE;\n\n    if (down)\n    {\n        switch (keycode)\n        {\n            case VK_LBUTTON:  input_event.mi.dwFlags = MOUSEEVENTF_LEFTDOWN;   break;\n            case VK_RBUTTON:  input_event.mi.dwFlags = MOUSEEVENTF_RIGHTDOWN;  break;\n            case VK_MBUTTON:  input_event.mi.dwFlags = MOUSEEVENTF_MIDDLEDOWN; break;\n            case VK_XBUTTON1: input_event.mi.dwFlags = MOUSEEVENTF_XDOWN;\n                              input_event.mi.mouseData = XBUTTON1;             break;\n            case VK_XBUTTON2: input_event.mi.dwFlags = MOUSEEVENTF_XDOWN;\n                              input_event.mi.mouseData = XBUTTON2;             break;\n            default:          break;\n        }\n    }\n    else\n    {\n        switch (keycode)\n        {\n            case VK_LBUTTON:  input_event.mi.dwFlags = MOUSEEVENTF_LEFTUP;   break;\n            case VK_RBUTTON:  input_event.mi.dwFlags = MOUSEEVENTF_RIGHTUP;  break;\n            case VK_MBUTTON:  input_event.mi.dwFlags = MOUSEEVENTF_MIDDLEUP; break;\n            case VK_XBUTTON1: input_event.mi.dwFlags = MOUSEEVENTF_XUP;\n                              input_event.mi.mouseData = XBUTTON1;           break;\n            case VK_XBUTTON2: input_event.mi.dwFlags = MOUSEEVENTF_XUP;\n                              input_event.mi.mouseData = XBUTTON2;           break;\n            default:          break;\n        }\n    }\n\n\n}\n\nbool InputSimulator::SetEventForKeyCode(INPUT& input_event, unsigned char keycode, bool down, bool skip_check)\n{\n    //Check if the mouse buttons are swapped as this also affects SendInput\n    if ( ((keycode == VK_LBUTTON) || (keycode == VK_RBUTTON)) && (::GetSystemMetrics(SM_SWAPBUTTON) != 0) )\n    {\n        keycode = (keycode == VK_LBUTTON) ? VK_RBUTTON : VK_LBUTTON;\n    }\n\n    bool key_down = (::GetAsyncKeyState(keycode) < 0);\n\n    if ( (keycode == 0) || ((key_down == down) && (!skip_check)) )\n        return false;\n\n    if ((keycode <= 6) && (keycode != VK_CANCEL)) //Mouse buttons need to be handled differently\n    {\n        SetEventForMouseKeyCode(input_event, keycode, down);\n    }\n    else\n    {\n        input_event.type = INPUT_KEYBOARD;\n\n        //Use scancodes if possible to increase compatibility (e.g. DirectInput games need scancodes)\n        UINT scancode = ::MapVirtualKey(keycode, MAPVK_VK_TO_VSC_EX);\n\n        //Pause/PrintScreen have a scancode too long for SendInput. May be possible to get to work with multiple input calls, but let's not bother for now\n        if ( (scancode != 0) || (keycode == VK_PAUSE) || (keycode == VK_SNAPSHOT) )\n        {\n            BYTE highbyte = HIBYTE(scancode);\n            bool is_extended = ((highbyte == 0xe0) || (highbyte == 0xe1));\n\n            //Not extended but needs to be for proper input simulation\n            if (!is_extended)\n            {\n                switch (keycode)\n                {\n                    case VK_INSERT:\n                    case VK_DELETE:\n                    case VK_PRIOR:\n                    case VK_NEXT:\n                    case VK_END:\n                    case VK_HOME:\n                    case VK_LEFT:\n                    case VK_UP:\n                    case VK_RIGHT:\n                    case VK_DOWN:\n                    {\n                        is_extended = true;\n                    }\n                    default: break;\n                }\n            }\n\n            input_event.ki.dwFlags = (is_extended) ? KEYEVENTF_SCANCODE | KEYEVENTF_EXTENDEDKEY : KEYEVENTF_SCANCODE;\n            input_event.ki.wScan   = scancode;\n        }\n        else //No scancode, use keycode\n        {\n            input_event.ki.dwFlags = 0;\n            input_event.ki.wVk     = keycode;\n        }\n\n        if (!down)\n        {\n            input_event.ki.dwFlags |= KEYEVENTF_KEYUP;\n        }\n    }\n\n    return true;\n}\n\nvoid InputSimulator::LoadPenFunctions()\n{\n    HMODULE h_user32 = ::LoadLibraryW(L\"user32.dll\");\n\n    if (h_user32 != nullptr)\n    {\n        s_p_CreateSyntheticPointerDevice  = (fn_CreateSyntheticPointerDevice) GetProcAddress(h_user32, \"CreateSyntheticPointerDevice\");\n        s_p_InjectSyntheticPointerInput   = (fn_InjectSyntheticPointerInput)  GetProcAddress(h_user32, \"InjectSyntheticPointerInput\");\n        s_p_DestroySyntheticPointerDevice = (fn_DestroySyntheticPointerDevice)GetProcAddress(h_user32, \"DestroySyntheticPointerDevice\");\n    }\n}\n\nvoid InputSimulator::CreatePenDeviceIfNeeded()\n{\n    if (m_PenDevice == nullptr)\n    {\n        m_PenDevice = s_p_CreateSyntheticPointerDevice(PT_PEN, 1, POINTER_FEEDBACK_INDIRECT);\n    }\n}\n\nvoid InputSimulator::RefreshScreenOffsets()\n{\n    if (m_ForwardToElevatedModeProcess)\n    {\n        IPCManager::Get().PostMessageToElevatedModeProcess(ipcmsg_elevated_action, ipceact_refresh);\n    }\n\n    m_SpaceMaxX = GetSystemMetrics(SM_CXVIRTUALSCREEN);\n    m_SpaceMaxY = GetSystemMetrics(SM_CYVIRTUALSCREEN);\n\n    m_SpaceMultiplierX = 65536.0f / m_SpaceMaxX;\n    m_SpaceMultiplierY = 65536.0f / m_SpaceMaxY;\n\n    m_SpaceOffsetX = GetSystemMetrics(SM_XVIRTUALSCREEN) * -1;\n    m_SpaceOffsetY = GetSystemMetrics(SM_YVIRTUALSCREEN) * -1;\n}\n\nvoid InputSimulator::MouseMove(int x, int y)\n{\n    if (m_ForwardToElevatedModeProcess)\n    {\n        IPCManager::Get().PostMessageToElevatedModeProcess(ipcmsg_elevated_action, ipceact_mouse_move, MAKELPARAM(x, y));\n        return;\n    }\n\n    INPUT input_event = { 0 };\n\n    input_event.type       = INPUT_MOUSE;\n    input_event.mi.dx      = LONG((x + m_SpaceOffsetX) * m_SpaceMultiplierX);\n    input_event.mi.dy      = LONG((y + m_SpaceOffsetY) * m_SpaceMultiplierY);\n    input_event.mi.dwFlags = MOUSEEVENTF_MOVE | MOUSEEVENTF_VIRTUALDESK | MOUSEEVENTF_ABSOLUTE;\n    \n    ::SendInput(1, &input_event, sizeof(INPUT));\n}\n\nvoid InputSimulator::MouseSetLeftDown(bool down)\n{\n    (down) ? KeyboardSetDown(VK_LBUTTON) : KeyboardSetUp(VK_LBUTTON);\n}\n\nvoid InputSimulator::MouseSetRightDown(bool down)\n{\n    (down) ? KeyboardSetDown(VK_RBUTTON) : KeyboardSetUp(VK_RBUTTON);\n}\n\nvoid InputSimulator::MouseSetMiddleDown(bool down)\n{\n    (down) ? KeyboardSetDown(VK_MBUTTON) : KeyboardSetUp(VK_MBUTTON);\n}\n\nvoid InputSimulator::MouseWheelHorizontal(float delta)\n{\n    if (m_ForwardToElevatedModeProcess)\n    {\n        IPCManager::Get().PostMessageToElevatedModeProcess(ipcmsg_elevated_action, ipceact_mouse_hwheel, pun_cast<LPARAM, float>(delta));\n        return;\n    }\n\n    INPUT input_event = {0};\n\n    input_event.type         = INPUT_MOUSE;\n    input_event.mi.dwFlags   = MOUSEEVENTF_HWHEEL;\n    input_event.mi.mouseData = DWORD(WHEEL_DELTA * delta);\n\n    ::SendInput(1, &input_event, sizeof(INPUT));\n}\n\nvoid InputSimulator::MouseWheelVertical(float delta)\n{\n    if (m_ForwardToElevatedModeProcess)\n    {\n        IPCManager::Get().PostMessageToElevatedModeProcess(ipcmsg_elevated_action, ipceact_mouse_vwheel, pun_cast<LPARAM, float>(delta));\n        return;\n    }\n\n    INPUT input_event = {0};\n\n    input_event.type         = INPUT_MOUSE;\n    input_event.mi.dwFlags   = MOUSEEVENTF_WHEEL;\n    input_event.mi.mouseData = DWORD(WHEEL_DELTA * delta);\n\n    ::SendInput(1, &input_event, sizeof(INPUT));\n}\n\nvoid InputSimulator::PenMove(int x, int y)\n{\n    if (!IsPenSimulationSupported())\n        return;\n\n    if (m_ForwardToElevatedModeProcess)\n    {\n        IPCManager::Get().PostMessageToElevatedModeProcess(ipcmsg_elevated_action, ipceact_pen_move, MAKELPARAM(x, y));\n        return;\n    }\n\n    CreatePenDeviceIfNeeded();\n\n    m_PenState.penInfo.pointerInfo.pointerFlags |= POINTER_FLAG_INRANGE | POINTER_FLAG_UPDATE;\n\n    //Pen input position doesn't appear to be clamped by OS like mouse input and can do weird things if it goes out of range\n    m_PenState.penInfo.pointerInfo.ptPixelLocation.x = clamp(x + m_SpaceOffsetX, 0, m_SpaceMaxX);\n    m_PenState.penInfo.pointerInfo.ptPixelLocation.y = clamp(y + m_SpaceOffsetY, 0, m_SpaceMaxY);\n\n    s_p_InjectSyntheticPointerInput(m_PenDevice, &m_PenState, 1);\n}\n\nvoid InputSimulator::PenSetPrimaryDown(bool down)\n{\n    if (!IsPenSimulationSupported())\n        return;\n\n    if (m_ForwardToElevatedModeProcess)\n    {\n        IPCManager::Get().PostMessageToElevatedModeProcess(ipcmsg_elevated_action, (down) ? ipceact_pen_button_down : ipceact_pen_button_up, 0);\n        return;\n    }\n\n    CreatePenDeviceIfNeeded();\n\n    if (down)\n    {\n        m_PenState.penInfo.pointerInfo.pointerFlags |= POINTER_FLAG_INCONTACT | POINTER_FLAG_DOWN | POINTER_FLAG_FIRSTBUTTON;\n        m_PenState.penInfo.pointerInfo.ButtonChangeType = POINTER_CHANGE_FIRSTBUTTON_DOWN;\n    }\n    else\n    {\n        m_PenState.penInfo.pointerInfo.pointerFlags &= ~(POINTER_FLAG_INCONTACT | POINTER_FLAG_DOWN | POINTER_FLAG_FIRSTBUTTON);\n        m_PenState.penInfo.pointerInfo.pointerFlags |= POINTER_FLAG_UP;\n        m_PenState.penInfo.pointerInfo.ButtonChangeType = POINTER_CHANGE_FIRSTBUTTON_UP;\n    }\n\n    m_PenState.penInfo.pointerInfo.pointerFlags &= ~POINTER_FLAG_UPDATE;\n\n    s_p_InjectSyntheticPointerInput(m_PenDevice, &m_PenState, 1);\n\n    m_PenState.penInfo.pointerInfo.pointerFlags &= ~(POINTER_FLAG_DOWN | POINTER_FLAG_UP);\n    m_PenState.penInfo.pointerInfo.ButtonChangeType = POINTER_CHANGE_NONE;\n}\n\nvoid InputSimulator::PenSetSecondaryDown(bool down)\n{\n    if (!IsPenSimulationSupported())\n        return;\n\n    if (m_ForwardToElevatedModeProcess)\n    {\n        IPCManager::Get().PostMessageToElevatedModeProcess(ipcmsg_elevated_action, (down) ? ipceact_pen_button_down : ipceact_pen_button_up, 1);\n        return;\n    }\n\n    CreatePenDeviceIfNeeded();\n\n    if (down)\n    {\n        m_PenState.penInfo.pointerInfo.pointerFlags |= POINTER_FLAG_INCONTACT | POINTER_FLAG_DOWN | POINTER_FLAG_SECONDBUTTON;\n        m_PenState.penInfo.pointerInfo.ButtonChangeType = POINTER_CHANGE_SECONDBUTTON_DOWN;\n        m_PenState.penInfo.penFlags = PEN_FLAG_BARREL;\n    }\n    else\n    {\n        m_PenState.penInfo.pointerInfo.pointerFlags &= ~(POINTER_FLAG_INCONTACT | POINTER_FLAG_DOWN | POINTER_FLAG_SECONDBUTTON);\n        m_PenState.penInfo.pointerInfo.pointerFlags |= POINTER_FLAG_UP;\n        m_PenState.penInfo.pointerInfo.ButtonChangeType = POINTER_CHANGE_SECONDBUTTON_UP;\n        m_PenState.penInfo.penFlags = 0;\n    }\n\n    m_PenState.penInfo.pointerInfo.pointerFlags &= ~POINTER_FLAG_UPDATE;\n\n    s_p_InjectSyntheticPointerInput(m_PenDevice, &m_PenState, 1);\n\n    m_PenState.penInfo.pointerInfo.pointerFlags &= ~(POINTER_FLAG_DOWN | POINTER_FLAG_UP);\n    m_PenState.penInfo.pointerInfo.ButtonChangeType = POINTER_CHANGE_NONE;\n}\n\nvoid InputSimulator::PenLeave()\n{\n    if (!IsPenSimulationSupported())\n        return;\n\n    if (m_ForwardToElevatedModeProcess)\n    {\n        IPCManager::Get().PostMessageToElevatedModeProcess(ipcmsg_elevated_action, ipceact_pen_leave);\n        return;\n    }\n\n    CreatePenDeviceIfNeeded();\n\n    if (m_PenState.penInfo.pointerInfo.pointerFlags & POINTER_FLAG_INRANGE)\n    {\n        m_PenState.penInfo.pointerInfo.pointerFlags = POINTER_FLAG_UPDATE;\n        m_PenState.penInfo.pointerInfo.ButtonChangeType = POINTER_CHANGE_NONE;\n        m_PenState.penInfo.penFlags = 0;\n\n        s_p_InjectSyntheticPointerInput(m_PenDevice, &m_PenState, 1);\n    }\n}\n\nvoid InputSimulator::KeyboardSetDown(unsigned char keycode)\n{\n    if (keycode == 0)\n        return;\n\n    if (m_ForwardToElevatedModeProcess)\n    {\n        LPARAM elevated_keycodes = 0;\n        memcpy(&elevated_keycodes, &keycode, 1);\n\n        IPCManager::Get().PostMessageToElevatedModeProcess(ipcmsg_elevated_action, ipceact_key_down, elevated_keycodes);\n        return;\n    }\n\n    INPUT input_event = {0};\n\n    if (SetEventForKeyCode(input_event, keycode, true))\n    {\n        ::SendInput(1, &input_event, sizeof(INPUT));\n    }\n}\n\nvoid InputSimulator::KeyboardSetDown(unsigned char keycode, bool down)\n{\n    (down) ? KeyboardSetDown(keycode) : KeyboardSetUp(keycode);\n}\n\nvoid InputSimulator::KeyboardSetUp(unsigned char keycode)\n{\n    if (keycode == 0)\n        return;\n\n    if (m_ForwardToElevatedModeProcess)\n    {\n        LPARAM elevated_keycodes = 0;\n        memcpy(&elevated_keycodes, &keycode, 1);\n\n        IPCManager::Get().PostMessageToElevatedModeProcess(ipcmsg_elevated_action, ipceact_key_up, elevated_keycodes);\n        return;\n    }\n\n    INPUT input_event = {0};\n\n    if (SetEventForKeyCode(input_event, keycode, false))\n    {\n        ::SendInput(1, &input_event, sizeof(INPUT));\n    }\n}\n\n//Why so awfully specific, seems wasteful? Spamming the key events separately can confuse applications sometimes and we want to make sure the keys are really pressed at once\nvoid InputSimulator::KeyboardSetDown(unsigned char keycodes[3])\n{\n    if (m_ForwardToElevatedModeProcess)\n    {\n        LPARAM elevated_keycodes = 0;\n        memcpy(&elevated_keycodes, keycodes, 3);\n\n        IPCManager::Get().PostMessageToElevatedModeProcess(ipcmsg_elevated_action, ipceact_key_down, elevated_keycodes);\n        return;\n    }\n\n    INPUT input_event[3] = { 0 };\n\n    int used_event_count = 0;\n    for (int i = 0; i < 3; ++i)\n    {\n        used_event_count += SetEventForKeyCode(input_event[used_event_count], keycodes[i], true);\n    }\n\n    if (used_event_count != 0)\n    {\n        ::SendInput(used_event_count, input_event, sizeof(INPUT));\n    }\n}\n\nvoid InputSimulator::KeyboardSetUp(unsigned char keycodes[3])\n{\n    if (m_ForwardToElevatedModeProcess)\n    {\n        LPARAM elevated_keycodes = 0;\n        memcpy(&elevated_keycodes, keycodes, 3);\n\n        IPCManager::Get().PostMessageToElevatedModeProcess(ipcmsg_elevated_action, ipceact_key_up, elevated_keycodes);\n        return;\n    }\n\n    INPUT input_event[3] = { 0 };\n\n    int used_event_count = 0;\n    for (int i = 0; i < 3; ++i)\n    {\n        used_event_count += SetEventForKeyCode(input_event[used_event_count], keycodes[i], false);\n    }\n\n    if (used_event_count != 0)\n    {\n        ::SendInput(used_event_count, input_event, sizeof(INPUT));\n    }\n}\n\nvoid InputSimulator::KeyboardToggleState(unsigned char keycode)\n{\n    if (keycode == 0)\n        return;\n\n    //GetAsyncKeyState is subject to UIPI, so always forward it\n    if (m_ForwardToElevatedModeProcess)\n    {\n        LPARAM elevated_keycodes = 0;\n        memcpy(&elevated_keycodes, &keycode, 1);\n\n        IPCManager::Get().PostMessageToElevatedModeProcess(ipcmsg_elevated_action, ipceact_key_toggle, elevated_keycodes);\n        return;\n    }\n\n    if (IsKeyDown(keycode))  //If already pressed, release key\n    {\n        KeyboardSetUp(keycode);\n    }\n    else\n    {\n        KeyboardSetDown(keycode);\n    }\n}\n\nvoid InputSimulator::KeyboardToggleState(unsigned char keycodes[3])\n{\n    if (m_ForwardToElevatedModeProcess)\n    {\n        LPARAM elevated_keycodes = 0;\n        memcpy(&elevated_keycodes, keycodes, 3);\n\n        IPCManager::Get().PostMessageToElevatedModeProcess(ipcmsg_elevated_action, ipceact_key_toggle, elevated_keycodes);\n        return;\n    }\n\n    INPUT input_event[3] = {0};\n    int used_event_count = 0;\n\n    for (int i = 0; i < 3; ++i)\n    {\n        used_event_count += SetEventForKeyCode(input_event[used_event_count], keycodes[i], !IsKeyDown(keycodes[i]));\n    }\n\n    if (used_event_count != 0)\n    {\n        ::SendInput(used_event_count, input_event, sizeof(INPUT));\n    }\n}\n\nvoid InputSimulator::KeyboardPressAndRelease(unsigned char keycode)\n{\n    if (keycode == 0)\n        return;\n\n    if (m_ForwardToElevatedModeProcess)\n    {\n        IPCManager::Get().PostMessageToElevatedModeProcess(ipcmsg_elevated_action, ipceact_key_press_and_release, keycode);\n        return;\n    }\n\n    INPUT input_event[2] = {0};\n    int used_event_count = 0;\n\n    used_event_count += SetEventForKeyCode(input_event[used_event_count], keycode, true);\n    used_event_count += SetEventForKeyCode(input_event[used_event_count], keycode, false);\n\n    ::SendInput(used_event_count, input_event, sizeof(INPUT));\n}\n\nvoid InputSimulator::KeyboardSetToggleKey(unsigned char keycode, bool toggled)\n{\n    if (keycode == 0) \n        return;\n\n    if (m_ForwardToElevatedModeProcess)\n    {\n        IPCManager::Get().PostMessageToElevatedModeProcess(ipcmsg_elevated_action, ipceact_key_togglekey_set, MAKELPARAM(keycode, toggled));\n        return;\n    }\n\n    bool is_toggled = ((::GetKeyState(keycode) & 0x0001) != 0);\n\n    if (toggled == is_toggled)\n        return;\n\n    INPUT input_event[3] = {0};\n    int used_event_count = 0;\n\n    used_event_count += SetEventForKeyCode(input_event[used_event_count], keycode, false);       //Release if it happens to be down\n    used_event_count += SetEventForKeyCode(input_event[used_event_count], keycode, true,  true); //Press...\n    used_event_count += SetEventForKeyCode(input_event[used_event_count], keycode, false, true); //...and release, even if the state wouldn't change (last arg)\n\n    ::SendInput(used_event_count, input_event, sizeof(INPUT));\n}\n\nvoid InputSimulator::KeyboardSetFromWin32KeyState(unsigned short keystate, bool down)\n{\n    unsigned char keycode  = LOBYTE(keystate);\n    bool key_down = IsKeyDown(keycode);\n\n    if (key_down == down)\n        return; //Nothing to be done\n\n    if (m_ForwardToElevatedModeProcess)\n    {\n        IPCManager::Get().PostMessageToElevatedModeProcess(ipcmsg_elevated_action, ipceact_keystate_w32_set, MAKELPARAM(keystate, down));\n        return;\n    }\n\n    unsigned char flags = HIBYTE(keystate);\n    bool flag_shift   = (flags & kbd_w32keystate_flag_shift_down);\n    bool flag_ctrl    = (flags & kbd_w32keystate_flag_ctrl_down);\n    bool flag_alt     = (flags & kbd_w32keystate_flag_alt_down);\n    bool caps_toggled = ((::GetKeyState(VK_CAPITAL) & 0x0001) != 0);\n\n    INPUT input_event[16] = { 0 };\n    int used_event_count = 0;\n\n    //Add events for modifier keys if needed\n    if (!flag_shift) \n    {\n        //Try releasing both if any is down\n        if (IsKeyDown(VK_SHIFT))\n        {\n            used_event_count += SetEventForKeyCode(input_event[used_event_count], VK_LSHIFT, false);\n            used_event_count += SetEventForKeyCode(input_event[used_event_count], VK_RSHIFT, false);\n        }\n    }\n    else if (!IsKeyDown(VK_SHIFT)) //Only set the left key if none are down\n    {\n        used_event_count += SetEventForKeyCode(input_event[used_event_count], VK_LSHIFT, true);\n    }\n\n    if (!flag_ctrl)\n    { \n        if (IsKeyDown(VK_CONTROL))\n        {\n            used_event_count += SetEventForKeyCode(input_event[used_event_count], VK_LCONTROL, false);\n            used_event_count += SetEventForKeyCode(input_event[used_event_count], VK_RCONTROL, false);\n        }\n    }\n    else if (!IsKeyDown(VK_CONTROL))\n    {\n        used_event_count += SetEventForKeyCode(input_event[used_event_count], VK_LCONTROL, true);\n    }\n\n    if (!flag_alt)\n    { \n        if (IsKeyDown(VK_MENU))\n        {\n            used_event_count += SetEventForKeyCode(input_event[used_event_count], VK_LMENU, false);\n            used_event_count += SetEventForKeyCode(input_event[used_event_count], VK_RMENU, false);\n        }\n    }\n    else if (!IsKeyDown(VK_MENU))\n    {\n        used_event_count += SetEventForKeyCode(input_event[used_event_count], VK_LMENU, true);\n    }\n\n    //Add events to handle caps lock state\n    if (caps_toggled)\n    {\n        used_event_count += SetEventForKeyCode(input_event[used_event_count], VK_CAPITAL, false);       //Release if it happens to be down\n        used_event_count += SetEventForKeyCode(input_event[used_event_count], VK_CAPITAL, true,  true); //Press...\n        used_event_count += SetEventForKeyCode(input_event[used_event_count], VK_CAPITAL, false, true); //...and release, even if the state wouldn't change (last arg)\n    }\n\n    //Add event for actual keycode\n    used_event_count += SetEventForKeyCode(input_event[used_event_count], keycode, down);\n\n    if (used_event_count != 0)\n    {\n        ::SendInput(used_event_count, input_event, sizeof(INPUT));\n    }\n}\n\nvoid InputSimulator::KeyboardSetKeyState(IPCKeyboardKeystateFlags flags, unsigned char keycode)\n{\n    if (m_ForwardToElevatedModeProcess)\n    {\n        IPCManager::Get().PostMessageToElevatedModeProcess(ipcmsg_elevated_action, ipceact_keystate_set, MAKELPARAM(flags, keycode));\n        return;\n    }\n\n    //Numpad input simulation is a bit weird\n    //It seems like numpad (especially shift+numpad) is something that is taken care of in a lower layer and as such confuses Windows if we just send our straight inputs\n    //Basically it either doesn't produce an input with numlock off or shift can get stuck in a way that requires both shift keys to be released\n    //So we just end up handling this manually below\n    if (((keycode >= VK_NUMPAD0) && (keycode <= VK_NUMPAD9)) || (keycode == VK_DECIMAL))\n    {\n        const bool is_numlock_on        = ((::GetKeyState(VK_NUMLOCK) & 0x0001) != 0);\n        const bool is_shift_down        = ((flags & kbd_keystate_flag_lshift_down) || (flags & kbd_keystate_flag_rshift_down));\n        const bool is_double_shift_down = ((flags & kbd_keystate_flag_lshift_down) && (flags & kbd_keystate_flag_rshift_down));\n\n        if ((!is_numlock_on) || (is_shift_down))\n        {\n            //Swap numpad keycodes with regular ones\n            //One might think these should be sent as unextended scancodes down the line, but doing so makes them be treated as numpad inputs again with all the issues we're trying to avoid here\n            switch (keycode)\n            {\n                case VK_NUMPAD0: keycode = VK_INSERT; break;\n                case VK_NUMPAD1: keycode = VK_END;    break;\n                case VK_NUMPAD2: keycode = VK_DOWN;   break;\n                case VK_NUMPAD3: keycode = VK_NEXT;   break;\n                case VK_NUMPAD4: keycode = VK_LEFT;   break;\n                case VK_NUMPAD5: keycode = VK_CLEAR;  break;\n                case VK_NUMPAD6: keycode = VK_RIGHT;  break;\n                case VK_NUMPAD7: keycode = VK_HOME;   break;\n                case VK_NUMPAD8: keycode = VK_UP;     break;\n                case VK_NUMPAD9: keycode = VK_PRIOR;  break;\n                case VK_DECIMAL: keycode = VK_DELETE; break;\n            }\n\n            //Shift needs to be up to get normal cursor movement (unless both shifts are down)\n            if ((is_numlock_on) && (is_shift_down) && (!is_double_shift_down))\n            {\n                flags = IPCKeyboardKeystateFlags(flags & ~(kbd_keystate_flag_lshift_down | kbd_keystate_flag_rshift_down));\n            }\n        }\n    }\n\n    INPUT input_event[10] = {0};\n    int used_event_count = 0;\n\n    //Add events for modifier keys if needed\n    used_event_count += SetEventForKeyCode(input_event[used_event_count], VK_LSHIFT,   (flags & kbd_keystate_flag_lshift_down));\n    used_event_count += SetEventForKeyCode(input_event[used_event_count], VK_RSHIFT,   (flags & kbd_keystate_flag_rshift_down));\n    used_event_count += SetEventForKeyCode(input_event[used_event_count], VK_LCONTROL, (flags & kbd_keystate_flag_lctrl_down));\n    used_event_count += SetEventForKeyCode(input_event[used_event_count], VK_RCONTROL, (flags & kbd_keystate_flag_rctrl_down));\n    used_event_count += SetEventForKeyCode(input_event[used_event_count], VK_LMENU,    (flags & kbd_keystate_flag_lalt_down));\n    used_event_count += SetEventForKeyCode(input_event[used_event_count], VK_RMENU,    (flags & kbd_keystate_flag_ralt_down));\n\n    //Add events to handle caps lock state\n    bool caps_toggled = ((::GetKeyState(VK_CAPITAL) & 0x0001) != 0);\n    if (caps_toggled != bool(flags & kbd_keystate_flag_capslock_toggled))\n    {\n        used_event_count += SetEventForKeyCode(input_event[used_event_count], VK_CAPITAL, false);       //Release if it happens to be down\n        used_event_count += SetEventForKeyCode(input_event[used_event_count], VK_CAPITAL, true,  true); //Press...\n        used_event_count += SetEventForKeyCode(input_event[used_event_count], VK_CAPITAL, false, true); //...and release, even if the state wouldn't change (last arg)\n    }\n\n    //Add event for actual keycode, but skip keys handled by the keystate flags\n    switch (keycode)\n    {\n        case VK_SHIFT:\n        case VK_LSHIFT:\n        case VK_RSHIFT:\n        case VK_CONTROL:\n        case VK_LCONTROL:\n        case VK_RCONTROL:\n        case VK_MENU:\n        case VK_LMENU:\n        case VK_RMENU:\n        case VK_CAPITAL:\n        {\n            break;\n        }\n\n        default:\n        {\n            used_event_count += SetEventForKeyCode(input_event[used_event_count], keycode, (flags & kbd_keystate_flag_key_down));\n        }\n    }\n\n    if (used_event_count != 0)\n    {\n        ::SendInput(used_event_count, input_event, sizeof(INPUT));\n    }\n}\n\nvoid InputSimulator::KeyboardText(const char* str_utf8, bool always_use_unicode_event)\n{\n    if (m_ForwardToElevatedModeProcess)\n    {\n        OutputManager* outmgr = OutputManager::Get();\n\n        if (outmgr != nullptr)\n        {\n            IPCElevatedStringID str_id = (always_use_unicode_event) ? ipcestrid_keyboard_text_force_unicode : ipcestrid_keyboard_text;\n            IPCManager::Get().SendStringToElevatedModeProcess(str_id, str_utf8, outmgr->GetWindowHandle());\n            m_ElevatedModeHasTextQueued = true;\n        }\n        return;\n    }\n\n    //Convert to UTF16\n    std::wstring wstr = WStringConvertFromUTF8(str_utf8);\n\n    INPUT input_event = { 0 };\n    input_event.type = INPUT_KEYBOARD;\n\n    if (!always_use_unicode_event)\n    {\n        //This function could just use KEYEVENTF_UNICODE on all printable characters to get working text input (we still do that for type string actions though)\n        //However, in order to trigger shortcuts and non-text events in applications, at least 0-9 & A-Z are simulated as proper key events\n        //For consistent handling, capslock and shift are reset at the start, but that shouldn't be an issue in practice\n\n        if ((::GetKeyState(VK_CAPITAL) & 0x0001) != 0) //Turn off capslock if it's on\n        {\n            input_event.ki.dwFlags = 0;\n            input_event.ki.wVk = VK_CAPITAL;\n            m_KeyboardTextQueue.push_back(input_event);\n\n            input_event.ki.dwFlags = KEYEVENTF_KEYUP;\n            m_KeyboardTextQueue.push_back(input_event);\n        }\n\n        if (::GetAsyncKeyState(VK_SHIFT) < 0) //Release shift if it's down\n        {\n            input_event.ki.dwFlags = KEYEVENTF_KEYUP;\n            input_event.ki.wVk = VK_SHIFT;\n            m_KeyboardTextQueue.push_back(input_event);\n        }\n\n        for (wchar_t current_char: wstr)\n        {\n            if (current_char == '\\b')   //Backspace, needs special handling\n            {\n                input_event.ki.dwFlags = 0;\n                input_event.ki.wVk = VK_BACK;\n                m_KeyboardTextQueue.push_back(input_event);\n\n                input_event.ki.dwFlags = KEYEVENTF_KEYUP;\n                m_KeyboardTextQueue.push_back(input_event);\n            }\n            else if (current_char == '\\n')  //Enter, needs special handling\n            {\n                input_event.ki.dwFlags = 0;\n                input_event.ki.wVk = VK_RETURN;\n                m_KeyboardTextQueue.push_back(input_event);\n\n                input_event.ki.dwFlags = KEYEVENTF_KEYUP;\n                m_KeyboardTextQueue.push_back(input_event);\n            }\n            else if ( ((current_char >= '0') && (current_char <= '9')) || (current_char == ' ') ) //0 - 9 and space, simulate keydown/up                          \n            {\n                input_event.ki.dwFlags = 0;\n                input_event.ki.wVk = current_char;\n                m_KeyboardTextQueue.push_back(input_event);\n\n                input_event.ki.dwFlags = KEYEVENTF_KEYUP;\n                m_KeyboardTextQueue.push_back(input_event);\n            }\n            else if ((current_char >= 'a') && (current_char <= 'z')) //a - z, simulate keydown/up  \n            {\n                input_event.ki.dwFlags = 0;\n                input_event.ki.wVk = current_char - ('a' - 'A');\n                m_KeyboardTextQueue.push_back(input_event);\n\n                input_event.ki.dwFlags = KEYEVENTF_KEYUP;\n                m_KeyboardTextQueue.push_back(input_event);\n            }\n            else if ((current_char >= 'A') && (current_char <= 'Z')) //A - Z, simulate keydown/up  \n            {\n                input_event.ki.dwFlags = 0;\n                input_event.ki.wVk = VK_SHIFT;\n                m_KeyboardTextQueue.push_back(input_event);\n\n                input_event.ki.dwFlags = 0;\n                input_event.ki.wVk = current_char;\n                m_KeyboardTextQueue.push_back(input_event);\n\n                input_event.ki.dwFlags = KEYEVENTF_KEYUP;\n                m_KeyboardTextQueue.push_back(input_event);\n\n                input_event.ki.wVk = VK_SHIFT;\n                m_KeyboardTextQueue.push_back(input_event);\n            }\n            else\n            {\n                input_event.ki.dwFlags = KEYEVENTF_UNICODE;\n                input_event.ki.wVk = 0;\n                input_event.ki.wScan = current_char;\n                m_KeyboardTextQueue.push_back(input_event);\n\n                input_event.ki.dwFlags = KEYEVENTF_UNICODE | KEYEVENTF_KEYUP;\n                m_KeyboardTextQueue.push_back(input_event);\n            }\n        }\n    }\n    else\n    {\n        for (wchar_t current_char: wstr)\n        {\n            input_event.ki.dwFlags = KEYEVENTF_UNICODE;\n            input_event.ki.wScan = current_char;\n            m_KeyboardTextQueue.push_back(input_event);\n\n            input_event.ki.dwFlags = KEYEVENTF_UNICODE | KEYEVENTF_KEYUP;\n            m_KeyboardTextQueue.push_back(input_event);\n        }\n    }\n}\n\nvoid InputSimulator::KeyboardTextFinish()\n{\n    if (m_ForwardToElevatedModeProcess)\n    {\n        //Only send if we know there is queued text in that process\n        if (m_ElevatedModeHasTextQueued)\n        {\n            IPCManager::Get().PostMessageToElevatedModeProcess(ipcmsg_elevated_action, ipceact_keyboard_text_finish);\n            m_ElevatedModeHasTextQueued = false;\n        }\n        return;\n    }\n\n    if (!m_KeyboardTextQueue.empty())\n    {\n        ::SendInput((UINT)m_KeyboardTextQueue.size(), m_KeyboardTextQueue.data(), sizeof(INPUT));\n\n        m_KeyboardTextQueue.clear();\n    }\n}\n\nvoid InputSimulator::SetElevatedModeForwardingActive(bool do_forward)\n{\n    m_ForwardToElevatedModeProcess = do_forward;\n}\n\nbool InputSimulator::IsKeyDown(unsigned char keycode)\n{\n    //Check if the mouse buttons are swapped\n    if ( ((keycode == VK_LBUTTON) || (keycode == VK_RBUTTON)) && (::GetSystemMetrics(SM_SWAPBUTTON) != 0) )\n    {\n        keycode = (keycode == VK_LBUTTON) ? VK_RBUTTON : VK_LBUTTON;\n    }\n\n    return (::GetAsyncKeyState(keycode) < 0);\n}\n\nbool InputSimulator::IsPenSimulationSupported()\n{\n    return (s_p_CreateSyntheticPointerDevice != nullptr);\n}\n"
  },
  {
    "path": "src/DesktopPlus/InputSimulator.h",
    "content": "#ifndef _INPUTSIMULATOR_H_\n#define _INPUTSIMULATOR_H_\n\n#define NOMINMAX\n#include <windows.h>\n\n#include <vector>\n\n//Dashboard_Back exists, but not doesn't map to \"Go Back\" ...okay!\n#define Button_Dashboard_GoHome vr::k_EButton_IndexController_A\n#define Button_Dashboard_GoBack vr::k_EButton_IndexController_B\n\nenum IPCKeyboardKeystateFlags : unsigned char;\n\n//SyntheticPointer functions are loaded manually to not require OS support to run the application (Windows 10 1809+ should have them though)\ntypedef HANDLE HSYNTHETICPOINTERDEVICE_DPLUS;\n#ifndef NTDDI_WIN10_RS5\n    typedef enum { POINTER_FEEDBACK_DEFAULT = 1, POINTER_FEEDBACK_INDIRECT = 2, POINTER_FEEDBACK_NONE = 3 } POINTER_FEEDBACK_MODE;\n#endif\n\ntypedef HSYNTHETICPOINTERDEVICE_DPLUS (WINAPI* fn_CreateSyntheticPointerDevice) (_In_ POINTER_INPUT_TYPE pointerType, _In_ ULONG maxCount, _In_ POINTER_FEEDBACK_MODE mode);\ntypedef BOOL                          (WINAPI* fn_InjectSyntheticPointerInput)  (_In_ HSYNTHETICPOINTERDEVICE_DPLUS device, _In_reads_(count) CONST POINTER_TYPE_INFO* pointerInfo, _In_ UINT32 count);\ntypedef void                          (WINAPI* fn_DestroySyntheticPointerDevice)(_In_ HSYNTHETICPOINTERDEVICE_DPLUS device);\n\nclass InputSimulator\n{\n    private:\n        int m_SpaceMaxX = 0;\n        int m_SpaceMaxY = 0;\n        float m_SpaceMultiplierX = 1.0f;\n        float m_SpaceMultiplierY = 1.0f;\n        int m_SpaceOffsetX = 0;\n        int m_SpaceOffsetY = 0;\n\n        static fn_CreateSyntheticPointerDevice  s_p_CreateSyntheticPointerDevice;\n        static fn_InjectSyntheticPointerInput   s_p_InjectSyntheticPointerInput;\n        static fn_DestroySyntheticPointerDevice s_p_DestroySyntheticPointerDevice;\n\n        HSYNTHETICPOINTERDEVICE_DPLUS m_PenDevice = nullptr;\n        POINTER_TYPE_INFO m_PenState = {0};\n\n        std::vector<INPUT> m_KeyboardTextQueue;\n        bool m_ForwardToElevatedModeProcess = false;\n        bool m_ElevatedModeHasTextQueued    = false;\n\n        void CreatePenDeviceIfNeeded();\n\n        static void LoadPenFunctions();\n        static void SetEventForMouseKeyCode(INPUT& input_event, unsigned char keycode, bool down);\n        //Set the event if it would change key state. Returns if anything was written to input_event\n        static bool SetEventForKeyCode(INPUT& input_event, unsigned char keycode, bool down, bool skip_check = false);\n\n    public:\n        InputSimulator();\n        ~InputSimulator();\n        void RefreshScreenOffsets();\n\n        void MouseMove(int x, int y);\n        void MouseSetLeftDown(bool down);\n        void MouseSetRightDown(bool down);\n        void MouseSetMiddleDown(bool down);\n        void MouseWheelHorizontal(float delta);\n        void MouseWheelVertical(float delta);\n\n        void PenMove(int x, int y);\n        void PenSetPrimaryDown(bool down);\n        void PenSetSecondaryDown(bool down);\n        void PenLeave();\n\n        void KeyboardSetDown(unsigned char keycode);\n        void KeyboardSetDown(unsigned char keycode, bool down);\n        void KeyboardSetUp(unsigned char keycode);\n        void KeyboardSetDown(unsigned char keycodes[3]);\n        void KeyboardSetUp(unsigned char keycodes[3]);\n        void KeyboardToggleState(unsigned char keycode);\n        void KeyboardToggleState(unsigned char keycodes[3]);\n        void KeyboardPressAndRelease(unsigned char keycode);\n        void KeyboardSetToggleKey(unsigned char keycode, bool toggled);\n        void KeyboardSetFromWin32KeyState(unsigned short keystate, bool down);          //Keystate as returned by VkKeyScan()\n        void KeyboardSetKeyState(IPCKeyboardKeystateFlags flags, unsigned char keycode);\n        void KeyboardText(const char* str_utf8, bool always_use_unicode_event = false);\n        void KeyboardTextFinish();\n\n        void SetElevatedModeForwardingActive(bool do_forward);\n\n        static bool IsPenSimulationSupported();\n        static bool IsKeyDown(unsigned char keycode);\n};\n\n#endif"
  },
  {
    "path": "src/DesktopPlus/LaserPointer.cpp",
    "content": "#include \"LaserPointer.h\"\n\n#include \"ConfigManager.h\"\n#include \"OverlayManager.h\"\n#include \"OutputManager.h\"\n#include \"Util.h\"\n#include \"OpenVRExt.h\"\n\n#define LASER_POINTER_OVERLAY_WIDTH 0.0025f\n#define LASER_POINTER_DEFAULT_LENGTH 5.0f\n\nLaserPointer::LaserPointer() : m_ActivationOrigin(dplp_activation_origin_none), \n                               m_HadPrimaryPointerDevice(false), \n                               m_DeviceMaxActiveID(0), \n                               m_LastPrimaryDeviceSwitchTick(0),\n                               m_LastScrollTick(0),\n                               m_DeviceHapticPending(vr::k_unTrackedDeviceIndexInvalid),\n                               m_IsForceTargetOverlayActive(false),\n                               m_ForceTargetOverlayHandle(vr::k_ulOverlayHandleInvalid)\n{\n    //Not calling Update() here since the OutputManager typically needs to load the config and OpenVR first\n}\n\nLaserPointer::~LaserPointer()\n{\n    if (vr::VROverlay() != nullptr)\n    {\n        for (auto& lp_device : m_Devices)\n        {\n            if (lp_device.OvrlHandle != vr::k_ulOverlayHandleInvalid)\n            {\n                vr::VROverlay()->DestroyOverlay(lp_device.OvrlHandle);\n            }\n        }\n    }\n}\n\nvoid LaserPointer::CreateDeviceOverlay(vr::TrackedDeviceIndex_t device_index)\n{\n    if (device_index >= vr::k_unMaxTrackedDeviceCount)\n        return;\n\n    LaserPointerDevice& lp_device = m_Devices[device_index];\n\n    std::string key = \"elvissteinjr.DesktopPlusPointer\" + std::to_string(device_index);\n    vr::EVROverlayError ovrl_error = vr::VROverlay()->CreateOverlay(key.c_str(), \"Desktop+ Laser Pointer\", &lp_device.OvrlHandle);\n\n    if (ovrl_error != vr::VROverlayError_None)\n        return;\n\n    //Set overlay as 2x2 half transparent blue-ish image\n    uint32_t pixels[2 * 2];\n    std::fill(std::begin(pixels), std::end(pixels), 0xFFBFA75F);      //Fairly close to SteamVR's pointer color (ABGR / little-endian order)\n\n    vr::VROverlay()->SetOverlayRaw(lp_device.OvrlHandle, pixels, 2, 2, 4);\n\n    vr::VROverlay()->SetOverlayWidthInMeters(lp_device.OvrlHandle, LASER_POINTER_OVERLAY_WIDTH);\n    vr::VROverlay()->SetOverlaySortOrder(lp_device.OvrlHandle, 2);\n}\n\nvoid LaserPointer::UpdateDeviceOverlay(vr::TrackedDeviceIndex_t device_index)\n{\n    if (device_index >= vr::k_unMaxTrackedDeviceCount)\n        return;\n\n    LaserPointerDevice& lp_device = m_Devices[device_index];\n\n    //Don't show any overlay when using HMD as origin\n    if (lp_device.UseHMDAsOrigin)\n        return;\n\n    //Create overlay if it doesn't exist yet\n    if (lp_device.OvrlHandle == vr::k_ulOverlayHandleInvalid)\n    {\n        CreateDeviceOverlay(device_index);\n    }\n\n    //Adjust visibility\n    bool do_show_later = false;\n    bool is_active = IsActive();\n    if ( (!lp_device.IsVisible) && (is_active) )\n    {\n        lp_device.IsVisible = true;\n        do_show_later = true;       //Postpone actually showing the overlay until after we set position and visuals\n    }\n    else if ( (lp_device.IsVisible) && (!is_active) )\n    {\n        vr::VROverlay()->HideOverlay(lp_device.OvrlHandle);\n        lp_device.IsVisible = false;\n    }\n\n    //Skip rest if not visible\n    if (!lp_device.IsVisible)\n        return;\n\n    //Position laser\n    Matrix4 transform_tip, transform_offset;\n\n    //Perform rotation locally and offset laser forward so it starts at the tip\n    transform_offset.rotateX(-90.0f);\n    transform_offset.translate_relative(0.0f, lp_device.LaserLength / 2.0f, 0.0f);\n\n    //Use tip if there is one\n    transform_tip = vr::IVRSystemEx::GetControllerTipMatrix( vr::VRSystem()->GetControllerRoleForTrackedDeviceIndex(device_index) );\n\n    transform_tip = transform_tip * transform_offset;\n    //A smart person could probably figure out how to also have the overlay spin towards the HMD so it doesn't appear flat\n\n    vr::HmdMatrix34_t transform_openvr = transform_tip.toOpenVR34();\n    vr::VROverlay()->SetOverlayTransformTrackedDeviceRelative(lp_device.OvrlHandle, (lp_device.UseHMDAsOrigin) ? vr::k_unTrackedDeviceIndex_Hmd : device_index, &transform_openvr);\n\n    //Adjust pointer alpha/brightness\n    bool is_primary_device = (device_index == (vr::TrackedDeviceIndex_t)ConfigManager::GetValue(configid_int_state_dplus_laser_pointer_device));\n    if (is_primary_device)\n    {\n        vr::VROverlay()->SetOverlayAlpha(lp_device.OvrlHandle, 1.0f);\n\n        if (lp_device.OvrlHandleTargetLast != vr::k_ulOverlayHandleInvalid)\n        {\n            vr::VROverlay()->SetOverlayColor(lp_device.OvrlHandle, 1.0f, 1.0f, 1.0f);\n        }\n        else\n        {\n            vr::VROverlay()->SetOverlayColor(lp_device.OvrlHandle, 0.25f, 0.25f, 0.25f);\n        }\n    }\n    else if (lp_device.IsActiveForMultiLaserInput)\n    {\n        vr::VROverlay()->SetOverlayAlpha(lp_device.OvrlHandle, 0.75f);\n        vr::VROverlay()->SetOverlayColor(lp_device.OvrlHandle, 0.50f, 0.50f, 0.50f);\n    }\n    else\n    {\n        vr::VROverlay()->SetOverlayAlpha(lp_device.OvrlHandle, 0.125f);\n        vr::VROverlay()->SetOverlayColor(lp_device.OvrlHandle, 0.25f, 0.25f, 0.25f);\n    }\n\n    if (do_show_later)\n    {\n        vr::VROverlay()->ShowOverlay(lp_device.OvrlHandle);\n    }\n}\n\nvoid LaserPointer::UpdateIntersection(vr::TrackedDeviceIndex_t device_index)\n{\n    if (device_index >= vr::k_unMaxTrackedDeviceCount)\n        return;\n\n    LaserPointerDevice& lp_device = m_Devices[device_index];\n    bool is_primary_device = (device_index == (vr::TrackedDeviceIndex_t)ConfigManager::GetValue(configid_int_state_dplus_laser_pointer_device));\n    bool was_active_for_multilaser_input = lp_device.IsActiveForMultiLaserInput;\n    bool skip_intersection_test = vr::IVROverlayEx::IsSystemLaserPointerActive();\n    bool skip_input = skip_intersection_test;\n\n    //Set up intersection test\n    bool hit_multilaser = false;\n    vr::VROverlayIntersectionParams_t  params  = {0};\n    vr::VROverlayIntersectionResults_t results = {0};\n\n    if (!vr::IVROverlayEx::GetOverlayIntersectionParamsForDevice(params, (lp_device.UseHMDAsOrigin) ? vr::k_unTrackedDeviceIndex_Hmd : device_index, vr::TrackingUniverseStanding))\n    {\n        skip_intersection_test = true; //Skip if pose isn't valid\n    }\n\n    //Find the nearest intersecting overlay\n    vr::VROverlayHandle_t nearest_target_overlay = vr::k_ulOverlayHandleInvalid;\n    OverlayTextureSource nearest_texture_source = ovrl_texsource_none;\n    vr::VROverlayIntersectionResults_t nearest_results = {0};\n    nearest_results.fDistance = FLT_MAX;\n\n    //If requested via ForceTargetOverlay(), force a different target overlay\n    if (m_IsForceTargetOverlayActive)\n    {\n        skip_intersection_test = true;\n        nearest_target_overlay = m_ForceTargetOverlayHandle;\n\n        //Check if forced target overlay is UI overlay\n        const bool is_forced_ui = ( (std::find(m_OverlayHandlesUI.begin(), m_OverlayHandlesUI.end(), nearest_target_overlay) != m_OverlayHandlesUI.end()) || \n                                    (std::find(m_OverlayHandlesMultiLaser.begin(), m_OverlayHandlesMultiLaser.end(), nearest_target_overlay) != m_OverlayHandlesMultiLaser.end()) );\n\n        if (is_forced_ui)\n        {\n            nearest_texture_source = ovrl_texsource_ui;\n        }\n        else\n        {\n            unsigned int overlay_id = OverlayManager::Get().FindOverlayID(m_ForceTargetOverlayHandle);\n            nearest_texture_source = OverlayManager::Get().GetOverlay(overlay_id).GetTextureSource();\n        }\n    }\n\n    //If input is held down, do not switch overlays and act as if overlay space is extending past the surface when not hitting it\n    if (!skip_intersection_test)\n    {\n        if (lp_device.InputDownCount != 0)\n        {\n            //Check if input is still enabled\n            vr::VROverlayInputMethod input_method = vr::VROverlayInputMethod_None;\n            vr::VROverlay()->GetOverlayInputMethod(lp_device.OvrlHandleTargetLast, &input_method);\n\n            if (input_method == vr::VROverlayInputMethod_Mouse)\n            {\n                if (vr::VROverlay()->ComputeOverlayIntersection(lp_device.OvrlHandleTargetLast, &params, &results))\n                {\n                    if ( (vr::IVROverlayEx::IsOverlayIntersectionHitFrontFacing(params, results)) && (IntersectionMaskHitTest(lp_device.OvrlHandleTargetLastTextureSource, results.vUVs)) )\n                    {\n                        nearest_target_overlay = lp_device.OvrlHandleTargetLast;\n                    }\n                }\n\n                //These results are still valid even when not actually hitting\n                nearest_results = results;\n            }\n        }\n        else\n        {\n            //Desktop+ overlays\n            for (unsigned int i = 0; i < OverlayManager::Get().GetOverlayCount(); ++i)\n            {\n                const Overlay& overlay        = OverlayManager::Get().GetOverlay(i);\n                const OverlayConfigData& data = OverlayManager::Get().GetConfigData(i);\n\n                if ( (overlay.IsVisible()) && (data.ConfigBool[configid_bool_overlay_input_dplus_lp_enabled]) )\n                {\n                    //Check if input is enabled right now (could differ from config setting)\n                    vr::VROverlayInputMethod input_method = vr::VROverlayInputMethod_None;\n                    vr::VROverlay()->GetOverlayInputMethod(overlay.GetHandle(), &input_method);\n\n                    if ( (input_method == vr::VROverlayInputMethod_Mouse) && \n                         (vr::VROverlay()->ComputeOverlayIntersection(overlay.GetHandle(), &params, &results)) && (results.fDistance < nearest_results.fDistance) )\n                    {\n                        //If Desktop Duplication/Performance Montior, this also performs an extra hit-test as described below\n                        if ( (vr::IVROverlayEx::IsOverlayIntersectionHitFrontFacing(params, results)) && (IntersectionMaskHitTest(overlay.GetTextureSource(), results.vUVs)) )\n                        {\n                            nearest_target_overlay = overlay.GetHandle();\n                            nearest_texture_source = overlay.GetTextureSource();\n                            nearest_results        = results;\n                        }\n                    }\n                }\n            }\n\n            //Desktop+ UI overlays\n            //\n            //ComputeOverlayIntersection() does not take intersection masks into account. This has been reported under issue #1601 in the OpenVR repo.\n            //As masks can change any frame, sending them over all the time seems tedious and inefficient... we're doing this for now though to work around that.\n            for (vr::VROverlayHandle_t overlay_handle : m_OverlayHandlesUI)\n            {\n                if (vr::VROverlay()->IsOverlayVisible(overlay_handle))\n                {\n                    //Check if input is enabled\n                    vr::VROverlayInputMethod input_method = vr::VROverlayInputMethod_None;\n                    vr::VROverlay()->GetOverlayInputMethod(overlay_handle, &input_method);\n\n                    if ( (input_method == vr::VROverlayInputMethod_Mouse) && (vr::VROverlay()->ComputeOverlayIntersection(overlay_handle, &params, &results)) && \n                         (results.fDistance < nearest_results.fDistance) )\n                    {\n                        if ( (vr::IVROverlayEx::IsOverlayIntersectionHitFrontFacing(params, results)) && (IntersectionMaskHitTest(ovrl_texsource_ui, results.vUVs)) )\n                        {\n                            nearest_target_overlay       = overlay_handle;\n                            nearest_texture_source       = ovrl_texsource_ui;\n                            nearest_results              = results;\n                        }\n                    }\n                }\n            }\n\n            //MultiLaser overlays (just keyboard right now)\n            for (vr::VROverlayHandle_t overlay_handle : m_OverlayHandlesMultiLaser)\n            {\n                if (vr::VROverlay()->IsOverlayVisible(overlay_handle))\n                {\n                    //Check if input is enabled\n                    vr::VROverlayInputMethod input_method = vr::VROverlayInputMethod_None;\n                    vr::VROverlay()->GetOverlayInputMethod(overlay_handle, &input_method);\n\n                    if ( (input_method == vr::VROverlayInputMethod_Mouse) && (vr::VROverlay()->ComputeOverlayIntersection(overlay_handle, &params, &results)) && \n                         (results.fDistance < nearest_results.fDistance) )\n                    {\n                        if ( (vr::IVROverlayEx::IsOverlayIntersectionHitFrontFacing(params, results)) && (IntersectionMaskHitTest(ovrl_texsource_ui, results.vUVs)) )\n                        {\n                            hit_multilaser               = true;\n                            nearest_target_overlay       = overlay_handle;\n                            nearest_texture_source       = ovrl_texsource_ui;\n                            nearest_results              = results;\n                        }\n                    }\n                }\n            }\n\n            //Non-MultiLaser hits are discared when not using the primary device\n            if ( (!hit_multilaser) && (!is_primary_device) )\n            {\n                nearest_target_overlay = vr::k_ulOverlayHandleInvalid;\n                nearest_texture_source = ovrl_texsource_none;\n            }\n\n            lp_device.IsActiveForMultiLaserInput = hit_multilaser;\n        }\n    }\n\n    //If we hit a different overlay (or lack thereof)...\n    if ( ( (lp_device.InputDownCount == 0) && (nearest_target_overlay != lp_device.OvrlHandleTargetLast) ) || (m_IsForceTargetOverlayActive) )\n    {\n        //...send focus leave event to last entered overlay\n        if (lp_device.OvrlHandleTargetLast != vr::k_ulOverlayHandleInvalid)\n        {\n            vr::VREvent_t vr_event = {0};\n            vr_event.trackedDeviceIndex = device_index;\n            vr_event.eventType = vr::VREvent_FocusLeave;\n\n            vr::VROverlayView()->PostOverlayEvent(lp_device.OvrlHandleTargetLast, &vr_event);\n\n            //Clear overlay cursor if we were probably the last one to set it\n            if ( (!was_active_for_multilaser_input) || (is_primary_device) )\n            {\n                vr::VROverlay()->ClearOverlayCursorPositionOverride(lp_device.OvrlHandleTargetLast);\n            }\n        }\n\n        //...and enter to the new one, if any\n        if (nearest_target_overlay != vr::k_ulOverlayHandleInvalid)\n        {\n            vr::VREvent_t vr_event = {0};\n            vr_event.trackedDeviceIndex = device_index;\n            vr_event.eventType = vr::VREvent_FocusEnter;\n\n            vr::VROverlayView()->PostOverlayEvent(nearest_target_overlay, &vr_event);\n        }\n    }\n\n    //Send mouse move event if we hit an overlay\n    if ( (nearest_target_overlay != vr::k_ulOverlayHandleInvalid) && (IsActive()) ) //Check for IsActive() so we don't send mouse move events for 1 frame after deactivation\n    {\n        vr::HmdVector2_t mouse_scale;\n        vr::VROverlay()->GetOverlayMouseScale(nearest_target_overlay, &mouse_scale);\n\n        vr::VREvent_t vr_event = {0};\n        vr_event.trackedDeviceIndex = device_index;\n        vr_event.eventType = vr::VREvent_MouseMove;\n        vr_event.data.mouse.x = nearest_results.vUVs.v[0] * mouse_scale.v[0];\n        vr_event.data.mouse.y = nearest_results.vUVs.v[1] * mouse_scale.v[1];\n\n        vr::VROverlayView()->PostOverlayEvent(nearest_target_overlay, &vr_event);\n\n        //Set blob\n        if (is_primary_device)\n        {\n            bool hide_intersection = false;\n            vr::VROverlay()->GetOverlayFlag(nearest_target_overlay, vr::VROverlayFlags_HideLaserIntersection, &hide_intersection);\n\n            if (!hide_intersection)\n            {\n                vr::VRTextureBounds_t tex_bounds;\n                vr::VROverlay()->GetOverlayTextureBounds(nearest_target_overlay, &tex_bounds);\n                int mapped_x      = (mouse_scale.v[0] * tex_bounds.uMin);\n                int mapped_y      = (mouse_scale.v[1] * tex_bounds.vMin);\n                int mapped_width  = (mouse_scale.v[0] * tex_bounds.uMax) - mapped_x;\n                int mapped_height = (mouse_scale.v[1] * tex_bounds.vMax) - mapped_y;\n\n                //Flip to normal 2D space\n                float pointer_y = -round(vr_event.data.mouse.y) + mouse_scale.v[1];\n\n                float new_u = (vr_event.data.mouse.x - mapped_x) / (mapped_width);\n                float new_v = (pointer_y - mapped_y) / (mapped_height);\n\n                //Flip it back\n                new_v = -new_v + 1.0f;\n\n                vr::HmdVector2_t pos = {new_u * mouse_scale.v[0], new_v * mouse_scale.v[1]};\n\n                vr::VROverlay()->SetOverlayCursorPositionOverride(nearest_target_overlay, &pos);\n            }\n            else\n            {\n                vr::VROverlay()->ClearOverlayCursorPositionOverride(nearest_target_overlay);\n            }\n        }\n\n        //Set pointer length to overlay distance\n        lp_device.LaserLength = nearest_results.fDistance;\n    }\n    else if ( (!skip_input) && (lp_device.InputDownCount != 0) ) //Nothing hit, but input is down, extend\n    {\n        vr::HmdVector2_t mouse_scale;\n        vr::VROverlay()->GetOverlayMouseScale(lp_device.OvrlHandleTargetLast, &mouse_scale);\n\n        vr::VREvent_t vr_event = {0};\n        vr_event.trackedDeviceIndex = device_index;\n        vr_event.eventType = vr::VREvent_MouseMove;\n        vr_event.data.mouse.x =   nearest_results.vUVs.v[0] * mouse_scale.v[0];\n        vr_event.data.mouse.y = (-nearest_results.vUVs.v[1] + 1.0f) * mouse_scale.v[1]; //For some reason the V position is upside-down when the intersection did not happen\n\n        vr::VROverlayView()->PostOverlayEvent(lp_device.OvrlHandleTargetLast, &vr_event);\n\n        //Clear cursor override when we're off the overlay since the override won't go past it like the real cursor would\n        if (is_primary_device)\n        {\n            vr::VROverlay()->ClearOverlayCursorPositionOverride(lp_device.OvrlHandleTargetLast);\n        }\n\n        //Act as if the overlay was hit for the rest of this function\n        nearest_target_overlay = lp_device.OvrlHandleTargetLast;\n        nearest_texture_source = lp_device.OvrlHandleTargetLastTextureSource;\n    }\n    else\n    {\n        //Set pointer length to default\n        lp_device.LaserLength = LASER_POINTER_DEFAULT_LENGTH;\n    }\n\n    //Input events\n    if (nearest_target_overlay != vr::k_ulOverlayHandleInvalid)\n    {\n        VRInput& vr_input = OutputManager::Get()->GetVRInput();\n\n        //Clicking\n        auto click_state_array = vr_input.GetLaserPointerClickState(lp_device.InputValueHandle);\n        uint32_t mouse_button_id = vr::VRMouseButton_Left;\n        lp_device.InputDownCount = 0;\n\n        for (const auto& input_data : click_state_array)\n        {\n            if (input_data.bActive)\n            {\n                if (input_data.bChanged)\n                {\n                    vr::VREvent_t vr_event = {0};\n                    vr_event.trackedDeviceIndex = device_index;\n                    vr_event.eventType = (input_data.bState) ? vr::VREvent_MouseButtonDown : vr::VREvent_MouseButtonUp;\n                    vr_event.data.mouse.button = mouse_button_id;\n\n                    vr::VROverlayView()->PostOverlayEvent(nearest_target_overlay, &vr_event);\n\n                    //As VRInput::GetActionOrigins() isn't reliable for some reason, we may not have the input value handle yet... we grab it from here instead as a workaround\n                    if (lp_device.InputValueHandle == vr::k_ulInvalidInputValueHandle)\n                    {\n                        vr::InputOriginInfo_t origin_info = {0};\n\n                        if (vr::VRInput()->GetOriginTrackedDeviceInfo(input_data.activeOrigin, &origin_info, sizeof(vr::InputOriginInfo_t)) == vr::VRInputError_None)\n                        {\n                            if ( (origin_info.trackedDeviceIndex < vr::k_unMaxTrackedDeviceCount) )\n                            {\n                                m_Devices[origin_info.trackedDeviceIndex].InputValueHandle = origin_info.devicePath;\n                            }\n                        }\n                    }\n                }\n\n                if (input_data.bState)\n                {\n                    lp_device.InputDownCount++;\n                }\n            }\n\n            mouse_button_id *= 2; //with click_state_array containing 5 elements, this goes over the OpenVR defined buttons to include our custom VRMouseButton_DP_Aux01/02\n        }\n\n        //Scrolling\n        \n        //Get scroll mode from overlay and set it for VRInput\n        bool send_scroll_discrete = false, send_scroll_smooth = false;\n        vr::VROverlay()->GetOverlayFlag(nearest_target_overlay, vr::VROverlayFlags_SendVRDiscreteScrollEvents, &send_scroll_discrete);\n        vr::VROverlay()->GetOverlayFlag(nearest_target_overlay, vr::VROverlayFlags_SendVRSmoothScrollEvents,   &send_scroll_smooth);\n\n        VRInputScrollMode scroll_mode = (send_scroll_smooth) ? vrinput_scroll_smooth : (send_scroll_discrete) ? vrinput_scroll_discrete : vrinput_scroll_none;\n        vr_input.SetLaserPointerScrollMode(scroll_mode);\n\n        switch (scroll_mode)\n        {\n            case vrinput_scroll_discrete:\n            {\n                vr::InputAnalogActionData_t input_data = vr_input.GetLaserPointerScrollDiscreteState();\n\n                //If active and changed or not 0 on any axis\n                if ( (input_data.bActive) && ( (input_data.x != 0.0f) || (input_data.y != 0.0f) || (input_data.deltaX != 0.0f) || (input_data.deltaY != 0.0f) ) )\n                {\n                    vr::VREvent_t vr_event = {0};\n                    vr_event.trackedDeviceIndex = device_index;\n                    vr_event.eventType = vr::VREvent_ScrollDiscrete;\n                    vr_event.data.scroll.xdelta = input_data.x;\n                    vr_event.data.scroll.ydelta = input_data.y;\n\n                    vr::VROverlayView()->PostOverlayEvent(nearest_target_overlay, &vr_event);\n\n                    m_LastScrollTick = ::GetTickCount64();\n                }\n\n                break;\n            }\n            case vrinput_scroll_smooth:\n            {\n                vr::InputAnalogActionData_t input_data = vr_input.GetLaserPointerScrollSmoothState();\n\n                //If active and changed or not 0 on any axis\n                if ( (input_data.bActive) && ( (input_data.x != 0.0f) || (input_data.y != 0.0f) || (input_data.deltaX != 0.0f) || (input_data.deltaY != 0.0f) ) )\n                {\n                    vr::VREvent_t vr_event = {0};\n                    vr_event.trackedDeviceIndex = device_index;\n                    vr_event.eventType = vr::VREvent_ScrollSmooth;\n                    vr_event.data.scroll.xdelta = input_data.x;\n                    vr_event.data.scroll.ydelta = input_data.y;\n\n                    vr::VROverlayView()->PostOverlayEvent(nearest_target_overlay, &vr_event);\n\n                    m_LastScrollTick = ::GetTickCount64();\n                }\n\n                break;\n            }\n            default: break;\n        }\n\n        //Direct Drag\n        {\n            vr::InputDigitalActionData_t input_data = vr_input.GetLaserPointerDragState(lp_device.InputValueHandle);\n            if (input_data.bActive)\n            {\n                if (input_data.bChanged)\n                {\n                    SendDirectDragCommand(nearest_target_overlay, input_data.bState);\n                    lp_device.IsDragDown = input_data.bState;\n                }\n\n                if (input_data.bState)\n                {\n                    lp_device.InputDownCount++;\n                }\n            }\n        }\n    }\n    else\n    {\n        lp_device.InputDownCount = 0;\n    }\n\n    if (nearest_target_overlay != lp_device.OvrlHandleTargetLast)\n    {\n        lp_device.OvrlHandleTargetLast              = nearest_target_overlay;\n        lp_device.OvrlHandleTargetLastTextureSource = nearest_texture_source;\n\n        if (is_primary_device)\n        {\n            ConfigManager::SetValue(configid_handle_state_dplus_laser_pointer_target_overlay, lp_device.OvrlHandleTargetLast);\n            IPCManager::Get().PostConfigMessageToUIApp(configid_handle_state_dplus_laser_pointer_target_overlay, pun_cast<LPARAM, vr::VROverlayHandle_t>(lp_device.OvrlHandleTargetLast));\n        }\n    }\n\n    m_IsForceTargetOverlayActive = false;\n\n    //Update overlay length\n    vr::VRTextureBounds_t tex_bounds = {0};\n    tex_bounds.uMax = 1.0f;\n    tex_bounds.vMax = lp_device.LaserLength / LASER_POINTER_OVERLAY_WIDTH;\n\n    vr::VROverlay()->SetOverlayTextureBounds(lp_device.OvrlHandle, &tex_bounds);\n}\n\nvoid LaserPointer::SendDirectDragCommand(vr::VROverlayHandle_t overlay_handle_target, bool do_start_drag)\n{\n    unsigned int overlay_id = OverlayManager::Get().FindOverlayID(overlay_handle_target);\n\n    if (overlay_id == k_ulOverlayID_None)\n    {\n        //Check if target overlay is UI overlay\n        const bool is_ui = ( (std::find(m_OverlayHandlesUI.begin(), m_OverlayHandlesUI.end(), overlay_handle_target) != m_OverlayHandlesUI.end()) || \n                             (std::find(m_OverlayHandlesMultiLaser.begin(), m_OverlayHandlesMultiLaser.end(), overlay_handle_target) != m_OverlayHandlesMultiLaser.end()) );\n\n        //Tell UI to start a drag on it if it is\n        if (is_ui)\n        {\n            IPCManager::Get().PostMessageToUIApp(ipcmsg_action, ipcact_lpointer_ui_drag, (do_start_drag) ? 1 : 0);\n        }\n    }\n    else\n    {\n        (do_start_drag) ? OutputManager::Get()->OverlayDirectDragStart(overlay_id) : OutputManager::Get()->OverlayDirectDragFinish(overlay_id);\n    }\n}\n\nvoid LaserPointer::Update()\n{\n    //Refresh/Init handles in any case if they're empty\n    if (m_OverlayHandlesUI.empty())\n    {\n        RefreshCachedOverlayHandles();\n    }\n\n    vr::TrackedDeviceIndex_t primary_pointer_device = (vr::TrackedDeviceIndex_t)ConfigManager::GetValue(configid_int_state_dplus_laser_pointer_device);\n\n    if ( (primary_pointer_device == vr::k_unTrackedDeviceIndexInvalid) && (!m_HadPrimaryPointerDevice) )\n        return;\n\n    VRInput& vr_input = OutputManager::Get()->GetVRInput();\n\n    //Trigger pending vibrations if we know the action set is now active\n    if ( (m_HadPrimaryPointerDevice) && (m_DeviceHapticPending != vr::k_unTrackedDeviceIndexInvalid) )\n    {\n        TriggerLaserPointerHaptics(m_DeviceHapticPending);\n        m_DeviceHapticPending = vr::k_unTrackedDeviceIndexInvalid;\n    }\n\n    //Primary device switching\n    if (!vr::IVROverlayEx::IsSystemLaserPointerActive())\n    {\n        vr::InputDigitalActionData_t left_click_state = vr_input.GetLaserPointerLeftClickState();\n\n        if ( (left_click_state.bChanged) && (left_click_state.bState) )\n        {\n            vr::InputOriginInfo_t origin_info = vr_input.GetOriginTrackedDeviceInfoEx(left_click_state.activeOrigin);\n\n            if ( (origin_info.trackedDeviceIndex < vr::k_unMaxTrackedDeviceCount) && (origin_info.trackedDeviceIndex != primary_pointer_device) &&\n                 (!m_Devices[origin_info.trackedDeviceIndex].IsActiveForMultiLaserInput))\n            {\n                //Set device path here since we have it ready anyways\n                m_Devices[origin_info.trackedDeviceIndex].InputValueHandle = origin_info.devicePath;\n\n                //Store current tick to allow a grace period when switching devices while auto toggling is active and not actually hovering anything with the new pointer\n                m_LastPrimaryDeviceSwitchTick = ::GetTickCount64();\n\n                SetActiveDevice(origin_info.trackedDeviceIndex, m_ActivationOrigin);\n            }\n        }\n    }\n\n    //Don't do anything if a dashboard pointer is active or we have no primary pointer ourselves\n    bool should_pointer_be_active = IsActive();\n\n    //Also clear active device when we shouldn't be active\n    if (!should_pointer_be_active)\n    {\n        ClearActiveDevice();\n    }\n\n    if ( (m_HadPrimaryPointerDevice) || (should_pointer_be_active) )\n    {\n        for (vr::TrackedDeviceIndex_t i = 0; i <= m_DeviceMaxActiveID; ++i)\n        {\n            if ( (m_Devices[i].OvrlHandle != vr::k_ulOverlayHandleInvalid) || (m_Devices[i].UseHMDAsOrigin) )\n            {\n                UpdateIntersection(i);\n            }\n        }\n\n        //Store if we were active the previous frame so we can sent overlay leave events in this iteration and clean up state before not doing this when not needed afterwards\n        m_HadPrimaryPointerDevice = should_pointer_be_active;\n    }\n\n    for (vr::TrackedDeviceIndex_t i = 0; i <= m_DeviceMaxActiveID; ++i)\n    {\n        if (m_Devices[i].OvrlHandle != vr::k_ulOverlayHandleInvalid)\n        {\n            UpdateDeviceOverlay(i);\n        }\n    }\n\n    vr_input.SetLaserPointerActive(should_pointer_be_active);\n}\n\nvoid LaserPointer::SetActiveDevice(vr::TrackedDeviceIndex_t device_index, LaserPointerActivationOrigin activation_origin)\n{\n    if (device_index >= vr::k_unMaxTrackedDeviceCount)\n        return;\n\n    vr::TrackedDeviceIndex_t previous_active_device = (vr::TrackedDeviceIndex_t)ConfigManager::GetValue(configid_int_state_dplus_laser_pointer_device);\n\n    if (previous_active_device == device_index)\n        return;\n\n    //Clear last overlay cursor override\n    if (previous_active_device < vr::k_unMaxTrackedDeviceCount)\n    {\n        LaserPointerDevice& lp_device_prev = m_Devices[previous_active_device];\n\n        if (lp_device_prev.OvrlHandleTargetLast != vr::k_ulOverlayHandleInvalid)\n        {\n            vr::VROverlay()->ClearOverlayCursorPositionOverride(lp_device_prev.OvrlHandleTargetLast);\n        }\n    }\n\n    LaserPointerDevice& lp_device = m_Devices[device_index];\n\n    //Set UseHMDAsOrigin, which forces using the HMD as an origin and not actually display any laser\n    lp_device.UseHMDAsOrigin = ((device_index == vr::k_unTrackedDeviceIndex_Hmd) || (vr::VRSystem()->GetBoolTrackedDeviceProperty(device_index, vr::Prop_NeverTracked_Bool)));\n\n    //Create overlay if it doesn't exist yet (and HMD isn't used as origin)\n    if ( (!lp_device.UseHMDAsOrigin) && (lp_device.OvrlHandle == vr::k_ulOverlayHandleInvalid) )\n    {\n        CreateDeviceOverlay(device_index);\n    }\n\n    //Try finding the input value handle for this device if it's not set yet\n    if (lp_device.InputValueHandle == vr::k_ulInvalidInputValueHandle)\n    {\n        std::vector<vr::InputOriginInfo_t> devices_info = OutputManager::Get()->GetVRInput().GetLaserPointerDevicesInfo();\n        const auto it = std::find_if(devices_info.begin(), devices_info.end(), [&](const auto& input_origin_info){ return (input_origin_info.trackedDeviceIndex == device_index); });\n\n        if (it != devices_info.end())\n        {\n            lp_device.InputValueHandle = it->devicePath;\n        }\n    }\n\n    //Adjust max active ID if it's smaller\n    if (m_DeviceMaxActiveID < device_index)\n    {\n        m_DeviceMaxActiveID = device_index;\n    }\n\n    //Vibrate if this activates the Desktop+ pointer (not just switching)\n    if (previous_active_device == vr::k_unTrackedDeviceIndexInvalid)\n    {\n        m_DeviceHapticPending = device_index;   //Postpone the actual call until later since the relevant action set is not active yet\n    }\n\n    m_ActivationOrigin = activation_origin;\n\n    ConfigManager::SetValue(configid_int_state_dplus_laser_pointer_device, device_index);\n    IPCManager::Get().PostConfigMessageToUIApp(configid_int_state_dplus_laser_pointer_device, device_index);\n\n    ConfigManager::SetValue(configid_handle_state_dplus_laser_pointer_target_overlay, lp_device.OvrlHandleTargetLast);\n    IPCManager::Get().PostConfigMessageToUIApp(configid_handle_state_dplus_laser_pointer_target_overlay, pun_cast<LPARAM, vr::VROverlayHandle_t>(lp_device.OvrlHandleTargetLast));\n}\n\nvoid LaserPointer::ClearActiveDevice()\n{\n    //Clear last overlay cursor override\n    vr::TrackedDeviceIndex_t previous_active_device = (vr::TrackedDeviceIndex_t)ConfigManager::GetValue(configid_int_state_dplus_laser_pointer_device);\n\n    if (previous_active_device < vr::k_unMaxTrackedDeviceCount)\n    {\n        LaserPointerDevice& lp_device_prev = m_Devices[previous_active_device];\n\n        //Finish active direct drag\n        if (lp_device_prev.IsDragDown)\n        {\n            SendDirectDragCommand(lp_device_prev.OvrlHandleTargetLast, false);\n            lp_device_prev.IsDragDown = false;\n        }\n\n        //Send focus leave event to last entered overlay if there is any\n        if (lp_device_prev.OvrlHandleTargetLast != vr::k_ulOverlayHandleInvalid)\n        {\n            vr::VREvent_t vr_event = {0};\n            vr_event.trackedDeviceIndex = previous_active_device;\n            vr_event.eventType = vr::VREvent_FocusLeave;\n\n            vr::VROverlayView()->PostOverlayEvent(lp_device_prev.OvrlHandleTargetLast, &vr_event);\n\n            //Also clear cursor override\n            vr::VROverlay()->ClearOverlayCursorPositionOverride(lp_device_prev.OvrlHandleTargetLast);\n        }\n    }\n\n    m_ActivationOrigin = dplp_activation_origin_none;\n\n    ConfigManager::SetValue(configid_int_state_dplus_laser_pointer_device, vr::k_unTrackedDeviceIndexInvalid);\n    IPCManager::Get().PostConfigMessageToUIApp(configid_int_state_dplus_laser_pointer_device, vr::k_unTrackedDeviceIndexInvalid);\n\n    ConfigManager::SetValue(configid_handle_state_dplus_laser_pointer_target_overlay, vr::k_ulOverlayHandleInvalid);\n    IPCManager::Get().PostConfigMessageToUIApp(configid_handle_state_dplus_laser_pointer_target_overlay, vr::k_ulOverlayHandleInvalid);\n}\n\nvoid LaserPointer::RemoveDevice(vr::TrackedDeviceIndex_t device_index)\n{\n    if (device_index >= vr::k_unMaxTrackedDeviceCount)\n        return;\n\n    LaserPointerDevice& lp_device = m_Devices[device_index];\n\n    if (lp_device.OvrlHandle != vr::k_ulOverlayHandleInvalid)\n    {\n        vr::VROverlay()->DestroyOverlay(lp_device.OvrlHandle);\n    }\n\n    //Send focus leave event to last entered overlay if there is any\n    if (lp_device.OvrlHandleTargetLast != vr::k_ulOverlayHandleInvalid)\n    {\n        vr::VREvent_t vr_event = {0};\n        vr_event.trackedDeviceIndex = device_index;\n        vr_event.eventType = vr::VREvent_FocusLeave;\n\n        vr::VROverlayView()->PostOverlayEvent(lp_device.OvrlHandleTargetLast, &vr_event);\n\n        //Also remove pointer override in case there is any\n        bool is_primary_device = (device_index == (vr::TrackedDeviceIndex_t)ConfigManager::GetValue(configid_int_state_dplus_laser_pointer_device));\n        if ( (!lp_device.IsActiveForMultiLaserInput) || (is_primary_device) )\n        {\n            vr::VROverlay()->ClearOverlayCursorPositionOverride(lp_device.OvrlHandleTargetLast);\n        }\n    }\n\n    lp_device = LaserPointerDevice();\n}\n\nvoid LaserPointer::RefreshCachedOverlayHandles()\n{\n    m_OverlayHandlesUI.clear();\n    m_OverlayHandlesMultiLaser.clear();\n\n    vr::VROverlayHandle_t overlay_handle;\n\n    //Find UI overlays (except keyboard)\n    const char* ui_overlay_keys[] = \n    {\n        \"elvissteinjr.DesktopPlusUI\",\n        \"elvissteinjr.DesktopPlusUIFloating\",\n        \"elvissteinjr.DesktopPlusUISettings\",\n        \"elvissteinjr.DesktopPlusUIOverlayProperties\",\n        \"elvissteinjr.DesktopPlusUIAux\"\n    };\n\n    for (const char* key : ui_overlay_keys)\n    {\n        vr::VROverlay()->FindOverlay(key, &overlay_handle);\n\n        if (overlay_handle != vr::k_ulOverlayHandleInvalid)\n        {\n            m_OverlayHandlesUI.push_back(overlay_handle);\n        }\n    }\n\n    //Find MultiLaser overlays (which is just keyboard right now)\n    vr::VROverlay()->FindOverlay(\"elvissteinjr.DesktopPlusUIKeyboard\", &overlay_handle);\n\n    if (overlay_handle != vr::k_ulOverlayHandleInvalid)\n    {\n        m_OverlayHandlesMultiLaser.push_back(overlay_handle);\n    }\n\n    //Cache UI mouse scale, since it's not going to change\n    if (!m_OverlayHandlesUI.empty())\n    {\n        vr::HmdVector2_t mouse_scale;\n        vr::VROverlay()->GetOverlayMouseScale(m_OverlayHandlesUI[0], &mouse_scale);\n\n        m_UIMouseScale.set((int)mouse_scale.v[0], (int)mouse_scale.v[1]);\n    }\n}\n\nvoid LaserPointer::TriggerLaserPointerHaptics(vr::TrackedDeviceIndex_t device_index) const\n{\n    OutputManager::Get()->GetVRInput().TriggerLaserPointerHaptics((device_index < vr::k_unMaxTrackedDeviceCount) ? m_Devices[device_index].InputValueHandle : vr::k_ulInvalidInputValueHandle);\n}\n\nvoid LaserPointer::ForceTargetOverlay(vr::VROverlayHandle_t overlay_handle)\n{\n    vr::TrackedDeviceIndex_t primary_pointer_device = (vr::TrackedDeviceIndex_t)ConfigManager::GetValue(configid_int_state_dplus_laser_pointer_device);\n\n    if (primary_pointer_device >= vr::k_unMaxTrackedDeviceCount)\n        return;\n\n    LaserPointerDevice& lp_device = m_Devices[primary_pointer_device];\n\n    //Don't do anything if there is no target overlay to switch from or if it would be a no-op\n    if ( (lp_device.OvrlHandleTargetLast == vr::k_ulOverlayHandleInvalid) || (lp_device.OvrlHandleTargetLast == overlay_handle) )\n        return;\n\n    //Run UpdateIntersection() with last target removed so overlay leave events are sent\n    m_IsForceTargetOverlayActive = true;\n    m_ForceTargetOverlayHandle = vr::k_ulOverlayHandleInvalid;\n    UpdateIntersection(primary_pointer_device);\n\n    //Set force variables again so they apply next time UpdateIntersection() us called\n    m_IsForceTargetOverlayActive = true;\n    m_ForceTargetOverlayHandle = overlay_handle;\n}\n\nLaserPointerActivationOrigin LaserPointer::GetActivationOrigin() const\n{\n    return m_ActivationOrigin;\n}\n\nbool LaserPointer::IsActive() const\n{\n    vr::TrackedDeviceIndex_t primary_pointer_device = (vr::TrackedDeviceIndex_t)ConfigManager::GetValue(configid_int_state_dplus_laser_pointer_device);\n    return ( (!vr::IVROverlayEx::IsSystemLaserPointerActive()) && (primary_pointer_device != vr::k_unTrackedDeviceIndexInvalid) );\n}\n\nvr::TrackedDeviceIndex_t LaserPointer::IsAnyOverlayHovered(float max_distance) const\n{\n    //If active, just check if the primary pointer has a last target overlay\n    if (IsActive())\n    {\n        vr::TrackedDeviceIndex_t primary_pointer_device = (vr::TrackedDeviceIndex_t)ConfigManager::GetValue(configid_int_state_dplus_laser_pointer_device);\n\n        if (primary_pointer_device >= vr::k_unMaxTrackedDeviceCount)\n            return vr::k_unTrackedDeviceIndexInvalid;\n\n        const LaserPointerDevice& lp_device = m_Devices[primary_pointer_device];\n\n        //Allow a 1500ms grace period after device switching even when not actually hovering anything & ignore distance if input is down\n        if ( (m_LastPrimaryDeviceSwitchTick + 1500 > ::GetTickCount64() ) || ( (lp_device.OvrlHandleTargetLast != vr::k_ulOverlayHandleInvalid) && (lp_device.LaserLength <= max_distance) ) || \n             (lp_device.InputDownCount != 0) )\n        {\n            return primary_pointer_device;\n        }\n    }\n    else //...otherwise check all possible overlays\n    {\n        //Check left and right hand controller and HMD if setting is enabled\n        const bool lp_hmd_enabled_and_toggle_unbound = ((ConfigManager::GetValue(configid_bool_input_laser_pointer_hmd_device)) && \n                                                        (ConfigManager::GetValue(configid_int_input_laser_pointer_hmd_device_keycode_toggle) == 0));\n\n        const vr::TrackedDeviceIndex_t devices[] = \n        {\n            (lp_hmd_enabled_and_toggle_unbound) ? vr::k_unTrackedDeviceIndex_Hmd : vr::k_unTrackedDeviceIndexInvalid,\n            vr::VRSystem()->GetTrackedDeviceIndexForControllerRole(vr::TrackedControllerRole_LeftHand),\n            vr::VRSystem()->GetTrackedDeviceIndexForControllerRole(vr::TrackedControllerRole_RightHand),\n        };\n\n        for (int i = 0; i < sizeof(devices)/sizeof(*devices); ++i)\n        {\n            const vr::TrackedDeviceIndex_t device_index = devices[i];\n            OverlayOrigin origin_avoid = (OverlayOrigin)(ovrl_origin_hmd+i);\n\n            //Set up intersection test\n            vr::VROverlayIntersectionParams_t  params  = {0};\n            vr::VROverlayIntersectionResults_t results = {0};\n\n            if (!vr::IVROverlayEx::GetOverlayIntersectionParamsForDevice(params, device_index, vr::TrackingUniverseStanding))\n            {\n                continue; //Skip if pose isn't valid\n            }\n\n            //Desktop+ overlays\n            for (unsigned int i = 0; i < OverlayManager::Get().GetOverlayCount(); ++i)\n            {\n                const Overlay& overlay        = OverlayManager::Get().GetOverlay(i);\n                const OverlayConfigData& data = OverlayManager::Get().GetConfigData(i);\n\n                if ( (data.ConfigInt[configid_int_overlay_origin] != origin_avoid) && (overlay.IsVisible()) && (data.ConfigBool[configid_bool_overlay_input_dplus_lp_enabled]) )\n                {\n                    //Check if input is enabled right now (could differ from config setting)\n                    vr::VROverlayInputMethod input_method = vr::VROverlayInputMethod_None;\n                    vr::VROverlay()->GetOverlayInputMethod(overlay.GetHandle(), &input_method);\n\n                    if ( (input_method == vr::VROverlayInputMethod_Mouse) && \n                         (vr::VROverlay()->ComputeOverlayIntersection(overlay.GetHandle(), &params, &results)) && (results.fDistance <= max_distance) )\n                    {\n                        if ( (vr::IVROverlayEx::IsOverlayIntersectionHitFrontFacing(params, results)) && (IntersectionMaskHitTest(overlay.GetTextureSource(), results.vUVs)) )\n                        {\n                            return device_index;\n                        }\n                    }\n                }\n            }\n\n            //Desktop+ UI overlays\n            //\n            //See UpdateIntersection() for current issues\n            for (vr::VROverlayHandle_t overlay_handle : m_OverlayHandlesUI)\n            {\n                if (vr::VROverlay()->IsOverlayVisible(overlay_handle))\n                {\n                    if ( (vr::VROverlay()->ComputeOverlayIntersection(overlay_handle, &params, &results)) && (results.fDistance <= max_distance) )\n                    {\n                        if ( (vr::IVROverlayEx::IsOverlayIntersectionHitFrontFacing(params, results)) && (IntersectionMaskHitTest(ovrl_texsource_ui, results.vUVs)) )\n                        {\n                            return device_index;\n                        }\n                    }\n                }\n            }\n\n            //MultiLaser overlays (just keyboard right now)\n            for (vr::VROverlayHandle_t overlay_handle : m_OverlayHandlesMultiLaser)\n            {\n                if (vr::VROverlay()->IsOverlayVisible(overlay_handle))\n                {\n                    if ( (vr::VROverlay()->ComputeOverlayIntersection(overlay_handle, &params, &results)) && (results.fDistance <= max_distance) )\n                    {\n                        if ( (vr::IVROverlayEx::IsOverlayIntersectionHitFrontFacing(params, results)) && (IntersectionMaskHitTest(ovrl_texsource_ui, results.vUVs)) )\n                        {\n                            return device_index;\n                        }\n                    }\n                }\n            }\n        }\n    }\n\n    return vr::k_unTrackedDeviceIndexInvalid;\n}\n\nbool LaserPointer::IsScrolling() const\n{\n    //Treat a 500ms window after the last scroll event as active scrolling (discrete scroll events aren't a constant stream when active)\n    return (m_LastScrollTick + 500 > ::GetTickCount64());\n}\n\nbool LaserPointer::IntersectionMaskHitTest(OverlayTextureSource texsource, vr::HmdVector2_t& uv) const\n{\n    if (texsource == ovrl_texsource_desktop_duplication)\n    {\n        Vector2Int point(int(uv.v[0] * OutputManager::Get()->GetDesktopWidth()), int((-uv.v[1] + 1.0f) * OutputManager::Get()->GetDesktopHeight()));\n\n        for (const auto& rect : OutputManager::Get()->GetDesktopRects())\n        {\n            if (rect.Contains(point))\n            {\n                return true;\n            }\n        }\n\n        return false;\n    }\n    else if (texsource == ovrl_texsource_ui)\n    {\n        Vector2Int point(int(uv.v[0] * m_UIMouseScale.x), int((-uv.v[1] + 1.0f) * m_UIMouseScale.y));\n\n        for (const auto& rect : m_UIIntersectionMaskRects)\n        {\n            if (rect.Contains(point))\n            {\n                return true;\n            }\n        }\n\n        return false;\n    }\n\n    //Other texture sources don't have masks, so they always pass\n    return true;\n}\n\nvoid LaserPointer::UIIntersectionMaskAddRect(DPRect& rect)\n{\n    m_UIIntersectionMaskRectsPending.push_back(rect);\n}\n\nvoid LaserPointer::UIIntersectionMaskFinish()\n{\n    m_UIIntersectionMaskRects = m_UIIntersectionMaskRectsPending;\n    m_UIIntersectionMaskRectsPending.clear();\n}\n"
  },
  {
    "path": "src/DesktopPlus/LaserPointer.h",
    "content": "#pragma once\n\n#include \"DPRect.h\"\n#include \"Overlays.h\"\n#include \"openvr.h\"\n\n#include <vector>\n\nstruct LaserPointerDevice\n{\n    vr::VROverlayHandle_t OvrlHandle = vr::k_ulOverlayHandleInvalid;\n    vr::VRInputValueHandle_t InputValueHandle = vr::k_ulInvalidInputValueHandle;\n    bool UseHMDAsOrigin = false;\n    bool IsVisible = false;\n    float LaserLength = 0.0f;\n\n    vr::VROverlayHandle_t OvrlHandleTargetLast = vr::k_ulOverlayHandleInvalid;\n    OverlayTextureSource OvrlHandleTargetLastTextureSource = ovrl_texsource_none;\n    bool IsActiveForMultiLaserInput = false;\n    int InputDownCount = 0;\n    bool IsDragDown = false;\n};\n\n//Optional origin that can be passed to SetActiveDevice() in order to keep track what activated the laser pointer (and thus should be responsible for deactivating)\nenum LaserPointerActivationOrigin\n{\n    dplp_activation_origin_none,\n    dplp_activation_origin_auto_toggle,\n    dplp_activation_origin_input_binding\n};\n\n//Custom laser pointer implementation for use outside of the dashboard\n//Operates on Desktop+ and Desktop+ UI overlays, sending overlay events matching SteamVR laser input behavior as closely as possible\n//Actually sends middle mouse events when bound\n//Supports use of any tracked device that has the corrects inputs bound, though IsAnyOverlayHovered() only checks handed controllers for auto interaction toggle\n//Paralell/Multi-Laser input is supported for certain overlays that can handle it (currently only the VR keyboard)\nclass LaserPointer\n{\n    private:\n        LaserPointerDevice m_Devices[vr::k_unMaxTrackedDeviceCount];\n        std::vector<vr::VROverlayHandle_t> m_OverlayHandlesUI;\n        std::vector<vr::VROverlayHandle_t> m_OverlayHandlesMultiLaser;\n\n        LaserPointerActivationOrigin m_ActivationOrigin;\n        bool m_HadPrimaryPointerDevice;\n        vr::TrackedDeviceIndex_t m_DeviceMaxActiveID;\n        ULONGLONG m_LastPrimaryDeviceSwitchTick;\n        ULONGLONG m_LastScrollTick;\n        vr::TrackedDeviceIndex_t m_DeviceHapticPending;\n\n        //State set by ForceTargetOverlay()\n        bool m_IsForceTargetOverlayActive;\n        vr::VROverlayHandle_t m_ForceTargetOverlayHandle;\n\n        Vector2Int m_UIMouseScale;\n        std::vector<DPRect> m_UIIntersectionMaskRects;\n        std::vector<DPRect> m_UIIntersectionMaskRectsPending;\n\n        void CreateDeviceOverlay(vr::TrackedDeviceIndex_t device_index);\n        void UpdateDeviceOverlay(vr::TrackedDeviceIndex_t device_index);\n        void UpdateIntersection(vr::TrackedDeviceIndex_t device_index);\n\n        void SendDirectDragCommand(vr::VROverlayHandle_t overlay_handle_target, bool do_start_drag);\n\n    public:\n        LaserPointer();\n        ~LaserPointer();\n\n        void Update();\n\n        void SetActiveDevice(vr::TrackedDeviceIndex_t device_index, LaserPointerActivationOrigin activation_origin = dplp_activation_origin_none);\n        void ClearActiveDevice();\n        void RemoveDevice(vr::TrackedDeviceIndex_t device_index);           //Clears device entry, called on device disconnect\n\n        void RefreshCachedOverlayHandles();\n        void TriggerLaserPointerHaptics(vr::TrackedDeviceIndex_t device_index) const;\n        void ForceTargetOverlay(vr::VROverlayHandle_t overlay_handle);      //Forces a different overlay to be current pointer target (only if there's currently one)\n\n        //ComputeOverlayIntersection() does not take intersection masks into account. It's a bit cumbersome, but we track the DDP/UI ones ourselves to get around that.\n        bool IntersectionMaskHitTest(OverlayTextureSource texsource, vr::HmdVector2_t& uv) const;\n        void UIIntersectionMaskAddRect(DPRect& rect);\n        void UIIntersectionMaskFinish();\n\n        LaserPointerActivationOrigin GetActivationOrigin() const;\n        bool IsActive() const;\n        vr::TrackedDeviceIndex_t IsAnyOverlayHovered(float max_distance) const; //Returns hovering device_index (or invalid if none). Only checks for LaserPointer supported overlays\n        bool IsScrolling() const;\n};"
  },
  {
    "path": "src/DesktopPlus/OutputManager.cpp",
    "content": "#include \"OutputManager.h\"\n\n//Keep building with 10.0.17763.0 / 1809 SDK optional\n#ifdef NTDDI_WIN10_RS5\n    #include <dxgi1_5.h>\n#else\n    #define DPLUS_DUP_NO_HDR\n#endif\n\n#include <dwmapi.h>\n#include <windowsx.h>\n#include <ShlDisp.h>\n#include <DirectXPackedVector.h>\nusing namespace DirectX;\n#include <sstream>\n\n#include <limits.h>\n#include <time.h>\n\n#include \"WindowManager.h\"\n#include \"Util.h\"\n#include \"OpenVRExt.h\"\n#include \"Logging.h\"\n\n#include \"DesktopPlusWinRT.h\"\n#include \"DPBrowserAPIClient.h\"\n\nstatic OutputManager* g_OutputManager; //May not always exist, but there also should never be two, so this is fine\n\nOutputManager* OutputManager::Get()\n{\n    return g_OutputManager;\n}\n\n//\n//Quick note about OutputManager (and Desktop+ in general) handles multi-overlay access:\n//Most functions use the \"current\" overlay as set by the OverlayManager or by having ConfigManager forward config values from the *_overlay_* configids\n//When needed, the current overlay is temporarily changed to the one to act on. \n//To have the UI act in such a scenario, the configid_int_state_overlay_current_id_override is typically used, as there may be visible changes to the user for one frame otherwise\n//To change the current overlay while nested in a temporary override, post a configid_int_interface_overlay_current_id message to both applications instead of just the counterpart\n//\n//This may all seem a bit messy, but helped retrofit the single overlay code a lot. Feel like cleaning this up with a way better scheme? Go ahead.\n//\n\nOutputManager::OutputManager(HANDLE PauseDuplicationEvent, HANDLE ResumeDuplicationEvent) :\n    m_Device(nullptr),\n    m_DeviceContext(nullptr),\n    m_Sampler(nullptr),\n    m_BlendState(nullptr),\n    m_RasterizerState(nullptr),\n    m_VertexShader(nullptr),\n    m_PixelShader(nullptr),\n    m_PixelShaderCursor(nullptr),\n    m_InputLayout(nullptr),\n    m_SharedSurf(nullptr),\n    m_VertexBuffer(nullptr),\n    m_ShaderResource(nullptr),\n    m_KeyMutex(nullptr),\n    m_WindowHandle(nullptr),\n    m_PauseDuplicationEvent(PauseDuplicationEvent),\n    m_ResumeDuplicationEvent(ResumeDuplicationEvent),\n    m_DesktopX(0),\n    m_DesktopY(0),\n    m_DesktopWidth(-1),\n    m_DesktopHeight(-1),\n    m_MaxActiveRefreshDelay(16),\n    m_OutputPendingSkippedFrame(false),\n    m_OutputPendingFullRefresh(false),\n    m_OutputHDRAvailable(false),\n    m_OutputInvalid(false),\n    m_OutputPendingDirtyRect{-1, -1, -1, -1},\n    m_OutputAlphaCheckFailed(false),\n    m_OutputAlphaChecksPending(0),\n    m_OvrlHandleIcon(vr::k_ulOverlayHandleInvalid),\n    m_OvrlHandleDashboardDummy(vr::k_ulOverlayHandleInvalid),\n    m_OvrlHandleDesktopTexture(vr::k_ulOverlayHandleInvalid),\n    m_OvrlTex(nullptr),\n    m_OvrlRTV(nullptr),\n    m_OvrlActiveCount(0),\n    m_OvrlDesktopDuplActiveCount(0),\n    m_OvrlDashboardActive(false),\n    m_OvrlInputActive(false),\n    m_OvrlDirectDragActive(false),\n    m_OvrlTempDragStartTick(0),\n    m_PendingDashboardDummyHeight(0.0f),\n    m_LastApplyTransformTick(0),\n    m_LastFrameTransformUpdateTick(0),\n    m_MouseLastClickTick(0),\n    m_MouseIgnoreMoveEvent(false),\n    m_MouseCursorNeedsUpdate(false),\n    m_MouseLaserPointerUsedLastUpdate(false),\n    m_MouseLastLaserPointerMoveBlocked(false),\n    m_MouseLastLaserPointerX(-1),\n    m_MouseLastLaserPointerY(-1),\n    m_MouseIgnoreMoveEventMissCount(0),\n    m_MouseLeftDownOverlayID(k_ulOverlayID_None),\n    m_MouseLaserPointerScrollDeltaStart{0},\n    m_MouseLaserPointerScrollDeltaFrequency{0},\n    m_IsFirstLaunch(false),\n    m_ComInitDone(false),\n    m_DashboardActivatedOnce(false),\n    m_MultiGPUTargetDevice(nullptr),\n    m_MultiGPUTargetDeviceContext(nullptr),\n    m_MultiGPUTexStaging(nullptr),\n    m_MultiGPUTexTarget(nullptr),\n    m_PerformanceFrameCount(0),\n    m_PerformanceFrameCountStartTick(0),\n    m_PerformanceUpdateLimiterDelay{0},\n    m_IsAnyHotkeyActive(false),\n    m_RegisteredHotkeyCount(0)\n{\n    m_MouseLastInfo.ShapeInfo.Type = DXGI_OUTDUPL_POINTER_SHAPE_TYPE_COLOR;\n    ::QueryPerformanceFrequency(&m_MouseLaserPointerScrollDeltaFrequency);\n\n    //Initialize ConfigManager and set first launch state based on existence of config file (used to detect first launch in Steam version)\n    m_IsFirstLaunch = !ConfigManager::Get().LoadConfigFromFile();\n\n    g_OutputManager = this;\n}\n\n//\n// Destructor which calls CleanRefs to release all references and memory.\n//\nOutputManager::~OutputManager()\n{\n    CleanRefs();\n    g_OutputManager = nullptr;\n}\n\n//\n// Releases all references\n//\nvoid OutputManager::CleanRefs()\n{\n    //Release the shared desktop overlay texture since we're destroying the D3D11 device it's attached to (don't need this after shutting down OpenVR though)\n    if (vr::VROverlay() != nullptr)\n    {\n        vr::VROverlayEx()->ReleaseSharedOverlayTexture(m_OvrlHandleDesktopTexture);\n    }\n\n    if (m_VertexShader)\n    {\n        m_VertexShader->Release();\n        m_VertexShader = nullptr;\n    }\n\n    if (m_PixelShader)\n    {\n        m_PixelShader->Release();\n        m_PixelShader = nullptr;\n    }\n\n    if (m_PixelShaderCursor)\n    {\n        m_PixelShaderCursor->Release();\n        m_PixelShaderCursor = nullptr;\n    }\n\n    if (m_InputLayout)\n    {\n        m_InputLayout->Release();\n        m_InputLayout = nullptr;\n    }\n\n    if (m_Sampler)\n    {\n        m_Sampler->Release();\n        m_Sampler = nullptr;\n    }\n\n    if (m_BlendState)\n    {\n        m_BlendState->Release();\n        m_BlendState = nullptr;\n    }\n\n    if (m_RasterizerState)\n    {\n        m_RasterizerState->Release();\n        m_RasterizerState = nullptr;\n    }\n\n    if (m_DeviceContext)\n    {\n        m_DeviceContext->Release();\n        m_DeviceContext = nullptr;\n    }\n\n    if (m_Device)\n    {\n        m_Device->Release();\n        m_Device = nullptr;\n    }\n\n    if (m_SharedSurf)\n    {\n        m_SharedSurf->Release();\n        m_SharedSurf = nullptr;\n    }\n\n    if (m_VertexBuffer)\n    {\n        m_VertexBuffer->Release();\n        m_VertexBuffer = nullptr;\n    }\n\n    if (m_ShaderResource)\n    {\n        m_ShaderResource->Release();\n        m_ShaderResource = nullptr;\n    }\n\n    if (m_OvrlTex)\n    {\n        m_OvrlTex->Release();\n        m_OvrlTex = nullptr;\n    }\n\n    if (m_OvrlRTV)\n    {\n        m_OvrlRTV->Release();\n        m_OvrlRTV = nullptr;\n    }\n\n    m_MouseTex.Reset();\n    m_MouseShaderRes.Reset();\n\n    //Reset mouse state variables too\n    m_MouseLastClickTick = 0;\n    m_MouseIgnoreMoveEvent = false;\n    m_MouseLastInfo = PTR_INFO();\n    m_MouseLastInfo.ShapeInfo.Type = DXGI_OUTDUPL_POINTER_SHAPE_TYPE_COLOR;\n    m_MouseLastLaserPointerX = -1;\n    m_MouseLastLaserPointerY = -1;\n\n    if (m_KeyMutex)\n    {\n        m_KeyMutex->Release();\n        m_KeyMutex = nullptr;\n    }\n\n    if (m_ComInitDone)\n    {\n        ::CoUninitialize();\n    }\n\n    if (m_MultiGPUTargetDevice)\n    {\n        m_MultiGPUTargetDevice->Release();\n        m_MultiGPUTargetDevice = nullptr;\n    }\n\n    if (m_MultiGPUTargetDeviceContext)\n    {\n        m_MultiGPUTargetDeviceContext->Release();\n        m_MultiGPUTargetDeviceContext = nullptr;\n    }\n\n    if (m_MultiGPUTexStaging)\n    {\n        m_MultiGPUTexStaging->Release();\n        m_MultiGPUTexStaging = nullptr;\n    }\n\n    if (m_MultiGPUTexTarget)\n    {\n        m_MultiGPUTexTarget->Release();\n        m_MultiGPUTexTarget = nullptr;\n    }\n}\n\n//\n// Initialize all state\n//\nDUPL_RETURN OutputManager::InitOutput(HWND Window, _Out_ INT& SingleOutput, _Out_ UINT* OutCount, _Out_ RECT* DeskBounds)\n{\n    LOG_F(INFO, \"Initializing Desktop Duplication...\");\n\n    HRESULT hr = S_OK;\n\n    m_OutputInvalid = false;\n\n    if (ConfigManager::GetValue(configid_bool_performance_single_desktop_mirroring))\n    {\n        SingleOutput = clamp(ConfigManager::GetValue(configid_int_overlay_desktop_id), -1, ::GetSystemMetrics(SM_CMONITORS) - 1);\n    }\n    else\n    {\n        SingleOutput = -1;\n    }\n    \n    // Store window handle\n    m_WindowHandle = Window;\n\n    //Get preferred adapter if there is any, this detects which GPU the target desktop is on\n    Microsoft::WRL::ComPtr<IDXGIAdapter> adapter_ptr_preferred;\n    Microsoft::WRL::ComPtr<IDXGIAdapter> adapter_ptr_vr;\n\n    std::vector<DPRect> desktop_rects_prev = m_DesktopRects;\n    int desktop_x_prev = m_DesktopX;\n    int desktop_y_prev = m_DesktopY;\n\n    EnumerateOutputs(SingleOutput, &adapter_ptr_preferred, &adapter_ptr_vr);\n\n    //If there's no preferred adapter it should default to the one the HMD is connected to\n    if (adapter_ptr_preferred == nullptr) \n    {\n        //If both are nullptr it'll still try to find a working adapter to init, though it'll probably not work at the end in that scenario\n        adapter_ptr_preferred = adapter_ptr_vr; \n    }\n    //If they're the same, we don't need to do any multi-gpu handling\n    if (adapter_ptr_vr == adapter_ptr_preferred)\n    {\n        adapter_ptr_vr = nullptr;\n    }\n\n    // Driver types supported\n    D3D_DRIVER_TYPE DriverTypes[] =\n    {\n        D3D_DRIVER_TYPE_HARDWARE,\n        D3D_DRIVER_TYPE_WARP,       //WARP shouldn't work, but this was like this in the duplication sample, so eh\n        D3D_DRIVER_TYPE_REFERENCE,\n    };\n    UINT NumDriverTypes = ARRAYSIZE(DriverTypes);\n\n    // Feature levels supported\n    D3D_FEATURE_LEVEL FeatureLevels[] =\n    {\n        D3D_FEATURE_LEVEL_11_0,\n        D3D_FEATURE_LEVEL_10_1,\n        D3D_FEATURE_LEVEL_10_0,\n        D3D_FEATURE_LEVEL_9_1\n    };\n    UINT NumFeatureLevels = ARRAYSIZE(FeatureLevels);\n    D3D_FEATURE_LEVEL FeatureLevel;\n\n    // Create device\n    if (adapter_ptr_preferred != nullptr) //Try preferred adapter first if we have one\n    {\n        hr = D3D11CreateDevice(adapter_ptr_preferred.Get(), D3D_DRIVER_TYPE_UNKNOWN, nullptr, 0, FeatureLevels, NumFeatureLevels,\n                               D3D11_SDK_VERSION, &m_Device, &FeatureLevel, &m_DeviceContext);\n\n        if (FAILED(hr))\n        {\n            adapter_ptr_preferred = nullptr;\n        }\n    }\n\n    if (adapter_ptr_preferred == nullptr)\n    {\n        LOG_F(WARNING, \"Output enumeration did not return a preferred GPU. Trying first available device...\");\n\n        for (UINT DriverTypeIndex = 0; DriverTypeIndex < NumDriverTypes; ++DriverTypeIndex)\n        {\n            hr = D3D11CreateDevice(nullptr, DriverTypes[DriverTypeIndex], nullptr, 0, FeatureLevels, NumFeatureLevels,\n                                   D3D11_SDK_VERSION, &m_Device, &FeatureLevel, &m_DeviceContext);\n\n            if (SUCCEEDED(hr))\n            {\n                //Device creation succeeded. Take adapter from the device so it can be used further down\n                adapter_ptr_preferred.Attach(GetDXGIAdapter());\n                break;\n            }\n        }\n    }\n\n    if (FAILED(hr))\n    {\n        return ProcessFailure(m_Device, L\"Device creation failed\", L\"Desktop+ Error\", hr, SystemTransitionsExpectedErrors);\n    }\n\n    //Create multi-gpu target device if needed\n    if (adapter_ptr_vr != nullptr)\n    {\n        hr = D3D11CreateDevice(adapter_ptr_vr.Get(), D3D_DRIVER_TYPE_UNKNOWN, nullptr, 0, FeatureLevels, NumFeatureLevels,\n                               D3D11_SDK_VERSION, &m_MultiGPUTargetDevice, &FeatureLevel, &m_MultiGPUTargetDeviceContext);\n\n        if (FAILED(hr))\n        {\n            return ProcessFailure(m_Device, L\"Secondary device creation failed\", L\"Desktop+ Error\", hr, SystemTransitionsExpectedErrors);\n        }\n\n        adapter_ptr_vr = nullptr;\n\n        LOG_F(INFO, \"Using cross-GPU copy\");\n    }\n\n    //Check Desktop Duplication HDR support\n    m_OutputHDRAvailable = false;\n\n    #ifndef DPLUS_DUP_NO_HDR\n    if (adapter_ptr_preferred != nullptr)\n    {\n        Microsoft::WRL::ComPtr<IDXGIOutput> DxgiOutput;\n        hr = adapter_ptr_preferred->EnumOutputs(0, &DxgiOutput);\n        if (SUCCEEDED(hr))\n        {\n            Microsoft::WRL::ComPtr<IDXGIOutput5> DxgiOutput5;\n            hr = DxgiOutput.As(&DxgiOutput5);\n            m_OutputHDRAvailable = SUCCEEDED(hr);\n        }\n    }\n\n    if (m_OutputHDRAvailable)\n    {\n        LOG_F(INFO, \"Desktop Duplication HDR mirroring is supported\");\n    }\n    else\n    {\n        LOG_F(INFO, \"Desktop Duplication HDR mirroring is not supported\");\n    }\n    #else\n        LOG_F(INFO, \"Desktop+ was not built with Desktop Duplication HDR support\");\n    #endif\n\n    // Create shared texture\n    DUPL_RETURN Return = CreateTextures(SingleOutput, OutCount, DeskBounds);\n    if (Return != DUPL_RETURN_SUCCESS)\n    {\n        return Return;\n    }\n\n    // Make new render target view\n    Return = MakeRTV();\n    if (Return != DUPL_RETURN_SUCCESS)\n    {\n        return Return;\n    }\n\n    // Set view port\n    D3D11_VIEWPORT VP;\n    VP.Width = static_cast<FLOAT>(m_DesktopWidth);\n    VP.Height = static_cast<FLOAT>(m_DesktopHeight);\n    VP.MinDepth = 0.0f;\n    VP.MaxDepth = 1.0f;\n    VP.TopLeftX = 0;\n    VP.TopLeftY = 0;\n    m_DeviceContext->RSSetViewports(1, &VP);\n\n    // Create the sample state\n    D3D11_SAMPLER_DESC SampDesc;\n    RtlZeroMemory(&SampDesc, sizeof(SampDesc));\n    SampDesc.Filter = D3D11_FILTER_MIN_MAG_MIP_POINT;\n    SampDesc.AddressU = D3D11_TEXTURE_ADDRESS_CLAMP;\n    SampDesc.AddressV = D3D11_TEXTURE_ADDRESS_CLAMP;\n    SampDesc.AddressW = D3D11_TEXTURE_ADDRESS_CLAMP;\n    SampDesc.ComparisonFunc = D3D11_COMPARISON_NEVER;\n    SampDesc.MinLOD = 0;\n    SampDesc.MaxLOD = D3D11_FLOAT32_MAX;\n    hr = m_Device->CreateSamplerState(&SampDesc, &m_Sampler);\n    if (FAILED(hr))\n    {\n        return ProcessFailure(m_Device, L\"Failed to create sampler state\", L\"Desktop+ Error\", hr, SystemTransitionsExpectedErrors);\n    }\n\n    // Create the blend state\n    D3D11_BLEND_DESC BlendStateDesc;\n    BlendStateDesc.AlphaToCoverageEnable = FALSE;\n    BlendStateDesc.IndependentBlendEnable = FALSE;\n    BlendStateDesc.RenderTarget[0].BlendEnable = TRUE;\n    BlendStateDesc.RenderTarget[0].SrcBlend = D3D11_BLEND_SRC_ALPHA;\n    BlendStateDesc.RenderTarget[0].DestBlend = D3D11_BLEND_INV_SRC_ALPHA;\n    BlendStateDesc.RenderTarget[0].BlendOp = D3D11_BLEND_OP_ADD;\n    BlendStateDesc.RenderTarget[0].SrcBlendAlpha = D3D11_BLEND_ONE;\n    BlendStateDesc.RenderTarget[0].DestBlendAlpha = D3D11_BLEND_ZERO;\n    BlendStateDesc.RenderTarget[0].BlendOpAlpha = D3D11_BLEND_OP_ADD;\n    BlendStateDesc.RenderTarget[0].RenderTargetWriteMask = D3D11_COLOR_WRITE_ENABLE_RED | D3D11_COLOR_WRITE_ENABLE_GREEN | D3D11_COLOR_WRITE_ENABLE_BLUE;\n    hr = m_Device->CreateBlendState(&BlendStateDesc, &m_BlendState);\n    if (FAILED(hr))\n    {\n        return ProcessFailure(m_Device, L\"Failed to create blend state\", L\"Desktop+ Error\", hr, SystemTransitionsExpectedErrors);\n    }\n\n    //Create the rasterizer state\n    D3D11_RASTERIZER_DESC RasterizerDesc;\n    RtlZeroMemory(&RasterizerDesc, sizeof(RasterizerDesc));\n    RasterizerDesc.FillMode = D3D11_FILL_SOLID;\n    RasterizerDesc.CullMode = D3D11_CULL_BACK;\n    RasterizerDesc.ScissorEnable = true;\n\n    hr = m_Device->CreateRasterizerState(&RasterizerDesc, &m_RasterizerState);\n    if (FAILED(hr))\n    {\n        return ProcessFailure(m_Device, L\"Failed to create rasterizer state\", L\"Desktop+ Error\", hr, SystemTransitionsExpectedErrors);\n    }\n    m_DeviceContext->RSSetState(m_RasterizerState);\n\n    //Create vertex buffer for drawing whole texture\n    VERTEX Vertices[NUMVERTICES] =\n    {\n        { XMFLOAT3(-1.0f, -1.0f, 0.0f), XMFLOAT2(0.0f, 1.0f) },\n        { XMFLOAT3(-1.0f,  1.0f, 0.0f), XMFLOAT2(0.0f, 0.0f) },\n        { XMFLOAT3( 1.0f, -1.0f, 0.0f), XMFLOAT2(1.0f, 1.0f) },\n        { XMFLOAT3( 1.0f, -1.0f, 0.0f), XMFLOAT2(1.0f, 1.0f) },\n        { XMFLOAT3(-1.0f,  1.0f, 0.0f), XMFLOAT2(0.0f, 0.0f) },\n        { XMFLOAT3( 1.0f,  1.0f, 0.0f), XMFLOAT2(1.0f, 0.0f) },\n    };\n\n    D3D11_BUFFER_DESC BufferDesc;\n    RtlZeroMemory(&BufferDesc, sizeof(BufferDesc));\n    BufferDesc.Usage = D3D11_USAGE_DEFAULT;\n    BufferDesc.ByteWidth = sizeof(VERTEX) * NUMVERTICES;\n    BufferDesc.BindFlags = D3D11_BIND_VERTEX_BUFFER;\n    BufferDesc.CPUAccessFlags = 0;\n    D3D11_SUBRESOURCE_DATA InitData;\n    RtlZeroMemory(&InitData, sizeof(InitData));\n    InitData.pSysMem = Vertices;\n\n    // Create vertex buffer\n    hr = m_Device->CreateBuffer(&BufferDesc, &InitData, &m_VertexBuffer);\n    if (FAILED(hr))\n    {\n        return ProcessFailure(m_Device, L\"Failed to create vertex buffer\", L\"Desktop+ Error\", hr, SystemTransitionsExpectedErrors);\n    }\n\n    //Set scissor rect to full\n    const D3D11_RECT rect_scissor_full = { 0, 0, m_DesktopWidth, m_DesktopHeight };\n    m_DeviceContext->RSSetScissorRects(1, &rect_scissor_full);\n\n    // Initialize shaders\n    Return = InitShaders();\n    if (Return != DUPL_RETURN_SUCCESS)\n    {\n        return Return;\n    }\n\n    //In case this was called due to a resolution change, check if the crop was just exactly the set desktop in each overlay and adapt then\n    if (!ConfigManager::GetValue(configid_bool_performance_single_desktop_mirroring))\n    {\n        unsigned int current_overlay_old = OverlayManager::Get().GetCurrentOverlayID();\n\n        for (unsigned int i = 0; i < OverlayManager::Get().GetOverlayCount(); ++i)\n        {\n            OverlayConfigData& data = OverlayManager::Get().GetConfigData(i);\n\n            if (data.ConfigInt[configid_int_overlay_capture_source] == ovrl_capsource_desktop_duplication)\n            {\n                int desktop_id = data.ConfigInt[configid_int_overlay_desktop_id];\n                if ((desktop_id >= 0) && (desktop_id < desktop_rects_prev.size()) && (desktop_id < m_DesktopRects.size()))\n                {\n                    int crop_x = data.ConfigInt[configid_int_overlay_crop_x];\n                    int crop_y = data.ConfigInt[configid_int_overlay_crop_y];\n                    int crop_width = data.ConfigInt[configid_int_overlay_crop_width];\n                    int crop_height = data.ConfigInt[configid_int_overlay_crop_height];\n                    DPRect crop_rect(crop_x, crop_y, crop_x + crop_width, crop_y + crop_height);\n                    DPRect desktop_rect = desktop_rects_prev[desktop_id];\n                    desktop_rect.Translate({-desktop_x_prev, -desktop_y_prev});\n\n                    if (crop_rect == desktop_rect)\n                    {\n                        OverlayManager::Get().SetCurrentOverlayID(i);\n                        CropToDisplay(desktop_id, true);\n                    }\n                }\n            }\n        }\n\n        OverlayManager::Get().SetCurrentOverlayID(current_overlay_old);\n    }\n\n    ResetOverlays();\n\n    //On some systems, the Desktop Duplication output is translucent for some reason\n    //We check the texture's pixels for the first few frame updates to make sure we can use the straight copy path, which should be the case on most machines\n    //If it fails we use a pixel shader to fix the alpha channel during frame updates\n    m_OutputAlphaCheckFailed   = false;\n    m_OutputAlphaChecksPending = 10;\n\n    return Return;\n}\n\nstd::tuple<vr::EVRInitError, vr::EVROverlayError, bool> OutputManager::InitOverlay()\n{\n    vr::EVRInitError init_error   = vr::VRInitError_None;\n    vr::VROverlayError ovrl_error = vr::VROverlayError_None;\n\n    vr::VR_Init(&init_error, vr::VRApplication_Overlay);\n\n    if (init_error != vr::VRInitError_None)\n        return {init_error, ovrl_error, false};\n\n    if (!vr::VROverlay())\n        return {vr::VRInitError_Init_InvalidInterface, ovrl_error, false};\n\n    DPLog_SteamVR_SystemInfo();\n\n    m_OvrlHandleDashboardDummy = vr::k_ulOverlayHandleInvalid;\n    m_OvrlHandleIcon = vr::k_ulOverlayHandleInvalid;\n    m_OvrlHandleDesktopTexture = vr::k_ulOverlayHandleInvalid;\n\n    //We already got rid of another instance of this app if there was any, but this loop takes care of it too if the detection failed or something uses our overlay key\n    for (int tries = 0; tries < 10; ++tries)\n    {\n        //Create dashboard dummy overlay. It's only used to get a button, transform origin and position the top bar in the dashboard\n        ovrl_error = vr::VROverlay()->CreateDashboardOverlay(\"elvissteinjr.DesktopPlusDashboard\", \"Desktop+\", &m_OvrlHandleDashboardDummy, &m_OvrlHandleIcon);\n\n        if (ovrl_error == vr::VROverlayError_KeyInUse)  //If the key is already in use, kill the owning process (hopefully another instance of this app)\n        {\n            ovrl_error = vr::VROverlay()->FindOverlay(\"elvissteinjr.DesktopPlusDashboard\", &m_OvrlHandleDashboardDummy);\n\n            if ((ovrl_error == vr::VROverlayError_None) && (m_OvrlHandleDashboardDummy != vr::k_ulOverlayHandleInvalid))\n            {\n                LOG_F(INFO, \"Overlay key already in use, killing owning process...\");\n\n                uint32_t pid = vr::VROverlay()->GetOverlayRenderingPid(m_OvrlHandleDashboardDummy);\n\n                HANDLE phandle = ::OpenProcess(SYNCHRONIZE | PROCESS_TERMINATE, TRUE, pid);\n\n                if (phandle != nullptr)\n                {\n                    ::TerminateProcess(phandle, 0);\n                    ::CloseHandle(phandle);\n                }\n                else\n                {\n                    ovrl_error = vr::VROverlayError_KeyInUse;\n                }\n            }\n            else\n            {\n                ovrl_error = vr::VROverlayError_KeyInUse;\n            }\n        }\n        else\n        {\n            break;\n        }\n\n        //Try again in a bit to check if it's just a race with some external cleanup\n        ::Sleep(200);\n    }\n\n\n    if (m_OvrlHandleDashboardDummy != vr::k_ulOverlayHandleInvalid)\n    {\n        //Create desktop texture overlay. This overlay holds the desktop texture shared between Desktop Duplication Overlays\n        ovrl_error = vr::VROverlay()->CreateOverlay(\"elvissteinjr.DesktopPlusDesktopTexture\", \"Desktop+\", &m_OvrlHandleDesktopTexture);\n\n        //Load config again to properly initialize overlays that were loaded before OpenVR was available\n        const bool loaded_overlay_profile = ConfigManager::Get().GetAppProfileManager().ActivateProfileForCurrentSceneApp(); //Check if overlays from app profile need to be loaded first\n        if (!loaded_overlay_profile)\n        {\n            ConfigManager::Get().LoadConfigFromFile();\n        }\n\n        if (m_OvrlHandleDashboardDummy != vr::k_ulOverlayHandleInvalid)\n        {\n            unsigned char bytes[2 * 2 * 4] = {0}; //2x2 transparent RGBA\n\n            //Set dashboard dummy content instead of leaving it totally blank, which is undefined\n            vr::VROverlay()->SetOverlayRaw(m_OvrlHandleDashboardDummy, bytes, 2, 2, 4);\n\n            vr::VROverlay()->SetOverlayInputMethod(m_OvrlHandleDashboardDummy, vr::VROverlayInputMethod_None);\n            vr::VROverlay()->SetOverlayWidthInMeters(m_OvrlHandleDashboardDummy, 1.5f);\n\n            vr::VROverlay()->SetOverlayFlag(m_OvrlHandleDashboardDummy, vr::VROverlayFlags_MinimalControlBar, true);\n\n            //ResetOverlays() is called later\n\n            //Use different icon if GamepadUI (SteamVR 2 dashboard) exists\n            vr::VROverlayHandle_t handle_gamepad_ui = vr::k_ulOverlayHandleInvalid;\n            vr::VROverlay()->FindOverlay(\"valve.steam.gamepadui.bar\", &handle_gamepad_ui);\n            const char* icon_file = (handle_gamepad_ui != vr::k_ulOverlayHandleInvalid) ? \"images/icon_dashboard_gamepadui.png\" : \"images/icon_dashboard.png\";\n\n            vr::VROverlay()->SetOverlayFromFile(m_OvrlHandleIcon, (ConfigManager::Get().GetApplicationPath() + icon_file).c_str());\n        }\n    }\n\n    m_MaxActiveRefreshDelay = 1000.0f / GetHMDFrameRate();\n\n    //Check if this process was launched by Steam by checking if the \"SteamClientLaunch\" environment variable exists\n    bool is_steam_app = (::GetEnvironmentVariable(L\"SteamClientLaunch\", nullptr, 0) != 0);\n    ConfigManager::SetValue(configid_bool_state_misc_process_started_by_steam, is_steam_app);\n    IPCManager::Get().PostConfigMessageToUIApp(configid_bool_state_misc_process_started_by_steam, is_steam_app);\n\n    //Add application manifest and set app key to Steam one if needed (setting the app key will make it load Steam input bindings even when not launched by it)\n    if (!is_steam_app)\n    {\n        LOG_F(INFO, \"Process was not launched by Steam, setting SteamVR application identity\");\n\n        vr::VRApplications()->IdentifyApplication(::GetCurrentProcessId(), g_AppKeyDashboardApp);\n        m_IsFirstLaunch = (!vr::VRApplications()->IsApplicationInstalled(g_AppKeyDashboardApp));\n    }\n\n    //Even if not first launch, make sure the Theater Screen dummy app is installed\n    //The Theater Screen needs a separate app entry as on Steam we don't control the contents of the app manifest for our appid, yet need to have \"starts_theater_mode\" in it somewhere\n    if ((m_IsFirstLaunch) || (!vr::VRApplications()->IsApplicationInstalled(g_AppKeyTheaterScreen)))\n    {\n        vr::VRApplications()->AddApplicationManifest((ConfigManager::Get().GetApplicationPath() + \"manifest.vrmanifest\").c_str());\n    }\n\n    //Set application auto-launch to true if it's the first launch\n    if (m_IsFirstLaunch)\n    {\n        LOG_F(INFO, \"First launch detected. Setting application to auto-launch with SteamVR\");\n        vr::EVRApplicationError app_error = vr::VRApplications()->SetApplicationAutoLaunch(g_AppKeyDashboardApp, true);\n\n        if (app_error == vr::VRApplicationError_None)\n        {\n            //Have the UI app display the initial setup notification\n            IPCManager::Get().PostMessageToUIApp(ipcmsg_action, ipcact_notification_show);\n\n            //Show the dashboard overlay as well to make it easier to find when first using the app\n            vr::VROverlay()->ShowDashboard(\"elvissteinjr.DesktopPlusDashboard\");\n        }\n    }\n\n    const bool vrinput_init_success = m_VRInput.Init();\n\n    //Check if it's a WMR system and set up for that if needed\n    ConfigManager::Get().InitConfigForWMR();\n    DPWinRT_SetDesktopEnumerationFlags( (ConfigManager::GetValue(configid_int_interface_wmr_ignore_vscreens) == 1) );\n    LOG_IF_F(INFO, (ConfigManager::GetValue(configid_int_interface_wmr_ignore_vscreens) == 1), \"WMR headset detected, ignoring additional virtual displays\");\n\n    //Set pen simulation support state so the UI can act on it\n    ConfigManager::SetValue(configid_bool_state_pen_simulation_supported, m_InputSim.IsPenSimulationSupported());\n    LOG_F(INFO, \"Pen input simulation is %s\", (m_InputSim.IsPenSimulationSupported()) ? \"supported\" : \"not supported\");\n\n    //Init background overlay if needed\n    m_BackgroundOverlay.Update();\n\n    //Hotkeys can trigger actions requiring OpenVR, so only register after OpenVR init\n    RegisterHotkeys();\n\n    //Try to get dashboard in proper state if needed\n    FixInvalidDashboardLaunchState();\n\n    //Return error state to allow for accurate display if needed\n    return {vr::VRInitError_None, ovrl_error, vrinput_init_success};\n}\n\n//\n// Update Overlay and handle events\n//\nDUPL_RETURN_UPD OutputManager::Update(_In_ PTR_INFO* PointerInfo,  _In_ DPRect& DirtyRectTotal, bool NewFrame, bool SkipFrame)\n{\n    if (HandleOpenVREvents())   //If quit event received, quit.\n    {\n        return DUPL_RETURN_UPD_QUIT;\n    }\n\n    UINT64 sync_key = 1; //Key used by duplication threads to lock for this function (duplication threads lock with 1, Update() with 0 and unlock vice versa)\n\n    //If we previously skipped a frame, we want to actually process a new one at the next valid opportunity\n    if ( (m_OutputPendingSkippedFrame) && (!SkipFrame) )\n    {\n        //If there isn't new frame yet, we have to unlock the keyed mutex with the one we locked it with ourselves before\n        //However, if the laser pointer was used since the last update, we simply use the key for new frame data to wait for the new mouse position or frame\n        //Not waiting for it reduces latency usually, but laser pointer mouse movements are weirdly not picked up without doing this or enabling the rapid laser pointer update setting\n        if ( (!NewFrame) && (!m_MouseLaserPointerUsedLastUpdate) )\n        {\n            sync_key = 0;\n        }\n\n        NewFrame = true; //Treat this as a new frame now\n        m_MouseLaserPointerUsedLastUpdate = false;\n    }\n\n    //If frame skipped and no new frame, do nothing (if there's a new frame, we have to at least re-lock the keyed mutex so the duplication threads can access it again)\n    if ( (SkipFrame) && (!NewFrame) )\n    {\n        m_OutputPendingSkippedFrame = true; //Process the frame next time we can\n        return DUPL_RETURN_UPD_SUCCESS;\n    }\n\n    //When invalid output is set, key mutex can be null, so just do nothing\n    if (m_KeyMutex == nullptr)\n    {\n        return DUPL_RETURN_UPD_SUCCESS;\n    }\n\n    // Try and acquire sync on common display buffer (needed to safely access the PointerInfo)\n    HRESULT hr = m_KeyMutex->AcquireSync(sync_key, GetMaxRefreshDelay());\n    if (hr == static_cast<HRESULT>(WAIT_TIMEOUT))\n    {\n        // Another thread has the keyed mutex so try again later\n        return DUPL_RETURN_UPD_RETRY;\n    }\n    else if (FAILED(hr))\n    {\n        return (DUPL_RETURN_UPD)ProcessFailure(m_Device, L\"Failed to acquire keyed mutex\", L\"Desktop+ Error\", hr, SystemTransitionsExpectedErrors);\n    }\n\n    DUPL_RETURN_UPD ret = DUPL_RETURN_UPD_SUCCESS;\n\n    //If alternative cursor rendering is enabled, try to get software cursor data and use that as PointerInfo instead\n    if (ConfigManager::GetValue(configid_bool_performance_alternative_cursor_rendering))\n    {\n        m_MouseAlternativeCursor.SynthesizeDDPCursorInfo();\n        PointerInfo = &m_MouseAlternativeCursor.GetDDPCursorInfo();\n    }\n\n    //Got mutex, so we can access pointer info and shared surface\n    DPRect mouse_rect = {PointerInfo->Position.x, PointerInfo->Position.y, int(PointerInfo->Position.x + PointerInfo->ShapeInfo.Width),\n                         int(PointerInfo->Position.y + PointerInfo->ShapeInfo.Height)};\n\n    //If mouse state got updated, expand dirty rect to include old and new cursor regions\n    if ( (ConfigManager::GetValue(configid_bool_input_mouse_render_cursor)) && (m_MouseLastInfo.LastTimeStamp.QuadPart < PointerInfo->LastTimeStamp.QuadPart) )\n    {\n        //Only invalidate if position or shape changed, otherwise it would be a visually identical result\n        if ( (m_MouseLastInfo.Position.x != PointerInfo->Position.x) || (m_MouseLastInfo.Position.y != PointerInfo->Position.y) ||\n             (PointerInfo->CursorShapeChanged) || (m_MouseCursorNeedsUpdate) || (m_MouseLastInfo.Visible != PointerInfo->Visible) )\n        {\n            if ( (PointerInfo->Visible) )\n            {\n                (DirtyRectTotal.GetTL().x == -1) ? DirtyRectTotal = mouse_rect : DirtyRectTotal.Add(mouse_rect);\n            }\n\n            if (m_MouseLastInfo.Visible)\n            {\n                DPRect mouse_rect_last(m_MouseLastInfo.Position.x, m_MouseLastInfo.Position.y, int(m_MouseLastInfo.Position.x + m_MouseLastInfo.ShapeInfo.Width),\n                                       int(m_MouseLastInfo.Position.y + m_MouseLastInfo.ShapeInfo.Height));\n\n                (DirtyRectTotal.GetTL().x == -1) ? DirtyRectTotal = mouse_rect_last : DirtyRectTotal.Add(mouse_rect_last);\n            }\n        }\n    }\n\n\n    //If frame is skipped, skip all GPU work\n    if (SkipFrame)\n    {\n        //Collect dirty rects for the next time we render\n        (m_OutputPendingDirtyRect.GetTL().x == -1) ? m_OutputPendingDirtyRect = DirtyRectTotal : m_OutputPendingDirtyRect.Add(DirtyRectTotal);\n\n        //Remember if the cursor changed so it's updated the next time we actually render it\n        if (PointerInfo->CursorShapeChanged)\n        {\n            m_MouseCursorNeedsUpdate = true;\n        }\n\n        m_OutputPendingSkippedFrame = true;\n        hr = m_KeyMutex->ReleaseSync(0);\n\n        return DUPL_RETURN_UPD_SUCCESS;\n    }\n    else if (m_OutputPendingDirtyRect.GetTL().x != -1) //Add previously collected dirty rects if there are any\n    {\n        (DirtyRectTotal.GetTL().x == -1) ? DirtyRectTotal = m_OutputPendingDirtyRect : DirtyRectTotal.Add(m_OutputPendingDirtyRect);\n    }\n\n    bool has_updated_overlay = false;\n\n    //Check all overlays for overlap and collect clipping region from matches\n    DPRect clipping_region(-1, -1, -1, -1);\n\n    if (!m_OutputPendingFullRefresh)\n    {\n        for (unsigned int i = 0; i < OverlayManager::Get().GetOverlayCount(); ++i)\n        {\n            const Overlay& overlay = OverlayManager::Get().GetOverlay(i);\n\n            if ( (overlay.IsVisible()) && ( (overlay.GetTextureSource() == ovrl_texsource_desktop_duplication) || (overlay.GetTextureSource() == ovrl_texsource_desktop_duplication_3dou_converted) ) )\n            {\n                const DPRect& cropping_region = overlay.GetValidatedCropRect();\n\n                if (DirtyRectTotal.Overlaps(cropping_region))\n                {\n                    if (clipping_region.GetTL().x != -1)\n                    {\n                        clipping_region.Add(cropping_region);\n                    }\n                    else\n                    {\n                        clipping_region = cropping_region;\n                    }\n                }\n            }\n        }\n\n        DirtyRectTotal.ClipWithFull(clipping_region);\n    }\n    else   //Set dirty & clipping rect to total surface for full refresh\n    {\n        DirtyRectTotal = {0, 0, m_DesktopWidth, m_DesktopHeight};\n        clipping_region = DirtyRectTotal;\n        m_OutputPendingFullRefresh = false;\n    }\n\n    m_OutputLastClippingRect = clipping_region;\n\n    if (clipping_region.GetTL().x != -1) //Overlapped with at least one overlay\n    {\n        //Set scissor rect for overlay drawing function\n        const D3D11_RECT rect_scissor = { DirtyRectTotal.GetTL().x, DirtyRectTotal.GetTL().y, DirtyRectTotal.GetBR().x, DirtyRectTotal.GetBR().y };\n        m_DeviceContext->RSSetScissorRects(1, &rect_scissor);\n\n        //Draw shared surface to overlay texture to avoid trouble with transparency on some systems\n        bool is_full_texture = DirtyRectTotal.Contains({0, 0, m_DesktopWidth, m_DesktopHeight});\n        DrawFrameToOverlayTex(is_full_texture);\n\n        //Only handle cursor if it's in cropping region\n        if (mouse_rect.Overlaps(DirtyRectTotal))\n        {\n            DrawMouseToOverlayTex(PointerInfo);\n        }\n        else if (PointerInfo->CursorShapeChanged) //But remember if the cursor changed for next time\n        {\n            m_MouseCursorNeedsUpdate = true;\n        }\n\n        //Set Overlay texture\n        ret = RefreshOpenVROverlayTexture(DirtyRectTotal);\n\n        //Reset scissor rect\n        const D3D11_RECT rect_scissor_full = { 0, 0, m_DesktopWidth, m_DesktopHeight };\n        m_DeviceContext->RSSetScissorRects(1, &rect_scissor_full);\n\n        has_updated_overlay = (ret == DUPL_RETURN_UPD_SUCCESS_REFRESHED_OVERLAY);\n    }\n    else if (PointerInfo->CursorShapeChanged) //But remember if the cursor changed for next time\n    {\n        m_MouseCursorNeedsUpdate = true;\n    }\n\n    //Set cached mouse values (we don't want to copy the shape buffer, so no straight assignment)\n    m_MouseLastInfo.ShapeInfo              = PointerInfo->ShapeInfo;\n    m_MouseLastInfo.Position               = PointerInfo->Position;\n    m_MouseLastInfo.Visible                = PointerInfo->Visible;\n    m_MouseLastInfo.WhoUpdatedPositionLast = PointerInfo->WhoUpdatedPositionLast;\n    m_MouseLastInfo.LastTimeStamp          = PointerInfo->LastTimeStamp;\n    m_MouseLastInfo.CursorShapeChanged     = PointerInfo->CursorShapeChanged;\n\n    //Reset dirty rect\n    DirtyRectTotal = DPRect(-1, -1, -1, -1);\n\n    // Release keyed mutex\n    hr = m_KeyMutex->ReleaseSync(0);\n    if (FAILED(hr))\n    {\n        return (DUPL_RETURN_UPD)ProcessFailure(m_Device, L\"Failed to Release keyed mutex\", L\"Desktop+ Error\", hr, SystemTransitionsExpectedErrors);\n    }\n\n    //Count frames\n    if (has_updated_overlay)\n    {\n        m_PerformanceFrameCount++;\n    }\n\n    m_OutputPendingSkippedFrame = false;\n    m_OutputPendingDirtyRect = {-1, -1, -1, -1};\n\n    return ret;\n}\n\nvoid OutputManager::BusyUpdate()\n{\n    //Improve responsiveness of temp drag during overlay creation when applying capture source can take a bit longer (i.e. browser overlays)\n    if ( (m_OverlayDragger.IsDragActive()) && (ConfigManager::GetValue(configid_bool_state_overlay_dragmode_temp)) )\n    {\n        m_OverlayDragger.DragUpdate();\n    }\n}\n\nbool OutputManager::HandleIPCMessage(const MSG& msg)\n{\n    //Handle messages sent by browser process in the APIClient\n    if (msg.message == DPBrowserAPIClient::Get().GetRegisteredMessageID())\n    {\n        DPBrowserAPIClient::Get().HandleIPCMessage(msg);\n        return false;\n    }\n\n    //Apply overlay id override if needed\n    unsigned int current_overlay_old = OverlayManager::Get().GetCurrentOverlayID();\n    int overlay_override_id = ConfigManager::GetValue(configid_int_state_overlay_current_id_override);\n\n    if (overlay_override_id != -1)\n    {\n        OverlayManager::Get().SetCurrentOverlayID(overlay_override_id);\n    }\n\n    //Config strings come as WM_COPYDATA\n    if (msg.message == WM_COPYDATA)\n    {\n        COPYDATASTRUCT* pcds = (COPYDATASTRUCT*)msg.lParam;\n        \n        //Arbitrary size limit to prevent some malicous applications from sending bad data, especially when this is running elevated\n        if ( (pcds->dwData < configid_str_MAX) && (pcds->cbData <= 4096) ) \n        {\n            std::string copystr((char*)pcds->lpData, pcds->cbData); //We rely on the data length. The data is sent without the NUL byte\n\n            ConfigID_String str_id = (ConfigID_String)pcds->dwData;\n            ConfigManager::SetValue(str_id, copystr);\n\n            switch (str_id)\n            {\n                case configid_str_state_keyboard_string:\n                {\n                    m_InputSim.KeyboardText(copystr.c_str());\n                    break;\n                }\n                case configid_str_state_app_profile_data:\n                {\n                    const std::string& app_key = ConfigManager::GetValue(configid_str_state_app_profile_key);\n\n                    if (!app_key.empty())\n                    {\n                        AppProfile new_profile;\n                        new_profile.Deserialize(copystr);\n\n                        const bool loaded_overlay_profile = ConfigManager::Get().GetAppProfileManager().StoreProfile(app_key, new_profile);\n\n                        if (loaded_overlay_profile)\n                            ResetOverlays();\n                    }\n                    break;\n                }\n                case configid_str_state_action_data:\n                {\n                    Action new_action;\n                    new_action.Deserialize(copystr);\n\n                    ConfigManager::Get().GetActionManager().StoreAction(new_action);\n                    break;\n                }\n                default: break;\n            }\n        }\n\n        //Restore overlay id override\n        if (overlay_override_id != -1)\n        {\n            OverlayManager::Get().SetCurrentOverlayID(current_overlay_old);\n        }\n\n        return false;\n    }\n\n    bool reset_mirroring = false;\n    IPCMsgID msgid = IPCManager::Get().GetIPCMessageID(msg.message);\n\n    switch (msgid)\n    {\n        case ipcmsg_action:\n        {\n            switch (msg.wParam)\n            {\n                case ipcact_mirror_reset:\n                {\n                    reset_mirroring = true;\n                    break;\n                }\n                case ipcact_overlay_position_reset:\n                {\n                    DetachedTransformReset();\n                    break;\n                }\n                case ipcact_overlay_position_adjust:\n                {\n                    DetachedTransformAdjust(msg.lParam);\n                    break;\n                }\n                case ipcact_action_delete:\n                {\n                    ConfigManager::Get().GetActionManager().RemoveAction(msg.lParam);\n                    break;\n                }\n                case ipcact_action_do:\n                {\n                    ConfigManager::Get().GetActionManager().DoAction(msg.lParam, (overlay_override_id != -1) ? (unsigned int)overlay_override_id : k_ulOverlayID_None);\n                    break;\n                }\n                case ipcact_action_start:\n                {\n                    ConfigManager::Get().GetActionManager().StartAction(msg.lParam, (overlay_override_id != -1) ? (unsigned int)overlay_override_id : k_ulOverlayID_None);\n                    break;\n                }\n                case ipcact_action_stop:\n                {\n                    ConfigManager::Get().GetActionManager().StopAction(msg.lParam, (overlay_override_id != -1) ? (unsigned int)overlay_override_id : k_ulOverlayID_None);\n                    break;\n                }\n                case ipcact_keyboard_vkey:\n                case ipcact_keyboard_wchar:\n                {\n                    HandleKeyboardMessage((IPCActionID)msg.wParam, msg.lParam);\n                    break;\n                }\n                case ipcact_overlay_profile_load:\n                {\n                    reset_mirroring = HandleOverlayProfileLoadMessage(msg.lParam);\n                    break;\n                }\n                case ipcact_crop_to_active_window:\n                {\n                    CropToActiveWindow();\n                    break;\n                }\n                case ipcact_switch_task:\n                {\n                    ShowWindowSwitcher();\n                    break;\n                }\n                case ipcact_overlay_duplicate:\n                {\n                    DuplicateOverlay((unsigned int)msg.lParam);\n                    break;\n                }\n                case ipcact_overlay_new:\n                {\n                    int desktop_id = msg.lParam;\n\n                    OverlayCaptureSource capsource;\n\n                    switch (desktop_id)\n                    {\n                        case -2: capsource = ovrl_capsource_winrt_capture;       break;\n                        case -3: capsource = ovrl_capsource_ui;                  break;\n                        case -4: capsource = ovrl_capsource_browser;             break;\n                        default: capsource = ovrl_capsource_desktop_duplication;\n                    }\n\n                    AddOverlay(capsource, desktop_id, (HWND)ConfigManager::GetValue(configid_handle_state_arg_hwnd));\n                    break;\n                }\n                case ipcact_overlay_new_drag:\n                {\n                    int desktop_id         = GET_X_LPARAM(msg.lParam);\n                    float pointer_distance = GET_Y_LPARAM(msg.lParam) / 100.0f;\n\n                    OverlayCaptureSource capsource;\n\n                    switch (desktop_id)\n                    {\n                        case -2: capsource = ovrl_capsource_winrt_capture;       break;\n                        case -3: capsource = ovrl_capsource_ui;                  break;\n                        case -4: capsource = ovrl_capsource_browser;             break;\n                        default: capsource = ovrl_capsource_desktop_duplication;\n                    }\n\n                    AddOverlayDrag(pointer_distance, capsource, desktop_id, (HWND)ConfigManager::GetValue(configid_handle_state_arg_hwnd));\n                    break;\n                }\n                case ipcact_overlay_remove:\n                {\n                    OverlayManager::Get().RemoveOverlay((unsigned int)msg.lParam);\n                    //RemoveOverlay() may have changed active ID, keep in sync\n                    ConfigManager::SetValue(configid_int_interface_overlay_current_id, OverlayManager::Get().GetCurrentOverlayID());\n                    break;\n                }\n                case ipcact_overlay_transform_sync:\n                {\n                    DetachedTransformSyncAll();\n                    break;\n                }\n                case ipcact_overlay_swap:\n                {\n                    OverlayManager::Get().SwapOverlays(OverlayManager::Get().GetCurrentOverlayID(), (unsigned int)msg.lParam);\n                    break;\n                }\n                case ipcact_overlay_gaze_fade_auto:\n                {\n                    DetachedOverlayGazeFadeAutoConfigure();\n                    break;\n                }\n                case ipcact_overlay_make_standalone:\n                {\n                    OverlayManager::Get().ConvertDuplicatedOverlayToStandalone((unsigned int)msg.lParam);\n                    break;\n                }\n                case ipcact_browser_navigate_to_url:\n                {\n                    unsigned int overlay_id = (unsigned int)msg.lParam;\n                    DPBrowserAPIClient::Get().DPBrowser_SetURL(OverlayManager::Get().GetOverlay(overlay_id).GetHandle(), \n                                                               OverlayManager::Get().GetConfigData(overlay_id).ConfigStr[configid_str_overlay_browser_url]);\n\n                    break;\n                }\n                case ipcact_browser_recreate_context:\n                {\n                    unsigned int overlay_id = (unsigned int)msg.lParam;\n                    DPBrowserAPIClient::Get().DPBrowser_RecreateBrowser(OverlayManager::Get().GetOverlay(overlay_id).GetHandle(), \n                                                                        OverlayManager::Get().GetConfigData(overlay_id).ConfigBool[configid_bool_overlay_browser_allow_transparency]);\n\n                    break;\n                }\n                case ipcact_winmanager_drag_start:\n                {\n                    unsigned int overlay_id = (unsigned int)msg.lParam;\n                    const bool use_pen = ConfigManager::GetValue(configid_bool_input_mouse_simulate_pen_input);\n\n                    if (m_OverlayDragger.GetDragDeviceID() == -1)\n                    {\n                        unsigned int current_overlay_old = OverlayManager::Get().GetCurrentOverlayID();\n                        OverlayManager::Get().SetCurrentOverlayID(overlay_id);\n\n                        //Check if it's still being hovered since it could be off before the message is processed\n                        if (ConfigManager::Get().IsLaserPointerTargetOverlay(OverlayManager::Get().GetCurrentOverlay().GetHandle(), true))\n                        {\n                            //Reset input and WindowManager state manually since the overlay mouse up even will be consumed to finish the drag later\n                            (use_pen) ? m_InputSim.PenSetPrimaryDown(false) : m_InputSim.MouseSetLeftDown(false);\n                            WindowManager::Get().SetTargetWindow(nullptr);\n\n                            if (ConfigManager::GetValue(configid_int_overlay_origin) != ovrl_origin_theater_screen)\n                            {\n                                if (!ConfigManager::GetValue(configid_bool_overlay_transform_locked))\n                                {\n                                    m_OverlayDragger.DragStart(overlay_id);\n                                }\n                                else\n                                {\n                                    IPCManager::Get().PostConfigMessageToUIApp(configid_int_state_drag_hint_device, ConfigManager::Get().GetPrimaryLaserPointerDevice());\n                                    IPCManager::Get().PostConfigMessageToUIApp(configid_int_state_drag_hint_type, 1);\n                                }\n                            }\n                            else\n                            {\n                                IPCManager::Get().PostConfigMessageToUIApp(configid_int_state_drag_hint_device, ConfigManager::Get().GetPrimaryLaserPointerDevice());\n                                IPCManager::Get().PostConfigMessageToUIApp(configid_int_state_drag_hint_type, 2);\n                            }\n                        }\n\n                        OverlayManager::Get().SetCurrentOverlayID(current_overlay_old);\n                    }\n                    else if (overlay_id == k_ulOverlayID_None) //Means it came from a blocked drag, reset input and WindowManager state\n                    {\n                        (use_pen) ? m_InputSim.PenSetPrimaryDown(false) : m_InputSim.MouseSetLeftDown(false);\n                        WindowManager::Get().SetTargetWindow(nullptr);\n                    }\n\n                    break;\n                }\n                case ipcact_winmanager_winlist_add:\n                case ipcact_winmanager_winlist_update:\n                {\n                    const WindowInfo* window_info = nullptr;\n                    bool has_title_changed = true;\n\n                    if (msg.wParam == ipcact_winmanager_winlist_add)\n                        window_info = &WindowManager::Get().WindowListAdd((HWND)msg.lParam);\n                    else\n                        window_info = WindowManager::Get().WindowListUpdateTitle((HWND)msg.lParam, &has_title_changed);\n\n                    //Find inactive overlays using the window and start capture for them\n                    if ( (window_info != nullptr) && (has_title_changed) ) //Only do this when the title changed\n                    {\n                        for (auto& i : OverlayManager::Get().FindInactiveOverlaysForWindow(*window_info))\n                        {\n                            OverlayConfigData& data = OverlayManager::Get().GetConfigData(i);\n\n                            data.ConfigHandle[configid_handle_overlay_state_winrt_hwnd] = msg.lParam;\n\n                            if (ConfigManager::GetValue(configid_int_windows_winrt_capture_lost_behavior) == window_caplost_hide_overlay)\n                                data.ConfigBool[configid_bool_overlay_enabled] = true;\n\n                            OnSetOverlayWinRTCaptureWindow(i);\n\n                            //Send to UI\n                            IPCManager::Get().PostConfigMessageToUIApp(configid_int_state_overlay_current_id_override, (int)i);\n                            IPCManager::Get().PostConfigMessageToUIApp(configid_handle_overlay_state_winrt_hwnd, msg.lParam);\n\n                            if (ConfigManager::GetValue(configid_int_windows_winrt_capture_lost_behavior) == window_caplost_hide_overlay)\n                                IPCManager::Get().PostConfigMessageToUIApp(configid_bool_overlay_enabled, true);\n\n                            IPCManager::Get().PostConfigMessageToUIApp(configid_int_state_overlay_current_id_override, -1);\n                        }\n                    }\n                    break;\n                }\n                case ipcact_winmanager_winlist_remove:\n                {\n                    std::wstring last_title_w = WindowManager::Get().WindowListRemove((HWND)msg.lParam);\n                    std::string last_title = StringConvertFromUTF16(last_title_w.c_str());\n\n                    //Some windows clear their title entirely before ceasing to exist, skip those\n                    if (last_title.empty())\n                        break;\n\n                    //Set last known title for overlays that captured this window\n                    for (unsigned int i = 0; i < OverlayManager::Get().GetOverlayCount(); ++i)\n                    {\n                        OverlayConfigData& data = OverlayManager::Get().GetConfigData(i);\n\n                        if (data.ConfigHandle[configid_handle_overlay_state_winrt_hwnd] == msg.lParam)\n                        {\n                            data.ConfigStr[configid_str_overlay_winrt_last_window_title] = last_title;\n                        }\n                    }\n\n                    break;\n                }\n                case ipcact_winmanager_text_input_focus:\n                {\n                    WindowManager::Get().UpdateTextInputFocusedState(msg.lParam);\n                    break;\n                }\n                case ipcact_sync_config_state:\n                {\n                    //Overlay state\n                    for (unsigned int i = 0; i < OverlayManager::Get().GetOverlayCount(); ++i)\n                    {\n                        const Overlay& overlay        = OverlayManager::Get().GetOverlay(i);\n                        const OverlayConfigData& data = OverlayManager::Get().GetConfigData(i);\n\n                        IPCManager::Get().PostConfigMessageToUIApp(configid_int_state_overlay_current_id_override, (int)i);\n\n                        IPCManager::Get().PostConfigMessageToUIApp(configid_handle_overlay_state_overlay_handle,  data.ConfigHandle[configid_handle_overlay_state_overlay_handle]);\n\n                        IPCManager::Get().PostConfigMessageToUIApp(configid_int_overlay_state_content_width,  data.ConfigInt[configid_int_overlay_state_content_width]);\n                        IPCManager::Get().PostConfigMessageToUIApp(configid_int_overlay_state_content_height, data.ConfigInt[configid_int_overlay_state_content_height]);\n\n                        //Send over current HWND if there's an active capture\n                        if ( (overlay.GetTextureSource() == ovrl_texsource_winrt_capture) && (data.ConfigHandle[configid_handle_overlay_state_winrt_hwnd] != 0))\n                        {\n                            IPCManager::Get().PostConfigMessageToUIApp(configid_handle_overlay_state_winrt_hwnd, data.ConfigHandle[configid_handle_overlay_state_winrt_hwnd]);\n                        }\n                        else if (overlay.GetTextureSource() == ovrl_texsource_browser) //Send browser nav state if it's an active browser overlay\n                        {\n                            IPCManager::Get().PostConfigMessageToUIApp(configid_bool_overlay_state_browser_nav_can_go_back,    data.ConfigBool[configid_bool_overlay_state_browser_nav_can_go_back]);\n                            IPCManager::Get().PostConfigMessageToUIApp(configid_bool_overlay_state_browser_nav_can_go_forward, data.ConfigBool[configid_bool_overlay_state_browser_nav_can_go_forward]);\n                            IPCManager::Get().PostConfigMessageToUIApp(configid_bool_overlay_state_browser_nav_is_loading,     data.ConfigBool[configid_bool_overlay_state_browser_nav_is_loading]);\n                        }\n\n                        IPCManager::Get().PostConfigMessageToUIApp(configid_float_overlay_state_brightness_extra_multiplier, data.ConfigFloat[configid_float_overlay_state_brightness_extra_multiplier]);\n\n                        IPCManager::Get().PostConfigMessageToUIApp(configid_int_state_overlay_current_id_override, -1);\n                    }\n\n                    //Global config state\n                    IPCManager::Get().PostConfigMessageToUIApp(configid_int_state_interface_desktop_count,               ConfigManager::GetValue(configid_int_state_interface_desktop_count));\n                    IPCManager::Get().PostConfigMessageToUIApp(configid_bool_state_overlay_dragmode,                     ConfigManager::GetValue(configid_bool_state_overlay_dragmode));\n                    IPCManager::Get().PostConfigMessageToUIApp(configid_bool_state_overlay_selectmode,                   ConfigManager::GetValue(configid_bool_state_overlay_selectmode));\n                    IPCManager::Get().PostConfigMessageToUIApp(configid_bool_state_overlay_dragselectmode_show_hidden,   ConfigManager::GetValue(configid_bool_state_overlay_dragselectmode_show_hidden));\n                    IPCManager::Get().PostConfigMessageToUIApp(configid_bool_state_overlay_dragmode_temp,                ConfigManager::GetValue(configid_bool_state_overlay_dragmode_temp));\n                    IPCManager::Get().PostConfigMessageToUIApp(configid_bool_state_pen_simulation_supported,             ConfigManager::GetValue(configid_bool_state_pen_simulation_supported));\n                    IPCManager::Get().PostConfigMessageToUIApp(configid_bool_state_window_focused_process_elevated,      ConfigManager::GetValue(configid_bool_state_window_focused_process_elevated));\n                    IPCManager::Get().PostConfigMessageToUIApp(configid_bool_state_misc_process_elevated,                ConfigManager::GetValue(configid_bool_state_misc_process_elevated));\n                    IPCManager::Get().PostConfigMessageToUIApp(configid_bool_state_misc_process_started_by_steam,        ConfigManager::GetValue(configid_bool_state_misc_process_started_by_steam));\n                    IPCManager::Get().PostConfigMessageToUIApp(configid_int_state_dplus_laser_pointer_device,            ConfigManager::GetValue(configid_int_state_dplus_laser_pointer_device));\n                    IPCManager::Get().PostConfigMessageToUIApp(configid_handle_state_dplus_laser_pointer_target_overlay, ConfigManager::GetValue(configid_handle_state_dplus_laser_pointer_target_overlay));\n                    IPCManager::Get().PostConfigMessageToUIApp(configid_handle_state_theater_orig_overlay_handle,        ConfigManager::GetValue(configid_handle_state_theater_orig_overlay_handle));\n\n                    //Sync usually means new UI process, so get new handles\n                    m_LaserPointer.RefreshCachedOverlayHandles();\n                    break;\n                }\n                case ipcact_focus_window:\n                {\n                    WindowManager::Get().RaiseAndFocusWindow((HWND)msg.lParam, &m_InputSim);\n                    break;\n                }\n                case ipcact_keyboard_ovrl_focus_enter:\n                {\n                    //If a WinRT window capture is the focused overlay, check for window auto-focus so it's possible to type things\n                    if (ConfigManager::GetValue(configid_bool_windows_winrt_auto_focus))\n                    {\n                        const bool drag_or_select_mode_enabled = ( (ConfigManager::GetValue(configid_bool_state_overlay_dragmode)) || (ConfigManager::GetValue(configid_bool_state_overlay_selectmode)) );\n\n                        if (!drag_or_select_mode_enabled)\n                        {\n                            int focused_overlay_id = ConfigManager::Get().GetValue(configid_int_state_overlay_focused_id);\n\n                            if (focused_overlay_id != -1)\n                            {\n                                const Overlay& overlay = OverlayManager::Get().GetOverlay((unsigned int)focused_overlay_id);\n                                const OverlayConfigData& data = OverlayManager::Get().GetConfigData((unsigned int)focused_overlay_id);\n\n                                if ((overlay.GetTextureSource() == ovrl_texsource_winrt_capture) && (data.ConfigHandle[configid_handle_overlay_state_winrt_hwnd] != 0))\n                                {\n                                    WindowManager::Get().RaiseAndFocusWindow((HWND)data.ConfigHandle[configid_handle_overlay_state_winrt_hwnd], &m_InputSim);\n                                }\n                            }\n                        }\n                    }\n\n                    break;\n                }\n                case ipcact_keyboard_ovrl_focus_leave:\n                {\n                    //If leaving the keyboard while a WinRT window capture is the focused overlay and the option is enabled, focus the active scene app\n                    if (ConfigManager::GetValue(configid_bool_windows_winrt_auto_focus_scene_app))\n                    {\n                        int focused_overlay_id = ConfigManager::Get().GetValue(configid_int_state_overlay_focused_id);\n\n                        if (focused_overlay_id != -1)\n                        {\n                            const Overlay& overlay = OverlayManager::Get().GetOverlay((unsigned int)focused_overlay_id);\n                            const OverlayConfigData& data = OverlayManager::Get().GetConfigData((unsigned int)focused_overlay_id);\n\n                            if ((overlay.GetTextureSource() == ovrl_texsource_winrt_capture) && (data.ConfigHandle[configid_handle_overlay_state_winrt_hwnd] != 0))\n                            {\n                                WindowManager::Get().FocusActiveVRSceneApp(&m_InputSim);\n                            }\n                        }\n                    }\n\n                    break;\n                }\n                case ipcact_lpointer_trigger_haptics:\n                {\n                    m_LaserPointer.TriggerLaserPointerHaptics((vr::TrackedDeviceIndex_t)msg.lParam);\n                    break;\n                }\n                case ipcact_lpointer_ui_mask_rect:\n                {\n                    if (msg.lParam == -1)\n                    {\n                        m_LaserPointer.UIIntersectionMaskFinish();\n                    }\n                    else\n                    {\n                        DPRect rect;\n                        rect.Unpack16(msg.lParam);\n                        m_LaserPointer.UIIntersectionMaskAddRect(rect);\n                    }\n                    break;\n                }\n                case ipcact_app_profile_remove:\n                {\n                    const bool loaded_overlay_profile = ConfigManager::Get().GetAppProfileManager().RemoveProfile(ConfigManager::GetValue(configid_str_state_app_profile_key));\n\n                    if (loaded_overlay_profile)\n                        ResetOverlays();\n\n                    break;\n                }\n                case ipcact_global_shortcut_set:\n                {\n                    ActionManager::ActionList& global_shortcut_list = ConfigManager::Get().GetGlobalShortcuts();\n                    int shortcut_id = msg.lParam;\n\n                    if ((shortcut_id >= 0) && (shortcut_id < (int)global_shortcut_list.size()))\n                    {\n                        global_shortcut_list[shortcut_id] = ConfigManager::GetValue(configid_handle_state_action_uid);\n                    }\n                    break;\n                }\n                case ipcact_hotkey_set:\n                {\n                    ConfigHotkeyList& hotkey_list = ConfigManager::Get().GetHotkeys();\n                    const std::string& hotkey_data = ConfigManager::GetValue(configid_str_state_hotkey_data);\n                    int hotkey_id = msg.lParam;\n\n                    if ((hotkey_id >= 0) && (hotkey_id < (int)hotkey_list.size()))\n                    {\n                        if (!hotkey_data.empty())\n                        {\n                            hotkey_list[hotkey_id].Deserialize(hotkey_data);\n                        }\n                        else\n                        {\n                            hotkey_list.erase(hotkey_list.begin() + msg.lParam);\n                        }\n                    }\n                    else if (!hotkey_data.empty())\n                    {\n                        ConfigHotkey hotkey_new;\n                        hotkey_new.Deserialize(hotkey_data);\n\n                        hotkey_list.push_back(hotkey_new);\n                    }\n\n                    RegisterHotkeys();\n                    break;\n                }\n            }\n            break;\n        }\n        case ipcmsg_set_config:\n        {\n            if (msg.wParam < configid_bool_MAX)\n            {\n                ConfigID_Bool bool_id = (ConfigID_Bool)msg.wParam;\n\n                bool previous_value = ConfigManager::GetValue(bool_id);\n                ConfigManager::SetValue(bool_id, msg.lParam);\n\n                switch (bool_id)\n                {\n                    case configid_bool_overlay_3D_enabled:\n                    {\n                        ApplySettingTransform();\n                        ApplySetting3DMode();\n                        break;\n                    }\n                    case configid_bool_overlay_3D_swapped:\n                    {\n                        ApplySetting3DMode();\n                        break;\n                    }\n                    case configid_bool_overlay_origin_hmd_floor_use_turning:\n                    {\n                        const OverlayConfigData& data = OverlayManager::Get().GetCurrentConfigData();\n                        OverlayOriginConfig origin_config = OverlayManager::Get().GetOriginConfigFromData(data);\n                        OverlayOriginConfig origin_config_prev = origin_config;\n                        origin_config_prev.HMDFloorUseTurning = previous_value;\n\n                        OverlayOrigin origin = (OverlayOrigin)data.ConfigInt[configid_int_overlay_origin];\n\n                        DetachedTransformConvertOrigin(OverlayManager::Get().GetCurrentOverlayID(), origin, origin, origin_config_prev, origin_config);\n                        ApplySettingTransform();\n                        break;\n                    }\n                    case configid_bool_overlay_enabled:\n                    case configid_bool_overlay_show_backside:\n                    case configid_bool_overlay_gazefade_enabled:\n                    case configid_bool_overlay_update_invisible:\n                    {\n                        ApplySettingTransform();\n                        break;\n                    }\n                    case configid_bool_overlay_crop_enabled:\n                    {\n                        ApplySettingCrop();\n                        ApplySettingTransform();\n                        ApplySettingMouseScale();\n                        break;\n                    }\n                    case configid_bool_overlay_winrt_window_matching_strict:\n                    {\n                        OverlayConfigData& data = OverlayManager::Get().GetCurrentConfigData();\n\n                        //Check if new matching setting finds an existing window\n                        if ((OverlayManager::Get().GetCurrentOverlay().GetTextureSource() == ovrl_texsource_winrt_capture) && (data.ConfigHandle[configid_handle_overlay_state_winrt_hwnd] != 0))\n                            break;\n\n                        HWND window = WindowInfo::FindClosestWindowForTitle(data.ConfigStr[configid_str_overlay_winrt_last_window_title], data.ConfigStr[configid_str_overlay_winrt_last_window_class_name],\n                                                                            data.ConfigStr[configid_str_overlay_winrt_last_window_exe_name], WindowManager::Get().WindowListGet(),\n                                                                            data.ConfigBool[configid_bool_overlay_winrt_window_matching_strict]);\n\n                        if (window != nullptr)\n                        {\n                            data.ConfigHandle[configid_handle_overlay_state_winrt_hwnd] = (uint64_t)window;\n\n                            if (ConfigManager::GetValue(configid_int_windows_winrt_capture_lost_behavior) == window_caplost_hide_overlay)\n                            {\n                                data.ConfigBool[configid_bool_overlay_enabled] = true;\n                            }\n\n                            OnSetOverlayWinRTCaptureWindow(OverlayManager::Get().GetCurrentOverlayID());\n\n                            //Send to UI\n                            IPCManager::Get().PostConfigMessageToUIApp(configid_handle_overlay_state_winrt_hwnd, (LPARAM)window);\n\n                            if (ConfigManager::GetValue(configid_int_windows_winrt_capture_lost_behavior) == window_caplost_hide_overlay)\n                            {\n                                IPCManager::Get().PostConfigMessageToUIApp(configid_bool_overlay_enabled, true);\n                            }\n                        }\n                        break;\n                    }\n                    case configid_bool_overlay_input_enabled:\n                    case configid_bool_input_mouse_render_intersection_blob:\n                    {\n                        ApplySettingMouseInput();\n                        break;\n                    }\n                    case configid_bool_input_mouse_allow_pointer_override:\n                    {\n                        //Reset Pointer override\n                        if (m_MouseIgnoreMoveEvent)\n                        {\n                            m_MouseIgnoreMoveEvent = false;\n\n                            ResetMouseLastLaserPointerPos();\n                            ApplySettingMouseInput();\n                        }\n                        break;\n                    }\n                    case configid_bool_interface_dim_ui:\n                    {\n                        DimDashboard( ((m_OvrlDashboardActive) && (msg.lParam)) );\n                        break;\n                    }\n                    case configid_bool_performance_single_desktop_mirroring:\n                    {\n                        if (msg.lParam) //Unify the desktop IDs when turning the setting on\n                        {\n                            CropToDisplay(OverlayManager::Get().GetConfigData(0).ConfigInt[configid_int_overlay_desktop_id], true);\n                        }\n\n                        reset_mirroring = true;\n                        break;\n                    }\n                    case configid_bool_performance_hdr_mirroring:\n                    {\n                        reset_mirroring = true;\n                        DPWinRT_SetHDREnabled(msg.lParam);\n                        break;\n                    }\n                    case configid_bool_performance_alternative_cursor_rendering:\n                    {\n                        m_MouseCursorNeedsUpdate = true;\n                        break;\n                    }\n                    case configid_bool_input_mouse_render_cursor:\n                    {\n                        m_OutputPendingFullRefresh = true;\n\n                        if (DPWinRT_IsCaptureCursorEnabledPropertySupported())\n                            DPWinRT_SetCaptureCursorEnabled(msg.lParam);\n\n                        break;\n                    }\n                    case configid_bool_input_mouse_scroll_smooth:\n                    {\n                        ApplySettingMouseInput();\n                        break;\n                    }\n                    case configid_bool_input_laser_pointer_block_input:\n                    {\n                        //Set SteamVR setting to allow global overlay input as we need it for this to work. Messing with user settings is not ideal, but we're not the first to do so\n                        if ( (msg.lParam) && (!vr::VRSettings()->GetBool(vr::k_pch_SteamVR_Section, vr::k_pch_SteamVR_AllowGlobalActionSetPriority)) )\n                        {\n                            vr::VRSettings()->SetBool(vr::k_pch_SteamVR_Section, vr::k_pch_SteamVR_AllowGlobalActionSetPriority, true);\n                        }\n                        break;\n                    }\n                    case configid_bool_input_laser_pointer_hmd_device:\n                    {\n                        //Laser pointer keyboard input hotkeys should be disabled if this setting is off\n                        RegisterHotkeys();\n                        break;\n                    }\n                    case configid_bool_windows_winrt_keep_on_screen:\n                    {\n                        WindowManager::Get().UpdateConfigState();\n                        break;\n                    }\n                    case configid_bool_browser_content_blocker:\n                    {\n                        DPBrowserAPIClient::Get().DPBrowser_ContentBlockSetEnabled(msg.lParam);\n                        break;\n                    }\n                    case configid_bool_state_overlay_dragmode:\n                    {\n                        //Update temporary standing position if dragmode has been activated and dashboard tab isn't active\n                        if ((msg.lParam) && (!m_OvrlDashboardActive))\n                        {\n                            m_OverlayDragger.UpdateTempStandingPosition();\n                        }\n\n                        ApplySettingInputMode();\n                        break;\n                    }\n                    case configid_bool_state_overlay_selectmode:\n                    {\n                        ApplySettingInputMode();\n                        break;\n                    }\n                    case configid_bool_state_misc_elevated_mode_active:\n                    {\n                        m_InputSim.SetElevatedModeForwardingActive(msg.lParam);\n                        break;\n                    }\n                    default: break;\n                }\n            }\n            else if (msg.wParam < configid_bool_MAX + configid_int_MAX)\n            {\n                ConfigID_Int int_id = (ConfigID_Int)(msg.wParam - configid_bool_MAX);\n\n                int previous_value = ConfigManager::GetValue(int_id);\n                ConfigManager::SetValue(int_id, msg.lParam);\n\n                switch (int_id)\n                {\n                    case configid_int_interface_overlay_current_id:\n                    {\n                        OverlayManager::Get().SetCurrentOverlayID(msg.lParam);\n                        current_overlay_old = (unsigned int)msg.lParam;\n                        break;\n                    }\n                    case configid_int_interface_background_color:\n                    case configid_int_interface_background_color_display_mode:\n                    {\n                        m_BackgroundOverlay.Update();\n                        break;\n                    }\n                    case configid_int_overlay_desktop_id:\n                    {\n                        if (ConfigManager::GetValue(configid_bool_overlay_crop_enabled))\n                        {\n                            CropToDisplay(msg.lParam);\n                        }\n                        else //Don't touch cropping setting values if it's disabled and just update the validated crop rect instead\n                        {\n                            OverlayManager::Get().GetCurrentOverlay().UpdateValidatedCropRect();        \n                            ApplySettingCrop();\n                            ApplySettingTransform();\n                            ApplySettingMouseScale();\n                            ApplySettingExtraBrightness();\n                        }\n\n                        reset_mirroring = (ConfigManager::GetValue(configid_bool_performance_single_desktop_mirroring) && (msg.lParam != previous_value));\n                        break;\n                    }\n                    case configid_int_overlay_capture_source:\n                    {\n                        ResetOverlayActiveCount();\n                        ResetCurrentOverlay();\n                        break;\n                    }\n                    case configid_int_overlay_winrt_desktop_id:\n                    {\n                        if (previous_value != msg.lParam)\n                        {\n                            OverlayManager::Get().GetCurrentOverlay().SetTextureSource(ovrl_texsource_none);\n                            ResetCurrentOverlay();\n                        }\n                        break;\n                    }\n                    case configid_int_overlay_user_width:\n                    case configid_int_overlay_user_height:\n                    {\n                        if (OverlayManager::Get().GetCurrentOverlay().GetTextureSource() == ovrl_texsource_browser)\n                        {\n                            const int user_width  = ConfigManager::GetValue(configid_int_overlay_user_width);\n                            const int user_height = ConfigManager::GetValue(configid_int_overlay_user_height);\n\n                            DPBrowserAPIClient::Get().DPBrowser_SetResolution(OverlayManager::Get().GetCurrentOverlay().GetHandle(), user_width, user_height);\n\n                            //Also set as content width\n                            ConfigManager::SetValue(configid_int_overlay_state_content_width,  user_width);\n                            ConfigManager::SetValue(configid_int_overlay_state_content_height, user_height);\n\n                            //Update crop as it depends on user size\n                            if ((ConfigManager::GetValue(configid_bool_overlay_crop_enabled)) || (ConfigManager::GetValue(configid_bool_overlay_3D_enabled)))\n                            {\n                                ApplySettingCrop();\n                            }\n\n                            //Send to UI\n                            IPCManager::Get().PostConfigMessageToUIApp(configid_int_state_overlay_current_id_override, (int)OverlayManager::Get().GetCurrentOverlayID());\n                            IPCManager::Get().PostConfigMessageToUIApp(configid_int_overlay_state_content_width,  user_width);\n                            IPCManager::Get().PostConfigMessageToUIApp(configid_int_overlay_state_content_height, user_height);\n                            IPCManager::Get().PostConfigMessageToUIApp(configid_int_state_overlay_current_id_override, -1);\n\n                            //Also do it for everything using this as duplication source\n                            unsigned int current_overlay_old = OverlayManager::Get().GetCurrentOverlayID();\n                            for (unsigned int overlay_id : OverlayManager::Get().FindDuplicatedOverlaysForOverlay(OverlayManager::Get().GetCurrentOverlayID()))\n                            {\n                                OverlayManager::Get().SetCurrentOverlayID(overlay_id);\n\n                                //Set config values for duplicated overlays as well so code only reading from it doesn't have to care about them being duplicated\n                                ConfigManager::SetValue(configid_int_overlay_user_width,           user_width);\n                                ConfigManager::SetValue(configid_int_overlay_user_height,          user_height);\n                                ConfigManager::SetValue(configid_int_overlay_state_content_width,  user_width);\n                                ConfigManager::SetValue(configid_int_overlay_state_content_height, user_height);\n\n                                IPCManager::Get().PostConfigMessageToUIApp(configid_int_state_overlay_current_id_override, (int)overlay_id);\n                                IPCManager::Get().PostConfigMessageToUIApp(configid_int_overlay_state_content_width,  user_width);\n                                IPCManager::Get().PostConfigMessageToUIApp(configid_int_overlay_state_content_height, user_height);\n                                IPCManager::Get().PostConfigMessageToUIApp(configid_int_state_overlay_current_id_override, -1);\n\n                                if (ConfigManager::GetValue(configid_bool_overlay_crop_enabled))\n                                {\n                                    ApplySettingCrop();\n                                }\n                            }\n                            OverlayManager::Get().SetCurrentOverlayID(current_overlay_old);\n\n                            ApplySettingMouseInput();\n                        }\n                        break;\n                    }\n                    case configid_int_overlay_crop_x:\n                    case configid_int_overlay_crop_y:\n                    case configid_int_overlay_crop_width:\n                    case configid_int_overlay_crop_height:\n                    {\n                        ApplySettingCrop();\n                        ApplySettingTransform();\n                        ApplySettingMouseScale();\n                        break;\n                    }\n                    case configid_int_overlay_3D_mode:\n                    {\n                        ApplySettingTransform();\n                        ApplySetting3DMode();\n                        break;\n                    }\n                    case configid_int_overlay_display_mode:\n                    {\n                        ApplySettingTransform();\n                        break;\n                    }\n                    case configid_int_overlay_origin:\n                    {\n                        DetachedTransformConvertOrigin(OverlayManager::Get().GetCurrentOverlayID(), (OverlayOrigin)previous_value, (OverlayOrigin)msg.lParam);\n                        ApplySettingTransform();\n                        break;\n                    }\n                    case configid_int_overlay_origin_smoothing_level:\n                    {\n                        //Reset smoothers if there was previously no smoothing enabled\n                        if (previous_value == 0)\n                        {\n                            Overlay& overlay = OverlayManager::Get().GetCurrentOverlay();\n                            overlay.GetSmootherPos().ResetLastPos();\n                            overlay.GetSmootherRot().ResetLastPos();\n                        }\n\n                        ApplySettingTransform();\n                        break;\n                    }\n                    case configid_int_overlay_browser_max_fps_override:\n                    {\n                        if (OverlayManager::Get().GetCurrentOverlay().GetTextureSource() == ovrl_texsource_browser)\n                        {\n                            DPBrowserAPIClient::Get().DPBrowser_SetFPS(OverlayManager::Get().GetCurrentOverlay().GetHandle(), msg.lParam);\n                        }\n                        break;\n                    }\n                    case configid_int_interface_wmr_ignore_vscreens:\n                    {\n                        DPWinRT_SetDesktopEnumerationFlags((msg.lParam == 1));\n                        //May affect desktop enumeration, reset mirroring\n                        reset_mirroring = true;\n                        break;\n                    }\n                    case configid_int_input_mouse_dbl_click_assist_duration_ms:\n                    {\n                        ApplySettingMouseInput();\n                        break;\n                    }\n                    case configid_int_input_laser_pointer_hmd_device_keycode_toggle:\n                    case configid_int_input_laser_pointer_hmd_device_keycode_left:\n                    case configid_int_input_laser_pointer_hmd_device_keycode_right:\n                    case configid_int_input_laser_pointer_hmd_device_keycode_middle:\n                    {\n                        RegisterHotkeys();\n                        break;\n                    }\n                    case configid_int_windows_winrt_dragging_mode:\n                    {\n                        WindowManager::Get().UpdateConfigState();\n                        break;\n                    }\n                    case configid_int_performance_update_limit_mode:\n                    case configid_int_performance_update_limit_fps:\n                    case configid_int_overlay_update_limit_override_mode:\n                    case configid_int_overlay_update_limit_override_fps:\n                    {\n                        ApplySettingUpdateLimiter();\n                        break;\n                    }\n                    case configid_int_browser_max_fps:\n                    {\n                        DPBrowserAPIClient::Get().DPBrowser_GlobalSetFPS(msg.lParam);\n                        break;\n                    }\n                    default: break;\n                }\n            }\n            else if (msg.wParam < configid_bool_MAX + configid_int_MAX + configid_float_MAX)\n            {\n                ConfigID_Float float_id = (ConfigID_Float)(msg.wParam - configid_bool_MAX - configid_int_MAX);\n\n                float value = pun_cast<float, LPARAM>(msg.lParam);\n                float previous_value = ConfigManager::GetValue(float_id);\n                ConfigManager::SetValue(float_id, value);\n\n                switch (float_id)\n                {\n                    case configid_float_overlay_width:\n                    case configid_float_overlay_curvature:\n                    case configid_float_overlay_opacity:\n                    case configid_float_overlay_brightness:\n                    case configid_float_overlay_offset_right:\n                    case configid_float_overlay_offset_up:\n                    case configid_float_overlay_offset_forward:\n                    {\n                        ApplySettingTransform();\n                        break;\n                    }\n                    case configid_float_overlay_browser_zoom:\n                    {\n                        if (OverlayManager::Get().GetCurrentOverlay().GetTextureSource() == ovrl_texsource_browser)\n                        {\n                            DPBrowserAPIClient::Get().DPBrowser_SetZoomLevel(OverlayManager::Get().GetCurrentOverlay().GetHandle(), value);\n                        }\n                        break;\n                    }\n                    case configid_float_performance_update_limit_ms:\n                    case configid_float_overlay_update_limit_override_ms:\n                    {\n                        ApplySettingUpdateLimiter();\n                        break;\n                    }\n                    default: break;\n                }\n\n            }\n            else if (msg.wParam < configid_bool_MAX + configid_int_MAX + configid_float_MAX + configid_handle_MAX)\n            {\n                ConfigID_Handle handle_id = (ConfigID_Handle)(msg.wParam - configid_bool_MAX - configid_int_MAX - configid_float_MAX);\n\n                uint64_t value = pun_cast<uint64_t, LPARAM>(msg.lParam);\n                uint64_t previous_value = ConfigManager::GetValue(handle_id);\n                ConfigManager::SetValue(handle_id, value);\n\n                switch (handle_id)\n                {\n                    case configid_handle_overlay_state_winrt_hwnd:\n                    {\n                        if (value != previous_value)\n                        {\n                            OnSetOverlayWinRTCaptureWindow(OverlayManager::Get().GetCurrentOverlayID());\n                        }\n                        break;\n                    }\n                }\n            }\n\n            break;\n        }\n    }\n\n    //Restore overlay id override\n    if (overlay_override_id != -1)\n    {\n        OverlayManager::Get().SetCurrentOverlayID(current_overlay_old);\n    }\n\n    return reset_mirroring;\n}\n\nvoid OutputManager::HandleWinRTMessage(const MSG& msg)\n{\n    switch (msg.message)\n    {\n        case WM_DPLUSWINRT_SIZE:\n        {\n            const unsigned int overlay_id = OverlayManager::Get().FindOverlayID(msg.wParam);\n\n            if (overlay_id == k_ulOverlayID_None)\n            {\n                break;\n            }\n\n            const int content_width  = GET_X_LPARAM(msg.lParam);\n            const int content_height = GET_Y_LPARAM(msg.lParam);\n\n            const Overlay& overlay  = OverlayManager::Get().GetOverlay(overlay_id);\n            OverlayConfigData& data = OverlayManager::Get().GetConfigData(overlay_id);\n\n            //Skip if no real change\n            if ((data.ConfigInt[configid_int_overlay_state_content_width] == content_width) && (data.ConfigInt[configid_int_overlay_state_content_height] == content_height))\n            {\n                break;\n            }\n\n            //Adaptive Size\n            bool adaptive_size_apply = ( (ConfigManager::GetValue(configid_bool_windows_winrt_auto_size_overlay)) && (overlay.GetTextureSource() == ovrl_texsource_winrt_capture) && \n                                         (data.ConfigHandle[configid_handle_overlay_state_winrt_hwnd] != 0) && (data.ConfigInt[configid_int_overlay_state_content_width] != -1) && \n                                         (content_width != -1) );\n\n            if (adaptive_size_apply)\n            {\n                data.ConfigFloat[configid_float_overlay_width] *= (float)content_width / data.ConfigInt[configid_int_overlay_state_content_width];\n            }\n\n            data.ConfigInt[configid_int_overlay_state_content_width]  = content_width;\n            data.ConfigInt[configid_int_overlay_state_content_height] = content_height;\n\n            //Send update to UI\n            IPCManager::Get().PostConfigMessageToUIApp(configid_int_state_overlay_current_id_override, (int)overlay_id);\n\n            IPCManager::Get().PostConfigMessageToUIApp(configid_int_overlay_state_content_width,  content_width);\n            IPCManager::Get().PostConfigMessageToUIApp(configid_int_overlay_state_content_height, content_height);\n\n            if (adaptive_size_apply)\n            {\n                IPCManager::Get().PostConfigMessageToUIApp(configid_float_overlay_width, data.ConfigFloat[configid_float_overlay_width]);\n            }\n\n            IPCManager::Get().PostConfigMessageToUIApp(configid_int_state_overlay_current_id_override, -1);\n\n            //Apply change to overlay\n            unsigned int current_overlay_old = OverlayManager::Get().GetCurrentOverlayID();\n            OverlayManager::Get().SetCurrentOverlayID(overlay_id);\n            ApplySettingCrop();\n            ApplySettingTransform();\n            ApplySettingMouseScale();\n            OverlayManager::Get().SetCurrentOverlayID(current_overlay_old);\n\n            break;\n        }\n        case WM_DPLUSWINRT_CAPTURE_LOST:\n        {\n            const unsigned int overlay_id = OverlayManager::Get().FindOverlayID(msg.wParam);\n\n            if (overlay_id == k_ulOverlayID_None)\n            {\n                break;\n            }\n\n            Overlay& overlay = OverlayManager::Get().GetOverlay(overlay_id);\n            OverlayConfigData& data = OverlayManager::Get().GetConfigData(overlay_id);\n\n            //Hide affected overlay if setting enabled\n            if ( (data.ConfigBool[configid_bool_overlay_enabled]) && (ConfigManager::GetValue(configid_int_windows_winrt_capture_lost_behavior) == window_caplost_hide_overlay) )\n            {\n                unsigned int current_overlay_old = OverlayManager::Get().GetCurrentOverlayID();\n                OverlayManager::Get().SetCurrentOverlayID(overlay_id);\n                data.ConfigBool[configid_bool_overlay_enabled] = false;\n                ApplySettingTransform();\n                OverlayManager::Get().SetCurrentOverlayID(current_overlay_old);\n\n                //Send to UI\n                IPCManager::Get().PostConfigMessageToUIApp(configid_int_state_overlay_current_id_override, (int)overlay_id);\n                IPCManager::Get().PostConfigMessageToUIApp(configid_bool_overlay_enabled, false);\n                IPCManager::Get().PostConfigMessageToUIApp(configid_int_state_overlay_current_id_override, -1);\n            }\n            else if (ConfigManager::GetValue(configid_int_windows_winrt_capture_lost_behavior) == window_caplost_remove_overlay) //Or remove it\n            {\n                //Queue up removal instead of doing it right away in case there are multiple overlays with the same target lost at once (which breaks otherwise)\n                m_RemoveOverlayQueue.push_back(overlay_id);\n                break;\n            }\n\n            //Only change texture source if the overlay is still a winrt capture (this can be false when a picker gets canceled late)\n            if ( (data.ConfigInt[configid_int_overlay_capture_source] == ovrl_capsource_winrt_capture) || (overlay.GetTextureSource() == ovrl_texsource_winrt_capture) )\n            {\n                overlay.SetTextureSource(ovrl_texsource_none);\n            }\n\n            break;\n        }\n        case WM_DPLUSWINRT_THREAD_ERROR:\n        {\n            //We get capture lost messages for each affected overlay, so just forward the error to the UI so a warning can be displayed for now\n            IPCManager::Get().PostMessageToUIApp(ipcmsg_action, ipcact_winrt_thread_error, msg.lParam);\n\n            LOG_F(ERROR, \"Unexpected error occurred in a Graphics Capture thread! (%#x)\", (unsigned int)msg.lParam);\n            break;\n        }\n        case WM_DPLUSWINRT_FPS:\n        {\n            const unsigned int overlay_id = OverlayManager::Get().FindOverlayID(msg.wParam);\n\n            if (overlay_id == k_ulOverlayID_None)\n            {\n                break;\n            }\n\n            OverlayConfigData& data = OverlayManager::Get().GetConfigData(overlay_id);\n            data.ConfigInt[configid_int_overlay_state_fps] = msg.lParam;\n\n            //Send update to UI\n            IPCManager::Get().PostConfigMessageToUIApp(configid_int_state_overlay_current_id_override, (int)overlay_id);\n            IPCManager::Get().PostConfigMessageToUIApp(configid_int_overlay_state_fps, msg.lParam);\n            IPCManager::Get().PostConfigMessageToUIApp(configid_int_state_overlay_current_id_override, -1);\n\n            break;\n        }\n    }\n}\n\nvoid OutputManager::HandleHotkeyMessage(const MSG& msg)\n{\n    ConfigHotkeyList& hotkey_list = ConfigManager::Get().GetHotkeys();\n    int hotkey_id = msg.wParam;\n\n    if ((hotkey_id >= 0) && (hotkey_id < (int)hotkey_list.size()))\n    {\n        ConfigHotkey& hotkey = hotkey_list[hotkey_id];\n\n        //StateIsDown blocks HandleHotkeys() and the hotkey messages from triggering hotkey actions twice. It's reset in HandleHotkeys() when the key is no longer pressed\n        if (!hotkey.StateIsDown)\n        {\n            ConfigManager::Get().GetActionManager().DoAction(hotkey.ActionUID);\n\n            hotkey.StateIsDown = true;\n        }\n    }\n}\n\nvoid OutputManager::OnExit()\n{\n    //Release all held keyboard keys. Might be going overboard but better than keeping keys down unexpectedly\n    for (int i = 0; i < 256; ++i) \n    {\n        m_InputSim.KeyboardSetKeyState((IPCKeyboardKeystateFlags)0, i);\n    }\n\n    //Undo dimmed dashboard\n    DimDashboard(false);\n\n    //Shutdown VR for good\n    vr::VR_Shutdown();\n}\n\nHWND OutputManager::GetWindowHandle()\n{\n    return m_WindowHandle;\n}\n\n//\n// Returns shared handle\n//\nHANDLE OutputManager::GetSharedHandle()\n{\n    HANDLE Hnd = nullptr;\n\n    // QI IDXGIResource interface to synchronized shared surface.\n    IDXGIResource* DXGIResource = nullptr;\n    HRESULT hr = m_SharedSurf->QueryInterface(__uuidof(IDXGIResource), reinterpret_cast<void**>(&DXGIResource));\n    if (SUCCEEDED(hr))\n    {\n        // Obtain handle to IDXGIResource object.\n        DXGIResource->GetSharedHandle(&Hnd);\n        DXGIResource->Release();\n        DXGIResource = nullptr;\n    }\n\n    return Hnd;\n}\n\nIDXGIAdapter* OutputManager::GetDXGIAdapter()\n{\n    HRESULT hr;\n\n    // Get DXGI factory\n    IDXGIDevice* DxgiDevice = nullptr;\n    hr = m_Device->QueryInterface(__uuidof(IDXGIDevice), reinterpret_cast<void**>(&DxgiDevice));\n    if (FAILED(hr))\n    {\n        return nullptr;\n    }\n\n    IDXGIAdapter* DxgiAdapter = nullptr;\n    hr = DxgiDevice->GetParent(__uuidof(IDXGIAdapter), reinterpret_cast<void**>(&DxgiAdapter));\n    DxgiDevice->Release();\n    DxgiDevice = nullptr;\n    if (FAILED(hr))\n    {\n        return nullptr;\n    }\n\n    return DxgiAdapter;\n}\n\nvoid OutputManager::ResetOverlays()\n{\n    //Check if process is elevated and send that info to the UI too (DPBrowser needs this info so do this first)\n    bool elevated = IsProcessElevated();\n    ConfigManager::SetValue(configid_bool_state_misc_process_elevated, elevated);\n    IPCManager::Get().PostConfigMessageToUIApp(configid_bool_state_misc_process_elevated, elevated);\n\n    //Reset all overlays\n    unsigned int current_overlay_old = OverlayManager::Get().GetCurrentOverlayID();\n    for (unsigned int i = 0; i < OverlayManager::Get().GetOverlayCount(); ++i)\n    {\n        OverlayManager::Get().SetCurrentOverlayID(i);\n\n        ApplySettingCrop();\n        ApplySettingTransform();\n        ApplySettingCaptureSource();\n        ApplySetting3DMode();\n    }\n\n    //Second pass for browser overlays using a duplication ID that is higher than the overlay's\n    for (unsigned int i = 0; i < OverlayManager::Get().GetOverlayCount(); ++i)\n    {\n        OverlayManager::Get().SetCurrentOverlayID(i);\n        const OverlayConfigData& data = OverlayManager::Get().GetCurrentConfigData();\n\n        if ( (data.ConfigInt[configid_int_overlay_capture_source] == ovrl_capsource_browser) && (data.ConfigInt[configid_int_overlay_duplication_id] != -1) )\n        {\n            ApplySettingCrop();\n            ApplySettingCaptureSource();\n            ApplySetting3DMode();\n        }\n    }\n\n    OverlayManager::Get().SetCurrentOverlayID(current_overlay_old);\n\n    //These apply to all overlays within the function itself\n    ApplySettingInputMode();\n    ApplySettingUpdateLimiter();\n\n    ResetOverlayActiveCount();\n\n    //Post overlays reset message to UI app\n    IPCManager::Get().PostMessageToUIApp(ipcmsg_action, ipcact_overlays_reset);\n\n    //Make sure that the entire overlay texture gets at least one full update for regions that will never be dirty (i.e. blank space not occupied by any desktop)\n    m_OutputPendingFullRefresh = true;\n}\n\nvoid OutputManager::ResetCurrentOverlay()\n{\n    if ((OverlayManager::Get().GetCurrentOverlayID() == k_ulOverlayID_None) || (OverlayManager::Get().GetCurrentOverlay().GetID() == k_ulOverlayID_None))\n        return;\n\n    ApplySettingCrop();\n    ApplySettingTransform();\n    ApplySettingCaptureSource();\n    ApplySettingInputMode();\n    ApplySetting3DMode();\n\n    ApplySettingUpdateLimiter();\n\n    //Make sure that the entire overlay texture gets at least one full update for regions that will never be dirty (i.e. blank space not occupied by any desktop)\n    if (ConfigManager::GetValue(configid_int_overlay_capture_source) == ovrl_capsource_desktop_duplication)\n    {\n        m_OutputPendingFullRefresh = true;\n    }\n}\n\nID3D11Texture2D* OutputManager::GetOverlayTexture() const\n{\n    return m_OvrlTex;\n}\n\nID3D11Texture2D* OutputManager::GetMultiGPUTargetTexture() const\n{\n    return m_MultiGPUTexTarget;\n}\n\nvr::VROverlayHandle_t OutputManager::GetDesktopTextureOverlay() const\n{\n    return m_OvrlHandleDesktopTexture;\n}\n\nbool OutputManager::GetOverlayActive() const\n{\n    return (m_OvrlActiveCount != 0);\n}\n\nbool OutputManager::GetOverlayInputActive() const\n{\n    return m_OvrlInputActive;\n}\n\nDWORD OutputManager::GetMaxRefreshDelay() const\n{\n    if ( (m_OvrlActiveCount != 0) || (m_OvrlDashboardActive) || (m_LaserPointer.IsActive()) )\n    {\n        //Actually causes extreme load while not really being necessary (looks nice tho)\n        if ( (m_OvrlInputActive) && (ConfigManager::GetValue(configid_bool_performance_rapid_laser_pointer_updates)) )\n        {\n            return 0;\n        }\n        else if (m_LaserPointer.IsScrolling())\n        {\n            //While scrolling with the Desktop+ Laser Pointer, we actually need to update frequently to keep scroll speeds and generated haptic feedback at the usual pace\n            return 3;\n        }\n        else if (m_OvrlInputActive)\n        {\n            //While input is active, especially with the HMD pointer, we need to update more frequently to allow for smooth cursor movements\n            return m_MaxActiveRefreshDelay / 2;\n        }\n        else\n        {\n            return m_MaxActiveRefreshDelay;\n        }\n    }\n    else if ( (m_VRInput.IsAnyGlobalActionBound()) || (IsAnyOverlayUsingGazeFade()) || (m_IsAnyHotkeyActive) )\n    {\n        return m_MaxActiveRefreshDelay * 2;\n    }\n    else\n    {\n        return 300;\n    }\n}\n\nfloat OutputManager::GetHMDFrameRate() const\n{\n    return vr::VRSystem()->GetFloatTrackedDeviceProperty(vr::k_unTrackedDeviceIndex_Hmd, vr::Prop_DisplayFrequency_Float);\n}\n\nint OutputManager::GetDesktopWidth() const\n{\n    return m_DesktopWidth;\n}\n\nint OutputManager::GetDesktopHeight() const\n{\n    return m_DesktopHeight;\n}\n\nconst std::vector<DPRect>& OutputManager::GetDesktopRects() const\n{\n    return m_DesktopRects;\n}\n\nfloat OutputManager::GetDesktopHDRWhiteLevelAdjustment(int desktop_id, bool is_for_graphics_capture, bool wmr_ignore_vscreens) const\n{\n    #ifdef DPLUS_DUP_NO_HDR\n        return 1.0f;\n    #else\n\n    if (desktop_id == -1)\n        desktop_id = 0;\n\n    if ((!m_OutputHDRAvailable) && (!is_for_graphics_capture))\n        return 1.0f;\n\n    DXGI_OUTPUT_DESC output_desc = {};\n    Microsoft::WRL::ComPtr<IDXGIFactory1> factory_ptr;\n\n    //This needs to go through DXGI as QueryDisplayConfig()'s order can be different\n    HRESULT hr = CreateDXGIFactory1(__uuidof(IDXGIFactory1), (void**)&factory_ptr);\n    if (!FAILED(hr))\n    {\n        Microsoft::WRL::ComPtr<IDXGIAdapter> adapter_ptr;\n        UINT i = 0;\n        int output_count = 0;\n\n        while (factory_ptr->EnumAdapters(i, &adapter_ptr) != DXGI_ERROR_NOT_FOUND)\n        {\n            //Check if this a WMR virtual display adapter and skip it when the option is enabled\n            if (wmr_ignore_vscreens)\n            {\n                DXGI_ADAPTER_DESC adapter_desc;\n                adapter_ptr->GetDesc(&adapter_desc);\n\n                if (wcscmp(adapter_desc.Description, L\"Virtual Display Adapter\") == 0)\n                {\n                    ++i;\n                    continue;\n                }\n            }\n\n            //Enum the available outputs\n            Microsoft::WRL::ComPtr<IDXGIOutput> output_ptr;\n            UINT output_index = 0;\n            while (adapter_ptr->EnumOutputs(output_index, &output_ptr) != DXGI_ERROR_NOT_FOUND)\n            {\n                //Check if this happens to be the output we're looking for\n                if (desktop_id == output_count)\n                {\n                    //Get output desc\n                    output_ptr->GetDesc(&output_desc);\n                }\n\n                ++output_index;\n                ++output_count;\n            }\n\n            ++i;\n        }\n    }\n\n    //Find display config with the same device path\n    std::vector<DISPLAYCONFIG_PATH_INFO> paths;\n    std::vector<DISPLAYCONFIG_MODE_INFO> modes;\n    const UINT32 flags = QDC_ONLY_ACTIVE_PATHS | QDC_VIRTUAL_MODE_AWARE;\n    LONG result = ERROR_SUCCESS;\n\n    //Loop until buffer allocation for paths match the requirements\n    do\n    {\n        UINT32 path_count, mode_count;\n        result = ::GetDisplayConfigBufferSizes(flags, &path_count, &mode_count);\n\n        if (result != ERROR_SUCCESS)\n        {\n            LOG_F(ERROR, \"GetDisplayConfigBufferSizes() failed with %ld\", result);\n            return 1.0f;\n        }\n\n        paths.resize(path_count);\n        modes.resize(mode_count);\n\n        result = ::QueryDisplayConfig(flags, &path_count, paths.data(), &mode_count, modes.data(), nullptr);\n\n        paths.resize(path_count);\n        modes.resize(mode_count);\n    } \n    while (result == ERROR_INSUFFICIENT_BUFFER);\n\n    if (result != ERROR_SUCCESS)\n    {\n        LOG_F(ERROR, \"QueryDisplayConfig() failed with %ld\", result);\n        return 1.0f;\n    }\n\n    //Check each active path\n    for (auto& path : paths)\n    {\n        DISPLAYCONFIG_SOURCE_DEVICE_NAME source_name = {};\n        source_name.header.adapterId = path.sourceInfo.adapterId;\n        source_name.header.id = path.sourceInfo.id;\n        source_name.header.type = DISPLAYCONFIG_DEVICE_INFO_GET_SOURCE_NAME;\n        source_name.header.size = sizeof(source_name);\n\n        result = ::DisplayConfigGetDeviceInfo(&source_name.header);\n\n        if (result == ERROR_SUCCESS)\n        {\n            if (wcscmp(source_name.viewGdiDeviceName, output_desc.DeviceName) == 0)\n            {\n                //Found the right display config path, time to grab the data\n                bool is_hdr_enabled = false;\n                bool is_8bit = true;\n                ULONG sdr_white_level = 1000;\n\n                #if (NTDDI_VERSION >= 0x0A00000F/*NTDDI_WIN11_GA*/)\n                    DISPLAYCONFIG_GET_ADVANCED_COLOR_INFO_2 adv_color_info_2 = {};\n                    adv_color_info_2.header.adapterId = path.targetInfo.adapterId;\n                    adv_color_info_2.header.id = path.targetInfo.id;\n                    adv_color_info_2.header.type = DISPLAYCONFIG_DEVICE_INFO_GET_ADVANCED_COLOR_INFO_2;\n                    adv_color_info_2.header.size = sizeof(adv_color_info_2);\n\n                    result = ::DisplayConfigGetDeviceInfo(&adv_color_info_2.header);\n\n                    if (result == ERROR_SUCCESS)\n                    {\n                        is_8bit = (adv_color_info_2.bitsPerColorChannel == 8);\n                        //DISPLAYCONFIG_ADVANCED_COLOR_MODE_WCG is still higher bit-depth but seems like it needs to be handled differently\n                        is_hdr_enabled = (adv_color_info_2.activeColorMode == DISPLAYCONFIG_ADVANCED_COLOR_MODE_HDR);\n                    }\n\n                    if (is_hdr_enabled)\n                    {\n                        DISPLAYCONFIG_SDR_WHITE_LEVEL config_sdr_white_level = {};\n                        config_sdr_white_level.header.adapterId = path.targetInfo.adapterId;\n                        config_sdr_white_level.header.id = path.targetInfo.id;\n                        config_sdr_white_level.header.type = DISPLAYCONFIG_DEVICE_INFO_GET_SDR_WHITE_LEVEL;\n                        config_sdr_white_level.header.size = sizeof(sdr_white_level);\n\n                        result = ::DisplayConfigGetDeviceInfo(&config_sdr_white_level.header);\n\n                        if (result == ERROR_SUCCESS)\n                        {\n                            sdr_white_level = config_sdr_white_level.SDRWhiteLevel;\n                        }\n                    }\n                #endif\n\n                DISPLAYCONFIG_GET_ADVANCED_COLOR_INFO adv_color_info = {};\n                adv_color_info.header.adapterId = path.targetInfo.adapterId;\n                adv_color_info.header.id = path.targetInfo.id;\n                adv_color_info.header.type = DISPLAYCONFIG_DEVICE_INFO_GET_ADVANCED_COLOR_INFO;\n                adv_color_info.header.size = sizeof(adv_color_info);\n\n                result = ::DisplayConfigGetDeviceInfo(&adv_color_info.header);\n\n                if (result == ERROR_SUCCESS)\n                {\n                    is_8bit = (adv_color_info.bitsPerColorChannel == 8);\n                }\n\n                //The following is based on potentially incomplete observations and doesn't appear to be documented anywhere otherwise\n                //Checking different OS versions without access to a HDR display in a VM makes things a little bit messy... so this likely needs to be fixed up later\n                if (is_8bit)\n                {\n                    //This the easiest to check and has been observed across several Windows 10 and 11 versions, why it's like this I don't know\n                    return (is_for_graphics_capture) ? 0.5f : 1.0f;\n                }\n                else if (is_hdr_enabled)\n                {\n                    //Observed on Windows 11 24H2, but not on Windows 10 (DISPLAYCONFIG_GET_ADVANCED_COLOR_INFO_2 doesn't exist there so it won't hit this path)\n                    return 1000.0f / sdr_white_level;\n                }\n                else\n                {\n                    //Observed on Windows 10 20H2, but potentially always applies to DISPLAYCONFIG_ADVANCED_COLOR_MODE_WCG in general and DISPLAYCONFIG_ADVANCED_COLOR_MODE_HDR doesn't exist there?\n                    //However, also observed displays set to HDR get non-linear pixel data written by Desktop Duplication on there (Graphics Capture and Desktop Duplication non-HDR display pixels are correct)\n                    //Might have some unknown factor causing it, even if fixable with extra steps in theory... so it is what it is for now.\n                    return (is_for_graphics_capture) ? 0.5f : 1.0f;\n                }\n            }\n        }\n    }\n\n    LOG_F(WARNING, \"Could not find display config for desktop %d, defaulting to 100%% brightness adjustment\", desktop_id);\n    return 1.0f;\n\n    #endif //DPLUS_DUP_NO_HDR\n}\n\nvoid OutputManager::ShowOverlay(unsigned int id)\n{\n    Overlay& overlay = OverlayManager::Get().GetOverlay(id);\n\n    if (overlay.IsVisible()) //Already visible? Abort.\n    {\n        return;\n    }\n\n    unsigned int current_overlay_old = OverlayManager::Get().GetCurrentOverlayID();\n    OverlayManager::Get().SetCurrentOverlayID(id);\n    vr::VROverlayHandle_t ovrl_handle = overlay.GetHandle();\n    const OverlayConfigData& data = OverlayManager::Get().GetConfigData(id);\n\n    if (m_OvrlActiveCount == 0) //First overlay to become active\n    {\n        ::timeBeginPeriod(1);   //This is somewhat frowned upon, but we want to hit the polling rate, it's only when active and we're in a high performance situation anyways\n\n        //Set last pointer values to current to not trip the movement detection up\n        ResetMouseLastLaserPointerPos();\n        m_MouseIgnoreMoveEvent = false;\n\n        WindowManager::Get().SetOverlayActive(true);\n    }\n\n    m_OvrlActiveCount++;\n\n    if (data.ConfigInt[configid_int_overlay_capture_source] == ovrl_capsource_desktop_duplication)\n    {\n        if (m_OvrlDesktopDuplActiveCount == 0) //First Desktop Duplication overlay to become active\n        {\n            //Signal duplication threads to resume in case they're paused\n            ::ResetEvent(m_PauseDuplicationEvent);\n            ::SetEvent(m_ResumeDuplicationEvent);\n\n            ForceScreenRefresh();\n        }\n\n        m_OvrlDesktopDuplActiveCount++;\n    }\n    else if (data.ConfigInt[configid_int_overlay_capture_source] == ovrl_capsource_winrt_capture)\n    {\n        //Unpause capture\n        DPWinRT_PauseCapture(ovrl_handle, false);\n    }\n    else if (data.ConfigInt[configid_int_overlay_capture_source] == ovrl_capsource_browser)\n    {\n        //Don't call this before texture source is set (browser process may not be running), ApplySettingCaptureSource() will call it in that case\n        if (overlay.GetTextureSource() == ovrl_texsource_browser)\n        {\n            //Unpause browser\n            DPBrowserAPIClient::Get().DPBrowser_PauseBrowser(overlay.GetHandle(), false);\n        }\n    }\n\n    overlay.SetVisible(true);\n\n    ApplySettingTransform();\n\n    //Overlay could affect update limiter, so apply setting\n    if (data.ConfigInt[configid_int_overlay_update_limit_override_mode] != update_limit_mode_off)\n    {\n        ApplySettingUpdateLimiter();\n    }\n\n    //If the last clipping rect doesn't fully contain the overlay's crop rect, the desktop texture overlay is probably outdated there, so force a full refresh\n    if ( (data.ConfigInt[configid_int_overlay_capture_source] == ovrl_capsource_desktop_duplication) && (!m_OutputLastClippingRect.Contains(overlay.GetValidatedCropRect())) )\n    {\n        RefreshOpenVROverlayTexture(DPRect(-1, -1, -1, -1), true);\n    }\n\n    OverlayManager::Get().SetCurrentOverlayID(current_overlay_old);\n}\n\nvoid OutputManager::ShowTheaterOverlay(unsigned int id)\n{\n    if (OverlayManager::Get().GetTheaterOverlayID() == id)\n        return;\n\n    //Don't set theater overlay before texture source is initialized \n    if (OverlayManager::Get().GetOverlay(id).GetTextureSource() == ovrl_texsource_invalid)\n        return;\n\n    OverlayManager::Get().SetTheaterOverlayID(id);\n\n    //Check every other existing overlay for theater origin and disable them (only one theater overlay can be active)\n    for (unsigned int i = 0; i < OverlayManager::Get().GetOverlayCount(); ++i)\n    {\n        Overlay& overlay = OverlayManager::Get().GetOverlay(i);\n        OverlayConfigData& data = OverlayManager::Get().GetConfigData(i);\n\n        if ((data.ConfigInt[configid_int_overlay_origin] == ovrl_origin_theater_screen) && (i != id))\n        {\n            SetOverlayEnabled(i, false);\n        }\n    }\n\n    //Reset overlay so the theater overlay has every property applied\n    unsigned int current_overlay_old = OverlayManager::Get().GetCurrentOverlayID();\n    OverlayManager::Get().SetCurrentOverlayID(id);\n    ResetCurrentOverlay();\n    OverlayManager::Get().GetCurrentOverlay().AssignDesktopDuplicationTexture();    //Desktop Duplication texture isn't reset and only assigned on change, so do it manually\n    OverlayManager::Get().SetCurrentOverlayID(current_overlay_old);\n}\n\nvoid OutputManager::HideOverlay(unsigned int id)\n{\n    Overlay& overlay = OverlayManager::Get().GetOverlay(id);\n\n    if (!overlay.IsVisible()) //Already hidden? Abort.\n    {\n        return;\n    }\n\n    unsigned int current_overlay_old = OverlayManager::Get().GetCurrentOverlayID();\n    OverlayManager::Get().SetCurrentOverlayID(id);\n    vr::VROverlayHandle_t ovrl_handle = overlay.GetHandle();\n    const OverlayConfigData& data = OverlayManager::Get().GetConfigData(id);\n\n    overlay.SetVisible(false);\n\n    //Overlay could've affected update limiter, so apply setting\n    if (data.ConfigInt[configid_int_overlay_update_limit_override_mode] != update_limit_mode_off)\n    {\n        ApplySettingUpdateLimiter();\n    }\n\n    m_OvrlActiveCount--;\n\n    if (m_OvrlActiveCount == 0) //Last overlay to become inactive\n    {\n        ::timeEndPeriod(1);\n        WindowManager::Get().SetOverlayActive(false);\n    }\n\n    if (data.ConfigInt[configid_int_overlay_capture_source] == ovrl_capsource_desktop_duplication)\n    {\n        m_OvrlDesktopDuplActiveCount--;\n\n        if (m_OvrlDesktopDuplActiveCount == 0) //Last Desktop Duplication overlay to become inactive\n        {\n            //Signal duplication threads to pause since we don't need them to do needless work\n            ::ResetEvent(m_ResumeDuplicationEvent);\n            ::SetEvent(m_PauseDuplicationEvent);\n        }\n    }\n    else if (data.ConfigInt[configid_int_overlay_capture_source] == ovrl_capsource_winrt_capture)\n    {\n        //Pause capture\n        DPWinRT_PauseCapture(ovrl_handle, true);\n    }\n    else if (data.ConfigInt[configid_int_overlay_capture_source] == ovrl_capsource_browser)\n    {\n        //Don't call this before texture source is set (browser process may not be running), ApplySettingCaptureSource() will call it in that case\n        if (overlay.GetTextureSource() == ovrl_texsource_browser)\n        {\n            //Pause browser\n            DPBrowserAPIClient::Get().DPBrowser_PauseBrowser(overlay.GetHandle(), true);\n        }\n    }\n\n    OverlayManager::Get().SetCurrentOverlayID(current_overlay_old);\n}\n\nvoid OutputManager::ResetOverlayActiveCount()\n{\n    bool desktop_duplication_was_paused = (m_OvrlDesktopDuplActiveCount == 0);\n\n    m_OvrlActiveCount = 0;\n    m_OvrlDesktopDuplActiveCount = 0;\n\n    //Check every existing overlay for visibility and count them as active\n    for (unsigned int i = 0; i < OverlayManager::Get().GetOverlayCount(); ++i)\n    {\n        Overlay& overlay = OverlayManager::Get().GetOverlay(i);\n        OverlayConfigData& data = OverlayManager::Get().GetConfigData(i);\n\n        if (overlay.IsVisible())\n        {\n            m_OvrlActiveCount++;\n\n            if (data.ConfigInt[configid_int_overlay_capture_source] == ovrl_capsource_desktop_duplication)\n            {\n                m_OvrlDesktopDuplActiveCount++;\n            }\n        }\n    }\n\n    //Fixup desktop duplication state\n    if ( (desktop_duplication_was_paused) && (m_OvrlDesktopDuplActiveCount > 0) )\n    {\n        //Signal duplication threads to resume\n        ::ResetEvent(m_PauseDuplicationEvent);\n        ::SetEvent(m_ResumeDuplicationEvent);\n\n        ForceScreenRefresh();\n    }\n    else if ( (!desktop_duplication_was_paused) && (m_OvrlDesktopDuplActiveCount == 0) )\n    {\n        //Signal duplication threads to pause\n        ::ResetEvent(m_ResumeDuplicationEvent);\n        ::SetEvent(m_PauseDuplicationEvent);\n    }\n\n    //Fixup WindowManager state\n    WindowManager::Get().SetOverlayActive( (m_OvrlActiveCount > 0) );\n}\n\nbool OutputManager::HasDashboardBeenActivatedOnce() const\n{\n    return m_DashboardActivatedOnce;\n}\n\nbool OutputManager::IsDashboardTabActive() const\n{\n    return m_OvrlDashboardActive;\n}\n\nfloat OutputManager::GetDashboardScale() const\n{\n    vr::HmdMatrix34_t matrix = {0};\n    vr::VROverlay()->GetTransformForOverlayCoordinates(m_OvrlHandleDashboardDummy, vr::TrackingUniverseStanding, {0.5f, 0.5f}, &matrix);\n    Vector3 row_1(matrix.m[0][0], matrix.m[1][0], matrix.m[2][0]);\n\n    return row_1.length(); //Scaling is always uniform so we just check the x-axis\n}\n\nfloat OutputManager::GetOverlayHeight(unsigned int overlay_id) const\n{\n    const Overlay& overlay        = OverlayManager::Get().GetOverlay(overlay_id);\n    const OverlayConfigData& data = OverlayManager::Get().GetConfigData(overlay_id);\n\n    const DPRect& crop_rect = overlay.GetValidatedCropRect();\n    int crop_width = crop_rect.GetWidth(), crop_height = crop_rect.GetHeight();\n\n    bool is_3d_enabled = data.ConfigBool[configid_bool_overlay_3D_enabled];\n    int mode_3d = data.ConfigInt[configid_int_overlay_3D_mode];\n\n    if ( (data.ConfigInt[configid_int_overlay_capture_source] == ovrl_capsource_desktop_duplication) && (m_OutputInvalid) ) //No cropping on invalid output image\n    {\n        crop_width  = k_lOverlayOutputErrorTextureWidth;\n        crop_height = k_lOverlayOutputErrorTextureHeight;\n        is_3d_enabled = false;\n    }\n    else if ( (overlay.GetTextureSource() == ovrl_texsource_none) || (crop_width <= 0) || (crop_height <= 0) )\n    {\n        //Get dimensions from mouse scale if possible \n        vr::HmdVector2_t mouse_scale;\n        if (vr::VROverlay()->GetOverlayMouseScale(overlay.GetHandle(), &mouse_scale) == vr::VROverlayError_None)\n        {\n            crop_width  = mouse_scale.v[0];\n            crop_height = mouse_scale.v[1];\n        }\n        else\n        {\n            crop_width  = k_lOverlayOutputErrorTextureWidth;\n            crop_height = k_lOverlayOutputErrorTextureHeight;\n        }\n\n        is_3d_enabled = false;\n    }\n    else if ( (is_3d_enabled) && (mode_3d == ovrl_3Dmode_ou) ) //Converted Over-Under changes texture dimensions, so adapt\n    {\n        crop_width  *= 2;\n        crop_height /= 2;\n    }\n\n    //Overlay is twice as tall when SBS3D/OU3D is active\n    if ( (is_3d_enabled) && ( (mode_3d == ovrl_3Dmode_sbs) || (mode_3d == ovrl_3Dmode_ou) ) )\n        crop_height *= 2;\n\n\n    return data.ConfigFloat[configid_float_overlay_width] * ((float)crop_height / crop_width);\n}\n\nMatrix4 OutputManager::GetFallbackOverlayTransform() const\n{\n    Matrix4 transform;\n\n    //Get HMD pose\n    vr::TrackedDevicePose_t poses[vr::k_unTrackedDeviceIndex_Hmd + 1];\n    vr::VRSystem()->GetDeviceToAbsoluteTrackingPose(vr::TrackingUniverseStanding, vr::IVRSystemEx::GetTimeNowToPhotons(), poses, vr::k_unTrackedDeviceIndex_Hmd + 1);\n\n    if (poses[vr::k_unTrackedDeviceIndex_Hmd].bPoseIsValid)\n    {\n        //Set to HMD position and offset 2m away\n        Matrix4 mat_hmd(poses[vr::k_unTrackedDeviceIndex_Hmd].mDeviceToAbsoluteTracking);\n        transform = mat_hmd;\n        transform.translate_relative(0.0f, 0.0f, -2.0f);\n\n        //Rotate towards HMD position\n        vr::IVRSystemEx::TransformLookAt(transform, mat_hmd.getTranslation());\n    }\n\n    return transform;\n}\n\nvoid OutputManager::SetOutputErrorTexture(vr::VROverlayHandle_t overlay_handle)\n{\n    vr::EVROverlayError vr_error = vr::VROverlayEx()->SetOverlayFromFileEx(overlay_handle, (ConfigManager::Get().GetApplicationPath() + \"images/output_error.png\").c_str());    \n\n    vr::VRTextureBounds_t tex_bounds = {0.0f};\n    tex_bounds.uMax = 1.0f;\n    tex_bounds.vMax = 1.0f;\n\n    vr::VROverlay()->SetOverlayTextureBounds(overlay_handle, &tex_bounds);\n\n    //Make sure to remove 3D on the overlay too\n    vr::VROverlay()->SetOverlayFlag(overlay_handle, vr::VROverlayFlags_SideBySide_Parallel, false);\n    vr::VROverlay()->SetOverlayFlag(overlay_handle, vr::VROverlayFlags_SideBySide_Crossed,  false);\n    vr::VROverlay()->SetOverlayTexelAspect(overlay_handle, 1.0f);\n\n    //Mouse scale needs to be updated as well\n    ApplySettingMouseInput();\n}\n\nvoid OutputManager::SetOutputInvalid()\n{\n    LOG_F(WARNING, \"No outputs available!\");\n\n    m_OutputInvalid = true;\n    SetOutputErrorTexture(m_OvrlHandleDesktopTexture);\n    m_DesktopWidth  = k_lOverlayOutputErrorTextureWidth;\n    m_DesktopHeight = k_lOverlayOutputErrorTextureHeight;\n\n    ResetOverlays();\n}\n\nbool OutputManager::IsOutputInvalid() const\n{\n    return m_OutputInvalid;\n}\n\nvoid OutputManager::SetOverlayEnabled(unsigned int overlay_id, bool is_enabled)\n{\n    OverlayConfigData& data = OverlayManager::Get().GetConfigData(overlay_id);\n\n    if (data.ConfigBool[configid_bool_overlay_enabled] == is_enabled)\n        return;\n\n    data.ConfigBool[configid_bool_overlay_enabled] = is_enabled;\n\n    unsigned int current_overlay_old = OverlayManager::Get().GetCurrentOverlayID();\n    OverlayManager::Get().SetCurrentOverlayID(overlay_id);\n    ApplySettingTransform();\n    OverlayManager::Get().SetCurrentOverlayID(current_overlay_old);\n\n    //Sync change\n    IPCManager::Get().PostConfigMessageToUIApp(configid_int_state_overlay_current_id_override, overlay_id);\n    IPCManager::Get().PostConfigMessageToUIApp(configid_bool_overlay_enabled, data.ConfigBool[configid_bool_overlay_enabled]);\n    IPCManager::Get().PostConfigMessageToUIApp(configid_int_state_overlay_current_id_override, -1);\n}\n\nvoid OutputManager::CropToActiveWindowToggle(unsigned int overlay_id)\n{\n    //If the action is used with one of the controller buttons, the events will fire another time if the new cropping values happen to have the laser pointer leave and\n    //re-enter the overlay for a split second while the button is still down during the dimension change. \n    //This would immediately undo the action, which we want to prevent, so a 100 ms pause between toggles is enforced \n    //-Currently not actually enforcing this with the new action commands to see where this goes-\n    static ULONGLONG last_toggle_tick = 0;\n\n    if ((overlay_id == k_ulOverlayID_None) /*|| (::GetTickCount64() <= last_toggle_tick + 100)*/)\n        return;\n\n    last_toggle_tick = ::GetTickCount64();\n\n    unsigned int current_overlay_old = OverlayManager::Get().GetCurrentOverlayID();\n    OverlayManager::Get().SetCurrentOverlayID(overlay_id);\n\n    bool& crop_enabled = ConfigManager::GetRef(configid_bool_overlay_crop_enabled);\n    int& crop_x        = ConfigManager::GetRef(configid_int_overlay_crop_x);\n    int& crop_y        = ConfigManager::GetRef(configid_int_overlay_crop_y);\n    int& crop_width    = ConfigManager::GetRef(configid_int_overlay_crop_width);\n    int& crop_height   = ConfigManager::GetRef(configid_int_overlay_crop_height);\n\n    //Check if crop is just exactly the current desktop\n    bool crop_equals_current_desktop = false;\n    const int desktop_id = ConfigManager::GetValue(configid_int_overlay_desktop_id);\n\n    if ( (desktop_id >= 0) && (desktop_id < m_DesktopRects.size()) )\n    {\n        DPRect crop_rect(crop_x, crop_y, crop_x + crop_width, crop_y + crop_height);\n\n        crop_equals_current_desktop = (crop_rect == m_DesktopRects[desktop_id]);\n    }\n\n    //Check if crop already matches the active window\n    bool crop_equals_new_window_crop = false;\n    int crop_x_new      = crop_x;\n    int crop_y_new      = crop_y;\n    int crop_width_new  = crop_width;\n    int crop_height_new = crop_height;\n\n    if (CropToActiveWindow(crop_x_new, crop_y_new, crop_width_new, crop_height_new))\n    {\n        crop_equals_new_window_crop = ((crop_x == crop_x_new) && (crop_y == crop_y_new) && (crop_width == crop_width_new) && (crop_height == crop_height_new));\n    }\n\n    //If uncropped or different, crop to active window\n    if ( (!crop_enabled) || (!crop_equals_new_window_crop) )\n    {\n        CropToActiveWindow();\n    }\n    else //Otherwise, disable cropping (leaving the existing cropping values around)\n    {\n        crop_enabled = false;\n        IPCManager::Get().PostConfigMessageToUIApp(configid_bool_overlay_crop_enabled, crop_enabled);\n\n        ApplySettingCrop();\n        ApplySettingTransform();\n        ApplySettingMouseScale();\n    }\n\n    OverlayManager::Get().SetCurrentOverlayID(current_overlay_old);\n}\n\nvoid OutputManager::ShowWindowSwitcher()\n{\n    InitComIfNeeded();\n\n    Microsoft::WRL::ComPtr<IShellDispatch5> shell_dispatch;\n    HRESULT sc = ::CoCreateInstance(CLSID_Shell, nullptr, CLSCTX_SERVER, IID_PPV_ARGS(&shell_dispatch));\n\n    if (SUCCEEDED(sc))\n    {\n        shell_dispatch->WindowSwitcher();\n    }\n}\n\nvoid OutputManager::SwitchToWindow(HWND window, bool warp_cursor)\n{\n    WindowManager::Get().RaiseAndFocusWindow(window, &m_InputSim);\n\n    if (warp_cursor)\n    {\n        //Figure out center point of the window and put the cursor there\n        RECT window_rect = {0};\n\n        //Just using GetWindowRect() can include shadows and such, which we don't want\n        if (::DwmGetWindowAttribute(window, DWMWA_EXTENDED_FRAME_BOUNDS, &window_rect, sizeof(window_rect)) == S_OK)\n        {\n            DPRect window_dprect(window_rect.left, window_rect.top, window_rect.right, window_rect.bottom);\n            Vector2Int pt_center = window_dprect.GetCenter();\n\n            m_InputSim.MouseMove(pt_center.x, pt_center.y);\n        }\n    }\n}\n\nvoid OutputManager::OverlayDirectDragStart(unsigned int overlay_id)\n{\n    if (overlay_id == k_ulOverlayID_None)\n        return;\n\n    const OverlayConfigData& data = OverlayManager::Get().GetConfigData(overlay_id);\n\n    if (m_OverlayDragger.GetDragDeviceID() == -1)\n    {\n        if (data.ConfigInt[configid_int_overlay_origin] != ovrl_origin_theater_screen)\n        {\n            if (!data.ConfigBool[configid_bool_overlay_transform_locked])\n            {\n                m_OverlayDragger.DragStart(overlay_id);\n\n                if (m_OverlayDragger.IsDragActive())\n                {\n                    m_OvrlDirectDragActive = true;\n                    ApplySettingInputMode();\n\n                    if (m_LaserPointer.IsActive())\n                    {\n                        m_LaserPointer.ForceTargetOverlay(OverlayManager::Get().GetOverlay(overlay_id).GetHandle());\n                    }\n                }\n            }\n            else\n            {\n                IPCManager::Get().PostConfigMessageToUIApp(configid_int_state_drag_hint_device, ConfigManager::Get().GetPrimaryLaserPointerDevice());\n                IPCManager::Get().PostConfigMessageToUIApp(configid_int_state_drag_hint_type, 1);\n            }\n        }\n        else\n        {\n            IPCManager::Get().PostConfigMessageToUIApp(configid_int_state_drag_hint_device, ConfigManager::Get().GetPrimaryLaserPointerDevice());\n            IPCManager::Get().PostConfigMessageToUIApp(configid_int_state_drag_hint_type, 2);\n        }\n    }\n}\n\nvoid OutputManager::OverlayDirectDragFinish(unsigned int overlay_id)\n{\n    if (overlay_id == k_ulOverlayID_None)\n        return;\n\n    const OverlayConfigData& data = OverlayManager::Get().GetConfigData(overlay_id);\n\n    if ((m_OverlayDragger.GetDragOverlayID() == overlay_id) && (m_OverlayDragger.IsDragActive()))\n    {\n        OnDragFinish();\n        m_OverlayDragger.DragFinish();\n\n        ApplySettingTransform();\n    }\n\n    IPCManager::Get().PostConfigMessageToUIApp(configid_int_state_drag_hint_type, 0);\n}\n\nVRInput& OutputManager::GetVRInput()\n{\n    return m_VRInput;\n}\n\nInputSimulator& OutputManager::GetInputSimulator()\n{\n    return m_InputSim;\n}\n\nvoid OutputManager::UpdatePerformanceStates()\n{\n    //Frame counter, the frames themselves are counted in Update()\n    if (::GetTickCount64() >= m_PerformanceFrameCountStartTick + 1000)\n    {\n        //A second has passed, reset the value\n        ConfigManager::SetValue(configid_int_state_performance_duplication_fps, m_PerformanceFrameCount);\n        IPCManager::Get().PostConfigMessageToUIApp(configid_int_state_performance_duplication_fps, m_PerformanceFrameCount);\n\n        m_PerformanceFrameCountStartTick = ::GetTickCount64();\n        m_PerformanceFrameCount = 0;\n    }\n}\n\nconst LARGE_INTEGER& OutputManager::GetUpdateLimiterDelay()\n{\n    return m_PerformanceUpdateLimiterDelay;\n}\n\nint OutputManager::EnumerateOutputs(int target_desktop_id, Microsoft::WRL::ComPtr<IDXGIAdapter>* out_adapter_preferred, Microsoft::WRL::ComPtr<IDXGIAdapter>* out_adapter_vr)\n{\n    Microsoft::WRL::ComPtr<IDXGIFactory1> factory_ptr;\n    Microsoft::WRL::ComPtr<IDXGIAdapter> adapter_ptr_preferred;\n    Microsoft::WRL::ComPtr<IDXGIAdapter> adapter_ptr_vr;\n    int output_id_adapter = target_desktop_id;           //Output ID on the adapter actually used. Only different from initial SingleOutput if there's desktops across multiple GPUs\n\n    m_DesktopRects.clear();\n    m_DesktopRectTotal = DPRect();   //Figure out right dimensions for full size desktop rect (this is also done in CreateTextures() but for Desktop Duplication only)\n    m_DesktopHDRWhiteLevelAdjustments.clear();\n\n    const bool is_hdr_in_use = ((m_OutputHDRAvailable) && (ConfigManager::GetValue(configid_bool_performance_hdr_mirroring)));\n    const UINT target_adapter_deviceid    = (ConfigManager::GetValue(configid_int_misc_force_gpu_deviceid)    == -1) ? 0 : (UINT)ConfigManager::GetValue(configid_int_misc_force_gpu_deviceid);\n    const UINT target_adapter_deviceid_vr = (ConfigManager::GetValue(configid_int_misc_force_gpu_vr_deviceid) == -1) ? 0 : (UINT)ConfigManager::GetValue(configid_int_misc_force_gpu_vr_deviceid);\n\n    //Also look for the device the HMD is connected to\n    int32_t vr_gpu_id;\n    vr::VRSystem()->GetDXGIOutputInfo(&vr_gpu_id);\n\n    HRESULT hr = CreateDXGIFactory1(__uuidof(IDXGIFactory1), (void**)&factory_ptr);\n    if (!FAILED(hr))\n    {\n        LOG_SCOPE_F(INFO, \"Detected Outputs\");\n\n        Microsoft::WRL::ComPtr<IDXGIAdapter> adapter_ptr;\n        UINT i = 0;\n        int output_count = 0;\n        bool wmr_ignore_vscreens = (ConfigManager::GetValue(configid_int_interface_wmr_ignore_vscreens) == 1);\n\n        while (factory_ptr->EnumAdapters(i, &adapter_ptr) != DXGI_ERROR_NOT_FOUND)\n        {\n            DXGI_ADAPTER_DESC adapter_desc;\n            adapter_ptr->GetDesc(&adapter_desc);\n\n            //Check if this is the adapter SteamVR wants\n            if ( ((target_adapter_deviceid_vr == 0) && (i == vr_gpu_id)) || ((adapter_ptr_vr == nullptr) && (adapter_desc.DeviceId == target_adapter_deviceid_vr)) )\n            {\n                adapter_ptr_vr = adapter_ptr;\n            }\n\n            //Check if this a WMR virtual display adapter and skip it when the option is enabled\n            //This still only works correctly when they have the last desktops in the system, but that should pretty much be always the case\n            if (wmr_ignore_vscreens)\n            {\n                if (wcscmp(adapter_desc.Description, L\"Virtual Display Adapter\") == 0)\n                {\n                    LOG_F(INFO, \"Skipping \\\"Virtual Display Adapter\\\"\");\n                    ++i;\n                    continue;\n                }\n            }\n\n            //Count the available outputs\n            Microsoft::WRL::ComPtr<IDXGIOutput> output_ptr;\n\n            //Check if there are gonna be any outputs before logging the GPU to avoid confusion (there may be multiple adapters per GPU with no outputs attached)\n            if (adapter_ptr->EnumOutputs(0, &output_ptr) == DXGI_ERROR_NOT_FOUND)\n            {\n                //Still log them with higher verbosity level just in case\n                LOG_F(1, \"GPU %u: %s (Device ID %u) (No Outputs)\", i + 1, StringConvertFromUTF16(adapter_desc.Description).c_str(), adapter_desc.DeviceId);\n\n                ++i;\n                continue;\n            }\n\n            LOG_SCOPE_F(INFO, \"GPU %u: %s (Device ID %u)\", i + 1, StringConvertFromUTF16(adapter_desc.Description).c_str(), adapter_desc.DeviceId);\n\n            UINT output_index = 0;\n            while (adapter_ptr->EnumOutputs(output_index, &output_ptr) != DXGI_ERROR_NOT_FOUND)\n            {\n                //Check if this happens to be the output we're looking for (or for combined desktop, set the first adapter with available output unless forced otherwise)\n                if ( (adapter_ptr_preferred == nullptr) && \n                     ( (target_desktop_id == output_count) || ((target_desktop_id == -1) && ((target_adapter_deviceid == 0) || (adapter_desc.DeviceId == target_adapter_deviceid))) ) )\n                {\n                    adapter_ptr_preferred = adapter_ptr;\n\n                    if (target_desktop_id != -1)\n                    {\n                        output_id_adapter = output_index;\n                    }\n                }\n\n                //Cache rect of the output\n                DXGI_OUTPUT_DESC output_desc;\n                output_ptr->GetDesc(&output_desc);\n                m_DesktopRects.emplace_back(output_desc.DesktopCoordinates.left,  output_desc.DesktopCoordinates.top, \n                                            output_desc.DesktopCoordinates.right, output_desc.DesktopCoordinates.bottom);\n\n                (m_DesktopRectTotal.GetWidth() == 0) ? m_DesktopRectTotal = m_DesktopRects.back() : m_DesktopRectTotal.Add(m_DesktopRects.back());\n\n\n                //Cache HDR white level adjustment\n                const float white_level_adjust = GetDesktopHDRWhiteLevelAdjustment(output_count, false, wmr_ignore_vscreens);\n                m_DesktopHDRWhiteLevelAdjustments.push_back(white_level_adjust);\n\n                //Log display info, with white level adjustment if HDR is actually on\n                LOG_IF_F(INFO, !is_hdr_in_use, \"Desktop %u: %4d,%4d | %4dx%4d (%s)\", output_count + 1, \n                         output_desc.DesktopCoordinates.left, output_desc.DesktopCoordinates.top,\n                         output_desc.DesktopCoordinates.right - output_desc.DesktopCoordinates.left, output_desc.DesktopCoordinates.bottom - output_desc.DesktopCoordinates.top,\n                         StringConvertFromUTF16(output_desc.DeviceName).c_str());\n\n                LOG_IF_F(INFO, is_hdr_in_use, \"Desktop %u: %4d,%4d | %4dx%4d (%s) | %.2fx SDR Brightness\", output_count + 1, \n                         output_desc.DesktopCoordinates.left, output_desc.DesktopCoordinates.top,\n                         output_desc.DesktopCoordinates.right - output_desc.DesktopCoordinates.left, output_desc.DesktopCoordinates.bottom - output_desc.DesktopCoordinates.top,\n                         StringConvertFromUTF16(output_desc.DeviceName).c_str(), 1.0f / white_level_adjust);\n\n\n                ++output_count;\n                ++output_index;\n            }\n\n            ++i;\n        }\n\n        //Store output/desktop count and send it over to UI\n        ConfigManager::SetValue(configid_int_state_interface_desktop_count, output_count);\n        IPCManager::Get().PostConfigMessageToUIApp(configid_int_state_interface_desktop_count, output_count);\n    }\n\n    LOG_IF_F(WARNING, (adapter_ptr_preferred == nullptr) && (target_adapter_deviceid == 0) && (target_desktop_id == -1), \"No GPU with desktop outputs was found!\");\n    LOG_IF_F(WARNING, (adapter_ptr_preferred == nullptr) && (target_adapter_deviceid != 0) && (target_desktop_id == -1), \"GPU forced by config (DeviceID %u) was not found or has no outputs!\", target_adapter_deviceid);\n    LOG_IF_F(WARNING, (adapter_ptr_preferred == nullptr) && (target_desktop_id != -1), \"GPU for target Desktop %i was not found!\", target_desktop_id);\n    LOG_IF_F(WARNING, (adapter_ptr_vr == nullptr) && (target_adapter_deviceid_vr == 0), \"GPU requested by SteamVR (%u) was not found!\", vr_gpu_id + 1);\n    LOG_IF_F(WARNING, (adapter_ptr_vr == nullptr) && (target_adapter_deviceid_vr != 0), \"VR GPU forced by config (DeviceID %u) was not found!\", target_adapter_deviceid_vr);\n\n    if (out_adapter_preferred != nullptr)\n    {\n        *out_adapter_preferred = adapter_ptr_preferred;\n    }\n\n    if (out_adapter_vr != nullptr)\n    {\n        *out_adapter_vr = adapter_ptr_vr;\n    }\n\n    m_InputSim.RefreshScreenOffsets();\n    ResetMouseLastLaserPointerPos();\n\n    return output_id_adapter;\n}\n\nvoid OutputManager::CropToDisplay(int display_id, int& crop_x, int& crop_y, int& crop_width, int& crop_height)\n{   \n    if ( (!ConfigManager::GetValue(configid_bool_performance_single_desktop_mirroring)) && (display_id >= 0) && (display_id < m_DesktopRects.size()) ) \n    {\n        //Individual desktop on full desktop texture\n        const DPRect& rect = m_DesktopRects[display_id];\n\n        crop_x      = rect.GetTL().x;\n        crop_y      = rect.GetTL().y;\n        crop_width  = rect.GetWidth();\n        crop_height = rect.GetHeight();\n\n        //Offset by desktop coordinates\n        crop_x      -= m_DesktopX;\n        crop_y      -= m_DesktopY;\n    }\n    else //Full desktop\n    {\n        crop_x      =  0;\n        crop_y      =  0;\n        crop_width  = -1;\n        crop_height = -1;\n    }\n}\n\nbool OutputManager::CropToActiveWindow(int& crop_x, int& crop_y, int& crop_width, int& crop_height)\n{\n    HWND window_handle = ::GetForegroundWindow();\n\n    if (window_handle != nullptr)\n    {\n        RECT window_rect = {0};\n\n        //Just using GetWindowRect() can include shadows and such, which we don't want\n        if (::DwmGetWindowAttribute(window_handle, DWMWA_EXTENDED_FRAME_BOUNDS, &window_rect, sizeof(window_rect)) == S_OK)\n        {\n            DPRect crop_rect(window_rect.left, window_rect.top, window_rect.right, window_rect.bottom);\n\n            crop_rect.Translate({-m_DesktopX, -m_DesktopY});                    //Translate crop rect by desktop offset to get desktop-local coordinates\n            crop_rect.ClipWithFull({0, 0, m_DesktopWidth, m_DesktopHeight});    //Clip to available desktop space\n\n            if ((crop_rect.GetWidth() > 0) && (crop_rect.GetHeight() > 0))\n            {\n                //Set new crop values\n                crop_x      = crop_rect.GetTL().x;\n                crop_y      = crop_rect.GetTL().y;\n                crop_width  = crop_rect.GetWidth();\n                crop_height = crop_rect.GetHeight();\n\n                return true;\n            }\n        }\n    }\n\n    return false;\n}\n\nvoid OutputManager::InitComIfNeeded()\n{\n    if (!m_ComInitDone)\n    {\n        if (::CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE) != RPC_E_CHANGED_MODE)\n        {\n            m_ComInitDone = true;\n        }\n    }\n}\n\nvoid OutputManager::ConvertOUtoSBS(Overlay& overlay, OUtoSBSConverter& converter)\n{\n    //Convert()'s arguments are almost all stuff from OutputManager, so we take this roundabout way of calling it\n    const DPRect& crop_rect = overlay.GetValidatedCropRect();\n\n    HRESULT hr = converter.Convert(m_Device, m_DeviceContext, m_MultiGPUTargetDevice, m_MultiGPUTargetDeviceContext, m_OvrlTex,\n                                   m_DesktopWidth, m_DesktopHeight, crop_rect.GetTL().x, crop_rect.GetTL().y, crop_rect.GetWidth(), crop_rect.GetHeight());\n\n    if (hr == S_OK)\n    {\n        vr::Texture_t vrtex;\n        vrtex.eType = vr::TextureType_DirectX;\n        vrtex.eColorSpace = vr::ColorSpace_Gamma;\n        vrtex.handle = converter.GetTexture(); //OUtoSBSConverter takes care of multi-gpu support automatically, so no further processing needed\n\n        vr::VROverlay()->SetOverlayTexture(overlay.GetHandle(), &vrtex);\n    }\n    else\n    {\n        ProcessFailure(m_Device, L\"Failed to convert OU texture to SBS\", L\"Desktop+ Error\", hr, SystemTransitionsExpectedErrors);\n    }\n}\n\n\n//\n// Process both masked and monochrome pointers\n//\nDUPL_RETURN OutputManager::ProcessMonoMask(bool is_mono, PTR_INFO& ptr_info, int& ptr_width, int& ptr_height, int& ptr_left, int& ptr_top, \n                                           Microsoft::WRL::ComPtr<ID3D11Texture2D>& out_tex, DXGI_FORMAT& out_tex_format, D3D11_BOX& box)\n{\n    out_tex_format = DXGI_FORMAT_UNKNOWN;\n\n    //ShapeBuffer can sometimes be empty when the secure desktop is active, skip\n    if (ptr_info.ShapeBuffer.empty())\n        return DUPL_RETURN_SUCCESS;\n\n    //Desktop dimensions\n    D3D11_TEXTURE2D_DESC FullDesc;\n    m_SharedSurf->GetDesc(&FullDesc);\n    int desktop_width  = FullDesc.Width;\n    int desktop_height = FullDesc.Height;\n\n    //Pointer position\n    int ptr_info_pos_left = ptr_info.Position.x;\n    int ptr_info_pos_top  = ptr_info.Position.y;\n\n    //Figure out if any adjustment is needed for out of bound positions\n    if (ptr_info_pos_left < 0)\n    {\n        ptr_width = ptr_info_pos_left + (int)ptr_info.ShapeInfo.Width;\n    }\n    else if ((ptr_info_pos_left + (int)ptr_info.ShapeInfo.Width) > desktop_width)\n    {\n        ptr_width = desktop_height - ptr_info_pos_left;\n    }\n    else\n    {\n        ptr_width = (int)ptr_info.ShapeInfo.Width;\n    }\n\n    if (is_mono)\n    {\n        ptr_info.ShapeInfo.Height = ptr_info.ShapeInfo.Height / 2;\n    }\n\n    if (ptr_info_pos_top < 0)\n    {\n        ptr_height = ptr_info_pos_top + (int)ptr_info.ShapeInfo.Height;\n    }\n    else if ((ptr_info_pos_top + (int)ptr_info.ShapeInfo.Height) > desktop_height)\n    {\n        ptr_height = desktop_height - ptr_info_pos_top;\n    }\n    else\n    {\n        ptr_height = (int)ptr_info.ShapeInfo.Height;\n    }\n\n    if (is_mono)\n    {\n        ptr_info.ShapeInfo.Height = ptr_info.ShapeInfo.Height * 2;\n    }\n\n    ptr_left = (ptr_info_pos_left < 0) ? 0 : ptr_info_pos_left;\n    ptr_top  = (ptr_info_pos_top < 0)  ? 0 : ptr_info_pos_top;\n\n    // Staging buffer/texture\n    D3D11_TEXTURE2D_DESC copy_buffer_desc = {};\n    copy_buffer_desc.Width              = ptr_width;\n    copy_buffer_desc.Height             = ptr_height;\n    copy_buffer_desc.MipLevels          = 1;\n    copy_buffer_desc.ArraySize          = 1;\n    copy_buffer_desc.Format             = DXGI_FORMAT_B8G8R8A8_UNORM;\n    copy_buffer_desc.SampleDesc.Count   = 1;\n    copy_buffer_desc.SampleDesc.Quality = 0;\n    copy_buffer_desc.Usage              = D3D11_USAGE_STAGING;\n    copy_buffer_desc.BindFlags          = 0;\n    copy_buffer_desc.CPUAccessFlags     = D3D11_CPU_ACCESS_READ;\n    copy_buffer_desc.MiscFlags          = 0;\n\n    Microsoft::WRL::ComPtr<ID3D11Texture2D> copy_buffer;\n    HRESULT hr = m_Device->CreateTexture2D(&copy_buffer_desc, nullptr, &copy_buffer);\n    if (FAILED(hr))\n    {\n        return ProcessFailure(m_Device, L\"Failed creating staging texture for pointer\", L\"Desktop+ Error\", S_OK, SystemTransitionsExpectedErrors); //Shouldn't be critical\n    }\n\n    //Copy needed part of desktop image\n    box.left   = ptr_left;\n    box.top    = ptr_top;\n    box.right  = ptr_left + ptr_width;\n    box.bottom = ptr_top  + ptr_height;\n    m_DeviceContext->CopySubresourceRegion(copy_buffer.Get(), 0, 0, 0, 0, m_SharedSurf, 0, &box);\n\n    //QI for IDXGISurface\n    Microsoft::WRL::ComPtr<IDXGISurface> copy_surface;\n    hr = copy_buffer.As(&copy_surface);\n    if (FAILED(hr))\n    {\n        return ProcessFailure(nullptr, L\"Failed to QI staging texture into IDXGISurface for pointer\", L\"Desktop+ Error\", hr, SystemTransitionsExpectedErrors);\n    }\n\n    // Map pixels\n    DXGI_MAPPED_RECT mapped_surface;\n    hr = copy_surface->Map(&mapped_surface, DXGI_MAP_READ);\n    if (FAILED(hr))\n    {\n        return ProcessFailure(m_Device, L\"Failed to map surface for pointer\", L\"Desktop+ Error\", hr, SystemTransitionsExpectedErrors);\n    }\n\n    //New mouseshape buffer\n    auto init_buffer = std::unique_ptr<BYTE[]>{new (std::nothrow)BYTE[ptr_width * ptr_height * 4]};\n    if (init_buffer == nullptr)\n    {\n        return ProcessFailure(nullptr, L\"Failed to allocate memory for new mouse shape buffer.\", L\"Desktop+ Error\", E_OUTOFMEMORY);\n    }\n\n    uint32_t* init_buffer_u32    = (uint32_t*)init_buffer.get();\n    uint32_t* desktop_buffer_u32 = (uint32_t*)mapped_surface.pBits;\n    int desktop_buffer_pitch     = mapped_surface.Pitch / sizeof(uint32_t);\n\n    // What to skip (pixel offset)\n    unsigned int ptr_skip_x = (ptr_info_pos_left < 0) ? (-1 * ptr_info_pos_left) : (0);\n    unsigned int ptr_skip_y = (ptr_info_pos_top < 0)  ? (-1 * ptr_info_pos_top)  : (0);\n    int ptr_height_half = ptr_info.ShapeInfo.Height / 2;\n\n    if (is_mono)\n    {\n        //Iterate through pointer shape pixels\n        for (size_t ptr_pixel_row = 0; ptr_pixel_row < ptr_height; ++ptr_pixel_row)\n        {\n            //Set mask\n            uint8_t mask_base = 0x80;\n            mask_base = mask_base >> (ptr_skip_x % 8);\n            for (int ptr_pixel_col = 0; ptr_pixel_col < ptr_width; ++ptr_pixel_col)\n            {\n                //Get masks using appropriate offsets\n                size_t mask_offset_base = ((ptr_pixel_col + ptr_skip_x) / 8);\n                BYTE mask_and = ptr_info.ShapeBuffer[mask_offset_base + ((ptr_pixel_row + ptr_skip_y) * ptr_info.ShapeInfo.Pitch)                  ] & mask_base;\n                BYTE mask_xor = ptr_info.ShapeBuffer[mask_offset_base + ((ptr_pixel_row + ptr_skip_y + ptr_height_half) * ptr_info.ShapeInfo.Pitch)] & mask_base;\n                uint32_t mask_and_u32 = (mask_and) ? 0xFFFFFFFF : 0xFF000000;\n                uint32_t mask_xor_u32 = (mask_xor) ? 0x00FFFFFF : 0x00000000;\n\n                //Set new pixel\n                init_buffer_u32[(ptr_pixel_row * ptr_width) + ptr_pixel_col] = (desktop_buffer_u32[(ptr_pixel_row * desktop_buffer_pitch) + ptr_pixel_col] & mask_and_u32) ^ mask_xor_u32;\n\n                //Adjust mask\n                mask_base = (mask_base == 0x01) ? 0x80 : mask_base >> 1;\n            }\n        }\n    }\n    else\n    {\n        uint32_t* shape_buffer_u32 = (uint32_t*)ptr_info.ShapeBuffer.data();\n\n        //Iterate through pointer shape pixels\n        for (size_t ptr_pixel_row = 0; ptr_pixel_row < ptr_height; ++ptr_pixel_row)\n        {\n            for (size_t ptr_pixel_col = 0; ptr_pixel_col < ptr_width; ++ptr_pixel_col)\n            {\n                // Set up mask\n                uint32_t MaskVal = 0xFF000000 & shape_buffer_u32[(ptr_pixel_col + ptr_skip_x) + ((ptr_pixel_row + ptr_skip_y) * uint32_t(ptr_info.ShapeInfo.Pitch / sizeof(uint32_t)))];\n                if (MaskVal)\n                {\n                    //mask_value was 0xFF\n                    init_buffer_u32[(ptr_pixel_row * ptr_width) + ptr_pixel_col] = (desktop_buffer_u32[(ptr_pixel_row * desktop_buffer_pitch) + ptr_pixel_col] ^ shape_buffer_u32[(ptr_pixel_col + ptr_skip_x) + \n                                                                                    ((ptr_pixel_row + ptr_skip_y) * uint32_t(ptr_info.ShapeInfo.Pitch / sizeof(uint32_t)))]) | 0xFF000000;\n                }\n                else\n                {\n                    //mask_value was 0x00\n                    init_buffer_u32[(ptr_pixel_row * ptr_width) + ptr_pixel_col] = shape_buffer_u32[(ptr_pixel_col + ptr_skip_x) + \n                                                                                   ((ptr_pixel_row + ptr_skip_y) * uint32_t(ptr_info.ShapeInfo.Pitch / sizeof(uint32_t)))] | 0xFF000000;\n                }\n            }\n        }\n    }\n\n    //Unmap surface\n    hr = copy_surface->Unmap();\n    if (FAILED(hr))\n    {\n        return ProcessFailure(m_Device, L\"Failed to unmap surface for pointer\", L\"Desktop+ Error\", hr, SystemTransitionsExpectedErrors);\n    }\n\n    //Create texture\n    D3D11_TEXTURE2D_DESC tex_desc = {};\n    tex_desc.Width  = ptr_width;\n    tex_desc.Height = ptr_height;\n    tex_desc.MipLevels          = 1;\n    tex_desc.ArraySize          = 1;\n    tex_desc.Format             = DXGI_FORMAT_B8G8R8A8_UNORM;\n    tex_desc.SampleDesc.Count   = 1;\n    tex_desc.SampleDesc.Quality = 0;\n    tex_desc.Usage              = D3D11_USAGE_DEFAULT;\n    tex_desc.BindFlags          = D3D11_BIND_SHADER_RESOURCE;\n    tex_desc.CPUAccessFlags     = 0;\n    tex_desc.MiscFlags          = 0;\n\n    //Set shader resource properties\n    D3D11_SHADER_RESOURCE_VIEW_DESC srv_desc = {};\n    srv_desc.Format                    = tex_desc.Format;\n    srv_desc.ViewDimension             = D3D11_SRV_DIMENSION_TEXTURE2D;\n    srv_desc.Texture2D.MostDetailedMip = tex_desc.MipLevels - 1;\n    srv_desc.Texture2D.MipLevels       = tex_desc.MipLevels;\n\n    //Set up init data\n    D3D11_SUBRESOURCE_DATA init_data = {};\n    init_data.pSysMem     = init_buffer.get();\n    init_data.SysMemPitch = ptr_width * 4;\n\n    //Create mouse pointer texture\n    hr = m_Device->CreateTexture2D(&tex_desc, &init_data, &out_tex);\n    if (FAILED(hr))\n    {\n        return ProcessFailure(m_Device, L\"Failed to create mouse pointer texture\", L\"Desktop+ Error\", hr, SystemTransitionsExpectedErrors);\n    }\n\n    out_tex_format = tex_desc.Format;\n\n    return DUPL_RETURN_SUCCESS;\n}\n\nDUPL_RETURN OutputManager::ProcessMonoMaskFloat16(bool is_mono, PTR_INFO& ptr_info, int& ptr_width, int& ptr_height, int& ptr_left, int& ptr_top, \n                                                  Microsoft::WRL::ComPtr<ID3D11Texture2D>& out_tex, DXGI_FORMAT& out_tex_format, D3D11_BOX& box)\n{\n    out_tex_format = DXGI_FORMAT_UNKNOWN;\n\n    //ShapeBuffer can sometimes be empty when the secure desktop is active, skip\n    if (ptr_info.ShapeBuffer.empty())\n        return DUPL_RETURN_SUCCESS;\n\n    //Desktop dimensions\n    D3D11_TEXTURE2D_DESC FullDesc;\n    m_SharedSurf->GetDesc(&FullDesc);\n    int DesktopWidth  = FullDesc.Width;\n    int DesktopHeight = FullDesc.Height;\n\n    //Pointer position\n    int ptr_info_pos_left = ptr_info.Position.x;\n    int ptr_info_pos_top  = ptr_info.Position.y;\n\n    //Figure out if any adjustment is needed for out of bound positions\n    if (ptr_info_pos_left < 0)\n    {\n        ptr_width = ptr_info_pos_left + (int)ptr_info.ShapeInfo.Width;\n    }\n    else if ((ptr_info_pos_left + (int)ptr_info.ShapeInfo.Width) > DesktopWidth)\n    {\n        ptr_width = DesktopWidth - ptr_info_pos_left;\n    }\n    else\n    {\n        ptr_width = (int)ptr_info.ShapeInfo.Width;\n    }\n\n    if (is_mono)\n    {\n        ptr_info.ShapeInfo.Height = ptr_info.ShapeInfo.Height / 2;\n    }\n\n    if (ptr_info_pos_top < 0)\n    {\n        ptr_height = ptr_info_pos_top + (int)ptr_info.ShapeInfo.Height;\n    }\n    else if ((ptr_info_pos_top + (int)ptr_info.ShapeInfo.Height) > DesktopHeight)\n    {\n        ptr_height = DesktopHeight - ptr_info_pos_top;\n    }\n    else\n    {\n        ptr_height = (int)ptr_info.ShapeInfo.Height;\n    }\n\n    if (is_mono)\n    {\n        ptr_info.ShapeInfo.Height = ptr_info.ShapeInfo.Height * 2;\n    }\n\n    ptr_left = (ptr_info_pos_left < 0) ? 0 : ptr_info_pos_left;\n    ptr_top  = (ptr_info_pos_top < 0)  ? 0 : ptr_info_pos_top;\n\n    //Staging buffer/texture\n    D3D11_TEXTURE2D_DESC copy_buffer_desc = {};\n    copy_buffer_desc.Width              = ptr_width;\n    copy_buffer_desc.Height             = ptr_height;\n    copy_buffer_desc.MipLevels          = 1;\n    copy_buffer_desc.ArraySize          = 1;\n    copy_buffer_desc.Format             = DXGI_FORMAT_R16G16B16A16_FLOAT;\n    copy_buffer_desc.SampleDesc.Count   = 1;\n    copy_buffer_desc.SampleDesc.Quality = 0;\n    copy_buffer_desc.Usage              = D3D11_USAGE_STAGING;\n    copy_buffer_desc.BindFlags          = 0;\n    copy_buffer_desc.CPUAccessFlags     = D3D11_CPU_ACCESS_READ;\n    copy_buffer_desc.MiscFlags          = 0;\n\n    Microsoft::WRL::ComPtr<ID3D11Texture2D> copy_buffer;\n    HRESULT hr = m_Device->CreateTexture2D(&copy_buffer_desc, nullptr, &copy_buffer);\n    if (FAILED(hr))\n    {\n        return ProcessFailure(m_Device, L\"Failed creating staging texture for pointer\", L\"Desktop+ Error\", S_OK, SystemTransitionsExpectedErrors); //Shouldn't be critical\n    }\n\n    //Copy needed part of desktop image\n    box.left   = ptr_left;\n    box.top    = ptr_top;\n    box.right  = ptr_left + ptr_width;\n    box.bottom = ptr_top  + ptr_height;\n    m_DeviceContext->CopySubresourceRegion(copy_buffer.Get(), 0, 0, 0, 0, m_SharedSurf, 0, &box);\n\n    //QI for IDXGISurface\n    Microsoft::WRL::ComPtr<IDXGISurface> copy_surface;\n    hr = copy_buffer.As(&copy_surface);\n    if (FAILED(hr))\n    {\n        return ProcessFailure(nullptr, L\"Failed to QI staging texture into IDXGISurface for pointer\", L\"Desktop+ Error\", hr, SystemTransitionsExpectedErrors);\n    }\n\n    //Map pixels\n    DXGI_MAPPED_RECT mapped_surface;\n    hr = copy_surface->Map(&mapped_surface, DXGI_MAP_READ);\n    if (FAILED(hr))\n    {\n        return ProcessFailure(m_Device, L\"Failed to map surface for pointer\", L\"Error\", hr, SystemTransitionsExpectedErrors);\n    }\n\n    //New mouseshape buffer\n    auto init_buffer = std::unique_ptr<BYTE[]>{new (std::nothrow)BYTE[ptr_width * ptr_height * 4 * sizeof(PackedVector::HALF)]};\n    if (init_buffer == nullptr)\n    {\n        return ProcessFailure(nullptr, L\"Failed to allocate memory for new mouse shape buffer.\", L\"Desktop+ Error\", E_OUTOFMEMORY);\n    }\n\n    PackedVector::HALF* init_buffer_f16    = (PackedVector::HALF*)init_buffer.get();\n    PackedVector::HALF* desktop_buffer_f16 = (PackedVector::HALF*)mapped_surface.pBits;\n    int desktop_buffer_pitch               = mapped_surface.Pitch / sizeof(PackedVector::HALF);\n\n    //What to skip (pixel offset)\n    unsigned int ptr_skip_x = (ptr_info_pos_left < 0) ? (-1 * ptr_info_pos_left) : (0);\n    unsigned int ptr_skip_y = (ptr_info_pos_top < 0)  ? (-1 * ptr_info_pos_top)  : (0);\n    int ptr_height_half = ptr_info.ShapeInfo.Height / 2;\n\n    //While the float value of SDR white may not be 1.0 depending on OS and system settings, the cursor texture is always 8-bit per channel\n    //This might not be 100% accurate, but masked color cursors are also very rare, so we mostly care about XOR negative color effects working\n    const float sdr_white_level_adjustment = (m_DesktopHDRWhiteLevelAdjustments.empty()) ? 1.0f : \n                                              m_DesktopHDRWhiteLevelAdjustments[clamp((size_t)ptr_info.WhoUpdatedPositionLast, (size_t)0, m_DesktopHDRWhiteLevelAdjustments.size()-1)];\n\n    if (is_mono)\n    {\n        //Iterate through pointer shape pixels\n        for (size_t ptr_pixel_row = 0; ptr_pixel_row < ptr_height; ++ptr_pixel_row)\n        {\n            //Set mask\n            uint8_t mask_base = 0x80;\n            mask_base = mask_base >> (ptr_skip_x % 8);\n            for (size_t ptr_pixel_col = 0; ptr_pixel_col < ptr_width; ++ptr_pixel_col)\n            {\n                const int offset_in  = (ptr_pixel_row * desktop_buffer_pitch) + (ptr_pixel_col * 4);\n                const int offset_out = ((ptr_pixel_row * ptr_width) + (ptr_pixel_col)) * 4;\n\n                //Get masks using appropriate offsets\n                size_t mask_offset_base = ((ptr_pixel_col + ptr_skip_x) / 8);\n                BYTE mask_and = ptr_info.ShapeBuffer[mask_offset_base + ((ptr_pixel_row + ptr_skip_y) * ptr_info.ShapeInfo.Pitch)                  ] & mask_base;\n                BYTE mask_xor = ptr_info.ShapeBuffer[mask_offset_base + ((ptr_pixel_row + ptr_skip_y + ptr_height_half) * ptr_info.ShapeInfo.Pitch)] & mask_base;\n\n                //Set new pixel\n                float f32_value_r = (mask_and) ? PackedVector::XMConvertHalfToFloat(desktop_buffer_f16[offset_in])     : 0.0f;\n                float f32_value_g = (mask_and) ? PackedVector::XMConvertHalfToFloat(desktop_buffer_f16[offset_in + 1]) : 0.0f;\n                float f32_value_b = (mask_and) ? PackedVector::XMConvertHalfToFloat(desktop_buffer_f16[offset_in + 2]) : 0.0f;\n                float f32_value_a = (mask_and) ? PackedVector::XMConvertHalfToFloat(desktop_buffer_f16[offset_in + 3]) : 0.0f;\n\n                if (mask_xor)\n                {\n                    //Approximation for XOR negative color effect in non-linear space\n                    const float xor_neg = 0.77f / sdr_white_level_adjustment;\n                    init_buffer_f16[offset_out]     = PackedVector::XMConvertFloatToHalf( std::max(xor_neg - f32_value_r, 0.0f) );\n                    init_buffer_f16[offset_out + 1] = PackedVector::XMConvertFloatToHalf( std::max(xor_neg - f32_value_g, 0.0f) );\n                    init_buffer_f16[offset_out + 2] = PackedVector::XMConvertFloatToHalf( std::max(xor_neg - f32_value_b, 0.0f) );\n                }\n                else\n                {\n                    init_buffer_f16[offset_out]     = PackedVector::XMConvertFloatToHalf(f32_value_r);\n                    init_buffer_f16[offset_out + 1] = PackedVector::XMConvertFloatToHalf(f32_value_g);\n                    init_buffer_f16[offset_out + 2] = PackedVector::XMConvertFloatToHalf(f32_value_b);\n                }\n\n                init_buffer_f16[offset_out + 3] = desktop_buffer_f16[offset_in + 3];\n\n                //Adjust mask\n                mask_base = (mask_base == 0x01) ? 0x80 : mask_base >> 1;\n            }\n        }\n    }\n    else\n    {\n        uint32_t* shape_buffer_u32 = (uint32_t*)ptr_info.ShapeBuffer.data();\n\n        //Iterate through pointer shape pixels\n        for (size_t ptr_pixel_row = 0; ptr_pixel_row < ptr_height; ++ptr_pixel_row)\n        {\n            for (size_t ptr_pixel_col = 0; ptr_pixel_col < ptr_width; ++ptr_pixel_col)\n            {\n                // Set up mask\n                const int offset_in  = (ptr_pixel_row * desktop_buffer_pitch) + (ptr_pixel_col * 4);\n                const int offset_out = ((ptr_pixel_row * ptr_width) + (ptr_pixel_col)) * 4;\n\n                uint32_t mask_value = 0xFF000000 & shape_buffer_u32[(ptr_pixel_col + ptr_skip_x) + ((ptr_pixel_row + ptr_skip_y) * uint32_t(ptr_info.ShapeInfo.Pitch / sizeof(uint32_t)))];\n                if (mask_value)\n                {\n                    //mask_value was 0xFF\n\n                    //Cast float values to regular RGB ones and XOR them as intended (though this is still in linear color space)\n                    float f32_value_r = PackedVector::XMConvertHalfToFloat(desktop_buffer_f16[offset_in]);\n                    float f32_value_g = PackedVector::XMConvertHalfToFloat(desktop_buffer_f16[offset_in + 1]);\n                    float f32_value_b = PackedVector::XMConvertHalfToFloat(desktop_buffer_f16[offset_in + 2]);\n                    float f32_value_a = PackedVector::XMConvertHalfToFloat(desktop_buffer_f16[offset_in + 3]);\n\n                    uint32_t u32_value_r = f32_value_r * 255.0f * sdr_white_level_adjustment;\n                    uint32_t u32_value_g = f32_value_g * 255.0f * sdr_white_level_adjustment;\n                    uint32_t u32_value_b = f32_value_b * 255.0f * sdr_white_level_adjustment;\n\n                    uint32_t ptr_rgba = shape_buffer_u32[(ptr_pixel_col + ptr_skip_x) + ((ptr_pixel_row + ptr_skip_y) * uint32_t(ptr_info.ShapeInfo.Pitch / sizeof(uint32_t)))];\n                    uint32_t u32_buff_r = (ptr_rgba >> 16) & 0xFF;\n                    uint32_t u32_buff_g = (ptr_rgba >>  8) & 0xFF;\n                    uint32_t u32_buff_b =  ptr_rgba        & 0xFF;\n\n                    u32_value_r ^= u32_buff_r;\n                    u32_value_g ^= u32_buff_g;\n                    u32_value_b ^= u32_buff_b;\n\n                    //Cast them back again\n                    f32_value_r = u32_value_r / 255.0f / sdr_white_level_adjustment;\n                    f32_value_g = u32_value_g / 255.0f / sdr_white_level_adjustment;\n                    f32_value_b = u32_value_b / 255.0f / sdr_white_level_adjustment;\n\n                    init_buffer_f16[offset_out]     = PackedVector::XMConvertFloatToHalf(f32_value_r);\n                    init_buffer_f16[offset_out + 1] = PackedVector::XMConvertFloatToHalf(f32_value_g);\n                    init_buffer_f16[offset_out + 2] = PackedVector::XMConvertFloatToHalf(f32_value_b);\n                    init_buffer_f16[offset_out + 3] = desktop_buffer_f16[offset_in + 3];\n                }\n                else\n                {\n                    //mask_value was 0x00\n                    uint32_t ptr_rgb = shape_buffer_u32[(ptr_pixel_col + ptr_skip_x) + ((ptr_pixel_row + ptr_skip_y) * uint32_t(ptr_info.ShapeInfo.Pitch / sizeof(UINT)))];\n\n                    float f32_value_r = ((ptr_rgb >> 16) & 0xFF) / 255.0f / sdr_white_level_adjustment;\n                    float f32_value_g = ((ptr_rgb >>  8) & 0xFF) / 255.0f / sdr_white_level_adjustment;\n                    float f32_value_b =  (ptr_rgb        & 0xFF) / 255.0f / sdr_white_level_adjustment;\n\n                    init_buffer_f16[offset_out]     = PackedVector::XMConvertFloatToHalf( f32_value_r );\n                    init_buffer_f16[offset_out + 1] = PackedVector::XMConvertFloatToHalf( f32_value_g );\n                    init_buffer_f16[offset_out + 2] = PackedVector::XMConvertFloatToHalf( f32_value_b );\n                    init_buffer_f16[offset_out + 3] = desktop_buffer_f16[offset_in + 3];\n                }\n            }\n        }\n    }\n\n    //Unmap surface\n    hr = copy_surface->Unmap();\n    if (FAILED(hr))\n    {\n        return ProcessFailure(m_Device, L\"Failed to unmap surface for pointer\", L\"Error\", hr, SystemTransitionsExpectedErrors);\n    }\n\n    //Create texture\n    D3D11_TEXTURE2D_DESC tex_desc = {};\n    tex_desc.Width  = ptr_width;\n    tex_desc.Height = ptr_height;\n    tex_desc.MipLevels          = 1;\n    tex_desc.ArraySize          = 1;\n    tex_desc.Format             = DXGI_FORMAT_R16G16B16A16_FLOAT;\n    tex_desc.SampleDesc.Count   = 1;\n    tex_desc.SampleDesc.Quality = 0;\n    tex_desc.Usage              = D3D11_USAGE_DEFAULT;\n    tex_desc.BindFlags          = D3D11_BIND_SHADER_RESOURCE;\n    tex_desc.CPUAccessFlags     = 0;\n    tex_desc.MiscFlags          = 0;\n\n    //Set shader resource properties\n    D3D11_SHADER_RESOURCE_VIEW_DESC srv_desc = {};\n    srv_desc.Format                    = tex_desc.Format;\n    srv_desc.ViewDimension             = D3D11_SRV_DIMENSION_TEXTURE2D;\n    srv_desc.Texture2D.MostDetailedMip = tex_desc.MipLevels - 1;\n    srv_desc.Texture2D.MipLevels       = tex_desc.MipLevels;\n\n    //Set up init data\n    D3D11_SUBRESOURCE_DATA init_data = {};\n    init_data.pSysMem     = init_buffer.get();\n    init_data.SysMemPitch = ptr_width * 4 * (int)sizeof(PackedVector::HALF);\n\n    //Create mouse pointer texture\n    hr = m_Device->CreateTexture2D(&tex_desc, &init_data, &out_tex);\n    if (FAILED(hr))\n    {\n        return ProcessFailure(m_Device, L\"Failed to create mouse pointer texture\", L\"Desktop+ Error\", hr, SystemTransitionsExpectedErrors);\n    }\n\n    out_tex_format = tex_desc.Format;\n\n    return DUPL_RETURN_SUCCESS;\n}\n\n//\n// Reset render target view\n//\nDUPL_RETURN OutputManager::MakeRTV()\n{\n    D3D11_TEXTURE2D_DESC desc_ovrl_tex;\n    m_OvrlTex->GetDesc(&desc_ovrl_tex);\n\n    //Create render target view for overlay texture\n    D3D11_RENDER_TARGET_VIEW_DESC ovrl_tex_rtv_desc = {};\n\n    ovrl_tex_rtv_desc.Format = desc_ovrl_tex.Format;\n    ovrl_tex_rtv_desc.ViewDimension = D3D11_RTV_DIMENSION_TEXTURE2D;\n    ovrl_tex_rtv_desc.Texture2D.MipSlice = 0;\n\n    m_Device->CreateRenderTargetView(m_OvrlTex, &ovrl_tex_rtv_desc, &m_OvrlRTV);\n\n    return DUPL_RETURN_SUCCESS;\n}\n\n//\n// Initialize shaders for drawing\n//\nDUPL_RETURN OutputManager::InitShaders()\n{\n    HRESULT hr;\n\n    UINT Size = ARRAYSIZE(g_VS);\n    hr = m_Device->CreateVertexShader(g_VS, Size, nullptr, &m_VertexShader);\n    if (FAILED(hr))\n    {\n        return ProcessFailure(m_Device, L\"Failed to create vertex shader\", L\"Desktop+ Error\", hr, SystemTransitionsExpectedErrors);\n    }\n\n    D3D11_INPUT_ELEMENT_DESC Layout[] =\n    {\n        { \"POSITION\", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D11_INPUT_PER_VERTEX_DATA, 0 },\n        { \"TEXCOORD\", 0, DXGI_FORMAT_R32G32_FLOAT, 0, 12, D3D11_INPUT_PER_VERTEX_DATA, 0 }\n    };\n    UINT NumElements = ARRAYSIZE(Layout);\n    hr = m_Device->CreateInputLayout(Layout, NumElements, g_VS, Size, &m_InputLayout);\n    if (FAILED(hr))\n    {\n        return ProcessFailure(m_Device, L\"Failed to create input layout\", L\"Desktop+ Error\", hr, SystemTransitionsExpectedErrors);\n    }\n    m_DeviceContext->IASetInputLayout(m_InputLayout);\n\n    Size = ARRAYSIZE(g_PS);\n    hr = m_Device->CreatePixelShader(g_PS, Size, nullptr, &m_PixelShader);\n    if (FAILED(hr))\n    {\n        return ProcessFailure(m_Device, L\"Failed to create pixel shader\", L\"Desktop+ Error\", hr, SystemTransitionsExpectedErrors);\n    }\n    Size = ARRAYSIZE(g_PSCURSOR);\n    hr = m_Device->CreatePixelShader(g_PSCURSOR, Size, nullptr, &m_PixelShaderCursor);\n    if (FAILED(hr))\n    {\n        return ProcessFailure(m_Device, L\"Failed to create cursor pixel shader\", L\"Desktop+ Error\", hr, SystemTransitionsExpectedErrors);\n    }\n\n    return DUPL_RETURN_SUCCESS;\n}\n\n\n//\n// Recreate textures\n//\nDUPL_RETURN OutputManager::CreateTextures(INT SingleOutput, _Out_ UINT* OutCount, _Out_ RECT* DeskBounds)\n{\n    HRESULT hr;\n    *OutCount = 0;\n    const int desktop_count = m_DesktopRects.size();\n\n    //Output doesn't exist. This will result in a soft-error invalid output state (system may be in an transition state, in which case we'll automatically recover)\n    if (SingleOutput >= desktop_count) \n    {\n        m_DesktopX      = 0;\n        m_DesktopY      = 0;\n        m_DesktopWidth  = -1;\n        m_DesktopHeight = -1;\n\n        return DUPL_RETURN_ERROR_EXPECTED;\n    }\n\n    //Figure out right dimensions for full size desktop texture\n    DPRect output_rect_total;\n    if (SingleOutput < 0)\n    {\n        //Combined desktop, also count desktops on the used adapter\n        Microsoft::WRL::ComPtr<IDXGIAdapter> adapter_ptr;\n        adapter_ptr.Attach(GetDXGIAdapter());\n\n        UINT output_index_adapter = 0;\n        hr = S_OK;\n\n        while (SUCCEEDED(hr))\n        {\n            //Break early if used desktop count is lower than actual output count\n            if (output_index_adapter >= desktop_count)\n            {\n                ++output_index_adapter;\n                break;\n            }\n\n            Microsoft::WRL::ComPtr<IDXGIOutput> output_ptr;\n            hr = adapter_ptr->EnumOutputs(output_index_adapter, &output_ptr);\n            if ((output_ptr != nullptr) && (hr != DXGI_ERROR_NOT_FOUND))\n            {\n                DXGI_OUTPUT_DESC output_desc;\n                output_ptr->GetDesc(&output_desc);\n\n                DPRect output_rect(output_desc.DesktopCoordinates.left,  output_desc.DesktopCoordinates.top, \n                                   output_desc.DesktopCoordinates.right, output_desc.DesktopCoordinates.bottom);\n\n                (output_rect_total.GetWidth() == 0) ? output_rect_total = output_rect : output_rect_total.Add(output_rect);\n            }\n\n            ++output_index_adapter;\n        }\n\n        *OutCount = output_index_adapter - 1;\n    }\n    else\n    {\n        //Single desktop, grab cached desktop rect\n        if (SingleOutput < desktop_count)\n        {\n            output_rect_total = m_DesktopRects[SingleOutput];\n            *OutCount = 1;\n        }\n    }\n\n    //Store size and position\n    m_DesktopX      = output_rect_total.GetTL().x;\n    m_DesktopY      = output_rect_total.GetTL().y;\n    m_DesktopWidth  = output_rect_total.GetWidth();\n    m_DesktopHeight = output_rect_total.GetHeight();\n\n    DeskBounds->left   = output_rect_total.GetTL().x;\n    DeskBounds->top    = output_rect_total.GetTL().y;\n    DeskBounds->right  = output_rect_total.GetBR().x;\n    DeskBounds->bottom = output_rect_total.GetBR().y;\n\n    //Set it as mouse scale on the desktop texture overlay for the UI to read the resolution from there\n    vr::HmdVector2_t mouse_scale = {0};\n    mouse_scale.v[0] = m_DesktopWidth;\n    mouse_scale.v[1] = m_DesktopHeight;\n    vr::VROverlay()->SetOverlayMouseScale(m_OvrlHandleDesktopTexture, &mouse_scale);\n\n    //Create shared texture for all duplication threads to draw into\n    D3D11_TEXTURE2D_DESC TexD;\n    RtlZeroMemory(&TexD, sizeof(D3D11_TEXTURE2D_DESC));\n    TexD.Width            = m_DesktopWidth;\n    TexD.Height           = m_DesktopHeight;\n    TexD.MipLevels        = 1;\n    TexD.ArraySize        = 1;\n    TexD.Format           = ((m_OutputHDRAvailable) && (ConfigManager::GetValue(configid_bool_performance_hdr_mirroring))) ? DXGI_FORMAT_R16G16B16A16_FLOAT : DXGI_FORMAT_B8G8R8A8_UNORM;\n    TexD.SampleDesc.Count = 1;\n    TexD.Usage            = D3D11_USAGE_DEFAULT;\n    TexD.BindFlags        = D3D11_BIND_RENDER_TARGET | D3D11_BIND_SHADER_RESOURCE;\n    TexD.CPUAccessFlags   = 0;\n    TexD.MiscFlags        = D3D11_RESOURCE_MISC_SHARED_KEYEDMUTEX;\n\n    hr = m_Device->CreateTexture2D(&TexD, nullptr, &m_SharedSurf);\n\n    if (!FAILED(hr))\n    {\n        TexD.MiscFlags = 0;\n        hr = m_Device->CreateTexture2D(&TexD, nullptr, &m_OvrlTex);\n    }\n\n    if (FAILED(hr))\n    {\n        if (output_rect_total.GetWidth() != 0)\n        {\n            // If we are duplicating the complete desktop we try to create a single texture to hold the\n            // complete desktop image and blit updates from the per output DDA interface.  The GPU can\n            // always support a texture size of the maximum resolution of any single output but there is no\n            // guarantee that it can support a texture size of the desktop.\n            return ProcessFailure(m_Device, L\"Failed to create shared texture. Combined desktop texture size may be larger than the maximum supported supported size of the GPU\", L\"Desktop+ Error\", hr, SystemTransitionsExpectedErrors);\n        }\n        else\n        {\n            return ProcessFailure(m_Device, L\"Failed to create shared texture\", L\"Desktop+ Error\", hr, SystemTransitionsExpectedErrors);\n        }\n    }\n\n    // Get keyed mutex\n    hr = m_SharedSurf->QueryInterface(__uuidof(IDXGIKeyedMutex), reinterpret_cast<void**>(&m_KeyMutex));\n\n    if (FAILED(hr))\n    {\n        return ProcessFailure(m_Device, L\"Failed to query for keyed mutex\", L\"Desktop+ Error\", hr);\n    }\n\n    //Create shader resource for shared texture\n    D3D11_TEXTURE2D_DESC FrameDesc;\n    m_SharedSurf->GetDesc(&FrameDesc);\n\n    D3D11_SHADER_RESOURCE_VIEW_DESC ShaderDesc;\n    ShaderDesc.Format = FrameDesc.Format;\n    ShaderDesc.ViewDimension = D3D11_SRV_DIMENSION_TEXTURE2D;\n    ShaderDesc.Texture2D.MostDetailedMip = FrameDesc.MipLevels - 1;\n    ShaderDesc.Texture2D.MipLevels = FrameDesc.MipLevels;\n\n    // Create new shader resource view\n    hr = m_Device->CreateShaderResourceView(m_SharedSurf, &ShaderDesc, &m_ShaderResource);\n    if (FAILED(hr))\n    {\n        return ProcessFailure(m_Device, L\"Failed to create shader resource\", L\"Desktop+ Error\", hr, SystemTransitionsExpectedErrors);\n    }\n\n    //Create textures for multi GPU handling if needed\n    if (m_MultiGPUTargetDevice != nullptr)\n    {\n        //Staging texture\n        TexD.Usage          = D3D11_USAGE_STAGING;\n        TexD.BindFlags      = 0;\n        TexD.CPUAccessFlags = D3D11_CPU_ACCESS_READ;\n        TexD.MiscFlags      = 0;\n\n        hr = m_Device->CreateTexture2D(&TexD, nullptr, &m_MultiGPUTexStaging);\n\n        if (FAILED(hr))\n        {\n            return ProcessFailure(m_Device, L\"Failed to create staging texture\", L\"Desktop+ Error\", hr);\n        }\n\n        //Copy-target texture\n        TexD.Usage          = D3D11_USAGE_DYNAMIC;\n        TexD.BindFlags      = D3D11_BIND_SHADER_RESOURCE;\n        TexD.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE;\n        TexD.MiscFlags      = 0;\n\n        hr = m_MultiGPUTargetDevice->CreateTexture2D(&TexD, nullptr, &m_MultiGPUTexTarget);\n\n        if (FAILED(hr))\n        {\n            return ProcessFailure(m_MultiGPUTargetDevice, L\"Failed to create copy-target texture\", L\"Desktop+ Error\", hr);\n        }\n    }\n\n    return DUPL_RETURN_SUCCESS;\n}\n\nvoid OutputManager::DrawFrameToOverlayTex(bool clear_rtv)\n{\n    //Do a straight copy if there are no issues with that or do the alpha check if it's still pending\n    if ((!m_OutputAlphaCheckFailed) || (m_OutputAlphaChecksPending > 0))\n    {\n        m_DeviceContext->CopyResource(m_OvrlTex, m_SharedSurf);\n\n        if (m_OutputAlphaChecksPending > 0)\n        {\n            //Check for translucent pixels (not fast)\n            m_OutputAlphaCheckFailed = DesktopTextureAlphaCheck();\n            m_OutputAlphaChecksPending--;\n\n            LOG_IF_F(WARNING, (m_OutputAlphaCheckFailed) && (m_OutputAlphaChecksPending == 0), \"Failed Desktop Duplication alpha check, using extra render pass\");\n        }\n    }\n\n    //Draw the frame to the texture with the alpha channel fixing pixel shader if we have to\n    if (m_OutputAlphaCheckFailed)\n    {\n        // Set resources\n        UINT Stride = sizeof(VERTEX);\n        UINT Offset = 0;\n        const FLOAT blendFactor[4] = { 0.0f, 0.0f, 0.0f, 0.0f };\n        m_DeviceContext->OMSetBlendState(nullptr, blendFactor, 0xffffffff);\n        m_DeviceContext->OMSetRenderTargets(1, &m_OvrlRTV, nullptr);\n        m_DeviceContext->VSSetShader(m_VertexShader, nullptr, 0);\n        m_DeviceContext->PSSetShader(m_PixelShader, nullptr, 0);\n        m_DeviceContext->PSSetShaderResources(0, 1, &m_ShaderResource);\n        m_DeviceContext->PSSetSamplers(0, 1, &m_Sampler);\n        m_DeviceContext->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);\n\n        m_DeviceContext->IASetVertexBuffers(0, 1, &m_VertexBuffer, &Stride, &Offset);\n\n        // Draw textured quad onto render target\n        if (clear_rtv)\n        {\n            const float bgColor[4] = { 0.0f, 0.0f, 0.0f, 1.0f };\n            m_DeviceContext->ClearRenderTargetView(m_OvrlRTV, bgColor);\n        }\n\n        m_DeviceContext->Draw(NUMVERTICES, 0);\n    }\n}\n\n//\n// Draw mouse provided in buffer to overlay texture\n//\nDUPL_RETURN OutputManager::DrawMouseToOverlayTex(_In_ PTR_INFO* PtrInfo)\n{\n    //Just return if we don't need to render it\n    if ((!ConfigManager::GetValue(configid_bool_input_mouse_render_cursor)) || (!PtrInfo->Visible))\n    {\n        return DUPL_RETURN_SUCCESS;\n    }\n\n    ID3D11Buffer* VertexBuffer = nullptr;\n\n    // Vars to be used\n    D3D11_SUBRESOURCE_DATA InitData = {};\n    D3D11_TEXTURE2D_DESC Desc = {};\n    D3D11_SHADER_RESOURCE_VIEW_DESC SDesc = {};\n\n    // Position will be changed based on mouse position\n    VERTEX Vertices[NUMVERTICES] =\n    {\n        { XMFLOAT3(-1.0f, -1.0f, 0), XMFLOAT2(0.0f, 1.0f) },\n        { XMFLOAT3(-1.0f,  1.0f, 0), XMFLOAT2(0.0f, 0.0f) },\n        { XMFLOAT3(1.0f,  -1.0f, 0), XMFLOAT2(1.0f, 1.0f) },\n        { XMFLOAT3(1.0f,  -1.0f, 0), XMFLOAT2(1.0f, 1.0f) },\n        { XMFLOAT3(-1.0f,  1.0f, 0), XMFLOAT2(0.0f, 0.0f) },\n        { XMFLOAT3(1.0f,   1.0f, 0), XMFLOAT2(1.0f, 0.0f) },\n    };\n\n    // Center of desktop dimensions\n    FLOAT CenterX = (m_DesktopWidth  / 2.0f);\n    FLOAT CenterY = (m_DesktopHeight / 2.0f);\n\n    // Clipping adjusted coordinates / dimensions\n    INT PtrWidth  = 0;\n    INT PtrHeight = 0;\n    INT PtrLeft   = 0;\n    INT PtrTop    = 0;\n\n    // Buffer used if necessary (in case of monochrome or masked pointer)\n    Microsoft::WRL::ComPtr<ID3D11Texture2D> CursorTexNew;\n    DXGI_FORMAT CursorTexNewFormat = DXGI_FORMAT_UNKNOWN;\n\n    // Used for copying pixels if necessary\n    D3D11_BOX Box = {};\n    Box.front = 0;\n    Box.back  = 1;\n\n    //Process shape (or just get position when not new cursor)\n    switch (PtrInfo->ShapeInfo.Type)\n    {\n        case DXGI_OUTDUPL_POINTER_SHAPE_TYPE_COLOR:\n        {\n            PtrLeft = PtrInfo->Position.x;\n            PtrTop  = PtrInfo->Position.y;\n\n            PtrWidth  = static_cast<INT>(PtrInfo->ShapeInfo.Width);\n            PtrHeight = static_cast<INT>(PtrInfo->ShapeInfo.Height);\n\n            break;\n        }\n        case DXGI_OUTDUPL_POINTER_SHAPE_TYPE_MONOCHROME:\n        case DXGI_OUTDUPL_POINTER_SHAPE_TYPE_MASKED_COLOR:\n        {\n            PtrInfo->CursorShapeChanged = true; //Texture content is screen dependent\n            const bool is_mono_cursor = (PtrInfo->ShapeInfo.Type == DXGI_OUTDUPL_POINTER_SHAPE_TYPE_MONOCHROME);\n\n            //Process for HDR is needed\n            if ((m_OutputHDRAvailable) && (ConfigManager::GetValue(configid_bool_performance_hdr_mirroring)))\n            {\n                ProcessMonoMaskFloat16(is_mono_cursor, *PtrInfo, PtrWidth, PtrHeight, PtrLeft, PtrTop, CursorTexNew, CursorTexNewFormat, Box);\n            }\n            else\n            {\n                ProcessMonoMask(is_mono_cursor, *PtrInfo, PtrWidth, PtrHeight, PtrLeft, PtrTop, CursorTexNew, CursorTexNewFormat, Box);\n            }\n            \n            break;\n        }\n        default: break;\n    }\n\n    if (m_MouseCursorNeedsUpdate)\n    {\n        PtrInfo->CursorShapeChanged = true;\n    }\n\n    // VERTEX creation\n    Vertices[0].Pos.x = (PtrLeft - CenterX) / CenterX;\n    Vertices[0].Pos.y = -1 * ((PtrTop + PtrHeight) - CenterY) / CenterY;\n    Vertices[1].Pos.x = (PtrLeft - CenterX) / CenterX;\n    Vertices[1].Pos.y = -1 * (PtrTop - CenterY) / CenterY;\n    Vertices[2].Pos.x = ((PtrLeft + PtrWidth) - CenterX) / CenterX;\n    Vertices[2].Pos.y = -1 * ((PtrTop + PtrHeight) - CenterY) / CenterY;\n    Vertices[3].Pos.x = Vertices[2].Pos.x;\n    Vertices[3].Pos.y = Vertices[2].Pos.y;\n    Vertices[4].Pos.x = Vertices[1].Pos.x;\n    Vertices[4].Pos.y = Vertices[1].Pos.y;\n    Vertices[5].Pos.x = ((PtrLeft + PtrWidth) - CenterX) / CenterX;\n    Vertices[5].Pos.y = -1 * (PtrTop - CenterY) / CenterY;\n\n    //Vertex buffer description\n    D3D11_BUFFER_DESC BDesc;\n    ZeroMemory(&BDesc, sizeof(D3D11_BUFFER_DESC));\n    BDesc.Usage = D3D11_USAGE_DEFAULT;\n    BDesc.ByteWidth = sizeof(VERTEX) * NUMVERTICES;\n    BDesc.BindFlags = D3D11_BIND_VERTEX_BUFFER;\n    BDesc.CPUAccessFlags = 0;\n\n    ZeroMemory(&InitData, sizeof(D3D11_SUBRESOURCE_DATA));\n    InitData.pSysMem = Vertices;\n\n    // Create vertex buffer\n    HRESULT hr = m_Device->CreateBuffer(&BDesc, &InitData, &VertexBuffer);\n    if (FAILED(hr))\n    {\n        m_MouseShaderRes.Reset();\n        m_MouseTex.Reset();\n\n        return ProcessFailure(m_Device, L\"Failed to create mouse pointer vertex buffer in OutputManager\", L\"Desktop+ Error\", hr, SystemTransitionsExpectedErrors);\n    }\n\n    //It can occasionally happen that no cursor shape update is detected after resetting duplication, so the m_MouseTex check is more of a workaround, but unproblematic\n    if ( (PtrInfo->CursorShapeChanged) || (m_MouseTex == nullptr) ) \n    {\n        //Only create a texture here for regular color cursors (mask/mono were already created)\n        if (PtrInfo->ShapeInfo.Type == DXGI_OUTDUPL_POINTER_SHAPE_TYPE_COLOR)\n        {\n            Desc.Width              = PtrWidth;\n            Desc.Height             = PtrHeight;\n            Desc.MipLevels          = 1;\n            Desc.ArraySize          = 1;\n            Desc.Format             = DXGI_FORMAT_B8G8R8A8_UNORM;\n            Desc.SampleDesc.Count   = 1;\n            Desc.SampleDesc.Quality = 0;\n            Desc.Usage              = D3D11_USAGE_DEFAULT;\n            Desc.BindFlags          = D3D11_BIND_SHADER_RESOURCE;\n            Desc.CPUAccessFlags     = 0;\n            Desc.MiscFlags          = 0;\n\n            //Set up init data\n            InitData.pSysMem          = PtrInfo->ShapeBuffer.data();\n            InitData.SysMemPitch      = PtrInfo->ShapeInfo.Pitch;\n            InitData.SysMemSlicePitch = 0;\n\n            // Create mouseshape as texture\n            hr = m_Device->CreateTexture2D(&Desc, &InitData, &m_MouseTex);\n            if (FAILED(hr))\n            {\n                return ProcessFailure(m_Device, L\"Failed to create mouse pointer texture\", L\"Desktop+ Error\", hr, SystemTransitionsExpectedErrors);\n            }\n        }\n        else\n        {\n            m_MouseTex = CursorTexNew;\n        }\n\n        if (m_MouseTex != nullptr)\n        {\n            //Set shader resource properties\n            SDesc.Format                    = (PtrInfo->ShapeInfo.Type == DXGI_OUTDUPL_POINTER_SHAPE_TYPE_COLOR) ? DXGI_FORMAT_B8G8R8A8_UNORM : CursorTexNewFormat;\n            SDesc.ViewDimension             = D3D11_SRV_DIMENSION_TEXTURE2D;\n            SDesc.Texture2D.MostDetailedMip = 0;\n            SDesc.Texture2D.MipLevels       = 1;\n\n            // Create shader resource from texture\n            hr = m_Device->CreateShaderResourceView(m_MouseTex.Get(), &SDesc, &m_MouseShaderRes);\n            if (FAILED(hr))\n            {\n                m_MouseTex.Reset();\n                return ProcessFailure(m_Device, L\"Failed to create shader resource from mouse pointer texture\", L\"Desktop+ Error\", hr, SystemTransitionsExpectedErrors);\n            }\n        }\n    }\n\n    // Set resources\n    FLOAT BlendFactor[4] = { 0.f, 0.f, 0.f, 0.f };\n    UINT Stride = sizeof(VERTEX);\n    UINT Offset = 0;\n    m_DeviceContext->IASetVertexBuffers(0, 1, &VertexBuffer, &Stride, &Offset);\n    m_DeviceContext->OMSetBlendState(m_BlendState, BlendFactor, 0xFFFFFFFF);\n    m_DeviceContext->OMSetRenderTargets(1, &m_OvrlRTV, nullptr);\n    m_DeviceContext->VSSetShader(m_VertexShader, nullptr, 0);\n    m_DeviceContext->PSSetShader(m_PixelShaderCursor, nullptr, 0);\n    m_DeviceContext->PSSetShaderResources(0, 1, m_MouseShaderRes.GetAddressOf());\n    m_DeviceContext->PSSetSamplers(0, 1, &m_Sampler);\n\n    // Draw\n    m_DeviceContext->Draw(NUMVERTICES, 0);\n\n    // Clean\n    if (VertexBuffer)\n    {\n        VertexBuffer->Release();\n        VertexBuffer = nullptr;\n    }\n\n    m_MouseCursorNeedsUpdate = false;\n\n    return DUPL_RETURN_SUCCESS;\n}\n\nDUPL_RETURN_UPD OutputManager::RefreshOpenVROverlayTexture(DPRect& DirtyRectTotal, bool force_full_copy)\n{\n    if ((m_OvrlHandleDesktopTexture != vr::k_ulOverlayHandleInvalid) && (m_OvrlTex))\n    {\n        vr::Texture_t vrtex;\n        vrtex.eType       = vr::TextureType_DirectX;\n        vrtex.eColorSpace = ((m_OutputHDRAvailable) && (ConfigManager::GetValue(configid_bool_performance_hdr_mirroring))) ? vr::ColorSpace_Linear : vr::ColorSpace_Gamma;\n        vrtex.handle      = m_OvrlTex;\n\n        //The intermediate texture can be assumed to be not complete when a full copy is forced, so redraw that\n        if (force_full_copy)\n        {\n            //Try to acquire sync for shared surface needed by DrawFrameToOverlayTex()\n            HRESULT hr = m_KeyMutex->AcquireSync(0, m_MaxActiveRefreshDelay);\n            if (hr == static_cast<HRESULT>(WAIT_TIMEOUT))\n            {\n                //Another thread has the keyed mutex so there will be a new frame ready after this.\n                //Bail out and just set the pending dirty region to full so everything gets drawn over on the next update\n                m_OutputPendingDirtyRect = {0, 0, m_DesktopWidth, m_DesktopHeight};\n                return DUPL_RETURN_UPD_RETRY;\n            }\n            else if (FAILED(hr))\n            {\n                return (DUPL_RETURN_UPD)ProcessFailure(m_Device, L\"Failed to acquire keyed mutex\", L\"Desktop+ Error\", hr, SystemTransitionsExpectedErrors);\n            }\n\n            DrawFrameToOverlayTex(true);\n\n            //Release keyed mutex\n            hr = m_KeyMutex->ReleaseSync(0);\n            if (FAILED(hr))\n            {\n                return (DUPL_RETURN_UPD)ProcessFailure(m_Device, L\"Failed to Release keyed mutex\", L\"Desktop+ Error\", hr, SystemTransitionsExpectedErrors);\n            }\n\n            //We don't draw the cursor here as this can lead to tons of issues for little gain. We might not even know what the cursor looks like if it was cropped out previously, etc.\n            //We do mark where the cursor has last been seen as pending dirty region, however, so it gets updated at the next best moment even if it didn't move\n\n            if (m_MouseLastInfo.Visible)\n            {\n                m_OutputPendingDirtyRect = {    m_MouseLastInfo.Position.x, m_MouseLastInfo.Position.y, int(m_MouseLastInfo.Position.x + m_MouseLastInfo.ShapeInfo.Width),\n                                            int(m_MouseLastInfo.Position.y + m_MouseLastInfo.ShapeInfo.Height) };\n            }\n        }\n\n        //Copy texture over to GPU connected to VR HMD if needed\n        if (m_MultiGPUTargetDevice != nullptr)\n        {\n            //This isn't very fast but the only way to my knowledge. Happy to receive improvements on this though\n            m_DeviceContext->CopyResource(m_MultiGPUTexStaging, m_OvrlTex);\n\n            D3D11_MAPPED_SUBRESOURCE mapped_resource_staging;\n            RtlZeroMemory(&mapped_resource_staging, sizeof(D3D11_MAPPED_SUBRESOURCE));\n            HRESULT hr = m_DeviceContext->Map(m_MultiGPUTexStaging, 0, D3D11_MAP_READ, 0, &mapped_resource_staging);\n\n            if (FAILED(hr))\n            {\n                return (DUPL_RETURN_UPD)ProcessFailure(m_Device, L\"Failed to map staging texture\", L\"Desktop+ Error\", hr, SystemTransitionsExpectedErrors);\n            }\n\n            D3D11_MAPPED_SUBRESOURCE mapped_resource_target;\n            RtlZeroMemory(&mapped_resource_target, sizeof(D3D11_MAPPED_SUBRESOURCE));\n            hr = m_MultiGPUTargetDeviceContext->Map(m_MultiGPUTexTarget, 0, D3D11_MAP_WRITE_DISCARD, 0, &mapped_resource_target);\n\n            if (FAILED(hr))\n            {\n                return (DUPL_RETURN_UPD)ProcessFailure(m_MultiGPUTargetDevice, L\"Failed to map copy-target texture\", L\"Desktop+ Error\", hr, SystemTransitionsExpectedErrors);\n            }\n\n            memcpy(mapped_resource_target.pData, mapped_resource_staging.pData, m_DesktopHeight * mapped_resource_staging.RowPitch);\n\n            m_DeviceContext->Unmap(m_MultiGPUTexStaging, 0);\n            m_MultiGPUTargetDeviceContext->Unmap(m_MultiGPUTexTarget, 0);\n\n            vrtex.handle = m_MultiGPUTexTarget;\n        }\n\n        //Do a simple full copy (done below) if the rect covers the whole texture (this isn't slower than a full rect copy and works with size changes)\n        force_full_copy = ( (force_full_copy) || (DirtyRectTotal.Contains({0, 0, m_DesktopWidth, m_DesktopHeight})) );\n\n        if (!force_full_copy) //Otherwise do a partial copy\n        {\n            //Get overlay texture from OpenVR and copy dirty rect directly into it\n            ID3D11Texture2D* reference_texture = (ID3D11Texture2D*)vrtex.handle;\n            ID3D11ShaderResourceView* ovrl_shader_rsv;\n\n            ovrl_shader_rsv = vr::VROverlayEx()->GetOverlayTextureEx(m_OvrlHandleDesktopTexture, reference_texture);\n\n            if (ovrl_shader_rsv != nullptr)\n            {\n                ID3D11DeviceContext* device_context = (m_MultiGPUTargetDevice != nullptr) ? m_MultiGPUTargetDeviceContext : m_DeviceContext;\n\n                Microsoft::WRL::ComPtr<ID3D11Resource> ovrl_tex;\n                ovrl_shader_rsv->GetResource(&ovrl_tex);\n\n                D3D11_BOX box = {0};\n                box.left   = DirtyRectTotal.GetTL().x;\n                box.top    = DirtyRectTotal.GetTL().y;\n                box.front  = 0;\n                box.right  = DirtyRectTotal.GetBR().x;\n                box.bottom = DirtyRectTotal.GetBR().y;\n                box.back   = 1;\n\n                device_context->CopySubresourceRegion(ovrl_tex.Get(), 0, box.left, box.top, 0, reference_texture, 0, &box);\n\n                //RSV is kept around by IVROverlayEx and not released here\n            }\n            else //Usually shouldn't fail, but fall back to full copy then\n            {\n                force_full_copy = true;\n            }\n        }\n\n        if (force_full_copy) //This is down here so a failed partial copy is picked up as well\n        {\n            bool refresh_shared_texture = false;\n            vr::VROverlayEx()->SetOverlayTextureEx(m_OvrlHandleDesktopTexture, &vrtex, {m_DesktopWidth, m_DesktopHeight}, &refresh_shared_texture);\n\n            //Apply potential texture change to all overlays and notify them of duplication update\n            for (unsigned int i = 0; i < OverlayManager::Get().GetOverlayCount(); ++i)\n            {\n                Overlay& overlay = OverlayManager::Get().GetOverlay(i);\n\n                if (refresh_shared_texture)\n                {\n                    overlay.AssignDesktopDuplicationTexture();\n                }\n\n                overlay.OnDesktopDuplicationUpdate();\n            }\n        }\n        else\n        {\n            //Notifiy all overlays of duplication update\n            for (unsigned int i = 0; i < OverlayManager::Get().GetOverlayCount(); ++i)\n            {\n                OverlayManager::Get().GetOverlay(i).OnDesktopDuplicationUpdate();\n            }\n        }\n    }\n\n    return DUPL_RETURN_UPD_SUCCESS_REFRESHED_OVERLAY;\n}\n\nbool OutputManager::DesktopTextureAlphaCheck()\n{\n    if (m_DesktopRects.empty())\n        return false;\n\n    //Sanity check texture dimensions\n    D3D11_TEXTURE2D_DESC desc_ovrl_tex;\n    m_OvrlTex->GetDesc(&desc_ovrl_tex);\n\n    if ( ((UINT)m_DesktopWidth != desc_ovrl_tex.Width) || ((UINT)m_DesktopHeight != desc_ovrl_tex.Height) )\n        return false;\n\n    //Read one pixel for each desktop\n    const int pixel_count = m_DesktopRects.size();\n\n    //Create a staging texture\n    D3D11_TEXTURE2D_DESC desc = {0};\n    desc.Width              = pixel_count;\n    desc.Height             = 1;\n    desc.MipLevels          = 1;\n    desc.ArraySize          = 1;\n    desc.Format             = desc_ovrl_tex.Format;\n    desc.SampleDesc.Count   = 1;\n    desc.SampleDesc.Quality = 0;\n    desc.Usage              = D3D11_USAGE_STAGING;\n    desc.CPUAccessFlags     = D3D11_CPU_ACCESS_READ;\n    desc.BindFlags          = 0;\n    desc.MiscFlags          = 0;\n\n    Microsoft::WRL::ComPtr<ID3D11Texture2D> tex_staging;\n    HRESULT hr = m_Device->CreateTexture2D(&desc, nullptr, &tex_staging);\n    if (FAILED(hr))\n    {\n        return false;\n    }\n\n    //Copy a single pixel to staging texture for each desktop\n    D3D11_BOX box = {0};\n    box.front = 0;\n    box.back  = 1;\n\n    UINT dst_x = 0;\n    for (const DPRect& rect : m_DesktopRects)\n    {\n        box.left   = clamp(rect.GetTL().x - m_DesktopX, 0, m_DesktopWidth - 1);\n        box.right  = clamp(box.left + 1, 1u, (UINT)m_DesktopWidth);\n        box.top    = clamp(rect.GetTL().y - m_DesktopY, 0, m_DesktopHeight - 1);\n        box.bottom = clamp(box.top + 1, 1u, (UINT)m_DesktopHeight);\n\n        m_DeviceContext->CopySubresourceRegion(tex_staging.Get(), 0, dst_x, 0, 0, m_OvrlTex, 0, &box);\n        dst_x++;\n    }\n\n    //Map texture and get the pixels we just copied\n    D3D11_MAPPED_SUBRESOURCE mapped_resource;\n    hr = m_DeviceContext->Map(tex_staging.Get(), 0, D3D11_MAP_READ, 0, &mapped_resource);\n    if (FAILED(hr))\n    {\n        return false;\n    }\n\n    //Check alpha value for anything between 0% and 100% transparency, which should not happen but apparently does\n    bool ret = false;\n\n    if (desc.Format == DXGI_FORMAT_B8G8R8A8_UNORM)\n    {\n        for (int i = 0; i < pixel_count * 4; i += 4)\n        {\n            unsigned char a = ((unsigned char*)mapped_resource.pData)[i + 3];\n\n            if ((a > 0) && (a < 255))\n            {\n                ret = true;\n                break;\n            }\n        }\n    }\n    else if (desc.Format == DXGI_FORMAT_R16G16B16A16_FLOAT)\n    {\n        for (int i = 0; i < pixel_count * 4; i += 4)\n        {\n            PackedVector::HALF a_half = ((PackedVector::HALF*)mapped_resource.pData)[i + 3];\n            float a = PackedVector::XMConvertHalfToFloat(a_half);\n\n            if ((a > 0.0f) && (a < 1.0f))\n            {\n                ret = true;\n                break;\n            }\n        }\n    }\n\n    //Cleanup\n    m_DeviceContext->Unmap(tex_staging.Get(), 0);\n\n    return ret;\n}\n\nbool OutputManager::HandleOpenVREvents()\n{\n    vr::VREvent_t vr_event;\n\n    //Handle Dashboard dummy ones first\n    while (vr::VROverlay()->PollNextOverlayEvent(m_OvrlHandleDashboardDummy, &vr_event, sizeof(vr_event)))\n    {\n        switch (vr_event.eventType)\n        {\n            case vr::VREvent_OverlayShown:\n            {\n                if (!m_OvrlDashboardActive)\n                {\n                    m_OvrlDashboardActive = true;\n                    m_DashboardActivatedOnce = true;    //Bringing up the Desktop+ also fixes what we work around with by keeping track of this\n\n                    for (unsigned int i = 0; i < OverlayManager::Get().GetOverlayCount(); ++i)\n                    {\n                        const OverlayConfigData& data = OverlayManager::Get().GetConfigData(i);\n\n                        if (data.ConfigInt[configid_int_overlay_display_mode] != ovrl_dispmode_scene)\n                        {\n                            ShowOverlay(i);\n                        }\n                    }\n\n                    m_BackgroundOverlay.Update();\n                    m_OverlayDragger.UpdateTempStandingPosition();\n\n                    if (ConfigManager::GetValue(configid_bool_interface_dim_ui))\n                    {\n                        DimDashboard(true);\n                    }\n                }\n\n                break;\n            }\n            case vr::VREvent_OverlayHidden:\n            {\n                if (m_OvrlDashboardActive)\n                {\n                    m_OvrlDashboardActive = false;\n\n                    for (unsigned int i = 0; i < OverlayManager::Get().GetOverlayCount(); ++i)\n                    {\n                        const OverlayConfigData& data = OverlayManager::Get().GetConfigData(i);\n\n                        if (data.ConfigInt[configid_int_overlay_display_mode] == ovrl_dispmode_dplustab)\n                        {\n                            HideOverlay(i);\n                        }\n                    }\n\n                    m_BackgroundOverlay.Update();\n\n                    if (ConfigManager::GetValue(configid_bool_interface_dim_ui))\n                    {\n                        DimDashboard(false);\n                    }\n                }\n\n                break;\n            }\n            case vr::VREvent_DashboardActivated:\n            {\n                //The dashboard transform we're using basically cannot be trusted to be correct unless the dashboard has been manually brought up once.\n                //On launch, SteamVR activates the dashboard automatically. Sometimes with and sometimes without this event firing.\n                //In that case, the primary dashboard device is the HMD (or sometimes just invalid). That means we can be sure other dashboard device's activations are user-initiated.\n                //We simply don't show dashboard-origin overlays until the dashboard has been properly activated once.\n                //For HMD-only usage, switching to the Desktop+ dashboard tab also works\n                if ( (!m_DashboardActivatedOnce) && (vr::VROverlay()->GetPrimaryDashboardDevice() != vr::k_unTrackedDeviceIndex_Hmd)  && \n                                                    (vr::VROverlay()->GetPrimaryDashboardDevice() != vr::k_unTrackedDeviceIndexInvalid) )\n                {\n                    m_DashboardActivatedOnce = true;\n                }\n\n                //Get current HMD y-position, used for getting the overlay position\n                UpdateDashboardHMD_Y();\n\n                //Hacky workaround, need to wait for the dashboard to finish appearing when not in Desktop+ tab\n                if (!m_OvrlDashboardActive)\n                {\n                    ::Sleep(300);\n                }\n\n                for (unsigned int i = 0; i < OverlayManager::Get().GetOverlayCount(); ++i)\n                {\n                    const OverlayConfigData& data = OverlayManager::Get().GetConfigData(i);\n\n                    if ((m_DashboardActivatedOnce) && (data.ConfigInt[configid_int_overlay_display_mode] == ovrl_dispmode_dashboard))\n                    {\n                        ShowOverlay(i);\n                    }\n                    else if (data.ConfigInt[configid_int_overlay_display_mode] == ovrl_dispmode_scene)\n                    {\n                        HideOverlay(i);\n                    }\n                    else if (data.ConfigInt[configid_int_overlay_origin] == ovrl_origin_dashboard) //Dashboard origin with Always display mode, update pos\n                    {\n                        unsigned int current_overlay_old = OverlayManager::Get().GetCurrentOverlayID();\n                        OverlayManager::Get().SetCurrentOverlayID(i);\n                        ApplySettingTransform();\n                        OverlayManager::Get().SetCurrentOverlayID(current_overlay_old);\n                    }\n                }\n\n                //Finish a direct drag if one's going as the Desktop+ laser pointer will not be told inputs are released when the dashboard is up\n                if (m_OvrlDirectDragActive)\n                {\n                    OverlayDirectDragFinish(m_OverlayDragger.GetDragOverlayID());\n                }\n\n                IPCManager::Get().PostConfigMessageToUIApp(configid_int_state_drag_hint_type, 0);\n                break;\n            }\n            case vr::VREvent_DashboardDeactivated:\n            {\n                for (unsigned int i = 0; i < OverlayManager::Get().GetOverlayCount(); ++i)\n                {\n                    OverlayConfigData& data = OverlayManager::Get().GetConfigData(i);\n\n                    if (data.ConfigInt[configid_int_overlay_display_mode] == ovrl_dispmode_scene)\n                    {\n                        ShowOverlay(i);\n                    }\n                    else if (data.ConfigInt[configid_int_overlay_display_mode] == ovrl_dispmode_dashboard)\n                    {\n                        HideOverlay(i);\n                    }\n                }\n\n                if (ConfigManager::GetValue(configid_bool_windows_auto_focus_scene_app_dashboard))\n                {\n                    WindowManager::Get().FocusActiveVRSceneApp(&m_InputSim);\n                }\n\n                //In unfortunate situations we can have a target window set and close the dashboard without getting a mouse up event ever, \n                //so we reset the target and mouse on dashboard close\n                if (WindowManager::Get().GetTargetWindow() != nullptr)\n                {\n                    m_InputSim.MouseSetLeftDown(false);\n                    WindowManager::Get().SetTargetWindow(nullptr);\n                }\n\n                //Finish a temp drag if one's going\n                DetachedTempDragFinish();\n\n                //If there is a drag going with a dashboard origin overlay (or related display mode), cancel or finish it to avoid the overlay ending up in a random spot\n                if ( (m_OverlayDragger.IsDragActive()) || (m_OverlayDragger.IsDragGestureActive()) )\n                {\n                    const OverlayConfigData& data = OverlayManager::Get().GetConfigData(m_OverlayDragger.GetDragOverlayID());\n\n                    if ( (data.ConfigInt[configid_int_overlay_origin] == ovrl_origin_dashboard) || (data.ConfigInt[configid_int_overlay_display_mode] == ovrl_dispmode_dashboard) || \n                         (data.ConfigInt[configid_int_overlay_display_mode] == ovrl_dispmode_dplustab) )\n                    { \n                        if (m_OverlayDragger.IsDragActive())\n                        {\n                            unsigned int current_overlay_old = OverlayManager::Get().GetCurrentOverlayID();\n                            OverlayManager::Get().SetCurrentOverlayID(m_OverlayDragger.GetDragOverlayID());\n\n                            OnDragFinish();\n                            m_OverlayDragger.DragCancel();  //Overlay can still disappear, so we just cancel the drag instead\n\n                            ApplySettingTransform();\n\n                            OverlayManager::Get().SetCurrentOverlayID(current_overlay_old);\n                        }\n                        else if (m_OverlayDragger.IsDragGestureActive())\n                        {\n                            m_OverlayDragger.DragGestureFinish();\n                        }\n                    }\n                }\n\n                IPCManager::Get().PostConfigMessageToUIApp(configid_int_state_drag_hint_type, 0);\n                break;\n            }\n            case vr::VREvent_SeatedZeroPoseReset:\n            case vr::VREvent_ChaperoneUniverseHasChanged:\n            {\n                DetachedTransformUpdateSeatedPosition();\n                break;\n            }\n            case vr::VREvent_SceneApplicationChanged:\n            {\n                DetachedTransformUpdateSeatedPosition();\n\n                const bool loaded_overlay_profile = ConfigManager::Get().GetAppProfileManager().ActivateProfileForProcess(vr_event.data.process.pid);\n\n                if (loaded_overlay_profile)\n                    ResetOverlays();\n\n                break;\n            }\n            case vr::VREvent_Input_ActionManifestReloaded:\n            case vr::VREvent_Input_BindingsUpdated:\n            case vr::VREvent_Input_BindingLoadSuccessful:\n            {\n                m_VRInput.RefreshAnyGlobalActionBound();\n                break;\n            }\n            case vr::VREvent_TrackedDeviceActivated:\n            {\n                m_VRInput.RefreshAnyGlobalActionBound();\n\n                //Apply transforms of all device origin overlays in case a previously unavailable device was used by one of them\n                unsigned int current_overlay_old = OverlayManager::Get().GetCurrentOverlayID();\n                for (unsigned int i = 0; i < OverlayManager::Get().GetOverlayCount(); ++i)\n                {\n                    const OverlayConfigData& data = OverlayManager::Get().GetConfigData(i);\n\n                    if ((data.ConfigInt[configid_int_overlay_origin] >= ovrl_origin_left_hand) && (data.ConfigInt[configid_int_overlay_origin] <= ovrl_origin_aux))\n                    {\n                        OverlayManager::Get().SetCurrentOverlayID(i);\n                        ApplySettingTransform();\n                    }\n                }\n\n                OverlayManager::Get().SetCurrentOverlayID(current_overlay_old);\n                break;\n            }\n            case vr::VREvent_TrackedDeviceDeactivated:\n            {\n                m_VRInput.RefreshAnyGlobalActionBound();\n                m_LaserPointer.RemoveDevice(vr_event.trackedDeviceIndex);\n\n                IPCManager::Get().PostConfigMessageToUIApp(configid_int_state_drag_hint_type, 0);\n                break;\n            }\n            case vr::VREvent_Quit:\n            {\n                return true;\n            }\n        }\n    }\n\n    //Now handle events for the actual overlays\n    int overlay_focus_count = (m_OvrlInputActive) ? 1 : 0;  //Keep track of multiple overlay focus enter/leave happening within the same frame to set m_OvrlInputActive correctly afterwards\n\n    unsigned int current_overlay_old = OverlayManager::Get().GetCurrentOverlayID();\n    for (unsigned int i = 0; i < OverlayManager::Get().GetOverlayCount(); ++i)\n    {\n        OverlayManager::Get().SetCurrentOverlayID(i);\n\n        Overlay& overlay = OverlayManager::Get().GetCurrentOverlay();\n        vr::VROverlayHandle_t ovrl_handle = overlay.GetHandle();\n        const OverlayConfigData& data = OverlayManager::Get().GetCurrentConfigData();\n\n        while (vr::VROverlay()->PollNextOverlayEvent(ovrl_handle, &vr_event, sizeof(vr_event)))\n        {\n            switch (vr_event.eventType)\n            {\n                case vr::VREvent_MouseMove:\n                case vr::VREvent_MouseButtonDown:\n                case vr::VREvent_MouseButtonUp:\n                case vr::VREvent_ScrollDiscrete:\n                case vr::VREvent_ScrollSmooth:\n                {\n                    OnOpenVRMouseEvent(vr_event, current_overlay_old);\n                    break;\n                }\n                case vr::VREvent_ButtonPress:\n                {\n                    if (vr_event.data.controller.button == Button_Dashboard_GoHome)\n                    {\n                        ConfigManager::Get().GetActionManager().StartAction(ConfigManager::GetValue(configid_handle_input_go_home_action_uid), i);\n                    }\n                    else if (vr_event.data.controller.button == Button_Dashboard_GoBack)\n                    {\n                        ConfigManager::Get().GetActionManager().StartAction(ConfigManager::GetValue(configid_handle_input_go_back_action_uid), i);\n                    }\n\n                    break;\n                }\n                case vr::VREvent_ButtonUnpress:\n                {\n                    if (vr_event.data.controller.button == Button_Dashboard_GoHome)\n                    {\n                        ConfigManager::Get().GetActionManager().StopAction(ConfigManager::GetValue(configid_handle_input_go_home_action_uid), i);\n                    }\n                    else if (vr_event.data.controller.button == Button_Dashboard_GoBack)\n                    {\n                        ConfigManager::Get().GetActionManager().StopAction(ConfigManager::GetValue(configid_handle_input_go_back_action_uid), i);\n                    }\n\n                    break;\n                }\n                case vr::VREvent_FocusEnter:\n                {\n                    overlay_focus_count++;\n\n                    const bool drag_or_select_mode_enabled = ( (ConfigManager::GetValue(configid_bool_state_overlay_dragmode)) || (ConfigManager::GetValue(configid_bool_state_overlay_selectmode)) );\n\n                    if (!drag_or_select_mode_enabled)\n                    {\n                        if (ConfigManager::Get().GetPrimaryLaserPointerDevice() == vr::k_unTrackedDeviceIndex_Hmd)\n                        {\n                            ResetMouseLastLaserPointerPos();\n                        }\n\n                        //If it's a WinRT window capture, check for window management stuff\n                        if ( (overlay.GetTextureSource() == ovrl_texsource_winrt_capture) && (data.ConfigHandle[configid_handle_overlay_state_winrt_hwnd] != 0) )\n                        {\n                            if ( (!m_MouseIgnoreMoveEvent) && (ConfigManager::GetValue(configid_bool_windows_winrt_auto_focus)) )\n                            {\n                                WindowManager::Get().RaiseAndFocusWindow((HWND)data.ConfigHandle[configid_handle_overlay_state_winrt_hwnd], &m_InputSim);\n                            }\n\n                            if (ConfigManager::GetValue(configid_bool_windows_winrt_keep_on_screen))\n                            {\n                                WindowManager::MoveWindowIntoWorkArea((HWND)data.ConfigHandle[configid_handle_overlay_state_winrt_hwnd]);\n                            }\n                        }\n                    }\n\n                    break;\n                }\n                case vr::VREvent_FocusLeave:\n                {\n                    overlay_focus_count--;\n\n                    const bool drag_or_select_mode_enabled = ( (ConfigManager::GetValue(configid_bool_state_overlay_dragmode)) || (ConfigManager::GetValue(configid_bool_state_overlay_selectmode)) );\n\n                    if (!drag_or_select_mode_enabled)\n                    {\n                        //If leaving a WinRT window capture and the option is enabled, focus the active scene app\n                        if ( (!m_MouseIgnoreMoveEvent) && (ConfigManager::GetValue(configid_bool_windows_winrt_auto_focus_scene_app)) &&\n                             (overlay.GetTextureSource() == ovrl_texsource_winrt_capture) && (data.ConfigHandle[configid_handle_overlay_state_winrt_hwnd] != 0) )\n                        {\n                            WindowManager::Get().FocusActiveVRSceneApp(&m_InputSim);\n                        }\n\n                        //A resize while drag can make the pointer lose focus, which is pretty janky. Remove target and do mouse up at least.\n                        if (WindowManager::Get().GetTargetWindow() != nullptr)\n                        {\n                            const bool use_pen = ConfigManager::GetValue(configid_bool_input_mouse_simulate_pen_input);\n                            (use_pen) ? m_InputSim.PenSetPrimaryDown(false) : m_InputSim.MouseSetLeftDown(false);\n\n                            WindowManager::Get().SetTargetWindow(nullptr);\n                        }\n\n                        WindowManager::Get().ClearTempTopMostWindow();\n                    }\n\n                    //Finish drag if there's somehow still one going (and not temp drag mode, where this is expected)\n                    if ( (m_OverlayDragger.IsDragActive()) && (!ConfigManager::GetValue(configid_bool_state_overlay_dragmode_temp)) )\n                    {\n                        OnDragFinish();\n                        m_OverlayDragger.DragFinish();\n\n                        ApplySettingTransform();\n                    }\n\n                    //For browser overlays, forward leave event to browser process\n                    if (overlay.GetTextureSource() == ovrl_texsource_browser)\n                    {\n                        DPBrowserAPIClient::Get().DPBrowser_MouseLeave(overlay.GetHandle());\n                        break;\n                    }\n\n                    m_InputSim.PenLeave();\n\n                    break;\n                }\n                case vr::VREvent_OverlayClosed:\n                case vr::VREvent_OverlayHidden:\n                {\n                    //Theater overlay was hidden by something (close button, other overlay taking over, etc.), disable it\n                    //There may be cases where the overlay doesn't send the hidden event for this (varies between SteamVR builds), for that we also have a hack further below,\n                    //though it's mostly harmless if this doesn't work (phantom dashboard tab)\n                    if (OverlayManager::Get().GetTheaterOverlayID() == i)\n                    {\n                        SetOverlayEnabled(i, false);\n                    }\n                    break;\n                }\n                case vr::VREvent_ChaperoneUniverseHasChanged:\n                {\n                    //We also get this when tracking is lost, which ends up updating the dashboard position\n                    if (m_OvrlActiveCount != 0)\n                    {\n                        ApplySettingTransform();\n                    }\n                    break;\n                }\n                default:\n                {\n                    //Output unhandled events when looking for something useful\n                    /*std::wstringstream ss;\n                    ss << L\"Event: \" << (int)vr_event.eventType << L\"\\n\";\n                    OutputDebugString(ss.str().c_str());*/\n                    break;\n                }\n            }\n        }\n    }\n\n    OverlayManager::Get().SetCurrentOverlayID(current_overlay_old);\n\n    m_OvrlInputActive = (overlay_focus_count > 0);\n\n    //Handle stuff coming from SteamVR Input\n    m_VRInput.Update();\n\n    //Handle Enable Global Laser Pointer binding\n    vr::InputDigitalActionData_t enable_laser_pointer_state = m_VRInput.GetEnableGlobalLaserPointerState();\n    if (enable_laser_pointer_state.bChanged)\n    {\n        if (enable_laser_pointer_state.bState)\n        {\n            if (!m_LaserPointer.IsActive())  //Don't switch devices if the pointer is already active\n            {\n                //Get tracked device index from origin\n                vr::InputOriginInfo_t origin_info = m_VRInput.GetOriginTrackedDeviceInfoEx(enable_laser_pointer_state.activeOrigin);\n\n                if (origin_info.trackedDeviceIndex != vr::k_unTrackedDeviceIndexInvalid)\n                {\n                    m_LaserPointer.SetActiveDevice(origin_info.trackedDeviceIndex, dplp_activation_origin_input_binding);\n\n                    //Disable active laser pointer override so the device's pointer can be used right away\n                    m_MouseIgnoreMoveEvent = false;\n                    ResetMouseLastLaserPointerPos();\n                }\n            }\n        }\n        else if (m_LaserPointer.GetActivationOrigin() == dplp_activation_origin_input_binding)\n        {\n            m_LaserPointer.ClearActiveDevice();\n        }\n    }\n\n    m_VRInput.HandleGlobalActionShortcuts(*this);\n\n    //Finish up pending keyboard input collected into the queue\n    m_InputSim.KeyboardTextFinish();\n\n    HandleHotkeys();\n    HandleKeyboardAutoVisibility();\n\n    //Update position if necessary\n    bool dashboard_origin_was_updated = false;\n    if (HasDashboardMoved()) //The dashboard can move from events we can't detect, like putting the HMD back on, so we check manually as a workaround\n    {\n        UpdateDashboardHMD_Y();\n        dashboard_origin_was_updated = true;\n    }\n\n    bool transform_frame_update_was_done = false; //Frame transform updates need to track the last time they were done, but only once for all overlays\n    for (unsigned int i = 0; i < OverlayManager::Get().GetOverlayCount(); ++i)\n    {\n        OverlayManager::Get().SetCurrentOverlayID(i);\n        Overlay& overlay = OverlayManager::Get().GetCurrentOverlay();\n        const OverlayConfigData& data = OverlayManager::Get().GetCurrentConfigData();\n\n        if (data.ConfigBool[configid_bool_overlay_enabled])\n        {\n            if (overlay.IsVisible())\n            {\n                if (m_OverlayDragger.GetDragOverlayID() == overlay.GetID())\n                {\n                    if (m_OverlayDragger.IsDragActive())\n                    {\n                        m_OverlayDragger.DragUpdate();\n                    }\n                    else if (m_OverlayDragger.IsDragGestureActive())\n                    {\n                        m_OverlayDragger.DragGestureUpdate();\n                    }\n                }\n                else if ((data.ConfigInt[configid_int_overlay_origin] == ovrl_origin_hmd_floor) || (data.ConfigInt[configid_int_overlay_origin] == ovrl_origin_hmd))\n                {\n                    if (DetachedTransformFrameUpdate())\n                    {\n                        transform_frame_update_was_done = true;\n                    }\n                }\n                else if ( (dashboard_origin_was_updated) && (m_OverlayDragger.GetDragDeviceID() == -1) && (!m_OverlayDragger.IsDragGestureActive()) && \n                          (data.ConfigInt[configid_int_overlay_origin] == ovrl_origin_dashboard) )\n                {\n                    ApplySettingTransform();\n                }\n            }\n\n            DetachedOverlayGazeFade();\n        }\n    }\n\n    if (transform_frame_update_was_done)\n    {\n        m_LastFrameTransformUpdateTick = ::GetTickCount64();\n    }\n\n    OverlayManager::Get().SetCurrentOverlayID(current_overlay_old);\n\n    DetachedInteractionAutoToggleAll();\n    DetachedOverlayAutoDockingAll();\n\n    m_LaserPointer.Update();\n\n    //Hack:\n    //We don't get an event when the Theater Screen close button is being used, which puts it in the dashboard (we don't want this), \n    //but IsActiveDashboardOverlay() is true if it's visible on the screen so it doesn't help either to detected this.\n    //So we instead check if its middle transform gets too close to the dashboard (transform is based on Theater Screen if it's on there, otherwise the dashboard tab). \n    //The case of this happening in normal use is fairly low, so it'll have to do for now\n    if (OverlayManager::Get().GetTheaterOverlayID() != k_ulOverlayID_None)\n    {\n        if (vr::VROverlay()->IsDashboardVisible())\n        {\n            vr::VROverlayHandle_t system_dashboard;\n            vr::VROverlay()->FindOverlay(\"system.systemui\", &system_dashboard);\n\n            const Matrix4 mat_dashboard = m_OverlayDragger.GetBaseOffsetMatrix(ovrl_origin_dashboard);\n            const Matrix4 mat_theater   = OverlayManager::Get().GetOverlayMiddleTransform(OverlayManager::Get().GetTheaterOverlayID());\n\n            Vector3 pos_dashboard = mat_dashboard.getTranslation();\n            Vector3 pos_theater   = mat_theater.getTranslation();\n            pos_dashboard.y = pos_theater.y;\n\n            float distance = pos_dashboard.distance(pos_theater);\n\n            //Distance should be between 0.009f and 0.019f (SteamVR 2 dashboard) in practice so this hopefully has a big enough tolerance\n            if (distance < 0.05f)\n            {\n                vr::VROverlay()->ShowDashboard(\"elvissteinjr.DesktopPlusDashboard\");    //Defaults to Steam tab so try to get it to display ours instead\n                SetOverlayEnabled(OverlayManager::Get().GetTheaterOverlayID(), false);\n            }\n        }\n    }\n\n    //Handle delayed dashboard dummy updates\n    if ( (m_PendingDashboardDummyHeight != 0.0f) && (m_LastApplyTransformTick + 100 < ::GetTickCount64()) )\n    {\n        UpdatePendingDashboardDummyHeight();\n    }\n\n    FinishQueuedOverlayRemovals();\n\n    return false;\n}\n\nvoid OutputManager::OnOpenVRMouseEvent(const vr::VREvent_t& vr_event, unsigned int& current_overlay_old)\n{\n    const Overlay& overlay_current = OverlayManager::Get().GetCurrentOverlay();\n    const OverlayConfigData& data = OverlayManager::Get().GetCurrentConfigData();\n    const bool use_pen = ConfigManager::GetValue(configid_bool_input_mouse_simulate_pen_input);\n\n    switch (vr_event.eventType)\n    {\n        case vr::VREvent_MouseMove:\n        {\n            if ( (!data.ConfigBool[configid_bool_overlay_input_enabled]) || ( (m_MouseIgnoreMoveEvent) && (overlay_current.GetTextureSource() != ovrl_texsource_browser) ) ||\n                 (ConfigManager::GetValue(configid_bool_state_overlay_dragmode)) || (ConfigManager::GetValue(configid_bool_state_overlay_selectmode)) || \n                 (m_OverlayDragger.IsDragActive()) || (overlay_current.GetTextureSource() == ovrl_texsource_none) || (overlay_current.GetTextureSource() == ovrl_texsource_ui) )\n            {\n                break;\n            }\n\n            Vector2 event_mouse_pos(vr_event.data.mouse.x, vr_event.data.mouse.y);\n\n            //Smooth input\n            if (ConfigManager::GetValue(configid_int_input_mouse_input_smoothing_level) != 0)\n            {\n                m_MouseLaserPointerSmoother.ApplyPresetSettings(ConfigManager::GetValue(configid_int_input_mouse_input_smoothing_level));\n\n                event_mouse_pos = m_MouseLaserPointerSmoother.Filter(event_mouse_pos);\n            }\n\n            //Offset depending on capture source\n            int content_height = data.ConfigInt[configid_int_overlay_state_content_height];\n            int offset_x = 0;\n            int offset_y = 0;\n\n            if (data.ConfigInt[configid_int_overlay_capture_source] == ovrl_capsource_winrt_capture)\n            {\n                int desktop_id = data.ConfigInt[configid_int_overlay_winrt_desktop_id];\n\n                if (desktop_id != -2) //Desktop capture through WinRT\n                {\n                    if ( (desktop_id >= 0) && (desktop_id < m_DesktopRects.size()) )\n                    {\n                        offset_x = m_DesktopRects[desktop_id].GetTL().x;\n                        offset_y = m_DesktopRects[desktop_id].GetTL().y;\n                    }\n                    else if (desktop_id == -1) //Combined desktop\n                    {\n                        content_height = m_DesktopRectTotal.GetHeight();\n                        offset_x = m_DesktopRectTotal.GetTL().x;\n                        offset_y = m_DesktopRectTotal.GetTL().y;\n                    }\n                }\n                else //Window capture\n                {\n                    HWND window_handle = (HWND)data.ConfigHandle[configid_handle_overlay_state_winrt_hwnd];\n\n                    //Get position of the window\n                    RECT window_rect = {0};\n\n                    if (::DwmGetWindowAttribute(window_handle, DWMWA_EXTENDED_FRAME_BOUNDS, &window_rect, sizeof(window_rect)) == S_OK)\n                    {\n                        offset_x = window_rect.left;\n                        offset_y = window_rect.top;\n                    }\n                }\n            }\n            else if (data.ConfigInt[configid_int_overlay_capture_source] == ovrl_capsource_desktop_duplication)\n            {\n                content_height = m_DesktopHeight;\n                offset_x = m_DesktopX;\n                offset_y = m_DesktopY;\n            }\n\n            //SteamVR ignores 3D properties when adjusting coordinates for custom UV values, so we need to add offsets manually\n            if (data.ConfigBool[configid_bool_overlay_3D_enabled])\n            {\n                Overlay3DMode mode_3D = (Overlay3DMode)data.ConfigInt[configid_int_overlay_3D_mode];\n\n                if (mode_3D >= ovrl_3Dmode_hou)\n                {\n                    offset_x += overlay_current.GetValidatedCropRect().GetTL().x;\n                }\n                else\n                {\n                    offset_x += overlay_current.GetValidatedCropRect().GetTL().x / 2;\n                }\n            }\n\n            //GL space (0,0 is bottom left), so we need to flip that around (not correct for browser overlays, but also not relevant for how the values are used with them right now)\n            int pointer_x =   round(event_mouse_pos.x) + offset_x;\n            int pointer_y = (-round(event_mouse_pos.y) + content_height) + offset_y;\n\n            //If double click assist is current active, check if there was an obviously deliberate movement and cancel it then\n            if ( (ConfigManager::GetValue(configid_int_state_mouse_dbl_click_assist_duration_ms) != 0) &&\n                 (::GetTickCount64() < m_MouseLastClickTick + ConfigManager::GetValue(configid_int_state_mouse_dbl_click_assist_duration_ms)) )\n            {\n                if ((abs(pointer_x - m_MouseLastLaserPointerX) > 64) || (abs(pointer_y - m_MouseLastLaserPointerY) > 64))\n                {\n                    m_MouseLastClickTick = 0;\n                }\n                else //But if not, still block the movement\n                {\n                    m_MouseLastLaserPointerMoveBlocked = true;\n                    break;\n                }\n            }\n\n            //If browser overlay, pass event along and skip the rest\n            if (overlay_current.GetTextureSource() == ovrl_texsource_browser)\n            {\n                DPBrowserAPIClient::Get().DPBrowser_MouseMove(overlay_current.GetHandle(), pointer_x, pointer_y);\n                m_MouseLastLaserPointerX = pointer_x;\n                m_MouseLastLaserPointerY = pointer_y;\n\n                break;\n            }\n\n            //Check if this mouse move would start a drag of a maximized window's title bar\n            if ( (ConfigManager::GetValue(configid_int_windows_winrt_dragging_mode) != window_dragging_none) && \n                 (overlay_current.GetTextureSource() == ovrl_texsource_winrt_capture) && (data.ConfigHandle[configid_handle_overlay_state_winrt_hwnd] != 0) )\n            {\n                if (WindowManager::Get().WouldDragMaximizedTitleBar((HWND)data.ConfigHandle[configid_handle_overlay_state_winrt_hwnd],\n                                                                    m_MouseLastLaserPointerX, m_MouseLastLaserPointerY, pointer_x, pointer_y))\n                {\n                    //Reset input and WindowManager state manually to block the drag but still move the cursor on the next mouse move event\n                    (use_pen) ? m_InputSim.PenSetPrimaryDown(false) : m_InputSim.MouseSetLeftDown(false);\n                    \n                    WindowManager::Get().SetTargetWindow(nullptr);\n\n                    //Start overlay drag if setting enabled\n                    if (ConfigManager::GetValue(configid_int_windows_winrt_dragging_mode) == window_dragging_overlay)\n                    {\n                        if (data.ConfigInt[configid_int_overlay_origin] != ovrl_origin_theater_screen)\n                        {\n                            if (!data.ConfigBool[configid_bool_overlay_transform_locked])\n                            {\n                                m_OverlayDragger.DragStart(OverlayManager::Get().GetCurrentOverlayID());\n                            }\n                            else\n                            {\n                                IPCManager::Get().PostConfigMessageToUIApp(configid_int_state_drag_hint_device, ConfigManager::Get().GetPrimaryLaserPointerDevice());\n                                IPCManager::Get().PostConfigMessageToUIApp(configid_int_state_drag_hint_type, 1);\n                            }\n                        }\n                        else\n                        {\n                            IPCManager::Get().PostConfigMessageToUIApp(configid_int_state_drag_hint_device, ConfigManager::Get().GetPrimaryLaserPointerDevice());\n                            IPCManager::Get().PostConfigMessageToUIApp(configid_int_state_drag_hint_type, 2);\n                        }\n                    }\n\n                    break; //We're not moving the cursor this time, get out\n                }\n            }\n\n            //Check coordinates if laser pointer override is enabled, unless left mouse is held down by the laser pointer\n            if ( (m_MouseLeftDownOverlayID == k_ulOverlayID_None) && (ConfigManager::GetValue(configid_bool_input_mouse_allow_pointer_override)) )\n            {\n                POINT pt;\n                ::GetCursorPos(&pt);\n\n                //Only check for override if the last laser pointer position was inside a desktop (outside coordinates are possible via extended laser drag and combined desktop edge cases)\n                bool do_check_for_override = false;\n                const Vector2Int laser_pos(m_MouseLastLaserPointerX, m_MouseLastLaserPointerY);\n                for (const DPRect& rect : m_DesktopRects)\n                {\n                    if (rect.Contains(laser_pos))\n                    {\n                        do_check_for_override = true;\n                        break;\n                    }\n                }\n\n                //Simulated pen input does not move the mouse cursor when an application supports pen input directly, which makes the normal check result in false positives\n                //In this case we only check for override when the mouse cursor position changed compared to last time\n                //While it might seem to make sense to always do this, it actually makes it harder to trigger the override during mouse simulation\n                if (ConfigManager::GetValue(configid_bool_input_mouse_simulate_pen_input))\n                {\n                    static POINT pt_last = {0};\n                    if ((pt.x == pt_last.x) && (pt.y == pt_last.y))\n                    {\n                        do_check_for_override = false;\n                    }\n\n                    pt_last = pt;\n                }\n\n                //Check if the cursor is near the corner of one of the desktops and opt out of the pointer override to avoid sticky mouse corners triggering it\n                //It can still happen sometimes, but this catches most cases\n                if (do_check_for_override)\n                {\n                    //Only do for combined desktops though\n                    if ( ((data.ConfigInt[configid_int_overlay_capture_source] == ovrl_capsource_desktop_duplication) && (data.ConfigInt[configid_int_overlay_desktop_id] == -1)) ||\n                         ((data.ConfigInt[configid_int_overlay_capture_source] == ovrl_capsource_winrt_capture) && (data.ConfigInt[configid_int_overlay_winrt_desktop_id] == -1)) )\n                    {\n                        const Vector2Int cursor_pos(pt.x, pt.y);\n                        for (const DPRect& rect : m_DesktopRects)\n                        {\n                            if ((fabs(cursor_pos.distance(rect.GetTL())) < 64.0f) || (fabs(cursor_pos.distance(rect.GetTR())) < 64.0f) ||\n                                (fabs(cursor_pos.distance(rect.GetBL())) < 64.0f) || (fabs(cursor_pos.distance(rect.GetBR())) < 64.0f))\n                            {\n                                do_check_for_override = false;\n                                break;\n                            }\n                        }\n                    }\n                }\n\n                //If mouse coordinates are not what the last laser pointer was (with tolerance), meaning some other source moved it\n                if ((do_check_for_override) && ((abs(pt.x - m_MouseLastLaserPointerX) > 32) || (abs(pt.y - m_MouseLastLaserPointerY) > 32)))\n                {\n                    m_MouseIgnoreMoveEventMissCount++; //GetCursorPos() may lag behind or other jumps may occasionally happen. We count up a few misses first before acting on them\n\n                    int max_miss_count = 10; //Arbitrary number, but appears to work reliably\n\n                    //Reduce max miss count to 1 for simulated pen input\n                    if (ConfigManager::GetValue(configid_bool_input_mouse_simulate_pen_input))\n                    {\n                        max_miss_count = 1;\n                    }\n                    else\n                    {\n                        //When updates are limited, try adapting for the lower update rate\n                        if (m_PerformanceUpdateLimiterDelay.QuadPart != 0)\n                        {\n                            max_miss_count = std::max(1, max_miss_count - int((m_PerformanceUpdateLimiterDelay.QuadPart / 1000) / 20));\n                        }\n                    }\n\n                    if (m_MouseIgnoreMoveEventMissCount > max_miss_count)\n                    {\n                        m_MouseIgnoreMoveEvent = true;\n\n                        //Set flag for all overlays\n                        for (unsigned int i = 0; i < OverlayManager::Get().GetOverlayCount(); ++i)\n                        {\n                            const Overlay& overlay = OverlayManager::Get().GetOverlay(i);\n\n                            if ( (overlay.GetTextureSource() != ovrl_texsource_none) && (overlay.GetTextureSource() != ovrl_texsource_ui) && (overlay.GetTextureSource() != ovrl_texsource_browser) )\n                            {\n                                vr::VROverlay()->SetOverlayFlag(overlay.GetHandle(), vr::VROverlayFlags_HideLaserIntersection, true);\n                            }\n                        }\n                    }\n                    break;\n                }\n                else\n                {\n                    m_MouseIgnoreMoveEventMissCount = 0;\n                }\n            }\n\n            //To improve compatibility with dragging certain windows around, simulate a small movement first before fully unlocking the cursor from double-click assist\n            if (m_MouseLastLaserPointerMoveBlocked) \n            {\n                //Move a single pixel in the direction of the new pointer position\n                pointer_x = m_MouseLastLaserPointerX + sgn(pointer_x - m_MouseLastLaserPointerX);\n                pointer_y = m_MouseLastLaserPointerY + sgn(pointer_y - m_MouseLastLaserPointerY);\n\n                (use_pen) ? m_InputSim.PenMove(pointer_x, pointer_y) : m_InputSim.MouseMove(pointer_x, pointer_y);\n\n                m_MouseLastLaserPointerMoveBlocked = false;\n                //Real movement continues on the next mouse move event\n            }\n            else\n            {\n                //Finally do the actual cursor movement if we're still here\n                (use_pen) ? m_InputSim.PenMove(pointer_x, pointer_y) : m_InputSim.MouseMove(pointer_x, pointer_y);\n\n                m_MouseLastLaserPointerX = pointer_x;\n                m_MouseLastLaserPointerY = pointer_y;\n            }\n\n            //This is only relevant when limiting updates. See Update() for details.\n            m_MouseLaserPointerUsedLastUpdate = true;\n\n            break;\n        }\n        case vr::VREvent_MouseButtonDown:\n        {\n            ActionManager& action_manager = ConfigManager::Get().GetActionManager();\n\n            if (ConfigManager::GetValue(configid_bool_state_overlay_selectmode))\n            {\n                if (vr_event.data.mouse.button == vr::VRMouseButton_Left)\n                {\n                    //Select this as current overlay\n                    IPCManager::Get().PostConfigMessageToUIApp(configid_int_interface_overlay_current_id, overlay_current.GetID());\n                    current_overlay_old = overlay_current.GetID(); //Set a new reset value since we're in the middle of a temporary current overlay loop\n                }\n                break;\n            }\n            else if (ConfigManager::GetValue(configid_bool_state_overlay_dragmode))\n            {\n                if (vr_event.data.mouse.button == vr::VRMouseButton_Left)\n                {\n                    if (m_OverlayDragger.GetDragDeviceID() == -1)\n                    {\n                        if (data.ConfigInt[configid_int_overlay_origin] != ovrl_origin_theater_screen)\n                        {\n                            if (!data.ConfigBool[configid_bool_overlay_transform_locked])\n                            {\n                                m_OverlayDragger.DragStart(OverlayManager::Get().GetCurrentOverlayID());\n                            }\n                            else\n                            {\n                                IPCManager::Get().PostConfigMessageToUIApp(configid_int_state_drag_hint_device, ConfigManager::Get().GetPrimaryLaserPointerDevice());\n                                IPCManager::Get().PostConfigMessageToUIApp(configid_int_state_drag_hint_type, 1);\n                            }\n                        }\n                        else\n                        {\n                            IPCManager::Get().PostConfigMessageToUIApp(configid_int_state_drag_hint_device, ConfigManager::Get().GetPrimaryLaserPointerDevice());\n                            IPCManager::Get().PostConfigMessageToUIApp(configid_int_state_drag_hint_type, 2);\n                        }\n                    }\n                }\n                else if (vr_event.data.mouse.button == vr::VRMouseButton_Right)\n                {\n                    if (!m_OverlayDragger.IsDragGestureActive())\n                    {\n                        if (data.ConfigInt[configid_int_overlay_origin] != ovrl_origin_theater_screen)\n                        {\n                            if (!data.ConfigBool[configid_bool_overlay_transform_locked])\n                            {\n                                m_OverlayDragger.DragGestureStart(OverlayManager::Get().GetCurrentOverlayID());\n                            }\n                            else\n                            {\n                                IPCManager::Get().PostConfigMessageToUIApp(configid_int_state_drag_hint_device, ConfigManager::Get().GetPrimaryLaserPointerDevice());\n                                IPCManager::Get().PostConfigMessageToUIApp(configid_int_state_drag_hint_type, 1);\n                            }\n                        }\n                        else\n                        {\n                            IPCManager::Get().PostConfigMessageToUIApp(configid_int_state_drag_hint_device, ConfigManager::Get().GetPrimaryLaserPointerDevice());\n                            IPCManager::Get().PostConfigMessageToUIApp(configid_int_state_drag_hint_type, 2);\n                        }\n                    }\n                }\n                break;\n            }\n\n            //Set focused ID when clicking on an overlay\n            ConfigManager::Get().SetValue(configid_int_state_overlay_focused_id, (int)overlay_current.GetID());\n            IPCManager::Get().PostConfigMessageToUIApp(configid_int_state_overlay_focused_id, (int)overlay_current.GetID());\n\n            if (overlay_current.GetTextureSource() == ovrl_texsource_browser)\n            {\n                if (vr_event.data.mouse.button <= vr::VRMouseButton_Middle)\n                {\n                    m_MouseLastClickTick = ::GetTickCount64();\n\n                    DPBrowserAPIClient::Get().DPBrowser_MouseDown(overlay_current.GetHandle(), (vr::EVRMouseButton)vr_event.data.mouse.button);\n                }\n                else\n                {\n                    switch (vr_event.data.mouse.button)\n                    {\n                        case VRMouseButton_DP_Aux01: action_manager.StartAction(ConfigManager::GetValue(configid_handle_input_go_back_action_uid), overlay_current.GetID()); break;\n                        case VRMouseButton_DP_Aux02: action_manager.StartAction(ConfigManager::GetValue(configid_handle_input_go_home_action_uid), overlay_current.GetID()); break;\n                    }\n                }\n                break;\n            }\n            else if ((overlay_current.GetTextureSource() == ovrl_texsource_none) || (overlay_current.GetTextureSource() == ovrl_texsource_ui))\n            {\n                break;\n            }\n\n            if (m_MouseIgnoreMoveEvent) //This can only be true if AllowPointerOverride enabled\n            {\n                m_MouseIgnoreMoveEvent = false;\n\n                ResetMouseLastLaserPointerPos();\n                ApplySettingMouseInput();\n\n                //If it's a WinRT window capture, also check for window management stuff that would've happened on overlay focus enter otherwise\n                if ( (overlay_current.GetTextureSource() == ovrl_texsource_winrt_capture) && (data.ConfigHandle[configid_handle_overlay_state_winrt_hwnd] != 0) )\n                {\n                    if (ConfigManager::GetValue(configid_bool_windows_winrt_auto_focus))\n                    {\n                        WindowManager::Get().RaiseAndFocusWindow((HWND)data.ConfigHandle[configid_handle_overlay_state_winrt_hwnd], &m_InputSim);\n                    }\n                }\n\n                break;  //Click to restore shouldn't generate a mouse click\n            }\n\n            //If a WindowManager drag event could occur, set the current window for it\n            if ( (vr_event.data.mouse.button == vr::VRMouseButton_Left) && (overlay_current.GetTextureSource() == ovrl_texsource_winrt_capture) && \n                 (data.ConfigHandle[configid_handle_overlay_state_winrt_hwnd] != 0) )\n            {\n                WindowManager::Get().SetTargetWindow((HWND)data.ConfigHandle[configid_handle_overlay_state_winrt_hwnd], overlay_current.GetID());\n            }\n\n            if (vr_event.data.mouse.button <= vr::VRMouseButton_Middle)\n            {\n                m_MouseLastClickTick = ::GetTickCount64();\n\n                if (vr_event.data.mouse.button == vr::VRMouseButton_Left)\n                {\n                    m_MouseLeftDownOverlayID = overlay_current.GetID();\n\n                    if (ConfigManager::GetValue(configid_bool_input_keyboard_auto_show_desktop))\n                    {\n                        WindowManager::Get().OnTextInputLeftMouseClick();\n                    }\n                }\n            }\n\n            switch (vr_event.data.mouse.button)\n            {\n                case vr::VRMouseButton_Left:   (use_pen) ? m_InputSim.PenSetPrimaryDown(true)   : m_InputSim.MouseSetLeftDown(true);   break;\n                case vr::VRMouseButton_Right:  (use_pen) ? m_InputSim.PenSetSecondaryDown(true) : m_InputSim.MouseSetRightDown(true);  break;\n                case vr::VRMouseButton_Middle:             m_InputSim.MouseSetMiddleDown(true);                                        break;\n                case VRMouseButton_DP_Aux01:   action_manager.StartAction(ConfigManager::GetValue(configid_handle_input_go_back_action_uid), overlay_current.GetID()); break;\n                case VRMouseButton_DP_Aux02:   action_manager.StartAction(ConfigManager::GetValue(configid_handle_input_go_home_action_uid), overlay_current.GetID()); break;\n            }\n\n            break;\n        }\n        case vr::VREvent_MouseButtonUp:\n        {\n            ActionManager& action_manager = ConfigManager::Get().GetActionManager();\n\n            if (ConfigManager::GetValue(configid_bool_state_overlay_selectmode))\n            {\n                break;\n            }\n            else if (ConfigManager::GetValue(configid_bool_state_overlay_dragmode_temp))\n            {\n                //Temp drag doesn't have proper overlay focus so can be called on any overlay if it's in front. Finish drag in any case.\n                if (vr_event.data.mouse.button == vr::VRMouseButton_Left)\n                {\n                    //Don't actually react to this unless it's been a few milliseconds\n                    //That way releasing the trigger after the UI selection instead of holding it doesn't just drop the overlay on the spot\n                    if (::GetTickCount64() >= m_OvrlTempDragStartTick + 250)\n                    {\n                        OnDragFinish();\n                        DetachedTempDragFinish();\n\n                        break;\n                    }\n                }\n            }\n            else if ( (m_OverlayDragger.GetDragOverlayID() == overlay_current.GetID()) && ( (m_OverlayDragger.IsDragActive()) || (m_OverlayDragger.IsDragGestureActive()) ) )\n            {\n                if ((vr_event.data.mouse.button == vr::VRMouseButton_Left) && (m_OverlayDragger.IsDragActive()))\n                {\n                    OnDragFinish();\n                    m_OverlayDragger.DragFinish();\n\n                    ApplySettingTransform();\n                }\n                else if ((vr_event.data.mouse.button == vr::VRMouseButton_Right) && (m_OverlayDragger.IsDragGestureActive()))\n                {\n                    m_OverlayDragger.DragGestureFinish();\n\n                    ApplySettingTransform();\n                }\n\n                break;\n            }\n            else if (overlay_current.GetTextureSource() == ovrl_texsource_browser)\n            {\n                if (vr_event.data.mouse.button <= vr::VRMouseButton_Middle)\n                {\n                    DPBrowserAPIClient::Get().DPBrowser_MouseUp(overlay_current.GetHandle(), (vr::EVRMouseButton)vr_event.data.mouse.button);\n                }\n                else\n                {\n                    switch (vr_event.data.mouse.button)\n                    {\n                        case VRMouseButton_DP_Aux01: action_manager.StopAction(ConfigManager::GetValue(configid_handle_input_go_back_action_uid), overlay_current.GetID()); break;\n                        case VRMouseButton_DP_Aux02: action_manager.StopAction(ConfigManager::GetValue(configid_handle_input_go_home_action_uid), overlay_current.GetID()); break;\n                    }\n                }\n\n                IPCManager::Get().PostConfigMessageToUIApp(configid_int_state_drag_hint_type, 0);\n                break;\n            }\n            else if ((overlay_current.GetTextureSource() == ovrl_texsource_none) || (overlay_current.GetTextureSource() == ovrl_texsource_ui))\n            {\n                IPCManager::Get().PostConfigMessageToUIApp(configid_int_state_drag_hint_type, 0);\n                break;\n            }\n\n            switch (vr_event.data.mouse.button)\n            {\n                case vr::VRMouseButton_Left:   (use_pen) ? m_InputSim.PenSetPrimaryDown(false)   : m_InputSim.MouseSetLeftDown(false);  break;\n                case vr::VRMouseButton_Right:  (use_pen) ? m_InputSim.PenSetSecondaryDown(false) : m_InputSim.MouseSetRightDown(false); break;\n                case vr::VRMouseButton_Middle:             m_InputSim.MouseSetMiddleDown(false);                                        break;\n                case VRMouseButton_DP_Aux01:   action_manager.StopAction(ConfigManager::GetValue(configid_handle_input_go_back_action_uid), overlay_current.GetID()); break;\n                case VRMouseButton_DP_Aux02:   action_manager.StopAction(ConfigManager::GetValue(configid_handle_input_go_home_action_uid), overlay_current.GetID()); break;\n            }\n\n            //If there was a possible WindowManager drag event prepared for, reset the target window\n            if ( (vr_event.data.mouse.button == vr::VRMouseButton_Left) && (overlay_current.GetTextureSource() == ovrl_texsource_winrt_capture) && \n                (data.ConfigHandle[configid_handle_overlay_state_winrt_hwnd] != 0) )\n            {\n                WindowManager::Get().SetTargetWindow(nullptr);\n            }\n\n            if (vr_event.data.mouse.button == vr::VRMouseButton_Left)\n            {\n                m_MouseLeftDownOverlayID = k_ulOverlayID_None;\n            }\n\n            IPCManager::Get().PostConfigMessageToUIApp(configid_int_state_drag_hint_type, 0);\n\n            break;\n        }\n        case vr::VREvent_ScrollDiscrete:\n        case vr::VREvent_ScrollSmooth:\n        {\n            //Discrete scroll events are sent at a fixed frame interval for the system laser pointer and at a fixed interval relative to SteamVR Input action set update calls for Desktop+ pointer\n            //This can result in vastly different scroll rates at different frame or update rates\n            //We counteract this by scaling the sent scroll values by the delta between the events to achieve a constant scroll rate\n            float scroll_step_multiplier = 1.0f;\n\n            if (vr_event.eventType == vr::VREvent_ScrollDiscrete)\n            {\n                LARGE_INTEGER scroll_delta = {0};\n                const float scroll_step_ms = 58.31f; //7 frame tick of a 120 Hz HMD... hardly universal, but going with that for now.\n\n                ::QueryPerformanceCounter(&scroll_delta);\n                scroll_delta.QuadPart -= m_MouseLaserPointerScrollDeltaStart.QuadPart;\n                scroll_delta.QuadPart *= 1000000;\n                scroll_delta.QuadPart /= m_MouseLaserPointerScrollDeltaFrequency.QuadPart;\n\n                scroll_step_multiplier = scroll_delta.QuadPart / (1000.0f * scroll_step_ms);\n\n                //We typically don't need more than 2x, so treat everything higher as interrupted scrolling and use 1x for them\n                if (scroll_step_multiplier > 2.0f)\n                {\n                    scroll_step_multiplier = 1.0f;\n                }\n\n                ::QueryPerformanceCounter(&m_MouseLaserPointerScrollDeltaStart);\n            }\n\n            const float xdelta = vr_event.data.scroll.xdelta * scroll_step_multiplier;  //Discrete scrolling will never have X as non-0, but just in case this ever changes\n            const float ydelta = vr_event.data.scroll.ydelta * scroll_step_multiplier;\n\n            //Check deadzone\n            const float xdelta_abs = fabs(xdelta);\n            const float ydelta_abs = fabs(ydelta);\n            const bool do_scroll_h = (xdelta_abs > 0.025f);\n            const bool do_scroll_v = (ydelta_abs > 0.025f);\n\n            //Drag-mode scroll\n            if (m_OverlayDragger.IsDragActive())\n            {\n                //Block drag scroll actions when a temp drag just started to avoid mis-inputs\n                if (::GetTickCount64() <= m_OvrlTempDragStartTick + 250)\n                    break;\n\n                //Additional deadzone\n                if ((xdelta_abs > 0.05f) || (ydelta_abs > 0.05f))\n                {\n                    //Add distance as long as y-delta input is bigger\n                    if (xdelta_abs < ydelta_abs)\n                    {\n                        m_OverlayDragger.DragAddDistance(ydelta);\n                    }\n                    else\n                    {\n                        m_OverlayDragger.DragAddWidth(xdelta * -0.25f);\n                    }\n                }\n\n                break;\n            }\n\n            //Overlay scrolls\n            if ( (ConfigManager::GetValue(configid_bool_state_overlay_dragmode)) || (ConfigManager::GetValue(configid_bool_state_overlay_selectmode)) || \n                 (overlay_current.GetTextureSource() == ovrl_texsource_none) || (overlay_current.GetTextureSource() == ovrl_texsource_ui) )\n            {\n                break;\n            }\n            else if (overlay_current.GetTextureSource() == ovrl_texsource_browser)\n            {\n                if ((do_scroll_h) || (do_scroll_v))\n                {\n                    DPBrowserAPIClient::Get().DPBrowser_Scroll(overlay_current.GetHandle(), (do_scroll_h) ? xdelta : 0.0f, (do_scroll_v) ? ydelta : 0.0f);\n                }\n                break;\n            }\n\n            //Overlay drag-scroll window-title-pull creation... thingy\n            if (do_scroll_v)\n            {\n                bool is_desktop_overlay = (  (data.ConfigInt[configid_int_overlay_capture_source] == ovrl_capsource_desktop_duplication) ||\n                                            ((data.ConfigInt[configid_int_overlay_capture_source] == ovrl_capsource_winrt_capture) && (data.ConfigHandle[configid_handle_overlay_state_winrt_hwnd] == 0)) );\n\n                const float delta_trigger_min = (ConfigManager::GetValue(configid_bool_input_mouse_scroll_smooth)) ? -0.16f : 0.9f;\n\n                if ( (is_desktop_overlay) && (m_MouseLeftDownOverlayID == overlay_current.GetID()) && (ydelta <= delta_trigger_min) )\n                {\n                    HWND current_window = ::GetForegroundWindow();\n\n                    if ( (WindowManager::Get().IsHoveringCapturableTitleBar(current_window, m_MouseLastLaserPointerX, m_MouseLastLaserPointerY)) )\n                    {\n                        vr::TrackedDeviceIndex_t device_index = ConfigManager::Get().GetPrimaryLaserPointerDevice();\n\n                        //If no dashboard device, try finding one\n                        if (device_index == vr::k_unTrackedDeviceIndexInvalid)\n                        {\n                            device_index = vr::IVROverlayEx::FindPointerDeviceForOverlay(overlay_current.GetHandle());\n                        }\n\n                        float source_distance = 1.0f;\n                        float target_width = 0.3f;\n                        vr::VROverlayIntersectionResults_t results;\n\n                        if (vr::IVROverlayEx::ComputeOverlayIntersectionForDevice(overlay_current.GetHandle(), device_index, vr::TrackingUniverseStanding, &results))\n                        {\n                            source_distance = results.fDistance;\n\n                            //Get window width in meters relative to the source overlay width\n                            RECT window_rect = {0};\n\n                            if (::DwmGetWindowAttribute(current_window, DWMWA_EXTENDED_FRAME_BOUNDS, &window_rect, sizeof(window_rect)) == S_OK)\n                            {\n                                target_width = (float(window_rect.right - window_rect.left) / overlay_current.GetValidatedCropRect().GetWidth()) * data.ConfigFloat[configid_float_overlay_width];\n                                target_width = std::max(target_width, 0.2f); //Don't let it become too tiny\n                            }\n                        }\n\n                        //Set device as hint, just in case\n                        ConfigManager::SetValue(configid_int_state_laser_pointer_device_hint, (int)device_index);\n                        IPCManager::Get().PostConfigMessageToUIApp(configid_int_state_laser_pointer_device_hint, (int)device_index);\n\n                        //Send to UI\n                        IPCManager::Get().PostConfigMessageToUIApp(configid_handle_state_arg_hwnd, (LPARAM)current_window);\n                        IPCManager::Get().PostMessageToUIApp(ipcmsg_action, ipcact_overlay_new_drag, MAKELPARAM(-2, 0 /*UI doesn't need distance*/));\n\n                        //Reset input and WindowManager state manually since the overlay mouse up even will be consumed to finish the drag later\n                        m_InputSim.MouseSetLeftDown(false);\n                        WindowManager::Get().SetTargetWindow(nullptr);\n\n                        //Start drag\n                        AddOverlayDrag(source_distance, ovrl_capsource_winrt_capture, -2, current_window, target_width);\n\n                        break;\n                    }\n                }\n\n            }\n\n            //Normal scrolling\n            if (do_scroll_v)\n            {\n                m_InputSim.MouseWheelVertical(ydelta);\n            }\n\n            if (do_scroll_h)\n            {\n                m_InputSim.MouseWheelHorizontal(-xdelta);\n            }\n\n            break;\n        }\n    }\n}\n\nvoid OutputManager::HandleKeyboardMessage(IPCActionID ipc_action_id, LPARAM lparam)\n{\n    switch (ipc_action_id)\n    {\n        case ipcact_keyboard_vkey:\n        {\n            m_InputSim.KeyboardSetKeyState((IPCKeyboardKeystateFlags)LOWORD(lparam), HIWORD(lparam));\n            break;\n        }\n        case ipcact_keyboard_wchar:\n        {\n            wchar_t wchar =  LOWORD(lparam);\n            bool down     = (HIWORD(lparam) == 1);\n\n            //Check if it can be pressed on the current windows keyboard layout\n            SHORT w32_keystate = ::VkKeyScanW(wchar);\n            unsigned char keycode = LOBYTE(w32_keystate);\n            unsigned char flags   = HIBYTE(w32_keystate);\n\n            bool is_valid = ((keycode != 255) || (flags != 255));\n\n            if (is_valid)\n            {\n                m_InputSim.KeyboardSetFromWin32KeyState(w32_keystate, down);\n            }\n            else if (down) //Otherwise use it as a text input\n            {\n                wchar_t wstr[2] = {0};\n                wstr[0] = wchar;\n\n                m_InputSim.KeyboardText(StringConvertFromUTF16(wstr).c_str(), true);\n            }\n            break;\n        }\n        default: return;\n    }\n}\n\nbool OutputManager::HandleOverlayProfileLoadMessage(LPARAM lparam)\n{\n    IPCActionOverlayProfileLoadArg profile_load_arg = (IPCActionOverlayProfileLoadArg)LOWORD(lparam);\n    int profile_overlay_id = GET_Y_LPARAM(lparam);\n\n    int desktop_id_prev = ConfigManager::GetValue(configid_int_overlay_desktop_id);\n    const std::string& profile_name = ConfigManager::GetValue(configid_str_state_profile_name_load);\n\n    if (profile_overlay_id == -2)\n    {\n        ConfigManager::Get().LoadOverlayProfileDefault(true);\n    }\n    else if (profile_load_arg == ipcactv_ovrl_profile_multi)\n    {\n        ConfigManager::Get().LoadMultiOverlayProfileFromFile(profile_name + \".ini\", true);\n    }\n    else if (profile_load_arg == ipcactv_ovrl_profile_multi_add)\n    {\n        if (profile_overlay_id == -1)  //Load queued up overlays\n        {\n            //Usually, elements should have been sent in order, but lets make sure they really are\n            std::sort(m_ProfileAddOverlayIDQueue.begin(), m_ProfileAddOverlayIDQueue.end());\n\n            std::vector<char> ovrl_inclusion_list;\n            ovrl_inclusion_list.reserve(m_ProfileAddOverlayIDQueue.back());\n\n            //Build overlay inclusion list\n            for (int ovrl_id : m_ProfileAddOverlayIDQueue)\n            {\n                if ((int)ovrl_inclusion_list.size() > ovrl_id)      //Skip if already in list (double entry)\n                {\n                    continue;\n                }\n\n                while ((int)ovrl_inclusion_list.size() < ovrl_id)   //Exclude overlays not queued\n                {\n                    ovrl_inclusion_list.push_back(0);\n                }\n\n                ovrl_inclusion_list.push_back(1);                   //Include ovrl_id\n            }\n\n            ConfigManager::Get().LoadMultiOverlayProfileFromFile(profile_name + \".ini\", false, &ovrl_inclusion_list);\n\n            m_ProfileAddOverlayIDQueue.clear();\n        }\n        else if ( (profile_overlay_id >= 0) && (profile_overlay_id < 1000) )  //Queue up overlays\n        {\n            m_ProfileAddOverlayIDQueue.push_back(profile_overlay_id);\n            return false;\n        }\n    }\n\n    //Reset mirroing entirely if desktop was changed (only in single desktop mode)\n    if ( (ConfigManager::GetValue(configid_bool_performance_single_desktop_mirroring)) && (ConfigManager::GetValue(configid_int_overlay_desktop_id) != desktop_id_prev) )\n        return true; //Reset mirroring\n\n    ResetOverlays(); //This does everything relevant\n\n    return false;\n}\n\nvoid OutputManager::ResetMouseLastLaserPointerPos()\n{\n    //Set last pointer values to current to not trip the movement detection up\n    POINT pt;\n    ::GetCursorPos(&pt);\n    m_MouseLastLaserPointerX = pt.x;\n    m_MouseLastLaserPointerY = pt.y;\n\n    //Also reset this state which may be left unclean when window drags get triggered\n    m_MouseLeftDownOverlayID = k_ulOverlayID_None;\n}\n\nvoid OutputManager::CropToActiveWindow()\n{\n    bool& crop_enabled = ConfigManager::GetRef(configid_bool_overlay_crop_enabled);\n    int& crop_x        = ConfigManager::GetRef(configid_int_overlay_crop_x);\n    int& crop_y        = ConfigManager::GetRef(configid_int_overlay_crop_y);\n    int& crop_width    = ConfigManager::GetRef(configid_int_overlay_crop_width);\n    int& crop_height   = ConfigManager::GetRef(configid_int_overlay_crop_height);\n\n    if (CropToActiveWindow(crop_x, crop_y, crop_width, crop_height))\n    {\n        crop_enabled = true;\n\n        //Send them over to UI\n        IPCManager::Get().PostConfigMessageToUIApp(configid_bool_overlay_crop_enabled, crop_enabled);\n        IPCManager::Get().PostConfigMessageToUIApp(configid_int_overlay_crop_x,        crop_x);\n        IPCManager::Get().PostConfigMessageToUIApp(configid_int_overlay_crop_y,        crop_y);\n        IPCManager::Get().PostConfigMessageToUIApp(configid_int_overlay_crop_width,    crop_width);\n        IPCManager::Get().PostConfigMessageToUIApp(configid_int_overlay_crop_height,   crop_height);\n\n        ApplySettingCrop();\n        ApplySettingTransform();\n        ApplySettingMouseScale();\n    }\n}\n\nvoid OutputManager::CropToDisplay(int display_id, bool do_not_apply_setting)\n{\n    int& crop_x      = ConfigManager::GetRef(configid_int_overlay_crop_x);\n    int& crop_y      = ConfigManager::GetRef(configid_int_overlay_crop_y);\n    int& crop_width  = ConfigManager::GetRef(configid_int_overlay_crop_width);\n    int& crop_height = ConfigManager::GetRef(configid_int_overlay_crop_height);\n\n    CropToDisplay(display_id, crop_x, crop_y, crop_width, crop_height);\n\n    //Send change to UI as well (also set override since this may be called during one)\n    IPCManager::Get().PostConfigMessageToUIApp(configid_int_state_overlay_current_id_override, OverlayManager::Get().GetCurrentOverlayID());\n    IPCManager::Get().PostConfigMessageToUIApp(configid_int_overlay_crop_x,      crop_x);\n    IPCManager::Get().PostConfigMessageToUIApp(configid_int_overlay_crop_y,      crop_y);\n    IPCManager::Get().PostConfigMessageToUIApp(configid_int_overlay_crop_width,  crop_width);\n    IPCManager::Get().PostConfigMessageToUIApp(configid_int_overlay_crop_height, crop_height);\n    IPCManager::Get().PostConfigMessageToUIApp(configid_int_state_overlay_current_id_override, -1);\n\n    //In single desktop mode, set desktop ID for all overlays\n    if (ConfigManager::GetValue(configid_bool_performance_single_desktop_mirroring))\n    {\n        for (unsigned int i = 0; i < OverlayManager::Get().GetOverlayCount(); ++i)\n        {\n            OverlayManager::Get().GetConfigData(i).ConfigInt[configid_int_overlay_desktop_id] = display_id;\n\n            IPCManager::Get().PostConfigMessageToUIApp(configid_int_state_overlay_current_id_override, (int)i);\n            IPCManager::Get().PostConfigMessageToUIApp(configid_int_overlay_desktop_id, display_id);\n            IPCManager::Get().PostConfigMessageToUIApp(configid_int_state_overlay_current_id_override, -1);\n        }\n    }\n\n    //Applying the setting when a duplication resets happens right after has the chance of screwing up the transform (too many transform updates?), so give the option to not do it\n    if (!do_not_apply_setting)\n    {\n        ApplySettingCrop();\n        ApplySettingTransform();\n        ApplySettingMouseScale();\n        ApplySettingExtraBrightness();\n    }\n}\n\nvoid OutputManager::DuplicateOverlay(unsigned int base_id, bool is_ui_overlay)\n{\n    //Add overlay based on data of base_id overlay and reset it\n    unsigned int new_id = k_ulOverlayID_None;\n\n    if (!is_ui_overlay)\n    {\n        new_id = OverlayManager::Get().DuplicateOverlay(OverlayManager::Get().GetConfigData(base_id), base_id);\n    }\n    else\n    {\n        new_id = OverlayManager::Get().AddUIOverlay();\n    }\n\n    unsigned int current_overlay_old = OverlayManager::Get().GetCurrentOverlayID();\n    OverlayManager::Get().SetCurrentOverlayID(new_id);\n\n    if (!is_ui_overlay)\n    {\n        const Overlay& overlay_base = OverlayManager::Get().GetOverlay(base_id);\n        Overlay& overlay_current = OverlayManager::Get().GetCurrentOverlay();\n\n        //If base overlay is an active WinRT Capture, duplicate capture before resetting the overlay\n        if (overlay_base.GetTextureSource() == ovrl_texsource_winrt_capture)\n        {\n            if (DPWinRT_StartCaptureFromOverlay(overlay_current.GetHandle(), overlay_base.GetHandle()))\n            {\n                overlay_current.SetTextureSource(ovrl_texsource_winrt_capture);\n            }\n        }\n\n        //Automatically reset the matrix to a saner default by putting it next to the base overlay in most cases\n        DetachedTransformReset(overlay_base.GetID());\n    }\n    else\n    {\n        DetachedTransformReset();\n    }\n\n    ResetCurrentOverlay();\n\n    OverlayManager::Get().SetCurrentOverlayID(current_overlay_old);\n}\n\nunsigned int OutputManager::AddOverlay(OverlayCaptureSource capture_source, int desktop_id, HWND window_handle)\n{\n    unsigned int new_id = OverlayManager::Get().AddOverlay(capture_source, desktop_id, window_handle);\n\n    unsigned int current_overlay_old = OverlayManager::Get().GetCurrentOverlayID();\n    OverlayManager::Get().SetCurrentOverlayID(new_id);\n\n    if (capture_source == ovrl_capsource_desktop_duplication)\n    {\n        CropToDisplay(desktop_id, true);\n    }\n\n    //Adjust width to a more suited default (UI does the same so no need to send over)\n    ConfigManager::SetValue(configid_float_overlay_width, 1.0f);\n\n    //Reset transform to default next to the primary dashboard overlay in most cases\n    DetachedTransformReset();\n    ResetCurrentOverlay();\n\n    OverlayManager::Get().SetCurrentOverlayID(current_overlay_old);\n\n    return new_id;\n}\n\nunsigned int OutputManager::AddOverlayDrag(float source_distance, OverlayCaptureSource capture_source, int desktop_id, HWND window_handle, float overlay_width)\n{\n    unsigned int new_id = OverlayManager::Get().AddOverlay(capture_source, desktop_id, window_handle);\n\n    unsigned int current_overlay_old = OverlayManager::Get().GetCurrentOverlayID();\n    OverlayManager::Get().SetCurrentOverlayID(new_id);\n\n    if (capture_source == ovrl_capsource_desktop_duplication)\n    {\n        CropToDisplay(desktop_id, true);\n    }\n\n    //Adjust width and send it over to UI app\n    ConfigManager::SetValue(configid_float_overlay_width, overlay_width);\n\n    IPCManager::Get().PostConfigMessageToUIApp(configid_int_state_overlay_current_id_override, (int)new_id);\n    IPCManager::Get().PostConfigMessageToUIApp(configid_float_overlay_width, overlay_width);\n    IPCManager::Get().PostConfigMessageToUIApp(configid_int_state_overlay_current_id_override, -1);\n\n    //Start drag and apply overlay config\n    DetachedTempDragStart(new_id, std::max(source_distance - 0.25f, 0.01f));\n    ResetCurrentOverlay();\n\n    OverlayManager::Get().SetCurrentOverlayID(current_overlay_old);\n\n    return new_id;\n}\n\nvoid OutputManager::ApplySettingCaptureSource()\n{\n    Overlay& overlay = OverlayManager::Get().GetCurrentOverlay();\n\n    switch (ConfigManager::GetValue(configid_int_overlay_capture_source))\n    {\n        case ovrl_capsource_desktop_duplication:\n        {\n            if (!m_OutputInvalid)\n            {\n                OverlayTextureSource tex_source = overlay.GetTextureSource();\n                if ((tex_source != ovrl_texsource_desktop_duplication) || (tex_source != ovrl_texsource_desktop_duplication_3dou_converted))\n                {\n                    ApplySetting3DMode(); //Sets texture source for us when capture source is desktop duplication\n                }\n            }\n            else\n            {\n                overlay.SetTextureSource(ovrl_texsource_none);\n            }\n            break;\n        }\n        case ovrl_capsource_winrt_capture:\n        {\n            if (overlay.GetTextureSource() != ovrl_texsource_winrt_capture)\n            {\n                if (DPWinRT_IsCaptureFromHandleSupported())\n                {\n                    OverlayConfigData& data = OverlayManager::Get().GetCurrentConfigData();\n\n                    if (data.ConfigHandle[configid_handle_overlay_state_winrt_hwnd] != 0)\n                    {\n                        //Set configid_str_overlay_winrt_last_* strings from window info so returning windows can be restored later\n                        const WindowInfo* window_info = WindowManager::Get().WindowListFindWindow((HWND)data.ConfigHandle[configid_handle_overlay_state_winrt_hwnd]);\n\n                        if (window_info != nullptr)\n                        {\n                            data.ConfigStr[configid_str_overlay_winrt_last_window_title]      = StringConvertFromUTF16(window_info->GetTitle().c_str());\n                            data.ConfigStr[configid_str_overlay_winrt_last_window_class_name] = StringConvertFromUTF16(window_info->GetWindowClassName().c_str());\n                            data.ConfigStr[configid_str_overlay_winrt_last_window_exe_name]   = window_info->GetExeName();\n                        }\n\n                        if (DPWinRT_StartCaptureFromHWND(overlay.GetHandle(), (HWND)data.ConfigHandle[configid_handle_overlay_state_winrt_hwnd]))\n                        {\n                            overlay.SetTextureSource(ovrl_texsource_winrt_capture);\n                            ApplySetting3DMode(); //Syncs 3D state if needed\n                            ApplySettingUpdateLimiter();\n\n                            //Pause if not visible\n                            if (!overlay.IsVisible())\n                            {\n                                DPWinRT_PauseCapture(overlay.GetHandle(), true);\n                            }\n\n                            if (ConfigManager::GetValue(configid_bool_windows_winrt_auto_focus))\n                            {\n                                WindowManager::Get().RaiseAndFocusWindow((HWND)data.ConfigHandle[configid_handle_overlay_state_winrt_hwnd], &m_InputSim);\n                            }\n                        }\n                        break;\n                    }\n                    else if (data.ConfigInt[configid_int_overlay_winrt_desktop_id] != -2)\n                    {\n                        if (DPWinRT_StartCaptureFromDesktop(overlay.GetHandle(), data.ConfigInt[configid_int_overlay_winrt_desktop_id]))\n                        {\n                            overlay.SetTextureSource(ovrl_texsource_winrt_capture);\n                            ApplySetting3DMode();\n                            ApplySettingUpdateLimiter();\n\n                            //Pause if not visible\n                            if (!overlay.IsVisible())\n                            {\n                                DPWinRT_PauseCapture(overlay.GetHandle(), true);\n                            }\n                        }\n                        break;\n                    }\n                }\n\n                //Couldn't set up capture, set source to none\n                overlay.SetTextureSource(ovrl_texsource_none);\n            }\n            break;\n        }\n        case ovrl_capsource_ui:\n        {\n            //Set texture source to UI if possible, which sets the rendering PID to the UI process\n            overlay.SetTextureSource(IPCManager::IsUIAppRunning() ? ovrl_texsource_ui : ovrl_texsource_none);\n\n            break;\n        }\n        case ovrl_capsource_browser:\n        {\n            if (overlay.GetTextureSource() != ovrl_texsource_browser)\n            {\n                bool has_started_browser = false;\n\n                if (DPBrowserAPIClient::Get().IsBrowserAvailable())\n                {\n                    OverlayConfigData& data = OverlayManager::Get().GetCurrentConfigData();\n\n                    //Load placeholder texture and apply input mode now since browser startup can take a few seconds\n                    vr::VROverlay()->SetOverlayFromFile(overlay.GetHandle(), (ConfigManager::Get().GetApplicationPath() + \"images/browser_load.png\").c_str());\n                    ApplySettingInputMode();\n\n                    if (data.ConfigInt[configid_int_overlay_duplication_id] == -1)\n                    {\n                        DPBrowserAPIClient::Get().DPBrowser_StartBrowser(overlay.GetHandle(), data.ConfigStr[configid_str_overlay_browser_url], \n                                                                         data.ConfigBool[configid_bool_overlay_browser_allow_transparency]);\n\n                        DPBrowserAPIClient::Get().DPBrowser_SetResolution(overlay.GetHandle(), data.ConfigInt[configid_int_overlay_user_width], data.ConfigInt[configid_int_overlay_user_height]);\n                        DPBrowserAPIClient::Get().DPBrowser_SetFPS(overlay.GetHandle(),        data.ConfigInt[configid_int_overlay_browser_max_fps_override]);\n                        DPBrowserAPIClient::Get().DPBrowser_SetZoomLevel(overlay.GetHandle(),  data.ConfigFloat[configid_float_overlay_browser_zoom]);\n\n                        has_started_browser = true;\n                    }\n                    else\n                    {\n                        const Overlay& overlay_src = OverlayManager::Get().GetOverlay((unsigned int)data.ConfigInt[configid_int_overlay_duplication_id]);\n\n                        //Source overlay may be invalid on launch or not have texture source set up if duplication ID is higher than this overlay's ID\n                        if ( (overlay_src.GetHandle() != vr::k_ulOverlayHandleInvalid) && (overlay_src.GetTextureSource() == ovrl_texsource_browser) )\n                        {\n                            DPBrowserAPIClient::Get().DPBrowser_DuplicateBrowserOutput(overlay_src.GetHandle(), overlay.GetHandle());\n                            DPBrowserAPIClient::Get().DPBrowser_SetFPS(overlay.GetHandle(), data.ConfigInt[configid_int_overlay_browser_max_fps_override]);\n                            has_started_browser = true;\n                        }\n                    }\n\n                    data.ConfigInt[configid_int_overlay_state_content_width]  = data.ConfigInt[configid_int_overlay_user_width];\n                    data.ConfigInt[configid_int_overlay_state_content_height] = data.ConfigInt[configid_int_overlay_user_height];\n\n                    ApplySetting3DMode();\n                    ApplySettingUpdateLimiter();\n\n                    //Set pause state in case overlay is hidden or differs from duplication source\n                    DPBrowserAPIClient::Get().DPBrowser_PauseBrowser(overlay.GetHandle(), !overlay.IsVisible());\n                }\n\n                //Set texture source to browser if possible, which sets the rendering PID to the browser server process\n                overlay.SetTextureSource((has_started_browser) ? ovrl_texsource_browser : ovrl_texsource_none);\n            }\n\n            break;\n        }\n        default:\n        {\n            //Unknown capture source, perhaps from the future. Set to texture source none.\n            overlay.SetTextureSource(ovrl_texsource_none);\n        }\n    }\n\n    ApplySettingExtraBrightness();\n}\n\nvoid OutputManager::ApplySetting3DMode()\n{\n    const Overlay& overlay_current = OverlayManager::Get().GetCurrentOverlay();\n    const OverlayConfigData& data = OverlayManager::Get().GetCurrentConfigData();\n\n    vr::VROverlayHandle_t ovrl_handle = overlay_current.GetHandle();\n    bool is_enabled = ConfigManager::GetValue(configid_bool_overlay_3D_enabled);\n    int mode = ConfigManager::GetValue(configid_int_overlay_3D_mode);\n\n    //Override mode to none if texsource is none or the desktop duplication output is invalid\n    if ( (overlay_current.GetTextureSource() == ovrl_texsource_none) || ( (data.ConfigInt[configid_int_overlay_capture_source] == ovrl_capsource_desktop_duplication) && (m_OutputInvalid) ) )\n    {\n        is_enabled = false;\n    }\n\n    if (is_enabled)\n    {\n        if (data.ConfigBool[configid_bool_overlay_3D_swapped])\n        {\n            vr::VROverlay()->SetOverlayFlag(ovrl_handle, vr::VROverlayFlags_SideBySide_Parallel, false);\n            vr::VROverlay()->SetOverlayFlag(ovrl_handle, vr::VROverlayFlags_SideBySide_Crossed, true);\n        }\n        else\n        {\n            vr::VROverlay()->SetOverlayFlag(ovrl_handle, vr::VROverlayFlags_SideBySide_Parallel, true);\n            vr::VROverlay()->SetOverlayFlag(ovrl_handle, vr::VROverlayFlags_SideBySide_Crossed, false);\n        }\n\n        switch (mode)\n        {\n            case ovrl_3Dmode_hsbs:\n            {\n                vr::VROverlay()->SetOverlayTexelAspect(ovrl_handle, 2.0f);\n                break;\n            }\n            case ovrl_3Dmode_sbs:\n            case ovrl_3Dmode_ou:  //Over-Under is converted to SBS\n            {\n                vr::VROverlay()->SetOverlayTexelAspect(ovrl_handle, 1.0f);\n                break;\n            }\n            case ovrl_3Dmode_hou: //Half-Over-Under is converted to SBS with half height\n            {\n                vr::VROverlay()->SetOverlayTexelAspect(ovrl_handle, 0.5f);\n                break;\n            }\n            default: break;\n        }\n    }\n    else\n    {\n        vr::VROverlay()->SetOverlayFlag(ovrl_handle, vr::VROverlayFlags_SideBySide_Parallel, false);\n        vr::VROverlay()->SetOverlayFlag(ovrl_handle, vr::VROverlayFlags_SideBySide_Crossed, false);\n        vr::VROverlay()->SetOverlayTexelAspect(ovrl_handle, 1.0f);\n    }\n\n    if (data.ConfigInt[configid_int_overlay_capture_source] == ovrl_capsource_desktop_duplication)\n    {\n        if ( (is_enabled) && ((mode == ovrl_3Dmode_ou) || (mode == ovrl_3Dmode_hou)) )\n        {\n            OverlayManager::Get().GetCurrentOverlay().SetTextureSource(ovrl_texsource_desktop_duplication_3dou_converted);\n        }\n        else\n        {\n            OverlayManager::Get().GetCurrentOverlay().SetTextureSource(ovrl_texsource_desktop_duplication);\n        }\n\n        RefreshOpenVROverlayTexture(DPRect(-1, -1, -1, -1), true);\n    }\n    //WinRT OU3D state is set in ApplySettingCrop since it needs cropping values\n\n    ApplySettingCrop();\n    ApplySettingMouseScale();\n}\n\nvoid OutputManager::ApplySettingTransform()\n{\n    Overlay& overlay = OverlayManager::Get().GetCurrentOverlay();\n    const OverlayConfigData& data = OverlayManager::Get().GetCurrentConfigData();\n\n    vr::VROverlayHandle_t ovrl_handle = overlay.GetHandle();\n\n    //Fixup overlay visibility if needed\n    //This has to be done first since there seem to be issues with moving invisible overlays\n    bool should_be_visible = overlay.ShouldBeVisible();\n\n    if ( (!should_be_visible) && (m_OvrlDashboardActive) && (m_OvrlDashboardActive) && (ConfigManager::GetValue(configid_bool_overlay_enabled)) &&\n         (ConfigManager::GetValue(configid_bool_state_overlay_dragselectmode_show_hidden)) )\n    {\n        should_be_visible = true;\n        overlay.SetOpacity(0.25f);\n    }\n    else if ( (!ConfigManager::GetValue(configid_bool_overlay_gazefade_enabled)) && (overlay.GetOpacity() != ConfigManager::GetValue(configid_float_overlay_opacity)) )\n    {\n        overlay.SetOpacity(ConfigManager::GetValue(configid_float_overlay_opacity));\n        should_be_visible = overlay.ShouldBeVisible(); //Re-evaluate this in case the overlay was left hidden after deactivating gaze fade\n    }\n\n    if ( (should_be_visible) && (!overlay.IsVisible()) )\n    {\n        ShowOverlay(overlay.GetID());\n        return;     //ShowOverlay() calls this function so we back out here\n    }\n    else if ( (!should_be_visible) && (overlay.IsVisible()) )\n    {\n        HideOverlay(overlay.GetID());\n    }\n\n    unsigned int primary_dashboard_overlay_id = OverlayManager::Get().GetPrimaryDashboardOverlay().GetID();\n    bool is_primary_dashboard_overlay = (primary_dashboard_overlay_id == overlay.GetID());\n\n    float width = ConfigManager::GetValue(configid_float_overlay_width);\n    float height = 0.0f;\n    float dummy_height = 0.0f;\n    OverlayOrigin overlay_origin = (OverlayOrigin)ConfigManager::GetValue(configid_int_overlay_origin);\n\n    if (is_primary_dashboard_overlay)\n    {\n        height = GetOverlayHeight(overlay.GetID());\n        //Dashboard uses differently scaled transform depending on the current setting. We counteract that scaling to ensure the config value actually matches world scale\n        dummy_height = height / GetDashboardScale();\n    }\n    else\n    {\n        //Clear theater mode if this overlay is used as source and being disabled or no longer set to theater origin\n        if ((OverlayManager::Get().GetTheaterOverlayID() == overlay.GetID()) && ((!ConfigManager::GetValue(configid_bool_overlay_enabled)) || (overlay_origin != ovrl_origin_theater_screen)) )\n        {\n            OverlayManager::Get().ClearTheaterOverlay();\n            ResetCurrentOverlay();                      //Reset overlay so all changes made to the theater one get reflected on the normal one\n            overlay.AssignDesktopDuplicationTexture();  //Re-assign Desktop Duplication Texture in case it has changed during theater mode\n            return;\n        }\n        else if ((ConfigManager::GetValue(configid_bool_overlay_enabled) && (overlay_origin == ovrl_origin_theater_screen)))   //Enable theater mode if needed\n        {\n            ShowTheaterOverlay(overlay.GetID());\n        }\n    }\n\n    //Dashboard dummy still needs correct width/height set for the top dashboard bar above it to be visible\n    if ( (is_primary_dashboard_overlay) || (primary_dashboard_overlay_id == k_ulOverlayID_None) )           //When no dashboard overlay exists we set this on every overlay, not ideal.\n    {\n        float old_dummy_height = 0.0f;\n        vr::VROverlay()->GetOverlayWidthInMeters(m_OvrlHandleDashboardDummy, &old_dummy_height);\n\n        dummy_height = std::max(dummy_height + 0.30f, 1.5f); //Enforce minimum height to fit default height offset (which makes space for Floating UI)\n\n        //Sanity check. Things like inf can make the entire interface disappear\n        if (dummy_height > 20.0f)\n        {\n            dummy_height = 1.525f;\n        }\n\n        if (dummy_height != old_dummy_height)\n        {\n            //Delay setting the dummy height to after we made sure ApplySettingTransform() is not called right again\n            //Avoiding flicker properly unfortunately needs a sleep, so we can't call this right away or else we're just gonna stutter around\n            m_PendingDashboardDummyHeight = dummy_height;\n        }\n    }\n\n    //Update transform\n    vr::HmdMatrix34_t matrix = {0};\n    vr::TrackingUniverseOrigin universe_origin = vr::TrackingUniverseStanding;\n\n    switch (overlay_origin)\n    {\n        case ovrl_origin_room:\n        {\n            matrix = ConfigManager::Get().GetOverlayDetachedTransform().toOpenVR34();\n\n            //Offset transform by additional offset values\n            vr::IVRSystemEx::TransformOpenVR34TranslateRelative(matrix, ConfigManager::GetValue(configid_float_overlay_offset_right),\n                                                                        ConfigManager::GetValue(configid_float_overlay_offset_up),\n                                                                        ConfigManager::GetValue(configid_float_overlay_offset_forward));\n\n            vr::VROverlay()->SetOverlayTransformAbsolute(ovrl_handle, universe_origin, &matrix);\n            break;\n        }\n        case ovrl_origin_hmd_floor:\n        {\n            DetachedTransformFrameUpdate();\n            break;\n        }\n        case ovrl_origin_seated_universe:\n        {\n            Matrix4 matrix_base = m_OverlayDragger.GetBaseOffsetMatrix() * ConfigManager::Get().GetOverlayDetachedTransform();\n\n            //Offset transform by additional offset values\n            matrix_base.translate_relative(ConfigManager::GetValue(configid_float_overlay_offset_right),\n                                           ConfigManager::GetValue(configid_float_overlay_offset_up),\n                                           ConfigManager::GetValue(configid_float_overlay_offset_forward));\n\n            matrix = matrix_base.toOpenVR34();\n            vr::VROverlay()->SetOverlayTransformAbsolute(ovrl_handle, vr::TrackingUniverseStanding, &matrix);\n            break;\n        }\n        case ovrl_origin_dashboard:\n        {\n            Matrix4 matrix_base = m_OverlayDragger.GetBaseOffsetMatrix() * ConfigManager::Get().GetOverlayDetachedTransform();\n\n            //SteamVR 2.15.1 made changes that affected legacy dashboard positioning in... odd ways\n            //This more of a band-aid solution for the rarely used legacy dashboard, short of just not supporting it anymore\n            bool has_applied_bandaid_fix = false;\n            const bool is_v2_15 = (strstr(vr::VRSystem()->GetRuntimeVersion(), \"2.15\") != nullptr);\n            if (is_v2_15)\n            {\n                vr::VROverlayHandle_t handle_gamepad_ui = vr::k_ulOverlayHandleInvalid;\n                vr::VROverlay()->FindOverlay(\"valve.steam.gamepadui.bar\", &handle_gamepad_ui);\n\n                if (handle_gamepad_ui == vr::k_ulOverlayHandleInvalid)\n                {\n                    if (is_primary_dashboard_overlay)\n                    {\n                        vr::HmdMatrix34_t matrix_dplus_tab;\n                        vr::TrackingUniverseOrigin origin = vr::TrackingUniverseStanding;\n                        vr::VROverlay()->GetTransformForOverlayCoordinates(m_OvrlHandleDashboardDummy, origin, {0.5f, 0.5f}, &matrix_dplus_tab);\n                        matrix_base = matrix_dplus_tab;\n\n                        Vector3 translation = matrix_base.getTranslation();\n                        matrix_base.setTranslation({0.0f, 0.0f, 0.0f});\n                        matrix_base.scale(1.0f / GetDashboardScale());\n                        matrix_base.setTranslation(translation);\n\n                        has_applied_bandaid_fix = true;\n                        //Yes, this does not apply the overlay's transform at all\n                    }\n                }\n            }\n\n            //Offset transform by additional offset values\n            matrix_base.translate_relative(ConfigManager::GetValue(configid_float_overlay_offset_right),\n                                           ConfigManager::GetValue(configid_float_overlay_offset_up),\n                                           ConfigManager::GetValue(configid_float_overlay_offset_forward));\n\n            if (!has_applied_bandaid_fix)\n            {\n                //Apply origin offset, which basically adjusts transform to be aligned bottom-center instead of centered on both axes\n                if (height == 0.0f)     //Get overlay height if it's not set yet\n                {\n                    height = GetOverlayHeight(overlay.GetID());\n                }\n\n                matrix_base.translate_relative(0.0f, height / 2.0f, 0.0f);\n            }\n\n            matrix = matrix_base.toOpenVR34();\n\n            vr::VROverlay()->SetOverlayTransformAbsolute(ovrl_handle, universe_origin, &matrix);\n            break;\n        }\n        case ovrl_origin_hmd:\n        {\n            //Unsmoothed uses device-relative transform, but smoothed ones are absolute updated each frame, like HMD Floor origin\n            if (ConfigManager::GetValue(configid_int_overlay_origin_smoothing_level) == 0)\n            {\n                matrix = ConfigManager::Get().GetOverlayDetachedTransform().toOpenVR34();\n\n                //Offset transform by additional offset values\n                vr::IVRSystemEx::TransformOpenVR34TranslateRelative(matrix, ConfigManager::GetValue(configid_float_overlay_offset_right),\n                                                                            ConfigManager::GetValue(configid_float_overlay_offset_up),\n                                                                            ConfigManager::GetValue(configid_float_overlay_offset_forward));\n\n                vr::VROverlay()->SetOverlayTransformTrackedDeviceRelative(ovrl_handle, vr::k_unTrackedDeviceIndex_Hmd, &matrix);\n            }\n            else\n            {\n                DetachedTransformFrameUpdate();\n            }\n            break;\n        }\n        case ovrl_origin_right_hand:\n        {\n            vr::TrackedDeviceIndex_t device_index = vr::VRSystem()->GetTrackedDeviceIndexForControllerRole(vr::TrackedControllerRole_RightHand);\n\n            if (device_index != vr::k_unTrackedDeviceIndexInvalid)\n            {\n                matrix = ConfigManager::Get().GetOverlayDetachedTransform().toOpenVR34();\n\n                //Offset transform by additional offset values\n                vr::IVRSystemEx::TransformOpenVR34TranslateRelative(matrix, ConfigManager::GetValue(configid_float_overlay_offset_right),\n                                                                            ConfigManager::GetValue(configid_float_overlay_offset_up),\n                                                                            ConfigManager::GetValue(configid_float_overlay_offset_forward));\n\n                vr::VROverlay()->SetOverlayTransformTrackedDeviceRelative(ovrl_handle, device_index, &matrix);\n            }\n            else //No controller connected, uh put it to 0?\n            {\n                vr::VROverlay()->SetOverlayTransformAbsolute(ovrl_handle, universe_origin, &matrix);\n            }\n            break;\n        }\n        case ovrl_origin_left_hand:\n        {\n            vr::TrackedDeviceIndex_t device_index = vr::VRSystem()->GetTrackedDeviceIndexForControllerRole(vr::TrackedControllerRole_LeftHand);\n\n            if (device_index != vr::k_unTrackedDeviceIndexInvalid)\n            {\n                matrix = ConfigManager::Get().GetOverlayDetachedTransform().toOpenVR34();\n\n                //Offset transform by additional offset values\n                vr::IVRSystemEx::TransformOpenVR34TranslateRelative(matrix, ConfigManager::GetValue(configid_float_overlay_offset_right),\n                                                                            ConfigManager::GetValue(configid_float_overlay_offset_up),\n                                                                            ConfigManager::GetValue(configid_float_overlay_offset_forward));\n\n                vr::VROverlay()->SetOverlayTransformTrackedDeviceRelative(ovrl_handle, device_index, &matrix);\n            }\n            else //No controller connected, uh put it to 0?\n            {\n                vr::VROverlay()->SetOverlayTransformAbsolute(ovrl_handle, universe_origin, &matrix);\n            }\n            break;\n        }\n        case ovrl_origin_aux:\n        {\n            vr::TrackedDeviceIndex_t index_tracker = vr::IVRSystemEx::GetFirstVRTracker();\n\n            if (index_tracker != vr::k_unTrackedDeviceIndexInvalid)\n            {\n                matrix = ConfigManager::Get().GetOverlayDetachedTransform().toOpenVR34();\n\n                //Offset transform by additional offset values\n                vr::IVRSystemEx::TransformOpenVR34TranslateRelative(matrix, ConfigManager::GetValue(configid_float_overlay_offset_right),\n                                                                            ConfigManager::GetValue(configid_float_overlay_offset_up),\n                                                                            ConfigManager::GetValue(configid_float_overlay_offset_forward));\n\n                vr::VROverlay()->SetOverlayTransformTrackedDeviceRelative(ovrl_handle, index_tracker, &matrix);\n            }\n            else //Not connected, uh put it to 0?\n            {\n                vr::VROverlay()->SetOverlayTransformAbsolute(ovrl_handle, universe_origin, &matrix);\n            }\n\n            break;\n        }\n    }\n\n    //Update Width\n    vr::VROverlay()->SetOverlayWidthInMeters(ovrl_handle, width);\n\n    //Update Curvature\n    vr::VROverlay()->SetOverlayCurvature(ovrl_handle, ConfigManager::GetValue(configid_float_overlay_curvature));\n\n    //Update Brightness\n    //We use the logarithmic counterpart since the changes in higher steps are barely visible while the lower range can really use those additional steps\n    float brightness = lin2log(ConfigManager::GetValue(configid_float_overlay_brightness)) * ConfigManager::GetValue(configid_float_overlay_state_brightness_extra_multiplier);\n    vr::VROverlay()->SetOverlayColor(ovrl_handle, brightness, brightness, brightness);\n\n    //Set backside visibility\n    vr::VROverlay()->SetOverlayFlag(ovrl_handle, vr::VROverlayFlags_NoBackside, !data.ConfigBool[configid_bool_overlay_show_backside]);\n\n    //Set last tick for dashboard dummy delayed update\n    m_LastApplyTransformTick = ::GetTickCount64();\n}\n\nvoid OutputManager::ApplySettingCrop()\n{\n    Overlay& overlay = OverlayManager::Get().GetCurrentOverlay();\n    OverlayConfigData& data = OverlayManager::Get().GetCurrentConfigData();\n    vr::VROverlayHandle_t ovrl_handle = overlay.GetHandle();\n\n    //UI overlays don't do any cropping and handle the texture bounds themselves\n    if (overlay.GetTextureSource() == ovrl_texsource_ui)\n        return;\n\n    //Set up overlay cropping\n    vr::VRTextureBounds_t tex_bounds;\n    vr::VRTextureBounds_t tex_bounds_prev;\n\n    const int content_width  = data.ConfigInt[configid_int_overlay_state_content_width];\n    const int content_height = data.ConfigInt[configid_int_overlay_state_content_height];\n\n    if ( (overlay.GetTextureSource() == ovrl_texsource_none) || ((content_width == -1) && (content_height == -1)) )\n    {\n        tex_bounds.uMin = 0.0f;\n        tex_bounds.vMin = 0.0f;\n        tex_bounds.uMax = 1.0f;\n        tex_bounds.vMax = 1.0f;\n\n        vr::VROverlay()->SetOverlayTextureBounds(ovrl_handle, &tex_bounds);\n        return;\n    }\n\n    overlay.UpdateValidatedCropRect();\n    const DPRect& crop_rect = overlay.GetValidatedCropRect();\n\n    const bool is_3d_enabled = ConfigManager::GetValue(configid_bool_overlay_3D_enabled);\n    const int mode_3d = ConfigManager::GetValue(configid_int_overlay_3D_mode);\n    const bool is_ou3d = ( (is_3d_enabled) && ((mode_3d == ovrl_3Dmode_ou) || (mode_3d == ovrl_3Dmode_hou)) );\n\n    //Use full texture if everything checks out or 3D mode is Over-Under (converted to a 1:1 fitting texture)\n    if ( (is_ou3d) || ( (crop_rect.GetTL().x == 0) && (crop_rect.GetTL().y == 0) && (crop_rect.GetWidth() == content_width) && (crop_rect.GetHeight() == content_height) ) )\n    {\n        tex_bounds.uMin = 0.0f;\n        tex_bounds.vMin = 0.0f;\n        tex_bounds.uMax = 1.0f;\n        tex_bounds.vMax = 1.0f;\n    }\n    else\n    {\n        //Otherwise offset the calculated texel coordinates a bit. This is to reduce having colors from outside the cropping area bleeding in from the texture filtering\n        //This means the border pixels of the overlay are smaller, but that's something we need to accept it seems\n        //This doesn't 100% solve texel bleed, especially not on high overlay rendering quality where it can require pretty big offsets depending on overlay size/distance\n        float offset_x = (crop_rect.GetWidth() <= 2) ? 0.0f : 1.5f, offset_y = (crop_rect.GetHeight() <= 2) ? 0.0f : 1.5f; //Yes, we do handle the case of <3 pixel crops\n\n        tex_bounds.uMin = (crop_rect.GetTL().x + offset_x) / content_width;\n        tex_bounds.vMin = (crop_rect.GetTL().y + offset_y) / content_height;\n        tex_bounds.uMax = (crop_rect.GetBR().x - offset_x) / content_width;\n        tex_bounds.vMax = (crop_rect.GetBR().y - offset_y) / content_height;\n    }\n\n    //If capture source is WinRT, set 3D mode with cropping values\n    if (ConfigManager::GetValue(configid_int_overlay_capture_source) == ovrl_capsource_winrt_capture)\n    {\n        DPWinRT_SetOverlayOverUnder3D(ovrl_handle, is_ou3d, crop_rect.GetTL().x, crop_rect.GetTL().y, crop_rect.GetWidth(), crop_rect.GetHeight());\n    }\n    else if (ConfigManager::GetValue(configid_int_overlay_capture_source) == ovrl_capsource_browser) //Same with browser\n    {\n        DPBrowserAPIClient::Get().DPBrowser_SetOverUnder3D(ovrl_handle, is_ou3d, crop_rect.GetTL().x, crop_rect.GetTL().y, crop_rect.GetWidth(), crop_rect.GetHeight());\n    }\n    else\n    {\n        //For Desktop Duplication, compare old to new bounds to see if a full refresh is required\n        //However, for Over-Under 3D this won't work and we just always do the full refresh\n        bool needs_full_refresh = is_ou3d;\n\n        if (!needs_full_refresh)\n        {\n            vr::VROverlay()->GetOverlayTextureBounds(ovrl_handle, &tex_bounds_prev);\n\n            needs_full_refresh = ((tex_bounds.uMin < tex_bounds_prev.uMin) || (tex_bounds.vMin < tex_bounds_prev.vMin) || \n                                  (tex_bounds.uMax > tex_bounds_prev.uMax) || (tex_bounds.vMax > tex_bounds_prev.vMax));\n        }\n        \n        if (needs_full_refresh)\n        {\n            RefreshOpenVROverlayTexture(DPRect(-1, -1, -1, -1), true);\n        }\n    }\n\n    vr::VROverlay()->SetOverlayTextureBounds(ovrl_handle, &tex_bounds);\n}\n\nvoid OutputManager::ApplySettingInputMode()\n{\n    //Apply/Restore mouse settings first\n    ApplySettingMouseInput();\n\n    const bool drag_or_select_mode_enabled = ( (ConfigManager::GetValue(configid_bool_state_overlay_dragmode)) || (ConfigManager::GetValue(configid_bool_state_overlay_selectmode)) );\n    //Always applies to all overlays\n    unsigned int current_overlay_old = OverlayManager::Get().GetCurrentOverlayID();\n    for (unsigned int i = 0; i < OverlayManager::Get().GetOverlayCount(); ++i)\n    {\n        OverlayManager::Get().SetCurrentOverlayID(i);\n\n        const Overlay& overlay_current = OverlayManager::Get().GetCurrentOverlay();\n        vr::VROverlayHandle_t ovrl_handle = overlay_current.GetHandle();\n\n        if ((ConfigManager::GetValue(configid_bool_overlay_input_enabled)) || (drag_or_select_mode_enabled) )\n        {\n            //Don't activate drag mode for HMD origin when the pointer is also the HMD (or it's the dashboard overlay)\n            if ( ((ConfigManager::Get().GetPrimaryLaserPointerDevice() == vr::k_unTrackedDeviceIndex_Hmd) && (ConfigManager::GetValue(configid_int_overlay_origin) == ovrl_origin_hmd)) )\n            {\n                vr::VROverlay()->SetOverlayInputMethod(ovrl_handle, vr::VROverlayInputMethod_None);\n            }\n            else\n            {\n                vr::VROverlay()->SetOverlayInputMethod(ovrl_handle, vr::VROverlayInputMethod_Mouse);\n            }\n        }\n        else\n        {\n            vr::VROverlay()->SetOverlayInputMethod(ovrl_handle, vr::VROverlayInputMethod_None);\n        }\n\n        //Sync matrix if it's been turned off\n        if ( (!drag_or_select_mode_enabled) && (!ConfigManager::GetValue(configid_bool_state_overlay_dragmode)) )\n        {\n            DetachedTransformSync(i);\n        }\n\n        ApplySettingTransform();\n    }\n\n    OverlayManager::Get().SetCurrentOverlayID(current_overlay_old);\n}\n\nvoid OutputManager::ApplySettingMouseInput()\n{\n    //Set double-click assist duration from user config value\n    if (ConfigManager::GetValue(configid_int_input_mouse_dbl_click_assist_duration_ms) == -1)\n    {\n        ConfigManager::SetValue(configid_int_state_mouse_dbl_click_assist_duration_ms, ::GetDoubleClickTime());\n    }\n    else\n    {\n        ConfigManager::SetValue(configid_int_state_mouse_dbl_click_assist_duration_ms, ConfigManager::GetValue(configid_int_input_mouse_dbl_click_assist_duration_ms));\n    }\n\n    const bool drag_mode_enabled = ((ConfigManager::GetValue(configid_bool_state_overlay_dragmode)) || (m_OvrlDirectDragActive));\n    const bool select_mode_enabled = ConfigManager::GetValue(configid_bool_state_overlay_selectmode);\n    const bool drag_or_select_mode_enabled = ((drag_mode_enabled) || (select_mode_enabled));\n    //Always applies to all overlays\n    unsigned int current_overlay_old = OverlayManager::Get().GetCurrentOverlayID();\n    for (unsigned int i = 0; i < OverlayManager::Get().GetOverlayCount(); ++i)\n    {\n        OverlayManager::Get().SetCurrentOverlayID(i);\n\n        Overlay& overlay = OverlayManager::Get().GetCurrentOverlay();\n        vr::VROverlayHandle_t ovrl_handle = overlay.GetHandle();\n\n        //Set input method (possibly overridden by ApplyInputMethod() right afterwards)\n        if ((ConfigManager::GetValue(configid_bool_overlay_input_enabled) || (drag_or_select_mode_enabled)))\n        {\n            //Temp drag needs every input-enabled overlay to have smooth scroll\n            if ( (ConfigManager::GetValue(configid_bool_input_mouse_scroll_smooth)) || (ConfigManager::GetValue(configid_bool_state_overlay_dragmode_temp)) || (drag_mode_enabled) )\n            {\n                vr::VROverlay()->SetOverlayFlag(ovrl_handle, vr::VROverlayFlags_SendVRDiscreteScrollEvents, false);\n                vr::VROverlay()->SetOverlayFlag(ovrl_handle, vr::VROverlayFlags_SendVRSmoothScrollEvents,   true);\n            }\n            else\n            {\n                vr::VROverlay()->SetOverlayFlag(ovrl_handle, vr::VROverlayFlags_SendVRDiscreteScrollEvents, true);\n                vr::VROverlay()->SetOverlayFlag(ovrl_handle, vr::VROverlayFlags_SendVRSmoothScrollEvents,   false);\n            }\n\n            vr::VROverlay()->SetOverlayInputMethod(ovrl_handle, vr::VROverlayInputMethod_Mouse);\n        }\n        else\n        {\n            vr::VROverlay()->SetOverlayInputMethod(ovrl_handle, vr::VROverlayInputMethod_None);\n        }\n\n        //Set intersection blob state\n        bool hide_intersection = false;\n\n        if (OverlayManager::Get().GetTheaterOverlayID() == i) //Always hide if theater overlay\n        {\n            hide_intersection = true;\n        }\n        else if ( (overlay.GetTextureSource() != ovrl_texsource_none) && (overlay.GetTextureSource() != ovrl_texsource_ui) && (overlay.GetTextureSource() != ovrl_texsource_browser) && \n                  (!drag_mode_enabled) && (!select_mode_enabled) )\n        {\n            hide_intersection = !ConfigManager::GetValue(configid_bool_input_mouse_render_intersection_blob);\n        }\n\n        vr::VROverlay()->SetOverlayFlag(ovrl_handle, vr::VROverlayFlags_HideLaserIntersection, hide_intersection);\n\n        ApplySettingMouseScale();\n\n        //Set intersection mask for desktop duplication overlays\n        if (overlay.GetTextureSource() == ovrl_texsource_desktop_duplication)\n        {\n            std::vector<vr::VROverlayIntersectionMaskPrimitive_t> primitives;\n            primitives.reserve(m_DesktopRects.size());\n\n            for (const DPRect& rect : m_DesktopRects)\n            {\n                vr::VROverlayIntersectionMaskPrimitive_t primitive;\n                primitive.m_nPrimitiveType = vr::OverlayIntersectionPrimitiveType_Rectangle;\n                primitive.m_Primitive.m_Rectangle.m_flTopLeftX = rect.GetTL().x - m_DesktopX;\n                primitive.m_Primitive.m_Rectangle.m_flTopLeftY = rect.GetTL().y - m_DesktopY;\n                primitive.m_Primitive.m_Rectangle.m_flWidth    = rect.GetWidth();\n                primitive.m_Primitive.m_Rectangle.m_flHeight   = rect.GetHeight();\n\n                primitives.push_back(primitive);\n            }\n\n            vr::EVROverlayError err = vr::VROverlay()->SetOverlayIntersectionMask(ovrl_handle, primitives.data(), (uint32_t)primitives.size());\n        }\n        else if (overlay.GetTextureSource() != ovrl_texsource_ui) //Or reset intersection mask if not UI overlay\n        {\n            vr::VROverlay()->SetOverlayIntersectionMask(ovrl_handle, nullptr, 0);\n        }\n    }\n\n    OverlayManager::Get().SetCurrentOverlayID(current_overlay_old);\n}\n\nvoid OutputManager::ApplySettingMouseScale()\n{\n    Overlay& overlay = OverlayManager::Get().GetCurrentOverlay();\n    OverlayConfigData& data = OverlayManager::Get().GetCurrentConfigData();\n    vr::VROverlayHandle_t ovrl_handle = overlay.GetHandle();\n\n    //UI overlays handle the mouse scale themselves\n    if (overlay.GetTextureSource() == ovrl_texsource_ui)\n        return;\n\n    //Set mouse scale based on capture source\n    vr::HmdVector2_t mouse_scale = {0};\n\n    if (overlay.GetTextureSource() == ovrl_texsource_none)\n    {\n        //The mouse scale defines the surface aspect ratio for the intersection test... yeah. If it's off there will be hits over empty space, so try to match it even here\n        //uint32_t ovrl_tex_width = 1, ovrl_tex_height = 1;\n\n        //Content size might not be what the current texture size is in case of ovrl_texsource_none\n        /*if (vr::VROverlay()->GetOverlayTextureSize(ovrl_handle, &ovrl_tex_width, &ovrl_tex_height) == vr::VROverlayError_None) //GetOverlayTextureSize() currently leaks, so don't use it\n        {\n        mouse_scale.v[0] = ovrl_tex_width;\n        mouse_scale.v[1] = ovrl_tex_height;\n        }\n        else*/ //ovrl_texsource_none pretty much means overlay output error texture, so fall back to that if we can't get the real size\n        {\n            mouse_scale.v[0] = k_lOverlayOutputErrorTextureWidth;\n            mouse_scale.v[1] = k_lOverlayOutputErrorTextureHeight;\n        }\n    }\n    else\n    {\n        switch (data.ConfigInt[configid_int_overlay_capture_source])\n        {\n            case ovrl_capsource_desktop_duplication:\n            {\n                mouse_scale.v[0] = m_DesktopWidth;\n                mouse_scale.v[1] = m_DesktopHeight;\n                break;\n            }\n            case ovrl_capsource_winrt_capture:\n            case ovrl_capsource_browser:\n            {\n                //Use duplication IDs' data if any is set\n                int duplication_id = ConfigManager::GetValue(configid_int_overlay_duplication_id);\n                const OverlayConfigData& overlay_data = (duplication_id != -1) ? OverlayManager::Get().GetConfigData((unsigned int)duplication_id) : data;\n\n                mouse_scale.v[0] = overlay_data.ConfigInt[configid_int_overlay_state_content_width];\n                mouse_scale.v[1] = overlay_data.ConfigInt[configid_int_overlay_state_content_height];\n                break;\n            }\n        }\n\n        //Adjust for 3D so surface aspect ratio for laser pointing matches what is seen.\n        //This blocks half or more pixels, but it's not clear what the real target coordinates are with content being different in each eye\n        if (data.ConfigBool[configid_bool_overlay_3D_enabled])\n        {\n            switch (data.ConfigInt[configid_int_overlay_3D_mode])\n            {\n                case ovrl_3Dmode_hsbs:\n                case ovrl_3Dmode_sbs:\n                {\n                    mouse_scale.v[0] /= 2.0f; \n                    break;\n                }\n                case ovrl_3Dmode_hou:\n                case ovrl_3Dmode_ou:\n                {\n                    //OU converted to SBS will have texture size based on crop rect\n                    const DPRect& crop_rect = overlay.GetValidatedCropRect();\n\n                    mouse_scale.v[0] = crop_rect.GetWidth();\n                    mouse_scale.v[1] = crop_rect.GetHeight() / 2.0f;\n                }\n            }\n        }\n    }\n\n    vr::VROverlay()->SetOverlayMouseScale(ovrl_handle, &mouse_scale);\n}\n\nvoid OutputManager::ApplySettingUpdateLimiter()\n{\n    //Here's the deal with the fps-based limiter: It just barely works\n    //A simple fps cut-off doesn't work since mouse updates add up to them\n    //Using the right frame time value seems to work in most cases\n    //A full-range, user-chosen fps value doesn't really work though, as the frame time values required don't seem to predictably change (\"1000/fps\" is close, but the needed adjustment varies)\n    //The frame time method also doesn't work reliably above 50 fps. It limits, but the resulting fps isn't constant.\n    //This is why the fps limiter is somewhat restricted in what settings it offers. It does cover the most common cases, however.\n    //The frame time limiter is still there to offer more fine-tuning after all\n\n    //Map tested frame time values to the fps enum IDs\n    //FPS:                                 1       2       5     10      15      20      25      30      40      50\n    const float fps_enum_values_ms[] = { 985.0f, 485.0f, 195.0f, 96.50f, 63.77f, 47.76f, 33.77f, 31.73f, 23.72f, 15.81f };\n\n    float limit_ms = 0.0f;\n\n    //Set limiter value from global setting\n    if (ConfigManager::GetValue(configid_int_performance_update_limit_mode) == update_limit_mode_ms)\n    {\n        limit_ms = ConfigManager::GetValue(configid_float_performance_update_limit_ms);\n    }\n    else if (ConfigManager::GetValue(configid_int_performance_update_limit_mode) == update_limit_mode_fps)\n    {\n        int enum_id = ConfigManager::GetValue(configid_int_performance_update_limit_fps);\n\n        if (enum_id <= update_limit_fps_50)\n        {\n            limit_ms = fps_enum_values_ms[enum_id];\n        }\n    }\n\n    LARGE_INTEGER limit_delay_global = {0};\n    limit_delay_global.QuadPart = 1000.0f * limit_ms;\n\n    //See if there are any overrides from visible overlays\n    //This is the straight forward and least error-prone way, not quite the most efficient one\n    //Calls to this are minimized and there typically aren't many overlays so it's not really that bad (and we do iterate over all of them in many other places too)\n    bool is_first_override = true;\n    for (unsigned int i = 0; i < OverlayManager::Get().GetOverlayCount(); ++i)\n    {\n        const Overlay& overlay        = OverlayManager::Get().GetOverlay(i);\n        const OverlayConfigData& data = OverlayManager::Get().GetConfigData(i);\n\n        if ( (overlay.IsVisible()) && (data.ConfigInt[configid_int_overlay_capture_source] == ovrl_capsource_desktop_duplication) && \n             (data.ConfigInt[configid_int_overlay_update_limit_override_mode] != update_limit_mode_off) )\n        {\n            float override_ms = 0.0f;\n\n            if (data.ConfigInt[configid_int_overlay_update_limit_override_mode] == update_limit_mode_ms)\n            {\n                override_ms = data.ConfigFloat[configid_float_overlay_update_limit_override_ms];\n            }\n            else\n            {\n                int enum_id = data.ConfigInt[configid_int_overlay_update_limit_override_fps];\n\n                if (enum_id <= update_limit_fps_50)\n                {\n                    override_ms = fps_enum_values_ms[enum_id];\n                }\n            }\n\n            //Use override if it results in more updates (except first override, which always has priority over global setting)\n            if ( (is_first_override) || (override_ms < limit_ms) )\n            {\n                limit_ms = override_ms;\n                is_first_override = false;\n            }\n        }\n        else if (data.ConfigInt[configid_int_overlay_capture_source] == ovrl_capsource_winrt_capture) //Set limit values for WinRT overlays as well\n        {\n            LARGE_INTEGER limit_delay = limit_delay_global;\n\n            if (data.ConfigInt[configid_int_overlay_update_limit_override_mode] == update_limit_mode_ms)\n            {\n                limit_delay.QuadPart = 1000.0f * data.ConfigFloat[configid_float_overlay_update_limit_override_ms];\n            }\n            else if (data.ConfigInt[configid_int_overlay_update_limit_override_mode] == update_limit_mode_fps)\n            {\n                int enum_id = data.ConfigInt[configid_int_overlay_update_limit_override_fps];\n\n                if (enum_id <= update_limit_fps_50)\n                {\n                    limit_delay.QuadPart = 1000.0f * fps_enum_values_ms[enum_id];\n                }\n            }\n\n            //Calling this regardless of change might be overkill, but doesn't seem too bad for now\n            DPWinRT_SetOverlayUpdateLimitDelay(overlay.GetHandle(), limit_delay.QuadPart);\n        }\n    }\n\n    m_PerformanceUpdateLimiterDelay.QuadPart = 1000.0f * limit_ms;\n}\n\nvoid OutputManager::ApplySettingExtraBrightness()\n{\n    Overlay& overlay = OverlayManager::Get().GetCurrentOverlay();\n    OverlayConfigData& data = OverlayManager::Get().GetCurrentConfigData();\n\n    float extra_brightness_mulitplier = 1.0f;\n\n    if (ConfigManager::GetValue(configid_bool_performance_hdr_mirroring))\n    {\n        const bool ignore_wmr_vscreens = (ConfigManager::GetValue(configid_int_interface_wmr_ignore_vscreens) == 1);\n\n        switch (overlay.GetTextureSource())\n        {\n            case ovrl_texsource_desktop_duplication:\n            case ovrl_texsource_desktop_duplication_3dou_converted:\n            {\n                extra_brightness_mulitplier = GetDesktopHDRWhiteLevelAdjustment(data.ConfigInt[configid_int_overlay_desktop_id], false, ignore_wmr_vscreens);\n                break;\n            }\n            case ovrl_texsource_winrt_capture:\n            {\n                int desktop_id = data.ConfigInt[configid_int_overlay_winrt_desktop_id];\n\n                //Window capture, need to find the desktop ID it's on\n                if (desktop_id == -2)\n                {\n                    HMONITOR monitor_handle = ::MonitorFromWindow((HWND)data.ConfigHandle[configid_handle_overlay_state_winrt_hwnd], MONITOR_DEFAULTTONEAREST);\n                    desktop_id = GetDisplayIDFromHMonitor(monitor_handle, ignore_wmr_vscreens);\n                }\n\n                extra_brightness_mulitplier = GetDesktopHDRWhiteLevelAdjustment(desktop_id, true, ignore_wmr_vscreens);\n            }\n            default: break;\n        }\n    }\n\n    if (data.ConfigFloat[configid_float_overlay_state_brightness_extra_multiplier] != extra_brightness_mulitplier)\n    {\n        data.ConfigFloat[configid_float_overlay_state_brightness_extra_multiplier] = extra_brightness_mulitplier;\n\n        //Send change over to UI\n        IPCManager::Get().PostConfigMessageToUIApp(configid_int_state_overlay_current_id_override, (int)overlay.GetID());\n        IPCManager::Get().PostConfigMessageToUIApp(configid_float_overlay_state_brightness_extra_multiplier, extra_brightness_mulitplier);\n        IPCManager::Get().PostConfigMessageToUIApp(configid_int_state_overlay_current_id_override, -1);\n    }\n}\n\nvoid OutputManager::DetachedTransformSync(unsigned int overlay_id)\n{\n    IPCManager::Get().PostConfigMessageToUIApp(configid_int_state_overlay_transform_sync_target_id, (int)overlay_id);\n\n    const float* values = OverlayManager::Get().GetConfigData(overlay_id).ConfigTransform.get();\n    for (size_t i = 0; i < 16; ++i)\n    {\n        IPCManager::Get().PostConfigMessageToUIApp(configid_float_state_overlay_transform_sync_value, values[i]);\n    }\n\n    IPCManager::Get().PostConfigMessageToUIApp(configid_int_state_overlay_transform_sync_target_id, -1);\n}\n\nvoid OutputManager::DetachedTransformSyncAll()\n{\n    for (unsigned int i = 0; i < OverlayManager::Get().GetOverlayCount(); ++i)\n    {\n        DetachedTransformSync(i);\n    }\n}\n\nvoid OutputManager::DetachedTransformReset(unsigned int overlay_id_ref)\n{\n    vr::TrackingUniverseOrigin universe_origin = vr::TrackingUniverseStanding;\n    Matrix4& transform = ConfigManager::Get().GetOverlayDetachedTransform();\n    transform.identity(); //Reset to identity\n\n    OverlayOrigin overlay_origin = (OverlayOrigin)ConfigManager::GetValue(configid_int_overlay_origin);\n    const Overlay& overlay_ref = OverlayManager::Get().GetOverlay(overlay_id_ref);\n    vr::VROverlayHandle_t ovrl_handle_ref = overlay_ref.GetHandle(); //Results in vr::k_ulOverlayHandleInvalid for k_ulOverlayID_None\n\n    const bool dont_use_reference = (overlay_origin >= ovrl_origin_hmd) || ( (overlay_origin == ovrl_origin_hmd_floor) && (ConfigManager::GetValue(configid_bool_overlay_origin_hmd_floor_use_turning)) );\n\n    if ( (ovrl_handle_ref == vr::k_ulOverlayHandleInvalid) && (!dont_use_reference) )\n    {\n        const Overlay& overlay_dashboard = OverlayManager::Get().GetPrimaryDashboardOverlay();\n\n        if (overlay_dashboard.GetID() != OverlayManager::Get().GetCurrentOverlayID())\n        {\n            overlay_id_ref  = overlay_dashboard.GetID();\n            ovrl_handle_ref = overlay_dashboard.GetHandle();\n        }\n    }\n\n    //Position next to reference if available\n    if (ovrl_handle_ref != vr::k_ulOverlayHandleInvalid)\n    {\n        OverlayConfigData& data = OverlayManager::Get().GetCurrentConfigData();\n        const OverlayConfigData& data_ref = OverlayManager::Get().GetConfigData(overlay_id_ref);\n        vr::HmdMatrix34_t overlay_transform;\n        vr::HmdVector2_t mouse_scale;\n\n        bool ref_overlay_changed = false;\n        float ref_overlay_alpha_orig = 0.0f;\n\n        //GetTransformForOverlayCoordinates() won't work if the reference overlay is not visible, so make it \"visible\" by showing it with 0% alpha\n        if (!vr::VROverlay()->IsOverlayVisible(ovrl_handle_ref))\n        {\n            vr::VROverlay()->GetOverlayAlpha(ovrl_handle_ref, &ref_overlay_alpha_orig);\n            vr::VROverlay()->SetOverlayAlpha(ovrl_handle_ref, 0.0f);\n            vr::VROverlay()->ShowOverlay(ovrl_handle_ref);\n\n            //Showing overlays and getting coordinates from them has a race condition if it's the first time the overlay is shown\n            //Doesn't seem like it can be truly detected when it's ready, so as cheap as it is, this Sleep() seems to get around the issue\n            ::Sleep(50);\n\n            ref_overlay_changed = true;\n        }\n\n        //Get mouse scale for overlay coordinate offset\n        vr::VROverlay()->GetOverlayMouseScale(ovrl_handle_ref, &mouse_scale);\n\n        //Get x-offset multiplier, taking width differences into account\n        float ref_overlay_width;\n        vr::VROverlay()->GetOverlayWidthInMeters(ovrl_handle_ref, &ref_overlay_width);\n        float dashboard_scale = GetDashboardScale();\n        float x_offset_mul = ( (data.ConfigFloat[configid_float_overlay_width] / ref_overlay_width) / 2.0f) + 1.0f;\n\n        //Put it next to the reference overlay so it can actually be seen\n        vr::HmdVector2_t coordinate_offset = {mouse_scale.v[0] * x_offset_mul, mouse_scale.v[1] / 2.0f};\n        vr::VROverlay()->GetTransformForOverlayCoordinates(ovrl_handle_ref, universe_origin, coordinate_offset, &overlay_transform);\n        transform = overlay_transform;\n\n        //Counteract additonal offset that might've been present on the transform\n        transform.translate_relative(-data_ref.ConfigFloat[configid_float_overlay_offset_right],\n                                     -data_ref.ConfigFloat[configid_float_overlay_offset_up],\n                                     -data_ref.ConfigFloat[configid_float_overlay_offset_forward]);\n\n        //Counteract origin offset for dashboard origin overlays\n        if (overlay_origin == ovrl_origin_dashboard)\n        {\n            float height = GetOverlayHeight(overlay_id_ref);\n            transform.translate_relative(0.0f, height / -2.0f, 0.0f);\n        }\n\n        //Restore reference overlay state if it was changed\n        if (ref_overlay_changed)\n        {\n            vr::VROverlay()->HideOverlay(ovrl_handle_ref);\n            vr::VROverlay()->SetOverlayAlpha(ovrl_handle_ref, ref_overlay_alpha_orig);\n        }\n\n        //If the reference overlay appears to be below ground we assume it has an invalid origin (i.e. dashboard tab never opened for dashboard overlay) and use the fallback transform\n        if (transform.getTranslation().y < 0.0f)\n        {\n            transform = GetFallbackOverlayTransform();\n        }\n\n        //Adapt to base offset for non-room origins\n        if (overlay_origin != ovrl_origin_room)\n        {\n            Matrix4 transform_base = m_OverlayDragger.GetBaseOffsetMatrix();\n            transform_base.invert();\n            transform = transform_base * transform;\n        }\n    }\n    else //Otherwise add some default offset to the previously reset to identity value\n    {\n        switch (overlay_origin)\n        {\n            case ovrl_origin_room:\n            case ovrl_origin_hmd_floor:\n            {\n                //Put it in front of the user's face instead when turning is enabled\n                if ( (overlay_origin == ovrl_origin_hmd_floor) && (ConfigManager::GetValue(configid_bool_overlay_origin_hmd_floor_use_turning)) )\n                {\n                    transform.translate_relative(0.0f, 1.0f, -2.5f);\n                    break;\n                }\n\n                vr::HmdMatrix34_t overlay_transform = {0};\n                vr::VROverlay()->GetTransformForOverlayCoordinates(m_OvrlHandleDashboardDummy, vr::TrackingUniverseStanding, {0.5f, 0.5f}, &overlay_transform);\n\n                transform = overlay_transform;\n\n                //Use fallback transform if dashboard dummy is still set to identity (never opened Desktop+ dashboard tab)\n                if (transform == Matrix4())\n                {\n                    transform = GetFallbackOverlayTransform();\n                }\n\n                //Adapt to base offset for non-room origin\n                if (overlay_origin != ovrl_origin_room)\n                {\n                    Matrix4 transform_base = m_OverlayDragger.GetBaseOffsetMatrix();\n                    transform_base.invert();\n                    transform = transform_base * transform;\n                }\n\n                //Remove dashboard scale from transform\n                Vector3 translation = transform.getTranslation();\n                transform.setTranslation({0.0f, 0.0f, 0.0f});\n                transform.scale(1.0f / GetDashboardScale());\n                transform.setTranslation(translation);\n\n                break;\n            }\n            case ovrl_origin_seated_universe:\n            {\n                transform.translate_relative(0.0f, 0.0f, -1.0f);\n                break;\n            }\n            case ovrl_origin_dashboard:\n            {\n                //Counteract dashboard scale applied by origin\n                transform.scale(1.0f / GetDashboardScale());\n                break;\n            }\n            case ovrl_origin_hmd:\n            {\n                transform.translate_relative(0.0f, 0.0f, -2.5f);\n                break;\n            }\n            case ovrl_origin_right_hand:\n            {\n                //Set it to a controller component so it doesn't appear right where the laser pointer comes out\n                //There's some doubt about this code, but it seems to work in the end (is there really no better way?)\n                char buffer[vr::k_unMaxPropertyStringSize];\n                vr::VRSystem()->GetStringTrackedDeviceProperty(vr::VRSystem()->GetTrackedDeviceIndexForControllerRole(vr::TrackedControllerRole_RightHand), \n                                                               vr::Prop_RenderModelName_String, buffer, vr::k_unMaxPropertyStringSize);\n\n                vr::VRInputValueHandle_t input_value = vr::k_ulInvalidInputValueHandle;\n                vr::VRInput()->GetInputSourceHandle(\"/user/hand/right\", &input_value);\n                vr::RenderModel_ControllerMode_State_t controller_state = {0};\n                vr::RenderModel_ComponentState_t component_state = {0};\n            \n                if (vr::VRRenderModels()->GetComponentStateForDevicePath(buffer, vr::k_pch_Controller_Component_HandGrip, input_value, &controller_state, &component_state))\n                {\n                    transform = component_state.mTrackingToComponentLocal;\n                    transform.rotateX(-90.0f);\n                    transform.translate_relative(0.0f, -0.1f, 0.0f); //This seems like a good default, at least for Index controllers\n                }\n\n                break;\n            }\n            case ovrl_origin_left_hand:\n            {\n                char buffer[vr::k_unMaxPropertyStringSize];\n                vr::VRSystem()->GetStringTrackedDeviceProperty(vr::VRSystem()->GetTrackedDeviceIndexForControllerRole(vr::TrackedControllerRole_LeftHand), \n                                                               vr::Prop_RenderModelName_String, buffer, vr::k_unMaxPropertyStringSize);\n\n                vr::VRInputValueHandle_t input_value = vr::k_ulInvalidInputValueHandle;\n                vr::VRInput()->GetInputSourceHandle(\"/user/hand/left\", &input_value);\n                vr::RenderModel_ControllerMode_State_t controller_state = {0};\n                vr::RenderModel_ComponentState_t component_state = {0};\n            \n                if (vr::VRRenderModels()->GetComponentStateForDevicePath(buffer, vr::k_pch_Controller_Component_HandGrip, input_value, &controller_state, &component_state))\n                {\n                    transform = component_state.mTrackingToComponentLocal;\n                    transform.rotateX(-90.0f);\n                    transform.translate_relative(0.0f, -0.1f, 0.0f);\n                }\n\n                break;\n            }\n            case ovrl_origin_aux:\n            {\n                transform.translate_relative(0.0f, 0.0f, -0.05f);\n                break;\n            }\n            default: break;\n        }\n    }\n\n    //Sync reset with UI app\n    DetachedTransformSync(OverlayManager::Get().GetCurrentOverlayID());\n\n    ApplySettingTransform();\n}\n\nvoid OutputManager::DetachedTransformAdjust(unsigned int packed_value)\n{\n    Matrix4& transform = ConfigManager::Get().GetOverlayDetachedTransform();\n\n    //Unpack\n    IPCActionOverlayPosAdjustTarget target = (IPCActionOverlayPosAdjustTarget)(packed_value & 0xF);\n    bool increase = (packed_value >> 4);\n\n    //\"To HMD\" / LookAt button, seperate code path entirely\n    if (target == ipcactv_ovrl_pos_adjust_lookat)\n    {\n        //Get HMD pose\n        vr::TrackedDevicePose_t poses[vr::k_unTrackedDeviceIndex_Hmd + 1];\n        vr::TrackingUniverseOrigin universe_origin = vr::TrackingUniverseStanding;\n        vr::VRSystem()->GetDeviceToAbsoluteTrackingPose(universe_origin, vr::IVRSystemEx::GetTimeNowToPhotons(), poses, vr::k_unTrackedDeviceIndex_Hmd + 1);\n\n        if (poses[vr::k_unTrackedDeviceIndex_Hmd].bPoseIsValid)\n        {\n            Matrix4 mat_base_offset = m_OverlayDragger.GetBaseOffsetMatrix();\n\n            //Preserve scaling from transform, which can be present in matrices originating from the dashboard\n            Vector3 row_1(transform[0], transform[1], transform[2]);\n            float scale_x = row_1.length(); //Scaling is always uniform so we just check the x-axis\n            //Dashboard origin itself also contains scale, so take the base scale in account as well\n            Vector3 row_1_base(mat_base_offset[0], mat_base_offset[1], mat_base_offset[2]);\n            float scale_x_base = row_1_base.length();\n            scale_x *= scale_x_base;\n\n            //Rotate towards HMD position\n            Matrix4 mat_hmd(poses[vr::k_unTrackedDeviceIndex_Hmd].mDeviceToAbsoluteTracking);\n            Matrix4 mat_lookat = mat_base_offset * transform;   //Apply base offset for LookAt\n\n            //Apply dashboard origin offset if needed\n            float overlay_height = 0.0f;\n            if (ConfigManager::GetValue(configid_int_overlay_origin) == ovrl_origin_dashboard)\n            {\n                overlay_height = GetOverlayHeight(OverlayManager::Get().GetCurrentOverlayID());\n                mat_lookat.translate_relative(0.0f, overlay_height / 2.0f, 0.0f);\n            }\n\n            vr::IVRSystemEx::TransformLookAt(mat_lookat, mat_hmd.getTranslation());\n\n            //Remove dashboard origin offset again\n            if (ConfigManager::GetValue(configid_int_overlay_origin) == ovrl_origin_dashboard)\n            {\n                mat_lookat.translate_relative(0.0f, overlay_height / -2.0f, 0.0f);\n            }\n\n            //Remove base offset again\n            mat_base_offset.invert();\n            mat_lookat = mat_base_offset * mat_lookat;\n\n            //Restore scale factor\n            mat_lookat.setTranslation({0.0f, 0.0f, 0.0f});\n            mat_lookat.scale(scale_x);\n            mat_lookat.setTranslation(transform.getTranslation());\n\n            transform = mat_lookat;\n            ApplySettingTransform();\n        }\n        return;\n    }\n\n    Matrix4 mat_back;\n    if (target >= ipcactv_ovrl_pos_adjust_rotx)\n    {\n        //Perform rotation locally\n        mat_back = transform;\n        transform.identity();\n    }\n\n    float distance = 0.05f;\n    float angle = 1.0f;\n\n    //Use snap size as distance if drag snapping is enabled\n    if (ConfigManager::GetValue(configid_bool_input_drag_snap_position))\n    {\n        distance = ConfigManager::GetValue(configid_float_input_drag_snap_position_size);\n    }\n\n    if (!increase)\n    {\n        distance *= -1.0f;\n        angle *= -1.0f;\n    }\n\n    switch (target)\n    {\n        case ipcactv_ovrl_pos_adjust_updown:    transform.translate_relative(0.0f,     distance, 0.0f);     break;\n        case ipcactv_ovrl_pos_adjust_rightleft: transform.translate_relative(distance, 0.0f,     0.0f);     break;\n        case ipcactv_ovrl_pos_adjust_forwback:  transform.translate_relative(0.0f,     0.0f,     distance); break;\n        case ipcactv_ovrl_pos_adjust_rotx:      transform.rotateX(angle);                                   break;\n        case ipcactv_ovrl_pos_adjust_roty:      transform.rotateY(angle);                                   break;\n        case ipcactv_ovrl_pos_adjust_rotz:      transform.rotateZ(angle);                                   break;\n    }\n\n    if (target >= ipcactv_ovrl_pos_adjust_rotx)\n    {\n        transform = mat_back * transform;\n    }\n\n    ApplySettingTransform();\n}\n\nvoid OutputManager::DetachedTransformConvertOrigin(unsigned int overlay_id, OverlayOrigin origin_from, OverlayOrigin origin_to)\n{\n    if (origin_from == origin_to)\n        return;\n\n    const OverlayConfigData& data = OverlayManager::Get().GetConfigData(overlay_id);\n    const OverlayOriginConfig origin_config = OverlayManager::Get().GetOriginConfigFromData(data);\n    DetachedTransformConvertOrigin(overlay_id, origin_from, origin_to, origin_config, origin_config);\n}\n\nvoid OutputManager::DetachedTransformConvertOrigin(unsigned int overlay_id, OverlayOrigin origin_from, OverlayOrigin origin_to, \n                                                   const OverlayOriginConfig& origin_config_from, const OverlayOriginConfig& origin_config_to)\n{\n    Overlay& overlay = OverlayManager::Get().GetOverlay(overlay_id);\n    OverlayConfigData& data = OverlayManager::Get().GetConfigData(overlay_id);\n    Matrix4& transform = data.ConfigTransform;\n\n    //With origin smoothing, just take the current in-between overlay transform even if it's technically less correct\n    bool has_direct_transform = false;\n    if ( (origin_from == ovrl_origin_hmd_floor) && ((origin_from == ovrl_origin_hmd) && (data.ConfigInt[configid_int_overlay_origin_smoothing_level] != 0)) )\n    {\n        //Only trust that transform if the overlay is visible, however\n        if (vr::VROverlay()->IsOverlayVisible(overlay.GetHandle()))\n        {\n            vr::HmdMatrix34_t transform_ovr;\n            vr::TrackingUniverseOrigin origin;\n            vr::VROverlay()->GetOverlayTransformAbsolute(overlay.GetHandle(), &origin, &transform_ovr);\n\n            transform = transform_ovr;\n\n            //Undo additional offset present in transform\n            transform.translate_relative(-ConfigManager::GetValue(configid_float_overlay_offset_right),\n                                         -ConfigManager::GetValue(configid_float_overlay_offset_up),\n                                         -ConfigManager::GetValue(configid_float_overlay_offset_forward));\n\n            has_direct_transform = true;\n        }\n    }\n\n    //Usual case, not HMD Floor or origin smoothing applied\n    if (!has_direct_transform)\n    {\n        OverlayOriginConfig origin_config = OverlayManager::Get().GetOriginConfigFromData(data);\n        Matrix4 mat_origin_from = m_OverlayDragger.GetBaseOffsetMatrix(origin_from, origin_config_from);\n\n        //Apply origin-from matrix to get absolute transform\n        transform = mat_origin_from * transform;\n\n        if ( (origin_from == ovrl_origin_dashboard) || (origin_to == ovrl_origin_dashboard) )\n        {\n            //Apply or counteract origin offset used in ApplySettingTransform for dashboard origin\n            float height = GetOverlayHeight(overlay_id);\n\n            transform.translate_relative(0.0f, (origin_from == ovrl_origin_dashboard) ? height / 2.0f : height / -2.0f, 0.0f);\n        }\n    }\n\n    //Apply inverse of origin-to matrix to get transform relative to new origin\n    Matrix4 mat_origin_to_inverse = m_OverlayDragger.GetBaseOffsetMatrix(origin_to, origin_config_to);\n    mat_origin_to_inverse.invert();\n\n    transform = mat_origin_to_inverse * transform;\n\n    //Sync transform so the UI doesn't have the wrong idea if no drag occurs after this\n    DetachedTransformSync(overlay_id);\n\n    //Reset smoothers to avoid potential hitching from previous uses\n    overlay.GetSmootherPos().ResetLastPos();\n    overlay.GetSmootherRot().ResetLastPos();\n}\n\nbool OutputManager::DetachedTransformFrameUpdate()\n{\n    const OverlayConfigData& data = OverlayManager::Get().GetCurrentConfigData();\n\n    //Skip if no frame level adjustments are needed\n    if (  (data.ConfigInt[configid_int_overlay_origin] != ovrl_origin_hmd_floor) && \n         ((data.ConfigInt[configid_int_overlay_origin] != ovrl_origin_hmd) || (data.ConfigInt[configid_int_overlay_origin_smoothing_level] == 0)) )\n    {\n        return false;\n    }\n\n    //We need to update the overlay pos at a somewhat constant rate for smoothing to be actually smooth\n    //The way RadialFollowCore works doesn't take time into account however\n    //Ideally this would be offloaded into a separate thread doing this at a truly fixed rate, but for now we just skip if we're going too fast (active overlays already force minimum update rate)\n    if (::GetTickCount64() < m_LastFrameTransformUpdateTick + m_MaxActiveRefreshDelay - 3)\n    {\n        return false;\n    }\n\n    Overlay& overlay = OverlayManager::Get().GetCurrentOverlay();\n\n    Matrix4 matrix = m_OverlayDragger.GetBaseOffsetMatrix();\n    matrix *= ConfigManager::Get().GetOverlayDetachedTransform();\n\n    //Offset transform by additional offset values\n    matrix.translate_relative(data.ConfigFloat[configid_float_overlay_offset_right],\n                              data.ConfigFloat[configid_float_overlay_offset_up],\n                              data.ConfigFloat[configid_float_overlay_offset_forward]);\n\n    //Use overlay's smoothers to filter the new matrix' position and rotation\n    if (data.ConfigInt[configid_int_overlay_origin_smoothing_level] != 0)\n    {\n        DetachedTransformFrameUpdateApplySmoothingParameters(overlay, data.ConfigInt[configid_int_overlay_origin_smoothing_level]);\n        matrix.setTranslation( overlay.GetSmootherPos().Filter(matrix.getTranslation()) );\n        matrix.setRotation(    overlay.GetSmootherRot().FilterWrapped(matrix.getRotation(), 0.0f, 360.0f) );\n    }\n\n    vr::HmdMatrix34_t matrix_ovr = matrix.toOpenVR34();\n    vr::VROverlay()->SetOverlayTransformAbsolute(OverlayManager::Get().GetCurrentOverlay().GetHandle(), vr::TrackingUniverseStanding, &matrix_ovr);\n\n    return true;\n}\n\nvoid OutputManager::DetachedTransformFrameUpdateApplySmoothingParameters(Overlay& overlay, int preset_id)\n{\n    preset_id = clamp(preset_id, 0, 5);\n\n    RadialFollowCore& smoother_pos = overlay.GetSmootherPos();\n    RadialFollowCore& smoother_rot = overlay.GetSmootherRot();\n\n    switch (preset_id)\n    {\n        case 0: //Not really used, calling Filter() is skipped entirely instead\n        {\n            smoother_pos.SetOuterRadius(0.0);\n            smoother_pos.SetInnerRadius(0.0);\n            smoother_pos.SetSmoothingCoefficient(0.0);\n            smoother_pos.SetSoftKneeScale(0.0);\n            smoother_pos.SetSmoothingLeakCoefficient(0.0);\n\n            smoother_rot.SetOuterRadius(0.0);\n            smoother_rot.SetInnerRadius(0.0);\n            smoother_rot.SetSmoothingCoefficient(0.0);\n            smoother_rot.SetSoftKneeScale(0.0);\n            smoother_rot.SetSmoothingLeakCoefficient(0.0);\n            break;\n        }\n        case 1:\n        {\n            smoother_pos.SetOuterRadius(0.0);\n            smoother_pos.SetInnerRadius(0.0);\n            smoother_pos.SetSmoothingCoefficient(0.85);\n            smoother_pos.SetSoftKneeScale(1.0);\n            smoother_pos.SetSmoothingLeakCoefficient(1.0);\n\n            smoother_rot.SetOuterRadius(0.0);\n            smoother_rot.SetInnerRadius(0.0);\n            smoother_rot.SetSmoothingCoefficient(0.8);\n            smoother_rot.SetSoftKneeScale(1.0);\n            smoother_rot.SetSmoothingLeakCoefficient(1.0);\n            break;\n        }\n        case 2:\n        {\n            smoother_pos.SetOuterRadius(0.0);\n            smoother_pos.SetInnerRadius(0.0);\n            smoother_pos.SetSmoothingCoefficient(0.90);\n            smoother_pos.SetSoftKneeScale(1.0);\n            smoother_pos.SetSmoothingLeakCoefficient(0.95);\n\n            smoother_rot.SetOuterRadius(0.5);\n            smoother_rot.SetInnerRadius(0.0);\n            smoother_rot.SetSmoothingCoefficient(0.85);\n            smoother_rot.SetSoftKneeScale(1.0);\n            smoother_rot.SetSmoothingLeakCoefficient(1.0);\n            break;\n        }\n        case 3:\n        {\n            smoother_pos.SetOuterRadius(0.02);\n            smoother_pos.SetInnerRadius(0.01);\n            smoother_pos.SetSmoothingCoefficient(0.95);\n            smoother_pos.SetSoftKneeScale(1.0);\n            smoother_pos.SetSmoothingLeakCoefficient(0.90);\n\n            smoother_rot.SetOuterRadius(1.0);\n            smoother_rot.SetInnerRadius(0.5);\n            smoother_rot.SetSmoothingCoefficient(0.75);\n            smoother_rot.SetSoftKneeScale(1.0);\n            smoother_rot.SetSmoothingLeakCoefficient(1.00);\n            break;\n        }\n        case 4:\n        {\n            smoother_pos.SetOuterRadius(0.10);\n            smoother_pos.SetInnerRadius(0.10);\n            smoother_pos.SetSmoothingCoefficient(0.93);\n            smoother_pos.SetSoftKneeScale(1.0);\n            smoother_pos.SetSmoothingLeakCoefficient(0.97);\n\n            smoother_rot.SetOuterRadius(7.5);\n            smoother_rot.SetInnerRadius(7.5);\n            smoother_rot.SetSmoothingCoefficient(0.75);\n            smoother_rot.SetSoftKneeScale(0.8);\n            smoother_rot.SetSmoothingLeakCoefficient(1.0);\n            break;\n        }\n        case 5:\n        {\n            smoother_pos.SetOuterRadius(0.25);\n            smoother_pos.SetInnerRadius(0.20);\n            smoother_pos.SetSmoothingCoefficient(0.95);\n            smoother_pos.SetSoftKneeScale(1.0);\n            smoother_pos.SetSmoothingLeakCoefficient(0.97);\n\n            smoother_rot.SetOuterRadius(30.0);\n            smoother_rot.SetInnerRadius(10.0);\n            smoother_rot.SetSmoothingCoefficient(0.96);\n            smoother_rot.SetSoftKneeScale(0.80);\n            smoother_rot.SetSmoothingLeakCoefficient(0.85);\n            break;\n        }\n    }\n}\n\nvoid OutputManager::DetachedTransformUpdateSeatedPosition()\n{\n    Matrix4 mat_seated_zero = vr::VRSystem()->GetSeatedZeroPoseToStandingAbsoluteTrackingPose();\n\n    //Sounds stupid, but we can be too fast to react to position updates and get the old seated zero pose as a result... so let's wait once if that happens\n    if (mat_seated_zero == m_SeatedTransformLast)\n    {\n        ::Sleep(100);\n        mat_seated_zero = vr::VRSystem()->GetSeatedZeroPoseToStandingAbsoluteTrackingPose();\n    }\n\n    //Update transforms of relevant overlays\n    unsigned int current_overlay_old = OverlayManager::Get().GetCurrentOverlayID();\n    for (unsigned int i = 0; i < OverlayManager::Get().GetOverlayCount(); ++i)\n    {\n        OverlayManager::Get().SetCurrentOverlayID(i);\n\n        if (ConfigManager::GetValue(configid_int_overlay_origin) == ovrl_origin_seated_universe)\n        {\n            ApplySettingTransform();\n        }\n    }\n\n    OverlayManager::Get().SetCurrentOverlayID(current_overlay_old);\n\n    m_SeatedTransformLast = mat_seated_zero;\n}\n\nvoid OutputManager::DetachedInteractionAutoToggleAll()\n{\n    //Don't change flags while any drag is currently active\n    if ( (m_OverlayDragger.IsDragActive()) || (m_OverlayDragger.IsDragGestureActive()) )\n        return;\n\n    float max_distance = ConfigManager::GetValue(configid_float_input_detached_interaction_max_distance);\n\n    if ( (max_distance != 0.0f) && (!vr::VROverlay()->IsDashboardVisible()) )\n    {\n        bool do_set_interactive = false;\n\n        //Add some additional distance for disabling interaction again\n        if (m_LaserPointer.IsActive())\n        {\n            //...but abort if pointer wasn't activated by this function (not our job to deactivate it)\n            if (m_LaserPointer.GetActivationOrigin() != dplp_activation_origin_auto_toggle)\n                return;\n\n            max_distance += 0.01f;\n        }\n\n        vr::TrackedDeviceIndex_t device_index_hovering = m_LaserPointer.IsAnyOverlayHovered(max_distance);\n\n        //Set pointer device if interaction not active yet and a device is hovering\n        if ( (!m_LaserPointer.IsActive()) && (device_index_hovering != vr::k_unTrackedDeviceIndexInvalid) )\n        {\n            m_LaserPointer.SetActiveDevice(device_index_hovering, dplp_activation_origin_auto_toggle);\n        }\n        else if ( (m_LaserPointer.IsActive()) && (device_index_hovering == vr::k_unTrackedDeviceIndexInvalid) ) //Clear pointer device if interaction active and no device is hovering\n        {\n            m_LaserPointer.ClearActiveDevice();\n        }\n    }\n}\n\nvoid OutputManager::DetachedOverlayGazeFade()\n{\n    if (ConfigManager::GetValue(configid_bool_overlay_gazefade_enabled))\n    {\n        Overlay& current_overlay = OverlayManager::Get().GetCurrentOverlay();\n\n        //When drag/select mode are active or HMD pose not available, default to most visible alpha setting\n        const float max_alpha = ConfigManager::GetValue(configid_float_overlay_opacity);\n        const float min_alpha = ConfigManager::GetValue(configid_float_overlay_gazefade_opacity);\n        float alpha = std::max(min_alpha, max_alpha);\n\n        if ((!ConfigManager::GetValue(configid_bool_state_overlay_dragmode)) && (!ConfigManager::GetValue(configid_bool_state_overlay_selectmode)))\n        {\n            vr::TrackingUniverseOrigin universe_origin = vr::TrackingUniverseStanding;\n            vr::TrackedDevicePose_t poses[vr::k_unTrackedDeviceIndex_Hmd + 1];\n            vr::VRSystem()->GetDeviceToAbsoluteTrackingPose(universe_origin, vr::IVRSystemEx::GetTimeNowToPhotons(), poses, vr::k_unTrackedDeviceIndex_Hmd + 1);\n\n            if (poses[vr::k_unTrackedDeviceIndex_Hmd].bPoseIsValid)\n            {\n                //Distance the gaze point is offset from HMD (useful range 0.25 - 1.0)\n                float gaze_distance = ConfigManager::GetValue(configid_float_overlay_gazefade_distance);\n                //Rate the fading gets applied when looking off the gaze point (useful range 4.0 - 30, depends on overlay size) \n                float fade_rate = ConfigManager::GetValue(configid_float_overlay_gazefade_rate) * 10.0f; \n\n                Matrix4 mat_pose = poses[vr::k_unTrackedDeviceIndex_Hmd].mDeviceToAbsoluteTracking;\n\n                Matrix4 mat_overlay = m_OverlayDragger.GetBaseOffsetMatrix();\n                mat_overlay *= ConfigManager::Get().GetOverlayDetachedTransform();\n\n                //Infinite/Auto distance mode\n                if (gaze_distance == 0.0f) \n                {\n                    gaze_distance = mat_overlay.getTranslation().distance(mat_pose.getTranslation()); //Match gaze distance to distance between HMD and overlay\n                }\n                else\n                {\n                    gaze_distance += 0.20f; //Useful range starts at ~0.20 - 0.25 (lower is in HMD or culled away), so offset the settings value\n                }\n\n                mat_pose.translate_relative(0.0f, 0.0f, -gaze_distance);\n\n                Vector3 pos_gaze = mat_pose.getTranslation();\n                float distance = mat_overlay.getTranslation().distance(pos_gaze);\n\n                gaze_distance = std::min(gaze_distance, 1.0f); //To get useful fading past 1m distance we'll have to limit the value to 1m here for the math below\n\n                alpha = clamp((distance * -fade_rate) + ((gaze_distance - 0.1f) * 10.0f), 0.0f, 1.0f); //There's nothing smart behind this, just trial and error\n\n                //Use max alpha when the overlay or the Floating UI targeting the overlay is being pointed at\n                if ((ConfigManager::Get().IsLaserPointerTargetOverlay(current_overlay.GetHandle())) || \n                    ((unsigned int)ConfigManager::GetValue(configid_int_state_interface_floating_ui_hovered_id) == current_overlay.GetID()))\n                {\n                    alpha = std::max(min_alpha, max_alpha); //Take whatever's more visible as the user probably wants to be able to see the overlay\n                }\n                else //Adapt alpha result from a 0.0 - 1.0 range to gazefade_opacity - overlay_opacity and invert if necessary\n                {\n                    const float range_length = max_alpha - min_alpha;\n\n                    if (range_length >= 0.0f)\n                    {\n                        alpha = (alpha * range_length) + min_alpha;\n                    }\n                    else //Gaze Fade target opacity higher than overlay opcacity, invert behavior\n                    {\n                        alpha = ((alpha - 1.0f) * range_length) + max_alpha;\n                    }\n                }\n            }\n        }\n\n        //Limit alpha change per frame to smooth out things when abrupt changes happen (i.e. overlay capture took a bit to re-enable or laser pointer forces full alpha)\n        const float prev_alpha = current_overlay.GetOpacity();\n        const float diff = alpha - prev_alpha;\n\n        current_overlay.SetOpacity(prev_alpha + clamp(diff, -0.1f, 0.1f));\n    }\n}\n\nvoid OutputManager::DetachedOverlayGazeFadeAutoConfigure()\n{\n    vr::TrackedDevicePose_t poses[vr::k_unTrackedDeviceIndex_Hmd + 1];\n    vr::VRSystem()->GetDeviceToAbsoluteTrackingPose(vr::TrackingUniverseStanding, vr::IVRSystemEx::GetTimeNowToPhotons(), poses, vr::k_unTrackedDeviceIndex_Hmd + 1);\n\n    if (poses[vr::k_unTrackedDeviceIndex_Hmd].bPoseIsValid)\n    {\n        OverlayConfigData& data = OverlayManager::Get().GetCurrentConfigData();\n\n        Matrix4 mat_pose = poses[vr::k_unTrackedDeviceIndex_Hmd].mDeviceToAbsoluteTracking;\n\n        Matrix4 mat_overlay = m_OverlayDragger.GetBaseOffsetMatrix();\n        mat_overlay *= ConfigManager::Get().GetOverlayDetachedTransform();\n\n        //Match gaze distance to distance between HMD and overlay\n        float gaze_distance = mat_overlay.getTranslation().distance(mat_pose.getTranslation());\n        gaze_distance -= 0.20f;\n\n        //Set fade rate to roughly decrease when the overlay is bigger and further away\n        float fade_rate = 2.5f / data.ConfigFloat[configid_float_overlay_width] * gaze_distance;\n\n        //Don't let the math go overboard\n        gaze_distance = std::max(gaze_distance, 0.01f);\n        fade_rate     = clamp(fade_rate, 0.3f, 1.75f);\n\n        data.ConfigFloat[configid_float_overlay_gazefade_distance] = gaze_distance;\n        data.ConfigFloat[configid_float_overlay_gazefade_rate]     = fade_rate;\n\n        IPCManager::Get().PostConfigMessageToUIApp(configid_float_overlay_gazefade_distance, gaze_distance);\n        IPCManager::Get().PostConfigMessageToUIApp(configid_float_overlay_gazefade_rate,     fade_rate);\n    }\n}\n\nvoid OutputManager::DetachedOverlayAutoDockingAll()\n{\n    if ( (!m_OverlayDragger.IsDragActive()) || (!ConfigManager::Get().GetValue(configid_bool_input_drag_auto_docking)) )\n    {\n        //Set config value to 0 if the drag ended while it wasn't yet\n        if (ConfigManager::GetValue(configid_int_state_auto_docking_state) != 0)\n        {\n            ConfigManager::SetValue(configid_int_state_auto_docking_state, 0);\n            IPCManager::Get().PostConfigMessageToUIApp(configid_int_state_auto_docking_state, 0);\n        }\n\n        return;\n    }\n\n    const OverlayConfigData& data = OverlayManager::Get().GetConfigData(m_OverlayDragger.GetDragOverlayID());\n    int config_value = 0;\n\n    vr::TrackedDevicePose_t poses[vr::k_unMaxTrackedDeviceCount];\n    vr::VRSystem()->GetDeviceToAbsoluteTrackingPose(vr::TrackingUniverseStanding, vr::IVRSystemEx::GetTimeNowToPhotons(), poses, vr::k_unMaxTrackedDeviceCount);\n\n    //Check left and right hand controller\n    vr::ETrackedControllerRole controller_role = vr::TrackedControllerRole_LeftHand;\n    for (int controller_role = vr::TrackedControllerRole_LeftHand; controller_role <= vr::TrackedControllerRole_RightHand; ++controller_role)\n    {\n        vr::TrackedDeviceIndex_t device_index = vr::VRSystem()->GetTrackedDeviceIndexForControllerRole((vr::ETrackedControllerRole)controller_role);\n\n        if ( ((int)device_index != m_OverlayDragger.GetDragDeviceID()) && (device_index < vr::k_unMaxTrackedDeviceCount) && (poses[device_index].bPoseIsValid) )\n        {\n            //Get distance between dragged overlay and controller\n            Matrix4 controller_pose = poses[device_index].mDeviceToAbsoluteTracking;\n            float distance = m_OverlayDragger.GetDragOverlayMatrix().getTranslation().distance( controller_pose.getTranslation() );\n            bool is_docked = false;\n\n            //Check if overlay is already docked\n            if ( ((controller_role == vr::TrackedControllerRole_LeftHand)  && (data.ConfigInt[configid_int_overlay_origin] == ovrl_origin_left_hand)) ||\n                 ((controller_role == vr::TrackedControllerRole_RightHand) && (data.ConfigInt[configid_int_overlay_origin] == ovrl_origin_right_hand)) )\n            {\n                is_docked = true;\n            }\n\n            //Set config value if distance is low enough\n            if ( (!is_docked) && (distance < 0.21f) )\n            {\n                config_value = controller_role;\n            }\n            else if ( (is_docked) && (distance > 0.23f) ) //...or high enough if already docked\n            {\n                config_value = controller_role + 2;\n            }\n        }\n    }\n\n    //Adjust config value if it changed\n    if (config_value != ConfigManager::GetValue(configid_int_state_auto_docking_state))\n    {\n        ConfigManager::SetValue(configid_int_state_auto_docking_state, config_value);\n        IPCManager::Get().PostConfigMessageToUIApp(configid_int_state_auto_docking_state, config_value);\n    }\n}\n\nvoid OutputManager::DetachedTempDragStart(unsigned int overlay_id, float offset_forward)\n{\n    m_OverlayDragger.DragStart(overlay_id);\n\n    //Drag may fail when dashboard device goes missing\n    //Should not happen as the relevant functions try their best to get an alternative before calling this, but avoid being stuck in temp drag mode\n    if (m_OverlayDragger.IsDragActive())\n    {\n        m_OverlayDragger.AbsoluteModeSet(true, offset_forward);\n        m_OvrlTempDragStartTick = ::GetTickCount64();\n\n        if (m_LaserPointer.IsActive())\n        {\n            m_LaserPointer.ForceTargetOverlay(OverlayManager::Get().GetOverlay(overlay_id).GetHandle());\n        }\n\n        ConfigManager::SetValue(configid_bool_state_overlay_dragmode_temp, true);\n        IPCManager::Get().PostConfigMessageToUIApp(configid_bool_state_overlay_dragmode_temp, true);\n    }\n}\n\nvoid OutputManager::DetachedTempDragFinish()\n{\n    if ( (m_OverlayDragger.IsDragActive()) && (ConfigManager::GetValue(configid_bool_state_overlay_dragmode_temp)) )\n    {\n        OnDragFinish();\n\n        //Adjust current overlay for ApplySettingTransform()\n        unsigned int current_overlay_old = OverlayManager::Get().GetCurrentOverlayID();\n        OverlayManager::Get().SetCurrentOverlayID(m_OverlayDragger.GetDragOverlayID());\n\n        m_OverlayDragger.DragFinish();\n\n        IPCManager::Get().PostConfigMessageToUIApp(configid_bool_state_overlay_dragmode_temp, false);\n        ConfigManager::SetValue(configid_bool_state_overlay_dragmode_temp, false);\n\n        ApplySettingTransform();\n        DetachedTransformSync(OverlayManager::Get().GetCurrentOverlayID());\n        OverlayManager::Get().SetCurrentOverlayID(current_overlay_old);\n\n        ApplySettingMouseInput();\n    }\n}\n\nvoid OutputManager::OnDragFinish()\n{\n    if (!m_OverlayDragger.IsDragActive())\n        return;\n\n    if (m_OvrlDirectDragActive)\n    {\n        m_OvrlDirectDragActive = false;\n        ApplySettingMouseInput();\n    }\n\n    ResetMouseLastLaserPointerPos();\n\n    //Handle auto-docking\n    int auto_dock_value = ConfigManager::GetValue(configid_int_state_auto_docking_state);\n\n    if (auto_dock_value != 0)\n    {\n        //Unpack settings value\n        vr::ETrackedControllerRole role = (auto_dock_value % 2 == 0) ? vr::TrackedControllerRole_RightHand : vr::TrackedControllerRole_LeftHand;\n        bool is_docking = (auto_dock_value <= 2);\n        OverlayConfigData& data = OverlayManager::Get().GetConfigData(m_OverlayDragger.GetDragOverlayID());\n\n        if (is_docking)\n        {\n            data.ConfigInt[configid_int_overlay_origin] = (role == vr::TrackedControllerRole_RightHand) ? ovrl_origin_right_hand : ovrl_origin_left_hand;\n        }\n        else\n        {\n            data.ConfigInt[configid_int_overlay_origin] = ovrl_origin_room;\n        }\n\n        //Send change over to UI\n        IPCManager::Get().PostConfigMessageToUIApp(configid_int_state_overlay_current_id_override, (int)m_OverlayDragger.GetDragOverlayID());\n        IPCManager::Get().PostConfigMessageToUIApp(configid_int_overlay_origin, data.ConfigInt[configid_int_overlay_origin]);\n        IPCManager::Get().PostConfigMessageToUIApp(configid_int_state_overlay_current_id_override, -1);\n    }\n\n    Overlay& overlay = OverlayManager::Get().GetOverlay(m_OverlayDragger.GetDragOverlayID());\n    overlay.GetSmootherPos().ResetLastPos();\n    overlay.GetSmootherRot().ResetLastPos();\n}\n\nvoid OutputManager::OnSetOverlayWinRTCaptureWindow(unsigned int overlay_id)\n{\n    unsigned int current_overlay_old = OverlayManager::Get().GetCurrentOverlayID();\n    OverlayManager::Get().SetCurrentOverlayID(overlay_id);\n\n    Overlay& overlay        = OverlayManager::Get().GetCurrentOverlay();\n    OverlayConfigData& data = OverlayManager::Get().GetCurrentConfigData();\n\n    overlay.SetTextureSource(ovrl_texsource_none);\n    ResetCurrentOverlay();\n\n    //Reset config_str_overlay_winrt_last_* strings when HWND was explicitly set to null\n    if (data.ConfigHandle[configid_handle_overlay_state_winrt_hwnd] == 0)\n    {\n        data.ConfigStr[configid_str_overlay_winrt_last_window_title]      = \"\";\n        data.ConfigStr[configid_str_overlay_winrt_last_window_class_name] = \"\";\n        data.ConfigStr[configid_str_overlay_winrt_last_window_exe_name]   = \"\";\n    }\n\n    OverlayManager::Get().SetCurrentOverlayID(current_overlay_old);\n}\n\nvoid OutputManager::FinishQueuedOverlayRemovals()\n{\n    if (m_RemoveOverlayQueue.empty())\n        return;\n\n    //Sort in descending order since removals from end will end up with less re-ordering (or even none if the top removed overlay is also the last one)\n    std::sort(m_RemoveOverlayQueue.rbegin(), m_RemoveOverlayQueue.rend());\n\n    for (unsigned int overlay_id : m_RemoveOverlayQueue)\n    {\n        OverlayManager::Get().RemoveOverlay(overlay_id);\n\n        IPCManager::Get().PostMessageToUIApp(ipcmsg_action, ipcact_overlay_remove, overlay_id);\n    }\n\n    m_RemoveOverlayQueue.clear();\n\n    //RemoveOverlay() may have changed active ID, keep in sync\n    ConfigManager::SetValue(configid_int_interface_overlay_current_id, OverlayManager::Get().GetCurrentOverlayID());\n}\n\nvoid OutputManager::FixInvalidDashboardLaunchState()\n{\n    //Workaround for glitchy behavior in SteamVR\n    //When launching SteamVR while wearing the HMD, the dashboard appears automatically. In this state, IsDashboardVisible() returns false and the HMD button is unable to close the dashboard.\n    //There are other things that aren't quite right and Desktop+ relies on the info to be correct in many cases.\n    //This somewhat works around the issue by opening the dashboard for our dashboard overlay. \n    //While a little bit intrusive and not 100% reliable when Desktop+ is auto-launched alongside, it's better than nothing.\n    vr::VROverlayHandle_t system_dashboard;\n    vr::VROverlay()->FindOverlay(\"system.systemui\", &system_dashboard);\n\n    if ( (vr::VROverlay()->IsOverlayVisible(system_dashboard)) && (!vr::VROverlay()->IsDashboardVisible()) )\n    {\n        vr::VROverlay()->ShowDashboard(\"elvissteinjr.DesktopPlusDashboard\");\n    }\n}\n\nvoid OutputManager::UpdateDashboardHMD_Y()\n{\n    m_OverlayDragger.UpdateDashboardHMD_Y();\n}\n\nbool OutputManager::HasDashboardMoved()\n{\n    //Don't trust anything if it's not visible\n    if (!vr::VROverlay()->IsDashboardVisible())\n        return false;\n\n    vr::HmdMatrix34_t hmd_matrix = {0};\n    vr::TrackingUniverseOrigin universe_origin = vr::TrackingUniverseStanding;\n\n    if (vr::VROverlay()->IsOverlayVisible(m_OvrlHandleDashboardDummy))\n    {\n        vr::VROverlay()->GetTransformForOverlayCoordinates(m_OvrlHandleDashboardDummy, universe_origin, {0.0f, 0.0f}, &hmd_matrix);\n    }\n    else\n    {\n        vr::VROverlayHandle_t handle_gamepad_ui = vr::k_ulOverlayHandleInvalid;\n        vr::VROverlay()->FindOverlay(\"valve.steam.gamepadui.bar\", &handle_gamepad_ui);\n\n        //Use GamepadUI as a reference if available since it's more stable due to the systemui overlay changing size depending on the active dashboard overlay's flags\n        vr::VROverlayHandle_t handle_dashboard = handle_gamepad_ui;\n\n        if (handle_dashboard == vr::k_ulOverlayHandleInvalid)\n        {\n            vr::VROverlay()->FindOverlay(\"system.systemui\", &handle_dashboard);\n        }\n\n        vr::HmdVector2_t overlay_dashboard_size;\n        vr::VROverlay()->GetOverlayMouseScale(handle_dashboard, &overlay_dashboard_size); //Coordinate size should be mouse scale\n\n        vr::VROverlay()->GetTransformForOverlayCoordinates(handle_dashboard, universe_origin, { overlay_dashboard_size.v[0]/2.0f, 0.0f }, &hmd_matrix);\n    }\n\n    Matrix4 matrix_new = hmd_matrix;\n\n    if (m_DashboardTransformLast != matrix_new)\n    {\n        m_DashboardTransformLast = hmd_matrix;\n\n        return true;\n    }\n\n    return false;\n}\n\nvoid OutputManager::DimDashboard(bool do_dim)\n{\n    if (vr::VROverlay() == nullptr)\n        return;\n\n    //This *could* terribly conflict with other apps messing with these settings, but I'm unaware of any that are right now, so let's just say we're the first\n    vr::VROverlayHandle_t system_dashboard;\n    vr::VROverlay()->FindOverlay(\"system.systemui\", &system_dashboard);\n\n    if (system_dashboard != vr::k_ulOverlayHandleInvalid)\n    {\n        if (do_dim)\n        {\n            vr::VROverlay()->SetOverlayColor(system_dashboard, 0.05f, 0.05f, 0.05f);\n        }\n        else\n        {\n            vr::VROverlay()->SetOverlayColor(system_dashboard, 1.0f, 1.0f, 1.0f);\n        }\n    }\n\n    vr::VROverlayHandle_t gamepadui;\n    vr::VROverlay()->FindOverlay(\"valve.steam.gamepadui.bar\", &gamepadui);\n\n    if (gamepadui != vr::k_ulOverlayHandleInvalid)\n    {\n        if (do_dim)\n        {\n            vr::VROverlay()->SetOverlayColor(gamepadui, 0.05f, 0.05f, 0.05f);\n        }\n        else\n        {\n            vr::VROverlay()->SetOverlayColor(gamepadui, 1.0f, 1.0f, 1.0f);\n        }\n    }\n}\n\nvoid OutputManager::UpdatePendingDashboardDummyHeight()\n{\n    float old_dummy_height = 0.0f;\n    vr::VROverlay()->GetOverlayWidthInMeters(m_OvrlHandleDashboardDummy, &old_dummy_height);\n\n    //Check for dummy height changes but enforce a minimum difference as the underlying size seems to flicker under certain conditions in current SteamVR versions for some reason\n    if (fabs(m_PendingDashboardDummyHeight - old_dummy_height) > 0.01f)\n    {\n        vr::VROverlay()->SetOverlayWidthInMeters(m_OvrlHandleDashboardDummy, m_PendingDashboardDummyHeight);\n\n        //Perform tactical sleep to avoid flickering caused by dummy adjustments not being done in time when dashboard overlay positioning depends on it\n        if (vr::VROverlay()->IsOverlayVisible(m_OvrlHandleDashboardDummy))\n        {\n            ::Sleep(100);\n        }\n\n        m_PendingDashboardDummyHeight = 0.0f;\n    }\n}\n\nbool OutputManager::IsAnyOverlayUsingGazeFade() const\n{\n    //This is the straight forward, simple version. The smart one, efficiently keeping track properly, could come some other time*\n    //*we know it probably won't happen any time soon\n    for (unsigned int i = 0; i < OverlayManager::Get().GetOverlayCount(); ++i)\n    {\n        const OverlayConfigData& data = OverlayManager::Get().GetConfigData(i);\n\n        if ( (data.ConfigBool[configid_bool_overlay_enabled]) && (data.ConfigBool[configid_bool_overlay_gazefade_enabled]) )\n        {\n            return true;\n        }\n    }\n\n    return false;\n}\n\nvoid OutputManager::RegisterHotkeys()\n{\n    //Just unregister all we have when updating any\n    for (int i = 0; i < m_RegisteredHotkeyCount; ++i)\n    {\n        ::UnregisterHotKey(nullptr, i);\n    }\n    m_IsAnyHotkeyActive = false;\n\n    //...and register them again if there's an action assigned\n    int id = 0;\n    for (ConfigHotkey& hotkey : ConfigManager::Get().GetHotkeys())\n    {\n        if (hotkey.ActionUID != k_ActionUID_Invalid)\n        {\n            ::RegisterHotKey(nullptr, id, hotkey.Modifiers | MOD_NOREPEAT, hotkey.KeyCode);\n            m_IsAnyHotkeyActive = true;\n        }\n        ++id;\n    }\n\n    m_RegisteredHotkeyCount = id;\n\n    //Laser Pointer HMD Device uses hotkeys just as means to block key input to other applications\n    //Actual inputs are checked via VRInput::UpdateKeyboardDeviceState(), so they do not need any handling in HandleHotkeys() or HandleHotkeyMessage()\n    const int hmd_device_hotkey_count = configid_int_input_laser_pointer_hmd_device_keycode_drag - configid_int_input_laser_pointer_hmd_device_keycode_toggle;\n    for (int i = 0; i < hmd_device_hotkey_count + 1; ++i)\n    {\n        ConfigID_Int config_id = (ConfigID_Int)(configid_int_input_laser_pointer_hmd_device_keycode_toggle + i);\n        ::UnregisterHotKey(nullptr, 0xBFFF - i);  //0xBFFF is max allowed id, we count down from that to avoid conflicts\n    }\n\n    if (ConfigManager::GetValue(configid_bool_input_laser_pointer_hmd_device))  //No need to block these keys when the feature is disabled\n    {\n        for (int i = 0; i < hmd_device_hotkey_count + 1; ++i)\n        {\n            ConfigID_Int config_id = (ConfigID_Int)(configid_int_input_laser_pointer_hmd_device_keycode_toggle + i);\n            ::RegisterHotKey(nullptr, 0xBFFF - i, MOD_NOREPEAT, ConfigManager::GetValue(config_id));\n        }\n    }\n}\n\nvoid OutputManager::HandleHotkeys()\n{\n    //This function handles hotkeys manually via GetAsyncKeyState() for some very special games that think consuming all keyboard input is a nice thing to do\n    //Win32 hotkeys are still used simultaneously. Their input blocking might be considered an advantage and the hotkey configurability is designed around them already\n    //Win32 hotkeys also still work while an elevated application has focus, GetAsyncKeyState() doesn't\n\n    if (!m_IsAnyHotkeyActive)\n        return;\n\n    for (ConfigHotkey& hotkey : ConfigManager::Get().GetHotkeys())\n    {\n        if (hotkey.ActionUID != k_ActionUID_Invalid)\n        {\n            if ( (::GetAsyncKeyState(hotkey.KeyCode) < 0) && \n                 ( ((hotkey.Modifiers & MOD_SHIFT)   == 0) ||  (::GetAsyncKeyState(VK_SHIFT)   < 0) ) &&\n                 ( ((hotkey.Modifiers & MOD_CONTROL) == 0) ||  (::GetAsyncKeyState(VK_CONTROL) < 0) ) &&\n                 ( ((hotkey.Modifiers & MOD_ALT)     == 0) ||  (::GetAsyncKeyState(VK_MENU)    < 0) ) &&\n                 ( ((hotkey.Modifiers & MOD_WIN)     == 0) || ((::GetAsyncKeyState(VK_LWIN)    < 0) || (::GetAsyncKeyState(VK_RWIN) < 0)) ) )\n            {\n                if (!hotkey.StateIsDown)\n                {\n                    hotkey.StateIsDown = true;\n\n                    ConfigManager::Get().GetActionManager().DoAction(hotkey.ActionUID);\n                }\n            }\n            else if (hotkey.StateIsDown)\n            {\n                hotkey.StateIsDown = false;\n            }\n        }\n    }\n}\n\nvoid OutputManager::HandleKeyboardAutoVisibility()\n{\n    //This only handles auto-visibility for desktop/window overlays\n\n    //Check if state changed\n    static bool is_text_input_focused_prev = false;\n    bool is_text_input_focused = ( (!ConfigManager::GetValue(configid_bool_input_keyboard_auto_show_desktop)) || (m_MouseIgnoreMoveEvent) ) ? false : WindowManager::Get().IsTextInputFocused();\n\n    //Overlay input has to be active during a state change for it to count as focused\n    if ((is_text_input_focused != is_text_input_focused_prev) && (!m_OvrlInputActive))\n    {\n        is_text_input_focused = false;\n    }\n\n    if (is_text_input_focused != is_text_input_focused_prev)\n    {\n        //Send update to UI process\n        IPCManager::Get().PostMessageToUIApp(ipcmsg_action, ipcact_keyboard_show_auto, (is_text_input_focused) ? ConfigManager::GetValue(configid_int_state_overlay_focused_id) : -1);\n\n        is_text_input_focused_prev = is_text_input_focused;\n    }\n}\n"
  },
  {
    "path": "src/DesktopPlus/OutputManager.h",
    "content": "#ifndef _OUTPUTMANAGER_H_\n#define _OUTPUTMANAGER_H_\n\n#include <stdio.h>\n\n#include \"CommonTypes.h\"\n#include \"warning.h\"\n\n#include \"openvr.h\"\n#include \"Matrices.h\"\n#include \"RadialFollowSmoothing.h\"\n\n#include \"OverlayManager.h\"\n#include \"ConfigManager.h\"\n#include \"SoftwareCursorGrabber.h\"\n#include \"InputSimulator.h\"\n#include \"VRInput.h\"\n#include \"BackgroundOverlay.h\"\n#include \"OUtoSBSConverter.h\"\n#include \"InterprocessMessaging.h\"\n#include \"OverlayDragger.h\"\n#include \"LaserPointer.h\"\n\nclass Overlay;\n//\n// This class evolved into handling almost everything\n// Updates the output texture, sends it to OpenVR, handles OpenVR events, IPC messages...\n// Most of the tasks are related, but splitting stuff up might be an idea in the future\n//\nclass OutputManager\n{\n    public:\n        static OutputManager* Get();\n\n        OutputManager(HANDLE PauseDuplicationEvent, HANDLE ResumeDuplicationEvent);\n        ~OutputManager();\n        void CleanRefs();\n        DUPL_RETURN InitOutput(HWND Window, _Out_ INT& SingleOutput, _Out_ UINT* OutCount, _Out_ RECT* DeskBounds);\n        std::tuple<vr::EVRInitError, vr::EVROverlayError, bool> InitOverlay();  //Returns error state <InitError, OverlayError, VRInputInitSuccess>\n        DUPL_RETURN_UPD Update(_In_ PTR_INFO* PointerInfo, _In_ DPRect& DirtyRegionTotal, bool NewFrame, bool SkipFrame);\n        void BusyUpdate();                        //Updates minimal state (i.e. OverlayDragger) during busy waits (i.e. waiting for browser startup) to appear more responsive\n        bool HandleIPCMessage(const MSG& msg);    //Returns true if message caused a duplication reset (i.e. desktop switch)\n        void HandleWinRTMessage(const MSG& msg);  //Messages sent by the Desktop+ WinRT library\n        void HandleHotkeyMessage(const MSG& msg);\n        void OnExit();\n\n        HWND GetWindowHandle();\n        HANDLE GetSharedHandle();\n        IDXGIAdapter* GetDXGIAdapter(); //Don't forget to call Release() on the returned pointer when done with it\n\n        void ResetOverlays();\n        void ResetCurrentOverlay();\n\n        ID3D11Texture2D* GetOverlayTexture() const; //This returns m_OvrlTex, the backing texture used by the desktop texture overlay (and all overlays stealing its texture)\n        ID3D11Texture2D* GetMultiGPUTargetTexture() const;\n        vr::VROverlayHandle_t GetDesktopTextureOverlay() const;\n        bool GetOverlayActive() const;\n        bool GetOverlayInputActive() const;\n        DWORD GetMaxRefreshDelay() const;\n        float GetHMDFrameRate() const;\n        int GetDesktopWidth() const;\n        int GetDesktopHeight() const;\n        const std::vector<DPRect>& GetDesktopRects() const;\n        float GetDesktopHDRWhiteLevelAdjustment(int desktop_id, bool is_for_graphics_capture, bool wmr_ignore_vscreens) const;\n\n        void ShowOverlay(unsigned int id);\n        void ShowTheaterOverlay(unsigned int id);\n        void HideOverlay(unsigned int id);\n        void ResetOverlayActiveCount();     //Called by OverlayManager after removing all overlays, makes sure the active counts are correct\n\n        bool HasDashboardBeenActivatedOnce() const;\n        bool IsDashboardTabActive() const;\n        float GetDashboardScale() const;\n        float GetOverlayHeight(unsigned int overlay_id) const;\n        Matrix4 GetFallbackOverlayTransform() const;    //Returns a matrix resulting in the overlay facing the HMD at 2m distance, used when other referenced aren't available\n\n        void SetOutputErrorTexture(vr::VROverlayHandle_t overlay_handle);\n        void SetOutputInvalid(); //Handles state when there's no valid output\n        bool IsOutputInvalid() const;\n\n        void SetOverlayEnabled(unsigned int overlay_id, bool is_enabled);\n        void CropToActiveWindowToggle(unsigned int overlay_id);\n        void ShowWindowSwitcher();\n        void SwitchToWindow(HWND window, bool warp_cursor);\n        void OverlayDirectDragStart(unsigned int overlay_id);\n        void OverlayDirectDragFinish(unsigned int overlay_id);\n\n        VRInput& GetVRInput();\n        InputSimulator& GetInputSimulator();\n\n        void UpdatePerformanceStates();\n        const LARGE_INTEGER& GetUpdateLimiterDelay();\n        //This updates the cached desktop rects and count and optionally chooses the adapters/desktop for desktop duplication (previously part of InitOutput())\n        int EnumerateOutputs(int target_desktop_id = -1, Microsoft::WRL::ComPtr<IDXGIAdapter>* out_adapter_preferred = nullptr, Microsoft::WRL::ComPtr<IDXGIAdapter>* out_adapter_vr = nullptr);\n        void CropToDisplay(int display_id, int& crop_x, int& crop_y, int& crop_width, int& crop_height);\n        bool CropToActiveWindow(int& crop_x, int& crop_y, int& crop_width, int& crop_height);             //Returns true if values have changed\n        void InitComIfNeeded();\n\n        void ConvertOUtoSBS(Overlay& overlay, OUtoSBSConverter& converter);\n\n    private:\n        DUPL_RETURN ProcessMonoMask(bool is_mono, PTR_INFO& ptr_info, int& ptr_width, int& ptr_height, int& ptr_left, int& ptr_top, \n                                    Microsoft::WRL::ComPtr<ID3D11Texture2D>& out_tex, DXGI_FORMAT& out_tex_format, D3D11_BOX& box);\n        DUPL_RETURN ProcessMonoMaskFloat16(bool is_mono, PTR_INFO& ptr_info, int& ptr_width, int& ptr_height, int& ptr_left, int& ptr_top, \n                                           Microsoft::WRL::ComPtr<ID3D11Texture2D>& out_tex, DXGI_FORMAT& out_tex_format, D3D11_BOX& box);\n        DUPL_RETURN MakeRTV();\n        DUPL_RETURN InitShaders();\n        DUPL_RETURN CreateTextures(INT SingleOutput, _Out_ UINT* OutCount, _Out_ RECT* DeskBounds);\n        void DrawFrameToOverlayTex(bool clear_rtv = true);\n        DUPL_RETURN DrawMouseToOverlayTex(_In_ PTR_INFO* PtrInfo);\n        DUPL_RETURN_UPD RefreshOpenVROverlayTexture(DPRect& DirtyRectTotal, bool force_full_copy = false); //Refreshes the overlay texture of the VR runtime with content of the m_OvrlTex backing texture\n        bool DesktopTextureAlphaCheck();\n\n        bool HandleOpenVREvents();  //Returns true if quit event happened\n        void OnOpenVRMouseEvent(const vr::VREvent_t& vr_event, unsigned int& current_overlay_old);\n        void HandleKeyboardMessage(IPCActionID ipc_action_id, LPARAM lparam);\n        bool HandleOverlayProfileLoadMessage(LPARAM lparam);\n\n        void ResetMouseLastLaserPointerPos();\n        void CropToActiveWindow();\n        void CropToDisplay(int display_id, bool do_not_apply_setting = false);\n        void DuplicateOverlay(unsigned int base_id, bool is_ui_overlay = false);\n        unsigned int AddOverlay(OverlayCaptureSource capture_source, int desktop_id = -2, HWND window_handle = nullptr);\n        unsigned int AddOverlayDrag(float source_distance, OverlayCaptureSource capture_source, int desktop_id = -2, HWND window_handle = nullptr, float overlay_width = 0.5f);\n\n        void ApplySettingCaptureSource();\n        void ApplySetting3DMode();\n        void ApplySettingTransform();\n        void ApplySettingCrop();\n        void ApplySettingInputMode();\n        void ApplySettingMouseInput();\n        void ApplySettingMouseScale();\n        void ApplySettingUpdateLimiter();\n        void ApplySettingExtraBrightness();\n\n        void DetachedTransformSync(unsigned int overlay_id);\n        void DetachedTransformSyncAll();\n        void DetachedTransformReset(unsigned int overlay_id_ref = k_ulOverlayID_None);\n        void DetachedTransformAdjust(unsigned int packed_value);\n        void DetachedTransformConvertOrigin(unsigned int overlay_id, OverlayOrigin origin_from, OverlayOrigin origin_to);\n        void DetachedTransformConvertOrigin(unsigned int overlay_id, OverlayOrigin origin_from, OverlayOrigin origin_to, \n                                            const OverlayOriginConfig& origin_config_from, const OverlayOriginConfig& origin_config_to);\n        bool DetachedTransformFrameUpdate();  //Returns if update was applied (not skipped)\n        void DetachedTransformFrameUpdateApplySmoothingParameters(Overlay& overlay, int preset_id);\n        void DetachedTransformUpdateSeatedPosition();\n\n        void DetachedInteractionAutoToggleAll();\n        void DetachedOverlayGazeFade();\n        void DetachedOverlayGazeFadeAutoConfigure();\n        void DetachedOverlayAutoDockingAll();\n\n        void DetachedTempDragStart(unsigned int overlay_id, float offset_forward = 0.5f);\n        void DetachedTempDragFinish();  //Calls OnDragFinish()\n        void OnDragFinish();            //Called before calling m_OverlayDragger.DragFinish() to handle OutputManager post drag state adjustments (like auto docking)\n\n        void OnSetOverlayWinRTCaptureWindow(unsigned int overlay_id); //Called when configid_intptr_overlay_state_winrt_hwnd changed\n        void FinishQueuedOverlayRemovals();                           //Overlay removals are currently only queued up when requested during HandleWinRTMessage()\n\n        void FixInvalidDashboardLaunchState();\n        void UpdateDashboardHMD_Y();\n        bool HasDashboardMoved();\n        void DimDashboard(bool do_dim);\n        void UpdatePendingDashboardDummyHeight();\n        bool IsAnyOverlayUsingGazeFade() const;\n\n        void RegisterHotkeys();\n        void HandleHotkeys();\n\n        void HandleKeyboardAutoVisibility();\n\n        InputSimulator m_InputSim;\n        VRInput m_VRInput;\n        IPCManager m_IPCMan;\n        BackgroundOverlay m_BackgroundOverlay;\n        OverlayDragger m_OverlayDragger;\n        LaserPointer m_LaserPointer;\n\n        ID3D11Device* m_Device;\n        ID3D11DeviceContext* m_DeviceContext;\n        ID3D11SamplerState* m_Sampler;\n        ID3D11BlendState* m_BlendState;\n        ID3D11RasterizerState* m_RasterizerState;\n        ID3D11VertexShader* m_VertexShader;\n        ID3D11PixelShader* m_PixelShader;\n        ID3D11PixelShader* m_PixelShaderCursor;\n        ID3D11InputLayout* m_InputLayout;\n        ID3D11Texture2D* m_SharedSurf;\n        ID3D11Buffer* m_VertexBuffer;\n        ID3D11ShaderResourceView* m_ShaderResource;\n        IDXGIKeyedMutex* m_KeyMutex;\n        HWND m_WindowHandle;\n        //These handles are not created or closed by this class, they're valid for the entire runtime though\n        HANDLE m_PauseDuplicationEvent;\n        HANDLE m_ResumeDuplicationEvent;\n\n        int m_DesktopX;                         //These are the desktop coordinates/size valid for Desktop Duplication and may be different from the m_DesktopRectTotal\n        int m_DesktopY;\n        int m_DesktopWidth;\n        int m_DesktopHeight;\n        std::vector<DPRect> m_DesktopRects;     //Cached position and size of available desktops\n        DPRect m_DesktopRectTotal;              //Total rect of all available desktops (may not be the same as above Desktop Duplication rect if that's not using the combined desktop)\n        std::vector<float> m_DesktopHDRWhiteLevelAdjustments; //Cached GetDesktopHDRWhiteLevelAdjustment() results used during cursor updates\n        DWORD m_MaxActiveRefreshDelay;\n        bool m_OutputHDRAvailable;              //False if OS doesn't support the required interface, regardless of hardware connected\n        bool m_OutputInvalid;\n        bool m_OutputPendingSkippedFrame;\n        bool m_OutputPendingFullRefresh;\n        DPRect m_OutputPendingDirtyRect;\n        DPRect m_OutputLastClippingRect;\n        int m_OutputAlphaChecksPending;\n        bool m_OutputAlphaCheckFailed;          //Output appears to be translucent and needs its alpha channel stripped during texture copy\n\n        vr::VROverlayHandle_t m_OvrlHandleDashboardDummy;\n        vr::VROverlayHandle_t m_OvrlHandleIcon;\n        vr::VROverlayHandle_t m_OvrlHandleDesktopTexture;\n        ID3D11Texture2D* m_OvrlTex;\n        ID3D11RenderTargetView* m_OvrlRTV;\n        int m_OvrlActiveCount;\n        int m_OvrlDesktopDuplActiveCount;\n        bool m_OvrlDashboardActive;\n        bool m_OvrlInputActive;\n        bool m_OvrlDirectDragActive;\n        ULONGLONG m_OvrlTempDragStartTick;\n        float m_PendingDashboardDummyHeight;\n        ULONGLONG m_LastApplyTransformTick;\n        ULONGLONG m_LastFrameTransformUpdateTick;\n\n        Microsoft::WRL::ComPtr<ID3D11Texture2D> m_MouseTex;\n        Microsoft::WRL::ComPtr<ID3D11ShaderResourceView> m_MouseShaderRes;\n\n        SoftwareCursorGrabber m_MouseAlternativeCursor;\n        ULONGLONG m_MouseLastClickTick;\n        bool m_MouseIgnoreMoveEvent;\n        bool m_MouseCursorNeedsUpdate;\n        PTR_INFO m_MouseLastInfo;\n        Vector2Int m_MouseLastCursorSize;\n        bool m_MouseLaserPointerUsedLastUpdate;\n        bool m_MouseLastLaserPointerMoveBlocked;\n        int m_MouseLastLaserPointerX;\n        int m_MouseLastLaserPointerY;\n        int m_MouseIgnoreMoveEventMissCount;\n        unsigned int m_MouseLeftDownOverlayID;\n        RadialFollowCore m_MouseLaserPointerSmoother;\n        LARGE_INTEGER m_MouseLaserPointerScrollDeltaStart;\n        LARGE_INTEGER m_MouseLaserPointerScrollDeltaFrequency;\n\n        bool m_IsFirstLaunch;\n        bool m_ComInitDone;\n\n        bool m_DashboardActivatedOnce;\n        Matrix4 m_DashboardTransformLast;       //This is only used to check if the dashboard has moved from events we can't detect otherwise\n        Matrix4 m_SeatedTransformLast;\n\n        //These are only used when duplicating outputs from a different GPU\n        ID3D11Device* m_MultiGPUTargetDevice;   //Target D3D11 device, meaning the one the HMD is connected to\n        ID3D11DeviceContext* m_MultiGPUTargetDeviceContext;\n        ID3D11Texture2D* m_MultiGPUTexStaging;  //Staging texture, owned by m_Device\n        ID3D11Texture2D* m_MultiGPUTexTarget;   //Target texture to copy to, owned by m_MultiGPUTargetDevice\n\n        int m_PerformanceFrameCount;\n        ULONGLONG m_PerformanceFrameCountStartTick;\n        LARGE_INTEGER m_PerformanceUpdateLimiterDelay;\n\n        std::vector<int> m_ProfileAddOverlayIDQueue;\n        std::vector<unsigned int> m_RemoveOverlayQueue;\n\n        bool m_IsAnyHotkeyActive;\n        int m_RegisteredHotkeyCount;\n};\n\n#endif\n"
  },
  {
    "path": "src/DesktopPlus/Overlays.cpp",
    "content": "#include \"Overlays.h\"\n\n#include <sstream>\n\n#include \"CommonTypes.h\"\n#include \"OpenVRExt.h\"\n#include \"OverlayManager.h\"\n#include \"OutputManager.h\"\n#include \"DesktopPlusWinRT.h\"\n#include \"DPBrowserAPIClient.h\"\n\nOverlay::Overlay(unsigned int id) : m_ID(id),\n                                    m_OvrlHandle(vr::k_ulOverlayHandleInvalid),\n                                    m_Visible(false),\n                                    m_Opacity(1.0f),\n                                    m_TextureSource(ovrl_texsource_invalid)\n{\n    //Don't call InitOverlay when OpenVR isn't loaded yet. This happens during startup when loading the config and will be fixed up by OutputManager::InitOverlay() afterwards\n    if (vr::VROverlay() != nullptr)\n    {\n        InitOverlay();\n    }\n\n    m_SmootherPos.SetDetectInterruptions(false);\n    m_SmootherRot.SetDetectInterruptions(false);\n}\n\nOverlay::Overlay(Overlay&& b)\n{\n    m_OvrlHandle = vr::k_ulOverlayHandleInvalid; //This needs a valid value first\n    *this = std::move(b);\n}\n\nOverlay& Overlay::operator=(Overlay&& b)\n{\n    if (this != &b)\n    {\n        if (m_OvrlHandle != vr::k_ulOverlayHandleInvalid)\n        {\n            if (m_TextureSource == ovrl_texsource_winrt_capture)\n            {\n                DPWinRT_StopCapture(m_OvrlHandle);\n            }\n            else if (m_TextureSource == ovrl_texsource_browser)\n            {\n                DPBrowserAPIClient::Get().DPBrowser_StopBrowser(m_OvrlHandle);\n            }\n\n            vr::VROverlayEx()->DestroyOverlayEx(m_OvrlHandle);\n        }\n\n        m_ID                = b.m_ID;\n        m_OvrlHandle        = b.m_OvrlHandle;\n        m_Visible           = b.m_Visible;\n        m_Opacity           = b.m_Opacity;\n        m_ValidatedCropRect = b.m_ValidatedCropRect;\n        m_TextureSource     = b.m_TextureSource;\n        //m_OUtoSBSConverter should just be left alone, it only holds cached state anyways\n\n        b.m_OvrlHandle = vr::k_ulOverlayHandleInvalid;\n    }\n\n    return *this;\n}\n\nOverlay::~Overlay()\n{\n    if (m_OvrlHandle != vr::k_ulOverlayHandleInvalid)\n    {\n        if (m_TextureSource == ovrl_texsource_winrt_capture)\n        {\n            DPWinRT_StopCapture(m_OvrlHandle);\n        }\n        else if (m_TextureSource == ovrl_texsource_browser)\n        {\n            DPBrowserAPIClient::Get().DPBrowser_StopBrowser(m_OvrlHandle);\n        }\n\n        vr::VROverlayEx()->DestroyOverlayEx(m_OvrlHandle);\n    }\n}\n\n\nvoid Overlay::InitOverlay()\n{\n    unsigned int id_offset = 0;\n    std::string key;\n    vr::VROverlayHandle_t overlay_handle_find;\n\n    //Generate overlay key from ID and check if it's not used yet, add to it if it's not\n    //Overlay keys & handles are fixed and don't change when overlays are re-ordered or deleted\n    do\n    {\n        key = \"elvissteinjr.DesktopPlus\" + std::to_string(m_ID + id_offset);\n        overlay_handle_find = vr::k_ulOverlayHandleInvalid;\n        id_offset++;\n\n        vr::VROverlay()->FindOverlay(key.c_str(), &overlay_handle_find);\n    }\n    while (overlay_handle_find != vr::k_ulOverlayHandleInvalid);\n\n    vr::VROverlayError ovrl_error = vr::VROverlayError_None;\n    ovrl_error = vr::VROverlay()->CreateOverlay(key.c_str(), \"Desktop+\", &m_OvrlHandle);\n\n    if (ovrl_error == vr::VROverlayError_None)\n    {\n        vr::VROverlay()->SetOverlayAlpha(m_OvrlHandle, m_Opacity);\n    } \n    else //Creation failed, send error to UI so the user at least knows (typically this only happens when the overlay limit is exceeded)\n    {\n        IPCManager::Get().PostMessageToUIApp(ipcmsg_action, ipcact_overlay_creation_error, ovrl_error);\n    }\n}\n\nvoid Overlay::AssignDesktopDuplicationTexture()\n{\n    if (m_OvrlHandle != vr::k_ulOverlayHandleInvalid)\n    {\n        OverlayConfigData& data = OverlayManager::Get().GetConfigData(m_ID);\n\n        if (data.ConfigInt[configid_int_overlay_capture_source] != ovrl_capsource_desktop_duplication)\n            return;\n\n        OutputManager* outmgr = OutputManager::Get();\n        if (outmgr == nullptr)\n            return;\n\n        //Set content size to desktop duplication values\n        int dwidth  = outmgr->GetDesktopWidth();\n        int dheight = outmgr->GetDesktopHeight();\n\n        //Avoid sending it over to UI if we can help it\n        if ( (data.ConfigInt[configid_int_overlay_state_content_width] != dwidth) || (data.ConfigInt[configid_int_overlay_state_content_height] != dheight) )\n        {\n            data.ConfigInt[configid_int_overlay_state_content_width]  = dwidth;\n            data.ConfigInt[configid_int_overlay_state_content_height] = dheight;\n            UpdateValidatedCropRect();\n\n            IPCManager::Get().PostConfigMessageToUIApp(configid_int_state_overlay_current_id_override, m_ID);\n            IPCManager::Get().PostConfigMessageToUIApp(configid_int_overlay_state_content_width,  dwidth);\n            IPCManager::Get().PostConfigMessageToUIApp(configid_int_overlay_state_content_height, dheight);\n            IPCManager::Get().PostConfigMessageToUIApp(configid_int_state_overlay_current_id_override, -1);\n        }\n\n        //Exclude indirect desktop duplication sources, like converted Over-Under 3D\n        if (m_TextureSource != ovrl_texsource_desktop_duplication)\n            return;\n\n        //Use desktop texture overlay as source for a shared overlay texture\n        ID3D11Resource* device_texture_ref = (OutputManager::Get()->GetMultiGPUTargetTexture() != nullptr) ? OutputManager::Get()->GetMultiGPUTargetTexture() : OutputManager::Get()->GetOverlayTexture();\n        vr::VROverlayEx()->SetSharedOverlayTexture(outmgr->GetDesktopTextureOverlay(), m_OvrlHandle, device_texture_ref);\n    }\n}\n\nunsigned int Overlay::GetID() const\n{\n    return m_ID;\n}\n\nvoid Overlay::SetID(unsigned int id)\n{\n    m_ID = id;\n}\n\nvr::VROverlayHandle_t Overlay::GetHandle() const\n{\n    return m_OvrlHandle;\n}\n\nvoid Overlay::SetHandle(vr::VROverlayHandle_t handle)\n{\n    m_OvrlHandle = handle;\n}\n\nvoid Overlay::SetOpacity(float opacity)\n{\n    if (opacity == m_Opacity)\n        return;\n\n    OutputManager* outmgr = OutputManager::Get();\n    if (outmgr == nullptr)\n        return;\n\n    vr::VROverlay()->SetOverlayAlpha(m_OvrlHandle, opacity);\n\n    if (m_Opacity == 0.0f) //If it was previously 0%, show if needed\n    {\n        m_Opacity = opacity; //ShouldBeVisible() depends on this being correct, so set it here\n\n        if ( (!m_Visible) && (ShouldBeVisible()) )\n        {\n            outmgr->ShowOverlay(m_ID);\n        }\n    }\n    else if (opacity == 0.0f) //If it's 0% now, hide if it shouldn't be visible (Update when Invisble setting can make ShouldBeVisible() return true still)\n    {\n        m_Opacity = opacity;\n\n        if (!ShouldBeVisible())\n        {\n            outmgr->HideOverlay(m_ID);\n        }\n    }\n\n    m_Opacity = opacity;\n}\n\nfloat Overlay::GetOpacity() const\n{\n    return m_Opacity;\n}\n\nvoid Overlay::SetVisible(bool visible)\n{\n    m_Visible = visible;\n    (visible) ? vr::VROverlay()->ShowOverlay(m_OvrlHandle) : vr::VROverlay()->HideOverlay(m_OvrlHandle);\n\n    m_SmootherPos.ResetLastPos();\n    m_SmootherRot.ResetLastPos();\n}\n\nbool Overlay::IsVisible() const\n{\n    return m_Visible;\n}\n\nbool Overlay::ShouldBeVisible() const\n{\n    const OverlayConfigData& data = OverlayManager::Get().GetConfigData(m_ID);\n    \n    if ( (m_Opacity == 0.0f) && (!data.ConfigBool[configid_bool_overlay_update_invisible]) )\n        return false;\n\n    bool should_be_visible = false;\n\n    if (!data.ConfigBool[configid_bool_overlay_enabled])\n        return false;\n\n    //Enabled theater mode is always visible\n    if (data.ConfigInt[configid_int_overlay_origin] == ovrl_origin_theater_screen)\n        return true;\n\n    OutputManager* outmgr = OutputManager::Get();\n    if (outmgr == nullptr)\n        return false;\n\n    switch (data.ConfigInt[configid_int_overlay_display_mode])\n    {\n        case ovrl_dispmode_always:\n        {\n            should_be_visible = true;\n            break;\n        }\n        case ovrl_dispmode_dashboard:\n        {\n            //Our method for getting the dashboard transform only works after it has been manually been brought up once OR the Desktop+ tab has been shown\n            //In practice this means we won't be showing dashboard display mode overlays on the initial SteamVR dashboard that is active when booting up\n            should_be_visible = ( (outmgr->HasDashboardBeenActivatedOnce()) && (vr::VROverlay()->IsDashboardVisible()) );\n            break;\n        }\n        case ovrl_dispmode_scene:\n        {\n            should_be_visible = !vr::VROverlay()->IsDashboardVisible();\n            break;\n        }\n        case ovrl_dispmode_dplustab:\n        {\n            should_be_visible = (outmgr->IsDashboardTabActive());\n            break;\n        }\n    }\n\n    //Also apply above when origin is dashboard\n    if ( (should_be_visible) && (data.ConfigInt[configid_int_overlay_origin] == ovrl_origin_dashboard) )\n    {\n        should_be_visible = outmgr->HasDashboardBeenActivatedOnce();\n    }\n\n    return should_be_visible;\n}\n\nvoid Overlay::UpdateValidatedCropRect()\n{\n    OutputManager* outmgr = OutputManager::Get();\n    if (outmgr == nullptr)\n        return;\n\n    const OverlayConfigData& data = OverlayManager::Get().GetConfigData(m_ID);\n\n    int x, y, width, height;\n\n    if (data.ConfigBool[configid_bool_overlay_crop_enabled])\n    {\n        x      = std::min( std::max(0, data.ConfigInt[configid_int_overlay_crop_x]), data.ConfigInt[configid_int_overlay_state_content_width]);\n        y      = std::min( std::max(0, data.ConfigInt[configid_int_overlay_crop_y]), data.ConfigInt[configid_int_overlay_state_content_height]);\n        width  = data.ConfigInt[configid_int_overlay_crop_width];\n        height = data.ConfigInt[configid_int_overlay_crop_height];\n    }\n    else //Fall back to default crop when cropping is disabled\n    {\n        //Current desktop cropping values for desktop duplication\n        if ((m_TextureSource == ovrl_texsource_desktop_duplication) || (m_TextureSource == ovrl_texsource_desktop_duplication_3dou_converted))\n        {\n            outmgr->CropToDisplay(data.ConfigInt[configid_int_overlay_desktop_id], x, y, width, height);\n        }\n        else //Content size for everything else\n        {\n            x = 0;\n            y = 0;\n            width  = data.ConfigInt[configid_int_overlay_state_content_width];\n            height = data.ConfigInt[configid_int_overlay_state_content_height];\n        }\n    }\n\n    int width_max  = std::max(data.ConfigInt[configid_int_overlay_state_content_width]  - x, 1);\n    int height_max = std::max(data.ConfigInt[configid_int_overlay_state_content_height] - y, 1);\n\n    if (width == -1)\n        width = width_max;\n    else\n        width = std::min(width, width_max);\n\n    if (height == -1)\n        height = height_max;\n    else\n        height = std::min(height, height_max);\n\n    m_ValidatedCropRect = DPRect(x, y, x + width, y + height);\n}\n\nconst DPRect& Overlay::GetValidatedCropRect() const\n{\n    return m_ValidatedCropRect;\n}\n\nvoid Overlay::SetTextureSource(OverlayTextureSource tex_source)\n{\n    //Skip if nothing changed (except texsource_ui/browser which are always re-applied)\n    if ( (m_TextureSource == tex_source) && (tex_source != ovrl_texsource_ui) && (tex_source != ovrl_texsource_browser) )\n        return;\n\n    //Cleanup old sources if needed\n    switch (m_TextureSource)\n    {\n        case ovrl_texsource_desktop_duplication_3dou_converted: m_OUtoSBSConverter.CleanRefs();    break;\n        case ovrl_texsource_winrt_capture:                      DPWinRT_StopCapture(m_OvrlHandle); break;\n        case ovrl_texsource_ui:\n        {\n            if (tex_source != ovrl_texsource_ui)\n            {\n                vr::VROverlay()->SetOverlayRenderingPid(m_OvrlHandle, ::GetCurrentProcessId());\n                vr::VROverlay()->SetOverlayIntersectionMask(m_OvrlHandle, nullptr, 0);\n            }\n            break;\n        }\n        case ovrl_texsource_browser:\n        {\n            if (tex_source != ovrl_texsource_browser)\n            {\n                DPBrowserAPIClient::Get().DPBrowser_StopBrowser(m_OvrlHandle);\n                vr::VROverlay()->SetOverlayRenderingPid(m_OvrlHandle, ::GetCurrentProcessId());\n            }\n            break;\n        }\n        default: break;\n    }\n\n    //If this overlay is the theater overlay, mark for refresh down below if the source changed\n    bool theater_refresh_needed = ( (m_TextureSource != tex_source) && (OverlayManager::Get().GetTheaterOverlayID() == m_ID) );\n\n    m_TextureSource = tex_source;\n\n    switch (m_TextureSource)\n    {\n        case ovrl_texsource_none:\n        {\n            if (OutputManager* outmgr = OutputManager::Get())\n            {\n                outmgr->SetOutputErrorTexture(m_OvrlHandle);\n            }\n            break;\n        }\n        case ovrl_texsource_desktop_duplication:                /*fallthrough*/\n        case ovrl_texsource_desktop_duplication_3dou_converted: AssignDesktopDuplicationTexture();                                                                        break;\n        case ovrl_texsource_ui:                                 vr::VROverlay()->SetOverlayRenderingPid(m_OvrlHandle, IPCManager::GetUIAppProcessID());                   break;\n        case ovrl_texsource_browser:                            vr::VROverlay()->SetOverlayRenderingPid(m_OvrlHandle, DPBrowserAPIClient::Get().GetServerAppProcessID()); break;\n        default: break;\n    }\n\n    //Set output error config state\n    {\n        const bool has_no_output = (m_TextureSource == ovrl_texsource_none);\n        OverlayConfigData& data = OverlayManager::Get().GetConfigData(m_ID);\n\n        if (data.ConfigBool[configid_bool_overlay_state_no_output] != has_no_output)\n        {\n            data.ConfigBool[configid_bool_overlay_state_no_output] = has_no_output;\n\n            IPCManager::Get().PostConfigMessageToUIApp(configid_int_state_overlay_current_id_override, m_ID);\n            IPCManager::Get().PostConfigMessageToUIApp(configid_bool_overlay_state_no_output, has_no_output);\n            IPCManager::Get().PostConfigMessageToUIApp(configid_int_state_overlay_current_id_override, -1);\n        }\n    }\n\n    if (theater_refresh_needed)\n    {\n        if (OutputManager* outmgr = OutputManager::Get())\n        {\n            unsigned int current_overlay_old = OverlayManager::Get().GetCurrentOverlayID();\n            OverlayManager::Get().SetCurrentOverlayID(m_ID);\n            outmgr->ResetCurrentOverlay();\n            OverlayManager::Get().SetCurrentOverlayID(current_overlay_old);\n        }\n    }\n}\n\nOverlayTextureSource Overlay::GetTextureSource() const\n{\n    return m_TextureSource;\n}\n\nvoid Overlay::OnDesktopDuplicationUpdate()\n{\n    if ( (m_Visible) && (m_TextureSource == ovrl_texsource_desktop_duplication_3dou_converted) )\n    {\n        OutputManager::Get()->ConvertOUtoSBS(*this, m_OUtoSBSConverter);\n    }\n}\n\nRadialFollowCore& Overlay::GetSmootherPos()\n{\n    return m_SmootherPos;\n}\n\nRadialFollowCore& Overlay::GetSmootherRot()\n{\n    return m_SmootherRot;\n}\n"
  },
  {
    "path": "src/DesktopPlus/Overlays.h",
    "content": "#pragma once\n\n#include \"openvr.h\"\n#include \"DPRect.h\"\n#include \"OUtoSBSConverter.h\"\n#include \"RadialFollowSmoothing.h\"\n\n//About the Overlay class:\n//OutputManager's m_OvrlHandleDesktopTexture holds the actual texture handle for every other desktop duplication overlay created by SteamVR\n//This is *not* documented functionality in SteamVR, but it is the one with the best results.\n//Additional overlays are also almost free except for the compositor rendering them.\n//The alternative approach for this would be the documented way of using one texture handle for every overlay created by the overlay application, but this\n//prevents SteamVR from using the advanced overlay texture filter, so we'd get blurry overlays. Not good.\n//Given that variant exists, doing it this way is probably somewhat safe. It wouldn't be super hard to fix this up if it broke eventually, though.\n//Using a separate texture for every overlay would be slower and take up more memory, so there's honestly no upside of that.\n\nenum OverlayTextureSource\n{\n    ovrl_texsource_invalid = -1,                        //Initial state, shouldn't be set to unless immediately calling SetTextureSource() to explicitly re-apply certain sources\n    ovrl_texsource_none,                                //Used with capture sources other than desktop duplication while capture is not active\n    ovrl_texsource_desktop_duplication,\n    ovrl_texsource_desktop_duplication_3dou_converted,\n    ovrl_texsource_winrt_capture,\n    ovrl_texsource_ui,\n    ovrl_texsource_browser\n};\n\nclass Overlay\n{\n    private:\n        unsigned int m_ID;\n        vr::VROverlayHandle_t m_OvrlHandle;\n        bool m_Visible;                       //IVROverlay::IsOverlayVisible() is unreliable if the state changed during the same frame so we keep track ourselves\n        float m_Opacity;                      //This is the opacity the overlay is currently set at, which may differ from what the config value is\n        DPRect m_ValidatedCropRect;           //Validated cropping rectangle used in OutputManager::Update() to check against dirty update regions\n        OverlayTextureSource m_TextureSource;\n\n        OUtoSBSConverter m_OUtoSBSConverter;\n        RadialFollowCore m_SmootherPos;\n        RadialFollowCore m_SmootherRot;\n\n    public:\n        Overlay(unsigned int id);\n        Overlay(Overlay&& b);\n        Overlay& operator=(Overlay&& b);\n        ~Overlay();\n\n        void InitOverlay();\n        void AssignDesktopDuplicationTexture();\n        unsigned int GetID() const;\n        void SetID(unsigned int id);\n        vr::VROverlayHandle_t GetHandle() const;\n        void SetHandle(vr::VROverlayHandle_t handle);   //Sets the handle of the overlay without calling DestroyOverlay() on the previous one, used by OverlayManager\n\n        void SetOpacity(float opacity);\n        float GetOpacity() const;\n\n        void SetVisible(bool visible);      //Call OutputManager::Show/HideOverlay() instead of this to properly manage duplication state based on active overlays\n        bool IsVisible() const;\n        bool ShouldBeVisible() const;\n\n        void UpdateValidatedCropRect();\n        const DPRect& GetValidatedCropRect() const;\n\n        void SetTextureSource(OverlayTextureSource tex_source);\n        OverlayTextureSource GetTextureSource() const;\n        void OnDesktopDuplicationUpdate();  //Called by OutputManager::RefreshOpenVROverlayTexture() for every overlay, but only if the texture has actually changed\n\n        RadialFollowCore& GetSmootherPos();\n        RadialFollowCore& GetSmootherRot();\n};"
  },
  {
    "path": "src/DesktopPlus/PixelShader.hlsl",
    "content": "Texture2D tx : register( t0 );\nSamplerState samLinear : register( s0 );\n\nstruct PS_INPUT\n{\n    float4 Pos : SV_POSITION;\n    float2 Tex : TEXCOORD;\n};\n\n//--------------------------------------------------------------------------------------\n// Pixel Shader\n//--------------------------------------------------------------------------------------\nfloat4 PS(PS_INPUT input) : SV_Target\n{\n\tfloat4 color;\n\tcolor = tx.Sample(samLinear, input.Tex);\n\tcolor.a = (color.a > 0.0) ? 1.0 : 0.0;   //Enforce 1-bit alpha as on some systems the duplication output isn't opaque for some reason\n\treturn color;\n}"
  },
  {
    "path": "src/DesktopPlus/PixelShaderCursor.hlsl",
    "content": "Texture2D tx : register( t0 );\nSamplerState samLinear : register( s0 );\n\nstruct PS_INPUT\n{\n    float4 Pos : SV_POSITION;\n    float2 Tex : TEXCOORD;\n};\n\n//--------------------------------------------------------------------------------------\n// Pixel Shader\n//--------------------------------------------------------------------------------------\nfloat4 PSCURSOR(PS_INPUT input) : SV_Target\n{\n\treturn tx.Sample(samLinear, input.Tex);\n}"
  },
  {
    "path": "src/DesktopPlus/RadialFollowSmoothing.cpp",
    "content": "/* This Source Code Form is subject to the terms of the Mozilla Public\n* License, v. 2.0. If a copy of the MPL was not distributed with this\n* file, You can obtain one at https://mozilla.org/MPL/2.0/. */\n\n//This source file and accompanying header is based on\n//AbstractQbit's Radial Follow Smoothing OpenTabletDriver Plugin (https://github.com/AbstractQbit/AbstractOTDPlugins) (RadialFollowCore.cs only)\n\n#include \"RadialFollowSmoothing.h\"\n\n#include <cmath>\n\ndouble RadialFollowCore::GetOuterRadius()\n{\n    return m_RadiusOuter;\n}\n\nvoid RadialFollowCore::SetOuterRadius(double value)\n{\n    m_RadiusOuter = clamp(m_RadiusOuter, 0.0, 1000000.0);\n}\n\ndouble RadialFollowCore::GetInnerRadius()\n{\n    return m_RadiusInner;\n}\n\nvoid RadialFollowCore::SetInnerRadius(double value)\n{\n    m_RadiusInner = clamp(value, 0.0, 1000000.0);\n}\n\ndouble RadialFollowCore::GetSmoothingCoefficient()\n{\n    return m_SmoothingCoef;\n}\n\nvoid RadialFollowCore::SetSmoothingCoefficient(double value)\n{\n    m_SmoothingCoef = clamp(value, 0.0001, 1.0);\n}\n\ndouble RadialFollowCore::GetSoftKneeScale()\n{\n    return m_SoftKneeScale;\n}\n\nvoid RadialFollowCore::SetSoftKneeScale(double value)\n{\n    m_SoftKneeScale = clamp(value, 0.0, 100.0);\n    UpdateDerivedParams();\n}\n\ndouble RadialFollowCore::GetSmoothingLeakCoefficient()\n{\n    return m_SmoothingLeakCoef;\n}\n\nvoid RadialFollowCore::SetSmoothingLeakCoefficient(double value)\n{\n    m_SmoothingLeakCoef = clamp(value, 0.0, 1.0);\n}\n\nbool RadialFollowCore::GetDetectInterruptions()\n{\n    return m_DetectInterruptions;\n}\n\nvoid RadialFollowCore::SetDetectInterruptions(bool value)\n{\n    m_DetectInterruptions = value;\n}\n\nvoid RadialFollowCore::ApplyPresetSettings(int preset_id)\n{\n    preset_id = clamp(preset_id, 0, 5);\n\n    //These are just presets used by Desktop+, in hopes that they make sense for laser pointing and are easier to use than adjusting values directly\n    switch (preset_id)\n    {\n        case 0: //Not really used, skip calling Filter() entirely instead\n        {\n            SetOuterRadius(0.0);\n            SetInnerRadius(0.0);\n            SetSmoothingCoefficient(0.0);\n            SetSoftKneeScale(0.0);\n            SetSmoothingLeakCoefficient(0.0);\n            break;\n        }\n        case 1:\n        {\n            SetOuterRadius(5.0);\n            SetInnerRadius(0.5);\n            SetSmoothingCoefficient(0.95);\n            SetSoftKneeScale(1.0);\n            SetSmoothingLeakCoefficient(0.0);\n            break;\n        }\n        case 2:\n        {\n            SetOuterRadius(5.0);\n            SetInnerRadius(3.0);\n            SetSmoothingCoefficient(0.95);\n            SetSoftKneeScale(1.0);\n            SetSmoothingLeakCoefficient(0.0);\n            break;\n        }\n        case 3:\n        {\n            SetOuterRadius(7.5);\n            SetInnerRadius(4.5);\n            SetSmoothingCoefficient(1.0);\n            SetSoftKneeScale(5.0);\n            SetSmoothingLeakCoefficient(0.25);\n            break;\n        }\n        case 4:\n        {\n            SetOuterRadius(12.5);\n            SetInnerRadius(7.5);\n            SetSmoothingCoefficient(1.0);\n            SetSoftKneeScale(10.0);\n            SetSmoothingLeakCoefficient(0.5);\n            break;\n        }\n        case 5:\n        {\n            SetOuterRadius(32.0);\n            SetInnerRadius(12.0);\n            SetSmoothingCoefficient(1.0);\n            SetSoftKneeScale(50.0);\n            SetSmoothingLeakCoefficient(0.75);\n            break;\n        }\n    }\n}\n\nfloat RadialFollowCore::SampleRadialCurve(float dist)\n{\n    return (float)DeltaFn(dist, m_XOffset, m_ScaleComp);\n}\n\nVector2 RadialFollowCore::Filter(const Vector2& target)\n{\n    Vector2 direction = target - m_LastPos;\n    float distToMove = SampleRadialCurve(direction.length());\n    direction.normalize();\n    m_LastPos = m_LastPos + (direction * distToMove);\n\n    //Catch NaNs and interrupted input\n    if ( !((std::isfinite(m_LastPos.x)) && (std::isfinite(m_LastPos.y))) || ((m_DetectInterruptions) && (::GetTickCount64() > m_LastTick + 50)) )\n        m_LastPos = target;\n\n    m_LastTick = ::GetTickCount64();\n\n    return m_LastPos;\n}\n\nVector3 RadialFollowCore::Filter(const Vector3& target)\n{\n    Vector3 direction = target - m_LastPos3;\n    float distToMove = SampleRadialCurve(direction.length());\n    direction.normalize();\n    m_LastPos3 = m_LastPos3 + (direction * distToMove);\n\n    //Catch NaNs and interrupted input\n    if ( !((std::isfinite(m_LastPos3.x)) && (std::isfinite(m_LastPos3.y)) && (std::isfinite(m_LastPos3.z))) || ((m_DetectInterruptions) && (::GetTickCount64() > m_LastTick + 50)) )\n        m_LastPos3 = target;\n\n    m_LastTick = ::GetTickCount64();\n\n    return m_LastPos3;\n}\n\nVector3 RadialFollowCore::FilterWrapped(const Vector3& target, float value_min, float value_max)\n{\n    auto unwrap_to_nearest = [&](const float value_wrapped, const float value_prev)\n    {\n        //Unwrap value in a way that ensures close to the previous one\n        const float range_width = value_max - value_min;\n        const float value_in_range = value_wrapped - std::floor((value_wrapped - value_min) / range_width) * range_width;\n        //Shift by multiples of range_width so the value is +/- half range_width to the previous value\n        const float delta = value_prev - value_in_range;\n        return value_in_range + std::round(delta / range_width) * range_width;\n    };\n\n    Vector3 target_unwrapped = target;\n\n    target_unwrapped.x = unwrap_to_nearest(target.x, m_LastPos3.x);\n    target_unwrapped.y = unwrap_to_nearest(target.y, m_LastPos3.y);\n    target_unwrapped.z = unwrap_to_nearest(target.z, m_LastPos3.z);\n\n    Vector3 direction = target_unwrapped - m_LastPos3;\n    float distToMove = SampleRadialCurve(direction.length());\n    direction.normalize();\n    m_LastPos3 = m_LastPos3 + (direction * distToMove);\n\n    //Catch NaNs and interrupted input\n    if ( !((std::isfinite(m_LastPos3.x)) && (std::isfinite(m_LastPos3.y)) && (std::isfinite(m_LastPos3.z))) || ((m_DetectInterruptions) && (::GetTickCount64() > m_LastTick + 50)) )\n        m_LastPos3 = target;\n\n    m_LastTick = ::GetTickCount64();\n\n    return m_LastPos3;\n}\n\nvoid RadialFollowCore::ResetLastPos()\n{\n    //Filter() functions will already fall back to the target pos if any results aren't finite so this does the trick\n    m_LastPos.x  = INFINITY;\n    m_LastPos3.x = INFINITY;\n}\n\nvoid RadialFollowCore::UpdateDerivedParams()\n{\n    if (m_SoftKneeScale > 0.0001f)\n    {\n        m_XOffset   = GetXOffset();\n        m_ScaleComp = GetScaleComp();\n    }\n    else //Calculating them with functions would use / by 0\n    {\n        m_XOffset   = -1.0;\n        m_ScaleComp =  1.0;\n    }\n}\n\ndouble RadialFollowCore::KneeFunc(double x)\n{\n    if (x < -3.0)\n        return x;\n    else if (x < 3.0)\n        return log(tanh(exp(x)));\n    else\n        return 0.0;\n}\n\ndouble RadialFollowCore::KneeScaled(double x)\n{\n    if (m_SoftKneeScale > 0.0001)\n        return m_SoftKneeScale * KneeFunc(x / m_SoftKneeScale) + 1.0;\n    else\n        return (x > 0.0) ? 1.0 : 1.0 + x;\n}\n\ndouble RadialFollowCore::InverseTanh(double x)\n{\n    return log((1.0 + x) / (1.0 - x)) / 2.0;\n}\n\ndouble RadialFollowCore::InverseKneeScaled(double x)\n{\n    return m_SoftKneeScale * log(InverseTanh(exp((x - 1.0) / m_SoftKneeScale)));\n}\n\ndouble RadialFollowCore::DeriveKneeScaled(double x)\n{\n    const double x_e = exp(x / m_SoftKneeScale);\n    const double x_e_tanh = tanh(x_e);\n    return (x_e - x_e * (x_e_tanh * x_e_tanh)) / x_e_tanh;\n}\n\ndouble RadialFollowCore::GetXOffset()\n{\n    return InverseKneeScaled(0.0);\n}\n\ndouble RadialFollowCore::GetScaleComp()\n{\n    return DeriveKneeScaled(GetXOffset());\n}\n\ndouble RadialFollowCore::GetRadiusOuterAdjusted()\n{\n    return m_GridScale * std::max(m_RadiusOuter, m_RadiusInner + 0.0001);\n}\n\ndouble RadialFollowCore::GetRadiusInnerAdjusted()\n{\n    return m_GridScale * m_RadiusInner;\n}\n\ndouble RadialFollowCore::LeakedFn(double x, double offset, double scaleComp)\n{\n    return KneeScaled(x + offset) * (1 - m_SmoothingLeakCoef) + x * m_SmoothingLeakCoef * scaleComp;\n}\n\ndouble RadialFollowCore::SmoothedFn(double x, double offset, double scaleComp)\n{\n    return LeakedFn(x * m_SmoothingCoef / scaleComp, offset, scaleComp);\n}\n\ndouble RadialFollowCore::ScaleToOuter(double x, double offset, double scaleComp)\n{\n    return (GetRadiusOuterAdjusted() - GetRadiusInnerAdjusted()) * SmoothedFn(x / (GetRadiusOuterAdjusted() - GetRadiusInnerAdjusted()), offset, scaleComp);\n}\n\ndouble RadialFollowCore::DeltaFn(double x, double offset, double scaleComp)\n{\n    return (x > GetRadiusInnerAdjusted()) ? x - ScaleToOuter(x - GetRadiusInnerAdjusted(), offset, scaleComp) - GetRadiusInnerAdjusted() : 0.0;\n}\n"
  },
  {
    "path": "src/DesktopPlus/RadialFollowSmoothing.h",
    "content": "/* This Source Code Form is subject to the terms of the Mozilla Public\n* License, v. 2.0. If a copy of the MPL was not distributed with this\n* file, You can obtain one at https://mozilla.org/MPL/2.0/. */\n\n//This header and accompanying source file is based on\n//AbstractQbit's Radial Follow Smoothing OpenTabletDriver Plugin (https://github.com/AbstractQbit/AbstractOTDPlugins) (RadialFollowCore.cs only)\n\n#pragma once\n\n#include \"Util.h\"\n\nclass RadialFollowCore\n{\n    public:\n        double GetOuterRadius();\n        void   SetOuterRadius(double value);\n\n        double GetInnerRadius();\n        void   SetInnerRadius(double value);\n\n        double GetSmoothingCoefficient();\n        void   SetSmoothingCoefficient(double value);\n\n        double GetSoftKneeScale();\n        void   SetSoftKneeScale(double value);\n\n        double GetSmoothingLeakCoefficient();\n        void   SetSmoothingLeakCoefficient(double value);\n\n        bool GetDetectInterruptions();\n        void SetDetectInterruptions(bool value);\n\n        void ApplyPresetSettings(int preset_id);\n\n        float SampleRadialCurve(float dist);\n\n        Vector2 Filter(const Vector2& target);\n        Vector3 Filter(const Vector3& target);\n        Vector3 FilterWrapped(const Vector3& target, float value_min, float value_max);\t//Treats changes like max to min as small steps, but doesn't wrap the return value\n\n        void ResetLastPos();\n\n    private:\n        double m_RadiusOuter\t   = 5.0;\n        double m_RadiusInner       = 0.0;\n        double m_SmoothingCoef     = 0.95;\n        double m_SoftKneeScale     = 1.0;\n        double m_SmoothingLeakCoef = 0.0;\n        double m_GridScale\t\t   = 1.0;\n\n        Vector2 m_LastPos;\n        Vector3 m_LastPos3;\n        ULONGLONG m_LastTick\t   = 0;\n        bool m_DetectInterruptions = true;\n\n        double m_XOffset   = -1.0;\n        double m_ScaleComp =  1.0;\n\n        void UpdateDerivedParams();\n\n        //Math functions\n        double KneeFunc(double x);\n        double KneeScaled(double x);\n        double InverseTanh(double x);\n        double InverseKneeScaled(double x);\n        double DeriveKneeScaled(double x);\n        double GetXOffset();\n        double GetScaleComp();\n        double GetRadiusOuterAdjusted();\n        double GetRadiusInnerAdjusted();\n        double LeakedFn(double x, double offset, double scaleComp);\n        double SmoothedFn(double x, double offset, double scaleComp);\n        double ScaleToOuter(double x, double offset, double scaleComp);\n        double DeltaFn(double x, double offset, double scaleComp);\n};"
  },
  {
    "path": "src/DesktopPlus/SoftwareCursorGrabber.cpp",
    "content": "#include \"SoftwareCursorGrabber.h\"\n#include \"Logging.h\"\n\nbool SoftwareCursorGrabber::CopyMonoMask(const ICONINFO& icon_info)\n{\n    bool ret = false;\n\n    HDC hdc = ::GetDC(nullptr);\n    if (!hdc)\n        return ret;\n\n    //Get bitmap info from cursor bitmap\n    BITMAPINFO bmp_info = {0};\n    bmp_info.bmiHeader.biSize = sizeof(bmp_info.bmiHeader);\n    if (::GetDIBits(hdc, icon_info.hbmMask, 0, 0, nullptr, &bmp_info, DIB_RGB_COLORS) != 0)\n    {\n        int cursor_width  = bmp_info.bmiHeader.biWidth;\n        int cursor_height = abs(bmp_info.bmiHeader.biHeight);\n\n        bmp_info.bmiHeader.biSize        = sizeof(bmp_info.bmiHeader);\n        bmp_info.bmiHeader.biBitCount    = 1;\n        bmp_info.bmiHeader.biCompression = BI_RGB;\n        bmp_info.bmiHeader.biHeight      = -cursor_height; //Always use top-down order (negative height)\n\n        //Add monochrome palette (and deal with the dynamic struct size)\n        RGBQUAD palette[2] = {{0, 0, 0, 0}, {255, 255, 255, 255}};\n\n        auto bmp_info_extended_data = std::unique_ptr<BYTE[]>{new BYTE[sizeof(bmp_info.bmiHeader) + sizeof(palette)]};\n        BITMAPINFO* bmp_info_extended_ptr = (BITMAPINFO*)bmp_info_extended_data.get();\n        memcpy(&bmp_info_extended_ptr->bmiHeader, &bmp_info.bmiHeader, sizeof(bmp_info.bmiHeader));\n        memcpy(bmp_info_extended_ptr->bmiColors,   palette,            sizeof(palette));\n\n        //Read the actual bitmap buffer into a temporary pixel data array (4-byte aligned)\n        const int padded_stride = ((cursor_width + 31) / 32) * 4;\n        auto temp_buffer = std::unique_ptr<BYTE[]>{new BYTE[padded_stride * cursor_height]};\n        if (::GetDIBits(hdc, icon_info.hbmMask, 0, cursor_height, temp_buffer.get(), bmp_info_extended_ptr, DIB_RGB_COLORS) != 0)\n        {\n            //Remove padding returned by GetDIBits so we end up with something that matches what Desktop Duplication returns\n            size_t used_bytes_per_scanline = ((size_t)cursor_width + 7) / 8;\n            m_DDPPointerInfo.ShapeBuffer.assign(used_bytes_per_scanline * cursor_height, 0);\n\n            for (int y = 0; y < cursor_height; ++y) \n            {\n                BYTE* src_line = temp_buffer.get()                   + y * padded_stride;\n                BYTE* dst_line = m_DDPPointerInfo.ShapeBuffer.data() + y * used_bytes_per_scanline;\n\n                memcpy(dst_line, src_line, used_bytes_per_scanline);\n            }\n\n            m_DDPPointerInfo.ShapeInfo.Width  = cursor_width;\n            m_DDPPointerInfo.ShapeInfo.Height = cursor_height;\n            m_DDPPointerInfo.ShapeInfo.Pitch  = 4;\n\n            ret = true;\n        }\n    }\n\n    ::ReleaseDC(nullptr, hdc);\n    return ret;\n}\n\nbool SoftwareCursorGrabber::CopyColor(const ICONINFO& icon_info)\n{\n    bool ret = false;\n\n    HDC hdc = ::GetDC(nullptr);\n    if (!hdc)\n        return ret;\n\n    BITMAPINFO bmp_info = {0};\n    bmp_info.bmiHeader.biSize = sizeof(bmp_info.bmiHeader);\n    if (::GetDIBits(hdc, icon_info.hbmColor, 0, 0, nullptr, &bmp_info, DIB_RGB_COLORS) != 0)\n    {\n        int cursor_width  = bmp_info.bmiHeader.biWidth;\n        int cursor_height = abs(bmp_info.bmiHeader.biHeight);\n\n        bmp_info.bmiHeader.biSize        = sizeof(bmp_info.bmiHeader);\n        bmp_info.bmiHeader.biBitCount    = 32;\n        bmp_info.bmiHeader.biCompression = BI_RGB;\n        bmp_info.bmiHeader.biHeight      = -cursor_height; //Always use top-down order (negative height)\n\n        m_DDPPointerInfo.ShapeBuffer.resize((size_t)cursor_width * cursor_height * 4);\n\n        if (::GetDIBits(hdc, icon_info.hbmColor, 0, cursor_height, m_DDPPointerInfo.ShapeBuffer.data(), &bmp_info, DIB_RGB_COLORS) != 0)\n        {\n            //Even if we don't override biBitCount to 32, it's still returned as that for 24-bit and lower bit-depth cursors (probably just the screen DC format)\n            //It seems the only way to check if the cursor needs its mask applied is to see if the alpha channel is fully blank\n            //32-bit cursors still come with masks, but applying them means to override the alpha channel with a 1-bit one (and doing so is also wasteful)\n            const bool needs_mask = IsShapeBufferBlank(m_DDPPointerInfo.ShapeBuffer.data(), m_DDPPointerInfo.ShapeBuffer.size(), DXGI_OUTDUPL_POINTER_SHAPE_TYPE_COLOR);\n            if (needs_mask)\n            {\n                auto pixel_data_mask = std::unique_ptr<BYTE[]>{new BYTE[m_DDPPointerInfo.ShapeBuffer.size()]};\n\n                //Read the mask bitmap buffer\n                if (::GetDIBits(hdc, icon_info.hbmMask, 0, cursor_height, (LPVOID)pixel_data_mask.get(), &bmp_info, DIB_RGB_COLORS) != 0)\n                {\n                    //Apply mask to color pixel data\n                    BYTE* psrc = m_DDPPointerInfo.ShapeBuffer.data() + 3;  //BGRA alpha pixel\n                    BYTE* pmsk = pixel_data_mask.get();                    //BGRA blue pixel (alpha channel is blank for the mask)\n                    const BYTE* const psrc_end = m_DDPPointerInfo.ShapeBuffer.data() + m_DDPPointerInfo.ShapeBuffer.size();\n                    for (; psrc < psrc_end; psrc += 4, pmsk += 4)\n                    {\n                        *psrc = ~(*pmsk);\n                    }\n                }\n            }\n\n            m_DDPPointerInfo.ShapeInfo.Width  = cursor_width;\n            m_DDPPointerInfo.ShapeInfo.Height = cursor_height;\n            m_DDPPointerInfo.ShapeInfo.Pitch  = cursor_width * 4;\n\n            ret = true;\n        }\n    }\n\n    ::ReleaseDC(nullptr, hdc);\n    return ret;\n}\n\nbool SoftwareCursorGrabber::IsShapeBufferBlank(BYTE* psrc, size_t size, UINT type)\n{\n    if (type == DXGI_OUTDUPL_POINTER_SHAPE_TYPE_COLOR)\n    {\n        const BYTE* const psrc_end = psrc + size;\n        psrc = psrc + 3; //first BGRA alpha pixel\n        for (; psrc < psrc_end; psrc += 4)\n        {\n            if (*psrc != 0)\n            {\n                return false;\n            }\n        }\n    }\n    else if (type == DXGI_OUTDUPL_POINTER_SHAPE_TYPE_MONOCHROME)\n    {\n        const BYTE* const psrc_end = psrc + size;\n        psrc = psrc + (size / 2); //first mask pixel\n        for (; psrc < psrc_end; ++psrc)\n        {\n            if (*psrc != 0)\n            {\n                return false;\n            }\n        }\n    }\n\n    return true;\n}\n\nbool SoftwareCursorGrabber::SynthesizeDDPCursorInfo()\n{\n    LOG_IF_F(INFO, !m_LoggedOnceUsed, \"Using Alternative Cursor Rendering\");\n    m_LoggedOnceUsed = true;\n\n    CURSORINFO cursor_info = {0};\n    cursor_info.cbSize = sizeof(CURSORINFO);\n\n    bool ret = false;\n\n    if (::GetCursorInfo(&cursor_info))\n    {\n        const bool shape_changed = (cursor_info.hCursor != m_CursorInfoLast.hCursor);\n        if (shape_changed)\n        {\n            if (cursor_info.hCursor != nullptr)\n            {\n                ICONINFO icon_info = {};\n                if (::GetIconInfo(cursor_info.hCursor, &icon_info))\n                {\n                    ret = (icon_info.hbmColor != nullptr) ? CopyColor(icon_info) : CopyMonoMask(icon_info);\n                    m_DDPPointerInfo.ShapeInfo.Type = (icon_info.hbmColor != nullptr) ? DXGI_OUTDUPL_POINTER_SHAPE_TYPE_COLOR : DXGI_OUTDUPL_POINTER_SHAPE_TYPE_MONOCHROME;\n\n                    ::DeleteObject(icon_info.hbmColor);\n                    ::DeleteObject(icon_info.hbmMask);\n                }\n\n                //Fallback: If getting the icon info failed or \"succeeded\" with blank data, try copying the default arrow pointer instead\n                if ((!ret) || (IsShapeBufferBlank(m_DDPPointerInfo.ShapeBuffer.data(), m_DDPPointerInfo.ShapeBuffer.size(), m_DDPPointerInfo.ShapeInfo.Type)))\n                {\n                    LOG_IF_F(WARNING, !m_LoggedOnceFallbackDefault, \"Alternative Cursor Rendering: Getting current cursor failed. Falling back to default cursor.\");\n                    m_LoggedOnceFallbackDefault = true;\n\n                    ret = false;\n\n                    if (::GetIconInfo(::LoadCursor(nullptr, IDC_ARROW), &icon_info))\n                    {\n                        ret = (icon_info.hbmColor != nullptr) ? CopyColor(icon_info) : CopyMonoMask(icon_info);\n                        m_DDPPointerInfo.ShapeInfo.Type = (icon_info.hbmColor != nullptr) ? DXGI_OUTDUPL_POINTER_SHAPE_TYPE_COLOR : DXGI_OUTDUPL_POINTER_SHAPE_TYPE_MONOCHROME;\n\n                        //Check if somehow still blank\n                        if (ret)\n                        {\n                            ret = !IsShapeBufferBlank(m_DDPPointerInfo.ShapeBuffer.data(), m_DDPPointerInfo.ShapeBuffer.size(), m_DDPPointerInfo.ShapeInfo.Type);\n                        }\n\n                        ::DeleteObject(icon_info.hbmColor);\n                        ::DeleteObject(icon_info.hbmMask);\n                    }\n                }\n\n                //Set other data if cursor copy was successful\n                if (ret)\n                {\n                    m_DDPPointerInfo.CursorShapeChanged = true;\n                    m_DDPPointerInfo.ShapeInfo.HotSpot.x = icon_info.xHotspot;\n                    m_DDPPointerInfo.ShapeInfo.HotSpot.y = icon_info.yHotspot;\n                }\n                else\n                {\n                    LOG_IF_F(WARNING, !m_LoggedOnceFallbackBlob, \"Alternative Cursor Rendering: Getting default cursor failed. Falling back to simple blob.\");\n                    m_LoggedOnceFallbackBlob = true;\n\n                    //Everything else failed, put a blob there\n                    m_DDPPointerInfo.ShapeInfo.Width  = 12;\n                    m_DDPPointerInfo.ShapeInfo.Height = 19 * 2; //Double height for mask\n                    m_DDPPointerInfo.ShapeBuffer.assign((size_t)m_DDPPointerInfo.ShapeInfo.Width * m_DDPPointerInfo.ShapeInfo.Height, 255);\n\n                    m_DDPPointerInfo.CursorShapeChanged = true;\n                    m_DDPPointerInfo.ShapeInfo.Type  = DXGI_OUTDUPL_POINTER_SHAPE_TYPE_MONOCHROME;\n                    m_DDPPointerInfo.ShapeInfo.Pitch = 4;\n                    m_DDPPointerInfo.ShapeInfo.HotSpot.x = 0;\n                    m_DDPPointerInfo.ShapeInfo.HotSpot.y = 0;\n\n                    ret = true;\n                }\n            }\n            else\n            {\n                //Create blank 1x1 pixel data for null cursor handle\n                m_DDPPointerInfo.ShapeBuffer.assign(4, 0);\n                m_DDPPointerInfo.ShapeInfo.Width  = 1;\n                m_DDPPointerInfo.ShapeInfo.Height = 1;\n\n                ret = true;\n            }\n        }\n        else\n        {\n            ret = true;\n        }\n\n        if (ret)\n        {\n            m_DDPPointerInfo.Visible = (cursor_info.flags == CURSOR_SHOWING);\n            m_DDPPointerInfo.Position = cursor_info.ptScreenPos;\n            m_DDPPointerInfo.Position.x -= m_DDPPointerInfo.ShapeInfo.HotSpot.x;\n            m_DDPPointerInfo.Position.y -= m_DDPPointerInfo.ShapeInfo.HotSpot.y;\n            QueryPerformanceCounter(&m_DDPPointerInfo.LastTimeStamp);\n        }\n\n        m_CursorInfoLast = cursor_info;\n    }\n\n    return ret;\n}\n\nPTR_INFO& SoftwareCursorGrabber::GetDDPCursorInfo()\n{\n    return m_DDPPointerInfo;\n}\n"
  },
  {
    "path": "src/DesktopPlus/SoftwareCursorGrabber.h",
    "content": "#pragma once\n\n#include <unordered_map>\n#include \"CommonTypes.h\"\n\n//Alternative method to grab the cursor image data\n//Stores the data in the same way the DuplicationManager class does for Desktop Duplication cursor output to act as a drop-in alternative\nclass SoftwareCursorGrabber\n{\n    private:\n        CURSORINFO m_CursorInfoLast = {};\n        PTR_INFO m_DDPPointerInfo = {};\n        std::unordered_map<HCURSOR, bool> m_CursorUseMaskCache;\n\n        //Log things only once per session as it would be quite spammy otherwise\n        bool m_LoggedOnceUsed = false;\n        bool m_LoggedOnceFallbackDefault = false;\n        bool m_LoggedOnceFallbackBlob = false;\n\n        bool CopyMonoMask(const ICONINFO& icon_info);\n        bool CopyColor(const ICONINFO& icon_info);\n        static bool IsShapeBufferBlank(BYTE* psrc, size_t size, UINT type);\n\n    public:\n        bool SynthesizeDDPCursorInfo();\n        PTR_INFO& GetDDPCursorInfo();\n};\n"
  },
  {
    "path": "src/DesktopPlus/ThreadManager.cpp",
    "content": "#include \"ThreadManager.h\"\n\nDWORD WINAPI CaptureThreadEntry(_In_ void* Param);\n\nTHREADMANAGER::THREADMANAGER() : m_ThreadCount(0),\n                                 m_ThreadHandles(nullptr),\n                                 m_ThreadData(nullptr)\n{\n\n}\n\nTHREADMANAGER::~THREADMANAGER()\n{\n    Clean();\n}\n\n//\n// Clean up resources\n//\nvoid THREADMANAGER::Clean()\n{\n    m_PtrInfo = PTR_INFO();\n\n    if (m_ThreadHandles)\n    {\n        for (UINT i = 0; i < m_ThreadCount; ++i)\n        {\n            if (m_ThreadHandles[i])\n            {\n                CloseHandle(m_ThreadHandles[i]);\n            }\n        }\n        delete [] m_ThreadHandles;\n        m_ThreadHandles = nullptr;\n    }\n\n    if (m_ThreadData)\n    {\n        for (UINT i = 0; i < m_ThreadCount; ++i)\n        {\n            CleanDx(&m_ThreadData[i].DxRes);\n        }\n        delete [] m_ThreadData;\n        m_ThreadData = nullptr;\n    }\n\n    m_ThreadCount = 0;\n}\n\n//\n// Clean up DX_RESOURCES\n//\nvoid THREADMANAGER::CleanDx(_Inout_ DX_RESOURCES* Data)\n{\n    if (Data->Device)\n    {\n        Data->Device->Release();\n        Data->Device = nullptr;\n    }\n\n    if (Data->Context)\n    {\n        Data->Context->Release();\n        Data->Context = nullptr;\n    }\n\n    if (Data->VertexShader)\n    {\n        Data->VertexShader->Release();\n        Data->VertexShader = nullptr;\n    }\n\n    if (Data->PixelShader)\n    {\n        Data->PixelShader->Release();\n        Data->PixelShader = nullptr;\n    }\n\n    if (Data->InputLayout)\n    {\n        Data->InputLayout->Release();\n        Data->InputLayout = nullptr;\n    }\n\n    if (Data->Sampler)\n    {\n        Data->Sampler->Release();\n        Data->Sampler = nullptr;\n    }\n}\n\n//\n// Start up threads for DDA\n//\nDUPL_RETURN THREADMANAGER::Initialize(INT SingleOutput, UINT OutputCount, HANDLE UnexpectedErrorEvent, HANDLE ExpectedErrorEvent, HANDLE NewFrameProcessedEvent,\n                                      HANDLE PauseDuplicationEvent, HANDLE ResumeDuplicationEvent, HANDLE TerminateThreadsEvent,\n                                      HANDLE SharedHandle, _In_ RECT* DesktopDim, IDXGIAdapter* DXGIAdapter, bool WMRIgnoreVScreens)\n{\n    m_ThreadCount = OutputCount;\n    m_ThreadHandles = new (std::nothrow) HANDLE[m_ThreadCount];\n    m_ThreadData = new (std::nothrow) THREAD_DATA[m_ThreadCount];\n    if (!m_ThreadHandles || !m_ThreadData)\n    {\n        return ProcessFailure(nullptr, L\"Failed to allocate array for threads\", L\"Desktop+ Error\", E_OUTOFMEMORY);\n    }\n\n    // Create appropriate # of threads for duplication\n    DUPL_RETURN Ret = DUPL_RETURN_SUCCESS;\n    for (UINT i = 0; i < m_ThreadCount; ++i)\n    {\n        m_ThreadData[i].UnexpectedErrorEvent = UnexpectedErrorEvent;\n        m_ThreadData[i].ExpectedErrorEvent = ExpectedErrorEvent;\n        m_ThreadData[i].NewFrameProcessedEvent = NewFrameProcessedEvent;\n        m_ThreadData[i].PauseDuplicationEvent = PauseDuplicationEvent;\n        m_ThreadData[i].ResumeDuplicationEvent = ResumeDuplicationEvent;\n        m_ThreadData[i].TerminateThreadsEvent = TerminateThreadsEvent;\n        m_ThreadData[i].Output = (SingleOutput < 0) ? i : SingleOutput;\n        m_ThreadData[i].TexSharedHandle = SharedHandle;\n        m_ThreadData[i].OffsetX = DesktopDim->left;\n        m_ThreadData[i].OffsetY = DesktopDim->top;\n        m_ThreadData[i].PtrInfo = &m_PtrInfo;\n        m_ThreadData[i].DirtyRegionTotal = &m_DirtyRegionTotal;\n        m_ThreadData[i].WMRIgnoreVScreens = WMRIgnoreVScreens;\n\n        RtlZeroMemory(&m_ThreadData[i].DxRes, sizeof(DX_RESOURCES));\n        Ret = InitializeDx(&m_ThreadData[i].DxRes, DXGIAdapter);\n        if (Ret != DUPL_RETURN_SUCCESS)\n        {\n            if (DXGIAdapter != nullptr)\n                DXGIAdapter->Release();\n\n            return Ret;\n        }\n\n        DWORD ThreadId;\n        m_ThreadHandles[i] = CreateThread(nullptr, 0, CaptureThreadEntry, &m_ThreadData[i], 0, &ThreadId);\n        if (m_ThreadHandles[i] == nullptr)\n        {\n            if (DXGIAdapter != nullptr)\n                DXGIAdapter->Release();\n\n            return ProcessFailure(nullptr, L\"Failed to create thread\", L\"Desktop+ Error\", E_FAIL);\n        }\n    }\n\n    if (DXGIAdapter != nullptr)\n        DXGIAdapter->Release();\n\n    return Ret;\n}\n\n//\n// Get DX_RESOURCES\n//\nDUPL_RETURN THREADMANAGER::InitializeDx(_Out_ DX_RESOURCES* Data, IDXGIAdapter* DXGIAdapter)\n{\n    HRESULT hr = S_OK;\n\n    // Driver types supported\n    D3D_DRIVER_TYPE DriverTypes[] =\n    {\n        D3D_DRIVER_TYPE_HARDWARE,\n        D3D_DRIVER_TYPE_WARP,\n        D3D_DRIVER_TYPE_REFERENCE,\n    };\n    UINT NumDriverTypes = ARRAYSIZE(DriverTypes);\n\n    // Feature levels supported\n    D3D_FEATURE_LEVEL FeatureLevels[] =\n    {\n        D3D_FEATURE_LEVEL_11_0,\n        D3D_FEATURE_LEVEL_10_1,\n        D3D_FEATURE_LEVEL_10_0,\n        D3D_FEATURE_LEVEL_9_1\n    };\n    UINT NumFeatureLevels = ARRAYSIZE(FeatureLevels);\n\n    D3D_FEATURE_LEVEL FeatureLevel;\n\n    // Create device\n    hr = D3D11CreateDevice(DXGIAdapter, D3D_DRIVER_TYPE_UNKNOWN, nullptr, 0, FeatureLevels, NumFeatureLevels,\n                            D3D11_SDK_VERSION, &Data->Device, &FeatureLevel, &Data->Context);\n\n    if (FAILED(hr))\n    {\n        return ProcessFailure(nullptr, L\"Failed to create device for thread\", L\"Desktop+ Error\", hr);\n    }\n\n    // VERTEX shader\n    UINT Size = ARRAYSIZE(g_VS);\n    hr = Data->Device->CreateVertexShader(g_VS, Size, nullptr, &Data->VertexShader);\n    if (FAILED(hr))\n    {\n        return ProcessFailure(Data->Device, L\"Failed to create vertex shader for thread\", L\"Desktop+ Error\", hr, SystemTransitionsExpectedErrors);\n    }\n\n    // Input layout\n    D3D11_INPUT_ELEMENT_DESC Layout[] =\n    {\n        {\"POSITION\", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D11_INPUT_PER_VERTEX_DATA, 0},\n        {\"TEXCOORD\", 0, DXGI_FORMAT_R32G32_FLOAT, 0, 12, D3D11_INPUT_PER_VERTEX_DATA, 0}\n    };\n    UINT NumElements = ARRAYSIZE(Layout);\n    hr = Data->Device->CreateInputLayout(Layout, NumElements, g_VS, Size, &Data->InputLayout);\n    if (FAILED(hr))\n    {\n        return ProcessFailure(Data->Device, L\"Failed to create input layout for thread\", L\"Desktop+ Error\", hr, SystemTransitionsExpectedErrors);\n    }\n    Data->Context->IASetInputLayout(Data->InputLayout);\n\n    // Pixel shader\n    Size = ARRAYSIZE(g_PS);\n    hr = Data->Device->CreatePixelShader(g_PS, Size, nullptr, &Data->PixelShader);\n    if (FAILED(hr))\n    {\n        return ProcessFailure(Data->Device, L\"Failed to create pixel shader for thread\", L\"Desktop+ Error\", hr, SystemTransitionsExpectedErrors);\n    }\n\n    // Set up sampler\n    D3D11_SAMPLER_DESC SampDesc;\n    RtlZeroMemory(&SampDesc, sizeof(SampDesc));\n    SampDesc.Filter = D3D11_FILTER_MIN_MAG_MIP_POINT;\n    SampDesc.AddressU = D3D11_TEXTURE_ADDRESS_CLAMP;\n    SampDesc.AddressV = D3D11_TEXTURE_ADDRESS_CLAMP;\n    SampDesc.AddressW = D3D11_TEXTURE_ADDRESS_CLAMP;\n    SampDesc.ComparisonFunc = D3D11_COMPARISON_NEVER;\n    SampDesc.MinLOD = 0;\n    SampDesc.MaxLOD = D3D11_FLOAT32_MAX;\n    hr = Data->Device->CreateSamplerState(&SampDesc, &Data->Sampler);\n    if (FAILED(hr))\n    {\n        return ProcessFailure(Data->Device, L\"Failed to create sampler state for thread\", L\"Desktop+ Error\", hr, SystemTransitionsExpectedErrors);\n    }\n\n    return DUPL_RETURN_SUCCESS;\n}\n\n//\n// Getter for the PTR_INFO structure\n//\nPTR_INFO* THREADMANAGER::GetPointerInfo()\n{\n    return &m_PtrInfo;\n}\n\nDPRect& THREADMANAGER::GetDirtyRegionTotal()\n{\n    return m_DirtyRegionTotal;\n}\n\n//\n// Waits infinitely for all spawned threads to terminate\n//\nvoid THREADMANAGER::WaitForThreadTermination()\n{\n    if (m_ThreadCount != 0)\n    {\n        WaitForMultipleObjectsEx(m_ThreadCount, m_ThreadHandles, TRUE, INFINITE, FALSE);\n    }\n}\n"
  },
  {
    "path": "src/DesktopPlus/ThreadManager.h",
    "content": "#ifndef _THREADMANAGER_H_\n#define _THREADMANAGER_H_\n\n#include \"CommonTypes.h\"\n\nclass THREADMANAGER\n{\n    public:\n        THREADMANAGER();\n        ~THREADMANAGER();\n        void Clean();\n        DUPL_RETURN Initialize(INT SingleOutput, UINT OutputCount, HANDLE UnexpectedErrorEvent, HANDLE ExpectedErrorEvent, HANDLE NewFrameProcessedEvent,\n                               HANDLE PauseDuplicationEvent, HANDLE ResumeDuplicationEvent, HANDLE TerminateThreadsEvent,\n                               HANDLE SharedHandle, _In_ RECT* DesktopDim, IDXGIAdapter* DXGIAdapter, bool WMRIgnoreVScreens);\n        PTR_INFO* GetPointerInfo();         //Should only be called when shared surface mutex has be aquired\n        DPRect& GetDirtyRegionTotal();      //Should only be called when shared surface mutex has be aquired\n        void WaitForThreadTermination();\n\n    private:\n        DUPL_RETURN InitializeDx(_Out_ DX_RESOURCES* Data, IDXGIAdapter* DXGIAdapter); //Doesn't Release() the DXGIAdapter\n        void CleanDx(_Inout_ DX_RESOURCES* Data);\n\n        PTR_INFO m_PtrInfo;\n        DPRect m_DirtyRegionTotal;\n        UINT m_ThreadCount;\n        _Field_size_(m_ThreadCount) HANDLE* m_ThreadHandles;\n        _Field_size_(m_ThreadCount) THREAD_DATA* m_ThreadData;\n};\n\n#endif\n"
  },
  {
    "path": "src/DesktopPlus/VRInput.cpp",
    "content": "#include \"VRInput.h\"\n#include \"VRInput.h\"\n\n#define NOMINMAX\n#include <string>\n#include <sstream>\n#include <windows.h>\n\n#include \"ConfigManager.h\"\n#include \"OutputManager.h\"\n#include \"OpenVRExt.h\"\n\nVRInput::VRInput() : m_HandleActionsetShortcuts(vr::k_ulInvalidActionSetHandle),\n                     m_HandleActionsetLaserPointer(vr::k_ulInvalidActionSetHandle),\n                     m_HandleActionsetScrollDiscrete(vr::k_ulInvalidActionSetHandle),\n                     m_HandleActionsetScrollSmooth(vr::k_ulInvalidActionSetHandle),\n                     m_HandleActionEnableGlobalLaserPointer(vr::k_ulInvalidActionHandle),\n                     m_HandleActionLaserPointerLeftClick(vr::k_ulInvalidActionHandle),\n                     m_HandleActionLaserPointerRightClick(vr::k_ulInvalidActionHandle),\n                     m_HandleActionLaserPointerMiddleClick(vr::k_ulInvalidActionHandle),\n                     m_HandleActionLaserPointerAux01Click(vr::k_ulInvalidActionHandle),\n                     m_HandleActionLaserPointerAux02Click(vr::k_ulInvalidActionHandle),\n                     m_HandleActionLaserPointerScrollDiscrete(vr::k_ulInvalidActionHandle),\n                     m_HandleActionLaserPointerScrollSmooth(vr::k_ulInvalidActionHandle),\n                     m_HandleActionLaserPointerHaptic(vr::k_ulInvalidActionHandle),\n                     m_IsAnyGlobalActionBound(false),\n                     m_IsAnyGlobalActionBoundStateValid(false),\n                     m_IsLaserPointerInputActive(false),\n                     m_LaserPointerScrollMode(vrinput_scroll_none),\n                     m_KeyboardDeviceInputValueHandle(vr::k_ulInvalidInputValueHandle),\n                     m_GamepadDeviceInputValueHandle(vr::k_ulInvalidInputValueHandle),\n                     m_KeyboardDeviceToggleState{0},\n                     m_KeyboardDeviceIsToggleKeyDown(false),\n                     m_KeyboardDeviceClickState{0},\n                     m_KeyboardDeviceDragState{0}\n{\n}\n\nvoid VRInput::UpdateKeyboardDeviceState()\n{\n    auto update_input_data = [](vr::InputDigitalActionData_t& input_data, int keycode)\n    {\n        if (keycode != 0)\n        {\n            if ((ConfigManager::GetValue(configid_bool_input_laser_pointer_hmd_device)) && (!vr::IVROverlayEx::IsSystemLaserPointerActive()))\n            {\n                input_data.bActive = true;\n                input_data.bChanged = false;\n\n                if (::GetAsyncKeyState(keycode) < 0)\n                {\n                    if (!input_data.bState)\n                    {\n                        input_data.bChanged = true;\n                        input_data.bState = true;\n                    }\n                }\n                else if (input_data.bState)\n                {\n                    input_data.bChanged = true;\n                    input_data.bState = false;\n                }\n            }\n            else\n            {\n                //Drop inputs if settings disabled or system laser pointer is active\n                input_data.bActive  = input_data.bState;    //true for one frame before we set it inactive\n                input_data.bChanged = input_data.bState;\n                input_data.bState   = false;\n            }\n        }\n        else\n        {\n            input_data.bActive  = false;\n            input_data.bChanged = false;\n            input_data.bState   = false;\n        }\n    };\n\n    auto update_input_data_toggle = [](vr::InputDigitalActionData_t& input_data, int keycode, bool& is_key_down)\n    {\n        if (keycode != 0)\n        {\n            if ((ConfigManager::GetValue(configid_bool_input_laser_pointer_hmd_device)) && (!vr::IVROverlayEx::IsSystemLaserPointerActive()))\n            {\n                input_data.bActive  = true;\n                input_data.bChanged = false;\n\n                if (::GetAsyncKeyState(keycode) < 0)\n                {\n                    if (!is_key_down)\n                    {\n                        input_data.bChanged = true;\n                        input_data.bState   = !input_data.bState;\n                    }\n\n                    is_key_down = true;\n                }\n                else\n                {\n                    is_key_down = false;\n                }\n            }\n            else\n            {\n                //Drop inputs if settings disabled or system laser pointer is active (Desktop+ pointer won't be doing anything either way, but the toggle key should reset at least)\n                input_data.bActive  = input_data.bState;    //true for one frame before we set it inactive\n                input_data.bChanged = input_data.bState;\n                input_data.bState   = false;\n            }\n        }\n        else\n        {\n            input_data.bActive  = false;\n            input_data.bChanged = false;\n            input_data.bState   = false;\n        }\n    };\n\n    //Toggle action state is always set up as a toggle binding\n    update_input_data_toggle(m_KeyboardDeviceToggleState, ConfigManager::GetValue(configid_int_input_laser_pointer_hmd_device_keycode_toggle), m_KeyboardDeviceIsToggleKeyDown);\n\n    update_input_data(m_KeyboardDeviceClickState[0], ConfigManager::GetValue(configid_int_input_laser_pointer_hmd_device_keycode_left));\n    update_input_data(m_KeyboardDeviceClickState[1], ConfigManager::GetValue(configid_int_input_laser_pointer_hmd_device_keycode_right));\n    update_input_data(m_KeyboardDeviceClickState[2], ConfigManager::GetValue(configid_int_input_laser_pointer_hmd_device_keycode_middle));\n    //Aux01/02 are not configurable but fields exist for parity with the regular action data array (they can still be pressed via actions if really needed)\n\n    update_input_data(m_KeyboardDeviceDragState, ConfigManager::GetValue(configid_int_input_laser_pointer_hmd_device_keycode_drag));\n}\n\nvr::InputDigitalActionData_t VRInput::CombineDigitalActionData(vr::InputDigitalActionData_t data_a, vr::InputDigitalActionData_t data_b)\n{\n    //Trying to make sense of having multiple action data sources at once, with some bias towards data_a\n    vr::InputDigitalActionData_t data_out = {0};\n\n    data_out.bActive  = data_a.bActive  || data_b.bActive;\n    data_out.bChanged = data_a.bChanged || data_b.bChanged;\n    data_out.bState   = data_a.bState   || data_b.bState;\n    data_out.activeOrigin = (data_a.bState == data_out.bState) ? data_a.activeOrigin : ((data_b.bState == data_out.bState) ? data_b.activeOrigin : data_a.activeOrigin);\n\n    return data_out;\n}\n\nbool VRInput::Init()\n{\n    //Load manifest, this will fail with VRInputError_MismatchedActionManifest when a Steam configured manifest is already associated with the app key, but we can just ignore that\n    vr::EVRInputError input_error = vr::VRInput()->SetActionManifestPath( (ConfigManager::Get().GetApplicationPath() + \"action_manifest.json\").c_str() );\n\n    if ( (input_error == vr::VRInputError_None) || (input_error == vr::VRInputError_MismatchedActionManifest) )\n    {\n        input_error = vr::VRInput()->GetActionSetHandle(\"/actions/shortcuts\", &m_HandleActionsetShortcuts);\n\n        if (input_error != vr::VRInputError_None)\n            return false;\n\n        //Load actions (we assume that the files are not messed with and skip some error checking)\n        vr::VRInput()->GetActionHandle(\"/actions/shortcuts/in/EnableGlobalLaserPointer\", &m_HandleActionEnableGlobalLaserPointer);\n\n        //Load as many global shortcut input actions as we can find. Up to configid_int_input_global_shortcuts_max_count at least.\n        //This allows for extended amounts via end-user modification, though the Steam manifest takes priority if present\n        m_HandleActionDoGlobalShortcuts.clear();\n        const int shortcut_max = ConfigManager::GetValue(configid_int_input_global_shortcuts_max_count);\n        for (int i = 0; i < shortcut_max; ++i)\n        {\n            vr::VRActionHandle_t handle_global_shortcut = vr::k_ulInvalidActionHandle;\n\n            std::stringstream ss;\n            ss << \"/actions/shortcuts/in/GlobalShortcut\" << std::setfill('0') << std::setw(2) << i + 1;\n\n            vr::VRInput()->GetActionHandle(ss.str().c_str(), &handle_global_shortcut);\n\n            //We do check if we got a handle here, but as of writing this, GetActionHandle() will always return a valid handle, regardless of presence in the manifest.\n            if (handle_global_shortcut != vr::k_ulInvalidActionHandle)\n            {\n                m_HandleActionDoGlobalShortcuts.push_back(handle_global_shortcut);\n            }\n            else\n            {\n                break;\n            }\n        }\n\n        vr::VRInput()->GetActionSetHandle(\"/actions/laserpointer\",                &m_HandleActionsetLaserPointer);\n        vr::VRInput()->GetActionHandle(\"/actions/laserpointer/in/LeftClick\",      &m_HandleActionLaserPointerLeftClick);\n        vr::VRInput()->GetActionHandle(\"/actions/laserpointer/in/RightClick\" ,    &m_HandleActionLaserPointerRightClick);\n        vr::VRInput()->GetActionHandle(\"/actions/laserpointer/in/MiddleClick\",    &m_HandleActionLaserPointerMiddleClick);\n        vr::VRInput()->GetActionHandle(\"/actions/laserpointer/in/Aux01Click\",     &m_HandleActionLaserPointerAux01Click);\n        vr::VRInput()->GetActionHandle(\"/actions/laserpointer/in/Aux02Click\",     &m_HandleActionLaserPointerAux02Click);\n        vr::VRInput()->GetActionHandle(\"/actions/laserpointer/in/Drag\",           &m_HandleActionLaserPointerDrag);\n        vr::VRInput()->GetActionHandle(\"/actions/laserpointer/out/Haptic\",        &m_HandleActionLaserPointerHaptic);\n\n        vr::VRInput()->GetActionSetHandle(\"/actions/scroll_discrete\",                &m_HandleActionsetScrollDiscrete);\n        vr::VRInput()->GetActionHandle(\"/actions/scroll_discrete/in/ScrollDiscrete\", &m_HandleActionLaserPointerScrollDiscrete);\n\n        vr::VRInput()->GetActionSetHandle(\"/actions/scroll_smooth\",                &m_HandleActionsetScrollSmooth);\n        vr::VRInput()->GetActionHandle(\"/actions/scroll_smooth/in/ScrollSmooth\",   &m_HandleActionLaserPointerScrollSmooth);\n\n        //This mimics OpenXR device path pattern but isn't actually formally defined (and this is OpenVR anyhow)\n        vr::VRInput()->GetInputSourceHandle(\"/user/keyboard\", &m_KeyboardDeviceInputValueHandle);\n        //Frequently used gamepad device path\n        vr::VRInput()->GetInputSourceHandle(\"/user/gamepad\", &m_GamepadDeviceInputValueHandle);\n\n        m_KeyboardDeviceToggleState.activeOrigin = m_KeyboardDeviceInputValueHandle;\n        for (auto& input_data : m_KeyboardDeviceClickState)\n        {\n            input_data.activeOrigin = m_KeyboardDeviceInputValueHandle;\n        }\n        m_KeyboardDeviceDragState.activeOrigin = m_KeyboardDeviceInputValueHandle;\n\n        return true;\n    }\n\n    return false;\n}\n\nvoid VRInput::Update()\n{\n    if (m_HandleActionsetShortcuts == vr::k_ulInvalidActionSetHandle)\n        return;\n\n    vr::VRActiveActionSet_t actionset_desc[3] = { 0 };\n    int actionset_active_count = 1;\n    actionset_desc[0].ulActionSet = m_HandleActionsetShortcuts;\n    actionset_desc[0].nPriority = 100; //Arbitrary number, but probably higher than the scene application's... if that even matters\n\n    if (m_IsLaserPointerInputActive)\n    {\n        actionset_active_count = 2;\n\n        actionset_desc[1].ulActionSet = m_HandleActionsetLaserPointer;\n        //+2 when blocking since OVRAS uses vr::k_nActionSetOverlayGlobalPriorityMin + 1 as priority for global input\n        //When not blocking laser pointer inputs should have priority over global shortcuts\n        actionset_desc[1].nPriority = ConfigManager::GetValue(configid_bool_input_laser_pointer_block_input) ? vr::k_nActionSetOverlayGlobalPriorityMin + 2 : 101;\n\n        if (m_LaserPointerScrollMode != vrinput_scroll_none)\n        {\n            actionset_active_count = 3;\n\n            actionset_desc[2].ulActionSet = (m_LaserPointerScrollMode == vrinput_scroll_discrete) ? m_HandleActionsetScrollDiscrete : m_HandleActionsetScrollSmooth;\n            actionset_desc[2].nPriority = actionset_desc[1].nPriority;\n        }\n    }\n\n    vr::VRInput()->UpdateActionState(actionset_desc, sizeof(vr::VRActiveActionSet_t), actionset_active_count);\n    UpdateKeyboardDeviceState();\n\n    //SteamVR Input is incredibly weird with the initial action state. The first couple attempts at getting any action state will fail. Probably some async loading stuff\n    //However, SteamVR also does not send any events once the initial state goes valid (it does for binding state changes after this)\n    //As we don't want to needlessly refresh the any-action-bound state on every update, we poll it until it succeeds once and then rely on events afterwards\n    if (!m_IsAnyGlobalActionBoundStateValid)\n    {\n        RefreshAnyGlobalActionBound();\n    }\n}\n\nvoid VRInput::RefreshAnyGlobalActionBound()\n{\n    auto is_action_bound = [&](vr::VRActionHandle_t action_handle)\n    {\n        vr::VRInputValueHandle_t action_origin = vr::k_ulInvalidInputValueHandle;\n        vr::EVRInputError error = vr::VRInput()->GetActionOrigins(m_HandleActionsetShortcuts, action_handle, &action_origin, 1);\n\n        if (action_origin != vr::k_ulInvalidInputValueHandle) \n        { \n            m_IsAnyGlobalActionBoundStateValid = true;\n\n            vr::InputOriginInfo_t action_origin_info = {0};\n            vr::EVRInputError error = vr::VRInput()->GetOriginTrackedDeviceInfo(action_origin, &action_origin_info, sizeof(vr::InputOriginInfo_t));\n\n            if ( (error == vr::VRInputError_None) && (vr::VRSystem()->IsTrackedDeviceConnected(action_origin_info.trackedDeviceIndex)) )\n            {\n                return true;\n            }\n        }\n        \n        return false;\n    };\n\n    //Doesn't trigger on app start since its actions are not valid yet\n    m_IsAnyGlobalActionBound = false;\n\n    if (is_action_bound(m_HandleActionEnableGlobalLaserPointer))\n    {\n        m_IsAnyGlobalActionBound = true;\n        return;\n    }\n\n    for (auto handle : m_HandleActionDoGlobalShortcuts)\n    {\n        if (is_action_bound(handle))\n        {\n            m_IsAnyGlobalActionBound = true;\n            return;\n        }\n    }\n}\n\nvoid VRInput::HandleGlobalActionShortcuts(OutputManager& outmgr)\n{\n    const ActionManager::ActionList& shortcut_actions = ConfigManager::Get().GetGlobalShortcuts();\n    vr::InputDigitalActionData_t data;\n\n    size_t shortcut_id = 0;\n    for (const auto shortcut_handle : m_HandleActionDoGlobalShortcuts)\n    {\n        vr::EVRInputError input_error = vr::VRInput()->GetDigitalActionData(shortcut_handle, &data, sizeof(data), vr::k_ulInvalidInputValueHandle);\n\n        if ((shortcut_id < shortcut_actions.size()) && (input_error == vr::VRInputError_None) && (data.bChanged))\n        {\n            if (data.bState)\n            {\n                ConfigManager::Get().GetActionManager().StartAction(shortcut_actions[shortcut_id]);\n            }\n            else\n            {\n                ConfigManager::Get().GetActionManager().StopAction(shortcut_actions[shortcut_id]);\n            }\n        }\n\n        ++shortcut_id;\n    }\n}\n\nvoid VRInput::TriggerLaserPointerHaptics(vr::VRInputValueHandle_t restrict_to_device) const\n{\n    if (restrict_to_device == m_KeyboardDeviceInputValueHandle)\n        return;\n\n    //There have been problems with rumble getting stuck indefinitely when calling TriggerHapticVibrationAction() on a gamepad device (even though haptics aren't even bound)\n    //Unsure if this an isolated issue, we're just avoid calling this function on gamepads altogether... this could be a one-liner otherwise\n    if (restrict_to_device == vr::k_ulInvalidInputValueHandle)\n    {\n        //All devices, but we're going to exclude the gamepad one and trigger manually for the rest\n        //Asking for haptic action origin doesn't seem to work, but GetLaserPointerDevicesInfo() gets every device with left click bound, which is good enough\n        //This function is usually not called with this value either way\n        std::vector<vr::InputOriginInfo_t> lp_devices_info = GetLaserPointerDevicesInfo();\n\n        for (vr::InputOriginInfo_t device_info : lp_devices_info)\n        {\n            if (device_info.devicePath != m_GamepadDeviceInputValueHandle)\n            {\n                vr::VRInput()->TriggerHapticVibrationAction(m_HandleActionLaserPointerHaptic, 0.0f, 0.0f, 1.0f, 0.16f, device_info.devicePath);\n            }\n        }\n    }\n    else\n    {\n        //Don't trigger the vibration if this was called for the gamepad\n        vr::InputOriginInfo_t device_info = GetOriginTrackedDeviceInfoEx(restrict_to_device);\n\n        if (device_info.devicePath != m_GamepadDeviceInputValueHandle)\n        {\n            vr::VRInput()->TriggerHapticVibrationAction(m_HandleActionLaserPointerHaptic, 0.0f, 0.0f, 1.0f, 0.16f, restrict_to_device);\n        }\n    }\n}\n\nvr::InputOriginInfo_t VRInput::GetOriginTrackedDeviceInfoEx(vr::VRInputValueHandle_t origin) const\n{\n    vr::InputOriginInfo_t origin_info = {0};\n    origin_info.trackedDeviceIndex = vr::k_unTrackedDeviceIndexInvalid;\n\n    if (origin == m_KeyboardDeviceInputValueHandle)\n    {\n        origin_info.trackedDeviceIndex = vr::k_unTrackedDeviceIndex_Hmd;\n        origin_info.devicePath = origin;\n    }\n    else\n    {\n        vr::VRInput()->GetOriginTrackedDeviceInfo(origin, &origin_info, sizeof(vr::InputOriginInfo_t));\n    }\n\n    return origin_info;\n}\n\nvr::InputDigitalActionData_t VRInput::GetEnableGlobalLaserPointerState() const\n{\n    vr::InputDigitalActionData_t data;\n    vr::VRInput()->GetDigitalActionData(m_HandleActionEnableGlobalLaserPointer, &data, sizeof(data), vr::k_ulInvalidInputValueHandle);\n\n    if (ConfigManager::GetValue(configid_bool_input_laser_pointer_hmd_device))\n    {\n        data = CombineDigitalActionData(data, m_KeyboardDeviceToggleState);\n    }\n\n    return data;\n}\n\nstd::vector<vr::InputOriginInfo_t> VRInput::GetLaserPointerDevicesInfo() const\n{\n    std::vector<vr::InputOriginInfo_t> devices_info;\n    vr::VRInputValueHandle_t input_value_handles[vr::k_unMaxTrackedDeviceCount];\n    vr::EVRInputError err = vr::VRInput()->GetActionOrigins(m_HandleActionsetLaserPointer, m_HandleActionLaserPointerLeftClick, input_value_handles, vr::k_unMaxTrackedDeviceCount);\n\n    vr::InputOriginInfo_t origin_info = {0};\n    for (auto input_value_handle : input_value_handles)\n    {\n        if ( (input_value_handle != vr::k_ulInvalidInputValueHandle) && (vr::VRInput()->GetOriginTrackedDeviceInfo(input_value_handle, &origin_info, sizeof(vr::InputOriginInfo_t)) == vr::VRInputError_None) )\n        {\n            devices_info.push_back(origin_info);\n        }\n    }\n\n    //If GetActionOrigins() did not return anything useful, try at least getting origins for left and right hand controllers\n    if (devices_info.empty())\n    {\n        for (int controller_role = vr::TrackedControllerRole_LeftHand; controller_role <= vr::TrackedControllerRole_RightHand; ++controller_role)\n        {\n            origin_info = {0};\n\n            origin_info.trackedDeviceIndex = vr::VRSystem()->GetTrackedDeviceIndexForControllerRole((vr::ETrackedControllerRole)controller_role);\n\n            if (origin_info.trackedDeviceIndex != vr::k_unTrackedDeviceIndexInvalid)\n            {\n                vr::VRInputValueHandle_t input_value = vr::k_ulInvalidInputValueHandle;\n                vr::VRInput()->GetInputSourceHandle((controller_role == vr::TrackedControllerRole_LeftHand) ? \"/user/hand/left\" : \"/user/hand/right\", &origin_info.devicePath);\n\n                devices_info.push_back(origin_info);\n            }\n        }\n    }\n\n    if (ConfigManager::GetValue(configid_bool_input_laser_pointer_hmd_device))\n    {\n        vr::InputOriginInfo_t origin_info = {0};\n        origin_info.trackedDeviceIndex = vr::k_unTrackedDeviceIndex_Hmd;    //Simulated Keyboard device is used for HMD interaction only so we use that\n        origin_info.devicePath = m_KeyboardDeviceInputValueHandle;\n\n        devices_info.push_back(origin_info);\n    }\n\n    return devices_info;\n}\n\nvr::InputDigitalActionData_t VRInput::GetLaserPointerLeftClickState(vr::VRInputValueHandle_t restrict_to_device) const\n{\n    vr::InputDigitalActionData_t data = {0};\n    vr::VRInput()->GetDigitalActionData(m_HandleActionLaserPointerLeftClick, &data, sizeof(data), restrict_to_device);\n\n    if (ConfigManager::GetValue(configid_bool_input_laser_pointer_hmd_device))\n    {\n        if ((restrict_to_device == vr::k_ulInvalidInputValueHandle) || (restrict_to_device == m_KeyboardDeviceInputValueHandle))\n        {\n            data = CombineDigitalActionData(data, m_KeyboardDeviceClickState[0]);\n        }\n    }\n\n    return data;\n}\n\nstd::array<vr::InputDigitalActionData_t, 5> VRInput::GetLaserPointerClickState(vr::VRInputValueHandle_t restrict_to_device) const\n{\n    std::array<vr::InputDigitalActionData_t, 5> data = {0};\n\n    vr::VRInput()->GetDigitalActionData(m_HandleActionLaserPointerLeftClick,   &data[0], sizeof(vr::InputDigitalActionData_t), restrict_to_device);\n    vr::VRInput()->GetDigitalActionData(m_HandleActionLaserPointerRightClick,  &data[1], sizeof(vr::InputDigitalActionData_t), restrict_to_device);\n    vr::VRInput()->GetDigitalActionData(m_HandleActionLaserPointerMiddleClick, &data[2], sizeof(vr::InputDigitalActionData_t), restrict_to_device);\n    vr::VRInput()->GetDigitalActionData(m_HandleActionLaserPointerAux01Click,  &data[3], sizeof(vr::InputDigitalActionData_t), restrict_to_device);\n    vr::VRInput()->GetDigitalActionData(m_HandleActionLaserPointerAux02Click,  &data[4], sizeof(vr::InputDigitalActionData_t), restrict_to_device);\n\n    if (ConfigManager::GetValue(configid_bool_input_laser_pointer_hmd_device))\n    {\n        if ((restrict_to_device == vr::k_ulInvalidInputValueHandle) || (restrict_to_device == m_KeyboardDeviceInputValueHandle))\n        {\n            for (int i = 0; i < data.size(); ++i)\n            {\n                data[i] = CombineDigitalActionData(data[i], m_KeyboardDeviceClickState[i]);\n            }\n        }\n    }\n\n    return data;\n}\n\nvr::InputAnalogActionData_t VRInput::GetLaserPointerScrollDiscreteState() const\n{\n    vr::InputAnalogActionData_t data = {0};\n    vr::VRInput()->GetAnalogActionData(m_HandleActionLaserPointerScrollDiscrete, &data, sizeof(vr::InputAnalogActionData_t), vr::k_ulInvalidInputValueHandle);\n\n    return data;\n}\n\nvr::InputAnalogActionData_t VRInput::GetLaserPointerScrollSmoothState() const\n{\n    vr::InputAnalogActionData_t data = {0};\n    vr::VRInput()->GetAnalogActionData(m_HandleActionLaserPointerScrollSmooth, &data, sizeof(vr::InputAnalogActionData_t), vr::k_ulInvalidInputValueHandle);\n\n    return data;\n}\n\nvr::InputDigitalActionData_t VRInput::GetLaserPointerDragState(vr::VRInputValueHandle_t restrict_to_device) const\n{\n    vr::InputDigitalActionData_t data = {0};\n    vr::VRInput()->GetDigitalActionData(m_HandleActionLaserPointerDrag, &data, sizeof(data), restrict_to_device);\n\n    if (ConfigManager::GetValue(configid_bool_input_laser_pointer_hmd_device))\n    {\n        if ((restrict_to_device == vr::k_ulInvalidInputValueHandle) || (restrict_to_device == m_KeyboardDeviceInputValueHandle))\n        {\n            data = CombineDigitalActionData(data, m_KeyboardDeviceDragState);\n        }\n    }\n\n    return data;\n}\n\nvoid VRInput::SetLaserPointerActive(bool is_active)\n{\n    m_IsLaserPointerInputActive = is_active;\n}\n\nvoid VRInput::SetLaserPointerScrollMode(VRInputScrollMode scroll_mode)\n{\n    m_LaserPointerScrollMode = scroll_mode;\n}\n\nVRInputScrollMode VRInput::GetLaserPointerScrollMode() const\n{\n    return m_LaserPointerScrollMode;\n}\n\nbool VRInput::IsAnyGlobalActionBound() const\n{\n    return m_IsAnyGlobalActionBound;\n}\n\nvr::VRInputValueHandle_t VRInput::GetKeyboardDeviceInputValueHandle() const\n{\n    return m_KeyboardDeviceInputValueHandle;\n}\n"
  },
  {
    "path": "src/DesktopPlus/VRInput.h",
    "content": "#ifndef _VRINPUT_H_\n#define _VRINPUT_H_\n\n#include \"openvr.h\"\n\n#include <array>\n#include <vector>\n\nclass OutputManager;\n\n//Additional VRMouseButton values to get full state auxiliary click events\n//Normal implementations shouldn't have issues with these, but they're only sent to Desktop+ overlays anyways\n#define VRMouseButton_DP_Aux01 0x0008\n#define VRMouseButton_DP_Aux02 0x0010\n\nenum VRInputScrollMode\n{\n    vrinput_scroll_none,\n    vrinput_scroll_discrete,\n    vrinput_scroll_smooth,\n};\n\n//Can't be used with open dashboard, but handles global shortcuts and Desktop+ laser pointer input instead.\nclass VRInput\n{\n    private:\n        vr::VRActionSetHandle_t m_HandleActionsetShortcuts;\n        vr::VRActionSetHandle_t m_HandleActionsetLaserPointer;\n        vr::VRActionSetHandle_t m_HandleActionsetScrollDiscrete;\n        vr::VRActionSetHandle_t m_HandleActionsetScrollSmooth;\n\n        vr::VRActionHandle_t m_HandleActionEnableGlobalLaserPointer;\n        std::vector<vr::VRActionHandle_t> m_HandleActionDoGlobalShortcuts;\n\n        vr::VRActionHandle_t m_HandleActionLaserPointerLeftClick;\n        vr::VRActionHandle_t m_HandleActionLaserPointerRightClick;\n        vr::VRActionHandle_t m_HandleActionLaserPointerMiddleClick;\n        vr::VRActionHandle_t m_HandleActionLaserPointerAux01Click;\n        vr::VRActionHandle_t m_HandleActionLaserPointerAux02Click;\n        vr::VRActionHandle_t m_HandleActionLaserPointerDrag;\n        vr::VRActionHandle_t m_HandleActionLaserPointerScrollDiscrete;\n        vr::VRActionHandle_t m_HandleActionLaserPointerScrollSmooth;\n        vr::VRActionHandle_t m_HandleActionLaserPointerHaptic;\n\n        bool m_IsAnyGlobalActionBound;            //\"Bound\" meaning assigned and the device is actually active\n        bool m_IsAnyGlobalActionBoundStateValid;\n\n        bool m_IsLaserPointerInputActive;\n        VRInputScrollMode m_LaserPointerScrollMode;\n\n        vr::VRInputValueHandle_t m_KeyboardDeviceInputValueHandle;\n        vr::VRInputValueHandle_t m_GamepadDeviceInputValueHandle;\n        vr::InputDigitalActionData_t m_KeyboardDeviceToggleState;\n        bool m_KeyboardDeviceIsToggleKeyDown;\n        std::array<vr::InputDigitalActionData_t, 5> m_KeyboardDeviceClickState;\n        vr::InputDigitalActionData_t m_KeyboardDeviceDragState;\n\n        void UpdateKeyboardDeviceState();\n\n        static vr::InputDigitalActionData_t CombineDigitalActionData(vr::InputDigitalActionData_t data_a, vr::InputDigitalActionData_t data_b);\n\n    public:\n        VRInput();\n        bool Init();\n        void Update();\n        void RefreshAnyGlobalActionBound();\n        void HandleGlobalActionShortcuts(OutputManager& outmgr);\n        void TriggerLaserPointerHaptics(vr::VRInputValueHandle_t restrict_to_device = vr::k_ulInvalidInputValueHandle) const;\n        vr::InputOriginInfo_t GetOriginTrackedDeviceInfoEx(vr::VRInputValueHandle_t origin) const; //Wraps GetOriginTrackedDeviceInfo() with keyboard device support\n\n        vr::InputDigitalActionData_t GetEnableGlobalLaserPointerState() const;\n\n        std::vector<vr::InputOriginInfo_t> GetLaserPointerDevicesInfo() const;\n        vr::InputDigitalActionData_t GetLaserPointerLeftClickState(vr::VRInputValueHandle_t restrict_to_device = vr::k_ulInvalidInputValueHandle)  const;\n        std::array<vr::InputDigitalActionData_t, 5> GetLaserPointerClickState(vr::VRInputValueHandle_t restrict_to_device = vr::k_ulInvalidInputValueHandle)  const;\n        vr::InputAnalogActionData_t GetLaserPointerScrollDiscreteState() const;\n        vr::InputAnalogActionData_t GetLaserPointerScrollSmoothState()   const;\n        vr::InputDigitalActionData_t GetLaserPointerDragState(vr::VRInputValueHandle_t restrict_to_device = vr::k_ulInvalidInputValueHandle)  const;\n\n        void SetLaserPointerActive(bool is_active);\n        void SetLaserPointerScrollMode(VRInputScrollMode scroll_mode);\n        VRInputScrollMode GetLaserPointerScrollMode() const;\n\n        bool IsAnyGlobalActionBound() const;\n        vr::VRInputValueHandle_t GetKeyboardDeviceInputValueHandle() const;\n};\n\n#endif"
  },
  {
    "path": "src/DesktopPlus/VertexShader.hlsl",
    "content": "struct VS_INPUT\n{\n    float4 Pos : POSITION;\n    float2 Tex : TEXCOORD;\n};\n\nstruct VS_OUTPUT\n{\n    float4 Pos : SV_POSITION;\n    float2 Tex : TEXCOORD;\n};\n\n\n//--------------------------------------------------------------------------------------\n// Vertex Shader\n//--------------------------------------------------------------------------------------\nVS_OUTPUT VS(VS_INPUT input)\n{\n    return input;\n}"
  },
  {
    "path": "src/DesktopPlus/resource.h",
    "content": "//{{NO_DEPENDENCIES}}\n// Microsoft Visual C++ generated include file.\n// Used by DesktopPlus.rc\n//\n#define IDI_ICON1                       101\n#define IDI_DPLUS                       101\n\n// Next default values for new objects\n// \n#ifdef APSTUDIO_INVOKED\n#ifndef APSTUDIO_READONLY_SYMBOLS\n#define _APS_NEXT_RESOURCE_VALUE        102\n#define _APS_NEXT_COMMAND_VALUE         40001\n#define _APS_NEXT_CONTROL_VALUE         1001\n#define _APS_NEXT_SYMED_VALUE           101\n#endif\n#endif\n"
  },
  {
    "path": "src/DesktopPlus.sln",
    "content": "﻿\nMicrosoft Visual Studio Solution File, Format Version 12.00\n# Visual Studio Version 16\nVisualStudioVersion = 16.0.29519.87\nMinimumVisualStudioVersion = 10.0.40219.1\nProject(\"{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}\") = \"DesktopPlus\", \"DesktopPlus\\DesktopPlus.vcxproj\", \"{05050918-71E9-AF87-0B3C-6F34D471A55A}\"\n\tProjectSection(ProjectDependencies) = postProject\n\t\t{045FFB0E-D0D4-404D-8C33-13C7074B3236} = {045FFB0E-D0D4-404D-8C33-13C7074B3236}\n\tEndProjectSection\nEndProject\nProject(\"{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}\") = \"DesktopPlusUI\", \"DesktopPlusUI\\DesktopPlusUI.vcxproj\", \"{14405CEC-DF3D-435E-8F11-B79BAC56D7D8}\"\n\tProjectSection(ProjectDependencies) = postProject\n\t\t{045FFB0E-D0D4-404D-8C33-13C7074B3236} = {045FFB0E-D0D4-404D-8C33-13C7074B3236}\n\tEndProjectSection\nEndProject\nProject(\"{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}\") = \"DesktopPlusWinRT\", \"DesktopPlusWinRT\\DesktopPlusWinRT.vcxproj\", \"{045FFB0E-D0D4-404D-8C33-13C7074B3236}\"\nEndProject\nGlobal\n\tGlobalSection(SolutionConfigurationPlatforms) = preSolution\n\t\tDebug|x64 = Debug|x64\n\t\tRelease|x64 = Release|x64\n\tEndGlobalSection\n\tGlobalSection(ProjectConfigurationPlatforms) = postSolution\n\t\t{05050918-71E9-AF87-0B3C-6F34D471A55A}.Debug|x64.ActiveCfg = Debug|x64\n\t\t{05050918-71E9-AF87-0B3C-6F34D471A55A}.Debug|x64.Build.0 = Debug|x64\n\t\t{05050918-71E9-AF87-0B3C-6F34D471A55A}.Release|x64.ActiveCfg = Release|x64\n\t\t{05050918-71E9-AF87-0B3C-6F34D471A55A}.Release|x64.Build.0 = Release|x64\n\t\t{14405CEC-DF3D-435E-8F11-B79BAC56D7D8}.Debug|x64.ActiveCfg = Debug|x64\n\t\t{14405CEC-DF3D-435E-8F11-B79BAC56D7D8}.Debug|x64.Build.0 = Debug|x64\n\t\t{14405CEC-DF3D-435E-8F11-B79BAC56D7D8}.Release|x64.ActiveCfg = Release|x64\n\t\t{14405CEC-DF3D-435E-8F11-B79BAC56D7D8}.Release|x64.Build.0 = Release|x64\n\t\t{045FFB0E-D0D4-404D-8C33-13C7074B3236}.Debug|x64.ActiveCfg = Debug|x64\n\t\t{045FFB0E-D0D4-404D-8C33-13C7074B3236}.Debug|x64.Build.0 = Debug|x64\n\t\t{045FFB0E-D0D4-404D-8C33-13C7074B3236}.Release|x64.ActiveCfg = Release|x64\n\t\t{045FFB0E-D0D4-404D-8C33-13C7074B3236}.Release|x64.Build.0 = Release|x64\n\tEndGlobalSection\n\tGlobalSection(SolutionProperties) = preSolution\n\t\tHideSolutionNode = FALSE\n\tEndGlobalSection\n\tGlobalSection(ExtensibilityGlobals) = postSolution\n\t\tSolutionGuid = {6AC6EA93-8452-42F0-A7BF-B88BDB800DB6}\n\tEndGlobalSection\nEndGlobal\n"
  },
  {
    "path": "src/DesktopPlusUI/AuxUI.cpp",
    "content": "#include \"AuxUI.h\"\n\n#include \"UIManager.h\"\n#include \"InterprocessMessaging.h\"\n#include \"WindowManager.h\"\n#include \"OpenVRExt.h\"\n\nAuxUIWindow::AuxUIWindow(AuxUIID ui_id) : m_AuxUIID(ui_id), m_Visible(false), m_Alpha(0.0f), m_IsTransitionFading(false), m_AutoSizeFrames(-1)\n{\n    //Nothing\n}\n\nbool AuxUIWindow::WindowUpdateBase()\n{\n    AuxUI& aux_ui = UIManager::Get()->GetAuxUI();\n\n    if (m_Visible)\n    {\n        if (aux_ui.GetActiveUI() != m_AuxUIID)\n        {\n            Hide();\n            return true;\n        }\n\n        //Wait for previous fade out to be done before continuing\n        if (aux_ui.IsUIFadingOut())\n            return false;\n\n        if ( (m_Alpha == 0.0f) && (m_AutoSizeFrames == -1) )\n        {\n            m_AutoSizeFrames = 2;\n            ApplyPendingValues();\n        }\n\n        float alpha_prev = m_Alpha;\n\n        //Alpha fade animation\n        if (m_AutoSizeFrames <= 0)\n        {\n            m_AutoSizeFrames = -1;\n\n            m_Alpha += ImGui::GetIO().DeltaTime * 7.5f;\n\n            if (m_Alpha > 1.0f)\n                m_Alpha = 1.0f;\n        }\n\n        //Set overlay alpha when not in desktop mode\n        if ( (!UIManager::Get()->IsInDesktopMode()) && (alpha_prev != m_Alpha) )\n        {\n            vr::VROverlay()->SetOverlayAlpha(UIManager::Get()->GetOverlayHandleAuxUI(), m_Alpha);\n        }\n    }\n    else if (m_Alpha != 0.0f)\n    {\n        float alpha_prev = m_Alpha;\n\n        //Alpha fade animation\n        m_Alpha -= ImGui::GetIO().DeltaTime * 7.5f;\n\n        if (m_Alpha < 0.0f)\n            m_Alpha = 0.0f;\n\n        //Set overlay alpha when not in desktop mode\n        if ( (!UIManager::Get()->IsInDesktopMode()) && (alpha_prev != m_Alpha) )\n        {\n            vr::VROverlay()->SetOverlayAlpha(UIManager::Get()->GetOverlayHandleAuxUI(), m_Alpha);\n        }\n\n        if (m_Alpha == 0.0f)\n        {\n            if (m_IsTransitionFading)\n            {\n                m_IsTransitionFading = false;\n                Show();\n            }\n            else\n            {\n                aux_ui.SetFadeOutFinished();\n                aux_ui.ClearActiveUI(m_AuxUIID);\n            }\n\n            if (!UIManager::Get()->IsInDesktopMode())\n                vr::VROverlay()->HideOverlay(UIManager::Get()->GetOverlayHandleAuxUI());\n        }\n    }\n    else\n    {\n        return false;\n    }\n\n    return true;\n}\n\nvoid AuxUIWindow::DrawFullDimmedRectBehindWindow()\n{\n    //Based on ImGui::RenderDimmedBackgrounds()\n    ImDrawList* draw_list = ImGui::GetWindowDrawList();\n    if (draw_list->CmdBuffer.Size == 0)\n        draw_list->AddDrawCmd();\n\n    draw_list->PushClipRect({0.0f, 0.0f},  ImGui::GetIO().DisplaySize, false); //ImGui FIXME: Need to stricty ensure ImDrawCmd are not merged (ElemCount==6 checks below will verify that)\n    draw_list->AddRectFilled({0.0f, 0.0f}, ImGui::GetIO().DisplaySize, ImGui::ColorConvertFloat4ToU32({0.0f, 0.0f, 0.0f, m_Alpha * 0.5f}));\n    ImDrawCmd cmd = draw_list->CmdBuffer.back();\n    //IM_ASSERT(cmd.ElemCount == 6);    //This seems to block in the way we use this function, but also appears fine without for now?\n    draw_list->CmdBuffer.pop_back();\n    draw_list->CmdBuffer.push_front(cmd);\n    draw_list->AddDrawCmd(); //ImGui: We need to create a command as CmdBuffer.back().IdxOffset won't be correct if we append to same command.\n    draw_list->PopClipRect();\n}\n\nvoid AuxUIWindow::SetUpTextureBounds()\n{\n    //Set overlay texture bounds based on m_Pos and m_Size (with padding), clamped to available texture space\n    vr::VRTextureBounds_t bounds = {};\n\n    const DPRect& rect_total = UITextureSpaces::Get().GetRect(ui_texspace_total);\n    float tex_width  = (float)rect_total.GetWidth();\n    float tex_height = (float)rect_total.GetHeight();\n\n    const DPRect& rect_aux_ui = UITextureSpaces::Get().GetRect(ui_texspace_aux_ui);\n    ImVec2 pos_ovrl = {m_Pos.x - rect_aux_ui.GetTL().x, m_Pos.y - rect_aux_ui.GetTL().y};\n\n    bounds.uMin = clamp(int(m_Pos.x - 2),            rect_aux_ui.GetTL().x, rect_aux_ui.GetBR().x) / tex_width;\n    bounds.vMin = clamp(int(m_Pos.y - 2),            rect_aux_ui.GetTL().y, rect_aux_ui.GetBR().y) / tex_height;\n    bounds.uMax = clamp(int(m_Pos.x + m_Size.x + 2), rect_aux_ui.GetTL().x, rect_aux_ui.GetBR().x) / tex_width;\n    bounds.vMax = clamp(int(m_Pos.y + m_Size.y + 2), rect_aux_ui.GetTL().y, rect_aux_ui.GetBR().y) / tex_height;\n\n    vr::VROverlay()->SetOverlayTextureBounds(UIManager::Get()->GetOverlayHandleAuxUI(), &bounds);\n}\n\nvoid AuxUIWindow::StartTransitionFade()\n{\n    if (m_Alpha != 0.0f)\n    {\n        m_Visible = false;\n        m_IsTransitionFading = true;\n    }\n    else //Just skip transition if the window is already invisible\n    {\n        m_Visible = true;\n    }\n}\n\nvoid AuxUIWindow::ApplyPendingValues()\n{\n    //Does nothing by default\n}\n\nbool AuxUIWindow::Show()\n{\n    if (m_IsTransitionFading)\n        return true;\n\n    m_Visible = UIManager::Get()->GetAuxUI().SetActiveUI(m_AuxUIID);\n    UIManager::Get()->GetIdleState().AddActiveTime();\n\n    return m_Visible;\n}\n\nvoid AuxUIWindow::Hide()\n{\n    m_Visible = false;\n    m_IsTransitionFading = false;\n\n    //Clear right away if this window never had the chance to fade in\n    if (m_Alpha == 0.0f)\n    {\n        AuxUI& aux_ui = UIManager::Get()->GetAuxUI();\n        aux_ui.ClearActiveUI(m_AuxUIID);\n        aux_ui.SetFadeOutFinished();\n    }\n\n    UIManager::Get()->GetIdleState().AddActiveTime();\n}\n\nbool AuxUIWindow::IsVisible()\n{\n    return m_Visible;\n}\n\n\n//--WindowDragHint\nWindowDragHint::WindowDragHint() : AuxUIWindow(auxui_drag_hint), m_TargetDevice(vr::k_unTrackedDeviceIndexInvalid), m_HintType(hint_docking), m_HintTypePending(hint_docking)\n{\n    //Leave 2 pixel padding around so interpolation doesn't cut off the pixel border\n    const DPRect& rect = UITextureSpaces::Get().GetRect(ui_texspace_aux_ui);\n    m_Pos  = {float(rect.GetTL().x + 2), float(rect.GetTL().y + 2)};\n    m_Size = {-1.0f, -1.0f};\n}\n\nvoid WindowDragHint::SetUpOverlay()\n{\n    vr::VROverlayHandle_t overlay_handle = UIManager::Get()->GetOverlayHandleAuxUI();\n\n    const float overlay_width = OVERLAY_WIDTH_METERS_AUXUI_DRAG_HINT * (m_Size.x / 200.0f);   //Scale width based on window width for consistent sizing\n\n    vr::VROverlay()->SetOverlayWidthInMeters(overlay_handle, overlay_width);\n    vr::VROverlay()->SetOverlaySortOrder(overlay_handle, 100);\n    vr::VROverlay()->SetOverlayAlpha(overlay_handle, 0.0f);\n    vr::VROverlay()->SetOverlayInputMethod(overlay_handle, vr::VROverlayInputMethod_None);\n\n    SetUpTextureBounds();\n\n    vr::VROverlay()->ShowOverlay(overlay_handle);\n}\n\nvoid WindowDragHint::UpdateOverlayPos()\n{\n    //Check for never tracked device and show for HMD instead then\n    const bool is_device_never_tracked = vr::VRSystem()->GetBoolTrackedDeviceProperty(m_TargetDevice, vr::Prop_NeverTracked_Bool);\n    vr::TrackedDeviceIndex_t device_index = (is_device_never_tracked) ? vr::k_unTrackedDeviceIndex_Hmd : m_TargetDevice;\n\n    Matrix4 mat;\n\n    if (device_index == vr::k_unTrackedDeviceIndex_Hmd) //Show in front of HMD\n    {\n        mat.scale(2.0f);                            //Scale on the transform itself for simplicity\n        mat.translate_relative(0.0f, 0.0f, -0.5f);  //Means this is -1m though\n    }\n    else //Show on controller\n    {\n        //Initial offset (somewhat centered inside controller model, for Index at least)\n        mat.translate_relative(0.0f, 0.0f, 0.10f);\n        Vector3 pos = mat.getTranslation();\n\n        //Get poses\n        vr::TrackedDevicePose_t poses[vr::k_unMaxTrackedDeviceCount];\n        vr::VRSystem()->GetDeviceToAbsoluteTrackingPose(vr::TrackingUniverseStanding, vr::IVRSystemEx::GetTimeNowToPhotons(), poses, vr::k_unMaxTrackedDeviceCount);\n\n        if ( (poses[vr::k_unTrackedDeviceIndex_Hmd].bPoseIsValid) && (device_index < vr::k_unMaxTrackedDeviceCount)  && (poses[device_index].bPoseIsValid) )\n        {\n            //Rotate towards HMD position\n            Matrix4 mat_hmd(poses[vr::k_unTrackedDeviceIndex_Hmd].mDeviceToAbsoluteTracking);\n            Matrix4 mat_controller(poses[device_index].mDeviceToAbsoluteTracking);\n            mat = mat_controller;\n\n            vr::IVRSystemEx::TransformLookAt(mat, mat_hmd.getTranslation());\n\n            //Apply rotation difference between controller used as origin and lookat angle\n            mat.setTranslation({0.0f, 0.0f, 0.0f});\n            mat_controller.setTranslation({0.0f, 0.0f, 0.0f});\n            mat_controller.invert();\n            mat = mat_controller * mat;\n\n            //Restore position\n            mat.setTranslation(pos);\n        }\n\n        //Additional offset (move away from controller to avoid clipping, again value fits mostly for Index)\n        mat.translate_relative(0.0f, 0.0f, 0.10f);\n\n        //At least on the controller we need to keep active time to keep rotation updated\n        UIManager::Get()->GetIdleState().AddActiveTime();\n    }\n\n    //Set transform\n    vr::HmdMatrix34_t mat_ovr = mat.toOpenVR34();\n    vr::VROverlay()->SetOverlayTransformTrackedDeviceRelative(UIManager::Get()->GetOverlayHandleAuxUI(), device_index, &mat_ovr);\n}\n\nvoid WindowDragHint::ApplyPendingValues()\n{\n    m_HintType = m_HintTypePending;\n}\n\nvoid WindowDragHint::Update()\n{\n    bool render_window = WindowUpdateBase() || m_Size.x == -1.0f;\n\n    if (!render_window)\n        return;\n\n    const bool desktop_mode = UIManager::Get()->IsInDesktopMode();\n\n    ImGuiWindowFlags flags = ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_AlwaysAutoResize;\n\n    if (!m_Visible)\n        flags |= ImGuiWindowFlags_NoInputs;\n\n    //Center on screen in desktop mode\n    if (desktop_mode)\n    {\n        ImGui::PushStyleVar(ImGuiStyleVar_Alpha, m_Alpha);\n        ImGui::SetNextWindowPos({ImGui::GetIO().DisplaySize.x / 2.0f, ImGui::GetIO().DisplaySize.y / 2.0f}, ImGuiCond_Always, {0.5f, 0.5f});\n    }\n    else\n    {\n        ImGui::SetNextWindowPos(m_Pos, ImGuiCond_Always);\n    }\n\n    ImGui::Begin(\"WindowDragHint\", nullptr, flags);\n\n    switch (m_HintType)\n    {\n        case WindowDragHint::hint_docking:                     ImGui::TextUnformatted(TranslationManager::GetString(tstr_AuxUIDragHintDocking));                  break;\n        case WindowDragHint::hint_undocking:                   ImGui::TextUnformatted(TranslationManager::GetString(tstr_AuxUIDragHintUndocking));                break;\n        case WindowDragHint::hint_ovrl_locked:                 ImGui::TextUnformatted(TranslationManager::GetString(tstr_AuxUIDragHintOvrlLocked));               break;\n        case WindowDragHint::hint_ovrl_theater_screen_blocked: ImGui::TextUnformatted(TranslationManager::GetString(tstr_AuxUIDragHintOvrlTheaterScreenBlocked)); break;\n    }\n\n    if (desktop_mode)\n        DrawFullDimmedRectBehindWindow();\n\n    m_Size = ImGui::GetWindowSize();\n    ImGui::End();\n\n    if (desktop_mode)\n        ImGui::PopStyleVar();\n\n    if ((!desktop_mode) && (!m_IsTransitionFading))\n    {\n        UpdateOverlayPos();\n    }\n\n    if (m_AutoSizeFrames > 0)\n    {\n        m_AutoSizeFrames--;\n\n        if ((m_AutoSizeFrames == 0) && (!desktop_mode))\n        {\n            SetUpOverlay();\n        }\n    }\n}\n\nvoid WindowDragHint::SetHintType(vr::TrackedDeviceIndex_t device_index, WindowDragHint::HintType hint_type)\n{\n    if ((m_TargetDevice == device_index) && (m_HintType == hint_type))\n        return;\n\n    m_TargetDevice    = device_index;\n    m_HintTypePending = hint_type;    //Actual value set after transition\n\n    if (m_Visible)\n        StartTransitionFade();\n}\n\n\n//--WindowGazeFadeAutoHint\nWindowGazeFadeAutoHint::WindowGazeFadeAutoHint() : AuxUIWindow(auxui_gazefade_auto_hint), m_TargetOverlay(k_ulOverlayID_None), m_Countdown(3), m_TickTime(0.0)\n{\n    //Leave 2 pixel padding around so interpolation doesn't cut off the pixel border\n    const DPRect& rect = UITextureSpaces::Get().GetRect(ui_texspace_aux_ui);\n    m_Pos  = {float(rect.GetTL().x + 2), float(rect.GetTL().y + 2)};\n    m_Size = {-1.0f, -1.0f};\n}\n\nvoid WindowGazeFadeAutoHint::SetUpOverlay()\n{\n    vr::VROverlayHandle_t overlay_handle = UIManager::Get()->GetOverlayHandleAuxUI();\n\n    vr::VROverlay()->SetOverlayWidthInMeters(overlay_handle, OVERLAY_WIDTH_METERS_AUXUI_GAZEFADE_AUTO_HINT);\n    vr::VROverlay()->SetOverlaySortOrder(overlay_handle, 100);\n    vr::VROverlay()->SetOverlayAlpha(overlay_handle, 0.0f);\n    vr::VROverlay()->SetOverlayInputMethod(overlay_handle, vr::VROverlayInputMethod_None);\n\n    SetUpTextureBounds();\n\n    vr::VROverlay()->ShowOverlay(overlay_handle);\n}\n\nvoid WindowGazeFadeAutoHint::UpdateOverlayPos()\n{\n    //Initial offset\n    Matrix4 mat;\n    mat.translate_relative(0.0f, 0.0f, -1.00f);\n\n    //Set transform\n    vr::HmdMatrix34_t mat_ovr = mat.toOpenVR34();\n    vr::VROverlay()->SetOverlayTransformTrackedDeviceRelative(UIManager::Get()->GetOverlayHandleAuxUI(), vr::k_unTrackedDeviceIndex_Hmd, &mat_ovr);\n}\n\nbool WindowGazeFadeAutoHint::Show()\n{\n    m_Countdown = 3;\n    m_TickTime = 0.0;         //Triggers label update right away on Update()\n\n    UIManager::Get()->GetIdleState().AddActiveTime(5000);\n\n    //Deactivate GazeFade during the countdown so the overlay is visible to the user\n    IPCManager::Get().PostConfigMessageToDashboardApp(configid_int_state_overlay_current_id_override, (int)m_TargetOverlay);\n    IPCManager::Get().PostConfigMessageToDashboardApp(configid_bool_overlay_gazefade_enabled, false);\n    IPCManager::Get().PostConfigMessageToDashboardApp(configid_int_state_overlay_current_id_override, -1);\n\n    return AuxUIWindow::Show();\n}\n\nvoid WindowGazeFadeAutoHint::Hide()\n{\n    //Trigger GazeFade auto-configure\n    IPCManager::Get().PostConfigMessageToDashboardApp(configid_int_state_overlay_current_id_override, (int)m_TargetOverlay);\n    IPCManager::Get().PostMessageToDashboardApp(ipcmsg_action, ipcact_overlay_gaze_fade_auto);\n    IPCManager::Get().PostConfigMessageToDashboardApp(configid_bool_overlay_gazefade_enabled, true);\n    IPCManager::Get().PostConfigMessageToDashboardApp(configid_int_state_overlay_current_id_override, -1);\n\n    //Enable gaze fade if it isn't yet\n    OverlayManager::Get().GetConfigData(m_TargetOverlay).ConfigBool[configid_bool_overlay_gazefade_enabled] = true;\n\n    AuxUIWindow::Hide();\n}\n\nvoid WindowGazeFadeAutoHint::Update()\n{\n    bool render_window = WindowUpdateBase() || m_Size.x == -1.0f;\n\n    if (!render_window)\n        return;\n\n    if (m_TickTime + 1.0 < ImGui::GetTime())\n    {\n        if (m_Countdown == 0)\n        {\n            //Hide and keep the old label during fade-out\n            Hide();\n        }\n        else //Update label\n        {\n            m_Label = TranslationManager::GetString((m_Countdown != 1) ? tstr_AuxUIGazeFadeAutoHint : tstr_AuxUIGazeFadeAutoHintSingular);\n            StringReplaceAll(m_Label, \"%SECONDS%\", std::to_string(m_Countdown));\n\n            m_Countdown--;\n            m_TickTime = ImGui::GetTime();\n        }\n    }\n\n    const bool desktop_mode = UIManager::Get()->IsInDesktopMode();\n\n    ImGuiWindowFlags flags = ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_AlwaysAutoResize;\n\n    if (!m_Visible)\n        flags |= ImGuiWindowFlags_NoInputs;\n\n    //Center on screen in desktop mode\n    if (desktop_mode)\n    {\n        ImGui::PushStyleVar(ImGuiStyleVar_Alpha, m_Alpha);\n        ImGui::SetNextWindowPos({ImGui::GetIO().DisplaySize.x / 2.0f, ImGui::GetIO().DisplaySize.y / 2.0f}, ImGuiCond_Always, {0.5f, 0.5f});\n    }\n    else\n    {\n        ImGui::SetNextWindowPos(m_Pos, ImGuiCond_Always);\n    }\n    \n    ImGui::Begin(\"WindowGazeFadeAutoHint\", nullptr, flags);\n\n    ImGui::TextUnformatted(m_Label.c_str());\n\n    if (desktop_mode)\n        DrawFullDimmedRectBehindWindow();\n\n    m_Size = ImGui::GetWindowSize();\n    ImGui::End();\n\n    if (desktop_mode)\n        ImGui::PopStyleVar();\n\n    if ((!desktop_mode) && (!m_IsTransitionFading))\n    {\n        UpdateOverlayPos();\n    }\n\n    if (m_AutoSizeFrames > 0)\n    {\n        m_AutoSizeFrames--;\n\n        if ((m_AutoSizeFrames == 0) && (!desktop_mode))\n        {\n            SetUpOverlay();\n        }\n    }\n}\n\nvoid WindowGazeFadeAutoHint::SetTargetOverlay(unsigned int overlay_id)\n{\n    m_TargetOverlay = overlay_id;\n}\n\n\n//--WindowQuickStart\nWindowQuickStart::WindowQuickStart() : AuxUIWindow(auxui_window_quickstart), m_CurrentPage(0)\n{\n    //Leave 2 pixel padding around so interpolation doesn't cut off the pixel border\n    const DPRect& rect = UITextureSpaces::Get().GetRect(ui_texspace_aux_ui);\n    m_Pos  = {float(rect.GetTL().x + 2), float(rect.GetTL().y + 2)};\n    m_Size = {-1.0f, -1.0f};\n\n    m_Transform.zero();\n}\n\nvoid WindowQuickStart::SetUpOverlay()\n{\n    vr::VROverlayHandle_t overlay_handle = UIManager::Get()->GetOverlayHandleAuxUI();\n\n    vr::VROverlay()->SetOverlayWidthInMeters(overlay_handle, OVERLAY_WIDTH_METERS_AUXUI_WINDOW_QUICKSTART);\n    vr::VROverlay()->SetOverlaySortOrder(overlay_handle, 2);\n    vr::VROverlay()->SetOverlayAlpha(overlay_handle, 0.0f);\n    vr::VROverlay()->SetOverlayInputMethod(overlay_handle, vr::VROverlayInputMethod_Mouse);\n\n    SetUpTextureBounds();\n    UpdateOverlayPos();\n\n    vr::VROverlay()->ShowOverlay(overlay_handle);\n}\n\nvoid WindowQuickStart::UpdateOverlayPos()\n{\n    const float auxui_ovrl_width  = OVERLAY_WIDTH_METERS_AUXUI_WINDOW_QUICKSTART;\n    const float auxui_ovrl_height = OVERLAY_WIDTH_METERS_AUXUI_WINDOW_QUICKSTART * (m_Size.y / m_Size.x);\n\n    //Set initial transform to zoom in from the first time this is called while visible\n    if ((m_Visible) && (m_Transform.isZero()))\n    {\n        m_Transform = vr::IVRSystemEx::ComputeHMDFacingTransform(1.0);\n\n        Vector3 pos = m_Transform.getTranslation();\n        m_Transform.setTranslation({0.0f, 0.0f, 0.0f});\n        m_Transform.scale(0.0f);\n        m_Transform.setTranslation(pos);\n\n        return;\n    }\n\n    //Set target transform based on page (may update while animating)\n    switch (m_CurrentPage)\n    {\n        case pageid_Welcome:\n        {\n            m_TransformAnimationEnd = vr::IVRSystemEx::ComputeHMDFacingTransform(1.0f);\n            break;\n        }\n        case pageid_Overlays:\n        {\n            //Aim for top right side of overlay bar (doesn't obscure the overlay menu mentioned on the page)\n            const auto& window = UIManager::Get()->GetOverlayBarWindow();\n            const DPRect& rect_tex = UITextureSpaces::Get().GetRect(ui_texspace_overlay_bar);\n\n            //Default to top left of window relative to texspace (note that AuxUI overlay is centered on that spot)\n            Vector2 point_2d(window.GetPos().x - rect_tex.GetTL().x, window.GetPos().y - rect_tex.GetTL().y);\n            point_2d.x += window.GetSize().x;\n\n            m_TransformAnimationEnd = UIManager::Get()->GetOverlay2DPointTransform(point_2d, UIManager::Get()->GetOverlayHandleOverlayBar());\n            m_TransformAnimationEnd.translate_relative(0.0f, auxui_ovrl_height / 2.0f, 0.0f);\n\n            Vector3 pos = m_TransformAnimationEnd.getTranslation();\n            m_TransformAnimationEnd.setTranslation({0.0f, 0.0f, 0.0f});\n            m_TransformAnimationEnd.rotateY(-10.0f);\n            m_TransformAnimationEnd.setTranslation(pos);\n\n            m_TransformAnimationEnd.translate_relative(0.0f, 0.0f, 0.05f);\n            break;\n        }\n        case pageid_Overlays_2:\n        {\n            //Aim for top left side of overlay bar (doesn't obscure the add overlay menu mentioned on the page)\n            const auto& window = UIManager::Get()->GetOverlayBarWindow();\n            const DPRect& rect_tex = UITextureSpaces::Get().GetRect(ui_texspace_overlay_bar);\n\n            //Default to top left of window relative to texspace (note that AuxUI overlay is centered on that spot)\n            Vector2 point_2d(window.GetPos().x - rect_tex.GetTL().x, window.GetPos().y - rect_tex.GetTL().y);\n\n            m_TransformAnimationEnd = UIManager::Get()->GetOverlay2DPointTransform(point_2d, UIManager::Get()->GetOverlayHandleOverlayBar());\n            m_TransformAnimationEnd.translate_relative(0.0f, auxui_ovrl_height / 2.0f, 0.0f);\n\n            Vector3 pos = m_TransformAnimationEnd.getTranslation();\n            m_TransformAnimationEnd.setTranslation({0.0f, 0.0f, 0.0f});\n            m_TransformAnimationEnd.rotateY(12.5f);\n            m_TransformAnimationEnd.setTranslation(pos);\n\n            m_TransformAnimationEnd.translate_relative(0.0f, 0.0f, 0.05f);\n            break;\n        }\n        case pageid_OverlayProperties:\n        case pageid_OverlayProperties_2:\n        {\n            const auto& window = UIManager::Get()->GetOverlayPropertiesWindow();\n            const DPRect& rect_tex = UITextureSpaces::Get().GetRect(ui_texspace_overlay_properties);\n\n            //Top left of window relative to texspace\n            Vector2 point_2d(window.GetPos().x - rect_tex.GetTL().x, window.GetPos().y - rect_tex.GetTL().y);\n            point_2d.y += window.GetSize().y;\n\n            m_TransformAnimationEnd = UIManager::Get()->GetOverlay2DPointTransform(point_2d, window.GetOverlayHandle());\n            m_TransformAnimationEnd.translate_relative(auxui_ovrl_width * 1.52f, auxui_ovrl_height / 4.0f, 0.05f);\n\n            Vector3 pos = m_TransformAnimationEnd.getTranslation();\n            m_TransformAnimationEnd.setTranslation({0.0f, 0.0f, 0.0f});\n            m_TransformAnimationEnd.rotateY(-25.0f);\n            m_TransformAnimationEnd.setTranslation(pos);\n\n            m_TransformAnimationEnd.translate_relative(0.0f, 0.0f, 0.125f);\n            break;\n        }\n        case pageid_Settings:\n        case pageid_Profiles:\n        case pageid_Actions:\n        case pageid_Actions_2:\n        case pageid_OverlayTags:\n        case pageid_Settings_End:\n        {\n            const auto& window = UIManager::Get()->GetSettingsWindow();\n            const DPRect& rect_tex = UITextureSpaces::Get().GetRect(ui_texspace_settings);\n\n            //Top left of window relative to texspace\n            Vector2 point_2d(window.GetPos().x - rect_tex.GetTL().x, window.GetPos().y - rect_tex.GetTL().y);\n            point_2d.y += window.GetSize().y;\n\n            m_TransformAnimationEnd = UIManager::Get()->GetOverlay2DPointTransform(point_2d, UIManager::Get()->GetOverlayHandleSettings());\n            m_TransformAnimationEnd.translate_relative(auxui_ovrl_width * -1.02f, auxui_ovrl_height / 4.0f, 0.05f);\n\n            Vector3 pos = m_TransformAnimationEnd.getTranslation();\n            m_TransformAnimationEnd.setTranslation({0.0f, 0.0f, 0.0f});\n            m_TransformAnimationEnd.rotateY(25.0f);\n            m_TransformAnimationEnd.setTranslation(pos);\n\n            m_TransformAnimationEnd.translate_relative(0.0f, 0.0f, 0.125f);\n            break;\n        }\n        case pageid_FloatingUI:\n        {\n            const auto& window = UIManager::Get()->GetFloatingUI().GetActionBarWindow();\n            const DPRect& rect_tex = UITextureSpaces::Get().GetRect(ui_texspace_floating_ui);\n\n            //Top left of window relative to texspace\n            Vector2 point_2d(window.GetPos().x - rect_tex.GetTL().x, window.GetPos().y - rect_tex.GetTL().y);\n            point_2d.x += window.GetSize().x / 2.0f;\n\n            m_TransformAnimationEnd = UIManager::Get()->GetOverlay2DPointTransform(point_2d, UIManager::Get()->GetOverlayHandleFloatingUI());\n            m_TransformAnimationEnd.translate_relative(0.0f, (auxui_ovrl_height / 2.0f) + 0.05f, 0.0f);\n\n            m_TransformAnimationEnd.translate_relative(0.0f, 0.0f, 0.05f);\n            break;\n        }\n        case pageid_DesktopMode:\n        case pageid_ReadMe:\n        {\n            m_TransformAnimationEnd = vr::IVRSystemEx::ComputeHMDFacingTransform(1.0f);\n            break;\n        }\n    }\n\n    //Set start point if this animation is just starting\n    if (m_TransformAnimationProgress == 0.0f)\n    {\n        m_TransformAnimationStart = m_Transform;\n    }\n\n    //Smoothstep over each matrix component and set the result\n    float mat_array[16] = {};\n    for (int i = 0; i < 16; ++i)\n    {\n        mat_array[i] = smoothstep(m_TransformAnimationProgress, m_TransformAnimationStart.get()[i], m_TransformAnimationEnd.get()[i]);\n    }\n\n    m_Transform.set(mat_array);\n\n    //Progress animation step\n    const float time_step = ImGui::GetIO().DeltaTime * 3.0f;\n    m_TransformAnimationProgress += time_step;\n\n    if (m_TransformAnimationProgress > 1.0f)\n    {\n        m_TransformAnimationProgress = 1.0f;\n    }\n\n    //Set transform\n    vr::HmdMatrix34_t mat_ovr = m_Transform.toOpenVR34();\n    vr::VROverlay()->SetOverlayTransformAbsolute(UIManager::Get()->GetOverlayHandleAuxUI(), vr::TrackingUniverseStanding, &mat_ovr);\n}\n\nvoid WindowQuickStart::OnPageChange(int page_id)\n{\n    //Set window visibility and scrolls as they should be for the page\n    WindowSettings& window_settings = UIManager::Get()->GetSettingsWindow();\n    WindowOverlayProperties& window_overlay_properties = UIManager::Get()->GetOverlayPropertiesWindow();\n\n    switch (page_id)\n    {\n        case pageid_Welcome:\n        case pageid_Overlays:\n        case pageid_Overlays_2:\n        {\n            window_settings.Hide();\n            window_overlay_properties.Hide();\n            break;\n        }\n        case pageid_OverlayProperties:\n        case pageid_OverlayProperties_2:\n        {\n            window_settings.Hide();\n            window_overlay_properties.SetActiveOverlayID(0);\n            window_overlay_properties.Show();\n            break;\n        }\n        case pageid_Settings:\n        case pageid_Profiles:\n        {\n            window_settings.Show();\n            window_settings.QuickStartGuideGoToPage(wndsettings_page_main);\n            window_overlay_properties.Hide();\n            break;\n        }\n        case pageid_Actions:\n        {\n            window_settings.Show();\n            window_settings.QuickStartGuideGoToPage(wndsettings_page_actions);\n            window_overlay_properties.Hide();\n            break;\n        }\n        case pageid_Actions_2:\n        case pageid_OverlayTags:\n        {\n            window_settings.Show();\n            window_settings.QuickStartGuideGoToPage(wndsettings_page_actions_edit);\n            window_overlay_properties.Hide();\n            break;\n        }\n        case pageid_Settings_End:\n        {\n            window_settings.Show();\n            window_settings.QuickStartGuideGoToPage(wndsettings_page_main);\n            window_overlay_properties.Hide();\n            break;\n        }\n        case pageid_FloatingUI:\n        case pageid_DesktopMode:\n        case pageid_ReadMe:\n        {\n            window_settings.Hide();\n            window_overlay_properties.Hide();\n            break;\n        }\n    }\n\n    m_TransformAnimationProgress = 0.0f;\n    m_TimeoutTickStart = 0;\n}\n\nbool WindowQuickStart::Show()\n{\n    return AuxUIWindow::Show();\n}\n\nvoid WindowQuickStart::Hide()\n{\n    m_TimeoutTickStart = 0;\n    AuxUIWindow::Hide();\n}\n\nvoid WindowQuickStart::Update()\n{\n    if (!ConfigManager::GetValue(configid_bool_interface_quick_start_hidden))\n    {\n        //Temporarily hide when leaving dashboard tab\n        if (UIManager::Get()->IsOverlayBarOverlayVisible())\n        {\n            if (!m_Visible)\n            {\n                //We need to wait a bit or else we might get old overlay transform data when calling UpdateOverlayPos() after this\n                //Waiting two frames seems to be sufficient and isn't much of an issue\n                vr::VROverlay()->WaitFrameSync(100);\n                vr::VROverlay()->WaitFrameSync(100);\n\n                Show();\n                m_TransformAnimationProgress = 0.0f;\n            }\n        }\n        else if (m_Visible)\n        {\n            Hide();\n        }\n\n        //Hide automatically after a timeout if there doesn't seem to be any input device (using the dashboard without one is weird, but still)\n        if ((m_CurrentPage == 0) && (UIManager::Get()->IsOpenVRLoaded()))\n        {\n            const vr::TrackedDeviceIndex_t device_index = ConfigManager::Get().GetPrimaryLaserPointerDevice();\n            if ((device_index == vr::k_unTrackedDeviceIndexInvalid) || ((device_index == vr::k_unTrackedDeviceIndex_Hmd))) //HMD may or may not being able to click\n            {\n                //Don't do it if the overlay is being pointed at, however (potentially still no input possible, but user can point somewhere else then)\n                if (!ConfigManager::Get().IsLaserPointerTargetOverlay(UIManager::Get()->GetOverlayHandleAuxUI()))\n                {\n                    const unsigned int timeout_ms = (device_index == vr::k_unTrackedDeviceIndex_Hmd) ? 20000 : 10000;\n\n                    if (m_TimeoutTickStart == 0)\n                    {\n                        m_TimeoutTickStart = ::GetTickCount64();\n                    }\n                    else if (m_TimeoutTickStart + timeout_ms < ::GetTickCount64())\n                    {\n                        ConfigManager::SetValue(configid_bool_interface_quick_start_hidden, true);\n                        Hide();\n                    }\n                }\n            }\n        }\n    }\n\n    bool render_window = WindowUpdateBase() || m_Size.x == -1.0f;\n\n    if (!render_window)\n        return;\n\n    ImGuiWindowFlags flags = ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoTitleBar;\n\n    if (!m_Visible)\n        flags |= ImGuiWindowFlags_NoInputs;\n\n    ImGuiStyle& style = ImGui::GetStyle();\n    const DPRect& rect = UITextureSpaces::Get().GetRect(ui_texspace_aux_ui);\n\n    ImGui::SetNextWindowPos(m_Pos, ImGuiCond_Always);\n    ImGui::SetNextWindowSize({(float)rect.GetWidth() - 4.0f, (float)rect.GetHeight() * 0.85f});\n    ImGui::Begin(\"WindowQuickStart\", nullptr, flags);\n\n    const float page_width = m_Size.x - style.WindowBorderSize - style.WindowPadding.x - style.WindowPadding.x;\n\n    //Page animation\n    if (m_PageAnimationDir != 0)\n    {\n        //Use the averaged framerate value instead of delta time for the first animation step\n        //This is to smooth over increased frame deltas that can happen when a new page needs to do initial larger computations or save/load files\n        const float progress_step = (m_PageAnimationProgress == 0.0f) ? (1.0f / ImGui::GetIO().Framerate) * 3.0f : ImGui::GetIO().DeltaTime * 3.0f;\n        m_PageAnimationProgress += progress_step;\n\n        if (m_PageAnimationProgress >= 1.0f)\n        {\n            m_PageAnimationProgress   = 1.0f;\n            m_PageAnimationDir        = 0;\n            m_CurrentPageAnimationMax = m_CurrentPageAnimation;\n        }\n    }\n    else if (m_CurrentPageAnimation != m_CurrentPage) //Only start new animation if none is running\n    {\n        m_PageAnimationDir        = (m_CurrentPageAnimation < m_CurrentPage) ? -1 : 1;\n        m_CurrentPageAnimation    = m_CurrentPage;\n        m_PageAnimationStartPos   = m_PageAnimationOffset;\n        m_PageAnimationProgress   = 0.0f;\n        m_CurrentPageAnimationMax = std::max(m_CurrentPage, m_CurrentPageAnimationMax);\n    }\n\n    const float target_x = (page_width + style.ItemSpacing.x) * -m_CurrentPageAnimation;\n    m_PageAnimationOffset = smoothstep(m_PageAnimationProgress, m_PageAnimationStartPos, target_x);\n\n    //Set up page offset and clipping\n    ImGui::SetCursorPosX( (ImGui::GetCursorPosX() + m_PageAnimationOffset) );\n\n    const ImVec2 child_size = {page_width, ImGui::GetContentRegionAvail().y - ImGui::GetFrameHeightWithSpacing() - style.ItemSpacing.y};\n    const int active_page_count = m_CurrentPageAnimationMax + 1;\n    for (int i = 0; i < active_page_count; ++i)\n    {\n        //Disable items when the page isn't active\n        const bool is_inactive_page = (i != m_CurrentPage);\n\n        if (is_inactive_page)\n        {\n            ImGui::PushItemDisabledNoVisual();\n        }\n\n        ImGui::PushStyleColor(ImGuiCol_ChildBg, ImVec4(0.00f, 0.00f, 0.00f, 0.00f));\n\n        if ( (ImGui::BeginChild(ImGui::GetID((void*)(intptr_t)i), child_size, ImGuiChildFlags_NavFlattened)) )\n        {\n            ImGui::PopStyleColor(); //ImGuiCol_ChildBg\n\n            ImGui::PushTextWrapPos(ImGui::GetContentRegionAvail().x);\n\n            switch (i)\n            {\n                case pageid_Welcome:\n                {\n                    ImGui::TextColoredUnformatted(ImGui::GetStyleColorVec4(ImGuiCol_ButtonHovered), TranslationManager::GetString(tstr_AuxUIQuickStartWelcomeHeader));\n                    ImGui::Indent();\n                    ImGui::TextUnformatted(TranslationManager::GetString(tstr_AuxUIQuickStartWelcomeBody));\n                    break;\n                }\n                case pageid_Overlays:\n                {\n                    ImGui::TextColoredUnformatted(ImGui::GetStyleColorVec4(ImGuiCol_ButtonHovered), TranslationManager::GetString(tstr_AuxUIQuickStartOverlaysHeader));\n                    ImGui::Indent();\n                    ImGui::TextUnformatted(TranslationManager::GetString(tstr_AuxUIQuickStartOverlaysBody));\n                    break;\n                }\n                case pageid_Overlays_2:\n                {\n\n                    ImGui::TextColoredUnformatted(ImGui::GetStyleColorVec4(ImGuiCol_ButtonHovered), TranslationManager::GetString(tstr_AuxUIQuickStartOverlaysHeader));\n                    ImGui::Indent();\n                    ImGui::TextUnformatted(TranslationManager::GetString(tstr_AuxUIQuickStartOverlaysBody2));\n                    break;\n                }\n                case pageid_OverlayProperties:\n                {\n                    ImGui::TextColoredUnformatted(ImGui::GetStyleColorVec4(ImGuiCol_ButtonHovered), TranslationManager::GetString(tstr_AuxUIQuickStartOverlayPropertiesHeader));\n                    ImGui::Indent();\n                    ImGui::TextUnformatted(TranslationManager::GetString(tstr_AuxUIQuickStartOverlayPropertiesBody));\n                    break;\n                }\n                case pageid_OverlayProperties_2:\n                {\n                    ImGui::TextColoredUnformatted(ImGui::GetStyleColorVec4(ImGuiCol_ButtonHovered), TranslationManager::GetString(tstr_AuxUIQuickStartOverlayPropertiesHeader));\n                    ImGui::Indent();\n                    ImGui::TextUnformatted(TranslationManager::GetString(tstr_AuxUIQuickStartOverlayPropertiesBody2));\n                    break;\n                }\n                case pageid_Settings:\n                {\n                    ImGui::TextColoredUnformatted(ImGui::GetStyleColorVec4(ImGuiCol_ButtonHovered), TranslationManager::GetString(tstr_AuxUIQuickStartSettingsHeader));\n                    ImGui::Indent();\n                    ImGui::TextUnformatted(TranslationManager::GetString(tstr_AuxUIQuickStartSettingsBody));\n                    break;\n                }\n                case pageid_Profiles:\n                {\n                    ImGui::TextColoredUnformatted(ImGui::GetStyleColorVec4(ImGuiCol_ButtonHovered), TranslationManager::GetString(tstr_AuxUIQuickStartProfilesHeader));\n                    ImGui::Indent();\n                    ImGui::TextUnformatted(TranslationManager::GetString(tstr_AuxUIQuickStartProfilesBody));\n                    break;\n                }\n                case pageid_Actions:\n                {\n                    ImGui::TextColoredUnformatted(ImGui::GetStyleColorVec4(ImGuiCol_ButtonHovered), TranslationManager::GetString(tstr_AuxUIQuickStartActionsHeader));\n                    ImGui::Indent();\n                    ImGui::TextUnformatted(TranslationManager::GetString(tstr_AuxUIQuickStartActionsBody));\n                    break;\n                }\n                case pageid_Actions_2:\n                {\n                    ImGui::TextColoredUnformatted(ImGui::GetStyleColorVec4(ImGuiCol_ButtonHovered), TranslationManager::GetString(tstr_AuxUIQuickStartActionsHeader));\n                    ImGui::Indent();\n                    ImGui::TextUnformatted(TranslationManager::GetString(tstr_AuxUIQuickStartActionsBody2));\n                    break;\n                }\n                case pageid_OverlayTags:\n                {\n                    ImGui::TextColoredUnformatted(ImGui::GetStyleColorVec4(ImGuiCol_ButtonHovered), TranslationManager::GetString(tstr_AuxUIQuickStartOverlayTagsHeader));\n                    ImGui::Indent();\n                    ImGui::TextUnformatted(TranslationManager::GetString(tstr_AuxUIQuickStartOverlayTagsBody));\n                    break;\n                }\n                case pageid_Settings_End:\n                {\n                    ImGui::TextColoredUnformatted(ImGui::GetStyleColorVec4(ImGuiCol_ButtonHovered), TranslationManager::GetString(tstr_AuxUIQuickStartSettingsHeader));\n                    ImGui::Indent();\n                    ImGui::TextUnformatted(TranslationManager::GetString(tstr_AuxUIQuickStartSettingsEndBody));\n                    break;\n                }\n                case pageid_FloatingUI:\n                {\n                    ImGui::TextColoredUnformatted(ImGui::GetStyleColorVec4(ImGuiCol_ButtonHovered), TranslationManager::GetString(tstr_AuxUIQuickStartFloatingUIHeader));\n                    ImGui::Indent();\n                    ImGui::TextUnformatted(TranslationManager::GetString(tstr_AuxUIQuickStartFloatingUIBody));\n                    break;\n                }\n                case pageid_DesktopMode:\n                {\n                    ImGui::TextColoredUnformatted(ImGui::GetStyleColorVec4(ImGuiCol_ButtonHovered), TranslationManager::GetString(tstr_AuxUIQuickStartDesktopModeHeader));\n                    ImGui::Indent();\n                    ImGui::TextUnformatted(TranslationManager::GetString(tstr_AuxUIQuickStartDesktopModeBody));\n                    break;\n                }\n                case pageid_ReadMe:\n                {\n                    ImGui::TextColoredUnformatted(ImGui::GetStyleColorVec4(ImGuiCol_ButtonHovered), TranslationManager::GetString(tstr_AuxUIQuickStartEndHeader));\n                    ImGui::Indent();\n                    ImGui::TextUnformatted(TranslationManager::GetString(tstr_AuxUIQuickStartEndBody));\n                    break;\n                }\n            }\n\n            ImGui::Unindent();\n            ImGui::PopTextWrapPos();\n        }\n        else\n        {\n            ImGui::PopStyleColor(); //ImGuiCol_ChildBg\n        }\n\n        if (is_inactive_page)\n        {\n            ImGui::PopItemDisabledNoVisual();\n        }\n\n        ImGui::EndChild();\n\n        if (i + 1 < active_page_count)\n        {\n            ImGui::SameLine();\n        }\n    }\n\n    //Bottom buttons\n    ImGui::Separator();\n\n    int current_page_prev = m_CurrentPage;\n\n    if (current_page_prev == 0)\n        ImGui::PushItemDisabled();\n\n    if (ImGui::Button(TranslationManager::GetString(tstr_AuxUIQuickStartButtonPrev)))\n    {\n        m_CurrentPage--;\n        OnPageChange(m_CurrentPage);\n\n        UIManager::Get()->RepeatFrame();\n    }\n\n    if (current_page_prev == 0)\n        ImGui::PopItemDisabled();\n\n    ImGui::SameLine(0.0f, style.ItemInnerSpacing.x);\n\n    if (current_page_prev == pageid_MAX)\n        ImGui::PushItemDisabled();\n\n    if (ImGui::Button(TranslationManager::GetString(tstr_AuxUIQuickStartButtonNext))) \n    {\n        m_CurrentPage++;\n        OnPageChange(m_CurrentPage);\n\n        UIManager::Get()->RepeatFrame();\n    }\n\n    if (current_page_prev == pageid_MAX)\n        ImGui::PopItemDisabled();\n\n    ImGui::SameLine();\n\n    if (ImGui::Button(TranslationManager::GetString(tstr_AuxUIQuickStartButtonClose))) \n    {\n        ConfigManager::SetValue(configid_bool_interface_quick_start_hidden, true);\n        Hide();\n    }\n\n    m_Size = ImGui::GetWindowSize();\n    ImGui::End();\n\n    if ( (!UIManager::Get()->IsInDesktopMode()) && (m_Visible) && (!m_IsTransitionFading) && (m_TransformAnimationProgress != 1.0f) )\n    {\n        UpdateOverlayPos();\n    }\n\n    if (m_AutoSizeFrames > 0)\n    {\n        m_AutoSizeFrames--;\n\n        if ((m_AutoSizeFrames == 0) && (!UIManager::Get()->IsInDesktopMode()))\n        {\n            SetUpOverlay();\n        }\n    }\n}\n\nvoid WindowQuickStart::Reset()\n{\n    Hide();\n    ConfigManager::SetValue(configid_bool_interface_quick_start_hidden, false);\n    UIManager::Get()->GetSettingsWindow().QuickStartGuideGoToPage(wndsettings_page_main);\n    m_Transform.zero();\n\n    m_CurrentPage = 0;\n    OnPageChange(m_CurrentPage);\n\n    if (m_Alpha == 0.0f)\n    {\n        m_PageAnimationProgress = 1.0f;\n    }\n\n    UIManager::Get()->RepeatFrame();\n}\n\n\n//--WindowCaptureWindowSelect\nWindowCaptureWindowSelect::WindowCaptureWindowSelect() : AuxUIWindow(auxui_window_select), m_WindowLastClicked(nullptr), m_HoveredTickLast(0)\n{\n    //Leave 2 pixel padding around so interpolation doesn't cut off the pixel border\n    const DPRect& rect = UITextureSpaces::Get().GetRect(ui_texspace_aux_ui);\n    m_Size = {float(rect.GetWidth() - 4), float(rect.GetHeight() - 4)};\n    m_Pos  = {float(rect.GetTL().x  + 2), float(rect.GetTL().y   + 2)};\n    m_PosCenter = {(float)rect.GetCenter().x, (float)rect.GetCenter().y};\n}\n\nvoid WindowCaptureWindowSelect::SetUpOverlay()\n{\n    vr::VROverlayHandle_t overlay_handle = UIManager::Get()->GetOverlayHandleAuxUI();\n\n    vr::VROverlay()->SetOverlayWidthInMeters(overlay_handle, OVERLAY_WIDTH_METERS_AUXUI_WINDOW_SELECT);\n    vr::VROverlay()->SetOverlaySortOrder(overlay_handle, 2);\n    vr::VROverlay()->SetOverlayAlpha(overlay_handle, 0.0f);\n    vr::VROverlay()->SetOverlayInputMethod(overlay_handle, vr::VROverlayInputMethod_Mouse);\n\n    //Take transform set by Overlay Bar and offset it a bit forward\n    Matrix4 mat = m_Transform;\n    mat.translate_relative(0.0f, 0.0f, 0.1f);\n\n    vr::HmdMatrix34_t mat_ovr = mat.toOpenVR34();\n    vr::VROverlay()->SetOverlayTransformAbsolute(overlay_handle, vr::TrackingUniverseStanding, &mat_ovr);\n\n    SetUpTextureBounds();\n\n    vr::VROverlay()->ShowOverlay(overlay_handle);\n}\n\nvoid WindowCaptureWindowSelect::ApplyPendingValues()\n{\n    m_WindowLastClicked = nullptr;\n}\n\nbool WindowCaptureWindowSelect::Show()\n{\n    //Limit window height to perfectly fit 14 list entries (not done in constructor since we need ImGui context)\n    m_Size.y = std::min( ((ImGui::GetTextLineHeight() + ImGui::GetStyle().ItemSpacing.y) * 14.0f) + (ImGui::GetStyle().WindowPadding.y * 2.0f), m_Size.y);\n    m_HoveredTickLast = ::GetTickCount64();\n\n    return AuxUIWindow::Show();\n}\n\nvoid WindowCaptureWindowSelect::Update()\n{\n    bool render_window = WindowUpdateBase() || m_Size.x == -1.0f;\n\n    if (!render_window)\n        return;\n\n    ImGuiWindowFlags flags = ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_AlwaysAutoResize;\n\n    if (!m_Visible)\n        flags |= ImGuiWindowFlags_NoInputs;\n\n\n    ImGui::SetNextWindowSizeConstraints({4.0f, 4.0f}, m_Size);\n    ImGui::SetNextWindowPos(m_PosCenter, ImGuiCond_Always, {0.5f, 0.5f});\n    ImGui::SetNextWindowScroll({0.0f, -1.0f});                               //Prevent horizontal scrolling from happening on overflow\n    ImGui::Begin(\"WindowCaptureWindowSelect\", nullptr, flags);\n\n    //Hide if clicked outside or not hovered for 3 seconds\n    if (!ImGui::IsWindowHovered(ImGuiHoveredFlags_AllowWhenBlockedByActiveItem))\n    {\n        if ((ImGui::IsAnyMouseClicked()) || (m_HoveredTickLast + 3000 < ::GetTickCount64()))\n        {\n           Hide();\n        }\n    }\n    else\n    {\n        m_HoveredTickLast = ::GetTickCount64();\n    }\n\n    //Adjust selectable colors to avoid flicker when fading the window out after activating\n    ImGui::PushStyleColor(ImGuiCol_Header,       ImGui::GetStyleColorVec4(ImGuiCol_HeaderHovered));\n    ImGui::PushStyleColor(ImGuiCol_HeaderActive, ImGui::GetStyleColorVec4(ImGuiCol_HeaderHovered));\n\n    //List windows\n    ImVec2 img_size_line_height = {ImGui::GetTextLineHeight(), ImGui::GetTextLineHeight()};\n    ImVec2 img_size, img_uv_min, img_uv_max;\n    for (const auto& window_info : WindowManager::Get().WindowListGet())\n    {\n        ImGui::PushID(window_info.GetWindowHandle());\n        ImGui::Selectable(\"\", (window_info.GetWindowHandle() == m_WindowLastClicked));\n\n        if (ImGui::IsItemActivated())\n        {\n            if (UIManager::Get()->IsOpenVRLoaded())\n            {\n                vr::TrackedDeviceIndex_t device_index = ConfigManager::Get().GetPrimaryLaserPointerDevice();\n\n                //If no dashboard device, try finding one\n                if (device_index == vr::k_unTrackedDeviceIndexInvalid)\n                {\n                    device_index = vr::IVROverlayEx::FindPointerDeviceForOverlay(UIManager::Get()->GetOverlayHandleAuxUI());\n                }\n\n                //Try to get the pointer distance\n                float source_distance = 1.0f;\n                vr::VROverlayIntersectionResults_t results;\n\n                if (vr::IVROverlayEx::ComputeOverlayIntersectionForDevice(UIManager::Get()->GetOverlayHandleAuxUI(), device_index, vr::TrackingUniverseStanding, &results))\n                {\n                    source_distance = results.fDistance;\n                }\n\n                //Set pointer hint in case dashboard app needs it\n                ConfigManager::SetValue(configid_int_state_laser_pointer_device_hint, (int)device_index);\n                IPCManager::Get().PostConfigMessageToDashboardApp(configid_int_state_laser_pointer_device_hint, (int)device_index);\n\n                //Add overlay\n                HWND window_handle = window_info.GetWindowHandle();\n                OverlayManager::Get().AddOverlay(ovrl_capsource_winrt_capture, -2, window_handle);\n\n                //Send to dashboard app\n                IPCManager::Get().PostConfigMessageToDashboardApp(configid_handle_state_arg_hwnd, (LPARAM)window_handle);\n                IPCManager::Get().PostMessageToDashboardApp(ipcmsg_action, ipcact_overlay_new_drag, MAKELPARAM(-2, (source_distance * 100.0f)));\n            }\n\n            //Store last clicked window to avoid Selectable flicker on fade out\n            m_WindowLastClicked = window_info.GetWindowHandle();\n\n            //We're done here, hide this window\n            Hide();\n        }\n\n        ImGui::SameLine(0.0f, 0.0f);\n\n        //Window icon and title\n        int icon_id = TextureManager::Get().GetWindowIconCacheID(window_info.GetIcon());\n\n        if (icon_id != -1)\n        {\n            TextureManager::Get().GetWindowIconTextureInfo(icon_id, img_size, img_uv_min, img_uv_max);\n            ImGui::Image(ImGui::GetIO().Fonts->TexID, img_size_line_height, img_uv_min, img_uv_max);\n\n            ImGui::SameLine(0.0f, ImGui::GetStyle().ItemInnerSpacing.x);\n        }\n\n        ImGui::TextUnformatted(window_info.GetListTitle().c_str());\n\n        ImGui::PopID();\n    }\n\n    ImGui::PopStyleColor();\n    ImGui::PopStyleColor();\n\n    ImGui::End();\n\n    //Handle auto-size frames\n    if (m_AutoSizeFrames > 0)\n    {\n        m_AutoSizeFrames--;\n\n        if ((m_AutoSizeFrames == 0) && (!UIManager::Get()->IsInDesktopMode()))\n        {\n            SetUpOverlay();\n        }\n    }\n}\n\nvoid WindowCaptureWindowSelect::SetTransform(Matrix4 transform)\n{\n    m_Transform = transform;\n\n    if (m_Visible)\n    {\n        StartTransitionFade();\n    }\n}\n\n\n//--AuxUI\nAuxUI::AuxUI() : m_ActiveUIID(auxui_none), m_IsUIFadingOut(false)\n{\n    //Nothing\n}\n\nvoid AuxUI::Update()\n{\n    //All Aux UI windows are updated even when not active\n    m_WindowDragHint.Update();\n    m_WindowGazeFadeAutoHint.Update();\n    m_WindowCaptureWindowSelect.Update();\n    m_WindowQuickStart.Update();\n}\n\nbool AuxUI::IsActive() const\n{\n    return (m_ActiveUIID != auxui_none);\n}\n\nvoid AuxUI::HideTemporaryWindows()\n{\n    switch (m_ActiveUIID)\n    {\n        case auxui_window_select:\n        {\n            ClearActiveUI(m_ActiveUIID);\n            break;\n        }\n        default: break;\n    }\n}\n\nbool AuxUI::SetActiveUI(AuxUIID new_ui_id)\n{\n    if (new_ui_id >= m_ActiveUIID)\n    {\n        if ( (m_ActiveUIID != auxui_none) && (new_ui_id != m_ActiveUIID) )\n        {\n            m_IsUIFadingOut = true;\n        }\n\n        m_ActiveUIID = new_ui_id;\n\n        return true;\n    }\n\n    return false;\n}\n\nAuxUIID AuxUI::GetActiveUI() const\n{\n    return m_ActiveUIID;\n}\n\nvoid AuxUI::ClearActiveUI(AuxUIID current_ui_id)\n{\n    if (m_ActiveUIID == current_ui_id)\n    {\n        m_ActiveUIID = auxui_none;\n    }\n}\n\nbool AuxUI::IsUIFadingOut() const\n{\n    return m_IsUIFadingOut;\n}\n\nvoid AuxUI::SetFadeOutFinished()\n{\n    m_IsUIFadingOut = false;\n}\n\nWindowDragHint& AuxUI::GetDragHintWindow()\n{\n    return m_WindowDragHint;\n}\n\nWindowGazeFadeAutoHint& AuxUI::GetGazeFadeAutoHintWindow()\n{\n    return m_WindowGazeFadeAutoHint;\n}\n\nWindowCaptureWindowSelect& AuxUI::GetCaptureWindowSelectWindow()\n{\n    return m_WindowCaptureWindowSelect;\n}\n\nWindowQuickStart& AuxUI::GetQuickStartWindow()\n{\n    return m_WindowQuickStart;\n}\n"
  },
  {
    "path": "src/DesktopPlusUI/AuxUI.h",
    "content": "#pragma once\n\n#define NOMINMAX\n#include <windows.h>\n\n#include \"imgui.h\"\n#include \"openvr.h\"\n#include \"Matrices.h\"\n\n//Hosts and manages UI that is displayed on the DesktopPlusUIAux overlay\n//Generally rarely and not parallel displayed things go here\n\n//Order of UI ID also determines priority. A higher priority UI can force an active lower one to stop displaying\nenum AuxUIID\n{\n    auxui_none,\n    auxui_window_quickstart,\n    auxui_drag_hint,\n    auxui_gazefade_auto_hint,\n    auxui_window_select\n};\n\nclass AuxUIWindow\n{\n    protected:\n        const AuxUIID m_AuxUIID;\n        ImVec2 m_Pos;                          //Used for overlay texture bounds, always top-left pivot, should already be offset for 2x2 pixel padding\n        ImVec2 m_Size;                         //Used for overlay texture bounds, read as max size for ImGui window when using auto-sizing\n\n        bool m_Visible;\n        float m_Alpha;\n        bool m_IsTransitionFading;             //If true, AuxUIWindow does not clear the active UI but instead shows the window again to transition to a different position with a fade\n        int m_AutoSizeFrames;                  //Overlay setup may need to be delayed when the window uses auto-sizing. In that case m_Alpha stays 0.0f while m_AutoSizeFrames is not 0\n\n        bool WindowUpdateBase();               //Handles alpha fading according to AuxUI state, but doesn't Begin() a window like FloatingWindow would. Returns true if window should be rendered\n        void DrawFullDimmedRectBehindWindow(); //Call before End() to draw a dimmed rect behind the window in desktop mode. Uses m_Alpha for fading. Messes with draw list, may break\n\n        virtual void SetUpTextureBounds();\n        virtual void StartTransitionFade();\n        virtual void ApplyPendingValues();     //Pending values when switching between states from a transition fade should be applied here, if any\n\n    public:\n        AuxUIWindow(AuxUIID ui_id);\n\n        virtual bool Show();\n        virtual void Hide();\n        bool IsVisible();\n};\n\nclass WindowDragHint : public AuxUIWindow\n{\n    public:\n        enum HintType\n        {\n            hint_none,\n            hint_docking,\n            hint_undocking,\n            hint_ovrl_locked,\n            hint_ovrl_theater_screen_blocked\n        };\n\n    private:\n        vr::TrackedDeviceIndex_t m_TargetDevice;\n        WindowDragHint::HintType m_HintType;\n        WindowDragHint::HintType m_HintTypePending;\n\n        void SetUpOverlay();\n        void UpdateOverlayPos();\n        virtual void ApplyPendingValues();\n\n    public:\n        WindowDragHint();\n\n        void Update();\n        void SetHintType(vr::TrackedDeviceIndex_t device_index, WindowDragHint::HintType hint_type);\n};\n\nclass WindowGazeFadeAutoHint : public AuxUIWindow\n{\n    private:\n        unsigned int m_TargetOverlay;\n        int m_Countdown;\n        double m_TickTime;\n        std::string m_Label;\n\n        void SetUpOverlay();\n        void UpdateOverlayPos();\n\n    public:\n        WindowGazeFadeAutoHint();\n\n        virtual bool Show();\n        virtual void Hide();\n        void Update();\n        void SetTargetOverlay(unsigned int overlay_id);\n};\n\nclass WindowQuickStart : public AuxUIWindow\n{\n    private:\n        enum PageID : int\n        {\n            pageid_Welcome,\n            pageid_Overlays,\n            pageid_Overlays_2,\n            pageid_OverlayProperties,\n            pageid_OverlayProperties_2,\n            pageid_Settings,\n            pageid_Profiles,\n            pageid_Actions,\n            pageid_Actions_2,\n            pageid_OverlayTags,\n            pageid_Settings_End,\n            pageid_FloatingUI,\n            pageid_DesktopMode,\n            pageid_ReadMe,\n            pageid_MAX = pageid_ReadMe,\n        };\n\n        int m_CurrentPage             = 0;\n        int m_CurrentPageAnimation    = 0;\n        int m_CurrentPageAnimationMax = 0;\n\n        int m_PageAnimationDir        = 0;\n        float m_PageAnimationProgress = 0.0f;\n        float m_PageAnimationStartPos = 0.0f;\n        float m_PageAnimationOffset   = 0.0f;\n\n        Matrix4 m_Transform;\n        Matrix4 m_TransformAnimationStart;\n        Matrix4 m_TransformAnimationEnd;\n        float m_TransformAnimationProgress = 0.0f;\n\n        ULONGLONG m_TimeoutTickStart = 0;\n\n        void SetUpOverlay();\n        void UpdateOverlayPos();\n        void OnPageChange(int page_id);\n\n    public:\n        WindowQuickStart();\n\n        virtual bool Show();\n        virtual void Hide();\n        void Update();\n        void Reset();\n};\n\nclass WindowCaptureWindowSelect : public AuxUIWindow\n{\n    private:\n        ImVec2 m_PosCenter;\n        Matrix4 m_Transform;\n        HWND m_WindowLastClicked;\n        ULONGLONG m_HoveredTickLast;\n\n        void SetUpOverlay();\n        virtual void ApplyPendingValues();\n\n    public:\n        WindowCaptureWindowSelect();\n\n        virtual bool Show();\n        void Update();\n        void SetTransform(Matrix4 transform);\n};\n\nclass AuxUI\n{\n    friend class AuxUIWindow;\n\n    private:\n        AuxUIID m_ActiveUIID;\n        bool m_IsUIFadingOut;\n\n        WindowDragHint m_WindowDragHint;\n        WindowGazeFadeAutoHint m_WindowGazeFadeAutoHint;\n        WindowCaptureWindowSelect m_WindowCaptureWindowSelect;\n        WindowQuickStart m_WindowQuickStart;\n\n        bool SetActiveUI(AuxUIID new_ui_id);        //May return false if higher priority UI is active\n        AuxUIID GetActiveUI() const;\n        void ClearActiveUI(AuxUIID current_ui_id);  //Sets active UI to auxui_none, but current ID has to match active one\n\n        bool IsUIFadingOut() const;\n        void SetFadeOutFinished();\n\n    public:\n        AuxUI();\n        void Update();\n        bool IsActive() const;\n\n        void HideTemporaryWindows();                //Called on certain OpenVR events to hide temporary windows when user interaction ended so they don't hang around needlessly\n\n        WindowDragHint& GetDragHintWindow();\n        WindowGazeFadeAutoHint& GetGazeFadeAutoHintWindow();\n        WindowCaptureWindowSelect& GetCaptureWindowSelectWindow();\n        WindowQuickStart& GetQuickStartWindow();\n};"
  },
  {
    "path": "src/DesktopPlusUI/DesktopPlusUI.cpp",
    "content": "﻿//This code belongs to the Desktop+ OpenVR overlay application, licensed under GPL 3.0\n//\n//Much of the code here is based on the Dear ImGui Win32 DirectX 11 sample\n\n#define NOMINMAX\n#include \"imgui.h\"\n#include \"imgui_impl_win32_openvr.h\"\n#include \"imgui_impl_dx11_openvr.h\"\n#include \"implot.h\"\n#include <d3d11.h>\n#include <wrl/client.h>\n#define DIRECTINPUT_VERSION 0x0800\n#include <dinput.h>\n#include <tchar.h>\n#include <dwmapi.h>\n#include <shellscalingapi.h>\n\n#include \"resource.h\"\n#include \"UIManager.h\"\n#include \"TextureManager.h\"\n#include \"InterprocessMessaging.h\"\n#include \"WindowSettings.h\"\n#include \"Util.h\"\n#include \"OpenVRExt.h\"\n#include \"ImGuiExt.h\"\n\n#include \"DesktopPlusWinRT.h\"\n#include \"DPBrowserAPIClient.h\"\n\n#include \"WindowDesktopMode.h\"\n\n// Data\nstatic Microsoft::WRL::ComPtr<ID3D11Device>           g_pd3dDevice;\nstatic Microsoft::WRL::ComPtr<ID3D11DeviceContext>    g_pd3dDeviceContext;\nstatic Microsoft::WRL::ComPtr<IDXGISwapChain>         g_pSwapChain;\nstatic Microsoft::WRL::ComPtr<ID3D11RenderTargetView> g_desktopRenderTargetView;\nstatic Microsoft::WRL::ComPtr<ID3D11Texture2D>        g_vrTex;\nstatic Microsoft::WRL::ComPtr<ID3D11RenderTargetView> g_vrRenderTargetView;\n\n\n// Forward declarations of helper functions\nbool CreateDeviceD3D(HWND hWnd, bool desktop_mode);\nvoid CleanupDeviceD3D();\nvoid CreateRenderTarget(bool desktop_mode);\nvoid CleanupRenderTarget();\nvoid RefreshOverlayTextureSharing();\nLRESULT WINAPI WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam);\nvoid InitImGui(HWND hwnd, bool desktop_mode);\nvoid ProcessCmdline(bool& force_desktop_mode, bool& open_keyboard_editor);\n\n// Main code\nint WINAPI WinMain(_In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPrevInstance, _In_ LPSTR lpCmdLine, _In_ INT nCmdShow)\n{\n    DPLog_Init(\"DesktopPlusUI\");\n\n    bool force_desktop_mode = false;\n    bool open_keyboard_editor = false;\n    ProcessCmdline(force_desktop_mode, open_keyboard_editor);\n\n    //Automatically use desktop mode if dashboard app isn't running\n    bool desktop_mode = ( (force_desktop_mode) || (!IPCManager::IsDashboardAppRunning()) );\n    LOG_F(INFO, \"Desktop+ UI running in %s\", (desktop_mode) ? ((open_keyboard_editor) ? \"desktop mode (keyboard editor)\" : \"desktop mode\") : \"VR mode\");\n\n    //Make sure only one instance is running\n    StopProcessByWindowClass(g_WindowClassNameUIApp);\n\n    //Enable DPI support for desktop mode\n    ImGui_ImplWin32_EnableDpiAwareness();\n\n    //Register application class\n    WNDCLASSEX wc = { sizeof(WNDCLASSEX), CS_CLASSDC, WndProc, 0L, 0L, hInstance, nullptr, nullptr, nullptr, nullptr, g_WindowClassNameUIApp, nullptr };\n    wc.hIcon   = (HICON)::LoadImage(hInstance, MAKEINTRESOURCE(IDI_DPLUS), IMAGE_ICON, GetSystemMetrics(SM_CXICON),   GetSystemMetrics(SM_CYICON),   LR_DEFAULTCOLOR);\n    wc.hIconSm = (HICON)::LoadImage(hInstance, MAKEINTRESOURCE(IDI_DPLUS), IMAGE_ICON, GetSystemMetrics(SM_CXSMICON), GetSystemMetrics(SM_CYSMICON), LR_DEFAULTCOLOR);\n    ::RegisterClassEx(&wc);\n\n    //Create window\n    HWND hwnd;\n    if (desktop_mode) \n        hwnd = ::CreateWindow(wc.lpszClassName, L\"Desktop+\", WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX, -1, -1, 100, 100, nullptr, nullptr, wc.hInstance, nullptr);\n    else\n        hwnd = ::CreateWindow(wc.lpszClassName, L\"Desktop+ UI\", 0, 0, 0, 1, 1, HWND_MESSAGE, nullptr, wc.hInstance, nullptr);\n\n    //Init UITextureSpaces\n    UITextureSpaces::Get().Init(desktop_mode, open_keyboard_editor);\n\n    //Init WinRT DLL\n    DPWinRT_Init();\n    LOG_F(INFO, \"Loaded WinRT library\");\n\n    //Init BrowserClientAPI (this doesn't start the browser process, only checks for presence)\n    DPBrowserAPIClient::Get().Init();\n\n    //Allow IPC messages even when elevated (though in normal operation, this process should not be elevated)\n    IPCManager::Get().DisableUIPForRegisteredMessages(hwnd);\n\n    //Init UIManager and load config\n    UIManager ui_manager(desktop_mode, open_keyboard_editor);\n    UIManager::IdleState& idle_state = ui_manager.GetIdleState();\n\n    ConfigManager::Get().LoadConfigFromFile();\n    IPCManager::Get().PostMessageToDashboardApp(ipcmsg_action, ipcact_sync_config_state);\n    ui_manager.SetWindowHandle(hwnd);\n\n    //Init OpenVR\n    //Don't try to init OpenVR without the dashboard app running since checking for active VR means launching SteamVR\n    if ( (!desktop_mode) || (IPCManager::IsDashboardAppRunning()) )\n    {\n        if (ui_manager.InitOverlay() != vr::VRInitError_None)\n        {\n            ::UnregisterClass(wc.lpszClassName, wc.hInstance);\n\n            LOG_F(ERROR, \"OpenVR init failed!\");\n\n            //Try starting in desktop mode instead\n            if (!desktop_mode)\n            {\n                LOG_F(INFO, \"Attempting to start in desktop mode instead...\");\n                ui_manager.Restart(true);\n            }\n\n            return 2;\n        }\n    }\n\n    DPLog_SteamVR_SystemInfo();\n\n    // Initialize Direct3D\n    if (!CreateDeviceD3D(hwnd, desktop_mode))\n    {\n        CleanupDeviceD3D();\n        ::UnregisterClass(wc.lpszClassName, wc.hInstance);\n\n        LOG_F(ERROR, \"Direct3D init failed!\");\n\n        return 1;\n    }\n    LOG_F(INFO, \"Loaded Direct3D\");\n\n    //Center window to the right monitor before setting real size for DPI detection to be correct\n    if (desktop_mode)\n    {\n        CenterWindowToMonitor(hwnd, true);\n    }\n\n    InitImGui(hwnd, desktop_mode);\n    ImGuiIO& io = ImGui::GetIO();\n\n    LOG_F(INFO, \"Loaded Dear ImGui\");\n\n    if (desktop_mode)\n    {\n        const DPRect& rect_total = UITextureSpaces::Get().GetRect(ui_texspace_total);\n\n        //Set real window size\n        RECT r;\n        r.left   = 0;\n        r.top    = 0;\n        r.right  = int(rect_total.GetWidth()  * ui_manager.GetUIScale());\n        r.bottom = int(rect_total.GetHeight() * ui_manager.GetUIScale());\n\n        ::AdjustWindowRect(&r, WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX, FALSE);\n        ::SetWindowPos(hwnd, NULL, 0, 0, r.right - r.left, r.bottom - r.top, SWP_NOMOVE | SWP_NOZORDER | SWP_NOACTIVATE);\n        //Center window on screen\n        CenterWindowToMonitor(hwnd, true);\n\n        ::ShowWindow(hwnd, SW_SHOWDEFAULT);\n        ::UpdateWindow(hwnd);\n    }\n\n    float clear_color[4] = {0.0f, 0.0f, 0.0f, 0.0f};\n\n    //Init notification icon if OpenVR is running (no need for it in pure desktop mode without switching back)\n    if ( (!ConfigManager::GetValue(configid_bool_interface_no_notification_icon)) && (ui_manager.IsOpenVRLoaded()) )\n    {\n        ui_manager.GetNotificationIcon().Init(hInstance);\n    }\n\n    ui_manager.OnInitDone();\n    LOG_F(INFO, \"Finished startup\");\n\n    //Main loop\n    MSG msg;\n    ZeroMemory(&msg, sizeof(msg));\n    while (msg.message != WM_QUIT)\n    {\n        //Poll and handle messages (inputs, window resize, etc.)\n        if (::PeekMessage(&msg, NULL, 0U, 0U, PM_REMOVE))\n        {\n            idle_state.OnWindowMessage(msg.message);\n\n            if (msg.message >= 0xC000)  //Custom message from overlay process, handle in UI manager\n            {\n                ui_manager.HandleIPCMessage(msg);\n            }\n            else\n            {\n                ::TranslateMessage(&msg);\n                ::DispatchMessage(&msg);\n            }\n            continue;\n        }\n\n        if (!desktop_mode)\n        {\n            vr::VREvent_t vr_event;\n            bool do_quit = false;\n\n            //Handle OpenVR events for the dashboard UI\n            ImVec4 rect_v4 = UITextureSpaces::Get().GetRectAsVec4(ui_texspace_overlay_bar);\n\n            while (vr::VROverlay()->PollNextOverlayEvent(ui_manager.GetOverlayHandleOverlayBar(), &vr_event, sizeof(vr_event)))\n            {\n                idle_state.OnOpenVREvent(vr_event.eventType);\n                ImGui_ImplOpenVR_InputEventHandler(vr_event, &rect_v4);\n\n                switch (vr_event.eventType)\n                {\n                    case vr::VREvent_FocusEnter:\n                    {\n                        //Adjust sort order so mainbar tooltips are displayed right\n                        vr::VROverlay()->SetOverlaySortOrder(ui_manager.GetOverlayHandleOverlayBar(), 1);\n                        break;\n                    }\n                    case vr::VREvent_FocusLeave:\n                    case vr::VREvent_OverlayHidden:\n                    {\n                        //Reset adjustment so other overlays are not always behind the UI unless really needed\n                        if (!ui_manager.GetOverlayBarWindow().IsAnyMenuVisible())\n                        {\n                            vr::VROverlay()->SetOverlaySortOrder(ui_manager.GetOverlayHandleOverlayBar(), 0);\n                        }\n                        break;\n                    }\n                    case vr::VREvent_TrackedDeviceActivated:\n                    case vr::VREvent_TrackedDeviceDeactivated:\n                    {\n                        ui_manager.GetPerformanceWindow().RefreshTrackerBatteryList();\n                        break;\n                    }\n                    case vr::VREvent_LeaveStandbyMode:\n                    {\n                        //Reset performance stats when leaving standby since it adds to the dropped frame count during that\n                        ui_manager.GetPerformanceWindow().ResetCumulativeValues();\n                        break;\n                    }\n                    case vr::VREvent_OverlaySharedTextureChanged:\n                    {\n                        //This should only happen during startup since the texture size never changes, but it has happened seemingly randomly before, so handle it\n                        RefreshOverlayTextureSharing();\n                        break;\n                    }\n                    case vr::VREvent_SceneApplicationChanged:\n                    {\n                        const bool loaded_overlay_profile = ConfigManager::Get().GetAppProfileManager().ActivateProfileForProcess(vr_event.data.process.pid);\n\n                        if (loaded_overlay_profile)\n                        {\n                            ui_manager.OnProfileLoaded();\n                        }\n                        break;\n                    }\n                    case vr::VREvent_Quit:\n                    {\n                        do_quit = true;\n                        break;\n                    }\n                }\n            }\n\n            //Handle OpenVR events for the floating UI\n            rect_v4 = UITextureSpaces::Get().GetRectAsVec4(ui_texspace_floating_ui);\n\n            while (vr::VROverlay()->PollNextOverlayEvent(ui_manager.GetOverlayHandleFloatingUI(), &vr_event, sizeof(vr_event)))\n            {\n                idle_state.OnOpenVREvent(vr_event.eventType);\n                ImGui_ImplOpenVR_InputEventHandler(vr_event, &rect_v4);\n\n                switch (vr_event.eventType)\n                {\n                    case vr::VREvent_FocusEnter:\n                    {\n                        //Adjust sort order so tooltips are displayed right\n                        vr::VROverlay()->SetOverlaySortOrder(ui_manager.GetOverlayHandleFloatingUI(), 1);\n                        break;\n                    }\n                    case vr::VREvent_FocusLeave:\n                    {\n                        //Reset adjustment so other overlays are not always behind the UI unless really needed\n                        vr::VROverlay()->SetOverlaySortOrder(ui_manager.GetOverlayHandleFloatingUI(), 0);\n                        break;\n                    }\n                }\n\n            }\n\n            //Handle OpenVR events for the floating settings\n            rect_v4 = UITextureSpaces::Get().GetRectAsVec4(ui_texspace_settings);\n\n            while (vr::VROverlay()->PollNextOverlayEvent(ui_manager.GetOverlayHandleSettings(), &vr_event, sizeof(vr_event)))\n            {\n                idle_state.OnOpenVREvent(vr_event.eventType);\n                ImGui_ImplOpenVR_InputEventHandler(vr_event, &rect_v4);\n            }\n\n            //Handle OpenVR events for the overlay properties\n            rect_v4 = UITextureSpaces::Get().GetRectAsVec4(ui_texspace_overlay_properties);\n\n            while (vr::VROverlay()->PollNextOverlayEvent(ui_manager.GetOverlayHandleOverlayProperties(), &vr_event, sizeof(vr_event)))\n            {\n                idle_state.OnOpenVREvent(vr_event.eventType);\n                ImGui_ImplOpenVR_InputEventHandler(vr_event);\n            }\n\n            //Handle OpenVR events for the VR keyboard\n            rect_v4 = UITextureSpaces::Get().GetRectAsVec4(ui_texspace_keyboard);\n\n            while (vr::VROverlay()->PollNextOverlayEvent(ui_manager.GetOverlayHandleKeyboard(), &vr_event, sizeof(vr_event)))\n            {\n                idle_state.OnOpenVREvent(vr_event.eventType);\n\n                //Let the keyboard window handle events first for multi-laser support\n                if (!ui_manager.GetVRKeyboard().GetWindow().HandleOverlayEvent(vr_event))\n                {\n                    ImGui_ImplOpenVR_InputEventHandler(vr_event, &rect_v4);\n                }\n            }\n\n            //Handle OpenVR events for the Aux UI\n            rect_v4 = UITextureSpaces::Get().GetRectAsVec4(ui_texspace_aux_ui);\n\n            while (vr::VROverlay()->PollNextOverlayEvent(ui_manager.GetOverlayHandleAuxUI(), &vr_event, sizeof(vr_event)))\n            {\n                idle_state.OnOpenVREvent(vr_event.eventType);\n                ImGui_ImplOpenVR_InputEventHandler(vr_event, &rect_v4);\n\n                switch (vr_event.eventType)\n                {\n                    case vr::VREvent_DashboardActivated:\n                    case vr::VREvent_DashboardDeactivated:\n                    {\n                        ui_manager.GetAuxUI().HideTemporaryWindows();\n                        break;\n                    }\n                }\n            }\n\n            ui_manager.PositionOverlay();\n            ui_manager.GetFloatingUI().UpdateUITargetState();\n            ui_manager.GetSettingsWindow().UpdateVisibility();\n            ui_manager.GetOverlayPropertiesWindow().UpdateVisibility();\n            ui_manager.GetVRKeyboard().GetWindow().UpdateVisibility();\n\n            if (do_quit)\n            {\n                break; //Breaks the message loop, causing clean shutdown\n            }\n        }\n\n        ui_manager.GetPerformanceWindow().UpdateVisibleState();\n\n        //Do texture reload now if it had been scheduled\n        //However, do not reload texture while an item is active as the ID changes on ImageButtons... but allow it if InputText is active to not have placeholder characters on user input\n        if ( (TextureManager::Get().GetReloadLaterFlag()) && ( (!ImGui::IsAnyItemActiveOrDeactivated()) || (ImGui::IsAnyInputTextActive()) ) )\n        {\n            TextureManager::Get().LoadAllTexturesAndBuildFonts();\n        }\n\n        //While we still need to poll, greatly reduce the rate and don't do any ImGui stuff to not waste resources (hopefully this does not mess up ImGui input state)\n        if ((idle_state.ShouldIdle()) && (!ui_manager.HasDelayedIPCMessages()))\n        {\n            ::Sleep(64);                 //Could wait longer, but it doesn't really make much of a difference in load and we stay more responsive like this)\n            idle_state.DoIdleTimestep(); //Delta time won't be updated by ImGui while idle, but some parts of the app still rely on it so we do it ourselves\n            continue;\n        }\n\n        // Start the Dear ImGui frame\n        ImGui_ImplDX11_NewFrame();\n\n        if (desktop_mode)\n            ImGui_ImplWin32_NewFrame();\n        else\n            ImGui_ImplOpenVR_NewFrame();\n\n        idle_state.OnImGuiNewFrame();\n        ui_manager.GetVRKeyboard().OnImGuiNewFrame();\n\n        ImGui::NewFrame();\n\n        //Handle delayed ICP messages that need to be handled within an ImGui frame now\n        ui_manager.HandleDelayedIPCMessages();\n\n        if (!desktop_mode)\n        {\n            //Overlay dragging needs to happen within an ImGui frame for a valid scroll input state (ImGui::EndFrame() clears it by the time we'd loop again)\n            ui_manager.UpdateOverlayDrag();\n\n            //Make ImGui think the surface is smaller than it is (a poor man's multi-viewport hack)\n\n            //Overlay Bar (this and floating UI need no X adjustments)\n            io.DisplaySize.y = (float)UITextureSpaces::Get().GetRect(ui_texspace_overlay_bar).GetBR().y;\n            ImGui::GetMainViewport()->Size.y = io.DisplaySize.y;\n\n            ui_manager.GetOverlayBarWindow().Update();\n\n            //Floating UI\n            io.DisplaySize.y = (float)UITextureSpaces::Get().GetRect(ui_texspace_floating_ui).GetBR().y;\n            ImGui::GetMainViewport()->Size.y = io.DisplaySize.y;\n\n            ui_manager.GetFloatingUI().Update();\n\n            //Overlay Properties\n            io.DisplaySize.x = (float)UITextureSpaces::Get().GetRect(ui_texspace_overlay_properties).GetBR().x;\n            ImGui::GetMainViewport()->Size.x = io.DisplaySize.x;\n            io.DisplaySize.y = (float)UITextureSpaces::Get().GetRect(ui_texspace_overlay_properties).GetBR().y;\n            ImGui::GetMainViewport()->Size.y = io.DisplaySize.y;\n\n            ui_manager.GetOverlayPropertiesWindow().Update();\n\n            //Settings\n            io.DisplaySize.x = (float)UITextureSpaces::Get().GetRect(ui_texspace_settings).GetBR().x;\n            ImGui::GetMainViewport()->Size.x = io.DisplaySize.x;\n            io.DisplaySize.y = (float)UITextureSpaces::Get().GetRect(ui_texspace_settings).GetBR().y;\n            ImGui::GetMainViewport()->Size.y = io.DisplaySize.y;\n\n            ui_manager.GetSettingsWindow().Update();\n\n            //Keyboard\n            io.DisplaySize.x = (float)UITextureSpaces::Get().GetRect(ui_texspace_keyboard).GetBR().x;\n            ImGui::GetMainViewport()->Size.x = io.DisplaySize.x;\n            io.DisplaySize.y = (float)UITextureSpaces::Get().GetRect(ui_texspace_keyboard).GetBR().y;\n            ImGui::GetMainViewport()->Size.y = io.DisplaySize.y;\n\n            ui_manager.GetVRKeyboard().GetWindow().Update();\n\n            //Performance Monitor\n            io.DisplaySize.x = (float)UITextureSpaces::Get().GetRect(ui_texspace_performance_monitor).GetBR().x;\n            ImGui::GetMainViewport()->Size.x = io.DisplaySize.x;\n            io.DisplaySize.y = (float)UITextureSpaces::Get().GetRect(ui_texspace_performance_monitor).GetBR().y;\n            ImGui::GetMainViewport()->Size.y = io.DisplaySize.y;\n\n            ui_manager.GetPerformanceWindow().Update();\n\n            //Aux UI\n            io.DisplaySize.x = (float)UITextureSpaces::Get().GetRect(ui_texspace_aux_ui).GetBR().x;\n            ImGui::GetMainViewport()->Size.x = io.DisplaySize.x;\n            io.DisplaySize.y = (float)UITextureSpaces::Get().GetRect(ui_texspace_aux_ui).GetBR().y;\n            ImGui::GetMainViewport()->Size.y = io.DisplaySize.y;\n\n            ui_manager.GetAuxUI().Update();\n        }\n        else\n        {\n            if (open_keyboard_editor)\n            {\n                ui_manager.GetVRKeyboard().GetEditor().Update();\n            }\n            else\n            {\n                ui_manager.GetDesktopModeWindow().Update();\n            }\n        }\n\n        //Haptic feedback for hovered items, like the rest of the SteamVR UI\n        if ( (!desktop_mode) && (ImGui::HasHoveredNewItem()) )\n        {\n            for (const auto& overlay_handle : ui_manager.GetUIOverlayHandles())\n            {\n                if (ConfigManager::Get().IsLaserPointerTargetOverlay(overlay_handle))\n                {\n                    ui_manager.TriggerLaserPointerHaptics(overlay_handle);\n                }\n            }\n        }\n\n        // Rendering\n        if (ui_manager.GetRepeatFrame()) //If frame repeat is enabled, don't actually render and skip vsync\n        {\n            ImGui::EndFrame();\n            ui_manager.DecreaseRepeatFrameCount();\n        }\n        else\n        {\n            ImGui::Render();\n\n            if (desktop_mode)\n            {\n                g_pd3dDeviceContext->OMSetRenderTargets(1, g_desktopRenderTargetView.GetAddressOf(), nullptr);\n                g_pd3dDeviceContext->ClearRenderTargetView(g_desktopRenderTargetView.Get(), clear_color);\n                ImGui_ImplDX11_RenderDrawData(ImGui::GetDrawData());\n\n                const int sync_count = idle_state.GetFrameSkipValue() + 1;\n                HRESULT res = g_pSwapChain->Present(std::min(sync_count, 4), 0); //Present with vsync (and optionally wait longer with frameskip)\n\n                if (res == DXGI_STATUS_OCCLUDED) //When occluded, Present() will not wait for us\n                {\n                    for (int i = 0; i < sync_count; ++i)\n                    {\n                        ::DwmFlush();   //We could wait longer, but let's stay responsive\n                    }\n                }\n            }\n            else\n            {\n                g_pd3dDeviceContext->OMSetRenderTargets(1, g_vrRenderTargetView.GetAddressOf(), nullptr);\n                g_pd3dDeviceContext->ClearRenderTargetView(g_vrRenderTargetView.Get(), clear_color);\n                ImGui_ImplDX11_RenderDrawData(ImGui::GetDrawData());\n\n                //Set Overlay texture\n                ID3D11ShaderResourceView* ovrl_shader_rsv = vr::VROverlayEx()->GetOverlayTextureEx(ui_manager.GetOverlayHandleOverlayBar(), g_vrTex.Get());\n                if (ovrl_shader_rsv != nullptr)\n                {\n                    //Do a partial update, only copying the active texture space into the overlay texture\n                    Microsoft::WRL::ComPtr<ID3D11Resource> ovrl_tex;\n                    ovrl_shader_rsv->GetResource(&ovrl_tex);\n\n                    const DPRect active_rect = ui_manager.CalcRectForActiveTexspace();\n                    if (active_rect.GetWidth() != 0)\n                    {\n                        D3D11_BOX box = {0};\n                        box.left   = active_rect.GetTL().x;\n                        box.top    = active_rect.GetTL().y;\n                        box.front  = 0;\n                        box.right  = active_rect.GetBR().x;\n                        box.bottom = active_rect.GetBR().y;\n                        box.back   = 1;\n\n                        g_pd3dDeviceContext->CopySubresourceRegion(ovrl_tex.Get(), 0, box.left, box.top, 0, g_vrTex.Get(), 0, &box);\n                    }\n\n                    //RSV is kept around by IVROverlayEx and not released here\n                }\n                else if ((ui_manager.GetOverlayHandleOverlayBar() != vr::k_ulOverlayHandleInvalid) && (g_vrTex))\n                {\n                    vr::Texture_t vrtex = {};\n                    vrtex.handle = g_vrTex.Get();\n                    vrtex.eType = vr::TextureType_DirectX;\n                    vrtex.eColorSpace = vr::ColorSpace_Gamma;\n\n                    vr::VROverlayEx()->SetOverlayTextureEx(ui_manager.GetOverlayHandleOverlayBar(), &vrtex, {(int)io.DisplaySize.x, (int)io.DisplaySize.y});\n                }\n\n                //Set overlay intersection mask... there doesn't seem to be much overhead from doing this every frame, even though we only need to update this sometimes\n                auto overlay_handles = ui_manager.GetUIOverlayHandles();\n                std::vector<vr::VROverlayIntersectionMaskPrimitive_t> mask_primitives;\n                ImGui_ImplOpenVR_SetIntersectionMaskFromWindows(overlay_handles.data(), overlay_handles.size(), &mask_primitives);\n                ui_manager.SendUIIntersectionMaskToDashboardApp(mask_primitives);\n\n                //Wait for VR frame sync, optionally repeatedly to achieve lower synched frame rates\n                g_pd3dDeviceContext->Flush();\n                \n                const int sync_count = idle_state.GetFrameSkipValue() + 1;\n                for (int i = 0; i < sync_count; ++i)\n                {\n                    vr::VROverlay()->WaitFrameSync(34); //(34 ms timeout so we don't go much below 30 fps worst case)\n                }\n            }\n        }\n    }\n\n    LOG_F(INFO, \"Shutting down...\");\n\n    // Cleanup\n    ui_manager.OnExit();\n    ImGui_ImplDX11_Shutdown();\n    ImGui_ImplWin32_Shutdown();\n    ImPlot::DestroyContext();\n    ImGui::DestroyContext();\n\n    CleanupDeviceD3D();\n    ::DestroyWindow(hwnd);\n    ::UnregisterClass(wc.lpszClassName, wc.hInstance);\n\n    return 0;\n}\n\n// Helper functions\n\nbool CreateDeviceD3D(HWND hWnd, bool desktop_mode)\n{\n    const UINT target_adapter_deviceid    = (ConfigManager::GetValue(configid_int_misc_force_gpu_deviceid)    == -1) ? 0 : (UINT)ConfigManager::GetValue(configid_int_misc_force_gpu_deviceid);\n    const UINT target_adapter_deviceid_vr = (ConfigManager::GetValue(configid_int_misc_force_gpu_vr_deviceid) == -1) ? 0 : (UINT)ConfigManager::GetValue(configid_int_misc_force_gpu_vr_deviceid);\n\n    //Get forced adapter if any and get the adapter recommended by OpenVR if it's loaded (needed for Performance Monitor even in desktop mode)\n    Microsoft::WRL::ComPtr<IDXGIAdapter> adapter_ptr_vr;\n    Microsoft::WRL::ComPtr<IDXGIAdapter> adapter_ptr_desktop;   //stays nullptr unless forced\n\n    {\n        Microsoft::WRL::ComPtr<IDXGIFactory1> factory_ptr;\n        int32_t vr_gpu_id = -1;\n\n        if (UIManager::Get()->IsOpenVRLoaded())\n        {\n            vr::VRSystem()->GetDXGIOutputInfo(&vr_gpu_id);\n        }\n\n        HRESULT hr = CreateDXGIFactory1(__uuidof(IDXGIFactory1), (void**)&factory_ptr);\n        if (!FAILED(hr))\n        {\n            Microsoft::WRL::ComPtr<IDXGIAdapter> adapter_ptr;\n            UINT i = 0;\n\n            while (factory_ptr->EnumAdapters(i, &adapter_ptr) != DXGI_ERROR_NOT_FOUND)\n            {\n                DXGI_ADAPTER_DESC adapter_desc;\n                adapter_ptr->GetDesc(&adapter_desc);\n\n                if ( ((target_adapter_deviceid_vr == 0) && (i == vr_gpu_id)) || (adapter_desc.DeviceId == target_adapter_deviceid_vr) )\n                {\n                    adapter_ptr_vr = adapter_ptr;\n                }\n\n                if (adapter_desc.DeviceId == target_adapter_deviceid)\n                {\n                    adapter_ptr_desktop = adapter_ptr;\n                }\n\n                ++i;\n            }\n        }\n\n        if (adapter_ptr_vr != nullptr)\n        {\n            //Set target GPU and total VRAM for Performance Monitor\n            DXGI_ADAPTER_DESC adapter_desc;\n            if (adapter_ptr_vr->GetDesc(&adapter_desc) == S_OK)\n            {\n                UIManager::Get()->GetPerformanceWindow().GetPerformanceData().SetTargetGPU(adapter_desc.AdapterLuid, adapter_desc.DedicatedVideoMemory);\n            }\n        }\n\n        LOG_IF_F(WARNING, (adapter_ptr_desktop == nullptr) && (target_adapter_deviceid != 0), \"GPU forced by config (DeviceID %u) was not found!\", target_adapter_deviceid);\n        LOG_IF_F(WARNING, (adapter_ptr_vr == nullptr) && (target_adapter_deviceid_vr == 0) && UIManager::Get()->IsOpenVRLoaded(), \"GPU requested by SteamVR (%u) was not found!\", vr_gpu_id + 1);\n        LOG_IF_F(WARNING, (adapter_ptr_vr == nullptr) && (target_adapter_deviceid_vr != 0), \"VR GPU forced by config (DeviceID %u) was not found!\", target_adapter_deviceid_vr);\n    }\n\n    D3D_FEATURE_LEVEL featureLevel;\n    const D3D_FEATURE_LEVEL featureLevelArray[2] = { D3D_FEATURE_LEVEL_11_0, D3D_FEATURE_LEVEL_10_0, };\n\n    if (desktop_mode)\n    {\n        //Setup swap chain\n        DXGI_SWAP_CHAIN_DESC sd;\n        ZeroMemory(&sd, sizeof(sd));\n        sd.BufferCount = 2;\n        sd.BufferDesc.Width = 0;\n        sd.BufferDesc.Height = 0;\n        sd.BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;\n        sd.BufferDesc.RefreshRate.Numerator = 60;\n        sd.BufferDesc.RefreshRate.Denominator = 1;\n        sd.Flags = DXGI_SWAP_CHAIN_FLAG_ALLOW_MODE_SWITCH;\n        sd.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;\n        sd.OutputWindow = hWnd;\n        sd.SampleDesc.Count = 1;\n        sd.SampleDesc.Quality = 0;\n        sd.Windowed = TRUE;\n        sd.SwapEffect = DXGI_SWAP_EFFECT_FLIP_SEQUENTIAL;   //DXGI_SWAP_EFFECT_FLIP_DISCARD also would work, but we still support Windows 8\n        \n        if (D3D11CreateDeviceAndSwapChain(adapter_ptr_desktop.Get(), (adapter_ptr_desktop != nullptr) ? D3D_DRIVER_TYPE_UNKNOWN : D3D_DRIVER_TYPE_HARDWARE, nullptr, 0, featureLevelArray, 2, D3D11_SDK_VERSION, \n                                          &sd, &g_pSwapChain, &g_pd3dDevice, &featureLevel, &g_pd3dDeviceContext) != S_OK)\n        {\n            return false;\n        }\n    }\n    else \n    {\n        //No swap chain needed for VR\n\n        if (adapter_ptr_vr != nullptr)\n        {\n            if (D3D11CreateDevice(adapter_ptr_vr.Get(), D3D_DRIVER_TYPE_UNKNOWN, nullptr, 0, featureLevelArray, 2, D3D11_SDK_VERSION, &g_pd3dDevice, &featureLevel, &g_pd3dDeviceContext) != S_OK)\n            {\n                return false;\n            }\n        }\n        else //Still try /something/, but it probably won't work\n        {\n            if (D3D11CreateDevice(nullptr, D3D_DRIVER_TYPE_HARDWARE, nullptr, 0, featureLevelArray, 2, D3D11_SDK_VERSION, &g_pd3dDevice, &featureLevel, &g_pd3dDeviceContext) != S_OK)\n            {\n                return false;\n            }\n        }\n    }\n\n    CreateRenderTarget(desktop_mode);\n    return true;\n}\n\nvoid CleanupDeviceD3D()\n{\n    CleanupRenderTarget();\n\n    g_pSwapChain.Reset();\n    g_pd3dDeviceContext.Reset();\n    g_pd3dDevice.Reset();\n}\n\nvoid CreateRenderTarget(bool desktop_mode)\n{\n    HRESULT hr;\n    const DPRect& texrect_total = UITextureSpaces::Get().GetRect(ui_texspace_total);\n\n    // Create overlay texture\n    D3D11_TEXTURE2D_DESC TexD;\n    RtlZeroMemory(&TexD, sizeof(D3D11_TEXTURE2D_DESC));\n    TexD.Width  = texrect_total.GetWidth();\n    TexD.Height = texrect_total.GetHeight();\n    TexD.MipLevels = 1;\n    TexD.ArraySize = 1;\n    TexD.Format = DXGI_FORMAT_B8G8R8A8_UNORM;\n    TexD.SampleDesc.Count = 1;\n    TexD.Usage = D3D11_USAGE_DEFAULT;\n    TexD.BindFlags = D3D11_BIND_RENDER_TARGET | D3D11_BIND_SHADER_RESOURCE;\n    TexD.CPUAccessFlags = 0;\n    TexD.MiscFlags = D3D11_RESOURCE_MISC_SHARED;\n\n    hr = g_pd3dDevice->CreateTexture2D(&TexD, nullptr, &g_vrTex);\n\n    if (FAILED(hr))\n    {\n        //Cry\n        return;\n    }\n\n    UIManager::Get()->SetSharedTextureRef(g_vrTex.Get());\n\n    // Create render target view for overlay texture\n    D3D11_RENDER_TARGET_VIEW_DESC tex_rtv_desc = {};\n\n    tex_rtv_desc.Format = DXGI_FORMAT_B8G8R8A8_UNORM;\n    tex_rtv_desc.ViewDimension = D3D11_RTV_DIMENSION_TEXTURE2D;\n    tex_rtv_desc.Texture2D.MipSlice = 0;\n\n    g_pd3dDevice->CreateRenderTargetView(g_vrTex.Get(), &tex_rtv_desc, &g_vrRenderTargetView);\n\n    if (desktop_mode)\n    {\n        Microsoft::WRL::ComPtr<ID3D11Texture2D> pBackBuffer;\n        g_pSwapChain->GetBuffer(0, IID_PPV_ARGS(&pBackBuffer));\n\n        if (pBackBuffer != nullptr)\n        {\n            g_pd3dDevice->CreateRenderTargetView(pBackBuffer.Get(), nullptr, &g_desktopRenderTargetView);\n        }\n    }\n}\n\nvoid CleanupRenderTarget()\n{\n    g_desktopRenderTargetView.Reset();\n    g_vrRenderTargetView.Reset();\n    g_vrTex.Reset();\n}\n\nvoid RefreshOverlayTextureSharing()\n{\n    //Set up advanced texture sharing between the overlays\n    vr::VROverlayEx()->SetSharedOverlayTexture(UIManager::Get()->GetOverlayHandleOverlayBar(), UIManager::Get()->GetOverlayHandleFloatingUI(),        g_vrTex.Get());\n    vr::VROverlayEx()->SetSharedOverlayTexture(UIManager::Get()->GetOverlayHandleOverlayBar(), UIManager::Get()->GetOverlayHandleSettings(),          g_vrTex.Get());\n    vr::VROverlayEx()->SetSharedOverlayTexture(UIManager::Get()->GetOverlayHandleOverlayBar(), UIManager::Get()->GetOverlayHandleOverlayProperties(), g_vrTex.Get());\n    vr::VROverlayEx()->SetSharedOverlayTexture(UIManager::Get()->GetOverlayHandleOverlayBar(), UIManager::Get()->GetOverlayHandleKeyboard(),          g_vrTex.Get());\n    vr::VROverlayEx()->SetSharedOverlayTexture(UIManager::Get()->GetOverlayHandleOverlayBar(), UIManager::Get()->GetOverlayHandleAuxUI(),             g_vrTex.Get());\n    //Also schedule for performance overlays, in case there are any\n    UIManager::Get()->GetPerformanceWindow().ScheduleOverlaySharedTextureUpdate();\n}\n\n\n\n// Win32 message handler\nextern LRESULT ImGui_ImplWin32_WndProcHandler(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam);\nLRESULT WINAPI WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)\n{\n    if ((UIManager::Get()) && (UIManager::Get()->IsInDesktopMode()))\n    {\n        ImGui_ImplWin32_WndProcHandler(hWnd, msg, wParam, lParam);\n    }\n\n    switch (msg)\n    {\n        case WM_SIZE:\n        {\n            if (g_pd3dDevice != nullptr && wParam != SIZE_MINIMIZED)\n            {\n                CleanupRenderTarget();\n                g_pSwapChain->ResizeBuffers(0, (UINT)LOWORD(lParam), (UINT)HIWORD(lParam), DXGI_FORMAT_UNKNOWN, 0);\n                CreateRenderTarget(UIManager::Get()->IsInDesktopMode());\n            }\n            return 0;\n        }\n        case WM_DPICHANGED:\n        {\n            UIManager::Get()->OnDPIChanged(HIWORD(wParam), *(RECT*)lParam);\n            return 0;\n        }\n        case WM_COPYDATA:\n        {\n            if (UIManager::Get())\n            {\n                MSG pmsg;\n                //Process all custom window messages posted before this\n                while (PeekMessage(&pmsg, nullptr, 0xC000, 0xFFFF, PM_REMOVE))\n                {\n                    UIManager::Get()->HandleIPCMessage(pmsg);\n                }\n\n                MSG wmsg;\n                wmsg.hwnd = hWnd;\n                wmsg.message = msg;\n                wmsg.wParam = wParam;\n                wmsg.lParam = lParam;\n\n                UIManager::Get()->HandleIPCMessage(wmsg);\n            }\n            return 0;\n        }\n        case WM_SYSCOMMAND:\n        {\n            if ((wParam & 0xfff0) == SC_KEYMENU) // Disable ALT application menu\n            {\n                return 0;\n            }\n            break;\n        }\n        case WM_DESTROY:\n        {\n            ::PostQuitMessage(0);\n            return 0;\n        }\n    }\n    return ::DefWindowProc(hWnd, msg, wParam, lParam);\n}\n\nvoid InitImGui(HWND hwnd, bool desktop_mode)\n{\n    //Setup Dear ImGui context\n    IMGUI_CHECKVERSION();\n    ImGui::CreateContext();\n    ImPlot::CreateContext();\n    ImGuiIO& io = ImGui::GetIO();\n\n    //Enable keyboard controls when in desktop mode\n    if (desktop_mode)\n        io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard;\n\n    io.IniFilename = nullptr;                   //We don't need any imgui.ini support\n    io.ConfigInputTrickleEventQueue = false;    //Opt out of input trickling since it doesn't play well with VR scrolling (and lowers responsiveness on certain inputs)\n    ImGui::ConfigDisableCtrlTab();\n\n    //Use system double-click time in desktop mode, but set it to something VR-trigger-friendly otherwise\n    io.MouseDoubleClickTime = (desktop_mode) ? ::GetDoubleClickTime() / 1000.0f : 0.50f;\n    io.MouseDoubleClickMaxDist = 16.0f;\n\n    //Setup Platform/Renderer bindings\n    ImGui_ImplWin32_Init(hwnd);\n    ImGui_ImplDX11_Init(g_pd3dDevice.Get(), g_pd3dDeviceContext.Get());\n\n    UIManager::Get()->UpdateStyle();\n}\n\nvoid ProcessCmdline(bool& force_desktop_mode, bool& open_keyboard_editor)\n{\n    //__argv and __argc are global vars set by system\n    for (UINT i = 0; i < static_cast<UINT>(__argc); ++i)\n    {\n        if ((strcmp(__argv[i], \"-DesktopMode\")  == 0) ||\n            (strcmp(__argv[i], \"--DesktopMode\") == 0) ||\n            (strcmp(__argv[i], \"/DesktopMode\")  == 0))\n        {\n            force_desktop_mode = true;\n        }\n        else if ((strcmp(__argv[i], \"-KeyboardEditor\")  == 0) ||\n                 (strcmp(__argv[i], \"--KeyboardEditor\") == 0) ||\n                 (strcmp(__argv[i], \"/KeyboardEditor\")  == 0))\n        {\n            open_keyboard_editor = true;\n            force_desktop_mode   = true;\n        }\n    }\n}"
  },
  {
    "path": "src/DesktopPlusUI/DesktopPlusUI.vcxproj",
    "content": "﻿<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<Project DefaultTargets=\"Build\" ToolsVersion=\"14.0\" xmlns=\"http://schemas.microsoft.com/developer/msbuild/2003\">\n  <ItemGroup Label=\"ProjectConfigurations\">\n    <ProjectConfiguration Include=\"Debug|x64\">\n      <Configuration>Debug</Configuration>\n      <Platform>x64</Platform>\n    </ProjectConfiguration>\n    <ProjectConfiguration Include=\"Release|x64\">\n      <Configuration>Release</Configuration>\n      <Platform>x64</Platform>\n    </ProjectConfiguration>\n  </ItemGroup>\n  <PropertyGroup Label=\"Globals\">\n    <ProjectGuid>{14405CEC-DF3D-435E-8F11-B79BAC56D7D8}</ProjectGuid>\n    <ProjectName>DesktopPlusUI</ProjectName>\n    <RootNamespace>DesktopPlusUI</RootNamespace>\n  </PropertyGroup>\n  <Import Project=\"$(VCTargetsPath)\\Microsoft.Cpp.Default.props\" />\n  <PropertyGroup Condition=\"'$(Configuration)|$(Platform)'=='Debug|x64'\" Label=\"Configuration\">\n    <ConfigurationType>Application</ConfigurationType>\n    <UseDebugLibraries>true</UseDebugLibraries>\n    <PlatformToolset>v142</PlatformToolset>\n    <CharacterSet>Unicode</CharacterSet>\n  </PropertyGroup>\n  <PropertyGroup Condition=\"'$(Configuration)|$(Platform)'=='Release|x64'\" Label=\"Configuration\">\n    <ConfigurationType>Application</ConfigurationType>\n    <UseDebugLibraries>false</UseDebugLibraries>\n    <PlatformToolset>v142</PlatformToolset>\n    <WholeProgramOptimization>true</WholeProgramOptimization>\n    <CharacterSet>Unicode</CharacterSet>\n  </PropertyGroup>\n  <Import Project=\"$(VCTargetsPath)\\Microsoft.Cpp.props\" />\n  <ImportGroup Label=\"ExtensionSettings\">\n  </ImportGroup>\n  <ImportGroup Label=\"Shared\">\n  </ImportGroup>\n  <ImportGroup Label=\"PropertySheets\" Condition=\"'$(Configuration)|$(Platform)'=='Debug|x64'\">\n    <Import Project=\"$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props\" Condition=\"exists('$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props')\" Label=\"LocalAppDataPlatform\" />\n  </ImportGroup>\n  <ImportGroup Label=\"PropertySheets\" Condition=\"'$(Configuration)|$(Platform)'=='Release|x64'\">\n    <Import Project=\"$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props\" Condition=\"exists('$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props')\" Label=\"LocalAppDataPlatform\" />\n  </ImportGroup>\n  <PropertyGroup Label=\"UserMacros\" />\n  <PropertyGroup Condition=\"'$(Configuration)|$(Platform)'=='Debug|x64'\">\n    <LinkIncremental>true</LinkIncremental>\n    <CodeAnalysisRuleSet>..\\DesktopPlus\\DesktopPlus.ruleset</CodeAnalysisRuleSet>\n  </PropertyGroup>\n  <PropertyGroup Condition=\"'$(Configuration)|$(Platform)'=='Release|x64'\">\n    <LinkIncremental>false</LinkIncremental>\n    <CodeAnalysisRuleSet>..\\DesktopPlus\\DesktopPlus.ruleset</CodeAnalysisRuleSet>\n  </PropertyGroup>\n  <ItemDefinitionGroup Condition=\"'$(Configuration)|$(Platform)'=='Debug|x64'\">\n    <ClCompile>\n      <PrecompiledHeader>\n      </PrecompiledHeader>\n      <WarningLevel>Level3</WarningLevel>\n      <Optimization>Disabled</Optimization>\n      <PreprocessorDefinitions>LOGURU_FILENAME_WIDTH=30;LOGURU_VERBOSE_SCOPE_ENDINGS=0;DPLUS_UI;DPLUS_SHA=$(DPLUS_SHA);_DEBUG;_WINDOWS;%(PreprocessorDefinitions)</PreprocessorDefinitions>\n      <AdditionalIncludeDirectories>.\\imgui_win32_dx11_openvr;.\\imgui;.\\implot;$(OutDir);..\\Shared;..\\DesktopPlusUI;..\\DesktopPlusWinRT;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>\n      <RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary>\n      <DisableSpecificWarnings>26812;%(DisableSpecificWarnings)</DisableSpecificWarnings>\n      <MultiProcessorCompilation>true</MultiProcessorCompilation>\n    </ClCompile>\n    <Link>\n      <SubSystem>Windows</SubSystem>\n      <GenerateDebugInformation>true</GenerateDebugInformation>\n      <AdditionalDependencies>d3d11.lib;dxgi.lib;dwmapi.lib;openvr_api.lib;gdiplus.lib;shcore.lib;shlwapi.lib;pdh.lib;DesktopPlusWinRT.lib;%(AdditionalDependencies)</AdditionalDependencies>\n      <AdditionalLibraryDirectories>$(SolutionDir)Shared;$(OutputPath)</AdditionalLibraryDirectories>\n    </Link>\n  </ItemDefinitionGroup>\n  <ItemDefinitionGroup Condition=\"'$(Configuration)|$(Platform)'=='Release|x64'\">\n    <ClCompile>\n      <WarningLevel>Level3</WarningLevel>\n      <PrecompiledHeader>\n      </PrecompiledHeader>\n      <Optimization>Full</Optimization>\n      <FunctionLevelLinking>true</FunctionLevelLinking>\n      <IntrinsicFunctions>true</IntrinsicFunctions>\n      <PreprocessorDefinitions>LOGURU_FILENAME_WIDTH=30;LOGURU_VERBOSE_SCOPE_ENDINGS=0;DPLUS_UI;DPLUS_SHA=$(DPLUS_SHA);NDEBUG;_WINDOWS;%(PreprocessorDefinitions)</PreprocessorDefinitions>\n      <AdditionalIncludeDirectories>.\\imgui_win32_dx11_openvr;.\\imgui;.\\implot;$(OutDir);..\\Shared;..\\DesktopPlusUI;..\\DesktopPlusWinRT;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>\n      <OmitFramePointers>true</OmitFramePointers>\n      <RuntimeLibrary>MultiThreaded</RuntimeLibrary>\n      <DisableSpecificWarnings>26812;%(DisableSpecificWarnings)</DisableSpecificWarnings>\n      <DebugInformationFormat>None</DebugInformationFormat>\n      <MultiProcessorCompilation>true</MultiProcessorCompilation>\n      <FavorSizeOrSpeed>Speed</FavorSizeOrSpeed>\n      <InlineFunctionExpansion>AnySuitable</InlineFunctionExpansion>\n    </ClCompile>\n    <Link>\n      <SubSystem>Windows</SubSystem>\n      <EnableCOMDATFolding>true</EnableCOMDATFolding>\n      <OptimizeReferences>true</OptimizeReferences>\n      <GenerateDebugInformation>false</GenerateDebugInformation>\n      <AdditionalDependencies>d3d11.lib;dxgi.lib;dwmapi.lib;openvr_api.lib;gdiplus.lib;shcore.lib;shlwapi.lib;pdh.lib;DesktopPlusWinRT.lib;%(AdditionalDependencies)</AdditionalDependencies>\n      <AdditionalLibraryDirectories>$(SolutionDir)Shared;$(OutputPath)</AdditionalLibraryDirectories>\n    </Link>\n  </ItemDefinitionGroup>\n  <ItemGroup>\n    <ClCompile Include=\"..\\Shared\\Actions.cpp\" />\n    <ClCompile Include=\"..\\Shared\\AppProfiles.cpp\" />\n    <ClCompile Include=\"..\\Shared\\ConfigManager.cpp\" />\n    <ClCompile Include=\"..\\Shared\\DPBrowserAPIClient.cpp\" />\n    <ClCompile Include=\"..\\Shared\\Ini.cpp\" />\n    <ClCompile Include=\"..\\Shared\\Logging.cpp\" />\n    <ClCompile Include=\"..\\Shared\\loguru.cpp\" />\n    <ClCompile Include=\"..\\Shared\\Matrices.cpp\" />\n    <ClCompile Include=\"..\\Shared\\OpenVRExt.cpp\" />\n    <ClCompile Include=\"..\\Shared\\OverlayDragger.cpp\" />\n    <ClCompile Include=\"..\\Shared\\OverlayManager.cpp\" />\n    <ClCompile Include=\"..\\Shared\\Util.cpp\" />\n    <ClCompile Include=\"..\\Shared\\WindowManager.cpp\" />\n    <ClCompile Include=\"AuxUI.cpp\" />\n    <ClCompile Include=\"DesktopPlusUI.cpp\" />\n    <ClCompile Include=\"FloatingWindow.cpp\" />\n    <ClCompile Include=\"FloatingUI.cpp\" />\n    <ClCompile Include=\"ImGuiExt.cpp\" />\n    <ClCompile Include=\"imgui\\imgui.cpp\" />\n    <ClCompile Include=\"imgui\\imgui_demo.cpp\" />\n    <ClCompile Include=\"imgui\\imgui_draw.cpp\" />\n    <ClCompile Include=\"imgui\\imgui_tables.cpp\" />\n    <ClCompile Include=\"imgui\\imgui_widgets.cpp\" />\n    <ClCompile Include=\"imgui_win32_dx11_openvr\\imgui_impl_dx11_openvr.cpp\" />\n    <ClCompile Include=\"imgui_win32_dx11_openvr\\imgui_impl_win32_openvr.cpp\" />\n    <ClCompile Include=\"..\\Shared\\InterprocessMessaging.cpp\" />\n    <ClCompile Include=\"implot\\implot.cpp\" />\n    <ClCompile Include=\"implot\\implot_items.cpp\" />\n    <ClCompile Include=\"NotificationIcon.cpp\" />\n    <ClCompile Include=\"TranslationManager.cpp\" />\n    <ClCompile Include=\"VRKeyboard.cpp\" />\n    <ClCompile Include=\"Win32PerformanceData.cpp\" />\n    <ClCompile Include=\"UIManager.cpp\" />\n    <ClCompile Include=\"WindowDesktopMode.cpp\" />\n    <ClCompile Include=\"WindowKeyboard.cpp\" />\n    <ClCompile Include=\"WindowFloatingUIBar.cpp\" />\n    <ClCompile Include=\"TextureManager.cpp\" />\n    <ClCompile Include=\"WindowKeyboardEditor.cpp\" />\n    <ClCompile Include=\"WindowOverlayBar.cpp\" />\n    <ClCompile Include=\"WindowOverlayProperties.cpp\" />\n    <ClCompile Include=\"WindowPerformance.cpp\" />\n    <ClCompile Include=\"WindowSettings.cpp\" />\n  </ItemGroup>\n  <ItemGroup>\n    <ClInclude Include=\"..\\Shared\\Actions.h\" />\n    <ClInclude Include=\"..\\Shared\\AppProfiles.h\" />\n    <ClInclude Include=\"..\\Shared\\ConfigManager.h\" />\n    <ClInclude Include=\"..\\Shared\\DPBrowserAPI.h\" />\n    <ClInclude Include=\"..\\Shared\\DPBrowserAPIClient.h\" />\n    <ClInclude Include=\"..\\Shared\\DPRect.h\" />\n    <ClInclude Include=\"..\\Shared\\Ini.h\" />\n    <ClInclude Include=\"..\\Shared\\InterprocessMessaging.h\" />\n    <ClInclude Include=\"..\\Shared\\Logging.h\" />\n    <ClInclude Include=\"..\\Shared\\loguru.hpp\" />\n    <ClInclude Include=\"..\\Shared\\Matrices.h\" />\n    <ClInclude Include=\"..\\Shared\\openvr.h\" />\n    <ClInclude Include=\"..\\Shared\\OpenVRExt.h\" />\n    <ClInclude Include=\"..\\Shared\\OverlayDragger.h\" />\n    <ClInclude Include=\"..\\Shared\\OverlayManager.h\" />\n    <ClInclude Include=\"..\\Shared\\Util.h\" />\n    <ClInclude Include=\"..\\Shared\\Vectors.h\" />\n    <ClInclude Include=\"..\\Shared\\WindowManager.h\" />\n    <ClInclude Include=\"AuxUI.h\" />\n    <ClInclude Include=\"FloatingWindow.h\" />\n    <ClInclude Include=\"FloatingUI.h\" />\n    <ClInclude Include=\"ImGuiExt.h\" />\n    <ClInclude Include=\"implot\\implot.h\" />\n    <ClInclude Include=\"implot\\implot_internal.h\" />\n    <ClInclude Include=\"NotificationIcon.h\" />\n    <ClInclude Include=\"TranslationManager.h\" />\n    <ClInclude Include=\"VRKeyboard.h\" />\n    <ClInclude Include=\"VRKeyboardCommon.h\" />\n    <ClInclude Include=\"Win32PerformanceData.h\" />\n    <ClInclude Include=\"resource.h\" />\n    <ClInclude Include=\"UIManager.h\" />\n    <ClInclude Include=\"WindowDesktopMode.h\" />\n    <ClInclude Include=\"WindowKeyboard.h\" />\n    <ClInclude Include=\"WindowFloatingUIBar.h\" />\n    <ClInclude Include=\"imgui\\imconfig.h\" />\n    <ClInclude Include=\"imgui\\imgui.h\" />\n    <ClInclude Include=\"imgui\\imgui_internal.h\" />\n    <ClInclude Include=\"imgui\\imstb_rectpack.h\" />\n    <ClInclude Include=\"imgui\\imstb_textedit.h\" />\n    <ClInclude Include=\"imgui\\imstb_truetype.h\" />\n    <ClInclude Include=\"imgui_win32_dx11_openvr\\imgui_impl_dx11_openvr.h\" />\n    <ClInclude Include=\"imgui_win32_dx11_openvr\\imgui_impl_win32_openvr.h\" />\n    <ClInclude Include=\"TextureManager.h\" />\n    <ClInclude Include=\"WindowKeyboardEditor.h\" />\n    <ClInclude Include=\"WindowOverlayBar.h\" />\n    <ClInclude Include=\"WindowOverlayProperties.h\" />\n    <ClInclude Include=\"WindowPerformance.h\" />\n    <ClInclude Include=\"WindowSettings.h\" />\n  </ItemGroup>\n  <ItemGroup>\n    <FxCompile Include=\"imgui_win32_dx11_openvr\\PixelShaderImGui.hlsl\">\n      <ShaderType Condition=\"'$(Configuration)|$(Platform)'=='Debug|x64'\">Pixel</ShaderType>\n      <ShaderModel Condition=\"'$(Configuration)|$(Platform)'=='Debug|x64'\">4.0_level_9_1</ShaderModel>\n      <ShaderType Condition=\"'$(Configuration)|$(Platform)'=='Release|x64'\">Pixel</ShaderType>\n      <HeaderFileOutput Condition=\"'$(Configuration)|$(Platform)'=='Debug|x64'\">$(OutDir)%(Filename).h</HeaderFileOutput>\n      <ObjectFileOutput Condition=\"'$(Configuration)|$(Platform)'=='Debug|x64'\">\n      </ObjectFileOutput>\n      <HeaderFileOutput Condition=\"'$(Configuration)|$(Platform)'=='Release|x64'\">$(OutDir)%(Filename).h</HeaderFileOutput>\n      <ObjectFileOutput Condition=\"'$(Configuration)|$(Platform)'=='Release|x64'\">\n      </ObjectFileOutput>\n      <EntryPointName Condition=\"'$(Configuration)|$(Platform)'=='Debug|x64'\">PS</EntryPointName>\n      <EntryPointName Condition=\"'$(Configuration)|$(Platform)'=='Release|x64'\">PS</EntryPointName>\n    </FxCompile>\n    <FxCompile Include=\"imgui_win32_dx11_openvr\\VertexShaderImGui.hlsl\">\n      <ShaderType Condition=\"'$(Configuration)|$(Platform)'=='Debug|x64'\">Vertex</ShaderType>\n      <ShaderType Condition=\"'$(Configuration)|$(Platform)'=='Release|x64'\">Vertex</ShaderType>\n      <ShaderModel Condition=\"'$(Configuration)|$(Platform)'=='Debug|x64'\">4.0_level_9_1</ShaderModel>\n      <HeaderFileOutput Condition=\"'$(Configuration)|$(Platform)'=='Debug|x64'\">$(OutDir)%(Filename).h</HeaderFileOutput>\n      <ObjectFileOutput Condition=\"'$(Configuration)|$(Platform)'=='Debug|x64'\">\n      </ObjectFileOutput>\n      <HeaderFileOutput Condition=\"'$(Configuration)|$(Platform)'=='Release|x64'\">$(OutDir)%(Filename).h</HeaderFileOutput>\n      <ObjectFileOutput Condition=\"'$(Configuration)|$(Platform)'=='Release|x64'\">\n      </ObjectFileOutput>\n      <EntryPointName Condition=\"'$(Configuration)|$(Platform)'=='Debug|x64'\">VS</EntryPointName>\n      <EntryPointName Condition=\"'$(Configuration)|$(Platform)'=='Release|x64'\">VS</EntryPointName>\n    </FxCompile>\n  </ItemGroup>\n  <ItemGroup>\n    <ResourceCompile Include=\"DesktopPlusUI.rc\" />\n  </ItemGroup>\n  <ItemGroup>\n    <Image Include=\"..\\Shared\\icon_desktop.ico\" />\n  </ItemGroup>\n  <Import Project=\"$(VCTargetsPath)\\Microsoft.Cpp.targets\" />\n  <ImportGroup Label=\"ExtensionTargets\">\n  </ImportGroup>\n</Project>"
  },
  {
    "path": "src/DesktopPlusUI/DesktopPlusUI.vcxproj.filters",
    "content": "﻿<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<Project ToolsVersion=\"4.0\" xmlns=\"http://schemas.microsoft.com/developer/msbuild/2003\">\n  <ItemGroup>\n    <Filter Include=\"ImGui\">\n      <UniqueIdentifier>{cf9e2ac6-b93d-4ce4-be2b-2dceec48ea9c}</UniqueIdentifier>\n    </Filter>\n    <Filter Include=\"ImGui Win32/DX11/OpenVR\">\n      <UniqueIdentifier>{02f38889-5b36-40a8-a60d-60d4016baec4}</UniqueIdentifier>\n    </Filter>\n    <Filter Include=\"Shared\">\n      <UniqueIdentifier>{2fac41b3-89b4-4cdf-9924-e4d9ae01e9e3}</UniqueIdentifier>\n    </Filter>\n    <Filter Include=\"ImPlot\">\n      <UniqueIdentifier>{accae70c-9225-413f-823d-dd068cbb1b5f}</UniqueIdentifier>\n    </Filter>\n  </ItemGroup>\n  <ItemGroup>\n    <ClCompile Include=\"imgui\\imgui_widgets.cpp\">\n      <Filter>ImGui</Filter>\n    </ClCompile>\n    <ClCompile Include=\"imgui\\imgui_draw.cpp\">\n      <Filter>ImGui</Filter>\n    </ClCompile>\n    <ClCompile Include=\"imgui\\imgui_demo.cpp\">\n      <Filter>ImGui</Filter>\n    </ClCompile>\n    <ClCompile Include=\"imgui\\imgui.cpp\">\n      <Filter>ImGui</Filter>\n    </ClCompile>\n    <ClCompile Include=\"DesktopPlusUI.cpp\" />\n    <ClCompile Include=\"imgui_win32_dx11_openvr\\imgui_impl_dx11_openvr.cpp\">\n      <Filter>ImGui Win32/DX11/OpenVR</Filter>\n    </ClCompile>\n    <ClCompile Include=\"imgui_win32_dx11_openvr\\imgui_impl_win32_openvr.cpp\">\n      <Filter>ImGui Win32/DX11/OpenVR</Filter>\n    </ClCompile>\n    <ClCompile Include=\"TextureManager.cpp\" />\n    <ClCompile Include=\"WindowFloatingUIBar.cpp\" />\n    <ClCompile Include=\"UIManager.cpp\" />\n    <ClCompile Include=\"..\\Shared\\Matrices.cpp\">\n      <Filter>Shared</Filter>\n    </ClCompile>\n    <ClCompile Include=\"..\\Shared\\InterprocessMessaging.cpp\">\n      <Filter>Shared</Filter>\n    </ClCompile>\n    <ClCompile Include=\"..\\Shared\\Util.cpp\">\n      <Filter>Shared</Filter>\n    </ClCompile>\n    <ClCompile Include=\"..\\Shared\\ConfigManager.cpp\">\n      <Filter>Shared</Filter>\n    </ClCompile>\n    <ClCompile Include=\"..\\Shared\\Actions.cpp\">\n      <Filter>Shared</Filter>\n    </ClCompile>\n    <ClCompile Include=\"ImGuiExt.cpp\" />\n    <ClCompile Include=\"..\\Shared\\Ini.cpp\">\n      <Filter>Shared</Filter>\n    </ClCompile>\n    <ClCompile Include=\"..\\Shared\\OverlayManager.cpp\">\n      <Filter>Shared</Filter>\n    </ClCompile>\n    <ClCompile Include=\"FloatingUI.cpp\" />\n    <ClCompile Include=\"WindowPerformance.cpp\" />\n    <ClCompile Include=\"implot\\implot_items.cpp\">\n      <Filter>ImPlot</Filter>\n    </ClCompile>\n    <ClCompile Include=\"Win32PerformanceData.cpp\" />\n    <ClCompile Include=\"implot\\implot.cpp\">\n      <Filter>ImPlot</Filter>\n    </ClCompile>\n    <ClCompile Include=\"NotificationIcon.cpp\" />\n    <ClCompile Include=\"imgui\\imgui_tables.cpp\">\n      <Filter>ImGui</Filter>\n    </ClCompile>\n    <ClCompile Include=\"WindowOverlayBar.cpp\" />\n    <ClCompile Include=\"WindowSettings.cpp\" />\n    <ClCompile Include=\"..\\Shared\\OverlayDragger.cpp\">\n      <Filter>Shared</Filter>\n    </ClCompile>\n    <ClCompile Include=\"FloatingWindow.cpp\" />\n    <ClCompile Include=\"WindowKeyboard.cpp\" />\n    <ClCompile Include=\"VRKeyboard.cpp\" />\n    <ClCompile Include=\"TranslationManager.cpp\" />\n    <ClCompile Include=\"WindowOverlayProperties.cpp\" />\n    <ClCompile Include=\"..\\Shared\\WindowManager.cpp\">\n      <Filter>Shared</Filter>\n    </ClCompile>\n    <ClCompile Include=\"AuxUI.cpp\" />\n    <ClCompile Include=\"..\\Shared\\DPBrowserAPIClient.cpp\">\n      <Filter>Shared</Filter>\n    </ClCompile>\n    <ClCompile Include=\"WindowDesktopMode.cpp\" />\n    <ClCompile Include=\"..\\Shared\\AppProfiles.cpp\">\n      <Filter>Shared</Filter>\n    </ClCompile>\n    <ClCompile Include=\"..\\Shared\\Logging.cpp\">\n      <Filter>Shared</Filter>\n    </ClCompile>\n    <ClCompile Include=\"..\\Shared\\loguru.cpp\">\n      <Filter>Shared</Filter>\n    </ClCompile>\n    <ClCompile Include=\"WindowKeyboardEditor.cpp\" />\n    <ClCompile Include=\"..\\Shared\\OpenVRExt.cpp\">\n      <Filter>Shared</Filter>\n    </ClCompile>\n  </ItemGroup>\n  <ItemGroup>\n    <ClInclude Include=\"imgui\\imstb_truetype.h\">\n      <Filter>ImGui</Filter>\n    </ClInclude>\n    <ClInclude Include=\"imgui\\imgui.h\">\n      <Filter>ImGui</Filter>\n    </ClInclude>\n    <ClInclude Include=\"imgui\\imgui_internal.h\">\n      <Filter>ImGui</Filter>\n    </ClInclude>\n    <ClInclude Include=\"imgui\\imstb_rectpack.h\">\n      <Filter>ImGui</Filter>\n    </ClInclude>\n    <ClInclude Include=\"imgui\\imstb_textedit.h\">\n      <Filter>ImGui</Filter>\n    </ClInclude>\n    <ClInclude Include=\"imgui\\imconfig.h\">\n      <Filter>ImGui</Filter>\n    </ClInclude>\n    <ClInclude Include=\"imgui_win32_dx11_openvr\\imgui_impl_win32_openvr.h\">\n      <Filter>ImGui Win32/DX11/OpenVR</Filter>\n    </ClInclude>\n    <ClInclude Include=\"imgui_win32_dx11_openvr\\imgui_impl_dx11_openvr.h\">\n      <Filter>ImGui Win32/DX11/OpenVR</Filter>\n    </ClInclude>\n    <ClInclude Include=\"TextureManager.h\" />\n    <ClInclude Include=\"WindowFloatingUIBar.h\" />\n    <ClInclude Include=\"UIManager.h\" />\n    <ClInclude Include=\"..\\Shared\\Vectors.h\">\n      <Filter>Shared</Filter>\n    </ClInclude>\n    <ClInclude Include=\"..\\Shared\\Matrices.h\">\n      <Filter>Shared</Filter>\n    </ClInclude>\n    <ClInclude Include=\"..\\Shared\\InterprocessMessaging.h\">\n      <Filter>Shared</Filter>\n    </ClInclude>\n    <ClInclude Include=\"..\\Shared\\Ini.h\">\n      <Filter>Shared</Filter>\n    </ClInclude>\n    <ClInclude Include=\"..\\Shared\\Util.h\">\n      <Filter>Shared</Filter>\n    </ClInclude>\n    <ClInclude Include=\"..\\Shared\\ConfigManager.h\">\n      <Filter>Shared</Filter>\n    </ClInclude>\n    <ClInclude Include=\"..\\Shared\\openvr.h\">\n      <Filter>Shared</Filter>\n    </ClInclude>\n    <ClInclude Include=\"ImGuiExt.h\" />\n    <ClInclude Include=\"..\\Shared\\Actions.h\">\n      <Filter>Shared</Filter>\n    </ClInclude>\n    <ClInclude Include=\"resource.h\" />\n    <ClInclude Include=\"..\\Shared\\DPRect.h\">\n      <Filter>Shared</Filter>\n    </ClInclude>\n    <ClInclude Include=\"..\\Shared\\OverlayManager.h\">\n      <Filter>Shared</Filter>\n    </ClInclude>\n    <ClInclude Include=\"FloatingUI.h\" />\n    <ClInclude Include=\"WindowPerformance.h\" />\n    <ClInclude Include=\"implot\\implot.h\">\n      <Filter>ImPlot</Filter>\n    </ClInclude>\n    <ClInclude Include=\"implot\\implot_internal.h\">\n      <Filter>ImPlot</Filter>\n    </ClInclude>\n    <ClInclude Include=\"Win32PerformanceData.h\" />\n    <ClInclude Include=\"NotificationIcon.h\" />\n    <ClInclude Include=\"WindowOverlayBar.h\" />\n    <ClInclude Include=\"WindowSettings.h\" />\n    <ClInclude Include=\"..\\Shared\\OverlayDragger.h\">\n      <Filter>Shared</Filter>\n    </ClInclude>\n    <ClInclude Include=\"FloatingWindow.h\" />\n    <ClInclude Include=\"WindowKeyboard.h\" />\n    <ClInclude Include=\"VRKeyboard.h\" />\n    <ClInclude Include=\"TranslationManager.h\" />\n    <ClInclude Include=\"WindowOverlayProperties.h\" />\n    <ClInclude Include=\"..\\Shared\\WindowManager.h\">\n      <Filter>Shared</Filter>\n    </ClInclude>\n    <ClInclude Include=\"AuxUI.h\" />\n    <ClInclude Include=\"..\\Shared\\DPBrowserAPI.h\">\n      <Filter>Shared</Filter>\n    </ClInclude>\n    <ClInclude Include=\"..\\Shared\\DPBrowserAPIClient.h\">\n      <Filter>Shared</Filter>\n    </ClInclude>\n    <ClInclude Include=\"WindowDesktopMode.h\" />\n    <ClInclude Include=\"..\\Shared\\AppProfiles.h\">\n      <Filter>Shared</Filter>\n    </ClInclude>\n    <ClInclude Include=\"..\\Shared\\Logging.h\">\n      <Filter>Shared</Filter>\n    </ClInclude>\n    <ClInclude Include=\"..\\Shared\\loguru.hpp\">\n      <Filter>Shared</Filter>\n    </ClInclude>\n    <ClInclude Include=\"WindowKeyboardEditor.h\" />\n    <ClInclude Include=\"VRKeyboardCommon.h\" />\n    <ClInclude Include=\"..\\Shared\\OpenVRExt.h\">\n      <Filter>Shared</Filter>\n    </ClInclude>\n  </ItemGroup>\n  <ItemGroup>\n    <FxCompile Include=\"imgui_win32_dx11_openvr\\PixelShaderImGui.hlsl\">\n      <Filter>ImGui Win32/DX11/OpenVR</Filter>\n    </FxCompile>\n    <FxCompile Include=\"imgui_win32_dx11_openvr\\VertexShaderImGui.hlsl\">\n      <Filter>ImGui Win32/DX11/OpenVR</Filter>\n    </FxCompile>\n  </ItemGroup>\n  <ItemGroup>\n    <ResourceCompile Include=\"DesktopPlusUI.rc\" />\n  </ItemGroup>\n  <ItemGroup>\n    <Image Include=\"..\\Shared\\icon_desktop.ico\" />\n  </ItemGroup>\n</Project>"
  },
  {
    "path": "src/DesktopPlusUI/DesktopPlusUI.vcxproj.user",
    "content": "﻿<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<Project ToolsVersion=\"14.0\" xmlns=\"http://schemas.microsoft.com/developer/msbuild/2003\">\n  <PropertyGroup>\n    <ShowAllFiles>false</ShowAllFiles>\n  </PropertyGroup>\n  <PropertyGroup Condition=\"'$(Configuration)|$(Platform)'=='Debug|x64'\">\n    <LocalDebuggerWorkingDirectory>$(OutputPath)</LocalDebuggerWorkingDirectory>\n    <DebuggerFlavor>WindowsLocalDebugger</DebuggerFlavor>\n    <LocalDebuggerCommandArguments>-DesktopMode</LocalDebuggerCommandArguments>\n  </PropertyGroup>\n  <PropertyGroup Condition=\"'$(Configuration)|$(Platform)'=='Release|x64'\">\n    <LocalDebuggerWorkingDirectory>$(OutputPath)</LocalDebuggerWorkingDirectory>\n    <DebuggerFlavor>WindowsLocalDebugger</DebuggerFlavor>\n    <LocalDebuggerCommandArguments>\n    </LocalDebuggerCommandArguments>\n  </PropertyGroup>\n</Project>"
  },
  {
    "path": "src/DesktopPlusUI/FloatingUI.cpp",
    "content": "#include \"FloatingUI.h\"\n\n#include \"UIManager.h\"\n#include \"OverlayManager.h\"\n#include \"InterprocessMessaging.h\"\n#include \"Util.h\"\n#include \"OpenVRExt.h\"\n\nFloatingUI::FloatingUI() : m_OvrlHandleCurrentUITarget(vr::k_ulOverlayHandleInvalid),\n                           m_OvrlIDCurrentUITarget(0),\n                           m_Width(1.0f),\n                           m_Alpha(0.0f),\n                           m_Visible(false),\n                           m_IsSwitchingTarget(false),\n                           m_FadeOutDelayCount(0.0f),\n                           m_AutoFitFrames(0),\n                           m_TheaterOffsetAnimationProgress(0.0f)\n{\n\n}\n\nvoid FloatingUI::Update()\n{\n    if ( (m_Alpha != 0.0f) || (m_Visible) )\n    {\n        vr::VROverlayHandle_t ovrl_handle_floating_ui = UIManager::Get()->GetOverlayHandleFloatingUI();\n\n        if ( (m_Alpha == 0.0f) && (m_AutoFitFrames == 0) ) //Overlay was hidden before\n        {\n            vr::VROverlay()->ShowOverlay(ovrl_handle_floating_ui);\n\n            OverlayConfigData& overlay_data = OverlayManager::Get().GetConfigData(m_OvrlIDCurrentUITarget);\n\n            //Instantly adjust action bar visibility to overlay state before fading in\n            if (overlay_data.ConfigBool[configid_bool_overlay_actionbar_enabled])\n            {\n                m_WindowActionBar.Show(true);\n            }\n            else\n            {\n                m_WindowActionBar.Hide(true);\n            }\n\n            //Give ImGui its two auto-fit frames it typically needs to rearrange widgets properly should they have changed from the last time FloatingUI was visible\n            m_AutoFitFrames = 2;\n        }\n\n        //Alpha fade animation\n        if ( (!UIManager::Get()->GetRepeatFrame()) && (m_AutoFitFrames == 0) )\n        {\n            const float alpha_step = ImGui::GetIO().DeltaTime * 6.0f;\n\n            m_Alpha += (m_Visible) ? alpha_step : -alpha_step;\n        }\n\n        if (m_Alpha > 1.0f)\n            m_Alpha = 1.0f;\n        else if (m_Alpha < 0.0f)\n            m_Alpha = 0.0f;\n\n        vr::VROverlay()->SetOverlayAlpha(ovrl_handle_floating_ui, m_Alpha);\n\n        if ( (m_Alpha == 0.0f) && (m_AutoFitFrames == 0) ) //Overlay was visible before\n        {\n            vr::VROverlay()->HideOverlay(ovrl_handle_floating_ui);\n            m_WindowActionBar.Hide(true);\n            //In case we were switching targets, reset switching state and target overlay\n            m_IsSwitchingTarget = false;\n            m_OvrlHandleCurrentUITarget = vr::k_ulOverlayHandleInvalid;\n            m_OvrlIDCurrentUITarget = 0;\n\n            //Request sync if drag-mode is still active while the UI is disappearing\n            if (ConfigManager::GetValue(configid_bool_state_overlay_dragmode))\n            {\n                IPCManager::Get().PostMessageToDashboardApp(ipcmsg_action, ipcact_overlay_transform_sync, -1);\n            }\n        }\n        else if (m_Visible) //Make sure the input method is always set when visible, even after partial fade-out\n        {\n            if (!ConfigManager::GetValue(configid_bool_state_overlay_dragmode_temp))\n            {\n                vr::VROverlay()->SetOverlayInputMethod(ovrl_handle_floating_ui, vr::VROverlayInputMethod_Mouse);\n            }\n        }\n    }\n\n    if ( (m_Alpha != 0.0f) || (m_AutoFitFrames != 0) )\n    {\n        OverlayConfigData& overlay_data = OverlayManager::Get().GetConfigData(m_OvrlIDCurrentUITarget);\n\n        //If action bar state was changed\n        if (overlay_data.ConfigBool[configid_bool_overlay_actionbar_enabled])\n        {\n            m_WindowActionBar.Show();\n        }\n        else\n        {\n            m_WindowActionBar.Hide();\n        }\n\n        m_WindowActionBar.Update(m_OvrlIDCurrentUITarget);\n        m_WindowMainBar.Update(m_WindowActionBar.GetSize().y, m_OvrlIDCurrentUITarget);\n        m_WindowOverlayStats.Update(m_WindowMainBar, m_WindowActionBar, m_OvrlIDCurrentUITarget);\n\n        if (m_AutoFitFrames > 0)\n        {\n            m_AutoFitFrames--;\n            UIManager::Get()->RepeatFrame();\n\n            if (m_AutoFitFrames == 0)\n            {\n                m_Alpha += ImGui::GetIO().DeltaTime * 6.0f;\n            }\n        }\n    }\n}\n\nvoid FloatingUI::UpdateUITargetState()\n{\n    UIManager& ui_manager = *UIManager::Get();\n    vr::VROverlayHandle_t ovrl_handle_floating_ui = ui_manager.GetOverlayHandleFloatingUI();\n\n    //Find which overlay is being hovered\n    vr::VROverlayHandle_t ovrl_handle_hover_target = vr::k_ulOverlayHandleInvalid;\n    unsigned int ovrl_id_hover_target = k_ulOverlayID_None;\n\n    //Also find primary dashboard overlay as fallback to always display when available\n    vr::VROverlayHandle_t ovrl_handle_primary_dashboard = vr::k_ulOverlayHandleInvalid;\n    unsigned int ovrl_id_primary_dashboard = k_ulOverlayID_None;\n\n    const bool has_pointer_device = (ConfigManager::Get().GetPrimaryLaserPointerDevice() != vr::k_unTrackedDeviceIndexInvalid);\n\n    //If previous target overlay is no longer visible\n    if ( (m_OvrlHandleCurrentUITarget != vr::k_ulOverlayHandleInvalid) && (!vr::VROverlay()->IsOverlayVisible(m_OvrlHandleCurrentUITarget)) )\n    {\n        m_FadeOutDelayCount = 100.0f;\n\n        //Disable input so the pointer will no longer hit the UI window\n        vr::VROverlay()->SetOverlayInputMethod(ovrl_handle_floating_ui, vr::VROverlayInputMethod_None);\n    }\n    else if ( (has_pointer_device) && (ConfigManager::Get().IsLaserPointerTargetOverlay(ovrl_handle_floating_ui)) )  //Use as target Floating UI if it's hovered\n    {\n        ovrl_handle_hover_target = ovrl_handle_floating_ui;\n    }\n\n    //Don't show while reordering overlays since config changes may cause the UI to jump around while doing that\n    if ( (ovrl_handle_hover_target == vr::k_ulOverlayHandleInvalid) && (!ConfigManager::GetValue(configid_bool_state_overlay_dragmode_temp)) &&\n         (!UIManager::Get()->GetOverlayBarWindow().IsDraggingOverlayButtons()) )\n    {\n        if (has_pointer_device)\n        {\n            for (unsigned int i = 0; i < OverlayManager::Get().GetOverlayCount(); ++i)\n            {\n                const OverlayConfigData& data = OverlayManager::Get().GetConfigData(i);\n                vr::VROverlayHandle_t ovrl_handle = data.ConfigHandle[configid_handle_overlay_state_overlay_handle];\n\n                if (ovrl_handle != vr::k_ulOverlayHandleInvalid)\n                {\n                    //Check if this is the primary dashboard overlay\n                    if ( (ovrl_id_primary_dashboard == k_ulOverlayID_None) && (data.ConfigInt[configid_int_overlay_origin] == ovrl_origin_dashboard) && \n                         (data.ConfigInt[configid_int_overlay_display_mode] != ovrl_dispmode_scene) )\n                    {\n                        //First dashboard origin with non-scene display mode is considered to be the primary dashboard overlay, but only really use it if enabled with FloatingUI on and in dashboard\n                        if ( (data.ConfigBool[configid_bool_overlay_enabled]) && (data.ConfigBool[configid_bool_overlay_floatingui_enabled]) && (UIManager::Get()->IsOverlayBarOverlayVisible()) && \n                            (vr::VROverlay()->IsOverlayVisible(ovrl_handle)) )\n                        {\n                            ovrl_handle_primary_dashboard = ovrl_handle;\n                            ovrl_id_primary_dashboard = i;\n                        }\n                    }\n\n                    //Check if this the hover target overlay\n                    if ( (data.ConfigBool[configid_bool_overlay_floatingui_enabled]) && (ConfigManager::Get().IsLaserPointerTargetOverlay(ovrl_handle)) )\n                    {\n                        ovrl_handle_hover_target = ovrl_handle;\n                        ovrl_id_hover_target = i;\n\n                        //Break here unless we still need to find the primary dashboard overlay\n                        if (ovrl_id_primary_dashboard != k_ulOverlayID_None)\n                        {\n                            break;\n                        }\n                    }\n                }\n            }\n\n            //Use fallback primary dashboard overlay if no hover target was found\n            if ( (ovrl_handle_hover_target == vr::k_ulOverlayHandleInvalid) && (m_FadeOutDelayCount == 0) &&\n                 ((m_OvrlHandleCurrentUITarget == vr::k_ulOverlayHandleInvalid) || (m_OvrlHandleCurrentUITarget == ovrl_handle_primary_dashboard)) )\n            {\n                ovrl_handle_hover_target = ovrl_handle_primary_dashboard;\n                ovrl_id_hover_target = ovrl_id_primary_dashboard;\n            }\n        }\n    }\n\n    bool is_newly_visible = false;\n\n    //Check if we're switching from another active overlay hover target, in which case we want to fade out completely first\n    if ( (m_OvrlHandleCurrentUITarget != vr::k_ulOverlayHandleInvalid) && (ovrl_handle_hover_target != vr::k_ulOverlayHandleInvalid) && (ovrl_handle_hover_target != ovrl_handle_floating_ui) && \n         (ovrl_handle_hover_target != m_OvrlHandleCurrentUITarget) )\n    {\n        m_IsSwitchingTarget = true;\n        m_Visible = false;\n\n        UIManager::Get()->GetIdleState().AddActiveTime();\n        return;\n    }\n    else if ( (ovrl_handle_hover_target != ovrl_handle_floating_ui) && (ovrl_handle_hover_target != vr::k_ulOverlayHandleInvalid) )\n    {\n        if ( (!m_Visible) && (m_Alpha == 0.0f) )\n        {\n            is_newly_visible = true;\n            UIManager::Get()->GetIdleState().AddActiveTime();\n        }\n\n        m_OvrlHandleCurrentUITarget = ovrl_handle_hover_target;\n        m_OvrlIDCurrentUITarget = ovrl_id_hover_target;\n        m_Visible = true;\n    }\n\n    //If there is an active hover target overlay, position the floating UI\n    if (m_OvrlHandleCurrentUITarget != vr::k_ulOverlayHandleInvalid)\n    {\n        OverlayConfigData& overlay_data = OverlayManager::Get().GetConfigData(m_OvrlIDCurrentUITarget);\n        Matrix4 matrix = OverlayManager::Get().GetOverlayCenterBottomTransform(m_OvrlIDCurrentUITarget, m_OvrlHandleCurrentUITarget);\n\n        //If the Floating UI is just appearing, adjust overlay size based on the distance between HMD and overlay\n        if (is_newly_visible)\n        {\n            bool use_fixed_size = false;\n\n            //Use fixed size when using primary dashboard overlay fallback and distance to dashboard is lower than 0.25m\n            if (ovrl_handle_primary_dashboard == m_OvrlHandleCurrentUITarget)\n            {\n                //Use relative transform data here as dashboard transform can be unreliable during launch\n                const float distance = overlay_data.ConfigTransform.getTranslation().distance({0.0f, 0.0f, 0.0f});\n                use_fixed_size = (distance < 0.25f);\n\n                m_Width = 1.2f;\n                vr::VROverlay()->SetOverlayWidthInMeters(ovrl_handle_floating_ui, m_Width);\n            }\n            else if (overlay_data.ConfigInt[configid_int_overlay_origin] == ovrl_origin_theater_screen) //Fixed size for theater screen too\n            {\n                m_Width = 3.0f;\n                vr::VROverlay()->SetOverlayWidthInMeters(ovrl_handle_floating_ui, m_Width);\n            }\n            else\n            {\n                vr::TrackedDevicePose_t poses[vr::k_unTrackedDeviceIndex_Hmd + 1];\n                vr::VRSystem()->GetDeviceToAbsoluteTrackingPose(vr::TrackingUniverseStanding, vr::IVRSystemEx::GetTimeNowToPhotons(), poses, vr::k_unTrackedDeviceIndex_Hmd + 1);\n\n                if (poses[vr::k_unTrackedDeviceIndex_Hmd].bPoseIsValid)\n                {\n                    Matrix4 mat_hmd = poses[vr::k_unTrackedDeviceIndex_Hmd].mDeviceToAbsoluteTracking;\n                    float distance = matrix.getTranslation().distance(mat_hmd.getTranslation());\n\n                    m_Width = 0.66f + (0.5f * distance);\n                    vr::VROverlay()->SetOverlayWidthInMeters(ovrl_handle_floating_ui, m_Width);\n                }\n            }\n        }\n\n        //Move down by Floating UI height\n        const DPRect& rect_floating_ui = UITextureSpaces::Get().GetRect(ui_texspace_floating_ui);\n        const float floating_ui_height_m = m_Width * ((float)rect_floating_ui.GetHeight() / (float)rect_floating_ui.GetWidth());\n\n        matrix.translate_relative(0.0f, -floating_ui_height_m / 3.0f, 0.0f);\n\n        //Additional offset for theater screen\n        if (overlay_data.ConfigInt[configid_int_overlay_origin] == ovrl_origin_theater_screen) \n        {\n            //SteamVR's control bar is only shown while system laser pointer is active, so only add offset if that's the case\n            const bool add_offset = vr::IVROverlayEx::IsSystemLaserPointerActive();\n\n            if (is_newly_visible)\n            {\n                m_TheaterOffsetAnimationProgress = (add_offset) ? 1.0f : 0.0f;\n            }\n            else //Also animate this\n            {\n                const float time_step = ImGui::GetIO().DeltaTime * 6.0f;\n                m_TheaterOffsetAnimationProgress += (add_offset) ? time_step : -time_step;\n\n                if (m_TheaterOffsetAnimationProgress > 1.0f)\n                    m_TheaterOffsetAnimationProgress = 1.0f;\n                else if (m_TheaterOffsetAnimationProgress < 0.0f)\n                    m_TheaterOffsetAnimationProgress = 0.0f;\n            }\n\n            matrix.translate_relative(0.0f, smoothstep(m_TheaterOffsetAnimationProgress, 0.0f, -0.29f), 0.0f);\n        }\n\n        //Don't update position if dummy transform is unstable unless it's target is not primary dashboard overlay or we're newly appearing\n        if ( (is_newly_visible) || (!UIManager::Get()->IsDummyOverlayTransformUnstable()) || (ovrl_id_hover_target != ovrl_id_primary_dashboard) )\n        {\n            //Only set transform and add active time if we actually need to move the overlay\n            if (matrix != m_TransformLast)\n            {\n                vr::HmdMatrix34_t hmd_matrix = matrix.toOpenVR34();\n                vr::VROverlay()->SetOverlayTransformAbsolute(ovrl_handle_floating_ui, vr::TrackingUniverseStanding, &hmd_matrix);\n\n                UIManager::Get()->GetIdleState().AddActiveTime(100);\n                m_TransformLast = matrix;\n            }\n        }\n\n        //Set floating UI curvature based on target overlay curvature\n        if (overlay_data.ConfigInt[configid_int_overlay_origin] == ovrl_origin_theater_screen)\n        {\n            //Set curvature to 0 in this case, as theater screen curve is controlled by SteamVR and not query-able)\n            vr::VROverlay()->SetOverlayCurvature(ovrl_handle_floating_ui, 0.0f);\n        }\n        else\n        {\n            float curvature = 0.0f;\n            vr::VROverlay()->GetOverlayCurvature(m_OvrlHandleCurrentUITarget, &curvature);\n            float overlay_width = overlay_data.ConfigFloat[configid_float_overlay_width];\n\n            vr::VROverlay()->SetOverlayCurvature(ovrl_handle_floating_ui, curvature * (m_Width / overlay_width) );\n        }\n    }\n\n    if ( (ovrl_handle_hover_target == vr::k_ulOverlayHandleInvalid) || (m_OvrlHandleCurrentUITarget == vr::k_ulOverlayHandleInvalid) ) //If not even the UI itself is being hovered, fade out\n    {\n        if (m_Visible)\n        {\n            //Don't fade out if this is the theater screen overlay and the systemui is hovered\n            //This does have false positives when really trying (systemui is used for many things), but it's better not to fade out when SteamVR's overlay controls are hovered\n            bool blocked_by_systemui = false;\n            if (m_OvrlHandleCurrentUITarget != vr::k_ulOverlayHandleInvalid)\n            {\n                vr::VROverlayHandle_t ovrl_handle_systemui;\n                vr::VROverlay()->FindOverlay(\"system.systemui\", &ovrl_handle_systemui);\n\n                if (ovrl_handle_systemui != vr::k_ulOverlayHandleInvalid)\n                {\n                    const OverlayConfigData& overlay_data = OverlayManager::Get().GetConfigData(m_OvrlIDCurrentUITarget);\n                    if (overlay_data.ConfigInt[configid_int_overlay_origin] == ovrl_origin_theater_screen)\n                    {\n                        blocked_by_systemui = ConfigManager::Get().IsLaserPointerTargetOverlay(ovrl_handle_systemui);\n                    }\n                }\n            }\n\n            if (!blocked_by_systemui)\n            {\n                m_FadeOutDelayCount += ImGui::GetIO().DeltaTime;\n\n                //Delay normal fade in order to not flicker when switching hover target between mirror overlay and floating UI (or don't while reordering overlays)\n                if ((m_FadeOutDelayCount > 0.8f) || (UIManager::Get()->GetOverlayBarWindow().IsDraggingOverlayButtons()))\n                {\n                    //Hide\n                    m_Visible = false;\n                    m_FadeOutDelayCount = 0.0f;\n                }\n            }\n\n            UIManager::Get()->GetIdleState().AddActiveTime();\n        }\n        else if (m_Alpha == 0.0f)\n        {\n            m_FadeOutDelayCount = 0.0f;\n        }\n    }\n    else\n    {\n        m_Visible = true;\n    }\n\n    //Update config state if it changed. This state is only set if the Floating UI itself is the hover target\n    const int target_overlay_id_new = ((m_OvrlIDCurrentUITarget != k_ulOverlayID_None) && (ovrl_handle_hover_target == ovrl_handle_floating_ui)) ? (int)m_OvrlIDCurrentUITarget : -1;\n    int& target_overlay_id_config = ConfigManager::GetRef(configid_int_state_interface_floating_ui_hovered_id);\n    if (target_overlay_id_config != target_overlay_id_new)\n    {\n        target_overlay_id_config = target_overlay_id_new;\n        IPCManager::Get().PostConfigMessageToDashboardApp(configid_int_state_interface_floating_ui_hovered_id, target_overlay_id_config);\n    }\n}\n\nbool FloatingUI::IsVisible() const\n{\n    return ((m_Visible) || (m_Alpha != 0.0f));\n}\n\nfloat FloatingUI::GetAlpha() const\n{\n    return m_Alpha;\n}\n\nWindowFloatingUIMainBar& FloatingUI::GetMainBarWindow()\n{\n    return m_WindowMainBar;\n}\n\nWindowFloatingUIActionBar& FloatingUI::GetActionBarWindow()\n{\n    return m_WindowActionBar;\n}\n"
  },
  {
    "path": "src/DesktopPlusUI/FloatingUI.h",
    "content": "#pragma once\n\n#include \"WindowFloatingUIBar.h\"\n\n#include \"openvr.h\"\n#include \"Matrices.h\"\n\nclass FloatingUI\n{\n    private:\n        WindowFloatingUIMainBar m_WindowMainBar;\n        WindowFloatingUIActionBar m_WindowActionBar;\n        WindowFloatingUIOverlayStats m_WindowOverlayStats;\n\n        vr::VROverlayHandle_t m_OvrlHandleCurrentUITarget;\n        unsigned int m_OvrlIDCurrentUITarget;\n\n        float m_Width;\n        float m_Alpha;\n        bool m_Visible;\n        bool m_IsSwitchingTarget;\n        float m_FadeOutDelayCount;\n        int m_AutoFitFrames;\n        Matrix4 m_TransformLast;\n\n        float m_TheaterOffsetAnimationProgress;\n\n    public:\n        FloatingUI();\n        void Update();\n        void UpdateUITargetState();\n        bool IsVisible() const;\n        float GetAlpha() const;\n\n        WindowFloatingUIMainBar& GetMainBarWindow();\n        WindowFloatingUIActionBar& GetActionBarWindow();\n};"
  },
  {
    "path": "src/DesktopPlusUI/FloatingWindow.cpp",
    "content": "#include \"FloatingWindow.h\"\n\n#include \"UIManager.h\"\n#include \"OverlayManager.h\"\n#include \"InterprocessMessaging.h\"\n#include \"Util.h\"\n#include \"OpenVRExt.h\"\n#include \"ImGuiExt.h\"\n#include \"imgui_internal.h\"\n\nFloatingWindow::FloatingWindow() : m_OvrlWidth(1.0f),\n                                   m_OvrlWidthMax(FLT_MAX),\n                                   m_Alpha(0.0f),\n                                   m_OvrlVisible(false),\n                                   m_IsTransitionFading(false),\n                                   m_OverlayStateCurrentID(floating_window_ovrl_state_dashboard_tab),\n                                   m_OverlayStateCurrent(&m_OverlayStateDashboardTab),\n                                   m_OverlayStatePending(&m_OverlayStateFading),\n                                   m_WindowTitleStrID(tstr_NONE),\n                                   m_WindowIcon(tmtex_icon_xsmall_desktop_none),\n                                   m_WindowIconWin32IconCacheID(-1),\n                                   m_AllowRoomUnpinning(false),\n                                   m_DragOrigin(ovrl_origin_dplus_tab),\n                                   m_TitleBarMinWidth(64.0f),\n                                   m_TitleBarTitleMaxWidth(-1.0f),\n                                   m_TitleBarTitleIconAlpha(1.0f),\n                                   m_IsTitleBarHovered(false),\n                                   m_IsTitleIconClicked(false),\n                                   m_HasAppearedOnce(false),\n                                   m_IsWindowAppearing(false),\n                                   m_CompactTableHeaderHeight(0.0f)\n{\n    m_Pos.x = FLT_MIN;\n    m_WindowFlags = ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoBringToFrontOnFocus;\n    m_WindowID = \"###\" + std::to_string((unsigned long long)this);\n\n    m_OverlayStateDashboardTab.TransformAbs.zero();\n    m_OverlayStateRoom.TransformAbs.zero();\n}\n\nvoid FloatingWindow::WindowUpdateBase()\n{\n    if ( ((!UIManager::Get()->GetRepeatFrame()) || (m_Alpha == 0.0f)) && ((m_Alpha != 0.0f) || (m_OverlayStateCurrent->IsVisible) || (m_IsTransitionFading)) )\n    {\n        const float alpha_prev = m_Alpha;\n        const float alpha_step = ImGui::GetIO().DeltaTime * 6.0f;\n\n        //Alpha fade animation\n        m_Alpha += ((m_OverlayStateCurrent->IsVisible) && (!m_IsTransitionFading)) ? alpha_step : -alpha_step;\n\n        if (m_Alpha > 1.0f)\n            m_Alpha = 1.0f;\n        else if (m_Alpha < 0.0f)\n            m_Alpha = 0.0f;\n\n        //Use overlay alpha when not in desktop mode for better blending\n        if ( (!UIManager::Get()->IsInDesktopMode()) && (alpha_prev != m_Alpha) )\n            vr::VROverlay()->SetOverlayAlpha(GetOverlayHandle(), m_Alpha);\n\n        if (m_Alpha == 0.0f)\n        {\n            //Not the best spot, but it can be difficult to cancel an active overlay hightlight when disappearing since the window code isn't running anymore\n            //So instead we always cancel the current highlight here, which shouldn't be problematic\n            UIManager::Get()->HighlightOverlay(k_ulOverlayID_None);\n\n            //Finish transition fade if one's active\n            if (m_IsTransitionFading)\n            {\n                OverlayStateSwitchFinish();\n            }\n        }\n    }\n\n    if (m_Alpha == 0.0f)\n        return;\n\n    if (UIManager::Get()->IsInDesktopMode())\n        ImGui::PushStyleVar(ImGuiStyleVar_Alpha, m_Alpha);\n\n    ImGuiIO& io = ImGui::GetIO();\n    ImGuiStyle& style = ImGui::GetStyle();\n\n    if (m_Pos.x == FLT_MIN)\n    {\n        //Expects m_Size to be padded 2 pixels around actual texture space, so -4 on each axis \n        m_Pos = {2.0f, (io.DisplaySize.y - m_Size.y) - 2.0f};\n    }\n\n    ImGui::SetNextWindowPos(m_Pos, ImGuiCond_Always, m_PosPivot);\n    ImGui::SetNextWindowSizeConstraints({m_TitleBarMinWidth, 4.0f}, m_Size);\n    ImGui::SetNextWindowScroll({0.0f, -1.0f}); //Prevent real horizontal scrolling from happening\n\n    ImGuiWindowFlags flags = m_WindowFlags;\n\n    if (!m_OverlayStateCurrent->IsVisible)\n        flags |= ImGuiWindowFlags_NoInputs;\n\n    ImGui::Begin(m_WindowID.c_str(), nullptr, flags);\n\n    m_IsTitleBarHovered = ((ImGui::IsItemHovered()) && (m_OverlayStateCurrent->IsVisible)); //Current item is the title bar (needs to be checked before BeginTitleBar())\n    m_IsWindowAppearing = ImGui::IsWindowAppearing();\n\n    //Title bar\n    ImVec4 title_rect = ImGui::BeginTitleBar();\n\n    //Icon and title text\n    ImVec2 img_size_line_height = {ImGui::GetTextLineHeight(), ImGui::GetTextLineHeight()};\n    ImVec2 img_size, img_uv_min, img_uv_max;\n\n    if (m_WindowIconWin32IconCacheID == -1)\n    {\n        TextureManager::Get().GetTextureInfo(m_WindowIcon, img_size, img_uv_min, img_uv_max);\n    }\n    else\n    {\n        TextureManager::Get().GetWindowIconTextureInfo(m_WindowIconWin32IconCacheID, img_size, img_uv_min, img_uv_max);\n    }\n\n    ImGui::PushStyleVar(ImGuiStyleVar_Alpha, m_TitleBarTitleIconAlpha);\n\n    ImGui::Image(io.Fonts->TexID, img_size_line_height, img_uv_min, img_uv_max);\n    m_IsTitleIconClicked = ImGui::IsItemClicked();\n\n    ImGui::SameLine(0.0f, style.ItemInnerSpacing.x);\n\n    ImVec2 clip_end = ImGui::GetCursorScreenPos();\n    clip_end.x += m_TitleBarTitleMaxWidth;\n    clip_end.y += ImGui::GetFrameHeight();\n\n    ImGui::PushClipRect(ImGui::GetCursorScreenPos(), clip_end, true);\n    ImGui::TextUnformatted( (m_WindowTitleStrID == tstr_NONE) ? m_WindowTitle.c_str() : TranslationManager::GetString(m_WindowTitleStrID) );\n    ImGui::PopClipRect();\n\n    ImGui::PopStyleVar();\n\n    float title_text_width = ImGui::GetItemRectSize().x;\n\n    //Right end of title bar\n    ImGui::PushStyleColor(ImGuiCol_Button, 0);\n    static float b_width = 0.0f;\n\n    ImGui::SetCursorScreenPos({title_rect.z - b_width - style.ItemInnerSpacing.x, title_rect.y + style.FramePadding.y});\n\n    //Buttons\n    TextureManager::Get().GetTextureInfo((m_OverlayStateCurrent->IsPinned) ? tmtex_icon_xxsmall_unpin : tmtex_icon_xxsmall_pin, img_size, img_uv_min, img_uv_max);\n\n    ImGui::BeginGroup();\n\n    const bool disable_pinning = ( (!m_AllowRoomUnpinning) && (m_OverlayStateCurrentID == floating_window_ovrl_state_room) );\n\n    if (disable_pinning)\n        ImGui::PushItemDisabled();\n\n    if (ImGui::ImageButton(\"PinButton\", io.Fonts->TexID, img_size, img_uv_min, img_uv_max, ImVec4(0.0f, 0.0f, 0.0f, 0.0f)))\n    {\n        SetPinned(!IsPinned());\n        OnWindowPinButtonPressed();\n    }\n    ImGui::SameLine(0.0f, 0.0f);\n\n    if (disable_pinning)\n        ImGui::PopItemDisabled();\n\n    TextureManager::Get().GetTextureInfo(tmtex_icon_xxsmall_close, img_size, img_uv_min, img_uv_max);\n\n    if (ImGui::ImageButton(\"CloseButton\", io.Fonts->TexID, img_size, img_uv_min, img_uv_max, ImVec4(0.0f, 0.0f, 0.0f, 0.0f)))\n    {\n        Hide();\n        OnWindowCloseButtonPressed();\n    }\n\n    ImGui::EndGroup();\n\n    m_IsTitleBarHovered = ( (m_IsTitleBarHovered) && (!ImGui::IsItemHovered()) ); //Title was hovered and no title bar element is hovered\n    b_width = ImGui::GetItemRectSize().x;\n\n    ImGui::PopStyleColor();\n\n    //Calculate title bar constraints\n    m_TitleBarMinWidth = img_size_line_height.x + b_width + (style.ItemSpacing.x * 2.0f);\n    m_TitleBarTitleMaxWidth = ImGui::GetWindowSize().x - m_TitleBarMinWidth;\n    m_TitleBarMinWidth += style.ItemSpacing.x * 2.0f;\n\n    //Shorten title bar string if it doesn't fit (this is destructive, but doesn't matter for the windows using this)\n    if ((m_WindowTitleStrID == tstr_NONE) && (title_text_width > std::max(ImGui::GetFontSize(), m_TitleBarTitleMaxWidth) ))\n    {\n        //Don't attempt to shorten the string during repeat frames as the size can still be adjusting in corner cases and throw us into a loop\n        if (!UIManager::Get()->GetRepeatFrame())\n        {\n            m_WindowTitle = ImGui::StringEllipsis(m_WindowTitle.c_str(), m_TitleBarTitleMaxWidth);\n\n            //Repeat frame to not make title shortening visible\n            UIManager::Get()->RepeatFrame();\n        }\n    }\n\n    //Title bar dragging\n    if ( (m_OverlayStateCurrent->IsVisible) && (m_IsTitleBarHovered) && (UIManager::Get()->IsOpenVRLoaded()) )\n    {\n        if ( (ImGui::IsMouseClicked(ImGuiMouseButton_Left)) && (!UIManager::Get()->GetOverlayDragger().IsDragActive()) && (!UIManager::Get()->GetOverlayDragger().IsDragGestureActive()) )\n        {\n            UIManager::Get()->GetOverlayDragger().DragStart(GetOverlayHandle(), m_DragOrigin);\n            UIManager::Get()->GetOverlayDragger().DragSetMaxWidth(m_OvrlWidthMax);\n        }\n        else if ( (ImGui::IsMouseClicked(ImGuiMouseButton_Right)) && (!UIManager::Get()->GetOverlayDragger().IsDragActive()) && (!UIManager::Get()->GetOverlayDragger().IsDragGestureActive()) )\n        {\n            UIManager::Get()->GetOverlayDragger().DragGestureStart(GetOverlayHandle(), m_DragOrigin);\n            UIManager::Get()->GetOverlayDragger().DragSetMaxWidth(m_OvrlWidthMax);\n        }\n    }\n\n    ImGui::EndTitleBar();\n\n    //To appease boundary extension error check\n    ImGui::Dummy({0.0f, 0.0f});\n    ImGui::SameLine(0.0f, 0.0f);\n\n    //Window content\n    WindowUpdate();\n\n    //Hack to work around ImGui's auto-sizing quirks. Just checking for ImGui::IsWindowAppearing() and using alpha 0 then doesn't help on its own so this is the next best thing\n    if (!m_HasAppearedOnce)\n    {\n        if (ImGui::IsWindowAppearing())\n        {\n            UIManager::Get()->RepeatFrame();\n        }\n        else\n        {\n            m_HasAppearedOnce = true;\n        }\n    }\n\n    //Blank space drag\n    if ( (ConfigManager::GetValue(configid_bool_interface_blank_space_drag_enabled)) && (m_OverlayStateCurrent->IsVisible) && (UIManager::Get()->IsOpenVRLoaded()) && (!ImGui::IsAnyItemHovered()) &&\n         (!IsVirtualWindowItemHovered()) && (ImGui::IsWindowHovered(ImGuiHoveredFlags_ChildWindows)) )\n    {\n        if ( (ImGui::IsMouseClicked(ImGuiMouseButton_Left)) && (!UIManager::Get()->GetOverlayDragger().IsDragActive()) && (!UIManager::Get()->GetOverlayDragger().IsDragGestureActive()) )\n        {\n            UIManager::Get()->GetOverlayDragger().DragStart(GetOverlayHandle(), m_DragOrigin);\n            UIManager::Get()->GetOverlayDragger().DragSetMaxWidth(m_OvrlWidthMax);\n        }\n        else if ( (ImGui::IsMouseClicked(ImGuiMouseButton_Right)) && (!UIManager::Get()->GetOverlayDragger().IsDragActive()) && (!UIManager::Get()->GetOverlayDragger().IsDragGestureActive()) )\n        {\n            UIManager::Get()->GetOverlayDragger().DragGestureStart(GetOverlayHandle(), m_DragOrigin);\n            UIManager::Get()->GetOverlayDragger().DragSetMaxWidth(m_OvrlWidthMax);\n        }\n    }\n\n    ImGui::End();\n\n    if (UIManager::Get()->IsInDesktopMode())\n        ImGui::PopStyleVar(); //ImGuiStyleVar_Alpha\n}\n\nvoid FloatingWindow::OverlayStateSwitchCurrent(bool use_dashboard_tab)\n{\n    if (m_OverlayStateCurrent == &m_OverlayStateFading)\n        return;\n\n    //Use transition fade if the overlay position will change\n    m_OverlayStatePending = (use_dashboard_tab) ? &m_OverlayStateDashboardTab : &m_OverlayStateRoom;\n\n    if ( (m_OverlayStateCurrent->TransformAbs != m_OverlayStatePending->TransformAbs) || (!m_OverlayStatePending->IsVisible) )\n    {\n        m_IsTransitionFading = true;\n        m_OverlayStateFading = *m_OverlayStateCurrent;\n        m_OverlayStateCurrent = &m_OverlayStateFading;\n    }\n    else\n    {\n        OverlayStateSwitchFinish();\n    }\n}\n\nvoid FloatingWindow::OverlayStateSwitchFinish()\n{\n    m_IsTransitionFading = false;\n    m_OverlayStateCurrent = m_OverlayStatePending;\n    m_OverlayStateCurrentID = (m_OverlayStateCurrent == &m_OverlayStateDashboardTab) ? floating_window_ovrl_state_dashboard_tab : floating_window_ovrl_state_room;\n\n    ApplyCurrentOverlayState();\n\n    if (m_OverlayStateCurrent->IsVisible)\n    {\n        Show();\n    }\n}\n\nvoid FloatingWindow::OnWindowPinButtonPressed()\n{\n    //If pin button pressed from dashboard tab, also make it visible for the room state so it can be used there\n    if ( (m_OverlayStateCurrentID == floating_window_ovrl_state_dashboard_tab) && (m_OverlayStateDashboardTab.IsPinned) )\n    {\n        m_OverlayStateRoom.IsVisible = true;\n    }\n}\n\nvoid FloatingWindow::OnWindowCloseButtonPressed()\n{\n    //Do nothing by default\n}\n\nbool FloatingWindow::IsVirtualWindowItemHovered() const\n{\n    return false;\n}\n\nvoid FloatingWindow::HelpMarker(const char* desc, const char* marker_str) const\n{\n    ImGui::TextDisabled(marker_str);\n\n    if (ImGui::IsItemHovered())\n    {\n        static float last_y_offset = FLT_MIN;       //Try to avoid getting having the tooltip cover the marker... the way it's done here is a bit messy to be fair\n\n        const ImGuiStyle& style = ImGui::GetStyle();\n        float pos_y = ImGui::GetItemRectMax().y + style.ItemSpacing.y;\n        bool is_invisible = false;\n\n        if (last_y_offset == FLT_MIN) //Same as IsWindowAppearing except the former doesn't work before beginning the window which is too late for the position...\n        {\n            //We need to create the tooltip window for size calculations to happen but also don't want to see it... so alpha 0, even if wasteful\n            ImGui::PushStyleVar(ImGuiStyleVar_Alpha, 0.0f);\n            is_invisible = true;\n        }\n        else\n        {\n            ImGui::SetNextWindowPos(ImVec2(m_Pos.x, m_Pos.y + m_Size.y + last_y_offset), 0, {0.0f, 1.0f});\n            ImGui::SetNextWindowSize(ImVec2(m_Size.x, -1.0f));\n        }\n\n        ImGui::BeginTooltip();\n        ImGui::PushTextWrapPos(m_Size.x - style.WindowPadding.x);\n        ImGui::TextUnformatted(desc);\n        ImGui::PopTextWrapPos();\n\n        if (ImGui::IsWindowAppearing()) //New tooltip, reset offset\n        {\n            //The window size isn't available in this frame yet, so we'll have to skip the having it visible for one more frame and then act on it\n            last_y_offset = FLT_MIN;\n        }\n        else\n        {\n            if (pos_y + ImGui::GetWindowSize().y > m_Pos.y + m_Size.y) //If it would cover the marker\n            {\n                if (UIManager::Get()->IsInDesktopMode())\n                {\n                    last_y_offset = -m_Size.y + ImGui::GetWindowSize().y + UIManager::Get()->GetDesktopModeWindow().GetTitleBarRect().w;\n                }\n                else\n                {\n                    last_y_offset = -m_Size.y + ImGui::GetWindowSize().y + ImGui::GetFontSize() + style.FramePadding.y * 2.0f;\n                }\n            }\n            else //Use normal pos\n            {\n                last_y_offset = 0.0f;\n            }\n        }\n\n        ImGui::EndTooltip();\n\n        if (is_invisible)\n            ImGui::PopStyleVar(); //ImGuiStyleVar_Alpha\n    }\n}\n\nvoid FloatingWindow::UpdateLimiterSetting(bool is_override) const\n{\n    const ImGuiStyle& style = ImGui::GetStyle();\n\n    const ConfigID_Int   configid_mode = (is_override) ? configid_int_overlay_update_limit_override_mode : configid_int_performance_update_limit_mode;\n    const ConfigID_Int   configid_fps  = (is_override) ? configid_int_overlay_update_limit_override_fps  : configid_int_performance_update_limit_fps;\n    const ConfigID_Float configid_ms   = (is_override) ? configid_float_overlay_update_limit_override_ms : configid_float_performance_update_limit_ms;\n\n    int& update_limit_mode = ConfigManager::GetRef(configid_mode);\n    bool limit_updates = (update_limit_mode != update_limit_mode_off);\n\n    if (ConfigManager::GetValue(configid_bool_interface_show_advanced_settings)) //Advanced view, choose limiter mode\n    {\n        ImGui::AlignTextToFramePadding();\n        ImGui::TextUnformatted(TranslationManager::GetString((is_override) ? tstr_SettingsPerformanceUpdateLimiterModeOverride : tstr_SettingsPerformanceUpdateLimiterMode));\n\n        if (update_limit_mode == update_limit_mode_ms)\n        {\n            ImGui::SameLine(0.0f, style.ItemInnerSpacing.x);\n            HelpMarker(TranslationManager::GetString(tstr_SettingsPerformanceUpdateLimiterModeMSTip));\n        }\n        else if (is_override)\n        {\n            ImGui::SameLine(0.0f, style.ItemInnerSpacing.x);\n            HelpMarker(TranslationManager::GetString(tstr_SettingsPerformanceUpdateLimiterOverrideTip));\n        }\n\n        ImGui::NextColumn();\n\n        //Manually set up combo items here to support different first string for override setting\n        const TRMGRStrID combo_strings[] = \n        {\n            (is_override) ? tstr_SettingsPerformanceUpdateLimiterModeOffOverride : tstr_SettingsPerformanceUpdateLimiterModeOff,\n            tstr_SettingsPerformanceUpdateLimiterModeMS,\n            tstr_SettingsPerformanceUpdateLimiterModeFPS\n        };\n\n        update_limit_mode = clamp(update_limit_mode, 0, IM_ARRAYSIZE(combo_strings) - 1);   //Avoid accessing past limits with invalid values\n\n        ImGui::SetNextItemWidth(-1.0f);\n        if (ImGui::BeginComboAnimated(\"##ComboUpdateLimitMode\", TranslationManager::GetString(combo_strings[update_limit_mode]) ))\n        {\n            int i = 0;\n            for (const auto& item : combo_strings)\n            {\n                if (ImGui::Selectable(TranslationManager::GetString(item), (update_limit_mode == i)))\n                {\n                    update_limit_mode = i;\n\n                    IPCManager::Get().PostConfigMessageToDashboardApp(configid_mode, update_limit_mode);\n                }\n\n                ++i;\n            }\n\n            ImGui::EndCombo();\n        }\n\n        ImGui::NextColumn();\n        ImGui::NextColumn();\n    }\n    else //Simple view, only switch between off and fps mode (still shows ms below if previously set active)\n    {\n        if (ImGui::Checkbox(TranslationManager::GetString((is_override) ? tstr_SettingsPerformanceUpdateLimiterOverride : tstr_SettingsPerformanceUpdateLimiter), &limit_updates))\n        {\n            update_limit_mode = (limit_updates) ? update_limit_mode_fps : update_limit_mode_off;\n            IPCManager::Get().PostConfigMessageToDashboardApp(configid_mode, update_limit_mode);\n        }\n\n        if (is_override)\n        {\n            ImGui::SameLine(0.0f, style.ItemInnerSpacing.x);\n            HelpMarker(TranslationManager::GetString(tstr_SettingsPerformanceUpdateLimiterOverrideTip));\n        }\n\n        ImGui::NextColumn();\n    }\n\n    if (update_limit_mode == update_limit_mode_ms)\n    {\n        VRKeyboard& vr_keyboard = UIManager::Get()->GetVRKeyboard();\n\n        float& update_limit_ms = ConfigManager::Get().GetRef(configid_ms);\n\n        vr_keyboard.VRKeyboardInputBegin( ImGui::SliderWithButtonsGetSliderID(\"UpdateLimitMS\") );\n        if (ImGui::SliderWithButtonsFloat(\"UpdateLimitMS\", update_limit_ms, 0.5f, 0.05f, 0.0f, 100.0f, \"%.2f ms\", ImGuiSliderFlags_Logarithmic))\n        {\n            if (update_limit_ms < 0.0f)\n                update_limit_ms = 0.0f;\n\n            IPCManager::Get().PostConfigMessageToDashboardApp(configid_ms, update_limit_ms);\n        }\n        vr_keyboard.VRKeyboardInputEnd();\n    }\n    else\n    {\n        if (!limit_updates)\n            ImGui::PushItemDisabled();\n\n        int& update_limit_fps = ConfigManager::Get().GetRef(configid_fps);\n        const int update_limit_fps_max = 9;\n        if (ImGui::SliderWithButtonsInt(\"UpdateLimitFPS\", update_limit_fps, 1, 1, 0, update_limit_fps_max, \"##%d\", ImGuiSliderFlags_NoInput, nullptr, \n                                        TranslationManager::Get().GetFPSLimitString(update_limit_fps)))\n        {\n            update_limit_fps = clamp(update_limit_fps, 0, update_limit_fps_max);\n\n            IPCManager::Get().PostConfigMessageToDashboardApp(configid_fps, update_limit_fps);\n        }\n\n        if (!limit_updates)\n            ImGui::PopItemDisabled();\n    }\n\n    ImGui::NextColumn();\n}\n\nbool FloatingWindow::InputOverlayTags(const char* str_id, char* buffer_tags, size_t buffer_tags_size, FloatingWindowInputOverlayTagsState& state, int clip_parent_depth, bool show_auto_tags)\n{\n    static const int single_tag_buffer_size = IM_ARRAYSIZE(state.TagEditBuffer);\n\n    ImGuiStyle& style = ImGui::GetStyle();\n\n    float widget_width = 0.0f;\n    bool is_widget_hovered = false;\n\n    float child_height = ImGui::GetTextLineHeight() + style.FramePadding.y * 2.0f;\n    child_height += ImGui::GetTextLineHeightWithSpacing() * (state.ChildHeightLines - 1.0f);\n\n    ImGui::PushID(str_id);\n\n    ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, style.FramePadding);\n    if (ImGui::BeginChild(\"InputOverlayTags\", {-style.ChildBorderSize, child_height}, ImGuiChildFlags_Borders | ImGuiChildFlags_NavFlattened))\n    {\n        ImGui::PopStyleVar();\n\n        state.ChildHeightLines = 1.0f;\n        widget_width = ImGui::GetWindowWidth();\n        is_widget_hovered = ImGui::IsWindowHovered(ImGuiHoveredFlags_AllowWhenBlockedByActiveItem);\n\n        //Split input string into individual tags and show a small button for each\n        const char* str = buffer_tags;\n        const char* str_end = str + strlen(str);\n        const char* tag_start = str;\n        const char* tag_end = nullptr;\n        char buffer_single_tag[single_tag_buffer_size] = \"\";\n        int tag_id = 0;\n\n        while (tag_start < str_end)\n        {\n            tag_end = (const char*)memchr(tag_start, ' ', str_end - tag_start);\n\n            if (tag_end == nullptr)\n                tag_end = str_end;\n\n            size_t length = tag_end - tag_start;\n\n            //Break if tag doesn't fit (would be comically long though)\n            if (length >= single_tag_buffer_size)\n                break;\n\n            if (length > 0)\n            {\n                memcpy(buffer_single_tag, tag_start, length);\n                buffer_single_tag[length] = '\\0';\n\n                if (tag_id != 0)\n                {\n                    ImVec2 text_size = ImGui::CalcTextSize(buffer_single_tag);\n                    text_size.x += style.ItemSpacing.x;\n\n                    if (text_size.x > ImGui::GetContentRegionAvail().x)\n                    {\n                        ImGui::NewLine();\n                        state.ChildHeightLines += 1.0f;\n                    }\n                }\n\n                ImGui::PushID(tag_id);\n\n                if (ImGui::SmallButton(buffer_single_tag))\n                {\n                    //Copy tag into edit buffer\n                    memcpy(state.TagEditBuffer, buffer_single_tag, single_tag_buffer_size);\n\n                    //Also keep a copy to put back when canceling\n                    state.TagEditOrigStr = state.TagEditBuffer;\n\n                    //Remove tag from full string\n                    std::string str_tags = buffer_tags;\n                    str_tags.erase(tag_start - str, length);\n                    StringReplaceAll(str_tags, \"  \", \" \");               //Clean up double whitespace separators, no matter where they came from\n\n                    //Cleanup stray space at the beginning too\n                    if ((!str_tags.empty()) && (str_tags[0] == ' '))\n                    {\n                        str_tags.erase(0, 1);\n                    }\n\n                    //Copy back to buffer\n                    size_t copied_length = str_tags.copy(buffer_tags, buffer_tags_size - 1);\n                    buffer_tags[copied_length] = '\\0';\n\n                    //Open popup to allow editing\n                    state.PopupShow = true;\n                    state.FocusTextInput = true;\n\n                    //We modified the buffer we're looping over, get out of here\n                    UIManager::Get()->RepeatFrame(5);   //Prevent hover flicker from the button that take this one's place\n                    ImGui::PopID();\n                    ImGui::EndChild();\n                    ImGui::PopID();\n\n                    return true;\n                }\n\n                ImGui::SameLine(0.0f, style.ItemInnerSpacing.x);\n\n                ImGui::PopID();\n                tag_id++;\n            }\n\n            tag_start = tag_end + 1;\n        }\n\n        if (tag_id != 0)\n        {\n            ImGui::SameLine(0.0f, style.ItemInnerSpacing.x);\n\n            ImVec2 text_size = ImGui::CalcTextSize(\"+\");\n            text_size.x += style.ItemSpacing.x;\n\n            if (text_size.x > ImGui::GetContentRegionAvail().x)\n            {\n                ImGui::NewLine();\n                state.ChildHeightLines += 1.0f;\n            }\n        }\n\n        //There are two plus-shaped buttons on the screen when the popup is up.\n        //This one will just clear the text input then and might be accidentally pressed instead of the bigger one. Disable it to prevent accidents\n        //Similar thing applies to the tag buttons themselves, but that behavior is at least useful for editing there\n        const bool disable_add = state.PopupShow;\n        if (disable_add)\n        {\n            ImGui::PushItemDisabledNoVisual();\n            ImGui::PushStyleColor(ImGuiCol_Button, ImGui::GetStyleColorVec4(ImGuiCol_ButtonActive));\n        }\n    \n        //Add some frame padding in VR mode only to have a more square-ish button that is also easier to hit\n        //Due to how we do style-scaling this isn't necessary in desktop mode\n        if (!UIManager::Get()->IsInDesktopMode())\n            ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, {style.FramePadding.x * 1.5f, 0.0f});\n\n        if (ImGui::SmallButton(\"+##AddTag\"))\n        {\n            state.TagEditBuffer[0] = '\\0';\n            state.TagEditOrigStr = state.TagEditBuffer;\n\n            state.PopupShow = true;\n            state.FocusTextInput = true;\n        }\n\n        if (!UIManager::Get()->IsInDesktopMode())\n            ImGui::PopStyleVar();\n\n        if (disable_add)\n        {\n            ImGui::PopStyleColor();\n            ImGui::PopItemDisabledNoVisual();\n        }\n    }\n    else\n    {\n        ImGui::PopStyleVar();   //ImGuiStyleVar_WindowPadding\n    }\n\n    //Avoid scrollbar flicker while figuring out child window size\n    if ((ImGui::IsAnyScrollBarVisible()) && (state.ChildHeightLines <= 3.0f))\n    {\n        UIManager::Get()->RepeatFrame();\n    }\n\n    //Show up to 3 lines before adding a scrollbar\n    state.ChildHeightLines = std::min(state.ChildHeightLines, 3.0f);\n\n    ImGui::EndChild();\n\n    //--Popup Window\n    if (!state.PopupShow)\n    {\n        ImGui::PopID();\n        return false;\n    }\n\n    ImGuiIO& io = ImGui::GetIO();\n    VRKeyboard& vr_keyboard = UIManager::Get()->GetVRKeyboard();\n\n    const float pos_y      = ImGui::GetItemRectMin().y - style.ItemSpacing.y;\n    const float pos_y_down = ImGui::GetItemRectMax().y + style.ItemInnerSpacing.y;\n    const float pos_y_up   = ImGui::GetItemRectMin().y - style.ItemSpacing.y - state.PopupHeight;\n\n    bool update_filter = false;\n    bool ret = false;\n\n    //Wait for window height to be known and stable before setting pos or animating fade/pos\n    if ((state.PopupHeight != FLT_MIN) && (state.PopupHeight == state.PopupHeightPrev))\n    {\n        ImGui::SetNextWindowPos(ImVec2(ImGui::GetItemRectMin().x, smoothstep(state.PosAnimationProgress, pos_y_down, pos_y_up) ));\n\n        const float time_step = ImGui::GetIO().DeltaTime * 6.0f;\n\n        state.PopupAlpha += (!state.IsFadingOut) ? time_step : -time_step;\n\n        if (state.PopupAlpha > 1.0f)\n            state.PopupAlpha = 1.0f;\n        else if (state.PopupAlpha < 0.0f)\n            state.PopupAlpha = 0.0f;\n\n        state.PosAnimationProgress += (state.PosDir == ImGuiDir_Up) ? time_step : -time_step;\n\n        if (state.PosAnimationProgress > 1.0f)\n            state.PosAnimationProgress = 1.0f;\n        else if (state.PosAnimationProgress < 0.0f)\n            state.PosAnimationProgress = 0.0f;\n    }\n    else if (state.PopupHeight == FLT_MIN)   //Popup is appearing\n    {\n        state.KnownTagsList = OverlayManager::Get().GetKnownOverlayTagList();\n        update_filter = true;\n    }\n\n    ImGui::PushStyleVar(ImGuiStyleVar_Alpha, state.PopupAlpha);\n\n    //Look up parent window (optionally digging deeper to avoid child windows) and get its clipping rect\n    ImGuiWindow* window_parent = ImGui::GetCurrentWindow();\n    ImGuiWindow* window_parent_lookup = window_parent;\n\n    while (clip_parent_depth > 0)\n    {\n        window_parent_lookup = window_parent_lookup->ParentWindow;\n        clip_parent_depth--;\n\n        if (window_parent_lookup != nullptr)\n        {\n            window_parent = window_parent_lookup;\n        }\n        else\n        {\n            break;\n        }\n    }\n\n    ImRect clip_rect = window_parent->ClipRect;\n    clip_rect.Max.y = window_parent->Size.y + clip_rect.Min.y;   //Set Max.y from window content size as FLT_MAX values break drawlist commands for some reason\n\n    const ImGuiWindowFlags flags = ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoSavedSettings | \n                                   ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoBackground;\n\n    ImGui::SetNextWindowSize(ImVec2(widget_width, ImGui::GetTextLineHeightWithSpacing() * 11.5f));\n    ImGui::Begin(\"##WindowAddTags\", nullptr, flags);\n\n\n    //Transfer scroll input to parent window (which isn't a real parent window but just the one in the stack), so this window doesn't block scrolling\n    ImGui::ScrollBeginStackParentWindow();\n\n    //Use clipping rect of parent window\n    ImGui::PushClipRect(clip_rect.Min, clip_rect.Max, false);\n\n    //Draw background + border manually so it can be clipped properly\n    ImGuiWindow* window = ImGui::GetCurrentWindow();\n    ImRect window_rect = window->Rect();\n    window->DrawList->AddRectFilled(window_rect.Min, window_rect.Max, ImGui::GetColorU32(ImGuiCol_PopupBg), window->WindowRounding);\n    window->DrawList->AddRect(window_rect.Min, window_rect.Max, ImGui::GetColorU32(ImGuiCol_Border), window->WindowRounding, 0, window->WindowBorderSize);\n\n    //Disable inputs when fading out\n    const bool disable_items = state.IsFadingOut;\n    if (disable_items)\n        ImGui::PushItemDisabledNoVisual();\n\n    //-Window contents\n    static float buttons_width = 0.0f;\n    const bool is_input_text_active = ImGui::IsAnyInputTextActive(); //Need to get this before InputText is canceled in the same frame\n    bool add_current_input = false;\n\n    if (state.FocusTextInput)\n    {\n        ImGui::SetKeyboardFocusHere();\n    }\n\n    ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x - buttons_width - style.ItemInnerSpacing.x);\n\n    //Set up shortcut window so it does not block the tag listing itself either. It's a little bit awkward looking like this, but better than blocking the buttons\n    vr_keyboard.SetShortcutWindowDirectionHint(ImGuiDir_Up, (state.PosDir == ImGuiDir_Down) ? -child_height - style.ItemSpacing.y - style.ItemSpacing.y : -style.ItemInnerSpacing.y);\n    vr_keyboard.VRKeyboardInputBegin(\"##InputTagEdit\");\n    if (ImGui::InputTextWithHint(\"##InputTagEdit\", TranslationManager::GetString(tstr_DialogInputTagsHint), state.TagEditBuffer, single_tag_buffer_size, \n                                 ImGuiInputTextFlags_CharsNoBlank | ImGuiInputTextFlags_EnterReturnsTrue))\n    {\n        add_current_input = !state.IsTagAlreadyInBuffer;\n    }\n    vr_keyboard.VRKeyboardInputEnd();\n\n    //Wait until the actually have focus before turning the flag off\n    if (ImGui::IsItemActive())\n    {\n        state.FocusTextInput = false;\n    }\n\n    //Check if tag would be a duplicate and disable adding in that case\n    if (ImGui::IsItemEdited())\n    {\n        state.IsTagAlreadyInBuffer = OverlayManager::MatchOverlayTagSingle(buffer_tags, state.TagEditBuffer);\n        update_filter = true;\n\n        UIManager::Get()->AddFontBuilderStringIfAnyUnmappedCharacters(state.TagEditBuffer);\n    }\n\n    ImGui::SameLine(0.0f, style.ItemInnerSpacing.x);\n\n    ImGui::BeginGroup();\n\n    ImVec2 b_size, b_uv_min, b_uv_max;\n    ImVec2 b_size_real = ImVec2(ImGui::GetTextLineHeight(), ImGui::GetTextLineHeight());\n    TextureManager::Get().GetTextureInfo(tmtex_icon_add, b_size, b_uv_min, b_uv_max);\n\n    if (state.IsTagAlreadyInBuffer)\n        ImGui::PushItemDisabled();\n\n    if (ImGui::ImageButton(\"AddButton\", io.Fonts->TexID, b_size_real, b_uv_min, b_uv_max, ImVec4(0.0f, 0.0f, 0.0f, 0.0f)))\n    {\n        add_current_input = true;\n    }\n\n    if (state.IsTagAlreadyInBuffer)\n        ImGui::PopItemDisabled();\n\n    ImGui::SameLine(0.0f, style.ItemInnerSpacing.x);\n\n    TextureManager::Get().GetTextureInfo(tmtex_icon_small_close, b_size, b_uv_min, b_uv_max);\n    if (ImGui::ImageButton(\"DeleteButton\", io.Fonts->TexID, b_size_real, b_uv_min, b_uv_max, ImVec4(0.0f, 0.0f, 0.0f, 0.0f)))\n    {\n        state.TagEditBuffer[0] = '\\0';\n        state.TagEditOrigStr = \"\";\n        state.IsTagAlreadyInBuffer = true;\n        update_filter = true;\n    }\n\n    ImGui::EndGroup();\n\n    buttons_width = ImGui::GetItemRectSize().x;\n\n    ImGui::BeginChild(\"ChildKnownTags\", ImVec2(0.0f, 0.0f), ImGuiChildFlags_NavFlattened, ImGuiWindowFlags_NoBackground);\n\n    for (const auto& list_entry : state.KnownTagsList)\n    {\n        if (state.KnownTagsFilter.PassFilter(list_entry.Tag.c_str()))\n        {\n            if ((list_entry.IsAutoTag) && (!show_auto_tags))\n                continue;\n\n            if (list_entry.IsAutoTag)\n                ImGui::PushStyleColor(ImGuiCol_Text, Style_ImGuiCol_TextNotification);\n\n            if (ImGui::Selectable(list_entry.Tag.c_str()))\n            {\n                size_t copied_length = list_entry.Tag.copy(state.TagEditBuffer, buffer_tags_size - 1);\n                state.TagEditBuffer[copied_length] = '\\0';\n\n                add_current_input = true;\n                state.TagEditOrigStr = \"\";\n            }\n\n            if (list_entry.IsAutoTag)\n                ImGui::PopStyleColor();\n        }\n    }\n\n    ImGui::EndChild();\n\n    if (disable_items)\n        ImGui::PopItemDisabledNoVisual();\n\n    //Abandoned popup while editing existing tag, put it back\n    if ((state.IsFadingOut) && (!state.TagEditOrigStr.empty()))\n    {\n        size_t copied_length = state.TagEditOrigStr.copy(state.TagEditBuffer, buffer_tags_size - 1);\n        state.TagEditBuffer[copied_length] = '\\0';\n\n        state.TagEditOrigStr = \"\";\n        add_current_input = true;\n        update_filter = true;\n    }\n\n    if (add_current_input)\n    {\n        //Check if tag is already in the string first and just don't add it then\n        if (!OverlayManager::MatchOverlayTagSingle(buffer_tags, state.TagEditBuffer))\n        {\n            std::string str_tags = buffer_tags;\n\n            if (!str_tags.empty())\n            {\n                str_tags += \" \";\n            }\n\n            str_tags += state.TagEditBuffer;\n\n            size_t copied_length = str_tags.copy(buffer_tags, buffer_tags_size - 1);\n            buffer_tags[copied_length] = '\\0';\n        }\n\n        state.TagEditOrigStr = \"\";\n\n        ret = true;\n        state.IsFadingOut = true;\n        UIManager::Get()->RepeatFrame();\n    }\n\n    if (update_filter)\n    {\n        //Update filter manually (we don't use its buffer directly as its size is fixed to 256)\n        size_t length = strlen(state.TagEditBuffer);\n        if (length < (size_t)IM_ARRAYSIZE(state.KnownTagsFilter.InputBuf))\n        {\n            memcpy(state.KnownTagsFilter.InputBuf, state.TagEditBuffer, length);\n            state.KnownTagsFilter.InputBuf[length] = '\\0';\n\n            state.KnownTagsFilter.Build();\n        }\n    }\n\n    //Switch directions if there's no space in the default direction\n    if (state.PosDirDefault == ImGuiDir_Down)\n    {\n        state.PosDir = (pos_y_down + ImGui::GetWindowSize().y > clip_rect.Max.y) ? ImGuiDir_Up : ImGuiDir_Down;\n    }\n    else\n    {\n        //Not using pos_y_up here as it's not valid yet (state.PopupHeight not set) \n        state.PosDir = (pos_y - ImGui::GetWindowSize().y < clip_rect.Min.y) ? ImGuiDir_Down : ImGuiDir_Up;\n    }\n\n    if (state.PopupAlpha == 0.0f)\n    {\n        state.PosAnimationProgress = (state.PosDir == ImGuiDir_Down) ? 0.0f : 1.0f;\n    }\n\n    //Fade out on focus loss/cancel input\n    if ( ((!ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows)) && (!ImGui::IsAnyItemActive())) ||\n         ((!is_input_text_active) && (ImGui::IsNavInputPressed(ImGuiNavInput_Cancel))) )\n    {\n        state.IsFadingOut = true;\n    }\n\n    //Cache window height so it's available on the next frame before beginning the window\n    state.PopupHeightPrev = state.PopupHeight;\n    state.PopupHeight = ImGui::GetWindowSize().y;\n\n    ImGui::End();\n\n    ImGui::PopStyleVar(); //ImGuiStyleVar_Alpha\n\n    //Reset when fade-out is done\n    if ( (state.IsFadingOut) && (state.PopupAlpha == 0.0f) )\n    {\n        state.IsFadingOut = false;\n        state.PopupHeight = FLT_MIN;\n        state.PosDir = state.PosDirDefault;\n        state.PosAnimationProgress = (state.PosDirDefault == ImGuiDir_Down) ? 0.0f : 1.0f;\n        state.PopupShow = false;\n    }\n\n    ImGui::PopID();\n\n    return ret;\n}\n\nbool FloatingWindow::ActionOrderList(ActionManager::ActionList& list_actions_target, bool is_appearing, bool is_returning, FloatingWindowActionOrderListState& state, \n                                     bool& go_add_actions, float height_offset)\n{\n    static float list_buttons_width = 0.0f;\n    static ImVec2 no_actions_text_size;\n\n    const ImGuiStyle& style = ImGui::GetStyle();\n    ImGuiIO& io = ImGui::GetIO();\n    ActionManager& action_manager = ConfigManager::Get().GetActionManager();\n\n    if ((is_appearing) || (is_returning))\n    {\n        state.HasSavedChanges = false;\n        state.SelectedIndex = -1;\n\n        state.ActionsList.clear();\n        for (ActionUID uid : list_actions_target)\n        {\n            state.ActionsList.push_back({uid, action_manager.GetTranslatedName(uid)});\n        }\n    }\n\n    if (is_appearing)\n    {\n        state.ActionListOrig = list_actions_target;\n    }\n\n    ImGui::TextColoredUnformatted(ImGui::GetStyleColorVec4(ImGuiCol_ButtonHovered), TranslationManager::GetString(tstr_SettingsActionsOrderHeader)); \n    ImGui::Indent();\n\n    ImGui::SetNextItemWidth(-1.0f);\n    const float item_height = ImGui::GetFrameHeight() + style.ItemSpacing.y;\n    const float inner_padding = style.FramePadding.y + style.ItemInnerSpacing.y;\n    const float item_count = (UIManager::Get()->IsInDesktopMode()) ? 16.0f : 14.0f;\n\n    ImGui::BeginChild(\"ActionList\", ImVec2(0.0f, (item_height * item_count) + inner_padding + height_offset), true);\n\n    if ((is_appearing) || (is_returning))\n    {\n        ImGui::SetScrollY(0.0f);\n    }\n\n    //Display error if there are no actions\n    if (state.ActionsList.size() == 0)\n    {\n        ImGui::SetCursorPosX(ImGui::GetCursorPosX() + ImGui::GetContentRegionAvail().x / 2.0f - (no_actions_text_size.x / 2.0f));\n        ImGui::SetCursorPosY(ImGui::GetCursorPosY() + ImGui::GetContentRegionAvail().y / 2.0f - (no_actions_text_size.y / 2.0f));\n\n        ImGui::TextUnformatted(TranslationManager::GetString(tstr_SettingsActionsOrderNoActions));\n        no_actions_text_size = ImGui::GetItemRectSize();\n    }\n    else\n    {\n        ActionUID hovered_action_prev = state.HoveredAction;\n\n        //List actions\n        int index = 0;\n        for (const auto& entry : state.ActionsList)\n        {\n            ImGui::PushID((void*)entry.UID);\n\n            //Set focus for nav if we previously re-ordered overlays via keyboard\n            if (state.KeyboardSwappedIndex == index)\n            {\n                ImGui::SetKeyboardFocusHere();\n\n                //Nav works against us here, so keep setting focus until ctrl isn't down anymore\n                if ((!io.KeyCtrl) || (!io.NavVisible))\n                {\n                    state.KeyboardSwappedIndex = -1;\n                }\n            }\n\n            ImGui::SetNextItemAllowOverlap();\n            if (ImGui::Selectable(entry.Name.c_str(), (index == state.SelectedIndex), ImGuiSelectableFlags_AllowOverlap))\n            {\n                state.SelectedIndex = index;\n            }\n\n            if ( (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenBlockedByActiveItem | ImGuiHoveredFlags_AllowWhenOverlappedByItem)) || ((io.NavVisible) && (ImGui::IsItemFocused())) )\n            {\n                state.HoveredAction = entry.UID;\n            }\n\n            if (ImGui::IsItemVisible())\n            {\n                //Drag reordering\n                if ((ImGui::IsItemActive()) && (!ImGui::IsItemHovered()))\n                {\n                    int index_swap = index + ((ImGui::GetMouseDragDelta(ImGuiMouseButton_Left).y < 0.0f) ? -1 : 1);\n                    if ((state.HoveredAction != entry.UID) && (index_swap >= 0) && (index_swap < state.ActionsList.size()))\n                    {\n                        std::iter_swap(state.ActionsList.begin()   + index, state.ActionsList.begin()   + index_swap);\n                        std::iter_swap(list_actions_target.begin() + index, list_actions_target.begin() + index_swap);\n                        state.SelectedIndex = index_swap;\n\n                        ImGui::ResetMouseDragDelta(ImGuiMouseButton_Left);\n                    }\n                }\n\n                //Keyboard reordering\n                if ((io.NavVisible) && (io.KeyCtrl) && (state.HoveredAction == entry.UID))\n                {\n                    int index_swap = index + ((ImGui::IsNavInputPressed(ImGuiNavInput_DpadDown, true)) ? 1 : (ImGui::IsNavInputPressed(ImGuiNavInput_DpadUp, true)) ? -1 : 0);\n                    if ((index != index_swap) && (index_swap >= 0) && (index_swap < state.ActionsList.size()))\n                    {\n                        std::iter_swap(state.ActionsList.begin()   + index, state.ActionsList.begin()   + index_swap);\n                        std::iter_swap(list_actions_target.begin() + index, list_actions_target.begin() + index_swap);\n\n                        //Skip the rest of this frame to avoid double-swaps\n                        state.KeyboardSwappedIndex = index_swap;\n                        ImGui::PopID();\n                        UIManager::Get()->RepeatFrame();\n                        break;\n                    }\n                }\n            }\n\n            ImGui::PopID();\n\n            index++;\n        }\n\n        //Reduce flicker from dragging and hovering\n        if (state.HoveredAction != hovered_action_prev)\n        {\n            UIManager::Get()->RepeatFrame();\n        }\n    }\n\n    ImGui::EndChild();\n    ImGui::Unindent();\n\n    const bool is_none  = (state.SelectedIndex == -1);\n\n    ImGui::SetCursorPosX(ImGui::GetCursorPosX() + ImGui::GetContentRegionAvail().x - list_buttons_width);\n\n    ImGui::BeginGroup();\n\n    go_add_actions = ImGui::Button(TranslationManager::GetString(tstr_SettingsActionsOrderAdd));\n\n    ImGui::SameLine();\n\n    if (is_none)\n        ImGui::PushItemDisabled();\n\n    if ((ImGui::Button(TranslationManager::GetString(tstr_SettingsActionsOrderRemove))) || (ImGui::IsKeyPressed(ImGuiKey_Delete)))\n    {\n        if ((state.SelectedIndex >= 0) && (state.SelectedIndex < state.ActionsList.size()))\n        {\n            state.ActionsList.erase(state.ActionsList.begin()     + state.SelectedIndex);\n            list_actions_target.erase(list_actions_target.begin() + state.SelectedIndex);\n\n            if (state.SelectedIndex >= (int)list_actions_target.size())\n            {\n                state.SelectedIndex--;\n            }\n        }\n    }\n\n    if (is_none)\n        ImGui::PopItemDisabled();\n\n    ImGui::EndGroup();\n\n    list_buttons_width = ImGui::GetItemRectSize().x + style.IndentSpacing;\n\n    ImGui::SetCursorPosY( ImGui::GetCursorPosY() + (ImGui::GetContentRegionAvail().y - ImGui::GetFrameHeightWithSpacing()) );\n\n    //Confirmation buttons\n    ImGui::Separator();\n\n    bool ret = false;\n    if (ImGui::Button(TranslationManager::GetString(tstr_DialogOk))) \n    {\n        ret = true;\n        state.HasSavedChanges = true;\n    }\n\n    ImGui::SameLine();\n\n    if (ImGui::Button(TranslationManager::GetString(tstr_DialogCancel))) \n    {\n        ret = true;\n        list_actions_target = state.ActionListOrig;\n    }\n\n    return ret;\n}\n\nbool FloatingWindow::ActionAddSelector(ActionManager::ActionList& list_actions_target, bool is_appearing, FloatingWindowActionAddSelectorState& state, float height_offset)\n{\n    static float list_buttons_width = 0.0f;\n    static ImVec2 no_actions_text_size;\n\n    const ImGuiStyle& style = ImGui::GetStyle();\n    ActionManager& action_manager = ConfigManager::Get().GetActionManager();\n\n    if (is_appearing)\n    {\n        state.ActionsList = action_manager.GetActionNameList();\n\n        //Remove actions already in the existing list\n        auto it = std::remove_if(state.ActionsList.begin(), state.ActionsList.end(), \n                                 [&](const auto& entry) { return (std::find(list_actions_target.begin(), list_actions_target.end(), entry.UID) != list_actions_target.end()); } );\n\n        state.ActionsList.erase(it, state.ActionsList.end());\n\n        state.ActionsTickedList.resize(state.ActionsList.size(), 0);\n        std::fill(state.ActionsTickedList.begin(), state.ActionsTickedList.end(), 0);\n    }\n\n    ImGui::TextColoredUnformatted(ImGui::GetStyleColorVec4(ImGuiCol_ButtonHovered), TranslationManager::GetString(tstr_SettingsActionsAddSelectorHeader)); \n    ImGui::Indent();\n\n    ImGui::SetNextItemWidth(-1.0f);\n    const float item_height = ImGui::GetFrameHeight() + style.ItemSpacing.y;\n    const float inner_padding = style.FramePadding.y + style.ItemInnerSpacing.y;\n    const float item_count = (UIManager::Get()->IsInDesktopMode()) ? 16.0f : 14.0f;\n\n    ImGui::BeginChild(\"ActionSelector\", ImVec2(0.0f, (item_height * item_count) + inner_padding + height_offset), true);\n\n    //Reset scroll when appearing\n    if (is_appearing)\n    {\n        ImGui::SetScrollY(0.0f);\n    }\n\n    //Display error if there are no actions\n    if (state.ActionsList.size() == 0)\n    {\n        ImGui::SetCursorPosX(ImGui::GetCursorPosX() + ImGui::GetContentRegionAvail().x / 2.0f - (no_actions_text_size.x / 2.0f));\n        ImGui::SetCursorPosY(ImGui::GetCursorPosY() + ImGui::GetContentRegionAvail().y / 2.0f - (no_actions_text_size.y / 2.0f));\n\n        ImGui::TextUnformatted(TranslationManager::GetString(tstr_DialogActionPickerEmpty));\n        no_actions_text_size = ImGui::GetItemRectSize();\n    }\n    else\n    {\n        //List actions\n        const float cursor_x_past_checkbox = ImGui::GetCursorPosX() + ImGui::GetFrameHeightWithSpacing();\n        \n        int index = 0;\n        for (const auto& entry : state.ActionsList)\n        {\n            ImGui::PushID(index);\n\n            //We're using a trick here to extend the checkbox interaction space to the end of the child window\n            //Checkbox() uses the item inner spacing if the label is not blank, so we increase that and use a space label\n            //Below we render a custom label after adjusting the cursor position to where it normally would be\n            ImGui::PushStyleVar(ImGuiStyleVar_ItemInnerSpacing, {ImGui::GetContentRegionAvail().x, style.ItemInnerSpacing.y});\n\n            if (ImGui::Checkbox(\" \", (bool*)&state.ActionsTickedList[index]))\n            {\n                //Update any ticked status\n                state.IsAnyActionTicked = false;\n                for (auto is_ticked : state.ActionsTickedList)\n                {\n                    if (is_ticked != 0)\n                    {\n                        state.IsAnyActionTicked = true;\n                        break;\n                    }\n                }\n            }\n\n            ImGui::PopStyleVar();\n\n            if (ImGui::IsItemVisible())\n            {\n                //Adjust cursor position to be after the checkbox\n                ImGui::SameLine();\n                float text_y = ImGui::GetCursorPosY();\n                ImGui::SetCursorPos({cursor_x_past_checkbox, text_y});\n\n                ImGui::TextUnformatted(entry.Name.c_str());\n            }\n\n            ImGui::PopID();\n\n            index++;\n        }\n    }\n\n    ImGui::EndChild();\n    ImGui::Unindent();\n\n    ImGui::SetCursorPosX(ImGui::GetCursorPosX() + ImGui::GetContentRegionAvail().x - list_buttons_width);\n\n    ImGui::BeginGroup();\n\n    if (ImGui::Button(TranslationManager::GetString(tstr_SettingsProfilesOverlaysProfileAddSelectAll)))\n    {\n        std::fill(state.ActionsTickedList.begin(), state.ActionsTickedList.end(), 1);\n        state.IsAnyActionTicked = true;\n    }\n\n    ImGui::SameLine();\n\n    if (ImGui::Button(TranslationManager::GetString(tstr_SettingsProfilesOverlaysProfileAddSelectNone)))\n    {\n        std::fill(state.ActionsTickedList.begin(), state.ActionsTickedList.end(), 0);\n        state.IsAnyActionTicked = false;\n    }\n    ImGui::EndGroup();\n\n    list_buttons_width = ImGui::GetItemRectSize().x + style.IndentSpacing;\n\n    ImGui::SetCursorPosY( ImGui::GetCursorPosY() + (ImGui::GetContentRegionAvail().y - ImGui::GetFrameHeightWithSpacing()) );\n\n    //Confirmation buttons\n    ImGui::Separator();\n\n    bool ret = false;\n    if (ImGui::Button(TranslationManager::GetString(tstr_SettingsActionsAddSelectorAdd))) \n    {\n        //Add ticked actions to the existing list\n        int index = 0;\n        for (const auto& entry : state.ActionsList)\n        {\n            if (state.ActionsTickedList[index])\n            {\n                list_actions_target.push_back(entry.UID);\n            }\n\n            index++;\n        }\n\n        ret = true;\n    }\n\n    ImGui::SameLine();\n\n    if (ImGui::Button(TranslationManager::GetString(tstr_DialogCancel))) \n    {\n        ret = true;\n    }\n\n    return ret;\n}\n\nbool FloatingWindow::BeginCompactTable(const char* str_id, int column, ImGuiTableFlags flags, const ImVec2& outer_size, float inner_width)\n{\n    const ImGuiStyle style = ImGui::GetStyle();\n\n    //There's minor breakage at certain fractional scales, but the ones we care about (100%, 160% (VR), 200%) work fine with this\n    ImGui::PushStyleVar(ImGuiStyleVar_CellPadding,      {style.CellPadding.x,     -1.0f});\n    ImGui::PushStyleVar(ImGuiStyleVar_ItemInnerSpacing, {style.ItemInnerSpacing.x, 0.0f});\n\n    flags = flags & (~ImGuiTableFlags_BordersOuter);    //Remove border flag, we draw our own later\n    flags |= ImGuiTableFlags_BordersInnerV | ImGuiTableFlags_PadOuterX;\n    bool ret = ImGui::BeginTable(str_id, column, flags, outer_size, inner_width);\n\n    if (ret)\n    {\n        m_CompactTableHeaderHeight = ImGui::GetCursorPosY();\n    }\n    else\n    {\n        ImGui::PopStyleVar(2);\n    }\n\n    return ret;\n}\n\nvoid FloatingWindow::CompactTableHeadersRow()\n{\n    ImGui::PushItemDisabledNoVisual();\n    ImGui::TableHeadersRow();\n    ImGui::SetCursorPosY(ImGui::GetCursorPosY() - 1.0f);\n    ImGui::Dummy({0.0f, 0.0f});         //To appease parent boundary extension error check\n    ImGui::PopItemDisabledNoVisual();\n\n    m_CompactTableHeaderHeight = ImGui::GetCursorPosY() - m_CompactTableHeaderHeight - ImGui::GetStyle().ItemSpacing.y;\n}\n\nvoid FloatingWindow::EndCompactTable()\n{\n    const ImGuiStyle style = ImGui::GetStyle();\n\n    ImGui::EndTable();\n    ImGui::PopStyleVar(2);\n\n    //Selectables cover parts of the default table border and the bottom border would be one pixel inside the last row, so we draw our own header and table border on top instead\n    ImVec2 rect_min = ImGui::GetItemRectMin(), rect_max = ImGui::GetItemRectMax();\n    ImGui::GetWindowDrawList()->AddRect(rect_min, {rect_max.x, rect_min.y + ceilf(m_CompactTableHeaderHeight - 1.0f)}, ImGui::GetColorU32(ImGuiCol_Border), 0.0f, 0, style.WindowBorderSize);\n    ImGui::GetWindowDrawList()->AddRect(rect_min, {rect_max.x, rect_max.y + 1.0f},                                     ImGui::GetColorU32(ImGuiCol_Border), 0.0f, 0, style.WindowBorderSize);\n}\n\nvoid FloatingWindow::Update()\n{\n    WindowUpdateBase();\n}\n\nvoid FloatingWindow::UpdateVisibility()\n{\n    //Set state depending on dashboard tab visibility\n    if ( (!UIManager::Get()->IsInDesktopMode()) && (!m_IsTransitionFading) )\n    {\n        const bool is_using_dashboard_state = (m_OverlayStateCurrentID == floating_window_ovrl_state_dashboard_tab);\n\n        if (is_using_dashboard_state != vr::VROverlay()->IsOverlayVisible(UIManager::Get()->GetOverlayHandleDPlusDashboard()))\n        {\n            OverlayStateSwitchCurrent(!is_using_dashboard_state);\n        }\n    }\n\n    //Overlay position and visibility\n    if (UIManager::Get()->IsOpenVRLoaded())\n    {\n        vr::VROverlayHandle_t overlay_handle = GetOverlayHandle();\n\n        if ( (m_OverlayStateCurrent->IsVisible) && (!m_OverlayStateCurrent->IsPinned) && (!UIManager::Get()->GetOverlayDragger().IsDragActive()) &&\n             (!UIManager::Get()->GetOverlayDragger().IsDragGestureActive()) )\n        {\n            //We don't update position when the dummy transform is unstable to avoid flicker, but we absolutely need to update it when the overlay is about to appear\n            if ( (!m_OvrlVisible) || (!UIManager::Get()->IsDummyOverlayTransformUnstable()) )\n            { \n                vr::TrackingUniverseOrigin origin = vr::TrackingUniverseStanding;\n                Matrix4 matrix_m4 = UIManager::Get()->GetOverlayDragger().GetBaseOffsetMatrix(ovrl_origin_dplus_tab) * m_OverlayStateCurrent->Transform;\n                vr::HmdMatrix34_t matrix_ovr = matrix_m4.toOpenVR34();\n\n                vr::VROverlay()->SetOverlayTransformAbsolute(overlay_handle, origin, &matrix_ovr);\n\n                m_OverlayStateCurrent->TransformAbs = matrix_m4;\n            }\n        }\n\n        if ((!m_OvrlVisible) && (m_OverlayStateCurrent->IsVisible))\n        {\n            vr::VROverlay()->ShowOverlay(overlay_handle);\n            m_OvrlVisible = true;\n        }\n        else if ((m_OvrlVisible) && (!m_OverlayStateCurrent->IsVisible) && (m_Alpha == 0.0f))\n        {\n            vr::VROverlay()->HideOverlay(overlay_handle);\n            m_OvrlVisible = false;\n        }\n    }\n}\n\nvoid FloatingWindow::Show(bool skip_fade)\n{\n    m_OverlayStateCurrent->IsVisible = true;\n\n    if (skip_fade)\n    {\n        m_Alpha = 1.0f;\n    }\n\n    UIManager::Get()->GetIdleState().AddActiveTime();\n}\n\nvoid FloatingWindow::Hide(bool skip_fade)\n{\n    m_OverlayStateCurrent->IsVisible = false;\n\n    if (skip_fade)\n    {\n        m_Alpha = 0.0f;\n    }\n\n    UIManager::Get()->GetIdleState().AddActiveTime();\n}\n\nvoid FloatingWindow::HideAll(bool skip_fade)\n{\n    Hide(skip_fade);\n\n    m_OverlayStateRoom.IsVisible         = false;\n    m_OverlayStateDashboardTab.IsVisible = false;\n}\n\nbool FloatingWindow::IsVisible() const\n{\n    return m_OverlayStateCurrent->IsVisible;\n}\n\nbool FloatingWindow::IsVisibleOrFading() const\n{\n    return ( (m_OverlayStateCurrent->IsVisible) || (m_Alpha != 0.0f) || (m_IsTransitionFading) );\n}\n\nfloat FloatingWindow::GetAlpha() const\n{\n    return m_Alpha;\n}\n\nvoid FloatingWindow::ApplyUIScale()\n{\n    m_Size.x = m_SizeUnscaled.x * UIManager::Get()->GetUIScale();\n    m_Size.y = m_SizeUnscaled.y * UIManager::Get()->GetUIScale();\n}\n\nbool FloatingWindow::CanUnpinRoom() const\n{\n    return m_AllowRoomUnpinning;\n}\n\nbool FloatingWindow::IsPinned() const\n{\n    return m_OverlayStateCurrent->IsPinned;\n}\n\nvoid FloatingWindow::SetPinned(bool is_pinned, bool no_state_copy)\n{\n    m_OverlayStateCurrent->IsPinned = is_pinned;\n\n    if (!UIManager::Get()->IsOpenVRLoaded())\n        return;\n\n    if (!is_pinned)\n    {\n        RebaseTransform();\n    }\n    else if (!no_state_copy)\n    {\n        if (m_OverlayStateCurrentID == floating_window_ovrl_state_dashboard_tab)\n        {\n            m_OverlayStateRoom.IsPinned     = m_OverlayStateDashboardTab.IsPinned;\n            m_OverlayStateRoom.Transform    = m_OverlayStateDashboardTab.Transform;\n            m_OverlayStateRoom.TransformAbs = m_OverlayStateDashboardTab.TransformAbs;\n        }\n\n        //Reset transform if TransformAbs doesn't have anything of value yet\n        if (m_OverlayStateRoom.TransformAbs.isZero())\n        {\n            ResetTransform(floating_window_ovrl_state_room);\n        }\n    }\n}\n\nFloatingWindowOverlayState& FloatingWindow::GetOverlayState(FloatingWindowOverlayStateID id)\n{\n    switch (id)\n    {\n        case floating_window_ovrl_state_room:          return m_OverlayStateRoom;\n        case floating_window_ovrl_state_dashboard_tab: return m_OverlayStateDashboardTab;\n    }\n\n    return m_OverlayStateRoom;\n}\n\nFloatingWindowOverlayStateID FloatingWindow::GetOverlayStateCurrentID()\n{\n    return m_OverlayStateCurrentID;\n}\n\nMatrix4& FloatingWindow::GetTransform()\n{\n    return m_OverlayStateCurrent->Transform;\n}\n\nvoid FloatingWindow::SetTransform(const Matrix4& transform)\n{\n    m_OverlayStateCurrent->Transform = transform;\n\n    //Store last absolute transform\n    vr::HmdMatrix34_t hmd_mat;\n    vr::TrackingUniverseOrigin universe_origin = vr::TrackingUniverseStanding;\n\n    vr::VROverlay()->GetOverlayTransformAbsolute(GetOverlayHandle(), &universe_origin, &hmd_mat);\n    m_OverlayStateCurrent->TransformAbs = hmd_mat;\n\n    //Store size multiplier\n    float current_width = m_OvrlWidth;\n    \n    if (vr::VROverlay()->GetOverlayWidthInMeters(GetOverlayHandle(), &current_width) == vr::VROverlayError_None)\n    {\n        m_OverlayStateCurrent->Size = current_width / m_OvrlWidth;\n    }\n}\n\nvoid FloatingWindow::ApplyCurrentOverlayState()\n{\n    if (!UIManager::Get()->IsOpenVRLoaded())\n        return;\n\n    if (m_OverlayStateCurrent->IsPinned)\n    {\n        vr::HmdMatrix34_t matrix_ovr = m_OverlayStateCurrent->TransformAbs.toOpenVR34();\n        vr::VROverlay()->SetOverlayTransformAbsolute(GetOverlayHandle(), vr::TrackingUniverseStanding, &matrix_ovr);\n    }\n\n    vr::VROverlay()->SetOverlayWidthInMeters(GetOverlayHandle(), m_OvrlWidth * m_OverlayStateCurrent->Size);\n}\n\nvoid FloatingWindow::RebaseTransform()\n{\n    vr::HmdMatrix34_t hmd_mat;\n    vr::TrackingUniverseOrigin universe_origin = vr::TrackingUniverseStanding;\n\n    vr::VROverlay()->GetOverlayTransformAbsolute(GetOverlayHandle(), &universe_origin, &hmd_mat);\n    Matrix4 mat_abs = hmd_mat;\n    Matrix4 mat_origin_inverse = UIManager::Get()->GetOverlayDragger().GetBaseOffsetMatrix(ovrl_origin_dplus_tab);\n\n    mat_origin_inverse.invert();\n    m_OverlayStateCurrent->Transform = mat_origin_inverse * mat_abs;\n}\n\nvoid FloatingWindow::ResetTransformAll()\n{\n    ResetTransform(floating_window_ovrl_state_dashboard_tab);\n    ResetTransform(floating_window_ovrl_state_room);\n\n    m_OverlayStateRoom.IsVisible = false;\n    m_OverlayStateRoom.IsPinned  = false;\n}\n\nvoid FloatingWindow::ResetTransform(FloatingWindowOverlayStateID state_id)\n{\n    GetOverlayState(state_id).Transform.identity();\n\n    //Set absolute transform to the dashboard tab one (as if pinning)\n    if (state_id == floating_window_ovrl_state_room)\n    {\n        if (!m_OverlayStateDashboardTab.TransformAbs.isZero())\n        {\n            m_OverlayStateRoom.TransformAbs = m_OverlayStateDashboardTab.TransformAbs;\n        }\n        else if ((UIManager::Get() != nullptr) && (UIManager::Get()->IsOpenVRLoaded())) //If the dashboard tab transform is still zero, generate a HMD facing transform instead (needs startup to be done though)\n        {\n            m_OverlayStateRoom.TransformAbs = vr::IVRSystemEx::ComputeHMDFacingTransform(1.25f);\n            UIManager::Get()->GetOverlayDragger().ApplyDashboardScale(m_OverlayStateRoom.TransformAbs);\n        }\n    }\n}\n\nvoid FloatingWindow::StartDrag()\n{\n    if (UIManager::Get()->IsOpenVRLoaded())\n    {\n        OverlayDragger& overlay_dragger = UIManager::Get()->GetOverlayDragger();\n\n        if ( (!overlay_dragger.IsDragActive()) && (!overlay_dragger.IsDragGestureActive()) )\n        {\n            overlay_dragger.DragStart(GetOverlayHandle(), m_DragOrigin);\n            overlay_dragger.DragSetMaxWidth(m_OvrlWidthMax);\n        }\n    }\n}\n\nconst ImVec2& FloatingWindow::GetPos() const\n{\n    return m_Pos;\n}\n\nconst ImVec2& FloatingWindow::GetSize() const\n{\n    return m_Size;\n}\n\nbool FloatingWindow::TranslatedComboAnimated(const char* label, int& value, TRMGRStrID trstr_min, TRMGRStrID trstr_max)\n{\n    bool ret = false;\n    const char* preview_value = ((trstr_min + value >= trstr_min) && (trstr_min + value <= trstr_max)) ? TranslationManager::GetString( (TRMGRStrID)(trstr_min + value) ) : \"???\";\n\n    if (ImGui::BeginComboAnimated(label, preview_value))\n    {\n        //Make use of the fact values and translation string IDs are laid out sequentially and shorten this to a nice loop\n        const int value_max = (trstr_max - trstr_min) + 1;\n        for (int i = 0; i < value_max; ++i)\n        {\n            ImGui::PushID(i);\n\n            if (ImGui::Selectable(TranslationManager::GetString( (TRMGRStrID)(trstr_min + i) ), (value == i)))\n            {\n                value = i;\n                ret = true;\n            }\n\n            ImGui::PopID();\n        }\n\n        ImGui::EndCombo();\n    }\n\n    return ret;\n}\n"
  },
  {
    "path": "src/DesktopPlusUI/FloatingWindow.h",
    "content": "#pragma once\n\n#include \"OverlayDragger.h\"\n#include \"TextureManager.h\"\n#include \"TranslationManager.h\"\n#include \"OverlayManager.h\"\n#include <string>\n\nenum FloatingWindowOverlayStateID\n{\n    floating_window_ovrl_state_room,\n    floating_window_ovrl_state_dashboard_tab\n};\n\nstruct FloatingWindowOverlayState\n{\n    bool IsVisible = false;\n    bool IsPinned  = false;\n    float Size = 1.0f;\n    Matrix4 Transform;\n    Matrix4 TransformAbs;\n};\n\nstruct FloatingWindowInputOverlayTagsState\n{\n    std::string TagEditOrigStr;\n    char TagEditBuffer[1024]   = \"\";\n    float ChildHeightLines     = 1.0f;\n\n    std::vector<OverlayManager::TagListEntry> KnownTagsList;\n    ImGuiTextFilter KnownTagsFilter;\n    bool IsTagAlreadyInBuffer  = false;\n    bool FocusTextInput        = false;\n\n    bool PopupShow             = false;\n    float PopupAlpha           = 0.0f;\n    float PopupHeight          = FLT_MIN;\n    float PopupHeightPrev      = FLT_MIN;\n    ImGuiDir PosDir            = ImGuiDir_Down;\n    ImGuiDir PosDirDefault     = ImGuiDir_Down;\n    float PosAnimationProgress = 0.0f;\n    bool IsFadingOut           = false;\n};\n\nstruct FloatingWindowActionOrderListState\n{\n    std::vector<ActionManager::ActionNameListEntry> ActionsList;\n    ActionManager::ActionList ActionListOrig;\n    bool HasSavedChanges       = false;\n    int KeyboardSwappedIndex   = -1;\n    int SelectedIndex          = -1;\n    ActionUID HoveredAction    = k_ActionUID_Invalid;    \n};\n\nstruct FloatingWindowActionAddSelectorState\n{\n    std::vector<ActionManager::ActionNameListEntry> ActionsList;\n    std::vector<char> ActionsTickedList;\n    bool IsAnyActionTicked = false;\n};\n\n//Base class for drag-able floating overlay windows, such as the Settings, Overlay Properties and Keyboard windows\nclass FloatingWindow\n{\n    protected:\n        float m_OvrlWidth;\n        float m_OvrlWidthMax;             //Maximum width passed to OverlayDragger\n        float m_Alpha;\n        bool m_OvrlVisible;\n        bool m_IsTransitionFading;\n\n        FloatingWindowOverlayStateID m_OverlayStateCurrentID;\n        FloatingWindowOverlayState m_OverlayStateRoom;\n        FloatingWindowOverlayState m_OverlayStateDashboardTab;\n        FloatingWindowOverlayState m_OverlayStateFading;\n        FloatingWindowOverlayState* m_OverlayStateCurrent;\n        FloatingWindowOverlayState* m_OverlayStatePending;\n\n        std::string m_WindowTitle;\n        std::string m_WindowID;\n        TRMGRStrID m_WindowTitleStrID;\n        TMNGRTexID m_WindowIcon;\n        int m_WindowIconWin32IconCacheID; //TextureManager Icon cache ID when using a Win32 window icon as the ImGui window icon\n\n        ImVec2 m_Pos;\n        ImVec2 m_PosPivot;\n        ImVec2 m_Size;                   //Set in derived constructor, 2 pixel-wide padding around actual texture space expected\n        ImVec2 m_SizeUnscaled;           //Set in derived constructor, size before applying UI scale factor, so equal to m_Size initally\n        ImGuiWindowFlags m_WindowFlags;\n        bool m_AllowRoomUnpinning;       //Set to enable pin button while room overlay state is active\n        OverlayOrigin m_DragOrigin;      //Origin passed to OverlayDragger for window drags, doesn't affect overlay positioning (override relevant functions instead)\n\n        float m_TitleBarMinWidth;\n        float m_TitleBarTitleMaxWidth;   //Width available for the title string without icon and buttons\n        float m_TitleBarTitleIconAlpha;  //Alpha value applied to both window title text & icon\n        bool m_IsTitleBarHovered;\n        bool m_IsTitleIconClicked;\n        bool m_HasAppearedOnce;\n        bool m_IsWindowAppearing;\n\n        float m_CompactTableHeaderHeight;\n\n        void WindowUpdateBase();         //Sets up ImGui window with custom title bar, pinning and overlay-based dragging\n        virtual void WindowUpdate() = 0; //Window content, called within an ImGui Begin()'d window\n\n        void OverlayStateSwitchCurrent(bool use_dashboard_tab);\n        void OverlayStateSwitchFinish();\n\n        virtual void OnWindowPinButtonPressed();         //Called when the pin button is pressed, after updating overlay state\n        virtual void OnWindowCloseButtonPressed();       //Called when the close button is pressed, after updating overlay state\n        virtual bool IsVirtualWindowItemHovered() const; //Returns false by default, can be overridden to signal hover state of widgets that don't touch global ImGui state (used for blank space drag)\n\n        void HelpMarker(const char* desc, const char* marker_str = \"(?)\") const;    //Help marker, but tooltip is fixed to top or bottom of the window\n        void UpdateLimiterSetting(bool is_override) const;\n\n        //Input widget for a collection of overlay tags. clip_parent_depth is the depth of parent window look up for popup's clipping rect, change when used in nested child windows\n        static bool InputOverlayTags(const char* str_id, char* buffer_tags, size_t buffer_tags_size, FloatingWindowInputOverlayTagsState& state, int clip_parent_depth = 0, bool show_auto_tags = true);\n\n        //Almost entire pages but implemented here to be shared between multiple windows\n        bool ActionOrderList(ActionManager::ActionList& list_actions_target, bool is_appearing, bool is_returning, FloatingWindowActionOrderListState& state, \n                             bool& go_add_actions, float height_offset = 0.0f);\n        bool ActionAddSelector(ActionManager::ActionList& list_actions_target, bool is_appearing, FloatingWindowActionAddSelectorState& state, float height_offset = 0.0f);\n\n        //BeginTable(), but with some hacks to allow for compact, gap-less selectable + border around header (always set). Fairly specific, so not a generic ImGui extension\n        //Text has to be aligned to frame padding with this\n        bool BeginCompactTable(const char* str_id, int column, ImGuiTableFlags flags = 0, const ImVec2& outer_size = ImVec2(0.0f, 0.0f), float inner_width = 0.0f);\n        void CompactTableHeadersRow();\n        void EndCompactTable();\n\n    public:\n        FloatingWindow();\n        virtual ~FloatingWindow() = default;\n        void Update();                   //Not called when idling (no windows visible)\n        virtual void UpdateVisibility(); //Only called in VR mode\n\n        virtual void Show(bool skip_fade = false);\n        virtual void Hide(bool skip_fade = false);\n        void HideAll(bool skip_fade = false);       //Hide(), but applies to all overlay visibility states\n        bool IsVisible() const;\n        bool IsVisibleOrFading() const;  //Returns true if m_Visible is true *or* m_Alpha isn't 0 yet\n        float GetAlpha() const;\n\n        virtual void ApplyUIScale();\n\n        bool CanUnpinRoom() const;\n        bool IsPinned() const;\n        void SetPinned(bool is_pinned, bool no_state_copy = false);  //no_state_copy = don't copy dashboard state to room (default behavior for pin button)\n\n        FloatingWindowOverlayState& GetOverlayState(FloatingWindowOverlayStateID id);\n        FloatingWindowOverlayStateID GetOverlayStateCurrentID();\n\n        Matrix4& GetTransform();\n        void SetTransform(const Matrix4& transform);\n        virtual void ApplyCurrentOverlayState(); //Applies current absolute transform to the overlay if pinned and sets the width\n        virtual void RebaseTransform();\n        virtual void ResetTransformAll();\n        virtual void ResetTransform(FloatingWindowOverlayStateID state_id);\n\n        virtual void StartDrag(); //Starts a regular laser pointer drag of the window with the necessary parameters\n\n        const ImVec2& GetPos() const;\n        const ImVec2& GetSize() const;\n        virtual vr::VROverlayHandle_t GetOverlayHandle() const = 0;\n\n        static bool TranslatedComboAnimated(const char* label, int& value, TRMGRStrID trstr_min, TRMGRStrID trstr_max);\n};"
  },
  {
    "path": "src/DesktopPlusUI/ImGuiExt.cpp",
    "content": "#include \"ImGuiExt.h\"\n\n#include <string>\n\n#ifndef IMGUI_DEFINE_MATH_OPERATORS\n    #define IMGUI_DEFINE_MATH_OPERATORS\n#endif\n#include \"imgui_internal.h\"\n#include \"UIManager.h\"\n#include \"Util.h\"\n\nImVec4 Style_ImGuiCol_TextNotification;\nImVec4 Style_ImGuiCol_TextWarning;\nImVec4 Style_ImGuiCol_TextError;\nImVec4 Style_ImGuiCol_TextOutline;\nImVec4 Style_ImGuiCol_ButtonPassiveToggled;\nImVec4 Style_ImGuiCol_SteamVRCursor;\nImVec4 Style_ImGuiCol_SteamVRCursorBorder;\n\nnamespace ImGui\n{\n    //Like InputFloat()'s buttons but with a slider instead. Not quite as flexible, though. Always takes as much space as available.\n    bool SliderWithButtonsFloat(const char* str_id, float& value, float step, float step_small, float min, float max, const char* format, ImGuiSliderFlags flags, bool* used_button, const char* text_alt)\n    {\n        //Hacky solution to make right mouse enable text input on the slider while not touching ImGui code or generalizing it as ctrl press\n        ImGuiIO& io = ImGui::GetIO();\n        const bool  mouse_left_clicked_old       = io.MouseClicked[ImGuiMouseButton_Left];\n        const bool  mouse_left_down_old          = io.MouseDown[ImGuiMouseButton_Left];\n        const float mouse_left_down_duration_old = io.MouseDownDuration[ImGuiMouseButton_Left];\n        const bool key_ctrl_old = io.KeyCtrl;\n\n        if (io.MouseClicked[ImGuiMouseButton_Right])\n        {\n            io.MouseClicked[ImGuiMouseButton_Left]      = true;\n            io.MouseDown[ImGuiMouseButton_Left]         = true;\n            io.MouseDownDuration[ImGuiMouseButton_Left] = 0.0f;\n            io.KeyCtrl = true;\n            io.KeyMods |= ImGuiMod_Ctrl; //KeyMods needs to stay consistent with KeyCtrl\n        }\n\n        //Use small step value when shift is down\n        if (io.KeyShift)\n        {\n            step = step_small;\n        }\n\n        ImGuiStyle& style = ImGui::GetStyle();\n\n        const float value_old = value;\n        const ImVec2 button_size(ImGui::GetFrameHeight(), ImGui::GetFrameHeight());\n\n        ImGui::BeginGroup();\n\n        ImGui::PushID(str_id);\n        ImGui::PushItemFlag(ImGuiItemFlags_ButtonRepeat, true);\n\n        //Calulate slider width (GetContentRegionAvail() returns 1 more than when using -1 width to fill)\n        ImGui::SetNextItemWidth((ImGui::GetContentRegionAvail().x - (ImGui::GetFrameHeight() + ImGui::GetStyle().ItemInnerSpacing.x) * 2) - 1.0f);\n        ImGui::SliderFloat(\"##Slider\", &value, min, max, format, flags);\n\n        if ( (text_alt != nullptr) && (ImGui::GetCurrentContext()->TempInputId != ImGui::GetID(\"##Slider\")) )\n        {\n            ImGui::RenderTextClipped(ImGui::GetItemRectMin(), ImGui::GetItemRectMax(), text_alt, nullptr, nullptr, ImVec2(0.5f, 0.5f));\n        }\n\n        bool has_slider_deactivated = false;\n        if (ImGui::IsItemDeactivated())\n        {\n            has_slider_deactivated = true;\n        }\n\n        ImGui::SameLine(0.0f, style.ItemInnerSpacing.x);\n\n        if (ImGui::Button(\"-\", button_size))\n        {\n            //Round to the step value while we're at it. This may not be the most expected thing at first, but it helps a lot to get the usually preferred even values\n            int step_count = (int)ceilf(value / step);\n\n            value = step_count * step;\n            value -= step;\n\n            if ( int( ceilf(value / step) ) >= step_count ) //Welcome to floating point math, this can happen\n                value -= step / 10000.f;                    //This works for what we need, but not quite elegant indeed\n\n            if (used_button)\n                *used_button = true;\n        }\n\n        ImGui::SameLine(0.0f, style.ItemInnerSpacing.x);\n\n        if (ImGui::Button(\"+\", button_size))\n        {\n            int step_count = int(value / step);\n\n            value = step_count * step;\n            value += step;\n\n            if ( int(value / step) <= step_count ) //See above\n                value += step / 10000.f;\n\n            if (used_button)\n                *used_button = true;\n        }\n\n        ImGui::PopItemFlag();   //ImGuiItemFlag_ButtonRepeat\n        ImGui::PopID();\n\n        ImGui::EndGroup();\n\n        //Deactivated flag for the slider gets swallowed up somewhere, but we really need it for the VR keyboard, so we tape it back on here\n        if (has_slider_deactivated)\n        {\n            ImGui::GetCurrentContext()->LastItemData.StatusFlags |= ImGuiItemStatusFlags_HasDeactivated | ImGuiItemStatusFlags_Deactivated;\n        }\n\n        //We generally don't want -0.0 to be a thing, so prevent it\n        if (value == -0.0f)\n            value = 0.0f;\n\n        //Restore hack\n        io.MouseClicked[ImGuiMouseButton_Left]      = mouse_left_clicked_old;\n        io.MouseDown[ImGuiMouseButton_Left]         = mouse_left_down_old;\n        io.MouseDownDuration[ImGuiMouseButton_Left] = mouse_left_down_duration_old;\n        io.KeyCtrl = key_ctrl_old;\n        if (!io.KeyCtrl)\n        {\n            io.KeyMods &= ~ImGuiMod_Ctrl;\n        }\n\n        return (value != value_old);\n    }\n\n    bool SliderWithButtonsInt(const char* str_id, int& value, int step, int step_small, int min, int max, const char* format, ImGuiSliderFlags flags, bool* used_button, const char* text_alt)\n    {\n        //Hacky solution to make right mouse enable text input on the slider while not touching ImGui code or generalizing it as ctrl press\n        ImGuiIO& io = ImGui::GetIO();\n        const bool  mouse_left_clicked_old       = io.MouseClicked[ImGuiMouseButton_Left];\n        const bool  mouse_left_down_old          = io.MouseDown[ImGuiMouseButton_Left];\n        const float mouse_left_down_duration_old = io.MouseDownDuration[ImGuiMouseButton_Left];\n        const bool key_ctrl_old = io.KeyCtrl;\n\n        if (io.MouseClicked[ImGuiMouseButton_Right])\n        {\n            io.MouseClicked[ImGuiMouseButton_Left]      = true;\n            io.MouseDown[ImGuiMouseButton_Left]         = true;\n            io.MouseDownDuration[ImGuiMouseButton_Left] = 0.0f;\n            io.KeyCtrl = true;\n            io.KeyMods |= ImGuiMod_Ctrl; //KeyMods needs to stay consistent with KeyCtrl\n        }\n\n        //Use small step value when shift is down\n        if (io.KeyShift)\n        {\n            step = step_small;\n        }\n\n        ImGuiStyle& style = ImGui::GetStyle();\n\n        const int value_old = value;\n        const ImVec2 button_size(ImGui::GetFrameHeight(), ImGui::GetFrameHeight());\n\n        ImGui::BeginGroup();\n\n        ImGui::PushID(str_id);\n        ImGui::PushItemFlag(ImGuiItemFlags_ButtonRepeat, true);\n\n        //Calulate slider width (GetContentRegionAvail() returns 1 more than when using -1 width to fill)\n        ImGui::SetNextItemWidth((ImGui::GetContentRegionAvail().x - (ImGui::GetFrameHeight() + ImGui::GetStyle().ItemInnerSpacing.x) * 2) - 1.0f);\n        ImGui::SliderInt(\"##Slider\", &value, min, max, format, flags);\n\n        if ( (text_alt != nullptr) && (ImGui::GetCurrentContext()->TempInputId != ImGui::GetID(\"##Slider\")) )\n        {\n            ImGui::RenderTextClipped(ImGui::GetItemRectMin(), ImGui::GetItemRectMax(), text_alt, nullptr, nullptr, ImVec2(0.5f, 0.5f));\n        }\n\n        bool has_slider_deactivated = false;\n        if (ImGui::IsItemDeactivated())\n        {\n            has_slider_deactivated = true;\n        }\n\n        ImGui::SameLine(0.0f, style.ItemInnerSpacing.x);\n\n        if (ImGui::Button(\"-\", button_size))\n        {\n            //Also rounding to steps here\n            value = (int)ceilf((float)value / step) * step;\n            value -= step;\n\n            if (used_button)\n                *used_button = true;\n        }\n\n        ImGui::SameLine(0.0f, style.ItemInnerSpacing.x);\n\n        if (ImGui::Button(\"+\", button_size))\n        {\n            value = (value / step) * step;\n            value += step;\n\n            if (used_button)\n                *used_button = true;\n        }\n\n        ImGui::PopItemFlag();   //ImGuiItemFlag_ButtonRepeat\n        ImGui::PopID();\n\n        ImGui::EndGroup();\n\n        //Deactivated flag for the slider gets swallowed up somewhere, but we really need it for the VR keyboard, so we tape it back on here\n        if (has_slider_deactivated)\n        {\n            ImGui::GetCurrentContext()->LastItemData.StatusFlags |= ImGuiItemStatusFlags_HasDeactivated | ImGuiItemStatusFlags_Deactivated;\n        }\n\n        //Restore hack\n        io.MouseClicked[ImGuiMouseButton_Left]      = mouse_left_clicked_old;\n        io.MouseDown[ImGuiMouseButton_Left]         = mouse_left_down_old;\n        io.MouseDownDuration[ImGuiMouseButton_Left] = mouse_left_down_duration_old;\n        io.KeyCtrl = key_ctrl_old;\n        if (!io.KeyCtrl)\n        {\n            io.KeyMods &= ~ImGuiMod_Ctrl;\n        }\n\n        return (value != value_old);\n    }\n\n    bool SliderWithButtonsFloatPercentage(const char* str_id, float& value, int step, int step_small, int min, int max, const char* format, ImGuiSliderFlags flags, bool* used_button, const char* text_alt)\n    {\n        int value_ui = int(value * 100.0f);\n\n        if (ImGui::SliderWithButtonsInt(str_id, value_ui, step, step_small, min, max, format, flags, used_button, text_alt))\n        {\n            value = value_ui / 100.0f;\n\n            //Floating point hell hacky fix (slider can get stuck when using + button otherwise)\n            int intvalue = int(value * 100.0f), intvalue_prev = intvalue;\n            while (intvalue < value_ui) \n            {\n                value += step / 10000.f;\n\n                intvalue_prev = intvalue;\n                intvalue = int(value * 100.0f);\n\n                if (intvalue == intvalue_prev) //Sanity check to avoid endless loop at big value + small step combinations\n                {\n                    break;\n                }\n            }\n\n            return true;\n        }\n\n        return false;\n    }\n\n    ImGuiID SliderWithButtonsGetSliderID(const char* str_id)\n    {\n        ImGui::PushID(str_id);\n        ImGuiID id = ImGui::GetID(\"##Slider\");\n        ImGui::PopID();\n\n        return id;\n    }\n\n    //Like imgui_demo's HelpMarker, but with a fixed position tooltip\n    void FixedHelpMarker(const char* desc, const char* marker_str)\n    {\n        ImGui::TextDisabled(marker_str);\n        if (ImGui::IsItemHovered())\n        {\n            static float last_y_offset = FLT_MIN;       //Try to avoid getting the tooltip off-screen... the way it's done here is a bit messy to be fair\n\n            float pos_y = ImGui::GetItemRectMin().y;\n            bool is_invisible = false;\n\n            if (last_y_offset == FLT_MIN) //Same as IsWindowAppearing except the former doesn't work before beginning the window which is too late for the position...\n            {\n                //We need to create the tooltip window for size calculations to happen but also don't want to see it... so alpha 0, even if wasteful\n                ImGui::PushStyleVar(ImGuiStyleVar_Alpha, 0.0f);\n                is_invisible = true;\n            }\n            else\n            {\n                ImGui::SetNextWindowPos(ImVec2(ImGui::GetItemRectMax().x + ImGui::GetStyle().ItemInnerSpacing.x, ImGui::GetItemRectMin().y + last_y_offset));\n            }\n\n            ImGui::BeginTooltip();\n            ImGui::PushTextWrapPos(ImGui::GetFontSize() * 35.0f);\n            ImGui::PushTextWrapPos(ImGui::GetIO().DisplaySize.x - ImGui::GetCursorScreenPos().x);\n            ImGui::TextUnformatted(desc);\n            ImGui::PopTextWrapPos();\n            ImGui::PopTextWrapPos();\n\n            if (ImGui::IsWindowAppearing()) //New tooltip, reset offset\n            {\n                //The window size isn't available in this frame yet, so we'll have to skip the having it visible for one more frame and then act on it\n                last_y_offset = FLT_MIN;\n            }\n            else\n            {\n                if (pos_y + ImGui::GetWindowSize().y > ImGui::GetIO().DisplaySize.y) //If it would be partially off-screen\n                {\n                    last_y_offset = ImGui::GetIO().DisplaySize.y - (pos_y + ImGui::GetWindowSize().y);\n                }\n                else //Use normal pos\n                {\n                    last_y_offset = 0.0f;\n                }\n            }\n\n            ImGui::EndTooltip();\n\n            if (is_invisible)\n                ImGui::PopStyleVar(); //ImGuiStyleVar_Alpha\n\n        }\n    }\n\n    bool ButtonWithWrappedLabel(const char* label, const ImVec2& size)\n    {\n        //This could probably be solved in a cleaner way, but it's not /that/ dirty\n        ImGui::PushID(label);\n        ImGui::BeginGroup();\n\n        ImGuiWindow* window = GetCurrentWindow();\n        const ImGuiStyle& style = ImGui::GetStyle();\n\n        bool ret = ImGui::Button(\"\", ImVec2(size.x + (style.FramePadding.x * 2.0f), size.y + (style.FramePadding.y * 2.0f)) );\n        ImGui::SameLine(0.0f, 0.0f);\n        ImGui::SetCursorPosX(ImGui::GetCursorPosX() - size.x - style.FramePadding.x);\n        ImGui::SetCursorPosY(ImGui::GetCursorPosY() + style.FramePadding.y);\n        \n        //Prevent child window moving line pos\n        float cursor_max_pos_prev_y = window->DC.CursorMaxPos.y;\n\n        ImGui::BeginChild(\"ButtonLabel\", ImVec2(size.x + style.FramePadding.x, size.y), false, ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoInputs);\n\n        ImVec2 text_size = ImGui::CalcTextSize(label, nullptr, false, size.x);\n        ImGui::SetCursorPosX( (size.x / 2.0f) - int(text_size.x / 2.0f) );\n        ImGui::SetCursorPosY( (size.x / 2.0f) - int(text_size.y / 2.0f) );\n\n        ImGui::PushTextWrapPos(size.x);\n        ImGui::TextUnformatted(label);\n        ImGui::PopTextWrapPos();\n\n        ImGui::EndChild();\n\n        window->DC.CursorMaxPos.y = cursor_max_pos_prev_y;\n\n        ImGui::EndGroup();\n        ImGui::PopID();\n\n        return ret;\n    }\n\n    void RenderButtonMultilineLabel(const char* label, float line_height_scale)\n    {\n        const ImGuiContext& g = *GImGui;\n        RenderButtonMultilineLabel(label, g.LastItemData.Rect, line_height_scale);\n    }\n\n    void RenderButtonMultilineLabel(const char* label, const ImRect& bb, float line_height_scale)\n    {\n        struct label_line_data\n        {\n            const char* text_start;\n            const char* text_end;\n            ImVec2 text_alignment;\n        };\n\n        const ImGuiStyle& style = ImGui::GetStyle();\n        const float line_height = ImGui::GetFontSize() * line_height_scale;\n\n        //Provided bounding box is intended to be for the button or whatever frame it's rendered on (also used for clipping), so apply inner padding here (vertical one not required)\n        ImRect bb_inner = bb;\n        bb_inner.Min.x += style.FramePadding.x;\n        bb_inner.Max.x -= style.FramePadding.x;\n\n        //Split label string into line segments (via offsets, no copying)\n        std::vector<label_line_data> label_lines;\n        float total_height = ImGui::GetFontSize();  //First line is always set and needs to be equal with font size, while other lines add line_height\n\n        {\n            const char* line_start = label;\n            const char* s = label;\n            float h_align = 0.5f;\n\n            for (;;)\n            {\n                if (*s == '\\0')\n                {\n                    label_lines.push_back({line_start, s, {h_align, 0.0f}});\n                    break;\n                }\n                else if (*s == '\\n')\n                {\n                    label_lines.push_back({line_start, s, {h_align, 0.0f}});\n\n                    //total_height starts with first line height so only add on newline characters, not string end\n                    total_height += line_height;\n                    line_start = s + 1;\n\n                    //Horizontal alignment defaults to center on line start\n                    h_align = 0.5f;\n                }\n                else if ((*s == '#') && (s[1] == '#')) //Double # is already being eaten by ImGui but not used for IDs in this context. Let's use it as control mechanism for horizontal alignment\n                {\n                    //Reminder this kind of advance access is fine because the string is always NUL-terminated and we didn't encounter any NULs\n                    if (s[2] == 'L')\n                    {\n                        h_align = 0.0f;\n                    }\n                    else if (s[2] == 'R')\n                    {\n                        h_align = 1.0f;\n                    }\n                }\n\n                ++s;\n            }\n        }\n\n        //Render the lines one-by-one to get correct horizontally centered alignment for each\n        ImVec2 pos_line;\n        const float base_offset_y = ((bb.Max.y - bb.Min.y) / 2.0f) - (total_height / 2.0f);\n\n        for (int i = 0; i < label_lines.size(); ++i)\n        {\n            const label_line_data& label_line = label_lines[i];\n\n            pos_line.x = bb_inner.Min.x;\n            pos_line.y = bb_inner.Min.y + base_offset_y + (line_height * i);\n\n            RenderTextClippedUnclamped(pos_line, bb_inner.Max, label_line.text_start, label_line.text_end, nullptr, label_line.text_alignment, &bb);\n        }\n    }\n\n    bool BeginComboWithInputText(const char* str_id, char* str_buffer, size_t buffer_size, bool& out_buffer_changed, bool& persist_input_visible,\n                                 bool& persist_input_activated, bool& persist_mouse_released_once, bool no_preview_text)\n    {\n        ImGuiContext& g = *GImGui;\n\n        out_buffer_changed = false;\n\n        if (persist_input_visible)\n        {\n            ImGui::PushID(\"InputText\");\n\n            g.NextItemData.Width  = ImGui::CalcItemWidth();\n            g.NextItemData.Width -= ImGui::GetFrameHeight();\n\n            if ((ImGui::InputText(str_id, str_buffer, buffer_size)))\n            {\n                out_buffer_changed = true;\n            }\n\n            ImGuiID input_text_id = ImGui::GetItemID();\n\n            if ( (persist_input_activated) && (persist_mouse_released_once) && (ImGui::PopupContextMenuInputText(str_id, str_buffer, buffer_size)) )\n            {\n                out_buffer_changed = true;\n            }\n\n            if (!persist_input_activated)\n            {\n                ImGui::ActivateItemByID(ImGui::GetItemID());\n                persist_input_activated = true;\n            }\n            else if ( (!ImGui::IsPopupOpen(str_id)) && ( (ImGui::IsItemDeactivated()) || (g.ActiveId != input_text_id) ) )\n            {\n                persist_input_visible = false;\n                persist_input_activated = false;\n                persist_mouse_released_once = false;\n                UIManager::Get()->RepeatFrame();\n            }\n\n            if (ImGui::IsMouseReleased(ImGuiMouseButton_Right))\n            {\n                persist_mouse_released_once = true;\n            }\n\n            ImGui::SameLine(0.0f, 0.0f);\n\n            ImGui::PopID();\n        }\n\n        return (ImGui::BeginCombo(str_id, (no_preview_text) ? \"\" : str_buffer, (persist_input_visible) ? (ImGuiComboFlags_NoPreview | ImGuiComboFlags_PopupAlignLeft) : ImGuiComboFlags_None));\n    }\n\n    void ComboWithInputTextActivationCheck(bool& persist_input_visible)\n    {\n        ImGuiContext& g = *GImGui;\n\n        //Right-click or Ctrl+Left-click to edit\n        if ( (ImGui::IsItemClicked(ImGuiMouseButton_Right)) || ((ImGui::IsItemClicked(ImGuiMouseButton_Left)) && g.IO.KeyCtrl) )\n        {\n            persist_input_visible = true;\n        }\n    }\n\n    static float CalcMaxPopupHeightFromItemCount(int items_count)\n    {\n        ImGuiContext& g = *GImGui;\n        if (items_count <= 0)\n            return FLT_MAX;\n        return (g.FontSize + g.Style.ItemSpacing.y) * items_count - g.Style.ItemSpacing.y + (g.Style.WindowPadding.y * 2);\n    }\n\n    bool BeginComboAnimated(const char* label, const char* preview_value, ImGuiComboFlags flags)\n    {\n        //-\n        //Custom code sections marked with //-\n        //Otherwise exactly the same as ImGui::BeginCombo() of ImGui v1.82 (with compat patches)\n        static float animation_progress = 0.0f;\n        static float scrollbar_alpha = 0.0f;\n        float popup_expected_width = FLT_MAX;\n        float popup_height = 0.0f;\n        //-\n\n        // Always consume the SetNextWindowSizeConstraint() call in our early return paths\n        ImGuiContext& g = *GImGui;\n        ImGuiWindow* window = GetCurrentWindow();\n\n        ImGuiNextWindowDataFlags backup_next_window_data_flags = g.NextWindowData.HasFlags;\n        g.NextWindowData.ClearFlags(); // We behave like Begin() and need to consume those values\n        if (window->SkipItems)\n            return false;\n\n        IM_ASSERT((flags & (ImGuiComboFlags_NoArrowButton | ImGuiComboFlags_NoPreview)) != (ImGuiComboFlags_NoArrowButton | ImGuiComboFlags_NoPreview)); // Can't use both flags together\n\n        const ImGuiStyle& style = g.Style;\n        const ImGuiID id = window->GetID(label);\n\n        const float arrow_size = (flags & ImGuiComboFlags_NoArrowButton) ? 0.0f : GetFrameHeight();\n        const ImVec2 label_size = CalcTextSize(label, NULL, true);\n        const float expected_w = CalcItemWidth();\n        const float w = (flags & ImGuiComboFlags_NoPreview) ? arrow_size : expected_w;\n        const ImRect frame_bb(window->DC.CursorPos, window->DC.CursorPos + ImVec2(w, label_size.y + style.FramePadding.y * 2.0f));\n        const ImRect total_bb(frame_bb.Min, frame_bb.Max + ImVec2(label_size.x > 0.0f ? style.ItemInnerSpacing.x + label_size.x : 0.0f, 0.0f));\n        ItemSize(total_bb, style.FramePadding.y);\n        if (!ItemAdd(total_bb, id, &frame_bb))\n            return false;\n\n        bool hovered, held;\n        bool pressed = ButtonBehavior(frame_bb, id, &hovered, &held);\n        bool popup_open = IsPopupOpen(id, ImGuiPopupFlags_None);\n\n        const ImU32 frame_col = GetColorU32(hovered ? ImGuiCol_FrameBgHovered : ImGuiCol_FrameBg);\n        const float value_x2 = ImMax(frame_bb.Min.x, frame_bb.Max.x - arrow_size);\n        RenderNavCursor(frame_bb, id);\n        if (!(flags & ImGuiComboFlags_NoPreview))\n            window->DrawList->AddRectFilled(frame_bb.Min, ImVec2(value_x2, frame_bb.Max.y), frame_col, style.FrameRounding, (flags & ImGuiComboFlags_NoArrowButton) ? ImDrawFlags_RoundCornersAll : ImDrawFlags_RoundCornersLeft);\n        if (!(flags & ImGuiComboFlags_NoArrowButton))\n        {\n            ImU32 bg_col = GetColorU32((popup_open || hovered) ? ImGuiCol_ButtonHovered : ImGuiCol_Button);\n            ImU32 text_col = GetColorU32(ImGuiCol_Text);\n            window->DrawList->AddRectFilled(ImVec2(value_x2, frame_bb.Min.y), frame_bb.Max, bg_col, style.FrameRounding, (w <= arrow_size) ? ImDrawFlags_RoundCornersAll : ImDrawFlags_RoundCornersRight);\n            if (value_x2 + arrow_size - style.FramePadding.x <= frame_bb.Max.x)\n                RenderArrow(window->DrawList, ImVec2(value_x2 + style.FramePadding.y, frame_bb.Min.y + style.FramePadding.y), text_col, ImGuiDir_Down, 1.0f);\n        }\n        RenderFrameBorder(frame_bb.Min, frame_bb.Max, style.FrameRounding);\n        if (preview_value != NULL && !(flags & ImGuiComboFlags_NoPreview))\n        {\n            ImVec2 preview_pos = frame_bb.Min + style.FramePadding;\n            if (g.LogEnabled)\n                LogSetNextTextDecoration(\"{\", \"}\");\n            RenderTextClipped(preview_pos, ImVec2(value_x2, frame_bb.Max.y), preview_value, NULL, NULL, ImVec2(0.0f, 0.0f));\n        }\n        if (label_size.x > 0)\n            RenderText(ImVec2(frame_bb.Max.x + style.ItemInnerSpacing.x, frame_bb.Min.y + style.FramePadding.y), label);\n\n        if ((pressed || g.NavActivateId == id) && !popup_open)\n        {\n            if (window->DC.NavLayerCurrent == 0)\n                window->NavLastIds[0] = id;\n            OpenPopupEx(id, ImGuiPopupFlags_None);\n            popup_open = true;\n\n            //-\n            //Reset animation\n            animation_progress = 0.0f;\n            scrollbar_alpha = 0.0f;\n            //-\n        }\n\n        if (!popup_open)\n            return false;\n\n        //-\n        ImVec2 clip_min = ImGui::GetCursorScreenPos();\n        clip_min.y -= style.FramePadding.y + style.PopupBorderSize;\n        //-\n\n        g.NextWindowData.HasFlags = backup_next_window_data_flags;\n\n        // Set popup size\n        if (g.NextWindowData.HasFlags & ImGuiNextWindowDataFlags_HasSizeConstraint)\n        {\n            g.NextWindowData.SizeConstraintRect.Min.x = ImMax(g.NextWindowData.SizeConstraintRect.Min.x, w);\n        }\n        else\n        {\n            if ((flags & ImGuiComboFlags_HeightMask_) == 0)\n                flags |= ImGuiComboFlags_HeightRegular;\n            IM_ASSERT(ImIsPowerOfTwo(flags & ImGuiComboFlags_HeightMask_)); // Only one\n            int popup_max_height_in_items = -1;\n            if (flags & ImGuiComboFlags_HeightRegular)     popup_max_height_in_items = 8;\n            else if (flags & ImGuiComboFlags_HeightSmall)  popup_max_height_in_items = 4;\n            else if (flags & ImGuiComboFlags_HeightLarge)  popup_max_height_in_items = 20;\n            ImVec2 constraint_min(0.0f, 0.0f), constraint_max(FLT_MAX, FLT_MAX);\n            if ((g.NextWindowData.HasFlags & ImGuiNextWindowDataFlags_HasSize) == 0 || g.NextWindowData.SizeVal.x <= 0.0f) // Don't apply constraints if user specified a size\n                constraint_min.x = w;\n            if ((g.NextWindowData.HasFlags & ImGuiNextWindowDataFlags_HasSize) == 0 || g.NextWindowData.SizeVal.y <= 0.0f)\n                constraint_max.y = CalcMaxPopupHeightFromItemCount(popup_max_height_in_items);\n            SetNextWindowSizeConstraints(constraint_min, constraint_max);\n        }\n\n        char name[16];\n        ImFormatString(name, IM_ARRAYSIZE(name), \"##Combo_%02d\", g.BeginPopupStack.Size); // Recycle windows based on depth\n\n        // Position the window given a custom constraint (peak into expected window size so we can position it)\n        // This might be easier to express with an hypothetical SetNextWindowPosConstraints() function.\n        if (ImGuiWindow* popup_window = FindWindowByName(name))\n            if (popup_window->WasActive)\n            {\n                // Always override 'AutoPosLastDirection' to not leave a chance for a past value to affect us.\n                ImVec2 size_expected = CalcWindowNextAutoFitSize(popup_window);\n                if (flags & ImGuiComboFlags_PopupAlignLeft)\n                    popup_window->AutoPosLastDirection = ImGuiDir_Left; // \"Below, Toward Left\"\n                else\n                    popup_window->AutoPosLastDirection = ImGuiDir_Down; // \"Below, Toward Right (default)\"\n                ImRect r_outer = GetPopupAllowedExtentRect(popup_window);\n                ImVec2 pos = FindBestWindowPosForPopupEx(frame_bb.GetBL(), size_expected, &popup_window->AutoPosLastDirection, r_outer, frame_bb, ImGuiPopupPositionPolicy_ComboBox);\n\n\n                //-\n                //Animated popup scrolling out of the widget\n                if (animation_progress < 1.0f)\n                {\n                    animation_progress += ImGui::GetIO().DeltaTime * 5.0f;\n                }\n                else\n                {\n                    animation_progress = 1.0f;\n                    scrollbar_alpha = std::min(scrollbar_alpha + (ImGui::GetIO().DeltaTime * 5.0f), 1.0f);\n                }\n\n                popup_height = IM_ROUND(smoothstep(animation_progress, 0.0f, size_expected.y)); //popup_height grows to full size in animation\n\n                //Offset position by animation progress\n                const bool pos_goes_down = (popup_window->AutoPosLastDirection == ImGuiDir_Down) || (popup_window->AutoPosLastDirection == ImGuiDir_Left);\n                pos.y += (pos_goes_down) ? -size_expected.y + popup_height : size_expected.y - popup_height;\n\n                popup_expected_width = size_expected.x;\n                //-\n\n                SetNextWindowPos(pos);\n            }\n            //-\n            else\n            {\n                //Expected width is not calculated in the first frame, having it default to larger than w is avoids flicker if it is the next frame. Kinda hacky to be fair\n                popup_expected_width = FLT_MAX;\n            }\n            //-\n\n        //-\n        //Hide background and border while animating since they don't get clipped (they're drawn manually below)\n        if (animation_progress != 1.0f)\n        {\n            ImGui::PushStyleColor(ImGuiCol_Border, 0);\n            ImGui::PushStyleColor(ImGuiCol_PopupBg, 0);\n        }\n\n        //Fade-in scrollbar colors\n        ImVec4 col;\n        for (int i = ImGuiCol_ScrollbarBg; i <= ImGuiCol_ScrollbarGrabActive; ++i)\n        {\n            col = style.Colors[i];\n            col.w *= scrollbar_alpha;\n            ImGui::PushStyleColor(i, col);\n        }\n        //-\n\n        // We don't use BeginPopupEx() solely because we have a custom name string, which we could make an argument to BeginPopupEx()\n        ImGuiWindowFlags window_flags = ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_Popup | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_NoMove;\n\n        //-\n        if (scrollbar_alpha == 0.0f)\n        {\n            if (popup_expected_width <= w)  //With popups wider than the widget, we run into resize issues when toggling the scrollbar. So we skip it there as it's the lesser evil\n            {\n                window_flags |= ImGuiWindowFlags_NoScrollbar; //Disable scrollbar when not visible so it can't be clicked accidentally\n            }\n        }\n        //-\n\n        // Horizontally align ourselves with the framed text\n        PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(style.FramePadding.x, style.WindowPadding.y));\n        bool ret = Begin(name, NULL, window_flags);\n        PopStyleVar();\n        if (!ret)\n        {\n            EndPopup();\n            IM_ASSERT(0);   // This should never happen as we tested for IsPopupOpen() above\n            return false;\n        }\n\n        //-\n        ImGui::PopStyleColor(4); //Scrollbar colors\n\n        if (animation_progress != 1.0f)\n        {\n            ImGui::PopStyleColor(2); //Border and PopupBg\n\n            ImGuiWindow* popup_window = GetCurrentWindow();\n\n            clip_min.x = popup_window->Pos.x;\n            ImVec2 clip_max = clip_min;\n            clip_max.x += popup_window->Size.x;\n\n            //Popup open direction\n            if ((popup_window->AutoPosLastDirection == ImGuiDir_Down) || (popup_window->AutoPosLastDirection == ImGuiDir_Left)) //ImGuiDir_Left is left aligned but still down\n            {\n                clip_max.y += popup_height + style.PopupBorderSize;\n\n                ImGui::PushClipRect(clip_min, clip_max, false);\n\n                clip_max.y -= style.PopupBorderSize;\n                popup_window->DrawList->AddRectFilled(clip_min, clip_max, GetColorU32(ImGuiCol_PopupBg));\n                popup_window->DrawList->AddRect(clip_min, clip_max, GetColorU32(ImGuiCol_Border));\n\n                clip_min.y += 1;\n            }\n            else //ImGuiDir_Up\n            {\n                clip_min.y -= frame_bb.GetHeight() + popup_height + style.PopupBorderSize;\n                clip_max.y -= frame_bb.GetHeight();\n\n                ImGui::PushClipRect(clip_min, clip_max, false);\n\n                clip_min.y += style.PopupBorderSize;\n                popup_window->DrawList->AddRectFilled(clip_min, clip_max, GetColorU32(ImGuiCol_PopupBg));\n                popup_window->DrawList->AddRect(clip_min, clip_max, GetColorU32(ImGuiCol_Border));\n            }\n\n            ImGui::PopClipRect();\n\n            //Push the clip rect for the window content... this doesn't get popped anywhere, but that seems to be harmless\n            ImGui::PushClipRect(GetCurrentWindow()->InnerClipRect.Min, GetCurrentWindow()->InnerClipRect.Max, false);\n            ImGui::PushClipRect(clip_min, clip_max, true);\n        }\n        //-\n\n        return true;\n    }\n\n    void TextRight(float offset_x, float fixed_w, const char* fmt, ...)\n    {\n        va_list args;\n        va_start(args, fmt);\n        TextRightV(offset_x, fixed_w, fmt, args);\n        va_end(args);\n    }\n\n    void TextRightV(float offset_x, float fixed_w, const char* fmt, va_list args)\n    {\n        ImGuiWindow* window = GetCurrentWindow();\n        if (window->SkipItems)\n            return;\n\n        ImGuiContext& g = *GImGui;\n        const char* text, *text_end;\n        ImFormatStringToTempBufferV(&text, &text_end, fmt, args);\n\n        TextRightUnformatted(offset_x, fixed_w, text, text_end);\n    }\n\n    void TextRightUnformatted(float offset_x, float fixed_w, const char* text, const char* text_end)\n    {\n        ImGuiWindow* window = GetCurrentWindow();\n        if (window->SkipItems)\n            return;\n\n        ImVec2 size = ImGui::CalcTextSize(text, text_end);\n        const float available_width = (fixed_w == 0.0f) ? ImGui::GetContentRegionAvail().x : fixed_w;\n        ImGui::SetCursorPosX(ImGui::GetCursorPosX() + (available_width - size.x - offset_x));\n\n        TextEx(text, text_end, ImGuiTextFlags_NoWidthForLargeClippedText);\n    }\n\n    void TextColoredUnformatted(const ImVec4& col, const char* text, const char* text_end)\n    {\n        ImGui::PushStyleColor(ImGuiCol_Text, col);\n        ImGui::TextUnformatted(text, text_end); \n        ImGui::PopStyleColor();\n    }\n\n    void TextOutlined(const char* fmt, ...)\n    {\n        va_list args;\n        va_start(args, fmt);\n        TextOutlinedV(fmt, args);\n        va_end(args);\n    }\n\n    void TextOutlinedV(const char* fmt, va_list args)\n    {\n        ImGuiWindow* window = GetCurrentWindow();\n        if (window->SkipItems)\n            return;\n\n        const char* text, *text_end;\n        ImFormatStringToTempBufferV(&text, &text_end, fmt, args);\n        TextUnformattedOutlined(text, text_end);\n    }\n\n    void TextUnformattedOutlined(const char* text, const char* text_end)\n    {\n        //This is mostly ImGui::TextUnformatted()\n        ImGuiWindow* window = GetCurrentWindow();\n        if (window->SkipItems)\n            return;\n        ImGuiContext& g = *GImGui;\n\n        // Accept null ranges\n        if (text == text_end)\n            text = text_end = \"\";\n\n        // Calculate length\n        const char* text_begin = text;\n        if (text_end == NULL)\n            text_end = text + ImStrlen(text); // FIXME-OPT\n\n        const ImVec2 pos(window->DC.CursorPos.x, window->DC.CursorPos.y + window->DC.CurrLineTextBaseOffset);\n\n        // Common case\n        const ImVec2 text_size = CalcTextSize(text_begin, text_end);\n\n        ImRect bb(pos, pos + text_size);\n        ItemSize(text_size, 0.0f);\n        if (!ItemAdd(bb, 0))\n            return;\n\n        // Render\n        const bool hide_text_after_hash = true;\n\n        // Hide anything after a '##' string\n        const char* text_display_end;\n        if (hide_text_after_hash)\n        {\n            text_display_end = FindRenderedTextEnd(text, text_end);\n        }\n        else\n        {\n            if (!text_end)\n                text_end = text + ImStrlen(text); // FIXME-OPT\n            text_display_end = text_end;\n        }\n\n        if (text != text_display_end)\n        {\n            const ImU32 col = GetColorU32(ImGuiCol_Text);\n            const ImU32 col_outline = GetColorU32(Style_ImGuiCol_TextOutline);\n\n            //This does indeed do 9x the AddText() calls of normal text, which is a bit wasteful but not too bad with the short strings this gets used with\n            ImVec2 pos_outline = pos;\n            pos_outline.y -= 1;\n            window->DrawList->AddText(g.Font, g.FontSize, pos_outline, col_outline, text, text_display_end);\n            pos_outline.x += 1;\n            window->DrawList->AddText(g.Font, g.FontSize, pos_outline, col_outline, text, text_display_end);\n            pos_outline.y += 1;\n            window->DrawList->AddText(g.Font, g.FontSize, pos_outline, col_outline, text, text_display_end);\n            pos_outline.y += 1;\n            window->DrawList->AddText(g.Font, g.FontSize, pos_outline, col_outline, text, text_display_end);\n            pos_outline.x -= 1;\n            window->DrawList->AddText(g.Font, g.FontSize, pos_outline, col_outline, text, text_display_end);\n            pos_outline.x -= 1;\n            window->DrawList->AddText(g.Font, g.FontSize, pos_outline, col_outline, text, text_display_end);\n            pos_outline.y -= 1;\n            window->DrawList->AddText(g.Font, g.FontSize, pos_outline, col_outline, text, text_display_end);\n            pos_outline.y -= 1;\n            window->DrawList->AddText(g.Font, g.FontSize, pos_outline, col_outline, text, text_display_end);\n\n            window->DrawList->AddText(g.Font, g.FontSize, pos, col, text, text_display_end);\n\n            if (g.LogEnabled)\n                LogRenderedText(&pos, text, text_display_end);\n        }\n    }\n\n    void TextRightOutlined(float offset_x, float fixed_w, const char* fmt, ...)\n    {\n        va_list args;\n        va_start(args, fmt);\n        TextRightOutlinedV(offset_x, fixed_w, fmt, args);\n        va_end(args);\n    }\n\n    void TextRightOutlinedV(float offset_x, float fixed_w, const char* fmt, va_list args)\n    {\n        ImGuiWindow* window = GetCurrentWindow();\n        if (window->SkipItems)\n            return;\n\n        const char* text, *text_end;\n        ImFormatStringToTempBufferV(&text, &text_end, fmt, args);\n        TextRightUnformattedOutlined(offset_x, fixed_w, text, text_end);\n    }\n\n    void TextRightUnformattedOutlined(float offset_x, float fixed_w, const char* text, const char* text_end)\n    {\n        ImGuiWindow* window = GetCurrentWindow();\n        if (window->SkipItems)\n            return;\n\n        const ImVec2 size = ImGui::CalcTextSize(text, text_end);\n        const float available_width = (fixed_w == 0.0f) ? ImGui::GetContentRegionAvail().x : fixed_w;\n        ImGui::SetCursorPosX(ImGui::GetCursorPosX() + (available_width - size.x - offset_x));\n\n        TextUnformattedOutlined(text, text_end);\n    }\n\n    void RenderTextClippedUnclamped(const ImVec2& pos_min, const ImVec2& pos_max, const char* text, const char* text_end, const ImVec2* text_size_if_known, const ImVec2& align, const ImRect* clip_rect)\n    {\n        // Hide anything after a '##' string\n        const char* text_display_end = FindRenderedTextEnd(text, text_end);\n        const int text_len = (int)(text_display_end - text);\n        if (text_len == 0)\n            return;\n\n        ImGuiContext& g = *GImGui;\n        ImGuiWindow* window = g.CurrentWindow;\n        //Aside from this line, this function is identical to ImGui::RenderTextClipped()\n        RenderTextClippedUnclampedEx(window->DrawList, pos_min, pos_max, text, text_display_end, text_size_if_known, align, clip_rect);\n        if (g.LogEnabled)\n            LogRenderedText(&pos_min, text, text_display_end);\n    }\n\n    void RenderTextClippedUnclampedEx(ImDrawList* draw_list, const ImVec2& pos_min, const ImVec2& pos_max, const char* text, const char* text_display_end, const ImVec2* text_size_if_known, const ImVec2& align, const ImRect* clip_rect)\n    {\n        // Perform CPU side clipping for single clipped element to avoid using scissor state\n        ImVec2 pos = pos_min;\n        const ImVec2 text_size = text_size_if_known ? *text_size_if_known : CalcTextSize(text, text_display_end, false, 0.0f);\n\n        const ImVec2* clip_min = clip_rect ? &clip_rect->Min : &pos_min;\n        const ImVec2* clip_max = clip_rect ? &clip_rect->Max : &pos_max;\n        bool need_clipping = (pos.x + text_size.x >= clip_max->x) || (pos.y + text_size.y >= clip_max->y);\n        if (clip_rect) // If we had no explicit clipping rectangle then pos==clip_min\n            need_clipping |= (pos.x < clip_min->x) || (pos.y < clip_min->y);\n\n        // Align whole block. We should defer that to the better rendering function when we'll have support for individual line alignment.\n        //Aside from these two lines, this function is identical to ImGui::RenderTextClippedEx()\n        if (align.x > 0.0f) pos.x = pos.x + (pos_max.x - pos.x - text_size.x) * align.x;\n        if (align.y > 0.0f) pos.y = pos.y + (pos_max.y - pos.y - text_size.y) * align.y;\n\n        // Render\n        if (need_clipping)\n        {\n            ImVec4 fine_clip_rect(clip_min->x, clip_min->y, clip_max->x, clip_max->y);\n            draw_list->AddText(nullptr, 0.0f, pos, GetColorU32(ImGuiCol_Text), text, text_display_end, 0.0f, &fine_clip_rect);\n        }\n        else\n        {\n            draw_list->AddText(nullptr, 0.0f, pos, GetColorU32(ImGuiCol_Text), text, text_display_end, 0.0f, nullptr);\n        }\n    }\n\n    int g_Stretched_BeginIndex = 0;\n    void BeginStretched()\n    {\n        g_Stretched_BeginIndex = ImGui::GetWindowDrawList()->VtxBuffer.Size;\n    }\n\n    void EndStretched(float scale_x)\n    {\n        auto& buffer = ImGui::GetWindowDrawList()->VtxBuffer;\n\n        const float base_x = (buffer.size() > g_Stretched_BeginIndex) ? buffer[g_Stretched_BeginIndex].pos.x : 0.0f;\n        for (int i = g_Stretched_BeginIndex; i < buffer.Size; ++i)\n        {\n            buffer[i].pos.x = ((buffer[i].pos.x - base_x) * scale_x) + base_x;\n        }\n    }\n\n    //Takes a nicely adjustable function and bolts it down to the options we need after making a few modifications\n    bool ColorPicker4Simple(const char* str_id, float col[4], float ref_col[4], const char* label_color_current, const char* label_color_original, float scale)\n    {\n        ImGuiWindow* window = GetCurrentWindow();\n        if (window->SkipItems)\n            return false;\n\n        //Hacky solution to make right mouse enable text input on the slider while not touching ImGui code or generalizing it as ctrl press\n        ImGuiIO& io = ImGui::GetIO();\n        const bool mouse_left_clicked_old = io.MouseClicked[ImGuiMouseButton_Left];\n        const float mouse_left_down_duration_old = io.MouseDownDuration[ImGuiMouseButton_Left];\n        const bool key_ctrl_old = io.KeyCtrl;\n\n        if (io.MouseClicked[ImGuiMouseButton_Right])\n        {\n            io.MouseDown[ImGuiMouseButton_Left]         = true;\n            io.MouseClicked[ImGuiMouseButton_Left]      = true;\n            io.MouseDownDuration[ImGuiMouseButton_Left] = 0.0f;\n            io.KeyCtrl = true;\n            io.KeyMods |= ImGuiMod_Ctrl; //Mods needs to stay consistent with KeyCtrl\n        }\n\n        ImGuiContext& g = *GImGui;\n        const ImGuiStyle& style = g.Style;\n\n        //Fixed flags, but we still kept some checks below in case we need to adjust later\n        const ImGuiColorEditFlags flags = ImGuiColorEditFlags_DisplayRGB | ImGuiColorEditFlags_Float | ImGuiColorEditFlags_AlphaBar | ImGuiColorEditFlags_NoSidePreview | \n                                          ImGuiColorEditFlags_NoLabel | ImGuiColorEditFlags_NoTooltip | ImGuiColorEditFlags_NoDragDrop;\n\n        const float start_y           = ImGui::GetCursorScreenPos().y;\n        const float square_sz         = ImGui::GetFrameHeight() * scale;\n        const float picker_width      = square_sz * 15.5f;\n        const float picker_bar_height = picker_width - (2.0f * (square_sz + style.ItemInnerSpacing.x));\n        g.NextItemData.ClearFlags();\n\n        ImGui::BeginGroup();\n        ImGui::PushID(str_id);\n\n        bool value_changed = false;\n\n        ImGuiColorEditFlags picker_flags_to_forward = ImGuiColorEditFlags_DataTypeMask_ | ImGuiColorEditFlags_PickerMask_ | ImGuiColorEditFlags_InputMask_ | ImGuiColorEditFlags_HDR | \n                                                      ImGuiColorEditFlags_NoAlpha | ImGuiColorEditFlags_AlphaBar | ImGuiColorEditFlags_NoTooltip | ImGuiColorEditFlags_NoDragDrop;\n\n        ImGuiColorEditFlags picker_flags = (flags & picker_flags_to_forward) | ImGuiColorEditFlags_DisplayMask_ | ImGuiColorEditFlags_NoLabel | ImGuiColorEditFlags_NoSidePreview | \n                                           ImGuiColorEditFlags_NoSmallPreview | ImGuiColorEditFlags_NoOptions;\n\n        ImGui::SetNextItemWidth(picker_width);\n        value_changed |= ImGui::ColorPicker4(\"##picker\", col, picker_flags, &g.ColorPickerRef.x);\n\n        //Restore hack\n        io.MouseDown[ImGuiMouseButton_Left]         = mouse_left_clicked_old;\n        io.MouseClicked[ImGuiMouseButton_Left]      = mouse_left_clicked_old;\n        io.MouseDownDuration[ImGuiMouseButton_Left] = mouse_left_down_duration_old;\n        io.KeyCtrl = key_ctrl_old;\n        if (!io.KeyCtrl)\n        {\n            io.KeyMods &= ~ImGuiMod_Ctrl;\n        }\n\n        //Picker style switching without popup\n        if ( (ImGui::IsItemClicked(ImGuiMouseButton_Right)) && (ImGui::GetMousePos().y <= start_y + picker_bar_height) ) //(don't react to text input clicks)\n        {\n            ImGuiColorEditFlags picker_flags = (g.ColorEditOptions & ImGuiColorEditFlags_PickerHueBar) ? ImGuiColorEditFlags_PickerHueWheel : ImGuiColorEditFlags_PickerHueBar;\n\n            g.ColorEditOptions = (g.ColorEditOptions & ~ImGuiColorEditFlags_PickerMask_) | (picker_flags & ImGuiColorEditFlags_PickerMask_);\n        }\n\n        //Side previews, but translatable\n        ImGui::SameLine(0.0f, style.ItemInnerSpacing.x);\n        window->DC.CursorPos.y -= style.ItemSpacing.y;\n\n        ImGui::BeginGroup();\n\n        ImGui::PushItemFlag(ImGuiItemFlags_NoNavDefaultFocus, true);\n        ImVec4 col_v4(col[0], col[1], col[2], (flags & ImGuiColorEditFlags_NoAlpha) ? 1.0f : col[3]);\n        if ((flags & ImGuiColorEditFlags_NoLabel))\n            ImGui::TextUnformatted(label_color_current);\n\n        ImGuiColorEditFlags sub_flags_to_forward = ImGuiColorEditFlags_InputMask_ | ImGuiColorEditFlags_HDR | ImGuiColorEditFlags_AlphaPreviewHalf | ImGuiColorEditFlags_NoTooltip | \n                                                   ImGuiColorEditFlags_NoDragDrop;\n\n        ImGui::ColorButton(\"##current\", col_v4, (flags & sub_flags_to_forward), ImVec2(square_sz * 3, square_sz * 2));\n        if (ref_col != nullptr)\n        {\n            ImGui::TextUnformatted(label_color_original);\n\n            ImVec4 ref_col_v4(ref_col[0], ref_col[1], ref_col[2], (flags & ImGuiColorEditFlags_NoAlpha) ? 1.0f : ref_col[3]);\n            if (ImGui::ColorButton(\"##original\", ref_col_v4, (flags & sub_flags_to_forward), ImVec2(square_sz * 3, square_sz * 2)))\n            {\n                memcpy(col, ref_col, ((flags & ImGuiColorEditFlags_NoAlpha) ? 3 : 4) * sizeof(float));\n                value_changed = true;\n            }\n        }\n\n        ImGui::PopItemFlag();   //ImGuiItemFlags_NoNavDefaultFocus\n        ImGui::EndGroup();\n\n        ImGui::PopID();\n        ImGui::EndGroup();\n\n        if (value_changed)\n            ImGui::MarkItemEdited(g.LastItemData.ID);\n\n        return value_changed;\n    }\n\n    bool CollapsingHeaderPadded(const char* label, ImGuiTreeNodeFlags flags)\n    {\n        ImGuiWindow* window = GetCurrentWindow();\n        //Move back a pixel to fix CollapsingHeader()'s position being off by one for some reason\n        window->DC.CursorPos.x -= 1.0f;\n\n        //Temporarily modify window padding to fool CollapsingHeader to size differently\n        const float padding_x = ImGui::GetStyle().ItemInnerSpacing.x / 2.0f;\n        window->WindowPadding.x -= padding_x;\n        bool ret = ImGui::CollapsingHeader(label, flags);\n        window->WindowPadding.x += padding_x;\n\n        return ret;\n    }\n\n    struct CollapsingAreaState\n    {\n        ImGuiID WidgetID        = 0;\n        float WidgetBeginY      = 0.0f;\n        bool PushedClipRect     = false;\n        bool PushedItemDisabled = false;\n    };\n\n    ImVector<CollapsingAreaState> g_CollapsingArea_Stack;\n\n    void BeginCollapsingArea(const char* str_id, bool show_content, float& persist_animation_progress)\n    {\n        g_CollapsingArea_Stack.push_back(CollapsingAreaState());\n        CollapsingAreaState& state = g_CollapsingArea_Stack.back();\n\n        //Animate when changing between show_content state\n        const float animation_step = ImGui::GetIO().DeltaTime * 3.0f;\n        persist_animation_progress = clamp(persist_animation_progress + ((show_content) ? animation_step : -animation_step), 0.0f, 1.0f);\n\n        //Set clipping\n        state.WidgetID = ImGui::GetID(str_id);\n\n        //Don't push clip rect on full progress to allow for popups and such\n        if (persist_animation_progress != 1.0f)\n        {\n            const float content_height = ImGui::GetStateStorage()->GetFloat(state.WidgetID, 0.0f);\n            const float clip_height = smoothstep(persist_animation_progress, 0.0f, content_height);\n            ImVec2 clip_begin = ImGui::GetCursorScreenPos();\n            ImVec2 clip_end(clip_begin.x + ImGui::GetContentRegionAvail().x + ImGui::GetStyle().ItemSpacing.x, clip_begin.y + clip_height);\n\n            ImGui::PushClipRect(clip_begin, clip_end, true);\n            state.PushedClipRect = true;\n\n            //Pull cursor position further up to make widgets scroll down as they animate, keep start Y-pos in global to use in the next EndCollapsingArea() call\n            state.WidgetBeginY = (ImGui::GetCursorPosY() - content_height) + clip_height;\n            ImGui::SetCursorPosY(state.WidgetBeginY);\n\n            if (persist_animation_progress == 0.0f)\n            {\n                PushItemDisabledNoVisual();\n                state.PushedItemDisabled = true;\n            }\n        }\n    }\n\n    void EndCollapsingArea()\n    {\n        IM_ASSERT(!g_CollapsingArea_Stack.empty() && \"Called EndCollapsingArea() before BeginCollapsingArea()\");\n\n        CollapsingAreaState& state = g_CollapsingArea_Stack.back();\n\n        float& content_height = *ImGui::GetStateStorage()->GetFloatRef(state.WidgetID);\n        content_height = ImGui::GetCursorPosY() - state.WidgetBeginY;\n\n        if (state.PushedClipRect)\n        {\n            ImGui::PopClipRect();\n        }\n\n        if (state.PushedItemDisabled)\n        {\n            PopItemDisabledNoVisual();\n        }\n\n        g_CollapsingArea_Stack.pop_back();\n    }\n\n    void PushItemDisabled()\n    {\n        const ImGuiStyle& style = ImGui::GetStyle();\n        ImGui::PushItemFlag(ImGuiItemFlags_Disabled, true);\n        ImGui::PushStyleVar(ImGuiStyleVar_Alpha, style.Alpha * style.DisabledAlpha);\n    }\n\n    void PopItemDisabled()\n    {\n        ImGui::PopItemFlag();\n        ImGui::PopStyleVar();\n    }\n\n    void PushItemDisabledNoVisual()\n    {\n        ImGui::PushItemFlag(ImGuiItemFlags_Disabled, true);\n    }\n\n    void PopItemDisabledNoVisual()\n    {\n        ImGui::PopItemFlag();\n    }\n\n    void ConfigDisableCtrlTab()\n    {\n        GImGui->ConfigNavWindowingKeyNext = ImGuiKey_None;\n        GImGui->ConfigNavWindowingKeyPrev = ImGuiKey_None;\n    }\n\n    bool PopupContextMenuInputText(const char* str_id, char* str_buffer, size_t buffer_size, bool paste_remove_newlines)\n    {\n        bool ret = false;\n\n        if (ImGui::BeginPopupContextItem(str_id))\n        {\n            if (ImGui::MenuItem(\"Copy all\"))\n            {\n                ImGui::SetClipboardText(str_buffer);\n            }\n\n            if (ImGui::MenuItem(\"Replace with Clipboard\", nullptr, false, (ImGui::GetClipboardText() != nullptr) ))\n            {\n                std::string str(ImGui::GetClipboardText());\n\n                if (paste_remove_newlines)\n                {\n                    //Remove newlines (all kinds, since you never know what might be in a clipboard)\n                    std::string newlines[] = {\"\\r\\n\", \"\\n\", \"\\r\"};\n\n                    for (auto& nline : newlines)\n                    {\n                        size_t start_pos = 0;\n                        while ((start_pos = str.find(nline, start_pos)) != std::string::npos)\n                        {\n                            str.replace(start_pos, nline.length(), \" \");\n                        }\n                    }\n                }\n\n                //Copy to buffer\n                size_t copied_length = str.copy(str_buffer, buffer_size - 1);\n                str_buffer[copied_length] = '\\0';\n\n                ret = true;\n            }\n\n            ImGui::EndPopup();\n        }\n\n        return ret;\n    }\n\n    //ImGui got rid of direct nav input access, but we keep it around as an abstraction layer\n    ImGuiKey MapNavToKey(ImGuiNavInput nav_input, ImGuiInputSource input_source)\n    {\n        ImGuiKey imgui_key = ImGuiKey_None;\n\n        if (input_source == ImGuiInputSource_Keyboard)\n        {\n            switch (nav_input)\n            {\n                case ImGuiNavInput_Activate:  imgui_key = ImGuiKey_Space;      break;\n                case ImGuiNavInput_Cancel:    imgui_key = ImGuiKey_Escape;     break;\n                case ImGuiNavInput_Input:     imgui_key = ImGuiKey_Enter;      break;\n                case ImGuiNavInput_Menu:      imgui_key = ImGuiMod_Alt;        break;\n                case ImGuiNavInput_DpadLeft:  imgui_key = ImGuiKey_LeftArrow;  break;\n                case ImGuiNavInput_DpadRight: imgui_key = ImGuiKey_RightArrow; break;\n                case ImGuiNavInput_DpadUp:    imgui_key = ImGuiKey_UpArrow;    break;\n                case ImGuiNavInput_DpadDown:  imgui_key = ImGuiKey_DownArrow;  break;\n                case ImGuiNavInput_TweakSlow: imgui_key = ImGuiMod_Ctrl;       break;\n                case ImGuiNavInput_TweakFast: imgui_key = ImGuiMod_Shift;      break;\n            }\n        }\n        else if (input_source == ImGuiInputSource_Gamepad)\n        {\n            switch (nav_input)\n            {\n                case ImGuiNavInput_Activate:  imgui_key = ImGuiKey_GamepadFaceDown;  break;\n                case ImGuiNavInput_Cancel:    imgui_key = ImGuiKey_GamepadFaceRight; break;\n                case ImGuiNavInput_Input:     imgui_key = ImGuiKey_GamepadFaceLeft;  break;\n                case ImGuiNavInput_Menu:      imgui_key = ImGuiKey_GamepadFaceUp;    break;\n                case ImGuiNavInput_DpadLeft:  imgui_key = ImGuiKey_GamepadDpadLeft;  break;\n                case ImGuiNavInput_DpadRight: imgui_key = ImGuiKey_GamepadDpadRight; break;\n                case ImGuiNavInput_DpadUp:    imgui_key = ImGuiKey_GamepadDpadUp;    break;\n                case ImGuiNavInput_DpadDown:  imgui_key = ImGuiKey_GamepadDpadDown;  break;\n                case ImGuiNavInput_TweakSlow: imgui_key = ImGuiKey_GamepadL1;        break;\n                case ImGuiNavInput_TweakFast: imgui_key = ImGuiKey_GamepadR1;        break;\n            }\n        }\n\n        return imgui_key;\n    }\n\n    bool IsNavInputDown(ImGuiNavInput nav_input)\n    {\n        return ImGui::IsKeyDown(MapNavToKey(nav_input, GImGui->NavInputSource));\n    }\n\n    bool ImGui::IsNavInputPressed(ImGuiNavInput nav_input, bool repeat)\n    {\n        return ImGui::IsKeyPressed(MapNavToKey(nav_input, GImGui->NavInputSource), repeat);\n    }\n\n    bool ImGui::IsNavInputReleased(ImGuiNavInput nav_input)\n    {\n        return ImGui::IsKeyReleased(MapNavToKey(nav_input, GImGui->NavInputSource));\n    }\n\n    float ImGui::GetPreviousLineHeight()\n    {\n        return GImGui->CurrentWindow->DC.PrevLineSize.y;\n    }\n\n    void SetPreviousLineHeight(float height)\n    {\n        GImGui->CurrentWindow->DC.PrevLineSize.y = height;\n    }\n\n    bool HasHoveredNewItem()\n    {\n        const ImGuiContext& g = *GImGui;\n        bool blocked_by_active_item = (g.ActiveId != 0 && !g.ActiveIdAllowOverlap);\n\n        return ( (g.HoveredId != g.HoveredIdPreviousFrame) && (g.HoveredId != 0) && (!g.HoveredIdIsDisabled) && (!blocked_by_active_item) );\n    }\n\n    bool IsAnyItemActiveOrDeactivated()\n    {\n        const ImGuiContext& g = *GImGui;\n        return ( (g.ActiveId != 0) || (g.ActiveIdPreviousFrame != 0) );\n    }\n\n    bool IsAnyItemDeactivated()\n    {\n        const ImGuiContext& g = *GImGui;\n        return ( (g.ActiveIdPreviousFrame != 0) && (g.ActiveId != g.ActiveIdPreviousFrame) );\n    }\n\n    bool IsAnyInputTextActive()\n    {\n        const ImGuiContext& g = *GImGui;\n        return ( (g.ActiveId != 0) && (g.ActiveId == g.InputTextState.ID) );\n    }\n\n    bool IsAnyTempInputTextActive()\n    {\n        const ImGuiContext& g = *GImGui;\n        return ( (g.ActiveId != 0) && (g.ActiveId == g.InputTextState.ID) && (g.TempInputId == g.InputTextState.ID) );\n    }\n\n    bool IsAnyMouseClicked()\n    {\n        const ImGuiContext& g = *GImGui;\n        for (int n = 0; n < IM_ARRAYSIZE(g.IO.MouseDown); n++)\n            if (g.IO.MouseClicked[n])\n                return true;\n        return false;\n    }\n\n    void BlockWidgetInput()\n    {\n        ImGuiContext& g = *GImGui;\n\n        ImGui::SetActiveID(ImGui::GetID(\"ImGuiExtInputBlock\"), nullptr);\n        ImGui::SetKeyOwner(ImGuiKey_MouseWheelX, g.ActiveId);\n        ImGui::SetKeyOwner(ImGuiKey_MouseWheelY, g.ActiveId);\n        g.WheelingWindow = nullptr;\n        g.WheelingWindowReleaseTimer = 0.0f;\n    }\n\n    void HScrollWindowFromMouseWheelV()\n    {\n        ImGuiContext& g = *GImGui;\n\n        //Don't do anything is real hscroll is active\n        if ( (g.IO.MouseWheelH != 0.0f && !g.IO.KeyShift) || (g.IO.MouseWheel != 0.0f && g.IO.KeyShift) )\n            return;\n\n        ImGuiWindow* window = ImGui::GetCurrentWindow();\n\n        if (g.WheelingWindow != window)\n            return;\n\n        const float wheel_x = g.IO.MouseWheel;\n        float max_step = window->InnerRect.GetWidth() * 0.67f;\n        float scroll_step = ImTrunc(ImMin(2.0f * window->CalcFontSize(), max_step));\n\n        ImGui::SetScrollX(window, window->Scroll.x - wheel_x * scroll_step);\n    }\n\n    void ScrollBeginStackParentWindow()\n    {\n        ImGuiContext& g = *GImGui;\n\n        ImGuiWindow* window = ImGui::GetCurrentWindowRead();\n\n        if (g.WheelingWindow != window)\n            return;\n\n        ImGuiWindow* window_target = window->ParentWindowInBeginStack;\n\n        if (window_target == nullptr)\n            return;\n\n        const float wheel_x = g.IO.MouseWheelH;\n        const float wheel_y = g.IO.MouseWheel;\n\n        //HScroll\n        float max_step = window_target->InnerRect.GetWidth() * 0.67f;\n        float scroll_step = ImTrunc(ImMin(2.0f * window_target->CalcFontSize(), max_step));\n\n        ImGui::SetScrollX(window_target, window_target->Scroll.x - wheel_x * scroll_step);\n\n        //VScroll\n        max_step = window_target->InnerRect.GetHeight() * 0.67f;\n        scroll_step = ImTrunc(ImMin(5.0f * window_target->CalcFontSize(), max_step));\n\n        ImGui::SetScrollY(window_target, window_target->Scroll.y - wheel_y * scroll_step);\n    }\n\n    bool IsAnyScrollBarVisible()\n    {\n        ImGuiWindow* window = ImGui::GetCurrentWindowRead();\n        return ((window->ScrollbarX) || (window->ScrollbarY));\n    }\n\n    ImVec4 BeginTitleBar()\n    {\n        ImGuiStyle& style = ImGui::GetStyle();\n\n        ImRect rect = ImGui::GetCurrentWindowRead()->TitleBarRect();\n        ImGui::SetCursorScreenPos({rect.Min.x + style.WindowBorderSize + style.FramePadding.x, rect.Min.y + style.FramePadding.y});\n\n        ImGui::PushClipRect(rect.Min, rect.Max, false);\n\n        return ImVec4(rect.Min.x, rect.Min.y, rect.Max.x, rect.Max.y);\n    }\n\n    void EndTitleBar()\n    {\n        ImGui::PopClipRect();\n\n        ImGuiWindow* window = ImGui::GetCurrentWindow();\n        ImGui::SetCursorScreenPos(window->ContentRegionRect.Min);\n        window->DC.CursorMaxPos.x = 0;                              //Title bar does *not* affect overall window width as it messes with auto-resize\n    }\n\n    bool StringContainsUnmappedCharacter(const char* str)\n    {\n        if (GImGui == nullptr)\n            return true;\n\n        //Use current style font if possible, otherwise fall back to default font\n        ImFont* font = (GImGui->FontStack.empty()) ? ImGui::GetDefaultFont() : GImGui->FontStack.back();\n\n        if (font == nullptr)\n            return true;\n\n        const char* str_end = str + strlen(str);\n        ImWchar32 c;\n        int decoded_length;\n\n        while (str < str_end)\n        {\n            decoded_length = ImTextCharFromUtf8(&c, str, str_end);\n\n            if ( (c >= ' ') && (font->FindGlyphNoFallback((ImWchar)c) == nullptr) ) //Don't return true on unprintable characters being unmapped\n            {\n                return true;\n            }\n\n            str += decoded_length;\n        }\n\n        return false;\n    }\n\n    std::string StringEllipsis(const char* str, float width_max)\n    {\n        //Naive approach, but we're not supposed to use RenderTextEllipsis(), so this will do\n        const char* str_begin = str;\n        const char* str_end   = str + strlen(str);\n\n        //Handle corner case of text fitting without ellipsis, but not with\n        if (ImGui::CalcTextSize(str, str_end).x <= width_max)\n        {\n            return str;\n        }\n\n        float ellipsis_width = ImGui::CalcTextSize(\"...\").x;\n        bool is_full_string = true;\n        ImWchar32 c;\n        int decoded_length = 0;\n\n        while (str < str_end)\n        {\n            if (ImGui::CalcTextSize(str_begin, str).x + ellipsis_width >= width_max)\n            {\n                str -= decoded_length;\n                is_full_string = false;\n                break;\n            }\n\n            decoded_length = ImTextCharFromUtf8(&c, str, str_end);\n\n            str += decoded_length;\n        }\n\n        std::string str_out(str_begin, str - str_begin);\n\n        if (!is_full_string)\n        {\n            str_out.append(\"...\");\n        }\n\n        return str_out;\n    }\n\n    bool DraggableRectArea(const char* str_id, const ImVec2& area_size, ImGuiDraggableRectAreaState& state)\n    {\n        ImGuiIO& io = ImGui::GetIO();\n\n        ImVec2& offset       = state.RectPos;\n        ImVec2& size         = state.RectSize;\n        ImVec2& offset_start = state.RectPosDragStart;\n        ImVec2& size_start   = state.RectSizeDragStart;\n        ImVec2 offset_prev   = offset;\n        ImVec2 size_prev     = size;\n\n        ImGuiMouseButton& drag_mouse_button = state.DragMouseButton;\n        ImGuiDir& edge_drag_h_dir           = state.EdgeDragHDir;\n        ImGuiDir& edge_drag_v_dir           = state.EdgeDragVDir;\n        bool& is_edge_drag_active           = state.EdgeDragActive;\n        bool& highlight_edge                = state.EdgeDragHighlightVisible;\n\n        bool is_drag_active = false;\n\n        //This widget is mouse-only and interacts weirdly with the ImGui navigation.\n        //I've been unable to get ImGui to skip over it or bend it to work in sane manner, so I've resorted to disabling the items when nav is visible\n        const bool was_disabled_initially = (GImGui->CurrentItemFlags & ImGuiItemFlags_Disabled);\n        const bool disable_for_nav = (io.NavVisible && !ImGui::IsMouseClicked(ImGuiMouseButton_Left));\n\n        if (disable_for_nav)\n            ImGui::PushItemDisabledNoVisual();\n\n        ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, {0.0f, 0.0f});\n        if (ImGui::BeginChild(str_id, area_size, ImGuiChildFlags_Borders | ImGuiChildFlags_NavFlattened, ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse))\n        {\n            const float drag_threshold_prev = io.MouseDragThreshold;\n            io.MouseDragThreshold = 0.0f;\n\n            ImVec2 pos_area = ImGui::GetCursorScreenPos();\n            ImVec2 pos_button = pos_area;\n            pos_button.x += roundf(offset.x);\n            pos_button.y += roundf(offset.y);\n            const float drag_margin = ImGui::GetFontSize() / 2.0f;\n\n            //Draw rectangle manually\n            ImVec4 col_button = ImGui::GetStyleColorVec4(ImGuiCol_ButtonHovered);\n            ImVec4 col_fill = col_button;\n            col_fill.w = 0.25f;\n\n            ImGui::GetWindowDrawList()->AddRectFilled(pos_button, {roundf(pos_button.x + size.x), roundf(pos_button.y + size.y)}, ImGui::GetColorU32(col_fill));\n            ImGui::GetWindowDrawList()->AddRect(pos_button,       {roundf(pos_button.x + size.x), roundf(pos_button.y + size.y)}, ImGui::GetColorU32(col_button));\n\n            if (highlight_edge)\n            {\n                if ((size.x > drag_margin * 2.0f) && (size.y > drag_margin * 2.0f))\n                {\n                    col_fill.w = 0.10f;\n\n                    //This highlights the inner part actually, but still does the trick\n                    ImGui::GetWindowDrawList()->AddRectFilled({pos_button.x + drag_margin, pos_button.y + drag_margin},\n                                                              {roundf(pos_button.x + size.x - drag_margin), roundf(pos_button.y + size.y - drag_margin)},\n                                                              ImGui::GetColorU32(col_fill));\n                }\n            }\n\n            highlight_edge = false;\n\n            //Invisible button spanning the entire area to catch right clicks anywhere for relative drag\n            ImGui::SetNextItemAllowOverlap();\n            if (ImGui::InvisibleButton(\"DraggableRectArea\", area_size, ImGuiButtonFlags_MouseButtonRight | ImGuiButtonFlags_PressedOnClick))\n            {\n                offset_start = offset;\n                size_start   = size;\n                drag_mouse_button = ImGuiMouseButton_Right;\n\n                edge_drag_h_dir = ImGuiDir_None;\n                edge_drag_v_dir = ImGuiDir_None;\n                is_edge_drag_active = false;\n            }\n            else if (ImGui::IsItemActive())\n            {\n                is_drag_active = true;\n            }\n\n            //Pad the button size/pos to allow for off-edge grabs\n            ImVec2 pos_button_padded = pos_button;\n            pos_button_padded.x -= drag_margin / 2.0f;\n            pos_button_padded.y -= drag_margin / 2.0f;\n\n            ImVec2 size_button_padded = size;\n            size_button_padded.x += drag_margin;\n            size_button_padded.y += drag_margin;\n\n            ImGui::SetCursorScreenPos(pos_button_padded);\n            if (ImGui::InvisibleButton(\"DraggableRect\", size_button_padded, ImGuiButtonFlags_MouseButtonLeft | ImGuiButtonFlags_PressedOnClick | ImGuiButtonFlags_AllowOverlap))\n            {\n                offset_start = offset;\n                size_start   = size;\n                drag_mouse_button = ImGuiMouseButton_Left;\n\n                const ImVec2 pos(io.MouseClickedPos[drag_mouse_button].x - pos_area.x, io.MouseClickedPos[drag_mouse_button].y - pos_area.y);\n\n                edge_drag_h_dir = ImGuiDir_None;\n                edge_drag_v_dir = ImGuiDir_None;\n\n                if (pos.x < offset.x + drag_margin)\n                    edge_drag_h_dir = ImGuiDir_Left;\n                else if (pos.x > offset.x + size.x - drag_margin)\n                    edge_drag_h_dir = ImGuiDir_Right;\n\n                if (pos.y < offset.y + drag_margin)\n                    edge_drag_v_dir = ImGuiDir_Up;\n                else if (pos.y > offset.y + size.y - drag_margin)\n                    edge_drag_v_dir = ImGuiDir_Down;\n\n                is_edge_drag_active = ((edge_drag_h_dir != ImGuiDir_None) || (edge_drag_v_dir != ImGuiDir_None));\n            }\n            else if (ImGui::IsItemActive())\n            {\n                is_drag_active = true;\n            }\n\n            if (is_drag_active)\n            {\n                if (is_edge_drag_active)\n                {\n                    if (edge_drag_h_dir == ImGuiDir_Left)\n                    {\n                        size.x = size_start.x - ImGui::GetMouseDragDelta(drag_mouse_button).x;\n                        offset.x = offset_start.x - (size.x - size_start.x);\n\n                        if (offset.x < 0.0f)\n                        {\n                            size.x += offset.x;\n                            offset.x = 0.0f;\n                        }\n                        else if (offset.x > area_size.x)   //When dragged to opposite edge (width is negative)\n                        {\n                            size.x -= area_size.x - offset.x;\n                            offset.x = area_size.x;\n                        }\n                    }\n                    else if (edge_drag_h_dir == ImGuiDir_Right)\n                    {\n                        size.x = size_start.x + ImGui::GetMouseDragDelta(drag_mouse_button).x;\n                        size.x = std::min(size.x, area_size.x - offset.x);\n                        offset.x = offset_start.x;\n\n                        if (size.x + offset.x < 0.0f) //When dragged to opposite edge (width is negative)\n                        {\n                            size.x -= size.x + offset.x;\n                        }\n                    }\n\n                    if (edge_drag_v_dir == ImGuiDir_Up)\n                    {\n                        size.y = size_start.y - ImGui::GetMouseDragDelta(drag_mouse_button).y;\n                        offset.y = offset_start.y - (size.y - size_start.y);\n\n                        if (offset.y < 0.0f)\n                        {\n                            size.y += offset.y;\n                            offset.y = 0.0f;\n                        }\n                        else if (offset.y > area_size.y)   //When dragged to opposite edge (height is negative)\n                        {\n                            size.y -= area_size.y - offset.y;\n                            offset.y = area_size.y;\n                        }\n                    }\n                    else if (edge_drag_v_dir == ImGuiDir_Down)\n                    {\n                        size.y = size_start.y + ImGui::GetMouseDragDelta(drag_mouse_button).y;\n                        size.y = std::min(size.y, area_size.y - offset.y);\n                        offset.y = offset_start.y;\n\n                        if (size.y + offset.y < 0.0f) //When dragged to opposite edge (height is negative)\n                        {\n                            size.y -= size.y + offset.y;\n                        }\n                    }\n                }\n                else  //Normal drag\n                {\n                    //Alt key scroll swapping is usually already done by ImGui but seems to be blocked by something down the line so we do it ourselves, whatever\n                    if (io.KeyAlt)\n                    {\n                        io.MouseWheelH = -io.MouseWheel;\n                        io.MouseWheel = 0.0f;\n                    }\n\n                    //Adjust size from wheel input (with deadzone for smooth scrolling input)\n                    if (fabs(io.MouseWheelH) > 0.05f)\n                    {\n                        float size_diff = size.x;\n                        size.x *= 1.0f + (io.MouseWheelH / -10.0f);\n                        size_diff = size.x - size_diff;\n\n                        offset_start.x -= size_diff / 2.0f;\n                    }\n\n                    if (fabs(io.MouseWheel) > 0.05f)\n                    {\n                        float size_diff = size.y;\n                        size.y *= 1.0f + (io.MouseWheel / 10.0f);\n                        size_diff = size.y - size_diff;\n\n                        offset_start.y -= size_diff / 2.0f;\n                    }\n\n                    offset.x = offset_start.x + ImGui::GetMouseDragDelta(drag_mouse_button).x;\n                    offset.y = offset_start.y + ImGui::GetMouseDragDelta(drag_mouse_button).y;\n                }\n\n                //Correct inverted rectangle\n                if (size.x < 0.0f)\n                {\n                    offset.x += size.x;\n                    size.x *= -1.0f;\n                }\n\n                if (size.y < 0.0f)\n                {\n                    offset.y += size.y;\n                    size.y *= -1.0f;\n                }\n\n                //Clamp to area\n                offset.x = clamp(offset.x, 0.0f, area_size.x - size.x);\n                offset.y = clamp(offset.y, 0.0f, area_size.y - size.y);\n\n                size.x = clamp(size.x, 1.0f, area_size.x);\n                size.y = clamp(size.y, 1.0f, area_size.y);\n            }\n            else\n            {\n                highlight_edge = ImGui::IsItemHovered( (was_disabled_initially) ? 0 : ImGuiHoveredFlags_AllowWhenDisabled );\n            }\n\n            io.MouseDragThreshold = drag_threshold_prev;\n        }\n        ImGui::EndChild();\n        ImGui::PopStyleVar();\n\n        if (disable_for_nav)\n            ImGui::PopItemDisabledNoVisual();\n\n        return ( (is_drag_active) && ((offset.x != offset_prev.x) || (offset.y != offset_prev.y) || \n                                      (size.x   != size_prev.x)   || (size.y   != size_prev.y)) );\n    }\n\n\n    ImGuiMouseState::ImGuiMouseState()\n    {\n        //Set everything to zero\n        memset(this, 0, sizeof(*this));\n        MousePos = MousePosPrev = ImVec2(-FLT_MAX,-FLT_MAX);\n    }\n\n    void ImGuiMouseState::SetFromGlobalState()\n    {\n        const ImGuiIO& io = ImGui::GetIO();\n\n        MousePos     = io.MousePos;\n        std::copy(std::begin(io.MouseDown), std::end(io.MouseDown), MouseDown);\n        MouseWheel   = io.MouseWheel;\n        MouseWheelH  = io.MouseWheelH;\n        MouseDelta   = io.MouseDelta;\n        MousePosPrev = io.MousePosPrev;\n        std::copy(std::begin(io.MouseClickedPos),                std::end(io.MouseClickedPos),                MouseClickedPos);\n        std::copy(std::begin(io.MouseClickedTime),               std::end(io.MouseClickedTime),               MouseClickedTime);\n        std::copy(std::begin(io.MouseClicked),                   std::end(io.MouseClicked),                   MouseClicked);\n        std::copy(std::begin(io.MouseDoubleClicked),             std::end(io.MouseDoubleClicked),             MouseDoubleClicked);\n        std::copy(std::begin(io.MouseClickedCount),              std::end(io.MouseClickedCount),              MouseClickedCount);\n        std::copy(std::begin(io.MouseClickedLastCount),          std::end(io.MouseClickedLastCount),          MouseClickedLastCount);\n        std::copy(std::begin(io.MouseReleased),                  std::end(io.MouseReleased),                  MouseReleased);\n        std::copy(std::begin(io.MouseDownOwned),                 std::end(io.MouseDownOwned),                 MouseDownOwned);\n        std::copy(std::begin(io.MouseDownOwnedUnlessPopupClose), std::end(io.MouseDownOwnedUnlessPopupClose), MouseDownOwnedUnlessPopupClose);\n        std::copy(std::begin(io.MouseDownDuration),              std::end(io.MouseDownDuration),              MouseDownDuration);\n        std::copy(std::begin(io.MouseDownDurationPrev),          std::end(io.MouseDownDurationPrev),          MouseDownDurationPrev);\n        std::copy(std::begin(io.MouseDragMaxDistanceSqr),        std::end(io.MouseDragMaxDistanceSqr),        MouseDragMaxDistanceSqr);\n    }\n\n    void ImGuiMouseState::ApplyToGlobalState()\n    {\n        ImGuiIO& io = ImGui::GetIO();\n\n        io.MousePos     = MousePos;\n        std::copy(std::begin(MouseDown), std::end(MouseDown), io.MouseDown);\n        io.MouseWheel   = MouseWheel;\n        io.MouseWheelH  = MouseWheelH;\n        io.MouseDelta   = MouseDelta;\n        io.MousePosPrev = MousePosPrev;\n        std::copy(std::begin(MouseClickedPos),                std::end(MouseClickedPos),                io.MouseClickedPos);\n        std::copy(std::begin(MouseClickedTime),               std::end(MouseClickedTime),               io.MouseClickedTime);\n        std::copy(std::begin(MouseClicked),                   std::end(MouseClicked),                   io.MouseClicked);\n        std::copy(std::begin(MouseDoubleClicked),             std::end(MouseDoubleClicked),             io.MouseDoubleClicked);\n        std::copy(std::begin(MouseClickedCount),              std::end(MouseClickedCount),              io.MouseClickedCount);\n        std::copy(std::begin(MouseClickedLastCount),          std::end(MouseClickedLastCount),          io.MouseClickedLastCount);\n        std::copy(std::begin(MouseReleased),                  std::end(MouseReleased),                  io.MouseReleased);\n        std::copy(std::begin(MouseDownOwned),                 std::end(MouseDownOwned),                 io.MouseDownOwned);\n        std::copy(std::begin(MouseDownOwnedUnlessPopupClose), std::end(MouseDownOwnedUnlessPopupClose), io.MouseDownOwnedUnlessPopupClose);\n        std::copy(std::begin(MouseDownDuration),              std::end(MouseDownDuration),              io.MouseDownDuration);\n        std::copy(std::begin(MouseDownDurationPrev),          std::end(MouseDownDurationPrev),          io.MouseDownDurationPrev);\n        std::copy(std::begin(MouseDragMaxDistanceSqr),        std::end(MouseDragMaxDistanceSqr),        io.MouseDragMaxDistanceSqr);\n    }\n\n    void ImGuiMouseState::Advance()\n    {\n        //This is almost just ImGui::UpdateMouseInputs(), but we don't touch any global state here\n        const ImGuiContext& g = *ImGui::GetCurrentContext();\n\n        // Round mouse position to avoid spreading non-rounded position (e.g. UpdateManualResize doesn't support them well)\n        if (IsMousePosValid(&MousePos))\n            MousePos = ImFloor(MousePos);\n\n        // If mouse just appeared or disappeared (usually denoted by -FLT_MAX components) we cancel out movement in MouseDelta\n        if (IsMousePosValid(&MousePos) && IsMousePosValid(&MousePosPrev))\n            MouseDelta = MousePos - MousePosPrev;\n        else\n            MouseDelta = ImVec2(0.0f, 0.0f);\n\n        MousePosPrev = MousePos;\n        for (int i = 0; i < IM_ARRAYSIZE(MouseDown); i++)\n        {\n            MouseClicked[i] = MouseDown[i] && MouseDownDuration[i] < 0.0f;\n            MouseClickedCount[i] = 0; // Will be filled below\n            MouseReleased[i] = !MouseDown[i] && MouseDownDuration[i] >= 0.0f;\n            MouseDownDurationPrev[i] = MouseDownDuration[i];\n            MouseDownDuration[i] = MouseDown[i] ? (MouseDownDuration[i] < 0.0f ? 0.0f : MouseDownDuration[i] + g.IO.DeltaTime) : -1.0f;\n            if (MouseClicked[i])\n            {\n                bool is_repeated_click = false;\n                if ((float)(g.Time - MouseClickedTime[i]) < g.IO.MouseDoubleClickTime)\n                {\n                    ImVec2 delta_from_click_pos = IsMousePosValid(&MousePos) ? (MousePos - MouseClickedPos[i]) : ImVec2(0.0f, 0.0f);\n                    if (ImLengthSqr(delta_from_click_pos) < g.IO.MouseDoubleClickMaxDist * g.IO.MouseDoubleClickMaxDist)\n                        is_repeated_click = true;\n                }\n                if (is_repeated_click)\n                    MouseClickedLastCount[i]++;\n                else\n                    MouseClickedLastCount[i] = 1;\n\n                MouseClickedTime[i] = g.Time;\n\n                MouseClickedPos[i] = MousePos;\n                MouseClickedCount[i] = MouseClickedLastCount[i];\n                MouseDragMaxDistanceSqr[i] = 0.0f;\n            }\n            else if (MouseDown[i])\n            {\n                // Maintain the maximum distance we reaching from the initial click position, which is used with dragging threshold\n                float delta_sqr_click_pos = IsMousePosValid(&MousePos) ? ImLengthSqr(MousePos - MouseClickedPos[i]) : 0.0f;\n                MouseDragMaxDistanceSqr[i] = ImMax(MouseDragMaxDistanceSqr[i], delta_sqr_click_pos);\n            }\n\n            // We provide io.MouseDoubleClicked[] as a legacy service\n            MouseDoubleClicked[i] = (MouseClickedCount[i] == 2);\n        }\n    }\n\n\n    ActiveWidgetStateStorage::ActiveWidgetStateStorage()\n    {\n        //These need to be the same or things will explode\n        IM_ASSERT(sizeof(ImGuiDeactivatedItemDataInternal) == sizeof(DeactivatedItemData));\n        IM_ASSERT(sizeof(ActiveIdValueOnActivation)        == sizeof(ActiveIdValueOnActivation));\n\n        IsInitialized                            = false;\n\n        HoveredId                                = 0;\n        HoveredIdPreviousFrame                   = 0;\n        HoveredIdPreviousFrameItemCount          = 0;\n        HoveredIdTimer                           = 0.0f;\n        HoveredIdNotActiveTimer                  = 0.0f;\n        HoveredIdAllowOverlap                    = false;\n        HoveredIdIsDisabled                      = false;\n        ItemUnclipByLog                          = false;\n        ActiveId                                 = 0;\n        ActiveIdIsAlive                          = 0;\n        ActiveIdTimer                            = 0.0f;\n        ActiveIdIsJustActivated                  = false;\n        ActiveIdAllowOverlap                     = false;\n        ActiveIdNoClearOnFocusLoss               = false;\n        ActiveIdHasBeenPressedBefore             = false;\n        ActiveIdHasBeenEditedBefore              = false;\n        ActiveIdHasBeenEditedThisFrame           = false;\n        ActiveIdFromShortcut                     = false;\n        ActiveIdUsingNavDirMask                  = 0x00;\n        ActiveIdClickOffset                      = ImVec2(-1, -1);\n        ActiveIdWindow                           = nullptr;\n        ActiveIdSource                           = ImGuiInputSource_None;\n        ActiveIdMouseButton                      = -1;\n        ActiveIdPreviousFrame                    = 0;\n        memset(&DeactivatedItemData,       0, sizeof(DeactivatedItemData));\n        memset(&ActiveIdValueOnActivation, 0, sizeof(ActiveIdValueOnActivation));\n        LastActiveId                             = 0;\n        LastActiveIdTimer                        = 0.0f;\n\n        ActiveIdUsingNavDirMask                  = 0;\n        ActiveIdUsingAllKeyboardKeys             = false;\n    }\n\n    void ActiveWidgetStateStorage::StoreCurrentState()\n    {\n        IsInitialized = true;\n\n        ImGuiContext& g = *ImGui::GetCurrentContext();\n\n        HoveredId                                = g.HoveredId;\n        HoveredIdPreviousFrame                   = g.HoveredIdPreviousFrame;\n        HoveredIdPreviousFrameItemCount          = g.HoveredIdPreviousFrameItemCount;\n        HoveredIdTimer                           = g.HoveredIdTimer;\n        HoveredIdNotActiveTimer                  = g.HoveredIdNotActiveTimer;\n        HoveredIdAllowOverlap                    = g.HoveredIdAllowOverlap;\n        HoveredIdIsDisabled                      = g.HoveredIdIsDisabled;\n        ItemUnclipByLog                          = g.ItemUnclipByLog;\n        ActiveId                                 = g.ActiveId;\n        ActiveIdIsAlive                          = g.ActiveIdIsAlive;\n        ActiveIdTimer                            = g.ActiveIdTimer;\n        ActiveIdIsJustActivated                  = g.ActiveIdIsJustActivated;\n        ActiveIdAllowOverlap                     = g.ActiveIdAllowOverlap;\n        ActiveIdNoClearOnFocusLoss               = g.ActiveIdNoClearOnFocusLoss;\n        ActiveIdHasBeenPressedBefore             = g.ActiveIdHasBeenPressedBefore;\n        ActiveIdHasBeenEditedBefore              = g.ActiveIdHasBeenEditedBefore;\n        ActiveIdHasBeenEditedThisFrame           = g.ActiveIdHasBeenEditedThisFrame;\n        ActiveIdFromShortcut                     = g.ActiveIdFromShortcut;\n        ActiveIdMouseButton                      = g.ActiveIdMouseButton;\n        ActiveIdClickOffset                      = g.ActiveIdClickOffset;\n        ActiveIdWindow                           = g.ActiveIdWindow;\n        ActiveIdSource                           = g.ActiveIdSource;\n        ActiveIdPreviousFrame                    = g.ActiveIdPreviousFrame;\n        DeactivatedItemData                      = *(ImGuiDeactivatedItemDataInternal*)&g.DeactivatedItemData;\n        ActiveIdValueOnActivation                = *(ImGuiDataTypeStorageInternal*)&g.ActiveIdValueOnActivation;\n        LastActiveId                             = g.LastActiveId;\n        LastActiveIdTimer                        = g.LastActiveIdTimer;\n\n        ActiveIdUsingNavDirMask                  = g.ActiveIdUsingNavDirMask;\n        ActiveIdUsingAllKeyboardKeys             = g.ActiveIdUsingAllKeyboardKeys;\n    }\n\n    void ActiveWidgetStateStorage::ApplyState()\n    {\n        //Do nothing if not initialized. Calls to this are typically followed by storing the state afterwards, initializing it correctly then\n        if (!IsInitialized)\n            return;\n\n        ImGuiContext& g = *ImGui::GetCurrentContext();\n\n        g.HoveredId                                = HoveredId;\n        g.HoveredIdPreviousFrame                   = HoveredIdPreviousFrame;\n        g.HoveredIdPreviousFrameItemCount          = HoveredIdPreviousFrameItemCount;\n        g.HoveredIdTimer                           = HoveredIdTimer;\n        g.HoveredIdNotActiveTimer                  = HoveredIdNotActiveTimer;\n        g.HoveredIdAllowOverlap                    = HoveredIdAllowOverlap;\n        g.HoveredIdIsDisabled                      = HoveredIdIsDisabled;\n        g.ItemUnclipByLog                          = ItemUnclipByLog;\n        g.ActiveId                                 = ActiveId;\n        g.ActiveIdIsAlive                          = ActiveIdIsAlive;\n        g.ActiveIdTimer                            = ActiveIdTimer;\n        g.ActiveIdIsJustActivated                  = ActiveIdIsJustActivated;\n        g.ActiveIdAllowOverlap                     = ActiveIdAllowOverlap;\n        g.ActiveIdNoClearOnFocusLoss               = ActiveIdNoClearOnFocusLoss;\n        g.ActiveIdHasBeenPressedBefore             = ActiveIdHasBeenPressedBefore;\n        g.ActiveIdHasBeenEditedBefore              = ActiveIdHasBeenEditedBefore;\n        g.ActiveIdHasBeenEditedThisFrame           = ActiveIdHasBeenEditedThisFrame;\n        g.ActiveIdFromShortcut                     = ActiveIdFromShortcut;\n        g.ActiveIdMouseButton                      = ActiveIdMouseButton;\n        g.ActiveIdClickOffset                      = ActiveIdClickOffset;\n        g.ActiveIdWindow                           = (ImGuiWindow*)ActiveIdWindow;\n        g.ActiveIdSource                           = (ImGuiInputSource)ActiveIdSource;\n        g.ActiveIdPreviousFrame                    = ActiveIdPreviousFrame;\n        g.DeactivatedItemData                      = *(ImGuiDeactivatedItemData*)&DeactivatedItemData;\n        g.ActiveIdValueOnActivation                = *(ImGuiDataTypeStorage*)&ActiveIdValueOnActivation;\n        g.LastActiveId                             = LastActiveId;\n        g.LastActiveIdTimer                        = LastActiveIdTimer;\n\n        g.ActiveIdUsingNavDirMask                  = ActiveIdUsingNavDirMask;\n        g.ActiveIdUsingAllKeyboardKeys             = ActiveIdUsingAllKeyboardKeys;\n    }\n\n    void ActiveWidgetStateStorage::AdvanceState()\n    {\n        if (!IsInitialized)\n            return;\n\n        const ImGuiIO& io = ImGui::GetIO();\n        ImGuiContext& g = *ImGui::GetCurrentContext();\n\n        // Update HoveredId data\n        if (!HoveredIdPreviousFrame)\n            HoveredIdTimer = 0.0f;\n        if (!HoveredIdPreviousFrame || (HoveredId && ActiveId == HoveredId))\n            HoveredIdNotActiveTimer = 0.0f;\n        if (HoveredId)\n            HoveredIdTimer += io.DeltaTime;\n        if (HoveredId && ActiveId != HoveredId)\n            HoveredIdNotActiveTimer += io.DeltaTime;\n        HoveredIdPreviousFrame = HoveredId;\n        HoveredId = 0;\n        HoveredIdAllowOverlap = false;\n        HoveredIdIsDisabled = false;\n\n        // Clear ActiveID if the item is not alive anymore.\n        // In 1.87, the common most call to KeepAliveID() was moved from GetID() to ItemAdd().\n        // As a result, custom widget using ButtonBehavior() _without_ ItemAdd() need to call KeepAliveID() themselves.\n        if (ActiveId != 0 && ActiveIdIsAlive != ActiveId && ActiveIdPreviousFrame == ActiveId)\n        {\n            //ClearActiveID\n            ImGuiDeactivatedItemDataInternal* deactivated_data = &DeactivatedItemData;\n            deactivated_data->ID = ActiveId;\n            deactivated_data->ElapseFrame = (g.LastItemData.ID == ActiveId) ? g.FrameCount : g.FrameCount + 1;\n            deactivated_data->HasBeenEditedBefore = ActiveIdHasBeenEditedBefore;\n            deactivated_data->IsAlive = (ActiveIdIsAlive == ActiveId);\n\n            ActiveIdIsJustActivated        = true;\n            ActiveIdTimer                  = 0.0f;\n            ActiveIdHasBeenPressedBefore   = false;\n            ActiveIdHasBeenEditedBefore    = false;\n            ActiveIdHasBeenEditedThisFrame = false;\n            ActiveIdFromShortcut           = false;\n            ActiveIdMouseButton            = -1;\n            ActiveId                       = 0;\n            ActiveIdAllowOverlap           = false;\n            ActiveIdNoClearOnFocusLoss     = false;\n            ActiveIdWindow                 = nullptr;\n            ActiveIdHasBeenEditedThisFrame = false;\n        }\n\n        // Update ActiveId data (clear reference to active widget if the widget isn't alive anymore)\n        if (ActiveIdIsAlive != ActiveId && ActiveIdPreviousFrame == ActiveId && ActiveId != 0)\n        {\n            ActiveIdIsJustActivated = true;\n\n            if (ActiveIdIsJustActivated)\n            {\n                ActiveIdTimer                = 0.0f;\n                ActiveIdHasBeenPressedBefore = false;\n                ActiveIdHasBeenEditedBefore  = false;\n                ActiveIdMouseButton          = -1;\n            }\n\n            ActiveId                       = 0;\n            ActiveIdAllowOverlap           = false;\n            ActiveIdNoClearOnFocusLoss     = false;\n            ActiveIdWindow                 = nullptr;\n            ActiveIdHasBeenEditedThisFrame = false;\n        }\n\n        if (ActiveId)\n            ActiveIdTimer += io.DeltaTime;\n\n        LastActiveIdTimer                       += io.DeltaTime;\n        ActiveIdPreviousFrame                    = ActiveId;\n        ActiveIdIsAlive                          = 0;\n        ActiveIdHasBeenEditedThisFrame           = false;\n        ActiveIdIsJustActivated                  = false;\n\n        if (ActiveId == 0)\n        {\n            ActiveIdUsingNavDirMask      = 0x00;\n            ActiveIdUsingAllKeyboardKeys = false;\n        }\n\n        if (DeactivatedItemData.ElapseFrame < g.FrameCount)\n            DeactivatedItemData.ID = 0;\n        DeactivatedItemData.IsAlive = false;\n    }\n}"
  },
  {
    "path": "src/DesktopPlusUI/ImGuiExt.h",
    "content": "//Some extra functions extending ImGui\n//Hopefully nothing here breaks when updating ImGui\n\n#pragma once\n\n#include \"imgui.h\"\n#include <string>\n\n//More colors\nextern ImVec4 Style_ImGuiCol_TextNotification;\nextern ImVec4 Style_ImGuiCol_TextWarning;\nextern ImVec4 Style_ImGuiCol_TextError;\nextern ImVec4 Style_ImGuiCol_TextOutline;\nextern ImVec4 Style_ImGuiCol_ButtonPassiveToggled; //Toggled state for a button indicating a passive state, rather a full-on eye-catching active state\nextern ImVec4 Style_ImGuiCol_SteamVRCursor;        //Inner color used to mimic a SteamVR overlay cursor\nextern ImVec4 Style_ImGuiCol_SteamVRCursorBorder;  //Border color used to mimic a SteamVR overlay cursor\n\n//Legacy ImGui enum, now provided by us instead\nenum ImGuiNavInput\n{\n    // Gamepad Mapping\n    ImGuiNavInput_Activate,      // Activate / Open / Toggle / Tweak value       // e.g. Cross  (PS4), A (Xbox), A (Switch), Space (Keyboard)\n    ImGuiNavInput_Cancel,        // Cancel / Close / Exit                        // e.g. Circle (PS4), B (Xbox), B (Switch), Escape (Keyboard)\n    ImGuiNavInput_Input,         // Text input / On-Screen keyboard              // e.g. Triang.(PS4), Y (Xbox), X (Switch), Return (Keyboard)\n    ImGuiNavInput_Menu,          // Tap: Toggle menu / Hold: Focus, Move, Resize // e.g. Square (PS4), X (Xbox), Y (Switch), Alt (Keyboard)\n    ImGuiNavInput_DpadLeft,      // Move / Tweak / Resize window (w/ PadMenu)    // e.g. D-pad Left/Right/Up/Down (Gamepads), Arrow keys (Keyboard)\n    ImGuiNavInput_DpadRight,     //\n    ImGuiNavInput_DpadUp,        //\n    ImGuiNavInput_DpadDown,      //\n    ImGuiNavInput_LStickLeft,    // Scroll / Move window (w/ PadMenu)            // e.g. Left Analog Stick Left/Right/Up/Down\n    ImGuiNavInput_LStickRight,   //\n    ImGuiNavInput_LStickUp,      //\n    ImGuiNavInput_LStickDown,    //\n    ImGuiNavInput_FocusPrev,     // Focus Next window (w/ PadMenu)               // e.g. L1 or L2 (PS4), LB or LT (Xbox), L or ZL (Switch)\n    ImGuiNavInput_FocusNext,     // Focus Prev window (w/ PadMenu)               // e.g. R1 or R2 (PS4), RB or RT (Xbox), R or ZL (Switch)\n    ImGuiNavInput_TweakSlow,     // Slower tweaks                                // e.g. L1 or L2 (PS4), LB or LT (Xbox), L or ZL (Switch)\n    ImGuiNavInput_TweakFast,     // Faster tweaks                                // e.g. R1 or R2 (PS4), RB or RT (Xbox), R or ZL (Switch)\n\n    // [Internal] Don't use directly! This is used internally to differentiate keyboard from gamepad inputs for behaviors that require to differentiate them.\n    // Keyboard behavior that have no corresponding gamepad mapping (e.g. CTRL+TAB) will be directly reading from keyboard keys instead of io.NavInputs[].\n    ImGuiNavInput_KeyLeft_,      // Move left                                    // = Arrow keys\n    ImGuiNavInput_KeyRight_,     // Move right\n    ImGuiNavInput_KeyUp_,        // Move up\n    ImGuiNavInput_KeyDown_,      // Move down\n    ImGuiNavInput_COUNT\n};\n\n//Forward declase ImRect as we have functions with overloads that take it, but it's not part of the public ImGui API\nstruct IMGUI_API ImRect;\n\nnamespace ImGui\n{\n    //Like InputFloat()'s buttons but with a slider instead. Not quite as flexible, though. Always takes as much space as available.\n    //alt_text is unformatted text rendered on top if set (for unsanitized translation strings). format shouldn't render anything then (use ##)\n    bool SliderWithButtonsFloat(const char* str_id, float& value, float step, float step_small, float min, float max, const char* format, ImGuiSliderFlags flags = 0, bool* used_button = nullptr, \n                                const char* text_alt = nullptr);\n    bool SliderWithButtonsInt(const char* str_id, int& value, int step, int step_small, int min, int max, const char* format, ImGuiSliderFlags flags = 0, bool* used_button = nullptr, \n                              const char* text_alt = nullptr);\n    // format is for int\n    bool SliderWithButtonsFloatPercentage(const char* str_id, float& value, int step, int step_small, int min, int max, const char* format, ImGuiSliderFlags flags = 0, bool* used_button = nullptr, \n                                          const char* text_alt = nullptr);\n    ImGuiID SliderWithButtonsGetSliderID(const char* str_id);\n\n    //Like imgui_demo's HelpMarker, but with a fixed position tooltip\n    void FixedHelpMarker(const char* desc, const char* marker_str = \"(?)\");\n\n    //Button, but with wrapped, cropped and center aligned label\n    bool ButtonWithWrappedLabel(const char* label, const ImVec2& size);\n\n    //Per-line centered multiline label render function. Renders on top of the last button/item or inside explicit bounding box\n    void RenderButtonMultilineLabel(const char* label, float line_height_scale = 1.0f);\n    void RenderButtonMultilineLabel(const char* label, const ImRect& bb, float line_height_scale = 1.0f);\n\n    //BeginCombo() but with the option of turning the Combo into an Input text like the sliders. Little bit awkward with the state variables but works otherwise\n    //Uses PopupContextMenuInputText() with the InputText\n    //Use normal EndCombo() to finish\n    bool BeginComboWithInputText(const char* str_id, char* str_buffer, size_t buffer_size, bool& out_buffer_changed,\n                                 bool& persist_input_visible, bool& persist_input_activated, bool& persist_mouse_released_once, bool no_preview_text = false);\n    void ComboWithInputTextActivationCheck(bool& persist_input_visible); //Always call after BeginComboWithInputText(), outside the if statement\n\n    //BeginCombo(), but opening it is animated\n    bool BeginComboAnimated(const char* label, const char* preview_value, ImGuiComboFlags flags = 0);\n\n    //Right-alinged Text(). Use offset_x if it's not supposed to take all of the available space, non-zero fixed_w to explicitly set total width instead of using window available space\n    //Note that text may not always be pixel-perfectly aligned with this\n    void TextRight(float offset_x, float fixed_w, const char* fmt, ...)           IM_FMTARGS(4);\n    void TextRightV(float offset_x, float fixed_w, const char* fmt, va_list args) IM_FMTLIST(4);\n    void TextRightUnformatted(float offset_x, float fixed_w, const char* text, const char* text_end = nullptr);\n\n    //Shortcut for unformatted colored text\n    void TextColoredUnformatted(const ImVec4& col, const char* text, const char* text_end = nullptr);\n\n    //Outlined Text(). Uses Style_ImGuiCol_TextOutline\n    void TextOutlined(const char* fmt, ...) IM_FMTARGS(2);\n    void TextOutlinedV(const char* fmt, va_list args) IM_FMTLIST(2);\n    void TextUnformattedOutlined(const char* text, const char* text_end = nullptr);\n    void TextRightOutlined(float offset_x, float fixed_w, const char* fmt, ...) IM_FMTARGS(4);\n    void TextRightOutlinedV(float offset_x, float fixed_w, const char* fmt, va_list args) IM_FMTLIST(4);\n    void TextRightUnformattedOutlined(float offset_x, float fixed_w, const char* text, const char* text_end = nullptr);\n\n    //RenderTextClipped, but does not limit rendered minimal position to alignment pos_min (so centered text will stay centered with left side being cut off instead)\n    void RenderTextClippedUnclamped(const ImVec2& pos_min, const ImVec2& pos_max, const char* text, const char* text_end, const ImVec2* text_size_if_known, const ImVec2& align = ImVec2(0, 0), const ImRect* clip_rect = nullptr);\n    void RenderTextClippedUnclampedEx(ImDrawList* draw_list, const ImVec2& pos_min, const ImVec2& pos_max, const char* text, const char* text_end, const ImVec2* text_size_if_known, const ImVec2& align = ImVec2(0, 0), const ImRect* clip_rect = nullptr);\n\n    //Stretches content added to drawlist between calls to BeginStretched() & EndStretched. Mostly for text.\n    void BeginStretched();\n    void EndStretched(float scale_x);\n\n    //ColorPicker simplified for embedded widget use instead having of popups + translation support\n    bool ColorPicker4Simple(const char* str_id, float col[4], float ref_col[4], const char* label_color_current = nullptr, const char* label_color_original = nullptr, float scale = 1.0f);\n\n    //CollapsingHeader() with padding hack applied for adjusted appearance within child windows\n    bool CollapsingHeaderPadded(const char* label, ImGuiTreeNodeFlags flags = 0);\n\n    //Collapsing area which animates the content sliding downwards. Always call content widget functions (for content height calculations)\n    //Uses external animation progress variable to allow overriding when needed\n    void BeginCollapsingArea(const char* str_id, bool show_content, float& persist_animation_progress);\n    void EndCollapsingArea();\n\n    //ImGuiItemFlags_Disabled is not exposed public API yet and has no styling, so here's something that does the job\n    //BeginDisabled()/EndDisabled() exists now, but behaves slightly differently and styling may change, so let's keep this for the time being\n    void PushItemDisabled();\n    void PopItemDisabled();    \n    void PushItemDisabledNoVisual();\n    void PopItemDisabledNoVisual();\n\n    //Disables window switcher by setting internal config values\n    void ConfigDisableCtrlTab();\n\n    //Just straight up brought into the public API, use -1.0f on one axis to leave as-is\n    void SetNextWindowScroll(const ImVec2& scroll);\n\n    //Brought into the public API since we need only that one sometimes\n    void ClearActiveID();\n\n    //Allow checking for mapped nav inputs for implementing nav-related behavior\n    bool IsNavInputDown(ImGuiNavInput nav_input);\n    bool IsNavInputPressed(ImGuiNavInput nav_input, bool repeat = false);\n    bool IsNavInputReleased(ImGuiNavInput nav_input);\n\n    //Get and set previous line height. Useful on complex layouts where a widget may take more height while not being supposed to push the next line further down\n    float GetPreviousLineHeight();\n    void SetPreviousLineHeight(float height);\n\n    //Something loosely resembling an InputText context menu, except it doesn't operate on the current cursor position or selection at all. Returns if buffer was modified\n    bool PopupContextMenuInputText(const char* str_id, char* str_buffer, size_t buffer_size, bool paste_remove_newlines = true);\n\n    //Returns true if the hovered item has changed to a different one\n    bool HasHoveredNewItem();\n\n    //Returns true if any item is or was active in the previous frame\n    bool IsAnyItemActiveOrDeactivated();\n    bool IsAnyItemDeactivated();\n\n    //Returns true if any InputText is active\n    //Use IsAnyTempInputTextActive() to handle widgets creating temp InputTexts instead as the text state ID doesn't get cleared and it can't tell normal use and text input apart\n    bool IsAnyInputTextActive();\n\n    //Returns true if any temp InputText (created for sliders and such) is active\n    bool IsAnyTempInputTextActive();\n\n    //Returns true if any mouse button is clicked\n    bool IsAnyMouseClicked();\n\n    //Takes active input focus and prevents scrolling on any ImGui widget while leaving input state untouched otherwise\n    void BlockWidgetInput();\n\n    //Scroll window horizontally from vertical mouse wheel input\n    void HScrollWindowFromMouseWheelV();\n\n    //Scroll the parent window in begin stack from current mouse wheel input (real child windows can scroll their parents by default already)\n    void ScrollBeginStackParentWindow();\n\n    //Returns true if either scroll bar is visible\n    bool IsAnyScrollBarVisible();\n\n    //Adjusts cursor pos and clipping rect to enable custom title bar content (assumes default left-aligned title). Returns title bar screen rect as X, Y, X2, Y2 coordinates\n    ImVec4 BeginTitleBar();\n    void EndTitleBar();\n\n    //Returns true if a character in the string is mapped in the active font\n    bool StringContainsUnmappedCharacter(const char* str);\n\n    //Returns string shortened to fit width_max in the active font\n    std::string StringEllipsis(const char* str, float width_max);\n\n    //Fairly specific, yet general enough custom widget that contains an draggable & resizable rectangle in a fixed size area\n    struct ImGuiDraggableRectAreaState\n    {\n        ImVec2 RectPos;\n        ImVec2 RectSize;\n\n        ImVec2 RectPosDragStart;\n        ImVec2 RectSizeDragStart;\n        ImGuiMouseButton DragMouseButton = ImGuiMouseButton_Left;\n        ImGuiDir EdgeDragHDir            = ImGuiDir_None;\n        ImGuiDir EdgeDragVDir            = ImGuiDir_None;\n        bool EdgeDragActive              = false;\n        bool EdgeDragHighlightVisible    = false;\n    };\n\n    //Returns true if the rectangle was modified\n    bool DraggableRectArea(const char* str_id, const ImVec2& area_size, ImGuiDraggableRectAreaState& state);\n\n    //Stores an ImGui mouse state or something that would like to be one and can set from or apply to global state if needed. \n    //Somewhat hacky, but pretty unproblematic when used correctly.\n    class ImGuiMouseState\n    {\n        public:\n            ImVec2 MousePos;\n            bool   MouseDown[5];\n            float  MouseWheel;\n            float  MouseWheelH;\n            ImVec2 MouseDelta;\n            ImVec2 MousePosPrev;\n            ImVec2 MouseClickedPos[5];\n            double MouseClickedTime[5];\n            bool   MouseClicked[5];\n            bool   MouseDoubleClicked[5];\n            ImU16  MouseClickedCount[5];\n            ImU16  MouseClickedLastCount[5];\n            bool   MouseReleased[5];\n            bool   MouseDownOwned[5];\n            bool   MouseDownOwnedUnlessPopupClose[5];\n            float  MouseDownDuration[5];\n            float  MouseDownDurationPrev[5];\n            float  MouseDragMaxDistanceSqr[5];\n\n            ImGuiMouseState();\n            void SetFromGlobalState();\n            void ApplyToGlobalState();\n            void Advance();        //Advance state similar to what happens in ImGui::NewFrame();\n    };\n\n    //Very dirty hack. Allows storing and restoring the active widget state. Works fine for a bunch of buttons not interrupting an InputText(), but likely breaks for anything complex.\n    //A separate ImGuiContext would be the sane option, but that comes with its own complications.\n    class ActiveWidgetStateStorage\n    {\n        private:\n            //Copies of structs declared in imgui_internal.h, since we don't want to pull that header in for everything that uses this one\n            //These need to be kept in sync\n            struct ImGuiDeactivatedItemDataInternal\n            {\n                ImGuiID ID;\n                int     ElapseFrame;\n                bool    HasBeenEditedBefore;\n                bool    IsAlive;\n            };\n\n            struct ImGuiDataTypeStorageInternal\n            {\n                ImU8 Data[8];\n            };\n\n            bool    IsInitialized;\n\n            ImGuiID HoveredId;\n            ImGuiID HoveredIdPreviousFrame;\n            int     HoveredIdPreviousFrameItemCount;\n            float   HoveredIdTimer;\n            float   HoveredIdNotActiveTimer;\n            bool    HoveredIdAllowOverlap;\n            bool    HoveredIdIsDisabled;\n            bool    ItemUnclipByLog;\n            ImGuiID ActiveId;\n            ImGuiID ActiveIdIsAlive;\n            float   ActiveIdTimer;\n            bool    ActiveIdIsJustActivated;\n            bool    ActiveIdAllowOverlap;\n            bool    ActiveIdNoClearOnFocusLoss;\n            bool    ActiveIdHasBeenPressedBefore;\n            bool    ActiveIdHasBeenEditedBefore;\n            bool    ActiveIdHasBeenEditedThisFrame;\n            bool    ActiveIdFromShortcut;\n            int     ActiveIdMouseButton;\n            ImVec2  ActiveIdClickOffset;\n            void*   ActiveIdWindow;\n            int     ActiveIdSource;\n            ImGuiID ActiveIdPreviousFrame;\n            ImGuiDeactivatedItemDataInternal DeactivatedItemData;\n            ImGuiDataTypeStorageInternal     ActiveIdValueOnActivation;\n            ImGuiID LastActiveId;\n            float   LastActiveIdTimer;\n\n            //ImGuiKeyOwnerData is not part of this. Probably doesn't need to be\n            ImU32   ActiveIdUsingNavDirMask;\n            bool    ActiveIdUsingAllKeyboardKeys;\n\n        public:\n            ActiveWidgetStateStorage();\n            void StoreCurrentState();\n            void ApplyState();\n            void AdvanceState();        //Advance state similar to what happens in ImGui::NewFrame();\n    };\n}"
  },
  {
    "path": "src/DesktopPlusUI/NotificationIcon.cpp",
    "content": "#include \"NotificationIcon.h\"\n\n#include <windowsx.h>\n#include <shellapi.h>\n#include <cwchar>\n\n#include \"UIManager.h\"\n#include \"InterprocessMessaging.h\"\n#include \"resource.h\"\n\n#undef _CRT_SECURE_NO_WARNINGS\n#define _CRT_SECURE_NO_WARNINGS\n#pragma warning(disable : 4996)\n\n#define WM_DPLUSUI_NOTIFICATIONICON WM_USER\nstatic LPCWSTR const g_WindowClassNameNotificationIcon = L\"elvdesktopUINotifIcon\";\n\nLRESULT __stdcall NotificationIcon::WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)\n{\n    if (msg == WM_DPLUSUI_NOTIFICATIONICON)\n    {\n        if (UIManager::Get())\n        {\n            UIManager::Get()->GetNotificationIcon().OnCallbackMessage(wParam, lParam);\n        }\n        return 0;\n    }\n\n    return ::DefWindowProc(hWnd, msg, wParam, lParam);\n}\n\nNotificationIcon::NotificationIcon() : m_Instance(nullptr), m_PopupMenu(nullptr)\n{\n    m_IconData = { sizeof(NOTIFYICONDATA) };\n}\n\nNotificationIcon::~NotificationIcon()\n{\n    if (m_PopupMenu != nullptr)\n    {\n        DestroyMenu(m_PopupMenu);\n    }\n\n    ::Shell_NotifyIcon(NIM_DELETE, &m_IconData);\n\n    if (m_IconData.hWnd != nullptr)\n    {\n        ::DestroyWindow(m_IconData.hWnd);\n        ::UnregisterClass(g_WindowClassNameNotificationIcon, m_Instance);\n    }\n}\n\nbool NotificationIcon::Init(HINSTANCE hinstance)\n{\n    m_Instance = hinstance;\n\n    //Register window class\n    //Using an extra window allows us to have the popup menu not focus the main window and also prevent Windows from treating desktop and non-desktop mode as separate icons\n    WNDCLASSEX wc = { sizeof(WNDCLASSEX), CS_CLASSDC, NotificationIcon::WndProc, 0L, 0L, m_Instance, nullptr, nullptr, nullptr, nullptr, g_WindowClassNameNotificationIcon, nullptr };\n    ::RegisterClassEx(&wc);\n\n    //Create notification icon\n    m_IconData.hWnd = ::CreateWindow(wc.lpszClassName, L\"Desktop+ UI Notification Icon\", 0, 0, 0, 1, 1, HWND_MESSAGE, nullptr, wc.hInstance, nullptr);\n    m_IconData.uID = 0;\n    m_IconData.uFlags = NIF_ICON | NIF_TIP | NIF_MESSAGE | NIF_SHOWTIP;\n    m_IconData.uCallbackMessage = WM_DPLUSUI_NOTIFICATIONICON;\n    m_IconData.hIcon = (HICON)::LoadImage(m_Instance, MAKEINTRESOURCE(IDI_DPLUS), IMAGE_ICON, ::GetSystemMetrics(SM_CXSMICON), ::GetSystemMetrics(SM_CYSMICON), LR_DEFAULTCOLOR);\n    wcscpy(m_IconData.szTip, L\"Desktop+\");\n    m_IconData.uVersion = NOTIFYICON_VERSION_4;\n\n    bool ret = ::Shell_NotifyIcon(NIM_ADD, &m_IconData);\n    ::Shell_NotifyIcon(NIM_SETVERSION, &m_IconData);\n\n    RefreshPopupMenu();\n\n    return ret;\n}\n\nvoid NotificationIcon::RefreshPopupMenu()\n{\n    //Destroy old menu if it exists\n    if (m_PopupMenu != nullptr)\n    {\n        DestroyMenu(m_PopupMenu);\n    }\n\n    //Create popup menu\n    m_PopupMenu = ::CreatePopupMenu();\n    if (m_PopupMenu != nullptr)\n    {\n        if (UIManager::Get()->IsInDesktopMode())\n        {\n            ::InsertMenu(m_PopupMenu, 0, MF_BYPOSITION | MF_STRING, 1, WStringConvertFromUTF8(TranslationManager::GetString(tstr_NotificationIconRestoreVR)).c_str());\n\n        }\n        else\n        {\n            ::InsertMenu(m_PopupMenu, 0, MF_BYPOSITION | MF_STRING, 1, WStringConvertFromUTF8(TranslationManager::GetString(tstr_NotificationIconOpenOnDesktop)).c_str());\n        }\n\n        ::InsertMenu(m_PopupMenu, 1, MF_BYPOSITION | MF_STRING, 2, WStringConvertFromUTF8(TranslationManager::GetString(tstr_NotificationIconQuit)).c_str());\n    }\n}\n\nvoid NotificationIcon::OnCallbackMessage(WPARAM wparam, LPARAM lparam)\n{\n    switch (LOWORD(lparam))\n    {\n        case NIN_SELECT:\n        case NIN_KEYSELECT:\n        case WM_CONTEXTMENU:\n        { \n            POINT const pt = { GET_X_LPARAM(wparam), GET_Y_LPARAM(wparam) };\n\n            //Respect menu drop alignment\n            UINT flags = TPM_BOTTOMALIGN | TPM_RIGHTBUTTON | TPM_RETURNCMD;\n            if (::GetSystemMetrics(SM_MENUDROPALIGNMENT) != 0)\n            {\n                flags |= TPM_RIGHTALIGN;\n            }\n            else\n            {\n                flags |= TPM_LEFTALIGN;\n            }\n\n            ::SetForegroundWindow(m_IconData.hWnd);\n            BOOL sel = ::TrackPopupMenu(m_PopupMenu, flags, pt.x, pt.y, 0, m_IconData.hWnd, nullptr);\n\n            switch (sel)\n            {\n                case 1 /*Open Settings on Desktop / Restore VR Interface*/:\n                {\n                    UIManager::Get()->Restart(!UIManager::Get()->IsInDesktopMode());\n                    break;\n                }\n                case 2 /*Quit*/:\n                {\n                    //Kindly ask dashboard process to quit\n                    if (HWND window = ::FindWindow(g_WindowClassNameDashboardApp, nullptr))\n                    {\n                        ::PostMessage(window, WM_QUIT, 0, 0);\n                    }\n\n                    UIManager::Get()->DisableRestartOnExit();\n                    ::PostMessage(UIManager::Get()->GetWindowHandle(), WM_QUIT, 0, 0);\n                    break;\n                }\n            }\n\n            break;\n        }\n        break;\n    }\n}\n"
  },
  {
    "path": "src/DesktopPlusUI/NotificationIcon.h",
    "content": "//Provides a notification area/tray icon\n//Technically a singleton, but is designed to have its one instance live in UIManager for proper lifetime management\n//Once intialized, it's fully self-contained\n\n#pragma once\n\n#define NOMINMAX\n#include <Windows.h>\n\nclass NotificationIcon\n{\n    private:\n        HINSTANCE m_Instance;\n        NOTIFYICONDATA m_IconData;\n        HMENU m_PopupMenu;\n\n        static LRESULT WINAPI WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam);\n\n    public:\n        NotificationIcon();\n        ~NotificationIcon();\n        bool Init(HINSTANCE hinstance);\n        void RefreshPopupMenu();        //Destroys the popup menu if it exists and recreates it. Called after Init() to update translation strings\n        void OnCallbackMessage(WPARAM wparam, LPARAM lparam);\n};"
  },
  {
    "path": "src/DesktopPlusUI/TextureManager.cpp",
    "content": "﻿#include \"TextureManager.h\"\n\n#define NOMINMAX\n#include <windows.h>\n#include <algorithm>\n#include <vector>\n\n//Make GDI+ header work with NOMINMAX\nnamespace Gdiplus\n{\n    using std::min;\n    using std::max;\n}\n\n#include <gdiplus.h>\n#include <gdipluspixelformats.h>\n\n#include \"ConfigManager.h\"\n#include \"Util.h\"\n#include \"UIManager.h\"\n#include \"OverlayManager.h\"\n#include \"WindowManager.h\"\n#include \"imgui_impl_dx11_openvr.h\"\n\nconst wchar_t* TextureManager::s_TextureFilenames[tmtex_MAX] =\n{\n    L\"images/icons/desktop.png\",\n    L\"images/icons/desktop_all.png\",\n    L\"images/icons/desktop_1.png\",\n    L\"images/icons/desktop_2.png\",\n    L\"images/icons/desktop_3.png\",\n    L\"images/icons/desktop_4.png\",\n    L\"images/icons/desktop_5.png\",\n    L\"images/icons/desktop_6.png\",\n    L\"images/icons/desktop_next.png\",\n    L\"images/icons/desktop_previous.png\",\n    L\"images/icons/desktop_none.png\",\n    L\"images/icons/performance_monitor.png\",\n    L\"images/icons/browser.png\",\n    L\"images/icons/settings.png\",\n    L\"images/icons/keyboard.png\",\n    L\"images/icons/task_switch.png\",\n    L\"images/icons/add.png\",\n    L\"images/icons/window_overlay.png\",\n    L\"images/icons_small/small_app_icon.png\",\n    L\"images/icons_small/small_close.png\",\n    L\"images/icons_small/small_move.png\",\n    L\"images/icons_small/small_move_locked.png\",\n    L\"images/icons_small/small_add_window.png\",\n    L\"images/icons_small/small_actionbar.png\",\n    L\"images/icons_small/small_performance_monitor_reset.png\",\n    L\"images/icons_small/small_browser_back.png\",\n    L\"images/icons_small/small_browser_forward.png\",\n    L\"images/icons_small/small_browser_refresh.png\",\n    L\"images/icons_small/small_browser_stop.png\",\n    L\"images/icons_small/xsmall_desktop.png\",\n    L\"images/icons_small/xsmall_desktop_all.png\",\n    L\"images/icons_small/xsmall_desktop_1.png\",\n    L\"images/icons_small/xsmall_desktop_2.png\",\n    L\"images/icons_small/xsmall_desktop_3.png\",\n    L\"images/icons_small/xsmall_desktop_4.png\",\n    L\"images/icons_small/xsmall_desktop_5.png\",\n    L\"images/icons_small/xsmall_desktop_6.png\",\n    L\"images/icons_small/xsmall_desktop_none.png\",\n    L\"images/icons_small/xsmall_performance_monitor.png\",\n    L\"images/icons_small/xsmall_browser.png\",\n    L\"images/icons_small/xsmall_settings.png\",\n    L\"images/icons_small/xsmall_keyboard.png\",\n    L\"images/icons_small/xsmall_origin_playspace.png\",\n    L\"images/icons_small/xsmall_origin_hmd_pos.png\",\n    L\"images/icons_small/xsmall_origin_seated.png\",\n    L\"images/icons_small/xsmall_origin_dashboard.png\",\n    L\"images/icons_small/xsmall_origin_hmd.png\",\n    L\"images/icons_small/xsmall_origin_controller_left.png\",\n    L\"images/icons_small/xsmall_origin_controller_right.png\",\n    L\"images/icons_small/xsmall_origin_aux.png\",\n    L\"images/icons_small/xsmall_origin_theater_screen.png\",\n    L\"images/icons_small/xxsmall_close.png\",\n    L\"images/icons_small/xxsmall_pin.png\",\n    L\"images/icons_small/xxsmall_unpin.png\",\n    L\"images/icons_small/xxsmall_browser_back.png\",\n    L\"\",                                            //tmtex_icon_temp, blank\n};\n\nstatic TextureManager g_TextureManager;\n\nTextureManager::TextureManager() : m_ReloadLater(false)\n{\n    std::fill(std::begin(m_ImGuiRectIDs), std::end(m_ImGuiRectIDs), -1);\n    std::fill(std::begin(m_AtlasSizes), std::end(m_AtlasSizes), ImVec2(-1, -1));\n    std::fill(std::begin(m_AtlasUVs), std::end(m_AtlasUVs), ImVec4(0, 0, 0, 0));\n}\n\nTextureManager& TextureManager::Get()\n{\n    return g_TextureManager;\n}\n\nbool TextureManager::LoadAllTexturesAndBuildFonts()\n{\n    bool all_ok = true;     //We don't need to abort when something fails, but let's not ignore it completely\n\n    ImGuiIO& io = ImGui::GetIO();\n\n    io.Fonts->Clear();\n    ImGui_ImplDX11_InvalidateDeviceObjects(); //I really feel like I shouldn't have to call a renderer-specific function to make reloading fonts work, but it seems necessary\n\n    //Clear arrays\n    std::fill(std::begin(m_ImGuiRectIDs), std::end(m_ImGuiRectIDs), -1);\n    std::fill(std::begin(m_AtlasSizes), std::end(m_AtlasSizes), ImVec2(-1, -1));\n    std::fill(std::begin(m_AtlasUVs), std::end(m_AtlasUVs), ImVec4(0, 0, 0, 0));\n\n    //Prepare font range to add more characters from action names/properties when needed\n    ImVector<ImWchar> ranges;\n    ImFontGlyphRangesBuilder builder;\n\n    ActionManager& action_manager = ConfigManager::Get().GetActionManager();\n    for (ActionUID uid : action_manager.GetActionOrderListUI())\n    {\n        const Action action = action_manager.GetAction(uid);\n\n        builder.AddText(action.Name.c_str());\n        builder.AddText(action.Label.c_str());\n\n        for (const auto& command : action.Commands)\n        {\n            builder.AddText(command.StrMain.c_str());\n            builder.AddText(command.StrArg.c_str());\n        }\n    }\n\n    for (const std::string& str : ConfigManager::Get().GetOverlayProfileList()) //Also from overlay profiles\n    {\n        builder.AddText(str.c_str());\n    }\n\n    for (unsigned int i = 0; i < OverlayManager::Get().GetOverlayCount(); ++i) //And overlay names\n    {\n        builder.AddText(OverlayManager::Get().GetConfigData(i).ConfigNameStr.c_str());\n    }\n\n    for (const WindowInfo& window_info :  WindowManager::Get().WindowListGet()) //And window list\n    {\n        builder.AddText(window_info.GetListTitle().c_str());\n    }\n\n    for (const std::string& str : m_FontBuilderExtraStrings) //And extra strings... yeah. This might not be the best way to tackle this issue\n    {\n        builder.AddText(str.c_str());\n    }\n\n    //Characters from current translation\n    TranslationManager::Get().AddStringsToFontBuilder(builder);\n\n    //Characters used by the VR Keyboard\n    builder.AddText(UIManager::Get()->GetVRKeyboard().GetKeyLabelsString().c_str());\n\n    //Extra characters used by the UI directly\n    builder.AddText(k_pch_bold_exclamation_mark);\n    builder.AddText(k_pch_degree_symbol);\n\n    builder.AddRanges(io.Fonts->GetGlyphRangesDefault());\n    builder.BuildRanges(&ranges);\n\n    ImFontConfig config_compact;\n    ImFontConfig config_large;\n    config_compact.GlyphOffset.y = -1; //Set offset to make it not look so bad\n    config_large.GlyphOffset.y   = -1;\n    ImFontConfig* config = &config_compact;\n\n    //Try to load fonts\n    ImFont* font = nullptr;\n    ImFont* font_compact = nullptr;\n    ImFont* font_large = nullptr;\n    float font_base_size = 32.0f;\n    bool load_large_font = ( (ConfigManager::GetValue(configid_bool_interface_large_style)) && (!UIManager::Get()->IsInDesktopMode()) );\n\n    //Loop to do the same for the large font if needed\n    for (;;)\n    {\n        //Load preferred font first, if the translation has set one\n        const std::string& preferred_font_name      = TranslationManager::Get().GetCurrentTranslationFontName();\n        const std::wstring preferred_font_name_wstr = WStringConvertFromUTF8(TranslationManager::Get().GetCurrentTranslationFontName().c_str());\n\n        if (!preferred_font_name.empty())\n        {\n            //AddFontFromFileTTF asserts when failing to load, so check for existence, though it's not really an issue in release mode\n            if (FileExists( (L\"C:\\\\Windows\\\\Fonts\\\\\" + preferred_font_name_wstr).c_str() ))\n            {\n                font = io.Fonts->AddFontFromFileTTF( (\"C:\\\\Windows\\\\Fonts\\\\\" + preferred_font_name).c_str(), font_base_size * UIManager::Get()->GetUIScale(), config, ranges.Data);\n\n                //Other fonts are still used as fallback\n                config->MergeMode = true;\n            }\n            else if (FileExists( (WStringConvertFromUTF8(ConfigManager::Get().GetApplicationPath().c_str()) + L\"/lang/\" + preferred_font_name_wstr).c_str() ))\n            {\n                //Also allow for a custom font from the application directory\n                font = io.Fonts->AddFontFromFileTTF( (ConfigManager::Get().GetApplicationPath() + \"/lang/\" + preferred_font_name).c_str(), font_base_size * UIManager::Get()->GetUIScale(), \n                                                    config, ranges.Data);\n\n                config->MergeMode = true;\n            }\n        }\n\n        //Continue with the standard font selection\n        if (FileExists(L\"C:\\\\Windows\\\\Fonts\\\\segoeui.ttf\"))\n        {\n            font = io.Fonts->AddFontFromFileTTF(\"C:\\\\Windows\\\\Fonts\\\\segoeui.ttf\", font_base_size * UIManager::Get()->GetUIScale(), config, ranges.Data);\n        }\n\n        if (font != nullptr)\n        {\n            //Segoe UI doesn't have any CJK, use some fallbacks (loading this is actually pretty fast)\n            config->MergeMode = true;\n\n            //Prefer Meiryo over MS Gothic. The former isn't installed on non-japanese systems by default though\n            if (FileExists(L\"C:\\\\Windows\\\\Fonts\\\\meiryo.ttc\"))\n                io.Fonts->AddFontFromFileTTF(\"C:\\\\Windows\\\\Fonts\\\\meiryo.ttc\", font_base_size * UIManager::Get()->GetUIScale(), config, ranges.Data);\n            else if (FileExists(L\"C:\\\\Windows\\\\Fonts\\\\msgothic.ttc\"))\n                io.Fonts->AddFontFromFileTTF(\"C:\\\\Windows\\\\Fonts\\\\msgothic.ttc\", font_base_size * UIManager::Get()->GetUIScale(), config, ranges.Data);\n\n            if (FileExists(L\"C:\\\\Windows\\\\Fonts\\\\malgun.ttf\"))\n                io.Fonts->AddFontFromFileTTF(\"C:\\\\Windows\\\\Fonts\\\\malgun.ttf\", font_base_size * UIManager::Get()->GetUIScale(), config, ranges.Data);\n\n            if (FileExists(L\"C:\\\\Windows\\\\Fonts\\\\msyh.ttc\"))\n                io.Fonts->AddFontFromFileTTF(\"C:\\\\Windows\\\\Fonts\\\\msyh.ttc\", font_base_size * UIManager::Get()->GetUIScale(), config, ranges.Data);\n            \n            //Thai font\n            if (FileExists(L\"C:\\\\Windows\\\\Fonts\\\\LeelawUI.ttf\"))\n                io.Fonts->AddFontFromFileTTF(\"C:\\\\Windows\\\\Fonts\\\\LeelawUI.ttf\", font_base_size * UIManager::Get()->GetUIScale(), config, ranges.Data);\n\n            //Also add some symbol support at least... yeah this is far from comprehensive all in all but should cover most uses\n            if (FileExists(L\"C:\\\\Windows\\\\Fonts\\\\seguisym.ttf\"))\n                io.Fonts->AddFontFromFileTTF(\"C:\\\\Windows\\\\Fonts\\\\seguisym.ttf\", font_base_size * UIManager::Get()->GetUIScale(), config, ranges.Data);\n        }\n        else\n        {\n            //Though we have the default as fallback if it isn't somehow\n            font = io.Fonts->AddFontDefault();\n        }\n\n        if (font_compact == nullptr)\n        {\n            font_compact = font;\n        }\n        else\n        {\n            font_large = font;\n        }\n\n        if ( (load_large_font) && (font_large == nullptr) )\n        {\n            font_base_size *= 1.5f;\n            config = &config_large;\n        }\n        else\n        {\n            break;\n        }\n    }\n\n    //Initialize GDI+.\n    Gdiplus::GdiplusStartupInput gdiplusStartupInput;\n    ULONG_PTR gdiplusToken;\n    if (GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, nullptr) != Gdiplus::Ok)\n    {\n        LOG_F(ERROR, \"Initializing GDI+ failed! Icons will not be loaded\");\n\n        //Still build the font so we can have text at least\n        const bool font_build_success = io.Fonts->Build();\n\n        //If building the font atlas failed, fall back to the internal default font and try again\n        if (!font_build_success)\n        {\n            LOG_F(ERROR, \"Building font atlas failed (invalid font file?)! Falling back to internal font\");\n\n            io.Fonts->Clear();\n\n            font = io.Fonts->AddFontDefault();\n            font_compact = font;\n            font_large   = font;\n\n            io.Fonts->Build();\n        }\n\n        io.Fonts->ClearInputData(); //We don't need to keep this around, reduces RAM use a lot\n\n        UIManager::Get()->SetFonts(font_compact, font_large);\n\n        m_ReloadLater = false;\n        return false;       //Everything below will fail as well if this did\n    }\n\n    //Load images and add custom rects for them\n    //Since we have to build the font before actually copying the image data, we have to keep the bitmaps loaded, which is a bit messy, especially with custom action icons\n    std::vector< std::unique_ptr<Gdiplus::Bitmap> > bitmaps;\n    //Load application icons\n    int icon_id = 0;\n    for (; icon_id < tmtex_MAX; ++icon_id)\n    {\n        std::unique_ptr<Gdiplus::Bitmap> bmp;\n\n        if (icon_id == tmtex_icon_temp)\n        {\n            bmp = std::unique_ptr<Gdiplus::Bitmap>( new Gdiplus::Bitmap(m_TextureFilenameIconTemp.c_str()) );\n        }\n        else\n        {\n            bmp = std::unique_ptr<Gdiplus::Bitmap>( new Gdiplus::Bitmap(s_TextureFilenames[icon_id]) );\n        }\n\n        if (bmp->GetLastStatus() == Gdiplus::Ok)\n        {\n            m_ImGuiRectIDs[icon_id] = io.Fonts->AddCustomRectRegular(bmp->GetWidth(), bmp->GetHeight());\n\n            if (io.Fonts->TexDesiredWidth <= (int)bmp->GetWidth())\n            {\n                //We could go smarter here, but let's be honest, we actually shouldn't load large images into the atlas in the first place!\n                //But well, I tried out of curiosity once and the result was a disaster without these checks.\n                //And yes, we need more space than the texture's width, unfortunately. Probably for that one white pixel in the atlas or something\n                io.Fonts->TexDesiredWidth = (bmp->GetWidth() >= 2048) ? 4096 : (bmp->GetWidth() >= 1024) ? 2048 : (bmp->GetWidth() >= 512) ? 1024 : 512;\n            }\n        }\n\n        bitmaps.push_back(std::move(bmp));\n    }\n\n    //Load custom action icons\n    struct ActionIconTextureData\n    {\n        int IconImGuiRectID  = -1;\n        ImVec2 IconAtlasSize;\n        ImVec4 IconAtlasUV;\n    };\n\n    std::vector<ActionIconTextureData> action_icon_tex_data;\n    action_icon_tex_data.reserve(action_manager.GetActionOrderListUI().size());\n\n    action_manager.ClearIconData();\n    for (ActionUID uid : action_manager.GetActionOrderListUI())\n    {\n        const Action& action = action_manager.GetAction(uid);\n        ActionIconTextureData tex_data;\n\n        if (!action.IconFilename.empty())\n        {\n            std::string icon_path = \"images/icons/\" + action.IconFilename;\n            std::unique_ptr<Gdiplus::Bitmap> bmp( new Gdiplus::Bitmap(WStringConvertFromUTF8(icon_path.c_str()).c_str()) );\n\n            if (bmp->GetLastStatus() == Gdiplus::Ok)\n            {\n                tex_data.IconImGuiRectID = io.Fonts->AddCustomRectRegular(bmp->GetWidth(), bmp->GetHeight());\n\n                if (io.Fonts->TexDesiredWidth <= (int)bmp->GetWidth())\n                {\n                    //See above\n                    io.Fonts->TexDesiredWidth = (bmp->GetWidth() >= 2048) ? 4096 : (bmp->GetWidth() >= 1024) ? 2048 : (bmp->GetWidth() >= 512) ? 1024 : 512;\n                }\n            }\n\n            bitmaps.push_back(std::move(bmp));\n        }\n\n        action_icon_tex_data.push_back(tex_data);\n    }\n\n    //Set up already loaded window icons\n    for (auto& window_icon : m_WindowIcons)\n    {\n        window_icon.ImGuiRectID = io.Fonts->AddCustomRectRegular((int)window_icon.Size.x, (int)window_icon.Size.y);\n\n        if (io.Fonts->TexDesiredWidth <= (int)window_icon.Size.x)\n        {\n            //See above\n            io.Fonts->TexDesiredWidth = (window_icon.Size.x >= 2048) ? 4096 : (window_icon.Size.x >= 1024) ? 2048 : (window_icon.Size.x >= 512) ? 1024 : 512;\n        }\n    }\n\n    //Build atlas\n    const bool font_build_success = io.Fonts->Build();\n\n    //If building the font atlas failed, fall back to the internal default font and try again\n    if (!font_build_success)\n    {\n        LOG_F(ERROR, \"Building font atlas failed (invalid font file?)! Falling back to internal font\");\n\n        ImVector<ImFontAtlasCustomRect> custom_rects_back = io.Fonts->CustomRects;\n        int desired_width_back = io.Fonts->TexDesiredWidth;\n        io.Fonts->Clear();\n\n        font = io.Fonts->AddFontDefault();\n        font_compact = font;\n        font_large   = font;\n\n        io.Fonts->CustomRects     = custom_rects_back;\n        io.Fonts->TexDesiredWidth = desired_width_back;\n\n        io.Fonts->Build();\n    }\n\n    //Retrieve atlas texture in RGBA format\n    unsigned char* tex_pixels = nullptr;\n    int tex_width, tex_height;\n    io.Fonts->GetTexDataAsRGBA32(&tex_pixels, &tex_width, &tex_height);\n\n    //Actually do the copying now\n    icon_id = 0;\n    auto action_tex_data_it = action_icon_tex_data.begin();\n    for (auto& bmp : bitmaps)\n    {\n        if (bmp->GetLastStatus() == Gdiplus::Ok)\n        {\n            int*    rect_id    = nullptr;\n            ImVec2* atlas_size = nullptr;\n            ImVec4* atlas_uvs  = nullptr;\n\n            if (icon_id < tmtex_MAX)\n            {\n                rect_id    = &m_ImGuiRectIDs[icon_id];\n                atlas_size = &m_AtlasSizes[icon_id];\n                atlas_uvs  = &m_AtlasUVs[icon_id];\n            }\n            else\n            {\n                //Get the next action, skipping the ones with no icon\n                do\n                {\n                    if (action_tex_data_it == action_icon_tex_data.end())\n                    {\n                        break;\n                    }\n\n                    ActionIconTextureData& tex_data = *action_tex_data_it;\n                    rect_id    = &tex_data.IconImGuiRectID;\n                    atlas_size = &tex_data.IconAtlasSize;\n                    atlas_uvs  = &tex_data.IconAtlasUV;\n\n                    action_tex_data_it++;\n                }\n                while (*rect_id == -1);\n            }\n\n            if ( (rect_id != nullptr) && (*rect_id != -1) )\n            {\n                if (const ImFontAtlasCustomRect* rect = io.Fonts->GetCustomRectByIndex(*rect_id))\n                {\n                    Gdiplus::BitmapData bitmapData;\n                    Gdiplus::Rect gdirect(0, 0, rect->Width, rect->Height);\n                    if (bmp->LockBits(&gdirect, Gdiplus::ImageLockModeRead, PixelFormat32bppARGB, &bitmapData) == Gdiplus::Ok)  //Access bitmap data from GDI+\n                    {\n                        for (int y = 0; y < rect->Height; ++y)\n                        {\n                            ImU32* p = (ImU32*)tex_pixels + (rect->Y + y) * tex_width + (rect->X);\n                            UINT8* pgdi = (UINT8*)bitmapData.Scan0 + (y * bitmapData.Stride);\n\n                            for (int x = 0; x < rect->Width; ++x)\n                            {\n                                //GDI+ order is BGRA, convert\n                                *p++ = IM_COL32(*(pgdi + 2), *(pgdi + 1), *pgdi, *(pgdi + 3));\n                                pgdi += 4;\n                            }\n                        }\n\n                        bmp->UnlockBits(&bitmapData);\n\n                        //Store UVs and size since we succeeded with copying\n                        atlas_size->x = rect->Width;\n                        atlas_size->y = rect->Height;\n\n                        atlas_uvs->x = (float)rect->X * io.Fonts->TexUvScale.x;                  //Min U\n                        atlas_uvs->y = (float)rect->Y * io.Fonts->TexUvScale.y;                  //Min V\n                        atlas_uvs->z = (float)(rect->X + rect->Width)  * io.Fonts->TexUvScale.x; //Max U\n                        atlas_uvs->w = (float)(rect->Y + rect->Height) * io.Fonts->TexUvScale.y; //Max V\n                    }\n                    else\n                    {\n                        *rect_id = -1;\n                        all_ok = false;\n                    }\n                }\n                else\n                {\n                    *rect_id = -1;\n                    all_ok = false;\n                }\n            }\n            else\n            {\n                all_ok = false;\n            }\n        }\n\n        icon_id++;\n    }\n\n    //Copy cached window icons into atlas\n    for (auto& window_icon : m_WindowIcons)\n    {\n        if (const ImFontAtlasCustomRect* rect = io.Fonts->GetCustomRectByIndex(window_icon.ImGuiRectID))\n        {\n            UINT8* psrc = (UINT8*)window_icon.PixelData.get();\n            size_t stride = rect->Width * 4;\n            //Copy RGBA pixels line-by-line\n            for (int y = 0; y < rect->Height; ++y, psrc += stride)\n            {\n                ImU32* p = (ImU32*)tex_pixels + (rect->Y + y) * tex_width + (rect->X);\n                memcpy(p, psrc, stride);\n            }\n\n            //Store UVs\n            window_icon.AtlasUV.x = (float)rect->X * io.Fonts->TexUvScale.x;                  //Min U\n            window_icon.AtlasUV.y = (float)rect->Y * io.Fonts->TexUvScale.y;                  //Min V\n            window_icon.AtlasUV.z = (float)(rect->X + rect->Width)  * io.Fonts->TexUvScale.x; //Max U\n            window_icon.AtlasUV.w = (float)(rect->Y + rect->Height) * io.Fonts->TexUvScale.y; //Max V\n        }\n        else\n        {\n            window_icon.ImGuiRectID = -1;\n            all_ok = false;\n        }\n    }\n\n    //Store action texture data in actual actions\n    IM_ASSERT(action_icon_tex_data.size() == action_manager.GetActionOrderListUI().size());\n    for (size_t i = 0; i < action_icon_tex_data.size(); ++i)\n    {\n        const ActionIconTextureData& tex_data = action_icon_tex_data[i];\n\n        //We can just skip this when the rect ID is still -1 since we cleared all texture data at beginning\n        if (tex_data.IconImGuiRectID != -1)\n        {\n            Action action = action_manager.GetAction( action_manager.GetActionOrderListUI()[i] );\n\n            action.IconImGuiRectID = tex_data.IconImGuiRectID;\n            action.IconAtlasSize   = tex_data.IconAtlasSize;\n            action.IconAtlasUV     = tex_data.IconAtlasUV;\n\n            action_manager.StoreAction(action);\n        }\n    }\n\n    //Delete Bitmaps before shutting down GDI+\n    bitmaps.clear();\n\n    //Shutdown GDI+, we won't need it again\n    Gdiplus::GdiplusShutdown(gdiplusToken);\n    m_ReloadLater = false;\n\n    //We don't need to keep this around, reduces RAM use a lot\n    io.Fonts->ClearInputData();\n\n    UIManager::Get()->SetFonts(font_compact, font_large);\n\n    return all_ok;\n}\n\nvoid TextureManager::ReloadAllTexturesLater()\n{\n    m_ReloadLater = true;\n}\n\nbool TextureManager::GetReloadLaterFlag()\n{\n    return m_ReloadLater;\n}\n\nconst wchar_t* TextureManager::GetTextureFilename(TMNGRTexID texid) const\n{\n    return (texid != tmtex_icon_temp) ? s_TextureFilenames[texid] : m_TextureFilenameIconTemp.c_str();\n}\n\nvoid TextureManager::SetTextureFilenameIconTemp(const wchar_t* filename)\n{\n    m_TextureFilenameIconTemp = filename;\n}\n\nbool TextureManager::GetTextureInfo(TMNGRTexID texid, ImVec2& size, ImVec2& uv_min, ImVec2& uv_max) const\n{\n    int rect_id = m_ImGuiRectIDs[texid];\n    if (rect_id != -1)\n    {\n        size = m_AtlasSizes[texid];\n        //Also set cached UV coordinates\n        uv_min.x = m_AtlasUVs[texid].x;\n        uv_min.y = m_AtlasUVs[texid].y;\n        uv_max.x = m_AtlasUVs[texid].z;\n        uv_max.y = m_AtlasUVs[texid].w;\n\n        return true;\n    }\n\n    return false;\n}\n\nbool TextureManager::GetTextureInfo(const Action& action, ImVec2& size, ImVec2& uv_min, ImVec2& uv_max) const\n{\n    if (action.IconImGuiRectID != -1)\n    {\n        size = action.IconAtlasSize;\n        //Also set cached UV coordinates\n        uv_min.x = action.IconAtlasUV.x;\n        uv_min.y = action.IconAtlasUV.y;\n        uv_max.x = action.IconAtlasUV.z;\n        uv_max.y = action.IconAtlasUV.w;\n\n        return true;\n    }\n\n    return false;\n}\n\nint TextureManager::GetWindowIconCacheID(HWND window_handle)\n{\n    WindowInfo const* info_ptr = WindowManager::Get().WindowListFindWindow(window_handle);\n\n    return (info_ptr != nullptr) ? GetWindowIconCacheID(info_ptr->GetIcon()) : -1;\n}\n\nint TextureManager::GetWindowIconCacheID(HWND window_handle, uint64_t& icon_handle_config)\n{\n    WindowInfo const* info_ptr = WindowManager::Get().WindowListFindWindow(window_handle);\n    HICON icon_handle = (info_ptr != nullptr) ? info_ptr->GetIcon() : nullptr;\n\n    if (icon_handle != nullptr)\n    {\n        icon_handle_config = (uint64_t)icon_handle;\n        return GetWindowIconCacheID(icon_handle);\n    }\n    else if (icon_handle_config != 0)\n    {\n        return GetWindowIconCacheID((HICON)icon_handle_config);\n    }\n\n    return -1;\n}\n\nint TextureManager::GetWindowIconCacheID(HICON icon_handle)\n{\n    //Look if the icon is already loaded\n    for (int i = 0; i < m_WindowIcons.size(); ++i)\n    {\n        if (m_WindowIcons[i].IconHandle == icon_handle)\n        {\n            return i;\n        }\n    }\n\n    //Icon not loaded yet, try to do that\n    int ret = -1;\n    ICONINFO icon_info = {0};\n    if (::GetIconInfo(icon_handle, &icon_info) != 0)\n    {\n        HDC hdc = ::GetDC(nullptr);\n\n        //Get bitmap info from icon bitmap\n        BITMAPINFO bmp_info = {0};\n        bmp_info.bmiHeader.biSize = sizeof(bmp_info.bmiHeader);\n        if (::GetDIBits(hdc, icon_info.hbmColor, 0, 0, nullptr, &bmp_info, DIB_RGB_COLORS) != 0)\n        {\n            TMNGRWindowIcon window_icon;\n            int icon_width  = bmp_info.bmiHeader.biWidth;\n            int icon_height = abs(bmp_info.bmiHeader.biHeight);\n            const size_t icon_pixel_count = icon_width * icon_height;\n\n            auto PixelData = std::unique_ptr<BYTE[]>{new BYTE[icon_pixel_count * 4]};\n\n            bmp_info.bmiHeader.biSize        = sizeof(bmp_info.bmiHeader);\n            bmp_info.bmiHeader.biBitCount    = 32;\n            bmp_info.bmiHeader.biCompression = BI_RGB;\n            bmp_info.bmiHeader.biHeight      = -icon_height; //Always use top-down order (negative height)\n\n            //Read the actual bitmap buffer into the pixel data array\n            if (::GetDIBits(hdc, icon_info.hbmColor, 0, bmp_info.bmiHeader.biHeight, (LPVOID)PixelData.get(), &bmp_info, DIB_RGB_COLORS) != 0)\n            {\n                //Even if we don't override biBitCount to 32, it's still returned as that for 24-bit and lower bit-depth icons (probably just the screen DC format)\n                //It seems the only way to check if the icon needs its mask applied is to see if the alpha channel is fully blank\n                //32-bit icons still come with masks, but applying them means to override the alpha channel with a 1-bit one (and doing so is also wasteful)\n                bool needs_mask = true;\n                BYTE* psrc = PixelData.get() + 3; //BGRA alpha pixel\n                const BYTE* const psrc_end = PixelData.get() + (icon_pixel_count * 4);\n                for (; psrc < psrc_end; psrc += 4)\n                {\n                    if (*psrc != 0)\n                    {\n                        needs_mask = false;\n                        break;\n                    }\n                }\n\n                //Apply mask if we need to\n                if (needs_mask)\n                {\n                    //Get bitmap info for the mask this time\n                    BITMAPINFO bmp_info = {0};\n                    bmp_info.bmiHeader.biSize = sizeof(bmp_info.bmiHeader);\n                    if (::GetDIBits(hdc, icon_info.hbmMask, 0, 0, nullptr, &bmp_info, DIB_RGB_COLORS) != 0)\n                    {\n                        int mask_width  = bmp_info.bmiHeader.biWidth;\n                        int mask_height = abs(bmp_info.bmiHeader.biHeight);\n\n                        //Only continue if icon and mask are really the same size (can be different for monochrome bitmap formats, which are not supported here)\n                        if ( (icon_width == mask_width) && (icon_height == mask_height) )\n                        {\n                            auto PixelDataMask = std::unique_ptr<BYTE[]>{new BYTE[icon_pixel_count * 4]};\n\n                            bmp_info.bmiHeader.biSize        = sizeof(bmp_info.bmiHeader);\n                            bmp_info.bmiHeader.biBitCount    = 32;\n                            bmp_info.bmiHeader.biCompression = BI_RGB;\n                            bmp_info.bmiHeader.biHeight      = -abs(bmp_info.bmiHeader.biHeight); //Always use top-down order (negative height)\n\n                            //Read the mask bitmap buffer\n                            if (::GetDIBits(hdc, icon_info.hbmMask, 0, bmp_info.bmiHeader.biHeight, (LPVOID)PixelDataMask.get(), &bmp_info, DIB_RGB_COLORS) != 0)\n                            {\n                                //Apply mask to color pixel data\n                                psrc       = PixelData.get() + 3; //BGRA alpha pixel\n                                BYTE* pmsk = PixelDataMask.get(); //BGRA blue pixel (alpha channel is blank for the mask)\n                                for (; psrc < psrc_end; psrc += 4, pmsk += 4)\n                                {\n                                    *psrc = ~(*pmsk);\n                                }\n                            }\n                        }\n                    }\n                }\n\n                //Convert BGRA to RGBA for ImGui's texture atlas\n                window_icon.PixelData = std::unique_ptr<BYTE[]>{new BYTE[icon_pixel_count * 4]};\n                psrc         = PixelData.get();\n                UINT32* pdst = (UINT32*)window_icon.PixelData.get();\n                for (; psrc < psrc_end; psrc += 4, ++pdst)\n                {\n                    *pdst = IM_COL32(*(psrc + 2), *(psrc + 1), *psrc, *(psrc + 3));\n                }\n\n                //Fill out other data and move the icon to the cache\n                window_icon.IconHandle = icon_handle;\n                window_icon.Size = {(float)icon_width, (float)icon_height};\n                m_WindowIcons.push_back(std::move(window_icon));\n\n                //We succeeded, but the icon won't be ready until next frame, so schedule reload and skip rendering this frame\n                ReloadAllTexturesLater();\n                UIManager::Get()->RepeatFrame();\n\n                ret = (int)m_WindowIcons.size() - 1;\n            }\n        }\n\n        ::DeleteObject(icon_info.hbmColor);\n        ::DeleteObject(icon_info.hbmMask);\n\n        ::ReleaseDC(nullptr, hdc);\n    }\n\n    return ret;\n}\n\nbool TextureManager::GetWindowIconTextureInfo(int icon_cache_id, ImVec2& size, ImVec2& uv_min, ImVec2& uv_max) const\n{\n    if ( (icon_cache_id >= 0) && (icon_cache_id < m_WindowIcons.size()) )\n    {\n        const TMNGRWindowIcon& window_icon = m_WindowIcons[icon_cache_id];\n\n        size     = window_icon.Size;\n        uv_min.x = window_icon.AtlasUV.x;\n        uv_min.y = window_icon.AtlasUV.y;\n        uv_max.x = window_icon.AtlasUV.z;\n        uv_max.y = window_icon.AtlasUV.w;\n\n        return true;\n    }\n\n    return false;\n}\n\nbool TextureManager::GetOverlayIconTextureInfo(OverlayConfigData& data, ImVec2& size, ImVec2& uv_min, ImVec2& uv_max, bool is_xsmall, bool* has_window_icon)\n{\n    if ( (is_xsmall) && (data.ConfigInt[configid_int_overlay_capture_source] == ovrl_capsource_winrt_capture) && (data.ConfigHandle[configid_handle_overlay_state_winrt_hwnd] != 0) )\n    {\n        //XSmall returns the window icon itself\n        int cache_id = GetWindowIconCacheID((HWND)data.ConfigHandle[configid_handle_overlay_state_winrt_hwnd], data.ConfigHandle[configid_handle_overlay_state_winrt_last_hicon]);\n\n        if (cache_id != -1)\n        {\n            if (has_window_icon != nullptr)\n                *has_window_icon = true;\n\n            return GetWindowIconTextureInfo(cache_id, size, uv_min, uv_max);\n        }\n    }\n\n    return GetTextureInfo(GetOverlayIconTextureID(data, is_xsmall, has_window_icon), size, uv_min, uv_max);\n}\n\nbool TextureManager::AddFontBuilderString(const std::string& str)\n{\n    //Add only if it's not already in the extra string list. Avoids duplicates and unnecessary texture rebuilds if the requested character can't be found in the loaded fonts\n    if (std::find(m_FontBuilderExtraStrings.begin(), m_FontBuilderExtraStrings.end(), str) == m_FontBuilderExtraStrings.end())\n    {\n        m_FontBuilderExtraStrings.push_back(str);\n        return true;\n    }\n\n    return false;\n}\n\nTMNGRTexID TextureManager::GetOverlayIconTextureID(const OverlayConfigData& data, bool is_xsmall, bool* has_window_icon)\n{\n    TMNGRTexID texture_id = (is_xsmall) ? tmtex_icon_xsmall_desktop_none : tmtex_icon_desktop_none;\n    int desktop_id = -2;\n\n    if (has_window_icon != nullptr)\n        *has_window_icon = false;\n\n    switch (data.ConfigInt[configid_int_overlay_capture_source])\n    {\n        case ovrl_capsource_desktop_duplication:\n        {\n            desktop_id = data.ConfigInt[configid_int_overlay_desktop_id];\n            break;\n        }\n        case ovrl_capsource_winrt_capture:\n        {\n            if (data.ConfigHandle[configid_handle_overlay_state_winrt_hwnd] != 0)\n            {\n                if (has_window_icon != nullptr)\n                    *has_window_icon = true;\n\n                texture_id = (is_xsmall) ? tmtex_icon_xsmall_desktop_none : tmtex_icon_window_overlay;\n            }\n            else if (data.ConfigInt[configid_int_overlay_winrt_desktop_id] != -2)\n            {\n                desktop_id = data.ConfigInt[configid_int_overlay_winrt_desktop_id];\n            }\n            else\n            {\n                texture_id = (is_xsmall) ? tmtex_icon_xsmall_desktop_none : tmtex_icon_desktop_none;\n            }\n            break;\n        }\n        case ovrl_capsource_ui:\n        {\n            texture_id = (is_xsmall) ? tmtex_icon_xsmall_performance_monitor : tmtex_icon_performance_monitor;\n            break;\n        }\n        case ovrl_capsource_browser:\n        {\n            texture_id = (is_xsmall) ? tmtex_icon_xsmall_browser : tmtex_icon_browser;\n            break;\n        }\n    }\n\n    if (desktop_id != -2)\n    {\n        if (is_xsmall)\n        {\n            texture_id = (tmtex_icon_xsmall_desktop_1 + desktop_id <= tmtex_icon_xsmall_desktop_6) ? (TMNGRTexID)(tmtex_icon_xsmall_desktop_1 + desktop_id) : tmtex_icon_xsmall_desktop;\n        }\n        else\n        {\n            texture_id = (tmtex_icon_desktop_1 + desktop_id <= tmtex_icon_desktop_6) ? (TMNGRTexID)(tmtex_icon_desktop_1 + desktop_id) : tmtex_icon_desktop;\n        }\n    }\n\n    return texture_id;\n}\n"
  },
  {
    "path": "src/DesktopPlusUI/TextureManager.h",
    "content": "//Desktop+UI loads all textures into Dear ImGui's font texture atlas\n//Bigger texture sizes are well supported on VR-running GPUs and less texture switching is more efficient\n//It's also more convenient in general\n//This is using GDI+ to load PNGs. Seemed like the next best option without too much overhead and doesn't need any extra library to ship\n\n#pragma once\n\n#define NOMINMAX\n#include <windows.h>\n\n#include <string>\n#include <vector>\n#include <memory>\n#include \"imgui.h\"\n\nstruct Action;\n\nenum TMNGRTexID\n{\n    tmtex_icon_desktop,\n    tmtex_icon_desktop_all,\n    tmtex_icon_desktop_1,\n    tmtex_icon_desktop_2,\n    tmtex_icon_desktop_3,\n    tmtex_icon_desktop_4,\n    tmtex_icon_desktop_5,\n    tmtex_icon_desktop_6,\n    tmtex_icon_desktop_next,\n    tmtex_icon_desktop_prev,\n    tmtex_icon_desktop_none,\n    tmtex_icon_performance_monitor,\n    tmtex_icon_browser,\n    tmtex_icon_settings,\n    tmtex_icon_keyboard,\n\ttmtex_icon_switch_task,\n    tmtex_icon_add,\n    tmtex_icon_window_overlay,\n    tmtex_icon_small_app_icon,\n    tmtex_icon_small_close,\n    tmtex_icon_small_move,\n    tmtex_icon_small_move_locked,\n    tmtex_icon_small_add_window,\n    tmtex_icon_small_actionbar,\n    tmtex_icon_small_performance_monitor_reset,\n    tmtex_icon_small_browser_back,\n    tmtex_icon_small_browser_forward,\n    tmtex_icon_small_browser_refresh,\n    tmtex_icon_small_browser_stop,\n    tmtex_icon_xsmall_desktop,\n    tmtex_icon_xsmall_desktop_all,\n    tmtex_icon_xsmall_desktop_1,\n    tmtex_icon_xsmall_desktop_2,\n    tmtex_icon_xsmall_desktop_3,\n    tmtex_icon_xsmall_desktop_4,\n    tmtex_icon_xsmall_desktop_5,\n    tmtex_icon_xsmall_desktop_6,\n    tmtex_icon_xsmall_desktop_none,\n    tmtex_icon_xsmall_performance_monitor,\n    tmtex_icon_xsmall_browser,\n    tmtex_icon_xsmall_settings,\n    tmtex_icon_xsmall_keyboard,\n    tmtex_icon_xsmall_origin_room,\n    tmtex_icon_xsmall_origin_hmd_floor,\n    tmtex_icon_xsmall_origin_seated_space,\n    tmtex_icon_xsmall_origin_dashboard,\n    tmtex_icon_xsmall_origin_hmd,\n    tmtex_icon_xsmall_origin_left_hand,\n    tmtex_icon_xsmall_origin_right_hand,\n    tmtex_icon_xsmall_origin_aux,\n    tmtex_icon_xsmall_origin_theater_screen,\n    tmtex_icon_xxsmall_close,\n    tmtex_icon_xxsmall_pin,\n    tmtex_icon_xxsmall_unpin,\n    tmtex_icon_xxsmall_browser_back,\n    tmtex_icon_temp,         //This is an odd one to hack-ishly load one icon without associating it with anything. The file for this can be set freely by TextureManager\n    tmtex_MAX\n};\n\nstruct TMNGRWindowIcon\n{\n    HICON IconHandle = nullptr;\n    std::unique_ptr<BYTE[]> PixelData;  //RGBA\n    ImVec2 Size = {0.0f, 0.0f};\n    int ImGuiRectID  = -1; //-1 when no icon loaded (ID on ImGui end is not valid after building the font)\n    ImVec4 AtlasUV = {0.0f, 0.0f, 0.0f, 0.0f};\n};\n\nclass OverlayConfigData;\n\nclass TextureManager\n{\n    private:\n        static const wchar_t* s_TextureFilenames[tmtex_MAX];\n        int m_ImGuiRectIDs[tmtex_MAX];  //-1 when not loaded (ID on ImGui end is not valid after building the font as data is cleared to save memory)\n        ImVec2 m_AtlasSizes[tmtex_MAX];\n        ImVec4 m_AtlasUVs[tmtex_MAX];\n        std::wstring m_TextureFilenameIconTemp;\n        std::vector<std::string> m_FontBuilderExtraStrings; //Extra strings containing characters to be included when building the fonts. Might fill up over time but better than nothing\n        std::vector<TMNGRWindowIcon> m_WindowIcons;\n\n        bool m_ReloadLater;\n\n    public:\n        TextureManager();\n        static TextureManager& Get();\n\n        bool LoadAllTexturesAndBuildFonts();\n        void ReloadAllTexturesLater();          //Schedule reload for the beginning of the next frame since we can't do it in the middle of one\n        bool GetReloadLaterFlag();\n\n        const wchar_t* GetTextureFilename(TMNGRTexID texid) const;\n        void SetTextureFilenameIconTemp(const wchar_t* filename);\n        bool GetTextureInfo(TMNGRTexID texid, ImVec2& size, ImVec2& uv_min, ImVec2& uv_max) const;\n        bool GetTextureInfo(const Action& action, ImVec2& size, ImVec2& uv_min, ImVec2& uv_max) const;\n\n        int  GetWindowIconCacheID(HWND window_handle); //Returns -1 on error\n        int  GetWindowIconCacheID(HWND window_handle, uint64_t& icon_handle_config); //Updates icon_handle_config when lookup with window_handle succeeds or falls back to icon_handle_config\n        int  GetWindowIconCacheID(HICON icon_handle);  //Returns -1 on error\n        bool GetWindowIconTextureInfo(int icon_cache_id, ImVec2& size, ImVec2& uv_min, ImVec2& uv_max) const;\n\n        bool GetOverlayIconTextureInfo(OverlayConfigData& data, ImVec2& size, ImVec2& uv_min, ImVec2& uv_max, bool is_xsmall = false, bool* has_window_icon = nullptr);\n\n        bool AddFontBuilderString(const std::string& str);   //Returns true if string has been added (not already in extra string list)\n\n        static TMNGRTexID GetOverlayIconTextureID(const OverlayConfigData& data, bool is_xsmall, bool* has_window_icon = nullptr);\n};"
  },
  {
    "path": "src/DesktopPlusUI/TranslationManager.cpp",
    "content": "#include \"TranslationManager.h\"\n\n#include \"ConfigManager.h\"\n#include \"Ini.h\"\n#include \"Util.h\"\n#include \"Logging.h\"\n\n#include <clocale>\n\nconst char* TranslationManager::s_StringIDNames[tstr_MAX] =\n{\n    \"tstr_SettingsWindowTitle\",\n    \"tstr_SettingsCatInterface\",\n    \"tstr_SettingsCatEnvironment\",\n    \"tstr_SettingsCatProfiles\",\n    \"tstr_SettingsCatActions\",\n    \"tstr_SettingsCatKeyboard\",\n    \"tstr_SettingsCatMouse\",\n    \"tstr_SettingsCatLaserPointer\",\n    \"tstr_SettingsCatWindowOverlays\",\n    \"tstr_SettingsCatBrowser\", \n    \"tstr_SettingsCatPerformance\",\n    \"tstr_SettingsCatVersionInfo\",\n    \"tstr_SettingsCatWarnings\",\n    \"tstr_SettingsCatStartup\",\n    \"tstr_SettingsCatTroubleshooting\",\n    \"tstr_SettingsWarningPrefix\",\n    \"tstr_SettingsWarningCompositorResolution\",\n    \"tstr_SettingsWarningCompositorQuality\",\n    \"tstr_SettingsWarningProcessElevated\",\n    \"tstr_SettingsWarningElevatedMode\",\n    \"tstr_SettingsWarningElevatedProcessFocus\",\n    \"tstr_SettingsWarningBrowserMissing\",\n    \"tstr_SettingsWarningBrowserMismatch\",\n    \"tstr_SettingsWarningUIAccessLost\",\n    \"tstr_SettingsWarningOverlayCreationErrorLimit\",\n    \"tstr_SettingsWarningOverlayCreationErrorOther\",\n    \"tstr_SettingsWarningGraphicsCaptureError\",\n    \"tstr_SettingsWarningAppProfileActive\",\n    \"tstr_SettingsWarningConfigMigrated\",\n    \"tstr_SettingsWarningMenuDontShowAgain\",\n    \"tstr_SettingsWarningMenuDismiss\",\n    \"tstr_SettingsInterfaceLanguage\",\n    \"tstr_SettingsInterfaceLanguageCommunity\",\n    \"tstr_SettingsInterfaceLanguageIncompleteWarning\",\n    \"tstr_SettingsInterfaceAdvancedSettings\",\n    \"tstr_SettingsInterfaceAdvancedSettingsTip\",\n    \"tstr_SettingsInterfaceBlankSpaceDrag\",\n    \"tstr_SettingsInterfacePersistentUI\",\n    \"tstr_SettingsInterfacePersistentUIManage\",\n    \"tstr_SettingsInterfaceDesktopButtons\",\n    \"tstr_SettingsInterfaceDesktopButtonsNone\",\n    \"tstr_SettingsInterfaceDesktopButtonsIndividual\",\n    \"tstr_SettingsInterfaceDesktopButtonsCycle\",\n    \"tstr_SettingsInterfaceDesktopButtonsAddCombined\",\n    \"tstr_SettingsInterfacePersistentUIHelp\",\n    \"tstr_SettingsInterfacePersistentUIHelp2\",\n    \"tstr_SettingsInterfacePersistentUIWindowsHeader\",\n    \"tstr_SettingsInterfacePersistentUIWindowsSettings\",\n    \"tstr_SettingsInterfacePersistentUIWindowsProperties\",\n    \"tstr_SettingsInterfacePersistentUIWindowsKeyboard\",\n    \"tstr_SettingsInterfacePersistentUIWindowsStateGlobal\",\n    \"tstr_SettingsInterfacePersistentUIWindowsStateDashboardTab\",\n    \"tstr_SettingsInterfacePersistentUIWindowsStateVisible\",\n    \"tstr_SettingsInterfacePersistentUIWindowsStatePinned\",\n    \"tstr_SettingsInterfacePersistentUIWindowsStatePosition\",\n    \"tstr_SettingsInterfacePersistentUIWindowsStatePositionReset\",\n    \"tstr_SettingsInterfacePersistentUIWindowsStateSize\",\n    \"tstr_SettingsInterfacePersistentUIWindowsStateLaunchRestore\",\n    \"tstr_SettingsEnvironmentBackgroundColor\",\n    \"tstr_SettingsEnvironmentBackgroundColorDispModeNever\",\n    \"tstr_SettingsEnvironmentBackgroundColorDispModeDPlusTab\",\n    \"tstr_SettingsEnvironmentBackgroundColorDispModeAlways\",\n    \"tstr_SettingsEnvironmentDimInterface\",\n    \"tstr_SettingsEnvironmentDimInterfaceTip\",\n    \"tstr_SettingsProfilesOverlays\",\n    \"tstr_SettingsProfilesApps\",\n    \"tstr_SettingsProfilesManage\",\n    \"tstr_SettingsProfilesOverlaysHeader\",\n    \"tstr_SettingsProfilesOverlaysNameDefault\",\n    \"tstr_SettingsProfilesOverlaysNameNew\",\n    \"tstr_SettingsProfilesOverlaysNameNewBase\",\n    \"tstr_SettingsProfilesOverlaysProfileLoad\",\n    \"tstr_SettingsProfilesOverlaysProfileAdd\",\n    \"tstr_SettingsProfilesOverlaysProfileSave\",\n    \"tstr_SettingsProfilesOverlaysProfileDelete\",\n    \"tstr_SettingsProfilesOverlaysProfileDeleteConfirm\",\n    \"tstr_SettingsProfilesOverlaysProfileFailedLoad\",\n    \"tstr_SettingsProfilesOverlaysProfileFailedDelete\",\n    \"tstr_SettingsProfilesOverlaysProfileAddSelectHeader\",\n    \"tstr_SettingsProfilesOverlaysProfileAddSelectEmpty\",\n    \"tstr_SettingsProfilesOverlaysProfileAddSelectDo\",\n    \"tstr_SettingsProfilesOverlaysProfileAddSelectAll\",\n    \"tstr_SettingsProfilesOverlaysProfileAddSelectNone\",\n    \"tstr_SettingsProfilesOverlaysProfileSaveSelectHeader\",\n    \"tstr_SettingsProfilesOverlaysProfileSaveSelectName\",\n    \"tstr_SettingsProfilesOverlaysProfileSaveSelectNameErrorBlank\",\n    \"tstr_SettingsProfilesOverlaysProfileSaveSelectNameErrorTaken\",\n    \"tstr_SettingsProfilesOverlaysProfileSaveSelectHeaderList\", \n    \"tstr_SettingsProfilesOverlaysProfileSaveSelectDo\",\n    \"tstr_SettingsProfilesOverlaysProfileSaveSelectDoFailed\",\n    \"tstr_SettingsProfilesAppsHeader\",\n    \"tstr_SettingsProfilesAppsHeaderNoVRTip\",\n    \"tstr_SettingsProfilesAppsListEmpty\",\n    \"tstr_SettingsProfilesAppsProfileHeaderActive\",\n    \"tstr_SettingsProfilesAppsProfileEnabled\",\n    \"tstr_SettingsProfilesAppsProfileOverlayProfile\",\n    \"tstr_SettingsProfilesAppsProfileActionEnter\",\n    \"tstr_SettingsProfilesAppsProfileActionLeave\",\n    \"tstr_SettingsActionsManage\",\n    \"tstr_SettingsActionsManageButton\",\n    \"tstr_SettingsActionsButtonsOrderDefault\",\n    \"tstr_SettingsActionsButtonsOrderOverlayBar\",\n    \"tstr_SettingsActionsShowBindings\",\n    \"tstr_SettingsActionsActiveShortcuts\",\n    \"tstr_SettingsActionsActiveShortcutsTip\",\n    \"tstr_SettingsActionsActiveShortuctsHome\",\n    \"tstr_SettingsActionsActiveShortuctsBack\",\n    \"tstr_SettingsActionsGlobalShortcuts\",\n    \"tstr_SettingsActionsGlobalShortcutsTip\",\n    \"tstr_SettingsActionsGlobalShortcutsEntry\",\n    \"tstr_SettingsActionsGlobalShortcutsAdd\",\n    \"tstr_SettingsActionsGlobalShortcutsRemove\",\n    \"tstr_SettingsActionsHotkeys\",\n    \"tstr_SettingsActionsHotkeysTip\",\n    \"tstr_SettingsActionsHotkeysAdd\",\n    \"tstr_SettingsActionsHotkeysRemove\",\n    \"tstr_SettingsActionsTableHeaderAction\",\n    \"tstr_SettingsActionsTableHeaderShortcut\",\n    \"tstr_SettingsActionsTableHeaderHotkey\",\n    \"tstr_SettingsActionsManageHeader\",\n    \"tstr_SettingsActionsManageCopyUID\",\n    \"tstr_SettingsActionsManageNew\",\n    \"tstr_SettingsActionsManageEdit\",\n    \"tstr_SettingsActionsManageDuplicate\",\n    \"tstr_SettingsActionsManageDelete\",\n    \"tstr_SettingsActionsManageDeleteConfirm\",\n    \"tstr_SettingsActionsManageDuplicatedName\",\n    \"tstr_SettingsActionsEditHeader\",\n    \"tstr_SettingsActionsEditName\",\n    \"tstr_SettingsActionsEditNameTranslatedTip\",\n    \"tstr_SettingsActionsEditTarget\",\n    \"tstr_SettingsActionsEditTargetDefault\",\n    \"tstr_SettingsActionsEditTargetDefaultTip\",\n    \"tstr_SettingsActionsEditTargetUseTags\",\n    \"tstr_SettingsActionsEditTargetActionTarget\",\n    \"tstr_SettingsActionsEditHeaderAppearance\",\n    \"tstr_SettingsActionsEditIcon\",\n    \"tstr_SettingsActionsEditLabel\",\n    \"tstr_SettingsActionsEditLabelTranslatedTip\",\n    \"tstr_SettingsActionsEditHeaderCommands\",\n    \"tstr_SettingsActionsEditNameNew\",\n    \"tstr_SettingsActionsEditCommandAdd\",\n    \"tstr_SettingsActionsEditCommandDelete\",\n    \"tstr_SettingsActionsEditCommandDeleteConfirm\",\n    \"tstr_SettingsActionsEditCommandType\",\n    \"tstr_SettingsActionsEditCommandTypeNone\",\n    \"tstr_SettingsActionsEditCommandTypeKey\",\n    \"tstr_SettingsActionsEditCommandTypeMousePos\",\n    \"tstr_SettingsActionsEditCommandTypeString\",\n    \"tstr_SettingsActionsEditCommandTypeLaunchApp\",\n    \"tstr_SettingsActionsEditCommandTypeShowKeyboard\",\n    \"tstr_SettingsActionsEditCommandTypeCropActiveWindow\",\n    \"tstr_SettingsActionsEditCommandTypeShowOverlay\",\n    \"tstr_SettingsActionsEditCommandTypeSwitchTask\",\n    \"tstr_SettingsActionsEditCommandTypeLoadOverlayProfile\",\n    \"tstr_SettingsActionsEditCommandTypeUnknown\",\n    \"tstr_SettingsActionsEditCommandVisibilityToggle\",\n    \"tstr_SettingsActionsEditCommandVisibilityShow\",\n    \"tstr_SettingsActionsEditCommandVisibilityHide\",\n    \"tstr_SettingsActionsEditCommandUndo\",\n    \"tstr_SettingsActionsEditCommandKeyCode\",\n    \"tstr_SettingsActionsEditCommandKeyToggle\",\n    \"tstr_SettingsActionsEditCommandMouseX\",\n    \"tstr_SettingsActionsEditCommandMouseY\",\n    \"tstr_SettingsActionsEditCommandMouseUseCurrent\",\n    \"tstr_SettingsActionsEditCommandString\",\n    \"tstr_SettingsActionsEditCommandPath\",\n    \"tstr_SettingsActionsEditCommandPathTip\",\n    \"tstr_SettingsActionsEditCommandArgs\",\n    \"tstr_SettingsActionsEditCommandArgsTip\",\n    \"tstr_SettingsActionsEditCommandVisibility\",\n    \"tstr_SettingsActionsEditCommandSwitchingMethod\",\n    \"tstr_SettingsActionsEditCommandSwitchingMethodSwitcher\",\n    \"tstr_SettingsActionsEditCommandSwitchingMethodFocus\",\n    \"tstr_SettingsActionsEditCommandWindow\",\n    \"tstr_SettingsActionsEditCommandWindowNone\",\n    \"tstr_SettingsActionsEditCommandWindowStrictMatchingTip\",\n    \"tstr_SettingsActionsEditCommandCursorWarp\",\n    \"tstr_SettingsActionsEditCommandProfile\",\n    \"tstr_SettingsActionsEditCommandProfileClear\",\n    \"tstr_SettingsActionsEditCommandDescNone\",\n    \"tstr_SettingsActionsEditCommandDescKey\",\n    \"tstr_SettingsActionsEditCommandDescKeyToggle\",\n    \"tstr_SettingsActionsEditCommandDescMousePos\",\n    \"tstr_SettingsActionsEditCommandDescString\",\n    \"tstr_SettingsActionsEditCommandDescLaunchApp\",\n    \"tstr_SettingsActionsEditCommandDescLaunchAppArgsOpt\",\n    \"tstr_SettingsActionsEditCommandDescKeyboardToggle\",\n    \"tstr_SettingsActionsEditCommandDescKeyboardShow\",\n    \"tstr_SettingsActionsEditCommandDescKeyboardHide\",\n    \"tstr_SettingsActionsEditCommandDescCropWindow\",\n    \"tstr_SettingsActionsEditCommandDescOverlayToggle\",\n    \"tstr_SettingsActionsEditCommandDescOverlayShow\",\n    \"tstr_SettingsActionsEditCommandDescOverlayHide\",\n    \"tstr_SettingsActionsEditCommandDescOverlayTargetDefault\",\n    \"tstr_SettingsActionsEditCommandDescSwitchTask\",\n    \"tstr_SettingsActionsEditCommandDescSwitchTaskWindow\",\n    \"tstr_SettingsActionsEditCommandDescLoadOverlayProfile\",\n    \"tstr_SettingsActionsEditCommandDescLoadOverlayProfileAdd\",\n    \"tstr_SettingsActionsEditCommandDescUnknown\",\n    \"tstr_SettingsActionsOrderHeader\",\n    \"tstr_SettingsActionsOrderButtonLabel\",\n    \"tstr_SettingsActionsOrderButtonLabelSingular\",\n    \"tstr_SettingsActionsOrderNoActions\",\n    \"tstr_SettingsActionsOrderAdd\",\n    \"tstr_SettingsActionsOrderRemove\",\n    \"tstr_SettingsActionsAddSelectorHeader\",\n    \"tstr_SettingsActionsAddSelectorAdd\",\n    \"tstr_SettingsKeyboardLayout\",\n    \"tstr_SettingsKeyboardSize\",\n    \"tstr_SettingsKeyboardBehavior\",\n    \"tstr_SettingsKeyboardStickyMod\",\n    \"tstr_SettingsKeyboardKeyRepeat\",\n    \"tstr_SettingsKeyboardAutoShow\",\n    \"tstr_SettingsKeyboardAutoShowDesktopOnly\",\n    \"tstr_SettingsKeyboardAutoShowDesktop\",\n    \"tstr_SettingsKeyboardAutoShowDesktopTip\",\n    \"tstr_SettingsKeyboardAutoShowBrowser\",\n    \"tstr_SettingsKeyboardLayoutAuthor\",\n    \"tstr_SettingsKeyboardKeyClusters\",\n    \"tstr_SettingsKeyboardKeyClusterBase\",\n    \"tstr_SettingsKeyboardKeyClusterFunction\",\n    \"tstr_SettingsKeyboardKeyClusterNavigation\",\n    \"tstr_SettingsKeyboardKeyClusterNumpad\",\n    \"tstr_SettingsKeyboardKeyClusterExtra\",\n    \"tstr_SettingsKeyboardSwitchToEditor\",\n    \"tstr_SettingsMouseShowCursor\",\n    \"tstr_SettingsMouseShowCursorGCUnsupported\",\n    \"tstr_SettingsMouseShowCursorGCActiveWarning\",\n    \"tstr_SettingsMouseScrollSmooth\",\n    \"tstr_SettingsMouseSimulatePen\",\n    \"tstr_SettingsMouseSimulatePenUnsupported\",\n    \"tstr_SettingsMouseAllowLaserPointerOverride\",\n    \"tstr_SettingsMouseAllowLaserPointerOverrideTip\",\n    \"tstr_SettingsMouseDoubleClickAssist\",\n    \"tstr_SettingsMouseDoubleClickAssistTip\",\n    \"tstr_SettingsMouseDoubleClickAssistTipValueOff\",\n    \"tstr_SettingsMouseDoubleClickAssistTipValueAuto\",\n    \"tstr_SettingsMouseSmoothing\",\n    \"tstr_SettingsMouseSmoothingLevelNone\",\n    \"tstr_SettingsMouseSmoothingLevelVeryLow\",\n    \"tstr_SettingsMouseSmoothingLevelLow\",\n    \"tstr_SettingsMouseSmoothingLevelMedium\",\n    \"tstr_SettingsMouseSmoothingLevelHigh\",\n    \"tstr_SettingsMouseSmoothingLevelVeryHigh\",\n    \"tstr_SettingsLaserPointerTip\",\n    \"tstr_SettingsLaserPointerBlockInput\",\n    \"tstr_SettingsLaserPointerAutoToggleDistance\",\n    \"tstr_SettingsLaserPointerAutoToggleDistanceValueOff\",\n    \"tstr_SettingsLaserPointerHMDPointer\",\n    \"tstr_SettingsLaserPointerHMDPointerTableHeaderInputAction\",\n    \"tstr_SettingsLaserPointerHMDPointerTableHeaderBinding\",\n    \"tstr_SettingsLaserPointerHMDPointerTableBindingToggle\",\n    \"tstr_SettingsLaserPointerHMDPointerTableBindingLeft\",\n    \"tstr_SettingsLaserPointerHMDPointerTableBindingRight\",\n    \"tstr_SettingsLaserPointerHMDPointerTableBindingMiddle\",\n    \"tstr_SettingsLaserPointerHMDPointerTableBindingDrag\",\n    \"tstr_SettingsWindowOverlaysAutoFocus\",\n    \"tstr_SettingsWindowOverlaysKeepOnScreen\",\n    \"tstr_SettingsWindowOverlaysKeepOnScreenTip\",\n    \"tstr_SettingsWindowOverlaysAutoSizeOverlay\",\n    \"tstr_SettingsWindowOverlaysFocusSceneApp\",\n    \"tstr_SettingsWindowOverlaysFocusSceneAppDashboard\",\n    \"tstr_SettingsWindowOverlaysOnWindowDrag\",\n    \"tstr_SettingsWindowOverlaysOnWindowDragDoNothing\",\n    \"tstr_SettingsWindowOverlaysOnWindowDragBlock\",\n    \"tstr_SettingsWindowOverlaysOnWindowDragOverlay\",\n    \"tstr_SettingsWindowOverlaysOnCaptureLoss\",\n    \"tstr_SettingsWindowOverlaysOnCaptureLossTip\",\n    \"tstr_SettingsWindowOverlaysOnCaptureLossDoNothing\",\n    \"tstr_SettingsWindowOverlaysOnCaptureLossHide\",\n    \"tstr_SettingsWindowOverlaysOnCaptureLossRemove\",\n    \"tstr_SettingsBrowserMaxFrameRate\",\n    \"tstr_SettingsBrowserMaxFrameRateOverrideOff\",\n    \"tstr_SettingsBrowserContentBlocker\",\n    \"tstr_SettingsBrowserContentBlockerTip\",\n    \"tstr_SettingsBrowserContentBlockerListCount\",\n    \"tstr_SettingsBrowserContentBlockerListCountSingular\",\n    \"tstr_SettingsPerformanceUpdateLimiter\",\n    \"tstr_SettingsPerformanceUpdateLimiterMode\",\n    \"tstr_SettingsPerformanceUpdateLimiterModeOff\",\n    \"tstr_SettingsPerformanceUpdateLimiterModeMS\",\n    \"tstr_SettingsPerformanceUpdateLimiterModeFPS\",\n    \"tstr_SettingsPerformanceUpdateLimiterModeOffOverride\",\n    \"tstr_SettingsPerformanceUpdateLimiterModeMSTip\",\n    \"tstr_SettingsPerformanceUpdateLimiterFPSValue\",\n    \"tstr_SettingsPerformanceUpdateLimiterOverride\",\n    \"tstr_SettingsPerformanceUpdateLimiterOverrideTip\",\n    \"tstr_SettingsPerformanceUpdateLimiterModeOverride\",\n    \"tstr_SettingsPerformanceRapidUpdates\",\n    \"tstr_SettingsPerformanceRapidUpdatesTip\",\n    \"tstr_SettingsPerformanceSingleDesktopMirror\",\n    \"tstr_SettingsPerformanceSingleDesktopMirrorTip\",\n    \"tstr_SettingsPerformanceAlternativeCursorRendering\",\n    \"tstr_SettingsPerformanceAlternativeCursorRenderingTip\",\n    \"tstr_SettingsPerformanceUseHDR\",\n    \"tstr_SettingsPerformanceUseHDRTip\",\n    \"tstr_SettingsPerformanceShowFPS\",\n    \"tstr_SettingsWarningsHidden\",\n    \"tstr_SettingsWarningsReset\",\n    \"tstr_SettingsStartupAutoLaunch\",\n    \"tstr_SettingsStartupSteamDisable\",\n    \"tstr_SettingsStartupSteamDisableTip\",\n    \"tstr_SettingsTroubleshootingRestart\",\n    \"tstr_SettingsTroubleshootingRestartSteam\",\n    \"tstr_SettingsTroubleshootingRestartDesktop\",\n    \"tstr_SettingsTroubleshootingElevatedModeEnter\",\n    \"tstr_SettingsTroubleshootingElevatedModeLeave\",\n    \"tstr_SettingsTroubleshootingSettingsReset\",\n    \"tstr_SettingsTroubleshootingSettingsResetConfirmDescription\",\n    \"tstr_SettingsTroubleshootingSettingsResetConfirmButton\",\n    \"tstr_SettingsTroubleshootingSettingsResetConfirmElementOverlays\",\n    \"tstr_SettingsTroubleshootingSettingsResetConfirmElementLegacyFiles\",\n    \"tstr_SettingsTroubleshootingSettingsResetShowQuickStart\",\n    \"tstr_KeyboardWindowTitle\",\n    \"tstr_KeyboardWindowTitleSettings\",\n    \"tstr_KeyboardWindowTitleOverlay\",\n    \"tstr_KeyboardWindowTitleOverlayUnknown\",\n    \"tstr_KeyboardShortcutsCut\",\n    \"tstr_KeyboardShortcutsCopy\",\n    \"tstr_KeyboardShortcutsPaste\",\n    \"tstr_OvrlPropsCatPosition\",\n    \"tstr_OvrlPropsCatAppearance\",\n    \"tstr_OvrlPropsCatCapture\",\n    \"tstr_OvrlPropsCatPerformanceMonitor\",\n    \"tstr_OvrlPropsCatBrowser\",\n    \"tstr_OvrlPropsCatAdvanced\",\n    \"tstr_OvrlPropsCatPerformance\",\n    \"tstr_OvrlPropsCatInterface\",\n    \"tstr_OvrlPropsPositionOrigin\",\n    \"tstr_OvrlPropsPositionOriginRoom\",\n    \"tstr_OvrlPropsPositionOriginHMDXY\",\n    \"tstr_OvrlPropsPositionOriginSeatedSpace\",\n    \"tstr_OvrlPropsPositionOriginDashboard\",\n    \"tstr_OvrlPropsPositionOriginHMD\",\n    \"tstr_OvrlPropsPositionOriginControllerL\",\n    \"tstr_OvrlPropsPositionOriginControllerR\",\n    \"tstr_OvrlPropsPositionOriginTracker1\",\n    \"tstr_OvrlPropsPositionOriginTheaterScreen\",\n    \"tstr_OvrlPropsPositionOriginConfigHMDXYTurning\",\n    \"tstr_OvrlPropsPositionOriginConfigTheaterScreenEnter\",\n    \"tstr_OvrlPropsPositionOriginConfigTheaterScreenLeave\",\n    \"tstr_OvrlPropsPositionOriginConfigSmoothing\",\n    \"tstr_OvrlPropsPositionOriginTheaterScreenTip\",\n    \"tstr_OvrlPropsPositionDispMode\",\n    \"tstr_OvrlPropsPositionDispModeAlways\",\n    \"tstr_OvrlPropsPositionDispModeDashboard\",\n    \"tstr_OvrlPropsPositionDispModeScene\",\n    \"tstr_OvrlPropsPositionDispModeDPlus\",\n    \"tstr_OvrlPropsPositionPos\",\n    \"tstr_OvrlPropsPositionPosTip\",\n    \"tstr_OvrlPropsPositionChange\",\n    \"tstr_OvrlPropsPositionReset\",\n    \"tstr_OvrlPropsPositionLock\",\n    \"tstr_OvrlPropsPositionChangeHeader\",\n    \"tstr_OvrlPropsPositionChangeHelp\",\n    \"tstr_OvrlPropsPositionChangeHelpDesktop\",\n    \"tstr_OvrlPropsPositionChangeManualAdjustment\",\n    \"tstr_OvrlPropsPositionChangeMove\",\n    \"tstr_OvrlPropsPositionChangeRotate\",\n    \"tstr_OvrlPropsPositionChangeForward\",\n    \"tstr_OvrlPropsPositionChangeBackward\",\n    \"tstr_OvrlPropsPositionChangeRollCW\",\n    \"tstr_OvrlPropsPositionChangeRollCCW\",\n    \"tstr_OvrlPropsPositionChangeLookAt\",\n    \"tstr_OvrlPropsPositionChangeDragButton\",\n    \"tstr_OvrlPropsPositionChangeOffset\",\n    \"tstr_OvrlPropsPositionChangeOffsetUpDown\",\n    \"tstr_OvrlPropsPositionChangeOffsetRightLeft\",\n    \"tstr_OvrlPropsPositionChangeOffsetForwardBackward\",\n    \"tstr_OvrlPropsPositionChangeDragSettings\",\n    \"tstr_OvrlPropsPositionChangeDragSettingsAutoDocking\",\n    \"tstr_OvrlPropsPositionChangeDragSettingsForceDistance\",\n    \"tstr_OvrlPropsPositionChangeDragSettingsForceDistanceShape\",\n    \"tstr_OvrlPropsPositionChangeDragSettingsForceDistanceShapeSphere\",\n    \"tstr_OvrlPropsPositionChangeDragSettingsForceDistanceShapeCylinder\",\n    \"tstr_OvrlPropsPositionChangeDragSettingsForceDistanceAutoCurve\",\n    \"tstr_OvrlPropsPositionChangeDragSettingsForceDistanceAutoTilt\",\n    \"tstr_OvrlPropsPositionChangeDragSettingsSnapPosition\",\n    \"tstr_OvrlPropsPositionChangeDragSettingsSnapRotation\",\n    \"tstr_OvrlPropsPositionChangeDragSettingsSnapRotationPitch\",\n    \"tstr_OvrlPropsPositionChangeDragSettingsSnapRotationYaw\",\n    \"tstr_OvrlPropsPositionChangeDragSettingsSnapRotationRoll\",\n    \"tstr_OvrlPropsAppearanceWidth\",\n    \"tstr_OvrlPropsAppearanceCurve\",\n    \"tstr_OvrlPropsAppearanceOpacity\",\n    \"tstr_OvrlPropsAppearanceBrightness\",\n    \"tstr_OvrlPropsAppearanceCrop\",\n    \"tstr_OvrlPropsAppearanceCropValueMax\",\n    \"tstr_OvrlPropsAppearanceShowBackside\",\n    \"tstr_OvrlPropsCrop\",\n    \"tstr_OvrlPropsCropHelp\",\n    \"tstr_OvrlPropsCropManualAdjust\",\n    \"tstr_OvrlPropsCropInvalidTip\",\n    \"tstr_OvrlPropsCropX\",\n    \"tstr_OvrlPropsCropY\",\n    \"tstr_OvrlPropsCropWidth\",\n    \"tstr_OvrlPropsCropHeight\",\n    \"tstr_OvrlPropsCropToWindow\",\n    \"tstr_OvrlPropsCaptureMethod\",\n    \"tstr_OvrlPropsCaptureMethodDup\",\n    \"tstr_OvrlPropsCaptureMethodGC\",\n    \"tstr_OvrlPropsCaptureMethodGCUnsupportedTip\",\n    \"tstr_OvrlPropsCaptureMethodGCUnsupportedPartialTip\",\n    \"tstr_OvrlPropsCaptureSource\",\n    \"tstr_OvrlPropsCaptureGCSource\",\n    \"tstr_OvrlPropsCaptureSourceUnknownWarning\",\n    \"tstr_OvrlPropsCaptureGCStrictMatching\",\n    \"tstr_OvrlPropsCaptureGCStrictMatchingTip\",\n    \"tstr_OvrlPropsPerfMonDesktopModeTip\",\n    \"tstr_OvrlPropsPerfMonGlobalTip\",\n    \"tstr_OvrlPropsPerfMonStyle\",\n    \"tstr_OvrlPropsPerfMonItems\",\n    \"tstr_OvrlPropsPerfMonStyleMinimal\",\n    \"tstr_OvrlPropsPerfMonStyleCompact\",\n    \"tstr_OvrlPropsPerfMonStyleLarge\",\n    \"tstr_OvrlPropsPerfMonStyleShowWindow\",\n    \"tstr_OvrlPropsPerfMonStyleShowTextOutline\",\n    \"tstr_OvrlPropsPerfMonStyleMinimalShowMore\",\n    \"tstr_OvrlPropsPerfMonItemCPU\",\n    \"tstr_OvrlPropsPerfMonItemGPU\",\n    \"tstr_OvrlPropsPerfMonItemGraphs\",\n    \"tstr_OvrlPropsPerfMonItemFrameStats\",\n    \"tstr_OvrlPropsPerfMonItemTime\",\n    \"tstr_OvrlPropsPerfMonItemBattery\",\n    \"tstr_OvrlPropsPerfMonItemTrackerBattery\",\n    \"tstr_OvrlPropsPerfMonItemViveWirelessTemp\",\n    \"tstr_OvrlPropsPerfMonResetValues\",\n    \"tstr_OvrlPropsBrowserNotAvailableTip\",\n    \"tstr_OvrlPropsBrowserCloned\",\n    \"tstr_OvrlPropsBrowserClonedTip\",\n    \"tstr_OvrlPropsBrowserClonedConvert\",\n    \"tstr_OvrlPropsBrowserURL\",\n    \"tstr_OvrlPropsBrowserURLHint\",\n    \"tstr_OvrlPropsBrowserGo\",\n    \"tstr_OvrlPropsBrowserRestore\",\n    \"tstr_OvrlPropsBrowserWidth\",\n    \"tstr_OvrlPropsBrowserHeight\",\n    \"tstr_OvrlPropsBrowserZoom\",\n    \"tstr_OvrlPropsBrowserAllowTransparency\",\n    \"tstr_OvrlPropsBrowserAllowTransparencyTip\",\n    \"tstr_OvrlPropsBrowserRecreateContext\",\n    \"tstr_OvrlPropsBrowserRecreateContextTip\",\n    \"tstr_OvrlPropsAdvanced3D\",\n    \"tstr_OvrlPropsAdvancedHSBS\",\n    \"tstr_OvrlPropsAdvancedSBS\",\n    \"tstr_OvrlPropsAdvancedHOU\",\n    \"tstr_OvrlPropsAdvancedOU\",\n    \"tstr_OvrlPropsAdvanced3DSwap\",\n    \"tstr_OvrlPropsAdvancedGazeFade\",\n    \"tstr_OvrlPropsAdvancedGazeFadeAuto\",\n    \"tstr_OvrlPropsAdvancedGazeFadeDistance\",\n    \"tstr_OvrlPropsAdvancedGazeFadeDistanceValueInf\",\n    \"tstr_OvrlPropsAdvancedGazeFadeSensitivity\",\n    \"tstr_OvrlPropsAdvancedGazeFadeOpacity\",\n    \"tstr_OvrlPropsAdvancedInput\",\n    \"tstr_OvrlPropsAdvancedInputInGame\",\n    \"tstr_OvrlPropsAdvancedInputFloatingUI\",\n    \"tstr_OvrlPropsAdvancedOverlayTags\",\n    \"tstr_OvrlPropsAdvancedOverlayTagsTip\",\n    \"tstr_OvrlPropsPerformanceInvisibleUpdate\",\n    \"tstr_OvrlPropsPerformanceInvisibleUpdateTip\",\n    \"tstr_OvrlPropsInterfaceOverlayName\",\n    \"tstr_OvrlPropsInterfaceOverlayNameAuto\",\n    \"tstr_OvrlPropsInterfaceActionOrderCustom\",\n    \"tstr_OvrlPropsInterfaceDesktopButtons\",\n    \"tstr_OvrlPropsInterfaceExtraButtons\",\n    \"tstr_OverlayBarOvrlHide\",\n    \"tstr_OverlayBarOvrlShow\",\n    \"tstr_OverlayBarOvrlClone\",\n    \"tstr_OverlayBarOvrlRemove\",\n    \"tstr_OverlayBarOvrlRemoveConfirm\",\n    \"tstr_OverlayBarOvrlProperties\",\n    \"tstr_OverlayBarOvrlAddWindow\",\n    \"tstr_OverlayBarTooltipOvrlAdd\",\n    \"tstr_OverlayBarTooltipSettings\",\n    \"tstr_OverlayBarTooltipResetHold\",\n    \"tstr_FloatingUIHideOverlayTip\",\n    \"tstr_FloatingUIHideOverlayHoldTip\",\n    \"tstr_FloatingUIDragModeEnableTip\",\n    \"tstr_FloatingUIDragModeDisableTip\",\n    \"tstr_FloatingUIDragModeHoldLockTip\",\n    \"tstr_FloatingUIDragModeHoldUnlockTip\",\n    \"tstr_FloatingUIWindowAddTip\",\n    \"tstr_FloatingUIActionBarShowTip\",\n    \"tstr_FloatingUIActionBarHideTip\",\n    \"tstr_FloatingUIBrowserGoBackTip\",\n    \"tstr_FloatingUIBrowserGoForwardTip\",\n    \"tstr_FloatingUIBrowserRefreshTip\",\n    \"tstr_FloatingUIBrowserStopTip\",\n    \"tstr_FloatingUIActionBarDesktopPrev\",\n    \"tstr_FloatingUIActionBarDesktopNext\",\n    \"tstr_FloatingUIActionBarEmpty\",\n    \"tstr_ActionNone\",\n    \"tstr_ActionKeyboardShow\",\n    \"tstr_ActionKeyboardHide\",\n    \"tstr_DefActionShowKeyboard\",\n    \"tstr_DefActionActiveWindowCrop\",\n    \"tstr_DefActionActiveWindowCropLabel\",\n    \"tstr_DefActionSwitchTask\",\n    \"tstr_DefActionToggleOverlays\",\n    \"tstr_DefActionToggleOverlaysLabel\",\n    \"tstr_DefActionMiddleMouse\",\n    \"tstr_DefActionMiddleMouseLabel\",\n    \"tstr_DefActionBackMouse\",\n    \"tstr_DefActionBackMouseLabel\",\n    \"tstr_DefActionReadMe\",\n    \"tstr_DefActionReadMeLabel\",\n    \"tstr_DefActionDashboardToggle\",\n    \"tstr_DefActionDashboardToggleLabel\",\n    \"tstr_PerformanceMonitorCPU\",\n    \"tstr_PerformanceMonitorGPU\",\n    \"tstr_PerformanceMonitorRAM\",\n    \"tstr_PerformanceMonitorVRAM\",\n    \"tstr_PerformanceMonitorFrameTime\",\n    \"tstr_PerformanceMonitorLoad\",\n    \"tstr_PerformanceMonitorFPS\",\n    \"tstr_PerformanceMonitorFPSAverage\",\n    \"tstr_PerformanceMonitorReprojectionRatio\",\n    \"tstr_PerformanceMonitorDroppedFrames\",\n    \"tstr_PerformanceMonitorBatteryLeft\",\n    \"tstr_PerformanceMonitorBatteryRight\",\n    \"tstr_PerformanceMonitorBatteryHMD\",\n    \"tstr_PerformanceMonitorBatteryTracker\",\n    \"tstr_PerformanceMonitorBatteryDisconnected\",\n    \"tstr_PerformanceMonitorViveWirelessTempNotAvailable\",\n    \"tstr_PerformanceMonitorCompactCPU\",\n    \"tstr_PerformanceMonitorCompactGPU\",\n    \"tstr_PerformanceMonitorCompactFPS\",\n    \"tstr_PerformanceMonitorCompactFPSAverage\",\n    \"tstr_PerformanceMonitorCompactReprojectionRatio\",\n    \"tstr_PerformanceMonitorCompactDroppedFrames\",\n    \"tstr_PerformanceMonitorCompactBattery\",\n    \"tstr_PerformanceMonitorCompactBatteryLeft\",\n    \"tstr_PerformanceMonitorCompactBatteryRight\",\n    \"tstr_PerformanceMonitorCompactBatteryHMD\",\n    \"tstr_PerformanceMonitorCompactBatteryTracker\",\n    \"tstr_PerformanceMonitorCompactBatteryDisconnected\",\n    \"tstr_PerformanceMonitorCompactViveWirelessTempNotAvailable\",\n    \"tstr_PerformanceMonitorEmpty\",\n    \"tstr_AuxUIDragHintDocking\",\n    \"tstr_AuxUIDragHintUndocking\",\n    \"tstr_AuxUIDragHintOvrlLocked\",\n    \"tstr_AuxUIDragHintOvrlTheaterScreenBlocked\",\n    \"tstr_AuxUIGazeFadeAutoHint\",\n    \"tstr_AuxUIGazeFadeAutoHintSingular\",\n    \"tstr_AuxUIQuickStartWelcomeHeader\",\n    \"tstr_AuxUIQuickStartWelcomeBody\",\n    \"tstr_AuxUIQuickStartOverlaysHeader\",\n    \"tstr_AuxUIQuickStartOverlaysBody\",\n    \"tstr_AuxUIQuickStartOverlaysBody2\",\n    \"tstr_AuxUIQuickStartOverlayPropertiesHeader\",\n    \"tstr_AuxUIQuickStartOverlayPropertiesBody\",\n    \"tstr_AuxUIQuickStartOverlayPropertiesBody2\",\n    \"tstr_AuxUIQuickStartSettingsHeader\",\n    \"tstr_AuxUIQuickStartSettingsBody\",\n    \"tstr_AuxUIQuickStartProfilesHeader\",\n    \"tstr_AuxUIQuickStartProfilesBody\",\n    \"tstr_AuxUIQuickStartActionsHeader\",\n    \"tstr_AuxUIQuickStartActionsBody\",\n    \"tstr_AuxUIQuickStartActionsBody2\",\n    \"tstr_AuxUIQuickStartOverlayTagsHeader\",\n    \"tstr_AuxUIQuickStartOverlayTagsBody\",\n    \"tstr_AuxUIQuickStartSettingsEndBody\",\n    \"tstr_AuxUIQuickStartFloatingUIHeader\",\n    \"tstr_AuxUIQuickStartFloatingUIBody\",\n    \"tstr_AuxUIQuickStartDesktopModeHeader\",\n    \"tstr_AuxUIQuickStartDesktopModeBody\",\n    \"tstr_AuxUIQuickStartEndHeader\",\n    \"tstr_AuxUIQuickStartEndBody\",\n    \"tstr_AuxUIQuickStartButtonNext\",\n    \"tstr_AuxUIQuickStartButtonPrev\",\n    \"tstr_AuxUIQuickStartButtonClose\",\n    \"tstr_DesktopModeCatTools\",\n    \"tstr_DesktopModeCatOverlays\",\n    \"tstr_DesktopModeToolSettings\",\n    \"tstr_DesktopModeToolActions\",\n    \"tstr_DesktopModeOverlayListAdd\",\n    \"tstr_DesktopModePageAddWindowOverlayTitle\",\n    \"tstr_DesktopModePageAddWindowOverlayHeader\",\n    \"tstr_KeyboardEditorKeyListTitle\",\n    \"tstr_KeyboardEditorKeyListTabContextReplace\",\n    \"tstr_KeyboardEditorKeyListTabContextClear\",\n    \"tstr_KeyboardEditorKeyListRow\",\n    \"tstr_KeyboardEditorKeyListSpacing\",\n    \"tstr_KeyboardEditorKeyListKeyAdd\",\n    \"tstr_KeyboardEditorKeyListKeyDuplicate\",\n    \"tstr_KeyboardEditorKeyListKeyRemove\",\n    \"tstr_KeyboardEditorKeyPropertiesTitle\",\n    \"tstr_KeyboardEditorKeyPropertiesNoSelection\",\n    \"tstr_KeyboardEditorKeyPropertiesType\",\n    \"tstr_KeyboardEditorKeyPropertiesTypeBlank\",\n    \"tstr_KeyboardEditorKeyPropertiesTypeVirtualKey\",\n    \"tstr_KeyboardEditorKeyPropertiesTypeVirtualKeyToggle\",\n    \"tstr_KeyboardEditorKeyPropertiesTypeVirtualKeyIsoEnter\",\n    \"tstr_KeyboardEditorKeyPropertiesTypeString\",\n    \"tstr_KeyboardEditorKeyPropertiesTypeSublayoutToggle\",\n    \"tstr_KeyboardEditorKeyPropertiesTypeAction\",\n    \"tstr_KeyboardEditorKeyPropertiesTypeVirtualKeyIsoEnterTip\",\n    \"tstr_KeyboardEditorKeyPropertiesTypeStringTip\",\n    \"tstr_KeyboardEditorKeyPropertiesSize\",\n    \"tstr_KeyboardEditorKeyPropertiesLabel\",\n    \"tstr_KeyboardEditorKeyPropertiesKeyCode\",\n    \"tstr_KeyboardEditorKeyPropertiesString\",\n    \"tstr_KeyboardEditorKeyPropertiesSublayout\",\n    \"tstr_KeyboardEditorKeyPropertiesAction\",\n    \"tstr_KeyboardEditorKeyPropertiesCluster\",\n    \"tstr_KeyboardEditorKeyPropertiesClusterTip\",\n    \"tstr_KeyboardEditorKeyPropertiesBlockModifiers\",\n    \"tstr_KeyboardEditorKeyPropertiesBlockModifiersTip\",\n    \"tstr_KeyboardEditorKeyPropertiesNoRepeat\",\n    \"tstr_KeyboardEditorKeyPropertiesNoRepeatTip\",\n    \"tstr_KeyboardEditorMetadataTitle\",\n    \"tstr_KeyboardEditorMetadataName\",\n    \"tstr_KeyboardEditorMetadataAuthor\",\n    \"tstr_KeyboardEditorMetadataHasAltGr\",\n    \"tstr_KeyboardEditorMetadataHasAltGrTip\",\n    \"tstr_KeyboardEditorMetadataClusterPreview\",\n    \"tstr_KeyboardEditorMetadataSave\",\n    \"tstr_KeyboardEditorMetadataLoad\",\n    \"tstr_KeyboardEditorMetadataSavePopupTitle\",\n    \"tstr_KeyboardEditorMetadataSavePopupFilename\",\n    \"tstr_KeyboardEditorMetadataSavePopupFilenameBlankTip\",\n    \"tstr_KeyboardEditorMetadataSavePopupConfirm\",\n    \"tstr_KeyboardEditorMetadataSavePopupConfirmError\",\n    \"tstr_KeyboardEditorMetadataLoadPopupTitle\",\n    \"tstr_KeyboardEditorMetadataLoadPopupConfirm\",\n    \"tstr_KeyboardEditorPreviewTitle\",\n    \"tstr_KeyboardEditorSublayoutBase\",\n    \"tstr_KeyboardEditorSublayoutShift\",\n    \"tstr_KeyboardEditorSublayoutAltGr\",\n    \"tstr_KeyboardEditorSublayoutAux\",\n    \"tstr_DialogOk\",\n    \"tstr_DialogCancel\",\n    \"tstr_DialogDone\",\n    \"tstr_DialogUndo\",\n    \"tstr_DialogRedo\",\n    \"tstr_DialogColorPickerHeader\",\n    \"tstr_DialogColorPickerCurrent\",\n    \"tstr_DialogColorPickerOriginal\",\n    \"tstr_DialogProfilePickerHeader\",\n    \"tstr_DialogProfilePickerNone\",\n    \"tstr_DialogActionPickerHeader\",\n    \"tstr_DialogActionPickerEmpty\",\n    \"tstr_DialogIconPickerHeader\",\n    \"tstr_DialogIconPickerHeaderTip\",\n    \"tstr_DialogIconPickerNone\",\n    \"tstr_DialogKeyCodePickerHeader\",\n    \"tstr_DialogKeyCodePickerHeaderHotkey\",\n    \"tstr_DialogKeyCodePickerModifiers\",\n    \"tstr_DialogKeyCodePickerKeyCode\",\n    \"tstr_DialogKeyCodePickerKeyCodeHint\",\n    \"tstr_DialogKeyCodePickerKeyCodeNone\",\n    \"tstr_DialogKeyCodePickerFromInput\",\n    \"tstr_DialogKeyCodePickerFromInputPopup\",\n    \"tstr_DialogKeyCodePickerFromInputPopupNoMouse\",\n    \"tstr_DialogWindowPickerHeader\",\n    \"tstr_DialogInputTagsHint\",\n    \"tstr_SourceDesktopAll\",\n    \"tstr_SourceDesktopID\",\n    \"tstr_SourceWinRTNone\",\n    \"tstr_SourceWinRTUnknown\",\n    \"tstr_SourceWinRTClosed\",\n    \"tstr_SourcePerformanceMonitor\",\n    \"tstr_SourceBrowser\",\n    \"tstr_SourceBrowserNoPage\",\n    \"tstr_NotificationIconRestoreVR\",\n    \"tstr_NotificationIconOpenOnDesktop\",\n    \"tstr_NotificationIconQuit\",\n    \"tstr_NotificationInitialStartupTitleVR\",\n    \"tstr_NotificationInitialStartupTitleDesktop\",\n    \"tstr_NotificationInitialStartupMessage\",\n    \"tstr_BrowserErrorPageTitle\",\n    \"tstr_BrowserErrorPageHeading\",\n    \"tstr_BrowserErrorPageMessage\",\n};\n\nstatic TranslationManager g_TranslationManager;\n\nTranslationManager::TranslationManager()\n{\n    //Init strings with ID names in case even fallback English doesn't have them\n    for (size_t i = 0; i < tstr_MAX; ++i)\n    {\n        m_Strings[i] = s_StringIDNames[i];\n    }\n}\n\nTranslationManager& TranslationManager::Get()\n{\n    return g_TranslationManager;\n}\n\nconst char* TranslationManager::GetString(TRMGRStrID str_id)\n{\n    return Get().m_Strings[str_id].c_str();\n}\n\nTRMGRStrID TranslationManager::GetStringID(const char* str)\n{\n    const auto it_begin = std::begin(s_StringIDNames), it_end = std::end(s_StringIDNames);\n    const auto it = std::find_if(it_begin, it_end, [&](const char* str_id_name){ return (strcmp(str_id_name, str) == 0); });\n\n    if (it != it_end)\n        return (TRMGRStrID)std::distance(it_begin, it);\n\n    return tstr_NONE;\n}\n\nstd::string TranslationManager::GetTranslationNameFromFile(const std::string& filename)\n{\n    std::string name;\n    std::string fullpath = ConfigManager::Get().GetApplicationPath() + \"lang/\" + filename;\n    Ini lang_file( WStringConvertFromUTF8(fullpath.c_str()).c_str() );\n\n    //Check if it's probably a translation/language file\n    if (lang_file.SectionExists(\"TranslationInfo\"))\n    {\n        name = lang_file.ReadString(\"TranslationInfo\", \"Name\");\n    }\n\n    return name;\n}\n\nstd::vector<TranslationManager::ListEntry> TranslationManager::GetTranslationList()\n{\n    std::vector<TranslationManager::ListEntry> lang_list;\n\n    const std::wstring wpath = WStringConvertFromUTF8( std::string(ConfigManager::Get().GetApplicationPath() + \"lang/*.ini\").c_str() );\n    WIN32_FIND_DATA find_data;\n    HANDLE handle_find = ::FindFirstFileW(wpath.c_str(), &find_data);\n\n    if (handle_find != INVALID_HANDLE_VALUE)\n    {\n        do\n        {\n            const std::string filename_utf8 = StringConvertFromUTF16(find_data.cFileName);\n            const std::string name = GetTranslationNameFromFile(filename_utf8);\n\n            //If name could be read, add to list\n            if (!name.empty())\n            {\n                lang_list.push_back({filename_utf8, name});\n            }\n        }\n        while (::FindNextFileW(handle_find, &find_data) != 0);\n\n        ::FindClose(handle_find);\n    }\n\n    return lang_list;\n}\n\nvoid TranslationManager::LoadTranslationFromFile(const std::string& filename)\n{\n    //When filename empty (called from empty config value), figure out the user's language to default to that\n    if (filename.empty())\n    {\n        wchar_t buffer[16] = {0};\n        ::GetLocaleInfoEx(LOCALE_NAME_USER_DEFAULT, LOCALE_SISO639LANGNAME, buffer, sizeof(buffer) / sizeof(wchar_t));\n        std::string lang = StringConvertFromUTF16(buffer);\n        ::GetLocaleInfoEx(LOCALE_NAME_USER_DEFAULT, LOCALE_SISO3166CTRYNAME, buffer, sizeof(buffer) / sizeof(wchar_t));\n        std::string ctry = StringConvertFromUTF16(buffer);\n\n        // ISO 639 code ideally matches the file names used, so this works as auto-detection\n        // try to detect the language file with the country code first (e.g. en_US.ini)\n        std::string lang_filename = lang + \"_\" + ctry + \".ini\";\n        if (!FileExists(WStringConvertFromUTF8((ConfigManager::Get().GetApplicationPath() + \"lang/\" + lang_filename).c_str()).c_str()))\n        {\n            // if the country code file does not exist, try to load the language file without the country code (e.g. en.ini)\n            lang_filename = lang + \".ini\";\n        }\n        \n        ConfigManager::SetValue(configid_str_interface_language_file, lang_filename);\n        LoadTranslationFromFile(lang_filename);\n\n        return;\n    }\n\n    //Load English first as a fallback\n    if (filename != \"en.ini\")\n    {\n        LoadTranslationFromFile(\"en.ini\");\n    }\n\n    std::string fullpath = ConfigManager::Get().GetApplicationPath() + \"lang/\" + filename;\n    Ini lang_file( WStringConvertFromUTF8(fullpath.c_str()).c_str() );\n\n    //Check if it's probably a translation/language file\n    if (lang_file.SectionExists(\"TranslationInfo\"))\n    {\n        m_CurrentTranslationName     = lang_file.ReadString(\"TranslationInfo\", \"Name\", \"Unknown\");\n        m_CurrentTranslationAuthor   = lang_file.ReadString(\"TranslationInfo\", \"Author\");\n        m_CurrentTranslationFontName = lang_file.ReadString(\"TranslationInfo\", \"PreferredFont\");\n        m_IsCurrentTranslationComplete = true;\n\n        LOG_IF_F(INFO, (filename != \"en.ini\"), \"Loading translation \\\"%s\\\", from \\\"%s\\\"...\", m_CurrentTranslationName.c_str(), filename.c_str());\n\n        //Clear precomputed strings to regenerate them with new translation later\n        m_StringsDesktopID.clear();\n        m_StringsFPSLimit.clear();\n\n        //Try loading all possible strings\n        for (size_t i = 0; i < tstr_MAX; ++i)\n        {\n            if (lang_file.KeyExists(\"Strings\", s_StringIDNames[i]))\n            {\n                m_Strings[i] = lang_file.ReadString(\"Strings\", s_StringIDNames[i]);\n                StringReplaceAll(m_Strings[i], \"\\\\n\", \"\\n\");    //Replace new line placeholder\n            }\n            else\n            {\n                m_IsCurrentTranslationComplete = false;\n\n                VLOG_F(1, \"Missing translation string %s\", s_StringIDNames[i]);\n            }\n        }\n\n        LOG_IF_F(WARNING, ((!m_IsCurrentTranslationComplete) && (loguru::current_verbosity_cutoff() < 1)), \"Translation has missing strings. Set logging verbosity to 1 or higher for more details\");\n\n        //Append fixed IDs to strings that need it\n        m_Strings[tstr_OverlayBarOvrlRemove]                         += \"###OverlayRemove\";\n        m_Strings[tstr_OverlayBarOvrlRemoveConfirm]                  += \"###OverlayRemove\";\n        m_Strings[tstr_SettingsProfilesOverlaysProfileDelete]        += \"###ProfileDelete\";\n        m_Strings[tstr_SettingsProfilesOverlaysProfileDeleteConfirm] += \"###ProfileDelete\";\n        m_Strings[tstr_SettingsActionsManageDelete]                  += \"###ActionDelete\";\n        m_Strings[tstr_SettingsActionsManageDeleteConfirm]           += \"###ActionDelete\";\n        m_Strings[tstr_SettingsActionsEditCommandDelete]             += \"###CommandDelete\";\n        m_Strings[tstr_SettingsActionsEditCommandDeleteConfirm]      += \"###CommandDelete\";\n\n        //Set locale or reset it if it's not in the file\n        //It's debatable if we should set the locale based on the language as they're separate things... but I personally like seeing separators and formats matching the typical language pattern.\n        //It's always possible to edit the locale key to blank to get the user one here\n        char* new_locale = setlocale(LC_ALL, lang_file.ReadString(\"TranslationInfo\", \"Locale\", \"C\").c_str());\n\n        if (new_locale == nullptr) //Reset to \"C\" if it failed\n        {\n            setlocale(LC_ALL, \"C\");\n        }\n    }\n    else\n    {\n        LOG_F(WARNING, \"Tried to load translation, but \\\"%s\\\" is not a valid translation file\", filename.c_str());\n    }\n}\n\nbool TranslationManager::IsCurrentTranslationComplete() const\n{\n    return m_IsCurrentTranslationComplete;\n}\n\nconst std::string& TranslationManager::GetCurrentTranslationName() const\n{\n    return m_CurrentTranslationName;\n}\n\nconst std::string& TranslationManager::GetCurrentTranslationAuthor() const\n{\n    return m_CurrentTranslationAuthor;\n}\n\nconst std::string& TranslationManager::GetCurrentTranslationFontName() const\n{\n    return m_CurrentTranslationFontName;\n}\n\nvoid TranslationManager::AddStringsToFontBuilder(ImFontGlyphRangesBuilder& builder) const\n{\n    builder.AddText(m_CurrentTranslationName.c_str());\n    builder.AddText(m_CurrentTranslationAuthor.c_str());\n\n    for (const auto& str : m_Strings)\n    {\n        builder.AddText(str.c_str());\n    }\n}\n\nconst char* TranslationManager::GetDesktopIDString(int desktop_id)\n{\n    if (desktop_id < 0)\n    {\n        return GetString(tstr_SourceDesktopAll);\n    }\n\n    int desktop_count = ConfigManager::GetValue(configid_int_state_interface_desktop_count);\n\n    desktop_id = clamp(desktop_id, 0, desktop_count - 1);\n\n    //Generate desktop ID strings based on current translation\n    while ((int)m_StringsDesktopID.size() < desktop_count)\n    {\n        std::string str = GetString(tstr_SourceDesktopID);\n        StringReplaceAll(str, \"%ID%\", std::to_string(m_StringsDesktopID.size() + 1));\n\n        m_StringsDesktopID.push_back(str);\n    }\n\n    return m_StringsDesktopID[desktop_id].c_str();\n}\n\nconst char* TranslationManager::GetFPSLimitString(int fps_limit_id)\n{\n    const int fps_limits[10] = { 1, 2, 5, 10, 15, 20, 25, 30, 40, 50 };\n\n    //Set out of range value to ID 10\n    if ( (fps_limit_id < 0) || (fps_limit_id > 9) )\n    {\n        fps_limit_id = 10;\n    }\n\n    //Generate fps limit strings based on current translation if needed\n    if (m_StringsFPSLimit.empty())\n    {\n        for (int limit : fps_limits)\n        {\n            std::string str = GetString(tstr_SettingsPerformanceUpdateLimiterFPSValue);\n            StringReplaceAll(str, \"%FPS%\", std::to_string(limit));\n\n            m_StringsFPSLimit.push_back(str);\n        }\n\n        //Out of range string\n        std::string str = GetString(tstr_SettingsPerformanceUpdateLimiterFPSValue);\n        StringReplaceAll(str, \"%FPS%\", \"?\");\n\n        m_StringsFPSLimit.push_back(str);\n    }\n\n    return m_StringsFPSLimit[fps_limit_id].c_str();\n}\n"
  },
  {
    "path": "src/DesktopPlusUI/TranslationManager.h",
    "content": "#pragma once\n\n#include <string>\n#include <vector>\n\n#include \"imgui.h\"\n\nenum TRMGRStrID\n{\n    tstr_SettingsWindowTitle,\n    tstr_SettingsCatInterface,\n    tstr_SettingsCatEnvironment,\n    tstr_SettingsCatProfiles,\n    tstr_SettingsCatActions,\n    tstr_SettingsCatKeyboard,\n    tstr_SettingsCatMouse,\n    tstr_SettingsCatLaserPointer,\n    tstr_SettingsCatWindowOverlays,\n    tstr_SettingsCatBrowser,\n    tstr_SettingsCatPerformance,\n    tstr_SettingsCatVersionInfo,\n    tstr_SettingsCatWarnings,\n    tstr_SettingsCatStartup,\n    tstr_SettingsCatTroubleshooting,\n    tstr_SettingsWarningPrefix,\n    tstr_SettingsWarningCompositorResolution,\n    tstr_SettingsWarningCompositorQuality,\n    tstr_SettingsWarningProcessElevated,\n    tstr_SettingsWarningElevatedMode,\n    tstr_SettingsWarningElevatedProcessFocus,\n    tstr_SettingsWarningBrowserMissing,\n    tstr_SettingsWarningBrowserMismatch,\n    tstr_SettingsWarningUIAccessLost,\n    tstr_SettingsWarningOverlayCreationErrorLimit,\n    tstr_SettingsWarningOverlayCreationErrorOther,              //%ERRORNAME% == VROverlay::GetOverlayErrorNameFromEnum()\n    tstr_SettingsWarningGraphicsCaptureError,                   //%ERRORCODE% == WinRT HRESULT in hex notation\n    tstr_SettingsWarningAppProfileActive,                       //%APPNAME% == name of active application\n    tstr_SettingsWarningConfigMigrated,\n    tstr_SettingsWarningMenuDontShowAgain,\n    tstr_SettingsWarningMenuDismiss,\n    tstr_SettingsInterfaceLanguage,\n    tstr_SettingsInterfaceLanguageCommunity,                    //%AUTHOR% == Language Author string\n    tstr_SettingsInterfaceLanguageIncompleteWarning,\n    tstr_SettingsInterfaceAdvancedSettings,\n    tstr_SettingsInterfaceAdvancedSettingsTip,\n    tstr_SettingsInterfaceBlankSpaceDrag,\n    tstr_SettingsInterfacePersistentUI,\n    tstr_SettingsInterfacePersistentUIManage,\n    tstr_SettingsInterfaceDesktopButtons,\n    tstr_SettingsInterfaceDesktopButtonsNone,\n    tstr_SettingsInterfaceDesktopButtonsIndividual,\n    tstr_SettingsInterfaceDesktopButtonsCycle,\n    tstr_SettingsInterfaceDesktopButtonsAddCombined,\n    tstr_SettingsInterfacePersistentUIHelp,\n    tstr_SettingsInterfacePersistentUIHelp2,\n    tstr_SettingsInterfacePersistentUIWindowsHeader,\n    tstr_SettingsInterfacePersistentUIWindowsSettings,\n    tstr_SettingsInterfacePersistentUIWindowsProperties,\n    tstr_SettingsInterfacePersistentUIWindowsKeyboard,\n    tstr_SettingsInterfacePersistentUIWindowsStateGlobal,\n    tstr_SettingsInterfacePersistentUIWindowsStateDashboardTab,\n    tstr_SettingsInterfacePersistentUIWindowsStateVisible,\n    tstr_SettingsInterfacePersistentUIWindowsStatePinned,\n    tstr_SettingsInterfacePersistentUIWindowsStatePosition,\n    tstr_SettingsInterfacePersistentUIWindowsStatePositionReset,\n    tstr_SettingsInterfacePersistentUIWindowsStateSize,\n    tstr_SettingsInterfacePersistentUIWindowsStateLaunchRestore,\n    tstr_SettingsEnvironmentBackgroundColor,\n    tstr_SettingsEnvironmentBackgroundColorDispModeNever,\n    tstr_SettingsEnvironmentBackgroundColorDispModeDPlusTab,\n    tstr_SettingsEnvironmentBackgroundColorDispModeAlways,\n    tstr_SettingsEnvironmentDimInterface,\n    tstr_SettingsEnvironmentDimInterfaceTip,\n    tstr_SettingsProfilesOverlays,\n    tstr_SettingsProfilesApps,\n    tstr_SettingsProfilesManage,\n    tstr_SettingsProfilesOverlaysHeader,\n    tstr_SettingsProfilesOverlaysNameDefault,\n    tstr_SettingsProfilesOverlaysNameNew,\n    tstr_SettingsProfilesOverlaysNameNewBase,                   //%ID% == ID of profile, increased if previous number is already taken\n    tstr_SettingsProfilesOverlaysProfileLoad,\n    tstr_SettingsProfilesOverlaysProfileAdd,\n    tstr_SettingsProfilesOverlaysProfileSave,\n    tstr_SettingsProfilesOverlaysProfileDelete,\n    tstr_SettingsProfilesOverlaysProfileDeleteConfirm,\n    tstr_SettingsProfilesOverlaysProfileFailedLoad,\n    tstr_SettingsProfilesOverlaysProfileFailedDelete,\n    tstr_SettingsProfilesOverlaysProfileAddSelectHeader,\n    tstr_SettingsProfilesOverlaysProfileAddSelectEmpty,\n    tstr_SettingsProfilesOverlaysProfileAddSelectDo,\n    tstr_SettingsProfilesOverlaysProfileAddSelectAll,\n    tstr_SettingsProfilesOverlaysProfileAddSelectNone,\n    tstr_SettingsProfilesOverlaysProfileSaveSelectHeader,\n    tstr_SettingsProfilesOverlaysProfileSaveSelectName,\n    tstr_SettingsProfilesOverlaysProfileSaveSelectNameErrorBlank,\n    tstr_SettingsProfilesOverlaysProfileSaveSelectNameErrorTaken,\n    tstr_SettingsProfilesOverlaysProfileSaveSelectHeaderList, \n    tstr_SettingsProfilesOverlaysProfileSaveSelectDo,\n    tstr_SettingsProfilesOverlaysProfileSaveSelectDoFailed,\n    tstr_SettingsProfilesAppsHeader,\n    tstr_SettingsProfilesAppsHeaderNoVRTip,\n    tstr_SettingsProfilesAppsListEmpty,\n    tstr_SettingsProfilesAppsProfileHeaderActive,\n    tstr_SettingsProfilesAppsProfileEnabled,\n    tstr_SettingsProfilesAppsProfileOverlayProfile,\n    tstr_SettingsProfilesAppsProfileActionEnter,\n    tstr_SettingsProfilesAppsProfileActionLeave,\n    tstr_SettingsActionsManage,\n    tstr_SettingsActionsManageButton,\n    tstr_SettingsActionsButtonsOrderDefault,\n    tstr_SettingsActionsButtonsOrderOverlayBar,\n    tstr_SettingsActionsShowBindings,\n    tstr_SettingsActionsActiveShortcuts,\n    tstr_SettingsActionsActiveShortcutsTip,\n    tstr_SettingsActionsActiveShortuctsHome,\n    tstr_SettingsActionsActiveShortuctsBack,\n    tstr_SettingsActionsGlobalShortcuts,\n    tstr_SettingsActionsGlobalShortcutsTip,\n    tstr_SettingsActionsGlobalShortcutsEntry,                   //%ID% == ID of shortcut, starts with 1\n    tstr_SettingsActionsGlobalShortcutsAdd,\n    tstr_SettingsActionsGlobalShortcutsRemove,\n    tstr_SettingsActionsHotkeys,\n    tstr_SettingsActionsHotkeysTip,\n    tstr_SettingsActionsHotkeysAdd,\n    tstr_SettingsActionsHotkeysRemove,\n    tstr_SettingsActionsTableHeaderAction,\n    tstr_SettingsActionsTableHeaderShortcut,\n    tstr_SettingsActionsTableHeaderHotkey,\n    tstr_SettingsActionsManageHeader,\n    tstr_SettingsActionsManageCopyUID,\n    tstr_SettingsActionsManageNew,\n    tstr_SettingsActionsManageEdit,\n    tstr_SettingsActionsManageDuplicate,\n    tstr_SettingsActionsManageDelete,\n    tstr_SettingsActionsManageDeleteConfirm,\n    tstr_SettingsActionsManageDuplicatedName,                   //%NAME% == Action name\n    tstr_SettingsActionsEditHeader,\n    tstr_SettingsActionsEditName,\n    tstr_SettingsActionsEditNameTranslatedTip,\n    tstr_SettingsActionsEditTarget,\n    tstr_SettingsActionsEditTargetDefault,\n    tstr_SettingsActionsEditTargetDefaultTip,\n    tstr_SettingsActionsEditTargetUseTags,\n    tstr_SettingsActionsEditTargetActionTarget,\n    tstr_SettingsActionsEditHeaderAppearance,\n    tstr_SettingsActionsEditIcon,\n    tstr_SettingsActionsEditLabel,\n    tstr_SettingsActionsEditLabelTranslatedTip,\n    tstr_SettingsActionsEditHeaderCommands,\n    tstr_SettingsActionsEditNameNew,\n    tstr_SettingsActionsEditCommandAdd,\n    tstr_SettingsActionsEditCommandDelete,\n    tstr_SettingsActionsEditCommandDeleteConfirm,\n    tstr_SettingsActionsEditCommandType,\n    tstr_SettingsActionsEditCommandTypeNone,\n    tstr_SettingsActionsEditCommandTypeKey,\n    tstr_SettingsActionsEditCommandTypeMousePos,\n    tstr_SettingsActionsEditCommandTypeString,\n    tstr_SettingsActionsEditCommandTypeLaunchApp,\n    tstr_SettingsActionsEditCommandTypeShowKeyboard,\n    tstr_SettingsActionsEditCommandTypeCropActiveWindow,\n    tstr_SettingsActionsEditCommandTypeShowOverlay,\n    tstr_SettingsActionsEditCommandTypeSwitchTask,\n    tstr_SettingsActionsEditCommandTypeLoadOverlayProfile,\n    tstr_SettingsActionsEditCommandTypeUnknown,\n    tstr_SettingsActionsEditCommandVisibilityToggle,\n    tstr_SettingsActionsEditCommandVisibilityShow,\n    tstr_SettingsActionsEditCommandVisibilityHide,\n    tstr_SettingsActionsEditCommandUndo,\n    tstr_SettingsActionsEditCommandKeyCode,\n    tstr_SettingsActionsEditCommandKeyToggle,\n    tstr_SettingsActionsEditCommandMouseX,\n    tstr_SettingsActionsEditCommandMouseY,\n    tstr_SettingsActionsEditCommandMouseUseCurrent,\n    tstr_SettingsActionsEditCommandString,\n    tstr_SettingsActionsEditCommandPath,\n    tstr_SettingsActionsEditCommandPathTip,\n    tstr_SettingsActionsEditCommandArgs,\n    tstr_SettingsActionsEditCommandArgsTip,\n    tstr_SettingsActionsEditCommandVisibility,\n    tstr_SettingsActionsEditCommandSwitchingMethod,\n    tstr_SettingsActionsEditCommandSwitchingMethodSwitcher,\n    tstr_SettingsActionsEditCommandSwitchingMethodFocus,\n    tstr_SettingsActionsEditCommandWindow,\n    tstr_SettingsActionsEditCommandWindowNone,\n    tstr_SettingsActionsEditCommandWindowStrictMatchingTip,\n    tstr_SettingsActionsEditCommandCursorWarp,\n    tstr_SettingsActionsEditCommandProfile,\n    tstr_SettingsActionsEditCommandProfileClear,\n    tstr_SettingsActionsEditCommandDescNone,\n    tstr_SettingsActionsEditCommandDescKey,                     //%KEYNAME% == key name\n    tstr_SettingsActionsEditCommandDescKeyToggle,               //^\n    tstr_SettingsActionsEditCommandDescMousePos,                //%X% & %Y% == position coordinates\n    tstr_SettingsActionsEditCommandDescString,                  //%STRING% == command string\n    tstr_SettingsActionsEditCommandDescLaunchApp,               //%APP% == app path, %ARGSOPT% == tstr_SettingsActionsEditCommandDescLaunchAppArgsOpt or blank if app args are empty\n    tstr_SettingsActionsEditCommandDescLaunchAppArgsOpt,        //%ARGS% == app arguments\n    tstr_SettingsActionsEditCommandDescKeyboardToggle,\n    tstr_SettingsActionsEditCommandDescKeyboardShow,\n    tstr_SettingsActionsEditCommandDescKeyboardHide,\n    tstr_SettingsActionsEditCommandDescCropWindow,\n    tstr_SettingsActionsEditCommandDescOverlayToggle,           //%TAGS% == target tags\n    tstr_SettingsActionsEditCommandDescOverlayShow,             //^\n    tstr_SettingsActionsEditCommandDescOverlayHide,             //^\n    tstr_SettingsActionsEditCommandDescOverlayTargetDefault,\n    tstr_SettingsActionsEditCommandDescSwitchTask,\n    tstr_SettingsActionsEditCommandDescSwitchTaskWindow,        //%WINDOW% == window title string\n    tstr_SettingsActionsEditCommandDescLoadOverlayProfile,      //%PROFILE% == profile name string\n    tstr_SettingsActionsEditCommandDescLoadOverlayProfileAdd,   //^\n    tstr_SettingsActionsEditCommandDescUnknown,\n    tstr_SettingsActionsOrderHeader,\n    tstr_SettingsActionsOrderButtonLabel,                       //%COUNT% == Action count\n    tstr_SettingsActionsOrderButtonLabelSingular,               //^\n    tstr_SettingsActionsOrderNoActions,\n    tstr_SettingsActionsOrderAdd,\n    tstr_SettingsActionsOrderRemove,\n    tstr_SettingsActionsAddSelectorHeader,\n    tstr_SettingsActionsAddSelectorAdd,\n    tstr_SettingsKeyboardLayout,\n    tstr_SettingsKeyboardSize,\n    tstr_SettingsKeyboardBehavior,\n    tstr_SettingsKeyboardStickyMod,\n    tstr_SettingsKeyboardKeyRepeat,\n    tstr_SettingsKeyboardAutoShow,\n    tstr_SettingsKeyboardAutoShowDesktopOnly,\n    tstr_SettingsKeyboardAutoShowDesktop,\n    tstr_SettingsKeyboardAutoShowDesktopTip,\n    tstr_SettingsKeyboardAutoShowBrowser,\n    tstr_SettingsKeyboardLayoutAuthor,                          //%AUTHOR% == Layout Author string\n    tstr_SettingsKeyboardKeyClusters,\n    tstr_SettingsKeyboardKeyClusterBase,\n    tstr_SettingsKeyboardKeyClusterFunction,\n    tstr_SettingsKeyboardKeyClusterNavigation,\n    tstr_SettingsKeyboardKeyClusterNumpad,\n    tstr_SettingsKeyboardKeyClusterExtra,\n    tstr_SettingsKeyboardSwitchToEditor,\n    tstr_SettingsMouseShowCursor,\n    tstr_SettingsMouseShowCursorGCUnsupported,\n    tstr_SettingsMouseShowCursorGCActiveWarning,\n    tstr_SettingsMouseScrollSmooth,\n    tstr_SettingsMouseSimulatePen,\n    tstr_SettingsMouseSimulatePenUnsupported,\n    tstr_SettingsMouseAllowLaserPointerOverride,\n    tstr_SettingsMouseAllowLaserPointerOverrideTip,\n    tstr_SettingsMouseDoubleClickAssist,\n    tstr_SettingsMouseDoubleClickAssistTip,\n    tstr_SettingsMouseDoubleClickAssistTipValueOff,\n    tstr_SettingsMouseDoubleClickAssistTipValueAuto,\n    tstr_SettingsMouseSmoothing,\n    tstr_SettingsMouseSmoothingLevelNone,\n    tstr_SettingsMouseSmoothingLevelVeryLow,\n    tstr_SettingsMouseSmoothingLevelLow,\n    tstr_SettingsMouseSmoothingLevelMedium,\n    tstr_SettingsMouseSmoothingLevelHigh,\n    tstr_SettingsMouseSmoothingLevelVeryHigh,\n    tstr_SettingsLaserPointerTip,\n    tstr_SettingsLaserPointerBlockInput,\n    tstr_SettingsLaserPointerAutoToggleDistance,\n    tstr_SettingsLaserPointerAutoToggleDistanceValueOff,\n    tstr_SettingsLaserPointerHMDPointer,\n    tstr_SettingsLaserPointerHMDPointerTableHeaderInputAction,\n    tstr_SettingsLaserPointerHMDPointerTableHeaderBinding,\n    tstr_SettingsLaserPointerHMDPointerTableBindingToggle,\n    tstr_SettingsLaserPointerHMDPointerTableBindingLeft,\n    tstr_SettingsLaserPointerHMDPointerTableBindingRight,\n    tstr_SettingsLaserPointerHMDPointerTableBindingMiddle,\n    tstr_SettingsLaserPointerHMDPointerTableBindingDrag,\n    tstr_SettingsWindowOverlaysAutoFocus,\n    tstr_SettingsWindowOverlaysKeepOnScreen,\n    tstr_SettingsWindowOverlaysKeepOnScreenTip,\n    tstr_SettingsWindowOverlaysAutoSizeOverlay,\n    tstr_SettingsWindowOverlaysFocusSceneApp,\n    tstr_SettingsWindowOverlaysFocusSceneAppDashboard,\n    tstr_SettingsWindowOverlaysOnWindowDrag,\n    tstr_SettingsWindowOverlaysOnWindowDragDoNothing,\n    tstr_SettingsWindowOverlaysOnWindowDragBlock,\n    tstr_SettingsWindowOverlaysOnWindowDragOverlay,\n    tstr_SettingsWindowOverlaysOnCaptureLoss,\n    tstr_SettingsWindowOverlaysOnCaptureLossTip,\n    tstr_SettingsWindowOverlaysOnCaptureLossDoNothing,\n    tstr_SettingsWindowOverlaysOnCaptureLossHide,\n    tstr_SettingsWindowOverlaysOnCaptureLossRemove,\n    tstr_SettingsBrowserMaxFrameRate,\n    tstr_SettingsBrowserMaxFrameRateOverrideOff,\n    tstr_SettingsBrowserContentBlocker,\n    tstr_SettingsBrowserContentBlockerTip,\n    tstr_SettingsBrowserContentBlockerListCount,          //%LISTCOUNT% == List count\n    tstr_SettingsBrowserContentBlockerListCountSingular,  //^\n    tstr_SettingsPerformanceUpdateLimiter,\n    tstr_SettingsPerformanceUpdateLimiterMode,\n    tstr_SettingsPerformanceUpdateLimiterModeOff,\n    tstr_SettingsPerformanceUpdateLimiterModeMS,\n    tstr_SettingsPerformanceUpdateLimiterModeFPS,\n    tstr_SettingsPerformanceUpdateLimiterModeOffOverride,\n    tstr_SettingsPerformanceUpdateLimiterModeMSTip,\n    tstr_SettingsPerformanceUpdateLimiterFPSValue,        //%FPS% == FPS value\n    tstr_SettingsPerformanceUpdateLimiterOverride,\n    tstr_SettingsPerformanceUpdateLimiterOverrideTip,\n    tstr_SettingsPerformanceUpdateLimiterModeOverride,\n    tstr_SettingsPerformanceRapidUpdates,\n    tstr_SettingsPerformanceRapidUpdatesTip,\n    tstr_SettingsPerformanceSingleDesktopMirror,\n    tstr_SettingsPerformanceSingleDesktopMirrorTip,\n    tstr_SettingsPerformanceAlternativeCursorRendering,\n    tstr_SettingsPerformanceAlternativeCursorRenderingTip,\n    tstr_SettingsPerformanceUseHDR,\n    tstr_SettingsPerformanceUseHDRTip,\n    tstr_SettingsPerformanceShowFPS,\n    tstr_SettingsWarningsHidden,\n    tstr_SettingsWarningsReset,\n    tstr_SettingsStartupAutoLaunch,\n    tstr_SettingsStartupSteamDisable,\n    tstr_SettingsStartupSteamDisableTip,\n    tstr_SettingsTroubleshootingRestart,\n    tstr_SettingsTroubleshootingRestartSteam,\n    tstr_SettingsTroubleshootingRestartDesktop,\n    tstr_SettingsTroubleshootingElevatedModeEnter,\n    tstr_SettingsTroubleshootingElevatedModeLeave,\n    tstr_SettingsTroubleshootingSettingsReset,\n    tstr_SettingsTroubleshootingSettingsResetConfirmDescription,\n    tstr_SettingsTroubleshootingSettingsResetConfirmButton,\n    tstr_SettingsTroubleshootingSettingsResetConfirmElementOverlays,\n    tstr_SettingsTroubleshootingSettingsResetConfirmElementLegacyFiles,\n    tstr_SettingsTroubleshootingSettingsResetShowQuickStart,\n    tstr_KeyboardWindowTitle,\n    tstr_KeyboardWindowTitleSettings,\n    tstr_KeyboardWindowTitleOverlay,                      //%OVERLAYNAME% == input target overlay name\n    tstr_KeyboardWindowTitleOverlayUnknown,\n    tstr_KeyboardShortcutsCut,\n    tstr_KeyboardShortcutsCopy,\n    tstr_KeyboardShortcutsPaste,\n    tstr_OvrlPropsCatPosition,\n    tstr_OvrlPropsCatAppearance,\n    tstr_OvrlPropsCatCapture,\n    tstr_OvrlPropsCatPerformanceMonitor,\n    tstr_OvrlPropsCatBrowser,\n    tstr_OvrlPropsCatAdvanced,\n    tstr_OvrlPropsCatPerformance,\n    tstr_OvrlPropsCatInterface,\n    tstr_OvrlPropsPositionOrigin,\n    tstr_OvrlPropsPositionOriginRoom,\n    tstr_OvrlPropsPositionOriginHMDXY,\n    tstr_OvrlPropsPositionOriginSeatedSpace,\n    tstr_OvrlPropsPositionOriginDashboard,\n    tstr_OvrlPropsPositionOriginHMD,\n    tstr_OvrlPropsPositionOriginControllerL,\n    tstr_OvrlPropsPositionOriginControllerR,\n    tstr_OvrlPropsPositionOriginTracker1,\n    tstr_OvrlPropsPositionOriginTheaterScreen,\n    tstr_OvrlPropsPositionOriginConfigHMDXYTurning,\n    tstr_OvrlPropsPositionOriginConfigTheaterScreenEnter,\n    tstr_OvrlPropsPositionOriginConfigTheaterScreenLeave,\n    tstr_OvrlPropsPositionOriginConfigSmoothing,\n    tstr_OvrlPropsPositionOriginTheaterScreenTip,\n    tstr_OvrlPropsPositionDispMode,\n    tstr_OvrlPropsPositionDispModeAlways,\n    tstr_OvrlPropsPositionDispModeDashboard,\n    tstr_OvrlPropsPositionDispModeScene,\n    tstr_OvrlPropsPositionDispModeDPlus,\n    tstr_OvrlPropsPositionPos,\n    tstr_OvrlPropsPositionPosTip,\n    tstr_OvrlPropsPositionChange,\n    tstr_OvrlPropsPositionReset,\n    tstr_OvrlPropsPositionLock,\n    tstr_OvrlPropsPositionChangeHeader,\n    tstr_OvrlPropsPositionChangeHelp,\n    tstr_OvrlPropsPositionChangeHelpDesktop,\n    tstr_OvrlPropsPositionChangeManualAdjustment,\n    tstr_OvrlPropsPositionChangeMove,\n    tstr_OvrlPropsPositionChangeRotate,\n    tstr_OvrlPropsPositionChangeForward,\n    tstr_OvrlPropsPositionChangeBackward,\n    tstr_OvrlPropsPositionChangeRollCW,\n    tstr_OvrlPropsPositionChangeRollCCW,\n    tstr_OvrlPropsPositionChangeLookAt,\n    tstr_OvrlPropsPositionChangeDragButton,\n    tstr_OvrlPropsPositionChangeOffset,\n    tstr_OvrlPropsPositionChangeOffsetUpDown,\n    tstr_OvrlPropsPositionChangeOffsetRightLeft,\n    tstr_OvrlPropsPositionChangeOffsetForwardBackward,\n    tstr_OvrlPropsPositionChangeDragSettings,\n    tstr_OvrlPropsPositionChangeDragSettingsAutoDocking,\n    tstr_OvrlPropsPositionChangeDragSettingsForceDistance,\n    tstr_OvrlPropsPositionChangeDragSettingsForceDistanceShape,\n    tstr_OvrlPropsPositionChangeDragSettingsForceDistanceShapeSphere,\n    tstr_OvrlPropsPositionChangeDragSettingsForceDistanceShapeCylinder,\n    tstr_OvrlPropsPositionChangeDragSettingsForceDistanceAutoCurve,\n    tstr_OvrlPropsPositionChangeDragSettingsForceDistanceAutoTilt,\n    tstr_OvrlPropsPositionChangeDragSettingsSnapPosition,\n    tstr_OvrlPropsPositionChangeDragSettingsSnapRotation,\n    tstr_OvrlPropsPositionChangeDragSettingsSnapRotationPitch,\n    tstr_OvrlPropsPositionChangeDragSettingsSnapRotationYaw,\n    tstr_OvrlPropsPositionChangeDragSettingsSnapRotationRoll,\n    tstr_OvrlPropsAppearanceWidth,\n    tstr_OvrlPropsAppearanceCurve,\n    tstr_OvrlPropsAppearanceOpacity,\n    tstr_OvrlPropsAppearanceBrightness,\n    tstr_OvrlPropsAppearanceCrop,\n    tstr_OvrlPropsAppearanceCropValueMax,\n    tstr_OvrlPropsAppearanceShowBackside,\n    tstr_OvrlPropsCrop,\n    tstr_OvrlPropsCropHelp,\n    tstr_OvrlPropsCropManualAdjust,\n    tstr_OvrlPropsCropInvalidTip,\n    tstr_OvrlPropsCropX,\n    tstr_OvrlPropsCropY,\n    tstr_OvrlPropsCropWidth,\n    tstr_OvrlPropsCropHeight,\n    tstr_OvrlPropsCropToWindow,\n    tstr_OvrlPropsCaptureMethod,\n    tstr_OvrlPropsCaptureMethodDup,\n    tstr_OvrlPropsCaptureMethodGC,\n    tstr_OvrlPropsCaptureMethodGCUnsupportedTip,\n    tstr_OvrlPropsCaptureMethodGCUnsupportedPartialTip,\n    tstr_OvrlPropsCaptureSource,\n    tstr_OvrlPropsCaptureGCSource,\n    tstr_OvrlPropsCaptureSourceUnknownWarning,\n    tstr_OvrlPropsCaptureGCStrictMatching,\n    tstr_OvrlPropsCaptureGCStrictMatchingTip,\n    tstr_OvrlPropsPerfMonDesktopModeTip,\n    tstr_OvrlPropsPerfMonGlobalTip,\n    tstr_OvrlPropsPerfMonStyle,\n    tstr_OvrlPropsPerfMonItems,\n    tstr_OvrlPropsPerfMonStyleMinimal,\n    tstr_OvrlPropsPerfMonStyleCompact,\n    tstr_OvrlPropsPerfMonStyleLarge,\n    tstr_OvrlPropsPerfMonStyleShowWindow,\n    tstr_OvrlPropsPerfMonStyleShowTextOutline,\n    tstr_OvrlPropsPerfMonStyleMinimalShowMore,\n    tstr_OvrlPropsPerfMonItemCPU,\n    tstr_OvrlPropsPerfMonItemGPU,\n    tstr_OvrlPropsPerfMonItemGraphs,\n    tstr_OvrlPropsPerfMonItemFrameStats,\n    tstr_OvrlPropsPerfMonItemTime,\n    tstr_OvrlPropsPerfMonItemBattery,\n    tstr_OvrlPropsPerfMonItemTrackerBattery,\n    tstr_OvrlPropsPerfMonItemViveWirelessTemp,\n    tstr_OvrlPropsPerfMonResetValues,\n    tstr_OvrlPropsBrowserNotAvailableTip,\n    tstr_OvrlPropsBrowserCloned,\n    tstr_OvrlPropsBrowserClonedTip,             //%OVERLAYNAME% == duplication source overlay name\n    tstr_OvrlPropsBrowserClonedConvert,\n    tstr_OvrlPropsBrowserURL,\n    tstr_OvrlPropsBrowserURLHint,\n    tstr_OvrlPropsBrowserGo,\n    tstr_OvrlPropsBrowserRestore,\n    tstr_OvrlPropsBrowserWidth,\n    tstr_OvrlPropsBrowserHeight,\n    tstr_OvrlPropsBrowserZoom,\n    tstr_OvrlPropsBrowserAllowTransparency,\n    tstr_OvrlPropsBrowserAllowTransparencyTip,\n    tstr_OvrlPropsBrowserRecreateContext,\n    tstr_OvrlPropsBrowserRecreateContextTip,\n    tstr_OvrlPropsAdvanced3D,\n    tstr_OvrlPropsAdvancedHSBS,\n    tstr_OvrlPropsAdvancedSBS,\n    tstr_OvrlPropsAdvancedHOU,\n    tstr_OvrlPropsAdvancedOU,\n    tstr_OvrlPropsAdvanced3DSwap,\n    tstr_OvrlPropsAdvancedGazeFade,\n    tstr_OvrlPropsAdvancedGazeFadeAuto,\n    tstr_OvrlPropsAdvancedGazeFadeDistance,\n    tstr_OvrlPropsAdvancedGazeFadeDistanceValueInf,\n    tstr_OvrlPropsAdvancedGazeFadeSensitivity,\n    tstr_OvrlPropsAdvancedGazeFadeOpacity,\n    tstr_OvrlPropsAdvancedInput,\n    tstr_OvrlPropsAdvancedInputInGame,\n    tstr_OvrlPropsAdvancedInputFloatingUI,\n    tstr_OvrlPropsAdvancedOverlayTags,\n    tstr_OvrlPropsAdvancedOverlayTagsTip,\n    tstr_OvrlPropsPerformanceInvisibleUpdate,\n    tstr_OvrlPropsPerformanceInvisibleUpdateTip,\n    tstr_OvrlPropsInterfaceOverlayName,\n    tstr_OvrlPropsInterfaceOverlayNameAuto,\n    tstr_OvrlPropsInterfaceActionOrderCustom,\n    tstr_OvrlPropsInterfaceDesktopButtons,\n    tstr_OvrlPropsInterfaceExtraButtons,\n    tstr_OverlayBarOvrlHide,\n    tstr_OverlayBarOvrlShow,\n    tstr_OverlayBarOvrlClone,\n    tstr_OverlayBarOvrlRemove,\n    tstr_OverlayBarOvrlRemoveConfirm,\n    tstr_OverlayBarOvrlProperties,\n    tstr_OverlayBarOvrlAddWindow,\n    tstr_OverlayBarTooltipOvrlAdd,\n    tstr_OverlayBarTooltipSettings,\n    tstr_OverlayBarTooltipResetHold,\n    tstr_FloatingUIHideOverlayTip,\n    tstr_FloatingUIHideOverlayHoldTip,\n    tstr_FloatingUIDragModeEnableTip,\n    tstr_FloatingUIDragModeDisableTip,\n    tstr_FloatingUIDragModeHoldLockTip,\n    tstr_FloatingUIDragModeHoldUnlockTip,\n    tstr_FloatingUIWindowAddTip,\n    tstr_FloatingUIActionBarShowTip,\n    tstr_FloatingUIActionBarHideTip,\n    tstr_FloatingUIBrowserGoBackTip,\n    tstr_FloatingUIBrowserGoForwardTip,\n    tstr_FloatingUIBrowserRefreshTip,\n    tstr_FloatingUIBrowserStopTip,\n    tstr_FloatingUIActionBarDesktopPrev,\n    tstr_FloatingUIActionBarDesktopNext,\n    tstr_FloatingUIActionBarEmpty,\n    tstr_ActionNone,\n    tstr_ActionKeyboardShow,\n    tstr_ActionKeyboardHide,\n    tstr_DefActionShowKeyboard,\n    tstr_DefActionActiveWindowCrop,\n    tstr_DefActionActiveWindowCropLabel,\n    tstr_DefActionSwitchTask,\n    tstr_DefActionToggleOverlays,\n    tstr_DefActionToggleOverlaysLabel,\n    tstr_DefActionMiddleMouse,\n    tstr_DefActionMiddleMouseLabel,\n    tstr_DefActionBackMouse,\n    tstr_DefActionBackMouseLabel,\n    tstr_DefActionReadMe,\n    tstr_DefActionReadMeLabel,\n    tstr_DefActionDashboardToggle,\n    tstr_DefActionDashboardToggleLabel,\n    tstr_PerformanceMonitorCPU,\n    tstr_PerformanceMonitorGPU,\n    tstr_PerformanceMonitorRAM,\n    tstr_PerformanceMonitorVRAM,\n    tstr_PerformanceMonitorFrameTime,\n    tstr_PerformanceMonitorLoad,\n    tstr_PerformanceMonitorFPS,\n    tstr_PerformanceMonitorFPSAverage,\n    tstr_PerformanceMonitorReprojectionRatio,\n    tstr_PerformanceMonitorDroppedFrames,\n    tstr_PerformanceMonitorBatteryLeft,\n    tstr_PerformanceMonitorBatteryRight,\n    tstr_PerformanceMonitorBatteryHMD,\n    tstr_PerformanceMonitorBatteryTracker,\n    tstr_PerformanceMonitorBatteryDisconnected,\n    tstr_PerformanceMonitorViveWirelessTempNotAvailable,\n    tstr_PerformanceMonitorCompactCPU,\n    tstr_PerformanceMonitorCompactGPU,\n    tstr_PerformanceMonitorCompactFPS,\n    tstr_PerformanceMonitorCompactFPSAverage,\n    tstr_PerformanceMonitorCompactReprojectionRatio,\n    tstr_PerformanceMonitorCompactDroppedFrames,\n    tstr_PerformanceMonitorCompactBattery,\n    tstr_PerformanceMonitorCompactBatteryLeft,\n    tstr_PerformanceMonitorCompactBatteryRight,\n    tstr_PerformanceMonitorCompactBatteryHMD,\n    tstr_PerformanceMonitorCompactBatteryTracker,\n    tstr_PerformanceMonitorCompactBatteryDisconnected,\n    tstr_PerformanceMonitorCompactViveWirelessTempNotAvailable,\n    tstr_PerformanceMonitorEmpty,\n    tstr_AuxUIDragHintDocking,\n    tstr_AuxUIDragHintUndocking,\n    tstr_AuxUIDragHintOvrlLocked,\n    tstr_AuxUIDragHintOvrlTheaterScreenBlocked,\n    tstr_AuxUIGazeFadeAutoHint,           //%SECONDS% == Countdown seconds\n    tstr_AuxUIGazeFadeAutoHintSingular,   //^\n    tstr_AuxUIQuickStartWelcomeHeader,\n    tstr_AuxUIQuickStartWelcomeBody,\n    tstr_AuxUIQuickStartOverlaysHeader,\n    tstr_AuxUIQuickStartOverlaysBody,\n    tstr_AuxUIQuickStartOverlaysBody2,\n    tstr_AuxUIQuickStartOverlayPropertiesHeader,\n    tstr_AuxUIQuickStartOverlayPropertiesBody,\n    tstr_AuxUIQuickStartOverlayPropertiesBody2,\n    tstr_AuxUIQuickStartSettingsHeader,\n    tstr_AuxUIQuickStartSettingsBody,\n    tstr_AuxUIQuickStartProfilesHeader,\n    tstr_AuxUIQuickStartProfilesBody,\n    tstr_AuxUIQuickStartActionsHeader,\n    tstr_AuxUIQuickStartActionsBody,\n    tstr_AuxUIQuickStartActionsBody2,\n    tstr_AuxUIQuickStartOverlayTagsHeader,\n    tstr_AuxUIQuickStartOverlayTagsBody,\n    tstr_AuxUIQuickStartSettingsEndBody,\n    tstr_AuxUIQuickStartFloatingUIHeader,\n    tstr_AuxUIQuickStartFloatingUIBody,\n    tstr_AuxUIQuickStartDesktopModeHeader,\n    tstr_AuxUIQuickStartDesktopModeBody,\n    tstr_AuxUIQuickStartEndHeader,\n    tstr_AuxUIQuickStartEndBody,\n    tstr_AuxUIQuickStartButtonNext,\n    tstr_AuxUIQuickStartButtonPrev,\n    tstr_AuxUIQuickStartButtonClose,\n    tstr_DesktopModeCatTools,\n    tstr_DesktopModeCatOverlays,\n    tstr_DesktopModeToolSettings,\n    tstr_DesktopModeToolActions,\n    tstr_DesktopModeOverlayListAdd,\n    tstr_DesktopModePageAddWindowOverlayTitle,\n    tstr_DesktopModePageAddWindowOverlayHeader,\n    tstr_KeyboardEditorKeyListTitle,\n    tstr_KeyboardEditorKeyListTabContextReplace,\n    tstr_KeyboardEditorKeyListTabContextClear,\n    tstr_KeyboardEditorKeyListRow,       //%ID% == Row ID\n    tstr_KeyboardEditorKeyListSpacing,\n    tstr_KeyboardEditorKeyListKeyAdd,\n    tstr_KeyboardEditorKeyListKeyDuplicate,\n    tstr_KeyboardEditorKeyListKeyRemove,\n    tstr_KeyboardEditorKeyPropertiesTitle,\n    tstr_KeyboardEditorKeyPropertiesNoSelection,\n    tstr_KeyboardEditorKeyPropertiesType,\n    tstr_KeyboardEditorKeyPropertiesTypeBlank,\n    tstr_KeyboardEditorKeyPropertiesTypeVirtualKey,\n    tstr_KeyboardEditorKeyPropertiesTypeVirtualKeyToggle,\n    tstr_KeyboardEditorKeyPropertiesTypeVirtualKeyIsoEnter,\n    tstr_KeyboardEditorKeyPropertiesTypeString,\n    tstr_KeyboardEditorKeyPropertiesTypeSublayoutToggle,\n    tstr_KeyboardEditorKeyPropertiesTypeAction,\n    tstr_KeyboardEditorKeyPropertiesTypeVirtualKeyIsoEnterTip,\n    tstr_KeyboardEditorKeyPropertiesTypeStringTip,\n    tstr_KeyboardEditorKeyPropertiesSize,\n    tstr_KeyboardEditorKeyPropertiesLabel,\n    tstr_KeyboardEditorKeyPropertiesKeyCode,\n    tstr_KeyboardEditorKeyPropertiesString,\n    tstr_KeyboardEditorKeyPropertiesSublayout,\n    tstr_KeyboardEditorKeyPropertiesAction,\n    tstr_KeyboardEditorKeyPropertiesCluster,\n    tstr_KeyboardEditorKeyPropertiesClusterTip,\n    tstr_KeyboardEditorKeyPropertiesBlockModifiers,\n    tstr_KeyboardEditorKeyPropertiesBlockModifiersTip,\n    tstr_KeyboardEditorKeyPropertiesNoRepeat,\n    tstr_KeyboardEditorKeyPropertiesNoRepeatTip,\n    tstr_KeyboardEditorMetadataTitle,\n    tstr_KeyboardEditorMetadataName,\n    tstr_KeyboardEditorMetadataAuthor,\n    tstr_KeyboardEditorMetadataHasAltGr,\n    tstr_KeyboardEditorMetadataHasAltGrTip,\n    tstr_KeyboardEditorMetadataClusterPreview,\n    tstr_KeyboardEditorMetadataSave,\n    tstr_KeyboardEditorMetadataLoad,\n    tstr_KeyboardEditorMetadataSavePopupTitle,\n    tstr_KeyboardEditorMetadataSavePopupFilename,\n    tstr_KeyboardEditorMetadataSavePopupFilenameBlankTip,\n    tstr_KeyboardEditorMetadataSavePopupConfirm,\n    tstr_KeyboardEditorMetadataSavePopupConfirmError,\n    tstr_KeyboardEditorMetadataLoadPopupTitle,\n    tstr_KeyboardEditorMetadataLoadPopupConfirm,\n    tstr_KeyboardEditorPreviewTitle,\n    tstr_KeyboardEditorSublayoutBase,\n    tstr_KeyboardEditorSublayoutShift,\n    tstr_KeyboardEditorSublayoutAltGr,\n    tstr_KeyboardEditorSublayoutAux,\n    tstr_DialogOk,\n    tstr_DialogCancel,\n    tstr_DialogDone,\n    tstr_DialogUndo,\n    tstr_DialogRedo,\n    tstr_DialogColorPickerHeader,\n    tstr_DialogColorPickerCurrent,\n    tstr_DialogColorPickerOriginal,\n    tstr_DialogProfilePickerHeader,\n    tstr_DialogProfilePickerNone,\n    tstr_DialogActionPickerHeader,\n    tstr_DialogActionPickerEmpty,\n    tstr_DialogIconPickerHeader,\n    tstr_DialogIconPickerHeaderTip,\n    tstr_DialogIconPickerNone,\n    tstr_DialogKeyCodePickerHeader,\n    tstr_DialogKeyCodePickerHeaderHotkey,\n    tstr_DialogKeyCodePickerModifiers,\n    tstr_DialogKeyCodePickerKeyCode,\n    tstr_DialogKeyCodePickerKeyCodeHint,\n    tstr_DialogKeyCodePickerKeyCodeNone,\n    tstr_DialogKeyCodePickerFromInput,\n    tstr_DialogKeyCodePickerFromInputPopup,\n    tstr_DialogKeyCodePickerFromInputPopupNoMouse,\n    tstr_DialogWindowPickerHeader,\n    tstr_DialogInputTagsHint,\n    tstr_SourceDesktopAll,\n    tstr_SourceDesktopID,   //%ID% == Desktop ID\n    tstr_SourceWinRTNone,\n    tstr_SourceWinRTUnknown,\n    tstr_SourceWinRTClosed,\n    tstr_SourcePerformanceMonitor,\n    tstr_SourceBrowser,\n    tstr_SourceBrowserNoPage,\n    tstr_NotificationIconRestoreVR,\n    tstr_NotificationIconOpenOnDesktop,\n    tstr_NotificationIconQuit,\n    tstr_NotificationInitialStartupTitleVR,\n    tstr_NotificationInitialStartupTitleDesktop,\n    tstr_NotificationInitialStartupMessage,\n    tstr_BrowserErrorPageTitle,\n    tstr_BrowserErrorPageHeading,\n    tstr_BrowserErrorPageMessage,\n    tstr_MAX,\n    tstr_NONE = tstr_MAX //Don't pass this into GetString()\n};\n\nclass TranslationManager\n{\n    public:\n        struct ListEntry\n        {\n            std::string FileName;\n            std::string ListName;\n        };\n\n    private:\n        static const char* s_StringIDNames[tstr_MAX];\n        std::string m_Strings[tstr_MAX];\n\n        //Precomputed strings, updated after loading different translations\n        std::vector<std::string> m_StringsDesktopID;\n        std::vector<std::string> m_StringsFPSLimit;\n\n        std::string m_CurrentTranslationName;\n        std::string m_CurrentTranslationAuthor;\n        std::string m_CurrentTranslationFontName;\n        bool m_IsCurrentTranslationComplete;\n\n    public:\n        TranslationManager();\n        static TranslationManager& Get();\n        static const char* GetString(TRMGRStrID str_id);      //Strings are not sanitized in any way, so probably a bad idea to feed straight into ImGui functions calling printf\n        static TRMGRStrID GetStringID(const char* str);       //May return tstr_NONE\n        static std::string GetTranslationNameFromFile(const std::string& filename);\n        static std::vector<TranslationManager::ListEntry> GetTranslationList();\n\n        void LoadTranslationFromFile(const std::string& filename);\n\n        const std::string& GetCurrentTranslationName()     const;\n        const std::string& GetCurrentTranslationAuthor()   const;\n        const std::string& GetCurrentTranslationFontName() const;\n        bool IsCurrentTranslationComplete() const;\n        void AddStringsToFontBuilder(ImFontGlyphRangesBuilder& builder) const;\n\n        const char* GetDesktopIDString(int desktop_id);\n        const char* GetFPSLimitString(int fps_limit_id);\n};"
  },
  {
    "path": "src/DesktopPlusUI/UIManager.cpp",
    "content": "#include \"UIManager.h\"\n\n#include <windowsx.h>\n\n#include \"imgui.h\"\n#include \"imgui_impl_win32_openvr.h\"\n#include \"implot.h\"\n\n#include \"resource.h\"\n#include \"InterprocessMessaging.h\"\n#include \"ConfigManager.h\"\n#include \"OverlayManager.h\"\n#include \"Util.h\"\n#include \"OpenVRExt.h\"\n#include \"WindowManager.h\"\n\n#include \"DesktopPlusWinRT.h\"\n#include \"DPBrowserAPIClient.h\"\n\n//This one holds mostly constant data, but depends on how the application was launched\nstatic UITextureSpaces g_UITextureSpaces;\n\nUITextureSpaces& UITextureSpaces::Get()\n{\n    return g_UITextureSpaces;\n}\n\nvoid UITextureSpaces::Init(bool desktop_mode, bool keyboard_editor_mode)\n{\n    if (!desktop_mode)\n    {\n        const int vertical_spacing = 2;\n\n        m_TexspaceRects[ui_texspace_total]               = {0, 0, 1920, -1};\n        m_TexspaceRects[ui_texspace_overlay_bar]         = {0, 0, m_TexspaceRects[ui_texspace_total].GetWidth(), 420};\n\n        m_TexspaceRects[ui_texspace_floating_ui] =         {0, \n                                                            m_TexspaceRects[ui_texspace_overlay_bar].GetBR().y + vertical_spacing,\n                                                            m_TexspaceRects[ui_texspace_total].GetWidth(),\n                                                            m_TexspaceRects[ui_texspace_overlay_bar].GetBR().y + vertical_spacing + 320};\n\n        m_TexspaceRects[ui_texspace_overlay_properties] =  {0,\n                                                            m_TexspaceRects[ui_texspace_floating_ui].GetBR().y + vertical_spacing,\n                                                            959,\n                                                            m_TexspaceRects[ui_texspace_floating_ui].GetBR().y + vertical_spacing + 800};\n\n        m_TexspaceRects[ui_texspace_settings] =            {m_TexspaceRects[ui_texspace_total].GetWidth() - m_TexspaceRects[ui_texspace_overlay_properties].GetWidth(),\n                                                            m_TexspaceRects[ui_texspace_overlay_properties].GetTL().y,\n                                                            m_TexspaceRects[ui_texspace_total].GetWidth(),\n                                                            m_TexspaceRects[ui_texspace_overlay_properties].GetBR().y};\n\n        m_TexspaceRects[ui_texspace_keyboard] =            {0,\n                                                            m_TexspaceRects[ui_texspace_overlay_properties].GetBR().y + vertical_spacing,\n                                                            m_TexspaceRects[ui_texspace_total].GetWidth(), \n                                                            m_TexspaceRects[ui_texspace_overlay_properties].GetBR().y + vertical_spacing + 750};\n\n        m_TexspaceRects[ui_texspace_performance_monitor] = {0, \n                                                            m_TexspaceRects[ui_texspace_keyboard].GetBR().y + vertical_spacing,\n                                                            850, \n                                                            m_TexspaceRects[ui_texspace_keyboard].GetBR().y + vertical_spacing + 550};\n\n        m_TexspaceRects[ui_texspace_aux_ui] =              {m_TexspaceRects[ui_texspace_performance_monitor].GetWidth() + vertical_spacing, \n                                                            m_TexspaceRects[ui_texspace_keyboard].GetBR().y + vertical_spacing,\n                                                            m_TexspaceRects[ui_texspace_total].GetWidth(),\n                                                            m_TexspaceRects[ui_texspace_keyboard].GetBR().y + vertical_spacing + 550};\n\n        //Set total height last\n        m_TexspaceRects[ui_texspace_total].Max.y = m_TexspaceRects[ui_texspace_performance_monitor].GetBR().y;\n    }\n    else\n    {\n        //Desktop mode only initializes total texture space (as the unscaled window size) and windows used in it\n        if (keyboard_editor_mode)\n        {\n            m_TexspaceRects[ui_texspace_total] = {0, 0, 2100, 1152};    //Results in 1312x720px at 100% DPI scaling\n        }\n        else\n        {\n            m_TexspaceRects[ui_texspace_total] = {0, 0, 1153, 1042};    //Results in 720x651px at 100% DPI scaling\n        }\n\n        m_TexspaceRects[ui_texspace_overlay_properties]  = m_TexspaceRects[ui_texspace_total];\n        m_TexspaceRects[ui_texspace_settings]            = m_TexspaceRects[ui_texspace_total];\n        m_TexspaceRects[ui_texspace_performance_monitor] = m_TexspaceRects[ui_texspace_total];\n        m_TexspaceRects[ui_texspace_keyboard]            = {0, 0, m_TexspaceRects[ui_texspace_total].GetWidth(), 750};\n    }\n}\n\nconst DPRect& UITextureSpaces::GetRect(UITexspaceID texspace_id) const\n{\n    return m_TexspaceRects[texspace_id];\n}\n\nImVec4 UITextureSpaces::GetRectAsVec4(UITexspaceID texspace_id) const\n{\n    const DPRect& rect = UITextureSpaces::Get().GetRect(texspace_id);\n    return ImVec4((float)rect.Min.x, (float)rect.Min.y, (float)rect.Max.x, (float)rect.Max.y);\n}\n\n//While this is a singleton like many other classes, we want to be careful about initializing it at global scope, so we leave that until a bit later in main()\nUIManager* g_UIManagerPtr = nullptr;\n\nUIManager::IdleState::IdleState()\n{\n    ::QueryPerformanceFrequency(&m_TimestepTicks);\n    ::QueryPerformanceCounter(&m_TimestepTime);\n}\n\nvoid UIManager::IdleState::SetLastActiveTick(ULONGLONG value)\n{\n    //Some functions set this into the future, so prevent it being overwritten in those cases\n    if (value > m_LastActiveTick)\n    {\n        m_LastActiveTick = value;\n    }\n}\n\nbool UIManager::IdleState::ShouldIdle()\n{\n    if (::IsIconic(UIManager::Get()->GetWindowHandle()))\n    {\n        m_WasIdleLastFrame = true;\n        return true;\n    }\n\n    UIManager& ui_manager = *UIManager::Get();\n\n    //Temporary setting to disable throttling in case it causes issues somewhere, matches old basic idle logic\n    if (!ConfigManager::GetValue(configid_bool_performance_ui_auto_throttle))\n    {\n        SetLastActiveTick(::GetTickCount64());\n\n        if (!ui_manager.IsInDesktopMode())\n        {\n            const bool do_idle = ( (!ui_manager.IsOverlayBarOverlayVisible())                     &&\n                                   (!ui_manager.GetFloatingUI().IsVisible())                      &&\n                                   (!ui_manager.GetSettingsWindow().IsVisibleOrFading())          &&\n                                   (!ui_manager.GetOverlayPropertiesWindow().IsVisibleOrFading()) &&\n                                   (!ui_manager.GetPerformanceWindow().IsVisible())               &&\n                                   (!ui_manager.GetVRKeyboard().GetWindow().IsVisibleOrFading())  &&\n                                   (!ui_manager.GetAuxUI().IsActive()) );\n\n            return do_idle;\n        }\n\n        return false;\n    }\n\n    //Performance Monitor needs constant updates, but it'll be throttled in GetFrameSkipValue() if possible\n    if (ui_manager.GetPerformanceWindow().IsVisible())\n    {\n        return false;\n    }\n\n    //Quick-Start Guide needs updates for its automatic timeout to work\n    if (ui_manager.GetAuxUI().GetQuickStartWindow().IsVisible())\n    {\n        return false;\n    }\n\n    //If any text input is active, we need to deal with the blinking caret so we wake up occasionally for it to animate\n    if (ImGui::IsAnyInputTextActive())\n    {\n        if (m_LastCaretTick + 100 < ::GetTickCount64())\n        {\n            m_LastCaretTick = ::GetTickCount64();\n            m_WasIdleLastFrame = false; //Explicitly avoid doing the idle timestep when only waking up for a single frame as the caret blinking relies on it advancing in real time\n            return false;\n        }\n    }\n\n    if (ImGui::IsAnyMouseDown())\n    {\n        return false;\n    }\n\n    if (m_LastActiveTick + s_MsBeforeIdle < ::GetTickCount64())\n    {\n        m_WasIdleLastFrame = true;\n        return true;\n    }\n\n    return false;\n}\n\nint UIManager::IdleState::GetFrameSkipValue()\n{\n    //If outside of active time, throttle to 1 frameskip or just use user frameskip value if it's higher\n    //This mainly causes Performance Monitor to animated at half rate. Other animating things are supposed to add active time\n    return std::max((m_LastActiveTick + s_MsBeforeIdle < ::GetTickCount64()) ? 1 : 0, ConfigManager::GetValue(configid_int_performance_ui_frameskip));\n}\n\nvoid UIManager::IdleState::OnImGuiNewFrame()\n{\n    //After idling we'll have a large delta time set by ImGui, so we run our own timestep once more to correct it for that one frame\n    if (m_WasIdleLastFrame)\n    {\n        DoIdleTimestep();\n    }\n\n    m_WasIdleLastFrame = false;\n}\n\nvoid UIManager::IdleState::OnWindowMessage(UINT message_id)\n{\n    if ( ((message_id >= WM_MOUSEFIRST) && (message_id <= WM_MOUSELAST)) || ((message_id >= WM_KEYFIRST) && (message_id <= WM_KEYLAST)) || (message_id == WM_SYSCOMMAND) )\n    {\n        //We may get periodic mouse move messages with no cursor moving, ignore those\n        if (message_id == WM_MOUSEMOVE)\n        {\n            POINT pt;\n            GetCursorPos(&pt);\n\n            if ((pt.x == m_LastCursorPos.x) && (pt.y == m_LastCursorPos.y))\n            {\n                return;\n            }\n\n            m_LastCursorPos = pt;\n        }\n\n        SetLastActiveTick(::GetTickCount64());\n    }\n}\n\nvoid UIManager::IdleState::OnOpenVREvent(uint32_t event_type)\n{\n    if ( ((event_type >= vr::VREvent_MouseMove)    && (event_type <= vr::VREvent_UnlockMousePosition)) || ((event_type >= vr::VREvent_OverlayShown) && (event_type <= vr::VREvent_SwitchGamepadFocus)) )\n    {\n        SetLastActiveTick(::GetTickCount64());\n    }\n}\n\nvoid UIManager::IdleState::AddActiveTime(unsigned int ms)\n{\n    SetLastActiveTick(::GetTickCount64() + ms - s_MsBeforeIdle);\n}\n\nvoid UIManager::IdleState::DoIdleTimestep()\n{\n    LARGE_INTEGER current_time{0};\n    ::QueryPerformanceCounter(&current_time);\n    ImGui::GetIO().DeltaTime = (float)(current_time.QuadPart - m_TimestepTime.QuadPart) / m_TimestepTicks.QuadPart;\n    m_TimestepTime = current_time;\n}\n\nUIManager::UIManager(bool desktop_mode, bool keyboard_editor_mode) : \n    m_WindowHandle(nullptr),\n    m_SharedTextureRef(nullptr),\n    m_RepeatFrame(false),\n    m_DesktopMode(desktop_mode),\n    m_KeyboardEditorMode(keyboard_editor_mode),\n    m_OpenVRLoaded(false),\n    m_NoRestartOnExit(false),\n    m_UIScale(1.0f),\n    m_FontCompact(nullptr),\n    m_FontLarge(nullptr),\n    m_LowCompositorRes(false),\n    m_LowCompositorQuality(false),\n    m_HasAnyWarning(false),\n    m_OverlayErrorLast(vr::VROverlayError_None),\n    m_WinRTErrorLast(S_OK),\n    m_ElevatedTaskSetUp(false),\n    m_ComInitDone(false),\n    m_OvrlHandleOverlayBar(vr::k_ulOverlayHandleInvalid),\n    m_OvrlHandleFloatingUI(vr::k_ulOverlayHandleInvalid),\n    m_OvrlHandleSettings(vr::k_ulOverlayHandleInvalid),\n    m_OvrlHandleOverlayProperties(vr::k_ulOverlayHandleInvalid),\n    m_OvrlHandleKeyboard(vr::k_ulOverlayHandleInvalid),\n    m_OvrlHandleAuxUI(vr::k_ulOverlayHandleInvalid),\n    m_OvrlHandleDPlusDashboard(vr::k_ulOverlayHandleInvalid),\n    m_OvrlHandleSystemUI(vr::k_ulOverlayHandleInvalid),\n    m_IsSystemUIHoveredFromSwitch(false),\n    m_IsDummyOverlayTransformUnstable(false),\n    m_OvrlVisible(false),\n    m_OvrlOverlayBarAlpha(0.0f),\n    m_SystemUIActiveTick(0),\n    m_OverlayBarFadeInTick(0),\n    m_OvrlPixelWidth(1),\n    m_OvrlPixelHeight(1),\n    m_TransformSyncValueCount(0),\n    m_TransformSyncValues{0}\n{\n    g_UIManagerPtr = this;\n\n    //Activate WindowManager\n    WindowManager::Get().SetActive(true);\n\n    //Check if the scheduled task is set up\n    STARTUPINFO si = {0};\n    PROCESS_INFORMATION pi = {0};\n    si.cb = sizeof(si);\n\n    WCHAR cmd[] = L\"\\\"schtasks\\\" /Query /TN \\\"DesktopPlus Elevated\\\"\";\n\n    if (::CreateProcess(nullptr, cmd, nullptr, nullptr, FALSE, CREATE_NO_WINDOW, nullptr, nullptr, &si, &pi))\n    {\n        //Wait for it to exit, which should be pretty much instant\n        ::WaitForSingleObject(pi.hProcess, INFINITE);\n\n        //Get the exit code. It should be 0 on success\n        DWORD exit_code;\n        ::GetExitCodeProcess(pi.hProcess, &exit_code);\n\n        m_ElevatedTaskSetUp = (exit_code == 0);\n    }\n    \n    ::CloseHandle(pi.hProcess);\n    ::CloseHandle(pi.hThread);\n}\n\nUIManager::~UIManager()\n{\n    g_UIManagerPtr = nullptr;\n}\n\nvoid UIManager::DisplayDashboardAppError(const std::string& str) //Ideally this is never called\n{\n    //Hide UI overlay\n    vr::VROverlay()->HideOverlay(m_OvrlHandleOverlayBar);\n    m_OvrlVisible = false;\n\n    //Hide all dashboard app overlays as well. Usually the dashboard app closes, but it may sometimes get stuck which could put its overlays in the way of the message overlay.\n    for (unsigned int i = 0; i < OverlayManager::Get().GetOverlayCount(); ++i)\n    {\n        vr::VROverlayHandle_t ovrl_handle = OverlayManager::Get().GetConfigData(i).ConfigHandle[configid_handle_overlay_state_overlay_handle];;\n\n        if (ovrl_handle != vr::k_ulOverlayHandleInvalid)\n        {\n            vr::VROverlay()->HideOverlay(ovrl_handle);\n        }\n    }\n\n    vr::VRMessageOverlayResponse res = vr::VROverlay()->ShowMessageOverlay(str.c_str(), \"Desktop+ Error\", \"Ok\", \"Restart Desktop+\");\n\n    if (res == vr::VRMessageOverlayResponse_ButtonPress_1)\n    {\n        RestartDashboardApp();\n    }\n\n    //Dashboard will be closed after dismissing the message overlay, so open it back up with Desktop+\n    vr::VROverlay()->ShowDashboard(\"elvissteinjr.DesktopPlusDashboard\");\n}\n\nvoid UIManager::DisplayInitialSetupNotification()\n{\n    //Check if the user is currently using the HMD and display the initial setup message as a VR notification instead then\n    bool use_vr_notification = false;\n    vr::EDeviceActivityLevel activity_level = vr::VRSystem()->GetTrackedDeviceActivityLevel(vr::k_unTrackedDeviceIndex_Hmd);\n\n    if ((activity_level == vr::k_EDeviceActivityLevel_UserInteraction) || (activity_level == vr::k_EDeviceActivityLevel_UserInteraction_Timeout))\n    {\n        //Also check if the HMD is tracking properly right now so the notification can actually be seen (fresh SteamVR start is active but not tracking for example)\n        vr::TrackedDevicePose_t poses[vr::k_unTrackedDeviceIndex_Hmd + 1];\n        vr::VRSystem()->GetDeviceToAbsoluteTrackingPose(vr::TrackingUniverseStanding, vr::IVRSystemEx::GetTimeNowToPhotons(), poses, vr::k_unTrackedDeviceIndex_Hmd + 1);\n\n        use_vr_notification = (poses[vr::k_unTrackedDeviceIndex_Hmd].eTrackingResult == vr::TrackingResult_Running_OK);\n    }\n\n    if (use_vr_notification)\n    {\n        vr::VRNotificationId notification_id = 0; //Unused, but documentation doesn't say if passing nullptr is allowed, so we pass this\n        std::string message = TranslationManager::GetString(tstr_NotificationInitialStartupTitleVR);\n        message += \"\\n\";\n        message += TranslationManager::GetString(tstr_NotificationInitialStartupMessage);\n\n        //Despite being sent after overlay creation, it may not be returned right away, so keep trying a bit\n        for (int tries = 0; tries < 20; ++tries)\n        {\n            vr::VROverlay()->FindOverlay(\"elvissteinjr.DesktopPlusDashboard\", &m_OvrlHandleDPlusDashboard);\n\n            if (m_OvrlHandleDPlusDashboard != vr::k_ulOverlayHandleInvalid)\n            {\n                break;\n            }\n            ::Sleep(50);\n        }\n\n        vr::VRNotifications()->CreateNotification(m_OvrlHandleDPlusDashboard, 0, vr::EVRNotificationType_Transient, message.c_str(), vr::EVRNotificationStyle_Application, nullptr, &notification_id);\n    }\n    else\n    {\n        ::MessageBoxW(nullptr,\n                      WStringConvertFromUTF8(TranslationManager::GetString(tstr_NotificationInitialStartupMessage)).c_str(), \n                      WStringConvertFromUTF8(TranslationManager::GetString(tstr_NotificationInitialStartupTitleDesktop)).c_str(), MB_OK);\n    }\n}\n\nvoid UIManager::SetOverlayInputEnabled(bool is_enabled)\n{\n    vr::VROverlayInputMethod input_method = (is_enabled) ? vr::VROverlayInputMethod_Mouse : vr::VROverlayInputMethod_None;\n\n    vr::VROverlay()->SetOverlayInputMethod(m_OvrlHandleOverlayBar,        input_method);\n    vr::VROverlay()->SetOverlayInputMethod(m_OvrlHandleFloatingUI,        input_method);\n    vr::VROverlay()->SetOverlayInputMethod(m_OvrlHandleSettings,          input_method);\n    vr::VROverlay()->SetOverlayInputMethod(m_OvrlHandleOverlayProperties, input_method);\n    vr::VROverlay()->SetOverlayInputMethod(m_OvrlHandleKeyboard,          input_method);\n    vr::VROverlay()->SetOverlayInputMethod(m_OvrlHandleAuxUI,             input_method);\n}\n\nUITexspaceID UIManager::GetTexspaceIDForOverlayHandle(vr::VROverlayHandle_t overlay_handle) const\n{\n    UITexspaceID overlay_texspace = ui_texspace_total;\n    if (overlay_handle == m_OvrlHandleOverlayBar)\n        overlay_texspace = ui_texspace_overlay_bar;\n    else if (overlay_handle == m_OvrlHandleFloatingUI)\n        overlay_texspace = ui_texspace_floating_ui;\n    else if (overlay_handle == m_OvrlHandleSettings)\n        overlay_texspace = ui_texspace_settings;\n    else if (overlay_handle == m_OvrlHandleOverlayProperties)\n        overlay_texspace = ui_texspace_overlay_properties;\n    else if (overlay_handle == m_OvrlHandleKeyboard)\n        overlay_texspace = ui_texspace_keyboard;\n    else if (overlay_handle == m_OvrlHandleAuxUI)\n        overlay_texspace = ui_texspace_aux_ui;\n    \n    return overlay_texspace;\n}\n\nvoid UIManager::HandleOverlayProfileLoadMessage(LPARAM lparam)\n{\n    IPCActionOverlayProfileLoadArg profile_load_arg = (IPCActionOverlayProfileLoadArg)LOWORD(lparam);\n    int profile_overlay_id = GET_Y_LPARAM(lparam);\n\n    const std::string& profile_name = ConfigManager::GetValue(configid_str_state_profile_name_load);\n\n    if (profile_overlay_id == -2)\n    {\n        ConfigManager::Get().LoadOverlayProfileDefault(true);\n    }\n    else if (profile_load_arg == ipcactv_ovrl_profile_multi)\n    {\n        ConfigManager::Get().LoadMultiOverlayProfileFromFile(profile_name + \".ini\", true);\n    }\n    else if (profile_load_arg == ipcactv_ovrl_profile_multi_add)\n    {\n        IM_ASSERT(profile_overlay_id != -1);   //Exclusion is unhandled for now. Shouldn't be sent anyways\n\n        ConfigManager::Get().LoadMultiOverlayProfileFromFile(profile_name + \".ini\", false);\n    }\n\n    OnProfileLoaded();\n}\n\nUIManager* UIManager::Get()\n{\n    return g_UIManagerPtr;\n}\n\nvr::EVRInitError UIManager::InitOverlay()\n{\n    vr::EVRInitError init_error;\n    vr::IVRSystem* vr_ptr = vr::VR_Init(&init_error, vr::VRApplication_Overlay);\n\n    if (init_error != vr::VRInitError_None)\n        return init_error;\n\n    if (!vr::VROverlay())\n        return vr::VRInitError_Init_InvalidInterface;\n\n    vr::VRApplications()->IdentifyApplication(::GetCurrentProcessId(), g_AppKeyUIApp);\n\n    vr::VROverlayError ovrl_error = vr::VROverlayError_None;\n\n    if (!m_DesktopMode) //For desktop mode we only init OpenVR, but don't set up any overlays\n    {\n        //This loop gets rid of any other process hogging our overlay key. Though in normal situations another Desktop+UI process would've already be killed before this\n        for (int tries = 0; tries < 10; ++tries)\n        {\n            ovrl_error = vr::VROverlay()->CreateOverlay(\"elvissteinjr.DesktopPlusUI\", \"Desktop+UI\", &m_OvrlHandleOverlayBar);\n\n            if (ovrl_error == vr::VROverlayError_KeyInUse)  //If the key is already in use, kill the owning process (hopefully another instance of this app)\n            {\n                ovrl_error = vr::VROverlay()->FindOverlay(\"elvissteinjr.DesktopPlusUI\", &m_OvrlHandleOverlayBar);\n\n                if ((ovrl_error == vr::VROverlayError_None) && (m_OvrlHandleOverlayBar != vr::k_ulOverlayHandleInvalid))\n                {\n                    LOG_F(INFO, \"Overlay key already in use, killing owning process...\");\n\n                    uint32_t pid = vr::VROverlay()->GetOverlayRenderingPid(m_OvrlHandleOverlayBar);\n\n                    HANDLE phandle;\n                    phandle = ::OpenProcess(SYNCHRONIZE | PROCESS_TERMINATE, TRUE, pid);\n\n                    if (phandle != nullptr)\n                    {\n                        ::TerminateProcess(phandle, 0);\n                        ::CloseHandle(phandle);\n                    }\n                    else\n                    {\n                        ovrl_error = vr::VROverlayError_KeyInUse;\n                    }\n                }\n                else\n                {\n                    ovrl_error = vr::VROverlayError_KeyInUse;\n                }\n            }\n            else\n            {\n                break;\n            }\n\n            //Try again in a bit to check if it's just a race with some external cleanup\n            ::Sleep(200);\n        }\n\n        if (m_OvrlHandleOverlayBar != vr::k_ulOverlayHandleInvalid)\n        {\n            vr::VROverlay()->SetOverlayWidthInMeters(m_OvrlHandleOverlayBar, OVERLAY_WIDTH_METERS_DASHBOARD_UI);\n\n            //Init additional overlays\n            vr::VROverlay()->CreateOverlay(\"elvissteinjr.DesktopPlusUIFloating\",          \"Desktop+ Floating UI\", &m_OvrlHandleFloatingUI);\n            vr::VROverlay()->CreateOverlay(\"elvissteinjr.DesktopPlusUISettings\",          \"Desktop+ Settings UI\", &m_OvrlHandleSettings);\n            vr::VROverlay()->CreateOverlay(\"elvissteinjr.DesktopPlusUIOverlayProperties\", \"Desktop+ Settings UI\", &m_OvrlHandleOverlayProperties);\n            vr::VROverlay()->CreateOverlay(\"elvissteinjr.DesktopPlusUIKeyboard\",          \"Desktop+ Keyboard\",    &m_OvrlHandleKeyboard);\n            vr::VROverlay()->CreateOverlay(\"elvissteinjr.DesktopPlusUIAux\",               \"Desktop+ Aux UI\",      &m_OvrlHandleAuxUI);\n\n            vr::VROverlay()->SetOverlayWidthInMeters(m_OvrlHandleFloatingUI,        OVERLAY_WIDTH_METERS_DASHBOARD_UI);\n            vr::VROverlay()->SetOverlayWidthInMeters(m_OvrlHandleSettings,          OVERLAY_WIDTH_METERS_SETTINGS);\n            vr::VROverlay()->SetOverlayWidthInMeters(m_OvrlHandleOverlayProperties, OVERLAY_WIDTH_METERS_SETTINGS);\n            vr::VROverlay()->SetOverlayWidthInMeters(m_OvrlHandleKeyboard,          OVERLAY_WIDTH_METERS_KEYBOARD);\n\n            vr::VROverlay()->SetOverlayAlpha(m_OvrlHandleFloatingUI, 0.0f);\n\n            //Set input parameters\n            vr::VROverlay()->SetOverlayFlag(m_OvrlHandleOverlayBar,        vr::VROverlayFlags_SendVRSmoothScrollEvents, true);\n            vr::VROverlay()->SetOverlayFlag(m_OvrlHandleSettings,          vr::VROverlayFlags_SendVRSmoothScrollEvents, true);\n            vr::VROverlay()->SetOverlayFlag(m_OvrlHandleOverlayProperties, vr::VROverlayFlags_SendVRSmoothScrollEvents, true);\n            vr::VROverlay()->SetOverlayFlag(m_OvrlHandleKeyboard,          vr::VROverlayFlags_SendVRSmoothScrollEvents, true);\n            vr::VROverlay()->SetOverlayFlag(m_OvrlHandleKeyboard,          vr::VROverlayFlags_MultiCursor,              true);\n            vr::VROverlay()->SetOverlayFlag(m_OvrlHandleAuxUI,             vr::VROverlayFlags_SendVRSmoothScrollEvents, true);\n\n            vr::HmdVector2_t mouse_scale;\n            mouse_scale.v[0] = (float)UITextureSpaces::Get().GetRect(ui_texspace_total).GetWidth();\n            mouse_scale.v[1] = (float)UITextureSpaces::Get().GetRect(ui_texspace_total).GetHeight();\n\n            vr::VROverlay()->SetOverlayMouseScale(m_OvrlHandleOverlayBar,         &mouse_scale);\n            vr::VROverlay()->SetOverlayMouseScale(m_OvrlHandleFloatingUI,         &mouse_scale);\n            vr::VROverlay()->SetOverlayMouseScale(m_OvrlHandleSettings,           &mouse_scale);\n            vr::VROverlay()->SetOverlayMouseScale(m_OvrlHandleOverlayProperties,  &mouse_scale);\n            vr::VROverlay()->SetOverlayMouseScale(m_OvrlHandleKeyboard,           &mouse_scale);\n            vr::VROverlay()->SetOverlayMouseScale(m_OvrlHandleAuxUI,              &mouse_scale);\n\n            SetOverlayInputEnabled(true);\n\n            //Setup texture bounds for all overlays\n            //The UI windows are rendered on the same texture as a form of discount multi-viewport rendering\n            vr::VRTextureBounds_t bounds = {};\n\n            const DPRect& rect_total = UITextureSpaces::Get().GetRect(ui_texspace_total);\n            float tex_width  = (float)rect_total.GetWidth();\n            float tex_height = (float)rect_total.GetHeight();\n\n            const DPRect& rect_overlay_bar = UITextureSpaces::Get().GetRect(ui_texspace_overlay_bar);\n            bounds.uMin = rect_overlay_bar.GetTL().x / tex_width;\n            bounds.vMin = rect_overlay_bar.GetTL().y / tex_height;\n            bounds.uMax = rect_overlay_bar.GetBR().x / tex_width;\n            bounds.vMax = rect_overlay_bar.GetBR().y / tex_height;\n            vr::VROverlay()->SetOverlayTextureBounds(m_OvrlHandleOverlayBar, &bounds);\n\n            const DPRect& rect_floating_ui = UITextureSpaces::Get().GetRect(ui_texspace_floating_ui);\n            bounds.uMin = rect_floating_ui.GetTL().x / tex_width;\n            bounds.vMin = rect_floating_ui.GetTL().y / tex_height;\n            bounds.uMax = rect_floating_ui.GetBR().x / tex_width;\n            bounds.vMax = rect_floating_ui.GetBR().y / tex_height;\n            vr::VROverlay()->SetOverlayTextureBounds(m_OvrlHandleFloatingUI, &bounds);\n\n            const DPRect& rect_settings = UITextureSpaces::Get().GetRect(ui_texspace_settings);\n            bounds.uMin = rect_settings.GetTL().x / tex_width;\n            bounds.vMin = rect_settings.GetTL().y / tex_height;\n            bounds.uMax = rect_settings.GetBR().x / tex_width;\n            bounds.vMax = rect_settings.GetBR().y / tex_height;\n            vr::VROverlay()->SetOverlayTextureBounds(m_OvrlHandleSettings, &bounds);\n\n            const DPRect& rect_ovrlprops = UITextureSpaces::Get().GetRect(ui_texspace_overlay_properties);\n            bounds.uMin = rect_ovrlprops.GetTL().x / tex_width;\n            bounds.vMin = rect_ovrlprops.GetTL().y / tex_height;\n            bounds.uMax = rect_ovrlprops.GetBR().x / tex_width;\n            bounds.vMax = rect_ovrlprops.GetBR().y / tex_height;\n            vr::VROverlay()->SetOverlayTextureBounds(m_OvrlHandleOverlayProperties, &bounds);\n\n            const DPRect& rect_keyboard = UITextureSpaces::Get().GetRect(ui_texspace_keyboard);\n            bounds.uMin = rect_keyboard.GetTL().x / tex_width;\n            bounds.vMin = rect_keyboard.GetTL().y / tex_height;\n            bounds.uMax = rect_keyboard.GetBR().x / tex_width;\n            bounds.vMax = rect_keyboard.GetBR().y / tex_height;\n            vr::VROverlay()->SetOverlayTextureBounds(m_OvrlHandleKeyboard, &bounds);\n\n            //Set curve pitch for overlay bar. This adjusts the pitch to match the SteamVR dashboard\n            vr::VROverlay()->SetOverlayPreCurvePitch(m_OvrlHandleOverlayBar, 0.25f);\n        }\n    }\n\n    //Cache SystemUI handle as it won't change during the session anyways\n    //We do not cache the GamepadUI handle as it may disappear during the session when Steam closes and make SteamVR switch to the previous dashboard\n    vr::VROverlay()->FindOverlay(\"system.systemui\", &m_OvrlHandleSystemUI);\n\n    m_OpenVRLoaded = true;\n    m_LowCompositorRes = (vr::VRSettings()->GetFloat(\"GpuSpeed\", \"gpuSpeedRenderTargetScale\") < 1.0f);\n\n    UpdateDesktopOverlayPixelSize();\n    UpdateCompositorRenderQualityLow();\n\n    m_WindowSettings.ApplyCurrentOverlayState();\n    m_WindowOverlayProperties.ApplyCurrentOverlayState();\n    m_VRKeyboard.GetWindow().ApplyCurrentOverlayState();\n\n    m_WindowPerformance.ResetCumulativeValues();\n    m_WindowPerformance.RefreshTrackerBatteryList();\n\n    ConfigManager::Get().InitConfigForWMR();\n\n    if ((ovrl_error == vr::VROverlayError_None))\n        return vr::VRInitError_None;\n    else\n        return vr::VRInitError_Compositor_OverlayInitFailed;\n}\n\nvoid UIManager::HandleIPCMessage(const MSG& msg, bool handle_delayed)\n{\n    //Handle messages sent by browser process in the APIClient\n    if (msg.message == DPBrowserAPIClient::Get().GetRegisteredMessageID())\n    {\n        DPBrowserAPIClient::Get().HandleIPCMessage(msg);\n        return;\n    }\n\n    //Apply overlay id override if needed\n    unsigned int current_overlay_old = OverlayManager::Get().GetCurrentOverlayID();\n    int overlay_override_id = ConfigManager::GetValue(configid_int_state_overlay_current_id_override);\n\n    if (overlay_override_id != -1)\n    {\n        OverlayManager::Get().SetCurrentOverlayID(overlay_override_id);\n    }\n\n    //Config strings come as WM_COPYDATA\n    if (msg.message == WM_COPYDATA)\n    {\n        COPYDATASTRUCT* pcds = (COPYDATASTRUCT*)msg.lParam;\n\n        //Arbitrary size limit to prevent some malicous applications from sending bad data\n        if ( (pcds->dwData < configid_str_MAX) && (pcds->cbData <= 4096) ) \n        {\n            std::string copystr((char*)pcds->lpData, pcds->cbData); //We rely on the data length. The data is sent without the NUL byte\n\n            ConfigID_String str_id = (ConfigID_String)pcds->dwData;\n            ConfigManager::SetValue(str_id, copystr);\n\n            switch (str_id)\n            {\n                case configid_str_state_ui_keyboard_string:\n                {\n                    ImGui_ImplOpenVR_AddInputFromOSK(copystr.c_str());\n                    break;\n                }\n                case configid_str_state_dashboard_error_string:\n                {\n                    DisplayDashboardAppError(copystr);\n                    break;\n                }\n            }\n        }\n        else if ( (pcds->dwData >= dpbrowser_ipcstr_MIN) && (pcds->dwData < dpbrowser_ipcstr_MAX) ) //Probably a string for DPBrowserAPIClient\n        {\n            DPBrowserAPIClient::Get().HandleIPCMessage(msg);\n        }\n\n        //Restore overlay id override\n        if (overlay_override_id != -1)\n        {\n            OverlayManager::Get().SetCurrentOverlayID(current_overlay_old);\n        }\n\n        return;\n    }\n\n    IPCMsgID msgid = IPCManager::Get().GetIPCMessageID(msg.message);\n\n    switch (msgid)\n    {\n        case ipcmsg_action:\n        {\n            switch (msg.wParam)\n            {\n                case ipcact_overlays_reset:\n                {\n                    UpdateDesktopOverlayPixelSize();\n                    m_WindowPerformance.ScheduleOverlaySharedTextureUpdate();\n                    break;\n                }\n                case ipcact_overlays_ui_reset:\n                {\n                    m_WindowPerformance.ScheduleOverlaySharedTextureUpdate();\n                    break;\n                }\n                case ipcact_overlay_profile_load:\n                {\n                    HandleOverlayProfileLoadMessage(msg.lParam);\n                    break;\n                }\n                case ipcact_overlay_new_drag:\n                {\n                    int desktop_id = GET_X_LPARAM(msg.lParam); //(No need to extract pointer distance)\n\n                    OverlayCaptureSource capsource;\n\n                    switch (desktop_id)\n                    {\n                        case -2: capsource = ovrl_capsource_winrt_capture;       break;\n                        case -3: capsource = ovrl_capsource_ui;                  break;\n                        default: capsource = ovrl_capsource_desktop_duplication;\n                    }\n\n                    OverlayManager::Get().AddOverlay(capsource, desktop_id, (HWND)ConfigManager::GetValue(configid_handle_state_arg_hwnd));\n                    break;\n                }\n                case ipcact_overlay_remove:\n                {\n                    unsigned int overlay_id = (unsigned int)msg.lParam;\n\n                    OverlayManager::Get().RemoveOverlay(overlay_id);\n\n                    //Hide properties window if it's open for this overlay\n                    if (m_WindowOverlayProperties.GetActiveOverlayID() == overlay_id)\n                    {\n                        m_WindowOverlayProperties.SetActiveOverlayID(k_ulOverlayID_None, true);\n                        m_WindowOverlayProperties.Hide();\n                    }\n                    else if (m_WindowOverlayProperties.GetActiveOverlayID() > overlay_id) //Adjust properties window active overlay ID if it's open for an overlay that had its shifted\n                    {\n                        m_WindowOverlayProperties.SetActiveOverlayID(m_WindowOverlayProperties.GetActiveOverlayID() - 1, true);\n                    }\n\n                    m_WindowOverlayBar.HideMenus();\n                    break;\n                }\n                case ipcact_overlay_creation_error:\n                {\n                    m_OverlayErrorLast = (vr::VROverlayError)msg.lParam;\n                    UpdateAnyWarningDisplayedState();\n                    break;\n                }\n                case ipcact_winrt_thread_error:\n                {\n                    m_WinRTErrorLast = (HRESULT)msg.lParam;\n                    UpdateAnyWarningDisplayedState();\n                    break;\n                }\n                case ipcact_notification_show:\n                {\n                    DisplayInitialSetupNotification();\n                    break;\n                }\n                case ipcact_winmanager_winlist_add:\n                case ipcact_winmanager_winlist_update:\n                {\n                    if (handle_delayed)\n                    {\n                        const WindowInfo* window_info = nullptr;\n                        bool has_title_changed = true;\n\n                        if (msg.wParam == ipcact_winmanager_winlist_add)\n                            window_info = &WindowManager::Get().WindowListAdd((HWND)msg.lParam);\n                        else\n                            window_info = WindowManager::Get().WindowListUpdateTitle((HWND)msg.lParam, &has_title_changed);\n\n                        if ( (window_info != nullptr) && (has_title_changed) ) //Only do this when the title changed\n                        {\n                            if (ImGui::StringContainsUnmappedCharacter(window_info->GetListTitle().c_str()))\n                            {\n                                TextureManager::Get().ReloadAllTexturesLater();\n                                UIManager::Get()->RepeatFrame();\n                            }\n\n                            //Update last window info strings for overlays using this window\n                            for (unsigned int i = 0; i < OverlayManager::Get().GetOverlayCount(); ++i)\n                            {\n                                OverlayConfigData& data = OverlayManager::Get().GetConfigData(i);\n\n                                if ( (data.ConfigHandle[configid_handle_overlay_state_winrt_hwnd] == msg.lParam) && (data.ConfigInt[configid_int_overlay_capture_source] == ovrl_capsource_winrt_capture) )\n                                {\n                                    data.ConfigStr[configid_str_overlay_winrt_last_window_title]      = StringConvertFromUTF16(window_info->GetTitle().c_str());\n                                    data.ConfigStr[configid_str_overlay_winrt_last_window_class_name] = StringConvertFromUTF16(window_info->GetWindowClassName().c_str());\n                                    data.ConfigStr[configid_str_overlay_winrt_last_window_exe_name]   = window_info->GetExeName();\n\n                                    OverlayManager::Get().SetOverlayNameAuto(i, window_info);\n                                    OnOverlayNameChanged();\n                                }\n                            }\n                        }\n                    }\n                    else\n                    {\n                        m_DelayedICPMessages.push_back(msg);\n                    }\n\n                    break;\n                }\n                case ipcact_winmanager_winlist_remove:\n                {\n                    std::wstring last_title_w = WindowManager::Get().WindowListRemove((HWND)msg.lParam);\n                    std::string last_title = StringConvertFromUTF16(last_title_w.c_str());\n\n                    //Some windows clear their title entirely before ceasing to exist, skip those\n                    if (last_title.empty())\n                        break;\n\n                    //Set last known title for overlays that captured this window\n                    for (unsigned int i = 0; i < OverlayManager::Get().GetOverlayCount(); ++i)\n                    {\n                        OverlayConfigData& data = OverlayManager::Get().GetConfigData(i);\n\n                        if (data.ConfigHandle[configid_handle_overlay_state_winrt_hwnd] == msg.lParam)\n                        {\n                            data.ConfigStr[configid_str_overlay_winrt_last_window_title] = last_title;\n                        }\n                    }\n                    break;\n                }\n                case ipcact_winmanager_focus_changed:\n                {\n                    m_FloatingUI.GetMainBarWindow().MarkCurrentWindowCapturableStateOutdated();\n                    break;\n                }\n                case ipcact_keyboard_show:\n                {\n                    m_VRKeyboard.GetWindow().SetAssignedOverlayID((int)msg.lParam);\n                    (msg.lParam != -1) ? m_VRKeyboard.GetWindow().Show() : m_VRKeyboard.GetWindow().Hide();\n                    break;\n                }\n                case ipcact_keyboard_show_auto:\n                {\n                    int overlay_id = (msg.lParam != -1) ? (int)msg.lParam : m_VRKeyboard.GetWindow().GetAssignedOverlayID();\n\n                    //Only set auto-visibility if source overlay is desktop/window capture (avoid overriding browser auto-visible keyboards)\n                    if ( (overlay_id >= 0) && (OverlayManager::Get().GetConfigData((unsigned int)overlay_id).ConfigInt[configid_int_overlay_capture_source] <= ovrl_capsource_winrt_capture) )\n                    {\n                        m_VRKeyboard.GetWindow().SetAutoVisibility(overlay_id, (msg.lParam != -1));\n                    }\n                    break;\n                }\n                case ipcact_lpointer_ui_drag:\n                {\n                    (msg.lParam == 1) ? StartOverlayDrag(ConfigManager::GetValue(configid_handle_state_dplus_laser_pointer_target_overlay)) : FinishOverlayDrag();\n                    break;\n                }\n            }\n            break;\n        }\n        case ipcmsg_set_config:\n        {\n            if (msg.wParam < configid_bool_MAX)\n            {\n                ConfigID_Bool bool_id = (ConfigID_Bool)msg.wParam;\n\n                bool previous_value = ConfigManager::GetValue(bool_id);\n                ConfigManager::SetValue(bool_id, (msg.lParam != 0) );\n\n                switch (bool_id)\n                {\n                    case configid_bool_overlay_enabled:\n                    {\n                        if (m_WindowOverlayBar.IsVisibleOrFading())\n                        {\n                            m_IdleState.AddActiveTime(50);\n                        }\n                        break;\n                    }\n                    case configid_bool_state_window_focused_process_elevated:\n                    case configid_bool_state_misc_process_elevated:\n                    case configid_bool_state_misc_elevated_mode_active:\n                    case configid_bool_state_misc_uiaccess_enabled:\n                    case configid_bool_state_misc_browser_version_mismatch:\n                    case configid_bool_state_misc_browser_used_but_missing:\n                    {\n                        if ((msg.lParam != 0) != previous_value)\n                        {\n                            UpdateAnyWarningDisplayedState();\n                        }\n                        break;\n                    }\n                    case configid_bool_state_overlay_dragmode_temp:\n                    {\n                        SetOverlayInputEnabled((msg.lParam == 0));\n                        break;\n                    }\n                }\n            }\n            else if (msg.wParam < configid_bool_MAX + configid_int_MAX)\n            {\n                ConfigID_Int int_id = (ConfigID_Int)(msg.wParam - configid_bool_MAX);\n\n                int previous_value = ConfigManager::GetValue(int_id);\n                ConfigManager::SetValue(int_id, (int)msg.lParam);\n\n                switch (int_id)\n                {\n                    case configid_int_overlay_winrt_desktop_id:\n                    {\n                        OverlayManager::Get().SetCurrentOverlayNameAuto();\n                        break;\n                    }\n                    case configid_int_overlay_crop_x:\n                    case configid_int_overlay_crop_y:\n                    case configid_int_overlay_crop_width:\n                    case configid_int_overlay_crop_height:\n                    {\n                        //Crop changed while properties window is visible for the current overlay, force refresh of cached crop button label\n                        if ( (m_WindowOverlayProperties.IsVisibleOrFading()) && (m_WindowOverlayProperties.GetActiveOverlayID() == OverlayManager::Get().GetCurrentOverlayID()) )\n                        {\n                            m_WindowOverlayProperties.SetActiveOverlayID(m_WindowOverlayProperties.GetActiveOverlayID(), true);\n                        }\n                        break;\n                    }\n                    case configid_int_overlay_state_fps:\n                    case configid_int_state_performance_duplication_fps:\n                    {\n                        if (ConfigManager::GetValue(configid_bool_performance_show_fps))\n                        {\n                            m_IdleState.AddActiveTime(50);\n                        }\n                        break;\n                    }\n                    case configid_int_interface_overlay_current_id:\n                    {\n                        OverlayManager::Get().SetCurrentOverlayID((unsigned int)msg.lParam);\n                        current_overlay_old = (unsigned int)msg.lParam;\n                        break;\n                    }\n                    case configid_int_state_overlay_transform_sync_target_id:\n                    {\n                        m_TransformSyncValueCount = 0;\n                        std::fill(m_TransformSyncValues, std::end(m_TransformSyncValues), 0.0f);\n                        break;\n                    }\n                    case configid_int_state_interface_desktop_count:\n                    {\n                        m_IdleState.AddActiveTime(50);\n                        RepeatFrame();\n                        break;\n                    }\n                    case configid_int_state_auto_docking_state:\n                    {\n                        if (msg.lParam == 0)\n                        {\n                            m_AuxUI.GetDragHintWindow().Hide();\n                        }\n                        else\n                        {\n                            //Even config values above 0 are right hand, odd ones are left hand\n                            const vr::ETrackedControllerRole role = (msg.lParam % 2 == 0) ? vr::TrackedControllerRole_RightHand : vr::TrackedControllerRole_LeftHand;\n                            const bool is_docking = (msg.lParam <= 2);\n\n                            m_AuxUI.GetDragHintWindow().SetHintType(vr::VRSystem()->GetTrackedDeviceIndexForControllerRole(role), (is_docking) ? WindowDragHint::hint_docking : WindowDragHint::hint_undocking);\n                            m_AuxUI.GetDragHintWindow().Show();\n                        }\n\n                        break;\n                    }\n                    case configid_int_state_drag_hint_type:\n                    {\n                        if (msg.lParam != 0)\n                        {\n                            //We don't use this config value for auto-docking hints since their display is handled alongside the docking state\n                            const WindowDragHint::HintType hint_type = (msg.lParam == 1) ? WindowDragHint::hint_ovrl_locked : WindowDragHint::hint_ovrl_theater_screen_blocked;\n\n                            m_AuxUI.GetDragHintWindow().SetHintType(ConfigManager::GetValue(configid_int_state_drag_hint_device), hint_type);\n                            m_AuxUI.GetDragHintWindow().Show();\n                        }\n                        else\n                        {\n                            m_AuxUI.GetDragHintWindow().Hide();\n                        }\n\n                        break;\n                    }\n                    case configid_int_state_overlay_focused_id:\n                    {\n                        //Hide auto-visible keyboard if there was one for the previously focused overlay\n                        if ( (previous_value != -1) && (previous_value != msg.lParam) )\n                        {\n                            m_VRKeyboard.GetWindow().SetAutoVisibility((unsigned int)previous_value, false);\n                        }\n                        break;\n                    }\n                    default: break;\n                }\n            }\n            else if (msg.wParam < configid_bool_MAX + configid_int_MAX + configid_float_MAX)\n            {\n                ConfigID_Float float_id = (ConfigID_Float)(msg.wParam - configid_bool_MAX - configid_int_MAX);\n                float value = pun_cast<float, LPARAM>(msg.lParam);\n                ConfigManager::SetValue(float_id, value);\n\n                switch (float_id)\n                {\n                    case configid_float_state_overlay_transform_sync_value:\n                    {\n                        if (m_TransformSyncValueCount < IM_ARRAYSIZE(m_TransformSyncValues))\n                        {\n                            m_TransformSyncValues[m_TransformSyncValueCount] = value;\n                            m_TransformSyncValueCount++;\n                        }\n\n                        if (m_TransformSyncValueCount >= IM_ARRAYSIZE(m_TransformSyncValues))\n                        {\n                            OverlayConfigData& data = OverlayManager::Get().GetConfigData((unsigned int)ConfigManager::GetValue(configid_int_state_overlay_transform_sync_target_id));\n                            data.ConfigTransform.set(m_TransformSyncValues);\n\n                            m_TransformSyncValueCount = 0;\n                            std::fill(m_TransformSyncValues, std::end(m_TransformSyncValues), 0.0f);\n                        }\n\n                        break;\n                    }\n                    default: break;\n                }\n            }\n            else if (msg.wParam < configid_bool_MAX + configid_int_MAX + configid_float_MAX + configid_handle_MAX)\n            {\n                ConfigID_Handle handle_id = (ConfigID_Handle)(msg.wParam - configid_bool_MAX - configid_int_MAX - configid_float_MAX);\n                uint64_t value = pun_cast<uint64_t, LPARAM>(msg.lParam);\n                ConfigManager::SetValue(handle_id, value);\n\n                switch (handle_id)\n                {\n                    case configid_handle_overlay_state_winrt_hwnd:\n                    {\n                        const WindowInfo* window_info = nullptr;\n\n                        if (value != 0)\n                            window_info = WindowManager::Get().WindowListFindWindow((HWND)value);\n\n                        //Set last known title and exe name from new handle\n                        if (window_info != nullptr)\n                        {\n                            ConfigManager::SetValue(configid_str_overlay_winrt_last_window_title,      StringConvertFromUTF16(window_info->GetTitle().c_str()));\n                            ConfigManager::SetValue(configid_str_overlay_winrt_last_window_class_name, StringConvertFromUTF16(window_info->GetWindowClassName().c_str()));\n                            ConfigManager::SetValue(configid_str_overlay_winrt_last_window_exe_name,   window_info->GetExeName());\n                        }\n                        else if (value == 0) //Only clear if HWND is really null\n                        {\n                            ConfigManager::SetValue(configid_str_overlay_winrt_last_window_title, \"\");\n                            ConfigManager::SetValue(configid_str_overlay_winrt_last_window_class_name, \"\");\n                            ConfigManager::SetValue(configid_str_overlay_winrt_last_window_exe_name, \"\");\n                            ConfigManager::SetValue(configid_handle_overlay_state_winrt_last_hicon, 0);\n                        }\n\n                        OverlayManager::Get().SetCurrentOverlayNameAuto();\n\n                        //Refresh Overlay Properties window if it's currently active for this overlay\n                        unsigned int overlay_props_active_id = m_WindowOverlayProperties.GetActiveOverlayID();\n\n                        if (OverlayManager::Get().GetCurrentOverlayID() == overlay_props_active_id)\n                        {\n                            m_WindowOverlayProperties.SetActiveOverlayID(overlay_props_active_id, true);\n                        }\n                    }\n                    default: break;\n                }\n            }\n\n            break;\n        }\n    }\n\n    //Restore overlay id override\n    if (overlay_override_id != -1)\n    {\n        OverlayManager::Get().SetCurrentOverlayID(current_overlay_old);\n    }\n}\n\nvoid UIManager::HandleDelayedIPCMessages()\n{\n    while (!m_DelayedICPMessages.empty())\n    {\n        HandleIPCMessage(m_DelayedICPMessages.back(), true);\n        m_DelayedICPMessages.pop_back();\n    }\n}\n\nbool UIManager::HasDelayedIPCMessages() const\n{\n    return !m_DelayedICPMessages.empty();\n}\n\nvoid UIManager::OnInitDone()\n{\n    //Re-apply active overlay ID since the first time config load couldn't apply it properly with the overlay not yet existing\n    unsigned int overlay_props_active_id = m_WindowOverlayProperties.GetActiveOverlayID();\n    m_WindowOverlayProperties.SetActiveOverlayID(overlay_props_active_id, true);\n\n    //Hide properties window if the overlay doesn't exist anymore for some reason\n    if ((overlay_props_active_id == k_ulOverlayID_None) || (OverlayManager::Get().GetOverlayCount() <= overlay_props_active_id))\n    {\n        m_WindowOverlayProperties.Hide(true);\n        m_WindowOverlayProperties.GetOverlayState(floating_window_ovrl_state_room).IsVisible = false;\n        m_WindowOverlayProperties.GetOverlayState(floating_window_ovrl_state_dashboard_tab).IsVisible = false;\n    }\n\n    UpdateDesktopOverlayPixelSize();\n    ConfigManager::Get().GetAppProfileManager().ActivateProfileForCurrentSceneApp();\n}\n\nvoid UIManager::OnExit()\n{\n    //Re-launch in VR when we were in desktop mode and probably got switched from VR mode before\n    //This is likely more intuitive than just removing the UI entirely when clicking X\n    if ( (m_DesktopMode) && (IPCManager::Get().IsDashboardAppRunning()) && (!ConfigManager::GetValue(configid_bool_interface_no_ui)) && (!m_NoRestartOnExit) )\n    {\n        Restart(false);\n    }\n    else\n    {\n        //Save config, just in case (we don't need to do this when calling Restart())\n        ConfigManager::Get().SaveConfigToFile();\n    }\n\n    //Release any held down keys\n    m_VRKeyboard.ResetState();\n\n    if (m_ComInitDone)\n    {\n        ::CoUninitialize();\n    }\n\n    m_SharedTextureRef.Reset();\n\n    WindowManager::Get().SetActive(false);\n\n    vr::VR_Shutdown();\n}\n\nvoid UIManager::OnProfileLoaded()\n{\n    //Adjust current overlay ID for UI since this may have made the old selection invalid\n    int& current_overlay = ConfigManager::Get().GetRef(configid_int_interface_overlay_current_id);\n    current_overlay = clamp(current_overlay, 0, (int)OverlayManager::Get().GetOverlayCount() - 1);\n\n    //Adjust overlay properties window    \n    //Hide window if overlay ID no longer in range\n    if (m_WindowOverlayProperties.GetActiveOverlayID() >= OverlayManager::Get().GetOverlayCount())\n    {\n        if (m_WindowOverlayProperties.GetActiveOverlayID() != k_ulOverlayID_None)\n        {\n            m_WindowOverlayProperties.SetActiveOverlayID(k_ulOverlayID_None, true);\n            m_WindowOverlayProperties.Hide();\n        }\n    }\n    else //Just adjust switch if it is still is\n    {\n        m_WindowOverlayProperties.SetActiveOverlayID(m_WindowOverlayProperties.GetActiveOverlayID(), true);\n    }\n\n    //Check if new overlays have any unmapped characters\n    for (unsigned int i = 0; i < OverlayManager::Get().GetOverlayCount(); ++i)\n    {\n        AddFontBuilderStringIfAnyUnmappedCharacters(OverlayManager::Get().GetConfigData(i).ConfigNameStr.c_str());\n    }\n\n    m_IdleState.AddActiveTime(50);\n    RepeatFrame();\n}\n\nFloatingUI& UIManager::GetFloatingUI()\n{\n    return m_FloatingUI;\n}\n\nVRKeyboard& UIManager::GetVRKeyboard()\n{\n    return m_VRKeyboard;\n}\n\nAuxUI& UIManager::GetAuxUI()\n{\n    return m_AuxUI;\n}\n\nWindowOverlayBar& UIManager::GetOverlayBarWindow()\n{\n    return m_WindowOverlayBar;\n}\n\nWindowSettings& UIManager::GetSettingsWindow()\n{\n    return m_WindowSettings;\n}\n\nWindowOverlayProperties& UIManager::GetOverlayPropertiesWindow()\n{\n    return m_WindowOverlayProperties;\n}\n\nWindowPerformance& UIManager::GetPerformanceWindow()\n{\n    return m_WindowPerformance;\n}\n\nWindowDesktopMode& UIManager::GetDesktopModeWindow()\n{\n    return m_WindowDesktopMode;\n}\n\nvoid UIManager::SetWindowHandle(HWND handle)\n{\n    m_WindowHandle = handle;\n\n    //WindowManager will be initialized before our window is ready, but won't be ready to receive window updates before our window is fully created, so we add it manually here\n    if (m_DesktopMode)\n        WindowManager::Get().WindowListAdd(handle);\n}\n\nHWND UIManager::GetWindowHandle() const\n{\n    return m_WindowHandle;\n}\n\nNotificationIcon& UIManager::GetNotificationIcon()\n{\n    return m_NotificationIcon;\n}\n\nvoid UIManager::SetSharedTextureRef(ID3D11Resource* ref)\n{\n   m_SharedTextureRef = ref;\n}\n\nID3D11Resource* UIManager::GetSharedTextureRef() const\n{\n    return m_SharedTextureRef.Get();\n}\n\nOverlayDragger& UIManager::GetOverlayDragger()\n{\n    return m_OverlayDragger;\n}\n\nvr::VROverlayHandle_t UIManager::GetOverlayHandleOverlayBar() const\n{\n    return m_OvrlHandleOverlayBar;\n}\n\nvr::VROverlayHandle_t UIManager::GetOverlayHandleFloatingUI() const\n{\n    return m_OvrlHandleFloatingUI;\n}\n\nvr::VROverlayHandle_t UIManager::GetOverlayHandleSettings() const\n{\n    return m_OvrlHandleSettings;\n}\n\nvr::VROverlayHandle_t UIManager::GetOverlayHandleOverlayProperties() const\n{\n    return m_OvrlHandleOverlayProperties;\n}\n\nvr::VROverlayHandle_t UIManager::GetOverlayHandleKeyboard() const\n{\n    return m_OvrlHandleKeyboard;\n}\n\nvr::VROverlayHandle_t UIManager::GetOverlayHandleAuxUI() const\n{\n    return m_OvrlHandleAuxUI;\n}\n\nvr::VROverlayHandle_t UIManager::GetOverlayHandleDPlusDashboard() const\n{\n    return m_OvrlHandleDPlusDashboard;\n}\n\nvr::VROverlayHandle_t UIManager::GetOverlayHandleSystemUI() const\n{\n    return m_OvrlHandleSystemUI;\n}\n\nstd::array<vr::VROverlayHandle_t, 6> UIManager::GetUIOverlayHandles() const\n{\n    return {m_OvrlHandleOverlayBar, m_OvrlHandleFloatingUI, m_OvrlHandleSettings, m_OvrlHandleOverlayProperties, m_OvrlHandleKeyboard, m_OvrlHandleAuxUI};\n}\n\nbool UIManager::IsDummyOverlayTransformUnstable() const\n{\n    return m_IsDummyOverlayTransformUnstable;\n}\n\nvoid UIManager::SendUIIntersectionMaskToDashboardApp(std::vector<vr::VROverlayIntersectionMaskPrimitive_t>& primitives) const\n{\n    static ULONGLONG last_tick = 0;\n\n    //Mask can change at any time, any frame. We don't really want to send too many messages either though, so we limit the rate and don't update at all if the pointer isn't active\n    if ( (ConfigManager::GetValue(configid_int_state_dplus_laser_pointer_device) != vr::k_unTrackedDeviceIndexInvalid) && (last_tick + 100 > ::GetTickCount64()) )\n        return;\n\n    for (const auto& rect : primitives)\n    {\n        DPRect dp_rect((int)rect.m_Primitive.m_Rectangle.m_flTopLeftX,  (int)rect.m_Primitive.m_Rectangle.m_flTopLeftY, \n                       (int)rect.m_Primitive.m_Rectangle.m_flTopLeftX + (int)rect.m_Primitive.m_Rectangle.m_flWidth, (int)rect.m_Primitive.m_Rectangle.m_flTopLeftY + (int)rect.m_Primitive.m_Rectangle.m_flHeight);\n\n        IPCManager::Get().PostMessageToDashboardApp(ipcmsg_action, ipcact_lpointer_ui_mask_rect, (LPARAM)dp_rect.Pack16());\n    }\n\n    IPCManager::Get().PostMessageToDashboardApp(ipcmsg_action, ipcact_lpointer_ui_mask_rect, -1); //Mark end of mask\n\n    last_tick = ::GetTickCount64();\n}\n\nUIManager::IdleState& UIManager::GetIdleState()\n{\n    return m_IdleState;\n}\n\nDPRect UIManager::CalcRectForActiveTexspace()\n{\n    DPRect rect;\n\n    if (m_WindowOverlayBar.IsVisibleOrFading())\n        (rect.GetWidth() == 0) ? rect = UITextureSpaces::Get().GetRect(ui_texspace_overlay_bar)         : rect.Add(UITextureSpaces::Get().GetRect(ui_texspace_overlay_bar));\n    if (m_FloatingUI.IsVisible())\n        (rect.GetWidth() == 0) ? rect = UITextureSpaces::Get().GetRect(ui_texspace_floating_ui)         : rect.Add(UITextureSpaces::Get().GetRect(ui_texspace_floating_ui));\n    if (m_WindowSettings.IsVisibleOrFading())\n        (rect.GetWidth() == 0) ? rect = UITextureSpaces::Get().GetRect(ui_texspace_settings)            : rect.Add(UITextureSpaces::Get().GetRect(ui_texspace_settings));\n    if (m_WindowOverlayProperties.IsVisibleOrFading())\n        (rect.GetWidth() == 0) ? rect = UITextureSpaces::Get().GetRect(ui_texspace_overlay_properties)  : rect.Add(UITextureSpaces::Get().GetRect(ui_texspace_overlay_properties));\n    if (m_VRKeyboard.GetWindow().IsVisibleOrFading())\n        (rect.GetWidth() == 0) ? rect = UITextureSpaces::Get().GetRect(ui_texspace_keyboard)            : rect.Add(UITextureSpaces::Get().GetRect(ui_texspace_keyboard));\n    if (m_WindowPerformance.IsVisible())\n        (rect.GetWidth() == 0) ? rect = UITextureSpaces::Get().GetRect(ui_texspace_performance_monitor) : rect.Add(UITextureSpaces::Get().GetRect(ui_texspace_performance_monitor));\n    if (m_AuxUI.IsActive())\n        (rect.GetWidth() == 0) ? rect = UITextureSpaces::Get().GetRect(ui_texspace_aux_ui)              : rect.Add(UITextureSpaces::Get().GetRect(ui_texspace_aux_ui));\n\n    return rect;\n}\n\nvoid UIManager::RepeatFrame(int extra_frame_count)\n{\n    if (m_RepeatFrame < extra_frame_count)\n        m_RepeatFrame = extra_frame_count;\n}\n\nbool UIManager::GetRepeatFrame() const\n{\n    return (m_RepeatFrame != 0);\n}\n\nvoid UIManager::DecreaseRepeatFrameCount()\n{\n    if (m_RepeatFrame != 0)\n        m_RepeatFrame--;\n}\n\nbool UIManager::IsInDesktopMode() const\n{\n    return m_DesktopMode;\n}\n\nbool UIManager::IsInKeyboardEditorMode() const\n{\n    return m_KeyboardEditorMode;\n}\n\nbool UIManager::IsOpenVRLoaded() const\n{\n    return m_OpenVRLoaded;\n}\n\nvoid UIManager::DisableRestartOnExit()\n{\n    m_NoRestartOnExit = true;\n}\n\nvoid UIManager::Restart(bool desktop_mode)\n{\n    LOG_IF_F(INFO, !desktop_mode, \"Restarting...\");\n    LOG_IF_F(INFO,  desktop_mode, \"Restarting into desktop mode...\");\n\n    ConfigManager::Get().SaveConfigToFile();\n\n    UIManager::Get()->DisableRestartOnExit();\n\n    STARTUPINFO si = {0};\n    PROCESS_INFORMATION pi = {0};\n    si.cb = sizeof(si);\n\n    std::wstring path = WStringConvertFromUTF8(ConfigManager::Get().GetApplicationPath().c_str()) + L\"DesktopPlusUI.exe\";\n    WCHAR cmd[] = L\"--DesktopMode\";\n\n    ::CreateProcess(path.c_str(), (desktop_mode) ? cmd : nullptr, nullptr, nullptr, FALSE, 0, nullptr, nullptr, &si, &pi);\n\n    //We don't care about these, so close right away\n    ::CloseHandle(pi.hProcess);\n    ::CloseHandle(pi.hThread);\n}\n\nvoid UIManager::RestartIntoKeyboardEditor()\n{\n    LOG_F(INFO, \"Restarting into Keyboard Editor...\");\n\n    ConfigManager::Get().SaveConfigToFile();\n\n    UIManager::Get()->DisableRestartOnExit();\n\n    STARTUPINFO si = {0};\n    PROCESS_INFORMATION pi = {0};\n    si.cb = sizeof(si);\n\n    std::wstring path = WStringConvertFromUTF8(ConfigManager::Get().GetApplicationPath().c_str()) + L\"DesktopPlusUI.exe\";\n    WCHAR cmd[] = L\"--KeyboardEditor\";\n\n    ::CreateProcess(path.c_str(), cmd, nullptr, nullptr, FALSE, 0, nullptr, nullptr, &si, &pi);\n\n    //We don't care about these, so close right away\n    ::CloseHandle(pi.hProcess);\n    ::CloseHandle(pi.hThread);\n}\n\nvoid UIManager::RestartDashboardApp(bool force_steam)\n{\n    LOG_F(INFO, \"Restarting dashboard app...%s\", (force_steam) ? \" (using Steam)\" : \"\");\n\n    ConfigManager::Get().ResetConfigStateValues();\n    ConfigManager::Get().SaveConfigToFile();\n\n    bool use_steam = ( (force_steam) || (ConfigManager::GetValue(configid_bool_state_misc_process_started_by_steam)) );\n\n    //LaunchDashboardOverlay() technically also launches the non-Steam version if it's registered, but there's no reason to use it in that case\n    if (use_steam)\n    {\n        //We need OpenVR loaded for this\n        if (!m_OpenVRLoaded)\n        {\n            InitOverlay();\n        }\n\n        //Steam will not launch the overlay if it's already running, so in this case we need to get rid of the running instance now\n        StopProcessByWindowClass(g_WindowClassNameDashboardApp);\n\n        ULONGLONG start_tick = ::GetTickCount64();\n        vr::EVRApplicationError app_error = vr::VRApplications()->LaunchDashboardOverlay(g_AppKeyDashboardApp);\n\n        while ( (app_error == vr::VRApplicationError_ApplicationAlreadyRunning) && (::GetTickCount64() - start_tick < 5000) ) //Try 5 seconds max\n        {\n            ::Sleep(250);\n            app_error = vr::VRApplications()->LaunchDashboardOverlay(g_AppKeyDashboardApp);\n        }\n\n        //Try without Steam below if launching failed somehow\n        if (app_error != vr::VRApplicationError_None)\n        {\n            use_steam = false;\n        }\n    }\n    \n    if (!use_steam)\n    {\n        std::wstring path = WStringConvertFromUTF8(ConfigManager::Get().GetApplicationPath().c_str()) + L\"DesktopPlus.exe\";\n\n        if (ConfigManager::GetValue(configid_bool_state_misc_uiaccess_enabled)) //UIAccess enabled executable doesn't run straight from CreateProcess()\n        {\n            if (!m_ComInitDone) //Let's only do this if really needed\n            {\n                m_ComInitDone = (::CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE) != RPC_E_CHANGED_MODE);\n            }\n\n            ::ShellExecute(nullptr, nullptr, path.c_str(), nullptr, nullptr, SW_SHOWNORMAL);\n        }\n        else\n        {\n            STARTUPINFO si = {0};\n            PROCESS_INFORMATION pi = {0};\n            si.cb = sizeof(si);\n\n            ::CreateProcess(path.c_str(), nullptr, nullptr, nullptr, FALSE, 0, nullptr, nullptr, &si, &pi);\n\n            //We don't care about these, so close right away\n            ::CloseHandle(pi.hProcess);\n            ::CloseHandle(pi.hThread);\n        }\n    }\n\n    m_WindowPerformance.ScheduleOverlaySharedTextureUpdate();\n    m_WindowOverlayProperties.Hide();                           //Current overlay won't be set on dashboard app, so close this now\n}\n\nvoid UIManager::ElevatedModeEnter()\n{\n    LOG_F(INFO, \"Entered elevated mode\");\n\n    STARTUPINFO si = {0};\n    PROCESS_INFORMATION pi = {0};\n    si.cb = sizeof(si);\n\n    WCHAR cmd[] = L\"\\\"schtasks\\\" /Run /TN \\\"DesktopPlus Elevated\\\"\"; //\"CreateProcessW, can modify the contents of this string\", so don't go optimize this away\n    ::CreateProcess(nullptr, cmd, nullptr, nullptr, FALSE, CREATE_NO_WINDOW, nullptr, nullptr, &si, &pi);\n\n    //We don't care about these, so close right away\n    ::CloseHandle(pi.hProcess);\n    ::CloseHandle(pi.hThread);\n}\n\nvoid UIManager::ElevatedModeLeave()\n{\n    LOG_F(INFO, \"Left elevated mode\");\n\n    //Kindly ask elevated mode process to quit\n    if (HWND window = ::FindWindow(g_WindowClassNameElevatedMode, nullptr))\n    {\n        ::PostMessage(window, WM_QUIT, 0, 0);\n    }\n}\n\nvoid UIManager::UpdateStyle()\n{\n    if (ImGui::GetCurrentContext() == nullptr)\n        return;\n\n    ImGuiIO& io = ImGui::GetIO();\n\n    //Setup Dear ImGui style\n    ImGuiStyle& style = ImGui::GetStyle();\n    style = ImGuiStyle();\n    ImGui::StyleColorsDark();\n    //Do a bit of custom styling\n    style.DisabledAlpha  = 0.5f;\n    style.WindowRounding = 7.0f;\n    style.FrameRounding  = 4.0f;\n    style.GrabRounding   = 3.0f;\n    style.IndentSpacing  = style.ItemSpacing.x;\n\n    if (m_DesktopMode)\n    {\n        style.WindowPadding.x /= 2.0f;\n        style.WindowPadding.y /= 2.0f;\n    }\n\n    ImVec4* colors = style.Colors;\n    colors[ImGuiCol_Text]                  = ImVec4(1.00f, 1.00f, 1.00f, 1.00f);\n    colors[ImGuiCol_TextDisabled]          = ImVec4(0.36f, 0.42f, 0.47f, 1.00f);\n    colors[ImGuiCol_TextDisabled]          = ImVec4(0.50f, 0.50f, 0.50f, 1.00f);\n    colors[ImGuiCol_WindowBg]              = ImVec4(0.085f, 0.135f, 0.155f, 0.96f);\n    colors[ImGuiCol_ChildBg]               = ImVec4(0.00f, 0.00f, 0.00f, 0.10f);\n    colors[ImGuiCol_PopupBg]               = ImVec4(0.088f, 0.138f, 0.158f, 0.96f);\n    colors[ImGuiCol_Border]                = ImVec4(0.20f, 0.25f, 0.29f, 1.00f);\n    colors[ImGuiCol_BorderShadow]          = ImVec4(0.00f, 0.00f, 0.00f, 0.00f);\n    colors[ImGuiCol_FrameBg]               = ImVec4(0.185f, 0.245f, 0.285f, 1.00f);\n    colors[ImGuiCol_FrameBgHovered]        = ImVec4(0.12f, 0.20f, 0.28f, 1.00f);\n    colors[ImGuiCol_FrameBgActive]         = ImVec4(0.109f, 0.175f, 0.224f, 1.000f);\n    colors[ImGuiCol_TitleBg]               = ImVec4(0.08f, 0.10f, 0.12f, 1.00f);\n    colors[ImGuiCol_TitleBgActive]         = ImVec4(0.08f, 0.10f, 0.12f, 1.00f);\n    colors[ImGuiCol_TitleBgCollapsed]      = ImVec4(0.00f, 0.00f, 0.00f, 0.51f);\n    colors[ImGuiCol_MenuBarBg]             = ImVec4(0.15f, 0.18f, 0.22f, 1.00f);\n    colors[ImGuiCol_ScrollbarBg]           = ImVec4(0.02f, 0.02f, 0.02f, 0.08f);\n    colors[ImGuiCol_ScrollbarGrab]         = ImVec4(0.20f, 0.25f, 0.29f, 1.00f);\n    colors[ImGuiCol_ScrollbarGrabHovered]  = ImVec4(0.18f, 0.22f, 0.25f, 1.00f);\n    colors[ImGuiCol_ScrollbarGrabActive]   = ImVec4(0.09f, 0.21f, 0.31f, 1.00f);\n    colors[ImGuiCol_CheckMark]             = ImVec4(1.00f, 1.00f, 1.00f, 1.00f);\n    colors[ImGuiCol_SliderGrab]            = ImVec4(0.298f, 0.596f, 0.859f, 1.000f);\n    colors[ImGuiCol_SliderGrabActive]      = ImVec4(0.333f, 0.616f, 1.000f, 1.000f);\n    colors[ImGuiCol_Button]                = ImVec4(0.20f, 0.25f, 0.29f, 1.00f);\n    colors[ImGuiCol_ButtonHovered]         = ImVec4(0.28f, 0.56f, 1.00f, 1.00f);\n    colors[ImGuiCol_ButtonActive]          = ImVec4(0.063f, 0.548f, 1.000f, 1.000f);\n    colors[ImGuiCol_Header]                = ImVec4(0.20f, 0.25f, 0.29f, 0.55f);\n    colors[ImGuiCol_HeaderHovered]         = ImVec4(0.26f, 0.59f, 0.98f, 0.80f);\n    colors[ImGuiCol_HeaderActive]          = ImVec4(0.26f, 0.59f, 0.98f, 1.00f);\n    colors[ImGuiCol_Separator]             = ImVec4(0.20f, 0.25f, 0.29f, 1.00f);\n    colors[ImGuiCol_SeparatorHovered]      = ImVec4(0.10f, 0.40f, 0.75f, 0.78f);\n    colors[ImGuiCol_SeparatorActive]       = ImVec4(0.10f, 0.40f, 0.75f, 1.00f);\n    colors[ImGuiCol_ResizeGrip]            = ImVec4(0.26f, 0.59f, 0.98f, 0.25f);\n    colors[ImGuiCol_ResizeGripHovered]     = ImVec4(0.26f, 0.59f, 0.98f, 0.67f);\n    colors[ImGuiCol_ResizeGripActive]      = ImVec4(0.26f, 0.59f, 0.98f, 0.95f);\n    colors[ImGuiCol_Tab]                   = ImVec4(0.28f, 0.305f, 0.3f, 0.25f);\n    colors[ImGuiCol_TabHovered]            = ImVec4(0.26f, 0.59f, 0.98f, 0.80f);\n    colors[ImGuiCol_TabSelected]           = ImVec4(0.20f, 0.25f, 0.29f, 1.00f);\n    colors[ImGuiCol_TabDimmed]             = ImVec4(0.11f, 0.15f, 0.17f, 1.00f);\n    colors[ImGuiCol_TabDimmedSelected]     = ImVec4(0.11f, 0.15f, 0.17f, 1.00f);\n    colors[ImGuiCol_PlotLines]             = ImVec4(0.61f, 0.61f, 0.61f, 1.00f);\n    colors[ImGuiCol_PlotLinesHovered]      = ImVec4(1.00f, 0.43f, 0.35f, 1.00f);\n    colors[ImGuiCol_PlotHistogram]         = ImVec4(0.90f, 0.70f, 0.00f, 1.00f);\n    colors[ImGuiCol_PlotHistogramHovered]  = ImVec4(1.00f, 0.60f, 0.00f, 1.00f);\n    colors[ImGuiCol_TableHeaderBg]         = ImVec4(0.20f, 0.25f, 0.29f, 0.55f);\n    colors[ImGuiCol_TableBorderStrong]     = ImVec4(0.20f, 0.25f, 0.29f, 1.00f);\n    colors[ImGuiCol_TableBorderLight]      = ImVec4(0.20f, 0.25f, 0.29f, 1.00f);\n    colors[ImGuiCol_TableRowBg]            = ImVec4(0.00f, 0.00f, 0.00f, 0.10f);\n    colors[ImGuiCol_TableRowBgAlt]         = ImVec4(0.00f, 0.00f, 0.00f, 0.20f);\n    colors[ImGuiCol_TextSelectedBg]        = ImVec4(0.26f, 0.59f, 0.98f, 0.35f);\n    colors[ImGuiCol_DragDropTarget]        = ImVec4(1.00f, 1.00f, 0.00f, 0.90f);\n    colors[ImGuiCol_NavCursor]             = ImVec4(0.26f, 0.59f, 0.98f, 1.00f);\n    colors[ImGuiCol_NavWindowingHighlight] = ImVec4(1.00f, 1.00f, 1.00f, 0.70f);\n    colors[ImGuiCol_NavWindowingDimBg]     = ImVec4(0.80f, 0.80f, 0.80f, 0.20f);\n    colors[ImGuiCol_ModalWindowDimBg]      = ImVec4(0.00f, 0.00f, 0.00f, 0.00f);\n    Style_ImGuiCol_TextNotification        = ImVec4(0.64f, 0.97f, 0.26f, 1.00f);\n    Style_ImGuiCol_TextWarning             = ImVec4(0.98f, 0.81f, 0.26f, 1.00f);\n    Style_ImGuiCol_TextError               = ImVec4(0.97f, 0.33f, 0.33f, 1.00f);\n    Style_ImGuiCol_TextOutline             = ImVec4(0.00f, 0.00f, 0.00f, 1.00f);\n    Style_ImGuiCol_ButtonPassiveToggled    = ImVec4(0.122f, 0.220f, 0.322f, 1.000f);\n    Style_ImGuiCol_SteamVRCursor           = ImVec4(0.463f, 0.765f, 0.882f, 1.000f);\n    Style_ImGuiCol_SteamVRCursorBorder     = ImVec4(0.161f, 0.176f, 0.196f, 0.929f);\n\n    //Setup ImPlot style\n    ImPlotStyle& plot_style = ImPlot::GetStyle();\n    plot_style.PlotPadding               = {0.0f, 0.0f};\n    plot_style.FillAlpha                 = 0.25f;\n    plot_style.Colors[ImPlotCol_FrameBg] = ImVec4(0.03f, 0.05f, 0.06f, 0.10f);\n    plot_style.Colors[ImPlotCol_PlotBg]  = ImVec4(0.00f, 0.00f, 0.00f, 0.00f);\n\n    //Adapt to DPI\n    float ui_scale = 1.0f;\n    if (UIManager::Get()->IsInDesktopMode())\n    {\n        float dpi_scale = ImGui_ImplWin32_GetDpiScaleForHwnd(m_WindowHandle);\n\n        //Allow overriding the UI scale\n        float scale_override = ConfigManager::GetValue(configid_float_interface_desktop_ui_scale_override);\n\n        if (scale_override != 0.0f)\n        {\n            dpi_scale = clamp(scale_override, 0.10f, 5.0f);\n        }\n\n        ui_scale = dpi_scale * 0.625f;      //Scaling based on 100% being the VR font at 32pt and desktop 100% DPI font being at 20pt\n\n        style.ScaleAllSizes(dpi_scale);     //Scale based on DPI scale, not UI scale (basically only go larger)\n    }\n\n    UIManager::Get()->SetUIScale(ui_scale);\n\n    //Set DPI-dependent style\n    style.LogSliderDeadzone = (float)int(58.0f * ui_scale); //Force whole pixel size\n\n    if (UIManager::Get()->IsInDesktopMode())\n    {\n        io.DisplaySize.x = (float)UITextureSpaces::Get().GetRect(ui_texspace_total).GetWidth()  * ui_scale;\n        io.DisplaySize.y = (float)UITextureSpaces::Get().GetRect(ui_texspace_total).GetHeight() * ui_scale;\n\n        style.ScrollbarSize = (float)int(23.0f * ui_scale); \n    }\n    else\n    {\n        io.DisplaySize.x = (float)UITextureSpaces::Get().GetRect(ui_texspace_total).GetWidth();\n        io.DisplaySize.y = (float)UITextureSpaces::Get().GetRect(ui_texspace_total).GetHeight();\n\n        style.ScrollbarSize = (float)int(32.0f * ui_scale);\n\n        //UpdateOverlayDimming() relies on loaded ImGui/style, so do the initial call to that here\n        UIManager::Get()->UpdateOverlayDimming();\n    }\n\n    TextureManager::Get().LoadAllTexturesAndBuildFonts();\n    RepeatFrame();\n}\n\nvoid UIManager::SetUIScale(float scale)\n{\n    m_UIScale = scale;\n\n    if (!m_DesktopMode)\n    {\n        ConfigManager::SetValue(configid_float_interface_last_vr_ui_scale, scale);\n    }\n\n    m_WindowOverlayProperties.ApplyUIScale();\n    m_WindowSettings.ApplyUIScale();\n    m_VRKeyboard.GetWindow().ApplyUIScale();\n}\n\nfloat UIManager::GetUIScale() const\n{\n    return m_UIScale;\n}\n\nvoid UIManager::SetFonts(ImFont* font_compact, ImFont* font_large)\n{\n    m_FontCompact = font_compact;\n    m_FontLarge = font_large;\n}\n\nImFont* UIManager::GetFontCompact() const\n{\n    return m_FontCompact;\n}\n\nImFont* UIManager::GetFontLarge() const\n{\n    return m_FontLarge;\n}\n\nvoid UIManager::AddFontBuilderStringIfAnyUnmappedCharacters(const char* str)\n{\n    if (ImGui::StringContainsUnmappedCharacter(str))\n    {\n        if (TextureManager::Get().AddFontBuilderString(str))\n        {\n            TextureManager::Get().ReloadAllTexturesLater();\n            RepeatFrame();\n        }\n    }\n}\n\nvoid UIManager::OnDPIChanged(int new_dpi, const RECT& new_window_rect)\n{\n    if (!m_DesktopMode)\n        return;\n\n    UpdateStyle();\n\n    //Set new window position from the provided rect\n    ::SetWindowPos(m_WindowHandle, nullptr, \n                   new_window_rect.left, \n                   new_window_rect.top, \n                   new_window_rect.right  - new_window_rect.left, \n                   new_window_rect.bottom - new_window_rect.top, SWP_NOZORDER | SWP_NOACTIVATE);\n\n    //Reload window icon in DPI-appropriate size. We don't really want to reload the big icon, but it gets reset to the small one if that one's changed, so we do both\n    HINSTANCE instance = (HINSTANCE)::GetWindowLongPtr(m_WindowHandle, GWLP_HINSTANCE);\n    const float new_scale = (float)new_dpi / USER_DEFAULT_SCREEN_DPI;\n\n    HICON icon_small = (HICON)LoadImage(instance, MAKEINTRESOURCE(IDI_DPLUS), IMAGE_ICON, int(GetSystemMetrics(SM_CXSMICON) * new_scale), int(GetSystemMetrics(SM_CYSMICON) * new_scale), LR_DEFAULTCOLOR);\n    HICON icon       = (HICON)LoadImage(instance, MAKEINTRESOURCE(IDI_DPLUS), IMAGE_ICON,     GetSystemMetrics(SM_CXICON),                    GetSystemMetrics(SM_CYICON),                LR_DEFAULTCOLOR);\n\n    HICON icon_prev_small = (HICON)::SendMessage(m_WindowHandle, WM_SETICON, ICON_SMALL, (LPARAM)icon_small);\n    HICON icon_prev       = (HICON)::SendMessage(m_WindowHandle, WM_SETICON, ICON_BIG,   (LPARAM)icon);\n\n    //Destroy the previous icons\n    ::DestroyIcon(icon_prev_small);\n    ::DestroyIcon(icon_prev);\n}\n\nvoid UIManager::OnTranslationChanged()\n{\n    TextureManager::Get().ReloadAllTexturesLater();\n\n    m_NotificationIcon.RefreshPopupMenu();\n    m_WindowSettings.ClearCachedTranslationStrings();\n    m_WindowOverlayProperties.SetActiveOverlayID(m_WindowOverlayProperties.GetActiveOverlayID(), true);\n\n    DPBrowserAPIClient::Get().DPBrowser_ErrorPageSetStrings(TranslationManager::GetString(tstr_BrowserErrorPageTitle), \n                                                            TranslationManager::GetString(tstr_BrowserErrorPageHeading), \n                                                            TranslationManager::GetString(tstr_BrowserErrorPageMessage));\n\n    RepeatFrame();\n}\n\nvoid UIManager::OnOverlayNameChanged()\n{\n    m_WindowOverlayProperties.SetActiveOverlayID(m_WindowOverlayProperties.GetActiveOverlayID(), true);\n\n    if ( (m_VRKeyboard.GetInputTarget() == kbdtarget_overlay) && (m_VRKeyboard.GetWindow().IsVisible()) )\n    {\n        m_VRKeyboard.GetWindow().Show();\n    }\n\n    RepeatFrame();\n}\n\nvoid UIManager::UpdateOverlayDimming()\n{\n    if ( (ConfigManager::GetValue(configid_bool_interface_dim_ui)) && (vr::VROverlay()->IsOverlayVisible(m_OvrlHandleDPlusDashboard)) )\n    {\n        for (const auto& overlay_handle : GetUIOverlayHandles())\n        {\n            vr::VROverlay()->SetOverlayColor(overlay_handle, 0.05f, 0.05f, 0.05f);\n        }\n\n        vr::VROverlay()->SetOverlayColor(m_OvrlHandleOverlayBar, 0.05f, 0.05f, 0.05f);\n        ImGui::GetStyle().Colors[ImGuiCol_WindowBg].w = 1.0f; //Set window bg alpha to 100% to not have the contrast be even worse on light backgrounds\n    }\n    else\n    {\n        for (const auto& overlay_handle : GetUIOverlayHandles())\n        {\n            vr::VROverlay()->SetOverlayColor(overlay_handle, 1.0f, 1.0f, 1.0f);\n        }\n        \n        ImGui::GetStyle().Colors[ImGuiCol_WindowBg].w = 0.96f;\n    }\n}\n\nbool UIManager::IsCompositorResolutionLow() const\n{\n    return m_LowCompositorRes;\n}\n\nbool UIManager::IsCompositorRenderQualityLow() const\n{\n    return m_LowCompositorQuality;\n}\n\nvoid UIManager::UpdateCompositorRenderQualityLow()\n{\n    if (!m_OpenVRLoaded)\n        return;\n\n    int compositor_quality = vr::VRSettings()->GetInt32(\"steamvr\", \"overlayRenderQuality_2\");\n    m_LowCompositorQuality = ((compositor_quality > 0) && (compositor_quality < 3)); //0 is Auto (not sure if the result of that is accessible), 3 is High\n\n    UpdateAnyWarningDisplayedState();\n}\n\nbool UIManager::IsAnyWarningDisplayed() const\n{\n    return m_HasAnyWarning;\n}\n\nvoid UIManager::UpdateAnyWarningDisplayedState()\n{\n    m_HasAnyWarning = false;\n    m_IdleState.AddActiveTime();    //Make sure any changes are reflected even while idling\n\n    //Check all possible warnings. This has to be in sync with what WindowSettings::UpdateWarnings() does to be correct.\n\n    //Compositor resolution warning\n    if ( (!ConfigManager::GetValue(configid_bool_interface_warning_compositor_res_hidden)) && (m_LowCompositorRes) )\n    {\n        m_HasAnyWarning = true;\n        return;\n    }\n\n    //Compositor quality warning\n    if ( (!ConfigManager::GetValue(configid_bool_interface_warning_compositor_quality_hidden)) && (m_LowCompositorQuality) )\n    {\n        m_HasAnyWarning = true;\n        return;\n    }\n\n    //Dashboard app process elevation warning\n    if ( (!ConfigManager::GetValue(configid_bool_interface_warning_process_elevation_hidden)) && (ConfigManager::GetValue(configid_bool_state_misc_process_elevated)) )\n    {\n        m_HasAnyWarning = true;\n        return;\n    }\n\n    //Elevated mode warning (this is different from elevated dashboard process)\n    if ( (!ConfigManager::GetValue(configid_bool_interface_warning_elevated_mode_hidden)) && (ConfigManager::GetValue(configid_bool_state_misc_elevated_mode_active)) )\n    {\n        m_HasAnyWarning = true;\n        return;\n    }\n\n    //Browser missing warning\n    if ( (!ConfigManager::GetValue(configid_bool_interface_warning_browser_missing_hidden)) && (ConfigManager::GetValue(configid_bool_state_misc_browser_used_but_missing)) )\n    {\n        m_HasAnyWarning = true;\n        return;\n    }\n\n    //Browser version mismatch warning\n    if ( (!ConfigManager::GetValue(configid_bool_interface_warning_browser_version_mismatch_hidden)) && (ConfigManager::GetValue(configid_bool_state_misc_browser_version_mismatch)) )\n    {\n        m_HasAnyWarning = true;\n        return;\n    }\n\n    //Focused process elevation warning\n    if (  (ConfigManager::GetValue(configid_bool_state_window_focused_process_elevated)) && (!ConfigManager::GetValue(configid_bool_state_misc_process_elevated)) && \n         (!ConfigManager::GetValue(configid_bool_state_misc_elevated_mode_active))       && (!ConfigManager::GetValue(configid_bool_state_misc_uiaccess_enabled)) )\n    {\n        m_HasAnyWarning = true;\n        return;\n    }\n\n    //UIAccess lost warning\n    if ( (ConfigManager::GetValue(configid_bool_misc_uiaccess_was_enabled)) && (!ConfigManager::GetValue(configid_bool_state_misc_uiaccess_enabled)) )\n    {\n        m_HasAnyWarning = true;\n        return;\n    }\n\n    //Overlay error warning\n    if ( (m_OverlayErrorLast != vr::VROverlayError_None) && (m_OpenVRLoaded) )\n    {\n        m_HasAnyWarning = true;\n        return;\n    }\n\n    //WinRT Capture error warning\n    if ( (m_WinRTErrorLast != S_OK) && (m_OpenVRLoaded) )\n    {\n        m_HasAnyWarning = true;\n        return;\n    }\n\n    //App profile with overlay profile active warning\n    if ( (!ConfigManager::GetRef(configid_bool_interface_warning_app_profile_active_hidden)) && (ConfigManager::Get().GetAppProfileManager().IsActiveProfileWithOverlayProfile()) )\n    {\n        m_HasAnyWarning = true;\n        return;\n    }\n\n    //App profile with overlay profile active warning\n    if (ConfigManager::GetValue(configid_bool_state_misc_config_migrated))\n    {\n        m_HasAnyWarning = true;\n        return;\n    }\n}\n\nvr::EVROverlayError UIManager::GetOverlayErrorLast() const\n{\n    return m_OverlayErrorLast;\n}\n\nHRESULT UIManager::GetWinRTErrorLast() const\n{\n    return m_WinRTErrorLast;\n}\n\nvoid UIManager::ResetOverlayErrorLast()\n{\n    m_OverlayErrorLast = vr::VROverlayError_None;\n}\n\nvoid UIManager::ResetWinRTErrorLast()\n{\n    m_WinRTErrorLast = S_OK;\n}\n\nbool UIManager::IsElevatedTaskSetUp() const\n{\n    return m_ElevatedTaskSetUp;\n}\n\nvoid UIManager::TryChangingWindowFocus() const\n{\n    //This is a non-exhaustive attempt to get a different window to set focus on, but it works in most cases\n    HWND window_top    = ::GetForegroundWindow();\n    HWND window_switch = nullptr;\n\n    //Try current VR app window first\n    if (m_OpenVRLoaded)\n    {\n        uint32_t pid = vr::VRApplications()->GetCurrentSceneProcessId();\n\n        if ( (pid != 0) && (!IsProcessElevated(pid)) )\n        {\n            window_switch = FindMainWindow(pid);\n        }\n    }\n\n    //Try getting the next window\n    if (window_switch == nullptr)\n    {\n        //Just use the capturable window list as a base, it's about what we want here anyways\n        auto& window_list = WindowManager::Get().WindowListGet();\n        auto it = std::find_if(window_list.begin(), window_list.end(), [&](const auto& info){ return (info.GetWindowHandle() == window_top); });\n\n        //Find the next window in that is not elevated\n        if (it != window_list.end())\n        {\n            for (++it; it != window_list.end(); ++it)\n            {\n                //Check if the window is also of an elevated process\n                DWORD process_id = 0;\n                ::GetWindowThreadProcessId(it->GetWindowHandle(), &process_id);\n\n                if (!IsProcessElevated(process_id))\n                {\n                    window_switch = it->GetWindowHandle();\n                    break;\n                }\n            }\n        }\n    }\n\n    //If no window was found fall back\n    if (window_switch == nullptr)\n    {\n        //Focusing the desktop as last resort works but can be awkward since the focus is not obvious and keyboard input could do unintended stuff\n        window_switch = ::GetShellWindow(); \n    }\n\n    //Dashboard app is more successful at changing focus for some reason, so let it try instead\n    IPCManager::Get().PostMessageToDashboardApp(ipcmsg_action, ipcact_focus_window, (LPARAM)window_switch);\n}\n\nbool UIManager::IsOverlayBarOverlayVisible() const\n{\n    return m_OvrlVisible;\n}\n\nvoid UIManager::GetDesktopOverlayPixelSize(int& width, int& height) const\n{\n    width  = m_OvrlPixelWidth;\n    height = m_OvrlPixelHeight;\n}\n\nvoid UIManager::UpdateDesktopOverlayPixelSize()\n{\n    //If OpenVR was loaded, get it from the overlay\n    if (m_OpenVRLoaded)\n    {\n        vr::VROverlayHandle_t ovrl_handle_dplus = vr::k_ulOverlayHandleInvalid;\n        vr::VROverlay()->FindOverlay(\"elvissteinjr.DesktopPlusDesktopTexture\", &ovrl_handle_dplus);\n\n        if (ovrl_handle_dplus != vr::k_ulOverlayHandleInvalid)\n        {\n            vr::HmdVector2_t mouse_scale;\n            vr::VROverlay()->GetOverlayMouseScale(ovrl_handle_dplus, &mouse_scale); //Mouse scale is pretty much the underlying pixel count\n\n            m_OvrlPixelWidth  = (int)mouse_scale.v[0];\n            m_OvrlPixelHeight = (int)mouse_scale.v[1];\n        }\n    }\n    else //What we get here may not reflect the real values, but let's do some good guesswork\n    {\n        int& desktop_id = ConfigManager::GetRef(configid_int_overlay_desktop_id);\n\n        if (desktop_id >= ConfigManager::GetValue(configid_int_state_interface_desktop_count))\n            desktop_id = -1;\n        else if (desktop_id == -2)  //-2 tell the dashboard application to crop it to desktop 0 and the value changes afterwards, though that doesn't happen when it's not running\n            desktop_id = 0;\n\n        if ( (desktop_id == -1) || (!ConfigManager::GetValue(configid_bool_performance_single_desktop_mirroring)) )   //All desktops, get virtual screen dimensions\n        {\n            m_OvrlPixelWidth  = GetSystemMetrics(SM_CXVIRTUALSCREEN);\n            m_OvrlPixelHeight = GetSystemMetrics(SM_CYVIRTUALSCREEN);\n        }\n        else    //Single desktop, try to get the screen resolution for it\n        {\n            DEVMODE mode = GetDevmodeForDisplayID(desktop_id, (ConfigManager::GetValue(configid_int_interface_wmr_ignore_vscreens) == 1));\n\n            if (mode.dmSize != 0)\n            {\n                m_OvrlPixelWidth  = mode.dmPelsWidth;\n                m_OvrlPixelHeight = mode.dmPelsHeight;\n            }\n        }\n    }\n}\n\nvoid UIManager::PositionOverlay()\n{\n    vr::VROverlay()->FindOverlay(\"elvissteinjr.DesktopPlusDashboard\", &m_OvrlHandleDPlusDashboard);\n\n    if (m_OvrlHandleDPlusDashboard != vr::k_ulOverlayHandleInvalid)\n    {\n        //Adjust behavior if gamepad ui (SteamVR 2 dashboard) exists\n        vr::VROverlayHandle_t handle_gamepad_ui = vr::k_ulOverlayHandleInvalid;\n        vr::VROverlay()->FindOverlay(\"valve.steam.gamepadui.bar\", &handle_gamepad_ui);\n\n        //Imagine if SetOverlayTransformOverlayRelative() actually worked\n        vr::HmdMatrix34_t matrix_ovr;\n        vr::TrackingUniverseOrigin origin = vr::TrackingUniverseStanding;\n\n        vr::VROverlay()->GetTransformForOverlayCoordinates(m_OvrlHandleDPlusDashboard, origin, { 0.5f, 0.0f }, &matrix_ovr);\n\n        //Adjust curve for dashboard position\n        float curve = 0.145f;\n\n        if (handle_gamepad_ui != vr::k_ulOverlayHandleInvalid)\n        {\n            curve = 0.19f;  //SteamVR 2 removed dashboard distance setting\n        }\n        else\n        {\n            int32_t dashboard_pos = vr::VRSettings()->GetInt32(vr::k_pch_Dashboard_Section, \"position_2\");\n\n            switch (dashboard_pos)\n            {\n                case 0: curve = 0.17f; break; //Near\n                case 1: curve = 0.16f; break; //Middle\n                case 2: curve = 0.15f; break; //Far\n            }\n        }\n\n        //Offset the overlay\n        //It's offset in so it's as close to the dashboard as possible while not messing up pointer input. \n        //Most problematic dashboard element is the current application button (SystemUI) and dashboard reposition bar (GamepadUI).\n        if (m_WindowOverlayBar.IsScrollBarVisible())\n        {\n            if (handle_gamepad_ui != vr::k_ulOverlayHandleInvalid)\n            {\n                vr::IVRSystemEx::TransformOpenVR34TranslateRelative(matrix_ovr, 0.0f, -0.247f, 0.400f);\n            }\n            else\n            {\n                vr::IVRSystemEx::TransformOpenVR34TranslateRelative(matrix_ovr, 0.0f, -0.272f, 0.195f);\n            }\n        }\n        else\n        {\n            if (handle_gamepad_ui != vr::k_ulOverlayHandleInvalid)\n            {\n                vr::IVRSystemEx::TransformOpenVR34TranslateRelative(matrix_ovr, 0.0f, -0.200f, 0.390f);\n            }\n            else\n            {\n                vr::IVRSystemEx::TransformOpenVR34TranslateRelative(matrix_ovr, 0.0f, -0.225f, 0.185f);\n            }\n        }\n\n        //SteamVR 2.15.1 made changes that affected offsets needed for Overlay Bar positioning and curvature\n        //While this change is only in the beta branch, we conditionally adjust properties on 2.15 runtimes to get back to the old position\n        const bool use_additional_offset = (strstr(vr::VRSystem()->GetRuntimeVersion(), \"2.15\") != nullptr);\n        if (use_additional_offset)\n        {\n            vr::IVRSystemEx::TransformOpenVR34TranslateRelative(matrix_ovr, 0.0f, -0.126f, 0.070f);\n\n            vr::VROverlay()->SetOverlayPreCurvePitch(m_OvrlHandleOverlayBar, 0.20f);\n            curve = 0.1725f;\n        }\n\n        //Rotate slightly forward (local rotation)\n        Matrix4 mat_m4;                 //is identity\n        mat_m4.rotateX(-14.0f);\n        mat_m4 = Matrix4(matrix_ovr) * mat_m4;\n        matrix_ovr = mat_m4.toOpenVR34();\n\n        //Try to reduce flicker by blocking abrupt Y movements (unless X has changed as well, which we assume to happen on real movement)\n        //The flicker itself comes from a race condition of the UI possibly getting the overlay transform while it's changing width and position, hard to predict\n        bool anti_flicker_can_move = false;\n        float anti_flicker_x = matrix_ovr.m[0][3];\n        float anti_flicker_y = matrix_ovr.m[1][3];\n        static float anti_flicker_x_last = anti_flicker_x;\n        static float anti_flicker_y_last = anti_flicker_y;\n        static int anti_flicker_block_count = 0;\n\n        const bool anti_flicker_moved_any = ((anti_flicker_x != anti_flicker_x_last) || (anti_flicker_y != anti_flicker_y_last));\n        if ( (anti_flicker_x != anti_flicker_x_last) || (fabs(anti_flicker_y - anti_flicker_y_last) < 0.001f) || (anti_flicker_block_count >= 2) )\n        {\n            anti_flicker_can_move = true;\n            anti_flicker_x_last = anti_flicker_x;\n            anti_flicker_y_last = anti_flicker_y;\n            anti_flicker_block_count = 0;\n        }\n        else\n        {\n            anti_flicker_block_count++;\n        }\n\n        //Block transform updates on abrupt movements, repeated frames, mouse down and shortly after switching desktops\n        m_IsDummyOverlayTransformUnstable = ( (!anti_flicker_can_move) || (GetRepeatFrame()) || (ImGui::IsMouseDown(ImGuiMouseButton_Left)) || \n                                              (ImGui::GetIO().MouseClickedTime[ImGuiMouseButton_Left] + 0.3 >= ImGui::GetTime()) ||\n                                              (m_FloatingUI.GetActionBarWindow().GetLastDesktopSwitchTime() + 0.3 >= ImGui::GetTime()) ); \n\n        if (!m_IsDummyOverlayTransformUnstable)\n        {\n            vr::VROverlay()->SetOverlayTransformAbsolute(m_OvrlHandleOverlayBar, origin, &matrix_ovr);\n            vr::VROverlay()->SetOverlayCurvature(m_OvrlHandleOverlayBar, curve);\n        }\n\n        //Set visibility\n        if (vr::VROverlay()->IsOverlayVisible(m_OvrlHandleDPlusDashboard))\n        {\n            bool is_systemui_hovered = ConfigManager::Get().IsLaserPointerTargetOverlay(m_OvrlHandleSystemUI);\n\n            //Check for GamepadUI as well if it exists\n            if (handle_gamepad_ui != vr::k_ulOverlayHandleInvalid)\n            {\n                is_systemui_hovered = (is_systemui_hovered || ConfigManager::Get().IsLaserPointerTargetOverlay(handle_gamepad_ui));\n            }\n\n            if (!m_OvrlVisible)\n            {\n                vr::VROverlay()->ShowOverlay(m_OvrlHandleOverlayBar);\n                m_WindowOverlayBar.Show();\n                m_OvrlVisible = true;\n                UpdateOverlayDimming();\n                m_OverlayDragger.UpdateTempStandingPosition();\n\n                //We prevent the fade-out when Overlay Bar is newly visible while the dashboard SystemUI is being hovered until the pointer leaves that overlay at least once\n                if (is_systemui_hovered)\n                {\n                    m_IsSystemUIHoveredFromSwitch = true;\n                }\n            }\n            else\n            {\n                //Fade out Overlay Bar when the dashboard SystemUI is being used as overlay z-order isn't fine-grained enough for it to just work\n                if ( (!m_IsSystemUIHoveredFromSwitch) && (is_systemui_hovered) )\n                {\n                    m_OverlayBarFadeInTick = 0;\n\n                    if (m_SystemUIActiveTick == 0)\n                    {\n                        m_SystemUIActiveTick = ::GetTickCount64();\n                    }\n                    else\n                    {\n                        unsigned int delay = (m_WindowOverlayBar.IsAnyMenuVisible()) ? 800 : 300;\n\n                        if (m_SystemUIActiveTick + delay < ::GetTickCount64())\n                        {\n                            if (m_OvrlOverlayBarAlpha != 0.0f)\n                            {\n                                m_OvrlOverlayBarAlpha -= ImGui::GetIO().DeltaTime * 12.0f;\n\n                                if (m_OvrlOverlayBarAlpha < 0.0f)\n                                    m_OvrlOverlayBarAlpha = 0.0f;\n\n                                vr::VROverlay()->SetOverlayAlpha(m_OvrlHandleOverlayBar, m_OvrlOverlayBarAlpha);\n                            }\n                            else if (vr::VROverlay()->IsOverlayVisible(m_OvrlHandleOverlayBar))\n                            {\n                                m_WindowOverlayBar.HideMenus();\n                                vr::VROverlay()->HideOverlay(m_OvrlHandleOverlayBar); //Hide to avoid input flicker\n                            }\n                        }\n                    }\n                }\n                else if (!anti_flicker_moved_any)   //Only fade-in again if the dashboard isn't moving around to avoid repeated fades while the dashboard is being dragged\n                {\n                    m_SystemUIActiveTick = 0;\n\n                    //Add a small delay before fading in again so the grab handle can potentially be hovered before this happens\n                    if ((m_OverlayBarFadeInTick == 0) && (!m_IsSystemUIHoveredFromSwitch))\n                    {\n                        m_OverlayBarFadeInTick = ::GetTickCount64();\n                        m_IdleState.AddActiveTime();\n                    }\n\n                    if (m_OverlayBarFadeInTick + 300 < ::GetTickCount64())\n                    {\n                        if (m_OvrlOverlayBarAlpha != 1.0f)\n                        {\n                            m_OvrlOverlayBarAlpha += ImGui::GetIO().DeltaTime * 12.0f;\n\n                            if (m_OvrlOverlayBarAlpha > 1.0f)\n                                m_OvrlOverlayBarAlpha = 1.0f;\n\n                            vr::VROverlay()->SetOverlayAlpha(m_OvrlHandleOverlayBar, m_OvrlOverlayBarAlpha);\n                            vr::VROverlay()->ShowOverlay(m_OvrlHandleOverlayBar);\n                        }\n                        else if (!is_systemui_hovered)\n                        {\n                            m_IsSystemUIHoveredFromSwitch = false;\n                        }\n                    }\n                }\n            }\n        }\n        else\n        {\n            if (m_OvrlVisible)\n            {\n                if (m_WindowOverlayBar.IsVisible())\n                {\n                    m_WindowOverlayBar.HideMenus();\n                    m_WindowOverlayBar.Hide();\n\n                    m_IdleState.AddActiveTime();\n                }\n                else if (!m_WindowOverlayBar.IsVisibleOrFading()) //Wait for window fade-out to finish before hiding the overlay\n                {\n                    vr::VROverlay()->HideOverlay(m_OvrlHandleOverlayBar);\n                    m_OvrlVisible = false;\n\n                    UpdateOverlayDimming();\n                }\n            }\n        }\n    }\n    else if (m_OvrlVisible) //Dashboard overlay has gone missing, hide\n    {\n        vr::VROverlay()->HideOverlay(m_OvrlHandleOverlayBar);\n        m_WindowOverlayBar.Hide();\n\n        m_OvrlVisible = false;\n    }\n}\n\nvoid UIManager::UpdateOverlayDrag()\n{\n    if (m_OverlayDragger.IsDragActive())\n    {\n        m_OverlayDragger.DragUpdate();\n\n        vr::VROverlayHandle_t drag_overlay_handle = m_OverlayDragger.GetDragOverlayHandle();\n\n        if (ImGui::IsMouseReleased(ImGuiMouseButton_Left))\n        {\n            FinishOverlayDrag();\n            return;\n        }\n\n        ImGuiIO& io = ImGui::GetIO();\n\n        //Wheel input (add distance & add width)\n        float hwheel_abs = fabs(io.MouseWheelH);\n        float ywheel_abs = fabs(io.MouseWheel);\n\n        //Deadzone\n        if ((hwheel_abs > 0.05f) || (ywheel_abs > 0.05f))\n        {\n            //Add distance as long as y-delta input is bigger\n            if (hwheel_abs < ywheel_abs)\n            {\n                m_OverlayDragger.DragAddDistance(io.MouseWheel * 0.5f);\n            }\n            else\n            {\n                m_OverlayDragger.DragAddWidth(io.MouseWheelH * -0.15f);\n            }\n        }\n\n        //Prevent widget input during active drag, except for the keyboard where this doesn't matter and would unfocus active InputTexts\n        if (drag_overlay_handle != m_OvrlHandleKeyboard)\n        {\n            ImGui::BlockWidgetInput();\n        }\n    }\n    else if (m_OverlayDragger.IsDragGestureActive())\n    {\n        m_OverlayDragger.DragGestureUpdate();\n\n        if (!ImGui::IsMouseDown(ImGuiMouseButton_Right))\n        {\n            vr::VROverlayHandle_t drag_overlay_handle = m_OverlayDragger.GetDragOverlayHandle();\n\n            Matrix4 matrix_relative_offset = m_OverlayDragger.DragGestureFinish();\n            float new_width = 1.0f;\n            vr::VROverlay()->GetOverlayWidthInMeters(drag_overlay_handle, &new_width);\n\n            //Store changed transform to the previously dragged overlay handle and update width/size config value\n            if (drag_overlay_handle == m_OvrlHandleSettings)\n            {\n                m_WindowSettings.SetTransform(matrix_relative_offset);\n            }\n            else if (drag_overlay_handle == m_OvrlHandleOverlayProperties)\n            {\n                m_WindowOverlayProperties.SetTransform(matrix_relative_offset);\n            }\n            else if (drag_overlay_handle == m_OvrlHandleKeyboard)\n            {\n                m_VRKeyboard.GetWindow().SetTransform(matrix_relative_offset);\n                m_VRKeyboard.GetWindow().RebaseTransform();\n            }\n        }\n        else\n        {\n            //Prevent widget input during active drag\n            ImGui::BlockWidgetInput();\n        }\n    }\n}\n\nvoid UIManager::StartOverlayDrag(vr::VROverlayHandle_t overlay_handle)\n{\n    if (overlay_handle == m_OvrlHandleSettings)\n    {\n        m_WindowSettings.StartDrag();\n    }\n    else if (overlay_handle == m_OvrlHandleOverlayProperties)\n    {\n        m_WindowOverlayProperties.StartDrag();\n    }\n    else if (overlay_handle == m_OvrlHandleKeyboard)\n    {\n        m_VRKeyboard.GetWindow().StartDrag();\n    }\n}\n\nvoid UIManager::FinishOverlayDrag()\n{\n    vr::VROverlayHandle_t drag_overlay_handle = m_OverlayDragger.GetDragOverlayHandle();\n    Matrix4 matrix_relative_offset = m_OverlayDragger.DragFinish();\n\n    //Store changed transform to the previously dragged overlay handle\n    if (drag_overlay_handle == m_OvrlHandleSettings)\n    {\n        m_WindowSettings.SetTransform(matrix_relative_offset);\n    }\n    else if (drag_overlay_handle == m_OvrlHandleOverlayProperties)\n    {\n        m_WindowOverlayProperties.SetTransform(matrix_relative_offset);\n    }\n    else if (drag_overlay_handle == m_OvrlHandleKeyboard)\n    {\n        m_VRKeyboard.GetWindow().SetTransform(matrix_relative_offset);\n        m_VRKeyboard.GetWindow().RebaseTransform();\n    }\n}\n\nvoid UIManager::HighlightOverlay(unsigned int overlay_id)\n{\n    //Indicate the current overlay by tinting it when hovering the overlay selector\n    if (m_OpenVRLoaded)\n    {\n        static vr::VROverlayHandle_t colored_handle = vr::k_ulOverlayHandleInvalid;\n\n        vr::VROverlayHandle_t ovrl_handle = OverlayManager::Get().GetConfigData(overlay_id).ConfigHandle[configid_handle_overlay_state_overlay_handle];;\n\n        //Tint overlay if no other overlay is currently tinted (adds one frame delay on switching but it doesn't really matter)\n        if ( (ovrl_handle != vr::k_ulOverlayHandleInvalid) && (colored_handle == vr::k_ulOverlayHandleInvalid) )\n        {\n            const OverlayConfigData& data = OverlayManager::Get().GetConfigData((unsigned int)overlay_id);\n            float brightness = lin2log(data.ConfigFloat[configid_float_overlay_brightness]) * data.ConfigFloat[configid_float_overlay_state_brightness_extra_multiplier];\n\n            ImVec4 col = ImGui::GetStyleColorVec4(ImGuiCol_ButtonHovered);\n\n            vr::VROverlay()->SetOverlayColor(ovrl_handle, col.x * brightness, col.y * brightness, col.z * brightness);\n\n            colored_handle = ovrl_handle;\n        }\n        else if ( (colored_handle != vr::k_ulOverlayHandleInvalid) && (colored_handle != ovrl_handle) ) //Remove tint if overlay handle is different or vr::k_ulOverlayHandleInvalid\n        {\n            const OverlayConfigData& data = OverlayManager::Get().GetConfigData(OverlayManager::Get().FindOverlayID(colored_handle));\n            float brightness = lin2log(data.ConfigFloat[configid_float_overlay_brightness]) * data.ConfigFloat[configid_float_overlay_state_brightness_extra_multiplier];\n\n            vr::VROverlay()->SetOverlayColor(colored_handle, brightness, brightness, brightness);\n\n            colored_handle = vr::k_ulOverlayHandleInvalid;\n        }\n    }\n}\n\nfloat UIManager::GetOverlayHeight(vr::VROverlayHandle_t overlay_handle) const\n{\n    //This only checks for overlays that are draggable by the UI\n    float overlay_width = 1.0f;\n    vr::VROverlay()->GetOverlayWidthInMeters(overlay_handle, &overlay_width);\n\n    const UITexspaceID overlay_texspace = GetTexspaceIDForOverlayHandle(overlay_handle);\n    if (overlay_texspace == ui_texspace_total)\n    {\n        return -1.0f;\n    }\n\n    const DPRect& rect_tex = UITextureSpaces::Get().GetRect(overlay_texspace);\n    return ((float)rect_tex.GetHeight() / (float)rect_tex.GetWidth()) * overlay_width;\n}\n\nMatrix4 UIManager::GetOverlay2DPointTransform(Vector2 point_2d, vr::VROverlayHandle_t overlay_handle) const\n{\n    //GetTransformForOverlayCoordinates() appears to be pretty much just offset from the center point of the overlay (well, that is the 0-point for the transform position anyways)\n    //It doesn't account for UV coordinates though, so it can be a bit messy to set up. That's why it's wrapped up for UI overlay usage\n    //The 2D coordinates it expects are also in GL-space (y+ is up)\n    const DPRect& rect_tex = UITextureSpaces::Get().GetRect( GetTexspaceIDForOverlayHandle(overlay_handle) );\n\n    float tex_y = UITextureSpaces::Get().GetRect(ui_texspace_total).GetHeight() / 2.0f; //Middle of overlay (UV is unaccounted for, so half of total texture height)\n    tex_y += rect_tex.GetHeight() / 2.0f;                                               //Offset from the middle however, so adding half of the texspace height gets us to the zero top point\n    tex_y -= point_2d.y;                                                                //Then we can substract the passed coordinate (not adding as y+ is up)\n\n    //Now we can get the coordinates as intended\n    vr::HmdMatrix34_t hmd_mat = {};\n    vr::VROverlay()->GetTransformForOverlayCoordinates(overlay_handle, vr::TrackingUniverseStanding, {rect_tex.GetTL().x + point_2d.x, tex_y}, &hmd_mat);\n\n    //Returned matrix usually contains dashboard scale, so undo it\n    Vector3 row_1(hmd_mat.m[0][0], hmd_mat.m[1][0], hmd_mat.m[2][0]);\n    Matrix4 mat(hmd_mat);\n    Vector3 pos = mat.getTranslation();\n    mat.setTranslation({0.0f, 0.0f, 0.0f});\n    mat.scale(1.0f / row_1.length());\n    mat.setTranslation(pos);\n\n    return mat;\n}\n\nvoid UIManager::TriggerLaserPointerHaptics(vr::VROverlayHandle_t overlay_handle, vr::TrackedDeviceIndex_t device_index) const\n{\n    if (!m_OpenVRLoaded)\n        return;\n\n    //Trigger directly when dashboard pointer is active as it's going to be the right device anyways\n    if ( (device_index == vr::k_unTrackedDeviceIndexInvalid) && (vr::IVROverlayEx::IsSystemLaserPointerActive()) )\n    {\n        vr::VROverlay()->TriggerLaserMouseHapticVibration(overlay_handle, 0.0f, 1.0f, 0.16f);\n    }\n    else  //Let dashboard app handle it as we'll need VR Input access\n    {\n        //If no device specified, get the primary laser pointer one\n        if (device_index == vr::k_unTrackedDeviceIndexInvalid)\n        {\n            device_index = ConfigManager::Get().GetPrimaryLaserPointerDevice();\n        }\n\n        IPCManager::Get().PostMessageToDashboardApp(ipcmsg_action, ipcact_lpointer_trigger_haptics, device_index);\n    }\n}\n"
  },
  {
    "path": "src/DesktopPlusUI/UIManager.h",
    "content": "//Manages the UI state and stuffs\n//Since this whole thing is the UI, it's the default place for things\n//Basic ImGui and rendering backend implementation is left out of it, though\n\n#pragma once\n\n#define NOMINMAX\n#include <windows.h>\n#include <d3d11.h>\n#include <wrl/client.h>\n\n#include <array>\n\n#include \"openvr.h\"\n#include \"Matrices.h\"\n#include \"DPRect.h\"\n\n#include \"Logging.h\"\n#include \"NotificationIcon.h\"\n#include \"OverlayDragger.h\"\n#include \"FloatingUI.h\"\n#include \"AuxUI.h\"\n#include \"VRKeyboard.h\"\n#include \"WindowOverlayBar.h\"\n#include \"WindowSettings.h\"\n#include \"WindowOverlayProperties.h\"\n#include \"WindowPerformance.h\"\n\n//Overlay width definitions\n#define OVERLAY_WIDTH_METERS_DASHBOARD_UI 2.75f\n#define OVERLAY_WIDTH_METERS_SETTINGS 1.5f\n#define OVERLAY_WIDTH_METERS_KEYBOARD 2.75f\n#define OVERLAY_WIDTH_METERS_AUXUI_DRAG_HINT 0.12f\n#define OVERLAY_WIDTH_METERS_AUXUI_GAZEFADE_AUTO_HINT 0.75f\n#define OVERLAY_WIDTH_METERS_AUXUI_WINDOW_SELECT 0.75f\n#define OVERLAY_WIDTH_METERS_AUXUI_WINDOW_QUICKSTART 0.75f\n\nenum UITexspaceID\n{\n    ui_texspace_total,\n    ui_texspace_overlay_bar,\n    ui_texspace_floating_ui,\n    ui_texspace_settings,\n    ui_texspace_overlay_properties,\n    ui_texspace_keyboard,\n    ui_texspace_performance_monitor,\n    ui_texspace_aux_ui,\n    ui_texspace_MAX\n};\n\nclass UITextureSpaces\n{\n    private:\n        DPRect m_TexspaceRects[ui_texspace_MAX];\n\n    public:\n        static UITextureSpaces& Get();\n\n        void Init(bool desktop_mode, bool keyboard_editor_mode);\n        const DPRect& GetRect(UITexspaceID texspace_id) const;\n        ImVec4 GetRectAsVec4(UITexspaceID texspace_id) const;\n};\n\nstatic const char* const k_pch_bold_exclamation_mark = \"\\xE2\\x9D\\x97\";\nstatic const char* const k_pch_degree_symbol = \"\\xc2\\xb0\";\n\nclass UIManager\n{\n    public:\n        class IdleState\n        {\n            private:\n                static const unsigned int s_MsBeforeIdle = 1000;    //A general 1 second buffer before idling saves us from adding active time for every short animation\n\n                ULONGLONG m_LastActiveTick = 0;\n                ULONGLONG m_LastCaretTick  = 0;\n                POINT m_LastCursorPos{0};\n                bool m_WasIdleLastFrame = false;\n\n                LARGE_INTEGER m_TimestepTime{0};\n                LARGE_INTEGER m_TimestepTicks{0};\n\n                void SetLastActiveTick(ULONGLONG value);\n\n            public:\n                IdleState();\n\n                bool ShouldIdle();\n                int GetFrameSkipValue();\n\n                void OnImGuiNewFrame();\n                void OnWindowMessage(UINT message_id);\n                void OnOpenVREvent(uint32_t event_type);\n\n                void AddActiveTime(unsigned int ms = s_MsBeforeIdle);\n                void DoIdleTimestep();\n        };\n\n    private:\n        FloatingUI m_FloatingUI;\n        VRKeyboard m_VRKeyboard;\n        AuxUI m_AuxUI;\n        WindowOverlayBar m_WindowOverlayBar;\n        WindowSettings m_WindowSettings;\n        WindowOverlayProperties m_WindowOverlayProperties;\n        WindowPerformance m_WindowPerformance;\n        WindowDesktopMode m_WindowDesktopMode;\n\n        HWND m_WindowHandle;\n        NotificationIcon m_NotificationIcon;\n        OverlayDragger m_OverlayDragger;\n        Microsoft::WRL::ComPtr<ID3D11Resource> m_SharedTextureRef; //Pointer to render target texture, should only be used for calls to SetSharedOverlayTexture()\n\n        IdleState m_IdleState;\n        int m_RepeatFrame;\n\n        bool m_DesktopMode;\n        bool m_KeyboardEditorMode;\n        bool m_OpenVRLoaded;         //Desktop mode can run with or without OpenVR and we want to avoid needlessly starting up SteamVR\n        bool m_NoRestartOnExit;      //Prevent auto-restart when closing from desktop mode while dashboard app is running (i.e. when using troubleshooting buttons)\n\n        float m_UIScale;\n        ImFont* m_FontCompact;\n        ImFont* m_FontLarge;         //Only loaded when the UI scale is set to large\n\n        bool m_LowCompositorRes;     //Set when compositor's resolution is set below 100% during init. The user is warned about this as it affects overlay rendering\n        bool m_LowCompositorQuality; //Set when the compositor's quality setting to set to something other than High or Auto\n        bool m_HasAnyWarning;        //Set when there's any warning (by calling UpdateWarningState)\n        vr::EVROverlayError m_OverlayErrorLast; //Last encountered error when adding an overlay (usually just overlay limit exceeded)\n        HRESULT m_WinRTErrorLast;    //Last encountered error when a Graphics Capture thread crashed (ideally never happens)\n\n        bool m_ElevatedTaskSetUp;\n        bool m_ComInitDone;\n\n        vr::VROverlayHandle_t m_OvrlHandleOverlayBar;\n        vr::VROverlayHandle_t m_OvrlHandleFloatingUI;\n        vr::VROverlayHandle_t m_OvrlHandleSettings;\n        vr::VROverlayHandle_t m_OvrlHandleOverlayProperties;\n        vr::VROverlayHandle_t m_OvrlHandleKeyboard;\n        vr::VROverlayHandle_t m_OvrlHandleAuxUI;\n        vr::VROverlayHandle_t m_OvrlHandleDPlusDashboard;   //Cached to minimize FindOverlay() calls\n        vr::VROverlayHandle_t m_OvrlHandleSystemUI;         //Cached to minimize FindOverlay() calls\n        bool m_OvrlVisible;\n\n        float m_OvrlOverlayBarAlpha;\n        ULONGLONG m_SystemUIActiveTick;\n        ULONGLONG m_OverlayBarFadeInTick;\n        bool m_IsSystemUIHoveredFromSwitch;     //Set when the dashboard was hovered when dashboard tab was switched to prevent the UI overlay fading out right away\n        bool m_IsDummyOverlayTransformUnstable;\n\n        //Dimensions of the mirror texture, updated from OpenVR when opening up settings or receiving a resolution update message from the overlay application\n        int m_OvrlPixelWidth;\n        int m_OvrlPixelHeight;\n\n        unsigned int m_TransformSyncValueCount;\n        float m_TransformSyncValues[16];        //Stores transform sync values until all are set for a full Matrix4\n\n        std::vector<MSG> m_DelayedICPMessages;  //Stores ICP messages that need to be delayed for processing within an ImGui frame\n\n        void DisplayDashboardAppError(const std::string& str);\n        void DisplayInitialSetupNotification();\n        void SetOverlayInputEnabled(bool is_enabled);\n\n        UITexspaceID GetTexspaceIDForOverlayHandle(vr::VROverlayHandle_t overlay_handle) const;\n\n        void HandleOverlayProfileLoadMessage(LPARAM lparam);\n\n    public:\n        static UIManager* Get();\n\n        UIManager(bool desktop_mode, bool keyboard_editor_mode);\n        ~UIManager();\n\n        vr::EVRInitError InitOverlay();\n        void HandleIPCMessage(const MSG& msg, bool handle_delayed = false); //Messages that need processing within an ImGui frame are stored in m_DelayedICPMessages when handle_delayed is false\n        void HandleDelayedIPCMessages();                                    //Calls HandleIPCMessage() for messages in m_DelayedICPMessages and clears it\n        bool HasDelayedIPCMessages() const;\n\n        void OnInitDone();                                                  //Finishes up applying things that can only be applied after everything has finished loading\n        void OnExit();\n        void OnProfileLoaded();\n\n        FloatingUI& GetFloatingUI();\n        VRKeyboard& GetVRKeyboard();\n        AuxUI& GetAuxUI();\n        WindowOverlayBar& GetOverlayBarWindow();\n        WindowSettings& GetSettingsWindow();\n        WindowOverlayProperties& GetOverlayPropertiesWindow();\n        WindowPerformance& GetPerformanceWindow();\n        WindowDesktopMode& GetDesktopModeWindow();\n\n        void SetWindowHandle(HWND handle);\n        HWND GetWindowHandle() const;\n        NotificationIcon& GetNotificationIcon();\n        void SetSharedTextureRef(ID3D11Resource* ref);\n        ID3D11Resource* GetSharedTextureRef() const;\n        OverlayDragger& GetOverlayDragger();\n\n        vr::VROverlayHandle_t GetOverlayHandleOverlayBar()         const;\n        vr::VROverlayHandle_t GetOverlayHandleFloatingUI()         const;\n        vr::VROverlayHandle_t GetOverlayHandleSettings()           const;\n        vr::VROverlayHandle_t GetOverlayHandleOverlayProperties()  const;\n        vr::VROverlayHandle_t GetOverlayHandleKeyboard()           const;\n        vr::VROverlayHandle_t GetOverlayHandleAuxUI()              const;\n        vr::VROverlayHandle_t GetOverlayHandleDPlusDashboard()     const;\n        vr::VROverlayHandle_t GetOverlayHandleSystemUI()           const;\n        std::array<vr::VROverlayHandle_t, 6> GetUIOverlayHandles() const;\n        bool IsDummyOverlayTransformUnstable() const;\n        void SendUIIntersectionMaskToDashboardApp(std::vector<vr::VROverlayIntersectionMaskPrimitive_t>& primitives) const;\n\n        IdleState& GetIdleState();\n        DPRect CalcRectForActiveTexspace();\n\n        //This can be called by functions knowingly making changes which will cause visible layout re-alignment due to ImGui's nature of intermediate UI\n        //This will cause 2 extra frames to be calculated but thrown away instantly to be more pleasing to the eye. In rare cases more are needed and can be specified instead\n        void RepeatFrame(int extra_frame_count = 2); \n        bool GetRepeatFrame() const;\n        void DecreaseRepeatFrameCount();\n\n        bool IsInDesktopMode() const;\n        bool IsInKeyboardEditorMode() const;\n        bool IsOpenVRLoaded() const;\n        void DisableRestartOnExit();\n\n        void Restart(bool desktop_mode);\n        void RestartIntoKeyboardEditor();\n        void RestartDashboardApp(bool force_steam = false);\n        void ElevatedModeEnter();\n        void ElevatedModeLeave();\n\n        void UpdateStyle();\n        void SetUIScale(float scale);\n        float GetUIScale() const;\n        void SetFonts(ImFont* font_compact, ImFont* font_large);\n        ImFont* GetFontCompact() const;\n        ImFont* GetFontLarge() const;               //May return nullptr\n        void AddFontBuilderStringIfAnyUnmappedCharacters(const char* str);  //Checks if string has unmapped characters and schedules a texture refresh to load them on the next frame\n        void OnDPIChanged(int new_dpi, const RECT& new_window_rect);\n        void OnTranslationChanged();                //Calls functions in several classes to reset translation-dependent cached strings. Called when translation is changed\n        void OnOverlayNameChanged();                //Calls functions in several classes to reset overlay-name-dependent cached strings. Called when overlay names changed\n        void UpdateOverlayDimming();\n\n        bool IsCompositorResolutionLow() const;\n        bool IsCompositorRenderQualityLow() const;\n        void UpdateCompositorRenderQualityLow();\n        bool IsAnyWarningDisplayed() const;\n        void UpdateAnyWarningDisplayedState();\n        vr::EVROverlayError GetOverlayErrorLast() const;\n        HRESULT GetWinRTErrorLast() const;\n        void ResetOverlayErrorLast();\n        void ResetWinRTErrorLast();\n        bool IsElevatedTaskSetUp() const;\n        void TryChangingWindowFocus() const;\n\n        bool IsOverlayBarOverlayVisible() const;\n\n        void GetDesktopOverlayPixelSize(int& width, int& height) const;\n        void UpdateDesktopOverlayPixelSize();\n\n        void PositionOverlay();\n        void UpdateOverlayDrag();\n        void StartOverlayDrag(vr::VROverlayHandle_t overlay_handle);\n        void FinishOverlayDrag();\n        void HighlightOverlay(unsigned int overlay_id);\n        float GetOverlayHeight(vr::VROverlayHandle_t overlay_handle) const;\n        Matrix4 GetOverlay2DPointTransform(Vector2 point_2d, vr::VROverlayHandle_t overlay_handle) const; //GetTransformForOverlayCoordinates() but works with cropped UVs (UI overlays only)\n\n        //Invalid device index triggers on current primary device\n        void TriggerLaserPointerHaptics(vr::VROverlayHandle_t overlay_handle, vr::TrackedDeviceIndex_t device_index = vr::k_unTrackedDeviceIndexInvalid) const;\n};"
  },
  {
    "path": "src/DesktopPlusUI/VRKeyboard.cpp",
    "content": "#include \"VRKeyboard.h\"\n\n#include <sstream>\n#include \"UIManager.h\"\n#include \"InterprocessMessaging.h\"\n#include \"Util.h\"\n#include \"Ini.h\"\n#include \"DPBrowserAPIClient.h\"\n\n#include \"imgui_internal.h\"\n#include \"imgui_impl_win32_openvr.h\"\n\nstatic const char* const g_KeyboardSublayoutNames[kbdlayout_sub_MAX]    = {\"Base\", \"Shift\", \"AltGr\", \"Aux\"};\nstatic const char* const g_KeyboardClusterNames[kbdlayout_cluster_MAX]  = {\"Base\", \"Function\", \"Navigation\", \"Numpad\", \"Extra\"};\nstatic const char* const g_KeyboardKeyTypeNames[kbdlayout_key_MAX]      = {\"Blank\", \"VirtualKey\", \"VirtualKeyToggle\", \"VirtualKeyIsoEnter\", \"String\", \"SubLayoutToggle\", \"Action\"};\n\nVRKeyboard::VRKeyboard() : \n    m_InputTarget(kbdtarget_desktop),\n    m_InputTargetOverlayID(k_ulOverlayID_None),\n    m_CapsLockToggled(false),\n    m_ActiveInputText(0),\n    m_InputBeginWidgetID(0),\n    m_ShortcutWindowDirHint(ImGuiDir_Down),\n    m_ShortcutWindowYOffset(0.0f),\n    m_ActiveInputTextIsMultiline(false),\n    m_MouseLeftDownPrevCached(false),\n    m_MouseLeftClickedPrevCached(false),\n    m_KeyboardHiddenLastFrame(false)\n{\n    std::fill_n(m_KeyDown, IM_ARRAYSIZE(m_KeyDown), 0);\n}\n\nunsigned char VRKeyboard::GetModifierFlags() const\n{\n    unsigned char flags = 0;\n    if (m_KeyDown[VK_LSHIFT])   { flags |= kbd_keystate_flag_lshift_down; }\n    if (m_KeyDown[VK_RSHIFT])   { flags |= kbd_keystate_flag_rshift_down; }\n    if (m_KeyDown[VK_LCONTROL]) { flags |= kbd_keystate_flag_lctrl_down;  }\n    if (m_KeyDown[VK_RCONTROL]) { flags |= kbd_keystate_flag_rctrl_down;  }\n    if (m_KeyDown[VK_LMENU])    { flags |= kbd_keystate_flag_lalt_down;   }\n    if (m_KeyDown[VK_RMENU])    { flags |= (m_LayoutMetadata.HasAltGr) ? (kbd_keystate_flag_ralt_down | kbd_keystate_flag_lctrl_down) : kbd_keystate_flag_ralt_down; }\n    if (m_CapsLockToggled)      { flags |= kbd_keystate_flag_capslock_toggled; }\n\n    return flags;\n}\n\nvr::VROverlayHandle_t VRKeyboard::GetTargetOverlayHandle() const\n{\n    if (m_InputTargetOverlayID != k_ulOverlayID_None)\n    {\n        return OverlayManager::Get().GetConfigData(m_InputTargetOverlayID).ConfigHandle[configid_handle_overlay_state_overlay_handle];\n    }\n\n    return vr::k_ulOverlayHandleInvalid;\n}\n\nWindowKeyboard& VRKeyboard::GetWindow()\n{\n    return m_WindowKeyboard;\n}\n\nKeyboardEditor& VRKeyboard::GetEditor()\n{\n    return m_KeyboardEditor;\n}\n\nvoid VRKeyboard::LoadLayoutFromFile(const std::string& filename)\n{\n    std::string fullpath = ConfigManager::Get().GetApplicationPath() + \"keyboards/\" + filename;\n    Ini layout_file( WStringConvertFromUTF8(fullpath.c_str()).c_str() );\n\n    //Check if it's probably a keyboard layout file\n    if (layout_file.SectionExists(\"LayoutInfo\"))\n    {\n        //Clear old layout data\n        ResetState();\n\n        for (auto& sublayout : m_KeyboardKeys)\n        {\n            sublayout.clear();\n        }\n\n        m_KeyLabels = \"\";\n\n        //Load new layout\n        m_LayoutMetadata = LoadLayoutMetadataFromFile(filename);\n\n        const bool keyboard_editor_mode = UIManager::Get()->IsInKeyboardEditorMode();   //Always load all clusters when in Keyboard Editor mode\n        const bool cluster_enabled[kbdlayout_cluster_MAX] = \n        {\n            true, \n            (ConfigManager::GetValue(configid_bool_input_keyboard_cluster_function_enabled)   || keyboard_editor_mode),\n            (ConfigManager::GetValue(configid_bool_input_keyboard_cluster_navigation_enabled) || keyboard_editor_mode),\n            (ConfigManager::GetValue(configid_bool_input_keyboard_cluster_numpad_enabled)     || keyboard_editor_mode),\n            (ConfigManager::GetValue(configid_bool_input_keyboard_cluster_extra_enabled)      || keyboard_editor_mode)\n        };\n\n        unsigned int sublayout_id = kbdlayout_sub_base;\n        unsigned int row_id = 0;\n        unsigned int key_id = 0;\n\n        std::string key_type_str;\n        std::string key_cluster_str;\n        std::string key_sublayout_toggle_str;\n        m_KeyLabels = \"\";\n\n        while (true)\n        {\n            std::stringstream key_section;\n            key_section << \"Key_\" << g_KeyboardSublayoutNames[sublayout_id] << \"_Row_\" << row_id << \"_ID_\" << key_id;\n\n            if (layout_file.SectionExists(key_section.str().c_str()))\n            {\n                //Key cluster\n                key_cluster_str = layout_file.ReadString(key_section.str().c_str(), \"Cluster\", \"Base\");\n\n                //Match cluster string and skip if the cluster has been disabled\n                bool skip_key = false;\n                size_t key_cluster_i = 0;\n                for (key_cluster_i = 0; key_cluster_i < kbdlayout_cluster_MAX; ++key_cluster_i)\n                {\n                    if (key_cluster_str == g_KeyboardClusterNames[key_cluster_i])\n                    {\n                        if (!cluster_enabled[key_cluster_i])\n                        {\n                            skip_key = true;\n                        }\n                        break;\n                    }\n                }\n\n                if (skip_key)\n                {\n                    key_id++;\n                    continue;\n                }\n\n                KeyboardLayoutKey key;\n\n                //Key cluster\n                key.KeyCluster = (KeyboardLayoutCluster)key_cluster_i;\n\n                //Key type\n                key_type_str = layout_file.ReadString(key_section.str().c_str(), \"Type\", \"Blank\");\n\n                if (key_type_str == \"Blank\")\n                {\n                    key.KeyType = kbdlayout_key_blank_space;\n                }\n                else if (key_type_str == \"VirtualKey\")\n                {\n                    key.KeyType = kbdlayout_key_virtual_key;\n                    key.KeyCode = layout_file.ReadInt(key_section.str().c_str(), \"KeyCode\", 0);\n                    key.BlockModifiers = layout_file.ReadBool(key_section.str().c_str(), \"BlockModifiers\", false);\n                }\n                else if (key_type_str == \"VirtualKeyToggle\")\n                {\n                    key.KeyType = kbdlayout_key_virtual_key_toggle;\n                    key.KeyCode = layout_file.ReadInt(key_section.str().c_str(), \"KeyCode\", 0);\n                }\n                else if (key_type_str == \"VirtualKeyIsoEnter\")\n                {\n                    key.KeyType = kbdlayout_key_virtual_key_iso_enter;\n                    key.KeyCode = layout_file.ReadInt(key_section.str().c_str(), \"KeyCode\", 0);\n                }\n                else if (key_type_str == \"String\")\n                {\n                    key.KeyType   = kbdlayout_key_string;\n                    key.KeyString = layout_file.ReadString(key_section.str().c_str(), \"String\");\n                }\n                else if (key_type_str == \"SubLayoutToggle\")\n                {\n                    key.KeyType = kbdlayout_key_sublayout_toggle;\n\n                    key_sublayout_toggle_str = layout_file.ReadString(key_section.str().c_str(), \"SubLayout\", \"Base\");\n\n                    //Match sublayout string\n                    for (size_t i = 0; i < kbdlayout_sub_MAX; ++i)\n                    {\n                        if (key_sublayout_toggle_str == g_KeyboardSublayoutNames[i])\n                        {\n                            key.KeySubLayoutToggle = (KeyboardLayoutSubLayout)i;\n                            break;\n                        }\n                    }\n                }\n                else if (key_type_str == \"Action\")\n                {\n                    key.KeyType = kbdlayout_key_action;\n                    key.KeyActionUID = std::strtoull(layout_file.ReadString(key_section.str().c_str(), \"ActionUID\", \"0\").c_str(), nullptr, 10);\n                }\n\n                //General\n                key.Width    = layout_file.ReadInt(key_section.str().c_str(), \"Width\",  100) / 100.0f;\n                key.Height   = layout_file.ReadInt(key_section.str().c_str(), \"Height\", 100) / 100.0f;\n                key.Label    = layout_file.ReadString(key_section.str().c_str(), \"Label\");\n                key.NoRepeat = layout_file.ReadBool(key_section.str().c_str(), \"NoRepeat\", false);\n\n                StringReplaceAll(key.Label, \"\\\\n\", \"\\n\");\n                m_KeyLabels += key.Label;\n\n                //We cache whether the label is multi-line to avoid calling the more complex label functions every frame for 100+ keys that won't need it\n                key.IsLabelMultiline = (key.Label.find('\\n') != std::string::npos);\n\n                //For non-multi-line labels pre-parse possible alignment commands in the label string\n                if (!key.IsLabelMultiline)\n                {\n                    size_t pos = key.Label.find(\"##\");\n                    key.LabelHAlignment = (key.Label.find('L', pos) != std::string::npos) ? 0.0f : ((key.Label.find('R', pos) != std::string::npos) ? 1.0f : 0.5f);\n                }\n\n                m_KeyboardKeys[sublayout_id].push_back(key);\n\n                key_id++;\n            }\n            else if (key_id == 0) //No more keys in sublayout\n            {\n                sublayout_id++; //Try next sublayout\n                row_id = 0;\n\n                if (sublayout_id == kbdlayout_sub_MAX)\n                {\n                    break; //No more possible sublayouts, we're done here\n                }\n            }\n            else //Try next row\n            {\n                //Mark last key as end of row\n                if (!m_KeyboardKeys[sublayout_id].empty())\n                {\n                    m_KeyboardKeys[sublayout_id].back().IsRowEnd = true;\n                }\n\n                row_id++;\n                key_id = 0;\n            }\n        }\n\n        //Reload texture to update font atlas with keyboard chars\n        if (ImGui::StringContainsUnmappedCharacter(m_KeyLabels.c_str()))\n        {\n            TextureManager::Get().ReloadAllTexturesLater();\n            UIManager::Get()->RepeatFrame();\n        }\n\n        //Show keyboard again if it's visible to refresh title that may have been cut off before\n        if (m_WindowKeyboard.IsVisible())\n        {\n            m_WindowKeyboard.Show();\n        }\n\n        UIManager::Get()->RepeatFrame();\n    }\n}\n\nbool VRKeyboard::SaveCurrentLayoutToFile(const std::string& filename)\n{\n    m_LayoutMetadata.FileName = filename;\n\n    std::string fullpath = ConfigManager::Get().GetApplicationPath() + \"keyboards/\" + filename;\n    std::wstring wpath = WStringConvertFromUTF8(fullpath.c_str());\n\n    //Update cluster availability metadata\n    for (int i_sublayout = kbdlayout_sub_base; i_sublayout < kbdlayout_sub_MAX; ++i_sublayout)\n    {\n        KeyboardLayoutSubLayout sublayout = (KeyboardLayoutSubLayout)i_sublayout;\n\n        for (const KeyboardLayoutKey& key : m_KeyboardKeys[sublayout])\n        {\n            m_LayoutMetadata.HasCluster[key.KeyCluster] = true;\n        }\n    }\n\n    Ini layout_file(wpath.c_str(), true);\n\n    //Write metadata\n    layout_file.WriteString(\"LayoutInfo\", \"Name\",                 m_LayoutMetadata.Name.c_str());\n    layout_file.WriteString(\"LayoutInfo\", \"Author\",               m_LayoutMetadata.Author.c_str());\n    layout_file.WriteBool(  \"LayoutInfo\", \"HasAltGr\",             m_LayoutMetadata.HasAltGr);\n    layout_file.WriteBool(  \"LayoutInfo\", \"HasClusterFunction\",   m_LayoutMetadata.HasCluster[kbdlayout_cluster_function]);\n    layout_file.WriteBool(  \"LayoutInfo\", \"HasClusterNavigation\", m_LayoutMetadata.HasCluster[kbdlayout_cluster_navigation]);\n    layout_file.WriteBool(  \"LayoutInfo\", \"HasClusterNumpad\",     m_LayoutMetadata.HasCluster[kbdlayout_cluster_numpad]);\n    layout_file.WriteBool(  \"LayoutInfo\", \"HasClusterExtra\",      m_LayoutMetadata.HasCluster[kbdlayout_cluster_extra]);\n\n    for (int i_sublayout = kbdlayout_sub_base; i_sublayout < kbdlayout_sub_MAX; ++i_sublayout)\n    {\n        KeyboardLayoutSubLayout sublayout = (KeyboardLayoutSubLayout)i_sublayout;\n        unsigned int row_id = 0;\n        unsigned int key_id = 0;\n\n        for (const KeyboardLayoutKey& key : m_KeyboardKeys[sublayout])\n        {\n            std::stringstream key_section;\n            key_section << \"Key_\" << g_KeyboardSublayoutNames[sublayout] << \"_Row_\" << row_id << \"_ID_\" << key_id;\n\n            //Key type\n            layout_file.WriteString(key_section.str().c_str(), \"Type\", g_KeyboardKeyTypeNames[key.KeyType]);\n\n            //General\n            if (key.Width != 1.0f)\n            {\n                layout_file.WriteInt(key_section.str().c_str(), \"Width\", int(key.Width * 100.0f));\n            }\n\n            if (key.Height != 1.0f)\n            {\n                layout_file.WriteInt(key_section.str().c_str(), \"Height\", int(key.Height * 100.0f));\n            }\n\n            if (key.KeyType != kbdlayout_key_blank_space)\n            {\n                std::string label_escaped = key.Label;\n                StringReplaceAll(label_escaped, \"\\n\", \"\\\\n\");\n\n                layout_file.WriteString(key_section.str().c_str(), \"Label\", label_escaped.c_str());\n            }\n\n            if (key.KeyCluster != kbdlayout_cluster_base)\n            {\n                layout_file.WriteString(key_section.str().c_str(), \"Cluster\", g_KeyboardClusterNames[key.KeyCluster]);\n            }\n\n            //Key type specific values\n            switch (key.KeyType)\n            {\n                case kbdlayout_key_virtual_key:\n                case kbdlayout_key_virtual_key_toggle:\n                case kbdlayout_key_virtual_key_iso_enter:\n                {\n                    layout_file.WriteInt(   key_section.str().c_str(), \"KeyCode\", key.KeyCode);\n\n                    if ((key.KeyType == kbdlayout_key_virtual_key) && (key.BlockModifiers))\n                    {\n                        layout_file.ReadBool(key_section.str().c_str(), \"BlockModifiers\", true);\n                    }\n                    break;\n                }\n                case kbdlayout_key_string:\n                {\n                    layout_file.WriteString(key_section.str().c_str(), \"String\", key.KeyString.c_str());\n                    break;\n                }\n                case kbdlayout_key_sublayout_toggle:\n                {\n                    layout_file.WriteString(key_section.str().c_str(), \"SubLayout\", g_KeyboardSublayoutNames[key.KeySubLayoutToggle]);\n                    break;\n                }\n                case kbdlayout_key_action:\n                {\n                    layout_file.WriteString(key_section.str().c_str(), \"ActionUID\", std::to_string(key.KeyActionUID).c_str());\n                    break;\n                }\n            }\n\n            if (key.NoRepeat)\n            {\n                layout_file.WriteBool(key_section.str().c_str(), \"NoRepeat\", true);\n            }\n\n            if (key.IsRowEnd)\n            {\n                row_id++;\n                key_id = 0;\n            }\n            else\n            {\n                key_id++;\n            }\n        }\n    }\n\n    return layout_file.Save();\n}\n\nvoid VRKeyboard::LoadCurrentLayout()\n{\n    LoadLayoutFromFile(ConfigManager::GetValue(configid_str_input_keyboard_layout_file));\n}\n\nstd::vector<KeyboardLayoutMetadata> VRKeyboard::GetKeyboardLayoutList()\n{\n    std::vector<KeyboardLayoutMetadata> layout_list;\n\n    const std::wstring wpath = WStringConvertFromUTF8( std::string(ConfigManager::Get().GetApplicationPath() + \"keyboards/*.ini\").c_str() );\n    WIN32_FIND_DATA find_data;\n    HANDLE handle_find = ::FindFirstFileW(wpath.c_str(), &find_data);\n\n    if (handle_find != INVALID_HANDLE_VALUE)\n    {\n        do\n        {\n            KeyboardLayoutMetadata metadata = LoadLayoutMetadataFromFile( StringConvertFromUTF16(find_data.cFileName) );\n\n            //If base cluster exists, layout is probably valid, add to list\n            if (metadata.HasCluster[kbdlayout_cluster_base])\n            {\n                layout_list.push_back(metadata);\n            }\n        }\n        while (::FindNextFileW(handle_find, &find_data) != 0);\n\n        ::FindClose(handle_find);\n    }\n\n    return layout_list;\n}\n\nconst KeyboardLayoutMetadata& VRKeyboard::GetLayoutMetadata() const\n{\n    return m_LayoutMetadata;\n}\n\nvoid VRKeyboard::SetLayoutMetadata(const KeyboardLayoutMetadata& metadata)\n{\n    m_LayoutMetadata = metadata;\n}\n\nstd::vector<KeyboardLayoutKey>& VRKeyboard::GetLayout(KeyboardLayoutSubLayout sublayout)\n{\n    return m_KeyboardKeys[sublayout];\n}\n\nvoid VRKeyboard::SetLayout(KeyboardLayoutSubLayout sublayout, std::vector<KeyboardLayoutKey>& keys)\n{\n    m_KeyboardKeys[sublayout] = keys;\n}\n\nconst std::string& VRKeyboard::GetKeyLabelsString() const\n{\n    return m_KeyLabels;\n}\n\nKeyboardInputTarget VRKeyboard::GetInputTarget() const\n{\n    return m_InputTarget;\n}\n\nunsigned int VRKeyboard::GetInputTargetOverlayID() const\n{\n    return m_InputTargetOverlayID;\n}\n\nbool VRKeyboard::GetKeyDown(unsigned char keycode) const\n{\n    return m_KeyDown[keycode];\n}\n\nvoid VRKeyboard::SetKeyDown(unsigned char keycode, bool down, bool block_modifiers)\n{\n    //Don't do anything if the key state didn't change\n    if (m_KeyDown[keycode] == down)\n        return;\n\n    m_KeyDown[keycode] = down;\n\n    //Update combined modifier key state if relevant\n    if ((keycode == VK_LSHIFT) || (keycode == VK_RSHIFT))\n    {\n        m_KeyDown[VK_SHIFT] = (m_KeyDown[VK_LSHIFT] || m_KeyDown[VK_RSHIFT]);\n\n        if (m_InputTarget == kbdtarget_ui)\n        {\n            ImGui::GetIO().AddKeyEvent(ImGuiMod_Shift, m_KeyDown[VK_SHIFT]);\n        }\n    }\n    else if ((keycode == VK_LCONTROL) || (keycode == VK_RCONTROL))\n    {\n        m_KeyDown[VK_CONTROL] = (m_KeyDown[VK_LCONTROL] || m_KeyDown[VK_RCONTROL]);\n\n        if (m_InputTarget == kbdtarget_ui)\n        {\n            ImGui::GetIO().AddKeyEvent(ImGuiMod_Ctrl, m_KeyDown[VK_CONTROL]);\n        }\n    }\n    else if ((keycode == VK_LMENU) || (keycode == VK_RMENU))\n    {\n        m_KeyDown[VK_MENU] = (m_KeyDown[VK_LMENU] || m_KeyDown[VK_RMENU]);\n\n        if (m_InputTarget == kbdtarget_ui)\n        {\n            ImGui::GetIO().AddKeyEvent(ImGuiMod_Alt, m_KeyDown[VK_MENU]);\n        }\n    }\n    else if ( (down) && (keycode == VK_CAPITAL) ) //For caps lock, update toggled state\n    {\n        m_CapsLockToggled = !m_CapsLockToggled;\n    }\n\n    if (m_InputTarget == kbdtarget_overlay)\n    {\n        //Send keystate message (additional wchar message is sent below)\n        unsigned char flags = (down) ? GetModifierFlags() | kbd_keystate_flag_key_down : GetModifierFlags();\n        DPBrowserAPIClient::Get().DPBrowser_KeyboardSetKeyState(GetTargetOverlayHandle(), (DPBrowserIPCKeyboardKeystateFlags)flags, keycode);\n    }\n\n    if (m_InputTarget != kbdtarget_desktop)\n    {\n        ImGui::GetIO().AddKeyEvent(ImGui_ImplWin32_KeyEventToImGuiKey(keycode, 0), down);\n\n        //Get text output for current state and key\n        if ((down) && (keycode != VK_BACK) && (keycode != VK_TAB))\n        {\n            //Win32 keyboard state for ToUnicode()\n            BYTE keyboard_state[256] = {0};\n            keyboard_state[VK_SHIFT]   = (m_KeyDown[VK_SHIFT])   ? -1 : 0;\n            keyboard_state[VK_CONTROL] = (m_KeyDown[VK_CONTROL]) ? -1 : 0;\n            keyboard_state[VK_MENU]    = (m_KeyDown[VK_MENU])    ? -1 : 0;\n            keyboard_state[VK_CAPITAL] = (m_CapsLockToggled)     ?  1 : 0;    //Key toggle state is high-order bit\n            keyboard_state[keycode]    = -1;\n\n\n            WCHAR key_wchars[16] = {0};\n\n            int wchars_written = ::ToUnicode(keycode, ::MapVirtualKey(keycode, MAPVK_VK_TO_VSC), keyboard_state, key_wchars, 15, 0);\n\n            if (wchars_written > 0)\n            {\n                //Documentation of ToUnicode() suggests there may be invalid characters in the buffer past the written ones if the return value is higher than 1\n                //One would like to believe the function NUL-terminates at that point, but there's no guarantee for that\n                if (wchars_written > 1)\n                {\n                    key_wchars[std::min(wchars_written, 15)] = '\\0';\n                }\n\n                if (m_InputTarget == kbdtarget_ui)\n                {\n                    AddTextToStringQueue(StringConvertFromUTF16(key_wchars));\n                }\n                else if (m_InputTarget == kbdtarget_overlay)\n                {\n                    DPBrowserAPIClient::Get().DPBrowser_KeyboardTypeString(GetTargetOverlayHandle(), StringConvertFromUTF16(key_wchars));\n                }\n            }\n        }\n    }\n    else\n    {\n        if (block_modifiers)\n        {\n            IPCManager::Get().PostMessageToDashboardApp(ipcmsg_action, ipcact_keyboard_vkey, MAKELPARAM((down) ? kbd_keystate_flag_key_down : 0, 0));   //Press key without modifiers\n            IPCManager::Get().PostMessageToDashboardApp(ipcmsg_action, ipcact_keyboard_vkey, MAKELPARAM(GetModifierFlags(), 0));                        //Restore old modifier state\n        }\n        else\n        {\n            unsigned char flags = (down) ? GetModifierFlags() | kbd_keystate_flag_key_down : GetModifierFlags();\n            IPCManager::Get().PostMessageToDashboardApp(ipcmsg_action, ipcact_keyboard_vkey, MAKELPARAM(flags, keycode));\n        }\n    }\n}\n\nvoid VRKeyboard::SetStringDown(const std::string text, bool down)\n{\n    if (m_InputTarget == kbdtarget_ui)\n    {\n        if (down)\n        {\n            AddTextToStringQueue(text);\n        }\n        return;\n    }\n    \n    std::wstring wstr = WStringConvertFromUTF8(text.c_str());\n    \n    if (m_InputTarget == kbdtarget_desktop)\n    {\n        //If it's a single character, send as wchar so it checks if it can be pressed on the current windows keyboard layout\n        if (wstr.length() == 1)\n        {\n            IPCManager::Get().PostMessageToDashboardApp(ipcmsg_action, ipcact_keyboard_wchar, MAKELPARAM(wstr[0], down));\n\n            if (!down)\n            {\n                RestoreDesktopModifierState();\n            }\n        }\n        else if (!down) //Otherwise, send as string input\n        {\n            IPCManager::Get().SendStringToDashboardApp(configid_str_state_keyboard_string, text, UIManager::Get()->GetWindowHandle());\n        }\n    }\n    else if (m_InputTarget == kbdtarget_overlay)\n    {\n        //If it's a single character, send as wchar so it checks if it can be pressed on the current windows keyboard layout\n        if (wstr.length() == 1)\n        {\n            DPBrowserAPIClient::Get().DPBrowser_KeyboardTypeWChar(GetTargetOverlayHandle(), wstr[0], down);\n        }\n        else if (!down) //Otherwise, send as string input\n        {\n            DPBrowserAPIClient::Get().DPBrowser_KeyboardTypeString(GetTargetOverlayHandle(), StringConvertFromUTF16(wstr.c_str()));\n        }\n    }\n}\n\nvoid VRKeyboard::SetActionDown(ActionUID action_uid, bool down)\n{\n    IPCManager::Get().PostMessageToDashboardApp(ipcmsg_action, (down) ? ipcact_action_start : ipcact_action_stop, action_uid);\n}\n\nbool VRKeyboard::IsCapsLockToggled() const\n{\n    return m_CapsLockToggled;\n}\n\nvoid VRKeyboard::ResetState()\n{\n    //Release any held down keys\n    for (int i = 0; i < 256; ++i) \n    {\n        if (m_KeyDown[i])\n        {\n            SetKeyDown(i, false);\n        }\n    }\n\n    if (m_CapsLockToggled)\n    {\n        SetKeyDown(VK_CAPITAL, true);\n        SetKeyDown(VK_CAPITAL, false);\n    }\n\n    m_StringQueue = {}; //Clear\n\n    m_WindowKeyboard.ResetButtonState();\n}\n\nvoid VRKeyboard::VRKeyboardInputBegin(const char* str_id, bool is_multiline)\n{\n    VRKeyboardInputBegin(ImGui::GetID(str_id), is_multiline);\n}\n\nvoid VRKeyboard::VRKeyboardInputBegin(ImGuiID widget_id, bool is_multiline)\n{\n    ImGuiContext& g = *GImGui;\n    ImGuiIO& io = ImGui::GetIO();\n\n    if (m_ActiveInputText == widget_id)\n    {\n        if ((io.InputQueueCharacters.empty()) && (!m_StringQueue.empty()))\n        {\n            io.AddInputCharactersUTF8(m_StringQueue.front().c_str());\n            m_StringQueue.pop();\n        }\n\n        m_ActiveInputTextIsMultiline = is_multiline;\n    }\n\n    m_MouseLeftDownPrevCached    = io.MouseDown[ImGuiMouseButton_Left];\n    m_MouseLeftClickedPrevCached = io.MouseClicked[ImGuiMouseButton_Left];\n\n    if ( (m_WindowKeyboard.IsHovered()) || (m_WindowKeyboardShortcuts.IsHovered()) )\n    {\n        io.MouseDown[ImGuiMouseButton_Left] = false;\n        io.MouseClicked[ImGuiMouseButton_Left] = false;\n    }\n\n    //Set mouse delta to 0 when keyboard shortcut window buttons are down in order to prevent the InputText's selection to change from cursor movement\n    if (m_WindowKeyboardShortcuts.IsAnyButtonDown())\n    {\n        io.MouseDelta.x = 0.0f;\n        io.MouseDelta.y = 0.0f;\n    }\n\n    m_InputBeginWidgetID = widget_id;\n}\n\nvoid VRKeyboard::VRKeyboardInputEnd()\n{\n    ImGuiIO& io = ImGui::GetIO();\n    ImGuiID widget_id = m_InputBeginWidgetID;\n\n    if (ImGui::IsItemActivated())\n    {\n        m_ActiveInputText = widget_id;\n    }\n    else if ((m_ActiveInputText == widget_id) && (ImGui::IsItemDeactivated()))\n    {\n        m_ActiveInputText = 0;\n    }\n\n    if ( (m_ActiveInputText == widget_id) && \n         ( (ImGui::IsKeyPressed(ImGuiKey_Tab)) || (ImGui::IsKeyPressed(ImGuiKey_Escape)) || ((!m_ActiveInputTextIsMultiline) && (ImGui::IsKeyPressed(ImGuiKey_Enter))) ) )\n    {\n        ImGui::ClearActiveID();\n        m_ActiveInputText = 0;\n        UIManager::Get()->RepeatFrame();\n    }\n\n    if ( (m_ActiveInputText == widget_id) && (!ImGui::IsItemHovered()) && (ImGui::IsItemActive()) && (ImGui::IsMouseClicked(ImGuiMouseButton_Left)) && \n         (!m_WindowKeyboard.IsHovered()) && (!m_WindowKeyboardShortcuts.IsHovered()) )\n    {\n        ImGui::ClearActiveID();\n        m_ActiveInputText = 0;\n        io.MouseDownDuration[ImGuiMouseButton_Left] = -1.0f;        //Reset mouse down duration so the click counts as newly pressed again in the next frame\n        UIManager::Get()->RepeatFrame();\n    }\n\n    //Restore mouse down in case modified in VRKeyboardInputBegin()\n    io.MouseDown[ImGuiMouseButton_Left]    = m_MouseLeftDownPrevCached;\n    io.MouseClicked[ImGuiMouseButton_Left] = m_MouseLeftClickedPrevCached;\n\n    if (!UIManager::Get()->IsInDesktopMode())\n    {\n        //Set active widget for keyboard shortcuts window if there's actually active text input (m_ActiveInputText may be a slider for example)\n        if ((m_ActiveInputText == widget_id) && (io.WantTextInput))\n        {\n            m_WindowKeyboardShortcuts.SetActiveWidget(m_ActiveInputText);\n            m_WindowKeyboardShortcuts.SetDefaultPositionDirection(m_ShortcutWindowDirHint, m_ShortcutWindowYOffset);\n        }\n        else if (m_ActiveInputText == 0)\n        {\n            m_WindowKeyboardShortcuts.SetActiveWidget(0);\n        }\n\n        m_WindowKeyboardShortcuts.Update(widget_id);    //This is called regardless of the widget being active in case a fade-out is still happening\n    }\n\n    m_InputBeginWidgetID = 0;\n    m_ShortcutWindowDirHint = ImGuiDir_Down;\n    m_ShortcutWindowYOffset = 0.0f;\n}\n\nvoid VRKeyboard::OnImGuiNewFrame()\n{\n    if (UIManager::Get()->IsInDesktopMode())\n        return;\n\n    ImGuiIO& io = ImGui::GetIO();\n\n    //Show keyboard for UI if needed\n    if (io.WantTextInput)\n    {\n        if ( (!m_KeyboardHiddenLastFrame) && ( (m_InputTarget != kbdtarget_ui) || (!m_WindowKeyboard.IsVisible()) ) )\n        {\n            m_InputTarget = kbdtarget_ui;\n\n            bool do_assign_to_ui = false;\n            int assigned_id = m_WindowKeyboard.GetAssignedOverlayID();\n\n            //Assign keyboard to UI if it's not assigned to any overlay yet\n            if (assigned_id == -1)\n            {\n                do_assign_to_ui = true;\n            }\n            else if (assigned_id >= 0)  //else do it if the assigned overlay is invisible\n            {\n                vr::VROverlayHandle_t ovrl_handle_assigned = OverlayManager::Get().GetConfigData((unsigned int)assigned_id).ConfigHandle[configid_handle_overlay_state_overlay_handle];\n                do_assign_to_ui = !vr::VROverlay()->IsOverlayVisible(ovrl_handle_assigned);\n            }\n\n            if (do_assign_to_ui)\n            {\n                m_WindowKeyboard.SetAutoVisibility(-2, true);\n            }\n\n            m_WindowKeyboard.Show();\n        }\n    }\n    else if (m_WindowKeyboard.IsVisible())\n    {\n        int assigned_id = m_WindowKeyboard.GetAssignedOverlayID();\n\n        //Disable UI target if it's active\n        if (m_InputTarget == kbdtarget_ui)\n        {\n            ResetState();\n            m_InputTarget = kbdtarget_desktop;\n            m_InputTargetOverlayID = k_ulOverlayID_None;\n            m_WindowKeyboard.Show(); //Show() updates window title\n        }\n\n        //If keyboard is visible for the UI, turn off auto-visibility\n        if (assigned_id == -2)\n        {\n            m_WindowKeyboard.SetAutoVisibility(-2, false);\n        }\n\n        //Check if overlay target should be used\n        int focused_overlay_id = ConfigManager::Get().GetValue(configid_int_state_overlay_focused_id);\n        OverlayCaptureSource capsource = ovrl_capsource_desktop_duplication;\n\n        if (focused_overlay_id >= 0)\n        {\n            capsource = (OverlayCaptureSource)OverlayManager::Get().GetConfigData((unsigned int)focused_overlay_id).ConfigInt[configid_int_overlay_capture_source];\n        }\n\n        if ( (m_InputTarget == kbdtarget_overlay) && (capsource != ovrl_capsource_browser) )\n        {\n            ResetState();\n            m_InputTarget = kbdtarget_desktop;\n            m_InputTargetOverlayID = k_ulOverlayID_None;\n\n            m_WindowKeyboard.Show(); //Show() updates window title\n        }\n        else if ( (m_InputTargetOverlayID != (unsigned int)focused_overlay_id) && (capsource == ovrl_capsource_browser) )  //Also resets state when overlay ID changes\n        {\n            ResetState();\n            m_InputTarget = kbdtarget_overlay;\n            m_InputTargetOverlayID = (unsigned int)focused_overlay_id;\n            m_WindowKeyboard.Show(); //Show() updates window title\n        }\n    }\n\n    m_KeyboardHiddenLastFrame = false;\n\n    if (m_InputTarget == kbdtarget_ui)\n    {\n        UpdateImGuiModifierState();\n    }\n}\n\nvoid VRKeyboard::OnWindowHidden()\n{\n    ImGuiIO& io = ImGui::GetIO();\n\n    if (io.WantTextInput)\n    {\n        ImGui::ClearActiveID();\n        m_ActiveInputText = 0;\n        io.WantTextInput = false;\n    }\n\n    m_KeyboardHiddenLastFrame = true; //Widgets will request the keyboard for the next frame anyways, so we prevent showing it again right away with this flag\n}\n\nvoid VRKeyboard::AddTextToStringQueue(const std::string text)\n{\n    //Only queue up if an InputText is focused\n    if (m_ActiveInputText != 0)\n    {\n        m_StringQueue.push(text);\n    }\n}\n\nvoid VRKeyboard::UpdateImGuiModifierState() const\n{\n    ImGuiIO& io = ImGui::GetIO();\n\n    io.KeyCtrl  =  m_KeyDown[VK_CONTROL];\n    io.KeyShift =  m_KeyDown[VK_SHIFT];\n    io.KeyAlt   =  m_KeyDown[VK_MENU];\n    io.KeySuper = (m_KeyDown[VK_LWIN] ||  m_KeyDown[VK_RWIN]);\n}\n\nvoid VRKeyboard::RestoreDesktopModifierState() const\n{\n    IPCManager::Get().PostMessageToDashboardApp(ipcmsg_action, ipcact_keyboard_vkey, MAKELPARAM(GetModifierFlags(), 0));\n}\n\nvoid VRKeyboard::SetShortcutWindowDirectionHint(ImGuiDir dir_hint, float y_offset)\n{\n    m_ShortcutWindowDirHint = dir_hint;\n    m_ShortcutWindowYOffset = y_offset;\n}\n\nKeyboardLayoutMetadata VRKeyboard::LoadLayoutMetadataFromFile(const std::string& filename)\n{\n    KeyboardLayoutMetadata metadata;\n\n    std::string fullpath = ConfigManager::Get().GetApplicationPath() + \"keyboards/\" + filename;\n    Ini layout_file( WStringConvertFromUTF8(fullpath.c_str()).c_str() );\n\n    if (layout_file.SectionExists(\"LayoutInfo\"))\n    {\n        metadata.Name     = layout_file.ReadString(\"LayoutInfo\", \"Name\");\n        metadata.Author   = layout_file.ReadString(\"LayoutInfo\", \"Author\");\n        metadata.FileName = filename;\n        metadata.HasCluster[kbdlayout_cluster_base]       = true;   //Always true for valid layouts\n        metadata.HasCluster[kbdlayout_cluster_function]   = layout_file.ReadBool(\"LayoutInfo\", \"HasClusterFunction\");\n        metadata.HasCluster[kbdlayout_cluster_navigation] = layout_file.ReadBool(\"LayoutInfo\", \"HasClusterNavigation\");\n        metadata.HasCluster[kbdlayout_cluster_numpad]     = layout_file.ReadBool(\"LayoutInfo\", \"HasClusterNumpad\");\n        metadata.HasCluster[kbdlayout_cluster_extra]      = layout_file.ReadBool(\"LayoutInfo\", \"HasClusterExtra\");\n        metadata.HasAltGr                                 = layout_file.ReadBool(\"LayoutInfo\", \"HasAltGr\");\n    }\n\n    return metadata;\n}\n"
  },
  {
    "path": "src/DesktopPlusUI/VRKeyboard.h",
    "content": "#pragma once\n\n#include \"VRKeyboardCommon.h\"\n#include \"WindowKeyboard.h\"\n#include \"WindowKeyboardEditor.h\"\n\n#include <vector>\n#include <queue>\n#include <string>\n\nclass VRKeyboard\n{\n    private:\n        WindowKeyboard m_WindowKeyboard;\n        WindowKeyboardShortcuts m_WindowKeyboardShortcuts;\n        KeyboardEditor m_KeyboardEditor;\n\n        KeyboardLayoutMetadata m_LayoutMetadata;\n        std::vector<KeyboardLayoutKey> m_KeyboardKeys[kbdlayout_sub_MAX];\n        std::string m_KeyLabels;                                          //String containing all labels, so their characters are always loaded from the font\n\n        KeyboardInputTarget m_InputTarget;\n        unsigned int m_InputTargetOverlayID;\n        bool m_KeyDown[256];\n        bool m_CapsLockToggled;\n        std::queue<std::string> m_StringQueue;\n\n        ImGuiID m_ActiveInputText;\n        ImGuiID m_InputBeginWidgetID;\n        ImGuiDir m_ShortcutWindowDirHint;\n        float m_ShortcutWindowYOffset;\n        bool m_ActiveInputTextIsMultiline;\n        bool m_MouseLeftDownPrevCached;\n        bool m_MouseLeftClickedPrevCached;\n        bool m_KeyboardHiddenLastFrame;\n\n        unsigned char GetModifierFlags() const;\n        vr::VROverlayHandle_t GetTargetOverlayHandle() const;\n\n    public:\n        VRKeyboard();\n        WindowKeyboard& GetWindow();\n        KeyboardEditor& GetEditor();\n\n        void LoadLayoutFromFile(const std::string& filename);\n        bool SaveCurrentLayoutToFile(const std::string& filename);\n        void LoadCurrentLayout();\n        static std::vector<KeyboardLayoutMetadata> GetKeyboardLayoutList();\n\n        const KeyboardLayoutMetadata& GetLayoutMetadata() const;\n        void SetLayoutMetadata(const KeyboardLayoutMetadata& metadata);\n        std::vector<KeyboardLayoutKey>& GetLayout(KeyboardLayoutSubLayout sublayout);\n        void SetLayout(KeyboardLayoutSubLayout sublayout, std::vector<KeyboardLayoutKey>& keys);\n        const std::string& GetKeyLabelsString() const;\n\n        KeyboardInputTarget GetInputTarget() const;\n        unsigned int GetInputTargetOverlayID() const;\n\n        bool GetKeyDown(unsigned char keycode) const;\n        void SetKeyDown(unsigned char keycode, bool down, bool block_modifiers = false);\n        void SetStringDown(const std::string text, bool down);\n        void SetActionDown(ActionUID action_uid, bool down);\n        bool IsCapsLockToggled() const;\n        void ResetState();\n\n        void VRKeyboardInputBegin(const char* str_id, bool is_multiline = false);\n        void VRKeyboardInputBegin(ImGuiID widget_id, bool is_multiline = false);\n        void VRKeyboardInputEnd();\n\n        void OnImGuiNewFrame();\n        void OnWindowHidden();\n\n        void AddTextToStringQueue(const std::string text);\n\n        void UpdateImGuiModifierState() const;\n        void RestoreDesktopModifierState() const;\n\n        //Positioning hint used by the shortcut window. Set when the default down direction would cover related widgets. Offset only used with hint dir. Resets automatically\n        void SetShortcutWindowDirectionHint(ImGuiDir dir_hint, float y_offset = 0.0f);\n\n        static KeyboardLayoutMetadata LoadLayoutMetadataFromFile(const std::string& filename);\n};"
  },
  {
    "path": "src/DesktopPlusUI/VRKeyboardCommon.h",
    "content": "#pragma once\n\n#include \"Actions.h\"\n#include <string>\n\nenum KeyboardLayoutSubLayout : unsigned char\n{\n    kbdlayout_sub_base,\n    kbdlayout_sub_shift,\n    kbdlayout_sub_altgr,\n    kbdlayout_sub_aux,\n    kbdlayout_sub_MAX\n};\n\nenum KeyboardLayoutKeyType\n{\n    kbdlayout_key_blank_space,\n    kbdlayout_key_virtual_key,\n    kbdlayout_key_virtual_key_toggle,\n    kbdlayout_key_virtual_key_iso_enter,\n    kbdlayout_key_string,\n    kbdlayout_key_sublayout_toggle,\n    kbdlayout_key_action,\n    kbdlayout_key_MAX\n};\n\nenum KeyboardLayoutCluster\n{\n    kbdlayout_cluster_base,\n    kbdlayout_cluster_function,\n    kbdlayout_cluster_navigation,\n    kbdlayout_cluster_numpad,\n    kbdlayout_cluster_extra,\n    kbdlayout_cluster_MAX\n};\n\nstruct KeyboardLayoutMetadata\n{\n    std::string Name = \"Unknown\";\n    std::string Author = \"\";\n    std::string FileName;\n    bool HasAltGr = false;                                  //Right Alt switches to AltGr sublayout when down\n    bool HasCluster[kbdlayout_cluster_MAX] = {false};\n};\n\nstruct KeyboardLayoutKey\n{\n    KeyboardLayoutCluster KeyCluster = kbdlayout_cluster_base;\n    KeyboardLayoutKeyType KeyType = kbdlayout_key_blank_space;\n    bool IsRowEnd = false;\n    float Width   = 1.0f;\n    float Height  = 1.0f;\n    std::string Label;\n    float LabelHAlignment = 0.5f;   //Computed at load-time\n    bool IsLabelMultiline = false;  //Computed at load-time\n    bool BlockModifiers   = false;\n    bool NoRepeat         = false;\n    unsigned char KeyCode = 0;\n    std::string KeyString;\n    KeyboardLayoutSubLayout KeySubLayoutToggle = kbdlayout_sub_base;\n    ActionUID KeyActionUID = k_ActionUID_Invalid;\n};\n\nenum KeyboardInputTarget\n{\n    kbdtarget_desktop,\n    kbdtarget_ui,\n    kbdtarget_overlay\n};\n"
  },
  {
    "path": "src/DesktopPlusUI/Win32PerformanceData.cpp",
    "content": "#include \"Win32PerformanceData.h\"\n\n#include <memory>\n\n#include <PdhMsg.h>\n\nLUID Win32PerformanceData::GetLUIDFromFormattedCounterNameString(const std::wstring& str)\n{\n    LUID parsed_luid = {0, 0};\n\n    //Find and extract LUID parts\n    size_t pos = str.find(L\"_0x\");\n    if (pos != std::string::npos)\n    {\n        std::wstring str_high_part = str.substr(pos + 1, 10);\n\n        pos = str.find(L\"_0x\", pos + 1);\n        if (pos != std::string::npos)\n        {\n            std::wstring str_low_part = str.substr(pos + 1, 10);\n\n            //Convert from string\n            parsed_luid.HighPart = stol(str_high_part, 0, 16);\n            parsed_luid.LowPart  = stoul(str_low_part, 0, 16);\n        }\n    }\n\n    return parsed_luid;\n}\n\nWin32PerformanceData::Win32PerformanceData() : \n    m_QueryCPU(nullptr), m_QueryGPU(nullptr), m_QueryVRAM(nullptr), m_CounterCPU(nullptr), m_CounterGPU(nullptr), m_CounterVRAM(nullptr), \n    m_GPUTargetLUID{0, 0}, m_CPULoad(0.0f), m_GPULoad(0.0f), m_VRAMTotalGB(0.0f), m_VRAMUsedGB(0.0f), m_LastUpdateTick(0)\n{\n    //RAM Total\n    MEMORYSTATUSEX mem_info;\n    mem_info.dwLength = sizeof(MEMORYSTATUSEX);\n    ::GlobalMemoryStatusEx(&mem_info);\n    m_RAMTotalGB = float(mem_info.ullTotalPhys / (1024.0 * 1024.0 * 1024.0));\n\n    Update(); //Fills m_RamUsedGB\n}\n\nWin32PerformanceData::~Win32PerformanceData()\n{\n    DisableCounters();\n}\n\nvoid Win32PerformanceData::EnableCounters(bool enable_gpu)\n{\n    PDH_STATUS pdh_status;\n    bool new_counter_added = false;\n\n    //CPU Load\n    if (m_QueryCPU == nullptr)\n    {\n        pdh_status = ::PdhOpenQuery(nullptr, 0, &m_QueryCPU);\n\n        if (pdh_status == ERROR_SUCCESS)\n        {\n            pdh_status = ::PdhAddEnglishCounter(m_QueryCPU, L\"\\\\Processor(_Total)\\\\% Processor Time\", 0, &m_CounterCPU);\n\n            if (pdh_status != ERROR_SUCCESS)\n            {\n                ::PdhCloseQuery(&m_QueryCPU);\n                m_QueryCPU = nullptr;\n            }\n            else\n            {\n                new_counter_added = true;\n            }\n        }\n    }\n\n    if (enable_gpu)\n    {\n        //GPU Load\n        if (m_QueryGPU == nullptr)\n        {\n            pdh_status = ::PdhOpenQuery(nullptr, 0, &m_QueryGPU);\n\n            if (pdh_status == ERROR_SUCCESS)\n            {\n                pdh_status = ::PdhAddEnglishCounter(m_QueryGPU, L\"\\\\GPU Engine(*)\\\\Utilization Percentage\", 0, &m_CounterGPU);\n\n                if (pdh_status != ERROR_SUCCESS)\n                {\n                    ::PdhCloseQuery(&m_QueryGPU);\n                    m_QueryGPU = nullptr;\n                }\n                else\n                {\n                    new_counter_added = true;\n                }\n            }\n        }\n\n        //GPU VRAM\n        if (m_QueryVRAM == nullptr)\n        {\n            pdh_status = ::PdhOpenQuery(nullptr, 0, &m_QueryVRAM);\n\n            if (pdh_status == ERROR_SUCCESS)\n            {\n                pdh_status = ::PdhAddEnglishCounter(m_QueryVRAM, L\"\\\\GPU Adapter Memory(*)\\\\Dedicated Usage\", 0, &m_CounterVRAM);\n\n                if (pdh_status != ERROR_SUCCESS)\n                {\n                    ::PdhCloseQuery(&m_QueryVRAM);\n                    m_QueryVRAM = nullptr;\n                }\n                else\n                {\n                    new_counter_added = true;\n                }\n            }\n        }\n    }\n\n    if (new_counter_added)\n    {\n        m_LastUpdateTick = ::GetTickCount64();  //Not an update, but we need to start waiting from when a counter was first added\n    }\n}\n\nvoid Win32PerformanceData::DisableGPUCounters()\n{\n    if (m_QueryGPU != nullptr)\n    {\n        PdhCloseQuery(&m_QueryGPU);\n        m_QueryGPU = nullptr;\n    }\n\n    if (m_QueryVRAM != nullptr)\n    {\n        PdhCloseQuery(&m_QueryVRAM);\n        m_QueryVRAM = nullptr;\n    }\n}\n\nvoid Win32PerformanceData::DisableCounters()\n{\n    if (m_QueryCPU != nullptr)\n    {\n        PdhCloseQuery(&m_QueryCPU);\n        m_QueryCPU = nullptr;\n    }\n\n    if (m_QueryGPU != nullptr)\n    {\n        PdhCloseQuery(&m_QueryGPU);\n        m_QueryGPU = nullptr;\n    }\n\n    if (m_QueryVRAM != nullptr)\n    {\n        PdhCloseQuery(&m_QueryVRAM);\n        m_QueryVRAM = nullptr;\n    }\n}\n\nbool Win32PerformanceData::Update()\n{\n    //Do not update more often than once a second to get reliable CPU load readings (and reduce said load)\n    if (::GetTickCount64() < m_LastUpdateTick + 1000)\n        return false;\n\n    m_LastUpdateTick = ::GetTickCount64();\n\n    PDH_STATUS pdh_status;\n\n    //CPU Load\n    if (m_QueryCPU != nullptr)\n    {\n        pdh_status = PdhCollectQueryData(m_QueryCPU);\n\n        if (pdh_status == ERROR_SUCCESS)\n        {\n            PDH_FMT_COUNTERVALUE counter_value = {0};\n            pdh_status = PdhGetFormattedCounterValue(m_CounterCPU, PDH_FMT_DOUBLE, nullptr, &counter_value);\n\n            if (pdh_status == ERROR_SUCCESS)\n            {\n                m_CPULoad = std::min((float)counter_value.doubleValue, 100.0f);\n            }\n        }\n    }\n\n    //GPU Load\n    if (m_QueryGPU != nullptr)\n    {\n        pdh_status = PdhCollectQueryData(m_QueryGPU);\n\n        if (pdh_status == ERROR_SUCCESS)\n        {\n            PDH_FMT_COUNTERVALUE counter_value = {0};\n            DWORD buffer_size = 0;\n            DWORD item_count = 0;\n\n            pdh_status = PdhGetFormattedCounterArray(m_CounterGPU, PDH_FMT_DOUBLE, &buffer_size, &item_count, nullptr);\n\n            if (pdh_status == PDH_MORE_DATA)\n            {\n                std::unique_ptr<uint8_t[]> item_buffer = std::unique_ptr<uint8_t[]>{new uint8_t[buffer_size]};\n                PDH_FMT_COUNTERVALUE_ITEM* items = (PDH_FMT_COUNTERVALUE_ITEM*)item_buffer.get();\n\n                pdh_status = PdhGetFormattedCounterArray(m_CounterGPU, PDH_FMT_DOUBLE, &buffer_size, &item_count, items);\n\n                if (pdh_status == ERROR_SUCCESS)\n                {\n                    double total_load = 0.0;\n\n                    for (DWORD i = 0; i < item_count; i++)\n                    {\n                        std::wstring item_name(items[i].szName);\n\n                        //Only count engine type \"3D\"\n                        if (item_name.find(L\"_engtype_3D\") != std::string::npos)\n                        {\n                            //Make sure it's from the right GPU\n                            LUID item_luid = GetLUIDFromFormattedCounterNameString(item_name);\n\n                            if ( (item_luid.LowPart == m_GPUTargetLUID.LowPart) && (item_luid.HighPart == m_GPUTargetLUID.HighPart) )\n                            {\n                                total_load += items[i].FmtValue.doubleValue;\n                            }\n                        }\n                    }\n\n                    m_GPULoad = std::min((float)total_load, 100.0f);\n                }\n            }\n        }\n    }\n\n    //GPU VRAM Used\n    if (m_QueryVRAM != nullptr)\n    {\n        pdh_status = PdhCollectQueryData(m_QueryVRAM);\n\n        if (pdh_status == ERROR_SUCCESS)\n        {\n            PDH_FMT_COUNTERVALUE counter_value = {0};\n            DWORD buffer_size = 0;\n            DWORD item_count = 0;\n\n            pdh_status = PdhGetFormattedCounterArray(m_CounterVRAM, PDH_FMT_DOUBLE, &buffer_size, &item_count, nullptr);\n\n            if (pdh_status == PDH_MORE_DATA)\n            {\n                std::unique_ptr<uint8_t[]> item_buffer = std::unique_ptr<uint8_t[]>{new uint8_t[buffer_size]};\n                PDH_FMT_COUNTERVALUE_ITEM* items = (PDH_FMT_COUNTERVALUE_ITEM*)item_buffer.get();\n\n                pdh_status = PdhGetFormattedCounterArray(m_CounterVRAM, PDH_FMT_DOUBLE, &buffer_size, &item_count, items);\n\n                if (pdh_status == ERROR_SUCCESS)\n                {\n                    for (DWORD i = 0; i < item_count; i++)\n                    {\n                        std::wstring item_name(items[i].szName);\n\n                        //Make sure it's from the right GPU\n                        LUID item_luid = GetLUIDFromFormattedCounterNameString(item_name);\n\n                        if ( (item_luid.LowPart == m_GPUTargetLUID.LowPart) && (item_luid.HighPart == m_GPUTargetLUID.HighPart) )\n                        {\n                            m_VRAMUsedGB = float(items[i].FmtValue.doubleValue / (1024.0 * 1024.0 * 1024.0));\n                            m_VRAMUsedGB = std::min(m_VRAMUsedGB, m_VRAMTotalGB);\n                            break;\n                        }\n                    }\n                }\n            }\n        }\n    }\n\n    //RAM Used\n    MEMORYSTATUSEX mem_info = {};\n    mem_info.dwLength = sizeof(MEMORYSTATUSEX);\n    ::GlobalMemoryStatusEx(&mem_info);\n    m_RAMUsedGB = float((mem_info.ullTotalPhys - mem_info.ullAvailPhys) / (1024.0 * 1024.0 * 1024.0));\n    m_RAMUsedGB = std::min(m_RAMUsedGB, m_RAMTotalGB);\n\n    return true;\n}\n\nvoid Win32PerformanceData::SetTargetGPU(LUID gpu_luid, DWORDLONG vram_total_bytes)\n{\n    m_GPUTargetLUID = gpu_luid;\n    m_VRAMTotalGB = float(vram_total_bytes / (1024.0 * 1024.0 * 1024.0));\n}\n\nfloat Win32PerformanceData::GetCPULoadPrecentage() const\n{\n    return m_CPULoad;\n}\n\nfloat Win32PerformanceData::GetGPULoadPrecentage() const\n{\n    return m_GPULoad;\n}\n\nfloat Win32PerformanceData::GetRAMTotalGB() const\n{\n    return m_RAMTotalGB;\n}\n\nfloat Win32PerformanceData::GetRAMUsedGB() const\n{\n    return m_RAMUsedGB;\n}\n\nfloat Win32PerformanceData::GetVRAMTotalGB() const\n{\n    return m_VRAMTotalGB;\n}\n\nfloat Win32PerformanceData::GetVRAMUsedGB() const\n{\n    return m_VRAMUsedGB;\n}\n"
  },
  {
    "path": "src/DesktopPlusUI/Win32PerformanceData.h",
    "content": "#pragma once\n\n#include <string>\n\n#define NOMINMAX\n#include <pdh.h>\n\nclass Win32PerformanceData\n{\n    private:\n        PDH_HQUERY m_QueryCPU;\n        PDH_HQUERY m_QueryGPU;\n        PDH_HQUERY m_QueryVRAM;\n        PDH_HCOUNTER m_CounterCPU;\n        PDH_HCOUNTER m_CounterGPU;\n        PDH_HCOUNTER m_CounterVRAM;\n\n        LUID m_GPUTargetLUID;\n\n        float m_CPULoad;\n        float m_GPULoad;\n        float m_RAMTotalGB;\n        float m_RAMUsedGB;\n        float m_VRAMTotalGB;\n        float m_VRAMUsedGB;\n\n        ULONGLONG m_LastUpdateTick;\n\n        static LUID GetLUIDFromFormattedCounterNameString(const std::wstring& str);\n\n    public:\n        Win32PerformanceData();\n        ~Win32PerformanceData();\n\n        void EnableCounters(bool enable_gpu);\n        void DisableGPUCounters();\n        void DisableCounters();\n\n        bool Update();\n        void SetTargetGPU(LUID gpu_luid, DWORDLONG vram_total_bytes);\n\n        float GetCPULoadPrecentage() const;\n        float GetGPULoadPrecentage() const;\n        float GetRAMTotalGB()        const;\n        float GetRAMUsedGB()         const;\n        float GetVRAMTotalGB()       const;\n        float GetVRAMUsedGB()        const;\n};\n"
  },
  {
    "path": "src/DesktopPlusUI/WindowDesktopMode.cpp",
    "content": "#include \"WindowDesktopMode.h\"\n\n#include \"imgui.h\"\n#include \"UIManager.h\"\n#include \"InterprocessMessaging.h\"\n#include \"WindowManager.h\"\n#include \"DesktopPlusWinRT.h\"\n#include \"DPBrowserAPIClient.h\"\n\nWindowDesktopMode::WindowDesktopMode()\n{\n    m_PageStack.push_back(wnddesktopmode_page_main);\n}\n\nvoid WindowDesktopMode::UpdateTitleBar()\n{\n    ImGuiStyle& style = ImGui::GetStyle();\n    ImGuiIO& io = ImGui::GetIO();\n\n    ImVec2 img_size_line_height = {ImGui::GetTextLineHeight() * 1.6f, ImGui::GetTextLineHeight() * 1.6f};\n    ImVec2 img_size, img_uv_min, img_uv_max;\n\n    m_TitleBarRect = {0.0f, 0.0f, ImGui::GetWindowSize().x, img_size_line_height.y + (style.WindowPadding.y * 2.0f)};\n\n    const ImVec2 title_bar_rect_min(m_TitleBarRect.x, m_TitleBarRect.y), title_bar_rect_max(m_TitleBarRect.z, m_TitleBarRect.w);\n    ImGui::PushClipRect(title_bar_rect_min, title_bar_rect_max, false);\n\n    //Background color\n    ImGui::GetWindowDrawList()->AddRectFilled({0.0f, 0.0f}, {m_TitleBarRect.z, m_TitleBarRect.w}, ImGui::GetColorU32(ImGuiCol_TitleBg));\n\n    //Icon and title text\n    static ImVec2 back_button_size;\n    const float back_button_x = m_TitleBarRect.z - back_button_size.x - style.FramePadding.x;\n\n    const char* title_str = nullptr;\n    float title_icon_alpha = 1.0f;\n\n    switch (m_PageStack[m_PageStackPos])\n    {\n        case wnddesktopmode_page_main:\n        {\n            title_str = \"Desktop+\";\n            TextureManager::Get().GetTextureInfo(tmtex_icon_small_app_icon, img_size, img_uv_min, img_uv_max);\n            break;\n        }\n        case wnddesktopmode_page_settings:\n        case wnddesktopmode_page_profiles:\n        case wnddesktopmode_page_app_profiles:\n        case wnddesktopmode_page_actions:\n        {\n            const WindowSettings& window_settings = UIManager::Get()->GetSettingsWindow();\n            title_str = window_settings.DesktopModeGetTitle();\n            window_settings.DesktopModeGetIconTextureInfo(img_size, img_uv_min, img_uv_max);\n            break;\n        }\n        case wnddesktopmode_page_properties:\n        {\n            const WindowOverlayProperties& window_properties = UIManager::Get()->GetOverlayPropertiesWindow();\n            title_str        = window_properties.DesktopModeGetTitle();\n            title_icon_alpha = window_properties.DesktopModeGetTitleIconAlpha();\n            window_properties.DesktopModeGetIconTextureInfo(img_size, img_uv_min, img_uv_max);\n            break;\n        }\n        case wnddesktopmode_page_add_window_overlay: \n        {\n            title_str = TranslationManager::GetString(tstr_DesktopModePageAddWindowOverlayTitle);\n            TextureManager::Get().GetTextureInfo(tmtex_icon_add, img_size, img_uv_min, img_uv_max);\n            break;\n        }\n    }\n\n    ImGui::SetCursorPos(style.WindowPadding);\n\n    ImGui::PushStyleVar(ImGuiStyleVar_Alpha, title_icon_alpha);\n\n    ImGui::Image(ImGui::GetIO().Fonts->TexID, img_size_line_height, img_uv_min, img_uv_max);\n\n    if ((ImGui::IsItemClicked()) && (m_PageStack[m_PageStackPos] == wnddesktopmode_page_properties))\n    {\n        UIManager::Get()->GetOverlayPropertiesWindow().DesktopModeOnTitleIconClick();\n    }\n\n    ImGui::SameLine(0.0f, style.ItemInnerSpacing.x);\n\n    ImGui::SetCursorPosY( (m_TitleBarRect.w / 2.0f) - (ImGui::GetTextLineHeight() / 1.90f) );   //1.90f to adjust alignment a bit\n\n    ImVec2 clip_end = ImGui::GetCursorScreenPos();\n    clip_end.x  = back_button_x - style.FramePadding.x;\n    clip_end.y += ImGui::GetFrameHeight();\n\n    ImGui::PushClipRect(ImGui::GetCursorScreenPos(), clip_end, true);\n    ImGui::TextUnformatted(title_str);\n    ImGui::PopClipRect();\n\n    ImGui::PopStyleVar();\n\n    float title_text_width = ImGui::GetItemRectSize().x;\n\n    //Right end of title bar\n    ImGui::PushStyleColor(ImGuiCol_Button, 0);\n\n    const bool can_go_back = (m_PageStackPos != 0);\n\n    if (!can_go_back)\n        ImGui::PushItemDisabled();\n\n    TextureManager::Get().GetTextureInfo(tmtex_icon_xxsmall_browser_back, img_size, img_uv_min, img_uv_max);\n    ImVec2 img_size_line_height_back(img_size_line_height.x * 0.75f, img_size_line_height.y * 0.75f);\n\n    ImGui::SetCursorScreenPos({back_button_x, m_TitleBarRect.y + (m_TitleBarRect.w / 2.0f) - (back_button_size.y / 2.0f) });\n\n    ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(1.0f, 1.0f));\n    if ( (ImGui::ImageButton(\"BackButton\", io.Fonts->TexID, img_size_line_height_back, img_uv_min, img_uv_max)) || \n         (ImGui::IsMouseClicked(3 /* MouseX1 / Back */)) || ( (ImGui::IsKeyPressed(ImGuiKey_Backspace)) && (!ImGui::IsAnyInputTextActive()) ) )\n    {\n        if (!ImGui::IsPopupOpen(nullptr, ImGuiPopupFlags_AnyPopup))\n        {\n            bool did_go_back = false;\n\n            switch (m_PageStack[m_PageStackPos])\n            {\n                case wnddesktopmode_page_settings:     /*fallthrough*/\n                case wnddesktopmode_page_profiles:     /*fallthrough*/\n                case wnddesktopmode_page_app_profiles: /*fallthrough*/\n                case wnddesktopmode_page_actions:      did_go_back = UIManager::Get()->GetSettingsWindow().DesktopModeGoBack();          break;\n                case wnddesktopmode_page_properties:   did_go_back = UIManager::Get()->GetOverlayPropertiesWindow().DesktopModeGoBack(); break;\n                default: break;\n            }\n\n            //If embedded page didn't go back, go back on our own pagination\n            if (!did_go_back)\n            {\n                PageGoBack();\n            }\n        }\n    }\n    ImGui::PopStyleVar();\n\n    if (!can_go_back)\n        ImGui::PopItemDisabled();\n\n    back_button_size = ImGui::GetItemRectSize();\n\n    //Check if title bar is hovered (except the back button) and notify embedded page windows that need this info\n    if (m_PageStack[m_PageStackPos] == wnddesktopmode_page_properties)\n    {\n        const bool is_title_bar_hovered = ( (!ImGui::IsItemHovered()) && (ImGui::IsMouseHoveringRect(title_bar_rect_min, title_bar_rect_max)) );\n        UIManager::Get()->GetOverlayPropertiesWindow().DesktopModeOnTitleBarHover(is_title_bar_hovered);\n    }\n\n    ImGui::PopStyleColor();\n    ImGui::PopClipRect();\n\n    ImGui::SetCursorPosY(m_TitleBarRect.w + style.WindowPadding.y);\n}\n\nvoid WindowDesktopMode::UpdatePageMain()\n{\n    ImGuiStyle& style = ImGui::GetStyle();\n    ImGuiIO& io = ImGui::GetIO();\n\n    ImGui::TextColoredUnformatted(ImGui::GetStyleColorVec4(ImGuiCol_ButtonHovered), TranslationManager::GetString(tstr_DesktopModeCatTools)); \n    ImGui::Indent();\n\n    const float item_height = ImGui::GetTextLineHeight() + style.ItemInnerSpacing.y;\n    ImGui::BeginChild(\"SettingsList\", ImVec2(0.0f, (item_height * 4.0f) + style.ItemInnerSpacing.y), true);\n\n    //Focus nav if we came back from settings\n    if ( (io.NavVisible) && (m_PageReturned == wnddesktopmode_page_settings) )\n    {\n        ImGui::SetKeyboardFocusHere();\n        m_PageReturned = wnddesktopmode_page_none;\n    }\n\n    if (ImGui::Selectable(TranslationManager::GetString(tstr_DesktopModeToolSettings))) \n    {\n        UIManager::Get()->GetSettingsWindow().DesktopModeSetRootPage(wndsettings_page_main);\n        PageGoForward(wnddesktopmode_page_settings);\n    }\n\n    //Focus nav if we came back from profiles\n    if ( (io.NavVisible) && (m_PageReturned == wnddesktopmode_page_profiles) )\n    {\n        ImGui::SetKeyboardFocusHere();\n        m_PageReturned = wnddesktopmode_page_none;\n    }\n\n    if (ImGui::Selectable(TranslationManager::GetString(tstr_SettingsProfilesOverlays))) \n    {\n        UIManager::Get()->GetSettingsWindow().DesktopModeSetRootPage(wndsettings_page_profiles);\n        PageGoForward(wnddesktopmode_page_profiles);\n    }\n\n    //Focus nav if we came back from app profiles\n    if ( (io.NavVisible) && (m_PageReturned == wnddesktopmode_page_app_profiles) )\n    {\n        ImGui::SetKeyboardFocusHere();\n        m_PageReturned = wnddesktopmode_page_none;\n    }\n\n    if (ImGui::Selectable(TranslationManager::GetString(tstr_SettingsProfilesApps))) \n    {\n        UIManager::Get()->GetSettingsWindow().DesktopModeSetRootPage(wndsettings_page_app_profiles);\n        PageGoForward(wnddesktopmode_page_app_profiles);\n    }\n\n    //Focus nav if we came back from actions\n    if ( (io.NavVisible) && (m_PageReturned == wnddesktopmode_page_actions) )\n    {\n        ImGui::SetKeyboardFocusHere();\n        m_PageReturned = wnddesktopmode_page_none;\n    }\n\n    if (ImGui::Selectable(TranslationManager::GetString(tstr_DesktopModeToolActions))) \n    {\n        UIManager::Get()->GetSettingsWindow().DesktopModeSetRootPage(wndsettings_page_actions);\n        PageGoForward(wnddesktopmode_page_actions);\n    }\n\n    ImGui::EndChild();\n\n    ImGui::Unindent();\n    ImGui::Spacing();\n\n    UpdatePageMainOverlayList();\n}\n\nvoid WindowDesktopMode::UpdatePageMainOverlayList()\n{\n    ImGuiStyle& style = ImGui::GetStyle();\n    ImGuiIO& io = ImGui::GetIO();\n\n    ImVec2 img_size_line_height = {ImGui::GetTextLineHeight(), ImGui::GetTextLineHeight()};\n    ImVec2 img_size, img_uv_min, img_uv_max;\n\n    //List of unique IDs for overlays so ImGui can identify the same list entries after reordering or list expansion (needed for drag reordering)\n    static std::vector<int> list_unique_ids;\n    static unsigned int drag_last_hovered_selectable = k_ulOverlayID_None;\n\n    const int overlay_count = (int)OverlayManager::Get().GetOverlayCount();\n    const unsigned int u_overlay_count = OverlayManager::Get().GetOverlayCount();\n\n    //Reset unique IDs when page is appearing\n    if (m_PageAppearing == wnddesktopmode_page_main)\n    {\n        list_unique_ids.clear();\n    }\n\n    //Expand unique id lists if overlays were added (also does initialization since it's empty then)\n    while (list_unique_ids.size() < u_overlay_count)\n    {\n        list_unique_ids.push_back((int)list_unique_ids.size());\n    }\n\n    //List overlays in a scrollable child container\n    ImGui::TextColoredUnformatted(ImGui::GetStyleColorVec4(ImGuiCol_ButtonHovered), TranslationManager::GetString(tstr_DesktopModeCatOverlays)); \n    ImGui::Indent();\n\n    ImGui::BeginChild(\"OverlayList\", ImVec2(0.0f, 0.0f), true);\n\n    static unsigned int hovered_overlay_id_last     = k_ulOverlayID_None;\n    static unsigned int right_down_overlay_id       = k_ulOverlayID_None;\n    static unsigned int keyboard_swapped_overlay_id = k_ulOverlayID_None;\n    static bool add_overlay_right_down = false;\n    unsigned int hovered_overlay_id = k_ulOverlayID_None;\n    bool has_swapped = false;\n\n    ImVec4 color_text_hidden = ImGui::GetStyleColorVec4(ImGuiCol_Text);\n    color_text_hidden.w = 0.5f;\n\n    for (unsigned int i = 0; i < u_overlay_count; ++i)\n    {\n        ImGui::PushID(list_unique_ids[i]);\n\n        //Force active header color when a menu is active or right mouse button is held down\n        const bool is_active = ( (m_OverlayListActiveMenuID == i) || (right_down_overlay_id == i) );\n\n        if (is_active)\n        {\n            ImGui::PushStyleColor(ImGuiCol_Header,        ImGui::GetStyleColorVec4(ImGuiCol_HeaderActive));\n            ImGui::PushStyleColor(ImGuiCol_HeaderHovered, ImGui::GetStyleColorVec4(ImGuiCol_HeaderActive));\n        }\n\n        //Set focus for nav if we just came back from the properties page and this is the active overlay\n        if ( (io.NavVisible) && (m_PageReturned == wnddesktopmode_page_properties) && (UIManager::Get()->GetOverlayPropertiesWindow().GetActiveOverlayID() == i) )\n        {\n            ImGui::SetKeyboardFocusHere();\n            m_PageReturned = wnddesktopmode_page_none;\n        }\n\n        //Set focus for nav if we previously re-ordered overlays via keyboard\n        if (keyboard_swapped_overlay_id == i)\n        {\n            ImGui::SetKeyboardFocusHere();\n\n            //Nav works against us here, so keep setting focus until ctrl isn't down anymore\n            if ((!io.KeyCtrl) || (!io.NavVisible))\n            {\n                keyboard_swapped_overlay_id = k_ulOverlayID_None;\n            }\n        }\n\n        //Use empty label here. Icon and actual label are manually created further down\n        if (ImGui::Selectable(\"\", is_active))\n        {\n            if (!m_IsDraggingOverlaySelectables)\n            {\n                UIManager::Get()->GetOverlayPropertiesWindow().SetActiveOverlayID(i, true);\n                PageGoForward(wnddesktopmode_page_properties);\n\n                HideMenus();\n            }\n        }\n\n        if (is_active)\n        {\n            ImGui::PopStyleColor();\n            ImGui::PopStyleColor();\n        }\n\n        if (ImGui::IsItemVisible())\n        {\n            if (ImGui::IsItemHovered())\n            {\n                hovered_overlay_id = i;\n            }\n\n            //Additional selectable behavior\n            bool selectable_active = ImGui::IsItemActive();\n\n            if ( (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenBlockedByActiveItem)) || ((io.NavVisible) && (ImGui::IsItemFocused())) )\n            {\n                drag_last_hovered_selectable = i;\n                hovered_overlay_id = i;\n\n                if ( (ImGui::IsMouseReleased(ImGuiMouseButton_Right)) || (ImGui::IsKeyReleased(ImGuiKey_Menu)) )\n                {\n                    if ((m_OverlayListActiveMenuID != i) && (!m_IsDraggingOverlaySelectables))\n                    {\n                        HideMenus();\n                        m_OverlayListActiveMenuID = i;\n                        ImGui::OpenPopup(\"OverlayListMenu\");\n                    }\n\n                    selectable_active = true;\n                }\n                else if ( (ImGui::IsMouseDown(ImGuiMouseButton_Right)) || (ImGui::IsKeyDown(ImGuiKey_Menu)) )\n                {\n                    right_down_overlay_id = i;   //For correct right-click visual\n                }\n            }\n\n            //Drag reordering\n            if ((ImGui::IsItemActive()) && (!ImGui::IsItemHovered()))\n            {\n                int index_swap = i + ((ImGui::GetMouseDragDelta(ImGuiMouseButton_Left).y < 0.0f) ? -1 : 1);\n                if ((drag_last_hovered_selectable != i) && (index_swap >= 0) && (index_swap < overlay_count))\n                {\n                    OverlayManager::Get().SwapOverlays(i, index_swap);\n                    IPCManager::Get().PostConfigMessageToDashboardApp(configid_int_state_overlay_current_id_override, i);\n                    IPCManager::Get().PostMessageToDashboardApp(ipcmsg_action, ipcact_overlay_swap, index_swap);\n                    IPCManager::Get().PostConfigMessageToDashboardApp(configid_int_state_overlay_current_id_override, -1);\n\n                    std::iter_swap(list_unique_ids.begin() + i, list_unique_ids.begin() + index_swap);\n\n                    ImGui::ResetMouseDragDelta(ImGuiMouseButton_Left);\n                    m_IsDraggingOverlaySelectables = true;\n\n                    m_OverlayListActiveMenuID = k_ulOverlayID_None;\n                }\n            }\n\n            //Keyboard reordering\n            if ((io.NavVisible) && (io.KeyCtrl) && (hovered_overlay_id == i))\n            {\n                int index_swap = i + ((ImGui::IsNavInputPressed(ImGuiNavInput_DpadDown, true)) ? 1 : (ImGui::IsNavInputPressed(ImGuiNavInput_DpadUp, true)) ? -1 : 0);\n                if ((i != index_swap) && (index_swap >= 0) && (index_swap < overlay_count))\n                {\n                    OverlayManager::Get().SwapOverlays(i, index_swap);\n                    IPCManager::Get().PostConfigMessageToDashboardApp(configid_int_state_overlay_current_id_override, i);\n                    IPCManager::Get().PostMessageToDashboardApp(ipcmsg_action, ipcact_overlay_swap, index_swap);\n                    IPCManager::Get().PostConfigMessageToDashboardApp(configid_int_state_overlay_current_id_override, -1);\n\n                    std::iter_swap(list_unique_ids.begin() + i, list_unique_ids.begin() + index_swap);\n\n                    m_OverlayListActiveMenuID = k_ulOverlayID_None;\n\n                    //Skip the rest of this frame to avoid double-swaps\n                    keyboard_swapped_overlay_id = index_swap;\n                    ImGui::PopID();\n                    UIManager::Get()->RepeatFrame();\n                    break;\n                }\n            }\n\n            if (m_OverlayListActiveMenuID == i)\n            {\n                MenuOverlayList(i);\n\n                //Check if menu modified overlay count and bail then\n                if (OverlayManager::Get().GetOverlayCount() != u_overlay_count)\n                {\n                    ImGui::PopID();\n                    UIManager::Get()->RepeatFrame();\n                    break;\n                }\n\n                hovered_overlay_id = i;\n            }\n\n            //Custom render the selectable label with icons\n            OverlayConfigData& data = OverlayManager::Get().GetConfigData(i);\n            const ImVec4 tint_color = ImVec4(1.0f, 1.0f, 1.0f, data.ConfigBool[configid_bool_overlay_enabled] ? 1.0f : 0.5f); //Transparent when hidden\n            //Overlay icon\n            ImGui::SameLine(0.0f, style.ItemInnerSpacing.x);\n            TextureManager::Get().GetOverlayIconTextureInfo(data, img_size, img_uv_min, img_uv_max, true);\n            ImGui::ImageWithBg(ImGui::GetIO().Fonts->TexID, img_size_line_height, img_uv_min, img_uv_max, {0, 0, 0, 0}, tint_color);\n\n            //Origin icon\n            ImGui::SameLine(0.0f, style.ItemInnerSpacing.x);\n            TextureManager::Get().GetTextureInfo((TMNGRTexID)(tmtex_icon_xsmall_origin_room + data.ConfigInt[configid_int_overlay_origin]), img_size, img_uv_min, img_uv_max);\n            ImGui::ImageWithBg(ImGui::GetIO().Fonts->TexID, img_size_line_height, img_uv_min, img_uv_max, {0, 0, 0, 0}, tint_color);\n\n            //Label\n            ImGui::SameLine(0.0f, style.ItemInnerSpacing.x);\n            ImGui::TextColoredUnformatted(data.ConfigBool[configid_bool_overlay_enabled] ? ImGui::GetStyleColorVec4(ImGuiCol_Text) : color_text_hidden, data.ConfigNameStr.c_str());\n        }\n\n        ImGui::PopID();\n    }\n\n    //Static Add Overlay entry\n    {\n        if (u_overlay_count != 0)\n        {\n            ImGui::Separator();\n        }\n\n        //Force active header color when a menu is active or right mouse button is held down\n        const bool is_active = ( (m_IsOverlayAddMenuVisible) || (add_overlay_right_down) );\n\n        if (is_active)\n        {\n            ImGui::PushStyleColor(ImGuiCol_Header,        ImGui::GetStyleColorVec4(ImGuiCol_HeaderActive));\n            ImGui::PushStyleColor(ImGuiCol_HeaderHovered, ImGui::GetStyleColorVec4(ImGuiCol_HeaderActive));\n        }\n\n        //Use empty label here. Icon and actual label are manually created further down\n        if (ImGui::Selectable(\"##AddOverlay\", is_active))\n        {\n            if (!m_IsDraggingOverlaySelectables)\n            {\n                HideMenus();\n                m_IsOverlayAddMenuVisible = true;\n                ImGui::OpenPopup(\"AddOverlayMenu\");\n            }\n        }\n\n        if (is_active)\n        {\n            ImGui::PopStyleColor();\n            ImGui::PopStyleColor();\n        }\n\n        if (ImGui::IsItemVisible())\n        {\n            //Also allow right-click/menu key as the result looks like a context menu\n            if ((ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenBlockedByActiveItem)) || ((io.NavVisible) && (ImGui::IsItemFocused())))\n            {\n                if ((ImGui::IsMouseReleased(ImGuiMouseButton_Right)) || (ImGui::IsKeyReleased(ImGuiKey_Menu)))\n                {\n                    if ((!m_IsOverlayAddMenuVisible) && (!m_IsDraggingOverlaySelectables))\n                    {\n                        HideMenus();\n                        m_IsOverlayAddMenuVisible = true;\n                        ImGui::OpenPopup(\"AddOverlayMenu\");\n                    }\n                }\n                else if ((ImGui::IsMouseDown(ImGuiMouseButton_Right)) || (ImGui::IsKeyDown(ImGuiKey_Menu)))\n                {\n                    add_overlay_right_down = true;   //For correct right-click visual\n                }\n            }\n\n            //Custom render the selectable label with icon\n            ImGui::SameLine(0.0f, style.ItemInnerSpacing.x);\n            TextureManager::Get().GetTextureInfo(tmtex_icon_add, img_size, img_uv_min, img_uv_max);\n            ImGui::Image(ImGui::GetIO().Fonts->TexID, img_size_line_height, img_uv_min, img_uv_max);\n\n            //Label\n            ImGui::SameLine(0.0f, style.ItemInnerSpacing.x);\n            ImGui::TextUnformatted(TranslationManager::GetString(tstr_DesktopModeOverlayListAdd));\n        }\n\n        if (m_IsOverlayAddMenuVisible)\n        {\n            MenuAddOverlay();\n        }\n    }\n\n    //Don't change overlay highlight while mouse down as it won't be correct while dragging and flicker just before it\n    if ( (!ImGui::IsMouseDown(ImGuiMouseButton_Left)) && ((hovered_overlay_id_last != k_ulOverlayID_None) || (hovered_overlay_id != k_ulOverlayID_None)) )\n    {\n        UIManager::Get()->HighlightOverlay(hovered_overlay_id);\n        hovered_overlay_id_last = hovered_overlay_id;\n    }\n\n    if (ImGui::IsMouseReleased(ImGuiMouseButton_Left))\n    {\n        m_IsDraggingOverlaySelectables = false;\n    }\n\n    if ( (ImGui::IsMouseReleased(ImGuiMouseButton_Right)) || (ImGui::IsKeyReleased(ImGuiKey_Menu)) )\n    {\n        right_down_overlay_id = k_ulOverlayID_None;\n        add_overlay_right_down = false;\n    }\n\n    ImGui::EndChild();\n    ImGui::Unindent();\n}\n\nvoid WindowDesktopMode::UpdatePageAddWindowOverlay()\n{\n    ImGui::TextColoredUnformatted(ImGui::GetStyleColorVec4(ImGuiCol_ButtonHovered), TranslationManager::GetString(tstr_DesktopModePageAddWindowOverlayHeader));\n    ImGui::Indent();\n\n    ImGui::SetNextItemWidth(-1.0f);\n    ImGui::BeginChild(\"WindowList\", ImVec2(0.0f, ImGui::GetContentRegionAvail().y - ImGui::GetFrameHeightWithSpacing() - ImGui::GetStyle().ItemSpacing.y), true);\n\n    ImVec2 img_size_line_height = {ImGui::GetTextLineHeight(), ImGui::GetTextLineHeight()};\n    ImVec2 img_size, img_uv_min, img_uv_max;\n\n    //List windows\n    for (const auto& window_info : WindowManager::Get().WindowListGet())\n    {\n        ImGui::PushID(window_info.GetWindowHandle());\n        if (ImGui::Selectable(\"\"))\n        {\n            //Add overlay\n            HWND window_handle = window_info.GetWindowHandle();\n            unsigned int overlay_id_new = OverlayManager::Get().AddOverlay(ovrl_capsource_winrt_capture, -2, window_handle);\n\n            //Send to dashboard app\n            IPCManager::Get().PostConfigMessageToDashboardApp(configid_handle_state_arg_hwnd, (LPARAM)window_handle);\n            IPCManager::Get().PostMessageToDashboardApp(ipcmsg_action, ipcact_overlay_new, -2);\n\n            //Adjust width to a more suited default (done here as well so it's set even without dashboard app running)\n            OverlayManager::Get().GetConfigData(overlay_id_new).ConfigFloat[configid_float_overlay_width] = 1.0f;\n\n            PageGoBack();\n        }\n\n        ImGui::SameLine(0.0f, 0.0f);\n\n        int icon_id = TextureManager::Get().GetWindowIconCacheID(window_info.GetIcon());\n\n        if (icon_id != -1)\n        {\n            TextureManager::Get().GetWindowIconTextureInfo(icon_id, img_size, img_uv_min, img_uv_max);\n            ImGui::Image(ImGui::GetIO().Fonts->TexID, img_size_line_height, img_uv_min, img_uv_max);\n\n            ImGui::SameLine(0.0f, ImGui::GetStyle().ItemInnerSpacing.x);\n        }\n\n        ImGui::TextUnformatted(window_info.GetListTitle().c_str());\n\n        ImGui::PopID();\n    }\n\n    ImGui::EndChild();\n\n    ImGui::Unindent();\n\n    ImGui::SetCursorPosY(ImGui::GetCursorPosY() + (ImGui::GetContentRegionAvail().y - ImGui::GetFrameHeight()));\n\n    //--Cancel button\n    //No separator since this is right after a boxed child window\n\n    if (ImGui::Button(TranslationManager::GetString(tstr_DialogCancel)))\n    {\n        PageGoBack();\n    }\n}\n\nvoid WindowDesktopMode::MenuOverlayList(unsigned int overlay_id)\n{\n    m_MenuAlpha += ImGui::GetIO().DeltaTime * 12.0f;\n\n    if (m_MenuAlpha > 1.0f)\n        m_MenuAlpha = 1.0f;\n\n    ImGui::PushStyleVar(ImGuiStyleVar_Alpha, m_MenuAlpha);\n    ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 0.0f);\n\n    if (ImGui::BeginPopup(\"OverlayListMenu\", ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoSavedSettings |\n                                             ImGuiWindowFlags_AlwaysAutoResize))\n    {\n        if ( (!ImGui::IsWindowHovered(ImGuiHoveredFlags_AllowWhenBlockedByActiveItem)) && (ImGui::IsAnyMouseClicked()) )\n        {\n            HideMenus();\n        }\n\n        bool& is_enabled = OverlayManager::Get().GetConfigData(overlay_id).ConfigBool[configid_bool_overlay_enabled];\n        WindowOverlayProperties& properties_window = UIManager::Get()->GetOverlayPropertiesWindow();\n\n        if (ImGui::Selectable(TranslationManager::GetString((is_enabled) ? tstr_OverlayBarOvrlHide : tstr_OverlayBarOvrlShow), false))\n        {\n            is_enabled = !is_enabled;\n\n            IPCManager::Get().PostConfigMessageToDashboardApp(configid_int_state_overlay_current_id_override, (int)overlay_id);\n            IPCManager::Get().PostConfigMessageToDashboardApp(configid_bool_overlay_enabled, is_enabled);\n            IPCManager::Get().PostConfigMessageToDashboardApp(configid_int_state_overlay_current_id_override, -1);\n\n            HideMenus();\n        }\n\n        if (ImGui::Selectable(TranslationManager::GetString(tstr_OverlayBarOvrlClone), false))\n        {\n            //Copy data of overlay and add a new one based on it\n            OverlayManager::Get().DuplicateOverlay(OverlayManager::Get().GetConfigData(overlay_id), overlay_id);\n            IPCManager::Get().PostMessageToDashboardApp(ipcmsg_action, ipcact_overlay_duplicate, (int)overlay_id);\n\n            HideMenus();\n        }\n\n        if (m_IsMenuRemoveConfirmationVisible)\n        {\n            if (ImGui::Selectable(TranslationManager::GetString(tstr_OverlayBarOvrlRemoveConfirm), false))\n            {\n                OverlayManager::Get().RemoveOverlay(overlay_id);\n                IPCManager::Get().PostMessageToDashboardApp(ipcmsg_action, ipcact_overlay_remove, overlay_id);\n\n                HideMenus();\n            }\n\n            if (ImGui::IsNavInputReleased(ImGuiNavInput_Activate))\n            {\n                ImGui::SetKeyboardFocusHere(-1);\n            }\n        }\n        else\n        {\n            if (ImGui::Selectable(TranslationManager::GetString(tstr_OverlayBarOvrlRemove), false, ImGuiSelectableFlags_NoAutoClosePopups))\n            {\n                m_IsMenuRemoveConfirmationVisible = true;\n                UIManager::Get()->RepeatFrame();\n            }\n        }\n\n        if (ImGui::IsNavInputPressed(ImGuiNavInput_Cancel))\n        {\n            HideMenus();\n        }\n\n        ImGui::EndPopup();\n    }\n\n    ImGui::PopStyleVar();\n    ImGui::PopStyleVar();\n\n    //Handle popup being closed from nav\n    if (!ImGui::IsPopupOpen(\"OverlayListMenu\"))\n    {\n        HideMenus();\n    }\n}\n\nvoid WindowDesktopMode::MenuAddOverlay()\n{\n    m_MenuAlpha += ImGui::GetIO().DeltaTime * 12.0f;\n\n    if (m_MenuAlpha > 1.0f)\n        m_MenuAlpha = 1.0f;\n\n    ImGui::PushStyleVar(ImGuiStyleVar_Alpha, m_MenuAlpha);\n    ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 0.0f);\n\n    if (ImGui::BeginPopup(\"AddOverlayMenu\", ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoSavedSettings |\n                                            ImGuiWindowFlags_AlwaysAutoResize))\n    {\n        if ( (!ImGui::IsWindowHovered(ImGuiHoveredFlags_AllowWhenBlockedByActiveItem)) && (ImGui::IsAnyMouseClicked()) )\n        {\n            HideMenus();\n        }\n\n        int desktop_count = ConfigManager::GetValue(configid_int_state_interface_desktop_count);\n\n        int new_overlay_desktop_id = -255;\n\n        for (int i = 0; i < desktop_count; ++i)\n        {\n            ImGui::PushID(i);\n\n            if (ImGui::Selectable(TranslationManager::Get().GetDesktopIDString(i)))\n            {\n                new_overlay_desktop_id = i;\n            }\n\n            ImGui::PopID();\n        }\n\n        if ( (DPWinRT_IsCaptureSupported()) && (ImGui::Selectable(TranslationManager::GetString(tstr_OverlayBarOvrlAddWindow), false)) )\n        {\n            HideMenus();\n            PageGoForward(wnddesktopmode_page_add_window_overlay);\n        }\n\n        //Don't offer Performance Monitor in desktop mode as this kind of overlay won't be visible after creation since its window isn't rendered in desktop mode\n        /*if (ImGui::Selectable(TranslationManager::GetString(tstr_SourcePerformanceMonitor)))\n        {\n            new_overlay_desktop_id = -3;\n        }*/\n\n        if (DPBrowserAPIClient::Get().IsBrowserAvailable())\n        {\n            if (ImGui::Selectable(TranslationManager::GetString(tstr_SourceBrowser)))\n            {\n                new_overlay_desktop_id = -4;\n            }\n        }\n\n        //Create new overlay if desktop or UI/Browser selectables were triggered\n        if (new_overlay_desktop_id != -255)\n        {\n            //Choose capture source\n            OverlayCaptureSource capsource;\n\n            switch (new_overlay_desktop_id)\n            {\n                case -3: capsource = ovrl_capsource_ui;      break;\n                case -4: capsource = ovrl_capsource_browser; break;\n                default: capsource = ovrl_capsource_desktop_duplication;\n            }\n\n            //Add overlay and sent to dashboard app\n            unsigned int overlay_id_new = OverlayManager::Get().AddOverlay(capsource, new_overlay_desktop_id);\n            IPCManager::Get().PostMessageToDashboardApp(ipcmsg_action, ipcact_overlay_new, new_overlay_desktop_id);\n            \n            //Adjust width to a more suited default (done here as well so it's set even without dashboard app running)\n            OverlayManager::Get().GetConfigData(overlay_id_new).ConfigFloat[configid_float_overlay_width] = 1.0f;\n\n            HideMenus();\n        }\n\n        ImGui::EndPopup();\n    }\n\n    ImGui::PopStyleVar();\n    ImGui::PopStyleVar();\n\n    //Handle popup being closed from nav\n    if (!ImGui::IsPopupOpen(\"AddOverlayMenu\"))\n    {\n        HideMenus();\n    }\n}\n\nvoid WindowDesktopMode::HideMenus()\n{\n    m_MenuAlpha = 0.0f;\n    m_OverlayListActiveMenuID = k_ulOverlayID_None;\n    m_IsOverlayAddMenuVisible = false;\n    m_IsMenuRemoveConfirmationVisible = false;\n\n    UIManager::Get()->RepeatFrame();\n}\n\nvoid WindowDesktopMode::PageGoForward(WindowDesktopModePage new_page)\n{\n    m_PageStack.push_back(new_page);\n    m_PageStackPos++;\n}\n\nvoid WindowDesktopMode::PageGoBack()\n{\n    if (m_PageStackPos != 0)\n    {\n        m_PageStackPos--;\n        m_PageReturned = m_PageStack.back();\n    }\n}\n\nvoid WindowDesktopMode::Update()\n{\n    ImGuiStyle& style = ImGui::GetStyle();\n\n    ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 0.0f);\n    ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 0.0f);\n\n    ImGui::SetNextWindowPos({0.0f,0.0f});\n    ImGui::SetNextWindowSize(ImGui::GetIO().DisplaySize);\n    ImGui::Begin(\"##WindowDesktopMode\", nullptr, ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoBringToFrontOnFocus);\n\n    ImGui::PopStyleVar(2);\n\n    UpdateTitleBar();\n    UIManager::Get()->GetSettingsWindow().UpdateDesktopModeWarnings();\n\n    const float page_width = ImGui::GetIO().DisplaySize.x - style.WindowPadding.x - style.WindowPadding.x;\n\n    //Page animation\n    if (m_PageAnimationDir != 0)\n    {\n        //Use the averaged framerate value instead of delta time for the first animation step\n        //This is to smooth over increased frame deltas that can happen when a new page needs to do initial larger computations or save/load files\n        const float progress_step = (m_PageAnimationProgress == 0.0f) ? (1.0f / ImGui::GetIO().Framerate) * 3.0f : ImGui::GetIO().DeltaTime * 3.0f;\n        m_PageAnimationProgress += progress_step;\n\n        if (m_PageAnimationProgress >= 1.0f)\n        {\n            //Remove pages in the stack after finishing going back\n            if (m_PageAnimationDir == 1)\n            {\n                while ((int)m_PageStack.size() > m_PageStackPosAnimation + 1)\n                {\n                    m_PageStack.pop_back();\n                }\n            }\n\n            m_PageAnimationProgress = 1.0f;\n            m_PageAnimationDir      = 0;\n        }\n    }\n    else if (m_PageStackPosAnimation != m_PageStackPos) //Only start new animation if none is running\n    {\n        m_PageAnimationDir      = (m_PageStackPosAnimation < m_PageStackPos) ? -1 : 1;\n        m_PageStackPosAnimation = m_PageStackPos;\n        m_PageAnimationStartPos = m_PageAnimationOffset;\n        m_PageAnimationProgress = 0.0f;\n\n        //Set appearing value to top of stack when starting animation to it\n        if (m_PageAnimationDir == -1)\n        {\n            m_PageAppearing = m_PageStack.back();\n        }\n    }\n\n    const float target_x = (page_width + style.ItemSpacing.x) * -m_PageStackPosAnimation;\n    m_PageAnimationOffset = smoothstep(m_PageAnimationProgress, m_PageAnimationStartPos, target_x);\n\n    //Set up page offset and clipping\n    ImGui::SetCursorPosX( (ImGui::GetCursorPosX() + m_PageAnimationOffset) );\n\n    ImGui::PushClipRect({0.0f, 0.0f}, {FLT_MAX, FLT_MAX}, false);\n\n    const char* const child_str_id[] {\"DesktopModePageMain\", \"DesktopModePage1\", \"DesktopModePage2\", \"DesktopModePage3\"}; //No point in generating these on the fly\n    const ImVec2 child_size = {page_width, ImGui::GetContentRegionAvail().y};\n    int child_id = 0;\n    int stack_size = (int)m_PageStack.size();\n    for (WindowDesktopModePage page_id : m_PageStack)\n    {\n        if (child_id >= IM_ARRAYSIZE(child_str_id))\n            break;\n\n        //Disable items when the page isn't active\n        const bool is_inactive_page = (child_id != m_PageStackPos);\n\n        if (is_inactive_page)\n        {\n            ImGui::PushItemDisabledNoVisual();\n        }\n\n        ImGui::PushStyleColor(ImGuiCol_ChildBg, ImVec4(0.00f, 0.00f, 0.00f, 0.00f)); //This prevents child bg color being visible if there's a widget before this (e.g. warnings)\n\n        if ( (ImGui::BeginChild(child_str_id[child_id], child_size, ImGuiChildFlags_NavFlattened)) || (m_PageAppearing == page_id) ) //Process page if currently appearing\n        {\n            ImGui::PopStyleColor(); //ImGuiCol_ChildBg\n\n            switch (page_id)\n            {\n                case wnddesktopmode_page_main:               UpdatePageMain();                                                   break;\n                case wnddesktopmode_page_settings:           /*fallthrough*/\n                case wnddesktopmode_page_profiles:           /*fallthrough*/\n                case wnddesktopmode_page_app_profiles:       /*fallthrough*/\n                case wnddesktopmode_page_actions:            UIManager::Get()->GetSettingsWindow().UpdateDesktopMode();          break;\n                case wnddesktopmode_page_properties:         UIManager::Get()->GetOverlayPropertiesWindow().UpdateDesktopMode(); break;\n                case wnddesktopmode_page_add_window_overlay: UpdatePageAddWindowOverlay();                                       break;\n                default: break;\n            }\n        }\n        else\n        {\n            ImGui::PopStyleColor(); //ImGuiCol_ChildBg\n        }\n\n        if (is_inactive_page)\n        {\n            ImGui::PopItemDisabledNoVisual();\n        }\n\n        ImGui::EndChild();\n\n        if (child_id + 1 < stack_size)\n        {\n            ImGui::SameLine();\n        }\n\n        child_id++;\n    }\n\n    m_PageAppearing = wnddesktopmode_page_none;\n\n    ImGui::PopClipRect();\n\n    ImGui::End();\n\n    //Display any potential Aux UI on top of this window\n    UIManager::Get()->GetAuxUI().Update();\n}\n\nImVec4 WindowDesktopMode::GetTitleBarRect() const\n{\n    return m_TitleBarRect;\n}\n"
  },
  {
    "path": "src/DesktopPlusUI/WindowDesktopMode.h",
    "content": "#pragma once\n\n#include <vector>\n#include \"imgui.h\"\n\n#include \"OverlayManager.h\"\n\nenum WindowDesktopModePage\n{\n    wnddesktopmode_page_none,\n    wnddesktopmode_page_main,\n    wnddesktopmode_page_settings,\n    wnddesktopmode_page_profiles,\n    wnddesktopmode_page_app_profiles,\n    wnddesktopmode_page_actions,\n    wnddesktopmode_page_properties,\n    wnddesktopmode_page_add_window_overlay\n};\n\n//Interop functions for windows that have their content hosted as a sub-page in desktop mode\nclass FloatingWindowDesktopModeInterop\n{\n    public:\n        virtual const char* DesktopModeGetTitle() const = 0;\n        virtual bool DesktopModeGetIconTextureInfo(ImVec2& size, ImVec2& uv_min, ImVec2& uv_max) const = 0;   //Returns false on no icon\n        virtual float DesktopModeGetTitleIconAlpha() const       { return 1.0f; }\n        virtual void DesktopModeOnTitleIconClick()               {};\n        virtual void DesktopModeOnTitleBarHover(bool is_hovered) {};\n        virtual bool DesktopModeGoBack() = 0;                                                                 //Returns false if already on main page\n};\n\nclass WindowDesktopMode\n{\n    private:\n        std::vector<WindowDesktopModePage> m_PageStack;\n        int m_PageStackPos = 0;\n        int m_PageStackPosAnimation = 0;\n        WindowDesktopModePage m_PageAppearing = wnddesktopmode_page_none; //Similar to ImGui::IsWindowAppearing(), equals the current page ID for a single frame if it or the window is newly appearing\n        WindowDesktopModePage m_PageReturned  = wnddesktopmode_page_none; //Equals the previous page ID after PageGoBack() was called, ideally cleared after making use of its value\n\n        ImVec4 m_TitleBarRect;\n\n        int m_PageAnimationDir        = 0;\n        float m_PageAnimationProgress = 0.0f;\n        float m_PageAnimationStartPos = 0.0f;\n        float m_PageAnimationOffset   = 0.0f;\n\n        unsigned int m_OverlayListActiveMenuID = k_ulOverlayID_None;\n        float m_MenuAlpha                      = 0.0f;\n        bool m_IsOverlayAddMenuVisible         = false;\n        bool m_IsMenuRemoveConfirmationVisible = false;\n        bool m_IsDraggingOverlaySelectables    = false;\n\n        void UpdateTitleBar();\n        void UpdatePageMain();\n        void UpdatePageMainOverlayList();\n        void UpdatePageAddWindowOverlay();\n\n        void MenuOverlayList(unsigned int overlay_id);\n        void MenuAddOverlay();\n        void HideMenus();\n\n        void PageGoForward(WindowDesktopModePage new_page);\n        void PageGoBack();\n\n    public:\n        WindowDesktopMode();\n        void Update();\n\n        ImVec4 GetTitleBarRect() const;\n};"
  },
  {
    "path": "src/DesktopPlusUI/WindowFloatingUIBar.cpp",
    "content": "#include \"WindowFloatingUIBar.h\"\n\n#include \"ImGuiExt.h\"\n#include \"TextureManager.h\"\n#include \"InterprocessMessaging.h\"\n#include \"Util.h\"\n#include \"OpenVRExt.h\"\n#include \"UIManager.h\"\n#include \"OverlayManager.h\"\n#include \"WindowManager.h\"\n#include \"DesktopPlusWinRT.h\"\n#include \"DPBrowserAPIClient.h\"\n\n//-WindowFloatingUIMainBar\nWindowFloatingUIMainBar::WindowFloatingUIMainBar() : m_IsCurrentWindowCapturable(-1), m_AnimationProgress(0.0f)\n{\n\n}\n\nvoid WindowFloatingUIMainBar::DisplayTooltipIfHovered(const char* text)\n{\n    if ( ((m_AnimationProgress == 0.0f) || (m_AnimationProgress == 1.0f)) && (ImGui::IsItemHovered()) ) //Also hide while animating\n    {\n        const ImGuiStyle& style = ImGui::GetStyle();\n\n        static ImVec2 button_pos_last; //Remember last position and use it when posible. This avoids flicker when the same tooltip string is used in different places\n        ImVec2 pos = ImGui::GetItemRectMin();\n        float button_width = ImGui::GetItemRectSize().x;\n\n        //Default tooltips are not suited for this as there's too much trouble with resize flickering and stuff\n        ImGui::Begin(text, nullptr, ImGuiWindowFlags_NoInputs | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize | \n                                    ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoFocusOnAppearing);\n\n        ImGui::TextUnformatted(text);\n\n        //Not using GetWindowSize() here since it's delayed and plays odd when switching between buttons with the same label\n        ImVec2 window_size = ImGui::GetItemRectSize();\n        window_size.x += style.WindowPadding.x * 2.0f;\n        window_size.y += style.WindowPadding.y * 2.0f;\n\n        //Repeat frame when the window is appearing as it will not have the right position (either from being first time or still having old pos)\n        if ( (ImGui::IsWindowAppearing()) || (pos.x != button_pos_last.x) )\n        {\n            UIManager::Get()->RepeatFrame();\n        }\n\n        button_pos_last = pos;\n\n        pos.x += (button_width / 2.0f) - (window_size.x / 2.0f);\n        pos.y += button_width + style.WindowPadding.y;\n\n        //Clamp x so the tooltip does not get cut off\n        pos.x = clamp(pos.x, 0.0f, ImGui::GetIO().DisplaySize.x - window_size.x);   //Clamp right side to texture end\n\n        ImGui::SetWindowPos(pos);\n\n        ImGui::End();\n    }\n}\n\nvoid WindowFloatingUIMainBar::Update(float actionbar_height, unsigned int overlay_id)\n{\n    OverlayConfigData& overlay_data = OverlayManager::Get().GetConfigData(overlay_id);\n\n    ImGuiIO& io = ImGui::GetIO();\n\n    ImVec2 b_size, b_uv_min, b_uv_max;\n    TextureManager::Get().GetTextureInfo(tmtex_icon_small_close, b_size, b_uv_min, b_uv_max);\n    const ImGuiStyle& style = ImGui::GetStyle();\n\n    //Put window near the bottom of the overlay with space for the tooltips + padding (touching action-bar when visible)\n    const float offset_base = ImGui::GetFontSize() + style.FramePadding.y + (style.WindowPadding.y * 2.0f) + 3.0f;\n    const float offset_y = smoothstep(m_AnimationProgress, offset_base + actionbar_height /* Action-Bar not visible */, offset_base /* Action-Bar visible */);\n\n    ImGui::SetNextWindowPos(ImVec2(io.DisplaySize.x / 2.0f, io.DisplaySize.y - offset_y), 0, ImVec2(0.5f, 1.0f));\n    ImGui::Begin(\"WindowFloatingUIMainBar\", nullptr, ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoFocusOnAppearing |\n                                                     ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_HorizontalScrollbar);\n\n    ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 4.0f);\n    ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, {style.ItemSpacing.y, style.ItemSpacing.y});\n    ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0, 0, 0, 0));\n\n    //Show Action-Bar Button (this is a per-overlay state)\n    bool& is_actionbar_enabled = overlay_data.ConfigBool[configid_bool_overlay_actionbar_enabled];\n    bool actionbar_was_enabled = is_actionbar_enabled;\n\n    if (actionbar_was_enabled)\n        ImGui::PushStyleColor(ImGuiCol_Button, Style_ImGuiCol_ButtonPassiveToggled);\n\n    TextureManager::Get().GetTextureInfo(tmtex_icon_small_actionbar, b_size, b_uv_min, b_uv_max);\n    if (ImGui::ImageButton(\"ToggleActionBar\", io.Fonts->TexID, b_size, b_uv_min, b_uv_max))\n    {\n        is_actionbar_enabled = !is_actionbar_enabled;\n        //This is an UI state so no need to sync\n    }\n\n    if (actionbar_was_enabled)\n        ImGui::PopStyleColor();\n\n    DisplayTooltipIfHovered(TranslationManager::GetString( (actionbar_was_enabled) ? tstr_FloatingUIActionBarHideTip : tstr_FloatingUIActionBarShowTip ));\n    //\n\n    ImGui::SameLine();\n\n    //Extra buttons\n    if (overlay_data.ConfigBool[configid_bool_overlay_floatingui_extras_enabled])\n    {\n        //Add current window as overlay (only show if desktop duplication or non-window WinRT capture)\n        if (  (overlay_data.ConfigInt[configid_int_overlay_capture_source] == ovrl_capsource_desktop_duplication) ||\n             ((overlay_data.ConfigInt[configid_int_overlay_capture_source] == ovrl_capsource_winrt_capture) && (overlay_data.ConfigHandle[configid_handle_overlay_state_winrt_hwnd] == 0)) )\n        {\n            //If marked to need update, refresh actual state\n            if (m_IsCurrentWindowCapturable == -1)\n            {\n                m_IsCurrentWindowCapturable = (WindowManager::Get().WindowListFindWindow(::GetForegroundWindow()) != nullptr);\n            }\n\n            if (m_IsCurrentWindowCapturable != 1)\n                ImGui::PushItemDisabled();\n\n            TextureManager::Get().GetTextureInfo(tmtex_icon_small_add_window, b_size, b_uv_min, b_uv_max);\n            ImGui::ImageButton(\"AddWindow\", io.Fonts->TexID, b_size, b_uv_min, b_uv_max); //This one's activated on mouse down\n\n            if (ImGui::IsItemActivated())\n            {\n                vr::TrackedDeviceIndex_t device_index = ConfigManager::Get().GetPrimaryLaserPointerDevice();\n\n                //If no dashboard device, try finding one\n                if (device_index == vr::k_unTrackedDeviceIndexInvalid)\n                {\n                    device_index = vr::IVROverlayEx::FindPointerDeviceForOverlay(UIManager::Get()->GetOverlayHandleFloatingUI());\n                }\n\n                //Try to get the pointer distance\n                float source_distance = 1.0f;\n                vr::VROverlayIntersectionResults_t results;\n\n                if (vr::IVROverlayEx::ComputeOverlayIntersectionForDevice(UIManager::Get()->GetOverlayHandleFloatingUI(), device_index, vr::TrackingUniverseStanding, &results))\n                {\n                    source_distance = results.fDistance;\n                }\n\n                //Set pointer hint in case dashboard app needs it\n                ConfigManager::SetValue(configid_int_state_laser_pointer_device_hint, (int)device_index);\n                IPCManager::Get().PostConfigMessageToDashboardApp(configid_int_state_laser_pointer_device_hint, (int)device_index);\n\n                //Add overlay\n                HWND current_window = ::GetForegroundWindow();\n                OverlayManager::Get().AddOverlay(ovrl_capsource_winrt_capture, -2, current_window);\n\n                //Send to dashboard app\n                IPCManager::Get().PostConfigMessageToDashboardApp(configid_handle_state_arg_hwnd, (LPARAM)current_window);\n                IPCManager::Get().PostMessageToDashboardApp(ipcmsg_action, ipcact_overlay_new_drag, MAKELPARAM(-2, (source_distance * 100.0f)));\n            }\n\n            if (m_IsCurrentWindowCapturable != 1)\n                ImGui::PopItemDisabled();\n\n            DisplayTooltipIfHovered(TranslationManager::GetString(tstr_FloatingUIWindowAddTip));\n\n            ImGui::SameLine();\n        }\n        //\n\n        if (overlay_data.ConfigInt[configid_int_overlay_capture_source] == ovrl_capsource_ui) //Performance Monitor reset button (only show if UI overlay)\n        {\n            UpdatePerformanceMonitorButtons();\n        }\n        else if (overlay_data.ConfigInt[configid_int_overlay_capture_source] == ovrl_capsource_browser) //Browser navigation/reload buttons (only show if browser overlay)\n        {\n            UpdateBrowserButtons(overlay_id);\n        }\n    }\n    //\n\n    //Drag-Mode Toggle Button (this is a global state)\n    bool& is_dragmode_enabled = ConfigManager::GetRef(configid_bool_state_overlay_dragmode);\n    bool dragmode_was_enabled = is_dragmode_enabled;\n    bool& is_overlay_transform_locked = overlay_data.ConfigBool[configid_bool_overlay_transform_locked];\n\n    if (dragmode_was_enabled)\n        ImGui::PushStyleColor(ImGuiCol_Button, ImGui::GetStyleColorVec4(ImGuiCol_ButtonActive));\n\n    TextureManager::Get().GetTextureInfo((is_overlay_transform_locked) ? tmtex_icon_small_move_locked : tmtex_icon_small_move, b_size, b_uv_min, b_uv_max);\n    if (ImGui::ImageButton(\"DragMode\", io.Fonts->TexID, b_size, b_uv_min, b_uv_max))\n    {\n        if (io.MouseDownDurationPrev[ImGuiMouseButton_Left] < 1.5f) //Don't do normal button behavior after lock toggle was triggered\n        {\n            is_dragmode_enabled = !is_dragmode_enabled;\n            IPCManager::Get().PostConfigMessageToDashboardApp(configid_bool_state_overlay_dragselectmode_show_hidden, false);\n            IPCManager::Get().PostConfigMessageToDashboardApp(configid_bool_state_overlay_dragmode, is_dragmode_enabled);\n\n            //Update temporary standing position if dragmode has been activated and dashboard tab isn't active\n            if ((is_dragmode_enabled) && (!UIManager::Get()->IsOverlayBarOverlayVisible()))\n            {\n                UIManager::Get()->GetOverlayDragger().UpdateTempStandingPosition();\n            }\n        }\n    }\n\n    //Toggle transform lock when holding for 1.5 seconds\n    bool show_hold_message_lock = false;\n\n    if (ImGui::IsItemActive())\n    {\n        if (io.MouseDownDuration[ImGuiMouseButton_Left] > 1.5f)\n        {\n            is_overlay_transform_locked = !is_overlay_transform_locked;\n\n            IPCManager::Get().PostConfigMessageToDashboardApp(configid_int_state_overlay_current_id_override, (int)overlay_id);\n            IPCManager::Get().PostConfigMessageToDashboardApp(configid_bool_overlay_transform_locked, is_overlay_transform_locked);\n            IPCManager::Get().PostConfigMessageToDashboardApp(configid_int_state_overlay_current_id_override, -1);\n\n            io.MouseDown[ImGuiMouseButton_Left] = false;    //Release mouse button so actual button won't get toggled\n        }\n        else if (io.MouseDownDuration[ImGuiMouseButton_Left] > 0.5f)\n        {\n            show_hold_message_lock = true;\n        }\n    }\n\n    if (dragmode_was_enabled)\n        ImGui::PopStyleColor();\n\n    if (show_hold_message_lock)\n    {\n        DisplayTooltipIfHovered(TranslationManager::GetString((is_overlay_transform_locked) ? tstr_FloatingUIDragModeHoldUnlockTip: tstr_FloatingUIDragModeHoldLockTip));\n    }\n    else\n    {\n        DisplayTooltipIfHovered(TranslationManager::GetString((dragmode_was_enabled) ? tstr_FloatingUIDragModeDisableTip : tstr_FloatingUIDragModeEnableTip));\n    }\n    //\n\n    ImGui::SameLine();\n\n    //Close/Disable Button\n    TextureManager::Get().GetTextureInfo(tmtex_icon_small_close, b_size, b_uv_min, b_uv_max);\n    if (ImGui::ImageButton(\"Close\", io.Fonts->TexID, b_size, b_uv_min, b_uv_max))\n    {\n        if (io.MouseDownDurationPrev[ImGuiMouseButton_Left] < 2.5f) //Don't do normal button behavior after lock toggle was triggered\n        {\n            overlay_data.ConfigBool[configid_bool_overlay_enabled] = false;\n\n            IPCManager::Get().PostConfigMessageToDashboardApp(configid_int_state_overlay_current_id_override, (int)overlay_id);\n            IPCManager::Get().PostConfigMessageToDashboardApp(configid_bool_overlay_enabled, false);\n            IPCManager::Get().PostConfigMessageToDashboardApp(configid_int_state_overlay_current_id_override, -1);\n        }\n    }\n\n    //Toggle transform lock when holding for 2.5 seconds\n    bool show_hold_message_remove = false;\n\n    if (ImGui::IsItemActive())\n    {\n        if (io.MouseDownDuration[ImGuiMouseButton_Left] > 2.5f) //Longer delay compared to other holds because this is pretty destructive (but still less than rare transform resets)\n        {\n            OverlayManager::Get().RemoveOverlay(overlay_id);\n            IPCManager::Get().PostMessageToDashboardApp(ipcmsg_action, ipcact_overlay_remove, overlay_id);\n\n            //Hide properties window if it's open for this overlay\n            WindowOverlayProperties& properties_window = UIManager::Get()->GetOverlayPropertiesWindow();\n\n            if (properties_window.GetActiveOverlayID() == overlay_id)\n            {\n                properties_window.SetActiveOverlayID(k_ulOverlayID_None, true);\n                properties_window.HideAll();\n            }\n            else if (properties_window.GetActiveOverlayID() > overlay_id) //Adjust properties window active overlay ID if it's open for an overlay that had its ID shifted\n            {\n                properties_window.SetActiveOverlayID(properties_window.GetActiveOverlayID() - 1, true);\n            }\n\n            io.MouseDown[ImGuiMouseButton_Left] = false;    //Release mouse button so actual button won't get toggled\n        }\n        else if (io.MouseDownDuration[ImGuiMouseButton_Left] > 0.5f)\n        {\n            show_hold_message_remove = true;\n        }\n    }\n\n    if (show_hold_message_remove)\n    {\n        DisplayTooltipIfHovered(TranslationManager::GetString(tstr_FloatingUIHideOverlayHoldTip));\n    }\n    else\n    {\n        DisplayTooltipIfHovered(TranslationManager::GetString(tstr_FloatingUIHideOverlayTip));\n    }\n    //\n\n    ImGui::PopStyleColor(); //ImGuiCol_Button\n    ImGui::PopStyleVar();   //ImGuiStyleVar_ItemSpacing\n    ImGui::PopStyleVar();   //ImGuiStyleVar_FrameRounding\n\n    m_Pos  = ImGui::GetWindowPos();\n    m_Size = ImGui::GetWindowSize();\n\n    ImGui::End();\n\n    //Sliding animation when action-bar state changes\n    if (UIManager::Get()->GetFloatingUI().GetAlpha() != 0.0f)\n    {\n        m_AnimationProgress += (is_actionbar_enabled) ? ImGui::GetIO().DeltaTime * 3.0f : ImGui::GetIO().DeltaTime * -3.0f;\n        m_AnimationProgress = clamp(m_AnimationProgress, 0.0f, 1.0f);\n    }\n    else //Skip animation when Floating UI is just fading in\n    {\n        m_AnimationProgress = (is_actionbar_enabled) ? 1.0f : 0.0f;\n    }\n}\n\nvoid WindowFloatingUIMainBar::UpdatePerformanceMonitorButtons()\n{\n    ImGuiIO& io = ImGui::GetIO();\n\n    ImVec2 b_size, b_uv_min, b_uv_max;\n    TextureManager::Get().GetTextureInfo(tmtex_icon_small_close, b_size, b_uv_min, b_uv_max);\n    const ImGuiStyle& style = ImGui::GetStyle();\n\n    //Reset Cumulative Values Button\n    TextureManager::Get().GetTextureInfo(tmtex_icon_small_performance_monitor_reset, b_size, b_uv_min, b_uv_max);\n    if (ImGui::ImageButton(\"Perfmon Reset\", io.Fonts->TexID, b_size, b_uv_min, b_uv_max))\n    {\n        UIManager::Get()->GetPerformanceWindow().ResetCumulativeValues();\n    }\n\n    DisplayTooltipIfHovered(TranslationManager::GetString(tstr_OvrlPropsPerfMonResetValues));\n\n    ImGui::SameLine();\n}\n\nvoid WindowFloatingUIMainBar::UpdateBrowserButtons(unsigned int overlay_id)\n{\n    //Use overlay data of duplication ID if one is set\n    int duplication_id = OverlayManager::Get().GetConfigData(overlay_id).ConfigInt[configid_int_overlay_duplication_id];\n    OverlayConfigData& overlay_data = OverlayManager::Get().GetConfigData((duplication_id == -1) ? overlay_id : (unsigned int)duplication_id);\n\n    ImGuiIO& io = ImGui::GetIO();\n\n    ImVec2 b_size, b_uv_min, b_uv_max;\n    TextureManager::Get().GetTextureInfo(tmtex_icon_small_close, b_size, b_uv_min, b_uv_max);\n    const ImGuiStyle& style = ImGui::GetStyle();\n\n    //Go Back Button\n    if (!overlay_data.ConfigBool[configid_bool_overlay_state_browser_nav_can_go_back])\n        ImGui::PushItemDisabled();\n\n    TextureManager::Get().GetTextureInfo(tmtex_icon_small_browser_back, b_size, b_uv_min, b_uv_max);\n    if (ImGui::ImageButton(\"GoBack\", io.Fonts->TexID, b_size, b_uv_min, b_uv_max))\n    {\n        DPBrowserAPIClient::Get().DPBrowser_GoBack(overlay_data.ConfigHandle[configid_handle_overlay_state_overlay_handle]);\n    }\n\n    if (!overlay_data.ConfigBool[configid_bool_overlay_state_browser_nav_can_go_back])\n        ImGui::PopItemDisabled();\n\n    DisplayTooltipIfHovered(TranslationManager::GetString(tstr_FloatingUIBrowserGoBackTip));\n    //\n\n    ImGui::SameLine();\n\n    //Go Forward Button\n    if (!overlay_data.ConfigBool[configid_bool_overlay_state_browser_nav_can_go_forward])\n        ImGui::PushItemDisabled();\n\n    TextureManager::Get().GetTextureInfo(tmtex_icon_small_browser_forward, b_size, b_uv_min, b_uv_max);\n    if (ImGui::ImageButton(\"GoForward\", io.Fonts->TexID, b_size, b_uv_min, b_uv_max))\n    {\n        DPBrowserAPIClient::Get().DPBrowser_GoForward(overlay_data.ConfigHandle[configid_handle_overlay_state_overlay_handle]);\n    }\n\n    if (!overlay_data.ConfigBool[configid_bool_overlay_state_browser_nav_can_go_forward])\n        ImGui::PopItemDisabled();\n\n    DisplayTooltipIfHovered(TranslationManager::GetString(tstr_FloatingUIBrowserGoForwardTip));\n    //\n\n    ImGui::SameLine();\n\n    //Refresh Button\n    const bool is_loading = overlay_data.ConfigBool[configid_bool_overlay_state_browser_nav_is_loading];\n    TextureManager::Get().GetTextureInfo((is_loading) ? tmtex_icon_small_browser_stop : tmtex_icon_small_browser_refresh, b_size, b_uv_min, b_uv_max);\n    if (ImGui::ImageButton(\"Refresh\", io.Fonts->TexID, b_size, b_uv_min, b_uv_max))\n    {\n        DPBrowserAPIClient::Get().DPBrowser_Refresh(overlay_data.ConfigHandle[configid_handle_overlay_state_overlay_handle]);\n    }\n\n    DisplayTooltipIfHovered(TranslationManager::GetString((is_loading) ? tstr_FloatingUIBrowserStopTip : tstr_FloatingUIBrowserRefreshTip));\n    //\n\n    ImGui::SameLine();\n}\n\nconst ImVec2& WindowFloatingUIMainBar::GetPos() const\n{\n    return m_Pos;\n}\n\nconst ImVec2& WindowFloatingUIMainBar::GetSize() const\n{\n    return m_Size;\n}\n\nfloat WindowFloatingUIMainBar::GetAnimationProgress() const\n{\n    return m_AnimationProgress;\n}\n\nvoid WindowFloatingUIMainBar::MarkCurrentWindowCapturableStateOutdated()\n{\n    //Mark state as outdated. We don't do the update here as the current window can change a lot while the UI isn't even displaying... no need to bother then.\n    m_IsCurrentWindowCapturable = -1;\n\n    if (UIManager::Get()->GetFloatingUI().IsVisible())\n    {\n        UIManager::Get()->GetIdleState().AddActiveTime(50);\n    }\n}\n\n\n//-WindowFloatingUIActionBar\nWindowFloatingUIActionBar::WindowFloatingUIActionBar() : m_Visible(false), m_Alpha(0.0f), m_LastDesktopSwitchTime(0.0), m_TooltipPositionForOverlayBar(false)\n{\n    m_Size.x = 32.0f;\n}\n\nvoid WindowFloatingUIActionBar::DisplayTooltipIfHovered(const char* text)\n{\n    if (ImGui::IsItemHovered())\n    {\n        const ImGuiStyle& style = ImGui::GetStyle();\n\n        static ImVec2 button_pos_last; //Remember last position and use it when posible. This avoids flicker when the same tooltip string is used in different places\n        ImVec2 pos = ImGui::GetItemRectMin();\n        float button_width = ImGui::GetItemRectSize().x;\n\n        //Default tooltips are not suited for this as there's too much trouble with resize flickering and stuff\n        ImGui::Begin(text, nullptr, ImGuiWindowFlags_NoInputs | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize | \n                                    ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoFocusOnAppearing);\n\n        ImGui::TextUnformatted(text);\n\n        //Not using GetWindowSize() here since it's delayed and plays odd when switching between buttons with the same label\n        ImVec2 window_size = ImGui::GetItemRectSize();\n        window_size.x += style.WindowPadding.x * 2.0f;\n        window_size.y += style.WindowPadding.y * 2.0f;\n\n        //Repeat frame when the window is appearing as it will not have the right position (either from being first time or still having old pos)\n        if ( (ImGui::IsWindowAppearing()) || (pos.x != button_pos_last.x) )\n        {\n            UIManager::Get()->RepeatFrame();\n        }\n\n        button_pos_last = pos;\n\n        pos.x += (button_width / 2.0f) - (window_size.x / 2.0f);\n\n        if (m_TooltipPositionForOverlayBar)\n        {\n            pos.y = ImGui::GetIO().DisplaySize.y;\n            pos.y -= window_size.y;\n        }\n        else\n        {\n            pos.y -= window_size.y + style.WindowPadding.y + 2.0f;\n        }\n\n        //Clamp x so the tooltip does not get cut off\n        pos.x = clamp(pos.x, 0.0f, ImGui::GetIO().DisplaySize.x - window_size.x);   //Clamp right side to texture end\n\n        ImGui::SetWindowPos(pos);\n\n        ImGui::End();\n    }\n}\n\nvoid WindowFloatingUIActionBar::UpdateDesktopButtons(unsigned int overlay_id)\n{\n    ImGui::PushID(\"DesktopButtons\");\n\n    ImGuiIO& io = ImGui::GetIO();\n\n    ImVec2 b_size, b_uv_min, b_uv_max;\n    OverlayConfigData& overlay_config = OverlayManager::Get().GetConfigData(overlay_id);\n    ConfigID_Int current_configid = configid_int_overlay_desktop_id;\n    bool disable_normal = false;\n    bool disable_combined = false;\n        \n    if (overlay_config.ConfigInt[configid_int_overlay_capture_source] == ovrl_capsource_winrt_capture)\n    {\n        current_configid = configid_int_overlay_winrt_desktop_id;\n        //Check if buttons would be usable for WinRT capture\n        disable_normal   = !DPWinRT_IsCaptureFromHandleSupported();\n        disable_combined = !DPWinRT_IsCaptureFromCombinedDesktopSupported();\n    }\n\n    int& current_desktop = overlay_config.ConfigInt[current_configid];\n    int current_desktop_new = current_desktop;\n\n    if (ConfigManager::GetValue(configid_bool_interface_desktop_buttons_include_combined))\n    {\n        if (disable_combined)\n            ImGui::PushItemDisabled();\n\n        if (current_desktop == -1)\n            ImGui::PushStyleColor(ImGuiCol_Button, Style_ImGuiCol_ButtonPassiveToggled);\n\n        TextureManager::Get().GetTextureInfo(tmtex_icon_desktop_all, b_size, b_uv_min, b_uv_max);\n        if (ImGui::ImageButton(\"Combined Desktop\", io.Fonts->TexID, b_size, b_uv_min, b_uv_max, ImVec4(0.0f, 0.0f, 0.0f, 0.0f)))\n        {\n            //Don't change to same value to avoid flicker from mirror reset\n            if ( (current_desktop != -1) || (!ConfigManager::GetValue(configid_bool_performance_single_desktop_mirroring)) )\n            {\n                current_desktop_new = -1;\n            }\n        }\n        DisplayTooltipIfHovered(TranslationManager::GetString(tstr_SourceDesktopAll));\n        ImGui::SameLine();\n\n        if (current_desktop == -1)\n            ImGui::PopStyleColor();\n\n        if (disable_combined)\n            ImGui::PopItemDisabled();\n    }\n\n    int desktop_count = ConfigManager::GetValue(configid_int_state_interface_desktop_count);\n\n    if (disable_normal)\n        ImGui::PushItemDisabled();\n\n    switch (ConfigManager::GetValue(configid_int_interface_desktop_listing_style))\n    {\n        case desktop_listing_style_individual:\n        {\n            for (int i = 0; i < desktop_count; ++i)\n            {\n                ImGui::PushID(tmtex_icon_desktop_1 + i);\n\n                //We have icons for up to 6 desktops, the rest (if it even exists) gets blank ones)\n                //Numbering on the icons starts with 1. \n                //While there is no guarantee this corresponds to the same display as in the Windows settings, it is more familiar than the 0-based ID used internally\n                //Why icon bitmaps? No need to create/load an icon font, cleaner rendered number and easily customizable icon per desktop for the end-user.\n                if (tmtex_icon_desktop_1 + i <= tmtex_icon_desktop_6)\n                    TextureManager::Get().GetTextureInfo((TMNGRTexID)(tmtex_icon_desktop_1 + i), b_size, b_uv_min, b_uv_max);\n                else\n                    TextureManager::Get().GetTextureInfo(tmtex_icon_desktop, b_size, b_uv_min, b_uv_max);\n\n                if (i == current_desktop)\n                    ImGui::PushStyleColor(ImGuiCol_Button, Style_ImGuiCol_ButtonPassiveToggled);\n\n                if (ImGui::ImageButton(\"\", io.Fonts->TexID, b_size, b_uv_min, b_uv_max, ImVec4(0.0f, 0.0f, 0.0f, 0.0f)))\n                {\n                    //Don't change to same value to avoid flicker from mirror reset\n                    if ( (i != current_desktop) || (!ConfigManager::GetValue(configid_bool_performance_single_desktop_mirroring)) )\n                    {\n                        current_desktop_new = i;\n                    }\n                }\n\n                if (i == current_desktop)\n                    ImGui::PopStyleColor();\n\n                DisplayTooltipIfHovered(TranslationManager::Get().GetDesktopIDString(i));\n\n                ImGui::PopID();\n                ImGui::SameLine();\n            }\n            break;\n        }\n        case desktop_listing_style_cycle:\n        {\n            TextureManager::Get().GetTextureInfo(tmtex_icon_desktop_prev, b_size, b_uv_min, b_uv_max);\n            if (ImGui::ImageButton(\"Previous Desktop\", io.Fonts->TexID, b_size, b_uv_min, b_uv_max, ImVec4(0.0f, 0.0f, 0.0f, 0.0f)))\n            {\n                current_desktop_new--;\n\n                if (current_desktop_new <= -1)\n                    current_desktop_new = desktop_count - 1;\n            }\n            DisplayTooltipIfHovered(TranslationManager::GetString(tstr_FloatingUIActionBarDesktopPrev));\n            ImGui::SameLine();\n\n            TextureManager::Get().GetTextureInfo(tmtex_icon_desktop_next, b_size, b_uv_min, b_uv_max);\n            if (ImGui::ImageButton(\"Next Destkop\", io.Fonts->TexID, b_size, b_uv_min, b_uv_max, ImVec4(0.0f, 0.0f, 0.0f, 0.0f)))\n            {\n                current_desktop_new++;\n\n                if (current_desktop_new == desktop_count)\n                    current_desktop_new = 0;\n            }\n            DisplayTooltipIfHovered(TranslationManager::GetString(tstr_FloatingUIActionBarDesktopNext));\n            ImGui::SameLine();\n            break;\n        }\n    }\n\n    if (disable_normal)\n        ImGui::PopItemDisabled();\n\n    if (current_desktop_new != current_desktop)\n    {\n        current_desktop = current_desktop_new;\n\n        IPCManager::Get().PostConfigMessageToDashboardApp(configid_int_state_overlay_current_id_override, (int)overlay_id);\n\n        //Reset window selection when switching to a desktop\n        if (current_configid == configid_int_overlay_winrt_desktop_id)\n        {\n            IPCManager::Get().PostConfigMessageToDashboardApp(configid_handle_overlay_state_winrt_hwnd, 0);\n            overlay_config.ConfigHandle[configid_handle_overlay_state_winrt_hwnd] = 0;\n        }\n\n        IPCManager::Get().PostConfigMessageToDashboardApp(current_configid, current_desktop);\n\n        IPCManager::Get().PostConfigMessageToDashboardApp(configid_int_state_overlay_current_id_override, -1);\n\n        //Update overlay name\n        OverlayManager::Get().SetOverlayNameAuto(overlay_id);\n        UIManager::Get()->OnOverlayNameChanged();\n\n        //Store last switch time for the anti-flicker code (only needed for dashboard origin)\n        if (overlay_config.ConfigInt[configid_int_overlay_origin] == ovrl_origin_dashboard)\n        {\n            m_LastDesktopSwitchTime = ImGui::GetTime();\n        }\n    }\n\n    ImGui::PopID();\n}\n\nvoid WindowFloatingUIActionBar::ButtonActionKeyboard(const Action& action, const ImVec2& size, unsigned int overlay_id)\n{\n    FloatingWindow& keyboard_window = UIManager::Get()->GetVRKeyboard().GetWindow();\n    const ActionManager& action_manager = ConfigManager::Get().GetActionManager();\n    ImGuiIO& io = ImGui::GetIO();\n    const bool is_keyboard_visible = keyboard_window.IsVisible();\n\n    if (is_keyboard_visible)\n        ImGui::PushStyleColor(ImGuiCol_Button, ImGui::GetStyleColorVec4(ImGuiCol_ButtonActive));\n\n    if (ButtonAction(action, size))\n    {\n        if (io.MouseDownDurationPrev[ImGuiMouseButton_Left] < 3.0f) //Don't do normal button behavior after reset was just triggered\n        {\n            action_manager.DoAction(action.UID);    //We only support DoAction here since we want holding down the button to not trigger the action already\n        }\n    }\n\n    if (is_keyboard_visible)\n        ImGui::PopStyleColor();\n\n    //Reset tranform when holding the button for 3 or more seconds\n    TRMGRStrID tooltip_strid = (is_keyboard_visible) ? tstr_ActionKeyboardHide : tstr_ActionKeyboardShow;\n\n    if (ImGui::IsItemActive())\n    {\n        if (io.MouseDownDuration[ImGuiMouseButton_Left] > 3.0f)\n        {\n            //Unpin if dashboard overlay is available\n            if ( (UIManager::Get()->IsOpenVRLoaded()) && (vr::VROverlay()->IsOverlayVisible(UIManager::Get()->GetOverlayHandleDPlusDashboard())) )\n            {\n                keyboard_window.SetPinned(false);\n            }\n\n            keyboard_window.ResetTransformAll();\n            io.MouseDown[ImGuiMouseButton_Left] = false;    //Release mouse button so transform changes don't get blocked\n        }\n        else if (io.MouseDownDuration[ImGuiMouseButton_Left] > 0.5f)\n        {\n            tooltip_strid = tstr_OverlayBarTooltipResetHold;\n        }\n    }\n\n    //Show tooltip depending on current keyboard state\n    DisplayTooltipIfHovered( TranslationManager::GetString(tooltip_strid) );\n}\n\nvoid WindowFloatingUIActionBar::Show(bool skip_fade)\n{\n    m_Visible = true;\n\n    if (skip_fade)\n    {\n        m_Alpha = 1.0f;\n    }\n}\n\nvoid WindowFloatingUIActionBar::Hide(bool skip_fade)\n{\n    m_Visible = false;\n\n    if (skip_fade)\n    {\n        m_Alpha = 0.0f;\n    }\n}\n\nvoid WindowFloatingUIActionBar::Update(unsigned int overlay_id)\n{\n    if ( (m_Alpha != 0.0f) || (m_Visible) )\n    {\n        const float alpha_step = ImGui::GetIO().DeltaTime * 6.0f;\n\n        //Alpha fade animation\n        m_Alpha += (m_Visible) ? alpha_step : -alpha_step;\n\n        if (m_Alpha > 1.0f)\n            m_Alpha = 1.0f;\n        else if (m_Alpha < 0.0f)\n            m_Alpha = 0.0f;\n    }\n\n    //We need to not skip on alpha 0.0 at least twice to get the real height of the bar. 32.0f (and sometimes 16.0f) is the placeholder width ImGui seems to use until then\n    if ( (m_Alpha == 0.0f) && (m_Size.x > 32.0f) )\n        return;\n\n    ImGui::PushStyleVar(ImGuiStyleVar_Alpha, m_Alpha);\n\n    ImGuiIO& io = ImGui::GetIO();\n    const ImGuiStyle& style = ImGui::GetStyle();\n\n    ImVec2 b_size, b_uv_min, b_uv_max;\n\n    //Put window near the top of the overlay with space for the tooltips + padding\n    const DPRect& rect_floating_ui = UITextureSpaces::Get().GetRect(ui_texspace_floating_ui);\n    const float tooltip_height = ImGui::GetFontSize() + (style.WindowPadding.y * 2.0f);\n    const float offset_y = rect_floating_ui.GetTL().y + tooltip_height + (style.FramePadding.y * 2.0f) + style.WindowPadding.y;\n\n    ImGui::SetNextWindowPos(ImVec2(io.DisplaySize.x / 2.0f, offset_y), 0, ImVec2(0.5f, 0.0f));\n\n    ImGui::Begin(\"WindowFloatingUIActionBar\", nullptr, ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoFocusOnAppearing |\n                                                       ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_HorizontalScrollbar | ImGuiWindowFlags_NoBringToFrontOnFocus);\n\n    //Set focused ID when clicking anywhere on the window\n    if ((ImGui::IsWindowHovered(ImGuiHoveredFlags_AllowWhenBlockedByActiveItem)) && (ImGui::IsMouseClicked(ImGuiMouseButton_Left)))\n    {\n        IPCManager::Get().PostConfigMessageToDashboardApp(configid_int_state_overlay_focused_id, (int)overlay_id);\n        IPCManager::Get().PostConfigMessageToUIApp(configid_int_state_overlay_focused_id,        (int)overlay_id);  //Sending to self to trigger change behavior\n    }\n\n    ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 4.0f);\n    ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, {style.ItemSpacing.y, style.ItemSpacing.y});\n    ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0, 0, 0, 0));\n\n    const ImVec2 cursor_pos = ImGui::GetCursorPos();\n\n    if (OverlayManager::Get().GetConfigData(overlay_id).ConfigBool[configid_bool_overlay_floatingui_desktops_enabled])\n    {\n        UpdateDesktopButtons(overlay_id);\n    }\n\n    UpdateActionButtons(overlay_id);\n\n    //Check if there any buttons added by above functions and if not, display placeholder text so the window doesn't look weird\n    const ImVec2 cursor_pos_orig = ImGui::GetCursorPos();\n    if (cursor_pos_orig.x == cursor_pos.x)\n    {\n        //Match height of what a normal button would take up and middle-align the text\n        ImVec2 b_size, b_uv_min, b_uv_max;\n        TextureManager::Get().GetTextureInfo(tmtex_icon_settings, b_size, b_uv_min, b_uv_max);\n        b_size.x += style.FramePadding.x * 2.0f;\n        b_size.y += style.FramePadding.y * 2.0f;\n\n        ImGui::Dummy({0.0f, b_size.y});\n        ImGui::SetCursorPosY(cursor_pos.y + (b_size.y / 2.0f) - (ImGui::GetFontSize() / 2.0f));\n        ImGui::TextUnformatted(TranslationManager::GetString(tstr_FloatingUIActionBarEmpty));\n    }\n\n    ImGui::PopStyleColor(); //ImGuiCol_Button\n    ImGui::PopStyleVar();   //ImGuiStyleVar_ItemSpacing\n    ImGui::PopStyleVar();   //ImGuiStyleVar_FrameRounding\n\n    m_Pos  = ImGui::GetWindowPos();\n    m_Size = ImGui::GetWindowSize();\n\n    ImGui::End();\n    ImGui::PopStyleVar(); //ImGuiStyleVar_Alpha\n}\n\nconst ImVec2& WindowFloatingUIActionBar::GetPos() const\n{\n    return m_Pos;\n}\n\nconst ImVec2& WindowFloatingUIActionBar::GetSize() const\n{\n    return m_Size;\n}\n\nbool WindowFloatingUIActionBar::IsVisible() const\n{\n    return m_Visible;\n}\n\nfloat WindowFloatingUIActionBar::GetAlpha() const\n{\n    return m_Alpha;\n}\n\ndouble WindowFloatingUIActionBar::GetLastDesktopSwitchTime() const\n{\n    return m_LastDesktopSwitchTime;\n}\n\nvoid WindowFloatingUIActionBar::UpdateActionButtons(unsigned int overlay_id)\n{\n    //This function can be called by WindowOverlayBar to optionally put some action buttons in there too\n    //It's a little bit hacky, but the only real difference is where to pull the action order from and how to display the tooltip\n    bool is_overlay_bar = (overlay_id == k_ulOverlayID_None);\n    m_TooltipPositionForOverlayBar = is_overlay_bar;\n\n    ImGui::PushID(\"ActionButtons\");\n    ImGui::PushStyleColor(ImGuiCol_ChildBg, ImVec4(0.00f, 0.00f, 0.00f, 0.00f));\n\n    ImGuiIO& io = ImGui::GetIO();\n    ImVec2 b_size, b_uv_min, b_uv_max;\n    TextureManager::Get().GetTextureInfo(tmtex_icon_settings, b_size, b_uv_min, b_uv_max);\n    //Default button size for custom actions to be the same as the settings icon so the user is able to provide oversized images without messing up the layout\n    //as well as still providing a way to change the size of text buttons by editing the settings icon's dimensions\n    ImVec2 b_size_default = b_size;\n\n    const ActionManager& action_manager = ConfigManager::Get().GetActionManager();\n    OverlayConfigData& data = OverlayManager::Get().GetConfigData(overlay_id);\n    const auto& action_order = (is_overlay_bar) ? action_manager.GetActionOrderListOverlayBar() :\n                               (data.ConfigBool[configid_bool_overlay_actionbar_order_use_global]) ? action_manager.GetActionOrderListBarDefault() : data.ConfigActionBarOrder;\n\n    int list_id = 0;\n    for (ActionUID uid : action_order)\n    {\n        const Action action = action_manager.GetAction(uid);\n\n        //Detect normal Show Keyboard action by translation ID and provide special behavior\n        if (action.NameTranslationID == tstr_DefActionShowKeyboard)\n        {\n            ButtonActionKeyboard(action, b_size, overlay_id);\n        }\n        else    //Every other action\n        {\n            ButtonAction(action, b_size);\n\n            if (ImGui::IsItemActivated())\n            {\n                action_manager.StartAction(uid);\n            }\n            else if (ImGui::IsItemDeactivated())\n            {\n                action_manager.StopAction(uid);\n            }\n\n            //Only display tooltip if the label isn't already on the button (is here since we need ImGui::IsItem*() to work on the button)\n            if (action.Label.empty())\n            {\n                DisplayTooltipIfHovered(action_manager.GetTranslatedName(uid));\n            }\n        }\n\n        ImGui::SameLine();\n    }\n\n    ImGui::PopStyleColor(); //ImGuiCol_ChildBg\n    ImGui::PopID();\n\n    m_TooltipPositionForOverlayBar = false;\n}\n\nbool WindowFloatingUIActionBar::ButtonAction(const Action& action, const ImVec2& size, bool use_temp_icon)\n{\n    bool ret = false;\n\n    ImGui::PushID((void*)action.UID);\n    ImGui::BeginGroup();\n\n    const ImGuiStyle& style = ImGui::GetStyle();\n\n    if ((action.IconImGuiRectID != -1) || ((use_temp_icon) && (!action.IconFilename.empty())))\n    {\n        ImGuiIO& io = ImGui::GetIO();\n        ImVec2 b_size, b_uv_min, b_uv_max;\n\n        if (use_temp_icon)\n        {\n            TextureManager::Get().GetTextureInfo(tmtex_icon_temp, b_size, b_uv_min, b_uv_max);\n        }\n        else\n        {\n            TextureManager::Get().GetTextureInfo(action, b_size, b_uv_min, b_uv_max);\n        }\n\n        ret = ImGui::ImageButton(\"##ActionButtonImg\", io.Fonts->TexID, size, b_uv_min, b_uv_max, ImVec4(0.0f, 0.0f, 0.0f, 0.0f));\n    }\n    else\n    {\n        ret = ImGui::Button(\"##ActionButton\", ImVec2(size.x + (style.FramePadding.x * 2.0f), size.y + (style.FramePadding.y * 2.0f)) );\n    }\n\n    if (!action.Label.empty())\n    {\n        ImGui::SameLine(0.0f, 0.0f);\n\n        const char* label = (action.LabelTranslationID == tstr_NONE) ? action.Label.c_str() : TranslationManager::GetString(action.LabelTranslationID);\n        const char* label_end = label + strlen(label);\n        const char* line_start = label;\n        const char* line_end = nullptr;\n        const ImVec2 label_size = ImGui::CalcTextSize(label, label_end);\n        const ImU32 col = ImGui::GetColorU32(ImGui::GetStyleColorVec4(ImGuiCol_Text));\n\n        const float pos_x = ImGui::GetCursorScreenPos().x - size.x - style.FramePadding.x;\n        float pos_y = ImGui::GetCursorScreenPos().y + int( (size.y / 2.0f) - (label_size.y / 2.0f) );\n\n        while (line_start < label_end)\n        {\n            line_end = (const char*)memchr(line_start, '\\n', label_end - line_start);\n\n            if (line_end == nullptr)\n                line_end = label_end;\n\n            ImVec2 line_size = ImGui::CalcTextSize(line_start, line_end);\n            float line_xscale = (size.x + style.FramePadding.x / 2.0f) / line_size.x;\n\n            if (line_xscale > 1.0f)\n                line_xscale = 1.0f;\n\n            ImGui::BeginStretched();\n            ImGui::GetWindowDrawList()->AddText({pos_x + int( ((size.x / 2.0f) - (line_size.x * line_xscale) / 2.0f) ), pos_y}, col, line_start, line_end);\n            ImGui::EndStretched(line_xscale);\n\n            line_start = line_end + 1;\n            pos_y += ImGui::GetTextLineHeight();\n        }\n    }\n\n    ImGui::EndGroup();\n    ImGui::PopID();\n\n    return ret;\n}\n\n\n//-WindowFloatingUIOverlayStats\nImVec2 WindowFloatingUIOverlayStats::CalcPos(const WindowFloatingUIMainBar& mainbar, const WindowFloatingUIActionBar& actionbar, float& window_width) const\n{\n    const ImGuiStyle& style = ImGui::GetStyle();\n\n    //Limit width to what we have left in the texture space\n    const float max_width = (mainbar.GetPos().x - UITextureSpaces::Get().GetRect(ui_texspace_floating_ui).GetBL().x) - style.ItemSpacing.x;\n    window_width = std::min(window_width, max_width);\n\n    //Position the window left to either action- or main-bar, depending on visibility of action-bar (switching is animated alongside mainbar)\n    //Additionally moves the window down if the action-bar occupies the horizontal space needed\n    const ImVec2& actionbar_pos  = actionbar.GetPos();\n    const ImVec2& actionbar_size = actionbar.GetSize();\n    const ImVec2& mainbar_pos    = mainbar.GetPos();\n    const bool is_actionbar_blocking = (actionbar_pos.x < window_width + style.ItemSpacing.x);\n\n    float max_x = actionbar_pos.x;\n\n    if (is_actionbar_blocking)\n    {\n        const float space_width = mainbar.GetPos().x - actionbar_pos.x + style.ItemSpacing.x;  //Space between left side of action-bar and left side of main-bar\n\n        if (window_width > space_width)\n        {\n            max_x = mainbar_pos.x;\n        }\n        else\n        {\n            max_x = actionbar_pos.x + window_width + style.ItemSpacing.x;\n        }\n    }\n\n    ImVec2 pos;\n    pos.x  = smoothstep(mainbar.GetAnimationProgress(), mainbar.GetPos().x, max_x);\n    pos.x -= style.ItemSpacing.x;\n    pos.y  = smoothstep(mainbar.GetAnimationProgress(), actionbar_pos.y, (is_actionbar_blocking) ? actionbar_pos.y + actionbar_size.y + style.ItemSpacing.y : actionbar_pos.y);\n\n    return pos;\n}\n\nvoid WindowFloatingUIOverlayStats::Update(const WindowFloatingUIMainBar& mainbar, const WindowFloatingUIActionBar& actionbar, unsigned int overlay_id)\n{\n    const ImGuiStyle& style = ImGui::GetStyle();\n    const ImGuiWindowFlags window_flags = ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoFocusOnAppearing;\n\n    const OverlayConfigData& overlay_data = OverlayManager::Get().GetConfigData(overlay_id);\n    const bool show_fps = !overlay_data.ConfigBool[configid_bool_overlay_state_no_output];\n\n    if (show_fps)\n    {\n        //Don't display if disabled\n        if (!ConfigManager::Get().GetValue(configid_bool_performance_show_fps))\n            return;\n\n        //Use overlay data of duplication ID if one is set\n        int duplication_id = OverlayManager::Get().GetConfigData(overlay_id).ConfigInt[configid_int_overlay_duplication_id];\n        const OverlayConfigData& overlay_data = OverlayManager::Get().GetConfigData((duplication_id == -1) ? overlay_id : (unsigned int)duplication_id);\n\n        int fps = -1;\n        if (overlay_data.ConfigInt[configid_int_overlay_capture_source] == ovrl_capsource_desktop_duplication)\n        {\n            fps = ConfigManager::GetValue(configid_int_state_performance_duplication_fps);\n        }\n        else if (overlay_data.ConfigInt[configid_int_overlay_capture_source] != ovrl_capsource_ui)\n        {\n            fps = overlay_data.ConfigInt[configid_int_overlay_state_fps];\n        }\n\n        //Don't display window if no fps value available\n        if (fps == -1)\n            return;\n\n        //This is a fixed size window, which seems like a bad idea for localization, but since it uses performance monitor strings it already uses size constrained text\n        //(and \"FPS\" will be untranslated in practice)\n        float window_width = ImGui::GetFontSize() * 3.5f;\n        ImVec2 pos = CalcPos(mainbar, actionbar, window_width);\n\n        //Actual window\n        ImGui::SetNextWindowPos(pos, 0, ImVec2(1.0f, 0.0f));    \n        ImGui::SetNextWindowSize(ImVec2(window_width, -1.0f));\n        ImGui::Begin(\"WindowFloatingUIStats\", nullptr, window_flags);\n\n        ImGui::TextUnformatted(TranslationManager::GetString(tstr_PerformanceMonitorFPS));\n        ImGui::SameLine();\n        ImGui::TextRight(0.0f, 0.0f, \"%d\", fps);\n\n        ImGui::End();\n    }\n    else  //Show name\n    {\n        float window_width = ImGui::CalcTextSize(overlay_data.ConfigNameStr.c_str()).x + style.ItemSpacing.x + style.ItemSpacing.x;\n        ImVec2 pos = CalcPos(mainbar, actionbar, window_width);\n\n        //Actual window\n        ImGui::SetNextWindowPos(pos, 0, ImVec2(1.0f, 0.0f));    \n        ImGui::SetNextWindowSize(ImVec2(window_width, -1.0f));\n        ImGui::Begin(\"WindowFloatingUIStats\", nullptr, window_flags);\n\n        ImGui::TextUnformatted(overlay_data.ConfigNameStr.c_str());\n\n        ImGui::End();\n    }\n}\n"
  },
  {
    "path": "src/DesktopPlusUI/WindowFloatingUIBar.h",
    "content": "#pragma once\n\n#include \"imgui.h\"\n\nstruct Action;\n\n//The main bar visible in the floating UI, containing buttons to hide the overlay, toggle drag-mode and show the Action-Bar\nclass WindowFloatingUIMainBar\n{\n    private:\n        ImVec2 m_Pos;\n        ImVec2 m_Size;\n\n        int m_IsCurrentWindowCapturable;        //-1 = needs update, otherwise bool\n        float m_AnimationProgress;\n\n        void DisplayTooltipIfHovered(const char* text);\n\n    public:\n        WindowFloatingUIMainBar();\n\n        void Update(float mainbar_height, unsigned int overlay_id);\n        void UpdatePerformanceMonitorButtons();\n        void UpdateBrowserButtons(unsigned int overlay_id);\n\n        const ImVec2& GetPos() const;\n        const ImVec2& GetSize() const;\n        float GetAnimationProgress() const;\n\n        void MarkCurrentWindowCapturableStateOutdated();\n};\n\n//Action-Bar visible in the floating UI, containing desktop buttons and user-defined action buttons\nclass WindowFloatingUIActionBar\n{\n    private:\n        ImVec2 m_Pos;\n        ImVec2 m_Size;\n        bool m_Visible;\n        float m_Alpha;\n\n        double m_LastDesktopSwitchTime;\n        bool m_TooltipPositionForOverlayBar;                //This is used instead of an extra argument to no have every custom button code need to be aware of this corner case\n\n        void DisplayTooltipIfHovered(const char* text);\n        void UpdateDesktopButtons(unsigned int overlay_id);\n\n        void ButtonActionKeyboard(const Action& action, const ImVec2& size, unsigned int overlay_id);\n\n    public:\n        WindowFloatingUIActionBar();\n\n        void Show(bool skip_fade = false);\n        void Hide(bool skip_fade = false);\n        void Update(unsigned int overlay_id);\n        const ImVec2& GetPos() const;\n        const ImVec2& GetSize() const;\n        bool IsVisible() const;\n        float GetAlpha() const;\n\n        double GetLastDesktopSwitchTime() const;\n\n        void UpdateActionButtons(unsigned int overlay_id);  //WindowOverlayBar can piggyback this function with k_ulOverlayID_None to get action buttons in its window\n\n        static bool ButtonAction(const Action& action, const ImVec2& size, bool use_temp_icon = false); //use_temp_icon still needs icon filename set in action\n};\n\n//Extra window currently only showing capture fps of the overlay if enabled\nclass WindowFloatingUIOverlayStats\n{\n    private:\n        ImVec2 CalcPos(const WindowFloatingUIMainBar& mainbar, const WindowFloatingUIActionBar& actionbar, float& window_width) const;\n\n    public:\n        void Update(const WindowFloatingUIMainBar& mainbar, const WindowFloatingUIActionBar& actionbar, unsigned int overlay_id);\n};"
  },
  {
    "path": "src/DesktopPlusUI/WindowKeyboard.cpp",
    "content": "#include \"WindowKeyboard.h\"\n\n#include \"ImGuiExt.h\"\n#include \"TextureManager.h\"\n#include \"TranslationManager.h\"\n#include \"InterprocessMessaging.h\"\n#include \"Util.h\"\n#include \"OpenVRExt.h\"\n#include \"UIManager.h\"\n#include \"OverlayManager.h\"\n\n#include <sstream>\n\n#include \"imgui_internal.h\"\n\n//-WindowKeyboard\nWindowKeyboard::WindowKeyboard() : \n    m_WindowWidth(-1.0f),\n    m_IsAutoVisible(false),\n    m_IsHovered(false),\n    m_IsAnyButtonHovered(false),\n    m_AssignedOverlayIDRoom(-1),\n    m_AssignedOverlayIDDashboardTab(-1),\n    m_IsIsoEnterDown(false),\n    m_IsDashboardPointerActiveLast(false),\n    m_UnstickModifiersLater(false),\n    m_SubLayoutOverride(kbdlayout_sub_base),\n    m_LastSubLayout(kbdlayout_sub_base)\n{\n    m_WindowIcon = tmtex_icon_xsmall_keyboard;\n    m_OvrlWidth    = OVERLAY_WIDTH_METERS_KEYBOARD;\n    m_OvrlWidthMax = OVERLAY_WIDTH_METERS_KEYBOARD * 5.0f;\n\n    //Leave 2 pixel padding around so interpolation doesn't cut off the pixel border\n    const DPRect& rect = UITextureSpaces::Get().GetRect(ui_texspace_keyboard);\n    m_Size = {float(rect.GetWidth() - 4), float(rect.GetHeight() - 4)};\n    m_SizeUnscaled = m_Size;\n\n    m_Pos = {(float)rect.GetCenter().x, (float)rect.GetCenter().y};\n    m_PosPivot = {0.5f, 0.5f};\n\n    m_WindowFlags |= ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoFocusOnAppearing;\n    m_AllowRoomUnpinning = true;\n\n    std::fill(std::begin(m_ManuallyStickingModifiers), std::end(m_ManuallyStickingModifiers), false);\n\n    FloatingWindow::ResetTransformAll();\n}\n\nvoid WindowKeyboard::UpdateVisibility()\n{\n    //Set state depending on dashboard tab visibility\n    if ( (!UIManager::Get()->IsInDesktopMode()) && (!m_IsTransitionFading) )\n    {\n        const bool is_using_dashboard_state = (m_OverlayStateCurrentID == floating_window_ovrl_state_dashboard_tab);\n\n        if (is_using_dashboard_state != vr::VROverlay()->IsOverlayVisible(UIManager::Get()->GetOverlayHandleDPlusDashboard()))\n        {\n            //Auto-visible keyboards don't persist between overlay state switches\n            if (m_IsAutoVisible)\n            {\n                SetAssignedOverlayID(-1);\n                Hide();\n            }\n\n            OverlayStateSwitchCurrent(!is_using_dashboard_state);\n        }\n    }\n\n    //Overlay position and visibility\n    if (UIManager::Get()->IsOpenVRLoaded())\n    {\n        vr::VROverlayHandle_t ovrl_handle_assigned = vr::k_ulOverlayHandleInvalid;\n        vr::VROverlayHandle_t overlay_handle = GetOverlayHandle();\n\n        int assigned_overlay_id = GetAssignedOverlayID();\n        bool assigned_overlay_use_fallback_origin = false;\n\n        if ( (m_OverlayStateCurrentID == floating_window_ovrl_state_room) && (assigned_overlay_id >= 0) )\n        {\n            const OverlayConfigData& data = OverlayManager::Get().GetConfigData((unsigned int)assigned_overlay_id);\n            ovrl_handle_assigned = data.ConfigHandle[configid_handle_overlay_state_overlay_handle];\n            assigned_overlay_use_fallback_origin = (data.ConfigInt[configid_int_overlay_origin] == ovrl_origin_theater_screen); //Theater Screen uses fallback origin if not pinned\n\n            if (ovrl_handle_assigned != vr::k_ulOverlayHandleInvalid)\n            {\n                if (m_OverlayStateCurrent->IsVisible != vr::VROverlay()->IsOverlayVisible(ovrl_handle_assigned))\n                {\n                    (m_OverlayStateCurrent->IsVisible) ? Hide() : Show();\n                }\n            }\n            else  //Overlay doesn't exist anymore, remove assignment\n            {\n                SetAssignedOverlayID(-1);\n            }\n        }\n\n        if ((!m_OvrlVisible) && (m_OverlayStateCurrent->IsVisible))\n        {\n            vr::VROverlay()->ShowOverlay(overlay_handle);\n            m_OvrlVisible = true;\n\n            ConfigManager::SetValue(configid_bool_state_keyboard_visible, true);\n            IPCManager::Get().PostConfigMessageToDashboardApp(configid_bool_state_keyboard_visible, true);\n        }\n\n        if ((m_OvrlVisible) && (!m_OverlayStateCurrent->IsVisible) && (m_Alpha == 0.0f))\n        {\n            vr::VROverlay()->HideOverlay(overlay_handle);\n            m_OvrlVisible = false;\n\n            ConfigManager::SetValue(configid_bool_state_keyboard_visible, false);\n            IPCManager::Get().PostConfigMessageToDashboardApp(configid_bool_state_keyboard_visible, false);\n        }\n\n        //Set position\n        if ( (m_OverlayStateCurrent->IsVisible) && (!m_IsTransitionFading) && (!UIManager::Get()->GetOverlayDragger().IsDragActive()) && (!UIManager::Get()->GetOverlayDragger().IsDragGestureActive()) )\n        {\n            if ( (ovrl_handle_assigned != vr::k_ulOverlayHandleInvalid) && (!assigned_overlay_use_fallback_origin) )    //Based on assigned overlay\n            {\n                if (!m_OverlayStateCurrent->IsPinned)\n                {\n                    Matrix4 matrix_m4 = OverlayManager::Get().GetOverlayCenterBottomTransform((unsigned int)assigned_overlay_id, ovrl_handle_assigned);\n                    UIManager::Get()->GetOverlayDragger().ApplyDashboardScale(matrix_m4);\n                    matrix_m4 *= m_OverlayStateCurrent->Transform;\n\n                    vr::HmdMatrix34_t matrix_ovr = matrix_m4.toOpenVR34();\n                    vr::VROverlay()->SetOverlayTransformAbsolute(GetOverlayHandle(), vr::TrackingUniverseStanding, &matrix_ovr);\n                    m_OverlayStateCurrent->TransformAbs = matrix_m4;\n                }\n            }\n            else if (m_OverlayStateCurrentID == floating_window_ovrl_state_dashboard_tab)   //Based on dashboard tab\n            {\n                if ((!m_OverlayStateCurrent->IsPinned) && ( (!UIManager::Get()->IsDummyOverlayTransformUnstable()) || (m_Alpha != 1.0f) ) )\n                {\n                    vr::TrackingUniverseOrigin origin = vr::TrackingUniverseStanding;\n                    Matrix4 matrix_m4 = UIManager::Get()->GetOverlayDragger().GetBaseOffsetMatrix(ovrl_origin_dplus_tab) * m_OverlayStateCurrent->Transform;\n\n                    vr::HmdMatrix34_t matrix_ovr = matrix_m4.toOpenVR34();\n                    vr::VROverlay()->SetOverlayTransformAbsolute(overlay_handle, origin, &matrix_ovr);\n                    m_OverlayStateCurrent->TransformAbs = matrix_m4;\n                }\n            }\n            else if (!m_OverlayStateCurrent->IsPinned)                                      //Based on m_TransformUIOrigin (fallback when above not available and unpinned)\n            {\n                if (!vr::VROverlay()->IsOverlayVisible(UIManager::Get()->GetOverlayHandleDPlusDashboard()))\n                {\n                    Matrix4 matrix_m4 = m_TransformUIOrigin;\n                    matrix_m4 *= m_OverlayStateCurrent->Transform;\n\n                    vr::HmdMatrix34_t matrix_ovr = matrix_m4.toOpenVR34();\n                    vr::VROverlay()->SetOverlayTransformAbsolute(GetOverlayHandle(), vr::TrackingUniverseStanding, &matrix_ovr);\n                    m_OverlayStateCurrent->TransformAbs = matrix_m4;\n                }\n            }\n        }\n    }\n}\n\nvoid WindowKeyboard::Show(bool skip_fade)\n{\n    switch (UIManager::Get()->GetVRKeyboard().GetInputTarget())\n    {\n        case kbdtarget_desktop: m_WindowTitleStrID = tstr_KeyboardWindowTitle;         break;\n        case kbdtarget_ui:      m_WindowTitleStrID = tstr_KeyboardWindowTitleSettings; break;\n        case kbdtarget_overlay:\n        {\n            m_WindowTitleStrID = tstr_NONE;\n            m_WindowTitle = TranslationManager::Get().GetString(tstr_KeyboardWindowTitleOverlay);\n\n            const unsigned int target_id = UIManager::Get()->GetVRKeyboard().GetInputTargetOverlayID();\n\n            if (target_id < OverlayManager::Get().GetOverlayCount())\n            {\n                StringReplaceAll(m_WindowTitle, \"%OVERLAYNAME%\", OverlayManager::Get().GetConfigData(target_id).ConfigNameStr);\n            }\n            else\n            {\n                StringReplaceAll(m_WindowTitle, \"%OVERLAYNAME%\", TranslationManager::Get().GetString(tstr_KeyboardWindowTitleOverlayUnknown));\n            }\n        }\n    }\n\n    //Update UI origin transform when newly visible, used when there's no overlay assignment to base the position off of\n    if ( (m_Alpha == 0.0f) && (UIManager::Get()->IsOpenVRLoaded()) )\n    {\n        //Get dashboard-similar transform and adjust it down a bit\n        Matrix4 matrix_facing = vr::IVRSystemEx::ComputeHMDFacingTransform(1.15f);\n        matrix_facing.translate_relative(0.0f, -0.50f, 0.0f);\n\n        //dplus_tab origin contains dashboard scale, so apply it to this transform to stay consistent in size\n        UIManager::Get()->GetOverlayDragger().ApplyDashboardScale(matrix_facing);\n\n        m_TransformUIOrigin = matrix_facing;\n    }\n\n    ApplyCurrentOverlayState();\n\n    FloatingWindow::Show(skip_fade);\n}\n\nvoid WindowKeyboard::Hide(bool skip_fade)\n{\n    m_IsAutoVisible = false;\n\n    //Refuse to hide if window is currently being dragged and remove assignment\n    if ( (UIManager::Get()->GetOverlayDragger().IsDragActive()) && (UIManager::Get()->GetOverlayDragger().GetDragOverlayHandle() == GetOverlayHandle()) )\n    {\n        SetAssignedOverlayID(-1);\n\n        return;\n    }\n\n    FloatingWindow::Hide(skip_fade);\n\n    UIManager::Get()->GetVRKeyboard().OnWindowHidden();\n}\n\nbool WindowKeyboard::SetAutoVisibility(int assigned_overlay_id, bool show)\n{\n    if (show)\n    {\n        if (!IsVisible())\n        {\n            SetAssignedOverlayID(assigned_overlay_id);\n            m_IsAutoVisible = true;\n\n            //This will not have a smooth transition if there was another auto-visible keyboard right before this, but let's skip the effort for that for now\n            Show();\n\n            return true;\n        }\n    }\n    else if ( (m_IsAutoVisible) && (GetAssignedOverlayID() == assigned_overlay_id) )\n    {\n        //Don't auto-hide while any buttons are being hovered, mostly relevant for the title bar buttons\n        if ((assigned_overlay_id == -2) && (m_IsAnyButtonHovered))\n        {\n            return false;\n        }\n\n        SetAssignedOverlayID(-1);\n        Hide();\n\n        return true;\n    }\n\n    return false;\n}\n\nvoid WindowKeyboard::WindowUpdate()\n{\n    ImGuiIO& io = ImGui::GetIO();\n    ImGuiStyle& style = ImGui::GetStyle();\n    VRKeyboard& vr_keyboard = UIManager::Get()->GetVRKeyboard();\n\n    //Set input state from the real mouse for ButtonLaser\n    LaserInputState& state_real = GetLaserInputState(vr::k_unTrackedDeviceIndexOther);\n    state_real.MouseState.SetFromGlobalState();\n\n    /*\n    //--LaserInputState desktop mode debug testing code \n    LaserInputState& state = GetLaserInputState(1);\n\n    if (!ImGui::IsMousePosValid(&state.MouseState.MousePos))\n    {\n        state.MouseState.MousePos = m_Pos;\n    }\n\n    if (ImGui::IsKeyDown(VK_NUMPAD4))\n    {\n        state.MouseState.MousePos.x -= 5;\n    }\n    if (ImGui::IsKeyDown(VK_NUMPAD6))\n    {\n        state.MouseState.MousePos.x += 5;\n    }\n    if (ImGui::IsKeyDown(VK_NUMPAD8))\n    {\n        state.MouseState.MousePos.y -= 5;\n    }\n    if (ImGui::IsKeyDown(VK_NUMPAD2))\n    {\n        state.MouseState.MousePos.y += 5;\n    }\n\n    state.MouseState.MouseDown[0] = ImGui::IsKeyDown(VK_NUMPAD7);\n    state.MouseState.MouseDown[1] = ImGui::IsKeyDown(VK_NUMPAD9);\n    //--\n    */\n\n    if (!UIManager::Get()->IsInDesktopMode())\n    {\n        vr::TrackedDeviceIndex_t primary_device = ConfigManager::Get().GetPrimaryLaserPointerDevice();\n        bool dashboard_device_exists = vr::IVROverlayEx::IsSystemLaserPointerActive();\n\n        //Overlay leave events are not processed when a dashboard pointer exists changes active state, so make sure to get the pointers out of the way anyways\n        if (dashboard_device_exists != m_IsDashboardPointerActiveLast)\n        {\n            for (auto& state : m_LaserInputStates)\n            {\n                //Pointer left the overlay\n                state.MouseState.MousePos = ImVec2(-FLT_MAX, -FLT_MAX);\n\n                //Release mouse buttons if they're held down\n                std::fill(std::begin(state.MouseState.MouseDown), std::end(state.MouseState.MouseDown), false);\n            }\n\n            state_real.MouseState.ApplyToGlobalState();\n\n            m_IsDashboardPointerActiveLast = dashboard_device_exists;\n        }\n\n        //Render pointer blobs for any input that isn't already controlled by the mouse\n        //Use pos/size from ImGui since our stored members describe center pivoted max window space\n        const ImVec2 cur_pos  = ImGui::GetWindowPos();\n        const ImVec2 cur_size = ImGui::GetWindowSize();\n        const ImRect window_bb(cur_pos.x, cur_pos.y, cur_pos.x + cur_size.x, cur_pos.y + cur_size.y);\n\n        for (auto& state : m_LaserInputStates)\n        {\n            if ( (state.DeviceIndex != vr::k_unTrackedDeviceIndexOther) && (state.DeviceIndex != primary_device) )\n            {\n                //Only render if inside window\n                if ((!dashboard_device_exists) && (window_bb.Contains(state.MouseState.MousePos)))\n                {\n                    ImGui::GetForegroundDrawList()->AddCircleFilled(state.MouseState.MousePos, 7.0f, ImGui::GetColorU32(Style_ImGuiCol_SteamVRCursorBorder));\n                    ImGui::GetForegroundDrawList()->AddCircleFilled(state.MouseState.MousePos, 5.0f, ImGui::GetColorU32(Style_ImGuiCol_SteamVRCursor));\n                }\n\n                //Also advance the mouse state as if it was managed by ImGui\n                state.MouseState.Advance();\n            }\n        }\n    }\n\n    //Increased key repeat delay and rate for the VR keyboard buttons\n    const float key_repeat_delay_old  = io.KeyRepeatDelay;\n    const float key_repeat_delay_rate = io.KeyRepeatRate;\n    io.KeyRepeatDelay = 0.5f;\n    io.KeyRepeatRate  = 0.025f;\n\n    //Exclude title bar buttons from hovered state to get regular widget focus behavior on them\n    const bool is_hovering_titlebar_buttons = ((ImGui::IsWindowHovered(ImGuiHoveredFlags_AllowWhenBlockedByActiveItem | ImGuiHoveredFlags_AllowWhenBlockedByPopup)) && \n                                               (io.MousePos.y < ImGui::GetCursorScreenPos().y - style.WindowPadding.y) && (!m_IsTitleBarHovered));\n\n    m_IsHovered = ((ImGui::IsWindowHovered(ImGuiHoveredFlags_AllowWhenBlockedByActiveItem | ImGuiHoveredFlags_AllowWhenBlockedByPopup)) && (!is_hovering_titlebar_buttons));\n\n    //Keyboard buttons use their own window-local active widget state in order to not interrupt active InputText() widgets. This is a dirty hack, but works for now\n    ImGui::ActiveWidgetStateStorage widget_state_back;\n    widget_state_back.StoreCurrentState();\n\n    m_KeyboardWidgetState.AdvanceState();\n    m_KeyboardWidgetState.ApplyState();\n\n    //Enable button repeat if setting is enabled\n    const bool use_key_repeat_global = ConfigManager::GetValue(configid_bool_input_keyboard_key_repeat);\n    if (use_key_repeat_global)\n        ImGui::PushItemFlag(ImGuiItemFlags_ButtonRepeat, true);\n\n    const float base_width = (float)int(ImGui::GetTextLineHeightWithSpacing() * 2.225f);\n\n    //Used for kbdlayout_key_virtual_key_iso_enter key\n    ImVec2 iso_enter_top_pos(-1.0f, -1.0f);\n    float  iso_enter_top_width    = -1.0f;\n    int    iso_enter_top_index    = -1;\n    int    iso_enter_button_state = 0;\n    int    iso_enter_click_state  = 0;\n    bool   iso_enter_hovered      = false;\n\n    KeyboardLayoutSubLayout current_sublayout = kbdlayout_sub_base;\n\n    //Select sublayout based on current state\n    if (m_SubLayoutOverride != kbdlayout_sub_base)\n    {\n        current_sublayout = m_SubLayoutOverride;\n    }\n    else if ((vr_keyboard.GetLayoutMetadata().HasAltGr) && (vr_keyboard.GetKeyDown(VK_RMENU)) && (!vr_keyboard.GetLayout(kbdlayout_sub_altgr).empty()))\n    {\n        current_sublayout = kbdlayout_sub_altgr;\n    }\n    else if ((vr_keyboard.GetKeyDown(VK_SHIFT) != vr_keyboard.IsCapsLockToggled()))\n    {\n        current_sublayout = kbdlayout_sub_shift;\n    }\n\n    //Resize button state storage if necessary\n    if (vr_keyboard.GetLayout(current_sublayout).size() > m_ButtonStates.size())\n    {\n        m_ButtonStates.resize(vr_keyboard.GetLayout(current_sublayout).size());\n    }\n\n    //Release keys that may have been held down when the sublayout switched (buttons won't fire release when they just disappear from that)\n    if (current_sublayout != m_LastSubLayout)\n    {\n        //Reset button state, but try to keep the same keys pressed if they exist in the new layout\n        std::vector<ButtonLaserState> button_states_prev = m_ButtonStates;\n        std::fill(m_ButtonStates.begin(), m_ButtonStates.end(), ButtonLaserState());\n\n        const bool sticky_modifiers_enabled = ConfigManager::GetValue(configid_bool_input_keyboard_sticky_modifiers);\n\n        int key_index = 0;\n        for (const auto& key : vr_keyboard.GetLayout(m_LastSubLayout))\n        {\n            ButtonLaserState& button_state = button_states_prev[key_index];\n\n            //If down, try to find same keys in both layouts\n            if (button_state.IsDown)\n            {\n                int key_index_new = FindSameKeyInNewSubLayout(key_index, m_LastSubLayout, current_sublayout);\n\n                if (key_index_new != -1)\n                {\n                    m_ButtonStates[key_index_new] = button_state;\n                }\n                else\n                {\n                    switch (key.KeyType)\n                    {\n                        case kbdlayout_key_virtual_key:\n                        {\n                            OnVirtualKeyUp(key.KeyCode);\n                            break;\n                        }\n                        case kbdlayout_key_virtual_key_iso_enter:\n                        {\n                            OnVirtualKeyUp(key.KeyCode);\n                            m_IsIsoEnterDown = false;\n                            break;\n                        }\n                        case kbdlayout_key_string:\n                        {\n                            OnStringKeyUp(key.KeyString);\n                            break;\n                        }\n                        case kbdlayout_key_action:\n                        {\n                            vr_keyboard.SetActionDown(key.KeyActionUID, false);\n                            break;\n                        }\n                        default: break;\n                    }\n                }\n            }\n\n            key_index++;\n        }\n\n        ImGui::ClearActiveID();\n    }\n\n    //Handle scheduled modifier releases\n    if (m_UnstickModifiersLater)\n    {\n        const unsigned char keycodes[] = {VK_LSHIFT, VK_RSHIFT, VK_LCONTROL, VK_RCONTROL, VK_LMENU, VK_RMENU, VK_LWIN, VK_RWIN};\n\n        //Release modifier keys unless they were manually sticky-ed by right-clicking them\n        for (auto keycode : keycodes)\n        {\n            if (!m_ManuallyStickingModifiers[GetModifierID(keycode)])\n            {\n                vr_keyboard.SetKeyDown(keycode, false);\n\n                //Look up correct button state to remove IsDown state\n                int key_index = 0;\n                for (const auto& key : vr_keyboard.GetLayout(current_sublayout))\n                {\n                    if (key.KeyCode == keycode)\n                    {\n                        m_ButtonStates[key_index].IsDown = false;\n                        break;\n                    }\n\n                    key_index++;\n                }\n            }\n        }\n\n        m_UnstickModifiersLater = false;\n    }\n\n    //Default to title bar button hover state as m_IsAnyButtonHovered is checked for blocking auto-hiding\n    m_IsAnyButtonHovered = is_hovering_titlebar_buttons;\n\n    int key_index = 0;\n    ImVec2 cursor_pos = ImGui::GetCursorPos();\n    for (const auto& key : vr_keyboard.GetLayout(current_sublayout))\n    {\n        ButtonLaserState& button_state = m_ButtonStates[key_index];\n        ImGui::PushID(key_index);\n\n        //Keep cursor pos on integer values\n        cursor_pos = ImGui::GetCursorPos();\n        ImGui::SetCursorPos({ceilf(cursor_pos.x), ceilf(cursor_pos.y)});\n\n        //This accounts for the spacing that is missing with wider keys so rows still line up and also forces integer values\n        const float key_width_f = base_width * key.Width  + (style.ItemInnerSpacing.x * (key.Width  - 1.0f));\n        const float key_height  = (float)(int)( base_width * key.Height + (style.ItemInnerSpacing.y * (key.Height - 1.0f)) );\n        float key_width         = (float)(int)( key_width_f );\n\n        //Add an extra pixel of width if the untruncated values would push it further\n        //There might be a smarter way to do this (simple rounding doesn't seem to be it), but this helps the keys align while rendering on full pixels only\n        if (cursor_pos.x + key_width_f > ImGui::GetCursorPosX() + key_width)\n        {\n            key_width += 1.0f;\n        }\n\n        //Disable button repeat if the individual key has its key repeat disabled (avoid doing it when it's already off though)\n        const bool use_key_repeat = ((use_key_repeat_global) && (!key.NoRepeat));\n\n        if ((use_key_repeat_global) && (!use_key_repeat))\n            ImGui::PushItemFlag(ImGuiItemFlags_ButtonRepeat, false);\n\n        //Set horizontal label alignment if needed\n        if (key.LabelHAlignment != 0.5f)\n            ImGui::PushStyleVarX(ImGuiStyleVar_ButtonTextAlign, key.LabelHAlignment);\n\n        switch (key.KeyType)\n        {\n            case kbdlayout_key_blank_space:\n            {\n                ImGui::Dummy({key_width, key_height});\n                break;\n            }\n            case kbdlayout_key_virtual_key:\n            {\n                const bool is_down = vr_keyboard.GetKeyDown(key.KeyCode);\n                const bool push_color = ((is_down) && (button_state.IsDown));  //Prevent flickering when repeat is active and there are multiple keys with the same key code\n\n                if (push_color)\n                    ImGui::PushStyleColor(ImGuiCol_Button, ImGui::GetStyleColorVec4(ImGuiCol_ButtonActive));\n\n                //We don't want key repeat on backspace for ImGui since it does that itself\n                const bool repeat_on_backspace = ((vr_keyboard.GetInputTarget() != kbdtarget_ui) || (key.KeyCode != VK_BACK));\n\n                if ( (ButtonLaser(key.Label.c_str(), {key_width, key_height}, button_state, key.IsLabelMultiline)) && (use_key_repeat) && (repeat_on_backspace) )\n                {\n                    (is_down) ? OnVirtualKeyUp(key.KeyCode, key.BlockModifiers) : OnVirtualKeyDown(key.KeyCode, key.BlockModifiers);\n                    button_state.IsDown = !button_state.IsDown;\n                }\n\n                if (button_state.IsActivated)\n                {\n                    OnVirtualKeyDown(key.KeyCode, key.BlockModifiers);\n                    button_state.IsDown = true;\n                }\n                else if (button_state.IsDeactivated)\n                {\n                    OnVirtualKeyUp(key.KeyCode, key.BlockModifiers);\n                    button_state.IsDown = false;\n                }\n\n                //Right click to toggle key\n                if (button_state.IsRightClicked)\n                {\n                    (is_down) ? OnVirtualKeyUp(key.KeyCode, key.BlockModifiers) : OnVirtualKeyDown(key.KeyCode, key.BlockModifiers);\n                    button_state.IsDown = !button_state.IsDown;\n\n                    SetManualStickyModifierState(key.KeyCode, !is_down);\n                }\n\n                if (push_color)\n                    ImGui::PopStyleColor();\n\n                break;\n            }\n            case kbdlayout_key_virtual_key_toggle:\n            {\n                if (use_key_repeat)\n                    ImGui::PushItemFlag(ImGuiItemFlags_ButtonRepeat, false);\n\n                const bool is_down = vr_keyboard.GetKeyDown(key.KeyCode);\n                const bool push_color = ((is_down) && (button_state.IsDown));\n\n                if (push_color)\n                    ImGui::PushStyleColor(ImGuiCol_Button, ImGui::GetStyleColorVec4(ImGuiCol_ButtonActive));\n\n                //Allow right click too to be consistent with toggling normal virtual keys\n                if ( (ButtonLaser(key.Label.c_str(), {key_width, key_height}, button_state, key.IsLabelMultiline)) || (button_state.IsRightClicked) )\n                {\n                    (is_down) ? OnVirtualKeyUp(key.KeyCode) : OnVirtualKeyDown(key.KeyCode);\n                    button_state.IsDown = !button_state.IsDown;\n\n                    if (button_state.IsRightClicked)\n                    {\n                        SetManualStickyModifierState(key.KeyCode, !is_down);\n                    }\n                }\n\n                if (push_color)\n                    ImGui::PopStyleColor();\n\n                if (use_key_repeat)\n                    ImGui::PopItemFlag();\n\n                break;\n            }\n            case kbdlayout_key_virtual_key_iso_enter:\n            {\n                //This one's a bit of a mess, but builds an ISO-Enter shaped button out of two key entries\n                //First step is the top \"key\". Its label is unused, but the width is.\n                //Second step is the bottom \"key\". It stretches itself over the row above it and hosts the label.\n                //First and second step used invisible buttons first to check item state and have it synced up,\n                //then in the second step there are two visual-only buttons used with style color adjusted to the state of the invisible buttons\n                //\n                //...now that a custom button implementation is used anyways this could be solved more cleanly... but it still works, so eh.\n\n                const bool is_bottom_key = (iso_enter_top_index != -1);\n                const ImVec2 cursor_pos = ImGui::GetCursorPos();\n                float offset_y = 0.0f;\n\n                //If second ISO-enter key, offset cursor to the previous row and stretch the button down to the end of the current row\n                if (is_bottom_key)\n                {\n                    offset_y = style.ItemSpacing.y + base_width;\n                    ImGui::SetCursorPosY(ImGui::GetCursorPosY() - offset_y);\n                }\n                else //else remember the width of the top part for later\n                {\n                    iso_enter_top_pos   = cursor_pos;\n                    iso_enter_top_width = key_width;\n                    iso_enter_top_index = key_index;\n                }\n\n                //Use an invisible button to collect active state from either (ImGui::InivisbleButton() does not pass button repeat flag, so can't be used here)\n                ImGui::PushStyleColor(ImGuiCol_Button,        {0.0f, 0.0f, 0.0f, 0.0f});\n                ImGui::PushStyleColor(ImGuiCol_ButtonHovered, {0.0f, 0.0f, 0.0f, 0.0f});\n                ImGui::PushStyleColor(ImGuiCol_ButtonActive,  {0.0f, 0.0f, 0.0f, 0.0f});\n\n                if ( (ButtonLaser(\"##IsoEnterDummy\", {key_width, base_width + offset_y}, button_state)) && (use_key_repeat) )\n                {\n                    iso_enter_click_state = 3;\n                }\n\n                ImGui::PopStyleColor(3);\n\n                if (button_state.IsHeld)\n                {\n                    iso_enter_button_state = 2;\n                }\n\n                if (button_state.IsHovered)\n                {\n                    if (button_state.IsRightClicked)\n                    {\n                        iso_enter_click_state = 4;\n                    }\n\n                    iso_enter_button_state = std::max(1, iso_enter_button_state);\n                    iso_enter_hovered = true;\n                }\n\n                if (button_state.IsActivated)\n                {\n                    iso_enter_click_state = 1;\n                }\n                else if (button_state.IsDeactivated)\n                {\n                    iso_enter_click_state = 2;\n                }\n\n                //If second ISO-enter key, create two visible buttons that match the active state collected from the invisible buttons (but otherwise don't do anything)\n                if (is_bottom_key)\n                {\n                    ImGui::SameLine(0.0f, style.ItemInnerSpacing.x);\n\n                    bool push_color = ((vr_keyboard.GetKeyDown(key.KeyCode)) && (m_IsIsoEnterDown));\n\n                    //Adjust button state depending on whether any invisible button was hovered to match ImGui behavior\n                    if ( (iso_enter_button_state == 0) && (iso_enter_hovered) )\n                        iso_enter_button_state = 1;\n                    else if (!iso_enter_hovered)\n                        iso_enter_button_state = 0;\n\n                    if (iso_enter_button_state == 1)\n                        ImGui::PushStyleColor(ImGuiCol_Button, ImGui::GetStyleColorVec4(ImGuiCol_ButtonHovered));\n                    else if ( (iso_enter_button_state >= 2) || (push_color) )\n                        ImGui::PushStyleColor(ImGuiCol_Button, ImGui::GetStyleColorVec4(ImGuiCol_ButtonActive));\n\n                    //Upper part without label\n                    ImGui::SetCursorPos(iso_enter_top_pos);\n\n                    //Shorten width by rounding so it doesn't stack corner AA\n                    ButtonVisual(\"##IsoEnterTop\", {iso_enter_top_width - style.FrameRounding, base_width});\n\n                    //Lower part with label\n                    ImGui::SetCursorPos({cursor_pos.x, cursor_pos.y - offset_y});\n\n                    ButtonVisual(key.Label.c_str(), {key_width, base_width + offset_y}, key.IsLabelMultiline);\n\n                    //React to button click state\n                    if ( ( (iso_enter_click_state == 3) /*button clicked*/ && (use_key_repeat) ) || (iso_enter_click_state == 4) /*button right-clicked*/)\n                    {\n                        bool is_down = vr_keyboard.GetKeyDown(key.KeyCode);\n                        (is_down) ? OnVirtualKeyUp(key.KeyCode) : OnVirtualKeyDown(key.KeyCode);\n\n                        if (iso_enter_click_state == 4) //button right-clicked\n                        {\n                            SetManualStickyModifierState(key.KeyCode, !is_down);\n                            m_IsIsoEnterDown = !m_IsIsoEnterDown;\n                        }\n                    }\n\n                    if (iso_enter_click_state == 1)      //button pressed\n                    {\n                        OnVirtualKeyDown(key.KeyCode);\n                        m_IsIsoEnterDown = true;\n                    }\n                    else if (iso_enter_click_state == 2) //button released\n                    {\n                        OnVirtualKeyUp(key.KeyCode);\n                        m_IsIsoEnterDown = false;\n                    }\n\n                    if ( (iso_enter_button_state != 0) || (push_color) )\n                        ImGui::PopStyleColor();\n\n                    //Sync up button hover state for haptics\n                    m_ButtonStates[iso_enter_top_index].IsHovered = iso_enter_hovered;\n                    button_state.IsHovered = iso_enter_hovered;\n\n                    //Restore cursor to normal row position\n                    ImGui::SameLine(0.0f, 0.0f);\n                    ImGui::SetCursorPosY( (key.IsRowEnd) ? ImGui::GetCursorPosY() : ImGui::GetCursorPosY() + offset_y);\n                    ImGui::Dummy({0.0f, base_width});\n                    ImGui::SetPreviousLineHeight(ImGui::GetPreviousLineHeight() - offset_y);\n                }\n                break;\n            }\n            case kbdlayout_key_string:\n            {\n                const bool is_down = button_state.IsDown;\n\n                if (is_down)\n                    ImGui::PushStyleColor(ImGuiCol_Button, ImGui::GetStyleColorVec4(ImGuiCol_ButtonActive));\n\n                if ( (ButtonLaser(key.Label.c_str(), {key_width, key_height}, button_state, key.IsLabelMultiline)) && (use_key_repeat) )\n                {\n                    (button_state.IsDown) ? OnStringKeyUp(key.KeyString) : OnStringKeyDown(key.KeyString);\n                    button_state.IsDown = !button_state.IsDown;\n                }\n\n                if (button_state.IsActivated)\n                {\n                    OnStringKeyDown(key.KeyString);\n                    button_state.IsDown = true;\n                }\n                else if (button_state.IsDeactivated)\n                {\n                    OnStringKeyUp(key.KeyString);\n                    button_state.IsDown = false;\n                }\n\n                //Right click to toggle key, only works properly when string maps to virtual key\n                if (button_state.IsRightClicked)\n                {\n                    (button_state.IsDown) ? OnStringKeyUp(key.KeyString) : OnStringKeyDown(key.KeyString);\n                    button_state.IsDown = !button_state.IsDown;\n                }\n\n                if (is_down)\n                    ImGui::PopStyleColor();\n\n                break;\n            }\n            case kbdlayout_key_sublayout_toggle:\n            {\n                if (use_key_repeat)\n                    ImGui::PushItemFlag(ImGuiItemFlags_ButtonRepeat, false);\n\n                const bool is_down = (m_SubLayoutOverride == key.KeySubLayoutToggle);\n\n                if (is_down)\n                    ImGui::PushStyleColor(ImGuiCol_Button, ImGui::GetStyleColorVec4(ImGuiCol_ButtonActive));\n\n                //Allow right click too to be consistent with toggling normal virtual keys\n                if ( (ButtonLaser(key.Label.c_str(), {key_width, key_height}, button_state, key.IsLabelMultiline)) || (button_state.IsRightClicked) )\n                {\n                    m_SubLayoutOverride = (is_down) ? kbdlayout_sub_base : key.KeySubLayoutToggle;\n                }\n\n                if (is_down)\n                    ImGui::PopStyleColor();\n\n                if (use_key_repeat)\n                    ImGui::PopItemFlag();\n\n                break;\n            }\n            case kbdlayout_key_action:\n            {\n                const bool is_down = button_state.IsDown;\n\n                if (is_down)\n                    ImGui::PushStyleColor(ImGuiCol_Button, ImGui::GetStyleColorVec4(ImGuiCol_ButtonActive));\n\n                if ( (ButtonLaser(key.Label.c_str(), {key_width, key_height}, button_state, key.IsLabelMultiline)) && (use_key_repeat) )\n                {\n                    vr_keyboard.SetActionDown(key.KeyActionUID, !button_state.IsDown);\n                    button_state.IsDown = !button_state.IsDown;\n                }\n\n                if (button_state.IsActivated)\n                {\n                    vr_keyboard.SetActionDown(key.KeyActionUID, true);\n                    button_state.IsDown = true;\n                }\n                else if (button_state.IsDeactivated)\n                {\n                    vr_keyboard.SetActionDown(key.KeyActionUID, false);\n                    button_state.IsDown = false;\n                }\n\n                //Right click to toggle key\n                if (button_state.IsRightClicked)\n                {\n                    vr_keyboard.SetActionDown(key.KeyActionUID, !button_state.IsDown);\n                    button_state.IsDown = !button_state.IsDown;\n                }\n\n                if (is_down)\n                    ImGui::PopStyleColor();\n\n                break;\n            }\n            default: break;\n        }\n\n        //Return to normal row position if the key was taller than 100%\n        if (key.Height > 1.0f)\n        {\n            ImGui::SetCursorPosY(ImGui::GetCursorPosY() - key_height + base_width);\n            ImGui::SetPreviousLineHeight(ImGui::GetPreviousLineHeight() - key_height + base_width);\n        }\n\n        if (!key.IsRowEnd)\n        {\n            ImGui::SameLine(0.0f, style.ItemInnerSpacing.x);\n            cursor_pos.x += key_width_f;\n        }\n\n        //Undo label alignment if necessary\n        if (key.LabelHAlignment != 0.5f)\n            ImGui::PopStyleVar();\n\n        //Undo disabled button repeat if necessary\n        if ((use_key_repeat_global) && (!use_key_repeat))\n            ImGui::PopItemFlag();\n\n        ImGui::PopID();\n\n        key_index++;\n    }\n\n    m_LastSubLayout = current_sublayout;\n\n    if (use_key_repeat_global)\n        ImGui::PopItemFlag();\n\n    m_KeyboardWidgetState.StoreCurrentState();\n    widget_state_back.ApplyState();\n\n    io.KeyRepeatDelay = key_repeat_delay_old;\n    io.KeyRepeatRate  = key_repeat_delay_rate;\n}\n\nvoid WindowKeyboard::OnWindowPinButtonPressed()\n{\n    FloatingWindow::OnWindowPinButtonPressed();\n\n    //Disable auto-hiding that may happen when the keyboard is no longer hovered after pressing this\n    m_IsAutoVisible = false;\n}\n\nvoid WindowKeyboard::OnWindowCloseButtonPressed()\n{\n    //Remove assignment on close button press to not just have to pop up again from overlay visbility tracking (but only when in room state)\n    if (m_OverlayStateCurrentID == floating_window_ovrl_state_room)\n    {\n        SetAssignedOverlayID(-1);\n    }\n}\n\nbool WindowKeyboard::IsVirtualWindowItemHovered() const\n{\n    return m_IsAnyButtonHovered;\n}\n\nvoid WindowKeyboard::OnVirtualKeyDown(unsigned char keycode, bool block_modifiers)\n{\n    VRKeyboard& vr_keyboard = UIManager::Get()->GetVRKeyboard();\n\n    vr_keyboard.SetKeyDown(keycode, true, block_modifiers);\n\n    if ((keycode != VK_BACK) && (keycode != VK_TAB))\n    {\n        HandleUnstickyModifiers(keycode);\n    }\n}\n\nvoid WindowKeyboard::OnVirtualKeyUp(unsigned char keycode, bool block_modifiers)\n{\n    VRKeyboard& vr_keyboard = UIManager::Get()->GetVRKeyboard();\n\n    vr_keyboard.SetKeyDown(keycode, false, block_modifiers);\n}\n\nvoid WindowKeyboard::OnStringKeyDown(const std::string& keystring)\n{\n    VRKeyboard& vr_keyboard = UIManager::Get()->GetVRKeyboard();\n    HandleUnstickyModifiers();\n    vr_keyboard.SetStringDown(keystring, true);\n}\n\nvoid WindowKeyboard::OnStringKeyUp(const std::string& keystring)\n{\n    VRKeyboard& vr_keyboard = UIManager::Get()->GetVRKeyboard();\n    vr_keyboard.SetStringDown(keystring, false);\n}\n\nvoid WindowKeyboard::HandleUnstickyModifiers(unsigned char source_keycode)\n{\n    //Skip if modifiers are set to be sticky\n    if (ConfigManager::GetValue(configid_bool_input_keyboard_sticky_modifiers))\n        return;\n\n    //Schedule releasing modifier keys next frame if this wasn't called for any of them\n    switch (source_keycode)\n    {\n        case VK_SHIFT:\n        case VK_LSHIFT:\n        case VK_RSHIFT:\n        case VK_CONTROL:\n        case VK_LCONTROL:\n        case VK_RCONTROL:\n        case VK_MENU:\n        case VK_LMENU:\n        case VK_RMENU:\n        case VK_LWIN:\n        case VK_RWIN:\n        {\n            break;\n        }\n\n        default:\n        {\n            m_UnstickModifiersLater = true;\n        }\n    }\n}\n\nvoid WindowKeyboard::SetManualStickyModifierState(unsigned char keycode, bool is_down)\n{\n    const int modifier_id = GetModifierID(keycode);\n    if (modifier_id != -1)\n    {\n        m_ManuallyStickingModifiers[modifier_id] = is_down;\n    }\n}\n\nint WindowKeyboard::GetModifierID(unsigned char keycode)\n{\n    switch (keycode)\n    {\n        case VK_SHIFT:\n        {\n            return 0;\n            break;\n        }\n        case VK_CONTROL:\n        {\n            return VK_LCONTROL - VK_LSHIFT;\n            break;\n        }\n        case VK_MENU:\n        {\n            return VK_LMENU - VK_LSHIFT;\n            break;\n        }\n        case VK_LSHIFT:\n        case VK_RSHIFT:\n        case VK_LCONTROL:\n        case VK_RCONTROL:\n        case VK_LMENU:\n        case VK_RMENU:\n        {\n            return keycode - VK_LSHIFT;\n        }\n        case VK_LWIN:\n        case VK_RWIN:\n        {\n            return (keycode - VK_LWIN) + 7;\n            break;\n        }\n        default:\n        {\n            break;\n        }\n    }\n\n    return -1;\n}\n\nint WindowKeyboard::FindSameKeyInNewSubLayout(int key_index, KeyboardLayoutSubLayout sublayout_id_current, KeyboardLayoutSubLayout sublayout_id_new)\n{\n    //Return index of key at the position as sublayout_current's key_index key exists and has the same function (i.e. the same key, but possibly with different label and index)\n    VRKeyboard& vr_keyboard = UIManager::Get()->GetVRKeyboard();\n    const auto& sublayout_current = vr_keyboard.GetLayout(sublayout_id_current);\n    const auto& sublayout_new     = vr_keyboard.GetLayout(sublayout_id_new);\n\n    //Skip if index doesn't exist in layout for some reason\n    if (sublayout_current.size() <= key_index)\n        return -1;\n\n    //Get key position in current layout\n    int i = 0;\n    ImVec2 key_current_pos;\n    const KeyboardLayoutKey* key_ptr_current = nullptr;\n\n    for (const auto& key : sublayout_current)\n    {\n        if (i == key_index)\n        {\n            key_ptr_current = &key;\n            break;\n        }\n\n        key_current_pos.x += key.Width;\n\n        if (key.IsRowEnd)\n        {\n            key_current_pos.x  = 0.0f;\n            key_current_pos.y += 1.0f; //Key height doesn't matter, advance a row\n        }\n\n        i++;\n    }\n\n    if (key_ptr_current == nullptr)\n        return -1;\n\n    //Find key at position in new layout\n    i = 0;\n    ImVec2 key_new_pos;\n\n    for (const auto& key : sublayout_new)\n    {\n        if ( (key_new_pos.y == key_current_pos.y) && (key_new_pos.x == key_current_pos.x) )\n        {\n            //Key exists at the same position, check if it's same function\n            if (key_ptr_current->KeyType == key.KeyType)\n            {\n                switch (key.KeyType)\n                {\n                    case kbdlayout_key_virtual_key:\n                    case kbdlayout_key_virtual_key_toggle:\n                    case kbdlayout_key_virtual_key_iso_enter:\n                    {\n                        if (key.KeyCode == key_ptr_current->KeyCode)\n                        {\n                            return i;\n                        }\n                        break;\n                    }\n                    case kbdlayout_key_string:\n                    {\n                        if (key.KeyString == key_ptr_current->KeyString)\n                        {\n                            return i;\n                        }\n                        break;\n                    }\n                    case kbdlayout_key_sublayout_toggle:\n                    {\n                        if (key.KeySubLayoutToggle == key_ptr_current->KeySubLayoutToggle) //This one seems a bit useless\n                        {\n                            return i;\n                        }\n                        break;\n                    }\n                    case kbdlayout_key_action:\n                    {\n                        if (key.KeyActionUID == key_ptr_current->KeyActionUID)\n                        {\n                            return i;\n                        }\n                        break;\n                    }\n                }\n            }\n\n            return -1;\n        }\n        else if (key_new_pos.y > key_current_pos.y) //We went past, break\n        {\n            break;\n        }\n\n        key_new_pos.x += key.Width;\n\n        if (key.IsRowEnd)\n        {\n            key_new_pos.x  = 0.0f;\n            key_new_pos.y += 1.0f;\n        }\n\n        i++;\n    }\n\n    return -1;\n}\n\nLaserInputState& WindowKeyboard::GetLaserInputState(vr::TrackedDeviceIndex_t device_index)\n{\n    auto it = std::find_if(m_LaserInputStates.begin(), m_LaserInputStates.end(), [&](const auto& state){ return (state.DeviceIndex == device_index); });\n\n    //Add if device isn't in the list yet\n    if (it == m_LaserInputStates.end())\n    {\n        m_LaserInputStates.push_back(LaserInputState());\n        it = m_LaserInputStates.end() - 1;\n        it->DeviceIndex = device_index;\n    }\n\n    return *it;\n}\n\nbool WindowKeyboard::ButtonLaser(const char* label, const ImVec2& size_arg, ButtonLaserState& button_state, bool is_label_multiline)\n{\n    ImGuiWindow* window = ImGui::GetCurrentWindow();\n    if (window->SkipItems)\n        return false;\n\n    ImGuiContext& g         = *GImGui;\n    const ImGuiStyle& style = g.Style;\n    const ImGuiID id        = window->GetID(label);\n    const ImVec2 label_size = ImGui::CalcTextSize(label, nullptr, true);\n    const ImVec2 half_spacing(style.ItemSpacing.x / 2.0f, style.ItemSpacing.y / 2.0f);\n\n    ImVec2 pos  = window->DC.CursorPos;\n    ImVec2 size = ImGui::CalcItemSize(size_arg, label_size.x + style.FramePadding.x * 2.0f, label_size.y + style.FramePadding.y * 2.0f);\n\n    const ImRect bb(pos.x, pos.y, pos.x + size.x, pos.y + size.y);\n    const ImRect bb_with_spacing(bb.Min.x - half_spacing.x, bb.Min.y - half_spacing.y, bb.Max.x + half_spacing.x, bb.Max.y + half_spacing.y);\n\n    ImGui::ItemSize(size, style.FramePadding.y);\n    if (!ImGui::ItemAdd(bb, id))\n        return false;\n\n    bool do_repeat = (g.LastItemData.ItemFlags & ImGuiItemFlags_ButtonRepeat);\n\n    //Check all laser input states for potential input on the button\n    LaserInputState orig_state;\n    orig_state.MouseState.SetFromGlobalState();\n\n    const ImGuiMouseButton mouse_button       = ImGuiMouseButton_Left;\n    const ImGuiMouseButton mouse_button_right = ImGuiMouseButton_Right;\n\n    bool is_global_mouse_state_modified = false;\n    bool is_any_hovering = false, is_pressed = false, is_held = button_state.IsHeld, is_released = false;\n\n    button_state.IsRightClicked = false;\n\n    if ( (!UIManager::Get()->GetOverlayDragger().IsDragActive()) && (!UIManager::Get()->GetOverlayDragger().IsDragGestureActive()) ) //Skip if an overlay drag is active\n    {\n        for (auto& state : m_LaserInputStates)\n        {\n            bool hovered_spacing = bb_with_spacing.Contains(state.MouseState.MousePos);\n            bool hovered = (hovered_spacing) ? bb.Contains(state.MouseState.MousePos) : false;\n\n            if (hovered)\n            {\n                if (!button_state.IsHeld)\n                {\n                    if (state.MouseState.MouseClicked[mouse_button])\n                    {\n                        is_held = true;\n                        is_pressed = !do_repeat;    //Only set pressed when not repeating (IsActivated/IsDeactivated is used instead then)\n                        button_state.DeviceIndexHeld = state.DeviceIndex;\n                    }\n                    else if (state.MouseState.MouseClicked[mouse_button_right]) //Right-click is only a simple click state\n                    {\n                        button_state.IsRightClicked = true;\n                    }\n                }\n\n                //Newly hovered, trigger haptics\n                if (!button_state.IsHovered)\n                {\n                    if (state.DeviceIndex == vr::k_unTrackedDeviceIndexOther) //other == mouse == current primary pointer device, don't use index\n                    {\n                        UIManager::Get()->TriggerLaserPointerHaptics(GetOverlayHandle());\n                    }\n                    else\n                    {\n                        UIManager::Get()->TriggerLaserPointerHaptics(GetOverlayHandle(), state.DeviceIndex);\n                    }\n                }\n            }\n\n            if ( (button_state.IsHeld) && (button_state.DeviceIndexHeld == state.DeviceIndex) )\n            {\n                if (state.MouseState.MouseReleased[mouse_button])\n                {\n                    button_state.DeviceIndexHeld = vr::k_unTrackedDeviceIndexInvalid;\n                    is_released = true;\n                }\n                else if ((do_repeat) && (state.MouseState.MouseDownDuration[mouse_button] > 0.0f))\n                {\n                    //In order to access the button repeat behavior, we have to apply this mouse state to the global input state\n                    //As it is avoidable in all other cases we only do it here, which is a rarely hit code path\n                    state.MouseState.ApplyToGlobalState();\n                    is_global_mouse_state_modified = true;\n\n                    if (ImGui::IsMouseClicked(mouse_button, true))\n                    {\n                        is_pressed = true;\n                    }\n                }\n            }\n\n            if ( (state.DeviceIndex == vr::k_unTrackedDeviceIndexOther) && ( (hovered_spacing) || (button_state.IsHeld) ) )\n            {\n                m_IsAnyButtonHovered = true;    //This state is only used for blank space dragging. We include the spacing area in order to not activate drags from clicking in-between key spacing\n            }\n\n            if (hovered)\n                is_any_hovering = hovered;\n        }\n    }\n\n    //Adjust button state\n    button_state.IsActivated   = ((!button_state.IsHeld) && (is_held));\n    button_state.IsDeactivated = ((button_state.IsHeld) && (is_released));\n    button_state.IsHovered     = ((is_any_hovering) || (is_held));\n    button_state.IsHeld        = (button_state.DeviceIndexHeld != vr::k_unTrackedDeviceIndexInvalid);\n    //button_state.IsDown is not set by this function\n\n    if (is_global_mouse_state_modified)\n    {\n        orig_state.MouseState.ApplyToGlobalState();\n    }\n\n    //Render button\n    const ImU32 col = ImGui::GetColorU32((button_state.IsHeld) ? ImGuiCol_ButtonActive : (is_any_hovering) ? ImGuiCol_ButtonHovered : ImGuiCol_Button);\n    ImGui::RenderNavCursor(bb, id);\n    ImGui::RenderFrame(bb.Min, bb.Max, col, true, style.FrameRounding);\n\n    if (is_label_multiline)\n    {\n        ImGui::RenderButtonMultilineLabel(label, bb, 0.82f);\n    }\n    else\n    {\n        const ImVec2 pos_min(bb.Min.x + style.FramePadding.x, bb.Min.y + style.FramePadding.y);\n        const ImVec2 pos_max(bb.Max.x - style.FramePadding.x, bb.Max.y - style.FramePadding.y);\n\n        ImGui::RenderTextClipped(pos_min, pos_max, label, nullptr, &label_size, style.ButtonTextAlign, &bb);\n    }\n\n    return is_pressed;\n}\n\nvoid WindowKeyboard::ButtonVisual(const char* label, const ImVec2& size_arg, bool is_label_multiline)\n{\n    ImGuiWindow* window = ImGui::GetCurrentWindow();\n    if (window->SkipItems)\n        return;\n\n    const ImGuiStyle& style = ImGui::GetStyle();\n    const ImVec2 label_size = ImGui::CalcTextSize(label, nullptr, true);\n\n    ImVec2 pos  = window->DC.CursorPos;\n    ImVec2 size = ImGui::CalcItemSize(size_arg, label_size.x + style.FramePadding.x * 2.0f, label_size.y + style.FramePadding.y * 2.0f);\n    const ImRect bb(pos.x, pos.y, pos.x + size.x, pos.y + size.y);\n\n    //Render button\n    const ImU32 col = ImGui::GetColorU32(ImGuiCol_Button);\n    ImGui::RenderFrame(bb.Min, bb.Max, col, true, style.FrameRounding);\n\n    if (is_label_multiline)\n    {\n        ImGui::RenderButtonMultilineLabel(label, bb, 0.82f);\n    }\n    else\n    {\n        const ImVec2 pos_min(bb.Min.x + style.FramePadding.x, bb.Min.y + style.FramePadding.y);\n        const ImVec2 pos_max(bb.Max.x - style.FramePadding.x, bb.Max.y - style.FramePadding.y);\n\n        ImGui::RenderTextClipped(pos_min, pos_max, label, nullptr, &label_size, style.ButtonTextAlign, &bb);\n    }\n}\n\nvr::VROverlayHandle_t WindowKeyboard::GetOverlayHandle() const\n{\n    return UIManager::Get()->GetOverlayHandleKeyboard();\n}\n\nvoid WindowKeyboard::RebaseTransform()\n{\n    if ( (m_OverlayStateCurrentID == floating_window_ovrl_state_room) && (!m_OverlayStateCurrent->IsPinned) )\n    {\n        vr::HmdMatrix34_t hmd_mat = {0};\n        vr::TrackingUniverseOrigin universe_origin = vr::TrackingUniverseStanding;\n\n        vr::VROverlay()->GetOverlayTransformAbsolute(GetOverlayHandle(), &universe_origin, &hmd_mat);\n        Matrix4 mat_abs = hmd_mat;\n        Matrix4 mat_origin_inverse;\n\n        int assigned_id = GetAssignedOverlayID();\n        bool assigned_overlay_use_fallback_origin = false;\n\n        if (assigned_id >= 0)\n        {\n            const OverlayConfigData& data = OverlayManager::Get().GetConfigData((unsigned int)assigned_id);\n            assigned_overlay_use_fallback_origin = (data.ConfigInt[configid_int_overlay_origin] == ovrl_origin_theater_screen); //Theater Screen uses fallback origin if not pinned\n        }\n\n        if ((!assigned_overlay_use_fallback_origin) && (assigned_id >= 0))\n        {\n            mat_origin_inverse = OverlayManager::Get().GetOverlayCenterBottomTransform((unsigned int)assigned_id);\n            UIManager::Get()->GetOverlayDragger().ApplyDashboardScale(mat_origin_inverse);\n        }\n        else\n        {\n            mat_origin_inverse = m_TransformUIOrigin;\n        }\n\n        mat_origin_inverse.invert();\n        m_OverlayStateCurrent->Transform = mat_origin_inverse * mat_abs;\n    }\n    else\n    {\n        FloatingWindow::RebaseTransform();\n    }\n}\n\nvoid WindowKeyboard::ResetTransform(FloatingWindowOverlayStateID state_id)\n{\n    FloatingWindow::ResetTransform(state_id);\n\n    FloatingWindowOverlayState& overlay_state = GetOverlayState(state_id);\n\n    //Offset keyboard position depending on the scaled size\n    const float overlay_height_m = (m_Size.y / m_Size.x) * OVERLAY_WIDTH_METERS_KEYBOARD;\n    const float offset_up        = -0.55f - ((overlay_height_m * overlay_state.Size) / 2.0f);\n\n    overlay_state.Transform.rotateX(-45);\n    overlay_state.Transform.translate_relative(0.0f, offset_up, 0.00f);\n\n    //If visible, pinned and dplus dashboard overlay not available, reset to transform useful outside of the dashboard\n    if ( (state_id == m_OverlayStateCurrentID) && (overlay_state.IsVisible) && (overlay_state.IsPinned) && (UIManager::Get()->IsOpenVRLoaded()) && \n         (!vr::VROverlay()->IsOverlayVisible(UIManager::Get()->GetOverlayHandleDPlusDashboard())) )\n    {\n        //Get dashboard-similar transform and adjust it down a bit\n        Matrix4 matrix_facing = vr::IVRSystemEx::ComputeHMDFacingTransform(1.15f);\n        matrix_facing.translate_relative(0.0f, -0.50f, 0.0f);\n\n        //dplus_tab origin contains dashboard scale, so get that scale and apply it to this transform to stay consistent in size\n        Matrix4 mat_origin = UIManager::Get()->GetOverlayDragger().GetBaseOffsetMatrix(ovrl_origin_dplus_tab);\n        Vector3 row_1(mat_origin[0], mat_origin[1], mat_origin[2]);\n\n        Vector3 translation = matrix_facing.getTranslation();\n        matrix_facing.setTranslation({0.0f, 0.0f, 0.0f});\n        matrix_facing.scale(row_1.length());\n        matrix_facing.setTranslation(translation);\n\n        //Apply facing transform to normal keyboard position\n        overlay_state.Transform = matrix_facing * overlay_state.Transform;\n\n        //Set transform directly as it may not be updated automatically\n        vr::HmdMatrix34_t matrix_ovr = overlay_state.Transform.toOpenVR34();\n        vr::VROverlay()->SetOverlayTransformAbsolute(GetOverlayHandle(), vr::TrackingUniverseStanding, &matrix_ovr);\n    }\n}\n\nvoid WindowKeyboard::SetAssignedOverlayID(int assigned_id)\n{\n    (m_OverlayStateCurrentID == floating_window_ovrl_state_dashboard_tab) ? m_AssignedOverlayIDDashboardTab = assigned_id : m_AssignedOverlayIDRoom = assigned_id;\n}\n\nvoid WindowKeyboard::SetAssignedOverlayID(int assigned_id, FloatingWindowOverlayStateID state_id)\n{\n    (state_id == floating_window_ovrl_state_dashboard_tab) ? m_AssignedOverlayIDDashboardTab = assigned_id : m_AssignedOverlayIDRoom = assigned_id;\n}\n\nint WindowKeyboard::GetAssignedOverlayID() const\n{\n    return (m_OverlayStateCurrentID == floating_window_ovrl_state_dashboard_tab) ? m_AssignedOverlayIDDashboardTab : m_AssignedOverlayIDRoom;\n}\n\nint WindowKeyboard::GetAssignedOverlayID(FloatingWindowOverlayStateID state_id) const\n{\n    return (state_id == floating_window_ovrl_state_dashboard_tab) ? m_AssignedOverlayIDDashboardTab : m_AssignedOverlayIDRoom;\n}\n\nbool WindowKeyboard::IsHovered() const\n{\n    return m_IsHovered;\n}\n\nvoid WindowKeyboard::ResetButtonState()\n{\n    m_ButtonStates.clear();\n    m_IsIsoEnterDown = false;\n}\n\nbool WindowKeyboard::HandleOverlayEvent(const vr::VREvent_t& vr_event)\n{\n    //Notify dashboard app for focus enter/leave in case there's a reason to react to it\n    switch (vr_event.eventType)\n    {\n        case vr::VREvent_FocusEnter:\n        {\n            IPCManager::Get().PostMessageToDashboardApp(ipcmsg_action, ipcact_keyboard_ovrl_focus_enter);\n            break;\n        }\n        case vr::VREvent_FocusLeave:\n        {\n            IPCManager::Get().PostMessageToDashboardApp(ipcmsg_action, ipcact_keyboard_ovrl_focus_leave);\n            break;\n        }\n    }\n\n    if (ImGui::GetCurrentContext() == nullptr)\n        return false;\n\n    vr::TrackedDeviceIndex_t device_index = vr_event.trackedDeviceIndex;\n\n    //System laser pointer expresses a secondary pointer device as a cursor index instead of providing the device index as our custom laser pointer does\n    uint32_t cursor_index = 0;\n    switch (vr_event.eventType)\n    {\n        case vr::VREvent_MouseMove:\n        case vr::VREvent_MouseButtonDown:\n        case vr::VREvent_MouseButtonUp:\n        {\n            cursor_index = vr_event.data.mouse.cursorIndex;\n            break;\n        }\n        case vr::VREvent_FocusEnter:\n        case vr::VREvent_FocusLeave:\n        {\n            cursor_index = vr_event.data.overlay.cursorIndex;\n            break;\n        }\n        case vr::VREvent_ScrollDiscrete:\n        case vr::VREvent_ScrollSmooth:\n        {\n            cursor_index = vr_event.data.scroll.cursorIndex;\n            break;\n        }\n        default: break;\n    }\n\n    //See if we already identified the device index for this cursor index if it's not the primary laser pointer\n    if (cursor_index != 0)\n    {\n        auto it = std::find_if(m_LaserInputStates.begin(), m_LaserInputStates.end(), [&](const auto& state){ return (state.CursorIndexLast == cursor_index); });\n\n        if (it != m_LaserInputStates.end())\n        {\n            device_index = it->DeviceIndex;\n        }\n    }\n\n    //Patch up mouse and button state when device is primary device and there's still a separate input state\n    if ((device_index == ConfigManager::Get().GetPrimaryLaserPointerDevice()) && (device_index != vr::k_unTrackedDeviceIndexInvalid))\n    {\n        auto it = std::find_if(m_LaserInputStates.begin(), m_LaserInputStates.end(), [&](const auto& state){ return (state.DeviceIndex == device_index); });\n\n        if (it != m_LaserInputStates.end())\n        {\n            for (ButtonLaserState& button_state : m_ButtonStates)\n            {\n                if (button_state.DeviceIndexHeld == device_index)\n                {\n                    button_state.DeviceIndexHeld = vr::k_unTrackedDeviceIndexOther; //Other == global mouse\n                }\n            }\n\n            it->MouseState.ApplyToGlobalState();\n\n            m_LaserInputStates.erase(it);\n        }\n\n        return false;\n    }\n\n    //Skip if no valid device index or it's the system pointer (sends as device 0 (HMD) or invalid)\n    //For mouse move events with cursor indices above 0 we allow invalid devices as they're used to guess the correct one\n    if ( ( (device_index >= vr::k_unMaxTrackedDeviceCount) || (device_index == vr::k_unTrackedDeviceIndex_Hmd) || \n          ((cursor_index == 0) && (vr::IVROverlayEx::IsSystemLaserPointerActive())) ) && ((cursor_index == 0) || (vr_event.eventType != vr::VREvent_MouseMove)) )\n    {\n        return (cursor_index != 0); //Still return true for non-0 index as we don't want it to be treated as ImGui mouse input\n    }\n\n    switch (vr_event.eventType)\n    {\n        case vr::VREvent_MouseMove:\n        {\n            //If we haven't seen this device on this cursor index before (or device index is invalid), guess which one it likely is based on the cursor position\n            if (GetLaserInputState(device_index).CursorIndexLast != cursor_index)\n            {\n                Vector2 uv_pos(vr_event.data.mouse.x / ImGui::GetIO().DisplaySize.x, vr_event.data.mouse.y / ImGui::GetIO().DisplaySize.y);\n                device_index = vr::IVROverlayEx::FindPointerDeviceForOverlay(UIManager::Get()->GetOverlayHandleKeyboard(), uv_pos);\n            }\n\n            if (device_index != vr::k_unTrackedDeviceIndexInvalid)\n            {\n                LaserInputState& state = GetLaserInputState(device_index);\n                state.MouseState.MousePos = ImVec2(vr_event.data.mouse.x, -vr_event.data.mouse.y + ImGui::GetIO().DisplaySize.y);\n                state.CursorIndexLast = cursor_index;\n            }\n\n            return true;\n        }\n        case vr::VREvent_FocusEnter:\n        {\n            return true;\n        }\n        case vr::VREvent_FocusLeave:\n        {\n            LaserInputState& state = GetLaserInputState(device_index);\n\n            //Pointer left the overlay\n            state.MouseState.MousePos = ImVec2(-FLT_MAX, -FLT_MAX);\n            state.CursorIndexLast = 0;\n\n            //Release mouse buttons if they're still down (not the case in most situations)\n            std::fill(std::begin(state.MouseState.MouseDown), std::end(state.MouseState.MouseDown), false);\n            return true;\n        }\n        case vr::VREvent_MouseButtonDown:\n        case vr::VREvent_MouseButtonUp:\n        {\n            LaserInputState& state = GetLaserInputState(device_index);\n            ImGuiMouseButton button = ImGuiMouseButton_Left;\n\n            switch (vr_event.data.mouse.button)\n            {\n                case vr::VRMouseButton_Left:    button = ImGuiMouseButton_Left;   break;\n                case vr::VRMouseButton_Right:   button = ImGuiMouseButton_Right;  break;\n                case vr::VRMouseButton_Middle:  button = ImGuiMouseButton_Middle; break;\n            }\n\n            state.MouseState.MouseDown[button] = (vr_event.eventType == vr::VREvent_MouseButtonDown);\n            return true;\n        }\n        case vr::VREvent_ScrollDiscrete:\n        case vr::VREvent_ScrollSmooth:\n        {\n            //Unhandled, but still consume them\n            return true;\n        }\n    }\n\n    return (cursor_index != 0);\n}\n\nvoid WindowKeyboard::LaserSetMousePos(vr::TrackedDeviceIndex_t device_index, ImVec2 pos)\n{\n    LaserInputState& state = GetLaserInputState(device_index);\n    state.MouseState.MousePos = pos;\n}\n\nvoid WindowKeyboard::LaserSetMouseButton(vr::TrackedDeviceIndex_t device_index, ImGuiMouseButton button_index, bool is_down)\n{\n    LaserInputState& state = GetLaserInputState(device_index);\n    state.MouseState.MouseDown[button_index] = is_down;\n}\n\n\n//-WindowKeyboardShortcuts\nvoid WindowKeyboardShortcuts::SetActiveWidget(ImGuiID widget_id)\n{\n    //Set active widget ID if none is set, otherwise fade the previous one out first\n    if (m_ActiveWidget == 0)\n    {\n        m_ActiveWidget = widget_id;\n        m_ActiveWidgetPending = 0;\n    }\n    else if (m_ActiveWidget != widget_id)\n    {\n        m_ActiveWidgetPending = widget_id;\n        m_IsFadingOut = true;\n    }\n}\n\nvoid WindowKeyboardShortcuts::SetDefaultPositionDirection(ImGuiDir pos_dir, float y_offset)\n{\n    if (!m_IsFadingOut)\n    {\n        m_PosDirDefault = pos_dir;\n        m_YOffsetDefault = y_offset;\n    }\n}\n\nvoid WindowKeyboardShortcuts::Update(ImGuiID widget_id)\n{\n    if (widget_id != m_ActiveWidget)\n        return;\n\n    ImGuiIO& io = ImGui::GetIO();\n\n    //Release previously held keys by button actions\n    switch (m_ActiveButtonAction)\n    {\n        case btn_act_cut:\n        {\n            io.AddKeyEvent(ImGuiMod_Ctrl, false);\n            io.AddKeyEvent(ImGuiKey_X, false);\n\n            m_ActiveButtonAction = btn_act_none;\n            break;\n        }\n        case btn_act_copy:\n        {\n            io.AddKeyEvent(ImGuiMod_Ctrl, false);\n            io.AddKeyEvent(ImGuiKey_C, false);\n\n            m_ActiveButtonAction = btn_act_none;\n            break;\n        }\n        case btn_act_paste:\n        {\n            io.AddKeyEvent(ImGuiMod_Ctrl, false);\n            io.AddKeyEvent(ImGuiKey_V, false);\n\n            m_ActiveButtonAction = btn_act_none;\n            break;\n        }\n        default: break;\n    }\n\n    const float offset_down = (m_PosDirDefault == ImGuiDir_Down) ? m_YOffsetDefault : 0.0f;\n    const float offset_up   = (m_PosDirDefault == ImGuiDir_Up)   ? m_YOffsetDefault : 0.0f;\n\n    const float pos_y      = ImGui::GetItemRectMin().y - ImGui::GetStyle().ItemSpacing.y + m_YOffsetDefault;\n    const float pos_y_down = ImGui::GetItemRectMax().y + ImGui::GetStyle().ItemInnerSpacing.y + offset_down;\n    const float pos_y_up   = ImGui::GetItemRectMin().y - ImGui::GetStyle().ItemSpacing.y - m_WindowHeight + offset_up;\n\n    //Wait for window height to be known and stable before setting pos or animating fade/pos\n    if ((m_WindowHeight != FLT_MIN) && (m_WindowHeight == m_WindowHeightPrev))\n    {\n        ImGui::SetNextWindowPos(ImVec2(ImGui::GetItemRectMin().x, smoothstep(m_PosAnimationProgress, pos_y_down, pos_y_up) ));\n\n        const float time_step = ImGui::GetIO().DeltaTime * 6.0f;\n\n        m_Alpha += (!m_IsFadingOut) ? time_step : -time_step;\n\n        if (m_Alpha > 1.0f)\n            m_Alpha = 1.0f;\n        else if (m_Alpha < 0.0f)\n            m_Alpha = 0.0f;\n\n        m_PosAnimationProgress += (m_PosDir == ImGuiDir_Up) ? time_step : -time_step;\n\n        if (m_PosAnimationProgress > 1.0f)\n            m_PosAnimationProgress = 1.0f;\n        else if (m_PosAnimationProgress < 0.0f)\n            m_PosAnimationProgress = 0.0f;\n    }\n\n    ImGui::PushStyleVar(ImGuiStyleVar_Alpha, m_Alpha);\n\n    //Get clipping rect of parent window\n    ImGuiWindow* window_parent = ImGui::GetCurrentWindow();\n    ImRect clip_rect = window_parent->ClipRect;\n\n    const ImGuiWindowFlags flags = ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoSavedSettings | \n                                   ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoFocusOnAppearing | ImGuiWindowFlags_NoBringToFrontOnFocus |\n                                   ImGuiWindowFlags_NoBackground;\n\n    ImGui::Begin(\"##VRKeyboardShortcuts\", nullptr, flags);\n\n    //Force this window to be in front. For this to be not a flicker-fest, the parent should have ImGuiWindowFlags_NoBringToFrontOnFocus set (default for FloatingWindow)\n    ImGuiWindow* window = ImGui::GetCurrentWindow();\n    if (!ImGui::IsWindowAbove(window, window_parent))\n    {\n        ImGui::BringWindowToDisplayFront(window);\n    }\n\n    //Transfer scroll input to parent window (which isn't a real parent window but just the one in the stack), so this window doesn't block scrolling\n    ImGui::ScrollBeginStackParentWindow();\n\n    //Use clipping rect of parent window\n    ImGui::PushClipRect(clip_rect.Min, clip_rect.Max, false);\n\n    //Draw background + border manually so it can be clipped properly\n    ImRect window_rect = window->Rect();\n    window->DrawList->AddRectFilled(window_rect.Min, window_rect.Max, ImGui::GetColorU32(ImGuiCol_PopupBg), window->WindowRounding);\n    window->DrawList->AddRect(window_rect.Min, window_rect.Max, ImGui::GetColorU32(ImGuiCol_Border), window->WindowRounding, 0, window->WindowBorderSize);\n\n    //Disable inputs when fading out\n    if (m_IsFadingOut)\n        ImGui::PushItemDisabledNoVisual();\n\n    //Since we don't want to lose input focus, we use a separate widget state for this window\n    ImGui::ActiveWidgetStateStorage prev_widget_state;\n    prev_widget_state.StoreCurrentState();\n\n    m_WindowWidgetState.AdvanceState();\n    m_WindowWidgetState.ApplyState();\n\n\n    //-Window buttons\n    m_IsAnyButtonDown = false;\n\n    if (ImGui::Button(TranslationManager::GetString(tstr_KeyboardShortcutsCut)))\n    {\n        io.AddKeyEvent(ImGuiMod_Ctrl, true);\n        io.AddKeyEvent(ImGuiKey_X, true);\n\n        m_ActiveButtonAction = btn_act_cut;\n    }\n\n    if (ImGui::IsItemActive())\n    {\n        m_IsAnyButtonDown = true;\n    }\n\n    ImGui::SameLine();\n\n    if (ImGui::Button(TranslationManager::GetString(tstr_KeyboardShortcutsCopy)))\n    {\n        io.AddKeyEvent(ImGuiMod_Ctrl, true);\n        io.AddKeyEvent(ImGuiKey_C, true);\n\n        m_ActiveButtonAction = btn_act_copy;\n    }\n\n    if (ImGui::IsItemActive())\n    {\n        m_IsAnyButtonDown = true;\n    }\n\n    ImGui::SameLine();\n\n    //Check if we have clipboard text to disable pasting if we don't\n    const char* clipboard_text = ImGui::GetClipboardText();\n    const bool has_clipboard_text = ((clipboard_text != nullptr) && (clipboard_text[0] != '\\0'));\n\n    if (!has_clipboard_text)\n        ImGui::PushItemDisabled();\n\n    if (ImGui::Button(TranslationManager::GetString(tstr_KeyboardShortcutsPaste)))\n    {\n        io.AddKeyEvent(ImGuiMod_Ctrl, true);\n        io.AddKeyEvent(ImGuiKey_V, true);\n\n        m_ActiveButtonAction = btn_act_paste;\n    }\n\n    if (ImGui::IsItemActive())\n    {\n        m_IsAnyButtonDown = true;\n    }\n\n    if (!has_clipboard_text)\n        ImGui::PopItemDisabled();\n\n    m_IsHovered = ImGui::IsWindowHovered();\n\n    //Restore global widget state\n    m_WindowWidgetState.StoreCurrentState();\n    prev_widget_state.ApplyState();\n\n    if (m_IsFadingOut)\n        ImGui::PopItemDisabledNoVisual();\n\n    //Switch directions if there's no space in the default direction\n    if (m_PosDirDefault == ImGuiDir_Down)\n    {\n        m_PosDir = (pos_y_down + ImGui::GetWindowSize().y > clip_rect.Max.y) ? ImGuiDir_Up : ImGuiDir_Down;\n    }\n    else\n    {\n        //Not using pos_y_up here as it's not valid yet (m_WindowHeight not set) \n        m_PosDir = (pos_y - ImGui::GetWindowSize().y < clip_rect.Min.y) ? ImGuiDir_Down : ImGuiDir_Up;\n    }\n\n    if (m_Alpha == 0.0f)\n    {\n        m_PosAnimationProgress = (m_PosDir == ImGuiDir_Down) ? 0.0f : 1.0f;\n    }\n\n    //Cache window height so it's available on the next frame before beginning the window\n    m_WindowHeightPrev = m_WindowHeight;\n    m_WindowHeight = ImGui::GetWindowSize().y;\n\n    ImGui::End();\n\n    ImGui::PopStyleVar(); //ImGuiStyleVar_Alpha\n\n    //Reset when fade-out is done\n    if ( (m_IsFadingOut) && (m_Alpha == 0.0f) )\n    {\n        m_ActiveWidget = m_ActiveWidgetPending;\n        m_ActiveWidgetPending = 0;\n\n        m_IsFadingOut = false;\n        m_WindowHeight = FLT_MIN;\n        m_PosDir = m_PosDirDefault;\n        m_PosAnimationProgress = (m_PosDirDefault == ImGuiDir_Down) ? 0.0f : 1.0f;\n    }\n}\n\nbool WindowKeyboardShortcuts::IsHovered() const\n{\n    return m_IsHovered;\n}\n\nbool WindowKeyboardShortcuts::IsAnyButtonDown() const\n{\n    return m_IsAnyButtonDown;\n}\n"
  },
  {
    "path": "src/DesktopPlusUI/WindowKeyboard.h",
    "content": "#pragma once\n\n#include \"FloatingWindow.h\"\n#include \"ImGuiExt.h\"\n#include \"openvr.h\"\n\nstruct LaserInputState\n{\n    vr::TrackedDeviceIndex_t DeviceIndex = vr::k_unTrackedDeviceIndexInvalid;\n    uint32_t CursorIndexLast = 0;\n    ImGui::ImGuiMouseState MouseState;\n};\n\nstruct ButtonLaserState\n{\n    vr::TrackedDeviceIndex_t DeviceIndexHeld = vr::k_unTrackedDeviceIndexInvalid;\n    bool IsDown         = false;\n    bool IsHeld         = false;\n    bool IsHovered      = false;\n    bool IsActivated    = false;\n    bool IsDeactivated  = false;\n    bool IsRightClicked = false;\n};\n\nenum KeyboardLayoutSubLayout : unsigned char;\n\nclass WindowKeyboard : public FloatingWindow\n{\n    private:\n        float m_WindowWidth;\n        bool m_IsAutoVisible;\n        bool m_IsHovered;\n        bool m_IsAnyButtonHovered;\n        Matrix4 m_TransformUIOrigin;\n\n        int m_AssignedOverlayIDRoom;            //-1 = None/Global, -2 = UI (only if newly shown for it)\n        int m_AssignedOverlayIDDashboardTab;    //^\n\n        bool m_IsIsoEnterDown;\n        bool m_IsDashboardPointerActiveLast;\n        bool m_UnstickModifiersLater;\n        KeyboardLayoutSubLayout m_SubLayoutOverride;\n        KeyboardLayoutSubLayout m_LastSubLayout;\n        bool m_ManuallyStickingModifiers[8];\n\n        ImGui::ActiveWidgetStateStorage m_KeyboardWidgetState;\n        std::vector<ButtonLaserState> m_ButtonStates;\n        std::vector<LaserInputState> m_LaserInputStates;\n\n        virtual void WindowUpdate();\n\n        virtual void OnWindowPinButtonPressed();\n        virtual void OnWindowCloseButtonPressed();\n        virtual bool IsVirtualWindowItemHovered() const;\n\n        void OnVirtualKeyDown(unsigned char keycode, bool block_modifiers = false);\n        void OnVirtualKeyUp(unsigned char keycode, bool block_modifiers = false);\n        void OnStringKeyDown(const std::string& keystring);\n        void OnStringKeyUp(const std::string& keystring);\n        void HandleUnstickyModifiers(unsigned char source_keycode = 0);\n\n        void SetManualStickyModifierState(unsigned char keycode, bool is_down);\n        static int GetModifierID(unsigned char keycode);\n        int FindSameKeyInNewSubLayout(int key_index, KeyboardLayoutSubLayout sublayout_id_current, KeyboardLayoutSubLayout sublayout_id_new);\n\n        LaserInputState& GetLaserInputState(vr::TrackedDeviceIndex_t device_index);\n        bool ButtonLaser(const char* label, const ImVec2& size_arg, ButtonLaserState& button_state, bool is_label_multiline = false);\n        void ButtonVisual(const char* label, const ImVec2& size_arg, bool is_label_multiline = false);\n\n    public:\n        WindowKeyboard();\n        virtual void UpdateVisibility();\n\n        virtual void Show(bool skip_fade = false);\n        virtual void Hide(bool skip_fade = false);\n        bool SetAutoVisibility(int assigned_overlay_id, bool show);                         //Returns true on success\n        virtual vr::VROverlayHandle_t GetOverlayHandle() const;\n        virtual void RebaseTransform();\n        virtual void ResetTransform(FloatingWindowOverlayStateID state_id);\n\n        void SetAssignedOverlayID(int assigned_id);                                         //Sets for current state\n        void SetAssignedOverlayID(int assigned_id, FloatingWindowOverlayStateID state_id);\n        int GetAssignedOverlayID() const;                                                   //Gets from current state\n        int GetAssignedOverlayID(FloatingWindowOverlayStateID state_id) const;\n\n        bool IsHovered() const;\n        void ResetButtonState();    //Called by VRKeyboard when resetting the keyboard state\n\n        bool HandleOverlayEvent(const vr::VREvent_t& vr_event);\n        void LaserSetMousePos(vr::TrackedDeviceIndex_t device_index, ImVec2 pos);\n        void LaserSetMouseButton(vr::TrackedDeviceIndex_t device_index, ImGuiMouseButton button_index, bool is_down);\n};\n\nclass WindowKeyboardShortcuts\n{\n    private:\n        enum ButtonAction {btn_act_none = -1, btn_act_cut, btn_act_copy, btn_act_paste};\n\n        bool m_IsHovered   = false;\n        bool m_IsFadingOut = false;\n        float m_Alpha      = 0.0f;\n\n        ImGuiID m_ActiveWidget        = 0;\n        ImGuiID m_ActiveWidgetPending = 0;\n\n        ImGui::ActiveWidgetStateStorage m_WindowWidgetState;\n\n        float m_WindowHeight              = FLT_MIN;\n        float m_WindowHeightPrev          = FLT_MIN;\n        ImGuiDir m_PosDir                 = ImGuiDir_Down;\n        ImGuiDir m_PosDirDefault          = ImGuiDir_Down;\n        float m_YOffsetDefault            = 0.0f;\n        float m_PosAnimationProgress      = 0.0f;\n\n        ButtonAction m_ActiveButtonAction = btn_act_none;\n        bool m_IsAnyButtonDown            = false;\n\n    public:\n        void SetActiveWidget(ImGuiID widget_id);\n        void SetDefaultPositionDirection(ImGuiDir pos_dir, float y_offset = 0.0f);\n        void Update(ImGuiID window_id);\n\n        bool IsHovered() const;\n        bool IsAnyButtonDown() const;\n};"
  },
  {
    "path": "src/DesktopPlusUI/WindowKeyboardEditor.cpp",
    "content": "#include \"WindowKeyboardEditor.h\"\n\n#include \"UIManager.h\"\n\nvoid KeyboardEditor::UpdateWindowKeyList()\n{\n    ImGuiIO& io = ImGui::GetIO();\n    ImGuiStyle& style = ImGui::GetStyle();\n    VRKeyboard& vr_keyboard = UIManager::Get()->GetVRKeyboard();\n\n    ImGui::SetNextWindowSize({io.DisplaySize.x / 4.0f, m_TopWindowHeight});\n    ImGui::SetNextWindowPos({0.0f, 0.0f});\n\n    ImGui::Begin(TranslationManager::GetString(tstr_KeyboardEditorKeyListTitle), nullptr, ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse);\n\n    ImGui::SetCursorPosY(ImGui::GetCursorPosY() - (float)(int)(style.WindowPadding.y / 2.0f));   //Bring tab bar closer to window title\n    if (ImGui::BeginTabBar(\"TabBarSublayouts\"))\n    {\n        for (int i_sublayout = kbdlayout_sub_base; i_sublayout < kbdlayout_sub_MAX; ++i_sublayout)\n        {\n            KeyboardLayoutSubLayout current_sublayout = (KeyboardLayoutSubLayout)i_sublayout;\n\n            if ((current_sublayout == kbdlayout_sub_altgr) && (!vr_keyboard.GetLayoutMetadata().HasAltGr))\n                continue;\n\n            if (ImGui::BeginTabItem( TranslationManager::GetString((TRMGRStrID)(tstr_KeyboardEditorSublayoutBase + i_sublayout) ), nullptr))\n            {\n                auto& sublayout_keys = vr_keyboard.GetLayout(current_sublayout);\n\n                //Tab context menu\n                if (ImGui::BeginPopupContextItem())\n                {\n                    if (ImGui::BeginMenu(TranslationManager::GetString(tstr_KeyboardEditorKeyListTabContextReplace)))\n                    {\n                        for (int i_sublayout_menu = kbdlayout_sub_base; i_sublayout_menu < kbdlayout_sub_MAX; ++i_sublayout_menu)\n                        {\n                            if ((i_sublayout_menu == kbdlayout_sub_altgr) && (!vr_keyboard.GetLayoutMetadata().HasAltGr))\n                                continue;\n\n                            if ((i_sublayout_menu != i_sublayout) && (ImGui::MenuItem( TranslationManager::GetString((TRMGRStrID)(tstr_KeyboardEditorSublayoutBase + i_sublayout_menu)) )))\n                            {\n                                HistoryPush();\n                                sublayout_keys = vr_keyboard.GetLayout((KeyboardLayoutSubLayout)i_sublayout_menu);\n                                UIManager::Get()->RepeatFrame();\n                            }\n                        }\n\n                        ImGui::EndMenu();\n                    }\n\n                    if (ImGui::MenuItem(TranslationManager::GetString(tstr_KeyboardEditorKeyListTabContextClear)))\n                    {\n                        HistoryPush();\n                        sublayout_keys.clear();\n                        UIManager::Get()->RepeatFrame();\n                    }\n\n                    ImGui::EndPopup();\n                }\n\n                //Make the child expand past window padding\n                ImGui::SetCursorPos({ImGui::GetCursorPosX() - style.WindowPadding.x, ImGui::GetCursorPosY() - style.WindowPadding.y - style.TabBarBorderSize});\n\n                ImGui::BeginChild(\"TabContents\", {ImGui::GetContentRegionAvail().x + style.WindowPadding.x, -ImGui::GetFrameHeightWithSpacing()}, ImGuiChildFlags_Borders);\n\n                if (current_sublayout != m_SelectedSublayout)\n                {\n                    m_SelectedKeyID = FindKeyWithClosestPosInNewSubLayout(m_SelectedKeyID, m_SelectedSublayout, current_sublayout);\n                    m_SelectedSublayout = current_sublayout;\n\n                    m_HasChangedSelectedKey = true;\n                }\n\n                int row_id = -1;\n                int row_id_show = -1;\n                int i = 0;\n\n                //Drag-reordering needs stable IDs to work\n                static std::vector<int> list_unique_ids;\n                static std::vector<int> list_unique_row_ids;\n\n                while (list_unique_ids.size() < sublayout_keys.size())\n                {\n                    list_unique_ids.push_back((int)list_unique_ids.size());\n                }\n\n                static std::vector<std::string> str_row_id;\n\n                //Find which row to open in order to focus the newly selected key\n                if (m_HasChangedSelectedKey)\n                {\n                    if (m_SelectedRowID != -1)\n                    {\n                        row_id_show = m_SelectedRowID;\n                    }\n                    else\n                    {\n                        row_id_show = 0;\n                        for (const auto& key : vr_keyboard.GetLayout(current_sublayout))\n                        {\n                            if (i == m_SelectedKeyID)\n                            {\n                                break;\n                            }\n                            else if (key.IsRowEnd)\n                            {\n                                ++row_id_show;\n                            }\n\n                            ++i;\n                        }\n                    }\n\n                    i = 0;\n                }\n\n                bool pushed_row = false;\n                bool is_row_node_open = false;\n\n                ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, {style.ItemSpacing.x, 0.0f});\n\n                for (auto& key : vr_keyboard.GetLayout(current_sublayout))\n                {\n                    //Row entry\n                    if (!pushed_row)\n                    {\n                        row_id++;\n\n                        //Expand unique row IDs here since we don't know how many there are beforehand\n                        while (list_unique_row_ids.size() <= row_id)\n                        {\n                            list_unique_row_ids.push_back((int)list_unique_row_ids.size());\n                        }\n\n                        //Expand row ID translated strings if we need to\n                        while (str_row_id.size() <= row_id)\n                        {\n                            std::string row_str = TranslationManager::GetString(tstr_KeyboardEditorKeyListRow);\n                            StringReplaceAll(row_str, \"%ID%\", std::to_string(str_row_id.size() + 1));\n                            str_row_id.push_back(row_str);\n                        }\n\n                        //Set focus if row itself was just selected\n                        if ((m_HasChangedSelectedKey) && (row_id == m_SelectedRowID))\n                        {\n                            ImGui::SetKeyboardFocusHere();\n                            UIManager::Get()->RepeatFrame();\n                        }\n                        else if (row_id == row_id_show)     //Open row when this is the row that has the key we want to focus\n                        {\n                            ImGui::SetNextItemOpen(true);\n                        }\n\n                        ImGui::PushID(list_unique_row_ids[row_id]);\n\n                        ImGuiTreeNodeFlags flags = ImGuiTreeNodeFlags_SpanAvailWidth | ImGuiTreeNodeFlags_OpenOnDoubleClick | ImGuiTreeNodeFlags_OpenOnArrow;\n                        flags |= (row_id == m_SelectedRowID) ? ImGuiTreeNodeFlags_Selected : 0;\n\n                        is_row_node_open = ImGui::TreeNodeEx(\"##Row\", flags, str_row_id[row_id].c_str());\n\n                        if ((ImGui::IsItemClicked()) && (!ImGui::IsItemToggledOpen()))\n                        {\n                            m_SelectedRowID = row_id;\n                            m_SelectedKeyID = -1;\n                            m_HasChangedSelectedKey = true;\n                        }\n\n                        //Drag reordering\n                        static int hovered_row_id = -1;\n                        if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenBlockedByActiveItem))\n                        {\n                            hovered_row_id = row_id;\n                        }\n\n                        if ((ImGui::IsItemActive()) && (!ImGui::IsItemHovered()))\n                        {\n                            int row_id_swap = row_id + ((ImGui::GetMouseDragDelta(ImGuiMouseButton_Left).y < 0.0f) ? -1 : 1);\n                            if ((hovered_row_id != row_id) && (row_id_swap >= 0) && (row_id_swap < sublayout_keys.size()))\n                            {\n                                HistoryPush();\n                                m_HistoryHasPendingEdit = true;\n\n                                auto pair_row      = GetKeyRowRange(current_sublayout, row_id);\n                                auto pair_row_swap = GetKeyRowRange(current_sublayout, row_id_swap);\n\n                                //Swap the row ranges if needed so the second row is always after the first\n                                if (pair_row.first > pair_row_swap.first)\n                                {\n                                    pair_row.swap(pair_row_swap);\n                                }\n\n                                //Swap row contents by copying the first row to a temporary, erasing the first row and pasting the first row past where the second row now is\n                                std::vector<KeyboardLayoutKey> row_keys(sublayout_keys.begin() + pair_row.first, sublayout_keys.begin() + pair_row.second);\n                                sublayout_keys.erase(sublayout_keys.begin()  + pair_row.first, sublayout_keys.begin() + pair_row.second);\n                                sublayout_keys.insert(sublayout_keys.begin() + pair_row.first + (pair_row_swap.second - pair_row_swap.first), row_keys.begin(), row_keys.end());\n\n                                std::iter_swap(list_unique_row_ids.begin() + row_id, list_unique_row_ids.begin() + row_id_swap);\n\n                                m_SelectedRowID = row_id_swap;\n\n                                ImGui::ResetMouseDragDelta(ImGuiMouseButton_Left);\n                                UIManager::Get()->RepeatFrame();\n                            }\n                        }\n\n                        pushed_row = true;\n                    }\n\n                    //Key entry\n                    if (is_row_node_open)\n                    {\n                        ImGui::PushID(list_unique_ids[i]);\n\n                        ImGuiTreeNodeFlags flags = ImGuiTreeNodeFlags_Leaf | ImGuiTreeNodeFlags_NoTreePushOnOpen | ImGuiTreeNodeFlags_SpanAvailWidth;\n                        flags |= (i == m_SelectedKeyID) ? ImGuiTreeNodeFlags_Selected : 0;\n\n                        if ((m_HasChangedSelectedKey) && (i == m_SelectedKeyID))\n                        {\n                            ImGui::SetKeyboardFocusHere();\n                            UIManager::Get()->RepeatFrame();\n                        }\n\n                        if (!m_PreviewCluster[key.KeyCluster])\n                            ImGui::PushStyleVar(ImGuiStyleVar_Alpha, 0.5f);\n\n                        if (key.KeyType == kbdlayout_key_blank_space)\n                        {\n                            ImGui::TreeNodeEx(TranslationManager::GetString(tstr_KeyboardEditorKeyListSpacing), flags);\n                        }\n                        else\n                        {\n                            ImGui::TreeNodeEx(key.Label.c_str(), flags);\n                        }\n\n                        if (!m_PreviewCluster[key.KeyCluster])\n                            ImGui::PopStyleVar();\n\n                        if ((ImGui::IsItemClicked()) && (!ImGui::IsItemToggledOpen()))\n                        {\n                            m_SelectedRowID = -1;\n                            m_SelectedKeyID = i;\n                            m_HasChangedSelectedKey = true;\n                        }\n\n                        //Drag reordering\n                        static int hovered_id = -1;\n                        if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenBlockedByActiveItem))\n                        {\n                            hovered_id = i;\n                        }\n\n                        if ((ImGui::IsItemActive()) && (!ImGui::IsItemHovered()))\n                        {\n                            int index_swap = i + ((ImGui::GetMouseDragDelta(ImGuiMouseButton_Left).y < 0.0f) ? -1 : 1);\n                            if ((hovered_id != i) && (index_swap >= 0) && (index_swap < sublayout_keys.size()))\n                            {\n                                HistoryPush();\n                                m_HistoryHasPendingEdit = true;\n\n                                KeyboardLayoutKey& key_swap = sublayout_keys[index_swap];\n                                std::swap(key.IsRowEnd, key_swap.IsRowEnd);\n\n                                std::iter_swap(sublayout_keys.begin()  + i, sublayout_keys.begin()  + index_swap);\n                                std::iter_swap(list_unique_ids.begin() + i, list_unique_ids.begin() + index_swap);\n\n                                m_SelectedKeyID = index_swap;\n\n                                ImGui::ResetMouseDragDelta(ImGuiMouseButton_Left);\n                            }\n                        }\n\n                        ImGui::PopID();\n                    }\n\n                    if (key.IsRowEnd)\n                    {\n                        ImGui::PopID();\n                        if (is_row_node_open)\n                        {\n                            ImGui::TreePop();\n                        }\n\n                        pushed_row = false;\n                    }\n\n                    ++i;\n                }\n\n                if (pushed_row)\n                {\n                    ImGui::TreePop();\n                    ImGui::PopID();\n                }\n\n                //Clamp selected row ID to available range (with invalid 0 being corrected to -1)\n                m_SelectedRowID = clamp(m_SelectedRowID, -1, row_id);\n\n                ImGui::PopStyleVar();\n\n                ImGui::EndChild();\n                ImGui::EndTabItem();\n            }\n        }\n\n        ImGui::EndTabBar();\n    }\n\n    const bool has_no_undo = m_HistoryUndo.empty();\n    const bool has_no_redo = m_HistoryRedo.empty();\n\n    //ImGui::IsKeyChordPressed() seems neat here but doesn't appear to support key repeat...\n    const bool undo_shortcut_pressed = ((io.KeyMods == ImGuiMod_Ctrl) && (ImGui::IsKeyPressed(ImGuiKey_Z)));\n    const bool redo_shortcut_pressed = ((io.KeyMods == ImGuiMod_Ctrl) && (ImGui::IsKeyPressed(ImGuiKey_Y)));\n\n    if (has_no_undo)\n        ImGui::PushItemDisabled();\n\n    if ( (ImGui::Button(TranslationManager::GetString(tstr_DialogUndo))) || ((!has_no_undo) && (!ImGui::IsAnyInputTextActive()) && (undo_shortcut_pressed)) )\n    {\n        HistoryUndo();\n        UIManager::Get()->RepeatFrame();\n    }\n\n    if (has_no_undo)\n        ImGui::PopItemDisabled();\n\n    ImGui::SameLine(0.0f, style.ItemInnerSpacing.x);\n\n    if (has_no_redo)\n        ImGui::PushItemDisabled();\n\n    if ( (ImGui::Button(TranslationManager::GetString(tstr_DialogRedo))) || ((!has_no_redo) && (!ImGui::IsAnyInputTextActive()) && (redo_shortcut_pressed)) )\n    {\n        HistoryRedo();\n        UIManager::Get()->RepeatFrame();\n    }\n\n    if (has_no_redo)\n        ImGui::PopItemDisabled();\n\n    ImGui::SameLine();\n\n    static float bottom_buttons_width = 0.0f;\n    ImGui::SetCursorPosX(ImGui::GetCursorPosX() + ImGui::GetContentRegionAvail().x - bottom_buttons_width);\n    ImGui::BeginGroup();\n\n    if (ImGui::Button(TranslationManager::GetString(tstr_KeyboardEditorKeyListKeyAdd)))\n    {\n        HistoryPush();\n\n        auto& sublayout_keys = vr_keyboard.GetLayout(m_SelectedSublayout);\n        KeyboardLayoutKey new_key;\n        new_key.KeyType = kbdlayout_key_virtual_key;\n\n        auto it_insert = sublayout_keys.end();\n\n        if (m_SelectedRowID != -1)\n        {\n            int row_id = 0;\n            for (auto it = sublayout_keys.begin(); it != sublayout_keys.end(); ++it)\n            {\n                if (row_id == m_SelectedRowID + 1)\n                {\n                    it_insert = it;\n                    break;\n                }\n                else if (it->IsRowEnd)\n                {\n                    ++row_id;\n                }\n            }\n\n            //Set row end for previous key to make a new row\n            if (it_insert != sublayout_keys.begin())\n            {\n                (it_insert - 1)->IsRowEnd = true;\n            }\n\n            new_key.IsRowEnd = true;\n        }\n        else if (m_SelectedKeyID != -1)\n        {\n            it_insert = sublayout_keys.begin() + m_SelectedKeyID;\n\n            //Take over row end if selected key is\n            if (it_insert->IsRowEnd)\n            {\n                it_insert->IsRowEnd = false;\n                new_key.IsRowEnd = true;\n            }\n\n            ++it_insert;    //Insert after selected key\n        }\n        else\n        {\n            new_key.IsRowEnd = true;\n        }\n\n        new_key.Label = \"#\" + std::to_string(sublayout_keys.size());\n\n        sublayout_keys.insert(it_insert, new_key);\n        UIManager::Get()->RepeatFrame();\n    }\n\n    ImGui::SameLine(0.0f, style.ItemInnerSpacing.x);\n\n    const bool has_invalid_selection = ((m_SelectedKeyID == -1) && (m_SelectedRowID == -1));\n\n    if (has_invalid_selection)\n        ImGui::PushItemDisabled();\n\n    if (ImGui::Button(TranslationManager::GetString(tstr_KeyboardEditorKeyListKeyDuplicate)))\n    {        \n        auto& sublayout_keys = vr_keyboard.GetLayout(m_SelectedSublayout);\n\n        if (m_SelectedRowID != -1)\n        {\n            HistoryPush();\n\n            auto pair_row_key_id = GetKeyRowRange(m_SelectedSublayout, m_SelectedRowID);\n            sublayout_keys.insert(sublayout_keys.begin() + pair_row_key_id.second, sublayout_keys.begin() + pair_row_key_id.first, sublayout_keys.begin() + pair_row_key_id.second);\n        }\n        else if (m_SelectedKeyID != -1)\n        {\n            HistoryPush();\n\n            auto it_duplicate = sublayout_keys.begin() + m_SelectedKeyID;\n\n            KeyboardLayoutKey new_key = *it_duplicate;\n\n            //Take over row end if selected key is\n            if (it_duplicate->IsRowEnd)\n            {\n                it_duplicate->IsRowEnd = false;\n                new_key.IsRowEnd = true;\n            }\n\n            sublayout_keys.insert(it_duplicate + 1, new_key);\n        }\n\n        UIManager::Get()->RepeatFrame();\n    }\n\n    ImGui::SameLine(0.0f, style.ItemInnerSpacing.x);\n\n    if (ImGui::Button(TranslationManager::GetString(tstr_KeyboardEditorKeyListKeyRemove)))\n    {\n        auto& sublayout_keys = vr_keyboard.GetLayout(m_SelectedSublayout);\n\n        if (m_SelectedRowID != -1)\n        {\n            HistoryPush();\n\n            auto pair_row_key_id = GetKeyRowRange(m_SelectedSublayout, m_SelectedRowID);\n            sublayout_keys.erase(sublayout_keys.begin() + pair_row_key_id.first, sublayout_keys.begin() + pair_row_key_id.second);\n        }\n        else if (m_SelectedKeyID != -1)\n        {\n            HistoryPush();\n\n            auto it_erase = sublayout_keys.begin() + m_SelectedKeyID;\n\n            //Set row end for previous key if selected key has it\n            if ((it_erase->IsRowEnd) && (it_erase != sublayout_keys.begin()))\n            {\n                (it_erase - 1)->IsRowEnd = true;\n            }\n\n            sublayout_keys.erase(it_erase);\n        }\n\n        m_HasChangedSelectedKey = true;\n    }\n\n    if (has_invalid_selection)\n        ImGui::PopItemDisabled();\n\n    ImGui::EndGroup();\n    bottom_buttons_width = ImGui::GetItemRectSize().x;\n\n    ImGui::End();\n}\n\nvoid KeyboardEditor::UpdateWindowKeyProperties()\n{\n    ImGuiIO& io = ImGui::GetIO();\n    ImGuiStyle& style = ImGui::GetStyle();\n    VRKeyboard& vr_keyboard = UIManager::Get()->GetVRKeyboard();\n\n    //This adds border size to overlap with neighboring window borders instead of making it thicker\n    ImGui::SetNextWindowSize({io.DisplaySize.x / 2.0f + (style.WindowBorderSize + style.WindowBorderSize), m_TopWindowHeight});\n    ImGui::SetNextWindowPos({io.DisplaySize.x / 2.0f, 0.0f}, ImGuiCond_Always, {0.5f, 0.0f});\n\n    ImGui::Begin(TranslationManager::GetString(tstr_KeyboardEditorKeyPropertiesTitle), nullptr, ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse);\n\n    auto& sublayout_keys = vr_keyboard.GetLayout(m_SelectedSublayout);\n    m_SelectedKeyID = clamp(m_SelectedKeyID, -1, (int)sublayout_keys.size() - 1);\n\n    if (m_SelectedKeyID == -1)\n    {\n        static ImVec2 no_selection_text_size;\n        ImGui::SetCursorPosX(ImGui::GetCursorPosX() + ImGui::GetContentRegionAvail().x / 2.0f - (no_selection_text_size.x / 2.0f));\n        ImGui::SetCursorPosY(ImGui::GetCursorPosY() + ImGui::GetContentRegionAvail().y / 2.0f - (no_selection_text_size.y / 2.0f));\n\n        ImGui::TextUnformatted(TranslationManager::GetString(tstr_KeyboardEditorKeyPropertiesNoSelection));\n        no_selection_text_size = ImGui::GetItemRectSize();\n\n        ImGui::End();\n        return;\n    }\n\n    KeyboardLayoutKey& key = sublayout_keys[m_SelectedKeyID];\n    static char buffer_key_label[128]   = \"\";\n    static char buffer_key_string[1024] = \"\";\n\n    if (m_HasChangedSelectedKey)\n    {\n        size_t copied_length = key.Label.copy(buffer_key_label, IM_ARRAYSIZE(buffer_key_label) - 1);\n        buffer_key_label[copied_length] = '\\0';\n        copied_length = key.KeyString.copy(buffer_key_string, IM_ARRAYSIZE(buffer_key_string) - 1);\n        buffer_key_string[copied_length] = '\\0';\n    }\n\n    ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, m_WindowRounding);\n\n    const float item_spacing_x = style.ItemSpacing.x;\n    ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, {0.0f, style.ItemSpacing.y});    //Avoid horizontal padding from the column\n\n    ImGui::Columns(2, \"ColumnKeyProps\", false);\n    ImGui::SetColumnWidth(0, (float)(int)(io.DisplaySize.x / 6.0f));\n\n    ImGui::AlignTextToFramePadding();\n    ImGui::TextUnformatted(TranslationManager::GetString(tstr_KeyboardEditorKeyPropertiesType));\n\n    if (key.KeyType == kbdlayout_key_virtual_key_iso_enter)\n    {\n        ImGui::SameLine(0.0f, style.ItemInnerSpacing.x);\n        ImGui::FixedHelpMarker(TranslationManager::GetString(tstr_KeyboardEditorKeyPropertiesTypeVirtualKeyIsoEnterTip));\n    }\n    else if (key.KeyType == kbdlayout_key_string)\n    {\n        ImGui::SameLine(0.0f, style.ItemInnerSpacing.x);\n        ImGui::FixedHelpMarker(TranslationManager::GetString(tstr_KeyboardEditorKeyPropertiesTypeStringTip));\n    }\n    ImGui::NextColumn();\n\n    key.KeyType = (KeyboardLayoutKeyType)clamp((int)key.KeyType, 0, (tstr_KeyboardEditorKeyPropertiesTypeAction - tstr_KeyboardEditorKeyPropertiesTypeBlank));\n    int key_type_temp = key.KeyType;\n\n    ImGui::SetNextItemWidth(-1.0f);\n    if (FloatingWindow::TranslatedComboAnimated(\"##ComboType\", key_type_temp, tstr_KeyboardEditorKeyPropertiesTypeBlank, tstr_KeyboardEditorKeyPropertiesTypeAction))\n    {\n        if (key_type_temp != key.KeyType)\n        {\n            HistoryPush();\n            key.KeyType = (KeyboardLayoutKeyType)key_type_temp;\n        }\n    }\n    ImGui::NextColumn();\n\n    ImGui::AlignTextToFramePadding();\n    ImGui::TextUnformatted(TranslationManager::GetString(tstr_KeyboardEditorKeyPropertiesSize));\n    ImGui::NextColumn();\n\n    const float input_width = ImGui::GetFontSize() * 5.5f;\n    float input_size_w = key.Width  * 100.0f;\n    float input_size_h = key.Height * 100.0f;\n\n    ImGui::SetNextItemWidth(input_width);\n    if (ImGui::InputFloat(\"##InputSizeW\", &input_size_w, 5.0f, 10.0f, \"%.0f%%\"))\n    {\n        HistoryPush();\n        m_HistoryHasPendingEdit = true;\n\n        key.Width = std::max(0.10f, roundf(input_size_w) / 100.0f);\n    }\n\n    ImGui::SameLine(0.0f, item_spacing_x);\n\n    ImGui::TextUnformatted(\"x\");\n    ImGui::SameLine(0.0f, item_spacing_x);\n\n    if (key.KeyType == kbdlayout_key_virtual_key_iso_enter)\n        ImGui::PushItemDisabled();\n\n    ImGui::SetNextItemWidth(input_width);\n    if (ImGui::InputFloat(\"##InputSizeH\", &input_size_h, 5.0f, 10.0f, \"%.0f%%\"))\n    {\n        HistoryPush();\n        m_HistoryHasPendingEdit = true;\n\n        key.Height = std::max(0.10f, roundf(input_size_h) / 100.0f);\n    }\n\n    if (key.KeyType == kbdlayout_key_virtual_key_iso_enter)\n        ImGui::PopItemDisabled();\n\n    ImGui::NextColumn();\n\n    if (key.KeyType != kbdlayout_key_blank_space)\n    {\n        ImGui::AlignTextToFramePadding();\n        ImGui::TextUnformatted(TranslationManager::GetString(tstr_KeyboardEditorKeyPropertiesLabel));\n        ImGui::NextColumn();\n\n        ImVec2 multiline_input_size(-1, (ImGui::GetTextLineHeight() * 2.0f) + (style.FramePadding.y * 2.0f));\n        if (ImGui::InputTextMultiline(\"##InputLabel\", buffer_key_label, IM_ARRAYSIZE(buffer_key_label), multiline_input_size))\n        {\n            HistoryPush();\n            m_HistoryHasPendingEdit = true;\n\n            UIManager::Get()->AddFontBuilderStringIfAnyUnmappedCharacters(buffer_key_label);\n            key.Label = buffer_key_label;\n        }\n        ImGui::NextColumn();\n\n        ImGui::AlignTextToFramePadding();\n\n        switch (key.KeyType)\n        {\n            case kbdlayout_key_virtual_key:\n            case kbdlayout_key_virtual_key_toggle:\n            case kbdlayout_key_virtual_key_iso_enter:\n            {\n                ImGui::TextUnformatted(TranslationManager::GetString(tstr_KeyboardEditorKeyPropertiesKeyCode));\n                ImGui::NextColumn();\n\n                if (ImGui::Button((key.KeyCode == 0) ? TranslationManager::GetString(tstr_DialogKeyCodePickerKeyCodeNone) : GetStringForKeyCode(key.KeyCode)))\n                {\n                    ImGui::OpenPopup(TranslationManager::GetString(tstr_DialogKeyCodePickerHeader));\n                }\n                break;\n            }\n            case kbdlayout_key_string:\n            {\n                ImGui::TextUnformatted(TranslationManager::GetString(tstr_KeyboardEditorKeyPropertiesString));\n                ImGui::NextColumn();\n\n                ImVec2 multiline_input_size(-1, (ImGui::GetTextLineHeight() * 3.0f) + (style.FramePadding.y * 2.0f));\n                if (ImGui::InputTextMultiline(\"##InputKeyString\", buffer_key_string, IM_ARRAYSIZE(buffer_key_string), multiline_input_size))\n                {\n                    UIManager::Get()->AddFontBuilderStringIfAnyUnmappedCharacters(buffer_key_string);\n                    key.KeyString = buffer_key_string;\n                }\n                break;\n            }\n            case kbdlayout_key_sublayout_toggle:\n            {\n                ImGui::TextUnformatted(TranslationManager::GetString(tstr_KeyboardEditorKeyPropertiesSublayout));\n                ImGui::NextColumn();\n\n                key.KeySubLayoutToggle = (KeyboardLayoutSubLayout)clamp((int)key.KeySubLayoutToggle, 0, (tstr_KeyboardEditorSublayoutAux - tstr_KeyboardEditorSublayoutBase));\n                int key_sublayout_toggle_temp = key.KeySubLayoutToggle;\n\n                ImGui::SetNextItemWidth(-1.0f);\n                if (FloatingWindow::TranslatedComboAnimated(\"##ComboSublayout\", key_sublayout_toggle_temp, tstr_KeyboardEditorSublayoutBase, tstr_KeyboardEditorSublayoutAux))\n                {\n                    if (key_sublayout_toggle_temp != key.KeySubLayoutToggle)\n                    {\n                        HistoryPush();\n                        key.KeySubLayoutToggle = (KeyboardLayoutSubLayout)key_sublayout_toggle_temp;\n                    }\n                }\n                break;\n            }\n            case kbdlayout_key_action:\n            {\n                ImGui::TextUnformatted(TranslationManager::GetString(tstr_KeyboardEditorKeyPropertiesAction));\n                ImGui::NextColumn();\n\n                if (ImGui::Button(ConfigManager::Get().GetActionManager().GetTranslatedName(key.KeyActionUID)))\n                {\n                    ImGui::OpenPopup(TranslationManager::GetString(tstr_DialogActionPickerHeader));\n                }\n                break;\n            }\n            default: break;\n        }\n\n        ImGui::NextColumn();\n    }\n\n    ImGui::AlignTextToFramePadding();\n    ImGui::TextUnformatted(TranslationManager::GetString(tstr_KeyboardEditorKeyPropertiesCluster));\n    ImGui::SameLine(0.0f, style.ItemInnerSpacing.x);\n    ImGui::FixedHelpMarker(TranslationManager::GetString(tstr_KeyboardEditorKeyPropertiesClusterTip));\n\n    ImGui::NextColumn();\n\n    key.KeyCluster = (KeyboardLayoutCluster)clamp((int)key.KeyCluster, 0, (tstr_SettingsKeyboardKeyClusterExtra - tstr_SettingsKeyboardKeyClusterBase));\n    int key_cluster_temp = key.KeyCluster;\n\n    ImGui::SetNextItemWidth(-1.0f);\n    if (FloatingWindow::TranslatedComboAnimated(\"##ComboCluster\", key_cluster_temp, tstr_SettingsKeyboardKeyClusterBase, tstr_SettingsKeyboardKeyClusterExtra))\n    {\n        if (key_cluster_temp != key.KeyCluster)\n        {\n            HistoryPush();\n            key.KeyCluster = (KeyboardLayoutCluster)key_cluster_temp;\n        }\n    }\n    ImGui::NextColumn();\n\n    ImGui::Columns(1);\n    ImGui::PopStyleVar();   //ImGuiStyleVar_ItemSpacing\n\n    if (key.KeyType == kbdlayout_key_virtual_key)\n    {\n        if (ImGui::Checkbox(TranslationManager::GetString(tstr_KeyboardEditorKeyPropertiesBlockModifiers), &key.BlockModifiers))\n        {\n            //Switch around the values temporarily to allow the current state to be pushed just in time\n            key.BlockModifiers = !key.BlockModifiers;\n            HistoryPush();\n            key.BlockModifiers = !key.BlockModifiers;\n        }\n        ImGui::SameLine(0.0f, style.ItemInnerSpacing.x);\n        ImGui::FixedHelpMarker(TranslationManager::GetString(tstr_KeyboardEditorKeyPropertiesBlockModifiersTip));\n\n        ImGui::SameLine();\n    }\n\n    if (key.KeyType != kbdlayout_key_blank_space)\n    {\n        if (ImGui::Checkbox(TranslationManager::GetString(tstr_KeyboardEditorKeyPropertiesNoRepeat), &key.NoRepeat))\n        {\n            key.NoRepeat = !key.NoRepeat;\n            HistoryPush();\n            key.NoRepeat = !key.NoRepeat;\n        }\n        ImGui::SameLine(0.0f, style.ItemInnerSpacing.x);\n        ImGui::FixedHelpMarker(TranslationManager::GetString(tstr_KeyboardEditorKeyPropertiesNoRepeatTip));\n    }\n\n\n    //-Key Code Picker popup\n    ImGui::SetNextWindowSize({io.DisplaySize.x / 3.0f, io.DisplaySize.y * 0.8f});\n    ImGui::SetNextWindowPos( {io.DisplaySize.x / 2.0f, io.DisplaySize.y / 2.0f}, ImGuiCond_Always, {0.5f, 0.5f});\n    if (ImGui::BeginPopupModal(TranslationManager::GetString(tstr_DialogKeyCodePickerHeader), nullptr, ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize))\n    {\n        static ImGuiTextFilter filter;\n        static int list_id = 0;\n        static bool scroll_to_selection = false;\n\n        if (ImGui::IsWindowAppearing())\n        {\n            scroll_to_selection = true;\n\n            for (int i = 0; i < 256; i++)\n            {\n                //Not the smartest, but most straight forward way\n                if (GetKeyCodeForListID(i) == key.KeyCode)\n                {\n                    list_id = i;\n\n                    //Clear filter if it wouldn't show the current selection\n                    if (!filter.PassFilter( (key.KeyCode == 0) ? TranslationManager::GetString(tstr_DialogKeyCodePickerKeyCodeNone) : GetStringForKeyCode(key.KeyCode) ))\n                    {\n                        filter.Clear();\n                    }\n\n                    break;\n                }\n            }\n        }\n\n        CloseCurrentModalPopupFromInput();\n\n        ImGui::SetNextItemWidth(-1.0f);\n        if (ImGui::InputTextWithHint(\"##FilterList\", TranslationManager::GetString(tstr_DialogKeyCodePickerKeyCodeHint), filter.InputBuf, IM_ARRAYSIZE(filter.InputBuf)))\n        {\n            UIManager::Get()->AddFontBuilderStringIfAnyUnmappedCharacters(filter.InputBuf);\n\n            filter.Build();\n        }\n\n        ImGui::BeginChild(\"KeyCodePickerList\", ImVec2(0.0f, -ImGui::GetFrameHeightWithSpacing()), ImGuiChildFlags_Borders);\n\n        unsigned char list_keycode;\n        const char* list_keycode_str = nullptr;\n        for (int i = 0; i < 256; i++)\n        {\n            list_keycode = GetKeyCodeForListID(i);\n            list_keycode_str = (list_keycode == 0) ? TranslationManager::GetString(tstr_DialogKeyCodePickerKeyCodeNone) : GetStringForKeyCode(list_keycode);\n            if (filter.PassFilter(list_keycode_str))\n            {\n                if (ImGui::Selectable(list_keycode_str, (i == list_id)))\n                {\n                    list_id = i;\n\n                    if (key.KeyCode != list_keycode)\n                    {\n                        HistoryPush();\n                        key.KeyCode = list_keycode;\n                    }\n\n                    ImGui::CloseCurrentPopup();\n                    UIManager::Get()->RepeatFrame();\n                }\n\n                if ( (scroll_to_selection) && (i == list_id) )\n                {\n                    ImGui::SetScrollHereY();\n\n                    if (ImGui::IsItemVisible())\n                    {\n                        scroll_to_selection = false;\n                    }\n                }\n            }\n        }\n\n        ImGui::EndChild();\n\n        static float list_buttons_width = 0.0f;\n        ImGui::SetCursorPosX(ImGui::GetCursorPosX() + ImGui::GetContentRegionAvail().x - list_buttons_width);\n\n        if (ImGui::Button(TranslationManager::GetString(tstr_DialogKeyCodePickerFromInput)))\n        {\n            ImGui::OpenPopup(\"PopupBindKey\");\n        }\n\n        list_buttons_width = ImGui::GetItemRectSize().x;\n\n        ImGui::SetNextWindowPos({ImGui::GetIO().DisplaySize.x * 0.5f, ImGui::GetIO().DisplaySize.y * 0.5f}, ImGuiCond_Always, ImVec2(0.5f, 0.5f));\n        if (ImGui::BeginPopupModal(\"PopupBindKey\", nullptr, ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoNavInputs))\n        {\n            bool close_picker = false;\n\n            ImGui::Text(TranslationManager::GetString(tstr_DialogKeyCodePickerFromInputPopup));\n\n            ImGuiIO& io = ImGui::GetIO();\n\n            //We can no longer use ImGui's keyboard state to query all possible keyboard keys, so we do it manually via GetAsyncKeyState()\n            //To avoid issues with keys that are already down to begin with, we store the state in the moment of the popup appearing and only act on changes to that\n            static bool keyboard_state_initial[255] = {0};\n            static bool wait_for_key_release = false; \n\n            if (ImGui::IsWindowAppearing())\n            {\n                for (int i = 0; i < IM_ARRAYSIZE(keyboard_state_initial); ++i)\n                {\n                    keyboard_state_initial[i] = (::GetAsyncKeyState(i) < 0);\n                }\n\n                wait_for_key_release = false;\n            }\n\n            for (int i = 0; i < IM_ARRAYSIZE(keyboard_state_initial); ++i)\n            {\n                if ((::GetAsyncKeyState(i) < 0) != keyboard_state_initial[i])\n                {\n                    //Key was up before, so it's a key press\n                    if (!keyboard_state_initial[i])\n                    {\n                        if (key.KeyCode != i)\n                        {\n                            HistoryPush();\n                            key.KeyCode = i;\n                        }\n\n                        for (int i = 0; i < 256; i++)\n                        {\n                            if (GetKeyCodeForListID(i) == key.KeyCode)\n                            {\n                                list_id = i;\n                                break;\n                            }\n                        }\n\n                        scroll_to_selection = true;\n\n                        //Wait for the key to be released to avoid inputs triggering other things\n                        wait_for_key_release = true;\n                        keyboard_state_initial[i] = true;\n                        break;\n                    }\n                    else   //Key was down before, so it's a key release. Update the initial state so it can be pressed again and registered as such\n                    {\n                        keyboard_state_initial[i] = false;\n\n                        //Close popup here if we are waiting for this key to be released\n                        if ((wait_for_key_release) && (i == key.KeyCode))\n                        {\n                            close_picker = true;\n\n                            ImGui::CloseCurrentPopup();\n                            io.ClearInputKeys();\n                        }\n                    }\n                }\n            }\n\n            ImGui::EndPopup();\n\n            if (close_picker)\n            {\n                ImGui::CloseCurrentPopup();\n                UIManager::Get()->RepeatFrame();\n            }\n        }\n\n        ImGui::EndPopup();\n    }\n\n\n    //-Action Picker popup\n    ImGui::SetNextWindowSize({io.DisplaySize.x / 3.0f, io.DisplaySize.y * 0.8f});\n    ImGui::SetNextWindowPos( {io.DisplaySize.x / 2.0f, io.DisplaySize.y / 2.0f}, ImGuiCond_Always, {0.5f, 0.5f});\n    if (ImGui::BeginPopupModal(TranslationManager::GetString(tstr_DialogActionPickerHeader), nullptr, ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize))\n    {\n        static std::vector<ActionManager::ActionNameListEntry> action_list;\n        static ActionUID list_uid = k_ActionUID_Invalid;\n        static bool scroll_to_selection = false;\n        static ImVec2 no_actions_text_size;\n\n        if (ImGui::IsWindowAppearing())\n        {\n            action_list = ConfigManager::Get().GetActionManager().GetActionNameList();\n\n            list_uid = key.KeyActionUID;\n            scroll_to_selection = true;\n\n            //Set to invalid if selection doesn't exist\n            if (!ConfigManager::Get().GetActionManager().ActionExists(list_uid))\n            {\n                list_uid = k_ActionUID_Invalid;\n            }\n        }\n\n        CloseCurrentModalPopupFromInput();\n\n        //No Action entry\n        {\n            ImGui::PushID(0);\n\n            if (ImGui::Selectable(TranslationManager::GetString(tstr_ActionNone), (list_uid == k_ActionUID_Invalid) ))\n            {\n                list_uid = k_ActionUID_Invalid;\n\n                if (key.KeyActionUID != k_ActionUID_Invalid)\n                {\n                    HistoryPush();\n                    key.KeyActionUID = k_ActionUID_Invalid;\n                }\n\n                ImGui::CloseCurrentPopup();\n            }\n\n            if ( (scroll_to_selection) && (list_uid == k_ActionUID_Invalid) )\n            {\n                ImGui::SetScrollHereY();\n\n                if (ImGui::IsItemVisible())\n                {\n                    scroll_to_selection = false;\n                }\n            }\n\n            ImGui::PopID();\n        }\n\n        //List actions\n        for (const auto& entry : action_list)\n        {\n            ImGui::PushID((void*)entry.UID);\n\n            if (ImGui::Selectable(entry.Name.c_str(), (entry.UID == list_uid) ))\n            {\n                list_uid = entry.UID;\n\n                if (key.KeyActionUID != entry.UID)\n                {\n                    HistoryPush();\n                    key.KeyActionUID = entry.UID;\n                }\n\n                ImGui::CloseCurrentPopup();\n            }\n\n            if ( (scroll_to_selection) && (entry.UID == list_uid) )\n            {\n                ImGui::SetScrollHereY();\n\n                if (ImGui::IsItemVisible())\n                {\n                    scroll_to_selection = false;\n                }\n            }\n\n            ImGui::PopID();\n        }\n\n        ImGui::EndPopup();\n    }\n\n    ImGui::PopStyleVar();   //ImGuiStyleVar_WindowRounding\n    ImGui::End();\n}\n\nvoid KeyboardEditor::UpdateWindowMetadata()\n{\n    ImGuiIO& io = ImGui::GetIO();\n    ImGuiStyle& style = ImGui::GetStyle();\n    VRKeyboard& vr_keyboard = UIManager::Get()->GetVRKeyboard();\n    const KeyboardLayoutMetadata& metadata = vr_keyboard.GetLayoutMetadata();\n\n    ImGui::SetNextWindowSize({io.DisplaySize.x / 4.0f, m_TopWindowHeight});\n    ImGui::SetNextWindowPos({io.DisplaySize.x, 0.0f}, ImGuiCond_Always, {1.0f, 0.0f});\n\n    ImGui::Begin(TranslationManager::GetString(tstr_KeyboardEditorMetadataTitle), nullptr, ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse);\n\n    static char buffer_name[128]   = \"\";\n    static char buffer_author[256] = \"\";\n\n    if (m_RefreshLayout)\n    {\n        size_t copied_length = vr_keyboard.GetLayoutMetadata().Name.copy(buffer_name, IM_ARRAYSIZE(buffer_name) - 1);\n        buffer_name[copied_length] = '\\0';\n        copied_length = vr_keyboard.GetLayoutMetadata().Author.copy(buffer_author, IM_ARRAYSIZE(buffer_author) - 1);\n        buffer_author[copied_length] = '\\0';\n    }\n\n    ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, m_WindowRounding);\n\n    //We don't need scrolling yet but the child window with border offers consistency with the key list window for now\n    //Make the child expand past window padding\n    ImGui::SetCursorPos({ImGui::GetCursorPosX() - style.WindowPadding.x, ImGui::GetCursorPosY() - style.WindowPadding.y - style.TabBarBorderSize});\n\n    ImGui::SetNextWindowBgAlpha(0.0f);\n    ImGui::BeginChild(\"MetadataContents\", {ImGui::GetContentRegionAvail().x + style.WindowPadding.x, -ImGui::GetFrameHeightWithSpacing()}, ImGuiChildFlags_Borders);\n\n    ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, {0.0f, style.ItemSpacing.y});    //Avoid horizontal padding from the column\n\n    ImGui::Columns(2, \"ColumnMetadata\", false);\n    ImGui::SetColumnWidth(0, io.DisplaySize.x / 12.0f);\n\n    ImGui::AlignTextToFramePadding();\n    ImGui::TextUnformatted(TranslationManager::GetString(tstr_KeyboardEditorMetadataName));\n    ImGui::NextColumn();\n\n    ImGui::SetNextItemWidth(-1);\n    if (ImGui::InputText(\"##InputName\", buffer_name, IM_ARRAYSIZE(buffer_name)))\n    {\n        HistoryPush();\n        m_HistoryHasPendingEdit = true;\n\n        UIManager::Get()->AddFontBuilderStringIfAnyUnmappedCharacters(buffer_name);\n\n        KeyboardLayoutMetadata metadata_new = metadata;\n        metadata_new.Name = buffer_name;\n        vr_keyboard.SetLayoutMetadata(metadata_new);\n    }\n    ImGui::NextColumn();\n\n    ImGui::AlignTextToFramePadding();\n    ImGui::TextUnformatted(TranslationManager::GetString(tstr_KeyboardEditorMetadataAuthor));\n    ImGui::NextColumn();\n\n    ImGui::SetNextItemWidth(-1);\n    if (ImGui::InputText(\"##InputAuthor\", buffer_author, IM_ARRAYSIZE(buffer_author)))\n    {\n        HistoryPush();\n        m_HistoryHasPendingEdit = true;\n\n        UIManager::Get()->AddFontBuilderStringIfAnyUnmappedCharacters(buffer_author);\n\n        KeyboardLayoutMetadata metadata_new = metadata;\n        metadata_new.Author = buffer_author;\n        vr_keyboard.SetLayoutMetadata(metadata_new);\n    }\n\n    ImGui::Columns(1);\n    ImGui::PopStyleVar();   //ImGuiStyleVar_ItemSpacing\n\n    ImGui::Spacing();\n\n    bool has_altgr_temp = metadata.HasAltGr;\n    if (ImGui::Checkbox(TranslationManager::GetString(tstr_KeyboardEditorMetadataHasAltGr), &has_altgr_temp))\n    {\n        HistoryPush();\n\n        KeyboardLayoutMetadata metadata_new = metadata;\n        metadata_new.HasAltGr = has_altgr_temp;\n        vr_keyboard.SetLayoutMetadata(metadata_new);\n\n        UIManager::Get()->RepeatFrame();\n    }\n    ImGui::SameLine(0.0f, style.ItemInnerSpacing.x);\n    ImGui::FixedHelpMarker(TranslationManager::GetString(tstr_KeyboardEditorMetadataHasAltGrTip));\n\n    ImGui::Spacing();\n\n    const float column_start_x = (float)(int)((ImGui::GetContentRegionAvail().x / 2.0f) + style.IndentSpacing);\n    ImGui::TextUnformatted(TranslationManager::GetString(tstr_KeyboardEditorMetadataClusterPreview));\n    ImGui::Indent();\n\n    if (ImGui::Checkbox(TranslationManager::GetString(tstr_SettingsKeyboardKeyClusterFunction), &m_PreviewCluster[kbdlayout_cluster_function]))\n    {\n        UIManager::Get()->RepeatFrame();\n    }\n    ImGui::SameLine(column_start_x);\n\n    if (ImGui::Checkbox(TranslationManager::GetString(tstr_SettingsKeyboardKeyClusterNavigation), &m_PreviewCluster[kbdlayout_cluster_navigation]))\n    {\n        UIManager::Get()->RepeatFrame();\n    }\n\n    if (ImGui::Checkbox(TranslationManager::GetString(tstr_SettingsKeyboardKeyClusterNumpad), &m_PreviewCluster[kbdlayout_cluster_numpad]))\n    {\n        UIManager::Get()->RepeatFrame();\n    }\n    ImGui::SameLine(column_start_x);\n\n    if (ImGui::Checkbox(TranslationManager::GetString(tstr_SettingsKeyboardKeyClusterExtra), &m_PreviewCluster[kbdlayout_cluster_extra]))\n    {\n        UIManager::Get()->RepeatFrame();\n    }\n\n    ImGui::EndChild();\n\n    static float bottom_buttons_width = 0.0f;\n    ImGui::SetCursorPosX(ImGui::GetCursorPosX() + ImGui::GetContentRegionAvail().x - bottom_buttons_width);\n    ImGui::BeginGroup();\n\n    if (ImGui::Button(TranslationManager::GetString(tstr_KeyboardEditorMetadataSave))) \n    {\n        ImGui::OpenPopup(TranslationManager::GetString(tstr_KeyboardEditorMetadataSavePopupTitle));\n    }\n\n    ImGui::SameLine(0.0f, style.ItemInnerSpacing.x);\n\n    if (ImGui::Button(TranslationManager::GetString(tstr_KeyboardEditorMetadataLoad))) \n    {\n        ImGui::OpenPopup(TranslationManager::GetString(tstr_KeyboardEditorMetadataLoadPopupTitle));\n    }\n\n    ImGui::EndGroup();\n    bottom_buttons_width = ImGui::GetItemRectSize().x;\n\n\n    //-Save Layout popup\n    ImGui::SetNextWindowSize({io.DisplaySize.x / 3.0f, io.DisplaySize.y * 0.8f});\n    ImGui::SetNextWindowPos( {io.DisplaySize.x / 2.0f, io.DisplaySize.y / 2.0f}, ImGuiCond_Always, {0.5f, 0.5f});\n    if (ImGui::BeginPopupModal(TranslationManager::GetString(tstr_KeyboardEditorMetadataSavePopupTitle), nullptr, ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize))\n    {\n        static std::vector<KeyboardLayoutMetadata> list_layouts;\n        static int list_id = 0;\n        static char buffer_filename[256] = \"\";\n        static bool is_name_blank = false;\n        static bool is_name_taken = false;\n        static bool has_saving_failed = false;\n\n        bool update_entry_match = false;\n        bool is_entry_double_clicked = false;\n\n        if (ImGui::IsWindowAppearing())\n        {\n            list_id = -1;\n            list_layouts = VRKeyboard::GetKeyboardLayoutList();\n            has_saving_failed = false;\n\n            size_t copied_length = vr_keyboard.GetLayoutMetadata().FileName.copy(buffer_filename, IM_ARRAYSIZE(buffer_filename) - 1);\n            buffer_filename[copied_length] = '\\0';\n\n            update_entry_match = true;\n        }\n\n        CloseCurrentModalPopupFromInput();\n\n        ImGui::AlignTextToFramePadding();\n        ImGui::TextUnformatted(TranslationManager::GetString(tstr_KeyboardEditorMetadataSavePopupFilename));\n\n        const float input_spacing_offset =  (float)int(ImGui::GetContentRegionAvail().x * 0.10f);\n        const float input_spacing = ImGui::GetItemRectMax().x - ImGui::GetWindowPos().x;\n\n        if (is_name_blank)\n        {\n            ImGui::SameLine(0.0f, style.ItemInnerSpacing.x);\n            ImGui::FixedHelpMarker(TranslationManager::GetString(tstr_KeyboardEditorMetadataSavePopupFilenameBlankTip), \"(!)\");\n        }\n\n        ImGui::SameLine(input_spacing_offset, input_spacing);\n\n        ImGui::SetNextItemWidth(-1);\n        if (ImGui::InputText(\"##InputFilename\", buffer_filename, IM_ARRAYSIZE(buffer_filename), ImGuiInputTextFlags_CallbackCharFilter,\n                                                                                                [](ImGuiInputTextCallbackData* data)\n                                                                                                {\n                                                                                                    return (int)IsWCharInvalidForFileName(data->EventChar);\n                                                                                                }\n           ))\n        {\n            UIManager::Get()->AddFontBuilderStringIfAnyUnmappedCharacters(buffer_filename);\n            update_entry_match = true;\n        }\n\n        //Select matching entry\n        if (update_entry_match)\n        {\n            const std::string& current_filename = buffer_filename;\n            auto it = std::find_if(list_layouts.begin(), list_layouts.end(), [&current_filename](const auto& list_entry){ return (current_filename == list_entry.FileName); });\n\n            if (it != list_layouts.end())\n            {\n                list_id = (int)std::distance(list_layouts.begin(), it);\n            }\n            else\n            {\n                list_id = -1;\n            }\n\n            is_name_blank = current_filename.empty();\n        }\n\n        ImGui::BeginChild(\"LayoutList\", ImVec2(0.0f, -ImGui::GetFrameHeightWithSpacing()), ImGuiChildFlags_Borders);\n\n        int index = 0;\n        for (const auto& metadata : list_layouts)\n        {\n            ImGui::PushID(index);\n\n            if (ImGui::Selectable(metadata.Name.c_str(), (index == list_id)))\n            {\n                list_id = index;\n\n                size_t copied_length = metadata.FileName.copy(buffer_filename, IM_ARRAYSIZE(buffer_filename) - 1);\n                buffer_filename[copied_length] = '\\0';\n\n                is_name_blank = false;\n            }\n\n            if ((ImGui::IsItemActive()) && (ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left)))\n            {\n                is_entry_double_clicked = true;\n            }\n\n            ImGui::SameLine();\n            ImGui::PushStyleColor(ImGuiCol_Text, style.Colors[ImGuiCol_TextDisabled]);\n            ImGui::TextRightUnformatted(0.0f, 0.0f, metadata.FileName.c_str());\n            ImGui::PopStyleColor();\n\n            ImGui::PopID();\n\n            index++;\n        }\n\n        ImGui::EndChild();\n\n        if (is_name_blank)\n            ImGui::PushItemDisabled();\n\n        if ((ImGui::Button(TranslationManager::GetString(tstr_KeyboardEditorMetadataSavePopupConfirm))) || (is_entry_double_clicked))\n        {\n            //Append extension if needed\n            std::string filename(buffer_filename);\n            if (filename.rfind(\".ini\") != filename.length() - 4)\n            {\n                filename += \".ini\";\n            }\n\n            has_saving_failed = !vr_keyboard.SaveCurrentLayoutToFile(filename);\n\n            if (!has_saving_failed)\n            {\n                ImGui::CloseCurrentPopup();\n            }\n        }\n\n        if (is_name_blank)\n            ImGui::PopItemDisabled();\n\n        ImGui::SameLine(0.0f, style.ItemInnerSpacing.x);\n\n        if (ImGui::Button(TranslationManager::GetString(tstr_DialogCancel)))\n        {\n            ImGui::CloseCurrentPopup();\n        }\n\n        if (has_saving_failed)\n        {\n            ImGui::SameLine();\n\n            ImGui::AlignTextToFramePadding();\n            ImGui::PushStyleColor(ImGuiCol_Text, Style_ImGuiCol_TextError);\n            ImGui::TextRightUnformatted(style.ItemInnerSpacing.x, 0.0f, TranslationManager::GetString(tstr_KeyboardEditorMetadataSavePopupConfirmError));\n            ImGui::PopStyleColor();\n        }\n\n        ImGui::EndPopup();\n    }\n\n\n    //-Load Layout popup\n    ImGui::SetNextWindowSize({io.DisplaySize.x / 3.0f, io.DisplaySize.y * 0.8f});\n    ImGui::SetNextWindowPos( {io.DisplaySize.x / 2.0f, io.DisplaySize.y / 2.0f}, ImGuiCond_Always, {0.5f, 0.5f});\n    if (ImGui::BeginPopupModal(TranslationManager::GetString(tstr_KeyboardEditorMetadataLoadPopupTitle), nullptr, ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize))\n    {\n        static std::vector<KeyboardLayoutMetadata> list_layouts;\n        static int list_id = 0;\n\n        bool is_entry_double_clicked = false;\n\n        if (ImGui::IsWindowAppearing())\n        {\n            list_id = -1;\n            list_layouts = VRKeyboard::GetKeyboardLayoutList();\n\n            //Select matching entry\n            const std::string& current_filename = vr_keyboard.GetLayoutMetadata().FileName;\n            auto it = std::find_if(list_layouts.begin(), list_layouts.end(), [&current_filename](const auto& list_entry){ return (current_filename == list_entry.FileName); });\n\n            if (it != list_layouts.end())\n            {\n                list_id = (int)std::distance(list_layouts.begin(), it);\n            }\n        }\n\n        CloseCurrentModalPopupFromInput();\n\n        ImGui::BeginChild(\"LayoutList\", ImVec2(0.0f, -ImGui::GetFrameHeightWithSpacing()), ImGuiChildFlags_Borders);\n\n        int index = 0;\n        for (const auto& metadata : list_layouts)\n        {\n            ImGui::PushID(index);\n\n            if (ImGui::Selectable(metadata.Name.c_str(), (index == list_id)))\n            {\n                list_id = index;\n            }\n\n            if ((ImGui::IsItemActive()) && (ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left)))\n            {\n                is_entry_double_clicked = true;\n            }\n\n            ImGui::SameLine();\n            ImGui::PushStyleColor(ImGuiCol_Text, style.Colors[ImGuiCol_TextDisabled]);\n            ImGui::TextRightUnformatted(0.0f, 0.0f, metadata.FileName.c_str());\n            ImGui::PopStyleColor();\n\n            ImGui::PopID();\n\n            index++;\n        }\n\n        ImGui::EndChild();\n\n        if ((ImGui::Button(TranslationManager::GetString(tstr_KeyboardEditorMetadataLoadPopupConfirm))) || (is_entry_double_clicked))\n        {\n            vr_keyboard.LoadLayoutFromFile(list_layouts[list_id].FileName);\n            ImGui::CloseCurrentPopup();\n\n            m_RefreshLayout = true;\n            m_HasChangedSelectedKey = true;\n            HistoryClear();\n        }\n\n        ImGui::SameLine(0.0f, style.ItemInnerSpacing.x);\n\n        if (ImGui::Button(TranslationManager::GetString(tstr_DialogCancel)))\n        {\n            ImGui::CloseCurrentPopup();\n        }\n\n        ImGui::EndPopup();\n    }\n\n    ImGui::PopStyleVar();   //ImGuiStyleVar_WindowRounding\n    ImGui::End();\n}\n\nvoid KeyboardEditor::UpdateWindowPreview()\n{\n    //This needs to be kept in sync with WindowKeyboard::WindowUpdate(), but is also sufficiently different to not try to make a mess in that function from trying to shove it there\n    ImGuiIO& io = ImGui::GetIO();\n    ImGuiStyle& style = ImGui::GetStyle();\n    VRKeyboard& vr_keyboard = UIManager::Get()->GetVRKeyboard();\n\n    //We only scale style sizes up in desktop mode (using DPI instead of UI scale value), but for accurate keyboard preview we need spacing to scale with UI scale\n    const float ui_scale = UIManager::Get()->GetUIScale();\n    ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing,      {8.0f * ui_scale, 4.0f * ui_scale});\n    ImGui::PushStyleVar(ImGuiStyleVar_ItemInnerSpacing, {4.0f * ui_scale, 4.0f * ui_scale});\n\n    const DPRect& rect = UITextureSpaces::Get().GetRect(ui_texspace_keyboard);\n    ImGui::SetNextWindowSizeConstraints({ImGui::GetFrameHeight() * 6.0f, ImGui::GetFrameHeight() * 2.0f}, {(rect.GetWidth() - 4) * ui_scale, (rect.GetHeight() - 4) * ui_scale});\n\n    ImGui::SetNextWindowPos({io.DisplaySize.x / 2.0f, io.DisplaySize.y}, ImGuiCond_Always, {0.5f, 1.0f});\n\n    ImGui::Begin(TranslationManager::GetString(tstr_KeyboardEditorPreviewTitle), nullptr, ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_AlwaysAutoResize);\n\n    const float base_width = (float)int(ImGui::GetTextLineHeightWithSpacing() * 2.225f);\n\n    //Used for kbdlayout_key_virtual_key_iso_enter key\n    ImVec2 iso_enter_top_pos(-1.0f, -1.0f);\n    float  iso_enter_top_width    = -1.0f;\n    int    iso_enter_top_index    = -1;\n\n    int row_index = 0;\n    int key_index = 0;\n    ImVec2 cursor_pos = ImGui::GetCursorPos();\n    ImVec2 cursor_pos_line_start = cursor_pos;\n    for (const auto& key : vr_keyboard.GetLayout(m_SelectedSublayout))\n    {\n        //Skip key as if it was never loaded if preview is disabled for it\n        if (!m_PreviewCluster[key.KeyCluster])\n        {\n            key_index++;\n\n            if (key.IsRowEnd)\n            {\n                ++row_index;\n\n                //Only force a new line if the cursor moved (meaning we didn't skip all keys in this row)\n                if ((cursor_pos_line_start.x != ImGui::GetCursorPos().x) || (cursor_pos_line_start.x != ImGui::GetCursorPos().x))\n                {\n                    ImGui::NewLine();\n                    cursor_pos_line_start = ImGui::GetCursorPos();\n                }\n            }\n            continue;\n        }\n        else if ((cursor_pos_line_start.x != ImGui::GetCursorPos().x) || (cursor_pos_line_start.x != ImGui::GetCursorPos().x))\n        {\n            //To make above work correctly, spacing is applied here, with cursor_pos still holding values from the previous key\n            cursor_pos.x += style.ItemInnerSpacing.x;\n            ImGui::SetCursorPos(cursor_pos);\n        }\n\n        ImGui::PushID(key_index);\n\n        //Keep cursor pos on integer values\n        cursor_pos = ImGui::GetCursorPos();\n        ImGui::SetCursorPos({ceilf(cursor_pos.x), ceilf(cursor_pos.y)});\n\n        //This accounts for the spacing that is missing with wider keys so rows still line up and also forces integer values\n        const float key_width_f = base_width * key.Width  + (style.ItemInnerSpacing.x * (key.Width  - 1.0f));\n        const float key_height  = (float)(int)( base_width * key.Height + (style.ItemInnerSpacing.y * (key.Height - 1.0f)) );\n        float key_width         = (float)(int)( key_width_f );\n\n        //Add an extra pixel of width if the untruncated values would push it further\n        //There might be a smarter way to do this (simple rounding doesn't seem to be it), but this helps the keys align while rendering on full pixels only\n        if (cursor_pos.x + key_width_f > ImGui::GetCursorPosX() + key_width)\n        {\n            key_width += 1.0f;\n        }\n\n        const bool highlight_key = ((key_index == m_SelectedKeyID) || (m_SelectedRowID == row_index));\n\n        if (highlight_key)\n            ImGui::PushStyleColor(ImGuiCol_Button, Style_ImGuiCol_ButtonPassiveToggled);\n\n        switch (key.KeyType)\n        {\n            case kbdlayout_key_blank_space:\n            {\n                ImGui::PushStyleColor(ImGuiCol_Button, 0);\n                if (ImGui::Button(\"\", {key_width, key_height}))\n                {\n                    m_SelectedRowID = -1;\n                    m_SelectedKeyID = key_index;\n                    m_HasChangedSelectedKey = true;\n                }\n                ImGui::PopStyleColor();\n\n                if (highlight_key)\n                {\n                    ImGui::GetWindowDrawList()->AddRect(ImGui::GetItemRectMin(), ImGui::GetItemRectMax(), ImGui::GetColorU32(ImGuiCol_Button), style.FrameRounding, 0, style.WindowBorderSize * 2.0f);\n                }\n                break;\n            }\n            case kbdlayout_key_virtual_key:\n            case kbdlayout_key_virtual_key_toggle:\n            case kbdlayout_key_string:\n            case kbdlayout_key_sublayout_toggle:\n            case kbdlayout_key_action:\n            {\n                if (ImGui::Button(\"\", {key_width, key_height}))\n                {\n                    m_SelectedRowID = -1;\n                    m_SelectedKeyID = key_index;\n                    m_HasChangedSelectedKey = true;\n                }\n                ImGui::RenderButtonMultilineLabel(key.Label.c_str(), 0.82f);\n\n                break;\n            }\n            case kbdlayout_key_virtual_key_iso_enter:\n            {\n                //This one's a bit of a mess, but builds an ISO-Enter shaped button out of two key entries\n                //First step is the top \"key\". Its label is unused, but the width is.\n                //Second step is the bottom \"key\". It stretches itself over the row above it and hosts the label.\n                //First and second step used invisible buttons first to check item state and have it synced up,\n                //then in the second step there are two visual-only buttons used with style color adjusted to the state of the invisible buttons\n                //\n                //...now that a custom button implementation is used anyways this could be solved more cleanly... but it still works, so eh.\n\n                const bool is_bottom_key = (iso_enter_top_index != -1);\n                const ImVec2 cursor_pos = ImGui::GetCursorPos();\n                float offset_y = 0.0f;\n\n                //If second ISO-enter key, offset cursor to the previous row and stretch the button down to the end of the current row\n                if (is_bottom_key)\n                {\n                    offset_y = style.ItemSpacing.y + base_width;\n                    ImGui::SetCursorPosY(ImGui::GetCursorPosY() - offset_y);\n                }\n                else //else remember the width of the top part for later\n                {\n                    iso_enter_top_pos   = cursor_pos;\n                    iso_enter_top_width = key_width;\n                    iso_enter_top_index = key_index;\n                }\n\n                if (ImGui::Button(\"\", {key_width, base_width + offset_y}))\n                {\n                    m_SelectedRowID = -1;\n                    m_SelectedKeyID = key_index;\n                    m_HasChangedSelectedKey = true;\n                }\n                ImGui::RenderButtonMultilineLabel((is_bottom_key) ? key.Label.c_str() : \"##IsoEnterDummy\", 0.82f);\n\n                break;\n            }\n            default: break;\n        }\n\n        if (highlight_key)\n            ImGui::PopStyleColor();\n\n        //Select row by right clicking\n        if (ImGui::IsItemClicked(ImGuiMouseButton_Right))\n        {\n            m_SelectedRowID = row_index;\n            m_SelectedKeyID = -1;\n            m_HasChangedSelectedKey = true;\n        }\n\n        //Return to normal row position if the key was taller than 100%\n        if (key.Height > 1.0f)\n        {\n            ImGui::SetCursorPosY(ImGui::GetCursorPosY() - key_height + base_width);\n            ImGui::SetPreviousLineHeight(ImGui::GetPreviousLineHeight() - key_height + base_width);\n        }\n\n        if (!key.IsRowEnd)\n        {\n            ImGui::SameLine(0.0f, style.ItemInnerSpacing.x);\n            cursor_pos.x += key_width_f;\n        }\n        else\n        {\n            ++row_index;\n            cursor_pos_line_start = ImGui::GetCursorPos();\n        }\n\n        ImGui::PopID();\n\n        ++key_index;\n    }\n\n    ImGui::End();\n\n    ImGui::PopStyleVar(2);  //ImGuiStyleVar_ItemSpacing, ImGuiStyleVar_ItemInnerSpacing\n}\n\nstd::pair<int, int> KeyboardEditor::GetKeyRowRange(KeyboardLayoutSubLayout sublayout, int row_id)\n{\n    auto& sublayout_keys = UIManager::Get()->GetVRKeyboard().GetLayout(sublayout);\n\n    int key_id_begin = 0;\n    int key_id_end   = (int)sublayout_keys.size(); //Set to size() so begin() + key_id_end results in end() if nothing is found\n\n    for (int i = 0, loop_row_id = 0; i < sublayout_keys.size(); ++i)\n    {\n        if (sublayout_keys[i].IsRowEnd)\n        {\n            ++loop_row_id;\n\n            if (loop_row_id == row_id)\n            {\n                key_id_begin = i + 1;\n            }\n            else if (loop_row_id == row_id + 1)\n            {\n                key_id_end = i + 1;\n                break;\n            }\n        }\n    }\n\n    return std::make_pair(key_id_begin, key_id_end);\n}\n\nint KeyboardEditor::FindKeyWithClosestPosInNewSubLayout(int key_index, KeyboardLayoutSubLayout sublayout_id_current, KeyboardLayoutSubLayout sublayout_id_new)\n{\n    //Not to be confused with WindowKeyboard::FindSameKeyInNewSubLayout() which only finds exact matches with same function at same pos instead\n    if (key_index < 0)\n        return -1;\n\n    //Return index of key with position closest to sublayout_current's key_index key position\n    VRKeyboard& vr_keyboard = UIManager::Get()->GetVRKeyboard();\n    const auto& sublayout_current = vr_keyboard.GetLayout(sublayout_id_current);\n    const auto& sublayout_new     = vr_keyboard.GetLayout(sublayout_id_new);\n\n    //Skip if index doesn't exist in layout for some reason\n    if (sublayout_current.size() <= key_index)\n        return -1;\n\n    //Get key position in current layout\n    int i = 0;\n    Vector2 key_current_pos;\n\n    for (const auto& key : sublayout_current)\n    {\n        if (i == key_index)\n        {\n            break;\n        }\n\n        key_current_pos.x += key.Width;\n\n        if (key.IsRowEnd)\n        {\n            key_current_pos.x  = 0.0f;\n            key_current_pos.y += 1.0f; //Key height doesn't matter, advance a row\n        }\n\n        i++;\n    }\n\n    //Find key closest to the old position in new layout\n    i = 0;\n    Vector2 key_new_pos;\n    int key_lowest_dist_id = -1;\n    float key_lowest_dist = FLT_MAX;\n\n    for (const auto& key : sublayout_new)\n    {\n        if ( (key_new_pos.y == key_current_pos.y) && (key_new_pos.x == key_current_pos.x) )\n        {\n            //Key exists at the same position return that\n            return i;\n        }\n        else\n        {\n            //Alternatively get the closest key and return that at the end if we don't find anything better\n            float distance = key_new_pos.distance(key_current_pos);\n\n            if (distance < key_lowest_dist)\n            {\n                key_lowest_dist_id = i;\n                key_lowest_dist = distance;\n            }\n        }\n\n        key_new_pos.x += key.Width;\n\n        if (key.IsRowEnd)\n        {\n            key_new_pos.x  = 0.0f;\n            key_new_pos.y += 1.0f;\n        }\n\n        i++;\n    }\n\n    return key_lowest_dist_id;\n}\n\nvoid KeyboardEditor::HistoryPush()\n{\n    if (!m_HistoryHasPendingEdit)\n    {\n        HistoryPushInternal(m_HistoryUndo);\n        m_HistoryRedo.clear();\n    }\n}\n\nvoid KeyboardEditor::HistoryPushInternal(std::vector<HistoryItem>& target_history)\n{\n    //This copies the entire keyboard data for every history item\n    VRKeyboard& vr_keyboard = UIManager::Get()->GetVRKeyboard();\n\n    HistoryItem item;\n    item.Metadata = vr_keyboard.GetLayoutMetadata();\n\n    for (int i_sublayout = kbdlayout_sub_base; i_sublayout < kbdlayout_sub_MAX; ++i_sublayout)\n    {\n        KeyboardLayoutSubLayout sublayout = (KeyboardLayoutSubLayout)i_sublayout;\n        item.KeyboardKeys[sublayout] = vr_keyboard.GetLayout(sublayout);\n    }\n\n    target_history.push_back(item);\n}\n\nvoid KeyboardEditor::HistoryUndo()\n{\n    if (m_HistoryUndo.empty())\n        return;\n\n    VRKeyboard& vr_keyboard = UIManager::Get()->GetVRKeyboard();\n\n    HistoryItem item = m_HistoryUndo.back();\n    m_HistoryUndo.pop_back();\n    HistoryPushInternal(m_HistoryRedo);\n\n    vr_keyboard.SetLayoutMetadata(item.Metadata);\n\n    for (int i_sublayout = kbdlayout_sub_base; i_sublayout < kbdlayout_sub_MAX; ++i_sublayout)\n    {\n        KeyboardLayoutSubLayout sublayout = (KeyboardLayoutSubLayout)i_sublayout;\n        vr_keyboard.SetLayout(sublayout, item.KeyboardKeys[sublayout]);\n    }\n\n    m_HasChangedSelectedKey = true;\n    m_RefreshLayout = true;\n}\n\nvoid KeyboardEditor::HistoryRedo()\n{\n    if (m_HistoryRedo.empty())\n        return;\n\n    VRKeyboard& vr_keyboard = UIManager::Get()->GetVRKeyboard();\n\n    HistoryItem item = m_HistoryRedo.back();\n    m_HistoryRedo.pop_back();\n    HistoryPushInternal(m_HistoryUndo);\n\n    vr_keyboard.SetLayoutMetadata(item.Metadata);\n\n    for (int i_sublayout = kbdlayout_sub_base; i_sublayout < kbdlayout_sub_MAX; ++i_sublayout)\n    {\n        KeyboardLayoutSubLayout sublayout = (KeyboardLayoutSubLayout)i_sublayout;\n        vr_keyboard.SetLayout(sublayout, item.KeyboardKeys[sublayout]);\n    }\n\n    m_HasChangedSelectedKey = true;\n    m_RefreshLayout = true;\n}\n\nvoid KeyboardEditor::HistoryClear()\n{\n    m_HistoryUndo.clear();\n    m_HistoryRedo.clear();\n}\n\nvoid KeyboardEditor::CloseCurrentModalPopupFromInput()\n{\n    //Imitate non-modal non-window click closing behavior (we go modal to get the title bar)\n    if ( ( (!ImGui::IsWindowHovered(ImGuiHoveredFlags_ChildWindows)) && ( (ImGui::IsMouseClicked(ImGuiMouseButton_Left)) || (ImGui::IsMouseClicked(ImGuiMouseButton_Right)) )) || \n           (ImGui::IsKeyPressed(ImGuiKey_Escape)) )\n    {\n        ImGui::CloseCurrentPopup();\n    }\n}\n\nvoid KeyboardEditor::Update()\n{\n    const bool has_changed_key_prev = m_HasChangedSelectedKey;\n    const bool refresh_layout_prev  = m_RefreshLayout;\n\n    m_TopWindowHeight = ImGui::GetIO().DisplaySize.y - (UITextureSpaces::Get().GetRect(ui_texspace_keyboard).GetHeight() * UIManager::Get()->GetUIScale());\n    m_WindowRounding = ImGui::GetStyle().WindowRounding;    //Store window rounding before pushing it to 0 so we have the scaled value for popups later\n\n    ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 0.0f);\n\n    UpdateWindowKeyList();\n    UpdateWindowKeyProperties();\n    UpdateWindowMetadata();\n\n    ImGui::PopStyleVar();\n\n    UpdateWindowPreview();\n\n\n    if (ImGui::IsAnyItemDeactivated())\n    {\n        m_HistoryHasPendingEdit = false;\n    }\n\n    if (has_changed_key_prev == m_HasChangedSelectedKey)\n    {\n        m_HasChangedSelectedKey = false;\n    }\n    else\n    {\n        UIManager::Get()->RepeatFrame();\n    }\n\n    if (refresh_layout_prev == m_RefreshLayout)\n    {\n        m_RefreshLayout = false;\n    }\n    else\n    {\n        UIManager::Get()->RepeatFrame();\n    }\n}\n"
  },
  {
    "path": "src/DesktopPlusUI/WindowKeyboardEditor.h",
    "content": "#pragma once\n\n#include \"VRKeyboardCommon.h\"\n#include <utility>\n\nclass KeyboardEditor\n{\n    //Not exactly space efficient, but simple and works\n    struct HistoryItem\n    {\n        KeyboardLayoutMetadata Metadata;\n        std::vector<KeyboardLayoutKey> KeyboardKeys[kbdlayout_sub_MAX];\n    };\n\n    private:\n        float m_TopWindowHeight = 0.0f;\n        float m_WindowRounding  = 0.0f;\n\n        KeyboardLayoutSubLayout m_SelectedSublayout = kbdlayout_sub_base;\n        int m_SelectedRowID = -1;\n        int m_SelectedKeyID = -1;\n        bool m_HasChangedSelectedKey = false;\n        bool m_RefreshLayout = true;\n\n        std::vector<HistoryItem> m_HistoryUndo;\n        std::vector<HistoryItem> m_HistoryRedo;\n        bool m_HistoryHasPendingEdit = false;   //Set by widgets that make continuous changes but should only push history once until deactivated\n\n        bool m_PreviewCluster[kbdlayout_cluster_MAX] = {true, true, true, true, true};\n\n        void UpdateWindowKeyList();\n        void UpdateWindowKeyProperties();\n        void UpdateWindowMetadata();\n        void UpdateWindowPreview();\n\n        std::pair<int, int> GetKeyRowRange(KeyboardLayoutSubLayout sublayout, int row_id);  //Returns beginning & end key IDs\n        int FindKeyWithClosestPosInNewSubLayout(int key_index, KeyboardLayoutSubLayout sublayout_id_current, KeyboardLayoutSubLayout sublayout_id_new);\n\n        void HistoryPush();\n        void HistoryPushInternal(std::vector<HistoryItem>& target_history);\n        void HistoryUndo();\n        void HistoryRedo();\n        void HistoryClear();\n\n        void CloseCurrentModalPopupFromInput();                                             //Imitates non-modal popup closing behavior\n\n    public:\n        void Update();\n};\n"
  },
  {
    "path": "src/DesktopPlusUI/WindowOverlayBar.cpp",
    "content": "#include \"WindowOverlayBar.h\"\n\n#include \"ImGuiExt.h\"\n#include \"TextureManager.h\"\n#include \"InterprocessMessaging.h\"\n#include \"Util.h\"\n#include \"OpenVRExt.h\"\n#include \"UIManager.h\"\n#include \"OverlayManager.h\"\n#include \"DesktopPlusWinRT.h\"\n#include \"DPBrowserAPIClient.h\"\n\n\nWindowOverlayBar::WindowOverlayBar() : m_Visible(true),\n                                       m_Alpha(1.0f), \n                                       m_IsScrollBarVisible(false),\n                                       m_OverlayButtonActiveMenuID(k_ulOverlayID_None),\n                                       m_IsAddOverlayButtonActive(false),\n                                       m_MenuAlpha(0.0f),\n                                       m_IsMenuRemoveConfirmationVisible(false),\n                                       m_IsDraggingOverlayButtons(false)\n{\n    m_Size.x = 32.0f;\n}\n\nvoid WindowOverlayBar::DisplayTooltipIfHovered(const char* text, unsigned int overlay_id)\n{\n    //Blank name is not allowed by ImGui and doesn't much sense to display anyways\n    if ((text == nullptr) || (text[0] == '\\0'))\n        return;\n\n    if (ImGui::IsItemHovered())\n    {\n        const ImGuiStyle& style = ImGui::GetStyle();\n\n        static ImVec2 button_pos_last; //Remember last position and use it when posible. This avoids flicker when the same tooltip string is used in different places\n        ImVec2 pos = ImGui::GetItemRectMin();\n        pos.y = ImGui::GetIO().DisplaySize.y;\n        float button_width = ImGui::GetItemRectSize().x;\n\n        //Default tooltips are not suited for this as there's too much trouble with resize flickering and stuff\n        ImGui::Begin(text, nullptr, ImGuiWindowFlags_NoInputs | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize | \n                                    ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoFocusOnAppearing);\n\n        ImGui::BeginGroup();\n\n        //Display icon for overlay origin if the tooltip is for a overlay\n        if (overlay_id != k_ulOverlayID_None)\n        {\n            const OverlayConfigData& data = OverlayManager::Get().GetConfigData(overlay_id);\n\n            ImVec2 img_size_line_height = {ImGui::GetTextLineHeight(), ImGui::GetTextLineHeight()};\n            ImVec2 img_size, img_uv_min, img_uv_max;\n\n            TextureManager::Get().GetTextureInfo((TMNGRTexID)(tmtex_icon_xsmall_origin_room + data.ConfigInt[configid_int_overlay_origin]), img_size, img_uv_min, img_uv_max);\n            ImGui::Image(ImGui::GetIO().Fonts->TexID, img_size_line_height, img_uv_min, img_uv_max);\n\n            ImGui::SameLine(0.0f, style.ItemInnerSpacing.x);\n        }\n\n        ImGui::TextUnformatted(text);\n        ImGui::EndGroup();\n\n        //Not using GetWindowSize() here since it's delayed and plays odd when switching between buttons with the same label\n        ImVec2 window_size = ImGui::GetItemRectSize();\n        window_size.x += style.WindowPadding.x * 2.0f;\n        window_size.y += style.WindowPadding.y * 2.0f;\n\n        //Repeat frame when the window is appearing as it will not have the right position (either from being first time or still having old pos)\n        if ( (ImGui::IsWindowAppearing()) || (pos.x != button_pos_last.x) )\n        {\n            UIManager::Get()->RepeatFrame();\n        }\n\n        button_pos_last = pos;\n\n        pos.x += (button_width / 2.0f) - (window_size.x / 2.0f);\n        pos.y -= window_size.y;\n\n        pos.x = clamp(pos.x, 0.0f, ImGui::GetIO().DisplaySize.x - window_size.x);   //Clamp right side to texture end\n\n        ImGui::SetWindowPos(pos);\n\n        ImGui::End();\n    }\n}\n\nvoid WindowOverlayBar::UpdateOverlayButtons()\n{\n    ImGuiIO& io = ImGui::GetIO();\n    ImGuiStyle& style = ImGui::GetStyle();\n\n    struct OverlayButtonState\n    {\n        float starting_x = -1.0f;\n        float animation_progress = 0.0f;\n    };\n\n    //List of unique IDs for overlays so ImGui can identify the same list entries after reordering or list expansion (needed for drag reordering)\n    static std::vector<int> list_unique_ids;\n    static std::vector<OverlayButtonState> list_unique_data;\n    static unsigned int drag_button_id  = k_ulOverlayID_None;\n    static unsigned int drag_overlay_id = k_ulOverlayID_None;\n    static float drag_mouse_offset = 0.0f;\n\n    const int overlay_count = (int)OverlayManager::Get().GetOverlayCount();\n    const unsigned int properties_active_overlay = (UIManager::Get()->GetOverlayPropertiesWindow().IsVisible()) ? (UIManager::Get()->GetOverlayPropertiesWindow().GetActiveOverlayID()) : k_ulOverlayID_None;\n\n    //Reset unique IDs & widget data when appearing\n    if (ImGui::IsWindowAppearing())\n    {\n        list_unique_ids.clear();\n        list_unique_data.clear();\n    }\n\n    //Expand unique id lists if overlays were added (also does initialization since it's empty then)\n    while (list_unique_ids.size() < OverlayManager::Get().GetOverlayCount())\n    {\n        list_unique_ids.push_back((int)list_unique_ids.size());\n    }\n\n    while (list_unique_data.size() < OverlayManager::Get().GetOverlayCount())\n    {\n        list_unique_data.emplace_back();\n    }\n\n    //Get settings icons dimensions for uniform button size\n    ImVec2 b_size, b_uv_min, b_uv_max;\n    TextureManager::Get().GetTextureInfo(tmtex_icon_settings, b_size, b_uv_min, b_uv_max);\n    ImVec2 b_size_default = b_size;\n\n    static unsigned int left_down_overlay_id  = k_ulOverlayID_None;\n    static unsigned int right_down_overlay_id = k_ulOverlayID_None;\n    static unsigned int hovered_overlay_id_last = k_ulOverlayID_None;\n    unsigned int hovered_overlay_id = k_ulOverlayID_None;\n\n    //Lambda for an overlay button widget. Interactions are implemented separately where needed\n    auto overlay_button = [&](unsigned int overlay_id)\n    {\n        bool is_active = ( (m_OverlayButtonActiveMenuID == overlay_id) || (drag_overlay_id == overlay_id) || (properties_active_overlay == overlay_id) || (left_down_overlay_id == overlay_id) || \n                           (right_down_overlay_id == overlay_id) );\n\n        if (is_active)\n        {\n            ImGui::PushStyleColor(ImGuiCol_Button,        ImGui::GetStyleColorVec4(ImGuiCol_ButtonActive));\n            ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImGui::GetStyleColorVec4(ImGuiCol_ButtonActive));\n        }\n\n        //Get icon texture ID\n        OverlayConfigData& data = OverlayManager::Get().GetConfigData(overlay_id);\n        bool b_window_icon_available;\n        TextureManager::Get().GetOverlayIconTextureInfo(data, b_size, b_uv_min, b_uv_max, false, &b_window_icon_available);\n\n        const ImVec4 tint_color = ImVec4(1.0f, 1.0f, 1.0f, data.ConfigBool[configid_bool_overlay_enabled] ? 1.0f : 0.5f); //Half-transparent when hidden\n\n        bool ret = ImGui::ImageButton(\"OverlayButton\", io.Fonts->TexID, b_size_default, b_uv_min, b_uv_max, ImVec4(0.0f, 0.0f, 0.0f, 0.0f), tint_color);\n\n        if (is_active)\n        {\n            ImGui::PopStyleColor(2);\n        }\n\n        //Additional button behavior\n        const ImVec2 pos  = ImGui::GetItemRectMin();\n        const float width = ImGui::GetItemRectSize().x;\n\n        //Draw window icon on top\n        if (b_window_icon_available)\n        {\n            TextureManager::Get().GetOverlayIconTextureInfo(data, b_size, b_uv_min, b_uv_max, true);\n\n            //Downscale oversized icons\n            float icon_scale = (b_size_default.x * 0.5f) / std::max(b_size.x, b_size.y);\n\n            if (icon_scale < 1.0f)\n            {\n                b_size *= icon_scale;\n            }\n\n            ImVec2 p_min = {pos.x + (width / 2.0f) - (b_size.x / 2.0f), pos.y + (width / 2.0f) - (b_size.y / 2.0f)};\n            ImVec2 p_max = p_min;\n            p_max.x += b_size.x;\n            p_max.y += b_size.y;\n\n            ImGui::GetWindowDrawList()->AddImage(io.Fonts->TexID, p_min, p_max, b_uv_min, b_uv_max, ImGui::ColorConvertFloat4ToU32(tint_color));\n        }\n\n        return ret;\n    };\n\n    //List overlays\n    const float button_width_base = b_size_default.x + (style.ItemSpacing.x * 3.0f);\n    const ImVec2 cursor_pos_first = ImGui::GetCursorPos();\n    const ImVec2 cursor_screen_pos_first = ImGui::GetCursorScreenPos();\n\n    ImGui::PushID(\"OverlayButtons\");\n\n    const unsigned int u_overlay_count = OverlayManager::Get().GetOverlayCount();\n    for (unsigned int i = 0; i < u_overlay_count; ++i)\n    {\n        const unsigned int button_id = list_unique_ids[i];\n\n        if (drag_button_id != button_id)\n        {\n            ImGui::PushID(button_id);\n\n            //Button positioning\n            OverlayButtonState& button_state = list_unique_data[button_id];\n            const float target_x = cursor_pos_first.x + (button_width_base * i);\n\n            if (button_state.starting_x != -1.0f)\n            {\n                ImGui::SetCursorPosX(smoothstep(button_state.animation_progress, button_state.starting_x, target_x));\n\n                const float animation_step = ImGui::GetIO().DeltaTime * 6.0f;\n                button_state.animation_progress = clamp(button_state.animation_progress + animation_step, 0.0f, 1.0f);\n\n                if (button_state.animation_progress == 1.0f)\n                {\n                    button_state.starting_x = -1.0f;\n                    button_state.animation_progress = 0.0f;\n                }\n            }\n            else\n            {\n                ImGui::SetCursorPosX(target_x);\n            }\n\n            //Overlay button widget\n            if (overlay_button(i))\n            {\n                if (io.MouseDownDurationPrev[ImGuiMouseButton_Left] < 3.0f) //Don't do normal button behavior after reset was just triggered\n                {\n                    if ((m_OverlayButtonActiveMenuID != i) && (!m_IsDraggingOverlayButtons))\n                    {\n                        HideMenus();\n                        m_OverlayButtonActiveMenuID = i;\n                    }\n                    else\n                    {\n                        HideMenus();\n                    }\n                }\n            }\n\n            //-Additional button behavior\n            bool button_active = ImGui::IsItemActive();\n            ImVec2 pos         = ImGui::GetItemRectMin();\n            float width        = ImGui::GetItemRectSize().x;\n\n            //Reset transform when holding the button for 3 or more seconds\n            bool show_hold_message = false;\n\n            if ( (button_active) && (!m_IsDraggingOverlayButtons) )\n            {\n                if (io.MouseDownDuration[ImGuiMouseButton_Left] > 3.0f)\n                {\n                    FloatingWindow& overlay_properties = UIManager::Get()->GetOverlayPropertiesWindow();\n                    overlay_properties.SetPinned(false);\n                    overlay_properties.ResetTransformAll();\n                    io.MouseDown[ImGuiMouseButton_Left] = false;    //Release mouse button so transform changes don't get blocked\n                }\n                else if (io.MouseDownDuration[ImGuiMouseButton_Left] > 0.5f)\n                {\n                    show_hold_message = true;\n                }\n            }\n\n            if (ImGui::IsItemHovered())\n            {\n                //Quick toggle visibility via double-click\n                const int click_count = ImGui::GetMouseClickedCount(ImGuiMouseButton_Left);\n                if ((click_count > 1) && (click_count % 2 == 0))     //ImGui keeps counting up, so fast double-clicks in a row don't get detected as such with ImGui::IsMouseDoubleClicked()\n                {\n                    OverlayConfigData& data = OverlayManager::Get().GetConfigData(i);\n                    bool& is_enabled = data.ConfigBool[configid_bool_overlay_enabled];\n\n                    is_enabled = !is_enabled;\n\n                    IPCManager::Get().PostConfigMessageToDashboardApp(configid_int_state_overlay_current_id_override, i);\n                    IPCManager::Get().PostConfigMessageToDashboardApp(configid_bool_overlay_enabled, is_enabled);\n                    IPCManager::Get().PostConfigMessageToDashboardApp(configid_int_state_overlay_current_id_override, -1);\n                }\n\n                //Quick switch properties via right-click\n                if (ImGui::IsMouseClicked(ImGuiMouseButton_Right))\n                {\n                    right_down_overlay_id = i;\n                }\n\n                if ((ImGui::IsMouseReleased(ImGuiMouseButton_Right)) && (right_down_overlay_id == i))\n                {\n                    WindowOverlayProperties& properties_window = UIManager::Get()->GetOverlayPropertiesWindow();\n\n                    //Hide window instead if it's already open for this overlay\n                    if ((properties_window.IsVisible()) && (properties_window.GetActiveOverlayID() == i))\n                    {\n                        properties_window.Hide();\n                    }\n                    else\n                    {\n                        properties_window.SetActiveOverlayID(i);\n                        properties_window.Show();\n                    }\n\n                    HideMenus();\n                }\n            }\n\n            //Remember hovered overlay for highlighting\n            if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenBlockedByActiveItem))\n            {\n                hovered_overlay_id = i;\n            }\n\n            //Tooltip, but don't show while dragging or animating position\n            if ((!m_IsDraggingOverlayButtons) && (button_state.starting_x == -1.0f))\n            {\n                (show_hold_message) ? DisplayTooltipIfHovered(TranslationManager::GetString(tstr_OverlayBarTooltipResetHold)) : \n                                      DisplayTooltipIfHovered(OverlayManager::Get().GetConfigData(i).ConfigNameStr.c_str(), i);\n            }\n\n            //Button menu\n            if (m_OverlayButtonActiveMenuID == i)\n            {\n                float dist = width / 2.0f;\n                float menu_y = m_Pos.y + ImGui::GetStyle().WindowBorderSize + dist - (dist * m_MenuAlpha);\n\n                MenuOverlayButton(i, {pos.x + width / 2.0f, menu_y}, button_active);\n\n                //Check if menu modified overlay count and bail then\n                if (OverlayManager::Get().GetOverlayCount() != u_overlay_count)\n                {\n                    ImGui::PopID();\n                    UIManager::Get()->RepeatFrame();\n                    break;\n                }\n            }\n\n            //Dragging start\n            if (button_active)\n            {\n                if (!m_IsDraggingOverlayButtons)\n                {\n                    //Record cursor offset at the time of clicking as we require a large drag delta to avoid mis-inputs when trying to just press the button with a shaky pointer\n                    //The button will be animated to snap to this offset\n                    if (ImGui::IsMouseClicked(ImGuiMouseButton_Left))\n                    {\n                        drag_mouse_offset = -(ImGui::GetMousePos().x - ImGui::GetItemRectMin().x);\n                    }\n\n                    //Not using ImGui::IsMouseDragging() here to only check for horizontal dragging\n                    if (fabs(ImGui::GetMouseDragDelta().x) > width / 2.0f)\n                    {\n                        drag_button_id = button_id;\n                        drag_overlay_id = i;\n                        m_IsDraggingOverlayButtons = true;\n\n                        //Set starting drag state for animating the button snapping to the mouse offset posiiton\n                        OverlayButtonState& drag_button_state = list_unique_data[list_unique_ids[drag_overlay_id]];\n                        const float target_x = ImGui::GetMousePos().x + drag_mouse_offset - ImGui::GetMouseDragDelta().x;\n                        if (drag_button_state.starting_x != -1.0f)   //account for ongoing animation even if it's really quick\n                        {\n                            drag_button_state.starting_x = smoothstep(drag_button_state.animation_progress, drag_button_state.starting_x, target_x);\n                        }\n                        else\n                        {\n                            drag_button_state.starting_x = target_x;\n                        }\n\n                        //This state is only used to avoid flickering when dragging right at the button edge since in Dear ImGui a held-down button doesn't stay visually down if the cursor leaves it\n                        //It's going to highlight the wrong buttons if it stays set during the drag however, so we already clear it here\n                        left_down_overlay_id = k_ulOverlayID_None;\n\n                        UIManager::Get()->RepeatFrame();\n                    }\n                    else\n                    {\n                        left_down_overlay_id = i;\n                    }\n                }\n            }\n\n            ImGui::SameLine();\n            ImGui::PopID();\n        }\n    }\n\n    if (m_IsDraggingOverlayButtons)\n    {\n        ImGui::PushID(drag_button_id);\n\n        //Add active dragged button separately so it's always on top of the other ones\n        OverlayButtonState& drag_button_state = list_unique_data[list_unique_ids[drag_overlay_id]];\n        const float target_x_drag = ImGui::GetMousePos().x + drag_mouse_offset;\n\n        //Animate the button snapping from its initial positon into the mouse position with drag offset\n        float drag_button_x = -1.0f;\n        if (drag_button_state.starting_x != -1.0f)\n        {\n            drag_button_x = smoothstep(drag_button_state.animation_progress, drag_button_state.starting_x, target_x_drag);\n\n            const float animation_step = ImGui::GetIO().DeltaTime * 8.0f;\n            drag_button_state.animation_progress = clamp(drag_button_state.animation_progress + animation_step, 0.0f, 1.0f);\n\n            if (drag_button_state.animation_progress == 1.0f)\n            {\n                drag_button_state.starting_x = -1.0f;\n                drag_button_state.animation_progress = 0.0f;\n            }\n        }\n        else\n        {\n            drag_button_x = target_x_drag;\n        }\n\n        drag_button_x = clamp(drag_button_x, cursor_screen_pos_first.x, cursor_screen_pos_first.x + (button_width_base * (u_overlay_count - 1) ));\n\n        //Overlay button widget\n        ImGui::SetCursorScreenPos({drag_button_x, ImGui::GetCursorScreenPos().y});\n        ImGui::SetNextItemAllowOverlap();\n\n        overlay_button(drag_overlay_id);\n        ImGui::SameLine();\n\n        //Button & overlay swapping\n        unsigned int index_swap = (unsigned int)( ((drag_button_x - cursor_screen_pos_first.x) + (button_width_base / 2.0f)) / button_width_base );\n        if (drag_overlay_id != index_swap)\n        {\n            //Animate swap\n            OverlayButtonState& button_state_swap = list_unique_data[list_unique_ids[index_swap]];\n            const float target_x = cursor_pos_first.x + (button_width_base * index_swap);\n            if (button_state_swap.starting_x != -1.0f)   //account for ongoing animation even if it's really quick\n            {\n                button_state_swap.starting_x = smoothstep(button_state_swap.animation_progress, button_state_swap.starting_x, target_x);\n            }\n            else\n            {\n                button_state_swap.starting_x = target_x;\n            }\n            button_state_swap.animation_progress = 0.0f;\n\n            //Actually swap the overlay\n            OverlayManager::Get().SwapOverlays(drag_overlay_id, index_swap);\n            IPCManager::Get().PostConfigMessageToDashboardApp(configid_int_state_overlay_current_id_override, drag_overlay_id);\n            IPCManager::Get().PostMessageToDashboardApp(ipcmsg_action, ipcact_overlay_swap, index_swap);\n            IPCManager::Get().PostConfigMessageToDashboardApp(configid_int_state_overlay_current_id_override, -1);\n\n            std::iter_swap(list_unique_ids.begin() + drag_overlay_id, list_unique_ids.begin() + index_swap);\n\n            //Also adjust the active properties window if we just swapped that\n            if (properties_active_overlay == drag_overlay_id)\n            {\n                UIManager::Get()->GetOverlayPropertiesWindow().SetActiveOverlayID(index_swap, true);\n            }\n            else if (properties_active_overlay == index_swap)\n            {\n                UIManager::Get()->GetOverlayPropertiesWindow().SetActiveOverlayID(drag_overlay_id, true);\n            }\n\n            drag_overlay_id = index_swap;\n            m_OverlayButtonActiveMenuID = k_ulOverlayID_None;\n\n            UIManager::Get()->RepeatFrame();\n        }\n\n        //Dragging release\n        if (ImGui::IsMouseReleased(ImGuiMouseButton_Left))\n        {\n            drag_button_state.starting_x = drag_button_x - ImGui::GetWindowPos().x + ImGui::GetScrollX();\n            drag_button_state.animation_progress = 0.0f;\n\n            drag_button_id = k_ulOverlayID_None;\n            drag_overlay_id = k_ulOverlayID_None;\n            m_IsDraggingOverlayButtons = false;\n        }\n\n        ImGui::PopID();\n    }\n\n    if (ImGui::IsMouseReleased(ImGuiMouseButton_Left))\n    {\n        left_down_overlay_id = k_ulOverlayID_None;\n    }\n\n    if (ImGui::IsMouseReleased(ImGuiMouseButton_Right))\n    {\n        right_down_overlay_id = k_ulOverlayID_None;\n    }\n\n    //Don't change overlay highlight while mouse down as it won't be correct while dragging and flicker just before it\n    if ( (!ImGui::IsMouseDown(ImGuiMouseButton_Left)) && ((hovered_overlay_id_last != k_ulOverlayID_None) || (hovered_overlay_id != k_ulOverlayID_None)) )\n    {\n        UIManager::Get()->HighlightOverlay(hovered_overlay_id);\n        hovered_overlay_id_last = hovered_overlay_id;\n    }\n\n    //Always leave cursor where the last unmoved button would've left it (leaves blank space during during drags if necessary)\n    ImGui::SetCursorPosX(cursor_pos_first.x + (button_width_base * u_overlay_count));\n\n    ImGui::PopID();\n}\n\nvoid WindowOverlayBar::MenuOverlayButton(unsigned int overlay_id, ImVec2 pos, bool is_item_active)\n{\n    m_MenuAlpha += ImGui::GetIO().DeltaTime * 12.0f;\n\n    if (m_MenuAlpha > 1.0f)\n        m_MenuAlpha = 1.0f;\n\n    ImGui::PushStyleVar(ImGuiStyleVar_Alpha, m_MenuAlpha);\n    ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 0.0f);\n    if (ImGui::Begin(\"OverlayButtonMenu\", nullptr, ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoSavedSettings | \n                                                   ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoFocusOnAppearing | ImGuiWindowFlags_NoBringToFrontOnFocus))\n    {\n        if ( (!ImGui::IsWindowHovered(ImGuiHoveredFlags_AllowWhenBlockedByActiveItem)) && (ImGui::IsAnyMouseClicked()) && (!is_item_active) )\n        {\n            HideMenus();\n        }\n\n        bool& is_enabled = OverlayManager::Get().GetConfigData(overlay_id).ConfigBool[configid_bool_overlay_enabled];\n        WindowOverlayProperties& properties_window = UIManager::Get()->GetOverlayPropertiesWindow();\n\n        if (ImGui::Selectable(TranslationManager::GetString((is_enabled) ? tstr_OverlayBarOvrlHide : tstr_OverlayBarOvrlShow), false))\n        {\n            is_enabled = !is_enabled;\n\n            IPCManager::Get().PostConfigMessageToDashboardApp(configid_int_state_overlay_current_id_override, (int)overlay_id);\n            IPCManager::Get().PostConfigMessageToDashboardApp(configid_bool_overlay_enabled, is_enabled);\n            IPCManager::Get().PostConfigMessageToDashboardApp(configid_int_state_overlay_current_id_override, -1);\n\n            HideMenus();\n        }\n\n        if (ImGui::Selectable(TranslationManager::GetString(tstr_OverlayBarOvrlClone), false))\n        {\n            //Copy data of overlay and add a new one based on it\n            OverlayManager::Get().DuplicateOverlay(OverlayManager::Get().GetConfigData(overlay_id), overlay_id);\n            IPCManager::Get().PostMessageToDashboardApp(ipcmsg_action, ipcact_overlay_duplicate, (int)overlay_id);\n\n            HideMenus();\n        }\n\n        if (m_IsMenuRemoveConfirmationVisible)\n        {\n            if (ImGui::Selectable(TranslationManager::GetString(tstr_OverlayBarOvrlRemoveConfirm), false))\n            {\n                OverlayManager::Get().RemoveOverlay(overlay_id);\n                IPCManager::Get().PostMessageToDashboardApp(ipcmsg_action, ipcact_overlay_remove, overlay_id);\n\n                //Hide properties window if it's open for this overlay\n                if (properties_window.GetActiveOverlayID() == overlay_id)\n                {\n                    properties_window.SetActiveOverlayID(k_ulOverlayID_None, true);\n                    properties_window.HideAll();\n                }\n                else if (properties_window.GetActiveOverlayID() > overlay_id) //Adjust properties window active overlay ID if it's open for an overlay that had its ID shifted\n                {\n                    properties_window.SetActiveOverlayID(properties_window.GetActiveOverlayID() - 1, true);\n                }\n\n                HideMenus();\n            }\n        }\n        else\n        {\n            if (ImGui::Selectable(TranslationManager::GetString(tstr_OverlayBarOvrlRemove), false))\n            {\n                m_IsMenuRemoveConfirmationVisible = true;\n            }\n        }\n\n\n        if (ImGui::Selectable(TranslationManager::GetString(tstr_OverlayBarOvrlProperties), false))\n        {\n            //Hide window instead if it's already open for this overlay\n            if ((properties_window.IsVisible()) && (properties_window.GetActiveOverlayID() == overlay_id))\n            {\n                properties_window.Hide();\n            }\n            else\n            {\n                properties_window.SetActiveOverlayID(overlay_id);\n                properties_window.Show();\n            }\n\n            HideMenus();\n        }\n\n        //Position window while clamping to overlay bar size\n        ImVec2 window_size = ImGui::GetWindowSize();\n        pos.x = clamp(pos.x - (window_size.x / 2.0f), m_Pos.x, m_Pos.x + m_Size.x - window_size.x);\n        pos.y -= window_size.y;\n\n        ImGui::SetWindowPos(pos);\n\n        if (ImGui::IsWindowAppearing())\n        {\n            //We need valid window size for positioning (can't use ImGui::SetNextWindowPos() because of clamping), so reset things and repeat frame if don't have it yet\n            UIManager::Get()->RepeatFrame();\n            m_MenuAlpha = 0.0f;\n        }\n\n    }\n\n    ImGui::PopStyleVar();\n    ImGui::PopStyleVar();\n\n    ImGui::End();\n}\n\nvoid WindowOverlayBar::MenuAddOverlayButton(ImVec2 pos, bool is_item_active)\n{\n    m_MenuAlpha += ImGui::GetIO().DeltaTime * 12.0f;\n\n    if (m_MenuAlpha > 1.0f)\n        m_MenuAlpha = 1.0f;\n\n    ImGui::PushStyleVar(ImGuiStyleVar_Alpha, m_MenuAlpha);\n    ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 0.0f);\n    ImGui::SetNextWindowSizeConstraints({-1.0f, 0.0f}, {-1.0f, (ImGui::GetTextLineHeightWithSpacing() * 6.0f) + (ImGui::GetStyle().WindowPadding.y * 2.0f) });\n    if (ImGui::Begin(\"AddOverlayButtonMenu\", nullptr, ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoSavedSettings | \n                                                      ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoFocusOnAppearing | ImGuiWindowFlags_NoBringToFrontOnFocus))\n    {\n        if ( (!ImGui::IsWindowHovered(ImGuiHoveredFlags_AllowWhenBlockedByActiveItem)) && (ImGui::IsAnyMouseClicked()) && (!is_item_active) )\n        {\n            HideMenus();\n        }\n\n        int desktop_count = ConfigManager::GetValue(configid_int_state_interface_desktop_count);\n\n        int new_overlay_desktop_id = -255;\n\n        for (int i = 0; i < desktop_count; ++i)\n        {\n            ImGui::PushID(i);\n\n            ImGui::Selectable(TranslationManager::Get().GetDesktopIDString(i));\n\n            if (ImGui::IsItemActivated())\n            {\n                new_overlay_desktop_id = i;\n            }\n\n            ImGui::PopID();\n        }\n\n        if ( (DPWinRT_IsCaptureSupported()) && (ImGui::Selectable(TranslationManager::GetString(tstr_OverlayBarOvrlAddWindow))) )\n        {\n            //Get current pointer transform and set window transform from it\n            if (UIManager::Get()->IsOpenVRLoaded())\n            {\n                vr::TrackedDeviceIndex_t device_index = ConfigManager::Get().GetPrimaryLaserPointerDevice();\n\n                //If no dashboard device, try finding one\n                if (device_index == vr::k_unTrackedDeviceIndexInvalid)\n                {\n                    device_index = vr::IVROverlayEx::FindPointerDeviceForOverlay(UIManager::Get()->GetOverlayHandleOverlayBar());\n                }\n\n                Matrix4 overlay_transform;\n                vr::TrackingUniverseOrigin universe_origin = vr::TrackingUniverseStanding;\n\n                vr::VROverlayIntersectionResults_t results;\n\n                if (vr::IVROverlayEx::ComputeOverlayIntersectionForDevice(UIManager::Get()->GetOverlayHandleOverlayBar(), device_index, vr::TrackingUniverseStanding, &results))\n                {\n                    overlay_transform.setTranslation(results.vPoint);\n                }\n                else //Shouldn't happen, but have some fallback\n                {\n                    vr::HmdMatrix34_t transform;\n                    vr::VROverlay()->GetOverlayTransformAbsolute(UIManager::Get()->GetOverlayHandleOverlayBar(), &universe_origin, &transform);\n\n                    overlay_transform = transform;\n                }\n\n                //Get devices poses\n                vr::TrackedDevicePose_t poses[vr::k_unMaxTrackedDeviceCount];\n                vr::VRSystem()->GetDeviceToAbsoluteTrackingPose(universe_origin, vr::IVRSystemEx::GetTimeNowToPhotons(), poses, vr::k_unMaxTrackedDeviceCount);\n\n                if (poses[vr::k_unTrackedDeviceIndex_Hmd].bPoseIsValid)\n                {\n                    //Take the average between HMD and controller position (at controller's height) and rotate towards that\n                    Matrix4 mat_hmd(poses[vr::k_unTrackedDeviceIndex_Hmd].mDeviceToAbsoluteTracking);\n                    Vector3 pos = mat_hmd.getTranslation();\n\n                    if ( (device_index < vr::k_unMaxTrackedDeviceCount) && (poses[device_index].bPoseIsValid) ) //If pointer doesn't have a pose, it falls back to rotating to HMD\n                    {\n                        Matrix4 mat_controller(poses[device_index].mDeviceToAbsoluteTracking);\n                        pos = mat_controller.getTranslation();\n                        pos.x += mat_hmd.getTranslation().x;\n                        pos.x /= 2.0f;\n                        pos.z += mat_hmd.getTranslation().z;\n                        pos.z /= 2.0f;\n                    }\n\n                    vr::IVRSystemEx::TransformLookAt(overlay_transform, pos);\n                }\n\n                UIManager::Get()->GetAuxUI().GetCaptureWindowSelectWindow().SetTransform(overlay_transform);\n            }\n\n            UIManager::Get()->GetAuxUI().GetCaptureWindowSelectWindow().Show();\n            HideMenus();\n        }\n\n        ImGui::Selectable(TranslationManager::GetString(tstr_SourcePerformanceMonitor));\n\n        if (ImGui::IsItemActivated())\n        {\n            new_overlay_desktop_id = -3;\n        }\n\n        if (DPBrowserAPIClient::Get().IsBrowserAvailable())\n        {\n            ImGui::Selectable(TranslationManager::GetString(tstr_SourceBrowser));\n\n            if (ImGui::IsItemActivated())\n            {\n                new_overlay_desktop_id = -4;\n            }\n        }\n\n        //Create new overlay if desktop or UI/Browser selectables were triggered\n        if (new_overlay_desktop_id != -255)\n        {\n            vr::TrackedDeviceIndex_t device_index = ConfigManager::Get().GetPrimaryLaserPointerDevice();\n\n            //If no dashboard device, try finding one\n            if (device_index == vr::k_unTrackedDeviceIndexInvalid)\n            {\n                device_index = vr::IVROverlayEx::FindPointerDeviceForOverlay(UIManager::Get()->GetOverlayHandleOverlayBar());\n            }\n\n            float pointer_distance = 0.5f;\n\n            if (UIManager::Get()->IsOpenVRLoaded())\n            {\n                vr::VROverlayIntersectionResults_t results;\n\n                if (vr::IVROverlayEx::ComputeOverlayIntersectionForDevice(UIManager::Get()->GetOverlayHandleOverlayBar(), device_index, vr::TrackingUniverseStanding, &results))\n                {\n                    pointer_distance = results.fDistance;\n                }\n            }\n\n            //Set pointer hint in case dashboard app needs it\n            ConfigManager::SetValue(configid_int_state_laser_pointer_device_hint, (int)device_index);\n            IPCManager::Get().PostConfigMessageToDashboardApp(configid_int_state_laser_pointer_device_hint, (int)device_index);\n\n            //Choose capture source\n            OverlayCaptureSource capsource;\n\n            switch (new_overlay_desktop_id)\n            {\n                case -3: capsource = ovrl_capsource_ui;      break;\n                case -4: capsource = ovrl_capsource_browser; break;\n                default: capsource = ovrl_capsource_desktop_duplication;\n            }\n\n            //Add overlay and sent to dashboard app\n            OverlayManager::Get().AddOverlay(capsource, new_overlay_desktop_id);\n            IPCManager::Get().PostMessageToDashboardApp(ipcmsg_action, ipcact_overlay_new_drag, MAKELPARAM(new_overlay_desktop_id, int(pointer_distance * 100.0f) ));\n\n            HideMenus();\n        }\n\n        //Position window while clamping to overlay bar size\n        ImVec2 window_size = ImGui::GetWindowSize();\n        pos.x = clamp(pos.x - (window_size.x / 2.0f), m_Pos.x, m_Pos.x + m_Size.x - window_size.x);\n        pos.y -= window_size.y;\n\n        ImGui::SetWindowPos(pos);\n\n        if (ImGui::IsWindowAppearing())\n        {\n            //We need valid window size for positioning (can't use ImGui::SetNextWindowPos() because of clamping), so reset things and repeat frame if don't have it yet\n            UIManager::Get()->RepeatFrame();\n            m_MenuAlpha = 0.0f;\n        }\n    }\n\n    ImGui::PopStyleVar();\n    ImGui::PopStyleVar();\n\n    ImGui::End();\n}\n\nvoid WindowOverlayBar::Show(bool skip_fade)\n{\n    m_Visible = true;\n\n    if (skip_fade)\n    {\n        m_Alpha = 1.0f;\n    }\n}\n\nvoid WindowOverlayBar::Hide(bool skip_fade)\n{\n    m_Visible = false;\n\n    if (skip_fade)\n    {\n        m_Alpha = 0.0f;\n    }\n}\n\nvoid WindowOverlayBar::HideMenus()\n{\n    m_MenuAlpha = 0.0f;\n    m_OverlayButtonActiveMenuID = k_ulOverlayID_None;\n    m_IsAddOverlayButtonActive = false;\n    m_IsMenuRemoveConfirmationVisible = false;\n\n    UIManager::Get()->RepeatFrame();\n\n    //Reset sort order if the overlay already isn't hovered anymore\n    if ( (UIManager::Get()->IsOpenVRLoaded()) && (!ConfigManager::Get().IsLaserPointerTargetOverlay(UIManager::Get()->GetOverlayHandleOverlayBar())) )\n    {\n        vr::VROverlay()->SetOverlaySortOrder(UIManager::Get()->GetOverlayHandleOverlayBar(), 0);\n    }\n}\n\nvoid WindowOverlayBar::Update()\n{\n    if ( (m_Alpha != 0.0f) || (m_Visible) )\n    {\n        const float alpha_step = ImGui::GetIO().DeltaTime * 6.0f;\n\n        //Alpha fade animation\n        m_Alpha += (m_Visible) ? alpha_step : -alpha_step;\n\n        if (m_Alpha > 1.0f)\n            m_Alpha = 1.0f;\n        else if (m_Alpha < 0.0f)\n            m_Alpha = 0.0f;\n    }\n\n    //We need to not skip on alpha 0.0 at least twice to get the real height of the bar. 32.0f is the placeholder width ImGui seems to use until then\n    if ( (m_Alpha == 0.0f) && (m_Size.x != 32.0f) )\n        return;\n\n    ImGui::PushStyleVar(ImGuiStyleVar_Alpha, m_Alpha);\n\n    ImGuiIO& io = ImGui::GetIO();\n    const ImGuiStyle& style = ImGui::GetStyle();\n\n    ImVec2 b_size, b_uv_min, b_uv_max;\n    TextureManager::Get().GetTextureInfo(tmtex_icon_settings, b_size, b_uv_min, b_uv_max);\n    //Default button size for custom actions to be the same as the settings icon so the user is able to provide oversized images without messing up the layout\n    //as well as still providing a way to change the size of text buttons by editing the settings icon's dimensions\n    ImVec2 b_size_default = b_size;\n\n    float tooltip_padding = ImGui::GetTextLineHeightWithSpacing() + (style.WindowPadding.y * 2.0f);\n    float min_width = io.DisplaySize.x * 0.50f;\n    ImGui::SetNextWindowSizeConstraints({min_width, -1.0f}, {io.DisplaySize.x * 0.95f, -1.0f});\n    ImGui::SetNextWindowPos(ImVec2(io.DisplaySize.x / 2.0f, io.DisplaySize.y - tooltip_padding), 0, ImVec2(0.5f, 1.0f));  //Center window at bottom of the overlay with space for tooltips\n\n    ImGui::Begin(\"WindowOverlayBar\", nullptr, ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoFocusOnAppearing |\n                                              ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_HorizontalScrollbar);\n\n    ImGui::HScrollWindowFromMouseWheelV();\n\n    //Scrollbar visible state can flicker for one frame when an overlay is added or removed while the bar is actually visible... no idea why, but work around it by repeating a frame\n    bool scrollbar_visible = ImGui::IsAnyScrollBarVisible();\n    if (scrollbar_visible != m_IsScrollBarVisible)\n    {\n        UIManager::Get()->RepeatFrame();\n        m_IsScrollBarVisible = ImGui::IsAnyScrollBarVisible();\n    }\n\n    ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 4.0f);\n    ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, {style.ItemSpacing.y, style.ItemSpacing.y});\n    ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.0f, 0.0f, 0.0f, 0.0f));\n\n    UpdateOverlayButtons();\n\n    static float right_buttons_width = 0.0f;\n\n    float free_width = min_width - ImGui::GetCursorPosX() - right_buttons_width;\n    if (free_width > 0)\n    {\n        ImGui::Dummy({free_width, 0.0f});\n        ImGui::SameLine(0.0f, 0.0f);\n    }\n\n    //Add Overlay Button\n    {\n        if (!UIManager::Get()->IsOpenVRLoaded())        //Add Overlay stuff doesn't work without OpenVR loaded, so disable it\n            ImGui::PushItemDisabled();\n\n        bool is_add_overlay_active = m_IsAddOverlayButtonActive;\n        if (is_add_overlay_active)\n            ImGui::PushStyleColor(ImGuiCol_Button, ImGui::GetStyleColorVec4(ImGuiCol_ButtonActive));\n\n        TextureManager::Get().GetTextureInfo(tmtex_icon_add, b_size, b_uv_min, b_uv_max);\n        if (ImGui::ImageButton(\"Add Overlay\", io.Fonts->TexID, b_size_default, b_uv_min, b_uv_max, ImVec4(0.0f, 0.0f, 0.0f, 0.0f)))\n        {\n            if (!m_IsAddOverlayButtonActive)\n            {\n                HideMenus();\n                m_IsAddOverlayButtonActive = true;\n            }\n            else\n            {\n                HideMenus();\n            }\n        }\n\n        if (is_add_overlay_active)\n            ImGui::PopStyleColor(); //ImGuiCol_Button\n\n        bool button_active = ImGui::IsItemActive();\n        ImVec2 pos = ImGui::GetItemRectMin();\n        float width = ImGui::GetItemRectSize().x;\n\n        DisplayTooltipIfHovered(TranslationManager::GetString(tstr_OverlayBarTooltipOvrlAdd));\n\n        if (m_IsAddOverlayButtonActive)\n        {\n            float dist   = width / 2.0f;\n            float menu_y = m_Pos.y + style.WindowBorderSize + dist - (dist * m_MenuAlpha);\n\n            MenuAddOverlayButton({pos.x + width / 2.0f, menu_y}, button_active);\n        }\n\n        if (!UIManager::Get()->IsOpenVRLoaded())\n            ImGui::PopItemDisabled();\n    }\n\n    ImGui::SameLine();\n\n    //Action Buttons, if any\n    if (!ConfigManager::Get().GetActionManager().GetActionOrderListOverlayBar().empty())\n    {\n        UIManager::Get()->GetFloatingUI().GetActionBarWindow().UpdateActionButtons(k_ulOverlayID_None);\n    }\n\n    //Settings Button\n    bool settings_shown = UIManager::Get()->GetSettingsWindow().IsVisible();\n    if (settings_shown)\n        ImGui::PushStyleColor(ImGuiCol_Button, ImGui::GetStyleColorVec4(ImGuiCol_ButtonActive));\n\n    TextureManager::Get().GetTextureInfo(tmtex_icon_settings, b_size, b_uv_min, b_uv_max);\n    if (ImGui::ImageButton(\"Settings\", io.Fonts->TexID, b_size, b_uv_min, b_uv_max, ImVec4(0.0f, 0.0f, 0.0f, 0.0f)))\n    {\n        if (io.MouseDownDurationPrev[ImGuiMouseButton_Left] < 3.0f) //Don't do normal button behavior after reset was just triggered\n        {\n            FloatingWindow& floating_settings = UIManager::Get()->GetSettingsWindow();\n            (floating_settings.IsVisible()) ? floating_settings.Hide() : floating_settings.Show();\n        }\n    }\n\n    //Reset tranform when holding the button for 3 or more seconds\n    bool show_hold_message = false;\n\n    if (ImGui::IsItemActive())  \n    {\n        if (io.MouseDownDuration[ImGuiMouseButton_Left] > 3.0f)\n        {\n            FloatingWindow& floating_settings = UIManager::Get()->GetSettingsWindow();\n            floating_settings.SetPinned(false);\n            floating_settings.ResetTransformAll();\n            io.MouseDown[ImGuiMouseButton_Left] = false;    //Release mouse button so transform changes don't get blocked\n        }\n        else if (io.MouseDownDuration[ImGuiMouseButton_Left] > 0.5f)\n        {\n            show_hold_message = true;\n        }\n    }\n\n    if (settings_shown)\n        ImGui::PopStyleColor(); //ImGuiCol_Button\n\n    //Warning/Error marker\n    if (UIManager::Get()->IsAnyWarningDisplayed())\n    {\n        ImVec2 p_max = {ImGui::GetItemRectMax().x - style.ItemInnerSpacing.x, ImGui::GetItemRectMin().y + style.ItemInnerSpacing.y};\n        ImVec2 p_min = p_max;\n        p_min.x -= ImGui::CalcTextSize(k_pch_bold_exclamation_mark).x;\n        p_max.y += ImGui::GetTextLineHeight();\n\n        ImGui::GetWindowDrawList()->AddRectFilled(p_min, p_max, ImGui::GetColorU32(Style_ImGuiCol_TextError), style.WindowRounding);\n        ImGui::GetWindowDrawList()->AddText(p_min, ImGui::GetColorU32(ImGui::GetStyleColorVec4(ImGuiCol_Text)), k_pch_bold_exclamation_mark);\n    }\n\n    right_buttons_width = (ImGui::GetItemRectSize().x * 2.0f) + style.ItemSpacing.x;\n\n    DisplayTooltipIfHovered( TranslationManager::GetString((show_hold_message) ? tstr_OverlayBarTooltipResetHold : tstr_OverlayBarTooltipSettings) );\n\n    ImGui::PopStyleColor(); //ImGuiCol_Button\n    ImGui::PopStyleVar();   //ImGuiStyleVar_ItemSpacing\n    ImGui::PopStyleVar();   //ImGuiStyleVar_FrameRounding\n\n    m_Pos  = ImGui::GetWindowPos();\n    m_Size = ImGui::GetWindowSize();\n\n    ImGui::End();\n    ImGui::PopStyleVar(); //ImGuiStyleVar_Alpha\n}\n\nconst ImVec2 & WindowOverlayBar::GetPos() const\n{\n    return m_Pos;\n}\n\nconst ImVec2 & WindowOverlayBar::GetSize() const\n{\n    return m_Size;\n}\n\nbool WindowOverlayBar::IsVisible() const\n{\n    return m_Visible;\n}\n\nbool WindowOverlayBar::IsVisibleOrFading() const\n{\n    return ( (m_Visible) || (m_Alpha != 0.0f) );\n}\n\nbool WindowOverlayBar::IsAnyMenuVisible() const\n{\n    return (m_MenuAlpha != 0.0f);\n}\n\nbool WindowOverlayBar::IsScrollBarVisible() const\n{\n    return m_IsScrollBarVisible;\n}\n\nbool WindowOverlayBar::IsDraggingOverlayButtons() const\n{\n    return m_IsDraggingOverlayButtons;\n}\n\nfloat WindowOverlayBar::GetAlpha() const\n{\n    return m_Alpha;\n}\n"
  },
  {
    "path": "src/DesktopPlusUI/WindowOverlayBar.h",
    "content": "#pragma once\n\n#include \"imgui.h\"\n#include \"OverlayManager.h\"\n\nclass WindowSettings;\n\n//The bar visible below the dashboard, containing overlay buttons and access to the settings window\nclass WindowOverlayBar\n{\n    private:\n        ImVec2 m_Pos;\n        ImVec2 m_Size;\n        bool m_Visible; //This and m_Alpha default to visible state, meaning the dashboard UI, where it's always visible doesn't need to call Show()\n        float m_Alpha;\n        bool m_IsScrollBarVisible;\n\n        unsigned int m_OverlayButtonActiveMenuID;\n        bool m_IsAddOverlayButtonActive;\n        float m_MenuAlpha;\n        bool m_IsMenuRemoveConfirmationVisible;\n        bool m_IsDraggingOverlayButtons;\n\n        void DisplayTooltipIfHovered(const char* text, unsigned int overlay_id = k_ulOverlayID_None);\n        void UpdateOverlayButtons();\n        void MenuOverlayButton(unsigned int overlay_id, ImVec2 pos, bool is_item_active);\n        void MenuAddOverlayButton(ImVec2 pos, bool is_item_active);\n\n    public:\n        WindowOverlayBar();\n\n        void Show(bool skip_fade = false);\n        void Hide(bool skip_fade = false);\n        void HideMenus();\n        void Update();\n\n        const ImVec2& GetPos() const;\n        const ImVec2& GetSize() const;\n\n        bool IsVisible() const;\n        bool IsVisibleOrFading() const;\n        bool IsAnyMenuVisible() const;\n        bool IsScrollBarVisible() const;\n        bool IsDraggingOverlayButtons() const;\n\n        float GetAlpha() const;\n};"
  },
  {
    "path": "src/DesktopPlusUI/WindowOverlayProperties.cpp",
    "content": "#include \"WindowOverlayProperties.h\"\n\n#include \"ImGuiExt.h\"\n#include \"UIManager.h\"\n#include \"TranslationManager.h\"\n#include \"InterprocessMessaging.h\"\n#include \"WindowManager.h\"\n#include \"Util.h\"\n#include \"OpenVRExt.h\"\n#include \"DesktopPlusWinRT.h\"\n#include \"DPBrowserAPIClient.h\"\n\n#include <sstream>\n\nWindowOverlayProperties::WindowOverlayProperties() :\n    m_PageStackPos(0),\n    m_PageStackPosAnimation(0),\n    m_PageAnimationDir(0),\n    m_PageAnimationProgress(0.0f),\n    m_PageAnimationStartPos(0.0f),\n    m_PageAnimationOffset(0.0f),\n    m_PageFadeDir(0),\n    m_PageFadeAlpha(1.0f),\n    m_PageAppearing(wndovrlprop_page_none),\n    m_PageReturned(wndovrlprop_page_none),\n    m_ActiveOverlayID(k_ulOverlayID_None),\n    m_Column0Width(0.0f),\n    m_IsConfigDataModified(false),\n    m_OriginHMDFloorSettingsAnimationProgress(0.0f),\n    m_OriginHMDSettingsAnimationProgress(0.0f),\n    m_OriginTheaterScreenSettingsAnimationProgress(0.0f),\n    m_PerfMonStyleCheckboxAnimationProgress(0.0f),\n    m_BufferOverlayName{0},\n    m_IsBrowserURLChanged(false)\n{\n    m_WindowIcon = tmtex_icon_xsmall_settings;\n    m_OvrlWidth    = OVERLAY_WIDTH_METERS_SETTINGS;\n    m_OvrlWidthMax = OVERLAY_WIDTH_METERS_SETTINGS * 3.0f;\n\n    //Leave 2 pixel padding around so interpolation doesn't cut off the pixel border\n    const DPRect& rect = UITextureSpaces::Get().GetRect(ui_texspace_overlay_properties);\n    m_Size = {float(rect.GetWidth() - 4), float(rect.GetHeight() - 4)};\n    m_SizeUnscaled = m_Size;\n\n    m_Pos = {float(rect.GetTL().x + 2), float(rect.GetTL().y + 2)};\n\n    m_PageStack.push_back(wndovrlprop_page_main);\n\n    FloatingWindow::ResetTransformAll();\n}\n\nvoid WindowOverlayProperties::Show(bool skip_fade)\n{\n    UIManager::Get()->UpdateDesktopOverlayPixelSize();\n    SetActiveOverlayID(m_ActiveOverlayID, true);        //ensure that current overlay ID is still in sync with dashboard app (can desync if dashboard app was restarted)\n    FloatingWindow::Show(skip_fade);\n}\n\nvoid WindowOverlayProperties::Hide(bool skip_fade)\n{\n    FloatingWindow::Hide(skip_fade);\n\n    //When hiding while the position change page is open, sync transform\n    if (m_PageStack.back() == wndovrlprop_page_position_change)\n    {\n        IPCManager::Get().PostMessageToDashboardApp(ipcmsg_action, ipcact_overlay_transform_sync, (int)m_ActiveOverlayID);\n    }\n}\n\nvoid WindowOverlayProperties::ResetTransform(FloatingWindowOverlayStateID state_id)\n{\n    FloatingWindow::ResetTransform(state_id);\n\n    FloatingWindowOverlayState& overlay_state = GetOverlayState(state_id);\n\n    overlay_state.Transform.rotateY(15.0f);\n    overlay_state.Transform.translate_relative(-OVERLAY_WIDTH_METERS_DASHBOARD_UI / 3.0f, 0.70f, 0.15f);\n}\n\nvr::VROverlayHandle_t WindowOverlayProperties::GetOverlayHandle() const\n{\n    return UIManager::Get()->GetOverlayHandleOverlayProperties();\n}\n\nvoid WindowOverlayProperties::ApplyUIScale()\n{\n    FloatingWindow::ApplyUIScale();\n\n    m_CachedSizes = {};\n}\n\nunsigned int WindowOverlayProperties::GetActiveOverlayID() const\n{\n    return m_ActiveOverlayID;\n}\n\nvoid WindowOverlayProperties::SetActiveOverlayID(unsigned int overlay_id, bool skip_fade)\n{\n    //This needs to cancel any active page state for the previous overlay if it's not the same\n    if ( (!skip_fade) && (m_ActiveOverlayID != overlay_id) && (m_ActiveOverlayID != k_ulOverlayID_None) )\n    {\n        //Animate overlay switching by fading out first if the window is visible\n        if (m_PageFadeDir == 0)\n        {\n            PageFadeStart(overlay_id);\n            return;\n        }\n\n        PageGoHome(true);\n    }\n\n    //These need to always reset in case of underlying changes or even just language switching\n    m_CropButtonLabel = \"\";\n    m_WinRTSourceButtonLabel = \"\";\n    m_ActionButtonsLabel = \"\";\n    m_BrowserMaxFPSValueText = \"\";\n    MarkBrowserURLChanged();\n    m_CachedSizes = {};\n\n    m_ActiveOverlayID = overlay_id;\n\n    OverlayManager::Get().SetCurrentOverlayID(overlay_id);\n\n    //k_ulOverlayID_None is used when fading out an overlay that got deleted, so keep old info around while fading out\n    if (overlay_id != k_ulOverlayID_None)\n    {\n        OverlayConfigData& data = OverlayManager::Get().GetCurrentConfigData();\n\n        m_WindowTitle = data.ConfigNameStr;\n\n        //Update overlay name buffer\n        if (data.ConfigBool[configid_bool_overlay_name_custom])\n        {\n            size_t copied_length = m_WindowTitle.copy(m_BufferOverlayName, IM_ARRAYSIZE(m_BufferOverlayName) - 1);\n            m_BufferOverlayName[copied_length] = '\\0';\n        }\n        else\n        {\n            m_BufferOverlayName[0] = '\\0';\n        }\n\n        //Update tags buffer\n        size_t copied_length = data.ConfigStr[configid_str_overlay_tags].copy(m_BufferOverlayTags, IM_ARRAYSIZE(m_BufferOverlayTags) - 1);\n        m_BufferOverlayTags[copied_length] = '\\0';\n\n        bool has_win32_window_icon = false;\n        m_WindowIcon = TextureManager::Get().GetOverlayIconTextureID(data, true, &has_win32_window_icon);\n    \n        if (has_win32_window_icon)\n        {\n            m_WindowIconWin32IconCacheID = TextureManager::Get().GetWindowIconCacheID((HWND)data.ConfigHandle[configid_handle_overlay_state_winrt_hwnd], \n                                                                                      data.ConfigHandle[configid_handle_overlay_state_winrt_last_hicon]);\n        }\n        else\n        {\n            m_WindowIconWin32IconCacheID = -1;\n        }\n\n        m_OriginHMDFloorSettingsAnimationProgress      = (data.ConfigInt[configid_int_overlay_origin] == ovrl_origin_hmd_floor)      ? 1.0f : 0.0f;\n        m_OriginHMDSettingsAnimationProgress           = (data.ConfigInt[configid_int_overlay_origin] == ovrl_origin_hmd)            ? 1.0f : 0.0f;\n        m_OriginTheaterScreenSettingsAnimationProgress = (data.ConfigInt[configid_int_overlay_origin] == ovrl_origin_theater_screen) ? 1.0f : 0.0f;\n        m_PerfMonStyleCheckboxAnimationProgress        = (ConfigManager::GetValue(configid_bool_performance_monitor_minimal_style))  ? 1.0f : 0.0f;\n    }\n\n    IPCManager::Get().PostConfigMessageToDashboardApp(configid_int_interface_overlay_current_id, (int)overlay_id);\n\n    if (IsVisibleOrFading())\n    {\n        UIManager::Get()->GetIdleState().AddActiveTime();\n    }\n}\n\nvoid WindowOverlayProperties::UpdateDesktopMode()\n{\n    WindowUpdate();\n}\n\nconst char* WindowOverlayProperties::DesktopModeGetTitle() const\n{\n    return m_WindowTitle.c_str();\n}\n\nbool WindowOverlayProperties::DesktopModeGetIconTextureInfo(ImVec2& size, ImVec2& uv_min, ImVec2& uv_max) const\n{\n    if (m_WindowIconWin32IconCacheID == -1)\n    {\n        return TextureManager::Get().GetTextureInfo(m_WindowIcon, size, uv_min, uv_max);\n    }\n    else\n    {\n        return TextureManager::Get().GetWindowIconTextureInfo(m_WindowIconWin32IconCacheID, size, uv_min, uv_max);\n    }\n}\n\nfloat WindowOverlayProperties::DesktopModeGetTitleIconAlpha() const\n{\n    return m_TitleBarTitleIconAlpha;\n}\n\nvoid WindowOverlayProperties::DesktopModeOnTitleIconClick()\n{\n    //Toggle visibility on title icon double-click\n    const int click_count = ImGui::GetMouseClickedCount(ImGuiMouseButton_Left);\n    if ((click_count > 1) && (click_count % 2 == 0))     //ImGui keeps counting up so fast double-clicks in a row don't get detected as such with ImGui::IsMouseDoubleClicked()\n    {    \n        bool& is_enabled = ConfigManager::GetRef(configid_bool_overlay_enabled);\n        is_enabled = !is_enabled;\n\n        IPCManager::Get().PostConfigMessageToDashboardApp(configid_bool_overlay_enabled, is_enabled);\n    }\n}\n\nvoid WindowOverlayProperties::DesktopModeOnTitleBarHover(bool is_hovered)\n{\n    m_IsTitleBarHovered = is_hovered;\n}\n\nbool WindowOverlayProperties::DesktopModeGoBack()\n{\n    if (m_PageStackPos != 0)\n    {\n        PageGoBack();\n        return true;\n    }\n\n    return false;\n}\n\nvoid WindowOverlayProperties::MarkBrowserURLChanged()\n{\n    m_IsBrowserURLChanged = true;\n}\n\nvoid WindowOverlayProperties::WindowUpdate()\n{\n    ImGui::SetWindowSize(m_Size);\n\n    //Keep window blank if no overlay is set (like when fading out from removing an overlay)\n    if (m_ActiveOverlayID == k_ulOverlayID_None)\n        return;\n\n    //Highlight overlay if title bar is hovered\n    static bool has_highlighted_overlay = false;\n    if (m_IsTitleBarHovered)\n    {\n        UIManager::Get()->HighlightOverlay(m_ActiveOverlayID);\n        has_highlighted_overlay = true;\n    }                                   //Don't remove highlight during drag (hover state is false) or mouse released state (causes one frame flicker after drag)\n    else if ( (has_highlighted_overlay) && (!UIManager::Get()->GetOverlayDragger().IsDragActive()) && (!ImGui::IsMouseReleased(ImGuiMouseButton_Left)) )\n    {\n        UIManager::Get()->HighlightOverlay(k_ulOverlayID_None);\n        has_highlighted_overlay = false;\n    }\n\n    //Set title icon/text alpha based on overlay visibility\n    m_TitleBarTitleIconAlpha = (ConfigManager::GetValue(configid_bool_overlay_enabled)) ? 1.0f : 0.5f;\n\n    //Check for double clicks if the icon is being clicked\n    if (m_IsTitleIconClicked)\n    {\n        DesktopModeOnTitleIconClick();  //Desktop mode function, but no reason to do anything differently in VR so we always use it\n    }\n\n    ImGuiStyle& style = ImGui::GetStyle();\n\n    m_Column0Width = ImGui::GetFontSize() * 12.75f;\n\n    float page_width = m_Size.x - style.WindowBorderSize - style.WindowPadding.x - style.WindowPadding.x;\n\n    //Compensate for the padding added in constructor and ignore border in desktop mode\n    if (UIManager::Get()->IsInDesktopMode())\n    {\n        page_width += 2 + style.WindowBorderSize;\n    }\n\n    //Page animation\n    if (m_PageAnimationDir != 0)\n    {\n        //Use the averaged framerate value instead of delta time for the first animation step\n        //This is to smooth over increased frame deltas that can happen when a new page needs to do initial larger computations or save/load files\n        const float progress_step = (m_PageAnimationProgress == 0.0f) ? (1.0f / ImGui::GetIO().Framerate) * 3.0f : ImGui::GetIO().DeltaTime * 3.0f;\n        m_PageAnimationProgress += progress_step;\n\n        if (m_PageAnimationProgress >= 1.0f)\n        {\n            //Remove pages in the stack after finishing going back\n            if (m_PageAnimationDir == 1)\n            {\n                while ((int)m_PageStack.size() > m_PageStackPosAnimation + 1)\n                {\n                    m_PageStack.pop_back();\n                }\n\n                m_PageAnimationDir = 0;\n\n                //Add pending pages now that we don't have an active animation\n                while (!m_PageStackPending.empty())\n                {\n                    PageGoForward(m_PageStackPending[0]);\n                    m_PageStackPending.erase(m_PageStackPending.begin());\n                }\n            }\n\n            m_PageAnimationProgress = 1.0f;\n            m_PageAnimationDir      = 0;\n        }\n    }\n    else if (m_PageStackPosAnimation != m_PageStackPos) //Only start new animation if none is running\n    {\n        m_PageAnimationDir = (m_PageStackPosAnimation < m_PageStackPos) ? -1 : 1;\n        m_PageStackPosAnimation = m_PageStackPos;\n        m_PageAnimationStartPos = m_PageAnimationOffset;\n        m_PageAnimationProgress = 0.0f;\n\n        //Set appearing value to top of stack when starting animation to it\n        if (m_PageAnimationDir == -1)\n        {\n            m_PageAppearing = m_PageStack.back();\n        }\n    }\n    else if ((m_PageStackPosAnimation == m_PageStackPos) && ((int)m_PageStack.size() > m_PageStackPos + 1))\n    {\n        //Remove pages that were added and left again while there was no chance to animate anything\n        while ((int)m_PageStack.size() > m_PageStackPos + 1)\n        {\n            m_PageStack.pop_back();\n        }\n    }\n    \n    //Set appearing value when the whole window appeared again\n    if ((m_PageAnimationDir == 0) && (ImGui::IsWindowAppearing()))\n    {\n        m_PageAppearing = m_PageStack.back();\n    }\n\n    const float target_x = (page_width + style.ItemSpacing.x) * -m_PageStackPosAnimation;\n    m_PageAnimationOffset = smoothstep(m_PageAnimationProgress, m_PageAnimationStartPos, target_x);\n\n    //Page Fade\n    ImGui::PushStyleVar(ImGuiStyleVar_Alpha, (UIManager::Get()->IsInDesktopMode()) ? m_PageFadeAlpha : m_Alpha * m_PageFadeAlpha);\n\n    if (m_PageFadeDir == -1)\n    {\n        m_PageFadeAlpha -= ImGui::GetIO().DeltaTime * 6.0f;\n\n        if (m_PageFadeAlpha <= 0.0f) //Completed fade-out\n        {\n            //Switch active overlay and fade back in\n            m_PageFadeAlpha = 0.0f;\n            m_PageFadeDir = 1;\n\n            SetActiveOverlayID(m_PageFadeTargetOverlayID); //Calls PageGoHome() among other things\n        }\n    }\n    else if (m_PageFadeDir == 1)\n    {\n        m_PageFadeAlpha += ImGui::GetIO().DeltaTime * 6.0f;\n\n        if (m_PageFadeAlpha >= 1.0f) //Completed fading back in\n        {\n            m_PageFadeAlpha = 1.0f;\n            m_PageFadeDir = 0;\n        }\n    }\n\n    //Set up page offset and clipping\n    ImGui::SetCursorPosX(ImGui::GetCursorPosX() + m_PageAnimationOffset);\n\n    ImGui::PushClipRect({m_Pos.x + style.WindowBorderSize, 0.0f}, {m_Pos.x + m_Size.x - style.WindowBorderSize, FLT_MAX}, false);\n\n    const char* const child_str_id[]{\"OvrlPropsPageMain\", \"OvrlPropsPage1\", \"OvrlPropsPage2\", \"OvrlPropsPage3\"}; //No point in generating these on the fly\n    const ImVec2 child_size = {page_width, ImGui::GetContentRegionAvail().y};\n    int child_id = 0;\n    int stack_size = (int)m_PageStack.size();\n    for (WindowOverlayPropertiesPage page_id : m_PageStack)\n    {\n        if (child_id >= IM_ARRAYSIZE(child_str_id))\n            break;\n\n        //Disable items when the page isn't active\n        const bool is_inactive_page = (child_id != m_PageStackPos);\n\n        if (is_inactive_page)\n        {\n            ImGui::PushItemDisabledNoVisual();\n        }\n\n        ImGui::PushStyleColor(ImGuiCol_ChildBg, ImVec4(0.00f, 0.00f, 0.00f, 0.00f)); //This prevents child bg color being visible if there's a widget before this\n\n        if ((ImGui::BeginChild(child_str_id[child_id], child_size, ImGuiChildFlags_NavFlattened)) || (m_PageAppearing == page_id)) //Process page if currently appearing\n        {\n            ImGui::PopStyleColor(); //ImGuiCol_ChildBg\n\n            switch (page_id)\n            {\n                case wndovrlprop_page_main:                    UpdatePageMain();                  break;\n                case wndovrlprop_page_position_change:         UpdatePagePositionChange();        break;\n                case wndovrlprop_page_crop_change:             UpdatePageCropChange();            break;\n                case wndovrlprop_page_graphics_capture_source: UpdatePageGraphicsCaptureSource(); break;\n                case wndovrlprop_page_actions_order:           UpdatePageActionsOrder();          break;\n                case wndovrlprop_page_actions_order_add:       UpdatePageActionsOrderAdd();       break;\n                default: break;\n            }\n        }\n        else\n        {\n            ImGui::PopStyleColor(); //ImGuiCol_ChildBg\n        }\n\n        if (is_inactive_page)\n        {\n            ImGui::PopItemDisabledNoVisual();\n        }\n\n        ImGui::EndChild();\n\n        if (child_id + 1 < stack_size)\n        {\n            ImGui::SameLine();\n        }\n\n        child_id++;\n    }\n\n    m_PageAppearing = wndovrlprop_page_none;\n\n    ImGui::PopClipRect();\n\n    ImGui::PopStyleVar(); //ImGuiStyleVar_Alpha\n}\n\nvoid WindowOverlayProperties::OverlayPositionReset()\n{\n    float& up      = ConfigManager::GetRef(configid_float_overlay_offset_up);\n    float& right   = ConfigManager::GetRef(configid_float_overlay_offset_right);\n    float& forward = ConfigManager::GetRef(configid_float_overlay_offset_forward);\n    up = right = forward = 0.0f;\n\n    IPCManager::Get().PostConfigMessageToDashboardApp(configid_float_overlay_offset_up,      up);\n    IPCManager::Get().PostConfigMessageToDashboardApp(configid_float_overlay_offset_right,   right);\n    IPCManager::Get().PostConfigMessageToDashboardApp(configid_float_overlay_offset_forward, forward);\n\n    IPCManager::Get().PostMessageToDashboardApp(ipcmsg_action, ipcact_overlay_position_reset);\n}\n\nvoid WindowOverlayProperties::UpdatePageMain()\n{\n    ImGui::PushStyleColor(ImGuiCol_ChildBg, ImVec4(0.00f, 0.00f, 0.00f, 0.00f));\n    ImGui::BeginChild(\"OvrlPropsMainContent\", ImVec2(0.00f, 0.00f), ImGuiChildFlags_NavFlattened);\n    ImGui::PopStyleColor();\n\n    UpdatePageMainCatPosition();\n    UpdatePageMainCatAppearance();\n    UpdatePageMainCatCapture();\n    UpdatePageMainCatPerformanceMonitor();\n    UpdatePageMainCatBrowser();\n    UpdatePageMainCatAdvanced();\n    UpdatePageMainCatPerformance();\n    UpdatePageMainCatInterface();\n\n    ImGui::EndChild();\n}\n\nvoid WindowOverlayProperties::UpdatePageMainCatPosition()\n{\n    ImGuiStyle& style = ImGui::GetStyle();\n\n    ImGui::TextColoredUnformatted(ImGui::GetStyleColorVec4(ImGuiCol_ButtonHovered), TranslationManager::GetString(tstr_OvrlPropsCatPosition));\n    ImGui::Columns(2, \"ColumnPosition\", false);\n    ImGui::SetColumnWidth(0, m_Column0Width);\n\n    //Origin\n    int& mode_origin = ConfigManager::GetRef(configid_int_overlay_origin);\n    bool& is_enabled = ConfigManager::GetRef(configid_bool_overlay_enabled);\n\n    ImGui::AlignTextToFramePadding();\n    ImGui::TextUnformatted(TranslationManager::GetString(tstr_OvrlPropsPositionOrigin));\n\n    if (mode_origin == ovrl_origin_theater_screen)\n    {\n        ImGui::SameLine(0.0f, style.ItemInnerSpacing.x);\n        HelpMarker(TranslationManager::GetString(tstr_OvrlPropsPositionOriginTheaterScreenTip));\n    }\n\n    ImGui::NextColumn();\n\n    const ImVec2 img_size_line_height = {ImGui::GetTextLineHeight(), ImGui::GetTextLineHeight()};\n    ImVec2 img_size, img_uv_min, img_uv_max;\n    const ImVec2 combo_pos = ImGui::GetCursorScreenPos();\n\n    ImGui::PushItemWidth(-1.0f);\n    if (ImGui::BeginComboAnimated(\"##ComboPositionOrigin\", \"\"))\n    {\n        static bool is_generic_tracker_connected = false;\n\n        if ((ImGui::IsWindowAppearing()) && (UIManager::Get()->IsOpenVRLoaded()))\n        {\n            is_generic_tracker_connected = (vr::IVRSystemEx::GetFirstVRTracker() != vr::k_unTrackedDeviceIndexInvalid);\n        }\n\n        //Make use of the fact origin, icon and translation string IDs are laid out sequentially and shorten this to a nice loop\n        for (int i = ovrl_origin_room; i < ovrl_origin_dplus_tab; ++i)\n        {\n            //Skip over some entries if they're not available\n            if ((i == ovrl_origin_aux) && (!is_generic_tracker_connected))\n                continue;\n\n            ImGui::PushID(i);\n\n            if ((ImGui::Selectable(\"\", (mode_origin == i))) && (mode_origin != i))\n            {\n                mode_origin = i;\n\n                //If overlay is switched to theater screen origin, disable the overlay so it doesn't enter theater mode right away and has the properties window disappear\n                if (mode_origin == ovrl_origin_theater_screen)\n                {\n                    if (is_enabled)\n                    {\n                        is_enabled = false;\n                        IPCManager::Get().PostConfigMessageToDashboardApp(configid_bool_overlay_enabled, false);\n                    }\n\n                    //Also disable Gaze Fade if it's on\n                    ConfigManager::SetValue(configid_bool_overlay_gazefade_enabled, false);\n                    IPCManager::Get().PostConfigMessageToDashboardApp(configid_bool_overlay_gazefade_enabled, false);\n\n                    //...and reset display mode to Always. It technically works in that the overlay stops updating but that's not helpful if the theater screen is showing\n                    ConfigManager::SetValue(configid_int_overlay_display_mode, ovrl_dispmode_always);\n                    IPCManager::Get().PostConfigMessageToDashboardApp(configid_int_overlay_display_mode, ovrl_dispmode_always);\n                }\n\n                IPCManager::Get().PostConfigMessageToDashboardApp(configid_int_overlay_origin, mode_origin);\n            }\n\n            ImGui::SameLine(0.0f, 0.0f);\n\n            TextureManager::Get().GetTextureInfo((TMNGRTexID)(tmtex_icon_xsmall_origin_room + i), img_size, img_uv_min, img_uv_max);\n            ImGui::Image(ImGui::GetIO().Fonts->TexID, img_size_line_height, img_uv_min, img_uv_max);\n\n            ImGui::SameLine(0.0f, style.ItemInnerSpacing.x);\n            ImGui::TextUnformatted(TranslationManager::GetString( (TRMGRStrID)(tstr_OvrlPropsPositionOriginRoom + i) ));\n\n            ImGui::PopID();\n        }\n\n        ImGui::EndCombo();\n    }\n\n    //Custom combo preview content (icon with text)\n    const ImVec2 backup_pos = ImGui::GetCursorScreenPos();\n    ImVec2 clip_end = ImGui::GetItemRectMax();\n    clip_end.x -= ImGui::GetFrameHeight();\n\n    ImGui::SetCursorScreenPos(ImVec2(combo_pos.x + style.FramePadding.x, combo_pos.y + style.FramePadding.y));\n\n    TextureManager::Get().GetTextureInfo((TMNGRTexID)(tmtex_icon_xsmall_origin_room + mode_origin), img_size, img_uv_min, img_uv_max);\n    ImGui::Image(ImGui::GetIO().Fonts->TexID, img_size_line_height, img_uv_min, img_uv_max);\n\n    ImGui::SameLine(0.0f, style.ItemInnerSpacing.x);\n    ImGui::SetCursorPosY(ImGui::GetCursorPosY());\n\n    ImGui::PushClipRect(ImGui::GetCursorScreenPos(), clip_end, true);\n    ImGui::TextUnformatted(TranslationManager::GetString( (TRMGRStrID)(tstr_OvrlPropsPositionOriginRoom + mode_origin) ));\n    ImGui::PopClipRect();\n\n    ImGui::SetCursorScreenPos(backup_pos);\n    ImGui::Columns(1);\n\n    //Origin-specific settings\n    //-Origin HMD Floor\n    ImGui::BeginCollapsingArea(\"OriginHMDFloorSettings\", (mode_origin == ovrl_origin_hmd_floor), m_OriginHMDFloorSettingsAnimationProgress);\n\n    ImGui::Columns(2, \"ColumnOriginHMDFloorSettings\", false);\n    ImGui::SetColumnWidth(0, m_Column0Width);\n\n    ImGui::NextColumn();\n\n    bool& use_turning = ConfigManager::GetRef(configid_bool_overlay_origin_hmd_floor_use_turning);\n\n    ImGui::AlignTextToFramePadding();\n    if (ImGui::Checkbox(TranslationManager::GetString(tstr_OvrlPropsPositionOriginConfigHMDXYTurning), &use_turning))\n    {\n        IPCManager::Get().PostConfigMessageToDashboardApp(configid_bool_overlay_origin_hmd_floor_use_turning, use_turning);\n    }\n\n    ImGui::NextColumn();\n\n    ImGui::AlignTextToFramePadding();\n    ImGui::TextUnformatted(TranslationManager::GetString(tstr_OvrlPropsPositionOriginConfigSmoothing));\n    ImGui::NextColumn();\n\n    int& origin_smoothing_level = ConfigManager::Get().GetRef(configid_int_overlay_origin_smoothing_level);\n    const int origin_smoothing_level_max = tstr_SettingsMouseSmoothingLevelVeryHigh - tstr_SettingsMouseSmoothingLevelNone;\n    origin_smoothing_level = clamp(origin_smoothing_level, 0, origin_smoothing_level_max);\n    if (ImGui::SliderWithButtonsInt(\"SmoothingLevelHMDFloor\", origin_smoothing_level, 1, 1, 0, origin_smoothing_level_max, \"##%d\", ImGuiSliderFlags_NoInput, nullptr, \n                                    TranslationManager::GetString( (TRMGRStrID)(tstr_SettingsMouseSmoothingLevelNone + origin_smoothing_level) )))\n    {\n        origin_smoothing_level = clamp(origin_smoothing_level, 0, origin_smoothing_level_max);\n\n        IPCManager::Get().PostConfigMessageToDashboardApp(configid_int_overlay_origin_smoothing_level, origin_smoothing_level);\n    }\n\n    ImGui::Columns(1);\n\n    ImGui::Spacing();\n    ImGui::EndCollapsingArea();\n\n    //-Origin HMD \n    ImGui::BeginCollapsingArea(\"OriginHMDSettings\", (mode_origin == ovrl_origin_hmd), m_OriginHMDSettingsAnimationProgress);\n\n    ImGui::Columns(2, \"ColumnOriginHMDSettings\", false);\n    ImGui::SetColumnWidth(0, m_Column0Width);\n\n    ImGui::AlignTextToFramePadding();\n    ImGui::TextUnformatted(TranslationManager::GetString(tstr_OvrlPropsPositionOriginConfigSmoothing));\n    ImGui::NextColumn();\n\n    if (ImGui::SliderWithButtonsInt(\"SmoothingLevelHMD\", origin_smoothing_level, 1, 1, 0, origin_smoothing_level_max, \"##%d\", ImGuiSliderFlags_NoInput, nullptr, \n                                    TranslationManager::GetString( (TRMGRStrID)(tstr_SettingsMouseSmoothingLevelNone + origin_smoothing_level) )))\n    {\n        origin_smoothing_level = clamp(origin_smoothing_level, 0, origin_smoothing_level_max);\n\n        IPCManager::Get().PostConfigMessageToDashboardApp(configid_int_overlay_origin_smoothing_level, origin_smoothing_level);\n    }\n\n    ImGui::Columns(1);\n\n    ImGui::Spacing();\n    ImGui::EndCollapsingArea();\n\n    //-Origin Theater Screen\n    ImGui::Columns(2, \"ColumnPosition2\", false);\n    ImGui::SetColumnWidth(0, m_Column0Width);\n\n    ImGui::NextColumn();\n\n    if (UIManager::Get()->IsOpenVRLoaded())\n    {\n        ImGui::BeginCollapsingArea(\"OriginTheaterScreenSettings\", (mode_origin == ovrl_origin_theater_screen), m_OriginTheaterScreenSettingsAnimationProgress);\n\n        if (ImGui::Button(TranslationManager::GetString( (is_enabled) ? tstr_OvrlPropsPositionOriginConfigTheaterScreenLeave : tstr_OvrlPropsPositionOriginConfigTheaterScreenEnter )))\n        {\n            //Entering and leaving theater mode is nothing special but rather just a side effect of enabling the overlay. This is only for better UX\n            is_enabled = !is_enabled;\n            IPCManager::Get().PostConfigMessageToDashboardApp(configid_bool_overlay_enabled, is_enabled);\n        }\n\n        ImGui::Spacing();\n        ImGui::EndCollapsingArea();\n    }\n\n    ImGui::NextColumn();\n\n    if (mode_origin == ovrl_origin_theater_screen)\n    {\n        ImGui::PushItemDisabled();\n    }\n\n    //Display Mode\n    ImGui::AlignTextToFramePadding();\n    ImGui::TextUnformatted(TranslationManager::GetString(tstr_OvrlPropsPositionDispMode));\n    ImGui::NextColumn();\n\n    int& mode_display = ConfigManager::GetRef(configid_int_overlay_display_mode);\n\n    ImGui::PushItemWidth(-1.0f);\n\n    if (TranslatedComboAnimated(\"##ComboPositionDisplayMode\", mode_display, tstr_OvrlPropsPositionDispModeAlways, tstr_OvrlPropsPositionDispModeDPlus))\n    {\n        IPCManager::Get().PostConfigMessageToDashboardApp(configid_int_overlay_display_mode, mode_display);\n    }\n\n    ImGui::NextColumn();\n\n    //Position Change\n    ImGui::AlignTextToFramePadding();\n    ImGui::TextUnformatted(TranslationManager::GetString(tstr_OvrlPropsPositionPos));\n\n    if (!UIManager::Get()->IsOpenVRLoaded()) //Show tip if position can't be changed due to desktop mode\n    {\n        ImGui::SameLine(0.0f, style.ItemInnerSpacing.x);\n        HelpMarker(TranslationManager::GetString(tstr_OvrlPropsPositionPosTip));\n    }\n\n    ImGui::NextColumn();\n\n    if (!UIManager::Get()->IsOpenVRLoaded())\n        ImGui::PushItemDisabled();\n\n    if (ImGui::Button(TranslationManager::GetString(tstr_OvrlPropsPositionChange)))\n    {\n        PageGoForward(wndovrlprop_page_position_change);\n    }\n\n    ImGui::SameLine(0.0f, style.ItemInnerSpacing.x);\n\n    if (ImGui::Button(TranslationManager::GetString(tstr_OvrlPropsPositionReset)))\n    {\n        OverlayPositionReset();\n    }\n\n    if (!UIManager::Get()->IsOpenVRLoaded())\n        ImGui::PopItemDisabled();\n\n    ImGui::SameLine();\n\n    bool& transform_locked = ConfigManager::GetRef(configid_bool_overlay_transform_locked);\n    if (ImGui::Checkbox(TranslationManager::GetString(tstr_OvrlPropsPositionLock), &transform_locked))\n    {\n        IPCManager::Get().PostConfigMessageToDashboardApp(configid_bool_overlay_transform_locked, transform_locked);\n    }\n\n    if (mode_origin == ovrl_origin_theater_screen)\n        ImGui::PopItemDisabled();\n\n    ImGui::Columns(1);\n}\n\nvoid WindowOverlayProperties::UpdatePageMainCatAppearance()\n{\n    ImGuiStyle& style = ImGui::GetStyle();\n    VRKeyboard& vr_keyboard = UIManager::Get()->GetVRKeyboard();\n\n    const int& mode_origin = ConfigManager::GetRef(configid_int_overlay_origin);\n\n    ImGui::Spacing();\n    ImGui::TextColoredUnformatted(ImGui::GetStyleColorVec4(ImGuiCol_ButtonHovered), TranslationManager::GetString(tstr_OvrlPropsCatAppearance));\n    ImGui::Columns(2, \"ColumnAppearance\", false);\n    ImGui::SetColumnWidth(0, m_Column0Width);\n\n    //Width\n    if (mode_origin == ovrl_origin_theater_screen)\n        ImGui::PushItemDisabled();\n\n    ImGui::AlignTextToFramePadding();\n    ImGui::TextUnformatted(TranslationManager::GetString(tstr_OvrlPropsAppearanceWidth));\n    ImGui::NextColumn();\n\n    float& width = ConfigManager::GetRef(configid_float_overlay_width);\n    const float width_slider_max = (mode_origin >= ovrl_origin_right_hand) ? 1.5f : 5.0f;\n\n    vr_keyboard.VRKeyboardInputBegin( ImGui::SliderWithButtonsGetSliderID(\"OverlayWidth\") );\n    if (ImGui::SliderWithButtonsFloat(\"OverlayWidth\", width, 0.1f, 0.01f, 0.05f, width_slider_max, \"%.2f m\", ImGuiSliderFlags_Logarithmic))\n    {\n        if (width < 0.05f)\n            width = 0.05f;\n\n        IPCManager::Get().PostConfigMessageToDashboardApp(configid_float_overlay_width, width);\n    }\n    vr_keyboard.VRKeyboardInputEnd();\n\n    ImGui::NextColumn();\n\n    //Curvature\n    ImGui::AlignTextToFramePadding();\n    ImGui::TextUnformatted(TranslationManager::GetString(tstr_OvrlPropsAppearanceCurve));\n    ImGui::NextColumn();\n\n    float& curve = ConfigManager::GetRef(configid_float_overlay_curvature);\n\n    vr_keyboard.VRKeyboardInputBegin( ImGui::SliderWithButtonsGetSliderID(\"OverlayCurvature\") );\n    if (ImGui::SliderWithButtonsFloatPercentage(\"OverlayCurvature\", curve, 5, 1, 0, 35, \"%d%%\"))\n    {\n        curve = clamp(curve, 0.0f, 1.0f);\n\n        IPCManager::Get().PostConfigMessageToDashboardApp(configid_float_overlay_curvature, curve);\n    }\n    vr_keyboard.VRKeyboardInputEnd();\n\n    ImGui::NextColumn();\n\n    if (mode_origin == ovrl_origin_theater_screen)\n        ImGui::PopItemDisabled();\n\n    //Opacity\n    ImGui::AlignTextToFramePadding();\n    ImGui::TextUnformatted(TranslationManager::GetString(tstr_OvrlPropsAppearanceOpacity));\n    ImGui::NextColumn();\n\n    float& opacity = ConfigManager::GetRef(configid_float_overlay_opacity);\n\n    vr_keyboard.VRKeyboardInputBegin( ImGui::SliderWithButtonsGetSliderID(\"OverlayOpacity\") );\n    if (ImGui::SliderWithButtonsFloatPercentage(\"OverlayOpacity\", opacity, 5, 1, 0, 100, \"%d%%\"))\n    {\n        opacity = clamp(opacity, 0.0f, 1.0f);\n\n        IPCManager::Get().PostConfigMessageToDashboardApp(configid_float_overlay_opacity, opacity);\n    }\n    vr_keyboard.VRKeyboardInputEnd();\n\n    ImGui::NextColumn();\n\n    //Brightness\n    ImGui::AlignTextToFramePadding();\n    ImGui::TextUnformatted(TranslationManager::GetString(tstr_OvrlPropsAppearanceBrightness));\n    ImGui::NextColumn();\n\n    float& brightness = ConfigManager::GetRef(configid_float_overlay_brightness);\n\n    vr_keyboard.VRKeyboardInputBegin( ImGui::SliderWithButtonsGetSliderID(\"OverlayBrightness\") );\n    if (ImGui::SliderWithButtonsFloatPercentage(\"OverlayBrightness\", brightness, 5, 1, 0, 100, \"%d%%\"))\n    {\n        brightness = clamp(brightness, 0.0f, (ConfigManager::GetValue(configid_bool_performance_hdr_mirroring)) ? 10.0f : 1.0f);\n\n        IPCManager::Get().PostConfigMessageToDashboardApp(configid_float_overlay_brightness, brightness);\n    }\n    vr_keyboard.VRKeyboardInputEnd();\n\n    ImGui::NextColumn();\n\n    //Crop\n    if (ConfigManager::GetValue(configid_int_overlay_capture_source) != ovrl_capsource_ui) //Don't show crop settings for UI source overlays\n    {\n        bool& crop_enabled = ConfigManager::GetRef(configid_bool_overlay_crop_enabled);\n\n        ImGui::Spacing();\n        if (ImGui::Checkbox(TranslationManager::GetString(tstr_OvrlPropsAppearanceCrop), &crop_enabled))\n        {\n            IPCManager::Get().PostConfigMessageToDashboardApp(configid_bool_overlay_crop_enabled, crop_enabled);\n        }\n        ImGui::NextColumn();\n        ImGui::Spacing();\n\n        //Build button string if empty\n        if (m_CropButtonLabel.empty())\n        {\n            int crop_width  = ConfigManager::GetValue(configid_int_overlay_crop_width);\n            int crop_height = ConfigManager::GetValue(configid_int_overlay_crop_height);\n\n            std::stringstream ss;\n            ss << ConfigManager::GetValue(configid_int_overlay_crop_x) << \", \" << ConfigManager::GetValue(configid_int_overlay_crop_y) << \" | \";\n            (crop_width  == -1) ? ss << TranslationManager::GetString(tstr_OvrlPropsAppearanceCropValueMax) << \" \" : ss << crop_width;\n            ss << \"x\";\n            (crop_height == -1) ? ss << \" \" << TranslationManager::GetString(tstr_OvrlPropsAppearanceCropValueMax) : ss << crop_height;\n\n            m_CropButtonLabel = ss.str();\n        }\n\n        if (ImGui::Button(m_CropButtonLabel.c_str()))\n        {\n            PageGoForward(wndovrlprop_page_crop_change);\n        }\n\n        ImGui::NextColumn();\n    }\n    else\n    {\n        ImGui::Spacing();\n    }\n\n    //Show Backside\n    bool& show_backside = ConfigManager::GetRef(configid_bool_overlay_show_backside);\n\n    if (ImGui::Checkbox(TranslationManager::GetString(tstr_OvrlPropsAppearanceShowBackside), &show_backside))\n    {\n        IPCManager::Get().PostConfigMessageToDashboardApp(configid_bool_overlay_show_backside, show_backside);\n    }\n    ImGui::NextColumn();\n\n    ImGui::Columns(1);\n}\n\nvoid WindowOverlayProperties::UpdatePageMainCatCapture()\n{\n    //All capture settings are considered advanced\n    if (!ConfigManager::GetValue(configid_bool_interface_show_advanced_settings))\n        return;\n\n    ImGuiStyle& style = ImGui::GetStyle();\n    VRKeyboard& vr_keyboard = UIManager::Get()->GetVRKeyboard();\n\n    int& capture_method = ConfigManager::GetRef(configid_int_overlay_capture_source);\n\n    //Don't show capture settings for UI or Browser source overlays\n    if ( (capture_method == ovrl_capsource_ui) || (capture_method == ovrl_capsource_browser) )\n        return;\n\n    int& winrt_selected_desktop = ConfigManager::GetRef(configid_int_overlay_winrt_desktop_id);\n    HWND winrt_selected_window  = (HWND)ConfigManager::GetValue(configid_handle_overlay_state_winrt_hwnd);\n\n    ImGui::Spacing();\n    ImGui::TextColoredUnformatted(ImGui::GetStyleColorVec4(ImGuiCol_ButtonHovered), TranslationManager::GetString(tstr_OvrlPropsCatCapture));\n\n    //Show some warning when an unknown capture source is used and don't go further\n    if (capture_method > ovrl_capsource_browser)\n    {\n        ImGui::Indent();\n        ImGui::PushTextWrapPos();\n        ImGui::TextColoredUnformatted(Style_ImGuiCol_TextWarning, TranslationManager::GetString(tstr_OvrlPropsCaptureSourceUnknownWarning));\n        ImGui::PopTextWrapPos();\n        ImGui::Unindent();\n        return;\n    }\n\n    ImGui::Columns(2, \"ColumnCapture\", false);\n    ImGui::SetColumnWidth(0, m_Column0Width);\n\n    //Capture Method\n    ImGui::AlignTextToFramePadding();\n    ImGui::TextUnformatted(TranslationManager::GetString(tstr_OvrlPropsCaptureMethod));\n\n    if (!DPWinRT_IsCaptureSupported())\n    {\n        ImGui::SameLine(0.0f, style.ItemInnerSpacing.x);\n        HelpMarker(TranslationManager::GetString(tstr_OvrlPropsCaptureMethodGCUnsupportedTip), \"(!)\");\n    }\n    else if (!DPWinRT_IsCaptureFromCombinedDesktopSupported())\n    {\n        ImGui::SameLine(0.0f, style.ItemInnerSpacing.x);\n        HelpMarker(TranslationManager::GetString(tstr_OvrlPropsCaptureMethodGCUnsupportedPartialTip), \"(!)\");\n    }\n\n    ImGui::NextColumn();\n\n    if (ImGui::RadioButton(TranslationManager::GetString(tstr_OvrlPropsCaptureMethodDup), (capture_method == ovrl_capsource_desktop_duplication)))\n    {\n        capture_method = ovrl_capsource_desktop_duplication;\n        IPCManager::Get().PostConfigMessageToDashboardApp(configid_int_overlay_capture_source, capture_method);\n\n        OverlayManager::Get().SetCurrentOverlayNameAuto();\n        SetActiveOverlayID(m_ActiveOverlayID);\n\n        UIManager::Get()->RepeatFrame();\n    }\n\n    ImGui::SameLine(0.0f, style.ItemSpacing.x / 2.0f); //Getting tight there, use less spacing\n\n    if (!DPWinRT_IsCaptureSupported())\n        ImGui::PushItemDisabled();\n\n    if (ImGui::RadioButton(TranslationManager::GetString(tstr_OvrlPropsCaptureMethodGC), (capture_method == ovrl_capsource_winrt_capture)))\n    {\n        capture_method = ovrl_capsource_winrt_capture;\n        IPCManager::Get().PostConfigMessageToDashboardApp(configid_int_overlay_capture_source, capture_method);\n\n        OverlayManager::Get().SetCurrentOverlayNameAuto();\n        SetActiveOverlayID(m_ActiveOverlayID);\n\n        UIManager::Get()->RepeatFrame();\n    }\n\n    if (!DPWinRT_IsCaptureSupported())\n        ImGui::PopItemDisabled();\n\n    ImGui::NextColumn();\n\n    //Source\n    ImGui::AlignTextToFramePadding();\n    ImGui::TextUnformatted(TranslationManager::GetString(tstr_OvrlPropsCaptureSource));\n    ImGui::NextColumn();\n\n    if (capture_method == ovrl_capsource_desktop_duplication)\n    {\n        int desktop_count = ConfigManager::GetValue(configid_int_state_interface_desktop_count);\n        int& selected_desktop = ConfigManager::GetRef(configid_int_overlay_desktop_id);\n\n        ImGui::PushItemWidth(-1.0f);\n        if (ImGui::BeginComboAnimated(\"##ComboDesktopSource\", TranslationManager::Get().GetDesktopIDString(selected_desktop) ))\n        {\n            for (int i = -1; i < desktop_count; ++i)\n            {\n                ImGui::PushID(i);\n\n                if (ImGui::Selectable(TranslationManager::Get().GetDesktopIDString(i), (selected_desktop == i)))\n                {\n                    selected_desktop = i;\n                    IPCManager::Get().PostConfigMessageToDashboardApp(configid_int_overlay_desktop_id, selected_desktop);\n\n                    OverlayManager::Get().SetCurrentOverlayNameAuto();\n                    SetActiveOverlayID(m_ActiveOverlayID);\n                }\n\n                ImGui::PopID();\n            }\n\n            ImGui::EndCombo();\n        }\n\n        ImGui::NextColumn();\n    }\n    else if (capture_method == ovrl_capsource_winrt_capture)\n    {\n        if ( (m_WinRTSourceButtonLabel.empty()) || (m_CachedSizes.MainCatCapture_WinRTSourceLabelWidth == 0.0f) )\n        {\n            m_WinRTSourceButtonLabel = GetStringForWinRTSource(winrt_selected_window, winrt_selected_desktop);\n            m_CachedSizes.MainCatCapture_WinRTSourceLabelWidth = ImGui::CalcTextSize(m_WinRTSourceButtonLabel.c_str()).x;\n        }\n\n        ImVec2 button_size(0.0f, 0.0f);\n\n        if (m_CachedSizes.MainCatCapture_WinRTSourceLabelWidth > ImGui::GetContentRegionAvail().x - style.FramePadding.x * 2.0f)\n        {\n            button_size.x = ImGui::GetContentRegionAvail().x;\n        }\n\n        if (ImGui::Button(m_WinRTSourceButtonLabel.c_str(), button_size))\n        {\n            PageGoForward(wndovrlprop_page_graphics_capture_source);\n        }\n\n        bool& strict_matching = ConfigManager::GetRef(configid_bool_overlay_winrt_window_matching_strict);\n        if (ImGui::Checkbox(TranslationManager::GetString(tstr_OvrlPropsCaptureGCStrictMatching), &strict_matching))\n        {\n            IPCManager::Get().PostConfigMessageToDashboardApp(configid_bool_overlay_winrt_window_matching_strict, strict_matching);\n        }\n        ImGui::SameLine(0.0f, style.ItemInnerSpacing.x);\n        HelpMarker(TranslationManager::GetString(tstr_OvrlPropsCaptureGCStrictMatchingTip));\n    }\n\n    ImGui::Columns(1);\n}\n\nvoid WindowOverlayProperties::UpdatePageMainCatPerformanceMonitor()\n{\n    //Don't show performance monitor settings for non-UI source overlays\n    if (ConfigManager::GetValue(configid_int_overlay_capture_source) != ovrl_capsource_ui)\n        return;\n\n    ImGui::Spacing();\n    ImGui::TextColoredUnformatted(ImGui::GetStyleColorVec4(ImGuiCol_ButtonHovered), TranslationManager::GetString(tstr_OvrlPropsCatPerformanceMonitor));\n    ImGui::SameLine(0.0f, ImGui::GetStyle().ItemInnerSpacing.x);\n    if ( (UIManager::Get()->IsOpenVRLoaded()) && (UIManager::Get()->IsInDesktopMode()) )\n    {\n        HelpMarker(TranslationManager::GetString(tstr_OvrlPropsPerfMonDesktopModeTip));\n    }\n    else\n    {\n        HelpMarker(TranslationManager::GetString(tstr_OvrlPropsPerfMonGlobalTip));\n    }\n\n    const float column_1_width = (ImGui::GetContentRegionAvail().x - m_Column0Width) * 0.5f;\n\n    //Monitor style radio buttons\n    ImGui::Columns(2, \"ColumnPerformanceMonitor\", false);\n    ImGui::SetColumnWidth(0, m_Column0Width);\n\n    ImGui::AlignTextToFramePadding();\n    ImGui::TextUnformatted(TranslationManager::GetString(tstr_OvrlPropsPerfMonStyle));\n    ImGui::NextColumn();\n\n    bool& use_minimal_style      = ConfigManager::GetRef(configid_bool_performance_monitor_minimal_style);\n    bool& use_large_style        = ConfigManager::GetRef(configid_bool_performance_monitor_large_style);\n    const bool use_compact_style = (!use_minimal_style && !use_large_style);\n\n    bool& show_window       = ConfigManager::GetRef(configid_bool_performance_monitor_style_show_window);\n    bool& show_text_outline = ConfigManager::GetRef(configid_bool_performance_monitor_style_show_text_outline);\n    bool& minimal_show_more = ConfigManager::GetRef(configid_bool_performance_monitor_style_minimal_show_more);\n\n    if (ImGui::RadioButton(TranslationManager::GetString(tstr_OvrlPropsPerfMonStyleMinimal), use_minimal_style))\n    {\n        use_minimal_style = true;\n        use_large_style   = false;\n        UIManager::Get()->RepeatFrame();\n    }\n\n    ImGui::SameLine();\n\n    if (ImGui::RadioButton(TranslationManager::GetString(tstr_OvrlPropsPerfMonStyleCompact), use_compact_style))\n    {\n        use_minimal_style = false;\n        use_large_style   = false;\n        UIManager::Get()->RepeatFrame();\n    }\n\n    ImGui::SameLine();\n\n    if (ImGui::RadioButton(TranslationManager::GetString(tstr_OvrlPropsPerfMonStyleLarge), use_large_style))\n    {\n        use_minimal_style = false;\n        use_large_style   = true;\n        UIManager::Get()->RepeatFrame();\n    }\n\n    ImGui::Columns(1);\n\n    //Monitor style checkboxes\n    ImGui::Columns(3, \"ColumnPerformanceMonitorStyleCheckboxes\", false);\n    ImGui::SetColumnWidth(0, m_Column0Width);\n    ImGui::SetColumnWidth(1, column_1_width);\n    ImGui::SetColumnWidth(2, column_1_width);\n\n    ImGui::NextColumn();\n\n    if (ImGui::Checkbox(TranslationManager::GetString(tstr_OvrlPropsPerfMonStyleShowWindow), &show_window))\n    {\n        UIManager::Get()->RepeatFrame();\n    }\n\n    ImGui::NextColumn();\n\n    if (ImGui::Checkbox(TranslationManager::GetString(tstr_OvrlPropsPerfMonStyleShowTextOutline), &show_text_outline))\n    {\n        UIManager::Get()->RepeatFrame();\n    }\n\n    ImGui::Columns(1);\n\n    //This needs more space in some languages, so we give it the full width as it's the only checkbox for now\n    ImGui::Columns(2, \"ColumnPerformanceMonitorStyleMinimalSettings\", false);\n    ImGui::SetColumnWidth(0, m_Column0Width);\n\n    ImGui::NextColumn();\n\n    ImGui::BeginCollapsingArea(\"PerformanceMonitorStyleMinimalSettings\", use_minimal_style, m_PerfMonStyleCheckboxAnimationProgress);\n\n    if (ImGui::Checkbox(TranslationManager::GetString(tstr_OvrlPropsPerfMonStyleMinimalShowMore), &minimal_show_more))\n    {\n        UIManager::Get()->RepeatFrame();\n    }\n\n    ImGui::EndCollapsingArea();\n\n    ImGui::Columns(1);\n\n    //Monitor items\n    ImGui::Spacing();\n\n    ImGui::Columns(3, \"ColumnPerformanceMonitorItems\", false);\n    ImGui::SetColumnWidth(0, m_Column0Width);\n    ImGui::SetColumnWidth(1, column_1_width);\n    ImGui::SetColumnWidth(2, column_1_width);\n\n    bool& show_cpu             = ConfigManager::GetRef(configid_bool_performance_monitor_show_cpu);\n    bool& show_gpu             = ConfigManager::GetRef(configid_bool_performance_monitor_show_gpu);\n    bool& show_graphs          = ConfigManager::GetRef(configid_bool_performance_monitor_show_graphs);\n    bool& show_fps             = ConfigManager::GetRef(configid_bool_performance_monitor_show_fps);\n    bool& show_battery         = ConfigManager::GetRef(configid_bool_performance_monitor_show_battery);\n    bool& show_time            = ConfigManager::GetRef(configid_bool_performance_monitor_show_time);\n    bool& show_trackers        = ConfigManager::GetRef(configid_bool_performance_monitor_show_trackers);\n    bool& show_vive_wireless   = ConfigManager::GetRef(configid_bool_performance_monitor_show_vive_wireless);\n    bool& disable_gpu_counters = ConfigManager::GetRef(configid_bool_performance_monitor_disable_gpu_counters);\n\n    const bool can_show_graphs = ((use_large_style) && ((show_cpu) || (show_gpu)))   || (!use_large_style);\n    const bool can_show_time   = ((use_large_style) && (show_fps) && (show_battery)) || (use_minimal_style);\n\n    //Keep unavailable options as enabled but show the check boxes as unticked to avoid confusion\n    bool show_graphs_visual  = (can_show_graphs) ? show_graphs : false;\n    bool show_time_visual    = (can_show_time)   ? show_time : false;\n    bool show_trackers_visual      = (!show_battery) ? false : show_trackers;\n    bool show_vive_wireless_visual = (!show_battery) ? false : show_vive_wireless;\n\n    ImGui::AlignTextToFramePadding();\n    ImGui::TextUnformatted(TranslationManager::GetString(tstr_OvrlPropsPerfMonItems));\n    ImGui::NextColumn();\n\n    if (ImGui::Checkbox(TranslationManager::GetString(tstr_OvrlPropsPerfMonItemCPU), &show_cpu))\n    {\n        UIManager::Get()->RepeatFrame();\n    }\n\n    ImGui::NextColumn();\n\n    if (ImGui::Checkbox(TranslationManager::GetString(tstr_OvrlPropsPerfMonItemGPU), &show_gpu))\n    {\n        UIManager::Get()->RepeatFrame();\n    }\n\n    ImGui::NextColumn();\n    ImGui::NextColumn();\n\n    if (ImGui::Checkbox(TranslationManager::GetString(tstr_OvrlPropsPerfMonItemFrameStats), &show_fps))\n    {\n        UIManager::Get()->RepeatFrame();\n    }\n\n    ImGui::NextColumn();\n\n    if (ImGui::Checkbox(TranslationManager::GetString(tstr_OvrlPropsPerfMonItemBattery), &show_battery))\n    {\n        UIManager::Get()->RepeatFrame();\n    }\n\n    ImGui::NextColumn();\n    ImGui::NextColumn();\n\n    if (!can_show_graphs)\n        ImGui::PushItemDisabled();\n\n    if (ImGui::Checkbox(TranslationManager::GetString(tstr_OvrlPropsPerfMonItemGraphs), &show_graphs_visual))\n    {\n        show_graphs = show_graphs_visual;\n        UIManager::Get()->RepeatFrame();\n    }\n\n    if (!can_show_graphs)\n        ImGui::PopItemDisabled();\n\n    ImGui::NextColumn();\n\n    if (!can_show_time)\n        ImGui::PushItemDisabled();\n\n    if (ImGui::Checkbox(TranslationManager::GetString(tstr_OvrlPropsPerfMonItemTime), &show_time_visual))\n    {\n        show_time = show_time_visual;\n        UIManager::Get()->RepeatFrame();\n    }\n\n    if (!can_show_time)\n        ImGui::PopItemDisabled();\n\n    ImGui::Columns(1);\n\n    //These need more space than the other entries, so one per line here. Doesn't look as nice, but will have to do for now\n    ImGui::Columns(2, \"ColumnPerformanceMonitorItemsLong\", false);\n    ImGui::SetColumnWidth(0, m_Column0Width);\n\n    ImGui::NextColumn();\n\n    if (!show_battery)\n        ImGui::PushItemDisabled();\n\n    if (ImGui::Checkbox(TranslationManager::GetString(tstr_OvrlPropsPerfMonItemTrackerBattery), &show_trackers_visual))\n    {\n        show_trackers = show_trackers_visual;\n        UIManager::Get()->RepeatFrame();\n    }\n\n    ImGui::NextColumn();\n    ImGui::NextColumn();\n    ImGui::NextColumn();\n\n    if (UIManager::Get()->GetPerformanceWindow().IsViveWirelessInstalled())\n    {\n        if (ImGui::Checkbox(TranslationManager::GetString(tstr_OvrlPropsPerfMonItemViveWirelessTemp), &show_vive_wireless_visual))\n        {\n            show_vive_wireless = show_vive_wireless_visual;\n            UIManager::Get()->RepeatFrame();\n        }\n    }\n\n    if (!show_battery)\n        ImGui::PopItemDisabled();\n\n    ImGui::Columns(1);\n\n    ImGui::Indent();\n\n    ImGui::Spacing();\n\n    if (ImGui::Button(TranslationManager::GetString(tstr_OvrlPropsPerfMonResetValues)))\n    {\n        UIManager::Get()->GetPerformanceWindow().ResetCumulativeValues();\n    }\n\n    ImGui::Unindent();\n}\n\nvoid WindowOverlayProperties::UpdatePageMainCatBrowser()\n{\n    //Don't show these settings if not browser overlay\n    if (ConfigManager::GetValue(configid_int_overlay_capture_source) != ovrl_capsource_browser)\n        return;\n\n    //Use duplication IDs' data if any is set\n    const int duplication_id = ConfigManager::GetValue(configid_int_overlay_duplication_id);\n    OverlayConfigData& data = (duplication_id == -1) ? OverlayManager::Get().GetCurrentConfigData() : OverlayManager::Get().GetConfigData((unsigned int)duplication_id);\n    //We also need to set overrides for most changes done here if the duplication is used\n    const int config_overlay_id = (duplication_id == -1) ? (int)OverlayManager::Get().GetCurrentOverlayID() : duplication_id;\n\n    ImGuiStyle& style = ImGui::GetStyle();\n    VRKeyboard& vr_keyboard = UIManager::Get()->GetVRKeyboard();\n\n    ImGui::Spacing();\n    ImGui::TextColoredUnformatted(ImGui::GetStyleColorVec4(ImGuiCol_ButtonHovered), TranslationManager::GetString(tstr_OvrlPropsCatBrowser));\n\n    if (!DPBrowserAPIClient::Get().IsBrowserAvailable())\n    {\n        ImGui::SameLine(0.0f, style.ItemInnerSpacing.x);\n        HelpMarker(TranslationManager::GetString(tstr_OvrlPropsBrowserNotAvailableTip), \"(!)\");\n    }\n\n    ImGui::Columns(2, \"ColumnBrowser\", false);\n    ImGui::SetColumnWidth(0, m_Column0Width);\n\n    //Cloned Output\n    if (duplication_id != -1)\n    {\n        static std::string clone_tip_text;\n        static unsigned int highlighted_overlay_id_last = k_ulOverlayID_None;\n        unsigned int highlighted_overlay_id = k_ulOverlayID_None;\n\n        ImGui::AlignTextToFramePadding();\n        ImGui::TextUnformatted(TranslationManager::GetString(tstr_OvrlPropsBrowserCloned));\n\n        ImGui::SameLine(0.0f, style.ItemInnerSpacing.x);\n        HelpMarker(clone_tip_text.c_str());\n\n        if (ImGui::IsItemHovered())\n        {\n            //Highlight clone source overlay to give the user a better idea which one is the source (names are usually the same and the ID is not a good indicator)\n            highlighted_overlay_id = (unsigned int)duplication_id;\n\n            //If newly hovered, generate tooltip text\n            if (clone_tip_text.empty())\n            {\n                clone_tip_text = TranslationManager::GetString(tstr_OvrlPropsBrowserClonedTip);\n                StringReplaceAll(clone_tip_text, \"%OVERLAYNAME%\", data.ConfigNameStr);\n\n                UIManager::Get()->RepeatFrame(3);   //Tooltip needs more frames to adjust to changing text since we changed it after it already rendered here\n            }\n        }\n        else if (!clone_tip_text.empty())\n        {\n            //Clear when not hovering so it'll be updated next time\n            clone_tip_text = \"\";\n        }\n\n        if (highlighted_overlay_id_last != highlighted_overlay_id)\n        {\n            UIManager::Get()->HighlightOverlay(highlighted_overlay_id);\n            highlighted_overlay_id_last = highlighted_overlay_id;\n        }\n\n        ImGui::NextColumn();\n\n        if (ImGui::Button(TranslationManager::GetString(tstr_OvrlPropsBrowserClonedConvert)))\n        {\n            //Send string over to dashboard app so it uses the right URLs during conversion (it doesn't get or need updated URLs during runtime in most other cases)\n            IPCManager::Get().PostConfigMessageToDashboardApp(configid_int_state_overlay_current_id_override, config_overlay_id);\n            IPCManager::Get().SendStringToDashboardApp(configid_str_overlay_browser_url,           data.ConfigStr[configid_str_overlay_browser_url],           UIManager::Get()->GetWindowHandle());\n            IPCManager::Get().SendStringToDashboardApp(configid_str_overlay_browser_url_user_last, data.ConfigStr[configid_str_overlay_browser_url_user_last], UIManager::Get()->GetWindowHandle());\n            IPCManager::Get().PostConfigMessageToDashboardApp(configid_int_state_overlay_current_id_override, -1);\n\n            OverlayManager::Get().ConvertDuplicatedOverlayToStandalone(m_ActiveOverlayID);\n            IPCManager::Get().PostMessageToDashboardApp(ipcmsg_action, ipcact_overlay_make_standalone, (int)m_ActiveOverlayID);\n        }\n\n        ImGui::Spacing();\n        ImGui::NextColumn();\n    }\n\n    //URL\n    {\n        static char buffer_url[1024] = \"\";\n        static bool can_restore_last_user_input = false;\n        bool has_pressed_enter_on_url = false;\n\n        ImGui::AlignTextToFramePadding();\n        ImGui::TextUnformatted(TranslationManager::GetString(tstr_OvrlPropsBrowserURL));\n        ImGui::NextColumn();\n\n        ImGui::PushItemWidth(-1.0f);\n        vr_keyboard.VRKeyboardInputBegin(\"##InputURL\");\n        vr_keyboard.SetShortcutWindowDirectionHint(ImGuiDir_Up);\n        if (ImGui::InputTextWithHint(\"##InputURL\", TranslationManager::GetString(tstr_OvrlPropsBrowserURLHint), buffer_url, IM_ARRAYSIZE(buffer_url), \n                                     ImGuiInputTextFlags_EnterReturnsTrue | ImGuiInputTextFlags_AutoSelectAll))\n        {\n            has_pressed_enter_on_url = true;\n        }\n        vr_keyboard.VRKeyboardInputEnd();\n\n        if (ImGui::IsItemEdited())\n        {\n            //Add unmapped characters if they appear while typing\n            UIManager::Get()->AddFontBuilderStringIfAnyUnmappedCharacters(buffer_url);\n\n            can_restore_last_user_input = true;\n        }\n        else if ( ((m_IsBrowserURLChanged) || (m_PageAppearing == wndovrlprop_page_main)) && (!ImGui::IsItemActive()) )\n        {\n            //Update buffer if URL changed externally or window/page is appearing while text input isn't active\n            size_t copied_length = data.ConfigStr[configid_str_overlay_browser_url].copy(buffer_url, IM_ARRAYSIZE(buffer_url) - 1);\n            buffer_url[copied_length] = '\\0';\n\n            //Enable \"Restore Last Input\" only when they're actually different\n            can_restore_last_user_input = (data.ConfigStr[configid_str_overlay_browser_url] != data.ConfigStr[configid_str_overlay_browser_url_user_last]);\n\n            m_IsBrowserURLChanged = false;\n        }\n\n        ImGui::NextColumn();\n        ImGui::NextColumn();\n\n        if ( (ImGui::Button(TranslationManager::GetString(tstr_OvrlPropsBrowserGo))) || (has_pressed_enter_on_url) )\n        {\n            //If buffer is not empty, set URL from user input, otherwise fall back to current URL\n            if (buffer_url[0] != '\\0')\n            {\n                data.ConfigStr[configid_str_overlay_browser_url]           = buffer_url;\n                data.ConfigStr[configid_str_overlay_browser_url_user_last] = data.ConfigStr[configid_str_overlay_browser_url];\n\n                //Send string over to dashboard app so it can call SetURL()\n                IPCManager::Get().PostConfigMessageToDashboardApp(configid_int_state_overlay_current_id_override, config_overlay_id);\n                IPCManager::Get().SendStringToDashboardApp(configid_str_overlay_browser_url, data.ConfigStr[configid_str_overlay_browser_url], UIManager::Get()->GetWindowHandle());\n                IPCManager::Get().PostConfigMessageToDashboardApp(configid_int_state_overlay_current_id_override, -1);\n\n                IPCManager::Get().PostMessageToDashboardApp(ipcmsg_action, ipcact_browser_navigate_to_url, config_overlay_id);\n\n                can_restore_last_user_input = false;\n            }\n            else\n            {\n                //This doesn't update the last user entered URL\n                m_IsBrowserURLChanged = true;\n            }\n        }\n\n        ImGui::SameLine();\n\n        const bool disable_restore_button = !can_restore_last_user_input;\n\n        if (disable_restore_button)\n            ImGui::PushItemDisabled();\n\n        if (ImGui::Button(TranslationManager::GetString(tstr_OvrlPropsBrowserRestore)))\n        {\n            //This button only changes the input buffer. It doesn't set the url or navigates anywhere.\n            size_t copied_length = data.ConfigStr[configid_str_overlay_browser_url_user_last].copy(buffer_url, IM_ARRAYSIZE(buffer_url) - 1);\n            buffer_url[copied_length] = '\\0';\n\n            can_restore_last_user_input = false;\n        }\n\n        if (disable_restore_button)\n            ImGui::PopItemDisabled();\n\n        ImGui::Spacing();\n        ImGui::NextColumn();\n    }\n\n    //Resolution\n    {\n        ImGui::AlignTextToFramePadding();\n        ImGui::TextUnformatted(TranslationManager::GetString(tstr_OvrlPropsBrowserWidth));\n        ImGui::NextColumn();\n\n        bool changed_width = false, changed_height = false;\n        int& user_width = data.ConfigInt[configid_int_overlay_user_width];\n\n        vr_keyboard.VRKeyboardInputBegin( ImGui::SliderWithButtonsGetSliderID(\"UserWidth\") );\n        if (ImGui::SliderWithButtonsInt(\"UserWidth\", user_width, 5, 1, 1, 3840, \"%d px\"))\n        {\n            user_width = clamp(user_width, 1, 7680);\n\n            //Don't do live updates from text input (can lead to undesirable visuals, especially when removing all text)\n            if (!ImGui::IsAnyTempInputTextActive())\n            {\n                changed_width = true;\n            }\n        }\n        vr_keyboard.VRKeyboardInputEnd();\n\n        if (ImGui::IsItemDeactivatedAfterEdit())\n        {\n            changed_width = true;\n        }\n\n        ImGui::NextColumn();\n        ImGui::TextUnformatted(TranslationManager::GetString(tstr_OvrlPropsBrowserHeight));\n        ImGui::NextColumn();\n\n        int& user_height = data.ConfigInt[configid_int_overlay_user_height];\n\n        vr_keyboard.VRKeyboardInputBegin( ImGui::SliderWithButtonsGetSliderID(\"UserHeight\") );\n        if (ImGui::SliderWithButtonsInt(\"UserHeight\", user_height, 5, 1, 1, 2160, \"%d px\"))\n        {\n            user_height = clamp(user_height, 1, 4320);\n\n            if (!ImGui::IsAnyTempInputTextActive())\n            {\n                changed_height = true;\n            }\n        }\n        vr_keyboard.VRKeyboardInputEnd();\n\n        if (ImGui::IsItemDeactivatedAfterEdit())\n        {\n            changed_height = true;\n        }\n\n        if (changed_width)\n        {\n            IPCManager::Get().PostConfigMessageToDashboardApp(configid_int_state_overlay_current_id_override, config_overlay_id);\n            IPCManager::Get().PostConfigMessageToDashboardApp(configid_int_overlay_user_width, user_width);\n            IPCManager::Get().PostConfigMessageToDashboardApp(configid_int_state_overlay_current_id_override, -1);\n\n            //Also set for all overlays using this as duplication source (so reading code doesn't need to care about it being duplicated)\n            for (unsigned int overlay_id : OverlayManager::Get().FindDuplicatedOverlaysForOverlay(config_overlay_id))\n            {\n                OverlayConfigData& data_dup = OverlayManager::Get().GetConfigData(overlay_id);\n                data_dup.ConfigInt[configid_int_overlay_user_width] = user_width;\n                //Dashboard app does the same, so no need to send these changes over\n            }\n        }\n        else if (changed_height)\n        {\n            IPCManager::Get().PostConfigMessageToDashboardApp(configid_int_state_overlay_current_id_override, config_overlay_id);\n            IPCManager::Get().PostConfigMessageToDashboardApp(configid_int_overlay_user_height, user_height);\n            IPCManager::Get().PostConfigMessageToDashboardApp(configid_int_state_overlay_current_id_override, -1);\n\n            for (unsigned int overlay_id : OverlayManager::Get().FindDuplicatedOverlaysForOverlay(config_overlay_id))\n            {\n                OverlayConfigData& data_dup = OverlayManager::Get().GetConfigData(overlay_id);\n                data_dup.ConfigInt[configid_int_overlay_user_height] = user_height;\n            }\n        }\n\n        ImGui::NextColumn();\n    }\n\n    //Zoom\n    {\n        ImGui::TextUnformatted(TranslationManager::GetString(tstr_OvrlPropsBrowserZoom));\n        ImGui::NextColumn();\n\n        float& zoom_level = data.ConfigFloat[configid_float_overlay_browser_zoom];\n\n        vr_keyboard.VRKeyboardInputBegin(ImGui::SliderWithButtonsGetSliderID(\"ZoomLevel\"));\n        if (ImGui::SliderWithButtonsFloatPercentage(\"ZoomLevel\", zoom_level, 5, 1, 50, 400, \"%d%%\"))\n        {\n            zoom_level = clamp(zoom_level, 0.01f, 8.0f);\n\n            IPCManager::Get().PostConfigMessageToDashboardApp(configid_int_state_overlay_current_id_override, config_overlay_id);\n            IPCManager::Get().PostConfigMessageToDashboardApp(configid_float_overlay_browser_zoom, zoom_level);\n            IPCManager::Get().PostConfigMessageToDashboardApp(configid_int_state_overlay_current_id_override, -1);\n        }\n        vr_keyboard.VRKeyboardInputEnd();\n\n        ImGui::Spacing();\n        ImGui::NextColumn();\n    }\n\n    //Transparent Background\n    {\n        bool& allow_transparency = data.ConfigBool[configid_bool_overlay_browser_allow_transparency];\n        bool& is_allow_transparency_pending = data.ConfigBool[configid_bool_overlay_state_browser_allow_transparency_is_pending];\n\n        if (ImGui::Checkbox(TranslationManager::GetString(tstr_OvrlPropsBrowserAllowTransparency), &allow_transparency))\n        {\n            IPCManager::Get().PostConfigMessageToDashboardApp(configid_int_state_overlay_current_id_override, config_overlay_id);\n            IPCManager::Get().PostConfigMessageToDashboardApp(configid_bool_overlay_browser_allow_transparency, allow_transparency);\n            IPCManager::Get().PostConfigMessageToDashboardApp(configid_int_state_overlay_current_id_override, -1);\n\n            is_allow_transparency_pending = !is_allow_transparency_pending; //If it was already pending, then the value is back to what's already applied\n        }\n        ImGui::SameLine(0.0f, style.ItemInnerSpacing.x);\n        HelpMarker(TranslationManager::GetString(tstr_OvrlPropsBrowserAllowTransparencyTip));\n\n        ImGui::NextColumn();\n\n        if ( (is_allow_transparency_pending) && (UIManager::Get()->IsOpenVRLoaded()) )\n        {\n            if (ImGui::Button(TranslationManager::GetString(tstr_OvrlPropsBrowserRecreateContext)))\n            {\n                IPCManager::Get().PostMessageToDashboardApp(ipcmsg_action, ipcact_browser_recreate_context, config_overlay_id);\n                is_allow_transparency_pending = false;\n            }\n            ImGui::SameLine(0.0f, style.ItemInnerSpacing.x);\n            HelpMarker(TranslationManager::GetString(tstr_OvrlPropsBrowserRecreateContextTip));\n        }\n    }\n\n    ImGui::Columns(1);\n}\n\nvoid WindowOverlayProperties::UpdatePageMainCatAdvanced()\n{\n    ImGuiStyle& style = ImGui::GetStyle();\n    VRKeyboard& vr_keyboard = UIManager::Get()->GetVRKeyboard();\n\n    const int& mode_origin = ConfigManager::GetRef(configid_int_overlay_origin);\n\n    ImGui::Spacing();\n    ImGui::TextColoredUnformatted(ImGui::GetStyleColorVec4(ImGuiCol_ButtonHovered), TranslationManager::GetString(tstr_OvrlPropsCatAdvanced));\n    ImGui::Columns(2, \"ColumnAdvanced\", false);\n    ImGui::SetColumnWidth(0, m_Column0Width);\n\n    //3D\n    if (ConfigManager::GetValue(configid_int_overlay_capture_source) != ovrl_capsource_ui) //Don't show 3D settings for UI source overlays\n    {\n        bool& is_3D_enabled = ConfigManager::GetRef(configid_bool_overlay_3D_enabled);\n        bool& is_3D_swapped = ConfigManager::GetRef(configid_bool_overlay_3D_swapped);\n        int& mode_3d        = ConfigManager::GetRef(configid_int_overlay_3D_mode);\n\n        if (ImGui::Checkbox(TranslationManager::GetString(tstr_OvrlPropsAdvanced3D), &is_3D_enabled))\n        {\n            IPCManager::Get().PostConfigMessageToDashboardApp(configid_bool_overlay_3D_enabled, is_3D_enabled);\n        }\n        ImGui::NextColumn();\n\n        if (!is_3D_enabled)\n            ImGui::PushItemDisabled();\n\n        ImGui::PushItemWidth(-1.0f);\n        if (ImGui::BeginComboAnimated(\"##Combo3DMode\", TranslationManager::GetString( (TRMGRStrID)(tstr_OvrlPropsAdvancedHSBS + mode_3d) ) ))\n        {\n            for (int i = ovrl_3Dmode_hsbs; i < ovrl_3Dmode_MAX; ++i)\n            {\n                if (ImGui::Selectable(TranslationManager::GetString( (TRMGRStrID)(tstr_OvrlPropsAdvancedHSBS + i) ), (mode_3d == i)))\n                {\n                    mode_3d = i;\n                    IPCManager::Get().PostConfigMessageToDashboardApp(configid_int_overlay_3D_mode, mode_3d);\n                }\n            }\n\n            ImGui::EndCombo();\n        }\n\n        ImGui::NextColumn();\n        ImGui::NextColumn();\n\n        if (ImGui::Checkbox(TranslationManager::GetString(tstr_OvrlPropsAdvanced3DSwap), &is_3D_swapped))\n        {\n            IPCManager::Get().PostConfigMessageToDashboardApp(configid_bool_overlay_3D_swapped, is_3D_swapped);\n        }\n\n        if (!is_3D_enabled)\n            ImGui::PopItemDisabled();\n\n        ImGui::Spacing();\n        ImGui::NextColumn();\n    }\n\n    //Gaze Fade\n    {\n        if (mode_origin == ovrl_origin_theater_screen)\n            ImGui::PushItemDisabled();\n\n        bool& gazefade_enabled = ConfigManager::GetRef(configid_bool_overlay_gazefade_enabled);\n\n        if (ImGui::Checkbox(TranslationManager::GetString(tstr_OvrlPropsAdvancedGazeFade), &gazefade_enabled))\n        {\n            IPCManager::Get().PostConfigMessageToDashboardApp(configid_bool_overlay_gazefade_enabled, gazefade_enabled);\n        }\n        ImGui::NextColumn();\n\n        if (!UIManager::Get()->IsOpenVRLoaded())\n            ImGui::PushItemDisabled();\n\n        if (ImGui::Button(TranslationManager::GetString(tstr_OvrlPropsAdvancedGazeFadeAuto)))\n        {\n            if (!UIManager::Get()->GetAuxUI().IsActive())\n            {\n                //Show GazeFade Auto Hint window which will do the countdown and trigger auto-configuration in the dashboard app when done\n                UIManager::Get()->GetAuxUI().GetGazeFadeAutoHintWindow().SetTargetOverlay(m_ActiveOverlayID);\n                UIManager::Get()->GetAuxUI().GetGazeFadeAutoHintWindow().Show();\n            }\n        }\n        ImGui::NextColumn();\n\n        if (!UIManager::Get()->IsOpenVRLoaded())\n            ImGui::PopItemDisabled();\n\n        //Only show auto-configure when advanced settings are hidden\n        if (ConfigManager::GetValue(configid_bool_interface_show_advanced_settings))\n        {\n            if (!gazefade_enabled)\n                ImGui::PushItemDisabled();\n\n            ImGui::Indent(ImGui::GetFrameHeightWithSpacing());\n\n            ImGui::AlignTextToFramePadding();\n            ImGui::TextUnformatted(TranslationManager::GetString(tstr_OvrlPropsAdvancedGazeFadeDistance));\n            ImGui::NextColumn();\n\n            float& distance = ConfigManager::GetRef(configid_float_overlay_gazefade_distance);\n            const char* alt_text = (distance < 0.01f) ? TranslationManager::GetString(tstr_OvrlPropsAdvancedGazeFadeDistanceValueInf) : nullptr;\n\n            vr_keyboard.VRKeyboardInputBegin( ImGui::SliderWithButtonsGetSliderID(\"GazeFadeDistance\") );\n            if (ImGui::SliderWithButtonsFloat(\"GazeFadeDistance\", distance, 0.05f, 0.01f, 0.0f, 1.5f, (distance < 0.01f) ? \"##%.2f\" : \"%.2f m\", 0, nullptr, alt_text))\n            {\n                if (distance < 0.01f)\n                    distance = 0.0f;\n\n                IPCManager::Get().PostConfigMessageToDashboardApp(configid_float_overlay_gazefade_distance, distance);\n            }\n            vr_keyboard.VRKeyboardInputEnd();\n\n            ImGui::NextColumn();\n            ImGui::AlignTextToFramePadding();\n            ImGui::TextUnformatted(TranslationManager::GetString(tstr_OvrlPropsAdvancedGazeFadeSensitivity));\n            ImGui::NextColumn();\n\n            float& rate = ConfigManager::GetRef(configid_float_overlay_gazefade_rate);\n\n            vr_keyboard.VRKeyboardInputBegin( ImGui::SliderWithButtonsGetSliderID(\"GazeFadeRate\") );\n            if (ImGui::SliderWithButtonsFloat(\"GazeFadeRate\", rate, 0.1f, 0.025f, 0.4f, 3.0f, \"%.2fx\", ImGuiSliderFlags_Logarithmic))\n            {\n                if (rate < 0.0f)\n                    rate = 0.0f;\n\n                IPCManager::Get().PostConfigMessageToDashboardApp(configid_float_overlay_gazefade_rate, rate);\n            }\n            vr_keyboard.VRKeyboardInputEnd();\n            ImGui::NextColumn();\n\n            ImGui::AlignTextToFramePadding();\n            ImGui::TextUnformatted(TranslationManager::GetString(tstr_OvrlPropsAdvancedGazeFadeOpacity));\n            ImGui::NextColumn();\n\n            float& target_opacity = ConfigManager::GetRef(configid_float_overlay_gazefade_opacity);\n\n            vr_keyboard.VRKeyboardInputBegin( ImGui::SliderWithButtonsGetSliderID(\"GazeFadeOpacity\") );\n            if (ImGui::SliderWithButtonsFloatPercentage(\"GazeFadeOpacity\", target_opacity, 5, 1, 0, 100, \"%d%%\"))\n            {\n                target_opacity = clamp(target_opacity, 0.0f, 1.0f);\n\n                IPCManager::Get().PostConfigMessageToDashboardApp(configid_float_overlay_gazefade_opacity, target_opacity);\n            }\n            vr_keyboard.VRKeyboardInputEnd();\n\n            if (!gazefade_enabled)\n                ImGui::PopItemDisabled();\n\n            ImGui::Spacing();\n            ImGui::NextColumn();\n\n            ImGui::Unindent(ImGui::GetFrameHeightWithSpacing());\n        }\n\n        if (mode_origin == ovrl_origin_theater_screen)\n            ImGui::PopItemDisabled();\n    }\n\n    //Laser Pointer Input\n    {\n        bool& input_enabled = ConfigManager::GetRef(configid_bool_overlay_input_enabled);\n\n        if (ImGui::Checkbox(TranslationManager::GetString(tstr_OvrlPropsAdvancedInput), &input_enabled))\n        {\n            IPCManager::Get().PostConfigMessageToDashboardApp(configid_bool_overlay_input_enabled, input_enabled);\n        }\n        ImGui::NextColumn();\n\n        if (!input_enabled)\n            ImGui::PushItemDisabled();\n\n        bool& input_dplus_lp_enabled = ConfigManager::GetRef(configid_bool_overlay_input_dplus_lp_enabled);\n        bool& floating_ui_enabled    = ConfigManager::GetRef(configid_bool_overlay_floatingui_enabled);\n\n        //Show disabled as unticked to avoid confusion\n        bool input_dplus_lp_enabled_visual = (!input_enabled) ? false : input_dplus_lp_enabled;\n        bool floating_ui_enabled_visual    = (!input_enabled) ? false : floating_ui_enabled;\n\n        if (ImGui::Checkbox(TranslationManager::GetString(tstr_OvrlPropsAdvancedInputInGame), &input_dplus_lp_enabled_visual))\n        {\n            input_dplus_lp_enabled = input_dplus_lp_enabled_visual;\n            IPCManager::Get().PostConfigMessageToDashboardApp(configid_bool_overlay_input_dplus_lp_enabled, input_dplus_lp_enabled);\n        }\n\n        ImGui::NextColumn();\n        ImGui::NextColumn();\n\n        if (ImGui::Checkbox(TranslationManager::GetString(tstr_OvrlPropsAdvancedInputFloatingUI), &floating_ui_enabled_visual))\n        {\n            floating_ui_enabled = floating_ui_enabled_visual;\n            IPCManager::Get().PostConfigMessageToDashboardApp(configid_bool_overlay_floatingui_enabled, floating_ui_enabled);\n        }\n\n        if (!input_enabled)\n            ImGui::PopItemDisabled();\n\n        ImGui::Spacing();\n        ImGui::NextColumn();\n    }\n\n    //Overlay Tags\n    {\n        static FloatingWindowInputOverlayTagsState input_tags_state;\n\n        ImGui::AlignTextToFramePadding();\n        ImGui::TextUnformatted(TranslationManager::GetString(tstr_OvrlPropsAdvancedOverlayTags));\n\n        ImGui::SameLine(0.0f, style.ItemInnerSpacing.x);\n        HelpMarker(TranslationManager::GetString(tstr_OvrlPropsAdvancedOverlayTagsTip));\n\n        ImGui::NextColumn();\n\n        if (InputOverlayTags(\"OverlayTags\", m_BufferOverlayTags, IM_ARRAYSIZE(m_BufferOverlayTags), input_tags_state, 0, false))\n        {\n            OverlayConfigData& data = OverlayManager::Get().GetCurrentConfigData();\n\n            data.ConfigStr[configid_str_overlay_tags] = m_BufferOverlayTags;\n            IPCManager::Get().SendStringToDashboardApp(configid_str_overlay_tags, data.ConfigStr[configid_str_overlay_tags], UIManager::Get()->GetWindowHandle());\n        }\n    }\n\n    ImGui::Columns(1);\n}\n\nvoid WindowOverlayProperties::UpdatePageMainCatPerformance()\n{\n    //Don't show performance settings for UI source overlays\n    if (ConfigManager::GetValue(configid_int_overlay_capture_source) == ovrl_capsource_ui)\n        return;\n\n    ImGui::Spacing();\n    ImGui::TextColoredUnformatted(ImGui::GetStyleColorVec4(ImGuiCol_ButtonHovered), TranslationManager::GetString(tstr_OvrlPropsCatPerformance));\n    ImGui::Columns(2, \"ColumnPerformance\", false);\n    ImGui::SetColumnWidth(0, m_Column0Width);\n\n    //Max FPS setting for browser overlays\n    if (ConfigManager::GetValue(configid_int_overlay_capture_source) == ovrl_capsource_browser)\n    {\n        OverlayConfigData& data = OverlayManager::Get().GetCurrentConfigData();\n        VRKeyboard& vr_keyboard = UIManager::Get()->GetVRKeyboard();\n\n        ImGui::AlignTextToFramePadding();\n        ImGui::TextUnformatted(TranslationManager::GetString(tstr_SettingsBrowserMaxFrameRate));\n        ImGui::NextColumn();\n\n        int& max_fps = data.ConfigInt[configid_int_overlay_browser_max_fps_override];\n\n        if (m_BrowserMaxFPSValueText.empty())\n        {\n            m_BrowserMaxFPSValueText = TranslationManager::GetString(tstr_SettingsPerformanceUpdateLimiterFPSValue);\n            StringReplaceAll(m_BrowserMaxFPSValueText, \"%FPS%\", std::to_string(max_fps));\n        }\n\n        const char* alt_text = (max_fps < 1) ? TranslationManager::GetString(tstr_SettingsBrowserMaxFrameRateOverrideOff) : m_BrowserMaxFPSValueText.c_str();\n\n        vr_keyboard.VRKeyboardInputBegin(ImGui::SliderWithButtonsGetSliderID(\"MaxFPS\"));\n        if (ImGui::SliderWithButtonsInt(\"MaxFPS\", max_fps, 5, 1, 0, 144, \"##%d\", 0, nullptr, alt_text))\n        {\n            if (max_fps < 1)\n                max_fps = -1;\n\n            IPCManager::Get().PostConfigMessageToDashboardApp(configid_int_overlay_browser_max_fps_override, max_fps);\n\n            m_BrowserMaxFPSValueText = \"\";\n        }\n        vr_keyboard.VRKeyboardInputEnd();\n\n        ImGui::NextColumn();\n    }\n    else    //Update limiter for everything else\n    {\n        UpdateLimiterSetting(true);\n    }\n\n    if (ConfigManager::GetValue(configid_bool_interface_show_advanced_settings))\n    {\n        ImGui::Spacing();\n\n        bool& update_invisible = ConfigManager::GetRef(configid_bool_overlay_update_invisible);\n\n        if (ImGui::Checkbox(TranslationManager::GetString(tstr_OvrlPropsPerformanceInvisibleUpdate), &update_invisible))\n        {\n            IPCManager::Get().PostConfigMessageToDashboardApp(configid_bool_overlay_update_invisible, update_invisible);\n        }\n        ImGui::SameLine(0.0f, ImGui::GetStyle().ItemInnerSpacing.x);\n        HelpMarker(TranslationManager::GetString(tstr_OvrlPropsPerformanceInvisibleUpdateTip));\n\n        ImGui::NextColumn();\n        ImGui::NextColumn();\n    }\n\n    ImGui::Columns(1);\n}\n\nvoid WindowOverlayProperties::UpdatePageMainCatInterface()\n{\n    OverlayConfigData& data = OverlayManager::Get().GetCurrentConfigData();\n    VRKeyboard& vr_keyboard = UIManager::Get()->GetVRKeyboard();\n\n    if (m_PageReturned == wndovrlprop_page_actions_order)\n    {\n        m_ActionButtonsLabel.clear();\n    }\n\n    if (m_ActionButtonsLabel.empty())\n    {\n        m_ActionButtonsLabel = TranslationManager::GetString( (data.ConfigActionBarOrder.size() == 1) ? tstr_SettingsActionsOrderButtonLabelSingular : tstr_SettingsActionsOrderButtonLabel );\n        StringReplaceAll(m_ActionButtonsLabel, \"%COUNT%\", std::to_string(data.ConfigActionBarOrder.size()));\n    }\n\n    ImGui::Spacing();\n    ImGui::TextColoredUnformatted(ImGui::GetStyleColorVec4(ImGuiCol_ButtonHovered), TranslationManager::GetString(tstr_OvrlPropsCatInterface));\n    ImGui::Columns(2, \"ColumnInterface\", false);\n    ImGui::SetColumnWidth(0, m_Column0Width);\n\n    //Overlay Name\n    {\n        ImGui::AlignTextToFramePadding();\n        ImGui::TextUnformatted(TranslationManager::GetString(tstr_OvrlPropsInterfaceOverlayName));\n        ImGui::NextColumn();\n\n        ImGui::PushItemWidth(-1.0f);\n        vr_keyboard.VRKeyboardInputBegin(\"##InputOverlayName\");\n        if (ImGui::InputTextWithHint(\"##InputOverlayName\", TranslationManager::GetString(tstr_OvrlPropsInterfaceOverlayNameAuto), m_BufferOverlayName, 1024))\n        {\n            //If name buffer is not empty, set name from user input, otherwise fall back to auto naming\n            if (m_BufferOverlayName[0] != '\\0')\n            {\n                data.ConfigBool[configid_bool_overlay_name_custom] = true;\n                data.ConfigNameStr = m_BufferOverlayName;\n\n                if (ImGui::StringContainsUnmappedCharacter(m_BufferOverlayName))\n                {\n                    TextureManager::Get().ReloadAllTexturesLater();\n                }\n            }\n            else\n            {\n                data.ConfigBool[configid_bool_overlay_name_custom] = false;\n                OverlayManager::Get().SetCurrentOverlayNameAuto();\n            }\n\n            m_WindowTitle = data.ConfigNameStr;\n        }\n        vr_keyboard.VRKeyboardInputEnd();\n\n        ImGui::Spacing();\n        ImGui::NextColumn();\n    }\n\n    bool custom_order = !ConfigManager::GetValue(configid_bool_overlay_actionbar_order_use_global);  //Meaning is reversed in config, but just handle it here\n\n    if (ImGui::Checkbox(TranslationManager::GetString(tstr_OvrlPropsInterfaceActionOrderCustom), &custom_order))\n    {\n        ConfigManager::SetValue(configid_bool_overlay_actionbar_order_use_global, !custom_order);\n    }\n    ImGui::NextColumn();\n\n    if (!custom_order)\n        ImGui::PushItemDisabled();\n\n    if (ImGui::Button(m_ActionButtonsLabel.c_str()))\n    {\n       PageGoForward(wndovrlprop_page_actions_order);\n    }\n    ImGui::NextColumn();\n\n    if (!custom_order)\n        ImGui::PopItemDisabled();\n\n    //Don't show for UI source overlays\n    if (data.ConfigInt[configid_int_overlay_capture_source] != ovrl_capsource_ui)\n    {\n        if (ImGui::Checkbox(TranslationManager::GetString(tstr_OvrlPropsInterfaceDesktopButtons), &data.ConfigBool[configid_bool_overlay_floatingui_desktops_enabled]))\n        {\n            UIManager::Get()->RepeatFrame();\n        }\n    }\n\n    if (ImGui::Checkbox(TranslationManager::GetString(tstr_OvrlPropsInterfaceExtraButtons), &data.ConfigBool[configid_bool_overlay_floatingui_extras_enabled]))\n    {\n        UIManager::Get()->RepeatFrame();\n    }\n\n    ImGui::Columns(1);\n}\n\nvoid WindowOverlayProperties::UpdatePagePositionChange(bool only_restore_settings)\n{\n    ImGuiStyle& style = ImGui::GetStyle();\n    ImGuiIO& io = ImGui::GetIO();\n    VRKeyboard& vr_keyboard = UIManager::Get()->GetVRKeyboard();\n\n    float& column_width_0 = m_CachedSizes.PositionChange_Column0Width;\n\n    static int active_capture_type = 0; //0 = off, 1 = Move, 2 = Rotate\n    static ImVec2 active_capture_pos;\n\n    static bool is_overlay_transform_temp_unlocked = false;\n\n    if (only_restore_settings)\n    {\n        //Undo temporary transform unlocking\n        if (is_overlay_transform_temp_unlocked)\n        {\n            ConfigManager::SetValue(configid_bool_overlay_transform_locked, true);\n            IPCManager::Get().PostConfigMessageToDashboardApp(configid_bool_overlay_transform_locked, true);\n\n            is_overlay_transform_temp_unlocked = false;\n        }\n\n        return;\n    }\n\n    if ((m_PageAppearing == wndovrlprop_page_position_change) || (m_CachedSizes.PositionChange_Column0Width == 0.0f))\n    {\n        //Find longer label and use that as minimum column width\n        float column_label_width = std::max(ImGui::CalcTextSize(TranslationManager::GetString(tstr_OvrlPropsPositionChangeMove)).x,\n                                            ImGui::CalcTextSize(TranslationManager::GetString(tstr_OvrlPropsPositionChangeRotate)).x);\n\n        column_width_0 = std::max(ImGui::GetFontSize() * 3.75f, column_label_width + style.ItemSpacing.x + style.ItemSpacing.x);\n\n        //Find longest button label\n        float button_label_width = 0.0f;\n        float label_width = 0.0f;\n\n        for (int i = tstr_OvrlPropsPositionChangeForward; i <= tstr_OvrlPropsPositionChangeLookAt; ++i)\n        {\n            label_width = ImGui::CalcTextSize(TranslationManager::GetString((TRMGRStrID)i)).x;\n\n            if (label_width > button_label_width)\n            {\n                button_label_width = label_width;\n            }\n        }\n\n        m_CachedSizes.PositionChange_ButtonWidth = button_label_width + (style.FramePadding.x * 2.0f);\n    }\n\n    if (m_PageAppearing == wndovrlprop_page_position_change)\n    {\n        //Set dragmode state\n        //Dragging the overlay when the UI is open on desktop is pretty inconvenient to get out of when not sitting in front of a real mouse, so let's prevent this\n        if (!UIManager::Get()->IsInDesktopMode())\n        {\n            //Automatically reset the matrix to a saner default if it still has the zero value\n            if (ConfigManager::Get().GetOverlayDetachedTransform().isZero())\n            {\n                IPCManager::Get().PostMessageToDashboardApp(ipcmsg_action, ipcact_overlay_position_reset);\n            }\n\n            bool& is_changing_position = ConfigManager::GetRef(configid_bool_state_overlay_dragmode);\n            is_changing_position = true;\n            IPCManager::Get().PostConfigMessageToDashboardApp(configid_bool_state_overlay_dragselectmode_show_hidden, is_changing_position);\n            IPCManager::Get().PostConfigMessageToDashboardApp(configid_bool_state_overlay_dragmode, is_changing_position);\n        }\n\n        //Temporarily unlock overlay transform if it's locked\n        bool& is_overlay_transform_locked = ConfigManager::GetRef(configid_bool_overlay_transform_locked);\n        if (is_overlay_transform_locked)\n        {\n            is_overlay_transform_locked = false;\n            IPCManager::Get().PostConfigMessageToDashboardApp(configid_bool_overlay_transform_locked, false);\n\n            is_overlay_transform_temp_unlocked = true;\n        }\n    }\n\n    ImGui::PushStyleColor(ImGuiCol_ChildBg, ImVec4(0.00f, 0.00f, 0.00f, 0.00f));\n    ImGui::BeginChild(\"PositionChangeChild\", ImVec2(0.0f, ImGui::GetContentRegionAvail().y - ImGui::GetFrameHeightWithSpacing() - style.ItemSpacing.y), ImGuiChildFlags_NavFlattened);\n    ImGui::PopStyleColor();\n\n    const float column_width_1 = ImGui::GetFrameHeightWithSpacing() * 3.0f + style.ItemInnerSpacing.x;\n    const float column_width_2 = m_CachedSizes.PositionChange_ButtonWidth + (style.ItemInnerSpacing.x * 2.0f);\n    const float column_width_3 = style.ItemSpacing.x * 3.0f;\n\n    ImGui::TextColoredUnformatted(ImGui::GetStyleColorVec4(ImGuiCol_ButtonHovered), TranslationManager::GetString(tstr_OvrlPropsPositionChangeHeader)); \n    ImGui::Indent();\n\n    if (!UIManager::Get()->IsInDesktopMode())\n    {\n        ImGui::TextUnformatted(TranslationManager::GetString(tstr_OvrlPropsPositionChangeHelp));\n    }\n    else\n    {\n        ImGui::TextWrapped(\"%s\", TranslationManager::GetString(tstr_OvrlPropsPositionChangeHelpDesktop));\n    }\n\n    ImGui::Unindent();\n\n    //--Manual Adjustment\n    ImGui::Spacing();\n    ImGui::TextColoredUnformatted(ImGui::GetStyleColorVec4(ImGuiCol_ButtonHovered), TranslationManager::GetString(tstr_OvrlPropsPositionChangeManualAdjustment));\n    ImGui::Indent();\n\n    ImGui::Columns(7, \"ColumnManualAdjust\", false);\n\n    const ImVec2 arrow_button_size(ImGui::GetFrameHeight(), ImGui::GetFrameHeight());\n    const ImVec2 button_size(m_CachedSizes.PositionChange_ButtonWidth, 0.0f);\n\n    ImGui::SetColumnWidth(0, column_width_0);\n    ImGui::SetColumnWidth(1, column_width_1);\n    ImGui::SetColumnWidth(2, column_width_2);\n    ImGui::SetColumnWidth(3, column_width_3);\n    ImGui::SetColumnWidth(4, column_width_0);\n    ImGui::SetColumnWidth(5, column_width_1);\n    ImGui::SetColumnWidth(6, column_width_2);\n\n    ImGui::PushItemFlag(ImGuiItemFlags_ButtonRepeat, true);\n\n    //Row 1\n    ImGui::NextColumn();\n\n    ImGui::SetCursorPosX(ImGui::GetCursorPosX() + ImGui::GetFrameHeightWithSpacing());\n\n    if (ImGui::ArrowButton(\"MoveUp\", ImGuiDir_Up))\n    {\n        //Do some packing\n        unsigned int packed_value = ipcactv_ovrl_pos_adjust_increase;  //Increase bit\n        packed_value |= ipcactv_ovrl_pos_adjust_updown;\n\n        IPCManager::Get().PostMessageToDashboardApp(ipcmsg_action, ipcact_overlay_position_adjust, packed_value);\n    }\n\n    ImGui::NextColumn();\n\n    if (ImGui::Button(TranslationManager::GetString(tstr_OvrlPropsPositionChangeForward), button_size))\n    {\n        unsigned int packed_value = ipcactv_ovrl_pos_adjust_increase;\n        packed_value |= ipcactv_ovrl_pos_adjust_forwback;\n\n        IPCManager::Get().PostMessageToDashboardApp(ipcmsg_action, ipcact_overlay_position_adjust, packed_value);\n    }\n\n    ImGui::NextColumn();\n    ImGui::NextColumn();\n    ImGui::NextColumn();\n\n    ImGui::SetCursorPosX(ImGui::GetCursorPosX() + ImGui::GetFrameHeightWithSpacing());\n\n    if (ImGui::ArrowButton(\"RotUp\", ImGuiDir_Up))\n    {\n        IPCManager::Get().PostMessageToDashboardApp(ipcmsg_action, ipcact_overlay_position_adjust, ipcactv_ovrl_pos_adjust_rotx);\n    }\n\n    ImGui::NextColumn();\n\n    if (ImGui::Button(TranslationManager::GetString(tstr_OvrlPropsPositionChangeRollCW), button_size))\n    {\n        IPCManager::Get().PostMessageToDashboardApp(ipcmsg_action, ipcact_overlay_position_adjust, ipcactv_ovrl_pos_adjust_rotz);\n    }\n\n    ImGui::NextColumn();\n\n    //Row 2\n    ImGui::SetCursorPosX(ImGui::GetCursorPosX() + style.ItemSpacing.x);\n    ImGui::AlignTextToFramePadding();\n    ImGui::TextUnformatted(TranslationManager::GetString(tstr_OvrlPropsPositionChangeMove));\n    ImGui::NextColumn();\n\n    if (ImGui::ArrowButton(\"MoveLeft\", ImGuiDir_Left))\n    {\n        IPCManager::Get().PostMessageToDashboardApp(ipcmsg_action, ipcact_overlay_position_adjust, ipcactv_ovrl_pos_adjust_rightleft);\n    }\n\n    ImGui::SameLine(0.0f, style.ItemInnerSpacing.x);\n\n    if (UIManager::Get()->IsInDesktopMode())\n    {\n        if (io.NavVisible)\n            ImGui::PushItemDisabled();\n\n        bool is_active = (active_capture_type == 1);\n\n        if (is_active)\n            ImGui::PushStyleColor(ImGuiCol_Button, style.Colors[ImGuiCol_ButtonActive]);\n\n        ImGui::Button(TranslationManager::GetString(tstr_OvrlPropsPositionChangeDragButton), arrow_button_size);\n\n        //Activate on mouse down instead of normal button behavior, which is on mouse up\n        if ((active_capture_type == 0) && (ImGui::IsItemHovered()) && ImGui::IsMouseDown(ImGuiMouseButton_Left))\n        {\n            active_capture_type = 1;\n            active_capture_pos = io.MousePos;\n        }\n\n        if (is_active)\n            ImGui::PopStyleColor();\n\n        if (io.NavVisible)\n            ImGui::PopItemDisabled();\n    }\n    else\n    {\n        ImGui::Dummy(arrow_button_size);\n    }\n\n    ImGui::SameLine(0.0f, style.ItemInnerSpacing.x);\n\n    if (ImGui::ArrowButton(\"MoveRight\", ImGuiDir_Right))\n    {\n        unsigned int packed_value = ipcactv_ovrl_pos_adjust_increase;\n        packed_value |= ipcactv_ovrl_pos_adjust_rightleft;\n\n        IPCManager::Get().PostMessageToDashboardApp(ipcmsg_action, ipcact_overlay_position_adjust, packed_value);\n    }\n\n    ImGui::NextColumn();\n\n    if (ImGui::Button(TranslationManager::GetString(tstr_OvrlPropsPositionChangeBackward), button_size))\n    {\n        IPCManager::Get().PostMessageToDashboardApp(ipcmsg_action, ipcact_overlay_position_adjust, ipcactv_ovrl_pos_adjust_forwback);\n    }\n\n    ImGui::NextColumn();\n    ImGui::NextColumn();\n\n    ImGui::AlignTextToFramePadding();\n    ImGui::TextUnformatted(TranslationManager::GetString(tstr_OvrlPropsPositionChangeRotate));\n    ImGui::NextColumn();\n\n    if (ImGui::ArrowButton(\"RotLeft\", ImGuiDir_Left))\n    {\n        IPCManager::Get().PostMessageToDashboardApp(ipcmsg_action, ipcact_overlay_position_adjust, ipcactv_ovrl_pos_adjust_roty);\n    }\n\n    ImGui::SameLine(0.0f, style.ItemInnerSpacing.x);\n\n    if (UIManager::Get()->IsInDesktopMode())\n    {\n        if (io.NavVisible)\n            ImGui::PushItemDisabled();\n\n        ImGui::PushID(\"##Rot\");\n\n        bool is_active = (active_capture_type == 2);\n\n        if (is_active)\n            ImGui::PushStyleColor(ImGuiCol_Button, style.Colors[ImGuiCol_ButtonActive]);\n\n        ImGui::Button(TranslationManager::GetString(tstr_OvrlPropsPositionChangeDragButton), arrow_button_size);\n\n        //Activate on mouse down instead of normal button behavior, which is on mouse up\n        if ((active_capture_type == 0) && (ImGui::IsItemHovered()) && ImGui::IsMouseDown(ImGuiMouseButton_Left))\n        {\n            active_capture_type = 2;\n            active_capture_pos = io.MousePos;\n        }\n\n        if (is_active)\n            ImGui::PopStyleColor();\n    \n        ImGui::PopID();\n\n        if (io.NavVisible)\n            ImGui::PopItemDisabled();\n    }\n    else\n    {\n        ImGui::Dummy(arrow_button_size);\n    }\n\n    ImGui::SameLine(0.0f, style.ItemInnerSpacing.x);\n\n    if (ImGui::ArrowButton(\"RotRight\", ImGuiDir_Right))\n    {\n        unsigned int packed_value = ipcactv_ovrl_pos_adjust_increase;\n        packed_value |= ipcactv_ovrl_pos_adjust_roty;\n\n        IPCManager::Get().PostMessageToDashboardApp(ipcmsg_action, ipcact_overlay_position_adjust, packed_value);\n    }\n\n    ImGui::NextColumn();\n\n    if (ImGui::Button(TranslationManager::GetString(tstr_OvrlPropsPositionChangeRollCCW), button_size))\n    {\n        unsigned int packed_value = ipcactv_ovrl_pos_adjust_increase;\n        packed_value |= ipcactv_ovrl_pos_adjust_rotz;\n\n        IPCManager::Get().PostMessageToDashboardApp(ipcmsg_action, ipcact_overlay_position_adjust, packed_value);\n    }\n\n    ImGui::NextColumn();\n\n    //Row 3\n    ImGui::NextColumn();\n\n    ImGui::SetCursorPosX(ImGui::GetCursorPosX() + ImGui::GetFrameHeightWithSpacing());\n\n    if (ImGui::ArrowButton(\"MoveDown\", ImGuiDir_Down))\n    {\n        IPCManager::Get().PostMessageToDashboardApp(ipcmsg_action, ipcact_overlay_position_adjust, ipcactv_ovrl_pos_adjust_updown);\n    }\n\n    ImGui::NextColumn();\n    ImGui::NextColumn();\n    ImGui::NextColumn();\n    ImGui::NextColumn();\n\n    ImGui::SetCursorPosX(ImGui::GetCursorPosX() + ImGui::GetFrameHeightWithSpacing());\n\n    if (ImGui::ArrowButton(\"RotDown\", ImGuiDir_Down))\n    {\n        unsigned int packed_value = ipcactv_ovrl_pos_adjust_increase;\n        packed_value |= ipcactv_ovrl_pos_adjust_rotx;\n\n        IPCManager::Get().PostMessageToDashboardApp(ipcmsg_action, ipcact_overlay_position_adjust, packed_value);\n    }\n\n    ImGui::PopItemFlag();   //ImGuiItemFlags_ButtonRepeat\n\n    ImGui::NextColumn();\n\n    if (ImGui::Button(TranslationManager::GetString(tstr_OvrlPropsPositionChangeLookAt), button_size))\n    {\n        IPCManager::Get().PostMessageToDashboardApp(ipcmsg_action, ipcact_overlay_position_adjust, ipcactv_ovrl_pos_adjust_lookat);\n    }\n\n    ImGui::Columns(1);\n    ImGui::Unindent();\n\n    //--Additional Offset\n    if (ConfigManager::GetValue(configid_bool_interface_show_advanced_settings))\n    {\n        ImGui::Spacing();\n        ImGui::TextColoredUnformatted(ImGui::GetStyleColorVec4(ImGuiCol_ButtonHovered), TranslationManager::GetString(tstr_OvrlPropsPositionChangeOffset));\n\n        ImGui::Columns(2, \"ColumnOffset\", false);\n        ImGui::SetColumnWidth(0, m_Column0Width);\n\n        ImGui::AlignTextToFramePadding();\n        ImGui::TextUnformatted(TranslationManager::GetString(tstr_OvrlPropsPositionChangeOffsetUpDown));\n        ImGui::NextColumn();\n\n        float& up = ConfigManager::GetRef(configid_float_overlay_offset_up);\n\n        vr_keyboard.VRKeyboardInputBegin( ImGui::SliderWithButtonsGetSliderID(\"OverlayOffsetUp\") );\n        if (ImGui::SliderWithButtonsFloat(\"OverlayOffsetUp\", up, 0.1f, 0.01f, -5.0f, 5.0f, \"%.2f m\", ImGuiSliderFlags_Logarithmic))\n        {\n            IPCManager::Get().PostConfigMessageToDashboardApp(configid_float_overlay_offset_up, up);\n        }\n        vr_keyboard.VRKeyboardInputEnd();\n\n        ImGui::NextColumn();\n\n        ImGui::AlignTextToFramePadding();\n        ImGui::TextUnformatted(TranslationManager::GetString(tstr_OvrlPropsPositionChangeOffsetRightLeft));\n        ImGui::NextColumn();\n\n        float& right = ConfigManager::GetRef(configid_float_overlay_offset_right);\n\n        vr_keyboard.VRKeyboardInputBegin( ImGui::SliderWithButtonsGetSliderID(\"OverlayOffsetRight\") );\n        if (ImGui::SliderWithButtonsFloat(\"OverlayOffsetRight\", right, 0.1f, 0.01f, -5.0f, 5.0f, \"%.2f m\", ImGuiSliderFlags_Logarithmic))\n        {\n            IPCManager::Get().PostConfigMessageToDashboardApp(configid_float_overlay_offset_right, right);\n        }\n        vr_keyboard.VRKeyboardInputEnd();\n\n        ImGui::NextColumn();\n\n        ImGui::AlignTextToFramePadding();\n        ImGui::TextUnformatted(TranslationManager::GetString(tstr_OvrlPropsPositionChangeOffsetForwardBackward));\n        ImGui::NextColumn();\n\n        float& forward = ConfigManager::GetRef(configid_float_overlay_offset_forward);\n\n        vr_keyboard.VRKeyboardInputBegin( ImGui::SliderWithButtonsGetSliderID(\"OverlayOffsetForward\") );\n        if (ImGui::SliderWithButtonsFloat(\"OverlayOffsetForward\", forward, 0.1f, 0.01f, -5.0f, 5.0f, \"%.2f m\", ImGuiSliderFlags_Logarithmic))\n        {\n            IPCManager::Get().PostConfigMessageToDashboardApp(configid_float_overlay_offset_forward, forward);\n        }\n        vr_keyboard.VRKeyboardInputEnd();\n\n        ImGui::Columns(1);\n    }\n\n    //--Drag Settings\n    ImGui::Spacing();\n    ImGui::TextColoredUnformatted(ImGui::GetStyleColorVec4(ImGuiCol_ButtonHovered), TranslationManager::GetString(tstr_OvrlPropsPositionChangeDragSettings));\n\n    ImGui::Indent();\n\n    bool& auto_docking = ConfigManager::GetRef(configid_bool_input_drag_auto_docking);\n\n    if (ImGui::Checkbox(TranslationManager::GetString(tstr_OvrlPropsPositionChangeDragSettingsAutoDocking), &auto_docking))\n    {\n        IPCManager::Get().PostConfigMessageToDashboardApp(configid_bool_input_drag_auto_docking, auto_docking);\n    }\n\n    ImGui::Unindent();\n\n    //Fixed Size\n    ImGui::Spacing();\n    ImGui::Columns(2, \"ColumnFixedDistance\", false);\n    ImGui::SetColumnWidth(0, m_Column0Width);\n\n    bool&  force_fixed_distance            = ConfigManager::GetRef(configid_bool_input_drag_fixed_distance);\n    float& force_fixed_distance_value      = ConfigManager::GetRef(configid_float_input_drag_fixed_distance_m);\n    int&   force_fixed_distance_shape      = ConfigManager::GetRef(configid_int_input_drag_fixed_distance_shape);\n    bool&  force_fixed_distance_auto_curve = ConfigManager::GetRef(configid_bool_input_drag_fixed_distance_auto_curve);\n    bool&  force_fixed_distance_auto_tilt  = ConfigManager::GetRef(configid_bool_input_drag_fixed_distance_auto_tilt);\n\n    if (ImGui::Checkbox(TranslationManager::GetString(tstr_OvrlPropsPositionChangeDragSettingsForceDistance), &force_fixed_distance))\n    {\n        IPCManager::Get().PostConfigMessageToDashboardApp(configid_bool_input_drag_fixed_distance, force_fixed_distance);\n    }\n\n    ImGui::NextColumn();\n\n    if (!force_fixed_distance)\n        ImGui::PushItemDisabled();\n\n    vr_keyboard.VRKeyboardInputBegin( ImGui::SliderWithButtonsGetSliderID(\"FixedDistance\") );\n    if (ImGui::SliderWithButtonsFloat(\"FixedDistanceValue\", force_fixed_distance_value, 0.1f, 0.01f, 0.5f, 10.0f, \"%.2f m\", ImGuiSliderFlags_Logarithmic))\n    {\n        if (force_fixed_distance_value < 0.5f)\n            force_fixed_distance_value = 0.5f;\n\n        IPCManager::Get().PostConfigMessageToDashboardApp(configid_float_input_drag_fixed_distance_m, force_fixed_distance_value);\n    }\n    vr_keyboard.VRKeyboardInputEnd();\n\n    ImGui::NextColumn();\n    ImGui::Indent(ImGui::GetFrameHeightWithSpacing());\n\n    ImGui::AlignTextToFramePadding();\n    ImGui::TextUnformatted(TranslationManager::GetString(tstr_OvrlPropsPositionChangeDragSettingsForceDistanceShape));\n    ImGui::NextColumn();\n\n    ImGui::SetNextItemWidth(-1);\n    if (TranslatedComboAnimated(\"##ComboFixedDistanceShape\", force_fixed_distance_shape, \n                                tstr_OvrlPropsPositionChangeDragSettingsForceDistanceShapeSphere, tstr_OvrlPropsPositionChangeDragSettingsForceDistanceShapeCylinder))\n    {\n        IPCManager::Get().PostConfigMessageToDashboardApp(configid_int_input_drag_fixed_distance_shape, force_fixed_distance_shape);\n    }\n\n    ImGui::NextColumn();\n    ImGui::NextColumn();\n\n    if (ImGui::Checkbox(TranslationManager::GetString(tstr_OvrlPropsPositionChangeDragSettingsForceDistanceAutoCurve), &force_fixed_distance_auto_curve))\n    {\n        IPCManager::Get().PostConfigMessageToDashboardApp(configid_bool_input_drag_fixed_distance_auto_curve, force_fixed_distance_auto_curve);\n    }\n\n    ImGui::NextColumn();\n    ImGui::NextColumn();\n\n    if (ImGui::Checkbox(TranslationManager::GetString(tstr_OvrlPropsPositionChangeDragSettingsForceDistanceAutoTilt), &force_fixed_distance_auto_tilt))\n    {\n        IPCManager::Get().PostConfigMessageToDashboardApp(configid_bool_input_drag_fixed_distance_auto_tilt, force_fixed_distance_auto_tilt);\n    }\n\n    if (!force_fixed_distance)\n        ImGui::PopItemDisabled();\n\n    ImGui::Unindent(ImGui::GetFrameHeightWithSpacing());\n    ImGui::Spacing();\n    ImGui::NextColumn();\n\n    //Position Snapping\n    bool&  snap_position      = ConfigManager::GetRef(configid_bool_input_drag_snap_position);\n    float& snap_position_size = ConfigManager::GetRef(configid_float_input_drag_snap_position_size);\n\n    if (ImGui::Checkbox(TranslationManager::GetString(tstr_OvrlPropsPositionChangeDragSettingsSnapPosition), &snap_position))\n    {\n        IPCManager::Get().PostConfigMessageToDashboardApp(configid_bool_input_drag_snap_position, snap_position);\n    }\n    ImGui::NextColumn();\n\n    if (!snap_position)\n        ImGui::PushItemDisabled();\n\n    vr_keyboard.VRKeyboardInputBegin( ImGui::SliderWithButtonsGetSliderID(\"SnapPosition\") );\n    if (ImGui::SliderWithButtonsFloat(\"SnapPositionSize\", snap_position_size, 0.1f, 0.01f, 0.01f, 1.0f, \"%.2f m\", ImGuiSliderFlags_Logarithmic))\n    {\n        if (snap_position_size < 0.01f)\n            snap_position_size = 0.01f;\n\n        IPCManager::Get().PostConfigMessageToDashboardApp(configid_float_input_drag_snap_position_size, snap_position_size);\n    }\n    vr_keyboard.VRKeyboardInputEnd();\n\n    if (!snap_position)\n        ImGui::PopItemDisabled();\n\n    ImGui::NextColumn();\n\n    //Rotation Snapping\n    bool& snap_rotation      = ConfigManager::GetRef(configid_bool_input_drag_snap_rotation);\n    bool& snap_rotation_x    = ConfigManager::GetRef(configid_bool_input_drag_snap_rotation_x);\n    bool& snap_rotation_y    = ConfigManager::GetRef(configid_bool_input_drag_snap_rotation_y);\n    bool& snap_rotation_z    = ConfigManager::GetRef(configid_bool_input_drag_snap_rotation_z);\n    int& snap_rotation_angle = ConfigManager::GetRef(configid_int_input_drag_snap_rotation_angle);\n\n    //This checkbox is somewhat redundant and is mostly there for aesthetical reasons\n    if (ImGui::Checkbox(TranslationManager::GetString(tstr_OvrlPropsPositionChangeDragSettingsSnapRotation), &snap_rotation))\n    {\n        IPCManager::Get().PostConfigMessageToDashboardApp(configid_bool_input_drag_snap_rotation, snap_rotation);\n    }\n    ImGui::NextColumn();\n\n    if (!snap_rotation)\n        ImGui::PushItemDisabled();\n\n    vr_keyboard.VRKeyboardInputBegin( ImGui::SliderWithButtonsGetSliderID(\"SnapRotationAngle\") );\n    if (ImGui::SliderWithButtonsInt(\"SnapRotationAngle\", snap_rotation_angle, 5, 1, 1, 180, \"%i\\xc2\\xb0\"))  //using k_pch_degree_symbol here\n    {\n        snap_rotation_angle = clamp(snap_rotation_angle, 1, 359);\n\n        IPCManager::Get().PostConfigMessageToDashboardApp(configid_int_input_drag_snap_rotation_angle, snap_rotation_angle);\n    }\n    vr_keyboard.VRKeyboardInputEnd();\n\n    ImGui::NextColumn();\n    ImGui::NextColumn();\n\n    //Arranged in Y-X-Z order to result in hopefully more intuitive Yaw-Pitch-Roll checkboxes\n    if (ImGui::Checkbox(TranslationManager::GetString(tstr_OvrlPropsPositionChangeDragSettingsSnapRotationYaw), &snap_rotation_y))\n    {\n        IPCManager::Get().PostConfigMessageToDashboardApp(configid_bool_input_drag_snap_rotation_y, snap_rotation_y);\n    }\n    ImGui::SameLine();\n\n    if (ImGui::Checkbox(TranslationManager::GetString(tstr_OvrlPropsPositionChangeDragSettingsSnapRotationPitch), &snap_rotation_x))\n    {\n        IPCManager::Get().PostConfigMessageToDashboardApp(configid_bool_input_drag_snap_rotation_x, snap_rotation_x);\n    }\n    ImGui::SameLine();\n\n    if (ImGui::Checkbox(TranslationManager::GetString(tstr_OvrlPropsPositionChangeDragSettingsSnapRotationRoll), &snap_rotation_z))\n    {\n        IPCManager::Get().PostConfigMessageToDashboardApp(configid_bool_input_drag_snap_rotation_z, snap_rotation_z);\n    }\n    ImGui::SameLine();\n\n    if (!snap_rotation)\n        ImGui::PopItemDisabled();\n\n    ImGui::Columns(1);\n\n    ImGui::EndChild();\n    ImGui::SetCursorPosY( ImGui::GetCursorPosY() + (ImGui::GetContentRegionAvail().y - ImGui::GetFrameHeightWithSpacing()) );\n\n    //--Confirmation buttons\n    ImGui::Separator();\n\n    if (ImGui::Button(TranslationManager::GetString(tstr_DialogDone))) \n    {\n        PageGoBack();\n    }\n\n    ImGui::SameLine();\n\n    static float button_reset_width = 0.0f;\n\n    ImGui::SetCursorPosX(ImGui::GetCursorPosX() + ImGui::GetContentRegionAvail().x - button_reset_width);\n\n    if (ImGui::Button(TranslationManager::GetString(tstr_OvrlPropsPositionReset)))\n    {\n        OverlayPositionReset();\n    }\n\n    button_reset_width = ImGui::GetItemRectSize().x;\n\n    //--Mouse Dragging\n    if (active_capture_type != 0)\n    {\n        if (ImGui::IsMouseReleased(ImGuiMouseButton_Left))\n        {\n            active_capture_type = 0;\n            ImGui::SetMouseCursor(ImGuiMouseCursor_Arrow);\n        }\n        else\n        {\n            ImGui::SetMouseCursor(ImGuiMouseCursor_None);\n\n            const float delta_step = 5.0f;\n            ImVec2 mouse_delta = ImGui::GetMouseDragDelta(ImGuiMouseButton_Left);\n\n            if (active_capture_type == 1)\n            {\n                //X -> Right/Left\n                if (fabs(mouse_delta.x) > delta_step)\n                {\n                    unsigned int packed_value = (mouse_delta.x > 0.0f) ? ipcactv_ovrl_pos_adjust_increase : 0;\n                    packed_value |= ipcactv_ovrl_pos_adjust_rightleft;\n\n                    //Using the existing position adjust message a few times might be cheap, but it also results in actually useful grid-snapped adjustments\n                    int steps = (int)(fabs(mouse_delta.x) / delta_step);\n                    for (int i = 0; i < steps; ++i)\n                    {\n                        IPCManager::Get().PostMessageToDashboardApp(ipcmsg_action, ipcact_overlay_position_adjust, packed_value);\n                    }\n                }\n\n                //Y -> Up/Down\n                if (fabs(mouse_delta.y) > delta_step)\n                {\n                    unsigned int packed_value = (mouse_delta.y < 0.0f) ? ipcactv_ovrl_pos_adjust_increase : 0;\n                    packed_value |= ipcactv_ovrl_pos_adjust_updown;\n\n                    int steps = (int)(fabs(mouse_delta.y) / delta_step);\n                    for (int i = 0; i < steps; ++i)\n                    {\n                        IPCManager::Get().PostMessageToDashboardApp(ipcmsg_action, ipcact_overlay_position_adjust, packed_value);\n                    }\n                }\n\n                //Wheel -> Forward/Backward or Change Width (when shift is down)\n                if (fabs(io.MouseWheel) > 0.0f)\n                {\n                    if (io.KeyShift)\n                    {\n                        const float size_step = (snap_position) ? snap_position_size : 0.20f;\n                        float& overlay_width = ConfigManager::GetRef(configid_float_overlay_width);\n                        overlay_width = std::max(0.05f, overlay_width + (io.MouseWheel * size_step) );\n\n                        //Send adjusted width to dashboard app\n                        IPCManager::Get().PostConfigMessageToDashboardApp(configid_float_overlay_width, overlay_width);\n                    }\n                    else\n                    {\n                        unsigned int packed_value = (io.MouseWheel < 0.0f) ? ipcactv_ovrl_pos_adjust_increase : 0;\n                        packed_value |= ipcactv_ovrl_pos_adjust_forwback;\n\n                        int steps = (int)(fabs(io.MouseWheel) * delta_step);\n                        for (int i = 0; i < steps; ++i)\n                        {\n                            IPCManager::Get().PostMessageToDashboardApp(ipcmsg_action, ipcact_overlay_position_adjust, packed_value);\n                        }\n                    }\n                }\n            }\n            else //active_capture_type == 2\n            {\n                //X -> Rotate Y+-\n                if (fabs(mouse_delta.x) > delta_step)\n                {\n                    unsigned int packed_value = (mouse_delta.x > 0.0f) ? ipcactv_ovrl_pos_adjust_increase : 0;\n                    packed_value |= ipcactv_ovrl_pos_adjust_roty;\n\n                    int steps = (int)(fabs(mouse_delta.x) / delta_step);\n                    for (int i = 0; i < steps; ++i)\n                    {\n                        IPCManager::Get().PostMessageToDashboardApp(ipcmsg_action, ipcact_overlay_position_adjust, packed_value);\n                    }\n                }\n\n                //Y -> Rotate X+-\n                if (fabs(mouse_delta.y) > delta_step)\n                {\n                    unsigned int packed_value = (mouse_delta.y > 0.0f) ? ipcactv_ovrl_pos_adjust_increase : 0;\n                    packed_value |= ipcactv_ovrl_pos_adjust_rotx;\n\n                    int steps = (int)(fabs(mouse_delta.y) / delta_step);\n                    for (int i = 0; i < steps; ++i)\n                    {\n                        IPCManager::Get().PostMessageToDashboardApp(ipcmsg_action, ipcact_overlay_position_adjust, packed_value);\n                    }\n                }\n\n                //Wheel -> Rotate Z+-\n                if (fabs(io.MouseWheel) > 0.0f)\n                {\n                    unsigned int packed_value = (io.MouseWheel > 0.0f) ? ipcactv_ovrl_pos_adjust_increase : 0;\n                    packed_value |= ipcactv_ovrl_pos_adjust_rotz;\n\n                    int steps = (int)(fabs(io.MouseWheel) * delta_step);\n                    for (int i = 0; i < steps; ++i)\n                    {\n                        IPCManager::Get().PostMessageToDashboardApp(ipcmsg_action, ipcact_overlay_position_adjust, packed_value);\n                    }\n                }\n            }\n\n            //Reset mouse cursor if needed\n            if (fabs(mouse_delta.x) > delta_step)\n            {\n                io.WantSetMousePos = true;\n                io.MousePos.x = active_capture_pos.x;\n                io.MouseClickedPos[0].x = io.MousePos.x;    //for drag delta\n            }\n\n            if (fabs(mouse_delta.y) > delta_step)\n            {\n                io.WantSetMousePos = true;\n                io.MousePos.y = active_capture_pos.y;\n                io.MouseClickedPos[0].y = io.MousePos.y;\n            }\n\n            //Prevent scrolling on the parent window\n            ImGui::BlockWidgetInput();\n        }\n    }\n}\n\nvoid WindowOverlayProperties::UpdatePageCropChange(bool only_restore_settings)\n{\n    VRKeyboard& vr_keyboard = UIManager::Get()->GetVRKeyboard();\n    ImGuiStyle& style = ImGui::GetStyle();\n\n    static ImGui::ImGuiDraggableRectAreaState draggable_rect_state;\n    static ImVec2 draggable_rect_area;\n    static ImVec2 draggable_rect_area_base_size;\n    static ImVec4 draggable_rect_base_rect;\n    static float draggable_rect_scale = 1.0f;\n\n    bool& crop_enabled = ConfigManager::GetRef(configid_bool_overlay_crop_enabled);\n    int& crop_x        = ConfigManager::GetRef(configid_int_overlay_crop_x);\n    int& crop_y        = ConfigManager::GetRef(configid_int_overlay_crop_y);\n    int& crop_width    = ConfigManager::GetRef(configid_int_overlay_crop_width);\n    int& crop_height   = ConfigManager::GetRef(configid_int_overlay_crop_height);\n\n    if ( (only_restore_settings) && (m_IsConfigDataModified) )\n    {\n        //Restore previous settings\n        OverlayConfigData& data = OverlayManager::Get().GetCurrentConfigData();\n        crop_enabled       = m_ConfigDataTemp.ConfigBool[configid_bool_overlay_crop_enabled];\n        crop_x             = m_ConfigDataTemp.ConfigInt[configid_int_overlay_crop_x];\n        crop_y             = m_ConfigDataTemp.ConfigInt[configid_int_overlay_crop_y];\n        crop_width         = m_ConfigDataTemp.ConfigInt[configid_int_overlay_crop_width];\n        crop_height        = m_ConfigDataTemp.ConfigInt[configid_int_overlay_crop_height];\n        data.ConfigNameStr = m_ConfigDataTemp.ConfigNameStr;\n\n        SetActiveOverlayID(m_ActiveOverlayID);\n\n        IPCManager::Get().PostConfigMessageToDashboardApp(configid_bool_overlay_crop_enabled, crop_enabled);\n        IPCManager::Get().PostConfigMessageToDashboardApp(configid_int_overlay_crop_width,    crop_width);\n        IPCManager::Get().PostConfigMessageToDashboardApp(configid_int_overlay_crop_height,   crop_height);\n        IPCManager::Get().PostConfigMessageToDashboardApp(configid_int_overlay_crop_x,        crop_x);\n        IPCManager::Get().PostConfigMessageToDashboardApp(configid_int_overlay_crop_y,        crop_y);\n\n        m_IsConfigDataModified = false;\n        return;\n    }\n\n    if ( (m_PageAppearing == wndovrlprop_page_crop_change) && (!m_IsWindowAppearing) )\n    {\n        m_IsConfigDataModified = true;\n        m_ConfigDataTemp = OverlayManager::Get().GetCurrentConfigData();\n\n        //Enable cropping so modifications are always visible\n        crop_enabled = true;\n        IPCManager::Get().PostConfigMessageToDashboardApp(configid_bool_overlay_crop_enabled, true);\n    }\n\n    int ovrl_width, ovrl_height;\n\n    if (ConfigManager::GetValue(configid_int_overlay_capture_source) == ovrl_capsource_desktop_duplication)\n    {\n        UIManager::Get()->GetDesktopOverlayPixelSize(ovrl_width, ovrl_height);\n    }\n    else //This would also work for desktop duplication except the above works without the dashboard app running while this doesn't\n    {\n        ovrl_width  = ConfigManager::GetValue(configid_int_overlay_state_content_width);\n        ovrl_height = ConfigManager::GetValue(configid_int_overlay_state_content_height);\n\n        //If overlay width and height are uninitialized, set them to the crop values at least\n        if ((ovrl_width == -1) && (ovrl_height == -1))\n        {\n            //Use user width and height if it's a browser overlay, however\n            if (ConfigManager::GetValue(configid_int_overlay_capture_source) == ovrl_capsource_browser)\n            {\n                ovrl_width  = ConfigManager::GetValue(configid_int_overlay_user_width);\n                ovrl_height = ConfigManager::GetValue(configid_int_overlay_user_height);\n            }\n            else\n            {\n                ovrl_width  = crop_width  + crop_x;\n                ovrl_height = crop_height + crop_y;\n\n                ConfigManager::SetValue(configid_int_overlay_state_content_width,  ovrl_width);\n                ConfigManager::SetValue(configid_int_overlay_state_content_height, ovrl_height);\n            }\n        }\n    }\n\n    int crop_width_max  = ovrl_width  - crop_x;\n    int crop_height_max = ovrl_height - crop_y;\n    int crop_width_ui   = (crop_width  == -1) ? crop_width_max  + 1 : crop_width;\n    int crop_height_ui  = (crop_height == -1) ? crop_height_max + 1 : crop_height;\n\n    const bool disable_crop_edit = ((ovrl_width == -1) && (ovrl_height == -1));\n    const bool is_crop_invalid = ((crop_x > ovrl_width - 1) || (crop_y > ovrl_height - 1) || (crop_width_max < 1) || (crop_height_max < 1));\n\n    //--Cropping Area (Draggable Crop Rect)\n    const bool ovrl_size_changed = ((draggable_rect_area_base_size.x != ovrl_width) || (draggable_rect_area_base_size.y != ovrl_height));\n    const bool base_rect_changed = ((draggable_rect_base_rect.x != crop_x)     || (draggable_rect_base_rect.y != crop_y) || \n                                    (draggable_rect_base_rect.z != crop_width) || (draggable_rect_base_rect.w != crop_height));\n\n    if (ovrl_size_changed)\n    {\n        //Figure out sizing for the draggable rect area\n        const float region_width = ImGui::GetContentRegionAvail().x - style.IndentSpacing - 2.0f;   //The -2 comes from ceiling the area floats for slight oversizing\n        const float region_height = (UIManager::Get()->IsInDesktopMode()) ? ImGui::GetFontSize() * 16.0f : ImGui::GetFontSize() * 10.5f;\n\n        if (!disable_crop_edit)\n        {\n            draggable_rect_scale = std::min(region_width / ovrl_width, region_height / ovrl_height);\n            draggable_rect_area = {std::max(8.0f, ceilf(ovrl_width * draggable_rect_scale)), std::max(8.0f, ceilf(ovrl_height * draggable_rect_scale))};\n        }\n        else    //Don't break UI when we can't calculate a rect area\n        {\n            draggable_rect_scale = 1.0f;\n            draggable_rect_area = {region_width, region_height};\n        }\n\n        draggable_rect_area_base_size = {(float)ovrl_width, (float)ovrl_height};\n    }\n\n    //Detect external changes to the rect and apply to draggable rect (we try to avoid constantly converting back and forth to not fall into float inaccuracy troubles)\n    if (base_rect_changed)\n    {\n        draggable_rect_state.RectPos.x  = crop_x         * draggable_rect_scale;\n        draggable_rect_state.RectPos.y  = crop_y         * draggable_rect_scale;\n        draggable_rect_state.RectSize.x = ((crop_width  == -1) ? crop_width_max  : crop_width)  * draggable_rect_scale;\n        draggable_rect_state.RectSize.y = ((crop_height == -1) ? crop_height_max : crop_height) * draggable_rect_scale;\n\n        draggable_rect_base_rect = {(float)crop_x, (float)crop_y, (float)crop_width, (float)crop_height};\n    }\n\n    ImGui::TextColoredUnformatted(ImGui::GetStyleColorVec4(ImGuiCol_ButtonHovered), TranslationManager::GetString(tstr_OvrlPropsCrop));\n\n    ImGui::Indent();\n    ImGui::PushTextWrapPos();\n    ImGui::TextUnformatted(TranslationManager::GetString(tstr_OvrlPropsCropHelp));\n    ImGui::PopTextWrapPos();\n\n    ImGui::SetCursorPosX(ImGui::GetCursorPosX() + (ImGui::GetContentRegionAvail().x / 2.0f) - (draggable_rect_area.x / 2.0f));\n\n    if (disable_crop_edit)\n        ImGui::PushItemDisabled();\n\n    if (ImGui::DraggableRectArea(\"DragArea\", draggable_rect_area, draggable_rect_state))\n    {\n        crop_x      = clamp((int)floorf(draggable_rect_state.RectPos.x / draggable_rect_scale), 0, ovrl_width  - 1);\n        crop_y      = clamp((int)floorf(draggable_rect_state.RectPos.y / draggable_rect_scale), 0, ovrl_height - 1);\n        crop_width_max  = ovrl_width  - crop_x;\n        crop_height_max = ovrl_height - crop_y;\n        crop_width  = clamp((int)ceilf(draggable_rect_state.RectSize.x / draggable_rect_scale), 1, crop_width_max);\n        crop_height = clamp((int)ceilf(draggable_rect_state.RectSize.y / draggable_rect_scale), 1, crop_height_max);\n\n        //Set to max if width/height matches total width (we don't do it partial crops which could still use max values tho)\n        if (crop_width >= ovrl_width)\n        {\n            crop_width = -1;\n        }\n\n        if (crop_height >= ovrl_height)\n        {\n            crop_height = -1;\n        }\n\n        IPCManager::Get().PostConfigMessageToDashboardApp(configid_int_overlay_crop_width,  crop_width);\n        IPCManager::Get().PostConfigMessageToDashboardApp(configid_int_overlay_crop_height, crop_height);\n        IPCManager::Get().PostConfigMessageToDashboardApp(configid_int_overlay_crop_x,      crop_x);\n        IPCManager::Get().PostConfigMessageToDashboardApp(configid_int_overlay_crop_y,      crop_y);\n\n        draggable_rect_base_rect = {(float)crop_x, (float)crop_y, (float)crop_width, (float)crop_height};\n    }\n\n    if (disable_crop_edit)\n        ImGui::PopItemDisabled();\n\n    ImGui::Unindent();\n\n    //--Manual Adjustment\n    static int crop_width_before_edit  = -1;\n    static int crop_height_before_edit = -1;\n\n    ImGui::TextColoredUnformatted(ImGui::GetStyleColorVec4(ImGuiCol_ButtonHovered), TranslationManager::GetString(tstr_OvrlPropsCropManualAdjust));\n\n    if ( (!disable_crop_edit) && (is_crop_invalid) )\n    {\n        ImGui::SameLine(0.0f, style.ItemInnerSpacing.x);\n        HelpMarker(TranslationManager::GetString(tstr_OvrlPropsCropInvalidTip), \"(!)\");\n    }\n\n    ImGui::Columns(2, \"ColumnCrop\", false);\n    ImGui::SetColumnWidth(0, m_Column0Width);\n\n    if (disable_crop_edit)\n        ImGui::PushItemDisabled();\n\n    ImGui::AlignTextToFramePadding();\n    ImGui::TextUnformatted(TranslationManager::GetString(tstr_OvrlPropsCropX));\n    ImGui::NextColumn();\n\n    //We need to delay handling the change after checking if it was newly activated in order to have the crop width adjust back automatically when possible\n    vr_keyboard.VRKeyboardInputBegin( ImGui::SliderWithButtonsGetSliderID(\"CropX\") );\n    const bool crop_x_changed = ImGui::SliderWithButtonsInt(\"CropX\", crop_x, 1, 1, 0, ovrl_width - 1, \"%d px\");\n    vr_keyboard.VRKeyboardInputEnd();\n\n    if (ImGui::IsItemActivated())\n    {\n        crop_width_before_edit = crop_width;\n    }\n\n    if (crop_x_changed)\n    {\n        //Note that we need to clamp the new value as neither the buttons nor the slider on direct input do so (they could, but this is in line with the rest of ImGui)\n        crop_x = clamp(crop_x, 0, ovrl_width - 1);\n\n        if (crop_x + crop_width_before_edit > ovrl_width)\n        {\n            crop_width = ovrl_width - crop_x;\n            IPCManager::Get().PostConfigMessageToDashboardApp(configid_int_overlay_crop_width, crop_width);\n        }\n        else if (crop_width != crop_width_before_edit)\n        {\n            crop_width = crop_width_before_edit;\n            IPCManager::Get().PostConfigMessageToDashboardApp(configid_int_overlay_crop_width, crop_width);\n        }\n\n        IPCManager::Get().PostConfigMessageToDashboardApp(configid_int_overlay_crop_x, crop_x);\n    }\n\n    ImGui::NextColumn();\n\n    ImGui::AlignTextToFramePadding();\n    ImGui::TextUnformatted(TranslationManager::GetString(tstr_OvrlPropsCropY));\n    ImGui::NextColumn();\n\n    vr_keyboard.VRKeyboardInputBegin( ImGui::SliderWithButtonsGetSliderID(\"CropY\") );\n    const bool crop_y_changed = ImGui::SliderWithButtonsInt(\"CropY\", crop_y, 1, 1, 0, ovrl_height - 1, \"%d px\");\n    vr_keyboard.VRKeyboardInputEnd();\n\n    if (ImGui::IsItemActivated())\n    {\n        crop_height_before_edit = crop_height;\n    }\n\n    if (crop_y_changed)\n    {\n        crop_y = clamp(crop_y, 0, ovrl_height - 1);\n\n        if (crop_y + crop_height_before_edit > ovrl_height)\n        {\n            crop_height = ovrl_height - crop_y;\n            IPCManager::Get().PostConfigMessageToDashboardApp(configid_int_overlay_crop_height, crop_height);\n        }\n        else if (crop_height != crop_height_before_edit)\n        {\n            crop_height = crop_height_before_edit;\n            IPCManager::Get().PostConfigMessageToDashboardApp(configid_int_overlay_crop_height, crop_height);\n        }\n\n        IPCManager::Get().PostConfigMessageToDashboardApp(configid_int_overlay_crop_y, crop_y);\n    }\n\n    ImGui::NextColumn();\n\n    ImGui::AlignTextToFramePadding();\n    ImGui::TextUnformatted(TranslationManager::GetString(tstr_OvrlPropsCropWidth));\n    ImGui::NextColumn();\n\n    ImGui::SetNextItemWidth(-1);\n\n    //The way mapping max + 1 == -1 value into the slider is done is a bit convoluted, but it works\n    const char* text_alt_w = (crop_width == -1) ? TranslationManager::GetString(tstr_OvrlPropsAppearanceCropValueMax) : nullptr;\n\n    vr_keyboard.VRKeyboardInputBegin( ImGui::SliderWithButtonsGetSliderID(\"CropWidth\") );\n    if (ImGui::SliderWithButtonsInt(\"CropWidth\", crop_width_ui, 1, 1, 1, crop_width_max + 1, (crop_width == -1) ? \"\" : \"%d px\", 0, nullptr, text_alt_w))\n    {\n        crop_width = clamp(crop_width_ui, 1, crop_width_max + 1);\n\n        if (crop_width_ui > crop_width_max)\n            crop_width = -1;\n\n        IPCManager::Get().PostConfigMessageToDashboardApp(configid_int_overlay_crop_width, crop_width);\n    }\n    vr_keyboard.VRKeyboardInputEnd();\n\n    ImGui::NextColumn();\n\n    ImGui::AlignTextToFramePadding();\n    ImGui::TextUnformatted(TranslationManager::GetString(tstr_OvrlPropsCropHeight));\n    ImGui::NextColumn();\n\n    ImGui::SetNextItemWidth(-1);\n\n    const char* text_alt_h = (crop_height == -1) ? TranslationManager::GetString(tstr_OvrlPropsAppearanceCropValueMax) : nullptr;\n\n    vr_keyboard.VRKeyboardInputBegin( ImGui::SliderWithButtonsGetSliderID(\"CropHeight\") );\n    if (ImGui::SliderWithButtonsInt(\"CropHeight\", crop_height_ui, 1, 1, 1, crop_height_max + 1, (crop_height == -1) ? \"\" : \"%d px\", 0, nullptr, text_alt_h))\n    {\n        crop_height = clamp(crop_height_ui, 1, crop_height_max + 1);\n\n        if (crop_height_ui > crop_height_max)\n            crop_height = -1;\n\n        IPCManager::Get().PostConfigMessageToDashboardApp(configid_int_overlay_crop_height, crop_height);\n    }\n    vr_keyboard.VRKeyboardInputEnd();\n\n    ImGui::NextColumn();\n    ImGui::NextColumn();\n\n    //Needs dashboard app to be running\n    if ( (ConfigManager::GetValue(configid_int_overlay_capture_source) == ovrl_capsource_desktop_duplication) && (UIManager::Get()->IsOpenVRLoaded()) )\n    {\n        if (ImGui::Button(TranslationManager::GetString(tstr_OvrlPropsCropToWindow)))\n        {\n            //Have the dashboard app figure out how to do this as the UI doesn't have all data needed at hand\n            IPCManager::Get().PostMessageToDashboardApp(ipcmsg_action, ipcact_crop_to_active_window);\n\n            WindowInfo window_info(::GetForegroundWindow());\n            OverlayManager::Get().SetCurrentOverlayNameAuto(&window_info);\n            SetActiveOverlayID(m_ActiveOverlayID);\n        }\n    }\n\n    if (disable_crop_edit)\n        ImGui::PopItemDisabled();\n\n    ImGui::Columns(1);\n\n    ImGui::SetCursorPosY( ImGui::GetCursorPosY() + (ImGui::GetContentRegionAvail().y - ImGui::GetFrameHeightWithSpacing()) );\n\n    //--Confirmation buttons\n    ImGui::Separator();\n\n    if (ImGui::Button(TranslationManager::GetString(tstr_DialogOk))) \n    {\n        m_IsConfigDataModified = false;\n        PageGoBack();\n\n        //DraggableRectArea() runs into positional flicker from ImGui::GetCursorScreenPos() for one frame when going back from here. \n        //Haven't been able to track down the reason for it (only happens on this button), but the RepeatFrame() bandaid works\n        UIManager::Get()->RepeatFrame();\n    }\n\n    ImGui::SameLine();\n\n    if (ImGui::Button(TranslationManager::GetString(tstr_DialogCancel))) \n    {\n        PageGoBack();\n    }\n\n    ImGui::SameLine();\n\n    static float button_reset_width = 0.0f;\n\n    ImGui::SetCursorPosX(ImGui::GetCursorPosX() + ImGui::GetContentRegionAvail().x - button_reset_width);\n\n    if (ImGui::Button(TranslationManager::GetString(tstr_OvrlPropsPositionReset)))\n    {\n        if ( (ConfigManager::GetValue(configid_int_overlay_capture_source) != ovrl_capsource_desktop_duplication) || (ConfigManager::GetValue(configid_bool_performance_single_desktop_mirroring)) || \n             (!UIManager::Get()->IsOpenVRLoaded()) )\n        {\n            crop_x = 0;\n            crop_y = 0;\n            crop_width  = -1;\n            crop_height = -1;\n\n            IPCManager::Get().PostConfigMessageToDashboardApp(configid_int_overlay_crop_x,      crop_x);\n            IPCManager::Get().PostConfigMessageToDashboardApp(configid_int_overlay_crop_y,      crop_y);\n            IPCManager::Get().PostConfigMessageToDashboardApp(configid_int_overlay_crop_width,  crop_width);\n            IPCManager::Get().PostConfigMessageToDashboardApp(configid_int_overlay_crop_height, crop_height);\n        }\n        else\n        {\n            //Have the dashboard figure out the right crop by changing the desktop ID to the current value again\n            IPCManager::Get().PostConfigMessageToDashboardApp(configid_int_overlay_desktop_id, ConfigManager::GetValue(configid_int_overlay_desktop_id));\n        }\n\n        OverlayManager::Get().SetCurrentOverlayNameAuto();\n        SetActiveOverlayID(m_ActiveOverlayID);\n    }\n\n    button_reset_width = ImGui::GetItemRectSize().x;\n}\n\nvoid WindowOverlayProperties::UpdatePageGraphicsCaptureSource(bool only_restore_settings)\n{\n    int desktop_count            = ConfigManager::GetValue(configid_int_state_interface_desktop_count);\n    int& winrt_selected_desktop  = ConfigManager::GetRef(configid_int_overlay_winrt_desktop_id);\n    HWND winrt_selected_window   = (HWND)ConfigManager::GetValue(configid_handle_overlay_state_winrt_hwnd);\n    bool has_selection_changed   = false;\n    bool is_entry_double_clicked = false;\n\n    if (only_restore_settings)\n    {\n        if (m_IsConfigDataModified)\n        {\n            //Restore previous settings\n            OverlayConfigData& data = OverlayManager::Get().GetCurrentConfigData();\n            data.ConfigInt[configid_int_overlay_winrt_desktop_id]             = m_ConfigDataTemp.ConfigInt[configid_int_overlay_winrt_desktop_id];\n            data.ConfigHandle[configid_handle_overlay_state_winrt_hwnd]       = m_ConfigDataTemp.ConfigHandle[configid_handle_overlay_state_winrt_hwnd];\n            data.ConfigStr[configid_str_overlay_winrt_last_window_title]      = m_ConfigDataTemp.ConfigStr[configid_str_overlay_winrt_last_window_title];\n            data.ConfigStr[configid_str_overlay_winrt_last_window_class_name] = m_ConfigDataTemp.ConfigStr[configid_str_overlay_winrt_last_window_class_name];\n            data.ConfigStr[configid_str_overlay_winrt_last_window_exe_name]   = m_ConfigDataTemp.ConfigStr[configid_str_overlay_winrt_last_window_exe_name];\n            data.ConfigNameStr = m_ConfigDataTemp.ConfigNameStr;\n\n            SetActiveOverlayID(m_ActiveOverlayID);\n\n            IPCManager::Get().PostConfigMessageToDashboardApp(configid_handle_overlay_state_winrt_hwnd, (LPARAM)data.ConfigHandle[configid_handle_overlay_state_winrt_hwnd]);\n            IPCManager::Get().PostConfigMessageToDashboardApp(configid_int_overlay_winrt_desktop_id,            data.ConfigInt[configid_int_overlay_winrt_desktop_id]);\n\n            m_IsConfigDataModified = false;\n        }\n        return;\n    }\n\n    if ( (m_PageAppearing == wndovrlprop_page_graphics_capture_source) && (!m_IsWindowAppearing) )\n    {\n        m_IsConfigDataModified = true;\n        m_ConfigDataTemp = OverlayManager::Get().GetCurrentConfigData();\n    }\n\n    const ImGuiStyle& style = ImGui::GetStyle();\n\n    ImGui::TextColoredUnformatted(ImGui::GetStyleColorVec4(ImGuiCol_ButtonHovered), TranslationManager::GetString(tstr_OvrlPropsCaptureGCSource));\n    ImGui::Indent();\n\n    ImGui::SetNextItemWidth(-1.0f);\n    ImGui::BeginChild(\"SourceList\", ImVec2(0.0f, ImGui::GetContentRegionAvail().y - ImGui::GetFrameHeightWithSpacing() - style.ItemSpacing.y), true);\n\n    ImVec2 img_size_line_height = {ImGui::GetTextLineHeight(), ImGui::GetTextLineHeight()};\n    ImVec2 img_size, img_uv_min, img_uv_max;\n\n    //List None\n    ImGui::PushID(-2);\n\n    if (ImGui::Selectable(\"\", ((winrt_selected_desktop == -2) && (winrt_selected_window == nullptr))))\n    {\n        has_selection_changed = ((winrt_selected_desktop != -2) || (winrt_selected_window != nullptr));\n\n        winrt_selected_desktop = -2;\n        winrt_selected_window = nullptr;\n        ConfigManager::SetValue(configid_handle_overlay_state_winrt_hwnd, 0);\n        ConfigManager::SetValue(configid_str_overlay_winrt_last_window_title, \"\");\n        ConfigManager::SetValue(configid_str_overlay_winrt_last_window_class_name, \"\");\n        ConfigManager::SetValue(configid_str_overlay_winrt_last_window_exe_name, \"\");\n        ConfigManager::SetValue(configid_handle_overlay_state_winrt_last_hicon, 0);\n\n        OverlayManager::Get().SetCurrentOverlayNameAuto();\n        SetActiveOverlayID(m_ActiveOverlayID);\n\n        IPCManager::Get().PostConfigMessageToDashboardApp(configid_int_overlay_winrt_desktop_id, winrt_selected_desktop);\n        IPCManager::Get().PostConfigMessageToDashboardApp(configid_handle_overlay_state_winrt_hwnd, 0);\n\n        UIManager::Get()->RepeatFrame();\n    }\n\n    if ((!has_selection_changed) && (ImGui::IsItemClicked()) && (ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left)))\n    {\n        is_entry_double_clicked = true;\n    }\n\n    ImGui::SameLine(0.0f, 0.0f);\n\n    TextureManager::Get().GetTextureInfo(tmtex_icon_xsmall_desktop_none, img_size, img_uv_min, img_uv_max);\n    ImGui::Image(ImGui::GetIO().Fonts->TexID, img_size_line_height, img_uv_min, img_uv_max);\n\n    ImGui::SameLine(0.0f, style.ItemInnerSpacing.x);\n    ImGui::TextUnformatted(TranslationManager::GetString(tstr_SourceWinRTNone));\n\n    ImGui::PopID();\n\n    ImGui::Separator();\n\n    //List Combined desktop, if supported\n    if (DPWinRT_IsCaptureFromCombinedDesktopSupported())\n    {\n        ImGui::PushID(-1);\n\n        if (ImGui::Selectable(\"\", (winrt_selected_desktop == -1)))\n        {\n            has_selection_changed = (winrt_selected_desktop != -1);\n\n            winrt_selected_desktop = -1;\n            winrt_selected_window = nullptr;\n            ConfigManager::SetValue(configid_handle_overlay_state_winrt_hwnd, 0);\n            ConfigManager::SetValue(configid_str_overlay_winrt_last_window_title, \"\");\n            ConfigManager::SetValue(configid_str_overlay_winrt_last_window_class_name, \"\");\n            ConfigManager::SetValue(configid_str_overlay_winrt_last_window_exe_name, \"\");\n            ConfigManager::SetValue(configid_handle_overlay_state_winrt_last_hicon, 0);\n\n            OverlayManager::Get().SetCurrentOverlayNameAuto();\n            SetActiveOverlayID(m_ActiveOverlayID);\n\n            IPCManager::Get().PostConfigMessageToDashboardApp(configid_handle_overlay_state_winrt_hwnd, 0);\n            IPCManager::Get().PostConfigMessageToDashboardApp(configid_int_overlay_winrt_desktop_id, winrt_selected_desktop);\n\n            UIManager::Get()->RepeatFrame();\n        }\n\n        if ((!has_selection_changed) && (ImGui::IsItemClicked()) && (ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left)))\n        {\n            is_entry_double_clicked = true;\n        }\n\n        ImGui::SameLine(0.0f, 0.0f);\n\n        TextureManager::Get().GetTextureInfo(tmtex_icon_xsmall_desktop_all, img_size, img_uv_min, img_uv_max);\n        ImGui::Image(ImGui::GetIO().Fonts->TexID, img_size_line_height, img_uv_min, img_uv_max);\n\n        ImGui::SameLine(0.0f, style.ItemInnerSpacing.x);\n        ImGui::TextUnformatted(TranslationManager::GetString(tstr_SourceDesktopAll));\n\n        ImGui::PopID();\n    }\n\n    //List desktops\n    for (int i = 0; i < desktop_count; ++i)\n    {\n        ImGui::PushID(i);\n\n        if (ImGui::Selectable(\"\", (winrt_selected_desktop == i)))\n        {\n            has_selection_changed = (winrt_selected_desktop == i);\n\n            winrt_selected_desktop = i;\n            winrt_selected_window = nullptr;\n            ConfigManager::SetValue(configid_handle_overlay_state_winrt_hwnd, 0);\n            ConfigManager::SetValue(configid_str_overlay_winrt_last_window_title, \"\");\n            ConfigManager::SetValue(configid_str_overlay_winrt_last_window_class_name, \"\");\n            ConfigManager::SetValue(configid_str_overlay_winrt_last_window_exe_name, \"\");\n            ConfigManager::SetValue(configid_handle_overlay_state_winrt_last_hicon, 0);\n\n            OverlayManager::Get().SetCurrentOverlayNameAuto();\n            SetActiveOverlayID(m_ActiveOverlayID);\n\n            IPCManager::Get().PostConfigMessageToDashboardApp(configid_handle_overlay_state_winrt_hwnd, 0);\n            IPCManager::Get().PostConfigMessageToDashboardApp(configid_int_overlay_winrt_desktop_id, winrt_selected_desktop);\n\n            UIManager::Get()->RepeatFrame();\n        }\n\n        if ((!has_selection_changed) && (ImGui::IsItemClicked()) && (ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left)))\n        {\n            is_entry_double_clicked = true;\n        }\n\n        ImGui::SameLine(0.0f, 0.0f);\n\n        const TMNGRTexID texid = (tmtex_icon_desktop_1 + i <= tmtex_icon_desktop_6) ? (TMNGRTexID)(tmtex_icon_xsmall_desktop_1 + i) : tmtex_icon_xsmall_desktop;\n        TextureManager::Get().GetTextureInfo(texid, img_size, img_uv_min, img_uv_max);\n        ImGui::Image(ImGui::GetIO().Fonts->TexID, img_size_line_height, img_uv_min, img_uv_max);\n\n        ImGui::SameLine(0.0f, style.ItemInnerSpacing.x);\n        ImGui::TextUnformatted(TranslationManager::Get().GetDesktopIDString(i));\n\n        ImGui::PopID();\n    }\n\n    ImGui::Separator();\n\n    //List windows\n    for (const auto& window_info : WindowManager::Get().WindowListGet())\n    {\n        ImGui::PushID(window_info.GetWindowHandle());\n        if (ImGui::Selectable(\"\", (winrt_selected_window == window_info.GetWindowHandle())))\n        {\n            has_selection_changed = (winrt_selected_window != window_info.GetWindowHandle());\n\n            winrt_selected_desktop = -2;\n            winrt_selected_window = window_info.GetWindowHandle();\n\n            ConfigManager::SetValue(configid_handle_overlay_state_winrt_hwnd, (uint64_t)winrt_selected_window);\n            ConfigManager::SetValue(configid_str_overlay_winrt_last_window_title,       StringConvertFromUTF16(window_info.GetTitle().c_str())); //No need to sync these\n            ConfigManager::SetValue(configid_str_overlay_winrt_last_window_class_name,  StringConvertFromUTF16(window_info.GetWindowClassName().c_str()));\n            ConfigManager::SetValue(configid_str_overlay_winrt_last_window_exe_name,    window_info.GetExeName());\n\n            OverlayManager::Get().SetCurrentOverlayNameAuto();\n            SetActiveOverlayID(m_ActiveOverlayID);\n\n            IPCManager::Get().PostConfigMessageToDashboardApp(configid_int_overlay_winrt_desktop_id, -2);\n            IPCManager::Get().PostConfigMessageToDashboardApp(configid_handle_overlay_state_winrt_hwnd, (LPARAM)winrt_selected_window);\n\n            UIManager::Get()->RepeatFrame();\n        }\n\n        if ((!has_selection_changed) && (ImGui::IsItemClicked()) && (ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left)))\n        {\n            is_entry_double_clicked = true;\n        }\n\n        ImGui::SameLine(0.0f, 0.0f);\n\n        int icon_id = TextureManager::Get().GetWindowIconCacheID(window_info.GetIcon());\n\n        if (icon_id != -1)\n        {\n            TextureManager::Get().GetWindowIconTextureInfo(icon_id, img_size, img_uv_min, img_uv_max);\n            ImGui::Image(ImGui::GetIO().Fonts->TexID, img_size_line_height, img_uv_min, img_uv_max);\n\n            ImGui::SameLine(0.0f, style.ItemInnerSpacing.x);\n        }\n\n        ImGui::TextUnformatted(window_info.GetListTitle().c_str());\n\n        ImGui::PopID();\n    }\n\n    ImGui::EndChild();\n\n    ImGui::Unindent();\n\n    ImGui::SetCursorPosY(ImGui::GetCursorPosY() + (ImGui::GetContentRegionAvail().y - ImGui::GetFrameHeight()));\n\n    //--Confirmation buttons\n    //No separator since this is right after a boxed child window\n\n    if ((ImGui::Button(TranslationManager::GetString(tstr_DialogOk))) || (is_entry_double_clicked))\n    {\n        m_IsConfigDataModified = false; //Stops reset from being triggered on page leave\n        PageGoBack();\n    }\n\n    ImGui::SameLine();\n\n    if (ImGui::Button(TranslationManager::GetString(tstr_DialogCancel)))\n    {\n        PageGoBack();\n    }\n}\n\nvoid WindowOverlayProperties::UpdatePageActionsOrder(bool only_restore_settings)\n{\n    static FloatingWindowActionOrderListState page_state;\n\n    OverlayConfigData& data = OverlayManager::Get().GetCurrentConfigData();\n\n    if (only_restore_settings)\n    {\n        if (!page_state.HasSavedChanges)\n        {\n            data.ConfigActionBarOrder = page_state.ActionListOrig;\n        }\n        return;\n    }\n\n    const float height_offset = (UIManager::Get()->IsInDesktopMode()) ? -UIManager::Get()->GetSettingsWindow().DesktopModeGetWarningHeight() : 0.0f;\n\n    bool go_add_actions = false;\n    bool go_back = ActionOrderList(data.ConfigActionBarOrder, (m_PageAppearing == wndovrlprop_page_actions_order), (m_PageReturned == wndovrlprop_page_actions_order_add), \n                                   page_state, go_add_actions, height_offset);\n\n    if (m_PageReturned == wndovrlprop_page_actions_order_add)\n    {\n        m_PageReturned = wndovrlprop_page_none;\n    }\n\n    if (go_add_actions)\n    {\n        PageGoForward(wndovrlprop_page_actions_order_add);\n    }\n    else if (go_back)\n    {\n        PageGoBack();\n    }\n}\n\nvoid WindowOverlayProperties::UpdatePageActionsOrderAdd()\n{\n    static FloatingWindowActionAddSelectorState page_state;\n\n    OverlayConfigData& data = OverlayManager::Get().GetCurrentConfigData();\n\n    bool go_back = ActionAddSelector(data.ConfigActionBarOrder, (m_PageAppearing == wndovrlprop_page_actions_order_add), page_state);\n\n    if (go_back)\n    {\n        PageGoBack();\n    }\n}\n\nstd::string WindowOverlayProperties::GetStringForWinRTSource(HWND source_window, int source_desktop)\n{\n    std::string source_str;\n\n    if (source_window == nullptr)\n    {\n        switch (source_desktop)\n        {\n            case -2: source_str = TranslationManager::GetString(tstr_SourceWinRTNone); break;\n            default: source_str = TranslationManager::Get().GetDesktopIDString(source_desktop);\n        }\n    }\n    else\n    {\n        const auto& window_list = WindowManager::Get().WindowListGet();\n        const auto it = std::find_if(window_list.begin(), window_list.end(), [&](const auto& window) { return (window.GetWindowHandle() == source_window); });\n\n        if ( (it != window_list.end()) && (::IsWindow(source_window)) )\n        {\n            source_str = it->GetListTitle();\n        }\n        else\n        {\n            const std::string& last_title = ConfigManager::GetValue(configid_str_overlay_winrt_last_window_title);\n            source_str = (last_title.empty()) ? TranslationManager::GetString(tstr_SourceWinRTUnknown) : TranslationManager::GetString(tstr_SourceWinRTClosed) + std::string(\" \" + last_title);\n        }\n    }\n\n    return source_str;\n}\n\nvoid WindowOverlayProperties::PageGoForward(WindowOverlayPropertiesPage new_page)\n{\n    //We can't just mess with the stack while a backwards animation is going, so we save this for later\n    if (m_PageAnimationDir == 1)\n    {\n        m_PageStackPending.push_back(new_page);\n        return;\n    }\n\n    m_PageStack.push_back(new_page);\n    m_PageStackPos++;\n}\n\nvoid WindowOverlayProperties::PageGoBack()\n{\n    if (m_PageStackPos != 0)\n    {\n        OnPageLeaving(m_PageStack[m_PageStackPos]);\n        m_PageStackPos--;\n        m_PageReturned = m_PageStack.back();\n    }\n}\n\nvoid WindowOverlayProperties::PageGoHome(bool skip_animation)\n{\n    while (m_PageStackPos != 0)\n    {\n        OnPageLeaving(m_PageStack[m_PageStackPos]);\n        m_PageStackPos--;\n    }\n\n    if (skip_animation)\n    {\n        m_PageStackPosAnimation = m_PageStackPos;\n        m_PageAnimationOffset = 0.0f;\n\n        while (m_PageStack.size() > 1)\n        {\n            m_PageStack.pop_back();\n        }\n    }\n}\n\nvoid WindowOverlayProperties::PageFadeStart(unsigned int overlay_id)\n{\n    m_PageFadeDir = -1; //Needed in any case for SetActiveOverlayID()\n\n    if (m_OverlayStateCurrent->IsVisible)\n    {\n        m_PageFadeTargetOverlayID = overlay_id;\n    }\n    else //Not visible, skip fade\n    {\n        SetActiveOverlayID(overlay_id); //Calls PageGoHome() for us\n\n        m_PageFadeAlpha = 1.0f;\n        m_PageFadeDir = 0;\n    }\n}\n\nvoid WindowOverlayProperties::OnPageLeaving(WindowOverlayPropertiesPage previous_page)\n{\n    switch (previous_page)\n    {\n        case wndovrlprop_page_position_change:\n        {\n            //Disable dragmode when leaving page\n            ConfigManager::SetValue(configid_bool_state_overlay_dragmode, false);\n            IPCManager::Get().PostConfigMessageToDashboardApp(configid_bool_state_overlay_dragselectmode_show_hidden, false);\n            IPCManager::Get().PostConfigMessageToDashboardApp(configid_bool_state_overlay_dragmode,                   false);\n\n            UpdatePagePositionChange(true); //Call to reset settings\n            break;\n        }\n        case wndovrlprop_page_crop_change:\n        {\n            m_CropButtonLabel = \"\";\n            UpdatePageCropChange(true); //Call to reset settings\n            break;\n        }\n        case wndovrlprop_page_graphics_capture_source:\n        {\n            m_WinRTSourceButtonLabel = \"\";\n            UpdatePageGraphicsCaptureSource(true); //Call to reset settings\n            break;\n        }\n        default: break;\n    }\n}\n"
  },
  {
    "path": "src/DesktopPlusUI/WindowOverlayProperties.h",
    "content": "#pragma once\n\n#include \"FloatingWindow.h\"\n#include \"WindowDesktopMode.h\"\n\nenum WindowOverlayPropertiesPage\n{\n    wndovrlprop_page_none,\n    wndovrlprop_page_main,\n    wndovrlprop_page_position_change,\n    wndovrlprop_page_crop_change,\n    wndovrlprop_page_graphics_capture_source,\n    wndovrlprop_page_actions_order,\n    wndovrlprop_page_actions_order_add,\n};\n\nclass WindowOverlayProperties : public FloatingWindow, public FloatingWindowDesktopModeInterop\n{\n    private:\n        std::vector<WindowOverlayPropertiesPage> m_PageStack;\n        std::vector<WindowOverlayPropertiesPage> m_PageStackPending; //Stores pending page forward calls that they were requested during an active backwards animation\n        int m_PageStackPos;\n        int m_PageStackPosAnimation;\n        WindowOverlayPropertiesPage m_PageAppearing; //Similar to ImGui::IsWindowAppearing(), equals the current page ID for a single frame if it or the window is newly appearing\n        WindowOverlayPropertiesPage m_PageReturned;  //Equals the previous page ID after PageGoBack() was called, ideally cleared after making use of its value\n\n        int m_PageAnimationDir;\n        float m_PageAnimationProgress;\n        float m_PageAnimationStartPos;\n        float m_PageAnimationOffset;\n        int m_PageFadeDir;\n        float m_PageFadeAlpha;\n        unsigned int m_PageFadeTargetOverlayID;\n\n        unsigned int m_ActiveOverlayID;\n        float m_Column0Width;\n\n        OverlayConfigData m_ConfigDataTemp;          //Stores config data used for restoring when a page is canceled\n        bool m_IsConfigDataModified;                 //Set to true on page enter and cleared when a page is saving data\n\n        float m_OriginHMDFloorSettingsAnimationProgress;\n        float m_OriginHMDSettingsAnimationProgress;\n        float m_OriginTheaterScreenSettingsAnimationProgress;\n        float m_PerfMonStyleCheckboxAnimationProgress;\n        std::string m_CropButtonLabel;\n        std::string m_WinRTSourceButtonLabel;\n        std::string m_ActionButtonsLabel;\n        std::string m_BrowserMaxFPSValueText;\n        char m_BufferOverlayName[1024];\n        char m_BufferOverlayTags[1024];\n        bool m_IsBrowserURLChanged;\n\n        //Struct of cached sizes which may change at any time on translation or DPI switching (only the ones that aren't updated unconditionally)\n        struct\n        {\n            float MainCatCapture_WinRTSourceLabelWidth = 0.0f;\n            float PositionChange_Column0Width          = 0.0f;\n            float PositionChange_ButtonWidth           = 0.0f;\n        } \n        m_CachedSizes;\n\n        virtual void WindowUpdate();\n        void OverlayPositionReset();\n\n        void UpdatePageMain();\n        void UpdatePageMainCatPosition();\n        void UpdatePageMainCatAppearance();\n        void UpdatePageMainCatCapture();\n        void UpdatePageMainCatPerformanceMonitor();\n        void UpdatePageMainCatBrowser();\n        void UpdatePageMainCatAdvanced();\n        void UpdatePageMainCatPerformance();\n        void UpdatePageMainCatInterface();\n\n        void UpdatePagePositionChange(bool only_restore_settings = false);\n        void UpdatePageCropChange(bool only_restore_settings = false);\n        void UpdatePageGraphicsCaptureSource(bool only_restore_settings = false);\n        void UpdatePageActionsOrder(bool only_restore_settings = false);\n        void UpdatePageActionsOrderAdd();\n\n        std::string GetStringForWinRTSource(HWND source_window, int source_desktop);\n\n        void PageGoForward(WindowOverlayPropertiesPage new_page);\n        void PageGoBack();\n        void PageGoHome(bool skip_animation = false);\n        void PageFadeStart(unsigned int overlay_id);\n\n        void OnPageLeaving(WindowOverlayPropertiesPage previous_page); //Called from PageGoBack() and PageGoHome() to allow for page-specific cleanup if necessary\n\n    public:\n        WindowOverlayProperties();\n        virtual void Show(bool skip_fade = false);\n        virtual void Hide(bool skip_fade = false);\n        virtual void ResetTransform(FloatingWindowOverlayStateID state_id);\n        virtual vr::VROverlayHandle_t GetOverlayHandle() const;\n\n        virtual void ApplyUIScale();\n\n        unsigned int GetActiveOverlayID() const;\n        void SetActiveOverlayID(unsigned int overlay_id, bool skip_fade = false);              //Call with same as active ID to refresh window title and icon\n\n        void UpdateDesktopMode();\n        virtual const char* DesktopModeGetTitle() const;\n        virtual bool DesktopModeGetIconTextureInfo(ImVec2& size, ImVec2& uv_min, ImVec2& uv_max) const;\n        virtual float DesktopModeGetTitleIconAlpha() const;\n        virtual void DesktopModeOnTitleIconClick();\n        virtual void DesktopModeOnTitleBarHover(bool is_hovered);\n        virtual bool DesktopModeGoBack();\n\n        void MarkBrowserURLChanged();\n};"
  },
  {
    "path": "src/DesktopPlusUI/WindowPerformance.cpp",
    "content": "#include \"WindowPerformance.h\"\n\n#include <fstream>\n#include <codecvt>\n\n#include \"implot.h\"\n#include \"ImGuiExt.h\"\n#include \"TextureManager.h\"\n#include \"InterprocessMessaging.h\"\n#include \"Util.h\"\n#include \"OpenVRExt.h\"\n#include \"UIManager.h\"\n#include \"OverlayManager.h\"\n\nstatic LPCWSTR const g_ViveWirelessLogPathBase = L\"%ProgramData%\\\\VIVE Wireless\\\\ConnectionUtility\\\\Log\\\\\";\n\nWindowPerformance::WindowPerformance() : \n    m_Visible(false),\n    m_VisibleTickLast(0),\n    m_IsPopupOpen(false),\n    m_MinimalItemLineWrapMaxLength(FLT_MAX),\n    m_MinimalItemLineWrapPrevX(0.0f),\n    m_PIDLast(0),\n    m_OffsetFrameIndex(0),\n    m_OffsetFramesPresents(0),\n    m_OffsetReprojectedFrames(0),\n    m_OffsetDroppedFrames(0),\n    m_FrameTimeCPU(0.0f),\n    m_FrameTimeGPU(0.0f),\n    m_ReprojectionRatio(0.0f),\n    m_DroppedFrames(0),\n    m_BatteryHMD(-1.0f),\n    m_BatteryLeft(-1.0f),\n    m_BatteryRight(-1.0f),\n    m_FrameTimeLastIndex(0),\n    m_ViveWirelessTemp(-1),\n    m_ViveWirelessLogFileLastLine(0),\n    m_IsOverlaySharedTextureUpdateNeeded(false)\n{\n    ResetCumulativeValues();\n\n    //Set m_TimeLast to something invalid so the first time always counts as different\n    m_TimeLast = {0};\n    m_TimeLast.wHour   = 99;\n    m_TimeLast.wMinute = 99;\n\n    //Expand path for Vive Wireless log files\n    wchar_t wpath[MAX_PATH] = L\"\\0\";\n    ::ExpandEnvironmentStrings(g_ViveWirelessLogPathBase, wpath, MAX_PATH);\n    m_ViveWirelessLogPath = wpath;\n    m_ViveWirelessLogPathExists = DirectoryExists(wpath);\n}\n\nvoid WindowPerformance::Update(bool show_as_popup)\n{\n    ImGuiIO& io = ImGui::GetIO();\n    const ImGuiStyle& style = ImGui::GetStyle();\n\n    const DPRect& tex_rect = UITextureSpaces::Get().GetRect(ui_texspace_performance_monitor);\n    ImGui::SetNextWindowSizeConstraints({0.0f, 0.0f}, {(float)tex_rect.GetWidth() - 1, (float)tex_rect.GetHeight() - 1});\n\n    const ImGuiWindowFlags flags_extra = (!ConfigManager::GetValue(configid_bool_performance_monitor_style_show_window)) ? ImGuiWindowFlags_NoBackground | ImGuiWindowFlags_NoDecoration : 0;\n\n    if (show_as_popup)\n    {\n        //Don't update values when repeating the frame\n        if (!UIManager::Get()->GetRepeatFrame())\n        {\n            UpdateStatValues();\n        }\n\n        //Set popup rounding to the same as a normal window\n        ImGui::PushStyleVar(ImGuiStyleVar_PopupRounding, style.WindowRounding);\n        bool is_popup_rounding_pushed = true;\n\n        const bool ui_large_style = ((ConfigManager::GetValue(configid_bool_interface_large_style)) && (!UIManager::Get()->IsInDesktopMode()));\n\n        if (ui_large_style)\n        {\n            ImGui::PushFont(UIManager::Get()->GetFontCompact());\n        }\n\n        ImGui::SetNextWindowPos({io.DisplaySize.x * 0.5f, io.DisplaySize.y * 0.5f}, ImGuiCond_Always, ImVec2(0.5f, 0.5f));\n\n        if (ImGui::BeginPopup(\"PopupPerformanceMonitor\", ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_AlwaysAutoResize |\n                                                         ImGuiWindowFlags_NoScrollbar | flags_extra))\n        {\n            ImGui::PopStyleVar(); //ImGuiStyleVar_PopupRounding\n            is_popup_rounding_pushed = false;\n\n            if (ImGui::IsWindowAppearing())\n            {\n                UIManager::Get()->RepeatFrame();\n            }\n\n            if (ConfigManager::GetValue(configid_bool_performance_monitor_large_style))\n            {\n                DisplayStatsLarge();\n            }\n            else if (ConfigManager::GetValue(configid_bool_performance_monitor_minimal_style))\n            {\n                DisplayStatsMinimal();\n            }\n            else\n            {\n                DisplayStatsCompact();\n            }\n\n            ImGui::EndPopup();\n        }\n\n        if (ui_large_style)\n        {\n            ImGui::PopFont();\n        }\n\n        if (is_popup_rounding_pushed)\n        {\n            ImGui::PopStyleVar(); //ImGuiStyleVar_PopupRounding\n        }\n    }\n    else\n    {\n        if (!m_Visible)\n            return;\n\n        //Don't update values when repeating the frame\n        if (!UIManager::Get()->GetRepeatFrame())\n        {\n            UpdateStatValues();\n            CheckScheduledOverlaySharedTextureUpdate();\n        }\n\n        //Center in overlay space\n        const DPRect& rect = UITextureSpaces::Get().GetRect(ui_texspace_performance_monitor);\n        ImGui::SetNextWindowPos(ImVec2((float)rect.GetCenter().x, (float)rect.GetCenter().y), 0, ImVec2(0.5f, 0.5f));\n\n        ImGui::Begin(\"WindowPerformance\", nullptr, ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoFocusOnAppearing |\n                                                   ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoScrollbar | flags_extra);\n\n        if (ConfigManager::GetValue(configid_bool_performance_monitor_large_style))\n        {\n            DisplayStatsLarge();\n        }\n        else if (ConfigManager::GetValue(configid_bool_performance_monitor_minimal_style))\n        {\n            DisplayStatsMinimal();\n        }\n        else\n        {\n            DisplayStatsCompact();\n        }\n\n        //Store window bounds and update affected overlays if they changed\n        ImVec2 pos_new  = ImGui::GetWindowPos();\n        ImVec2 size_new = ImGui::GetWindowSize();\n\n        if ((pos_new.x != m_Pos.x) || (pos_new.y != m_Pos.y) || (size_new.x != m_Size.x) || (size_new.y != m_Size.y))\n        {\n            m_Pos  = pos_new;\n            m_Size = size_new;\n\n            OnWindowBoundsChanged();\n        }\n\n        ImGui::End();\n    }\n}\n\nvoid WindowPerformance::UpdateVisibleState()\n{\n    bool was_visible = m_Visible;\n\n    m_Visible = ( (m_IsPopupOpen) || (IsAnyOverlayUsingPerformanceMonitor()) );\n\n    if (m_Visible)\n    {\n        if (!was_visible)\n        {\n            m_PerfData.EnableCounters(!ConfigManager::GetValue(configid_bool_performance_monitor_disable_gpu_counters));\n        }\n    }\n    else\n    {\n        if (was_visible)\n        {\n            m_VisibleTickLast = ::GetTickCount64();\n        }\n        else if (m_VisibleTickLast + 3000 <= ::GetTickCount64())    //Only actually disable counters after at least 3 seconds of being hidden\n        {\n            m_PerfData.DisableCounters();\n        }\n    }\n}\n\nvoid WindowPerformance::PerfMonText(const char* fmt, ...)\n{\n    va_list args;\n    va_start(args, fmt);\n\n    ConfigManager::GetValue(configid_bool_performance_monitor_style_show_text_outline) ? ImGui::TextOutlinedV(fmt, args) : \n                                                                                         ImGui::TextV(fmt, args);\n\n    va_end(args);\n}\n\nvoid WindowPerformance::PerfMonTextUnformatted(const char* text, const char* text_end)\n{\n    ConfigManager::GetValue(configid_bool_performance_monitor_style_show_text_outline) ? ImGui::TextUnformattedOutlined(text, text_end) : \n                                                                                         ImGui::TextUnformatted(text, text_end);\n}\n\nvoid WindowPerformance::PerfMonTextRight(float offset_x, float fixed_w, const char* fmt, ...)\n{\n    va_list args;\n    va_start(args, fmt);\n\n    ConfigManager::GetValue(configid_bool_performance_monitor_style_show_text_outline) ? ImGui::TextRightOutlinedV(offset_x, fixed_w, fmt, args) : \n                                                                                         ImGui::TextRightV(offset_x, fixed_w, fmt, args);\n\n    va_end(args);\n}\n\nvoid WindowPerformance::PerfMonTextRightUnformatted(float offset_x, float fixed_w, const char* text, const char* text_end)\n{\n    ConfigManager::GetValue(configid_bool_performance_monitor_style_show_text_outline) ? ImGui::TextRightUnformattedOutlined(offset_x, fixed_w, text, text_end) : \n                                                                                         ImGui::TextRightUnformatted(offset_x, fixed_w, text, text_end);\n}\n\nvoid WindowPerformance::StatsMinimalItemLineWrap()\n{\n    //Poor man's widget line-wrap\n    //We take advantage of being able to just toss a frame away if we're not happy and avoid calculating the size of each stat item\n    //Instead we just check if the last widget got clipped, and in that case store the starting position and use that as the max allowed starting pos before a line break\n    //Far from perfect, especially since there's no mechanism to reset the max length, but it does the job to provide a way to still display what is too many items anyways\n    const ImGuiStyle style = ImGui::GetStyle();\n    const DPRect& tex_rect = UITextureSpaces::Get().GetRect(ui_texspace_performance_monitor);\n    const float max_window_content_x = (float)tex_rect.GetWidth() - 1.0f - style.WindowPadding.x + ImGui::GetWindowPos().x;\n\n    if (ImGui::GetItemRectMax().x > max_window_content_x)\n    {\n        m_MinimalItemLineWrapMaxLength = m_MinimalItemLineWrapPrevX;\n        UIManager::Get()->RepeatFrame();\n        return;\n    }\n    else if (ImGui::GetCursorPosX() >= m_MinimalItemLineWrapMaxLength)\n    {\n        ImGui::SetCursorPosX(ImGui::GetCursorPosX() - ImGui::GetStyle().ItemSpacing.x);\n        ImGui::NewLine();\n    }\n\n    //Could use item groups but this way works to leave the way the window is laid out mostly untouched\n    m_MinimalItemLineWrapPrevX = ImGui::GetCursorPosX() - ImGui::GetStyle().ItemSpacing.x;\n}\n\nvoid WindowPerformance::DisplayStatsLarge()\n{\n    const ImGuiStyle& style = ImGui::GetStyle();\n\n    const float column_width_0 = ImGui::GetFontSize() * 7.5f;\n    const float column_width_1 = ImGui::GetFontSize() * 4.0f;\n    const float column_width_3 = ImGui::GetFontSize() * 6.0f;\n    const ImVec2 graph_size    = {column_width_0 + column_width_3 - style.WindowPadding.x, ImGui::GetFontSize() * 2.0f};\n\n    static float row_gpu_y = 0.0f;\n\n    //Both graphs are being created outside of the normal widget flow, due to how they span multiple rows and columns, which isn't exactly supported by ImGui's columns\n\n    //Shared offsets\n    ImVec2 cursor_pos_prev = ImGui::GetCursorPos();\n    float graph_pos_x  = cursor_pos_prev.x + column_width_0 + column_width_1 - style.FramePadding.x;\n\n    double plot_xmin = (m_FrameTimeLastIndex > (uint32_t)m_FrameTimeCPUHistory.MaxSize) ? m_FrameTimeLastIndex - m_FrameTimeCPUHistory.MaxSize + 0.5f : 0.5f;\n    double plot_xmax = m_FrameTimeLastIndex - 0.5f;\n    double plot_ymax = ceilf(m_FrameTimeVsyncLimit * 1.4f); \n\n    //Set fixed window width from graph cursor pos even if all graphs are disabled since columns don't increase the window size\n    ImGui::SetCursorPos({graph_pos_x, cursor_pos_prev.y + style.FramePadding.y});\n\n    ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, {0.0f, 0.0f});\n    ImGui::Dummy({graph_size.x, 0.0f});\n    ImGui::PopStyleVar();\n    ImGui::SetCursorPos({graph_pos_x, cursor_pos_prev.y + style.FramePadding.y});\n\n    if (ConfigManager::GetValue(configid_bool_performance_monitor_show_graphs))\n    {\n        //-CPU Frame Time Graph\n        if (ConfigManager::GetValue(configid_bool_performance_monitor_show_cpu))\n        {\n            DrawFrameTimeGraphCPU(graph_size, plot_xmin, plot_xmax, plot_ymax);\n        }\n\n        //-GPU Frame Time Graph\n        if (ConfigManager::GetValue(configid_bool_performance_monitor_show_gpu))\n        {\n            ImGui::SetCursorPos({graph_pos_x, row_gpu_y + style.FramePadding.y});\n\n            DrawFrameTimeGraphGPU(graph_size, plot_xmin, plot_xmax, plot_ymax);\n        }\n    }\n\n    ImGui::SetCursorPos(cursor_pos_prev);\n\n    //Store text width of units for right alignment\n    //This is required since the text width can be off by one pixel on certain values if the units are rendered as part of the formatted string... odd but whatever\n    static const float text_ms_width      = ImGui::CalcTextSize(\" ms\").x;\n    static const float text_percent_width = ImGui::CalcTextSize(\"%\").x;\n    static const float text_temp_width    = ImGui::CalcTextSize(\"\\xC2\\xB0\"\"C\").x;\n\n    float item_spacing_half = style.ItemSpacing.x / 2.0f;\n    float frame_time_warning_limit = m_FrameTimeVsyncLimit * 0.95f;\n\n\n    ImGui::Columns(4, \"ColumnStatsLarge\", false);\n    ImGui::SetColumnWidth(0, column_width_0);\n    ImGui::SetColumnWidth(1, column_width_1);\n    ImGui::SetColumnWidth(2, column_width_0);\n    ImGui::SetColumnWidth(3, column_width_3);\n\n    float right_border_offset = -style.FramePadding.x;\n\n    //--Table CPU\n    if (ConfigManager::GetValue(configid_bool_performance_monitor_show_cpu))\n    {\n        ImGui::PushStyleColor(ImGuiCol_Text, ImGui::GetStyleColorVec4(ImGuiCol_ButtonHovered));\n        PerfMonTextUnformatted(TranslationManager::GetString(tstr_PerformanceMonitorCPU));\n        ImGui::PopStyleColor();\n\n        ImGui::NextColumn();\n        ImGui::NextColumn();\n        ImGui::NextColumn();\n        ImGui::NextColumn();\n\n        //-CPU Frame Time\n        PerfMonTextUnformatted(TranslationManager::GetString(tstr_PerformanceMonitorFrameTime));\n        ImGui::NextColumn();\n\n        //Warning color when frame time above 95% vsync time\n        if (m_FrameTimeCPU > frame_time_warning_limit)\n            ImGui::PushStyleColor(ImGuiCol_Text, Style_ImGuiCol_TextWarning);\n\n        PerfMonTextRight(text_ms_width, 0.0f, \"%.2f\", m_FrameTimeCPU);\n        ImGui::SameLine(0.0f, 0.0f);\n        PerfMonTextUnformatted(\" ms\");\n\n        if (m_FrameTimeCPU > frame_time_warning_limit)\n            ImGui::PopStyleColor();\n\n        ImGui::NextColumn();\n\n        ImGui::NextColumn();\n        ImGui::NextColumn();\n\n        //-CPU Load\n        PerfMonTextUnformatted(TranslationManager::GetString(tstr_PerformanceMonitorLoad));\n        ImGui::NextColumn();\n\n        PerfMonTextRight(text_percent_width, 0.0f, \"%.2f\", m_PerfData.GetCPULoadPrecentage());\n        ImGui::SameLine(0.0f, 0.0f);\n        PerfMonTextUnformatted(\"%\");\n\n        ImGui::NextColumn();\n\n        //-RAM\n        ImGui::SetCursorPosX(ImGui::GetCursorPosX() - item_spacing_half);  //Reduce horizontal spacing\n        PerfMonTextUnformatted(TranslationManager::GetString(tstr_PerformanceMonitorRAM));\n        ImGui::NextColumn();\n\n        //Right align\n        PerfMonTextRight(right_border_offset - 1.0f, 0.0f, \"%.2f/%.2f GB\", m_PerfData.GetRAMUsedGB(), m_PerfData.GetRAMTotalGB());\n        ImGui::NextColumn();\n    }\n\n    //--Table GPU\n    if (ConfigManager::GetValue(configid_bool_performance_monitor_show_gpu))\n    {\n        row_gpu_y = ImGui::GetCursorPosY();\n        ImGui::PushStyleColor(ImGuiCol_Text, ImGui::GetStyleColorVec4(ImGuiCol_ButtonHovered));\n        PerfMonTextUnformatted(TranslationManager::GetString(tstr_PerformanceMonitorGPU));\n        ImGui::PopStyleColor();\n\n        ImGui::NextColumn();\n        ImGui::NextColumn();\n        ImGui::NextColumn();\n        ImGui::NextColumn();\n\n        //-GPU Frame Time\n        PerfMonTextUnformatted(TranslationManager::GetString(tstr_PerformanceMonitorFrameTime));\n        ImGui::NextColumn();\n\n        if (m_FrameTimeGPU > frame_time_warning_limit)\n            ImGui::PushStyleColor(ImGuiCol_Text, Style_ImGuiCol_TextWarning);\n\n        PerfMonTextRight(text_ms_width, 0.0f, \"%.2f\", m_FrameTimeGPU);\n        ImGui::SameLine(0.0f, 0.0f);\n        PerfMonTextUnformatted(\" ms\");\n        ImGui::NextColumn();\n\n        if (m_FrameTimeGPU > frame_time_warning_limit)\n            ImGui::PopStyleColor();\n\n        ImGui::NextColumn();\n        ImGui::NextColumn();\n\n        if (!ConfigManager::GetValue(configid_bool_performance_monitor_disable_gpu_counters)) //No point in showing it all if it's not updating\n        {\n            //-GPU Load\n            PerfMonTextUnformatted(TranslationManager::GetString(tstr_PerformanceMonitorLoad));\n            ImGui::NextColumn();\n\n            PerfMonTextRight(text_percent_width, 0.0f, \"%.2f\", m_PerfData.GetGPULoadPrecentage());\n            ImGui::SameLine(0.0f, 0.0f);\n            PerfMonTextUnformatted(\"%\");\n            ImGui::NextColumn();\n\n            //-VRAM\n            ImGui::SetCursorPosX(ImGui::GetCursorPosX() - item_spacing_half);  //Reduce horizontal spacing\n            PerfMonTextUnformatted(TranslationManager::GetString(tstr_PerformanceMonitorVRAM));\n            ImGui::NextColumn();\n            PerfMonTextRight(right_border_offset - 1.0f, 0.0f, \"%.2f/%.2f GB\", m_PerfData.GetVRAMUsedGB(), m_PerfData.GetVRAMTotalGB());\n            ImGui::NextColumn();\n        }\n    }\n\n    //-Table SteamVR\n    if ( (ConfigManager::GetValue(configid_bool_performance_monitor_show_fps)) || (ConfigManager::GetValue(configid_bool_performance_monitor_show_battery)) )\n    {\n        ImGui::PushStyleColor(ImGuiCol_Text, ImGui::GetStyleColorVec4(ImGuiCol_ButtonHovered));\n        PerfMonTextUnformatted(\"SteamVR\");\n        ImGui::PopStyleColor();\n\n        ImGui::NextColumn();\n        ImGui::NextColumn();\n        ImGui::NextColumn();\n\n        //-Time\n        if (ConfigManager::GetValue(configid_bool_performance_monitor_show_time))\n        {\n            PerfMonTextRightUnformatted(right_border_offset, 0.0f, m_TimeStr.c_str());\n        }\n\n        ImGui::NextColumn();\n\n        if (ConfigManager::GetValue(configid_bool_performance_monitor_show_fps))\n        {\n            //-FPS\n            PerfMonTextUnformatted(TranslationManager::GetString(tstr_PerformanceMonitorFPS));\n            ImGui::NextColumn();\n            PerfMonTextRight(0.0f, 0.0f, \"%d\", m_FPS);\n            ImGui::NextColumn();\n\n            //-Average FPS\n            ImGui::SetCursorPosX(ImGui::GetCursorPosX() - item_spacing_half);  //Reduce horizontal spacing\n            PerfMonTextUnformatted(TranslationManager::GetString(tstr_PerformanceMonitorFPSAverage));\n            ImGui::NextColumn();\n            PerfMonTextRight(right_border_offset, 0.0f, \"%.2f\", m_FPS_Average);\n            ImGui::NextColumn();\n\n            //No VR app means no frame statistics (FPS still gets counted, though)\n            if (m_PIDLast == 0)\n                ImGui::PushItemDisabled();\n\n            //-Reprojection Ratio\n            PerfMonTextUnformatted(TranslationManager::GetString(tstr_PerformanceMonitorReprojectionRatio));\n            ImGui::NextColumn();\n\n            PerfMonTextRight(text_percent_width, 0.0f, \"%.2f\", m_ReprojectionRatio);\n            ImGui::SameLine(0.0f, 0.0f);\n            PerfMonTextUnformatted(\"%\");\n            ImGui::NextColumn();\n\n            //-Dropped Frames\n            ImGui::SetCursorPosX(ImGui::GetCursorPosX() - item_spacing_half);  //Reduce horizontal spacing\n            PerfMonTextUnformatted(TranslationManager::GetString(tstr_PerformanceMonitorDroppedFrames));\n            ImGui::NextColumn();\n            PerfMonTextRight(right_border_offset, 0.0f, \"%u\", m_DroppedFrames);\n            ImGui::NextColumn();\n\n            if (m_PIDLast == 0)\n                ImGui::PopItemDisabled();\n        }\n\n        if (ConfigManager::GetValue(configid_bool_performance_monitor_show_battery))\n        {\n            //-Battery Left\n            PerfMonTextUnformatted(TranslationManager::GetString(tstr_PerformanceMonitorBatteryLeft));\n            ImGui::NextColumn();\n\n            if (m_BatteryLeft != -1.0f)\n            {\n                //15% warning color (Same percentage the SteamVR dashboard battery icon goes red at)\n                if (m_BatteryLeft < 15.0f)\n                    ImGui::PushStyleColor(ImGuiCol_Text, Style_ImGuiCol_TextWarning);\n\n                PerfMonTextRight(text_percent_width, 0.0f, \"%.0f\", m_BatteryLeft);\n                ImGui::SameLine(0.0f, 0.0f);\n                PerfMonTextUnformatted(\"%\");\n                ImGui::NextColumn();\n\n                if (m_BatteryLeft < 15.0f)\n                    ImGui::PopStyleColor();\n            }\n            else\n            {\n                ImGui::PushItemDisabled();\n                PerfMonTextRightUnformatted(0.0f, 0.0f, TranslationManager::GetString(tstr_PerformanceMonitorBatteryDisconnected));\n                ImGui::NextColumn();\n                ImGui::PopItemDisabled();\n            }\n\n            //-Battery Right\n            ImGui::SetCursorPosX(ImGui::GetCursorPosX() - item_spacing_half);  //Reduce horizontal spacing\n            PerfMonTextUnformatted(TranslationManager::GetString(tstr_PerformanceMonitorBatteryRight));\n            ImGui::NextColumn();\n\n            if (m_BatteryRight != -1.0f)\n            {\n                //15% warning color\n                if (m_BatteryRight < 15.0f)\n                    ImGui::PushStyleColor(ImGuiCol_Text, Style_ImGuiCol_TextWarning);\n\n                PerfMonTextRight(text_percent_width + right_border_offset, 0.0f, \"%.0f\", m_BatteryRight);\n                ImGui::SameLine(0.0f, 0.0f);\n                PerfMonTextUnformatted(\"%\");\n                ImGui::NextColumn();\n\n                if (m_BatteryRight < 15.0f)\n                    ImGui::PopStyleColor();\n            }\n            else\n            {\n                ImGui::PushItemDisabled();\n                PerfMonTextRightUnformatted(right_border_offset, 0.0f, TranslationManager::GetString(tstr_PerformanceMonitorBatteryDisconnected));\n                ImGui::NextColumn();\n                ImGui::PopItemDisabled();\n            }\n\n            //-Battery HMD (only shown if available)\n            if (m_BatteryHMD != -1.0f)\n            {\n                PerfMonTextUnformatted(TranslationManager::GetString(tstr_PerformanceMonitorBatteryHMD));\n                ImGui::NextColumn();\n                //15% warning color\n                if (m_BatteryHMD < 15.0f)\n                    ImGui::PushStyleColor(ImGuiCol_Text, Style_ImGuiCol_TextWarning);\n\n                PerfMonTextRight(text_percent_width, 0.0f, \"%.0f\", m_BatteryHMD);\n                ImGui::SameLine(0.0f, 0.0f);\n                PerfMonTextUnformatted(\"%\");\n                ImGui::NextColumn();\n\n                if (m_BatteryHMD < 15.0f)\n                    ImGui::PopStyleColor();\n            }\n\n            float right_offset;\n\n            //-Battery Trackers\n            if (ConfigManager::GetValue(configid_bool_performance_monitor_show_trackers))\n            {\n                unsigned int tracker_number = 1;\n                for (const auto& tracker_info : m_BatteryTrackers)\n                {\n                    //Reduce horizontal spacing on the right column\n                    if (ImGui::GetColumnIndex() == 2)\n                    {\n                        ImGui::SetCursorPosX(ImGui::GetCursorPosX() - item_spacing_half);\n                        right_offset = text_percent_width + right_border_offset;\n                    }\n                    else\n                    {\n                        right_offset = text_percent_width;\n                    }\n\n                    PerfMonTextUnformatted(tracker_info.Name.c_str());\n                    ImGui::NextColumn();\n\n                    //15% warning color\n                    if (tracker_info.BatteryLevel < 15.0f)\n                        ImGui::PushStyleColor(ImGuiCol_Text, Style_ImGuiCol_TextWarning);\n\n                    PerfMonTextRight(right_offset, 0.0f, \"%.0f\", tracker_info.BatteryLevel);\n                    ImGui::SameLine(0.0f, 0.0f);\n                    PerfMonTextUnformatted(\"%\");\n                    ImGui::NextColumn();\n\n                    if (tracker_info.BatteryLevel < 15.0f)\n                        ImGui::PopStyleColor();\n\n                    tracker_number++;\n                }\n            }\n\n            //-Vive Wireless\n            if ( (m_ViveWirelessLogPathExists) && (ConfigManager::GetValue(configid_bool_performance_monitor_show_vive_wireless)) )\n            {\n                //Reduce horizontal spacing on the right column\n                if (ImGui::GetColumnIndex() == 2)\n                {\n                    ImGui::SetCursorPosX(ImGui::GetCursorPosX() - item_spacing_half);  \n                    right_offset = right_border_offset;\n                }\n                else\n                {\n                    right_offset = 0.0f;\n                }\n\n                PerfMonText(\"Vive Wireless:\");\n                ImGui::NextColumn();\n\n                if (m_ViveWirelessTemp != -1)\n                {\n                    //90 degrees celsius warning color (arbitrarily chosen, but that's not a temp it should be at constantly)\n                    if (m_ViveWirelessTemp > 90)\n                        ImGui::PushStyleColor(ImGuiCol_Text, Style_ImGuiCol_TextWarning);\n\n                    PerfMonTextRight(right_offset, 0.0f, \"%d\\xC2\\xB0\"\"C\", m_ViveWirelessTemp);\n\n                    if (m_ViveWirelessTemp > 90)\n                        ImGui::PopStyleColor();\n                }\n                else\n                {\n                    ImGui::PushItemDisabled();\n                    PerfMonTextRightUnformatted(right_offset, 0.0f, TranslationManager::GetString(tstr_PerformanceMonitorCompactViveWirelessTempNotAvailable));\n                    ImGui::PopItemDisabled();\n                }\n            }\n        }\n    }\n\n    //Last item rect height is the padding dummy == empty window\n    if (ImGui::GetItemRectSize().y == 0.0f)\n    {\n        ImGui::Columns(1);\n        PerfMonTextUnformatted(TranslationManager::GetString(tstr_PerformanceMonitorEmpty));\n    }\n}\n\nvoid WindowPerformance::DisplayStatsCompact()\n{\n    const ImGuiStyle& style = ImGui::GetStyle();\n    const float item_spacing_prev = style.ItemSpacing.x;\n\n    ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, {0.0f, style.ItemSpacing.y});\n\n    //These width caluclations are not using the translated strings, but there really is no space for those to be longer either, so whatever\n    static const float column_width_0     = ImGui::GetFontSize() * 1.5f;\n    float column_width_cpu_1              = ImGui::CalcTextSize(\" 000.00 ms \").x;\n    float column_width_cpu_2              = ImGui::CalcTextSize(\" 000.00% \").x;\n    float column_width_cpu_3              = ImGui::CalcTextSize(\" 000.00/000.00 GB \").x;\n    static const float column_width_fps_1 = ImGui::CalcTextSize(\" 000 \").x;\n    static const float column_width_fps_2 = ImGui::CalcTextSize(\" 000.00 AVG \").x;\n    static const float column_width_fps_3 = ImGui::CalcTextSize(\" 00.00% RPR \").x;\n    static const float column_width_fps_4 = ImGui::CalcTextSize(\" 0000 DRP \").x;\n    float column_width_bat                = ImGui::CalcTextSize(\" T1 100% \").x;\n\n    static const float text_percentage_cwidth = ImGui::CalcTextSize(\" 100%\").x;\n    static const float text_avg_cwidth        = ImGui::CalcTextSize(\" AVG\").x;\n    static const float text_rpr_cwidth        = ImGui::CalcTextSize(\"% RPR\").x;\n    static const float text_drp_cwidth        = ImGui::CalcTextSize(\" DRP\").x;\n    static const float text_ms_width          = ImGui::CalcTextSize(\" ms\").x;\n    static const float text_percent_width     = ImGui::CalcTextSize(\"%\").x;\n    static const float text_gb_part_width     = ImGui::CalcTextSize(\"00.00 GB\").x;\n\n    const float width_total_cpu = column_width_0 + column_width_cpu_1 + column_width_cpu_2 + column_width_cpu_3;\n    const float width_total_fps = column_width_0 + column_width_fps_1 + column_width_fps_2 + column_width_fps_3 + column_width_fps_4;\n    const float width_total_bat = column_width_0 + (column_width_bat * 4.0f);\n\n    const float frame_time_warning_limit = m_FrameTimeVsyncLimit * 0.95f;\n\n    //Pad shorter columns to match total length with the FPS row\n    float padding = (width_total_fps - width_total_cpu) / 2.0f; //Only pad the second and last column so the first one lines up\n    column_width_cpu_2 += padding;\n    column_width_cpu_3 += padding;\n\n    padding = (width_total_fps - width_total_bat) / 4.0f;\n    column_width_bat += padding;\n\n    //Pad window content to fps row width since columns don't increase the window size\n    ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, {0.0f, 0.0f});\n    ImGui::Dummy({width_total_fps, 0.0f});\n    ImGui::PopStyleVar();\n\n    //Workaround for an invalid clipping assert raised when switching columns in the compact layout\n    //Came with ImGui 1.80/1.90 (required workaround changed with 1.90), but has quite a few variables involved, so I'm not even sure who's at fault here. This works, though.\n    if (ImGui::IsWindowAppearing())\n    {\n        ImGui::PopStyleVar();\n        return;\n    }\n\n    //Align RAM and VRAM values even if they have different lengths\n    static float text_ram_total_width  = 0.0f;\n    static float text_vram_total_width = 0.0f;\n    float text_ram_padding = std::max(text_ram_total_width, text_vram_total_width);\n\n    //--CPU/GPU Frame Time Graphs\n\n    //Shared offsets\n    //Show both graphs if showing CPU and GPU stats or none of them (if graphs themselves are still active)\n    bool show_both_graphs = (ConfigManager::GetValue(configid_bool_performance_monitor_show_cpu) == ConfigManager::GetValue(configid_bool_performance_monitor_show_gpu));\n    const ImVec2 graph_size = {(show_both_graphs) ? floorf( (width_total_fps - item_spacing_prev) / 2.0f) : width_total_fps, ImGui::GetFontSize() * 2.0f};\n\n    double plot_xmin = (m_FrameTimeLastIndex > (uint32_t)m_FrameTimeCPUHistory.MaxSize) ? m_FrameTimeLastIndex - m_FrameTimeCPUHistory.MaxSize + 0.5f : 0.5f;\n    double plot_xmax = m_FrameTimeLastIndex - 0.75f;\n    double plot_ymax = ceilf(m_FrameTimeVsyncLimit * 1.4f); \n\n    if (ConfigManager::GetValue(configid_bool_performance_monitor_show_graphs))\n    {\n        //-CPU Frame Time Graph\n        if ( (show_both_graphs) || (ConfigManager::GetValue(configid_bool_performance_monitor_show_cpu)) )\n        {\n            DrawFrameTimeGraphCPU(graph_size, plot_xmin, plot_xmax, plot_ymax);\n\n            if (show_both_graphs)\n            {\n                ImGui::SameLine(0.0f, item_spacing_prev);\n            }\n        }\n\n        //-GPU Frame Time Graph\n        if ( (show_both_graphs) || (ConfigManager::GetValue(configid_bool_performance_monitor_show_gpu)) )\n        {\n            DrawFrameTimeGraphGPU(graph_size, plot_xmin, plot_xmax, plot_ymax);\n        }\n    }\n\n    //CPU/GPU columns\n    if ( (ConfigManager::GetValue(configid_bool_performance_monitor_show_cpu)) || (ConfigManager::GetValue(configid_bool_performance_monitor_show_gpu)) )\n    {\n        ImGui::Columns(4, \"ColumnStatsCompact\", false);\n        ImGui::SetColumnWidth(0, column_width_0);\n        ImGui::SetColumnWidth(1, column_width_cpu_1);\n        ImGui::SetColumnWidth(2, column_width_cpu_2);\n        ImGui::SetColumnWidth(3, column_width_cpu_3);\n    }\n\n    //--Row CPU\n    if (ConfigManager::GetValue(configid_bool_performance_monitor_show_cpu))\n    {\n        ImGui::PushStyleColor(ImGuiCol_Text, ImGui::GetStyleColorVec4(ImGuiCol_ButtonHovered));\n        PerfMonTextUnformatted(TranslationManager::GetString(tstr_PerformanceMonitorCompactCPU));\n        ImGui::PopStyleColor();\n\n        ImGui::NextColumn();\n\n        //-CPU Frame Time\n        //Warning color when frame time above 95% vsync time\n        if (m_FrameTimeCPU > frame_time_warning_limit)\n            ImGui::PushStyleColor(ImGuiCol_Text, Style_ImGuiCol_TextWarning);\n\n        PerfMonTextRight(text_ms_width, 0.0f, \"%.2f\", m_FrameTimeCPU);\n        ImGui::SameLine(0.0f, 0.0f);\n        PerfMonTextUnformatted(\" ms\");\n        ImGui::NextColumn();\n\n        if (m_FrameTimeCPU > frame_time_warning_limit)\n            ImGui::PopStyleColor();\n\n        //-CPU Load\n        PerfMonTextRight(text_percent_width, 0.0f, \"%.2f\", m_PerfData.GetCPULoadPrecentage());\n        ImGui::SameLine(0.0f, 0.0f);\n        PerfMonTextUnformatted(\"%\");\n        ImGui::NextColumn();\n\n        //-RAM\n        PerfMonTextRight(text_ram_padding, 0.0f, \"%.2f GB/\", m_PerfData.GetRAMUsedGB());\n        ImGui::SameLine(0.0f, 0.0f);\n        PerfMonTextRight(0.0f, 0.0f, \"%.2f GB\", m_PerfData.GetRAMTotalGB());\n        text_ram_total_width = ImGui::GetItemRectSize().x;\n        ImGui::NextColumn();\n    }\n\n    //--Row GPU\n    if (ConfigManager::GetValue(configid_bool_performance_monitor_show_gpu))\n    {\n        ImGui::PushStyleColor(ImGuiCol_Text, ImGui::GetStyleColorVec4(ImGuiCol_ButtonHovered));\n        PerfMonTextUnformatted(TranslationManager::GetString(tstr_PerformanceMonitorCompactGPU));\n        ImGui::PopStyleColor();\n\n        ImGui::NextColumn();\n\n        //-GPU Frame Time\n        if (m_FrameTimeGPU > frame_time_warning_limit)\n            ImGui::PushStyleColor(ImGuiCol_Text, Style_ImGuiCol_TextWarning);\n\n        PerfMonTextRight(text_ms_width, 0.0f, \"%.2f\", m_FrameTimeGPU);\n        ImGui::SameLine(0.0f, 0.0f);\n        PerfMonTextUnformatted(\" ms\");\n        ImGui::NextColumn();\n\n        if (m_FrameTimeGPU > frame_time_warning_limit)\n            ImGui::PopStyleColor();\n\n        if (!ConfigManager::GetValue(configid_bool_performance_monitor_disable_gpu_counters)) //No point in showing it all if it's not updating\n        {\n            //-GPU Load\n            PerfMonTextRight(text_percent_width, 0.0f, \"%.2f\", m_PerfData.GetGPULoadPrecentage());\n            ImGui::SameLine(0.0f, 0.0f);\n            PerfMonTextUnformatted(\"%\");\n            ImGui::NextColumn();\n\n            //-VRAM\n            PerfMonTextRight(text_ram_padding, 0.0f, \"%.2f GB/\", m_PerfData.GetVRAMUsedGB());\n            ImGui::SameLine(0.0f, 0.0f);\n            PerfMonTextRight(0.0f, 0.0f, \"%.2f GB\", m_PerfData.GetVRAMTotalGB());\n            text_vram_total_width = ImGui::GetItemRectSize().x;\n            ImGui::NextColumn();\n        }\n    }\n\n    //--Row FPS\n    if (ConfigManager::GetValue(configid_bool_performance_monitor_show_fps))\n    {\n        ImGui::Columns(5, \"ColumnStatsCompactFPS\", false);\n        ImGui::SetColumnWidth(0, column_width_0);\n        ImGui::SetColumnWidth(1, column_width_fps_1);\n        ImGui::SetColumnWidth(2, column_width_fps_2);\n        ImGui::SetColumnWidth(3, column_width_fps_3);\n        ImGui::SetColumnWidth(4, column_width_fps_4);\n\n        ImGui::PushStyleColor(ImGuiCol_Text, ImGui::GetStyleColorVec4(ImGuiCol_ButtonHovered));\n        PerfMonTextUnformatted(TranslationManager::GetString(tstr_PerformanceMonitorCompactFPS));\n        ImGui::PopStyleColor();\n\n        //-FPS\n        ImGui::NextColumn();\n        PerfMonTextRight(0.0f, 0.0f, \"%d\", m_FPS);\n        ImGui::NextColumn();\n\n        //-Average FPS\n        PerfMonTextRight(text_avg_cwidth, 0.0f, \"%.2f\", m_FPS_Average);\n        ImGui::SameLine(0.0f, 0.0f);\n        PerfMonTextRightUnformatted(0.0f, 0.0f, TranslationManager::GetString(tstr_PerformanceMonitorCompactFPSAverage));\n        ImGui::NextColumn();\n\n        //No VR app means no frame statistics (FPS still gets counted, though)\n        if (m_PIDLast == 0)\n            ImGui::PushItemDisabled();\n\n        //-Reprojection Ratio\n        PerfMonTextRight(text_rpr_cwidth, 0.0f, \"%.2f\", m_ReprojectionRatio);\n        ImGui::SameLine(0.0f, 0.0f);\n        PerfMonTextRightUnformatted(0.0f, 0.0f, TranslationManager::GetString(tstr_PerformanceMonitorCompactReprojectionRatio));\n        ImGui::NextColumn();\n\n        //-Dropped Frames\n        PerfMonTextRight(text_drp_cwidth, 0.0f, \"%u\", m_DroppedFrames);\n        ImGui::SameLine(0.0f, 0.0f);\n        PerfMonTextRightUnformatted(0.0f, 0.0f, TranslationManager::GetString(tstr_PerformanceMonitorCompactDroppedFrames));\n        ImGui::NextColumn();\n\n        if (m_PIDLast == 0)\n            ImGui::PopItemDisabled();\n    }\n\n    //--Row BAT\n    if (ConfigManager::GetValue(configid_bool_performance_monitor_show_battery))\n    {\n        ImGui::Columns(1);  //Reset columns or else it won't treat it as a sepearate layout\n        ImGui::Columns(5, \"ColumnStatsCompactBAT\", false);\n        ImGui::SetColumnWidth(0, column_width_0);\n        ImGui::SetColumnWidth(1, column_width_bat);\n        ImGui::SetColumnWidth(2, column_width_bat);\n        ImGui::SetColumnWidth(3, column_width_bat);\n        ImGui::SetColumnWidth(4, column_width_bat);\n\n        ImGui::PushStyleColor(ImGuiCol_Text, ImGui::GetStyleColorVec4(ImGuiCol_ButtonHovered));\n        PerfMonTextUnformatted(TranslationManager::GetString(tstr_PerformanceMonitorCompactBattery));\n        ImGui::PopStyleColor();\n\n        ImGui::NextColumn();\n\n        //-Battery Left\n        PerfMonTextRightUnformatted(text_percentage_cwidth, 0.0f, TranslationManager::GetString(tstr_PerformanceMonitorCompactBatteryLeft));\n        ImGui::SameLine(0.0f, 0.0f);\n\n        if (m_BatteryLeft != -1.0f)\n        {\n            //15% warning color\n            if (m_BatteryLeft < 15.0f)\n                ImGui::PushStyleColor(ImGuiCol_Text, Style_ImGuiCol_TextWarning);\n\n            PerfMonTextRight(0.0f, 0.0f, \"%.0f%%\", m_BatteryLeft);\n\n            if (m_BatteryLeft < 15.0f)\n                ImGui::PopStyleColor();\n        }\n        else\n        {\n            ImGui::PushItemDisabled();\n            PerfMonTextRightUnformatted(0.0f, 0.0f, TranslationManager::GetString(tstr_PerformanceMonitorCompactBatteryDisconnected));\n            ImGui::PopItemDisabled();\n        }\n\n        ImGui::NextColumn();\n\n        //-Battery Right\n        PerfMonTextRightUnformatted(text_percentage_cwidth, 0.0f, TranslationManager::GetString(tstr_PerformanceMonitorCompactBatteryRight));\n        ImGui::SameLine(0.0f, 0.0f);\n\n        if (m_BatteryRight != -1.0f)\n        {\n            //15% warning color\n            if (m_BatteryRight < 15.0f)\n                ImGui::PushStyleColor(ImGuiCol_Text, Style_ImGuiCol_TextWarning);\n\n            PerfMonTextRight(0.0f, 0.0f, \"%.0f%%\", m_BatteryRight);\n\n            if (m_BatteryRight < 15.0f)\n                ImGui::PopStyleColor();\n        }\n        else\n        {\n            ImGui::PushItemDisabled();\n            PerfMonTextRightUnformatted(0.0f, 0.0f, TranslationManager::GetString(tstr_PerformanceMonitorCompactBatteryDisconnected));\n            ImGui::PopItemDisabled();\n        }\n\n        ImGui::NextColumn();\n\n        //-Battery HMD (only shown if available)\n        if (m_BatteryHMD != -1.0f)\n        {\n            PerfMonTextRightUnformatted(text_percentage_cwidth, 0.0f, TranslationManager::GetString(tstr_PerformanceMonitorCompactBatteryHMD));\n            ImGui::SameLine(0.0f, 0.0f);\n\n            //15% warning color\n            if (m_BatteryHMD < 15.0f)\n                ImGui::PushStyleColor(ImGuiCol_Text, Style_ImGuiCol_TextWarning);\n\n            PerfMonTextRight(0.0f, 0.0f, \"%.0f%%\", m_BatteryHMD);\n\n            if (m_BatteryHMD < 15.0f)\n                ImGui::PopStyleColor();\n        }\n\n        ImGui::NextColumn();\n\n        //-Battery Trackers\n        if (ConfigManager::GetValue(configid_bool_performance_monitor_show_trackers))\n        {\n            unsigned int tracker_number = 1;\n            for (const auto& tracker_info : m_BatteryTrackers)\n            {\n                //Skip first column\n                if (ImGui::GetColumnIndex() == 0)\n                {\n                    ImGui::NextColumn();\n                }\n\n                PerfMonTextRightUnformatted(text_percentage_cwidth, 0.0f, tracker_info.NameCompact.c_str());\n                ImGui::SameLine(0.0f, 0.0f);\n\n                //15% warning color\n                if (tracker_info.BatteryLevel < 15.0f)\n                    ImGui::PushStyleColor(ImGuiCol_Text, Style_ImGuiCol_TextWarning);\n\n                PerfMonTextRight(0.0f, 0.0f, \"%.0f%%\", tracker_info.BatteryLevel);\n\n                if (tracker_info.BatteryLevel < 15.0f)\n                    ImGui::PopStyleColor();\n\n                ImGui::NextColumn();\n\n                tracker_number++;\n            }\n        }\n\n        //-Vive Wireless\n        if ( (m_ViveWirelessLogPathExists) && (ConfigManager::GetValue(configid_bool_performance_monitor_show_vive_wireless)) )\n        {\n            //Skip first column\n            if (ImGui::GetColumnIndex() == 0)\n            {\n                ImGui::NextColumn();\n            }\n\n            PerfMonTextRightUnformatted(text_percentage_cwidth, 0.0f, \"VW\");\n            ImGui::SameLine(0.0f, 0.0f);\n\n            if (m_ViveWirelessTemp != -1)\n            {\n                //90 degrees celsius warning color\n                if (m_ViveWirelessTemp > 90)\n                    ImGui::PushStyleColor(ImGuiCol_Text, Style_ImGuiCol_TextWarning);\n\n                PerfMonTextRight(0.0f, 0.0f, \"%d\\xC2\\xB0\"\"C\", m_ViveWirelessTemp);\n\n                if (m_ViveWirelessTemp > 90)\n                    ImGui::PopStyleColor();\n            }\n            else\n            {\n                ImGui::PushItemDisabled();\n                PerfMonTextRightUnformatted(0.0f, 0.0f, TranslationManager::GetString(tstr_PerformanceMonitorCompactViveWirelessTempNotAvailable));\n                ImGui::PopItemDisabled();\n            }\n        }\n    }\n\n    ImGui::PopStyleVar();\n\n    //Last item rect height is the padding dummy == empty window\n    if (ImGui::GetItemRectSize().y == 0.0f)\n    {\n        ImGui::Columns(1);\n        PerfMonTextUnformatted(TranslationManager::GetString(tstr_PerformanceMonitorEmpty));\n    }\n}\n\nvoid WindowPerformance::DisplayStatsMinimal()\n{\n    const ImGuiStyle& style = ImGui::GetStyle();\n    const float item_spacing_half = style.ItemSpacing.x / 2.0f;\n\n    //Items are using right-aligned text within a fixed expected width to avoid text jumping around as values change\n    static const float text_fps_width            = ImGui::CalcTextSize(\"100\").x;\n    static const float text_percentage_width     = ImGui::CalcTextSize(\"00.00%\").x;\n    static const float text_percentage_int_width = ImGui::CalcTextSize(\"100%\").x;\n    static const float text_rpr_width            = ImGui::CalcTextSize(\"00.00\").x;\n    static const float text_ms_width             = ImGui::CalcTextSize(\"00.00 ms\").x;\n    static const float text_gb_width             = ImGui::CalcTextSize(\"00.00 GB\").x;\n    static const float text_temperature_width    = ImGui::CalcTextSize(\"00\\xC2\\xB0\"\"C\").x;\n    static const float text_time_width           = ImGui::CalcTextSize(\"00:00\").x;\n\n    const float frame_time_warning_limit = m_FrameTimeVsyncLimit * 0.95f;\n\n    //StatsMinimalItemLineWrap() expects a previous item to exist, so we have a dummy fill that role\n    ImGui::Dummy({0.0f, 0.0f});\n    ImGui::SameLine(0.0f, 0.0f);\n\n    //--CPU/GPU Frame Time Graphs\n    //Show both graphs if showing CPU and GPU stats or none of them (if graphs themselves are still active)\n    bool show_both_graphs = (ConfigManager::GetValue(configid_bool_performance_monitor_show_cpu) == ConfigManager::GetValue(configid_bool_performance_monitor_show_gpu));\n    const ImVec2 graph_size = {ImGui::GetFontSize() * 2.0f, ImGui::GetFontSize()};\n\n    double plot_xmin = (m_FrameTimeLastIndex > (uint32_t)m_FrameTimeCPUHistory.MaxSize) ? m_FrameTimeLastIndex - m_FrameTimeCPUHistory.MaxSize + 0.5f : 0.5f;\n    double plot_xmax = m_FrameTimeLastIndex - 0.75f;\n    double plot_ymax = ceilf(m_FrameTimeVsyncLimit * 1.4f);\n\n    //--FPS\n    if (ConfigManager::GetValue(configid_bool_performance_monitor_show_fps))\n    {\n        StatsMinimalItemLineWrap(); //First item won't ever wrap, but this sets the last item position for wrapping\n\n        //-FPS\n        PerfMonTextRight(0.0f, text_fps_width, \"%d\", m_FPS);\n        ImGui::SameLine();\n\n        if (ConfigManager::GetValue(configid_bool_performance_monitor_style_minimal_show_more))\n        {\n            //No VR app means no frame statistics (FPS still gets counted, though)\n            if (m_PIDLast == 0)\n                ImGui::PushItemDisabled();\n\n            //-Reprojection Ratio\n            PerfMonTextRight(0.0f, text_rpr_width, \"%.2f\", m_ReprojectionRatio);\n            ImGui::SameLine(0.0f, 0.0f);\n            PerfMonTextUnformatted(TranslationManager::GetString(tstr_PerformanceMonitorCompactReprojectionRatio));\n            ImGui::SameLine();\n\n            if (m_PIDLast == 0)\n                ImGui::PopItemDisabled();\n        }\n    }\n\n    //--CPU\n    if (ConfigManager::GetValue(configid_bool_performance_monitor_show_cpu))\n    {\n        StatsMinimalItemLineWrap();\n\n        //-CPU Frame Time\n        //Warning color when frame time above 95% vsync time\n        if (m_FrameTimeCPU > frame_time_warning_limit)\n            ImGui::PushStyleColor(ImGuiCol_Text, Style_ImGuiCol_TextWarning);\n\n        PerfMonTextRight(0.0f, text_ms_width, \"%.2f ms\", m_FrameTimeCPU);\n        ImGui::SameLine();\n\n        if (m_FrameTimeCPU > frame_time_warning_limit)\n            ImGui::PopStyleColor();\n\n        if (ConfigManager::GetValue(configid_bool_performance_monitor_style_minimal_show_more))\n        {\n            //-CPU Load\n            PerfMonTextRight(0.0f, text_percentage_width, \"%.2f%%\", m_PerfData.GetCPULoadPrecentage());\n            ImGui::SameLine();\n\n            //-RAM\n            PerfMonTextRight(0.0f, text_gb_width, \"%.2f GB\", m_PerfData.GetRAMUsedGB());\n            ImGui::SameLine();\n        }\n    }\n\n    //--CPU Frame Time Graph\n    if (ConfigManager::GetValue(configid_bool_performance_monitor_show_graphs))\n    {\n        if ( (show_both_graphs) || (ConfigManager::GetValue(configid_bool_performance_monitor_show_cpu)) )\n        {\n            StatsMinimalItemLineWrap();\n\n            DrawFrameTimeGraphCPU(graph_size, plot_xmin, plot_xmax, plot_ymax);\n            ImGui::SameLine();\n        }\n    }\n\n    //--GPU\n    if (ConfigManager::GetValue(configid_bool_performance_monitor_show_gpu))\n    {\n        StatsMinimalItemLineWrap();\n\n        //-GPU Frame Time\n        if (m_FrameTimeGPU > frame_time_warning_limit)\n            ImGui::PushStyleColor(ImGuiCol_Text, Style_ImGuiCol_TextWarning);\n\n        PerfMonTextRight(0.0f, text_ms_width, \"%.2f ms\", m_FrameTimeGPU);\n        ImGui::SameLine();\n\n        if (m_FrameTimeGPU > frame_time_warning_limit)\n            ImGui::PopStyleColor();\n\n        if (ConfigManager::GetValue(configid_bool_performance_monitor_style_minimal_show_more))\n        {\n            if (!ConfigManager::GetValue(configid_bool_performance_monitor_disable_gpu_counters)) //No point in showing it all if it's not updating\n            {\n                //-GPU Load\n                PerfMonTextRight(0.0f, text_percentage_width, \"%.2f%%\", m_PerfData.GetGPULoadPrecentage());\n                ImGui::SameLine();\n\n                //-VRAM\n                PerfMonTextRight(0.0f, text_gb_width, \"%.2f GB\", m_PerfData.GetVRAMUsedGB());\n                ImGui::SameLine();\n            }\n        }\n    }\n\n    //--GPU Frame Time Graph\n    if (ConfigManager::GetValue(configid_bool_performance_monitor_show_graphs))\n    {\n        if ( (show_both_graphs) || (ConfigManager::GetValue(configid_bool_performance_monitor_show_gpu)) )\n        {\n            StatsMinimalItemLineWrap();\n\n            DrawFrameTimeGraphGPU(graph_size, plot_xmin, plot_xmax, plot_ymax);\n            ImGui::SameLine();\n        }\n    }\n\n    //--Battery Levels\n    if (ConfigManager::GetValue(configid_bool_performance_monitor_show_battery))\n    {\n        //-Battery Left\n        if (m_BatteryLeft != -1.0f)\n        {\n            StatsMinimalItemLineWrap();\n\n            PerfMonTextUnformatted(TranslationManager::GetString(tstr_PerformanceMonitorCompactBatteryLeft));\n            ImGui::SameLine(0.0f, item_spacing_half);\n\n            //15% warning color\n            if (m_BatteryLeft < 15.0f)\n                ImGui::PushStyleColor(ImGuiCol_Text, Style_ImGuiCol_TextWarning);\n\n            PerfMonTextRight(0.0f, text_percentage_int_width, \"%.0f%%\", m_BatteryLeft);\n\n            if (m_BatteryLeft < 15.0f)\n                ImGui::PopStyleColor();\n\n            ImGui::SameLine();\n        }\n\n        //-Battery Right\n        if (m_BatteryRight != -1.0f)\n        {\n            StatsMinimalItemLineWrap();\n\n            PerfMonTextUnformatted(TranslationManager::GetString(tstr_PerformanceMonitorCompactBatteryRight));\n            ImGui::SameLine(0.0f, item_spacing_half);\n\n            //15% warning color\n            if (m_BatteryRight < 15.0f)\n                ImGui::PushStyleColor(ImGuiCol_Text, Style_ImGuiCol_TextWarning);\n\n            PerfMonTextRight(0.0f, text_percentage_int_width, \"%.0f%%\", m_BatteryRight);\n\n            if (m_BatteryRight < 15.0f)\n                ImGui::PopStyleColor();\n\n            ImGui::SameLine();\n        }\n\n        //-Battery HMD\n        if (m_BatteryHMD != -1.0f)\n        {\n            StatsMinimalItemLineWrap();\n\n            PerfMonTextUnformatted(TranslationManager::GetString(tstr_PerformanceMonitorCompactBatteryHMD));\n            ImGui::SameLine(0.0f, item_spacing_half);\n\n            //15% warning color\n            if (m_BatteryHMD < 15.0f)\n                ImGui::PushStyleColor(ImGuiCol_Text, Style_ImGuiCol_TextWarning);\n\n            PerfMonTextRight(0.0f, text_percentage_int_width, \"%.0f%%\", m_BatteryHMD);\n\n            if (m_BatteryHMD < 15.0f)\n                ImGui::PopStyleColor();\n\n            ImGui::SameLine();\n        }\n\n        //-Battery Trackers\n        if (ConfigManager::GetValue(configid_bool_performance_monitor_show_trackers))\n        {\n            unsigned int tracker_number = 1;\n            for (const auto& tracker_info : m_BatteryTrackers)\n            {\n                StatsMinimalItemLineWrap();\n\n                PerfMonTextUnformatted(tracker_info.NameCompact.c_str());\n                ImGui::SameLine(0.0f, item_spacing_half);\n\n                //15% warning color\n                if (tracker_info.BatteryLevel < 15.0f)\n                    ImGui::PushStyleColor(ImGuiCol_Text, Style_ImGuiCol_TextWarning);\n\n                PerfMonTextRight(0.0f, text_percentage_int_width, \"%.0f%%\", tracker_info.BatteryLevel);\n\n                if (tracker_info.BatteryLevel < 15.0f)\n                    ImGui::PopStyleColor();\n\n                ImGui::SameLine();\n\n                tracker_number++;\n            }\n        }\n\n        //-Vive Wireless\n        if ( (m_ViveWirelessLogPathExists) && (ConfigManager::GetValue(configid_bool_performance_monitor_show_vive_wireless)) )\n        {\n            StatsMinimalItemLineWrap();\n\n            PerfMonTextUnformatted(\"VW\");\n            ImGui::SameLine(0.0f, item_spacing_half);\n\n            if (m_ViveWirelessTemp != -1)\n            {\n                //90 degrees celsius warning color\n                if (m_ViveWirelessTemp > 90)\n                    ImGui::PushStyleColor(ImGuiCol_Text, Style_ImGuiCol_TextWarning);\n\n                PerfMonTextRight(0.0f, text_temperature_width, \"%d\\xC2\\xB0\"\"C\", m_ViveWirelessTemp);\n\n                if (m_ViveWirelessTemp > 90)\n                    ImGui::PopStyleColor();\n            }\n            else\n            {\n                ImGui::PushItemDisabled();\n                PerfMonTextUnformatted(TranslationManager::GetString(tstr_PerformanceMonitorCompactViveWirelessTempNotAvailable));\n                ImGui::PopItemDisabled();\n            }\n\n            ImGui::SameLine();\n        }\n    }\n\n    //--Time\n    if (ConfigManager::GetValue(configid_bool_performance_monitor_show_time))\n    {\n        StatsMinimalItemLineWrap();\n\n        PerfMonTextRightUnformatted(0.0f, text_time_width, m_TimeStr.c_str());\n        ImGui::SameLine();\n    }\n\n    //Last item needs to have the wrapping function called too in case it went over\n    StatsMinimalItemLineWrap();\n\n    //Last item rect height is the padding dummy == empty window\n    if (ImGui::GetItemRectSize().y == 0.0f)\n    {\n        PerfMonTextUnformatted(TranslationManager::GetString(tstr_PerformanceMonitorEmpty));\n    }\n}\n\nvoid WindowPerformance::UpdateStatValues()\n{\n    m_PerfData.Update();\n\n    //Localized time string\n    SYSTEMTIME system_time;\n    ::GetSystemTime(&system_time);\n\n    if ( (system_time.wHour != m_TimeLast.wHour) || (system_time.wMinute != m_TimeLast.wMinute) ) //Only grab a new string when the hour or minute changed\n    {\n        wchar_t time_wstr[64];\n\n        if (::GetTimeFormatEx(LOCALE_NAME_USER_DEFAULT, TIME_NOSECONDS, nullptr, nullptr, time_wstr, 63) != 0)\n        {\n            m_TimeStr = StringConvertFromUTF16(time_wstr);\n        }\n\n        m_TimeLast = system_time;\n    }\n\n    UpdateStatValuesSteamVR();\n    UpdateStatValuesViveWireless();\n}\n\nvoid WindowPerformance::UpdateStatValuesSteamVR()\n{\n    //No OpenVR, no frame data\n    if (!UIManager::Get()->IsOpenVRLoaded())\n        return;\n\n    //Get compositor timing from OpenVR\n    vr::Compositor_FrameTiming frame_timing_current;\n    frame_timing_current.m_nSize    = sizeof(vr::Compositor_FrameTiming);\n    bool frame_timing_current_valid = vr::VRCompositor()->GetFrameTiming(&frame_timing_current, 0);\n\n    if (frame_timing_current_valid)\n    {\n        //Set current timings\n        m_FrameTimeCPU = frame_timing_current.m_flClientFrameIntervalMs + frame_timing_current.m_flCompositorRenderCpuMs;\n        m_FrameTimeGPU = frame_timing_current.m_flTotalRenderGpuMs;\n\n        //Update frame time history\n        vr::Compositor_FrameTiming frame_timing_prev;\n        frame_timing_prev.m_nSize    = sizeof(vr::Compositor_FrameTiming);\n        bool frame_timing_prev_valid = false;\n\n        //Sanity check\n        if (frame_timing_current.m_nFrameIndex < m_FrameTimeLastIndex)\n        {\n            m_FrameTimeLastIndex = 0;\n        }\n\n        //Get all frame timings since the last time we updated (but not more than max history size)\n        for (uint32_t frames_ago = std::min(frame_timing_current.m_nFrameIndex - m_FrameTimeLastIndex, (uint32_t)m_FrameTimeCPUHistory.MaxSize); frames_ago != 0; --frames_ago)\n        {\n            frame_timing_prev_valid = vr::VRCompositor()->GetFrameTiming(&frame_timing_prev, frames_ago);\n            //Calculate our own frame index as we get duplicates if SteamVR runs out of history\n            float frame_index = float(frame_timing_current.m_nFrameIndex - frames_ago);\n\n            if (frame_timing_prev_valid)\n            {\n                float frame_time_cpu = frame_timing_prev.m_flClientFrameIntervalMs + frame_timing_prev.m_flCompositorRenderCpuMs;\n\n                m_FrameTimeCPUHistory.AddFrame(frame_index, frame_time_cpu);\n                m_FrameTimeCPUHistoryWarning.AddFrame(frame_index, (frame_time_cpu > m_FrameTimeVsyncLimit) ? frame_time_cpu : 0.0f);\n\n                m_FrameTimeGPUHistory.AddFrame(frame_index, frame_timing_prev.m_flTotalRenderGpuMs);\n                m_FrameTimeGPUHistoryWarning.AddFrame(frame_index, (frame_timing_prev.m_flTotalRenderGpuMs > m_FrameTimeVsyncLimit) ? frame_timing_prev.m_flTotalRenderGpuMs : 0.0f);\n            }\n            else //No valid data, leave gap in history\n            {\n                m_FrameTimeCPUHistory.AddFrame(frame_index, 0.0f);\n                m_FrameTimeCPUHistoryWarning.AddFrame(frame_index, 0.0f);\n\n                m_FrameTimeGPUHistory.AddFrame(frame_index, 0.0f);\n                m_FrameTimeGPUHistoryWarning.AddFrame(frame_index, 0.0f);\n            }\n        }\n\n        m_FrameTimeLastIndex = frame_timing_current.m_nFrameIndex;\n    }\n    else\n    {\n        m_FrameTimeCPU = 0.0f;\n        m_FrameTimeGPU = 0.0f;\n    }\n\n    //Update cumulative stats\n    vr::Compositor_CumulativeStats frame_stats = {0};\n    vr::VRCompositor()->GetCumulativeStats(&frame_stats, sizeof(vr::Compositor_CumulativeStats));\n\n    //Reset when process changed\n    if (frame_stats.m_nPid != m_PIDLast)\n    {\n        //Additionally check if it's actually the running application and not just some past app's stats\n        if (frame_stats.m_nPid == vr::VRApplications()->GetCurrentSceneProcessId())\n        {\n            m_PIDLast = frame_stats.m_nPid;\n            ResetCumulativeValues();\n        }\n    }\n    else if ( (frame_stats.m_nPid != vr::VRApplications()->GetCurrentSceneProcessId()) && (m_PIDLast != 0) ) //Not actually the running application, set last pid to 0 instead if it isn't yet\n    {\n        m_PIDLast = 0;\n        ResetCumulativeValues();\n    }\n\n    //Apply offsets to stat values\n    uint32_t frame_presents               = frame_stats.m_nNumFramePresents             - m_OffsetFramesPresents;\n    uint32_t reprojected_frames           = frame_stats.m_nNumReprojectedFrames         - m_OffsetReprojectedFrames;\n\n    //Update frame count if at least a second passed since the last time\n    if (m_FPS_TickLast + 1000 <= ::GetTickCount64())\n    {\n        uint32_t frame_count = frame_timing_current.m_nFrameIndex - m_OffsetFrameIndex;\n\n        if (vr::VRSystem()->GetTrackedDeviceActivityLevel(vr::k_unTrackedDeviceIndex_Hmd) != vr::k_EDeviceActivityLevel_Standby) //Don't count frames when entering standby\n        {\n            if (m_FrameCountLast != 0)\n            {\n                m_FPS = frame_count - m_FrameCountLast;                                   //Total unreprojected frames rendered since last time\n                m_FPS = int(m_FPS / ((::GetTickCount64() - m_FPS_TickLast) / 1000.0f));   //Divided by seconds passed in case it has been more than just 1\n\n                m_FrameCountTotal += m_FPS;     //This means m_FrameCountTotal may not be the total frames rendered but the sum of whatever we displayed as FPS before\n                m_FrameCountTotalCount++;\n\n                if (m_FrameCountTotalCount != 0)\n                {\n                    m_FPS_Average = m_FrameCountTotal / (float)m_FrameCountTotalCount;\n\n                    //Ignore very small decimals as they occur often and are more confusing than helpful when the fps are actually totally fine (as they don't really go away)\n                    if (fabs(m_FPS_Average - roundf(m_FPS_Average)) < 0.05f)\n                    {\n                        m_FPS_Average = roundf(m_FPS_Average);\n                    }\n                }\n            }\n        }\n\n        m_FPS_TickLast   = ::GetTickCount64();\n        m_FrameCountLast = frame_count;\n    }\n\n    //Reprojection ratio and dropped frames\n    m_ReprojectionRatio = (frame_presents != 0) ? ((float)reprojected_frames / frame_presents) * 100.f : 0.0f;\n    m_DroppedFrames     = frame_stats.m_nNumDroppedFrames - m_OffsetDroppedFrames;\n\n    //Battery Left\n    vr::TrackedDeviceIndex_t device_index = vr::VRSystem()->GetTrackedDeviceIndexForControllerRole(vr::TrackedControllerRole_LeftHand);\n\n    if (device_index != vr::k_unTrackedDeviceIndexInvalid)\n    {\n        m_BatteryLeft = vr::VRSystem()->GetFloatTrackedDeviceProperty(device_index, vr::Prop_DeviceBatteryPercentage_Float) * 100.0f;\n    }\n    else\n    {\n        m_BatteryLeft = -1.0f;\n    }\n\n    //Battery Right\n    device_index = vr::VRSystem()->GetTrackedDeviceIndexForControllerRole(vr::TrackedControllerRole_RightHand);\n\n    if (device_index != vr::k_unTrackedDeviceIndexInvalid)\n    {\n        m_BatteryRight = vr::VRSystem()->GetFloatTrackedDeviceProperty(device_index, vr::Prop_DeviceBatteryPercentage_Float) * 100.0f;\n    }\n    else\n    {\n        m_BatteryRight = -1.0f;\n    }\n\n    //Battery HMD\n    if (vr::VRSystem()->GetBoolTrackedDeviceProperty(vr::k_unTrackedDeviceIndex_Hmd, vr::Prop_DeviceProvidesBatteryStatus_Bool))\n    {\n        m_BatteryHMD = vr::VRSystem()->GetFloatTrackedDeviceProperty(vr::k_unTrackedDeviceIndex_Hmd, vr::Prop_DeviceBatteryPercentage_Float) * 100.0f;\n    }\n    else\n    {\n        m_BatteryHMD = -1.0f;\n    }\n\n    //Battery Trackers\n    for (auto& tracker_info : m_BatteryTrackers)\n    {\n        tracker_info.BatteryLevel = vr::VRSystem()->GetFloatTrackedDeviceProperty(tracker_info.DeviceIndex, vr::Prop_DeviceBatteryPercentage_Float) * 100.0f;\n    }\n}\n\nvoid WindowPerformance::UpdateStatValuesViveWireless()\n{\n    //Vive Wireless Temperatures can seemingly only be read from the log file it's constantly writing too\n    //This doesn't seem terribly efficient, but it's better than nothing\n    //Reading is done every 5 seconds\n\n    //Don't update if the path doesn't exist or it's not even enabled\n    if ( (!m_ViveWirelessLogPathExists) || (!ConfigManager::GetValue(configid_bool_performance_monitor_show_vive_wireless)) )\n        return;\n\n    if (m_ViveWirelessTickLast + 5000 <= ::GetTickCount64())\n    {\n        m_ViveWirelessTickLast = ::GetTickCount64();\n        m_ViveWirelessTemp = -1;\n\n        //Find the newest log file\n        std::wstring path_str = m_ViveWirelessLogPath;\n        path_str += L\"*.txt\";\n        std::vector< std::pair<std::wstring, ULARGE_INTEGER> > file_list;   //std::pair<filename, last_modified_time>\n        WIN32_FIND_DATA find_data;\n        HANDLE handle_find = ::FindFirstFileW(path_str.c_str(), &find_data);\n\n        if (handle_find != INVALID_HANDLE_VALUE)\n        {\n            do\n            {\n                //Add filename and last modified time in list\n                file_list.emplace_back(find_data.cFileName, ULARGE_INTEGER{find_data.ftLastWriteTime.dwLowDateTime, find_data.ftLastWriteTime.dwHighDateTime});\n            }\n            while (::FindNextFileW(handle_find, &find_data) != 0);\n\n            ::FindClose(handle_find);\n        }\n\n        auto it = std::max_element(file_list.begin(), file_list.end(), [](const auto& data_a, const auto& data_b){ return (data_a.second.QuadPart < data_b.second.QuadPart); });\n\n        if (it == file_list.end())\n            return;\n\n        //Check if the newest file is older than 2 minutes, in which case we don't use it to read a temperature at all\n        FILETIME ftime_current;\n        ::GetSystemTimeAsFileTime(&ftime_current);\n        ULARGE_INTEGER time_current{ftime_current.dwLowDateTime, ftime_current.dwHighDateTime};\n\n        if (it->second.QuadPart + 1200000000 <= time_current.QuadPart) //+ 2 minutes in 100 ns intervals\n        {\n            return;\n        }\n\n        //If the newest file is not the same as last time, reset the last used line number\n        if (it->first != m_ViveWirelessLogFileLast)\n        {\n            m_ViveWirelessLogFileLast     = it->first;\n            m_ViveWirelessLogFileLastLine = 0;\n        }\n\n        //Read log file\n        {\n            path_str = m_ViveWirelessLogPath + m_ViveWirelessLogFileLast;\n\n            std::wifstream log_file(path_str);\n\n            if (log_file.good())\n            {\n                //Imbue with utf16-le locale, as are the files Vive Wireless writes (codecvt_utf16 is deprecated starting C++17, but this is the most straight forward way to deal with this)\n                log_file.imbue(std::locale(log_file.getloc(), new std::codecvt_utf16<wchar_t, 0x10ffff, std::little_endian>()));\n\n                //Read lines and see if the M_Temperature can be found (R_Temperature isn't interesting in our case)\n                int line_count = 0;\n                std::wstring line_str;\n                std::string temp_str;\n                size_t mtemp_pos;\n\n                while (log_file.good())\n                {\n                    std::getline(log_file, line_str);\n                    line_count++;\n\n                    //Only check for the temperature value if this line is the same or greater than last time (0 if it's a new file)\n                    if (line_count >= m_ViveWirelessLogFileLastLine)\n                    {\n                        mtemp_pos = line_str.find(L\"M_Temperature=\");\n\n                        if (mtemp_pos != std::wstring::npos)\n                        {\n                            temp_str = StringConvertFromUTF16(line_str.substr(mtemp_pos + 14).c_str());\n                            m_ViveWirelessTemp = atoi(temp_str.c_str());\n                            m_ViveWirelessLogFileLastLine = line_count;\n                        }\n                    }\n                }\n            }\n        }\n    }\n}\n\nvoid WindowPerformance::DrawFrameTimeGraphCPU(const ImVec2& graph_size, double plot_xmin, double plot_xmax, double plot_ymax)\n{\n    float frame_offset = 1.0f + ImGui::GetStyle().FrameBorderSize;\n    ImVec2 cursor_screen_pos_graph = ImGui::GetCursorScreenPos();\n\n    ImPlot::SetNextAxesLimits(plot_xmin, plot_xmax, 0.0, plot_ymax, ImGuiCond_Always);\n\n    if (ImPlot::BeginPlot(\"##PlotCPU\", graph_size, ImPlotFlags_CanvasOnly))\n    {\n        const ImVector<ImVec2>& plot_data      = m_FrameTimeCPUHistory.Data;\n        const ImVector<ImVec2>& plot_data_warn = m_FrameTimeCPUHistoryWarning.Data;\n\n        ImPlot::SetupAxes(nullptr, nullptr, ImPlotAxisFlags_NoDecorations, ImPlotAxisFlags_NoDecorations | ImPlotAxisFlags_LockMin);\n\n        if (!plot_data.empty())\n        {\n            ImGui::PushClipRect({cursor_screen_pos_graph.x + frame_offset, cursor_screen_pos_graph.y + frame_offset}, \n                                {(cursor_screen_pos_graph.x + graph_size.x) - frame_offset, cursor_screen_pos_graph.y + graph_size.y - frame_offset},\n                                false);\n\n\n            ImPlot::SetNextFillStyle(ImVec4(0.5f, 1.0f, 0.0f, 1.0f));\n            ImPlot::PlotShaded(\"##DataShaded\", &plot_data[0].x, &plot_data[0].y, plot_data.size(), 0.0, ImPlotShadedFlags_None, m_FrameTimeCPUHistory.Offset, 2 * sizeof(float));\n\n            ImPlot::SetNextLineStyle(ImVec4(0.5f, 1.0f, 0.0f, 1.0f));\n            ImPlot::PlotLine(\"##DataLine\", &plot_data[0].x, &plot_data[0].y, plot_data.size(), ImPlotLineFlags_None, m_FrameTimeCPUHistory.Offset, 2 * sizeof(float));\n\n            ImPlot::SetNextFillStyle(ImVec4(1.0f, 0.0f, 0.0f, 1.0f));\n            ImPlot::SetNextLineStyle(ImVec4(0.0f, 0.0f, 0.0f, 0.0f));\n            ImPlot::PlotBars(\"##DataWarn\", &plot_data_warn[0].x, &plot_data_warn[0].y, plot_data_warn.size(), 1.0, m_FrameTimeCPUHistoryWarning.Offset, 2 * sizeof(float));\n\n            ImVec2 rmin = ImPlot::PlotToPixels(ImPlotPoint(plot_xmin, m_FrameTimeVsyncLimit));\n            ImVec2 rmax = ImPlot::PlotToPixels(ImPlotPoint(plot_xmax, m_FrameTimeVsyncLimit));\n            ImPlot::PushPlotClipRect();\n            ImPlot::GetPlotDrawList()->AddLine(rmin, rmax, ImGui::ColorConvertFloat4ToU32(ImGui::GetStyleColorVec4(ImGuiCol_Border)) ); \n            ImPlot::PopPlotClipRect();\n\n            ImGui::PopClipRect();\n        }\n\n        ImPlot::EndPlot();\n    }\n}\n\nvoid WindowPerformance::DrawFrameTimeGraphGPU(const ImVec2& graph_size, double plot_xmin, double plot_xmax, double plot_ymax)\n{\n    float frame_offset = 1.0f + ImGui::GetStyle().FrameBorderSize;\n    ImVec2 cursor_screen_pos_graph = ImGui::GetCursorScreenPos();\n\n    ImPlot::SetNextAxesLimits(plot_xmin, plot_xmax, 0.0, plot_ymax, ImGuiCond_Always);\n\n    if (ImPlot::BeginPlot(\"##PlotGPU\", graph_size, ImPlotFlags_CanvasOnly))\n    {\n        const ImVector<ImVec2>& plot_data      = m_FrameTimeGPUHistory.Data;\n        const ImVector<ImVec2>& plot_data_warn = m_FrameTimeGPUHistoryWarning.Data;\n\n        ImPlot::SetupAxes(nullptr, nullptr, ImPlotAxisFlags_NoDecorations, ImPlotAxisFlags_NoDecorations | ImPlotAxisFlags_LockMin);\n\n        if (!plot_data.empty())\n        {\n            ImGui::PushClipRect({cursor_screen_pos_graph.x + frame_offset, cursor_screen_pos_graph.y + frame_offset}, \n                                {(cursor_screen_pos_graph.x + graph_size.x) - frame_offset, cursor_screen_pos_graph.y + graph_size.y - frame_offset},\n                                false);\n\n\n            ImPlot::SetNextFillStyle(ImVec4(0.5f, 1.0f, 0.0f, 1.0f));\n            ImPlot::PlotShaded(\"##DataShaded\", &plot_data[0].x, &plot_data[0].y, plot_data.size(), 0.0, ImPlotShadedFlags_None, m_FrameTimeGPUHistory.Offset, 2 * sizeof(float));\n\n            ImPlot::SetNextLineStyle(ImVec4(0.5f, 1.0f, 0.0f, 1.0f));\n            ImPlot::PlotLine(\"##DataLine\", &plot_data[0].x, &plot_data[0].y, plot_data.size(), ImPlotLineFlags_None, m_FrameTimeGPUHistory.Offset, 2 * sizeof(float));\n\n            ImPlot::SetNextFillStyle(ImVec4(1.0f, 0.0f, 0.0f, 1.0f));\n            ImPlot::SetNextLineStyle(ImVec4(0.0f, 0.0f, 0.0f, 0.0f));\n            ImPlot::PlotBars(\"##DataWarn\", &plot_data_warn[0].x, &plot_data_warn[0].y, plot_data_warn.size(), 1.0, m_FrameTimeGPUHistoryWarning.Offset, 2 * sizeof(float));\n\n            ImVec2 rmin = ImPlot::PlotToPixels(ImPlotPoint(plot_xmin, m_FrameTimeVsyncLimit));\n            ImVec2 rmax = ImPlot::PlotToPixels(ImPlotPoint(plot_xmax, m_FrameTimeVsyncLimit));\n            ImPlot::PushPlotClipRect();\n            ImPlot::GetPlotDrawList()->AddLine(rmin, rmax, ImGui::ColorConvertFloat4ToU32(ImGui::GetStyleColorVec4(ImGuiCol_Border)) ); \n            ImPlot::PopPlotClipRect();\n\n\n            ImGui::PopClipRect();\n        }\n\n        ImPlot::EndPlot();\n    }\n}\n\nvoid WindowPerformance::CheckScheduledOverlaySharedTextureUpdate()\n{\n    if (!UIManager::Get()->IsOpenVRLoaded())\n        return;\n\n    if (m_IsOverlaySharedTextureUpdateNeeded)\n    {\n        const DPRect& rect_total = UITextureSpaces::Get().GetRect(ui_texspace_total);\n\n        vr::HmdVector2_t mouse_scale = {(float)rect_total.GetWidth(), (float)rect_total.GetHeight()};\n\n        bool all_ok = true;\n\n        for (unsigned int i = 0; i < OverlayManager::Get().GetOverlayCount(); ++i)\n        {\n            const OverlayConfigData& data = OverlayManager::Get().GetConfigData(i);\n\n            if (data.ConfigInt[configid_int_overlay_capture_source] == ovrl_capsource_ui)\n            {\n                vr::VROverlayHandle_t ovrl_handle = data.ConfigHandle[configid_handle_overlay_state_overlay_handle];\n\n                if (ovrl_handle != vr::k_ulOverlayHandleInvalid)\n                {\n                    //Check if we're allowed to render to it from this process yet, otherwise try again next frame\n                    if (vr::VROverlay()->GetOverlayRenderingPid(ovrl_handle) == ::GetCurrentProcessId())\n                    {\n                        vr::VROverlayEx()->SetSharedOverlayTexture(UIManager::Get()->GetOverlayHandleOverlayBar(), ovrl_handle, UIManager::Get()->GetSharedTextureRef());\n                        vr::VROverlay()->SetOverlayMouseScale(ovrl_handle, &mouse_scale);\n                    }\n                    else\n                    {\n                        all_ok = false;\n                    }\n                }\n                else\n                {\n                    all_ok = false;\n                }\n            }\n        }\n\n        if (all_ok)\n        {\n            OnWindowBoundsChanged();\n            m_IsOverlaySharedTextureUpdateNeeded = false;\n        }\n    }\n}\n\nvoid WindowPerformance::OnWindowBoundsChanged()\n{\n    if (!UIManager::Get()->IsOpenVRLoaded())\n        return;\n\n    //Texture bounds\n    vr::VRTextureBounds_t bounds;\n    //Avoid resize flicker before a real size is known (ImGui defaults to 32) and just set bounds to 0 in that case\n    if (m_Size.x > 32.0f)\n    {\n        const DPRect& rect       = UITextureSpaces::Get().GetRect(ui_texspace_performance_monitor);\n        const DPRect& rect_total = UITextureSpaces::Get().GetRect(ui_texspace_total);\n        float tex_width  = (float)rect_total.GetWidth();\n        float tex_height = (float)rect_total.GetHeight();\n        bounds.uMin = rect.GetTL().x / tex_width;\n        bounds.vMin = rect.GetTL().y / tex_height;\n        bounds.uMax = rect.GetBR().x / tex_width;\n        bounds.vMax = rect.GetBR().y / tex_height;\n    }\n    else\n    {\n        bounds.uMin = 0.0f;\n        bounds.uMax = 0.0f;\n        bounds.vMin = 0.0f;\n        bounds.vMax = 0.0f;\n    }\n\n    //Interesection mask\n    vr::VROverlayIntersectionMaskPrimitive_t intersection_mask;\n    intersection_mask.m_nPrimitiveType = vr::OverlayIntersectionPrimitiveType_Rectangle;\n    intersection_mask.m_Primitive.m_Rectangle.m_flTopLeftX = m_Pos.x;\n    intersection_mask.m_Primitive.m_Rectangle.m_flTopLeftY = m_Pos.y;\n    intersection_mask.m_Primitive.m_Rectangle.m_flWidth    = m_Size.x;\n    intersection_mask.m_Primitive.m_Rectangle.m_flHeight   = m_Size.y;\n\n    for (unsigned int i = 0; i < OverlayManager::Get().GetOverlayCount(); ++i)\n    {\n        const OverlayConfigData& data = OverlayManager::Get().GetConfigData(i);\n\n        if (data.ConfigInt[configid_int_overlay_capture_source] == ovrl_capsource_ui)\n        {\n            vr::VROverlayHandle_t ovrl_handle = data.ConfigHandle[configid_handle_overlay_state_overlay_handle];\n\n            if (ovrl_handle != vr::k_ulOverlayHandleInvalid)\n            {\n                vr::VROverlay()->SetOverlayIntersectionMask(ovrl_handle, &intersection_mask, 1);\n                vr::VROverlay()->SetOverlayTextureBounds(ovrl_handle, &bounds);\n            }\n        }\n    }\n}\n\nvoid WindowPerformance::RefreshTrackerBatteryList()\n{\n    m_BatteryTrackers.clear();\n\n    for (vr::TrackedDeviceIndex_t i = 0; i < vr::k_unMaxTrackedDeviceCount; ++i)\n    {\n        if ( (vr::VRSystem()->GetTrackedDeviceClass(i) == vr::TrackedDeviceClass_GenericTracker) && (vr::VRSystem()->IsTrackedDeviceConnected(i)) )\n        {\n            TrackerInfo info;\n            info.DeviceIndex  = i;\n            info.BatteryLevel = vr::VRSystem()->GetFloatTrackedDeviceProperty(i, vr::Prop_DeviceBatteryPercentage_Float) * 100.0f;\n\n            info.Name = TranslationManager::GetString(tstr_PerformanceMonitorBatteryTracker);\n            StringReplaceAll(info.Name, \"%ID%\", std::to_string(m_BatteryTrackers.size() + 1));\n\n            info.NameCompact = TranslationManager::GetString(tstr_PerformanceMonitorCompactBatteryTracker);\n            StringReplaceAll(info.NameCompact, \"%ID%\", std::to_string(m_BatteryTrackers.size() + 1));\n\n            m_BatteryTrackers.push_back(info);\n        }\n    }\n}\n\nvoid WindowPerformance::ResetCumulativeValues()\n{\n    m_FPS         = 0;\n    m_FPS_Average = 0.0f;\n\n    m_FrameCountLast       = 0;\n    m_FrameCountTotal      = 0;\n    m_FrameCountTotalCount = 0;\n    m_FPS_TickLast         = 0;\n\n    m_ViveWirelessTickLast = 0;\n\n    //This is also called from the constructor when UIManager does not exist yet\n    if ((UIManager::Get() != nullptr) && (UIManager::Get()->IsOpenVRLoaded()))\n    {\n        m_FrameTimeVsyncLimit = 1000.f / vr::VRSystem()->GetFloatTrackedDeviceProperty(vr::k_unTrackedDeviceIndex_Hmd, vr::Prop_DisplayFrequency_Float);\n\n        //Update cumulative offset values\n        vr::Compositor_CumulativeStats frame_stats = {0};\n        vr::VRCompositor()->GetCumulativeStats(&frame_stats, sizeof(vr::Compositor_CumulativeStats));\n\n        m_OffsetFramesPresents              = frame_stats.m_nNumFramePresents;\n        m_OffsetReprojectedFrames           = frame_stats.m_nNumReprojectedFrames;\n        m_OffsetDroppedFrames               = frame_stats.m_nNumDroppedFrames;\n\n        //Update frame index offset\n        vr::Compositor_FrameTiming frame_timing_current;\n        frame_timing_current.m_nSize = sizeof(vr::Compositor_FrameTiming);\n        m_OffsetFrameIndex = 0;\n\n        if (vr::VRCompositor()->GetFrameTiming(&frame_timing_current, 0))\n        {\n            m_OffsetFrameIndex = frame_timing_current.m_nFrameIndex;\n        }\n    }\n}\n\nvoid WindowPerformance::ScheduleOverlaySharedTextureUpdate()\n{\n    //We only want to set the shared texture once when needed, but also have to wait for the render PID to change (which is async) and stay responsive\n    //so setting it is done by the performance window when possible\n\n    m_IsOverlaySharedTextureUpdateNeeded = true;\n}\n\nWin32PerformanceData& WindowPerformance::GetPerformanceData()\n{\n    return m_PerfData;\n}\n\nbool WindowPerformance::IsViveWirelessInstalled()\n{\n    return m_ViveWirelessLogPathExists;\n}\n\nconst ImVec2 & WindowPerformance::GetPos() const\n{\n    return m_Pos;\n}\n\nconst ImVec2 & WindowPerformance::GetSize() const\n{\n    return m_Size;\n}\n\nbool WindowPerformance::IsVisible() const\n{\n    return m_Visible;\n}\n\nvoid WindowPerformance::SetPopupOpen(bool is_open)\n{\n    m_IsPopupOpen = is_open;\n}\n\nbool WindowPerformance::IsAnyOverlayUsingPerformanceMonitor()\n{\n    if (!UIManager::Get()->IsOpenVRLoaded())\n        return false;\n\n    //This isn't the most efficient check thanks to the split responsibilities of each process, but it's still better than always rendering the window\n    for (unsigned int i = 0; i < OverlayManager::Get().GetOverlayCount(); ++i)\n    {\n        const OverlayConfigData& data = OverlayManager::Get().GetConfigData(i);\n\n        if ( (data.ConfigBool[configid_bool_overlay_enabled]) && (data.ConfigInt[configid_int_overlay_capture_source] == ovrl_capsource_ui) )\n        {\n            vr::VROverlayHandle_t ovrl_handle = data.ConfigHandle[configid_handle_overlay_state_overlay_handle];\n\n            if ( (ovrl_handle != vr::k_ulOverlayHandleInvalid) && (vr::VROverlay()->IsOverlayVisible(ovrl_handle)) )\n            {\n                return true;\n            }\n        }\n    }\n\n    return false;\n}\n"
  },
  {
    "path": "src/DesktopPlusUI/WindowPerformance.h",
    "content": "#pragma once\n\n#include <vector>\n\n#include \"imgui.h\"\n#include \"openvr.h\"\n\n#include \"Win32PerformanceData.h\"\n\n//Taken from ImPlot\nstruct ScrollingBufferFrameTime\n{\n    int MaxSize;\n    int Offset;\n    ImVector<ImVec2> Data;\n\n    ScrollingBufferFrameTime() \n    {\n        MaxSize = 150;\n        Offset  = 0;\n        Data.reserve(MaxSize);\n    }\n\n    void AddFrame(float frame_number, float ms) \n    {\n        if (Data.size() < MaxSize)\n        {\n            Data.push_back(ImVec2(frame_number, ms));\n        }\n        else \n        {\n            Data[Offset] = ImVec2(frame_number, ms);\n            Offset = (Offset + 1) % MaxSize;\n        }\n    }\n\n    void Erase() \n    {\n        if (Data.size() > 0) \n        {\n            Data.shrink(0);\n            Offset = 0;\n        }\n    }\n};\n\nstruct TrackerInfo\n{\n    vr::TrackedDeviceIndex_t DeviceIndex = vr::k_unTrackedDeviceIndexInvalid;\n    std::string Name;\n    std::string NameCompact;\n    float BatteryLevel;\n};\n\nclass WindowPerformance\n{\n    private:\n        ImVec2 m_Pos;\n        ImVec2 m_Size;\n        bool m_Visible;\n        ULONGLONG m_VisibleTickLast; //Valid when m_Visible is false\n        bool m_IsPopupOpen;\n\n        float m_MinimalItemLineWrapMaxLength;\n        float m_MinimalItemLineWrapPrevX;\n\n        Win32PerformanceData m_PerfData;\n\n        uint32_t m_PIDLast;\n\n        //Frame-Rate stats, updated once a second\n        int m_FPS;\n        float m_FPS_Average;\n\n        uint32_t m_FrameCountLast;\n        uint32_t m_FrameCountTotal;\n        uint32_t m_FrameCountTotalCount;\n        ULONGLONG m_FPS_TickLast;\n\n        //Offset values for cumulative counters\n        uint32_t m_OffsetFrameIndex;\n        uint32_t m_OffsetFramesPresents;\n        uint32_t m_OffsetReprojectedFrames;\n        uint32_t m_OffsetDroppedFrames;\n\n        //Updated every frame\n        float m_FrameTimeCPU;\n        ScrollingBufferFrameTime m_FrameTimeCPUHistory;\n        ScrollingBufferFrameTime m_FrameTimeCPUHistoryWarning;\n        float m_FrameTimeGPU;\n        ScrollingBufferFrameTime m_FrameTimeGPUHistory;\n        ScrollingBufferFrameTime m_FrameTimeGPUHistoryWarning;\n\n        float m_ReprojectionRatio;\n        uint32_t m_DroppedFrames;\n\n        float m_BatteryHMD;\n        float m_BatteryLeft;\n        float m_BatteryRight;\n        std::vector<TrackerInfo> m_BatteryTrackers; //List updated in RefreshTrackerBatteryList(), called on devices connect/disconnect\n\n        uint32_t m_FrameTimeLastIndex;\n        float m_FrameTimeVsyncLimit;\n\n        //Localized time string, updated once a minute\n        SYSTEMTIME m_TimeLast;\n        std::string m_TimeStr;\n\n        //Vive Wireless\n        int m_ViveWirelessTemp;\n        ULONGLONG m_ViveWirelessTickLast;\n        std::wstring m_ViveWirelessLogPath;\n        bool m_ViveWirelessLogPathExists;\n        std::wstring m_ViveWirelessLogFileLast;\n        int m_ViveWirelessLogFileLastLine;\n\n        //Overlay state\n        bool m_IsOverlaySharedTextureUpdateNeeded;\n\n        //Wrapped text functions to choose whether outline variant should be used or not\n        static void PerfMonText(const char* fmt, ...) IM_FMTARGS(2);\n        static void PerfMonTextUnformatted(const char* text, const char* text_end = nullptr);\n        static void PerfMonTextRight(float offset_x, float fixed_w, const char* fmt, ...) IM_FMTARGS(4);\n        static void PerfMonTextRightUnformatted(float offset_x, float fixed_w, const char* text, const char* text_end = nullptr);\n\n        //Called in Minimal style to do line wrapper on a per-item basis\n        void StatsMinimalItemLineWrap();\n\n        void DisplayStatsLarge();\n        void DisplayStatsCompact();\n        void DisplayStatsMinimal();\n\n        void UpdateStatValues();\n        void UpdateStatValuesSteamVR();\n        void UpdateStatValuesViveWireless();\n\n        void DrawFrameTimeGraphCPU(const ImVec2& graph_size, double plot_xmin, double plot_xmax, double plot_ymax);\n        void DrawFrameTimeGraphGPU(const ImVec2& graph_size, double plot_xmin, double plot_xmax, double plot_ymax);\n\n        void CheckScheduledOverlaySharedTextureUpdate();\n        void OnWindowBoundsChanged();\n\n    public:\n        WindowPerformance();\n\n        void Update(bool show_as_popup = false);\n        void UpdateVisibleState();\n        void RefreshTrackerBatteryList();\n        void ResetCumulativeValues();\n        void ScheduleOverlaySharedTextureUpdate();\n\n        Win32PerformanceData& GetPerformanceData();\n        bool IsViveWirelessInstalled();\n\n        const ImVec2& GetPos() const;\n        const ImVec2& GetSize() const;\n        bool IsVisible() const;\n        void SetPopupOpen(bool is_open);\n\n        static bool IsAnyOverlayUsingPerformanceMonitor();\n};"
  },
  {
    "path": "src/DesktopPlusUI/WindowSettings.cpp",
    "content": "#include \"WindowSettings.h\"\n\n#include <sstream>\n#include <unordered_set>\n#include <shlwapi.h>\n\n#include \"ImGuiExt.h\"\n#include \"UIManager.h\"\n#include \"TranslationManager.h\"\n#include \"WindowManager.h\"\n#include \"InterprocessMessaging.h\"\n#include \"Util.h\"\n#include \"DesktopPlusWinRT.h\"\n#include \"DPBrowserAPIClient.h\"\n\nWindowSettings::WindowSettings() :\n    m_PageStackPos(0),\n    m_PageStackPosAnimation(0),\n    m_PageAnimationDir(0),\n    m_PageAnimationProgress(0.0f),\n    m_PageAnimationStartPos(0.0f),\n    m_PageAnimationOffset(0.0f),\n    m_PageAppearing(wndsettings_page_none),\n    m_PageReturned(wndsettings_page_none),\n    m_PageCurrent(wndsettings_page_none),\n    m_Column0Width(0.0f),\n    m_WarningHeight(0.0f),\n    m_ProfileOverlaySelectIsSaving(false),\n    m_ActionSelectionUID(0),\n    m_ActionOrderListEditForOverlayBar(false),\n    m_ActionPickerUID(k_ActionUID_Invalid),\n    m_KeyCodePickerID(0),\n    m_KeyCodePickerHotkeyFlags(0),\n    m_KeyCodePickerNoMouse(false),\n    m_KeyCodePickerHotkeyMode(false),\n    m_WindowPickerHWND(nullptr)\n{\n    m_WindowTitleStrID = tstr_SettingsWindowTitle;\n    m_WindowIcon = tmtex_icon_xsmall_settings;\n    m_OvrlWidth    = OVERLAY_WIDTH_METERS_SETTINGS;\n    m_OvrlWidthMax = OVERLAY_WIDTH_METERS_SETTINGS * 3.0f;\n\n    //Leave 2 pixel padding around so interpolation doesn't cut off the pixel border\n    const DPRect rect = UITextureSpaces::Get().GetRect(ui_texspace_settings);\n    m_Size = {float(rect.GetWidth() - 4), float(rect.GetHeight() - 4)};\n    m_SizeUnscaled = m_Size;\n\n    m_Pos = {float(rect.GetTL().x + 2), float(rect.GetTL().y + 2)};\n\n    m_PageStack.push_back(wndsettings_page_main);\n\n    FloatingWindow::ResetTransformAll();\n}\n\nvoid WindowSettings::Hide(bool skip_fade)\n{\n    FloatingWindow::Hide();\n\n    ConfigManager::Get().SaveConfigToFile();\n}\n\nvoid WindowSettings::ResetTransform(FloatingWindowOverlayStateID state_id)\n{\n    FloatingWindow::ResetTransform(state_id);\n\n    FloatingWindowOverlayState& overlay_state = GetOverlayState(state_id);\n\n    overlay_state.Transform.rotateY(-15.0f);\n    overlay_state.Transform.translate_relative(OVERLAY_WIDTH_METERS_DASHBOARD_UI / 3.0f, 0.70f, 0.15f);\n}\n\nvr::VROverlayHandle_t WindowSettings::GetOverlayHandle() const\n{\n    return UIManager::Get()->GetOverlayHandleSettings();\n}\n\nvoid WindowSettings::ApplyUIScale()\n{\n    FloatingWindow::ApplyUIScale();\n\n    m_CachedSizes = {};\n}\n\nvoid WindowSettings::UpdateDesktopMode()\n{\n    WindowUpdate();\n}\n\nvoid WindowSettings::UpdateDesktopModeWarnings()\n{\n    UpdateWarnings();\n}\n\nvoid WindowSettings::DesktopModeSetRootPage(WindowSettingsPage root_page)\n{\n    m_PageStack[0] = root_page;\n    m_PageAppearing = root_page;\n}\n\nconst char* WindowSettings::DesktopModeGetTitle() const\n{\n    if (m_PageStack[0] == wndsettings_page_profiles)\n        return TranslationManager::GetString(tstr_SettingsProfilesOverlays);\n    else if (m_PageStack[0] == wndsettings_page_app_profiles)\n        return TranslationManager::GetString(tstr_SettingsProfilesApps);\n    else if (m_PageStack[0] == wndsettings_page_actions)\n        return TranslationManager::GetString(tstr_DesktopModeToolActions);\n    else\n        return TranslationManager::GetString(m_WindowTitleStrID);\n}\n\nbool WindowSettings::DesktopModeGetIconTextureInfo(ImVec2& size, ImVec2& uv_min, ImVec2& uv_max) const\n{\n    return TextureManager::Get().GetTextureInfo(m_WindowIcon, size, uv_min, uv_max);\n}\n\nbool WindowSettings::DesktopModeGoBack()\n{\n    if (m_PageStackPos != 0)\n    {\n        PageGoBack();\n        return true;\n    }\n\n    return false;\n}\n\nfloat WindowSettings::DesktopModeGetWarningHeight() const\n{\n    return m_WarningHeight;\n}\n\nvoid WindowSettings::QuickStartGuideGoToPage(WindowSettingsPage new_page)\n{\n    //This is only meant to be used by the Quick Start Guide window so it's not very flexible\n    const WindowSettingsPage current_top_page = m_PageStack[m_PageStackPos];\n\n    if (current_top_page == new_page)\n        return;\n\n    switch (new_page)\n    {\n        case wndsettings_page_main:\n        {\n            PageGoHome();\n            break;\n        }\n        case wndsettings_page_actions:\n        {\n            if (current_top_page == wndsettings_page_actions_edit)\n            {\n                PageGoBack();\n            }\n            else\n            {\n                PageGoForward(wndsettings_page_actions);\n            }\n            break;\n        }\n        case wndsettings_page_actions_edit:\n        {\n            if (current_top_page != wndsettings_page_actions)\n            {\n                PageGoForward(wndsettings_page_actions);\n            }\n\n            m_ActionSelectionUID = 0;\n            PageGoForward(wndsettings_page_actions_edit);\n            break;\n        }\n    }\n}\n\nvoid WindowSettings::ClearCachedTranslationStrings()\n{\n    m_WarningTextOverlayError.clear();\n    m_WarningTextWinRTError.clear();\n    m_WarningTextAppProfile.clear();\n    m_TranslationAuthorLabel.clear();\n    m_BrowserMaxFPSValueText.clear();\n    m_BrowserBlockListCountText.clear();\n    m_ActionButtonsDefaultLabel.clear();\n    m_ActionButtonsOverlayBarLabel.clear();\n    m_ActionGlobalShortcutLabels.clear();\n\n    for (ConfigHotkey& hotkey : ConfigManager::Get().GetHotkeys())\n    {\n        hotkey.StateUIName.clear();\n    }\n}\n\nvoid WindowSettings::WindowUpdate()\n{\n    ImGui::SetWindowSize(m_Size);\n\n    ImGuiStyle& style = ImGui::GetStyle();\n\n    m_Column0Width = ImGui::GetFontSize() * 12.75f;\n\n    float page_width = m_Size.x - style.WindowBorderSize - style.WindowPadding.x - style.WindowPadding.x;\n\n    //Compensate for the padding added in constructor and ignore border in desktop mode\n    if (UIManager::Get()->IsInDesktopMode())\n    {\n        page_width += 2 + style.WindowBorderSize;\n    }\n\n    //Page animation\n    if (m_PageAnimationDir != 0)\n    {\n        //Use the averaged framerate value instead of delta time for the first animation step\n        //This is to smooth over increased frame deltas that can happen when a new page needs to do initial larger computations or save/load files\n        const float progress_step = (m_PageAnimationProgress == 0.0f) ? (1.0f / ImGui::GetIO().Framerate) * 3.0f : ImGui::GetIO().DeltaTime * 3.0f;\n        m_PageAnimationProgress += progress_step;\n\n        if (m_PageAnimationProgress >= 1.0f)\n        {\n            //Remove pages in the stack after finishing going back\n            if (m_PageAnimationDir == 1)\n            {\n                while ((int)m_PageStack.size() > m_PageStackPosAnimation + 1)\n                {\n                    m_PageStack.pop_back();\n                }\n\n                m_PageAnimationDir = 0;\n\n                //Add pending pages now that we don't have an active animation\n                while (!m_PageStackPending.empty())\n                {\n                    PageGoForward(m_PageStackPending[0]);\n                    m_PageStackPending.erase(m_PageStackPending.begin());\n                }\n            }\n\n            m_PageAnimationProgress = 1.0f;\n            m_PageAnimationDir      = 0;\n        }\n    }\n    else if (m_PageStackPosAnimation != m_PageStackPos) //Only start new animation if none is running\n    {\n        m_PageAnimationDir      = (m_PageStackPosAnimation < m_PageStackPos) ? -1 : 1;\n        m_PageStackPosAnimation = m_PageStackPos;\n        m_PageAnimationStartPos = m_PageAnimationOffset;\n        m_PageAnimationProgress = 0.0f;\n\n        //Set appearing value to top of stack when starting animation to it\n        if (m_PageAnimationDir == -1)\n        {\n            m_PageAppearing = m_PageStack.back();\n        }\n    }\n    else if ((m_PageStackPosAnimation == m_PageStackPos) && ((int)m_PageStack.size() > m_PageStackPos + 1))\n    {\n        //Remove pages that were added and left again while there was no chance to animate anything\n        while ((int)m_PageStack.size() > m_PageStackPos + 1)\n        {\n            m_PageStack.pop_back();\n        }\n    }\n    \n    //Set appearing value when the whole window appeared again\n    if ((m_PageAnimationDir == 0) && (m_IsWindowAppearing))\n    {\n        m_PageAppearing = m_PageStack.back();\n    }\n\n    const float target_x = (page_width + style.ItemSpacing.x) * -m_PageStackPosAnimation;\n    m_PageAnimationOffset = smoothstep(m_PageAnimationProgress, m_PageAnimationStartPos, target_x);\n\n    if (!UIManager::Get()->IsInDesktopMode())\n    {\n        UpdateWarnings();\n    }\n\n    //Set up page offset and clipping\n    ImGui::SetCursorPosX( ImGui::GetCursorPosX() + m_PageAnimationOffset);\n\n    ImGui::PushClipRect({m_Pos.x + style.WindowBorderSize, 0.0f}, {m_Pos.x + m_Size.x - style.WindowBorderSize, FLT_MAX}, false);\n\n    const char* const child_str_id[] {\"SettingsPageMain\", \"SettingsPage1\", \"SettingsPage2\", \"SettingsPage3\"}; //No point in generating these on the fly\n    const ImVec2 child_size = {page_width, ImGui::GetContentRegionAvail().y};\n    int child_id = 0;\n    int stack_size = (int)m_PageStack.size();\n    for (WindowSettingsPage page_id : m_PageStack)\n    {\n        if (child_id >= IM_ARRAYSIZE(child_str_id))\n            break;\n\n        m_PageCurrent = page_id;\n\n        //Disable items when the page isn't active\n        const bool is_inactive_page = (child_id != m_PageStackPos);\n\n        if (is_inactive_page)\n        {\n            ImGui::PushItemDisabledNoVisual();\n        }\n\n        ImGui::PushStyleColor(ImGuiCol_ChildBg, ImVec4(0.00f, 0.00f, 0.00f, 0.00f)); //This prevents child bg color being visible if there's a widget before this (e.g. warnings)\n\n        if ( (ImGui::BeginChild(child_str_id[child_id], child_size, ImGuiChildFlags_NavFlattened)) || (m_PageAppearing == page_id) ) //Process page if currently appearing\n        {\n            ImGui::PopStyleColor(); //ImGuiCol_ChildBg\n\n            switch (page_id)\n            {\n                case wndsettings_page_main:                    UpdatePageMain();                    break;\n                case wndsettings_page_persistent_ui:           UpdatePagePersistentUI();            break;\n                case wndsettings_page_keyboard:                UpdatePageKeyboardLayout();          break;\n                case wndsettings_page_profiles:                UpdatePageProfiles();                break;\n                case wndsettings_page_profiles_overlay_select: UpdatePageProfilesOverlaySelect();   break;\n                case wndsettings_page_app_profiles:            UpdatePageAppProfiles();             break;\n                case wndsettings_page_actions:                 UpdatePageActions();                 break;\n                case wndsettings_page_actions_edit:            UpdatePageActionsEdit();             break;\n                case wndsettings_page_color_picker:            UpdatePageColorPicker();             break;\n                case wndsettings_page_profile_picker:          UpdatePageProfilePicker();           break;\n                case wndsettings_page_action_picker:           UpdatePageActionPicker();            break;\n                case wndsettings_page_actions_order_add:       UpdatePageActionsOrderAdd();         break;\n                case wndsettings_page_actions_order:           UpdatePageActionsOrder();            break;\n                case wndsettings_page_keycode_picker:          UpdatePageKeyCodePicker();           break;\n                case wndsettings_page_icon_picker:             UpdatePageIconPicker();              break;\n                case wndsettings_page_window_picker:           UpdatePageWindowPicker();            break;\n                case wndsettings_page_reset_confirm:           UpdatePageResetConfirm();            break;\n                default: break;\n            }\n        }\n        else\n        {\n            ImGui::PopStyleColor(); //ImGuiCol_ChildBg\n        }\n\n        if (is_inactive_page)\n        {\n            ImGui::PopItemDisabledNoVisual();\n        }\n\n        ImGui::EndChild();\n\n        if (child_id + 1 < stack_size)\n        {\n            ImGui::SameLine();\n        }\n\n        child_id++;\n    }\n\n    m_PageAppearing = wndsettings_page_none;\n    m_PageCurrent = wndsettings_page_none;\n\n    ImGui::PopClipRect();\n}\n\nvoid WindowSettings::UpdateWarnings()\n{\n    if (!UIManager::Get()->IsAnyWarningDisplayed())\n    {\n        m_WarningHeight = 0.0f;\n        return;\n    }\n\n    bool warning_displayed = false;\n    bool popup_visible = false;\n\n    static float popup_alpha = 0.0f;\n\n    const float warning_height_start = ImGui::GetCursorPosY();\n\n    //Compositor resolution warning\n    {\n        bool& hide_compositor_res_warning = ConfigManager::GetRef(configid_bool_interface_warning_compositor_res_hidden);\n\n        if ( (!hide_compositor_res_warning) && (UIManager::Get()->IsCompositorResolutionLow()) )\n        {\n            SelectableWarning(\"##WarningCompRes\", \"DontShowAgain\", TranslationManager::GetString(tstr_SettingsWarningCompositorResolution));\n\n            ImGui::PushStyleVar(ImGuiStyleVar_Alpha, popup_alpha);\n            if (ImGui::BeginPopup(\"DontShowAgain\", ImGuiWindowFlags_NoMove))\n            {\n                if (ImGui::Selectable(TranslationManager::GetString(tstr_SettingsWarningMenuDontShowAgain)))\n                {\n                    hide_compositor_res_warning = true;\n                }\n                ImGui::EndPopup();\n\n                popup_visible = true;\n            }\n            ImGui::PopStyleVar();\n\n            warning_displayed = true;\n        }\n    }\n\n    //Compositor quality warning\n    {\n        bool& hide_compositor_quality_warning = ConfigManager::GetRef(configid_bool_interface_warning_compositor_quality_hidden);\n\n        if ( (!hide_compositor_quality_warning) && (UIManager::Get()->IsCompositorRenderQualityLow()) )\n        {\n            SelectableWarning(\"##WarningCompQuality\", \"DontShowAgain2\", TranslationManager::GetString(tstr_SettingsWarningCompositorQuality));\n\n            ImGui::PushStyleVar(ImGuiStyleVar_Alpha, popup_alpha);\n            if (ImGui::BeginPopup(\"DontShowAgain2\", ImGuiWindowFlags_NoMove))\n            {\n                if (ImGui::Selectable(TranslationManager::GetString(tstr_SettingsWarningMenuDontShowAgain)))\n                {\n                    hide_compositor_quality_warning = true;\n                }\n                ImGui::EndPopup();\n\n                popup_visible = true;\n            }\n            ImGui::PopStyleVar();\n\n            warning_displayed = true;\n        }\n    }\n\n    //Dashboard app process elevation warning\n    {\n        bool& hide_process_elevation_warning = ConfigManager::GetRef(configid_bool_interface_warning_process_elevation_hidden);\n\n        if ( (!hide_process_elevation_warning) && (ConfigManager::GetValue(configid_bool_state_misc_process_elevated)) )\n        {\n            SelectableWarning(\"##WarningElevation\", \"DontShowAgain3\", TranslationManager::GetString(tstr_SettingsWarningProcessElevated));\n\n            ImGui::PushStyleVar(ImGuiStyleVar_Alpha, popup_alpha);\n            if (ImGui::BeginPopup(\"DontShowAgain3\", ImGuiWindowFlags_NoMove))\n            {\n                if (ImGui::Selectable(TranslationManager::GetString(tstr_SettingsWarningMenuDontShowAgain)))\n                {\n                    hide_process_elevation_warning = true;\n                }\n                ImGui::EndPopup();\n\n                popup_visible = true;\n            }\n            ImGui::PopStyleVar();\n\n            warning_displayed = true;\n        }\n    }\n\n    //Elevated mode warning (this is different from elevated dashboard process)\n    {\n        bool& hide_elevated_mode_warning = ConfigManager::GetRef(configid_bool_interface_warning_elevated_mode_hidden);\n\n        if ( (!hide_elevated_mode_warning) && (ConfigManager::GetValue(configid_bool_state_misc_elevated_mode_active)) )\n        {\n            SelectableWarning(\"##WarningElevatedMode\", \"DontShowAgain4\", TranslationManager::GetString(tstr_SettingsWarningElevatedMode));\n\n            ImGui::PushStyleVar(ImGuiStyleVar_Alpha, popup_alpha);\n            if (ImGui::BeginPopup(\"DontShowAgain4\", ImGuiWindowFlags_NoMove))\n            {\n                if (ImGui::Selectable(TranslationManager::GetString(tstr_SettingsWarningMenuDontShowAgain)))\n                {\n                    hide_elevated_mode_warning = true;\n                }\n                else if (ImGui::Selectable(TranslationManager::GetString(tstr_SettingsTroubleshootingElevatedModeLeave)))\n                {\n                    UIManager::Get()->ElevatedModeLeave();\n                }\n                ImGui::EndPopup();\n\n                popup_visible = true;\n            }\n            ImGui::PopStyleVar();\n\n            warning_displayed = true;\n        }\n    }\n\n    //Browser missing warning\n    {\n        bool& hide_browser_missing_warning = ConfigManager::GetRef(configid_bool_interface_warning_browser_missing_hidden);\n\n        if ( (!hide_browser_missing_warning) && (ConfigManager::GetValue(configid_bool_state_misc_browser_used_but_missing)) )\n        {\n            SelectableWarning(\"##WarningBrowserMissing\", \"DontShowAgain5\", TranslationManager::GetString(tstr_SettingsWarningBrowserMissing));\n\n            ImGui::PushStyleVar(ImGuiStyleVar_Alpha, popup_alpha);\n            if (ImGui::BeginPopup(\"DontShowAgain5\", ImGuiWindowFlags_NoMove))\n            {\n                if (ImGui::Selectable(TranslationManager::GetString(tstr_SettingsWarningMenuDontShowAgain)))\n                {\n                    hide_browser_missing_warning = true;\n                }\n                ImGui::EndPopup();\n\n                popup_visible = true;\n            }\n            ImGui::PopStyleVar();\n\n            warning_displayed = true;\n        }\n    }\n\n    //Browser mismatch warning\n    {\n        bool& hide_browser_version_mismatch_warning = ConfigManager::GetRef(configid_bool_interface_warning_browser_version_mismatch_hidden);\n\n        if ( (!hide_browser_version_mismatch_warning) && (ConfigManager::GetValue(configid_bool_state_misc_browser_version_mismatch)) )\n        {\n            SelectableWarning(\"##WarningBrowserMismatch\", \"DontShowAgain6\", TranslationManager::GetString(tstr_SettingsWarningBrowserMismatch));\n\n            ImGui::PushStyleVar(ImGuiStyleVar_Alpha, popup_alpha);\n            if (ImGui::BeginPopup(\"DontShowAgain6\", ImGuiWindowFlags_NoMove))\n            {\n                if (ImGui::Selectable(TranslationManager::GetString(tstr_SettingsWarningMenuDontShowAgain)))\n                {\n                    hide_browser_version_mismatch_warning = true;\n                }\n                ImGui::EndPopup();\n\n                popup_visible = true;\n            }\n            ImGui::PopStyleVar();\n\n            warning_displayed = true;\n        }\n    }\n\n    //Focused process elevation warning\n    {\n        if (  (ConfigManager::GetValue(configid_bool_state_window_focused_process_elevated)) && (!ConfigManager::GetValue(configid_bool_state_misc_process_elevated)) && \n             (!ConfigManager::GetValue(configid_bool_state_misc_elevated_mode_active))       && (!ConfigManager::GetValue(configid_bool_state_misc_uiaccess_enabled)) )\n        {\n            SelectableWarning(\"##WarningElevation2\", \"FocusedElevatedContext\", TranslationManager::GetString(tstr_SettingsWarningElevatedProcessFocus));\n\n            ImGui::PushStyleVar(ImGuiStyleVar_Alpha, popup_alpha);\n            if (ImGui::BeginPopup(\"FocusedElevatedContext\", ImGuiWindowFlags_NoMove))\n            {\n                if (ImGui::Selectable(TranslationManager::GetString(tstr_DefActionSwitchTask)))\n                {\n                    IPCManager::Get().PostMessageToDashboardApp(ipcmsg_action, ipcact_switch_task);\n                    UIManager::Get()->RepeatFrame();\n                }\n                else if ((UIManager::Get()->IsElevatedTaskSetUp()) && ImGui::Selectable(TranslationManager::GetString(tstr_SettingsTroubleshootingElevatedModeEnter)))\n                {\n                    UIManager::Get()->ElevatedModeEnter();\n                    UIManager::Get()->RepeatFrame();\n                }\n                ImGui::EndPopup();\n\n                popup_visible = true;\n            }\n            ImGui::PopStyleVar();\n\n            warning_displayed = true;\n        }\n    }\n\n    //UIAccess lost warning\n    {\n        if ( (ConfigManager::GetValue(configid_bool_misc_uiaccess_was_enabled)) && (!ConfigManager::GetValue(configid_bool_state_misc_uiaccess_enabled)) )\n        {\n            SelectableWarning(\"##WarningUIAccess\", \"DontShowAgain6\", TranslationManager::GetString(tstr_SettingsWarningUIAccessLost));\n\n            ImGui::PushStyleVar(ImGuiStyleVar_Alpha, popup_alpha);\n            if (ImGui::BeginPopup(\"DontShowAgain6\", ImGuiWindowFlags_NoMove))\n            {\n                if (ImGui::Selectable(TranslationManager::GetString(tstr_SettingsWarningMenuDontShowAgain)))\n                {\n                    ConfigManager::SetValue(configid_bool_misc_uiaccess_was_enabled, false);\n                }\n                ImGui::EndPopup();\n\n                popup_visible = true;\n            }\n            ImGui::PopStyleVar();\n\n            warning_displayed = true;\n        }\n    }\n\n    //Overlay error warning\n    {\n        vr::EVROverlayError overlay_error = UIManager::Get()->GetOverlayErrorLast();\n\n        if ( (overlay_error != vr::VROverlayError_None) && (UIManager::Get()->IsOpenVRLoaded()) )\n        {\n            if (overlay_error == vr::VROverlayError_OverlayLimitExceeded)\n            {\n                SelectableWarning(\"##WarningOverlayError\", \"DismissWarning\", TranslationManager::GetString(tstr_SettingsWarningOverlayCreationErrorLimit));\n            }\n            else\n            {\n                static vr::EVROverlayError overlay_error_last = overlay_error;\n\n                //Format error string into cached translated string\n                if ( (m_WarningTextOverlayError.empty()) || (overlay_error != overlay_error_last) )\n                {\n                    m_WarningTextWinRTError = TranslationManager::GetString(tstr_SettingsWarningGraphicsCaptureError);\n                    StringReplaceAll(m_WarningTextOverlayError, \"%ERRORNAME%\", vr::VROverlay()->GetOverlayErrorNameFromEnum(overlay_error));\n\n                    overlay_error_last = overlay_error;\n                }\n\n                SelectableWarning(\"##WarningOverlayError\", \"DismissWarning\", m_WarningTextOverlayError.c_str());\n            }\n\n            ImGui::PushStyleVar(ImGuiStyleVar_Alpha, popup_alpha);\n            if (ImGui::BeginPopup(\"DismissWarning\", ImGuiWindowFlags_NoMove))\n            {\n                if (ImGui::Selectable(TranslationManager::GetString(tstr_SettingsWarningMenuDismiss)))\n                {\n                    UIManager::Get()->ResetOverlayErrorLast();\n                }\n                ImGui::EndPopup();\n\n                popup_visible = true;\n            }\n            ImGui::PopStyleVar();\n\n            warning_displayed = true;\n        }\n    }\n\n    //WinRT Capture error warning\n    {\n        HRESULT hr_error = UIManager::Get()->GetWinRTErrorLast();\n\n        if ( (hr_error != S_OK) && (UIManager::Get()->IsOpenVRLoaded()) )\n        {\n            static HRESULT hr_error_last = hr_error;\n\n            //Format error code into cached translated string\n            if ( (m_WarningTextWinRTError.empty()) || (hr_error != hr_error_last) )\n            {\n                std::stringstream ss;\n                ss << \"0x\" << std::hex << hr_error;\n\n                m_WarningTextWinRTError = TranslationManager::GetString(tstr_SettingsWarningGraphicsCaptureError);\n                StringReplaceAll(m_WarningTextWinRTError, \"%ERRORCODE%\", ss.str());\n\n                hr_error_last = hr_error;\n            }\n\n            SelectableWarning(\"##WarningWinRTError\", \"DismissWarning2\", m_WarningTextWinRTError.c_str());\n\n            ImGui::PushStyleVar(ImGuiStyleVar_Alpha, popup_alpha);\n            if (ImGui::BeginPopup(\"DismissWarning2\", ImGuiWindowFlags_NoMove))\n            {\n                if (ImGui::Selectable(TranslationManager::GetString(tstr_SettingsWarningMenuDismiss)))\n                {\n                    UIManager::Get()->ResetWinRTErrorLast();\n                }\n                ImGui::EndPopup();\n\n                popup_visible = true;\n            }\n            ImGui::PopStyleVar();\n\n            warning_displayed = true;\n        }\n    }\n\n    //App profile with overlay profile active warning\n    {\n        bool& hide_app_profile_active_warning = ConfigManager::GetRef(configid_bool_interface_warning_app_profile_active_hidden);\n\n        if ( (!hide_app_profile_active_warning) && (ConfigManager::Get().GetAppProfileManager().IsActiveProfileWithOverlayProfile()) )\n        {\n            static std::string active_app_key_last;\n\n            //Format app name into cached translated string\n            if ( (m_WarningTextAppProfile.empty()) || (ConfigManager::Get().GetAppProfileManager().GetActiveProfileAppKey() != active_app_key_last) )\n            {\n                m_WarningTextAppProfile = TranslationManager::GetString(tstr_SettingsWarningAppProfileActive);\n                StringReplaceAll(m_WarningTextAppProfile, \"%APPNAME%\", ConfigManager::Get().GetAppProfileManager().GetActiveProfileAppName());\n\n                active_app_key_last = ConfigManager::Get().GetAppProfileManager().GetActiveProfileAppKey();\n            }\n\n            SelectableWarning(\"##WarningAppProfile\", \"DontShowAgain7\", m_WarningTextAppProfile.c_str()); \n\n            ImGui::PushStyleVar(ImGuiStyleVar_Alpha, popup_alpha);\n            if (ImGui::BeginPopup(\"DontShowAgain7\", ImGuiWindowFlags_NoMove))\n            {\n                if (ImGui::Selectable(TranslationManager::GetString(tstr_SettingsWarningMenuDontShowAgain)))\n                {\n                    hide_app_profile_active_warning = true;\n                }\n                ImGui::EndPopup();\n\n                popup_visible = true;\n            }\n            ImGui::PopStyleVar();\n\n            warning_displayed = true;\n        }\n    }\n\n    //Config migrated in current session \"warning\"\n    {\n        if (ConfigManager::GetValue(configid_bool_state_misc_config_migrated))\n        {\n            SelectableWarning(\"##WarningConfigMigrated\", \"DismissWarning3\", TranslationManager::GetString(tstr_SettingsWarningConfigMigrated), false, &Style_ImGuiCol_TextNotification);\n\n            ImGui::PushStyleVar(ImGuiStyleVar_Alpha, popup_alpha);\n            if (ImGui::BeginPopup(\"DismissWarning3\", ImGuiWindowFlags_NoMove))\n            {\n                if (ImGui::Selectable(TranslationManager::GetString(tstr_SettingsWarningMenuDismiss)))\n                {\n                    ConfigManager::SetValue(configid_bool_state_misc_config_migrated, false);\n                }\n                ImGui::EndPopup();\n\n                popup_visible = true;\n            }\n            ImGui::PopStyleVar();\n\n            warning_displayed = true;\n        }\n    }\n\n    //Separate from the main content if a warning was actually displayed\n    if (warning_displayed)\n    {\n        ImGui::Separator();\n    }\n    else //...no warning displayed but UIManager still thinks there is one, so update that state\n    {\n        UIManager::Get()->UpdateAnyWarningDisplayedState();\n    }\n\n    m_WarningHeight = ImGui::GetCursorPosY() - warning_height_start;\n\n    //Animate fade-in if any of the popups is visible\n    if (popup_visible)\n    {\n        popup_alpha += ImGui::GetIO().DeltaTime * 10.0f;\n\n        if (popup_alpha > 1.0f)\n            popup_alpha = 1.0f;\n    }\n    else\n    {\n        popup_alpha = 0.0f;\n    }\n}\n\n#include \"implot.h\"\n\nvoid WindowSettings::UpdatePageMain()\n{\n    ImGui::PushStyleColor(ImGuiCol_ChildBg, ImVec4(0.00f, 0.00f, 0.00f, 0.00f));\n    ImGui::BeginChild(\"SettingsMainContent\", ImVec2(0.00f, 0.00f), ImGuiChildFlags_NavFlattened);\n    ImGui::PopStyleColor();\n\n    //Page Content\n    UpdatePageMainCatInterface();\n    UpdatePageMainCatProfiles();\n    UpdatePageMainCatActions();\n    UpdatePageMainCatInput();\n    UpdatePageMainCatWindows();\n    UpdatePageMainCatBrowser();\n    UpdatePageMainCatPerformance();\n    UpdatePageMainCatMisc();\n\n    ImGui::EndChild();\n}\n\nvoid WindowSettings::UpdatePageMainCatInterface()\n{\n    const ImGuiStyle& style = ImGui::GetStyle();\n\n    //Interface\n    {\n        ImGui::TextColoredUnformatted(ImGui::GetStyleColorVec4(ImGuiCol_ButtonHovered), TranslationManager::GetString(tstr_SettingsCatInterface)); \n        ImGui::Columns(2, \"ColumnInterface\", false);\n        ImGui::SetColumnWidth(0, m_Column0Width);\n\n        ImGui::AlignTextToFramePadding();\n        ImGui::TextUnformatted( TranslationManager::GetString(tstr_SettingsInterfaceLanguage) );\n\n        if (!TranslationManager::Get().IsCurrentTranslationComplete())\n        {\n            ImGui::SameLine(0.0f, style.ItemInnerSpacing.x);\n            HelpMarker(TranslationManager::GetString(tstr_SettingsInterfaceLanguageIncompleteWarning), \"(!)\");\n        }\n\n        ImGui::NextColumn();\n\n        ImGui::PushItemWidth(-1);\n        if (ImGui::BeginComboAnimated(\"##ComboLang\", TranslationManager::Get().GetCurrentTranslationName().c_str() ))\n        {\n            static std::vector<TranslationManager::ListEntry> list_langs;\n            static int list_id = 0;\n\n            //Load language list when dropdown is used for the first time\n            if (ImGui::IsWindowAppearing())\n            {\n                if (list_langs.empty())\n                {\n                    list_id = 0;\n                    list_langs = TranslationManager::GetTranslationList();\n                }\n\n                //Select matching entry and add unmapped characters if needed\n                const std::string& current_filename = ConfigManager::GetValue(configid_str_interface_language_file);\n                for (auto it = list_langs.cbegin(); it != list_langs.cend(); ++it)\n                {\n                    if (current_filename == it->FileName)\n                    {\n                        list_id = (int)std::distance(list_langs.cbegin(), it);\n                    }\n\n                    UIManager::Get()->AddFontBuilderStringIfAnyUnmappedCharacters(it->ListName.c_str());\n                }\n            }\n\n            int i = 0;\n            for (const auto& item : list_langs)\n            {\n                if (ImGui::Selectable(item.ListName.c_str(), (list_id == i)))\n                {\n                    ConfigManager::SetValue(configid_str_interface_language_file, item.FileName);\n                    TranslationManager::Get().LoadTranslationFromFile(item.FileName);\n                    UIManager::Get()->OnTranslationChanged();\n\n                    list_id = i;\n                }\n\n                i++;\n            }\n\n            ImGui::EndCombo();\n        }\n\n        ImGui::NextColumn();\n        ImGui::NextColumn();\n\n        if (!TranslationManager::Get().GetCurrentTranslationAuthor().empty())\n        {\n            if (m_TranslationAuthorLabel.empty())\n            {\n                m_TranslationAuthorLabel = TranslationManager::GetString(tstr_SettingsInterfaceLanguageCommunity);\n                StringReplaceAll(m_TranslationAuthorLabel, \"%AUTHOR%\", TranslationManager::Get().GetCurrentTranslationAuthor());\n            }\n\n            ImGui::Indent(style.ItemInnerSpacing.x);    //Indent a bit since text lined up with the combo widget instead of the widget's text looks a bit odd\n            ImGui::PushTextWrapPos();\n            ImGui::TextUnformatted(m_TranslationAuthorLabel.c_str());\n            ImGui::PopTextWrapPos();\n            ImGui::Unindent(style.ItemInnerSpacing.x);\n        }\n\n        ImGui::Columns(1);\n\n        ImGui::Spacing();\n        ImGui::Indent();\n\n        if (ImGui::Checkbox(TranslationManager::GetString(tstr_SettingsInterfaceAdvancedSettings), &ConfigManager::GetRef(configid_bool_interface_show_advanced_settings)))\n        {\n            UIManager::Get()->RepeatFrame();\n        }\n        ImGui::SameLine(0.0f, style.ItemInnerSpacing.x);\n        HelpMarker(TranslationManager::GetString(tstr_SettingsInterfaceAdvancedSettingsTip));\n\n        if (ConfigManager::GetValue(configid_bool_interface_show_advanced_settings))\n        {\n            ImGui::Checkbox(TranslationManager::GetString(tstr_SettingsInterfaceBlankSpaceDrag), &ConfigManager::GetRef(configid_bool_interface_blank_space_drag_enabled));\n        }\n\n        ImGui::Unindent();\n\n        if (ConfigManager::GetValue(configid_bool_interface_show_advanced_settings))\n        {\n            ImGui::Spacing();\n            ImGui::Columns(2, \"ColumnInterface2\", false);\n            ImGui::SetColumnWidth(0, m_Column0Width);\n\n            ImGui::AlignTextToFramePadding();\n            ImGui::TextUnformatted(TranslationManager::GetString(tstr_SettingsInterfacePersistentUI));\n            ImGui::NextColumn();\n\n            ImGui::PushID(tstr_SettingsInterfacePersistentUI);  //Avoid ID conflict from common \"Manage\" label\n            if (ImGui::Button(TranslationManager::GetString(tstr_SettingsInterfacePersistentUIManage)))\n            {\n                PageGoForward(wndsettings_page_persistent_ui);\n            }\n            ImGui::PopID();\n\n            ImGui::Columns(1);\n        }\n\n        ImGui::Spacing();\n        ImGui::Columns(2, \"ColumnInterface3\", false);\n        ImGui::SetColumnWidth(0, m_Column0Width);\n        ImGui::AlignTextToFramePadding();\n        ImGui::Text(TranslationManager::GetString(tstr_SettingsInterfaceDesktopButtons));\n        ImGui::NextColumn();\n\n        ImGui::SetNextItemWidth(-1);\n        int button_style = clamp(ConfigManager::GetRef(configid_int_interface_desktop_listing_style), 0, (tstr_SettingsInterfaceDesktopButtonsCycle - tstr_SettingsInterfaceDesktopButtonsNone) - 1);\n        if (TranslatedComboAnimated(\"##ComboButtonStyle\", button_style, tstr_SettingsInterfaceDesktopButtonsNone, tstr_SettingsInterfaceDesktopButtonsCycle))\n        {\n            ConfigManager::SetValue(configid_int_interface_desktop_listing_style, button_style);\n            UIManager::Get()->RepeatFrame();\n        }\n\n        ImGui::NextColumn();\n        ImGui::NextColumn();\n\n        bool& include_all = ConfigManager::GetRef(configid_bool_interface_desktop_buttons_include_combined);\n        if (ImGui::Checkbox(TranslationManager::GetString(tstr_SettingsInterfaceDesktopButtonsAddCombined), &include_all))\n        {\n            UIManager::Get()->RepeatFrame();\n        }\n\n        ImGui::Columns(1);\n    }\n\n    //Environment (still Interface, but not really)\n    {\n        static ImVec4 background_color_vec4;\n\n        if ( (m_PageAppearing == wndsettings_page_main) || (m_PageReturned == wndsettings_page_color_picker) )\n        {\n            background_color_vec4 = ImGui::ColorConvertU32ToFloat4(*(ImU32*)&ConfigManager::Get().GetRef(configid_int_interface_background_color));\n            m_PageReturned = wndsettings_page_none;\n        }\n\n        ImGui::Spacing();\n\n        ImGui::TextColoredUnformatted(ImGui::GetStyleColorVec4(ImGuiCol_ButtonHovered), TranslationManager::GetString(tstr_SettingsCatEnvironment)); \n        ImGui::Columns(2, \"ColumnEnvironment\", false);\n        ImGui::SetColumnWidth(0, m_Column0Width);\n\n        ImGui::AlignTextToFramePadding();\n        ImGui::Text(TranslationManager::GetString(tstr_SettingsEnvironmentBackgroundColor));\n        ImGui::NextColumn();\n\n        if (ImGui::ColorButton(\"##BackgroundColor\", background_color_vec4, ImGuiColorEditFlags_NoTooltip | ImGuiColorEditFlags_NoDragDrop))\n        {\n            PageGoForward(wndsettings_page_color_picker);\n        }\n\n        ImGui::SameLine(0.0f, style.ItemInnerSpacing.x);\n\n        int& mode_display = ConfigManager::GetRef(configid_int_interface_background_color_display_mode);\n\n        ImGui::SetNextItemWidth(-1);\n        if (TranslatedComboAnimated(\"##ComboBackgroundDisplay\", mode_display, tstr_SettingsEnvironmentBackgroundColorDispModeNever, tstr_SettingsEnvironmentBackgroundColorDispModeAlways))\n        {\n            IPCManager::Get().PostConfigMessageToDashboardApp(configid_int_interface_background_color_display_mode, mode_display);\n        }\n\n        ImGui::NextColumn();\n\n        bool& dim_ui = ConfigManager::Get().GetRef(configid_bool_interface_dim_ui);\n        if (ImGui::Checkbox(TranslationManager::GetString(tstr_SettingsEnvironmentDimInterface), &dim_ui))\n        {\n            IPCManager::Get().PostConfigMessageToDashboardApp(configid_bool_interface_dim_ui, dim_ui);\n\n            if (UIManager::Get()->IsOpenVRLoaded())\n            {\n                UIManager::Get()->UpdateOverlayDimming();\n            }\n        }\n        ImGui::SameLine(0.0f, style.ItemInnerSpacing.x);\n        HelpMarker(TranslationManager::GetString(tstr_SettingsEnvironmentDimInterfaceTip));\n\n        ImGui::Columns(1);\n    }\n}\n\nvoid WindowSettings::UpdatePageMainCatProfiles()\n{\n    //Profiles\n    {\n        ImGui::Spacing();\n\n        ImGui::TextColoredUnformatted(ImGui::GetStyleColorVec4(ImGuiCol_ButtonHovered), TranslationManager::GetString(tstr_SettingsCatProfiles)); \n        ImGui::Columns(2, \"ColumnInterface\", false);\n        ImGui::SetColumnWidth(0, m_Column0Width);\n\n        ImGui::AlignTextToFramePadding();\n        ImGui::TextUnformatted(TranslationManager::GetString(tstr_SettingsProfilesOverlays));\n        ImGui::NextColumn();\n\n        ImGui::PushID(tstr_SettingsProfilesManage);  //Avoid ID conflict from common \"Manage\" label\n        if (ImGui::Button(TranslationManager::GetString(tstr_SettingsProfilesManage)))\n        {\n            PageGoForward(wndsettings_page_profiles);\n        }\n        ImGui::PopID();\n\n        ImGui::NextColumn();\n\n        ImGui::AlignTextToFramePadding();\n        ImGui::TextUnformatted(TranslationManager::GetString(tstr_SettingsProfilesApps));\n        ImGui::NextColumn();\n\n        ImGui::PushID(tstr_SettingsProfilesApps);  //Avoid ID conflict from common \"Manage\" label\n        if (ImGui::Button(TranslationManager::GetString(tstr_SettingsProfilesManage)))\n        {\n            PageGoForward(wndsettings_page_app_profiles);\n        }\n        ImGui::PopID();\n\n        ImGui::Columns(1);\n    }\n}\n\nvoid WindowSettings::UpdatePageMainCatActions()\n{\n    //Actions\n    {\n        const ActionManager& action_manager = ConfigManager::Get().GetActionManager();\n        ActionManager::ActionList& global_shortcut_list = ConfigManager::Get().GetGlobalShortcuts();\n        ConfigHotkeyList& hotkey_list = ConfigManager::Get().GetHotkeys();\n        const ImGuiStyle& style = ImGui::GetStyle();\n\n        static ConfigID_Handle action_picker_config_id = configid_handle_MAX;\n        static int action_picker_global_shortcut_id = -1;\n        static int action_picker_hotkey_id = -1;\n        static float button_binding_width = 0.0f;\n\n        if (m_PageReturned == wndsettings_page_actions_order)\n        {\n            m_ActionButtonsDefaultLabel.clear();\n            m_ActionButtonsOverlayBarLabel.clear();\n\n            m_PageReturned = wndsettings_page_none;\n        }\n        else if (m_PageReturned == wndsettings_page_action_picker)\n        {\n            if (action_picker_config_id != configid_handle_MAX)\n            {\n                ConfigManager::SetValue(action_picker_config_id, m_ActionPickerUID);\n                IPCManager::Get().PostConfigMessageToDashboardApp(action_picker_config_id, m_ActionPickerUID);\n                action_picker_config_id = configid_handle_MAX;\n\n                m_PageReturned = wndsettings_page_none;\n            }\n            else if (action_picker_global_shortcut_id != -1)\n            {\n                if ((action_picker_global_shortcut_id >= 0) && (action_picker_global_shortcut_id < (int)global_shortcut_list.size()))\n                {\n                    global_shortcut_list[action_picker_global_shortcut_id] = m_ActionPickerUID;\n\n                    IPCManager::Get().PostConfigMessageToDashboardApp(configid_handle_state_action_uid, m_ActionPickerUID);\n                    IPCManager::Get().PostMessageToDashboardApp(ipcmsg_action, ipcact_global_shortcut_set, action_picker_global_shortcut_id);\n                }\n\n                action_picker_global_shortcut_id = -1;\n                m_PageReturned = wndsettings_page_none;\n            }\n            else if (action_picker_hotkey_id != -1)\n            {\n                if ((action_picker_hotkey_id >= 0) && (action_picker_hotkey_id < (int)hotkey_list.size()))\n                {\n                    ConfigHotkey& hotkey = hotkey_list[action_picker_hotkey_id];\n\n                    hotkey.ActionUID = m_ActionPickerUID;\n\n                    IPCManager::Get().SendStringToDashboardApp(configid_str_state_hotkey_data, hotkey.Serialize(), UIManager::Get()->GetWindowHandle());\n                    IPCManager::Get().PostMessageToDashboardApp(ipcmsg_action, ipcact_hotkey_set, action_picker_hotkey_id);\n                }\n\n                action_picker_hotkey_id = -1;\n                m_PageReturned = wndsettings_page_none;\n            }\n        }\n\n        if (m_ActionButtonsDefaultLabel.empty())\n        {\n            const size_t action_count = ConfigManager::Get().GetActionManager().GetActionOrderListBarDefault().size();\n\n            m_ActionButtonsDefaultLabel = TranslationManager::GetString( (action_count == 1) ? tstr_SettingsActionsOrderButtonLabelSingular : tstr_SettingsActionsOrderButtonLabel );\n            StringReplaceAll(m_ActionButtonsDefaultLabel, \"%COUNT%\", std::to_string(action_count));\n        }\n\n        if (m_ActionButtonsOverlayBarLabel.empty())\n        {\n            const size_t action_count = ConfigManager::Get().GetActionManager().GetActionOrderListOverlayBar().size();\n\n            m_ActionButtonsOverlayBarLabel = TranslationManager::GetString( (action_count == 1) ? tstr_SettingsActionsOrderButtonLabelSingular : tstr_SettingsActionsOrderButtonLabel );\n            StringReplaceAll(m_ActionButtonsOverlayBarLabel, \"%COUNT%\", std::to_string(action_count));\n        }\n\n        if ((m_ActionGlobalShortcutLabels.empty()) || (m_ActionGlobalShortcutLabels.size() != global_shortcut_list.size()))\n        {\n            m_ActionGlobalShortcutLabels.clear();\n\n            for (int i = 0; i < global_shortcut_list.size(); ++i)\n            {\n                std::string label = TranslationManager::GetString(tstr_SettingsActionsGlobalShortcutsEntry);\n                StringReplaceAll(label, \"%ID%\", std::to_string(i + 1));\n                m_ActionGlobalShortcutLabels.push_back(label);\n            }\n        }\n\n        ImGui::Spacing();\n\n        ImGui::TextColored(ImGui::GetStyleColorVec4(ImGuiCol_ButtonHovered), TranslationManager::GetString(tstr_SettingsCatActions));\n        ImGui::Columns(2, \"ColumnActions\", false);\n        ImGui::SetColumnWidth(0, m_Column0Width);\n\n        ImGui::AlignTextToFramePadding();\n        ImGui::TextUnformatted(TranslationManager::GetString(tstr_SettingsActionsManage));\n        ImGui::NextColumn();\n\n        ImGui::PushID(tstr_SettingsActionsManage);  //Avoid ID conflict from common \"Manage\" label\n        if (ImGui::Button(TranslationManager::GetString(tstr_SettingsActionsManageButton)))\n        {\n            PageGoForward(wndsettings_page_actions);\n        }\n        ImGui::PopID();\n\n        ImGui::NextColumn();\n\n        ImGui::AlignTextToFramePadding();\n        ImGui::TextUnformatted(TranslationManager::GetString(tstr_SettingsActionsButtonsOrderDefault));\n        ImGui::NextColumn();\n\n        ImGui::PushID(tstr_SettingsActionsButtonsOrderDefault);\n        if (ImGui::Button(m_ActionButtonsDefaultLabel.c_str()))\n        {\n            m_ActionOrderListEditForOverlayBar = false;\n            PageGoForward(wndsettings_page_actions_order);\n        }\n        ImGui::PopID();\n\n        ImGui::NextColumn();\n\n        if (ConfigManager::GetValue(configid_bool_interface_show_advanced_settings))\n        {\n            ImGui::AlignTextToFramePadding();\n            ImGui::TextUnformatted(TranslationManager::GetString(tstr_SettingsActionsButtonsOrderOverlayBar));\n            ImGui::NextColumn();\n\n            ImGui::PushID(tstr_SettingsActionsButtonsOrderOverlayBar);\n            if (ImGui::Button(m_ActionButtonsOverlayBarLabel.c_str()))\n            {\n                m_ActionOrderListEditForOverlayBar = true;\n                PageGoForward(wndsettings_page_actions_order);\n            }\n            ImGui::PopID();\n\n            ImGui::NextColumn();\n        }\n        ImGui::Spacing();\n\n        ImGui::Columns(1);\n\n        //Active Shortcuts\n        if (UIManager::Get()->IsOpenVRLoaded())\n            ImGui::Spacing();\n\n        ImGui::Indent();\n        ImGui::TextUnformatted(TranslationManager::GetString(tstr_SettingsActionsActiveShortcuts));\n        ImGui::SameLine(0.0f, style.ItemInnerSpacing.x);\n        HelpMarker(TranslationManager::GetString(tstr_SettingsActionsActiveShortcutsTip));\n\n        ImGui::PushID(\"ActiveButtons\");\n\n        if (UIManager::Get()->IsOpenVRLoaded())\n        {\n            ImGui::SameLine(ImGui::GetContentRegionAvail().x - button_binding_width - 1.0f);\n            if (ImGui::SmallButton(TranslationManager::GetString(tstr_SettingsActionsShowBindings)))\n            {\n                //OpenBindingUI does not use that app key argument it takes, it always opens the bindings of the calling application\n                //To work around this, we pretend to be the app we want to open the bindings for during the call\n                //Works and seems to not break anything\n                vr::VRApplications()->IdentifyApplication(::GetCurrentProcessId(), \"openvr.component.vrcompositor\");\n                vr::VRInput()->OpenBindingUI(\"openvr.component.vrcompositor\", vr::k_ulInvalidActionSetHandle, vr::k_ulInvalidInputValueHandle, UIManager::Get()->IsInDesktopMode());\n                vr::VRApplications()->IdentifyApplication(::GetCurrentProcessId(), g_AppKeyUIApp);\n            }\n            button_binding_width = ImGui::GetItemRectSize().x;\n\n            //For reasons unknown, if there's no item added in this spot and the Show Bindings button exist, ImGuiContext::HoveredIdDisabled will be true for the table rows (needs to be false for haptics)\n            //This is despite item disabled last being set in CompactTableHeadersRow(), so I'm not sure how this transfers over, but this works, eh\n            ImGui::SameLine();\n            ImGui::Dummy({0.0f, 0.0f});\n        }\n\n        ImGui::Indent();\n\n        const ImVec2 table_size(-style.IndentSpacing - 1.0f, 0.0f);                                     //Replicate padding from columns\n        const float table_column_width = m_Column0Width - style.IndentSpacing - style.IndentSpacing;    //Align with width of other columns\n        const float table_cell_height = ImGui::GetFontSize() + style.ItemInnerSpacing.y;\n\n        if (BeginCompactTable(\"TableActiveButtons\", 2, ImGuiTableFlags_RowBg | ImGuiTableFlags_SizingFixedFit, table_size))\n        {\n            ImGui::TableSetupColumn(TranslationManager::GetString(tstr_SettingsActionsTableHeaderShortcut), 0, table_column_width);\n            ImGui::TableSetupColumn(TranslationManager::GetString(tstr_SettingsActionsTableHeaderAction),   ImGuiTableColumnFlags_WidthStretch);\n            CompactTableHeadersRow();\n\n            for (int i = 0; i < 2; ++i)\n            {\n                ConfigID_Handle config_id = (i == 0) ? configid_handle_input_go_home_action_uid : configid_handle_input_go_back_action_uid;\n                ActionUID uid = ConfigManager::GetValue(config_id);\n\n                ImGui::PushID(i);\n\n                ImGui::TableNextColumn();\n\n                ImGui::AlignTextToFramePadding();\n                ImGui::TextUnformatted(TranslationManager::GetString((i == 0) ? tstr_SettingsActionsActiveShortuctsHome : tstr_SettingsActionsActiveShortuctsBack));\n                ImGui::SameLine();\n\n                ImGui::TableNextColumn();\n\n                if (ImGui::Selectable(action_manager.GetTranslatedName(uid)))\n                {\n                    m_ActionPickerUID = uid;\n                    action_picker_config_id = config_id;\n                    PageGoForward(wndsettings_page_action_picker);\n                }\n\n                ImGui::PopID();\n            }\n\n            EndCompactTable();\n        }\n\n        ImGui::PopID();\n\n        ImGui::Unindent();\n\n        //Global Shortcuts\n        ImGui::Spacing();\n        ImGui::TextUnformatted(TranslationManager::GetString(tstr_SettingsActionsGlobalShortcuts));\n        ImGui::SameLine(0.0f, style.ItemInnerSpacing.x);\n        HelpMarker(TranslationManager::GetString(tstr_SettingsActionsGlobalShortcutsTip));\n\n        ImGui::PushID(\"GlobalShortcuts\");\n\n        if (UIManager::Get()->IsOpenVRLoaded())\n        {\n            ImGui::SameLine(ImGui::GetContentRegionAvail().x - button_binding_width - 1.0f);\n            if (ImGui::SmallButton(TranslationManager::GetString(tstr_SettingsActionsShowBindings)))\n            {\n                //See comment on the active shortcuts\n                vr::VRApplications()->IdentifyApplication(::GetCurrentProcessId(), g_AppKeyDashboardApp);\n                vr::VRInput()->OpenBindingUI(g_AppKeyDashboardApp, vr::k_ulInvalidActionSetHandle, vr::k_ulInvalidInputValueHandle, UIManager::Get()->IsInDesktopMode());\n                vr::VRApplications()->IdentifyApplication(::GetCurrentProcessId(), g_AppKeyUIApp);\n            }\n\n            //See Active Shortcuts comment\n            ImGui::SameLine();\n            ImGui::Dummy({0.0f, 0.0f});\n        }\n\n        ImGui::Indent();\n\n        static float table_global_shortcuts_buttons_width = 0.0f;\n\n        const int shortcuts_visible = (int)global_shortcut_list.size();\n        const int shortcuts_max     = ConfigManager::GetValue(configid_int_input_global_shortcuts_max_count);\n\n        if (BeginCompactTable(\"TableGlobalShortcuts\", 2, ImGuiTableFlags_RowBg | ImGuiTableFlags_SizingFixedFit, table_size))\n        {\n            ImGui::TableSetupColumn(TranslationManager::GetString(tstr_SettingsActionsTableHeaderShortcut), 0, table_column_width);\n            ImGui::TableSetupColumn(TranslationManager::GetString(tstr_SettingsActionsTableHeaderAction),   ImGuiTableColumnFlags_WidthStretch);\n            CompactTableHeadersRow();\n\n            IM_ASSERT(m_ActionGlobalShortcutLabels.size() == global_shortcut_list.size());\n\n            int shortcut_id = 0;\n            for (ActionUID uid : global_shortcut_list)\n            {\n                ImGui::PushID(shortcut_id);\n\n                ImGui::TableNextColumn();\n\n                ImGui::AlignTextToFramePadding();\n                ImGui::TextUnformatted(m_ActionGlobalShortcutLabels[shortcut_id].c_str());\n                ImGui::SameLine();\n\n                ImGui::TableNextColumn();\n                if (ImGui::Selectable(action_manager.GetTranslatedName(uid)))\n                {\n                    m_ActionPickerUID = uid;\n                    action_picker_global_shortcut_id = shortcut_id;\n                    PageGoForward(wndsettings_page_action_picker);\n                }\n\n                ImGui::PopID();\n\n                ++shortcut_id;\n            }\n\n            EndCompactTable();\n        }\n\n        ImGui::SetCursorPosX(ImGui::GetCursorPosX() + ImGui::GetContentRegionAvail().x - table_global_shortcuts_buttons_width - 1.0f);\n\n        ImGui::BeginGroup();\n\n        if (shortcuts_visible >= shortcuts_max)\n            ImGui::PushItemDisabled();\n\n        if (ImGui::Button(TranslationManager::GetString(tstr_SettingsActionsGlobalShortcutsAdd)))\n        {\n            global_shortcut_list.push_back(k_ActionUID_Invalid);\n\n            ImGui::SetScrollY(ImGui::GetScrollY() + table_cell_height);\n        }\n\n        if (shortcuts_visible >= shortcuts_max)\n            ImGui::PopItemDisabled();\n\n        ImGui::SameLine(0.0f, style.ItemInnerSpacing.x);\n\n        if (shortcuts_visible <= 1)\n            ImGui::PushItemDisabled();\n\n        if (ImGui::Button(TranslationManager::GetString(tstr_SettingsActionsGlobalShortcutsRemove)))\n        {\n            global_shortcut_list.pop_back();\n\n            ImGui::SetScrollY(ImGui::GetScrollY() - table_cell_height);\n        }\n\n        if (shortcuts_visible <= 1)\n            ImGui::PopItemDisabled();\n\n        ImGui::EndGroup();\n\n        table_global_shortcuts_buttons_width = ImGui::GetItemRectSize().x + style.IndentSpacing;\n\n        ImGui::PopID();\n\n        ImGui::Unindent();\n\n        //Hotkeys\n        static float table_hotkeys_max_column_width = 0.0f;\n        static float table_hotkeys_remove_button_width = 0.0f;\n        static float table_hotkeys_buttons_width = 0.0f;\n        static int table_hotkeys_hovered_row = -1;\n\n        ImGui::TextUnformatted(TranslationManager::GetString(tstr_SettingsActionsHotkeys));\n        ImGui::SameLine(0.0f, style.ItemInnerSpacing.x);\n        HelpMarker(TranslationManager::GetString(tstr_SettingsActionsHotkeysTip));\n\n        ImGui::Indent();\n\n        ImGui::PushID(\"Hotkeys\");\n\n        if (BeginCompactTable(\"TableHotkeys\", 2, ImGuiTableFlags_RowBg | ImGuiTableFlags_SizingFixedFit, table_size))\n        {\n            ImGui::TableSetupColumn(TranslationManager::GetString(tstr_SettingsActionsTableHeaderHotkey), 0, std::max(table_column_width, table_hotkeys_max_column_width));\n            ImGui::TableSetupColumn(TranslationManager::GetString(tstr_SettingsActionsTableHeaderAction), ImGuiTableColumnFlags_WidthStretch);\n            CompactTableHeadersRow();\n\n            ImGui::TableNextColumn();\n\n            float line_start_x = ImGui::GetCursorPosX();\n            table_hotkeys_max_column_width = 0.0f;\n            int hovered_row_new = -1;\n\n            int hotkey_id = 0;\n            for (ConfigHotkey& hotkey : hotkey_list)\n            {\n                ImGui::PushID(hotkey_id);\n\n                ImGui::AlignTextToFramePadding();\n                SelectableHotkey(hotkey, hotkey_id);\n                ImGui::SameLine(0.0f, 0.0f);\n                table_hotkeys_max_column_width = std::max(table_hotkeys_max_column_width, ImGui::GetCursorPosX() - line_start_x);\n\n                ImGui::TableNextColumn();\n\n                if (table_hotkeys_hovered_row == hotkey_id)\n                {\n                    //Use lower-alpha version of hovered header color while the remove button is hovered to increase the contrast with the button hover color (we don't really want to change it, however)\n                    ImVec4 col = ImGui::GetStyleColorVec4(ImGuiCol_HeaderHovered);\n                    col.w *= 0.75f;\n                    ImGui::PushStyleColor(ImGuiCol_Header, col);\n                }\n\n                if (ImGui::Selectable(action_manager.GetTranslatedName(hotkey.ActionUID), (table_hotkeys_hovered_row == hotkey_id), ImGuiSelectableFlags_AllowOverlap))\n                {\n                    m_ActionPickerUID = hotkey.ActionUID;\n                    action_picker_hotkey_id = hotkey_id;\n                    PageGoForward(wndsettings_page_action_picker);\n                }\n\n                if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenOverlapped | ImGuiHoveredFlags_AllowWhenBlockedByActiveItem))\n                {\n                    hovered_row_new = hotkey_id;\n                }\n\n                if (table_hotkeys_hovered_row == hotkey_id)\n                {\n                    ImGui::PopStyleColor();\n\n                    //Show remove button only if there's more than one hotkey or the single existing hotkey has properties set (basically no button that doesn't do anything)\n                    if ((hotkey_list.size() > 1) || (hotkey.KeyCode != 0) || (hotkey.ActionUID != k_ActionUID_Invalid))\n                    {\n                        ImGui::SetNextItemAllowOverlap();\n                        ImGui::SameLine();\n                        ImGui::SetCursorPosX(ImGui::GetCursorPosX() + ImGui::GetContentRegionAvail().x - table_hotkeys_remove_button_width);\n\n                        if (ImGui::SmallButton(TranslationManager::GetString(tstr_SettingsActionsHotkeysRemove)))\n                        {\n                            if (hotkey_list.size() > 1)\n                            {\n                                hotkey_list.erase(hotkey_list.begin() + hotkey_id);\n                                IPCManager::Get().SendStringToDashboardApp(configid_str_state_hotkey_data, \"\", UIManager::Get()->GetWindowHandle());\n                            }\n                            else //If there's only one entry, clear it instead to not have a weird looking table\n                            {\n                                hotkey = ConfigHotkey();\n                                IPCManager::Get().SendStringToDashboardApp(configid_str_state_hotkey_data, hotkey.Serialize(), UIManager::Get()->GetWindowHandle());\n                            }\n\n                            IPCManager::Get().PostMessageToDashboardApp(ipcmsg_action, ipcact_hotkey_set, hotkey_id);\n\n                            //Keep scroll position constant\n                            ImGui::SetScrollY(ImGui::GetScrollY() - table_cell_height);\n\n                            //Erased something straight out of the list, get out of the loop and discard frame\n                            UIManager::Get()->RepeatFrame();\n                            ImGui::PopID();\n                            break;\n                        }\n\n                        if (ImGui::IsItemHovered())\n                        {\n                            hovered_row_new = hotkey_id;\n                        }\n\n                        table_hotkeys_remove_button_width = ImGui::GetItemRectSize().x;\n                    }\n                }\n\n                ImGui::TableNextColumn();\n\n                ImGui::PopID();\n\n                ++hotkey_id;\n            }\n\n            table_hotkeys_hovered_row = hovered_row_new;\n\n            EndCompactTable();\n        }\n\n        ImGui::SetCursorPosX(ImGui::GetCursorPosX() + ImGui::GetContentRegionAvail().x - table_hotkeys_buttons_width - 1.0f);\n\n        if (ImGui::Button(TranslationManager::GetString(tstr_SettingsActionsHotkeysAdd)))\n        {\n            ConfigHotkey hotkey_new;\n            hotkey_list.push_back(hotkey_new);\n\n            IPCManager::Get().SendStringToDashboardApp(configid_str_state_hotkey_data, hotkey_new.Serialize(), UIManager::Get()->GetWindowHandle());\n            IPCManager::Get().PostMessageToDashboardApp(ipcmsg_action, ipcact_hotkey_set, hotkey_list.size() - 1);\n\n            ImGui::SetScrollY(ImGui::GetScrollY() + table_cell_height);\n\n            UIManager::Get()->RepeatFrame(3);   //Avoid some flicker from potentially hovering selectable + remove button appearing in the next few frames\n        }\n\n        table_hotkeys_buttons_width = ImGui::GetItemRectSize().x + style.IndentSpacing;\n\n        ImGui::PopID();\n\n        ImGui::Unindent();\n        ImGui::Unindent();\n\n        ImGui::Columns(1);\n    }\n}\n\nvoid WindowSettings::UpdatePageMainCatInput()\n{\n    static bool is_any_gc_overlay_active = false;\n\n    if (m_PageAppearing == wndsettings_page_main)\n    {\n        //Check if any Graphics Capture overlays are active\n        is_any_gc_overlay_active = false;\n        for (unsigned int i = 0; i < OverlayManager::Get().GetOverlayCount(); ++i)\n        {\n            if (OverlayManager::Get().GetConfigData(i).ConfigInt[configid_int_overlay_capture_source] == ovrl_capsource_winrt_capture)\n            {\n                is_any_gc_overlay_active = true;\n                break;\n            }\n        }\n    }\n\n    VRKeyboard& vr_keyboard = UIManager::Get()->GetVRKeyboard();\n    const ImGuiStyle& style = ImGui::GetStyle();\n\n    //Keyboard\n    {\n        ImGui::Spacing();\n\n        ImGui::TextColoredUnformatted(ImGui::GetStyleColorVec4(ImGuiCol_ButtonHovered), TranslationManager::GetString(tstr_SettingsCatKeyboard)); \n        ImGui::Columns(2, \"ColumnKeyboard\", false);\n        ImGui::SetColumnWidth(0, m_Column0Width);\n\n        ImGui::AlignTextToFramePadding();\n        ImGui::TextUnformatted(TranslationManager::GetString(tstr_SettingsKeyboardLayout));\n        ImGui::NextColumn();\n\n        if (ImGui::Button( UIManager::Get()->GetVRKeyboard().GetLayoutMetadata().Name.c_str() ))\n        {\n            PageGoForward(wndsettings_page_keyboard);\n        }\n\n        ImGui::NextColumn();\n\n        ImGui::AlignTextToFramePadding();\n        ImGui::TextUnformatted(TranslationManager::GetString(tstr_SettingsKeyboardSize));\n        ImGui::NextColumn();\n\n        //Keyboard size setting shows size of currently visible overlay state (usually dashboard tab) and applies it to all\n        WindowKeyboard& window_keyboard = UIManager::Get()->GetVRKeyboard().GetWindow();\n        float& size = window_keyboard.GetOverlayState(window_keyboard.GetOverlayStateCurrentID()).Size;\n\n        vr_keyboard.VRKeyboardInputBegin( ImGui::SliderWithButtonsGetSliderID(\"KeyboardSize\") );\n        if (ImGui::SliderWithButtonsFloatPercentage(\"KeyboardSize\", size, 5, 1, 50, 200, \"%d%%\"))\n        {\n            if (size < 0.10f)\n                size = 0.10f;\n\n            window_keyboard.GetOverlayState(floating_window_ovrl_state_room).Size          = size;\n            window_keyboard.GetOverlayState(floating_window_ovrl_state_dashboard_tab).Size = size;\n\n            UIManager::Get()->GetVRKeyboard().GetWindow().ApplyCurrentOverlayState();\n        }\n        vr_keyboard.VRKeyboardInputEnd();\n\n        ImGui::NextColumn();\n\n        ImGui::AlignTextToFramePadding();\n        ImGui::TextUnformatted(TranslationManager::GetString(tstr_SettingsKeyboardBehavior));\n        ImGui::NextColumn();\n\n        ImGui::Checkbox(TranslationManager::GetString(tstr_SettingsKeyboardStickyMod), &ConfigManager::GetRef(configid_bool_input_keyboard_sticky_modifiers));\n\n        ImGui::NextColumn();\n        ImGui::NextColumn();\n\n        ImGui::Checkbox(TranslationManager::GetString(tstr_SettingsKeyboardKeyRepeat), &ConfigManager::GetRef(configid_bool_input_keyboard_key_repeat));\n\n        bool& auto_show_desktop = ConfigManager::Get().GetRef(configid_bool_input_keyboard_auto_show_desktop);\n        bool& auto_show_browser = ConfigManager::Get().GetRef(configid_bool_input_keyboard_auto_show_browser);\n\n        //Arrange the checkboxes in their own group if browser is available, otherwise just add a single one for desktop/window under the behavior group\n        if (DPBrowserAPIClient::Get().IsBrowserAvailable())\n        {\n            ImGui::Spacing();\n            ImGui::NextColumn();\n            ImGui::AlignTextToFramePadding();\n            ImGui::TextUnformatted(TranslationManager::GetString(tstr_SettingsKeyboardAutoShow));\n            ImGui::NextColumn();\n\n            if (ImGui::Checkbox(TranslationManager::GetString(tstr_SettingsKeyboardAutoShowDesktop), &auto_show_desktop))\n            {\n                IPCManager::Get().PostConfigMessageToDashboardApp(configid_bool_input_keyboard_auto_show_desktop, auto_show_desktop);\n            }\n            ImGui::SameLine(0.0f, style.ItemInnerSpacing.x);\n            HelpMarker(TranslationManager::GetString(tstr_SettingsKeyboardAutoShowDesktopTip));\n\n            ImGui::NextColumn();\n            ImGui::NextColumn();\n\n            if (ImGui::Checkbox(TranslationManager::GetString(tstr_SettingsKeyboardAutoShowBrowser), &auto_show_browser))\n            {\n                if (!auto_show_browser)\n                {\n                    //Hide currently auto-visible keyboard if it's shown for a browser overlay\n                    int overlay_id = vr_keyboard.GetWindow().GetAssignedOverlayID();\n\n                    if ( (overlay_id >= 0) && (OverlayManager::Get().GetConfigData((unsigned int)overlay_id).ConfigInt[configid_int_overlay_capture_source] == ovrl_capsource_browser) )\n                    {\n                        vr_keyboard.GetWindow().SetAutoVisibility(overlay_id, false);\n                    }\n                }\n            }\n        }\n        else\n        {\n            if (ImGui::Checkbox(TranslationManager::GetString(tstr_SettingsKeyboardAutoShowDesktopOnly), &auto_show_desktop))\n            {\n                IPCManager::Get().PostConfigMessageToDashboardApp(configid_bool_input_keyboard_auto_show_desktop, auto_show_desktop);\n            }\n            ImGui::SameLine(0.0f, style.ItemInnerSpacing.x);\n            HelpMarker(TranslationManager::GetString(tstr_SettingsKeyboardAutoShowDesktopTip));\n        }\n\n        ImGui::Columns(1);\n    }\n\n    //Mouse\n    {\n        ImGui::Spacing();\n\n        ImGui::TextColoredUnformatted(ImGui::GetStyleColorVec4(ImGuiCol_ButtonHovered), TranslationManager::GetString(tstr_SettingsCatMouse)); \n\n        ImGui::Indent();\n\n        bool& render_cursor = ConfigManager::Get().GetRef(configid_bool_input_mouse_render_cursor);\n        if (ImGui::Checkbox(TranslationManager::GetString(tstr_SettingsMouseShowCursor), &render_cursor))\n        {\n            IPCManager::Get().PostMessageToDashboardApp(ipcmsg_set_config, ConfigManager::GetWParamForConfigID(configid_bool_input_mouse_render_cursor), render_cursor);\n        }\n\n        if ( (!render_cursor) && (is_any_gc_overlay_active) )\n        {\n            ImGui::SameLine(0.0f, style.ItemInnerSpacing.x);\n            HelpMarker(TranslationManager::GetString(tstr_SettingsMouseShowCursorGCActiveWarning), \"(!)\");\n        }\n        else if (!DPWinRT_IsCaptureCursorEnabledPropertySupported())\n        {\n            ImGui::SameLine(0.0f, style.ItemInnerSpacing.x);\n            HelpMarker(TranslationManager::GetString(tstr_SettingsMouseShowCursorGCUnsupported), \"(!)\");\n        }\n\n        bool& scroll_smooth = ConfigManager::GetRef(configid_bool_input_mouse_scroll_smooth);\n        if (ImGui::Checkbox(TranslationManager::GetString(tstr_SettingsMouseScrollSmooth), &scroll_smooth))\n        {\n            IPCManager::Get().PostConfigMessageToDashboardApp(configid_bool_input_mouse_scroll_smooth, scroll_smooth);\n        }\n\n        bool& simulate_pen = ConfigManager::GetRef(configid_bool_input_mouse_simulate_pen_input);\n        if (ImGui::Checkbox(TranslationManager::GetString(tstr_SettingsMouseSimulatePen), &simulate_pen))\n        {\n            IPCManager::Get().PostConfigMessageToDashboardApp(configid_bool_input_mouse_simulate_pen_input, simulate_pen);\n        }\n        if (!ConfigManager::GetValue(configid_bool_state_pen_simulation_supported))\n        {\n            ImGui::SameLine(0.0f, style.ItemInnerSpacing.x);\n            HelpMarker(TranslationManager::GetString(tstr_SettingsMouseSimulatePenUnsupported), \"(!)\");\n        }\n\n        bool& pointer_override = ConfigManager::Get().GetRef(configid_bool_input_mouse_allow_pointer_override);\n        if (ImGui::Checkbox(TranslationManager::GetString(tstr_SettingsMouseAllowLaserPointerOverride), &pointer_override))\n        {\n            IPCManager::Get().PostMessageToDashboardApp(ipcmsg_set_config, ConfigManager::GetWParamForConfigID(configid_bool_input_mouse_allow_pointer_override), pointer_override);\n        }\n        ImGui::SameLine(0.0f, style.ItemInnerSpacing.x);\n        HelpMarker(TranslationManager::GetString(tstr_SettingsMouseAllowLaserPointerOverrideTip));\n\n        ImGui::Unindent();\n\n        //Double-Click Assistant\n        ImGui::Columns(2, \"ColumnMouse\", false);\n        ImGui::SetColumnWidth(0, m_Column0Width);\n\n        ImGui::AlignTextToFramePadding();\n        ImGui::TextUnformatted(TranslationManager::GetString(tstr_SettingsMouseDoubleClickAssist)); \n        ImGui::SameLine(0.0f, style.ItemInnerSpacing.x);\n        HelpMarker(TranslationManager::GetString(tstr_SettingsMouseDoubleClickAssistTip));\n\n        ImGui::NextColumn();\n\n        //The way mapping max + 1 == -1 value into the slider is done is a bit convoluted, but still works\n        int& assist_duration = ConfigManager::Get().GetRef(configid_int_input_mouse_dbl_click_assist_duration_ms);\n        const int assist_duration_max = 3000; //The \"Auto\" wrapping makes this the absolute maximum value even with manual input, but longer than 3 seconds is questionable either way\n        int assist_duration_ui = (assist_duration == -1) ? assist_duration_max + 1 : assist_duration;\n\n        const char* text_alt_assist = nullptr;\n        if (assist_duration <= 0)\n        {\n            text_alt_assist = TranslationManager::GetString((assist_duration == -1) ? tstr_SettingsMouseDoubleClickAssistTipValueAuto : tstr_SettingsMouseDoubleClickAssistTipValueOff);\n        }\n\n        vr_keyboard.VRKeyboardInputBegin( ImGui::SliderWithButtonsGetSliderID(\"DBLClickAssist\") );\n        if (ImGui::SliderWithButtonsInt(\"DBLClickAssist\", assist_duration_ui, 25, 5, 0, assist_duration_max + 1, (text_alt_assist != nullptr) ? \"\" : \"%d ms\", 0, nullptr, text_alt_assist))\n        {\n            assist_duration = clamp(assist_duration_ui, 0, assist_duration_max + 1);\n\n            if (assist_duration_ui > assist_duration_max)\n                assist_duration = -1;\n\n            IPCManager::Get().PostConfigMessageToDashboardApp(configid_int_input_mouse_dbl_click_assist_duration_ms, assist_duration);\n        }\n        vr_keyboard.VRKeyboardInputEnd();\n\n        ImGui::NextColumn();\n\n        //Input Smoothing\n        ImGui::AlignTextToFramePadding();\n        ImGui::TextUnformatted(TranslationManager::GetString(tstr_SettingsMouseSmoothing));\n        ImGui::NextColumn();\n\n        int& input_smoothing_level = ConfigManager::Get().GetRef(configid_int_input_mouse_input_smoothing_level);\n        const int input_smoothing_level_max = tstr_SettingsMouseSmoothingLevelVeryHigh - tstr_SettingsMouseSmoothingLevelNone;\n        input_smoothing_level = clamp(input_smoothing_level, 0, input_smoothing_level_max);\n\n        if (ImGui::SliderWithButtonsInt(\"SmoothingLevel\", input_smoothing_level, 1, 1, 0, input_smoothing_level_max, \"##%d\", ImGuiSliderFlags_NoInput, nullptr, \n                                        TranslationManager::GetString( (TRMGRStrID)(tstr_SettingsMouseSmoothingLevelNone + input_smoothing_level) )))\n        {\n            input_smoothing_level = clamp(input_smoothing_level, 0, input_smoothing_level_max);\n\n            IPCManager::Get().PostConfigMessageToDashboardApp(configid_int_input_mouse_input_smoothing_level, input_smoothing_level);\n        }\n\n        ImGui::Columns(1);\n    }\n\n    //Laser Pointer\n    {\n        static ConfigID_Int edited_hmd_pointer_input_id = configid_int_MAX;\n\n        //Write changes if we're returning from a picker\n        if ((m_PageReturned == wndsettings_page_keycode_picker) && (edited_hmd_pointer_input_id != configid_int_MAX))\n        {\n            ConfigManager::SetValue(edited_hmd_pointer_input_id, m_KeyCodePickerID);\n            IPCManager::Get().PostConfigMessageToDashboardApp(edited_hmd_pointer_input_id, m_KeyCodePickerID);\n\n            m_PageReturned = wndsettings_page_none;\n            edited_hmd_pointer_input_id = configid_int_MAX;\n        }\n\n        ImGui::Spacing();\n\n        ImGui::TextColoredUnformatted(ImGui::GetStyleColorVec4(ImGuiCol_ButtonHovered), TranslationManager::GetString(tstr_SettingsCatLaserPointer));\n        ImGui::SameLine(0.0f, style.ItemInnerSpacing.x);\n        HelpMarker(TranslationManager::GetString(tstr_SettingsLaserPointerTip));\n\n        ImGui::Indent();\n\n        bool& block_input = ConfigManager::GetRef(configid_bool_input_laser_pointer_block_input);\n        if (ImGui::Checkbox(TranslationManager::GetString(tstr_SettingsLaserPointerBlockInput), &block_input))\n        {\n            IPCManager::Get().PostConfigMessageToDashboardApp(configid_bool_input_laser_pointer_block_input, block_input);\n        }\n\n        ImGui::Unindent();\n\n        ImGui::Columns(2, \"ColumnLaserPointer\", false);\n        ImGui::SetColumnWidth(0, m_Column0Width);\n\n        ImGui::AlignTextToFramePadding();\n        ImGui::TextUnformatted(TranslationManager::GetString(tstr_SettingsLaserPointerAutoToggleDistance));\n        ImGui::NextColumn();\n\n        float& distance = ConfigManager::GetRef(configid_float_input_detached_interaction_max_distance);\n        const char* alt_text = (distance < 0.01f) ? TranslationManager::GetString(tstr_SettingsLaserPointerAutoToggleDistanceValueOff) : nullptr;\n\n        vr_keyboard.VRKeyboardInputBegin( ImGui::SliderWithButtonsGetSliderID(\"LaserPointerMaxDistance\") );\n        if (ImGui::SliderWithButtonsFloat(\"LaserPointerMaxDistance\", distance, 0.05f, 0.01f, 0.0f, 3.0f, (distance < 0.01f) ? \"##%.2f\" : \"%.2f m\", ImGuiSliderFlags_Logarithmic, nullptr, alt_text))\n        {\n            if (distance < 0.01f)\n                distance = 0.0f;\n\n            IPCManager::Get().PostConfigMessageToDashboardApp(configid_float_input_detached_interaction_max_distance, distance);\n        }\n        vr_keyboard.VRKeyboardInputEnd();\n\n        ImGui::Columns(1);\n        ImGui::Spacing();\n\n        if (ConfigManager::GetValue(configid_bool_interface_show_advanced_settings))\n        {\n            ImGui::Indent();\n\n            bool& hmd_pointer_enabled = ConfigManager::GetRef(configid_bool_input_laser_pointer_hmd_device);\n            if (ImGui::Checkbox(TranslationManager::GetString(tstr_SettingsLaserPointerHMDPointer), &hmd_pointer_enabled))\n            {\n                IPCManager::Get().PostConfigMessageToDashboardApp(configid_bool_input_laser_pointer_hmd_device, hmd_pointer_enabled);\n            }\n\n            ImGui::Indent(ImGui::GetFrameHeightWithSpacing());\n\n            if (!hmd_pointer_enabled)\n                ImGui::PushItemDisabled();\n\n            ImGui::PushID(\"HMDPointer\");\n\n            const ImVec2 table_size(-style.IndentSpacing - 1.0f, 0.0f);                                                    //Replicate padding from columns\n            const float table_column_width = m_Column0Width - style.IndentSpacing - ImGui::GetFrameHeightWithSpacing();    //Align with width of other columns \n\n            if (BeginCompactTable(\"TableHMDPointerInput\", 2, ImGuiTableFlags_RowBg | ImGuiTableFlags_SizingFixedFit, table_size))\n            {\n                ImGui::TableSetupColumn(TranslationManager::GetString(tstr_SettingsLaserPointerHMDPointerTableHeaderInputAction), 0, table_column_width);\n                ImGui::TableSetupColumn(TranslationManager::GetString(tstr_SettingsLaserPointerHMDPointerTableHeaderBinding), ImGuiTableColumnFlags_WidthStretch);\n                CompactTableHeadersRow();\n\n                const int key_count = configid_int_input_laser_pointer_hmd_device_keycode_drag - configid_int_input_laser_pointer_hmd_device_keycode_toggle;\n                IM_ASSERT(configid_int_input_laser_pointer_hmd_device_keycode_toggle < configid_int_input_laser_pointer_hmd_device_keycode_drag);\n                IM_ASSERT(tstr_SettingsLaserPointerHMDPointerTableBindingToggle + key_count < tstr_MAX);\n\n                for (int i = 0; i < key_count + 1; ++i)\n                {\n                    ConfigID_Int config_id = (ConfigID_Int)(configid_int_input_laser_pointer_hmd_device_keycode_toggle + i);\n\n                    ImGui::PushID(i);\n\n                    ImGui::TableNextColumn();\n\n                    ImGui::AlignTextToFramePadding();\n                    ImGui::TextUnformatted(TranslationManager::GetString( (TRMGRStrID)(tstr_SettingsLaserPointerHMDPointerTableBindingToggle + i) ));\n                    ImGui::SameLine();\n\n                    ImGui::TableNextColumn();\n\n                    if (ImGui::Selectable( GetStringForKeyCode(ConfigManager::GetValue(config_id)) ))\n                    {\n                        m_KeyCodePickerNoMouse    = true;\n                        m_KeyCodePickerHotkeyMode = false;\n                        m_KeyCodePickerID = ConfigManager::GetValue(config_id);\n                        edited_hmd_pointer_input_id = config_id;\n\n                        PageGoForward(wndsettings_page_keycode_picker);\n                        m_PageReturned = wndsettings_page_none;\n                    }\n\n                    ImGui::PopID();\n                }\n\n                EndCompactTable();\n            }\n\n            ImGui::PopID();\n\n            if (!hmd_pointer_enabled)\n                ImGui::PopItemDisabled();\n\n            ImGui::Unindent(ImGui::GetFrameHeightWithSpacing());\n            ImGui::Unindent();\n        }\n    }\n}\n\nvoid WindowSettings::UpdatePageMainCatWindows()\n{\n    //Window Overlays\n    {\n        ImGui::Spacing();\n\n        ImGui::TextColoredUnformatted(ImGui::GetStyleColorVec4(ImGuiCol_ButtonHovered), TranslationManager::GetString(tstr_SettingsCatWindowOverlays)); \n\n        ImGui::Indent();\n\n        if (ConfigManager::GetValue(configid_bool_interface_show_advanced_settings))\n        {\n            bool& auto_focus = ConfigManager::GetRef(configid_bool_windows_winrt_auto_focus);\n            if (ImGui::Checkbox(TranslationManager::GetString(tstr_SettingsWindowOverlaysAutoFocus), &auto_focus))\n            {\n                IPCManager::Get().PostConfigMessageToDashboardApp(configid_bool_windows_winrt_auto_focus, auto_focus);\n            }\n\n            bool& keep_on_screen = ConfigManager::GetRef(configid_bool_windows_winrt_keep_on_screen);\n            if (ImGui::Checkbox(TranslationManager::GetString(tstr_SettingsWindowOverlaysKeepOnScreen), &keep_on_screen))\n            {\n                IPCManager::Get().PostConfigMessageToDashboardApp(configid_bool_windows_winrt_keep_on_screen, keep_on_screen);\n            }\n            ImGui::SameLine(0.0f, ImGui::GetStyle().ItemInnerSpacing.x);\n            HelpMarker(TranslationManager::GetString(tstr_SettingsWindowOverlaysKeepOnScreenTip));\n        }\n\n        bool& auto_size_overlay = ConfigManager::GetRef(configid_bool_windows_winrt_auto_size_overlay);\n        if (ImGui::Checkbox(TranslationManager::GetString(tstr_SettingsWindowOverlaysAutoSizeOverlay), &auto_size_overlay))\n        {\n            IPCManager::Get().PostConfigMessageToDashboardApp(configid_bool_windows_winrt_auto_size_overlay, auto_size_overlay);\n        }\n\n        bool& focus_scene_app = ConfigManager::GetRef(configid_bool_windows_winrt_auto_focus_scene_app);\n        if (ImGui::Checkbox(TranslationManager::GetString(tstr_SettingsWindowOverlaysFocusSceneApp), &focus_scene_app))\n        {\n            IPCManager::Get().PostConfigMessageToDashboardApp(configid_bool_windows_winrt_auto_focus_scene_app, focus_scene_app);\n        }\n\n        if (ConfigManager::GetValue(configid_bool_interface_show_advanced_settings))\n        {\n            bool& auto_focus_scene_app_dashboard = ConfigManager::GetRef(configid_bool_windows_auto_focus_scene_app_dashboard);\n            if (ImGui::Checkbox(TranslationManager::GetString(tstr_SettingsWindowOverlaysFocusSceneAppDashboard), &auto_focus_scene_app_dashboard))\n            {\n                IPCManager::Get().PostConfigMessageToDashboardApp(configid_bool_windows_auto_focus_scene_app_dashboard, auto_focus_scene_app_dashboard);\n            }\n        }\n\n        ImGui::Unindent();\n\n        if (ConfigManager::GetValue(configid_bool_interface_show_advanced_settings))\n        {\n            ImGui::Spacing();\n            ImGui::Columns(2, \"ColumnWindows\", false);\n            ImGui::SetColumnWidth(0, m_Column0Width);\n\n            ImGui::AlignTextToFramePadding();\n            ImGui::TextUnformatted(TranslationManager::GetString(tstr_SettingsWindowOverlaysOnWindowDrag));\n            ImGui::NextColumn();\n\n            int& mode_dragging = ConfigManager::GetRef(configid_int_windows_winrt_dragging_mode);\n\n            ImGui::SetNextItemWidth(-1);\n            if (TranslatedComboAnimated(\"##ComboWindowDrag\", mode_dragging, tstr_SettingsWindowOverlaysOnWindowDragDoNothing, tstr_SettingsWindowOverlaysOnWindowDragOverlay))\n            {\n                IPCManager::Get().PostConfigMessageToDashboardApp(configid_int_windows_winrt_dragging_mode, mode_dragging);\n            }\n\n            ImGui::NextColumn();\n\n            ImGui::AlignTextToFramePadding();\n            ImGui::TextUnformatted(TranslationManager::GetString(tstr_SettingsWindowOverlaysOnCaptureLoss));\n            ImGui::SameLine(0.0f, ImGui::GetStyle().ItemInnerSpacing.x);\n            HelpMarker(TranslationManager::GetString(tstr_SettingsWindowOverlaysOnCaptureLossTip));\n            ImGui::NextColumn();\n\n            int& behavior_capture_loss = ConfigManager::GetRef(configid_int_windows_winrt_capture_lost_behavior);\n\n            ImGui::SetNextItemWidth(-1);\n            if (TranslatedComboAnimated(\"##ComboCaptureLost\", behavior_capture_loss, tstr_SettingsWindowOverlaysOnCaptureLossDoNothing, tstr_SettingsWindowOverlaysOnCaptureLossRemove))\n            {\n                IPCManager::Get().PostConfigMessageToDashboardApp(configid_int_windows_winrt_capture_lost_behavior, behavior_capture_loss);\n            }\n        }\n\n        ImGui::Columns(1);\n    }\n}\n\nvoid WindowSettings::UpdatePageMainCatBrowser()\n{\n    //Browser\n    if (DPBrowserAPIClient::Get().IsBrowserAvailable())\n    {\n        VRKeyboard& vr_keyboard = UIManager::Get()->GetVRKeyboard();\n\n        ImGui::Spacing();\n\n        ImGui::TextColoredUnformatted(ImGui::GetStyleColorVec4(ImGuiCol_ButtonHovered), TranslationManager::GetString(tstr_SettingsCatBrowser));\n\n        ImGui::Columns(2, \"ColumnBrowser\", false);\n        ImGui::SetColumnWidth(0, m_Column0Width);\n\n        ImGui::TextUnformatted(TranslationManager::GetString(tstr_SettingsBrowserMaxFrameRate));\n        ImGui::NextColumn();\n\n        int& max_fps = ConfigManager::Get().GetRef(configid_int_browser_max_fps);\n\n        if (m_BrowserMaxFPSValueText.empty())\n        {\n            m_BrowserMaxFPSValueText =  TranslationManager::GetString(tstr_SettingsPerformanceUpdateLimiterFPSValue);\n            StringReplaceAll(m_BrowserMaxFPSValueText, \"%FPS%\", std::to_string(max_fps));\n        }\n\n        vr_keyboard.VRKeyboardInputBegin(ImGui::SliderWithButtonsGetSliderID(\"MaxFPS\"));\n        if (ImGui::SliderWithButtonsInt(\"MaxFPS\", max_fps, 5, 1, 1, 144, \"##%d\", 0, nullptr, m_BrowserMaxFPSValueText.c_str()))\n        {\n            if (max_fps < 1)\n                max_fps = 1;\n\n            IPCManager::Get().PostConfigMessageToDashboardApp(configid_int_browser_max_fps, max_fps);\n\n            m_BrowserMaxFPSValueText = \"\";\n        }\n        vr_keyboard.VRKeyboardInputEnd();\n\n        ImGui::NextColumn();\n\n        bool& content_blocker = ConfigManager::GetRef(configid_bool_browser_content_blocker);\n        if (ImGui::Checkbox(TranslationManager::GetString(tstr_SettingsBrowserContentBlocker), &content_blocker))\n        {\n            IPCManager::Get().PostConfigMessageToDashboardApp(configid_bool_browser_content_blocker, content_blocker);\n        }\n        ImGui::SameLine(0.0f, ImGui::GetStyle().ItemInnerSpacing.x);\n        HelpMarker(TranslationManager::GetString(tstr_SettingsBrowserContentBlockerTip));\n\n        ImGui::NextColumn();\n        \n        static int block_list_count_last = -1;\n\n        if ( (m_BrowserBlockListCountText.empty()) || (ConfigManager::GetValue(configid_int_state_browser_content_blocker_list_count) != block_list_count_last) )\n        {\n            block_list_count_last = ConfigManager::GetValue(configid_int_state_browser_content_blocker_list_count);\n\n            m_BrowserBlockListCountText = TranslationManager::GetString((block_list_count_last != 1) ? tstr_SettingsBrowserContentBlockerListCount : tstr_SettingsBrowserContentBlockerListCountSingular);\n            StringReplaceAll(m_BrowserBlockListCountText, \"%LISTCOUNT%\", std::to_string(block_list_count_last) );\n        }\n\n        if ( (content_blocker) && (block_list_count_last != -1) )\n        {\n            ImGui::AlignTextToFramePadding();\n            ImGui::TextUnformatted(m_BrowserBlockListCountText.c_str());\n        }\n\n        ImGui::Columns(1);\n    }\n}\n\nvoid WindowSettings::UpdatePageMainCatPerformance()\n{\n    const ImGuiStyle& style = ImGui::GetStyle();\n\n    //Performance\n    {\n        ImGui::Spacing();\n\n        ImGui::TextColoredUnformatted(ImGui::GetStyleColorVec4(ImGuiCol_ButtonHovered), TranslationManager::GetString(tstr_SettingsCatPerformance)); \n        ImGui::Columns(2, \"ColumnPerformance\", false);\n        ImGui::SetColumnWidth(0, m_Column0Width);\n\n        UpdateLimiterSetting(false);\n\n        if (ConfigManager::GetValue(configid_bool_interface_show_advanced_settings))\n        {\n            ImGui::Spacing();\n            ImGui::AlignTextToFramePadding();\n            ImGui::TextUnformatted(TranslationManager::GetString(tstr_OvrlPropsCaptureMethodDup));\n            ImGui::NextColumn();\n\n            ImGui::Spacing();\n            bool& rapid_updates = ConfigManager::Get().GetRef(configid_bool_performance_rapid_laser_pointer_updates);\n            if (ImGui::Checkbox(TranslationManager::GetString(tstr_SettingsPerformanceRapidUpdates), &rapid_updates))\n            {\n                IPCManager::Get().PostMessageToDashboardApp(ipcmsg_set_config, ConfigManager::GetWParamForConfigID(configid_bool_performance_rapid_laser_pointer_updates), rapid_updates);\n            }\n            ImGui::SameLine(0.0f, style.ItemInnerSpacing.x);\n            HelpMarker(TranslationManager::GetString(tstr_SettingsPerformanceRapidUpdatesTip));\n\n            ImGui::NextColumn();\n            ImGui::NextColumn();\n\n            bool& single_desktop = ConfigManager::Get().GetRef(configid_bool_performance_single_desktop_mirroring);\n            if (ImGui::Checkbox(TranslationManager::GetString(tstr_SettingsPerformanceSingleDesktopMirror), &single_desktop))\n            {\n                IPCManager::Get().PostMessageToDashboardApp(ipcmsg_set_config, ConfigManager::GetWParamForConfigID(configid_bool_performance_single_desktop_mirroring), single_desktop);\n            }\n            ImGui::SameLine(0.0f, style.ItemInnerSpacing.x);\n            HelpMarker(TranslationManager::GetString(tstr_SettingsPerformanceSingleDesktopMirrorTip));\n\n            bool& alt_cursor_render = ConfigManager::Get().GetRef(configid_bool_performance_alternative_cursor_rendering);\n            if (ImGui::Checkbox(TranslationManager::GetString(tstr_SettingsPerformanceAlternativeCursorRendering), &alt_cursor_render))\n            {\n                IPCManager::Get().PostMessageToDashboardApp(ipcmsg_set_config, ConfigManager::GetWParamForConfigID(configid_bool_performance_alternative_cursor_rendering), alt_cursor_render);\n            }\n            ImGui::SameLine(0.0f, style.ItemInnerSpacing.x);\n            HelpMarker(TranslationManager::GetString(tstr_SettingsPerformanceAlternativeCursorRenderingTip));\n\n            ImGui::NextColumn();\n            ImGui::Spacing();\n\n            bool& use_hdr = ConfigManager::Get().GetRef(configid_bool_performance_hdr_mirroring);\n            if (ImGui::Checkbox(TranslationManager::GetString(tstr_SettingsPerformanceUseHDR), &use_hdr))\n            {\n                IPCManager::Get().PostMessageToDashboardApp(ipcmsg_set_config, ConfigManager::GetWParamForConfigID(configid_bool_performance_hdr_mirroring), use_hdr);\n            }\n            ImGui::SameLine(0.0f, style.ItemInnerSpacing.x);\n            HelpMarker(TranslationManager::GetString(tstr_SettingsPerformanceUseHDRTip));\n\n            ImGui::NextColumn();\n            ImGui::NextColumn();\n        }\n\n        bool& show_fps = ConfigManager::Get().GetRef(configid_bool_performance_show_fps);\n        ImGui::Checkbox(TranslationManager::GetString(tstr_SettingsPerformanceShowFPS), &show_fps);\n\n        ImGui::Columns(1);\n    }\n}\n\nvoid WindowSettings::UpdatePageMainCatMisc()\n{\n    const ImGuiStyle& style = ImGui::GetStyle();\n\n    static bool is_autolaunch_enabled = false;\n\n    if ( (UIManager::Get()->IsOpenVRLoaded()) && (m_PageAppearing == wndsettings_page_main) )\n    {\n        is_autolaunch_enabled = vr::VRApplications()->GetApplicationAutoLaunch(g_AppKeyDashboardApp);\n    }\n\n    //Version Info\n    {\n        ImGui::Spacing();\n\n        ImGui::TextColoredUnformatted(ImGui::GetStyleColorVec4(ImGuiCol_ButtonHovered), TranslationManager::GetString(tstr_SettingsCatVersionInfo)); \n        ImGui::Indent();\n\n        ImGui::TextUnformatted(k_pch_DesktopPlusVersion);\n\n        ImGui::Unindent();\n    }\n\n    //Warnings\n    {\n        ImGui::Spacing();\n\n        ImGui::TextColoredUnformatted(ImGui::GetStyleColorVec4(ImGuiCol_ButtonHovered), TranslationManager::GetString(tstr_SettingsCatWarnings)); \n        ImGui::Columns(2, \"ColumnResetWarnings\", false);\n        ImGui::SetColumnWidth(0, m_Column0Width);\n\n        int warning_hidden_count = 0;\n\n        if (ConfigManager::GetValue(configid_bool_interface_warning_compositor_quality_hidden))\n            warning_hidden_count++;\n        if (ConfigManager::GetValue(configid_bool_interface_warning_compositor_res_hidden))\n            warning_hidden_count++;\n        if (ConfigManager::GetValue(configid_bool_interface_warning_process_elevation_hidden))\n            warning_hidden_count++;\n        if (ConfigManager::GetValue(configid_bool_interface_warning_elevated_mode_hidden))\n            warning_hidden_count++;\n        if (ConfigManager::GetValue(configid_bool_interface_warning_browser_missing_hidden))\n            warning_hidden_count++;\n        if (ConfigManager::GetValue(configid_bool_interface_warning_browser_version_mismatch_hidden))\n            warning_hidden_count++;\n\n        ImGui::AlignTextToFramePadding();\n        ImGui::TextUnformatted(TranslationManager::GetString(tstr_SettingsWarningsHidden));\n        ImGui::SameLine();\n        ImGui::Text(\"%i\", warning_hidden_count);\n\n        ImGui::NextColumn();\n\n        if (ImGui::Button(TranslationManager::GetString(tstr_SettingsWarningsReset)))\n        {\n            ConfigManager::SetValue(configid_bool_interface_warning_compositor_quality_hidden,       false);\n            ConfigManager::SetValue(configid_bool_interface_warning_compositor_res_hidden,           false);\n            ConfigManager::SetValue(configid_bool_interface_warning_process_elevation_hidden,        false);\n            ConfigManager::SetValue(configid_bool_interface_warning_elevated_mode_hidden,            false);\n            ConfigManager::SetValue(configid_bool_interface_warning_browser_missing_hidden,          false);\n            ConfigManager::SetValue(configid_bool_interface_warning_browser_version_mismatch_hidden, false);\n\n            UIManager::Get()->UpdateAnyWarningDisplayedState();\n        }\n\n        ImGui::Columns(1);\n    }\n\n    //Startup\n    bool& no_steam = ConfigManager::GetRef(configid_bool_misc_no_steam);\n\n    if ( (ConfigManager::Get().IsSteamInstall()) || (UIManager::Get()->IsOpenVRLoaded()) ) //Only show if Steam install or we can access OpenVR settings\n    {\n        ImGui::Spacing();\n\n        ImGui::TextColored(ImGui::GetStyleColorVec4(ImGuiCol_ButtonHovered), TranslationManager::GetString(tstr_SettingsCatStartup));\n        ImGui::Indent();\n\n        if (UIManager::Get()->IsOpenVRLoaded())\n        {\n            if (ImGui::Checkbox(TranslationManager::GetString(tstr_SettingsStartupAutoLaunch), &is_autolaunch_enabled))\n            {\n                vr::VRApplications()->SetApplicationAutoLaunch(g_AppKeyDashboardApp, is_autolaunch_enabled);\n            }\n        }\n\n        if (ConfigManager::Get().IsSteamInstall())\n        {\n            if (ImGui::Checkbox(TranslationManager::GetString(tstr_SettingsStartupSteamDisable), &no_steam))\n            {\n                IPCManager::Get().PostConfigMessageToDashboardApp(configid_bool_misc_no_steam, no_steam);\n            }\n            ImGui::SameLine(0.0f, style.ItemInnerSpacing.x);\n            HelpMarker(TranslationManager::GetString(tstr_SettingsStartupSteamDisableTip));\n        }\n\n        ImGui::Unindent();\n    }\n\n    //Troubleshooting\n    {\n        ImGui::Spacing();\n\n        ImGui::TextColored(ImGui::GetStyleColorVec4(ImGuiCol_ButtonHovered), TranslationManager::GetString(tstr_SettingsCatTroubleshooting));\n        ImGui::Columns(2, \"ColumnTroubleshooting\", false);\n        ImGui::SetColumnWidth(0, m_Column0Width);\n\n        //All the restart buttons only start up new processes, but both UI and dashboard app get rid of the older instance when starting\n        ImGui::AlignTextToFramePadding();\n        ImGui::TextUnformatted(\"Desktop+\");\n        ImGui::NextColumn();\n\n        if (ImGui::Button(TranslationManager::GetString(tstr_SettingsTroubleshootingRestart)))\n        {\n            UIManager::Get()->RestartDashboardApp();\n        }\n\n        bool has_restart_steam_button = ( (ConfigManager::Get().IsSteamInstall()) && (!ConfigManager::GetValue(configid_bool_state_misc_process_started_by_steam)) );\n\n        if (has_restart_steam_button)\n        {\n            ImGui::SameLine(0.0f, style.ItemInnerSpacing.x);\n\n            if (no_steam)\n                ImGui::PushItemDisabled();\n\n            if (ImGui::Button(TranslationManager::GetString(tstr_SettingsTroubleshootingRestartSteam)))\n            {\n                UIManager::Get()->RestartDashboardApp(true);\n            }\n\n            if (no_steam)\n                ImGui::PopItemDisabled();\n        }\n\n        if (UIManager::Get()->IsElevatedTaskSetUp())\n        {\n            static float elevated_task_button_width = 0.0f;\n\n            //Put this button on a new line if it doesn't fit (typically happens with [Restart with Steam] button present in VR mode)\n            if (ImGui::GetItemRectMax().x + style.ItemSpacing.x + elevated_task_button_width < ImGui::GetCursorScreenPos().x + ImGui::GetContentRegionAvail().x)\n                ImGui::SameLine(0.0f, style.ItemInnerSpacing.x);\n\n            const bool dashboard_app_running  = IPCManager::IsDashboardAppRunning();\n\n            if (!dashboard_app_running)\n                ImGui::PushItemDisabled();\n\n            if (!ConfigManager::GetValue(configid_bool_state_misc_elevated_mode_active))\n            {\n                if (ImGui::Button(TranslationManager::GetString(tstr_SettingsTroubleshootingElevatedModeEnter)))\n                {\n                    UIManager::Get()->ElevatedModeEnter();\n                }\n            }\n            else\n            {\n                if (ImGui::Button(TranslationManager::GetString(tstr_SettingsTroubleshootingElevatedModeLeave)))\n                {\n                    UIManager::Get()->ElevatedModeLeave();\n                }\n            }\n\n            elevated_task_button_width = ImGui::GetItemRectSize().x;\n\n            if (!dashboard_app_running)\n                ImGui::PopItemDisabled();\n        }\n\n        ImGui::NextColumn();\n\n        ImGui::AlignTextToFramePadding();\n        ImGui::TextUnformatted(\"Desktop+ UI\");\n        ImGui::NextColumn();\n\n        ImGui::PushID(\"##UI\");\n\n        if (ImGui::Button(TranslationManager::GetString(tstr_SettingsTroubleshootingRestart)))\n        {\n            UIManager::Get()->Restart(false);\n        }\n\n        ImGui::PopID();\n\n        ImGui::SameLine(0.0f, style.ItemInnerSpacing.x);\n\n        if (ImGui::Button(TranslationManager::GetString(tstr_SettingsTroubleshootingRestartDesktop)))\n        {\n            UIManager::Get()->Restart(true);\n        }\n\n        ImGui::Columns(1);\n\n        ImGui::Spacing();\n        ImGui::Indent();\n\n        if (ImGui::Button(TranslationManager::GetString(tstr_SettingsTroubleshootingSettingsReset)))\n        {\n            PageGoForward(wndsettings_page_reset_confirm);\n        }\n\n        ImGui::Unindent();\n    }\n}\n\nvoid WindowSettings::UpdatePagePersistentUI()\n{\n    static float tab_item_height = 0.0f;\n    //We only use half the size for column 0 on this page since we can really use the space for other things\n    const float column_0_width = m_Column0Width / 2.0f;\n    const float column_1_width = (ImGui::GetContentRegionAvail().x - column_0_width) / 2.0f;\n\n    auto window_state_tab_item = [&](const char* label, FloatingWindow& window, bool& config_var_state_restore) \n    {\n        if (ImGui::BeginTabItem(label, 0, ImGuiTabItemFlags_NoPushId))  //NoPushId avoids flickering from size calculations and is not problematic two controls are never active at the same time\n        {\n            if (ImGui::IsWindowAppearing())\n            {\n                UIManager::Get()->RepeatFrame();\n            }\n\n            ImGuiStyle& style = ImGui::GetStyle();\n            FloatingWindowOverlayState& state_room      = window.GetOverlayState(floating_window_ovrl_state_room);\n            FloatingWindowOverlayState& state_dplus_tab = window.GetOverlayState(floating_window_ovrl_state_dashboard_tab);\n\n            const bool use_lazy_resize = (&window == &UIManager::Get()->GetSettingsWindow()); //Lazily resize for settings window since the input is done through it\n\n            //Draw background and border manually to merge with the tab-bar\n            ImVec2 border_min = ImGui::GetCursorScreenPos();\n            ImVec2 border_max = border_min;\n            border_min.y -= style.ItemSpacing.y + 1;\n            border_max.x += ImGui::GetContentRegionAvail().x;\n            border_max.y += tab_item_height;\n            ImGui::GetWindowDrawList()->AddRectFilled(border_min, border_max, ImGui::GetColorU32(ImGuiCol_ChildBg));\n            ImGui::GetWindowDrawList()->AddRect(      border_min, border_max, ImGui::GetColorU32(ImGuiCol_Border ));\n\n            //Window state table\n            ImGui::Columns(3, \"ColumnPersistWindowState\", false);\n            ImGui::SetColumnWidth(0, column_0_width);\n            ImGui::SetColumnWidth(1, column_1_width);\n            ImGui::SetColumnWidth(2, column_1_width);\n\n            //Headers\n            ImGui::NextColumn();\n            ImGui::TextUnformatted(TranslationManager::GetString(tstr_SettingsInterfacePersistentUIWindowsStateGlobal));\n    \n            ImGui::NextColumn();\n            ImGui::TextUnformatted(TranslationManager::GetString(tstr_SettingsInterfacePersistentUIWindowsStateDashboardTab));\n            ImGui::NextColumn();\n\n            //Visible\n            ImGui::Spacing();\n            ImGui::AlignTextToFramePadding();\n            ImGui::TextUnformatted(TranslationManager::GetString(tstr_SettingsInterfacePersistentUIWindowsStateVisible));\n            ImGui::NextColumn();\n\n            ImGui::Spacing();\n            if (ImGui::Checkbox(\"##CheckVisibleRoom\", &state_room.IsVisible))\n            {\n                //Pin automatically if in room state and unpinned is not allowed\n                if ( (!window.CanUnpinRoom()) && (!state_room.IsPinned) )\n                {\n                    //Pin and unpin (if needed) current state to have it apply to the room state. This is only needed when there's no pinned transform yet\n                    const bool current_pinned = window.IsPinned();\n\n                    window.SetPinned(true);\n\n                    if (window.GetOverlayStateCurrentID() == floating_window_ovrl_state_dashboard_tab)\n                    {\n                        window.SetPinned(current_pinned, true);\n                    }\n                }\n\n                if (window.GetOverlayStateCurrentID() == floating_window_ovrl_state_room)\n                {\n                    window.ApplyCurrentOverlayState();\n                }\n\n                //When hiding keyboard, the assigned overlay also needs to be cleared so it doesn't automatically reappear\n                if ( (!state_room.IsVisible) && (&window == &UIManager::Get()->GetVRKeyboard().GetWindow()) )\n                {\n                    UIManager::Get()->GetVRKeyboard().GetWindow().SetAssignedOverlayID(-1, floating_window_ovrl_state_room);\n                }\n            }\n            ImGui::NextColumn();\n\n            ImGui::Spacing();\n            if (ImGui::Checkbox(\"##CheckVisibleDPlusTab\", &state_dplus_tab.IsVisible))\n            {\n                if (window.GetOverlayStateCurrentID() == floating_window_ovrl_state_dashboard_tab)\n                {\n                    window.ApplyCurrentOverlayState();\n                }\n            }\n            ImGui::NextColumn();\n\n            //Pinned\n            ImGui::AlignTextToFramePadding();\n            ImGui::TextUnformatted(TranslationManager::GetString(tstr_SettingsInterfacePersistentUIWindowsStatePinned));\n            ImGui::NextColumn();\n\n            if (!window.CanUnpinRoom())\n                ImGui::PushItemDisabled();\n\n            if (ImGui::Checkbox(\"##CheckPinnedRoom\", &state_room.IsPinned))\n            {\n                if (window.GetOverlayStateCurrentID() == floating_window_ovrl_state_room)\n                {\n                    window.SetPinned(state_room.IsPinned, true);\n                }\n                else\n                {\n                    //Pin and unpin current state to have it apply to the room state\n                    const bool current_pinned = window.IsPinned();\n\n                    window.SetPinned(state_room.IsPinned);\n                    window.SetPinned(current_pinned, true);\n                }\n            }\n\n            if (!window.CanUnpinRoom())\n                ImGui::PopItemDisabled();\n\n            ImGui::NextColumn();\n\n            if (ImGui::Checkbox(\"##CheckPinnedDPlusTab\", &state_dplus_tab.IsPinned))\n            {\n                if (window.GetOverlayStateCurrentID() == floating_window_ovrl_state_dashboard_tab)\n                {\n                    window.SetPinned(state_dplus_tab.IsPinned, true);\n                }\n            }\n            ImGui::NextColumn();\n\n            //Position\n            ImGui::AlignTextToFramePadding();\n            ImGui::TextUnformatted(TranslationManager::GetString(tstr_SettingsInterfacePersistentUIWindowsStatePosition));\n            ImGui::NextColumn();\n\n            ImGui::PushID(floating_window_ovrl_state_room);\n            if (ImGui::Button(TranslationManager::GetString(tstr_SettingsInterfacePersistentUIWindowsStatePositionReset)))\n            {\n                window.ResetTransform(floating_window_ovrl_state_room);\n\n                if (window.GetOverlayStateCurrentID() == floating_window_ovrl_state_room)\n                {\n                    window.ApplyCurrentOverlayState();\n                }\n            }\n            ImGui::PopID();\n            ImGui::NextColumn();\n\n            ImGui::PushID(floating_window_ovrl_state_dashboard_tab);\n            if (ImGui::Button(TranslationManager::GetString(tstr_SettingsInterfacePersistentUIWindowsStatePositionReset)))\n            {\n                if (state_dplus_tab.IsPinned)\n                {\n                    if (window.GetOverlayStateCurrentID() == floating_window_ovrl_state_dashboard_tab)\n                    {\n                        window.SetPinned(false, true);\n                    }\n                }\n\n                window.ResetTransform(floating_window_ovrl_state_dashboard_tab);\n            }\n            ImGui::PopID();\n            ImGui::NextColumn();\n\n            //Size\n            ImGui::AlignTextToFramePadding();\n            ImGui::TextUnformatted(TranslationManager::GetString(tstr_SettingsInterfacePersistentUIWindowsStateSize));\n            ImGui::NextColumn();\n\n            if (ImGui::SliderWithButtonsFloatPercentage(\"SizeRoom\", state_room.Size, 5, 1, 50, 200, \"%d%%\"))\n            {\n                if (state_dplus_tab.Size < 0.10f)\n                {\n                    state_dplus_tab.Size = 0.10f;\n                }\n\n                if ( (window.GetOverlayStateCurrentID() == floating_window_ovrl_state_room) && (!use_lazy_resize) )\n                {\n                    window.ApplyCurrentOverlayState();\n                }\n            }\n            \n            if ( (ImGui::IsItemDeactivated()) && (use_lazy_resize) && (window.GetOverlayStateCurrentID() == floating_window_ovrl_state_room) )\n            {\n                window.ApplyCurrentOverlayState();\n            }\n            ImGui::NextColumn();\n\n            if (ImGui::SliderWithButtonsFloatPercentage(\"SizeDPlusTab\", state_dplus_tab.Size, 5, 1, 50, 200, \"%d%%\"))\n            {\n                if (state_dplus_tab.Size < 0.10f)\n                {\n                    state_dplus_tab.Size = 0.10f;\n                }\n\n                if ( (window.GetOverlayStateCurrentID() == floating_window_ovrl_state_dashboard_tab) && (!use_lazy_resize) )\n                {\n                    window.ApplyCurrentOverlayState();\n                }\n            }\n            \n            if ( (ImGui::IsItemDeactivated()) && (use_lazy_resize) && (window.GetOverlayStateCurrentID() == floating_window_ovrl_state_dashboard_tab) )\n            {\n                window.ApplyCurrentOverlayState();\n            }\n\n            ImGui::Columns(1);\n\n            ImGui::Indent();\n            ImGui::Spacing();\n            ImGui::Checkbox(TranslationManager::GetString(tstr_SettingsInterfacePersistentUIWindowsStateLaunchRestore), &config_var_state_restore);\n            ImGui::Unindent();\n\n            ImGui::EndTabItem();\n\n            tab_item_height = ImGui::GetCursorScreenPos().y - border_min.y;\n        }\n    };\n\n    ImGui::TextColoredUnformatted(ImGui::GetStyleColorVec4(ImGuiCol_ButtonHovered), TranslationManager::GetString(tstr_SettingsInterfacePersistentUI));\n\n    ImGui::Indent();\n\n    ImGui::PushTextWrapPos();\n    ImGui::TextUnformatted(TranslationManager::GetString(tstr_SettingsInterfacePersistentUIHelp));\n    ImGui::Spacing();\n    ImGui::TextUnformatted(TranslationManager::GetString(tstr_SettingsInterfacePersistentUIHelp2));\n    ImGui::PopTextWrapPos();\n\n    ImGui::Unindent();\n\n    ImGui::TextColoredUnformatted(ImGui::GetStyleColorVec4(ImGuiCol_ButtonHovered), TranslationManager::GetString(tstr_SettingsInterfacePersistentUIWindowsHeader));\n\n    ImGui::Indent();\n\n    if (ImGui::BeginTabBar(\"TabBarPersist\"))\n    {\n        window_state_tab_item(TranslationManager::GetString(tstr_SettingsInterfacePersistentUIWindowsSettings),  *this, \n                              ConfigManager::GetRef(configid_bool_interface_window_settings_restore_state));\n        window_state_tab_item(TranslationManager::GetString(tstr_SettingsInterfacePersistentUIWindowsProperties), UIManager::Get()->GetOverlayPropertiesWindow(), \n                              ConfigManager::GetRef(configid_bool_interface_window_properties_restore_state));\n        window_state_tab_item(TranslationManager::GetString(tstr_SettingsInterfacePersistentUIWindowsKeyboard),   UIManager::Get()->GetVRKeyboard().GetWindow(),  \n                              ConfigManager::GetRef(configid_bool_interface_window_keyboard_restore_state));\n\n        ImGui::EndTabBar();\n    }\n\n    ImGui::Unindent();\n\n    ImGui::SetCursorPosY( ImGui::GetCursorPosY() + (ImGui::GetContentRegionAvail().y - ImGui::GetFrameHeightWithSpacing()) );\n\n    ImGui::Separator();\n\n    if (ImGui::Button(TranslationManager::GetString(tstr_DialogDone))) \n    {\n        PageGoBack();\n    }\n}\n\nvoid WindowSettings::UpdatePageKeyboardLayout(bool only_restore_settings)\n{\n    ImGuiStyle& style = ImGui::GetStyle();\n    VRKeyboard& vr_keyboard = UIManager::Get()->GetVRKeyboard();\n\n    static int list_id = -1;\n    static std::vector<KeyboardLayoutMetadata> list_layouts;\n    static std::vector<std::string> str_list_authors; \n    static bool cluster_enabled_prev[kbdlayout_cluster_MAX] = {false};\n\n    if (only_restore_settings)\n    {\n        //Restore previous cluster settings\n        ConfigManager::SetValue(configid_bool_input_keyboard_cluster_function_enabled,   cluster_enabled_prev[kbdlayout_cluster_function]);\n        ConfigManager::SetValue(configid_bool_input_keyboard_cluster_navigation_enabled, cluster_enabled_prev[kbdlayout_cluster_navigation]);\n        ConfigManager::SetValue(configid_bool_input_keyboard_cluster_numpad_enabled,     cluster_enabled_prev[kbdlayout_cluster_numpad]);\n        ConfigManager::SetValue(configid_bool_input_keyboard_cluster_extra_enabled,      cluster_enabled_prev[kbdlayout_cluster_extra]);\n\n        vr_keyboard.LoadCurrentLayout();\n        return;\n    }\n\n    if (m_PageAppearing == wndsettings_page_keyboard)\n    {\n        //Show the keyboard since that's probably useful\n        vr_keyboard.GetWindow().Show();\n\n        //Load layout list\n        list_id = -1;\n        list_layouts = VRKeyboard::GetKeyboardLayoutList();\n\n        //Generate cached author list strings\n        str_list_authors.clear();\n        for (const auto& metadata: list_layouts)\n        {\n            if (!metadata.Author.empty())\n            {\n                std::string author_str = TranslationManager::GetString(tstr_SettingsKeyboardLayoutAuthor);\n                StringReplaceAll(author_str, \"%AUTHOR%\", metadata.Author);\n                str_list_authors.push_back(author_str);\n            }\n            else\n            {\n                str_list_authors.emplace_back();\n            }\n        }\n\n        //Select matching entry\n        const std::string& current_filename = ConfigManager::GetValue(configid_str_input_keyboard_layout_file);\n        auto it = std::find_if(list_layouts.begin(), list_layouts.end(), [&current_filename](const auto& list_entry){ return (current_filename == list_entry.FileName); });\n\n        if (it != list_layouts.end())\n        {\n            list_id = (int)std::distance(list_layouts.begin(), it);\n        }\n\n        //Clusters\n        cluster_enabled_prev[kbdlayout_cluster_function]   = ConfigManager::GetValue(configid_bool_input_keyboard_cluster_function_enabled);\n        cluster_enabled_prev[kbdlayout_cluster_navigation] = ConfigManager::GetValue(configid_bool_input_keyboard_cluster_navigation_enabled);\n        cluster_enabled_prev[kbdlayout_cluster_numpad]     = ConfigManager::GetValue(configid_bool_input_keyboard_cluster_numpad_enabled);\n        cluster_enabled_prev[kbdlayout_cluster_extra]      = ConfigManager::GetValue(configid_bool_input_keyboard_cluster_extra_enabled);\n\n        //Reload current layout in case there's a previous pending selection still loaded\n        vr_keyboard.LoadCurrentLayout();\n\n        UIManager::Get()->RepeatFrame();\n    }\n\n    ImGui::TextColoredUnformatted(ImGui::GetStyleColorVec4(ImGuiCol_ButtonHovered), TranslationManager::GetString(tstr_SettingsKeyboardLayout) ); \n    ImGui::Indent();\n\n    ImGui::SetNextItemWidth(-1.0f);\n    const float item_height = ImGui::GetFontSize() + style.ItemSpacing.y;\n    const float inner_padding = style.FramePadding.y + style.FramePadding.y + style.ItemInnerSpacing.y;\n    const float item_count = (UIManager::Get()->IsInDesktopMode()) ? 19.0f : 15.0f;\n    ImGui::BeginChild(\"LayoutList\", ImVec2(0.0f, (item_height * item_count) + inner_padding - m_WarningHeight), true);\n\n    //List layouts\n    int index = 0;\n    for (const auto& metadata: list_layouts)\n    {\n        ImGui::PushID(index);\n\n        if (ImGui::Selectable(metadata.Name.c_str(), (index == list_id)))\n        {\n            list_id = index;\n            vr_keyboard.LoadLayoutFromFile(metadata.FileName);\n        }\n\n        if (!metadata.Author.empty())\n        {\n            ImGui::SameLine();\n            ImGui::PushStyleColor(ImGuiCol_Text, style.Colors[ImGuiCol_TextDisabled]);\n            ImGui::TextRightUnformatted(0.0f, 0.0f, str_list_authors[index].c_str());\n            ImGui::PopStyleColor();\n        }\n\n        ImGui::PopID();\n\n        index++;\n    }\n\n    ImGui::EndChild();\n\n    ImGui::Unindent();\n\n    //Key Clusters\n    ImGui::Spacing();\n    ImGui::TextColoredUnformatted(ImGui::GetStyleColorVec4(ImGuiCol_ButtonHovered), TranslationManager::GetString(tstr_SettingsKeyboardKeyClusters) ); \n    ImGui::Indent();\n\n    bool reload_layout = false;\n    bool& function_enabled   = ConfigManager::GetRef(configid_bool_input_keyboard_cluster_function_enabled);\n    bool& navigation_enabled = ConfigManager::GetRef(configid_bool_input_keyboard_cluster_navigation_enabled);\n    bool& numpad_enabled     = ConfigManager::GetRef(configid_bool_input_keyboard_cluster_numpad_enabled);\n    bool& extra_enabled      = ConfigManager::GetRef(configid_bool_input_keyboard_cluster_extra_enabled);\n\n    //Keep unavailable options as enabled but show the check boxes as unticked to avoid confusion\n    bool cluster_available[kbdlayout_cluster_MAX] = {false};\n\n    if (list_id != -1)\n    {\n        const KeyboardLayoutMetadata& current_metadata = list_layouts[list_id];\n        std::copy_n(current_metadata.HasCluster, kbdlayout_cluster_MAX, cluster_available);\n    }\n\n    bool function_enabled_visual   = (cluster_available[kbdlayout_cluster_function])   ? function_enabled   : false;\n    bool navigation_enabled_visual = (cluster_available[kbdlayout_cluster_navigation]) ? navigation_enabled : false;\n    bool numpad_enabled_visual     = (cluster_available[kbdlayout_cluster_numpad])     ? numpad_enabled     : false;\n    bool extra_enabled_visual      = (cluster_available[kbdlayout_cluster_extra])      ? extra_enabled      : false;\n\n    //Use bigger spacing for the checkbox listing\n    ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, {style.ItemSpacing.x * 2.0f, style.ItemSpacing.y});\n\n    if (!cluster_available[kbdlayout_cluster_function])\n        ImGui::PushItemDisabled();\n\n    if (ImGui::Checkbox(TranslationManager::GetString(tstr_SettingsKeyboardKeyClusterFunction), &function_enabled_visual))\n    {\n        function_enabled = function_enabled_visual;\n        reload_layout = true;\n    }\n\n    if (!cluster_available[kbdlayout_cluster_function])\n        ImGui::PopItemDisabled();\n\n    ImGui::SameLine();\n\n    if (!cluster_available[kbdlayout_cluster_navigation])\n        ImGui::PushItemDisabled();\n\n    if (ImGui::Checkbox(TranslationManager::GetString(tstr_SettingsKeyboardKeyClusterNavigation), &navigation_enabled_visual))\n    {\n        navigation_enabled = navigation_enabled_visual;\n        reload_layout = true;\n    }\n\n    if (!cluster_available[kbdlayout_cluster_navigation])\n        ImGui::PopItemDisabled();\n\n    ImGui::SameLine();\n\n    if (!cluster_available[kbdlayout_cluster_numpad])\n        ImGui::PushItemDisabled();\n\n    if (ImGui::Checkbox(TranslationManager::GetString(tstr_SettingsKeyboardKeyClusterNumpad), &numpad_enabled_visual))\n    {\n        numpad_enabled = numpad_enabled_visual;\n        reload_layout = true;\n    }\n\n    if (!cluster_available[kbdlayout_cluster_numpad])\n        ImGui::PopItemDisabled();\n\n    ImGui::SameLine();\n\n    if (!cluster_available[kbdlayout_cluster_extra])\n        ImGui::PushItemDisabled();\n\n    if (ImGui::Checkbox(TranslationManager::GetString(tstr_SettingsKeyboardKeyClusterExtra), &extra_enabled_visual))\n    {\n        extra_enabled = extra_enabled_visual;\n        reload_layout = true;\n    }\n\n    if (!cluster_available[kbdlayout_cluster_extra])\n        ImGui::PopItemDisabled();\n\n    ImGui::PopStyleVar(); //ImGuiStyleVar_ItemSpacing\n\n    if ( (reload_layout) && (list_id != -1) )\n    {\n        vr_keyboard.LoadLayoutFromFile(list_layouts[list_id].FileName);\n    }\n\n    ImGui::Unindent();\n\n    ImGui::SetCursorPosY( ImGui::GetCursorPosY() + (ImGui::GetContentRegionAvail().y - ImGui::GetFrameHeightWithSpacing()) );\n\n    //Confirmation buttons\n    ImGui::Separator();\n\n    if (ImGui::Button(TranslationManager::GetString(tstr_DialogOk))) \n    {\n        //Prevent them from being reset\n        cluster_enabled_prev[kbdlayout_cluster_function]   = function_enabled;\n        cluster_enabled_prev[kbdlayout_cluster_navigation] = navigation_enabled;\n        cluster_enabled_prev[kbdlayout_cluster_numpad]     = numpad_enabled;\n        cluster_enabled_prev[kbdlayout_cluster_extra]      = extra_enabled;\n\n        if (list_id != -1)\n        {\n            ConfigManager::SetValue(configid_str_input_keyboard_layout_file, list_layouts[list_id].FileName);\n        }\n\n        PageGoBack();\n    }\n\n    ImGui::SameLine();\n\n    if (ImGui::Button(TranslationManager::GetString(tstr_DialogCancel))) \n    {\n        PageGoBack();\n    }\n    \n    if (UIManager::Get()->IsInDesktopMode())\n    {\n        ImGui::SameLine();\n\n        static float keyboard_editor_button_width = 0.0f;\n        ImGui::SetCursorPosX(ImGui::GetCursorPosX() + ImGui::GetContentRegionAvail().x - keyboard_editor_button_width);\n\n        if (ImGui::Button(TranslationManager::GetString(tstr_SettingsKeyboardSwitchToEditor)))\n        {\n            UIManager::Get()->RestartIntoKeyboardEditor();\n        }\n\n        keyboard_editor_button_width = ImGui::GetItemRectSize().x;\n    }\n}\n\nvoid WindowSettings::UpdatePageProfiles()\n{\n    ImGuiStyle& style = ImGui::GetStyle();\n\n    static int list_id = -1;\n    static bool scroll_to_selection        = false;\n    static bool used_profile_save_new_page = false;\n    static bool delete_confirm_state       = false;\n    static bool has_loading_failed         = false;\n    static bool has_deletion_failed        = false;\n    static float list_buttons_width        = 0.0f;\n    const bool is_root_page = (m_PageStack[0] == wndsettings_page_profiles);\n\n    if (m_PageAppearing == wndsettings_page_profiles)\n    {\n        //Load profile list\n        m_ProfileList = ConfigManager::Get().GetOverlayProfileList();\n        list_id = -1;\n        m_ProfileSelectionName = \"\";\n        delete_confirm_state = false;\n        has_deletion_failed = false;\n\n        UIManager::Get()->RepeatFrame();\n    }\n    else if ( (used_profile_save_new_page) && (m_PageStack[m_PageStackPos] == wndsettings_page_profiles) ) //Find m_ProfileSelectionName profile after returning from saving profile\n    {\n        const auto it = std::find(m_ProfileList.begin(), m_ProfileList.end(), m_ProfileSelectionName);\n\n        if (it != m_ProfileList.end())\n        {\n            list_id = (int)std::distance(m_ProfileList.begin(), it);\n        }\n        else\n        {\n            list_id = (int)m_ProfileList.size() - 1;\n            m_ProfileSelectionName = \"\";\n        }\n\n        scroll_to_selection = true;\n        used_profile_save_new_page = false;\n    }\n\n    if ((m_PageAppearing == wndsettings_page_profiles) || (m_CachedSizes.Profiles_ButtonDeleteSize.x == 0.0f))\n    {\n        //Figure out size for delete button. We need it to stay the same but also consider the case of the confirm text being longer in some languages\n        ImVec2 text_size_delete  = ImGui::CalcTextSize(TranslationManager::GetString(tstr_SettingsProfilesOverlaysProfileDelete),        nullptr, true);\n        ImVec2 text_size_confirm = ImGui::CalcTextSize(TranslationManager::GetString(tstr_SettingsProfilesOverlaysProfileDeleteConfirm), nullptr, true);\n        m_CachedSizes.Profiles_ButtonDeleteSize = (text_size_delete.x > text_size_confirm.x) ? text_size_delete : text_size_confirm;\n\n        m_CachedSizes.Profiles_ButtonDeleteSize.x += style.FramePadding.x * 2.0f;\n        m_CachedSizes.Profiles_ButtonDeleteSize.y += style.FramePadding.y * 2.0f;\n\n        UIManager::Get()->RepeatFrame();\n    }\n\n    bool focus_add_button = false;\n\n    ImGui::TextColoredUnformatted(ImGui::GetStyleColorVec4(ImGuiCol_ButtonHovered), TranslationManager::GetString(tstr_SettingsProfilesOverlaysHeader) );\n\n    //Show errors up here when used as root page since there's no space elsewhere\n    if ( (is_root_page) && ( (has_loading_failed) || (has_deletion_failed) ) )\n    {\n        ImGui::SameLine();\n\n        ImGui::PushStyleColor(ImGuiCol_Text, Style_ImGuiCol_TextError);\n        ImGui::TextRightUnformatted(0.0f, 0.0f, TranslationManager::GetString((has_loading_failed) ? tstr_SettingsProfilesOverlaysProfileFailedLoad : tstr_SettingsProfilesOverlaysProfileFailedDelete));\n        ImGui::PopStyleColor();\n    }\n\n    ImGui::Indent();\n\n    ImGui::SetNextItemWidth(-1.0f);\n    const float item_height = ImGui::GetFontSize() + style.ItemSpacing.y;\n    const float inner_padding = style.FramePadding.y + style.FramePadding.y + style.ItemInnerSpacing.y;\n    const float item_count = (UIManager::Get()->IsInDesktopMode()) ? ( (is_root_page) ? 22.0f : 20.0f ) : 16.0f;\n    ImGui::BeginChild(\"ProfileList\", ImVec2(0.0f, (item_height * item_count) + inner_padding - m_WarningHeight), true);\n\n    //List profiles\n    int index = 0;\n    for (const auto& name : m_ProfileList)\n    {\n        ImGui::PushID(index);\n        if (ImGui::Selectable(name.c_str(), (index == list_id)))\n        {\n            list_id = index;\n            m_ProfileSelectionName = (list_id == m_ProfileList.size() - 1) ? \"\" : m_ProfileList[list_id];\n\n            delete_confirm_state = false;\n            focus_add_button = ImGui::GetIO().NavVisible;\n        }\n        ImGui::PopID();\n\n        if ( (scroll_to_selection) && (index == list_id) )\n        {\n            ImGui::SetScrollHereY();\n\n            if (ImGui::IsItemVisible())\n            {\n                scroll_to_selection = false;\n            }\n        }\n\n        index++;\n    }\n\n    ImGui::EndChild();\n\n    const bool is_none  = (list_id == -1);\n    const bool is_first = (list_id == 0);\n    const bool is_last  = (list_id == m_ProfileList.size() - 1);\n\n    ImGui::SetCursorPosX(ImGui::GetCursorPosX() + ImGui::GetContentRegionAvail().x - list_buttons_width);\n\n    ImGui::BeginGroup();\n\n    if ( (is_last) || (is_none) )\n        ImGui::PushItemDisabled();\n\n    if ( (is_first) && (focus_add_button) )\n        ImGui::SetKeyboardFocusHere();\n\n    if (ImGui::Button(TranslationManager::GetString(tstr_SettingsProfilesOverlaysProfileLoad)))\n    {\n        has_loading_failed = false;\n\n        if (list_id == 0)\n        {\n            ConfigManager::Get().LoadOverlayProfileDefault(true);\n\n            //Tell dashboard app to load the profile as well\n            IPCManager::Get().SendStringToDashboardApp(configid_str_state_profile_name_load, m_ProfileSelectionName, UIManager::Get()->GetWindowHandle());\n            IPCManager::Get().PostMessageToDashboardApp(ipcmsg_action, ipcact_overlay_profile_load, MAKELPARAM(ipcactv_ovrl_profile_multi, -2));\n        }\n        else\n        {\n            has_loading_failed = !ConfigManager::Get().LoadMultiOverlayProfileFromFile(m_ProfileSelectionName + \".ini\");\n\n            if (!has_loading_failed)\n            {\n                //Tell dashboard app to load the profile as well\n                IPCManager::Get().SendStringToDashboardApp(configid_str_state_profile_name_load, m_ProfileSelectionName, UIManager::Get()->GetWindowHandle());\n                IPCManager::Get().PostMessageToDashboardApp(ipcmsg_action, ipcact_overlay_profile_load, ipcactv_ovrl_profile_multi);\n            }\n        }\n\n        if (!has_loading_failed)\n        {\n            UIManager::Get()->OnProfileLoaded();\n        }\n\n        delete_confirm_state = false;\n        has_deletion_failed  = false;\n    }\n\n    ImGui::SameLine();\n\n    if ( (is_last) || (is_none) )\n        ImGui::PopItemDisabled();\n\n    if ( (is_first) || (is_last) || (is_none) )\n        ImGui::PushItemDisabled();\n\n    if ( (!is_first) && (focus_add_button) )\n        ImGui::SetKeyboardFocusHere();\n\n    if (ImGui::Button(TranslationManager::GetString(tstr_SettingsProfilesOverlaysProfileAdd)))\n    {\n        m_ProfileOverlaySelectIsSaving = false;\n        PageGoForward(wndsettings_page_profiles_overlay_select);\n\n        delete_confirm_state = false;\n        has_loading_failed   = false;\n        has_deletion_failed  = false;\n    }\n\n    ImGui::SameLine();\n\n    if ( (is_first) || (is_last) || (is_none) )\n        ImGui::PopItemDisabled();\n\n    if ( (is_first) || (OverlayManager::Get().GetOverlayCount() == 0) )\n        ImGui::PushItemDisabled();\n\n    if (ImGui::Button(TranslationManager::GetString(tstr_SettingsProfilesOverlaysProfileSave)))\n    {\n        m_ProfileOverlaySelectIsSaving = true;\n        PageGoForward(wndsettings_page_profiles_overlay_select);\n\n        if (is_last)\n        {\n            used_profile_save_new_page = true; //Refresh list ID when returning to this page later\n        }\n\n        delete_confirm_state = false;\n        has_loading_failed   = false;\n        has_deletion_failed  = false;\n    }\n\n    if ( (is_first) || (OverlayManager::Get().GetOverlayCount() == 0) )\n        ImGui::PopItemDisabled();\n\n    ImGui::SameLine();\n\n    if ( (is_first) || (is_last) || (is_none) )\n        ImGui::PushItemDisabled();\n\n    if (delete_confirm_state)\n    {\n        if (ImGui::Button(TranslationManager::GetString(tstr_SettingsProfilesOverlaysProfileDeleteConfirm), m_CachedSizes.Profiles_ButtonDeleteSize))\n        {\n            has_deletion_failed = !ConfigManager::Get().DeleteOverlayProfile(m_ProfileSelectionName + \".ini\");\n\n            if (!has_deletion_failed)\n            {\n                m_ProfileList = ConfigManager::Get().GetOverlayProfileList();\n                list_id--;\n                m_ProfileSelectionName = m_ProfileList[list_id];\n\n                UIManager::Get()->RepeatFrame();\n            }\n\n            has_loading_failed   = false;\n            delete_confirm_state = false;\n        }\n    }\n    else\n    {\n        if (ImGui::Button(TranslationManager::GetString(tstr_SettingsProfilesOverlaysProfileDelete), m_CachedSizes.Profiles_ButtonDeleteSize))\n        {\n            delete_confirm_state = true;\n        }\n    }\n\n    if ( (is_first) || (is_last) || (is_none) )\n        ImGui::PopItemDisabled();\n\n    ImGui::EndGroup();\n\n    list_buttons_width = ImGui::GetItemRectSize().x + style.IndentSpacing;\n\n    ImGui::Unindent();\n\n    ImGui::SetCursorPosY( ImGui::GetCursorPosY() + (ImGui::GetContentRegionAvail().y - ImGui::GetFrameHeightWithSpacing()) );\n\n    //Confirmation buttons (don't show when used as root page)\n    if (!is_root_page)\n    {\n        ImGui::Separator();\n\n        if (ImGui::Button(TranslationManager::GetString(tstr_DialogDone))) \n        {\n            PageGoBack();\n        }\n\n        if ( (has_loading_failed) || (has_deletion_failed) )\n        {\n            ImGui::SameLine();\n\n            ImGui::AlignTextToFramePadding();\n            ImGui::PushStyleColor(ImGuiCol_Text, Style_ImGuiCol_TextError);\n            ImGui::TextRightUnformatted(0.0f, 0.0f, TranslationManager::GetString((has_loading_failed) ? tstr_SettingsProfilesOverlaysProfileFailedLoad : tstr_SettingsProfilesOverlaysProfileFailedDelete));\n            ImGui::PopStyleColor();\n        }\n    }\n}\n\nvoid WindowSettings::UpdatePageProfilesOverlaySelect()\n{\n    ImGuiStyle& style = ImGui::GetStyle();\n\n    bool skip_selection = false;\n    static std::vector<std::wstring> list_profiles_w;\n    static std::vector< std::pair<std::string, OverlayOrigin> > list_overlays;\n    static std::vector<char> list_overlays_ticked;\n    static char buffer_profile_name[256] = \"\";\n    static bool pending_input_focus      = false;\n    static int appearing_framecount      = 0;\n    static bool is_any_ticked            = true;\n    static bool is_name_readonly         = true;\n    static bool is_name_taken            = false;\n    static bool is_name_blank            = false;\n    static bool has_saving_failed        = false;\n    static float list_buttons_width      = 0.0f;\n\n    auto check_profile_name_taken = [](const wchar_t* profile_name) \n                                    { \n                                       auto it = std::find_if(list_profiles_w.begin(), list_profiles_w.end(), \n                                                              [&profile_name](const auto& list_entry){ return (::StrCmpIW(profile_name, list_entry.c_str()) == 0); });\n                                    \n                                       return (it != list_profiles_w.end());\n                                    };\n    \n    if (m_PageAppearing == wndsettings_page_profiles_overlay_select)\n    {\n        appearing_framecount = ImGui::GetFrameCount();\n\n        //Load overlay list\n        if (m_ProfileOverlaySelectIsSaving)\n        {\n            //We also keep a list of profile names as wide strings to allow for case-insensitive comparisions through the WinAPI\n            list_profiles_w.clear();\n            for (const auto& name : m_ProfileList)\n            {\n                list_profiles_w.push_back(WStringConvertFromUTF8(name.c_str()));\n            }\n\n            list_overlays.clear();\n            for (unsigned int i = 0; i < OverlayManager::Get().GetOverlayCount(); ++i)\n            {\n                const OverlayConfigData& data = OverlayManager::Get().GetConfigData(i);\n                list_overlays.push_back( std::make_pair(data.ConfigNameStr, (OverlayOrigin)data.ConfigInt[configid_int_overlay_origin]) );\n            }\n\n            //Only do this part if the page is appearing from a page change, not window appearing again\n            if (!m_IsWindowAppearing)\n            {\n                is_name_readonly = !m_ProfileSelectionName.empty();\n\n                //Generate name if empty\n                if (m_ProfileSelectionName.empty())\n                {\n                    std::wstring new_profile_name_w_base = WStringConvertFromUTF8(TranslationManager::GetString(tstr_SettingsProfilesOverlaysNameNewBase));\n                    std::wstring new_profile_name_w;\n                    int i = 0;\n\n                    //Sanity check translation string for ID placeholder to avoid infinite loop\n                    if (new_profile_name_w_base.find(L\"%ID%\") == std::wstring::npos)\n                    {\n                        new_profile_name_w_base = L\"Profile %ID%\";\n                    }\n\n                    //Default to higher profile number if normal is already taken\n                    auto it = list_profiles_w.end();\n\n                    do\n                    {\n                        ++i;\n                        new_profile_name_w = new_profile_name_w_base;\n                        WStringReplaceAll(new_profile_name_w, L\"%ID%\", std::to_wstring(i));\n                    }\n                    while ( check_profile_name_taken(new_profile_name_w.c_str()) );\n\n                    m_ProfileSelectionName = StringConvertFromUTF16(new_profile_name_w.c_str());\n                }\n\n                //Set profile name buffer\n                size_t copied_length = m_ProfileSelectionName.copy(buffer_profile_name, IM_ARRAYSIZE(buffer_profile_name) - 1);\n                buffer_profile_name[copied_length] = '\\0';\n\n                //Focus InputText once as soon as we can (needs to be not clipped)\n                pending_input_focus = !is_name_readonly;\n            }\n        }\n        else\n        {\n            list_overlays = ConfigManager::Get().GetOverlayProfileOverlayNameList(m_ProfileSelectionName + \".ini\");\n\n            //Skip selection if there's only one overlay to choose from anyways\n            if (list_overlays.size() == 1)\n            {\n                skip_selection = true;\n            }\n            else\n            {\n                //Check overlay list for names with unmapped characters\n                for (const auto& pair : list_overlays)\n                {\n                    UIManager::Get()->AddFontBuilderStringIfAnyUnmappedCharacters(pair.first.c_str());\n                }\n            }\n        }\n\n        //Only clear if page changed\n        if (!m_IsWindowAppearing)\n        {\n            list_overlays_ticked.clear();\n        }\n\n        list_overlays_ticked.resize(list_overlays.size(), 1);\n\n        //If we're preserving the selections we need to check ticked state\n        if (m_IsWindowAppearing)\n        {\n            is_any_ticked = false;\n            for (auto is_ticked : list_overlays_ticked)\n            {\n                if (is_ticked != 0)\n                {\n                    is_any_ticked = true;\n                    break;\n                }\n            }\n        }\n        else\n        {\n            is_any_ticked = !list_overlays.empty();\n        }\n\n        is_name_taken = false;\n        is_name_blank = false;\n        has_saving_failed = false;\n\n        UIManager::Get()->RepeatFrame();\n    }\n\n    if (m_ProfileOverlaySelectIsSaving)\n    {\n        ImGui::TextColoredUnformatted(ImGui::GetStyleColorVec4(ImGuiCol_ButtonHovered), TranslationManager::GetString(tstr_SettingsProfilesOverlaysProfileSaveSelectHeader));\n        ImGui::Columns(2, \"ColumnProfileName\", false);\n        ImGui::SetColumnWidth(0, m_Column0Width);\n\n        ImGui::AlignTextToFramePadding();\n        ImGui::TextUnformatted(TranslationManager::GetString(tstr_SettingsProfilesOverlaysProfileSaveSelectName));\n\n        if (is_name_taken)\n        {\n            ImGui::SameLine(0.0f, style.ItemInnerSpacing.x);\n            HelpMarker(TranslationManager::GetString(tstr_SettingsProfilesOverlaysProfileSaveSelectNameErrorTaken), \"(!)\");\n        }\n        else if (is_name_blank)\n        {\n            ImGui::SameLine(0.0f, style.ItemInnerSpacing.x);\n            HelpMarker(TranslationManager::GetString(tstr_SettingsProfilesOverlaysProfileSaveSelectNameErrorBlank), \"(!)\");\n        }\n\n        ImGui::NextColumn();\n\n        VRKeyboard& vr_keyboard = UIManager::Get()->GetVRKeyboard();\n\n        if (is_name_readonly)\n            ImGui::PushItemDisabled();\n\n        ImGui::PushItemWidth(-1.0f);\n        ImGui::PushID(appearing_framecount);  //The idea is to have ImGui treat this as a new widget every time the page is opened, so the cursor position isn't remembered between page switches\n        vr_keyboard.VRKeyboardInputBegin(\"##InputProfileName\");\n        if (ImGui::InputText(\"##InputProfileName\", buffer_profile_name, IM_ARRAYSIZE(buffer_profile_name), ImGuiInputTextFlags_CallbackCharFilter,\n                                                                                                           [](ImGuiInputTextCallbackData* data)\n                                                                                                           {\n                                                                                                               return (int)IsWCharInvalidForFileName(data->EventChar);\n                                                                                                           }\n           ))\n        {\n            std::wstring wstr = WStringConvertFromUTF8(buffer_profile_name);\n            is_name_taken = check_profile_name_taken(wstr.c_str());\n            is_name_blank = wstr.empty();\n\n            //Check input for unmapped character\n            //This isn't ideal as it'll collect builder strings over time, but assuming there won't be too many of those in a session it's alright\n            UIManager::Get()->AddFontBuilderStringIfAnyUnmappedCharacters(buffer_profile_name);\n        }\n        vr_keyboard.VRKeyboardInputEnd();\n        ImGui::PopID();\n\n        if ( (pending_input_focus) && (ImGui::IsItemVisible()) && (m_PageAnimationDir == 0) )\n        {\n            ImGui::SetKeyboardFocusHere(-1);\n            pending_input_focus = false;\n        }\n\n        if (is_name_readonly)\n            ImGui::PopItemDisabled();\n\n        ImGui::Columns(1);\n    }\n\n    ImGui::TextColoredUnformatted(ImGui::GetStyleColorVec4(ImGuiCol_ButtonHovered), \n                                  TranslationManager::GetString((m_ProfileOverlaySelectIsSaving) ? tstr_SettingsProfilesOverlaysProfileSaveSelectHeaderList : \n                                                                                                   tstr_SettingsProfilesOverlaysProfileAddSelectHeader       ) ); \n    ImGui::Indent();\n\n    //Shouldn't really happen, but display error if there are no overlays\n    if (list_overlays.size() == 0)\n    {\n        ImGui::TextUnformatted(TranslationManager::GetString(tstr_SettingsProfilesOverlaysProfileAddSelectEmpty));\n    }\n    else\n    {\n        ImGui::SetNextItemWidth(-1.0f);\n        const float item_height = ImGui::GetFrameHeight() + style.ItemSpacing.y;\n        const float inner_padding = style.FramePadding.y + style.ItemInnerSpacing.y;\n        float item_count = (m_ProfileOverlaySelectIsSaving) ? 12.0f : 14.0f;\n\n        if (UIManager::Get()->IsInDesktopMode())\n        {\n            item_count +=  2.0f;\n        }\n\n        ImGui::BeginChild(\"OverlayList\", ImVec2(0.0f, (item_height * item_count) + inner_padding - m_WarningHeight), true);\n\n        //Reset scroll when appearing\n        if (m_PageAppearing == wndsettings_page_profiles_overlay_select)\n        {\n            ImGui::SetScrollY(0.0f);\n        }\n\n        //List overlays\n        const float cursor_x_past_checkbox = ImGui::GetCursorPosX() + ImGui::GetFrameHeightWithSpacing();\n        ImVec2 img_size_line_height = {ImGui::GetTextLineHeight(), ImGui::GetTextLineHeight()};\n        ImVec2 img_size, img_uv_min, img_uv_max;\n\n        static unsigned int hovered_overlay_id_last = k_ulOverlayID_None;\n        unsigned int hovered_overlay_id = k_ulOverlayID_None;\n        \n        int index = 0;\n        for (const auto& pair : list_overlays)\n        {\n            ImGui::PushID(index);\n\n            //We're using a trick here to extend the checkbox interaction space to the end of the child window\n            //Checkbox() uses the item inner spacing if the label is not blank, so we increase that and use a space label\n            //Below we render a custom label with icon in front of it after adjusting the cursor position to where it normally would be\n            ImGui::PushStyleVar(ImGuiStyleVar_ItemInnerSpacing, {ImGui::GetContentRegionAvail().x, style.ItemInnerSpacing.y});\n\n            if (ImGui::Checkbox(\" \", (bool*)&list_overlays_ticked[index]))\n            {\n                //Update any ticked status\n                is_any_ticked = false;\n                for (auto is_ticked : list_overlays_ticked)\n                {\n                    if (is_ticked != 0)\n                    {\n                        is_any_ticked = true;\n                        break;\n                    }\n                }\n            }\n\n            ImGui::PopStyleVar();\n\n            if (ImGui::IsItemVisible())\n            {\n                if ( (m_ProfileOverlaySelectIsSaving) && (ImGui::IsItemHovered()) )\n                {\n                    hovered_overlay_id = (unsigned int)index;\n                }\n\n                //Adjust cursor position to be after the checkbox + offset for icon\n                ImGui::SameLine();\n                float text_y = ImGui::GetCursorPosY();\n                ImGui::SetCursorPos({cursor_x_past_checkbox, text_y + style.FramePadding.y});\n\n                //Origin icon\n                TextureManager::Get().GetTextureInfo((TMNGRTexID)(tmtex_icon_xsmall_origin_room + pair.second), img_size, img_uv_min, img_uv_max);\n                ImGui::Image(ImGui::GetIO().Fonts->TexID, img_size_line_height, img_uv_min, img_uv_max);\n\n                //Checkbox label\n                ImGui::SameLine(0.0f, style.ItemInnerSpacing.x);\n                ImGui::SetCursorPosY(text_y);\n\n                ImGui::TextUnformatted(pair.first.c_str());\n            }\n\n            ImGui::PopID();\n\n            index++;\n        }\n\n        ImGui::EndChild();\n\n        ImGui::SetCursorPosX(ImGui::GetCursorPosX() + ImGui::GetContentRegionAvail().x - list_buttons_width);\n\n        ImGui::BeginGroup();\n\n        if (ImGui::Button(TranslationManager::GetString(tstr_SettingsProfilesOverlaysProfileAddSelectAll)))\n        {\n            std::fill(list_overlays_ticked.begin(), list_overlays_ticked.end(), 1);\n            is_any_ticked = true;\n        }\n\n        ImGui::SameLine();\n\n        if (ImGui::Button(TranslationManager::GetString(tstr_SettingsProfilesOverlaysProfileAddSelectNone)))\n        {\n            std::fill(list_overlays_ticked.begin(), list_overlays_ticked.end(), 0);\n            is_any_ticked = false;\n        }\n        ImGui::EndGroup();\n\n        list_buttons_width = ImGui::GetItemRectSize().x + style.IndentSpacing;\n\n        //Set overlay highlight if saving\n        if ( (m_ProfileOverlaySelectIsSaving) && (hovered_overlay_id_last != hovered_overlay_id) )\n        {\n            UIManager::Get()->HighlightOverlay(hovered_overlay_id);\n            hovered_overlay_id_last = hovered_overlay_id;\n        }\n    }\n\n    ImGui::Unindent();\n\n    ImGui::SetCursorPosY( ImGui::GetCursorPosY() + (ImGui::GetContentRegionAvail().y - ImGui::GetFrameHeightWithSpacing()) );\n\n    //Confirmation buttons\n    ImGui::Separator();\n\n    const bool disable_confirm_button = ((!is_any_ticked) || (is_name_taken) || (is_name_blank));\n\n    if (disable_confirm_button)\n        ImGui::PushItemDisabled();\n\n    if (m_ProfileOverlaySelectIsSaving)\n    {\n        if (ImGui::Button(TranslationManager::GetString(tstr_SettingsProfilesOverlaysProfileSaveSelectDo)))   //Save profile\n        {\n            m_ProfileSelectionName = buffer_profile_name;\n            has_saving_failed = !ConfigManager::Get().SaveMultiOverlayProfileToFile(m_ProfileSelectionName + \".ini\", &list_overlays_ticked);\n\n            if (!has_saving_failed)\n            {\n                m_ProfileList = ConfigManager::Get().GetOverlayProfileList();\n                PageGoBack();\n            }\n        }\n    }\n    else\n    {\n        if ( (ImGui::Button(TranslationManager::GetString(tstr_SettingsProfilesOverlaysProfileAddSelectDo))) || (skip_selection) )  //Add selected overlays from profile\n        {\n            ConfigManager::Get().LoadMultiOverlayProfileFromFile(m_ProfileSelectionName + \".ini\", false, &list_overlays_ticked);\n\n            //Tell dashboard app to load the profile as well\n            IPCManager::Get().SendStringToDashboardApp(configid_str_state_profile_name_load, m_ProfileSelectionName, UIManager::Get()->GetWindowHandle());\n\n            for (int i = 0; i < list_overlays_ticked.size(); ++i)  //Send each ticked overlay's ID to queue it up\n            {\n                if (list_overlays_ticked[i] == 1)\n                {\n                    IPCManager::Get().PostMessageToDashboardApp(ipcmsg_action, ipcact_overlay_profile_load, MAKELPARAM(ipcactv_ovrl_profile_multi_add, i) );\n                }\n            }\n\n            IPCManager::Get().PostMessageToDashboardApp(ipcmsg_action, ipcact_overlay_profile_load, MAKELPARAM(ipcactv_ovrl_profile_multi_add, -1) );       //Add queued overlays\n\n            (skip_selection) ? PageGoBackInstantly() : PageGoBack();\n            UIManager::Get()->RepeatFrame();\n        }\n    }\n\n    if (disable_confirm_button)\n        ImGui::PopItemDisabled();\n\n    ImGui::SameLine();\n\n    if (ImGui::Button(TranslationManager::GetString(tstr_DialogCancel))) \n    {\n        PageGoBack();\n    }\n\n    if (has_saving_failed)\n    {\n        ImGui::SameLine();\n\n        ImGui::AlignTextToFramePadding();\n        ImGui::PushStyleColor(ImGuiCol_Text, Style_ImGuiCol_TextError);\n        ImGui::TextRightUnformatted(0.0f, 0.0f, TranslationManager::GetString(tstr_SettingsProfilesOverlaysProfileSaveSelectDoFailed));\n        ImGui::PopStyleColor();\n    }\n}\n\nvoid WindowSettings::UpdatePageAppProfiles()\n{\n    ImGuiStyle& style = ImGui::GetStyle();\n    AppProfileManager& app_profiles = ConfigManager::Get().GetAppProfileManager();\n\n    static int list_id = 0;\n    static AppProfile app_profile_selected_edit;        //Temporary copy of the selected profile for editing\n    static bool is_action_picker_for_leave = false;\n    static bool delete_confirm_state       = false;\n    static bool delete_disabled            = false;     //No actually reason to do this but to avoid user confusion from allowing to delete the same profile over and over\n    static float delete_button_width       = 0.0f;\n    static float active_header_text_width  = 0.0f;\n    static ImVec2 no_apps_text_size;\n    const bool is_root_page = (m_PageStack[0] == wndsettings_page_app_profiles);\n\n    if (m_PageAppearing == wndsettings_page_app_profiles)\n    {\n        RefreshAppList();\n\n        list_id = 0;\n        app_profile_selected_edit = app_profiles.GetProfile((m_AppList.empty()) ? \"\" : m_AppList[0].first);\n        delete_confirm_state = false;\n        delete_disabled = false;\n\n        UIManager::Get()->RepeatFrame();\n    }\n    \n    if (list_id >= m_AppList.size())\n    {\n        list_id = (m_AppList.empty()) ? -1 : 0;\n        app_profile_selected_edit = {};\n    }\n\n    if ((m_PageAppearing == wndsettings_page_app_profiles) || (m_CachedSizes.Profiles_ButtonDeleteSize.x == 0.0f))\n    {\n        //Figure out size for delete button. We need it to stay the same but also consider the case of the confirm text being longer in some languages\n        ImVec2 text_size_delete  = ImGui::CalcTextSize(TranslationManager::GetString(tstr_SettingsProfilesOverlaysProfileDelete),        nullptr, true);\n        ImVec2 text_size_confirm = ImGui::CalcTextSize(TranslationManager::GetString(tstr_SettingsProfilesOverlaysProfileDeleteConfirm), nullptr, true);\n        m_CachedSizes.Profiles_ButtonDeleteSize = (text_size_delete.x > text_size_confirm.x) ? text_size_delete : text_size_confirm;\n\n        m_CachedSizes.Profiles_ButtonDeleteSize.x += style.FramePadding.x * 2.0f;\n        m_CachedSizes.Profiles_ButtonDeleteSize.y += style.FramePadding.y * 2.0f;\n\n        UIManager::Get()->RepeatFrame();\n    }\n\n    ImGui::TextColoredUnformatted(ImGui::GetStyleColorVec4(ImGuiCol_ButtonHovered), TranslationManager::GetString(tstr_SettingsProfilesAppsHeader) ); \n\n    if (!UIManager::Get()->IsOpenVRLoaded())\n    {\n        ImGui::SameLine(0.0f, style.ItemInnerSpacing.x);\n        HelpMarker(TranslationManager::GetString(tstr_SettingsProfilesAppsHeaderNoVRTip));\n    }\n\n    ImGui::Indent();\n\n    ImGui::SetNextItemWidth(-1.0f);\n    const float item_height = ImGui::GetFontSize() + style.ItemSpacing.y;\n    const float inner_padding = style.FramePadding.y + style.FramePadding.y + style.ItemInnerSpacing.y;\n    const float item_count = (UIManager::Get()->IsInDesktopMode()) ? ( (is_root_page) ? 16.0f : 15.0f ) : 11.0f;\n    ImGui::BeginChild(\"AppList\", ImVec2(0.0f, (item_height * item_count) + inner_padding - m_WarningHeight), true);\n\n    //Reset scroll when appearing\n    if (m_PageAppearing == wndsettings_page_app_profiles)\n    {\n        ImGui::SetScrollY(0.0f);\n    }\n\n    bool focus_app_section = false;\n\n    //List applications\n    const bool is_any_app_profile_active = !app_profiles.GetActiveProfileAppKey().empty();\n    const AppProfile& app_profile_active = app_profiles.GetProfile(app_profiles.GetActiveProfileAppKey());\n\n    //Use list clipper to minimize GetProfile() lookups\n    ImGuiListClipper list_clipper;\n    list_clipper.Begin((int)m_AppList.size());\n    while (list_clipper.Step())\n    {\n        for (int i = list_clipper.DisplayStart; i < list_clipper.DisplayEnd; ++i)\n        {\n            const AppProfile& app_profile = app_profiles.GetProfile(m_AppList[i].first);\n            const bool is_active_profile = ((is_any_app_profile_active) && (&app_profile == &app_profile_active));\n\n            if (!app_profile.IsEnabled)\n                ImGui::PushStyleVar(ImGuiStyleVar_Alpha, 0.5f);\n\n            if (is_active_profile)\n                ImGui::PushStyleColor(ImGuiCol_Text, Style_ImGuiCol_TextNotification);\n\n            ImGui::PushID(i);\n            if (ImGui::Selectable(m_AppList[i].second.c_str(), (i == list_id)))\n            {\n                list_id = i;\n                app_profile_selected_edit = app_profile;\n\n                delete_confirm_state = false;\n                delete_disabled = !app_profiles.ProfileExists(m_AppList[i].first);\n                focus_app_section = ImGui::GetIO().NavVisible;\n            }\n            ImGui::PopID();\n\n            if (is_active_profile)\n                ImGui::PopStyleColor();\n\n            if (!app_profile.IsEnabled)\n                ImGui::PopStyleVar();\n        }\n    }\n\n    if (m_AppList.empty())\n    {\n        ImGui::SetCursorPosX(ImGui::GetCursorPosX() + ImGui::GetContentRegionAvail().x / 2.0f - (no_apps_text_size.x / 2.0f));\n        ImGui::SetCursorPosY(ImGui::GetCursorPosY() + ImGui::GetContentRegionAvail().y / 2.0f - (no_apps_text_size.y / 2.0f));\n\n        ImGui::TextUnformatted(TranslationManager::GetString(tstr_SettingsProfilesAppsListEmpty));\n        no_apps_text_size = ImGui::GetItemRectSize();\n    }\n\n    ImGui::EndChild();\n    ImGui::Unindent();\n\n    const bool is_none  = (list_id == -1); //Would be rare, as we don't allow no selection unless the app list is really empty, but still handle that\n    const bool is_first = (list_id == 0);\n    const bool is_last  = (list_id == m_AppList.size() - 1);\n\n    if (!is_none)\n    {\n        const std::string& app_key_selected  = m_AppList[list_id].first;\n        const std::string& app_name_selected = m_AppList[list_id].second;\n        const bool is_active_profile = ((is_any_app_profile_active) && (app_key_selected == app_profiles.GetActiveProfileAppKey()));\n\n        static bool store_profile_changes = false;\n\n        if (m_PageReturned == wndsettings_page_profile_picker)\n        {\n            store_profile_changes = (app_profile_selected_edit.OverlayProfileFileName != m_ProfilePickerName);\n            app_profile_selected_edit.OverlayProfileFileName = m_ProfilePickerName;\n\n            m_PageReturned = wndsettings_page_none;\n        }\n        else if (m_PageReturned == wndsettings_page_action_picker)\n        {\n            if (is_action_picker_for_leave)\n            {\n                store_profile_changes = (app_profile_selected_edit.ActionUIDLeave != m_ActionPickerUID);\n                app_profile_selected_edit.ActionUIDLeave = m_ActionPickerUID;\n            }\n            else\n            {\n                store_profile_changes = (app_profile_selected_edit.ActionUIDEnter != m_ActionPickerUID);\n                app_profile_selected_edit.ActionUIDEnter = m_ActionPickerUID;\n            }\n\n            m_PageReturned = wndsettings_page_none;\n        }\n\n        ImGui::TextColoredUnformatted(ImGui::GetStyleColorVec4(ImGuiCol_ButtonHovered), app_name_selected.c_str());\n\n        if (is_active_profile)\n        {\n            ImGui::SameLine();\n            ImGui::TextColoredUnformatted(Style_ImGuiCol_TextNotification, TranslationManager::GetString(tstr_SettingsProfilesAppsProfileHeaderActive));\n\n            active_header_text_width = ImGui::GetItemRectSize().x;\n        }\n\n        if (focus_app_section)\n        {\n            ImGui::SetKeyboardFocusHere();\n        }\n\n        ImGui::Indent();\n        if (ImGui::Checkbox(TranslationManager::GetString(tstr_SettingsProfilesAppsProfileEnabled), &app_profile_selected_edit.IsEnabled))\n        {\n            store_profile_changes = true;\n        }\n        ImGui::Unindent();\n\n        ImGui::Columns(2, \"ColumnAppProfile\", false);\n        ImGui::SetColumnWidth(0, m_Column0Width);\n        \n        ImGui::AlignTextToFramePadding();\n        ImGui::TextUnformatted(TranslationManager::GetString(tstr_SettingsProfilesAppsProfileOverlayProfile));\n        ImGui::NextColumn();\n\n        const char* overlay_profile_button_label = (app_profile_selected_edit.OverlayProfileFileName.empty()) ? TranslationManager::GetString(tstr_DialogProfilePickerNone) : \n                                                                                                                app_profile_selected_edit.OverlayProfileFileName.c_str();\n\n        ImGui::PushID(\"ButtonOverlayProfile\");\n        if (ImGui::Button(overlay_profile_button_label))\n        {\n            m_ProfilePickerName = app_profile_selected_edit.OverlayProfileFileName;\n            PageGoForward(wndsettings_page_profile_picker);\n        }\n        ImGui::PopID();\n\n        ImGui::NextColumn();\n\n        const ActionManager& action_manager = ConfigManager::Get().GetActionManager();\n\n        ImGui::AlignTextToFramePadding();\n        ImGui::TextUnformatted(TranslationManager::GetString(tstr_SettingsProfilesAppsProfileActionEnter));\n        ImGui::NextColumn();\n\n        ImGui::PushID(\"ButtonActionEnter\");\n        if (ImGui::Button(action_manager.GetTranslatedName(app_profile_selected_edit.ActionUIDEnter)))\n        {\n            m_ActionPickerUID = app_profile_selected_edit.ActionUIDEnter;\n            is_action_picker_for_leave = false;\n            PageGoForward(wndsettings_page_action_picker);\n        }\n        ImGui::PopID();\n\n        ImGui::NextColumn();\n\n        ImGui::AlignTextToFramePadding();\n        ImGui::TextUnformatted(TranslationManager::GetString(tstr_SettingsProfilesAppsProfileActionLeave));\n        ImGui::NextColumn();\n\n        ImGui::PushID(\"ButtonActionLeave\");\n        if (ImGui::Button(action_manager.GetTranslatedName(app_profile_selected_edit.ActionUIDLeave)))\n        {\n            m_ActionPickerUID = app_profile_selected_edit.ActionUIDLeave;\n            is_action_picker_for_leave = true;\n            PageGoForward(wndsettings_page_action_picker);\n        }\n        ImGui::PopID();\n\n        ImGui::Columns(1);\n\n        ImGui::SetCursorPosX(ImGui::GetCursorPosX() + ImGui::GetContentRegionAvail().x - delete_button_width);\n\n        const bool delete_disabled_prev = delete_disabled;\n        if (delete_disabled_prev)\n            ImGui::PushItemDisabled();\n\n        if (delete_confirm_state)\n        {\n            if (ImGui::Button(TranslationManager::GetString(tstr_SettingsProfilesOverlaysProfileDeleteConfirm), m_CachedSizes.Profiles_ButtonDeleteSize))\n            {\n                IPCManager::Get().SendStringToDashboardApp(configid_str_state_app_profile_key, app_key_selected, UIManager::Get()->GetWindowHandle());\n                IPCManager::Get().PostMessageToDashboardApp(ipcmsg_action, ipcact_app_profile_remove);\n\n                app_profiles.RemoveProfile(app_key_selected);\n                app_profile_selected_edit = {};\n\n                delete_confirm_state = false;\n                delete_disabled = true;\n            }\n        }\n        else\n        {\n            if (ImGui::Button(TranslationManager::GetString(tstr_SettingsProfilesOverlaysProfileDelete), m_CachedSizes.Profiles_ButtonDeleteSize))\n            {\n                delete_confirm_state = true;\n            }\n        }\n\n        if (delete_disabled_prev)\n            ImGui::PopItemDisabled();\n\n        delete_button_width = ImGui::GetItemRectSize().x + style.IndentSpacing;\n\n        //Store and sync the profile if flag was set\n        if (store_profile_changes)\n        {\n            IPCManager::Get().SendStringToDashboardApp(configid_str_state_app_profile_key,  app_key_selected,                      UIManager::Get()->GetWindowHandle());\n            IPCManager::Get().SendStringToDashboardApp(configid_str_state_app_profile_data, app_profile_selected_edit.Serialize(), UIManager::Get()->GetWindowHandle());\n\n            const bool loaded_overlay_profile = app_profiles.StoreProfile(app_key_selected, app_profile_selected_edit);\n\n            if (loaded_overlay_profile)\n            {\n                UIManager::Get()->OnProfileLoaded();\n            }\n\n            store_profile_changes = false;\n            delete_disabled = false;\n        }\n    }\n\n    //Confirmation buttons (don't show when used as root page)\n    if (!is_root_page)\n    {\n        ImGui::SetCursorPosY( ImGui::GetCursorPosY() + (ImGui::GetContentRegionAvail().y - ImGui::GetFrameHeightWithSpacing()) );\n        ImGui::Separator();\n\n        if (ImGui::Button(TranslationManager::GetString(tstr_DialogDone))) \n        {\n            PageGoBack();\n        }\n    }\n}\n\nvoid WindowSettings::UpdatePageActions()\n{\n    ImGuiStyle& style = ImGui::GetStyle();\n    ImGuiIO& io = ImGui::GetIO();\n    ActionManager& action_manager = ConfigManager::Get().GetActionManager();\n\n    static int list_id = -1;\n    static bool delete_confirm_state  = false;\n    static bool scroll_to_selection   = false;\n    static int keyboard_swapped_index = -1;\n    static ActionUID hovered_action   = k_ActionUID_Invalid;\n    static float list_buttons_width   = 0.0f;\n    static ImVec2 no_actions_text_size;\n\n    const bool is_root_page = (m_PageStack[0] == wndsettings_page_actions);\n\n    if (m_PageAppearing == wndsettings_page_actions)\n    {\n        //Load action list\n        m_ActionList = ConfigManager::Get().GetActionManager().GetActionNameList();\n        list_id = -1;\n        delete_confirm_state = false;\n\n        UIManager::Get()->RepeatFrame();\n    }\n    else if (m_PageReturned == wndsettings_page_actions_edit)\n    {\n        m_ActionList = ConfigManager::Get().GetActionManager().GetActionNameList();\n\n        //Find potentially new action in the list and select it\n        auto it = std::find_if(m_ActionList.begin(), m_ActionList.end(), [&](const auto& list_entry) { return (list_entry.UID == m_ActionSelectionUID); } );\n        list_id = (it != m_ActionList.end()) ? (int)std::distance(m_ActionList.begin(), it) : -1;\n\n        m_PageReturned = wndsettings_page_none;\n    }\n\n    if ((m_PageAppearing == wndsettings_page_actions) || (m_CachedSizes.Actions_ButtonDeleteSize.x == 0.0f))\n    {\n        //Figure out size for delete button. We need it to stay the same but also consider the case of the confirm text being longer in some languages\n        ImVec2 text_size_delete  = ImGui::CalcTextSize(TranslationManager::GetString(tstr_SettingsActionsManageDelete),        nullptr, true);\n        ImVec2 text_size_confirm = ImGui::CalcTextSize(TranslationManager::GetString(tstr_SettingsActionsManageDeleteConfirm), nullptr, true);\n        m_CachedSizes.Actions_ButtonDeleteSize = (text_size_delete.x > text_size_confirm.x) ? text_size_delete : text_size_confirm;\n\n        m_CachedSizes.Actions_ButtonDeleteSize.x += style.FramePadding.x * 2.0f;\n        m_CachedSizes.Actions_ButtonDeleteSize.y += style.FramePadding.y * 2.0f;\n\n        UIManager::Get()->RepeatFrame();\n    }\n\n    bool focus_edit_button = false;\n\n    ImGui::TextColoredUnformatted(ImGui::GetStyleColorVec4(ImGuiCol_ButtonHovered), TranslationManager::GetString(tstr_SettingsActionsManageHeader)); \n    ImGui::Indent();\n\n    const float item_height = ImGui::GetFontSize() + style.ItemSpacing.y;\n    const float inner_padding = style.FramePadding.y + style.FramePadding.y + style.ItemInnerSpacing.y;\n    const float item_count = (UIManager::Get()->IsInDesktopMode()) ? ( (is_root_page) ? 22.0f : 20.0f ) : 16.0f;\n    ImGui::BeginChild(\"ActionList\", ImVec2(0.0f, (item_height * item_count) + inner_padding - m_WarningHeight), true);\n\n    //Display error if there are no actions\n    if (m_ActionList.empty())\n    {\n        ImGui::SetCursorPosX(ImGui::GetCursorPosX() + ImGui::GetContentRegionAvail().x / 2.0f - (no_actions_text_size.x / 2.0f));\n        ImGui::SetCursorPosY(ImGui::GetCursorPosY() + ImGui::GetContentRegionAvail().y / 2.0f - (no_actions_text_size.y / 2.0f));\n\n        ImGui::TextUnformatted(TranslationManager::GetString(tstr_DialogActionPickerEmpty));\n        no_actions_text_size = ImGui::GetItemRectSize();\n    }\n    else\n    {\n        //List actions\n        int index = 0;\n        for (const auto& entry : m_ActionList)\n        {\n            ImGui::PushID((void*)entry.UID);\n\n            //Set focus for nav if we previously re-ordered overlays via keyboard\n            if (keyboard_swapped_index == index)\n            {\n                ImGui::SetKeyboardFocusHere();\n\n                //Nav works against us here, so keep setting focus until ctrl isn't down anymore\n                if ((!io.KeyCtrl) || (!io.NavVisible))\n                {\n                    keyboard_swapped_index = -1;\n                }\n            }\n\n            if (ImGui::Selectable(entry.Name.c_str(), (index == list_id)))\n            {\n                list_id = index;\n                m_ActionSelectionUID = entry.UID;\n\n                delete_confirm_state = false;\n                focus_edit_button = io.NavVisible;\n            }\n\n            if ( (scroll_to_selection) && (index == list_id) )\n            {\n                ImGui::SetScrollHereY();\n\n                if (ImGui::IsItemVisible())\n                {\n                    scroll_to_selection = false;\n                }\n            }\n\n            if (ImGui::IsItemHovered())\n            {\n                hovered_action = entry.UID;\n            }\n\n            if (ImGui::IsItemVisible())\n            {\n                //Additional selectable behavior\n                bool selectable_active = ImGui::IsItemActive();\n\n                if ( (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenBlockedByActiveItem)) || ((io.NavVisible) && (ImGui::IsItemFocused())) )\n                {\n                    hovered_action = entry.UID;\n                }\n\n                if ((ImGui::IsItemClicked()) && (ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left)))\n                {\n                    PageGoForward(wndsettings_page_actions_edit);\n\n                    delete_confirm_state = false;\n                }\n\n                //Drag reordering\n                if ((ImGui::IsItemActive()) && (!ImGui::IsItemHovered()))\n                {\n                    int index_swap = index + ((ImGui::GetMouseDragDelta(ImGuiMouseButton_Left).y < 0.0f) ? -1 : 1);\n                    if ((hovered_action != entry.UID) && (index_swap >= 0) && (index_swap < m_ActionList.size()))\n                    {\n                        std::iter_swap(m_ActionList.begin() + index, m_ActionList.begin() + index_swap);\n\n                        ActionManager::ActionList ui_order = action_manager.GetActionOrderListUI();\n                        std::iter_swap(ui_order.begin() + index, ui_order.begin() + index_swap);\n                        action_manager.SetActionOrderListUI(ui_order);\n\n                        ImGui::ResetMouseDragDelta(ImGuiMouseButton_Left);\n                    }\n                }\n\n                //Keyboard reordering\n                if ((io.NavVisible) && (io.KeyCtrl) && (hovered_action == entry.UID))\n                {\n                    int index_swap = index + ((ImGui::IsNavInputPressed(ImGuiNavInput_DpadDown, true)) ? 1 : (ImGui::IsNavInputPressed(ImGuiNavInput_DpadUp, true)) ? -1 : 0);\n                    if ((index != index_swap) && (index_swap >= 0) && (index_swap < m_ActionList.size()))\n                    {\n                        std::iter_swap(m_ActionList.begin() + index, m_ActionList.begin() + index_swap);\n\n                        ActionManager::ActionList ui_order = action_manager.GetActionOrderListUI();\n                        std::iter_swap(ui_order.begin() + index, ui_order.begin() + index_swap);\n                        action_manager.SetActionOrderListUI(ui_order);\n\n                        //Skip the rest of this frame to avoid double-swaps\n                        keyboard_swapped_index = index_swap;\n                        ImGui::PopID();\n                        UIManager::Get()->RepeatFrame();\n                        break;\n                    }\n                }\n            }\n\n            ImGui::PopID();\n\n            index++;\n        }\n    }\n\n    ImGui::EndChild();\n\n    const bool is_none  = (list_id == -1);\n    const bool is_first = (list_id == 0);\n    const bool is_last  = (list_id == m_ProfileList.size() - 1);\n\n    ImGui::Indent();\n\n    if (ConfigManager::GetValue(configid_bool_interface_show_advanced_settings))\n    {\n        if (is_none)\n            ImGui::PushItemDisabled();\n\n        if (ImGui::Button(TranslationManager::GetString(tstr_SettingsActionsManageCopyUID)))\n        {\n            ImGui::SetClipboardText(std::to_string(m_ActionList[list_id].UID).c_str());\n            delete_confirm_state = false;\n        }\n\n        if (is_none)\n            ImGui::PopItemDisabled();\n\n        ImGui::SameLine();\n    }\n\n    ImGui::SetCursorPosX(ImGui::GetCursorPosX() + ImGui::GetContentRegionAvail().x - list_buttons_width);\n\n    ImGui::BeginGroup();\n\n    if (ImGui::Button(TranslationManager::GetString(tstr_SettingsActionsManageNew)))\n    {\n        m_ActionSelectionUID = 0;\n        PageGoForward(wndsettings_page_actions_edit);\n\n        delete_confirm_state = false;\n    }\n\n    ImGui::SameLine();\n\n    if (is_none)\n        ImGui::PushItemDisabled();\n\n    if (focus_edit_button)\n        ImGui::SetKeyboardFocusHere();\n\n    if (ImGui::Button(TranslationManager::GetString(tstr_SettingsActionsManageEdit)))\n    {\n        PageGoForward(wndsettings_page_actions_edit);\n\n        delete_confirm_state = false;\n    }\n\n    ImGui::SameLine();\n\n    if (ImGui::Button(TranslationManager::GetString(tstr_SettingsActionsManageDuplicate)))\n    {\n        ActionUID dup_uid = action_manager.DuplicateAction(action_manager.GetAction(m_ActionSelectionUID));\n\n        //Sync with dashboard app\n        IPCManager::Get().SendStringToDashboardApp(configid_str_state_action_data, action_manager.GetAction(dup_uid).Serialize(), UIManager::Get()->GetWindowHandle());\n\n        //Refresh list\n        m_ActionList = action_manager.GetActionNameList();\n        list_id++;\n        m_ActionSelectionUID = m_ActionList[list_id].UID;\n\n        UIManager::Get()->RepeatFrame();\n\n        delete_confirm_state = false;\n    }\n\n    ImGui::SameLine();\n\n    if (delete_confirm_state)\n    {\n        if (ImGui::Button(TranslationManager::GetString(tstr_SettingsActionsManageDeleteConfirm), m_CachedSizes.Actions_ButtonDeleteSize))\n        {\n            action_manager.RemoveAction(m_ActionSelectionUID);\n            IPCManager::Get().PostMessageToDashboardApp(ipcmsg_action, ipcact_action_delete, m_ActionSelectionUID);\n\n            m_ActionList = action_manager.GetActionNameList();\n            list_id--;\n            m_ActionSelectionUID = (list_id != -1) ? m_ActionList[list_id].UID : k_ActionUID_Invalid;\n\n            UIManager::Get()->RepeatFrame();\n\n            delete_confirm_state = false;\n        }\n    }\n    else\n    {\n        if (ImGui::Button(TranslationManager::GetString(tstr_SettingsActionsManageDelete), m_CachedSizes.Actions_ButtonDeleteSize))\n        {\n            delete_confirm_state = true;\n        }\n    }\n\n    if (is_none)\n        ImGui::PopItemDisabled();\n\n    ImGui::EndGroup();\n\n    list_buttons_width = ImGui::GetItemRectSize().x + style.IndentSpacing;\n\n    ImGui::Unindent();\n    ImGui::Unindent();\n\n    ImGui::SetCursorPosY( ImGui::GetCursorPosY() + (ImGui::GetContentRegionAvail().y - ImGui::GetFrameHeightWithSpacing()) );\n\n    //Confirmation buttons (don't show when used as root page)\n    if (!is_root_page)\n    {\n        ImGui::Separator();\n\n        if (ImGui::Button(TranslationManager::GetString(tstr_DialogDone))) \n        {\n            PageGoBack();\n        }\n    }\n}\n\nvoid WindowSettings::UpdatePageActionsEdit(bool only_restore_settings)\n{\n    struct CommandUIState\n    {\n        float header_animation_progress = 0.0f;\n        float header_2_animation_progress = 0.0f;\n        std::string header_label;\n        char buffer_str_main[1024] = \"\";\n        char buffer_str_arg[1024]  = \"\";\n        int temp_int_1 = 0;\n        int temp_int_2 = 0;\n        std::string temp_window_button_title;\n        FloatingWindowInputOverlayTagsState input_tags_state;\n    };\n\n    auto filter_newline_limit = [](ImGuiInputTextCallbackData* data)\n    {\n        static int newline_count = 0;\n        const int newline_max = 2;\n\n        if (data->EventFlag == ImGuiInputTextFlags_CallbackEdit)\n        {\n            //Iterate over buffer and count newlines\n            newline_count = 0;\n            for (char* ibuf = data->Buf, *ibuf_end = data->Buf + data->BufTextLen; ibuf != ibuf_end; ++ibuf)\n            {\n                if (*ibuf == '\\n')\n                {\n                    //Replace newlines that occur after counting to max with space character\n                    if (newline_count >= newline_max)\n                    {\n                        *ibuf = ' ';\n                        data->BufDirty = true;\n                    }\n                    else\n                    {\n                        ++newline_count;\n                    }\n                }\n            }\n        }\n        else if (data->EventFlag == ImGuiInputTextFlags_CallbackCharFilter) //Filter newlines during input if over limit\n        {\n            return (int)((data->EventChar == '\\n') && (newline_count >= newline_max));\n        }\n\n        return 0;\n    };\n\n    ImGuiStyle& style = ImGui::GetStyle();\n    ImGuiIO& io = ImGui::GetIO();\n    ActionManager& action_manager = ConfigManager::Get().GetActionManager();\n    VRKeyboard& vr_keyboard = UIManager::Get()->GetVRKeyboard();\n\n    static Action action_edit;                           //Temporary copy of the selected action for editing\n    static FloatingWindowInputOverlayTagsState input_tags_state;\n    static std::vector<CommandUIState> command_ui_states;\n    static char buffer_action_name[256]         = \"\";\n    static char buffer_action_label[1024]       = \"\";\n    static char buffer_action_target_tags[1024] = \"\";\n    static bool label_matches_name              = true;\n    static int appearing_framecount             = 0;\n    static float area_tags_animation_progress   = 0.0f;\n    static float tags_widget_height             = 0.0f;\n    static bool action_test_was_used            = false;\n    static bool delete_confirm_state            = false;\n    static float delete_button_width            = 0.0f;\n\n    if (only_restore_settings)\n    {\n        //Restore action to old state for dashboard app if it was sent over for testing\n        if (action_test_was_used)\n        {\n            if (action_manager.ActionExists(action_edit.UID))\n            {\n                IPCManager::Get().SendStringToDashboardApp(configid_str_state_action_data, action_edit.Serialize(), UIManager::Get()->GetWindowHandle());\n            }\n            else    //It shouldn't exist, so delete it on the other end\n            {\n                IPCManager::Get().PostMessageToDashboardApp(ipcmsg_action, ipcact_action_delete, action_edit.UID);\n            }\n        }\n\n        return;\n    }\n\n    bool reload_icon = false;\n\n    if (m_PageAppearing == wndsettings_page_actions_edit)\n    {\n        appearing_framecount = ImGui::GetFrameCount();\n        command_ui_states.clear();\n        action_test_was_used = false;\n        delete_confirm_state = false;\n        reload_icon = true;\n        input_tags_state.PopupShow = false;\n\n        //Make sure profile list is ready\n        m_ProfileList = ConfigManager::Get().GetOverlayProfileList();\n\n        //Create from scratch if selected UID is 0\n        if (m_ActionSelectionUID == k_ActionUID_Invalid)\n        {\n            action_edit = Action();\n            action_edit.UID = action_manager.GenerateUID();\n            action_edit.Name  = TranslationManager::GetString(tstr_SettingsActionsEditNameNew);\n            action_edit.Label = action_edit.Name;\n\n            m_ActionSelectionUID = action_edit.UID;\n        }\n        else\n        {\n            action_edit = action_manager.GetAction(m_ActionSelectionUID);\n        }\n\n        //Fill buffers from action data\n        size_t copied_length = action_edit.Name.copy(buffer_action_name, IM_ARRAYSIZE(buffer_action_name) - 1);\n        buffer_action_name[copied_length] = '\\0';\n        copied_length = action_edit.Label.copy(buffer_action_label, IM_ARRAYSIZE(buffer_action_label) - 1);\n        buffer_action_label[copied_length] = '\\0';\n        copied_length = action_edit.TargetTags.copy(buffer_action_target_tags, IM_ARRAYSIZE(buffer_action_target_tags) - 1);\n        buffer_action_target_tags[copied_length] = '\\0';\n\n        label_matches_name = (action_edit.Label == action_edit.Name);\n        area_tags_animation_progress = (action_edit.TargetUseTags) ? 1.0f : 0.0f;\n\n        UIManager::Get()->RepeatFrame();\n    }\n\n    if ((m_PageAppearing == wndsettings_page_actions_edit) || (m_CachedSizes.ActionEdit_ButtonDeleteSize.x == 0.0f))\n    {\n        //Figure out size for delete button. We need it to stay the same but also consider the case of the confirm text being longer in some languages\n        ImVec2 text_size_delete  = ImGui::CalcTextSize(TranslationManager::GetString(tstr_SettingsActionsEditCommandDelete),        nullptr, true);\n        ImVec2 text_size_confirm = ImGui::CalcTextSize(TranslationManager::GetString(tstr_SettingsActionsEditCommandDeleteConfirm), nullptr, true);\n        m_CachedSizes.ActionEdit_ButtonDeleteSize = (text_size_delete.x > text_size_confirm.x) ? text_size_delete : text_size_confirm;\n\n        m_CachedSizes.ActionEdit_ButtonDeleteSize.x += style.FramePadding.x * 2.0f;\n        m_CachedSizes.ActionEdit_ButtonDeleteSize.y += style.FramePadding.y * 2.0f;\n\n        UIManager::Get()->RepeatFrame();\n    }\n\n    if (m_PageReturned == wndsettings_page_icon_picker)\n    {\n        action_edit.IconFilename = m_IconPickerFile;\n        reload_icon = true;\n\n        m_PageReturned = wndsettings_page_none;\n    }\n\n    if ((reload_icon) && (!action_edit.IconFilename.empty()))\n    {\n        std::string icon_path = \"images/icons/\" + action_edit.IconFilename;\n        std::wstring icon_path_wstr = WStringConvertFromUTF8(icon_path.c_str());\n\n        //Avoid reloading if the path is already the same\n        if (wcscmp(icon_path_wstr.c_str(), TextureManager::Get().GetTextureFilename(tmtex_icon_temp)) != 0)\n        {\n            TextureManager::Get().SetTextureFilenameIconTemp(icon_path_wstr.c_str());\n            TextureManager::Get().ReloadAllTexturesLater();\n        }\n\n        UIManager::Get()->RepeatFrame();\n    }\n\n    ImGui::TextColoredUnformatted(ImGui::GetStyleColorVec4(ImGuiCol_ButtonHovered), TranslationManager::GetString(tstr_SettingsActionsEditHeader)); \n\n    ImGui::Columns(2, \"ColumnActionEditBase\", false);\n    ImGui::SetColumnWidth(0, m_Column0Width);\n\n    ImGui::AlignTextToFramePadding();\n    ImGui::TextUnformatted(TranslationManager::GetString(tstr_SettingsActionsEditName));\n\n    //Add a tooltip when translation ID is used to minimize confusion about the name not matching what's displayed outside this page\n    if (action_edit.NameTranslationID != tstr_NONE)\n    {\n        ImGui::SameLine(0.0f, style.ItemInnerSpacing.x);\n        HelpMarker(TranslationManager::GetString(tstr_SettingsActionsEditNameTranslatedTip));\n    }\n\n    ImGui::NextColumn();\n\n    ImGui::PushItemWidth(-1.0f);\n    ImGui::PushID(appearing_framecount);  //The idea is to have ImGui treat this as a new widget every time the page is opened, so the cursor position isn't remembered between page switches\n    vr_keyboard.VRKeyboardInputBegin(\"##InputActionName\");\n    if (ImGui::InputText(\"##InputActionName\", buffer_action_name, IM_ARRAYSIZE(buffer_action_name)))\n    {\n        UIManager::Get()->AddFontBuilderStringIfAnyUnmappedCharacters(buffer_action_name);\n\n        action_edit.Name = buffer_action_name;\n\n        //As long as label matches name, adjust label alongside it\n        if (label_matches_name)\n        {\n            action_edit.Label = action_edit.Name;\n\n            size_t copied_length = action_edit.Label.copy(buffer_action_label, IM_ARRAYSIZE(buffer_action_label) - 1);\n            buffer_action_label[copied_length] = '\\0';\n\n            action_edit.LabelTranslationID = action_manager.GetTranslationIDForName(action_edit.Label);\n        }\n\n        //Check for potential translation string\n        action_edit.NameTranslationID = action_manager.GetTranslationIDForName(action_edit.Name);\n    }\n    vr_keyboard.VRKeyboardInputEnd();\n    ImGui::PopID();\n\n    ImGui::NextColumn();\n\n    ImGui::AlignTextToFramePadding();\n    ImGui::TextUnformatted(TranslationManager::GetString(tstr_SettingsActionsEditTarget));\n    ImGui::NextColumn();\n\n    if (ImGui::RadioButton(TranslationManager::GetString(tstr_SettingsActionsEditTargetDefault), !action_edit.TargetUseTags))\n    {\n        action_edit.TargetUseTags = false;\n    }\n    ImGui::SameLine(0.0f, style.ItemInnerSpacing.x);\n    HelpMarker(TranslationManager::GetString(tstr_SettingsActionsEditTargetDefaultTip));\n\n    ImGui::SameLine();\n\n    if (ImGui::RadioButton(TranslationManager::GetString(tstr_SettingsActionsEditTargetUseTags), action_edit.TargetUseTags))\n    {\n        action_edit.TargetUseTags = true;\n    }\n\n    tags_widget_height = ImGui::GetCursorPosY();\n\n\n    ImGui::BeginCollapsingArea(\"AreaTags\", action_edit.TargetUseTags, area_tags_animation_progress);\n\n    if (InputOverlayTags(\"TargetTags\", buffer_action_target_tags, IM_ARRAYSIZE(buffer_action_target_tags), input_tags_state))\n    {\n        action_edit.TargetTags = buffer_action_target_tags;\n    }\n\n    ImGui::EndCollapsingArea();\n\n    ImGui::Columns(1);\n\n    tags_widget_height = ImGui::GetCursorPosY() - tags_widget_height;\n\n    ImGui::TextColoredUnformatted(ImGui::GetStyleColorVec4(ImGuiCol_ButtonHovered), TranslationManager::GetString(tstr_SettingsActionsEditHeaderAppearance));\n\n    //Use settings icon as default button size reference\n    ImVec2 b_size_default, b_uv_min, b_uv_max;\n    TextureManager::Get().GetTextureInfo(tmtex_icon_settings, b_size_default, b_uv_min, b_uv_max);\n\n    //Adapt to the last known scale used in VR so the text alignment matches what's seen in the headset later\n    if (UIManager::Get()->IsInDesktopMode())\n    {\n        b_size_default.x *= UIManager::Get()->GetUIScale();\n        b_size_default.y *= UIManager::Get()->GetUIScale();\n        b_size_default.x *= ConfigManager::GetValue(configid_float_interface_last_vr_ui_scale);\n        b_size_default.y *= ConfigManager::GetValue(configid_float_interface_last_vr_ui_scale);\n    }\n\n    //It's a bit hacky, but we add the preview button last to avoid it changing line heights and shift the position of widgets around to leave space for it instead\n    ImVec2 button_pos = ImGui::GetCursorPos();\n    const float line_x = ImGui::GetCursorPosX() + b_size_default.x + style.IndentSpacing + style.IndentSpacing + style.ItemSpacing.x + style.ItemSpacing.x;\n\n    ImGui::Columns(2, \"ColumnActionEditAppearance\", false);\n    ImGui::SetColumnWidth(0, m_Column0Width);\n\n    ImGui::SetCursorPosX(line_x);\n    ImGui::AlignTextToFramePadding();\n    ImGui::TextUnformatted(TranslationManager::GetString(tstr_SettingsActionsEditIcon));\n    ImGui::NextColumn();\n\n    ImGui::PushID(\"##FileName\");\n    if (ImGui::Button((!action_edit.IconFilename.empty()) ? action_edit.IconFilename.c_str() : TranslationManager::GetString(tstr_DialogIconPickerNone)))\n    {\n        PageGoForward(wndsettings_page_icon_picker);\n    }\n    ImGui::PopID();\n\n    ImGui::NextColumn();\n\n    ImGui::SetCursorPosX(line_x);\n    ImGui::AlignTextToFramePadding();\n    ImGui::TextUnformatted(TranslationManager::GetString(tstr_SettingsActionsEditLabel));\n\n    if (action_edit.LabelTranslationID != tstr_NONE)\n    {\n        ImGui::SameLine(0.0f, style.ItemInnerSpacing.x);\n        HelpMarker(TranslationManager::GetString(tstr_SettingsActionsEditLabelTranslatedTip));\n    }\n\n    ImGui::NextColumn();\n\n    ImGui::PushItemWidth(-1.0f);\n    ImGui::PushID(appearing_framecount);\n    vr_keyboard.VRKeyboardInputBegin(\"##InputActionLabel\", true);\n    ImVec2 multiline_input_size(-1, (ImGui::GetTextLineHeight() * 3.0f) + (style.FramePadding.y * 2.0f));\n    if (ImGui::InputTextMultiline(\"##InputActionLabel\", buffer_action_label, IM_ARRAYSIZE(buffer_action_label), multiline_input_size, \n                                  ImGuiInputTextFlags_CallbackCharFilter | ImGuiInputTextFlags_CallbackEdit, filter_newline_limit))\n    {\n        UIManager::Get()->AddFontBuilderStringIfAnyUnmappedCharacters(buffer_action_label);\n\n        action_edit.Label = buffer_action_label;\n        label_matches_name = (action_edit.Label == action_edit.Name);\n\n        //Check for potential translation string\n        action_edit.LabelTranslationID = action_manager.GetTranslationIDForName(action_edit.Label);\n    }\n    vr_keyboard.VRKeyboardInputEnd();\n    ImGui::PopID();\n\n    ImGui::Columns(1);\n\n    const ImVec2 command_header_pos = ImGui::GetCursorPos();\n\n    //Vertically center button\n    button_pos.x += style.IndentSpacing;\n    button_pos.y += (command_header_pos.y - button_pos.y) / 2.0f - (b_size_default.y / 2.0f);\n    ImGui::SetCursorPos(button_pos);\n\n    if (!UIManager::Get()->IsOpenVRLoaded())    //Disable when dashboard app isn't available\n        ImGui::PushItemDisabledNoVisual();\n\n    WindowFloatingUIActionBar::ButtonAction(action_edit, b_size_default, true);\n\n    if (ImGui::IsItemActivated())\n    {\n        //Send action over so it can be used for testing\n        IPCManager::Get().SendStringToDashboardApp(configid_str_state_action_data, action_edit.Serialize(), UIManager::Get()->GetWindowHandle());\n\n        action_manager.StartAction(action_edit.UID);\n\n        //Make sure to either revert or get rid of the action if canceling the edit later\n        action_test_was_used = true;\n    }\n    else if (ImGui::IsItemDeactivated())\n    {\n        action_manager.StopAction(action_edit.UID);\n    }\n\n    if (!UIManager::Get()->IsOpenVRLoaded())\n        ImGui::PopItemDisabledNoVisual();\n\n    ImGui::SetCursorPos(command_header_pos);\n\n    ImGui::TextColoredUnformatted(ImGui::GetStyleColorVec4(ImGuiCol_ButtonHovered), TranslationManager::GetString(tstr_SettingsActionsEditHeaderCommands));\n\n    ImGui::Indent();\n\n    ImGui::SetNextItemWidth(-1.0f);\n    const float item_height = ImGui::GetFontSize() + style.ItemSpacing.y;\n    const float inner_padding = style.FramePadding.y + style.FramePadding.y + style.ItemInnerSpacing.y;\n    const float item_count = (UIManager::Get()->IsInDesktopMode()) ? 13.5f : 9.5f;\n\n    ImGui::BeginChild(\"CommandList\", ImVec2(0.0f, (item_height * item_count) + inner_padding - m_WarningHeight - tags_widget_height), true, ImGuiWindowFlags_AlwaysVerticalScrollbar);\n\n    static int header_open = -1;\n    static float header_new_appearance_progress = 1.0f;     //We animate newly created command headers appearing as pop-in is a bit grating and it's simple, but do nothing for removal\n    static std::vector<int> list_unique_ids;\n    static unsigned int drag_last_hovered_header = 0;\n    static int keyboard_swapped_index            = -1;\n    static bool is_dragging_header = false;\n\n    command_ui_states.resize(action_edit.Commands.size());\n    const float header_text_max_width = ImGui::GetContentRegionAvail().x - ImGui::GetFrameHeightWithSpacing() - style.FramePadding.x;\n\n    //Reset unique IDs when appearing\n    if (m_PageAppearing == wndsettings_page_actions_edit)\n    {\n        list_unique_ids.clear();\n    }\n\n    //Expand unique id lists if commands were added (also does initialization since it's empty then)\n    while (list_unique_ids.size() < action_edit.Commands.size())\n    {\n        list_unique_ids.push_back((int)list_unique_ids.size());\n    }\n\n    int command_id = 0;\n    for (auto& command : action_edit.Commands)\n    {\n        CommandUIState& ui_state = command_ui_states[command_id];\n        const bool animate_appearing = (header_new_appearance_progress < 1.0f) && (command_id == action_edit.Commands.size() - 1);\n\n        if (ui_state.header_label.empty())\n        {\n            ui_state.header_label = ActionManager::GetCommandDescription(command, header_text_max_width) + \"###CommandHeader\";\n\n            size_t copied_length = command.StrMain.copy(ui_state.buffer_str_main, IM_ARRAYSIZE(ui_state.buffer_str_main) - 1);\n            ui_state.buffer_str_main[copied_length] = '\\0';\n            copied_length = command.StrArg.copy(ui_state.buffer_str_arg, IM_ARRAYSIZE(ui_state.buffer_str_arg) - 1);\n            ui_state.buffer_str_arg[copied_length] = '\\0';\n\n            //Skip header animation if command has the value set already\n            ui_state.header_2_animation_progress = ((command.Type == ActionCommand::command_show_overlay) && (LOWORD(command.UIntID) == 1)) ? 1.0f : 0.0f;\n        }\n\n        ImGui::PushID(list_unique_ids[command_id]);\n\n        //Set focus for nav if we previously re-ordered overlays via keyboard\n        if (keyboard_swapped_index == command_id)\n        {\n            ImGui::SetKeyboardFocusHere();\n\n            //Nav works against us here, so keep setting focus until ctrl isn't down anymore\n            if ((!io.KeyCtrl) || (!io.NavVisible))\n            {\n                keyboard_swapped_index = -1;\n            }\n        }\n\n        if (animate_appearing)\n            ImGui::BeginCollapsingArea(\"CollapsingAreaNewAppear\", true, header_new_appearance_progress);\n\n        ImGui::SetNextItemOpen((header_open == command_id));\n        if (ImGui::CollapsingHeaderPadded(ui_state.header_label.c_str()))\n        {\n            if (!is_dragging_header)\n            {\n                header_open = command_id;\n            }\n            else if (header_open != command_id)\n            {\n                UIManager::Get()->RepeatFrame();    //Collapsing header's arrow flickers if we deny the change, so skip that frame\n            }\n        }\n        else if (header_open == command_id)\n        {\n            if (!is_dragging_header)\n            {\n                header_open = -1;\n                delete_confirm_state = false;\n            }\n            else\n            {\n                UIManager::Get()->RepeatFrame();\n            }\n        }\n\n        if (animate_appearing)\n        {\n            ImGui::EndCollapsingArea();\n            ui_state.header_animation_progress = header_new_appearance_progress * 0.50f; //The collapsing areas are appearing independently, but slow the lower one down a bit to make it less jarring\n        }\n\n        if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenBlockedByActiveItem))\n        {\n            drag_last_hovered_header = command_id;\n        }\n\n        //Drag reordering\n        if ((ImGui::IsItemActive()) && (!ImGui::IsItemHovered()))\n        {\n            int index_swap = command_id + ((ImGui::GetMouseDragDelta(ImGuiMouseButton_Left).y < 0.0f) ? -1 : 1);\n            if ((drag_last_hovered_header != command_id) && (index_swap >= 0) && (index_swap < action_edit.Commands.size()))\n            {\n                std::iter_swap(action_edit.Commands.begin() + command_id, action_edit.Commands.begin() + index_swap);\n                std::iter_swap(command_ui_states.begin()    + command_id, command_ui_states.begin()    + index_swap);\n                std::iter_swap(list_unique_ids.begin()      + command_id, list_unique_ids.begin()      + index_swap);\n\n                if (header_open == command_id)\n                {\n                    header_open = index_swap;\n                }\n                else if (header_open == index_swap)\n                {\n                    header_open = command_id;\n                }\n\n                ImGui::ResetMouseDragDelta(ImGuiMouseButton_Left);\n                is_dragging_header = true;\n                UIManager::Get()->RepeatFrame();\n            }\n        }\n\n        //Keyboard reordering\n        if ((io.NavVisible) && (io.KeyCtrl) && (drag_last_hovered_header == command_id))\n        {\n            int index_swap = command_id + ((ImGui::IsNavInputPressed(ImGuiNavInput_DpadDown, true)) ? 1 : (ImGui::IsNavInputPressed(ImGuiNavInput_DpadUp, true)) ? -1 : 0);\n            if ((command_id != index_swap) && (index_swap >= 0) && (index_swap < action_edit.Commands.size()))\n            {\n                std::iter_swap(action_edit.Commands.begin() + command_id, action_edit.Commands.begin() + index_swap);\n                std::iter_swap(command_ui_states.begin()    + command_id, command_ui_states.begin()    + index_swap);\n                std::iter_swap(list_unique_ids.begin()      + command_id, list_unique_ids.begin()      + index_swap);\n\n                if (header_open == command_id)\n                {\n                    header_open = index_swap;\n                }\n                else if (header_open == index_swap)\n                {\n                    header_open = command_id;\n                }\n\n                //Skip the rest of this frame to avoid double-swaps\n                keyboard_swapped_index = index_swap;\n                ImGui::PopID();\n                UIManager::Get()->RepeatFrame();\n                break;\n            }\n        }\n\n        ImGui::BeginCollapsingArea(\"CollapsingArea\", (header_open == command_id), ui_state.header_animation_progress);\n        ImGui::Indent();\n        ImGui::Spacing();\n\n        ImGui::Columns(2, \"ColumnCommand\", false);\n        ImGui::SetColumnWidth(0, m_Column0Width);\n\n        ImGui::AlignTextToFramePadding();\n        ImGui::TextUnformatted(TranslationManager::GetString(tstr_SettingsActionsEditCommandType));\n        ImGui::NextColumn();\n\n        bool has_value_changed = false;\n\n        int command_type_temp = command.Type;\n        ImGui::SetNextItemWidth(-1);\n        if (TranslatedComboAnimated(\"##ComboCommandType\", command_type_temp, tstr_SettingsActionsEditCommandTypeNone, tstr_SettingsActionsEditCommandTypeLoadOverlayProfile))\n        {\n            //Reset command values, then set type\n            command = ActionCommand();\n            command.Type = (ActionCommand::CommandType)command_type_temp;\n\n            ui_state.buffer_str_main[0] = '\\0';\n            ui_state.buffer_str_arg[0]  = '\\0';\n            ui_state.temp_int_1 = 0;\n            ui_state.temp_int_2 = 0;\n\n            //Set command specific default values\n            if (command.Type == ActionCommand::command_load_overlay_profile)\n            {\n                command.UIntID = 1; //Clear existing overlays: true\n            }\n\n            has_value_changed = true;\n        }\n        ImGui::Spacing();\n        ImGui::NextColumn();\n\n        const float cursor_y_prev = ImGui::GetCursorPosY();\n\n        switch (command.Type)\n        {\n            case ActionCommand::command_none: break;\n            case ActionCommand::command_key:\n            {\n                if ((m_PageReturned == wndsettings_page_keycode_picker) && (header_open == command_id))\n                {\n                    command.UIntID = m_KeyCodePickerID;\n                    has_value_changed = true;\n\n                    m_PageReturned = wndsettings_page_none;\n                }\n\n                ImGui::AlignTextToFramePadding();\n                ImGui::TextUnformatted(TranslationManager::GetString(tstr_SettingsActionsEditCommandKeyCode));\n                ImGui::NextColumn();\n\n                if (ImGui::Button( (command.UIntID == 0) ? TranslationManager::GetString(tstr_DialogKeyCodePickerKeyCodeNone) : GetStringForKeyCode(command.UIntID) ))\n                {\n                    m_KeyCodePickerNoMouse    = false;\n                    m_KeyCodePickerHotkeyMode = false;\n                    m_KeyCodePickerID = (unsigned int)command.UIntID;\n                    PageGoForward(wndsettings_page_keycode_picker);\n                }\n                ImGui::NextColumn();\n\n                bool temp_bool = (command.UIntArg == 1);\n                if (ImGui::Checkbox(TranslationManager::GetString(tstr_SettingsActionsEditCommandKeyToggle), &temp_bool))\n                {\n                    command.UIntArg = temp_bool;\n                    has_value_changed = true;\n                }\n\n                break;\n            }\n            case ActionCommand::command_mouse_pos:\n            {\n                const float input_width = ImGui::GetFontSize() * 6.0f;\n\n                ImGui::AlignTextToFramePadding();\n                ImGui::TextUnformatted(TranslationManager::GetString(tstr_SettingsActionsEditCommandMouseX));\n                ImGui::NextColumn();\n\n                ImGui::SetNextItemWidth(input_width);\n                vr_keyboard.VRKeyboardInputBegin(\"##X\");\n                if (ImGui::InputInt(\"##X\", &ui_state.temp_int_1, 1, 25))\n                {\n                    has_value_changed = true;\n                }\n                vr_keyboard.VRKeyboardInputEnd();\n                ImGui::NextColumn();\n\n                ImGui::AlignTextToFramePadding();\n                ImGui::TextUnformatted(TranslationManager::GetString(tstr_SettingsActionsEditCommandMouseY));\n                ImGui::NextColumn();\n\n                ImGui::SetNextItemWidth(input_width);\n                vr_keyboard.VRKeyboardInputBegin(\"##Y\");\n                if (ImGui::InputInt(\"##Y\", &ui_state.temp_int_2, 1, 25))\n                {\n                    has_value_changed = true;\n                }\n                vr_keyboard.VRKeyboardInputEnd();\n                ImGui::NextColumn();\n                ImGui::NextColumn();\n\n                if (ImGui::Button(TranslationManager::GetString(tstr_SettingsActionsEditCommandMouseUseCurrent)))\n                {\n                    POINT mouse_pos = {0};\n                    ::GetCursorPos(&mouse_pos); \n\n                    ui_state.temp_int_1 = mouse_pos.x;\n                    ui_state.temp_int_2 = mouse_pos.y;\n\n                    has_value_changed = true;\n                }\n\n                if (has_value_changed)\n                {\n                    command.UIntID = MAKELPARAM(ui_state.temp_int_1, ui_state.temp_int_2);\n                }\n\n                break;\n            }\n            case ActionCommand::command_string:\n            {\n                ImGui::AlignTextToFramePadding();\n                ImGui::TextUnformatted(TranslationManager::GetString(tstr_SettingsActionsEditCommandString));\n                ImGui::NextColumn();\n\n                vr_keyboard.VRKeyboardInputBegin(\"##InputCommandString\");\n                if (ImGui::InputTextMultiline(\"##InputCommandString\", ui_state.buffer_str_main, IM_ARRAYSIZE(ui_state.buffer_str_main), multiline_input_size))\n                {\n                    //Check input for unmapped characters\n                    if (ImGui::StringContainsUnmappedCharacter(ui_state.buffer_str_main))\n                    {\n                        if (TextureManager::Get().AddFontBuilderString(ui_state.buffer_str_main))\n                        {\n                            TextureManager::Get().ReloadAllTexturesLater();\n                            UIManager::Get()->RepeatFrame();\n                        }\n                    }\n\n                    command.StrMain = ui_state.buffer_str_main;\n                    has_value_changed = true;\n                }\n                vr_keyboard.VRKeyboardInputEnd();\n\n                break;\n            }\n            case ActionCommand::command_launch_app:\n            {\n                ImGui::AlignTextToFramePadding();\n                ImGui::TextUnformatted(TranslationManager::GetString(tstr_SettingsActionsEditCommandPath));\n                ImGui::SameLine(0.0f, style.ItemInnerSpacing.x);\n                HelpMarker(TranslationManager::GetString(tstr_SettingsActionsEditCommandPathTip));\n\n                ImGui::NextColumn();\n\n                ImGui::SetNextItemWidth(-1.0f);\n                vr_keyboard.VRKeyboardInputBegin(\"##InputCommandPath\");\n                if (ImGui::InputText(\"##InputCommandPath\", ui_state.buffer_str_main, IM_ARRAYSIZE(ui_state.buffer_str_main)))\n                {\n                    UIManager::Get()->AddFontBuilderStringIfAnyUnmappedCharacters(ui_state.buffer_str_main);\n\n                    command.StrMain = ui_state.buffer_str_main;\n                    has_value_changed = true;\n                }\n                vr_keyboard.VRKeyboardInputEnd();\n                ImGui::NextColumn();\n\n                ImGui::AlignTextToFramePadding();\n                ImGui::TextUnformatted(TranslationManager::GetString(tstr_SettingsActionsEditCommandArgs));\n                ImGui::SameLine(0.0f, style.ItemInnerSpacing.x);\n                HelpMarker(TranslationManager::GetString(tstr_SettingsActionsEditCommandArgsTip));\n\n                ImGui::NextColumn();\n\n                ImGui::SetNextItemWidth(-1.0f);\n                vr_keyboard.VRKeyboardInputBegin(\"##InputCommandArg\");\n                if (ImGui::InputText(\"##InputCommandArg\", ui_state.buffer_str_arg, IM_ARRAYSIZE(ui_state.buffer_str_arg)))\n                {\n                    UIManager::Get()->AddFontBuilderStringIfAnyUnmappedCharacters(ui_state.buffer_str_arg);\n\n                    command.StrArg = ui_state.buffer_str_arg;\n                    has_value_changed = true;\n                }\n                vr_keyboard.VRKeyboardInputEnd();\n\n                break;\n            }\n            case ActionCommand::command_show_keyboard:\n            {\n                ImGui::AlignTextToFramePadding();\n                ImGui::TextUnformatted(TranslationManager::GetString(tstr_SettingsActionsEditCommandVisibility));\n                ImGui::NextColumn();\n\n                int command_arg_temp = command.UIntArg;\n                ImGui::SetNextItemWidth(-1);\n                if (TranslatedComboAnimated(\"##ComboCommandToggleArg\", command_arg_temp, tstr_SettingsActionsEditCommandVisibilityToggle, tstr_SettingsActionsEditCommandVisibilityHide))\n                {\n                    command.UIntArg = command_arg_temp;\n                    has_value_changed = true;\n                }\n\n                break;\n            }\n            case ActionCommand::command_crop_active_window: break;\n            case ActionCommand::command_show_overlay:\n            {\n                bool use_command_tags = (LOWORD(command.UIntID) == 1);\n                bool do_undo_command  = (HIWORD(command.UIntID) == 1);\n\n                ImGui::AlignTextToFramePadding();\n                ImGui::TextUnformatted(TranslationManager::GetString(tstr_SettingsActionsEditCommandVisibility));\n                ImGui::NextColumn();\n\n                int command_arg_temp = command.UIntArg;\n                ImGui::SetNextItemWidth(-1);\n                if (TranslatedComboAnimated(\"##ComboCommandToggleArg\", command_arg_temp, tstr_SettingsActionsEditCommandVisibilityToggle, tstr_SettingsActionsEditCommandVisibilityHide))\n                {\n                    command.UIntArg = command_arg_temp;\n                    has_value_changed = true;\n                }\n                ImGui::NextColumn();\n\n                ImGui::AlignTextToFramePadding();\n                ImGui::TextUnformatted(TranslationManager::GetString(tstr_SettingsActionsEditTarget));\n                ImGui::NextColumn();\n\n                if (ImGui::RadioButton(TranslationManager::GetString(tstr_SettingsActionsEditTargetActionTarget), !use_command_tags))\n                {\n                    command.UIntID = MAKELPARAM(false, do_undo_command);\n                    has_value_changed = true;\n                }\n\n                ImGui::SameLine();\n\n                if (ImGui::RadioButton(TranslationManager::GetString(tstr_SettingsActionsEditTargetUseTags), use_command_tags))\n                {\n                    command.UIntID = MAKELPARAM(true, do_undo_command);\n                    has_value_changed = true;\n                }\n\n                ImGui::BeginCollapsingArea(\"CommandAreaTags\", use_command_tags, ui_state.header_2_animation_progress);\n\n                if (InputOverlayTags(\"CommandTargetTags\", ui_state.buffer_str_main, IM_ARRAYSIZE(ui_state.buffer_str_main), ui_state.input_tags_state, 1))\n                {\n                    command.StrMain = ui_state.buffer_str_main;\n                    has_value_changed = true;\n                }\n\n                ImGui::EndCollapsingArea();\n\n                ImGui::NextColumn();\n\n                if (ImGui::Checkbox(TranslationManager::GetString(tstr_SettingsActionsEditCommandUndo), &do_undo_command))\n                {\n                    command.UIntID = MAKELPARAM(use_command_tags, do_undo_command);\n                    has_value_changed = true;\n                }\n\n                break;\n            }\n            case ActionCommand::command_switch_task:\n            {\n                bool use_strict_matching = (LOWORD(command.UIntArg) == 1);\n                bool warp_cursor         = (HIWORD(command.UIntArg) == 1);\n\n                //If window title needs to be refreshed or returned from widnow picker\n                if ((ui_state.temp_window_button_title.empty()) || ((m_PageReturned == wndsettings_page_window_picker) && (header_open == command_id)))\n                {\n                    const auto& window_list = WindowManager::Get().WindowListGet();\n                    const bool returned_from_picker = (m_WindowPickerHWND != nullptr);\n\n                    //If not returned from window picker, find HWND for the target window title if possible\n                    if (!returned_from_picker)\n                    {\n                        //exe & class names are packed into StrArg, seperated by |, which isn't allowed to appear in file names so it makes a decent separator here\n                        std::string exe_name;\n                        std::string class_name;\n                        size_t search_pos = command.StrArg.find(\"|\");\n\n                        if (search_pos != std::string::npos)\n                        {\n                            exe_name   = command.StrArg.substr(0, search_pos);\n                            class_name = command.StrArg.substr(search_pos + 1);\n                        }\n\n                        m_WindowPickerHWND = WindowInfo::FindClosestWindowForTitle(command.StrMain, class_name, exe_name, window_list, use_strict_matching);\n                    }\n\n                    const auto it = std::find_if(window_list.begin(), window_list.end(), [&](const auto& window) { return (window.GetWindowHandle() == m_WindowPickerHWND); });\n\n                    if (it != window_list.end())\n                    {\n                        command.StrMain = StringConvertFromUTF16(it->GetTitle().c_str());\n                        command.StrArg  = it->GetExeName() + \"|\" + StringConvertFromUTF16(it->GetWindowClassName().c_str());\n\n                        ui_state.temp_window_button_title = it->GetListTitle();\n                    }\n                    else if (!command.StrMain.empty())  //Referenced window doesn't exist right now\n                    {\n                        ui_state.temp_window_button_title = TranslationManager::GetString(tstr_SourceWinRTClosed) + std::string(\" \" + command.StrMain);\n                    }\n                    else  //Couldn't find any open window\n                    {\n                        command.StrArg.clear();\n\n                        ui_state.temp_window_button_title = TranslationManager::GetString(tstr_SettingsActionsEditCommandWindowNone);\n                    }\n\n                    has_value_changed = true;\n\n                    m_PageReturned = wndsettings_page_none;\n                    m_WindowPickerHWND = nullptr;\n                }\n\n                ImGui::AlignTextToFramePadding();\n                ImGui::TextUnformatted(TranslationManager::GetString(tstr_SettingsActionsEditCommandSwitchingMethod));\n                ImGui::NextColumn();\n\n                if (ImGui::RadioButton(TranslationManager::GetString(tstr_SettingsActionsEditCommandSwitchingMethodSwitcher), (command.UIntID == 0)))\n                {\n                    command.UIntID = 0;\n                    has_value_changed = true;\n                }\n\n                ImGui::SameLine();\n\n                if (ImGui::RadioButton(TranslationManager::GetString(tstr_SettingsActionsEditCommandSwitchingMethodFocus), (command.UIntID == 1)))\n                {\n                    command.UIntID = 1;\n                    has_value_changed = true;\n                }\n\n                ImGui::NextColumn();\n\n                if (command.UIntID == 0)\n                    ImGui::PushItemDisabled();\n\n                ImGui::AlignTextToFramePadding();\n                ImGui::TextUnformatted(TranslationManager::GetString(tstr_SettingsActionsEditCommandWindow));\n                ImGui::NextColumn();\n\n                //Window picker button\n                ImVec2 button_size(0.0f, 0.0f);\n\n                if (ImGui::CalcTextSize(ui_state.temp_window_button_title.c_str()).x > ImGui::GetContentRegionAvail().x - style.FramePadding.x * 2.0f)\n                {\n                    button_size.x = ImGui::GetContentRegionAvail().x - 1.0f;\n                }\n\n                if (ImGui::Button(ui_state.temp_window_button_title.c_str(), button_size))\n                {\n                    std::string exe_name;\n                    std::string class_name;\n                    size_t search_pos = command.StrArg.find(\"|\");\n\n                    if (search_pos != std::string::npos)\n                    {\n                        exe_name   = command.StrArg.substr(0, search_pos);\n                        class_name = command.StrArg.substr(search_pos + 1);\n                    }\n\n                    HWND hwnd_current = WindowInfo::FindClosestWindowForTitle(command.StrMain, class_name, exe_name, WindowManager::Get().WindowListGet(), use_strict_matching);\n\n                    m_WindowPickerHWND = hwnd_current;\n                    PageGoForward(wndsettings_page_window_picker);\n                }\n\n                ImGui::NextColumn();\n                ImGui::NextColumn();\n\n                if (ImGui::Checkbox(TranslationManager::GetString(tstr_OvrlPropsCaptureGCStrictMatching), &use_strict_matching))\n                {\n                    command.UIntArg = MAKELPARAM(use_strict_matching, warp_cursor);\n                    has_value_changed = true;\n                }\n                ImGui::SameLine(0.0f, style.ItemInnerSpacing.x);\n                HelpMarker(TranslationManager::GetString(tstr_SettingsActionsEditCommandWindowStrictMatchingTip));\n\n                ImGui::NextColumn();\n\n                if (ImGui::Checkbox(TranslationManager::GetString(tstr_SettingsActionsEditCommandCursorWarp), &warp_cursor))\n                {\n                    command.UIntArg = MAKELPARAM(use_strict_matching, warp_cursor);\n                    has_value_changed = true;\n                }\n\n                if (command.UIntID == 0)\n                    ImGui::PopItemDisabled();\n\n                break;\n            }\n            case ActionCommand::command_load_overlay_profile:\n            {\n                bool clear_existing = (command.UIntID == 1);\n\n                int& list_id = ui_state.temp_int_1;\n\n                //Find current selection index if needed\n                if ((list_id == 0) && (!command.StrMain.empty()))\n                {\n                    const auto it = std::find(m_ProfileList.begin(), m_ProfileList.end(), command.StrMain);\n                    list_id = (it != m_ProfileList.end()) ? (int)std::distance(m_ProfileList.begin(), it) : -1;\n                }\n\n                ImGui::AlignTextToFramePadding();\n                ImGui::TextUnformatted(TranslationManager::GetString(tstr_SettingsActionsEditCommandProfile));\n\n                ImGui::NextColumn();\n\n                ImGui::PushItemWidth(-1);\n                ImGui::SetNextItemWidth(-1);\n                if (ImGui::BeginComboAnimated(\"##ComboLang\", (!command.StrMain.empty()) ? command.StrMain.c_str() : TranslationManager::GetString(tstr_SettingsProfilesOverlaysNameDefault) ))\n                {\n                    int index = 0;\n                    for (const auto& name : m_ProfileList)\n                    {\n                        //Skip [New Profile] which is always at the end of the list\n                        if (index == m_ProfileList.size() - 1)\n                        {\n                            break;\n                        }\n\n                        ImGui::PushID(index);\n                        if (ImGui::Selectable(name.c_str(), (index == list_id)))\n                        {\n                            list_id = index;\n\n                            if (list_id == 0)\n                            {\n                                command.StrMain = \"\";\n                                command.UIntID  = 1;\n                            }\n                            else\n                            {\n                                command.StrMain = m_ProfileList[list_id];\n                            }\n\n                            has_value_changed = true;\n                        }\n                        ImGui::PopID();\n\n                        index++;\n                    }\n\n                    ImGui::EndCombo();\n                }\n\n                ImGui::NextColumn();\n\n                //\"Default Profile\" is always clearing overlays\n                if (list_id == 0)\n                    ImGui::PushItemDisabled();\n\n                if (ImGui::Checkbox(TranslationManager::GetString(tstr_SettingsActionsEditCommandProfileClear), &clear_existing))\n                {\n                    command.UIntID = clear_existing;\n                    has_value_changed = true;\n                }\n\n                if (list_id == 0)\n                    ImGui::PopItemDisabled();\n\n                break;\n            }\n            default: break;\n        }\n\n        if (has_value_changed)\n        {\n            ui_state.header_label = ActionManager::GetCommandDescription(command, header_text_max_width);\n        }\n\n        ImGui::Columns(1);\n\n        //Delete button\n        bool command_was_deleted = false;\n\n        if (cursor_y_prev != ImGui::GetCursorPosY())    //Only add spacing if the command had any settings\n            ImGui::Spacing();\n\n        ImGui::SetCursorPosX(ImGui::GetCursorPosX() + ImGui::GetContentRegionAvail().x - delete_button_width);\n\n        if (delete_confirm_state)\n        {\n            if (ImGui::Button(TranslationManager::GetString(tstr_SettingsActionsEditCommandDeleteConfirm), m_CachedSizes.ActionEdit_ButtonDeleteSize))\n            {\n                delete_confirm_state = false;\n\n                action_edit.Commands.erase(action_edit.Commands.begin() + command_id);\n                command_ui_states.erase(command_ui_states.begin() + command_id);\n\n                header_open = -1;\n                command_was_deleted = true;\n            }\n        }\n        else\n        {\n            if (ImGui::Button(TranslationManager::GetString(tstr_SettingsActionsEditCommandDelete), m_CachedSizes.ActionEdit_ButtonDeleteSize))\n            {\n                delete_confirm_state = true;\n            }\n        }\n\n        delete_button_width = ImGui::GetItemRectSize().x + style.IndentSpacing;\n\n        ImGui::Spacing();\n        ImGui::Unindent();\n        ImGui::EndCollapsingArea();\n\n        ImGui::PopID();\n\n        if (command_was_deleted)\n        {\n            UIManager::Get()->RepeatFrame();\n            break;\n        }\n\n        ++command_id;\n    }\n\n    if (!action_edit.Commands.empty())\n    {\n        ImGui::Separator();\n    }\n\n    //Use empty label here. Icon and actual label are manually created further down\n    if (ImGui::Selectable(\"##AddCommand\", false))\n    {\n        ActionCommand command;\n\n        action_edit.Commands.push_back(command);\n\n        //Animate appearance of new command and open its header\n        header_new_appearance_progress = 0.0f;\n        header_open = (int)action_edit.Commands.size() - 1;\n        UIManager::Get()->RepeatFrame(3);                           //3 frames to avoid scrollbar flickering from sizing calculations\n    }\n\n    if (ImGui::IsItemVisible())\n    {\n        //Custom render the selectable label with icon\n        ImGui::SameLine(0.0f, style.ItemInnerSpacing.x);\n\n        ImVec2 img_size_line_height = {ImGui::GetTextLineHeight(), ImGui::GetTextLineHeight()};\n        ImVec2 img_size, img_uv_min, img_uv_max;\n        TextureManager::Get().GetTextureInfo(tmtex_icon_add, img_size, img_uv_min, img_uv_max);\n        ImGui::Image(ImGui::GetIO().Fonts->TexID, img_size_line_height, img_uv_min, img_uv_max);\n\n        //Label\n        ImGui::SameLine(0.0f, style.ItemInnerSpacing.x);\n        ImGui::TextUnformatted(TranslationManager::GetString(tstr_SettingsActionsEditCommandAdd));\n    }\n\n    ImGui::EndChild();\n\n    ImGui::Unindent();\n\n    if (ImGui::IsMouseReleased(ImGuiMouseButton_Left))\n    {\n        is_dragging_header = false;\n    }\n\n    ImGui::SetCursorPosY( ImGui::GetCursorPosY() + (ImGui::GetContentRegionAvail().y - ImGui::GetFrameHeight()) );\n\n    //Confirmation buttons\n    if (ImGui::Button(TranslationManager::GetString(tstr_DialogOk))) \n    {\n        action_manager.StoreAction(action_edit);\n\n        //Reload texture to apply icon if there is any\n        if (!action_edit.IconFilename.empty())\n        {\n            TextureManager::Get().ReloadAllTexturesLater();\n        }\n\n        //Send action over\n        IPCManager::Get().SendStringToDashboardApp(configid_str_state_action_data, action_edit.Serialize(), UIManager::Get()->GetWindowHandle());\n\n        PageGoBack();\n    }\n\n    ImGui::SameLine();\n\n    if (ImGui::Button(TranslationManager::GetString(tstr_DialogCancel))) \n    {\n        PageGoBack();\n    }\n}\n\nvoid WindowSettings::UpdatePageActionsOrder(bool only_restore_settings)\n{\n    static FloatingWindowActionOrderListState page_state;\n\n    ActionManager& action_manager = ConfigManager::Get().GetActionManager();\n    ActionManager::ActionList& action_list = (m_ActionOrderListEditForOverlayBar) ? action_manager.GetActionOrderListOverlayBar() : action_manager.GetActionOrderListBarDefault();\n\n    if (only_restore_settings)\n    {\n        if (!page_state.HasSavedChanges)\n        {\n            action_list = page_state.ActionListOrig;\n        }\n        return;\n    }\n\n    bool go_add_actions = false;\n    bool go_back = ActionOrderList(action_list, (m_PageAppearing == wndsettings_page_actions_order), (m_PageReturned == wndsettings_page_actions_order_add), page_state, go_add_actions, -m_WarningHeight);\n\n    if (m_PageReturned == wndsettings_page_actions_order_add)\n    {\n        m_PageReturned = wndsettings_page_none;\n    }\n\n    if (go_add_actions)\n    {\n        PageGoForward(wndsettings_page_actions_order_add);\n    }\n    else if (go_back)\n    {\n        PageGoBack();\n    }\n}\n\nvoid WindowSettings::UpdatePageActionsOrderAdd()\n{\n    static FloatingWindowActionAddSelectorState page_state;\n\n    ActionManager& action_manager = ConfigManager::Get().GetActionManager();\n    ActionManager::ActionList& action_list = (m_ActionOrderListEditForOverlayBar) ? action_manager.GetActionOrderListOverlayBar() : action_manager.GetActionOrderListBarDefault();\n\n    bool go_back = ActionAddSelector(action_list, (m_PageAppearing == wndsettings_page_actions_order_add), page_state, -m_WarningHeight);\n\n    if (go_back)\n    {\n        PageGoBack();\n    }\n}\n\nvoid WindowSettings::UpdatePageColorPicker(bool only_restore_settings)\n{\n    static ImVec4 color_current;\n    static ImVec4 color_original;\n    static bool do_restore_color = true;\n    const ConfigID_Int config_id = configid_int_interface_background_color; //Currently only need this one, so we keep it simple\n\n    if (only_restore_settings)\n    {\n        if (do_restore_color)\n        {\n            ImU32 rgba = ImGui::ColorConvertFloat4ToU32(color_original);\n\n            ConfigManager::Get().SetValue(config_id, *(int*)&rgba);\n            IPCManager::Get().PostMessageToDashboardApp(ipcmsg_set_config, ConfigManager::GetWParamForConfigID(config_id), *(int*)&rgba);\n        }\n\n        return;\n    }\n\n    if (m_PageAppearing == wndsettings_page_color_picker)\n    {\n        color_current  = ImGui::ColorConvertU32ToFloat4( pun_cast<ImU32, int>(ConfigManager::Get().GetValue(config_id)) );\n        color_original = color_current;\n        do_restore_color = true;\n    }\n\n    ImGuiStyle& style = ImGui::GetStyle();\n    VRKeyboard& vr_keyboard = UIManager::Get()->GetVRKeyboard();\n\n    //Make page scrollable since we can't easily adjust the picker to space taken by warnings\n    ImGui::PushStyleColor(ImGuiCol_ChildBg, ImVec4(0.00f, 0.00f, 0.00f, 0.00f));\n    ImGui::BeginChild(\"SettingsColorPicker\", ImVec2(0.00f, -ImGui::GetFrameHeightWithSpacing() - style.ItemSpacing.y), ImGuiChildFlags_NavFlattened);\n    ImGui::PopStyleColor();\n\n    ImGui::TextColoredUnformatted(ImGui::GetStyleColorVec4(ImGuiCol_ButtonHovered), TranslationManager::GetString(tstr_DialogColorPickerHeader)); \n    ImGui::Indent();\n\n    vr_keyboard.VRKeyboardInputBegin(\"#ColorPicker\");\n    if (ImGui::ColorPicker4Simple(\"#ColorPicker\", &color_current.x, &color_original.x, \n                                  TranslationManager::GetString(tstr_DialogColorPickerCurrent), TranslationManager::GetString(tstr_DialogColorPickerOriginal),\n                                  UIManager::Get()->IsInDesktopMode() ? 1.25f : 1.00f))\n    {\n        int rgba = pun_cast<int, ImU32>( ImGui::ColorConvertFloat4ToU32(color_current) );\n\n        ConfigManager::Get().SetValue(config_id, rgba);\n        IPCManager::Get().PostConfigMessageToDashboardApp(config_id, rgba);\n    }\n    vr_keyboard.VRKeyboardInputEnd();\n\n    ImGui::Unindent();\n    ImGui::EndChild();\n\n    ImGui::SetCursorPosY( ImGui::GetCursorPosY() + (ImGui::GetContentRegionAvail().y - ImGui::GetFrameHeightWithSpacing()) );\n\n    //Confirmation buttons\n    ImGui::Separator();\n\n    if (ImGui::Button(TranslationManager::GetString(tstr_DialogOk))) \n    {\n        //Prevent restore settings code from overwriting it later\n        do_restore_color = false;\n\n        PageGoBack();\n    }\n\n    ImGui::SameLine();\n\n    if (ImGui::Button(TranslationManager::GetString(tstr_DialogCancel))) \n    {\n        PageGoBack();\n    }\n}\n\nvoid WindowSettings::UpdatePageProfilePicker()\n{\n    ImGuiStyle& style = ImGui::GetStyle();\n\n    static bool is_nav_focus_entry_pending = false;    //Focus has to be delayed until after the page animation is done\n    static bool scroll_to_selection = false;\n    static int list_id = -1;\n\n    if (m_PageAppearing == wndsettings_page_profile_picker)\n    {\n        //Load profile list\n        m_ProfileList = ConfigManager::Get().GetOverlayProfileList();\n        list_id = -1;\n\n        //Adjust entries from profile list for picker use\n        m_ProfileList[0] = TranslationManager::GetString(tstr_DialogProfilePickerNone);\n        m_ProfileList.erase(m_ProfileList.end() - 1);\n\n        //Pre-select previous selection if it can be found\n        if (m_ProfilePickerName.empty())\n        {\n            list_id = 0;\n            scroll_to_selection = true;\n            is_nav_focus_entry_pending = ImGui::GetIO().NavVisible;\n        }\n        else\n        {\n            const auto it = std::find(m_ProfileList.begin(), m_ProfileList.end(), m_ProfilePickerName);\n\n            if (it != m_ProfileList.end())\n            {\n                list_id = (int)std::distance(m_ProfileList.begin(), it);\n                scroll_to_selection = true;\n                is_nav_focus_entry_pending = ImGui::GetIO().NavVisible;\n            }\n        }\n    }\n\n    ImGui::TextColoredUnformatted(ImGui::GetStyleColorVec4(ImGuiCol_ButtonHovered), TranslationManager::GetString(tstr_DialogProfilePickerHeader)); \n    ImGui::Indent();\n\n    ImGui::SetNextItemWidth(-1.0f);\n    const float item_height = ImGui::GetFontSize() + style.ItemSpacing.y;\n    const float inner_padding = style.FramePadding.y + style.FramePadding.y + style.ItemInnerSpacing.y;\n    const float item_count = (UIManager::Get()->IsInDesktopMode()) ? 22.0f : 15.0f;\n    ImGui::BeginChild(\"ProfilePickerList\", ImVec2(0.0f, (item_height * item_count) + inner_padding - m_WarningHeight), true);\n\n    //List profiles\n    int index = 0;\n    for (const auto& name : m_ProfileList)\n    {\n        if ( (is_nav_focus_entry_pending) && (m_PageAnimationDir == 0) && (index == list_id) )\n        {\n            ImGui::SetKeyboardFocusHere();\n            is_nav_focus_entry_pending = false;\n        }\n\n        ImGui::PushID(index);\n        if (ImGui::Selectable(name.c_str(), (index == list_id)))\n        {\n            list_id = index;\n            m_ProfilePickerName = (list_id == 0) ? \"\" : m_ProfileList[list_id];\n\n            PageGoBack();\n        }\n        ImGui::PopID();\n\n        if ( (scroll_to_selection) && (index == list_id) )\n        {\n            ImGui::SetScrollHereY();\n\n            if (ImGui::IsItemVisible())\n            {\n                scroll_to_selection = false;\n            }\n        }\n\n        index++;\n    }\n\n    ImGui::EndChild();\n    ImGui::Unindent();\n\n    ImGui::SetCursorPosY( ImGui::GetCursorPosY() + (ImGui::GetContentRegionAvail().y - ImGui::GetFrameHeight()) );\n\n    //Cancel button\n    if (ImGui::Button(TranslationManager::GetString(tstr_DialogCancel))) \n    {\n        PageGoBack();\n    }\n}\n\nvoid WindowSettings::UpdatePageActionPicker()\n{\n    ImGuiStyle& style = ImGui::GetStyle();\n\n    static ActionUID list_uid = k_ActionUID_Invalid;\n    static bool is_nav_focus_entry_pending = false;    //Focus has to be delayed until after the page animation is done\n    static bool scroll_to_selection = false;\n\n    if (m_PageAppearing == wndsettings_page_action_picker)\n    {\n        //Load action list\n        m_ActionList = ConfigManager::Get().GetActionManager().GetActionNameList();\n\n        list_uid = m_ActionPickerUID;\n        scroll_to_selection = true;\n        is_nav_focus_entry_pending = ImGui::GetIO().NavVisible;\n\n        //Set to invalid if selection doesn't exist\n        if (!ConfigManager::Get().GetActionManager().ActionExists(list_uid))\n        {\n            list_uid = k_ActionUID_Invalid;\n        }\n    }\n\n    ImGui::TextColoredUnformatted(ImGui::GetStyleColorVec4(ImGuiCol_ButtonHovered), TranslationManager::GetString(tstr_DialogActionPickerHeader)); \n    ImGui::Indent();\n\n    ImGui::SetNextItemWidth(-1.0f);\n    const float item_height = ImGui::GetFontSize() + style.ItemSpacing.y;\n    const float inner_padding = style.FramePadding.y + style.FramePadding.y + style.ItemInnerSpacing.y;\n    const float item_count = (UIManager::Get()->IsInDesktopMode()) ? 22.0f : 15.0f;\n    ImGui::BeginChild(\"ActionPickerList\", ImVec2(0.0f, (item_height * item_count) + inner_padding - m_WarningHeight), true);\n\n    //No Action entry\n    {\n        ImGui::PushID(0);\n\n        if ( (is_nav_focus_entry_pending) && (m_PageAnimationDir == 0) && (list_uid == k_ActionUID_Invalid) )\n        {\n            ImGui::SetKeyboardFocusHere();\n            is_nav_focus_entry_pending = false;\n        }\n\n        if (ImGui::Selectable(TranslationManager::GetString(tstr_ActionNone), (list_uid == k_ActionUID_Invalid) ))\n        {\n            list_uid = k_ActionUID_Invalid;\n            m_ActionPickerUID = k_ActionUID_Invalid;\n\n            PageGoBack();\n        }\n\n        if ( (scroll_to_selection) && (list_uid == k_ActionUID_Invalid) )\n        {\n            ImGui::SetScrollHereY();\n\n            if (ImGui::IsItemVisible())\n            {\n                scroll_to_selection = false;\n            }\n        }\n\n        ImGui::PopID();\n    }\n\n    //List actions\n    for (const auto& entry : m_ActionList)\n    {\n        ImGui::PushID((void*)entry.UID);\n\n        if ( (is_nav_focus_entry_pending) && (m_PageAnimationDir == 0) && (entry.UID == list_uid) )\n        {\n            ImGui::SetKeyboardFocusHere();\n            is_nav_focus_entry_pending = false;\n        }\n\n        if (ImGui::Selectable(entry.Name.c_str(), (entry.UID == list_uid) ))\n        {\n            list_uid = entry.UID;\n            m_ActionPickerUID = entry.UID;\n\n            PageGoBack();\n        }\n\n        if ( (scroll_to_selection) && (entry.UID == list_uid) )\n        {\n            ImGui::SetScrollHereY();\n\n            if (ImGui::IsItemVisible())\n            {\n                scroll_to_selection = false;\n            }\n        }\n\n        ImGui::PopID();\n    }\n\n    ImGui::EndChild();\n    ImGui::Unindent();\n\n    ImGui::SetCursorPosY( ImGui::GetCursorPosY() + (ImGui::GetContentRegionAvail().y - ImGui::GetFrameHeight()) );\n\n    //Cancel button\n    if (ImGui::Button(TranslationManager::GetString(tstr_DialogCancel))) \n    {\n        PageGoBack();\n    }\n}\n\nvoid WindowSettings::UpdatePageKeyCodePicker(bool only_restore_settings)\n{\n    ImGuiStyle& style = ImGui::GetStyle();\n    VRKeyboard& vr_keyboard = UIManager::Get()->GetVRKeyboard();\n\n    static ImGuiTextFilter filter;\n    static int list_id = 0;\n    static bool is_nav_focus_entry_pending = false;    //Focus has to be delayed until after the page animation is done\n    static bool scroll_to_selection = false;\n\n    static unsigned char key_code_prev = 0;\n    static bool mod_ctrl  = false;\n    static bool mod_alt   = false;\n    static bool mod_shift = false;\n    static bool mod_win   = false;\n\n    if (only_restore_settings)\n    {\n        //Only really need to reset this in hotkey mode\n        m_KeyCodePickerID = key_code_prev;\n        return;\n    }\n\n    if (m_PageAppearing == wndsettings_page_keycode_picker)\n    {\n        list_id = m_KeyCodePickerID;\n        key_code_prev = m_KeyCodePickerID;\n        scroll_to_selection = true;\n        is_nav_focus_entry_pending = ImGui::GetIO().NavVisible;\n\n        for (int i = 0; i < 256; i++)\n        {\n            //Not the smartest, but most straight forward way\n            if (GetKeyCodeForListID(i) == m_KeyCodePickerID)\n            {\n                list_id = i;\n\n                //Clear filter if it wouldn't show the current selection\n                if (!filter.PassFilter( (m_KeyCodePickerID == 0) ? TranslationManager::GetString(tstr_DialogKeyCodePickerKeyCodeNone) : GetStringForKeyCode(m_KeyCodePickerID) ))\n                {\n                    filter.Clear();\n                }\n\n                break;\n            }\n        }\n\n        if (m_KeyCodePickerHotkeyMode)\n        {\n            mod_ctrl  = (m_KeyCodePickerHotkeyFlags & MOD_CONTROL);\n            mod_alt   = (m_KeyCodePickerHotkeyFlags & MOD_ALT);\n            mod_shift = (m_KeyCodePickerHotkeyFlags & MOD_SHIFT);\n            mod_win   = (m_KeyCodePickerHotkeyFlags & MOD_WIN);\n        }\n    }\n\n    //Modifier flags if this displayed to set a hotkey\n    if (m_KeyCodePickerHotkeyMode)\n    {\n        ImGui::TextColoredUnformatted(ImGui::GetStyleColorVec4(ImGuiCol_ButtonHovered), TranslationManager::GetString(tstr_DialogKeyCodePickerHeaderHotkey)); \n        ImGui::Indent();\n\n        static float checkbox_width = 0.0f;\n\n        ImGui::AlignTextToFramePadding();\n        ImGui::TextUnformatted(TranslationManager::GetString(tstr_DialogKeyCodePickerModifiers));\n        ImGui::SameLine();\n\n        ImGui::SetCursorPosX(ImGui::GetCursorPosX() + ImGui::GetContentRegionAvail().x - checkbox_width);\n\n        ImGui::BeginGroup();\n        ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, {style.ItemSpacing.x * 2.0f, style.ItemSpacing.y});\n\n        ImGui::Checkbox(\"Ctrl\",  &mod_ctrl);    //These could be translated, but the whole key list isn't, so no point right now\n        ImGui::SameLine();\n        ImGui::Checkbox(\"Alt\",   &mod_alt);\n        ImGui::SameLine();\n        ImGui::Checkbox(\"Shift\", &mod_shift);\n        ImGui::SameLine();\n        ImGui::Checkbox(\"Win\",   &mod_win);\n\n        ImGui::PopStyleVar();\n        ImGui::EndGroup();\n\n        checkbox_width = ImGui::GetItemRectSize().x + style.ItemSpacing.x;\n\n        ImGui::TextUnformatted(TranslationManager::GetString(tstr_DialogKeyCodePickerKeyCode)); \n        ImGui::Spacing();\n    }\n    else //Normal header\n    {\n        ImGui::TextColoredUnformatted(ImGui::GetStyleColorVec4(ImGuiCol_ButtonHovered), TranslationManager::GetString(tstr_DialogKeyCodePickerHeader)); \n    }\n\n    ImGui::Indent();\n\n    ImGui::SetNextItemWidth(-1.0f);\n    vr_keyboard.SetShortcutWindowDirectionHint(ImGuiDir_Up);\n    vr_keyboard.VRKeyboardInputBegin(\"##FilterList\");\n    if (ImGui::InputTextWithHint(\"##FilterList\", TranslationManager::GetString(tstr_DialogKeyCodePickerKeyCodeHint), filter.InputBuf, IM_ARRAYSIZE(filter.InputBuf)))\n    {\n        UIManager::Get()->AddFontBuilderStringIfAnyUnmappedCharacters(filter.InputBuf);\n\n        filter.Build();\n    }\n    vr_keyboard.VRKeyboardInputEnd();\n\n    const float item_height = ImGui::GetFontSize() + style.ItemSpacing.y;\n    const float inner_padding = style.FramePadding.y + style.FramePadding.y + style.ItemInnerSpacing.y;\n    const float item_count_offset = (m_KeyCodePickerHotkeyMode) ? ((UIManager::Get()->IsInDesktopMode()) ? -2.5f : -2.0f) : 0.0f;\n    const float item_count = ((UIManager::Get()->IsInDesktopMode()) ? 21.0f : 16.0f) + item_count_offset;\n    ImGui::BeginChild(\"KeyCodePickerList\", ImVec2(-1.0f, (item_height * item_count) + inner_padding - m_WarningHeight), true);\n\n    unsigned char list_keycode;\n    const char* list_keycode_str = nullptr;\n    for (int i = 0; i < 256; i++)\n    {\n        list_keycode = GetKeyCodeForListID(i);\n        list_keycode_str = (list_keycode == 0) ? TranslationManager::GetString(tstr_DialogKeyCodePickerKeyCodeNone) : GetStringForKeyCode(list_keycode);\n        if (filter.PassFilter(list_keycode_str))\n        {\n            if ( (m_KeyCodePickerNoMouse) && (list_keycode >= VK_LBUTTON) && (list_keycode <= VK_XBUTTON2) && (list_keycode != VK_CANCEL) )    //Skip mouse buttons if turned off\n                continue;\n\n            if ( (is_nav_focus_entry_pending) && (m_PageAnimationDir == 0) && (i == list_id) )\n            {\n                ImGui::SetKeyboardFocusHere();\n                is_nav_focus_entry_pending = false;\n            }\n\n            if (ImGui::Selectable(list_keycode_str, (i == list_id)))\n            {\n                list_id = i;\n                m_KeyCodePickerID = list_keycode;\n\n                if (!m_KeyCodePickerHotkeyMode)\n                {\n                    key_code_prev = m_KeyCodePickerID; //Prevent it from being reset\n                    PageGoBack();\n                }\n            }\n\n            if ((ImGui::IsItemClicked()) && (ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left)))\n            {\n                key_code_prev = m_KeyCodePickerID; //Prevent it from being reset\n                PageGoBack();\n            }\n\n            if ( (scroll_to_selection) && (i == list_id) )\n            {\n                ImGui::SetScrollHereY();\n\n                if (ImGui::IsItemVisible())\n                {\n                    scroll_to_selection = false;\n                }\n            }\n        }\n    }\n\n    ImGui::EndChild();\n    ImGui::Unindent();\n\n    if (m_KeyCodePickerHotkeyMode)\n    {\n        ImGui::Unindent();\n    }\n\n    ImGui::SetCursorPosY( ImGui::GetCursorPosY() + (ImGui::GetContentRegionAvail().y - ImGui::GetFrameHeight()) );\n\n    //Confirmation buttons\n    if (m_KeyCodePickerHotkeyMode)\n    {\n        if (ImGui::Button(TranslationManager::GetString(tstr_DialogOk)))\n        {\n            m_KeyCodePickerHotkeyFlags = 0;\n\n            if (mod_ctrl)\n                m_KeyCodePickerHotkeyFlags |= MOD_CONTROL;\n            if (mod_alt)\n                m_KeyCodePickerHotkeyFlags |= MOD_ALT;\n            if (mod_shift)\n                m_KeyCodePickerHotkeyFlags |= MOD_SHIFT;\n            if (mod_win)\n                m_KeyCodePickerHotkeyFlags |= MOD_WIN;\n\n            //Prevent restore settings code from overwriting it later\n            key_code_prev = m_KeyCodePickerID;\n\n            PageGoBack();\n        }\n\n        ImGui::SameLine();\n    }\n\n    if (ImGui::Button(TranslationManager::GetString(tstr_DialogCancel))) \n    {\n        PageGoBack();\n    }\n\n    //\"From Input...\" button\n    if (UIManager::Get()->IsInDesktopMode())\n    {\n        ImGui::SameLine();\n\n        static float list_buttons_width = 0.0f;\n        ImGui::SetCursorPosX(ImGui::GetCursorPosX() + ImGui::GetContentRegionAvail().x - list_buttons_width);\n\n        if (ImGui::Button(TranslationManager::GetString(tstr_DialogKeyCodePickerFromInput)))\n        {\n            ImGui::OpenPopup(\"Bind Key\");\n        }\n\n        list_buttons_width = ImGui::GetItemRectSize().x;\n\n        ImGui::SetNextWindowPos({ImGui::GetIO().DisplaySize.x * 0.5f, ImGui::GetIO().DisplaySize.y * 0.5f}, ImGuiCond_Always, ImVec2(0.5f, 0.5f));\n        if (ImGui::BeginPopupModal(\"Bind Key\", nullptr, ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoNavInputs))\n        {\n            ImGui::Text(TranslationManager::GetString( (m_KeyCodePickerNoMouse) ? tstr_DialogKeyCodePickerFromInputPopupNoMouse : tstr_DialogKeyCodePickerFromInputPopup ));\n\n            ImGuiIO& io = ImGui::GetIO();\n\n            //We can no longer use ImGui's keyboard state to query all possible keyboard keys, so we do it manually via GetAsyncKeyState()\n            //To avoid issues with keys that are already down to begin with, we store the state in the moment of the popup appearing and only act on changes to that\n            static bool keyboard_state_initial[255] = {0};\n            static bool wait_for_key_release = false; \n\n            if (ImGui::IsWindowAppearing())\n            {\n                for (int i = 0; i < IM_ARRAYSIZE(keyboard_state_initial); ++i)\n                {\n                    keyboard_state_initial[i] = (::GetAsyncKeyState(i) < 0);\n                }\n\n                wait_for_key_release = false;\n            }\n\n            for (int i = 0; i < IM_ARRAYSIZE(keyboard_state_initial); ++i)\n            {\n                if ((::GetAsyncKeyState(i) < 0) != keyboard_state_initial[i])\n                {\n                    //Key was up before, so it's a key press\n                    if (!keyboard_state_initial[i])\n                    {\n                        //Ignore mouse buttons if they're disabled\n                        bool skip_key = false;\n                        if (m_KeyCodePickerNoMouse)\n                        {\n                            switch (i)\n                            {\n                                case VK_LBUTTON:\n                                case VK_RBUTTON:\n                                case VK_MBUTTON:\n                                case VK_XBUTTON1:\n                                case VK_XBUTTON2: skip_key = true;\n                            }\n                        }\n\n                        if (!skip_key)\n                        {\n                            m_KeyCodePickerID = i;\n\n                            if (!m_KeyCodePickerHotkeyMode)\n                            {\n                                key_code_prev = m_KeyCodePickerID; //Prevent it from being reset\n                            }\n\n                            for (int i = 0; i < 256; i++)\n                            {\n                                if (GetKeyCodeForListID(i) == m_KeyCodePickerID)\n                                {\n                                    list_id = i;\n                                    break;\n                                }\n                            }\n\n                            scroll_to_selection = true;\n\n                            //Wait for the key to be released to avoid inputs triggering other things\n                            wait_for_key_release = true;\n                            keyboard_state_initial[i] = true;\n                        }\n                        break;\n                    }\n                    else   //Key was down before, so it's a key release. Update the initial state so it can be pressed again and registered as such\n                    {\n                        keyboard_state_initial[i] = false;\n\n                        //Close popup here if we are waiting for this key to be released\n                        if ((wait_for_key_release) && (i == m_KeyCodePickerID))\n                        {\n                            ImGui::CloseCurrentPopup();\n                            io.ClearInputKeys();\n\n                            if (!m_KeyCodePickerHotkeyMode)\n                            {\n                                PageGoBack();\n                            }\n                        }\n                    }\n                }\n            }\n\n            ImGui::EndPopup();\n        }\n    }\n}\n\nvoid WindowSettings::UpdatePageIconPicker()\n{\n    ImGuiStyle& style = ImGui::GetStyle();\n\n    static std::vector<std::string> icon_file_list;\n    static int list_id = 0;\n    static bool is_nav_focus_entry_pending = false;    //Focus has to be delayed until after the page animation is done\n    static bool scroll_to_selection = false;\n\n    if (m_PageAppearing == wndsettings_page_icon_picker)\n    {\n        list_id = 0;\n        scroll_to_selection = true;\n        is_nav_focus_entry_pending = ImGui::GetIO().NavVisible;\n\n        icon_file_list = ActionManager::GetIconFileList();\n        icon_file_list.insert(icon_file_list.begin(), TranslationManager::GetString(tstr_DialogIconPickerNone));\n\n        //Select matching entry\n        auto it = std::find_if(icon_file_list.begin(), icon_file_list.end(), [&](const auto& list_entry){ return (m_IconPickerFile == list_entry); });\n\n        if (it != icon_file_list.end())\n        {\n            list_id = (int)std::distance(icon_file_list.begin(), it);\n        }\n    }\n\n    ImGui::TextColoredUnformatted(ImGui::GetStyleColorVec4(ImGuiCol_ButtonHovered), TranslationManager::GetString(tstr_DialogIconPickerHeader));\n    ImGui::SameLine(0.0f, style.ItemInnerSpacing.x);\n    HelpMarker(TranslationManager::GetString(tstr_DialogIconPickerHeaderTip));\n\n    ImGui::Indent();\n\n    ImGui::SetNextItemWidth(-1.0f);\n    const float item_height = ImGui::GetFontSize() + style.ItemSpacing.y;\n    const float inner_padding = style.FramePadding.y + style.FramePadding.y + style.ItemInnerSpacing.y;\n    const float item_count = (UIManager::Get()->IsInDesktopMode()) ? 22.0f : 18.0f;\n    ImGui::BeginChild(\"IconPickerList\", ImVec2(0.0f, (item_height * item_count) + inner_padding - m_WarningHeight), true);\n\n    int i = 0;\n    for (const auto& file_entry : icon_file_list)\n    {\n        if ( (is_nav_focus_entry_pending) && (m_PageAnimationDir == 0) && (i == list_id) )\n        {\n            ImGui::SetKeyboardFocusHere();\n            is_nav_focus_entry_pending = false;\n        }\n\n        if (ImGui::Selectable( file_entry.c_str(), (i == list_id) ))\n        {\n            list_id = i;\n\n            std::string icon_path = \"images/icons/\" + file_entry;\n            TextureManager::Get().SetTextureFilenameIconTemp(WStringConvertFromUTF8(icon_path.c_str()).c_str());\n            TextureManager::Get().ReloadAllTexturesLater(); //Reloading everything on changing one icon path? Seems excessive, but works fine for now.\n\n            UIManager::Get()->RepeatFrame();\n        }\n\n        if ((ImGui::IsItemClicked()) && (ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left)))\n        {\n            m_IconPickerFile = (list_id != 0) ? icon_file_list[list_id] : \"\";\n\n            PageGoBack();\n        }\n\n        if ( (scroll_to_selection) && (i == list_id) )\n        {\n            ImGui::SetScrollHereY();\n\n            if (ImGui::IsItemVisible())\n            {\n                scroll_to_selection = false;\n            }\n        }\n\n        ++i;\n    }\n\n    const bool has_scrollbar = ImGui::IsAnyScrollBarVisible();\n\n    ImGui::EndChild();\n    ImGui::Unindent();\n\n    //Icon Preview\n    if (list_id != 0)\n    {\n        ImVec2 i_size, i_uv_min, i_uv_max;\n        TextureManager::Get().GetTextureInfo(tmtex_icon_settings, i_size, i_uv_min, i_uv_max);\n        //Default image size for custom actions\n        ImVec2 i_size_default = i_size;\n\n        //Draw at bottom right corner of the child window, but don't cover scroll bar if there is any\n        ImVec2 preview_max = ImGui::GetItemRectMax();\n\n        if (has_scrollbar)\n            preview_max.x -= style.ScrollbarSize;\n\n        preview_max.x -= style.ItemInnerSpacing.x;\n        preview_max.y -= style.ItemInnerSpacing.y;\n\n        ImVec2 preview_pos = preview_max;\n        preview_pos.x -= i_size_default.x;\n        preview_pos.y -= i_size_default.y;\n\n        TextureManager::Get().GetTextureInfo(tmtex_icon_temp, i_size, i_uv_min, i_uv_max);\n        ImGui::GetForegroundDrawList()->AddImage(ImGui::GetIO().Fonts->TexID, preview_pos, preview_max, i_uv_min, i_uv_max);\n    }\n\n    ImGui::SetCursorPosY( ImGui::GetCursorPosY() + (ImGui::GetContentRegionAvail().y - ImGui::GetFrameHeight()) );\n\n    //Confirmation buttons\n    if (ImGui::Button(TranslationManager::GetString(tstr_DialogOk))) \n    {\n        m_IconPickerFile = (list_id != 0) ? icon_file_list[list_id] : \"\";\n\n        PageGoBack();\n    }\n\n    ImGui::SameLine();\n\n    if (ImGui::Button(TranslationManager::GetString(tstr_DialogCancel))) \n    {\n        PageGoBack();\n    }\n}\n\nvoid WindowSettings::UpdatePageWindowPicker()\n{\n    ImGuiStyle& style = ImGui::GetStyle();\n\n    static HWND list_hwnd = nullptr;\n    static bool is_nav_focus_entry_pending = false;    //Focus has to be delayed until after the page animation is done\n    static bool scroll_to_selection = false;\n    static ImVec2 no_actions_text_size;\n\n    if (m_PageAppearing == wndsettings_page_action_picker)\n    {\n        is_nav_focus_entry_pending = m_WindowPickerHWND;\n        scroll_to_selection = true;\n        is_nav_focus_entry_pending = ImGui::GetIO().NavVisible;\n    }\n\n    ImGui::TextColoredUnformatted(ImGui::GetStyleColorVec4(ImGuiCol_ButtonHovered), TranslationManager::GetString(tstr_DialogWindowPickerHeader)); \n    ImGui::Indent();\n\n    ImGui::SetNextItemWidth(-1.0f);\n    const float item_height = ImGui::GetFontSize() + style.ItemSpacing.y;\n    const float inner_padding = style.FramePadding.y + style.FramePadding.y + style.ItemInnerSpacing.y;\n    const float item_count = (UIManager::Get()->IsInDesktopMode()) ? 22.0f : 15.0f;\n    ImGui::BeginChild(\"WindowPickerList\", ImVec2(0.0f, (item_height * item_count) + inner_padding - m_WarningHeight), true);\n\n    //List windows\n    ImVec2 img_size_line_height = {ImGui::GetTextLineHeight(), ImGui::GetTextLineHeight()};\n    ImVec2 img_size, img_uv_min, img_uv_max;\n    const ImVec2 combo_pos = ImGui::GetCursorScreenPos();\n\n    for (const auto& window_info : WindowManager::Get().WindowListGet())\n    {\n        ImGui::PushID(window_info.GetWindowHandle());\n        if (ImGui::Selectable(\"\", (list_hwnd == window_info.GetWindowHandle()) ))\n        {\n            list_hwnd = window_info.GetWindowHandle();\n            m_WindowPickerHWND = list_hwnd;\n\n            PageGoBack();\n        }\n\n        if ( (scroll_to_selection) && (list_hwnd == window_info.GetWindowHandle()) )\n        {\n            ImGui::SetScrollHereY();\n\n            if (ImGui::IsItemVisible())\n            {\n                scroll_to_selection = false;\n            }\n        }\n\n        ImGui::SameLine(0.0f, 0.0f);\n\n        int icon_id = TextureManager::Get().GetWindowIconCacheID(window_info.GetIcon());\n\n        if (icon_id != -1)\n        {\n            TextureManager::Get().GetWindowIconTextureInfo(icon_id, img_size, img_uv_min, img_uv_max);\n            ImGui::Image(ImGui::GetIO().Fonts->TexID, img_size_line_height, img_uv_min, img_uv_max);\n\n            ImGui::SameLine(0.0f, style.ItemInnerSpacing.x);\n        }\n\n        ImGui::TextUnformatted(window_info.GetListTitle().c_str());\n\n        ImGui::PopID();\n    }\n\n    ImGui::EndChild();\n    ImGui::Unindent();\n\n    ImGui::SetCursorPosY( ImGui::GetCursorPosY() + (ImGui::GetContentRegionAvail().y - ImGui::GetFrameHeight()) );\n\n    //Cancel button\n    if (ImGui::Button(TranslationManager::GetString(tstr_DialogCancel))) \n    {\n        PageGoBack();\n    }\n}\n\nvoid WindowSettings::UpdatePageResetConfirm()\n{\n    //Check if it makes sense to show the checkbox for deleting legacy files\n    static bool show_delete_legacy_check = false;\n\n    if (m_PageAppearing == m_PageCurrent)\n    {\n        const std::wstring wpath_config          = WStringConvertFromUTF8( std::string(ConfigManager::Get().GetApplicationPath() + \"config_legacy.ini\"       ).c_str() );\n        const std::wstring wpath_profiles_single = WStringConvertFromUTF8( std::string(ConfigManager::Get().GetApplicationPath() + \"profiles/overlays/\"      ).c_str() );\n        const std::wstring wpath_profiles_multi  = WStringConvertFromUTF8( std::string(ConfigManager::Get().GetApplicationPath() + \"profiles/multi-overlays/\").c_str() );\n\n        show_delete_legacy_check = ( (FileExists(wpath_config.c_str())) || (DirectoryExists(wpath_profiles_single.c_str())) || (DirectoryExists(wpath_profiles_multi.c_str())) );\n    }\n\n    ImGui::TextColoredUnformatted(ImGui::GetStyleColorVec4(ImGuiCol_ButtonHovered), TranslationManager::GetString(tstr_SettingsTroubleshootingSettingsReset) ); \n    ImGui::Indent();\n\n    ImGui::PushTextWrapPos();\n    ImGui::TextUnformatted(TranslationManager::GetString(tstr_SettingsTroubleshootingSettingsResetConfirmDescription));\n    ImGui::PopTextWrapPos();\n\n    static bool reset_settings = true, reset_current_profile = true, reset_profile_overlays = false, reset_profile_apps = false, reset_actions = false, delete_legacy = false;\n\n    //This uses existing translation strings to avoid duplication of strings that should reasonably stay the same for this context\n    //Might come back to bite for some language but we'll see about that\n    ImGui::Indent();\n    ImGui::Checkbox(TranslationManager::GetString(tstr_SettingsInterfacePersistentUIWindowsSettings), &reset_settings);\n    ImGui::Checkbox(TranslationManager::GetString(tstr_SettingsTroubleshootingSettingsResetConfirmElementOverlays), &reset_current_profile);\n    ImGui::Checkbox(TranslationManager::GetString(tstr_SettingsProfilesOverlays), &reset_profile_overlays);\n    ImGui::Checkbox(TranslationManager::GetString(tstr_SettingsProfilesApps), &reset_profile_apps);\n    ImGui::Checkbox(TranslationManager::GetString(tstr_SettingsCatActions), &reset_actions);\n\n    if (show_delete_legacy_check)\n    {\n        ImGui::Checkbox(TranslationManager::GetString(tstr_SettingsTroubleshootingSettingsResetConfirmElementLegacyFiles), &delete_legacy);\n    }\n\n    ImGui::Unindent();\n\n    ImGui::Unindent();\n    ImGui::SetCursorPosY( ImGui::GetCursorPosY() + (ImGui::GetContentRegionAvail().y - ImGui::GetFrameHeightWithSpacing()) );\n\n    //Confirmation buttons\n    ImGui::Separator();\n\n    if (ImGui::Button(TranslationManager::GetString(tstr_SettingsTroubleshootingSettingsResetConfirmButton))) \n    {\n        //Do the reset\n        if (reset_settings)\n        {\n            //If resetting current profile isn't on, store it separately for a moment so we can get it back after resetting the whole config file\n            if (!reset_current_profile)\n            {\n                ConfigManager::Get().SaveMultiOverlayProfileToFile(\"../overlays_temp.ini\");\n            }\n\n            ConfigManager::Get().RestoreConfigFromDefault();\n\n            if (!reset_current_profile)\n            {\n                ConfigManager::Get().LoadMultiOverlayProfileFromFile(\"../overlays_temp.ini\");\n                ConfigManager::Get().DeleteOverlayProfile(\"../overlays_temp.ini\");\n            }\n        }\n        else if (reset_current_profile)\n        {\n            ConfigManager::Get().LoadOverlayProfileDefault(true);\n        }\n\n        if (reset_profile_overlays)\n        {\n            ConfigManager::Get().DeleteAllOverlayProfiles();\n        }\n\n        if (reset_profile_apps)\n        {\n            ConfigManager::Get().GetAppProfileManager().RemoveAllProfiles();\n        }\n\n        if (reset_actions)\n        {\n            ConfigManager::Get().GetActionManager().RestoreActionsFromDefault();\n        }\n\n        if (delete_legacy)\n        {\n            const std::wstring wpath_config = WStringConvertFromUTF8( std::string(ConfigManager::Get().GetApplicationPath() + \"config_legacy.ini\").c_str() );\n            //The folder paths are double-NUL terminated for SHFileOperationW()\n            const std::wstring wpath_profiles_single = WStringConvertFromUTF8( std::string(ConfigManager::Get().GetApplicationPath() + \"profiles/overlays/\"      ).c_str() ) + L'\\0';\n            const std::wstring wpath_profiles_multi  = WStringConvertFromUTF8( std::string(ConfigManager::Get().GetApplicationPath() + \"profiles/multi-overlays/\").c_str() ) + L'\\0';\n\n            //Delete config_legacy.ini\n            ::DeleteFileW(wpath_config.c_str());\n\n            //Delete folders recursively with contained files\n            SHFILEOPSTRUCTW fileop = {0};\n            fileop.wFunc  = FO_DELETE;\n            fileop.fFlags = FOF_NO_UI;\n\n            fileop.pFrom = wpath_profiles_single.c_str();\n            ::SHFileOperationW(&fileop);\n\n            fileop.pFrom = wpath_profiles_multi.c_str();\n            ::SHFileOperationW(&fileop);\n        }\n\n        UIManager::Get()->Restart(UIManager::Get()->IsInDesktopMode());\n\n        //We restart this after the UI since the new UI process needs to detect the dashboard app running first so it doesn't launch in desktop mode\n        if (IPCManager::IsDashboardAppRunning())\n        {\n            UIManager::Get()->RestartDashboardApp();\n        }\n    }\n\n    ImGui::SameLine();\n\n    if (ImGui::Button(TranslationManager::GetString(tstr_DialogCancel))) \n    {\n        PageGoBack();\n    }\n\n    //Show Quick-Start Guide\n    if (!UIManager::Get()->IsInDesktopMode())\n    {\n        static float button_quick_start_width = -1.0f;\n        const bool button_quick_start_enabled = ConfigManager::GetValue(configid_bool_interface_quick_start_hidden);\n\n        ImGui::SameLine(ImGui::GetContentRegionAvail().x - button_quick_start_width);\n\n        if (!button_quick_start_enabled)\n            ImGui::PushItemDisabled();\n\n        if (ImGui::Button(TranslationManager::GetString(tstr_SettingsTroubleshootingSettingsResetShowQuickStart))) \n        {\n            UIManager::Get()->GetAuxUI().GetQuickStartWindow().Reset();\n        }\n        button_quick_start_width = ImGui::GetItemRectSize().x;\n\n        if (!button_quick_start_enabled)\n            ImGui::PopItemDisabled();\n    }\n}\n\nvoid WindowSettings::PageGoForward(WindowSettingsPage new_page)\n{\n    //We can't just mess with the stack while a backwards animation is going, so we save this for later\n    if (m_PageAnimationDir == 1)\n    {\n        m_PageStackPending.push_back(new_page);\n        return;\n    }\n\n    m_PageStack.push_back(new_page);\n    m_PageStackPos++;\n}\n\nvoid WindowSettings::PageGoBack()\n{\n    if (m_PageStackPos != 0)\n    {\n        OnPageLeaving(m_PageStack[m_PageStackPos]);\n        m_PageStackPos--;\n        m_PageReturned = m_PageStack.back();\n    }\n}\n\nvoid WindowSettings::PageGoBackInstantly()\n{\n    //Go back while skipping any active animations\n    PageGoBack();\n\n    while ((int)m_PageStack.size() > m_PageStackPosAnimation)\n    {\n        m_PageStack.pop_back();\n    }\n\n    m_PageAnimationDir      = 0;\n    m_PageStackPosAnimation = m_PageStackPos;\n    m_PageAnimationOffset   = m_PageAnimationStartPos;\n    m_PageAnimationProgress = 0.0f;\n}\n\nvoid WindowSettings::PageGoHome()\n{\n    while (m_PageStackPos != 0)\n    {\n        OnPageLeaving(m_PageStack[m_PageStackPos]);\n        m_PageStackPos--;\n    }\n}\n\nvoid WindowSettings::OnPageLeaving(WindowSettingsPage previous_page)\n{\n    switch (previous_page)\n    {\n        case wndsettings_page_keyboard:\n        {\n            UpdatePageKeyboardLayout(true); //Call to reset settings\n            break;\n        }\n        case wndsettings_page_actions_edit:\n        {\n            UpdatePageActionsEdit(true); //Call to reset settings\n            break;\n        }\n        case wndsettings_page_actions_order:\n        {\n            UpdatePageActionsOrder(true); //Call to reset settings\n            break;\n        }\n        case wndsettings_page_color_picker:\n        {\n            UpdatePageColorPicker(true); //Call to reset settings\n            break;\n        }\n        case wndsettings_page_keycode_picker:\n        {\n            UpdatePageKeyCodePicker(true); //Call to reset settings\n            break;\n        }\n        default: break;\n    }\n}\n\nvoid WindowSettings::SelectableWarning(const char* selectable_id, const char* popup_id, const char* text, bool show_warning_prefix, const ImVec4* text_color)\n{\n    float* selectable_height = ImGui::GetStateStorage()->GetFloatRef(ImGui::GetID(selectable_id), 1.0f);\n    const bool is_active = ImGui::IsPopupOpen(popup_id);\n\n    //Force active header color when a menu is active for consistency with other context menus in the app\n    if (is_active)\n    {\n        ImGui::PushStyleColor(ImGuiCol_Header,        ImGui::GetStyleColorVec4(ImGuiCol_HeaderActive));\n        ImGui::PushStyleColor(ImGuiCol_HeaderHovered, ImGui::GetStyleColorVec4(ImGuiCol_HeaderActive));\n    }\n\n    //Use selectable stretching over the text area to make it clickable\n    if (ImGui::Selectable(selectable_id, ImGui::IsPopupOpen(popup_id), 0, {0.0f, *selectable_height}))\n    {\n        ImGui::OpenPopup(popup_id);\n        UIManager::Get()->RepeatFrame();    //Avoid flicker from IsPopupOpen() not being true right away\n    }\n    ImGui::SameLine(0.0f, 0.0f);\n\n    const bool is_selectable_focused = ImGui::IsItemFocused();\n\n    //Render text (with wrapping for the actual warning text)\n    ImGui::PushStyleColor(ImGuiCol_Text, (text_color != nullptr) ? *text_color : Style_ImGuiCol_TextWarning);\n\n    if (show_warning_prefix)\n    {\n        ImGui::TextUnformatted(TranslationManager::GetString(tstr_SettingsWarningPrefix));\n        ImGui::SameLine();\n    }\n\n    ImGui::PushTextWrapPos();\n    ImGui::TextUnformatted(text);\n    ImGui::PopTextWrapPos();\n\n    ImGui::PopStyleColor();\n\n    if (is_active)\n    {\n        ImGui::PopStyleColor();\n        ImGui::PopStyleColor();\n    }\n\n    //Store height for the selectable for next time if window is being hovered or selectable focused (could get bogus value otherwise)\n    if ( (ImGui::IsWindowHovered()) || (is_selectable_focused) )\n    {\n        *selectable_height = ImGui::GetItemRectSize().y;\n    }\n}\n\nvoid WindowSettings::SelectableHotkey(ConfigHotkey& hotkey, int id)\n{\n    static int edited_id = -1;\n\n    //Write changes if we're returning from a picker\n    if ((m_PageReturned == wndsettings_page_keycode_picker) && (edited_id == id))\n    {\n        hotkey.KeyCode   = m_KeyCodePickerID;\n        hotkey.Modifiers = m_KeyCodePickerHotkeyFlags;\n\n        IPCManager::Get().SendStringToDashboardApp(configid_str_state_hotkey_data, hotkey.Serialize(), UIManager::Get()->GetWindowHandle());\n        IPCManager::Get().PostMessageToDashboardApp(ipcmsg_action, ipcact_hotkey_set, id);\n\n        m_PageReturned = wndsettings_page_none;\n        hotkey.StateUIName = \"\";\n        edited_id = -1;\n    }\n\n    //Update cached hotkey name if window is just appearing or the name is empty\n    if ( (m_PageAppearing == m_PageCurrent) || (hotkey.StateUIName.empty()) )\n    {\n        hotkey.StateUIName = \"\";\n\n        if (hotkey.KeyCode != 0)\n        {\n            if (hotkey.Modifiers & MOD_CONTROL)\n                hotkey.StateUIName += \"Ctrl+\";\n            if (hotkey.Modifiers & MOD_ALT)\n                hotkey.StateUIName += \"Alt+\";\n            if (hotkey.Modifiers & MOD_SHIFT)\n                hotkey.StateUIName += \"Shift+\";\n            if (hotkey.Modifiers & MOD_WIN)\n                hotkey.StateUIName += \"Win+\";\n        }\n\n        hotkey.StateUIName += (hotkey.KeyCode == 0) ? TranslationManager::GetString(tstr_DialogKeyCodePickerKeyCodeNone) : GetStringForKeyCode(hotkey.KeyCode);\n    }\n\n    ImGui::PushID(\"HotkeySelectable\");\n    if (ImGui::Selectable(hotkey.StateUIName.c_str()))\n    {\n        m_KeyCodePickerNoMouse    = false;\n        m_KeyCodePickerHotkeyMode = true;\n        m_KeyCodePickerHotkeyFlags = hotkey.Modifiers;\n        m_KeyCodePickerID = hotkey.KeyCode;\n        edited_id = id;\n\n        PageGoForward(wndsettings_page_keycode_picker);\n        m_PageReturned = wndsettings_page_none;\n    }\n    ImGui::PopID();\n}\n\nvoid WindowSettings::RefreshAppList()\n{\n    m_AppList.clear();\n\n    //Each section is sorted alphabetically before being appended to the app list (via Win32, so UTF-16 needed)\n    struct app_sublist_entry\n    {\n        std::string app_key;\n        std::string app_name_utf8;\n        std::wstring app_name_utf16;\n    };\n    std::vector<app_sublist_entry> app_sublist;\n    auto app_sublist_compare = [](app_sublist_entry& a, app_sublist_entry& b) { return WStringCompareNatural(a.app_name_utf16, b.app_name_utf16); };\n\n    std::unordered_set<std::string> unique_app_keys;\n    char app_key_buffer[vr::k_unMaxApplicationKeyLength] = \"\";\n    char app_prop_buffer[vr::k_unMaxPropertyStringSize]  = \"\";\n    vr::EVRApplicationError app_error = vr::VRApplicationError_None;\n\n    //Have some keys in the unique app key set by default as a way to never show them in the actual list\n    unique_app_keys.insert(\"steam.app.250820\");                         //\"SteamVR\"\n    unique_app_keys.insert(\"openvr.tool.steamvr_tutorial\");\n    unique_app_keys.insert(\"openvr.tool.steamvr_room_setup\");\n    unique_app_keys.insert(\"openvr.tool.steamvr_environments_tools\");\n\n    //Add active app first\n    if (UIManager::Get()->IsOpenVRLoaded())\n    {\n        app_error = vr::VRApplications()->GetApplicationKeyByProcessId(vr::VRApplications()->GetCurrentSceneProcessId(), app_key_buffer, vr::k_unMaxApplicationKeyLength);\n\n        if (app_error == vr::VRApplicationError_None)\n        {\n            std::string app_key  = app_key_buffer;\n            std::string app_name = app_key;\n\n            vr::VRApplications()->GetApplicationPropertyString(app_key_buffer, vr::VRApplicationProperty_Name_String, app_prop_buffer, vr::k_unMaxPropertyStringSize, &app_error);\n\n            if (app_error == vr::VRApplicationError_None)\n            {\n                app_name = app_prop_buffer;\n            }\n\n            unique_app_keys.insert(app_key);\n            m_AppList.emplace_back(app_key, app_name);\n        }\n    }\n\n    //Add apps with profiles before listing all eligble apps\n    for (const auto& app_key : ConfigManager::Get().GetAppProfileManager().GetProfileAppKeyList())\n    {\n        std::string app_name;\n\n        //Only add if not already in list\n        if (unique_app_keys.find(app_key) == unique_app_keys.end())\n        {\n            if (UIManager::Get()->IsOpenVRLoaded())\n            {\n                vr::VRApplications()->GetApplicationPropertyString(app_key.c_str(), vr::VRApplicationProperty_Name_String, app_prop_buffer, vr::k_unMaxPropertyStringSize, &app_error);\n\n                if (app_error == vr::VRApplicationError_None)\n                {\n                    app_name = app_prop_buffer;\n                }\n            }\n            \n            //Fall back to last known application name if we can't get it from SteamVR\n            if (app_name.empty())\n            {\n                app_name = ConfigManager::Get().GetAppProfileManager().GetProfile(app_key).LastApplicationName;\n\n                //If that's still empty for some reason (shouldn't happen), fall back to app key\n                if (app_name.empty())\n                {\n                    app_name = app_key;\n                }\n            }\n\n            unique_app_keys.insert(app_key);\n            app_sublist.push_back( {app_key, app_name, WStringConvertFromUTF8(app_name.c_str())} );\n        }\n    }\n\n    //Sort this list before adding it to the rest\n    std::sort(app_sublist.begin(), app_sublist.end(), app_sublist_compare);\n\n    for (const auto& app : app_sublist)\n    {\n        m_AppList.emplace_back(app.app_key, app.app_name_utf8);\n    }\n\n    app_sublist.clear();\n\n    //List registered apps\n    if (UIManager::Get()->IsOpenVRLoaded())\n    {\n        uint32_t app_count = vr::VRApplications()->GetApplicationCount();\n\n        for (uint32_t i = 0; i < app_count; ++i)\n        {\n            app_error = vr::VRApplications()->GetApplicationKeyByIndex(i, app_key_buffer, vr::k_unMaxApplicationKeyLength);\n\n            if (app_error == vr::VRApplicationError_None)\n            {\n                const bool is_installed = vr::VRApplications()->IsApplicationInstalled(app_key_buffer);\n                const bool is_overlay   = vr::VRApplications()->GetApplicationPropertyBool(app_key_buffer, vr::VRApplicationProperty_IsDashboardOverlay_Bool);\n                const bool is_internal  = vr::VRApplications()->GetApplicationPropertyBool(app_key_buffer, vr::VRApplicationProperty_IsInternal_Bool);\n                //Application manifests and their properties aren't really documented but these two properties don't seem to matter for us\n                //const bool is_template  = vr::VRApplications()->GetApplicationPropertyBool(app_key_buffer, vr::VRApplicationProperty_IsTemplate_Bool);\n                //const bool is_instanced = vr::VRApplications()->GetApplicationPropertyBool(app_key_buffer, vr::VRApplicationProperty_IsInstanced_Bool);\n\n                if ( (is_installed) && (!is_overlay) && (!is_internal) )\n                {\n                    std::string app_key  = app_key_buffer;\n                    std::string app_name = app_key;\n\n                    //Only add if not already in list\n                    if (unique_app_keys.find(app_key) == unique_app_keys.end())\n                    {\n                        vr::VRApplications()->GetApplicationPropertyString(app_key_buffer, vr::VRApplicationProperty_Name_String, app_prop_buffer, vr::k_unMaxPropertyStringSize, &app_error);\n\n                        if (app_error == vr::VRApplicationError_None)\n                        {\n                            app_name = app_prop_buffer;\n                        }\n\n                        unique_app_keys.insert(app_key);\n                        app_sublist.push_back( {app_key, app_name, WStringConvertFromUTF8(app_name.c_str())} );\n                    }\n                }\n            }\n        }\n\n        //Sort this list before adding it to the rest\n        std::sort(app_sublist.begin(), app_sublist.end(), app_sublist_compare);\n\n        for (const auto& app : app_sublist)\n        {\n            m_AppList.emplace_back(app.app_key, app.app_name_utf8);\n        }\n    }\n}\n"
  },
  {
    "path": "src/DesktopPlusUI/WindowSettings.h",
    "content": "#pragma once\n\n#include \"FloatingWindow.h\"\n#include \"WindowDesktopMode.h\"\n\nenum WindowSettingsPage\n{\n    wndsettings_page_none,\n    wndsettings_page_main,\n    wndsettings_page_persistent_ui,\n    wndsettings_page_keyboard,\n    wndsettings_page_profiles,\n    wndsettings_page_profiles_overlay_select,\n    wndsettings_page_app_profiles,\n    wndsettings_page_actions,\n    wndsettings_page_actions_edit,\n    wndsettings_page_actions_order,\n    wndsettings_page_actions_order_add,\n    wndsettings_page_color_picker,\n    wndsettings_page_profile_picker,\n    wndsettings_page_action_picker,\n    wndsettings_page_keycode_picker,\n    wndsettings_page_icon_picker,\n    wndsettings_page_window_picker,\n    wndsettings_page_reset_confirm\n};\n\nclass WindowSettings : public FloatingWindow, public FloatingWindowDesktopModeInterop\n{\n    private:\n        std::vector<WindowSettingsPage> m_PageStack;\n        std::vector<WindowSettingsPage> m_PageStackPending; //Stores pending page forward calls that they were requested during an active backwards animation\n        int m_PageStackPos;\n        int m_PageStackPosAnimation;\n        WindowSettingsPage m_PageAppearing; //Similar to ImGui::IsWindowAppearing(), equals the current page ID for a single frame if it or the window is newly appearing\n        WindowSettingsPage m_PageReturned;  //Equals the previous page ID after PageGoBack() was called, ideally cleared after making use of its value\n        WindowSettingsPage m_PageCurrent;   //Equals the current page ID during the function call\n\n        int m_PageAnimationDir;\n        float m_PageAnimationProgress;\n        float m_PageAnimationStartPos;\n        float m_PageAnimationOffset;\n\n        float m_Column0Width;\n        float m_WarningHeight;\n\n        std::string m_WarningTextOverlayError;\n        std::string m_WarningTextWinRTError;\n        std::string m_WarningTextAppProfile;\n        std::string m_TranslationAuthorLabel;\n        std::string m_ActionButtonsDefaultLabel;\n        std::string m_ActionButtonsOverlayBarLabel;\n        std::vector<std::string> m_ActionGlobalShortcutLabels;\n        std::string m_BrowserMaxFPSValueText;\n        std::string m_BrowserBlockListCountText;\n\n        std::string m_ProfileSelectionName;\n        bool m_ProfileOverlaySelectIsSaving;\n        std::vector<std::string> m_ProfileList;\n\n        ActionUID m_ActionSelectionUID;\n        std::vector<ActionManager::ActionNameListEntry> m_ActionList;\n        bool m_ActionOrderListEditForOverlayBar;\n\n        std::string m_ProfilePickerName;\n        ActionUID m_ActionPickerUID;\n        unsigned char m_KeyCodePickerID;\n        unsigned int m_KeyCodePickerHotkeyFlags;\n        bool m_KeyCodePickerNoMouse;\n        bool m_KeyCodePickerHotkeyMode;\n        std::string m_IconPickerFile;\n        HWND m_WindowPickerHWND;\n\n        std::vector< std::pair<std::string, std::string> > m_AppList;   //app key, app name\n\n        //Struct of cached sizes which may change at any time on translation or DPI switching (only the ones that aren't updated unconditionally)\n        struct\n        {\n            ImVec2 Profiles_ButtonDeleteSize;\n            ImVec2 Actions_ButtonDeleteSize;\n            ImVec2 ActionEdit_ButtonDeleteSize;\n        } \n        m_CachedSizes;\n\n        virtual void WindowUpdate();\n\n        void UpdateWarnings();\n\n        void UpdatePageMain();\n        void UpdatePageMainCatInterface();\n        void UpdatePageMainCatProfiles();\n        void UpdatePageMainCatActions();\n        void UpdatePageMainCatInput();\n        void UpdatePageMainCatWindows();\n        void UpdatePageMainCatBrowser();\n        void UpdatePageMainCatPerformance();\n        void UpdatePageMainCatMisc();\n        void UpdatePagePersistentUI();\n        void UpdatePageKeyboardLayout(bool only_restore_settings = false);\n        void UpdatePageProfiles();\n        void UpdatePageProfilesOverlaySelect();\n        void UpdatePageAppProfiles();\n        void UpdatePageActions();\n        void UpdatePageActionsEdit(bool only_restore_settings = false);\n        void UpdatePageActionsOrder(bool only_restore_settings = false);\n        void UpdatePageActionsOrderAdd();\n        void UpdatePageColorPicker(bool only_restore_settings = false);\n        void UpdatePageProfilePicker();\n        void UpdatePageActionPicker();\n        void UpdatePageKeyCodePicker(bool only_restore_settings = false);\n        void UpdatePageIconPicker();\n        void UpdatePageWindowPicker();\n        void UpdatePageResetConfirm();\n\n        void PageGoForward(WindowSettingsPage new_page);\n        void PageGoBack();\n        void PageGoBackInstantly();\n        void PageGoHome();\n\n        void OnPageLeaving(WindowSettingsPage previous_page); //Called from PageGoBack() and PageGoHome() to allow for page-specific cleanup if necessary\n\n        void SelectableWarning(const char* selectable_id, const char* popup_id, const char* text, bool show_warning_prefix = true, const ImVec4* text_color = nullptr);\n        void SelectableHotkey(ConfigHotkey& hotkey, int id);\n\n        void RefreshAppList();\n\n    public:\n        WindowSettings();\n        virtual void Hide(bool skip_fade = false);\n        virtual void ResetTransform(FloatingWindowOverlayStateID state_id);\n        virtual vr::VROverlayHandle_t GetOverlayHandle() const;\n\n        virtual void ApplyUIScale();\n\n        void UpdateDesktopMode();\n        void UpdateDesktopModeWarnings();\n        void DesktopModeSetRootPage(WindowSettingsPage root_page);\n        virtual const char* DesktopModeGetTitle() const;\n        virtual bool DesktopModeGetIconTextureInfo(ImVec2& size, ImVec2& uv_min, ImVec2& uv_max) const;\n        virtual bool DesktopModeGoBack();\n        float DesktopModeGetWarningHeight() const;\n\n        void QuickStartGuideGoToPage(WindowSettingsPage new_page);\n\n        void ClearCachedTranslationStrings();\n};"
  },
  {
    "path": "src/DesktopPlusUI/imgui/imconfig.h",
    "content": "//-----------------------------------------------------------------------------\n// DEAR IMGUI COMPILE-TIME OPTIONS\n// Runtime options (clipboard callbacks, enabling various features, etc.) can generally be set via the ImGuiIO structure.\n// You can use ImGui::SetAllocatorFunctions() before calling ImGui::CreateContext() to rewire memory allocation functions.\n//-----------------------------------------------------------------------------\n// A) You may edit imconfig.h (and not overwrite it when updating Dear ImGui, or maintain a patch/rebased branch with your modifications to it)\n// B) or '#define IMGUI_USER_CONFIG \"my_imgui_config.h\"' in your project and then add directives in your own file without touching this template.\n//-----------------------------------------------------------------------------\n// You need to make sure that configuration settings are defined consistently _everywhere_ Dear ImGui is used, which include the imgui*.cpp\n// files but also _any_ of your code that uses Dear ImGui. This is because some compile-time options have an affect on data structures.\n// Defining those options in imconfig.h will ensure every compilation unit gets to see the same data structure layouts.\n// Call IMGUI_CHECKVERSION() from your .cpp file to verify that the data structures your files are using are matching the ones imgui.cpp is using.\n//-----------------------------------------------------------------------------\n\n#pragma once\n\n//---- Define assertion handler. Defaults to calling assert().\n// If your macro uses multiple statements, make sure is enclosed in a 'do { .. } while (0)' block so it can be used as a single statement.\n//#define IM_ASSERT(_EXPR)  MyAssert(_EXPR)\n//#define IM_ASSERT(_EXPR)  ((void)(_EXPR))     // Disable asserts\n\n//---- Define attributes of all API symbols declarations, e.g. for DLL under Windows\n// Using Dear ImGui via a shared library is not recommended, because of function call overhead and because we don't guarantee backward nor forward ABI compatibility.\n// - Windows DLL users: heaps and globals are not shared across DLL boundaries! You will need to call SetCurrentContext() + SetAllocatorFunctions()\n//   for each static/DLL boundary you are calling from. Read \"Context and Memory Allocators\" section of imgui.cpp for more details.\n//#define IMGUI_API __declspec(dllexport)                   // MSVC Windows: DLL export\n//#define IMGUI_API __declspec(dllimport)                   // MSVC Windows: DLL import\n//#define IMGUI_API __attribute__((visibility(\"default\")))  // GCC/Clang: override visibility when set is hidden\n\n//---- Don't define obsolete functions/enums/behaviors. Consider enabling from time to time after updating to clean your code of obsolete function/names.\n#define IMGUI_DISABLE_OBSOLETE_FUNCTIONS\n\n//---- Disable all of Dear ImGui or don't implement standard windows/tools.\n// It is very strongly recommended to NOT disable the demo windows and debug tool during development. They are extremely useful in day to day work. Please read comments in imgui_demo.cpp.\n//#define IMGUI_DISABLE                                     // Disable everything: all headers and source files will be empty.\n#ifndef _DEBUG\n    #define IMGUI_DISABLE_DEMO_WINDOWS                        // Disable demo windows: ShowDemoWindow()/ShowStyleEditor() will be empty.\n    #define IMGUI_DISABLE_DEBUG_TOOLS                         // Disable metrics/debugger and other debug tools: ShowMetricsWindow(), ShowDebugLogWindow() and ShowIDStackToolWindow() will be empty.\n#endif\n\n//---- Don't implement some functions to reduce linkage requirements.\n//#define IMGUI_DISABLE_WIN32_DEFAULT_CLIPBOARD_FUNCTIONS   // [Win32] Don't implement default clipboard handler. Won't use and link with OpenClipboard/GetClipboardData/CloseClipboard etc. (user32.lib/.a, kernel32.lib/.a)\n//#define IMGUI_ENABLE_WIN32_DEFAULT_IME_FUNCTIONS          // [Win32] [Default with Visual Studio] Implement default IME handler (require imm32.lib/.a, auto-link for Visual Studio, -limm32 on command-line for MinGW)\n//#define IMGUI_DISABLE_WIN32_DEFAULT_IME_FUNCTIONS         // [Win32] [Default with non-Visual Studio compilers] Don't implement default IME handler (won't require imm32.lib/.a)\n//#define IMGUI_DISABLE_WIN32_FUNCTIONS                     // [Win32] Won't use and link with any Win32 function (clipboard, IME).\n//#define IMGUI_ENABLE_OSX_DEFAULT_CLIPBOARD_FUNCTIONS      // [OSX] Implement default OSX clipboard handler (need to link with '-framework ApplicationServices', this is why this is not the default).\n//#define IMGUI_DISABLE_DEFAULT_SHELL_FUNCTIONS             // Don't implement default platform_io.Platform_OpenInShellFn() handler (Win32: ShellExecute(), require shell32.lib/.a, Mac/Linux: use system(\"\")).\n//#define IMGUI_DISABLE_DEFAULT_FORMAT_FUNCTIONS            // Don't implement ImFormatString/ImFormatStringV so you can implement them yourself (e.g. if you don't want to link with vsnprintf)\n//#define IMGUI_DISABLE_DEFAULT_MATH_FUNCTIONS              // Don't implement ImFabs/ImSqrt/ImPow/ImFmod/ImCos/ImSin/ImAcos/ImAtan2 so you can implement them yourself.\n//#define IMGUI_DISABLE_FILE_FUNCTIONS                      // Don't implement ImFileOpen/ImFileClose/ImFileRead/ImFileWrite and ImFileHandle at all (replace them with dummies)\n//#define IMGUI_DISABLE_DEFAULT_FILE_FUNCTIONS              // Don't implement ImFileOpen/ImFileClose/ImFileRead/ImFileWrite and ImFileHandle so you can implement them yourself if you don't want to link with fopen/fclose/fread/fwrite. This will also disable the LogToTTY() function.\n//#define IMGUI_DISABLE_DEFAULT_ALLOCATORS                  // Don't implement default allocators calling malloc()/free() to avoid linking with them. You will need to call ImGui::SetAllocatorFunctions().\n//#define IMGUI_DISABLE_DEFAULT_FONT                        // Disable default embedded font (ProggyClean.ttf), remove ~9.5 KB from output binary. AddFontDefault() will assert.\n//#define IMGUI_DISABLE_SSE                                 // Disable use of SSE intrinsics even if available\n\n//---- Enable Test Engine / Automation features.\n//#define IMGUI_ENABLE_TEST_ENGINE                          // Enable imgui_test_engine hooks. Generally set automatically by include \"imgui_te_config.h\", see Test Engine for details.\n\n//---- Include imgui_user.h at the end of imgui.h as a convenience\n// May be convenient for some users to only explicitly include vanilla imgui.h and have extra stuff included.\n//#define IMGUI_INCLUDE_IMGUI_USER_H\n//#define IMGUI_USER_H_FILENAME         \"my_folder/my_imgui_user.h\"\n\n//---- Pack vertex colors as BGRA8 instead of RGBA8 (to avoid converting from one to another). Need dedicated backend support.\n//#define IMGUI_USE_BGRA_PACKED_COLOR\n\n//---- Use legacy CRC32-adler tables (used before 1.91.6), in order to preserve old .ini data that you cannot afford to invalidate.\n//#define IMGUI_USE_LEGACY_CRC32_ADLER\n\n//---- Use 32-bit for ImWchar (default is 16-bit) to support Unicode planes 1-16. (e.g. point beyond 0xFFFF like emoticons, dingbats, symbols, shapes, ancient languages, etc...)\n#define IMGUI_USE_WCHAR32\n\n//---- Avoid multiple STB libraries implementations, or redefine path/filenames to prioritize another version\n// By default the embedded implementations are declared static and not available outside of Dear ImGui sources files.\n//#define IMGUI_STB_TRUETYPE_FILENAME   \"my_folder/stb_truetype.h\"\n//#define IMGUI_STB_RECT_PACK_FILENAME  \"my_folder/stb_rect_pack.h\"\n//#define IMGUI_STB_SPRINTF_FILENAME    \"my_folder/stb_sprintf.h\"    // only used if IMGUI_USE_STB_SPRINTF is defined.\n//#define IMGUI_DISABLE_STB_TRUETYPE_IMPLEMENTATION\n//#define IMGUI_DISABLE_STB_RECT_PACK_IMPLEMENTATION\n//#define IMGUI_DISABLE_STB_SPRINTF_IMPLEMENTATION                   // only disabled if IMGUI_USE_STB_SPRINTF is defined.\n\n//---- Use stb_sprintf.h for a faster implementation of vsnprintf instead of the one from libc (unless IMGUI_DISABLE_DEFAULT_FORMAT_FUNCTIONS is defined)\n// Compatibility checks of arguments and formats done by clang and GCC will be disabled in order to support the extra formats provided by stb_sprintf.h.\n//#define IMGUI_USE_STB_SPRINTF\n\n//---- Use FreeType to build and rasterize the font atlas (instead of stb_truetype which is embedded by default in Dear ImGui)\n// Requires FreeType headers to be available in the include path. Requires program to be compiled with 'misc/freetype/imgui_freetype.cpp' (in this repository) + the FreeType library (not provided).\n// On Windows you may use vcpkg with 'vcpkg install freetype --triplet=x64-windows' + 'vcpkg integrate install'.\n//#define IMGUI_ENABLE_FREETYPE\n\n//---- Use FreeType + plutosvg or lunasvg to render OpenType SVG fonts (SVGinOT)\n// Only works in combination with IMGUI_ENABLE_FREETYPE.\n// - plutosvg is currently easier to install, as e.g. it is part of vcpkg. It will support more fonts and may load them faster. See misc/freetype/README for instructions.\n// - Both require headers to be available in the include path + program to be linked with the library code (not provided).\n// - (note: lunasvg implementation is based on Freetype's rsvg-port.c which is licensed under CeCILL-C Free Software License Agreement)\n//#define IMGUI_ENABLE_FREETYPE_PLUTOSVG\n//#define IMGUI_ENABLE_FREETYPE_LUNASVG\n\n//---- Use stb_truetype to build and rasterize the font atlas (default)\n// The only purpose of this define is if you want force compilation of the stb_truetype backend ALONG with the FreeType backend.\n//#define IMGUI_ENABLE_STB_TRUETYPE\n\n//---- Define constructor and implicit cast operators to convert back<>forth between your math types and ImVec2/ImVec4.\n// This will be inlined as part of ImVec2 and ImVec4 class declarations.\n/*\n#define IM_VEC2_CLASS_EXTRA                                                     \\\n        constexpr ImVec2(const MyVec2& f) : x(f.x), y(f.y) {}                   \\\n        operator MyVec2() const { return MyVec2(x,y); }\n\n#define IM_VEC4_CLASS_EXTRA                                                     \\\n        constexpr ImVec4(const MyVec4& f) : x(f.x), y(f.y), z(f.z), w(f.w) {}   \\\n        operator MyVec4() const { return MyVec4(x,y,z,w); }\n*/\n//---- ...Or use Dear ImGui's own very basic math operators.\n#define IMGUI_DEFINE_MATH_OPERATORS\n\n//---- Use 32-bit vertex indices (default is 16-bit) is one way to allow large meshes with more than 64K vertices.\n// Your renderer backend will need to support it (most example renderer backends support both 16/32-bit indices).\n// Another way to allow large meshes while keeping 16-bit indices is to handle ImDrawCmd::VtxOffset in your renderer.\n// Read about ImGuiBackendFlags_RendererHasVtxOffset for details.\n//#define ImDrawIdx unsigned int\n\n//---- Override ImDrawCallback signature (will need to modify renderer backends accordingly)\n//struct ImDrawList;\n//struct ImDrawCmd;\n//typedef void (*MyImDrawCallback)(const ImDrawList* draw_list, const ImDrawCmd* cmd, void* my_renderer_user_data);\n//#define ImDrawCallback MyImDrawCallback\n\n//---- Debug Tools: Macro to break in Debugger (we provide a default implementation of this in the codebase)\n// (use 'Metrics->Tools->Item Picker' to pick widgets with the mouse and break into them for easy debugging.)\n//#define IM_DEBUG_BREAK  IM_ASSERT(0)\n//#define IM_DEBUG_BREAK  __debugbreak()\n\n//---- Debug Tools: Enable slower asserts\n//#define IMGUI_DEBUG_PARANOID\n\n//---- Disable gamepad support. This was meaningful before <1.81 but we now load XInput dynamically so the option is now less relevant.\n#define IMGUI_IMPL_WIN32_DISABLE_GAMEPAD\n\n//---- Tip: You can add extra functions within the ImGui:: namespace from anywhere (e.g. your own sources/header files)\n/*\nnamespace ImGui\n{\n    void MyFunction(const char* name, MyMatrix44* mtx);\n}\n*/\n"
  },
  {
    "path": "src/DesktopPlusUI/imgui/imgui.cpp",
    "content": "// dear imgui, v1.91.9b\n// (main code and documentation)\n\n// Help:\n// - See links below.\n// - Call and read ImGui::ShowDemoWindow() in imgui_demo.cpp. All applications in examples/ are doing that.\n// - Read top of imgui.cpp for more details, links and comments.\n\n// Resources:\n// - FAQ ........................ https://dearimgui.com/faq (in repository as docs/FAQ.md)\n// - Homepage ................... https://github.com/ocornut/imgui\n// - Releases & changelog ....... https://github.com/ocornut/imgui/releases\n// - Gallery .................... https://github.com/ocornut/imgui/issues?q=label%3Agallery (please post your screenshots/video there!)\n// - Wiki ....................... https://github.com/ocornut/imgui/wiki (lots of good stuff there)\n//   - Getting Started            https://github.com/ocornut/imgui/wiki/Getting-Started (how to integrate in an existing app by adding ~25 lines of code)\n//   - Third-party Extensions     https://github.com/ocornut/imgui/wiki/Useful-Extensions (ImPlot & many more)\n//   - Bindings/Backends          https://github.com/ocornut/imgui/wiki/Bindings (language bindings, backends for various tech/engines)\n//   - Glossary                   https://github.com/ocornut/imgui/wiki/Glossary\n//   - Debug Tools                https://github.com/ocornut/imgui/wiki/Debug-Tools\n//   - Software using Dear ImGui  https://github.com/ocornut/imgui/wiki/Software-using-dear-imgui\n// - Issues & support ........... https://github.com/ocornut/imgui/issues\n// - Test Engine & Automation ... https://github.com/ocornut/imgui_test_engine (test suite, test engine to automate your apps)\n\n// For first-time users having issues compiling/linking/running/loading fonts:\n// please post in https://github.com/ocornut/imgui/discussions if you cannot find a solution in resources above.\n// Everything else should be asked in 'Issues'! We are building a database of cross-linked knowledge there.\n\n// Copyright (c) 2014-2025 Omar Cornut\n// Developed by Omar Cornut and every direct or indirect contributors to the GitHub.\n// See LICENSE.txt for copyright and licensing details (standard MIT License).\n// This library is free but needs your support to sustain development and maintenance.\n// Businesses: you can support continued development via B2B invoiced technical support, maintenance and sponsoring contracts.\n// PLEASE reach out at omar AT dearimgui DOT com. See https://github.com/ocornut/imgui/wiki/Funding\n// Businesses: you can also purchase licenses for the Dear ImGui Automation/Test Engine.\n\n// It is recommended that you don't modify imgui.cpp! It will become difficult for you to update the library.\n// Note that 'ImGui::' being a namespace, you can add functions into the namespace from your own source files, without\n// modifying imgui.h or imgui.cpp. You may include imgui_internal.h to access internal data structures, but it doesn't\n// come with any guarantee of forward compatibility. Discussing your changes on the GitHub Issue Tracker may lead you\n// to a better solution or official support for them.\n\n/*\n\nIndex of this file:\n\nDOCUMENTATION\n\n- MISSION STATEMENT\n- CONTROLS GUIDE\n- PROGRAMMER GUIDE\n  - READ FIRST\n  - HOW TO UPDATE TO A NEWER VERSION OF DEAR IMGUI\n  - GETTING STARTED WITH INTEGRATING DEAR IMGUI IN YOUR CODE/ENGINE\n  - HOW A SIMPLE APPLICATION MAY LOOK LIKE\n  - HOW A SIMPLE RENDERING FUNCTION MAY LOOK LIKE\n- API BREAKING CHANGES (read me when you update!)\n- FREQUENTLY ASKED QUESTIONS (FAQ)\n  - Read all answers online: https://www.dearimgui.com/faq, or in docs/FAQ.md (with a Markdown viewer)\n\nCODE\n(search for \"[SECTION]\" in the code to find them)\n\n// [SECTION] INCLUDES\n// [SECTION] FORWARD DECLARATIONS\n// [SECTION] CONTEXT AND MEMORY ALLOCATORS\n// [SECTION] USER FACING STRUCTURES (ImGuiStyle, ImGuiIO, ImGuiPlatformIO)\n// [SECTION] MISC HELPERS/UTILITIES (Geometry functions)\n// [SECTION] MISC HELPERS/UTILITIES (String, Format, Hash functions)\n// [SECTION] MISC HELPERS/UTILITIES (File functions)\n// [SECTION] MISC HELPERS/UTILITIES (ImText* functions)\n// [SECTION] MISC HELPERS/UTILITIES (Color functions)\n// [SECTION] ImGuiStorage\n// [SECTION] ImGuiTextFilter\n// [SECTION] ImGuiTextBuffer, ImGuiTextIndex\n// [SECTION] ImGuiListClipper\n// [SECTION] STYLING\n// [SECTION] RENDER HELPERS\n// [SECTION] INITIALIZATION, SHUTDOWN\n// [SECTION] MAIN CODE (most of the code! lots of stuff, needs tidying up!)\n// [SECTION] ID STACK\n// [SECTION] INPUTS\n// [SECTION] ERROR CHECKING, STATE RECOVERY\n// [SECTION] ITEM SUBMISSION\n// [SECTION] LAYOUT\n// [SECTION] SCROLLING\n// [SECTION] TOOLTIPS\n// [SECTION] POPUPS\n// [SECTION] WINDOW FOCUS\n// [SECTION] KEYBOARD/GAMEPAD NAVIGATION\n// [SECTION] DRAG AND DROP\n// [SECTION] LOGGING/CAPTURING\n// [SECTION] SETTINGS\n// [SECTION] LOCALIZATION\n// [SECTION] VIEWPORTS, PLATFORM WINDOWS\n// [SECTION] PLATFORM DEPENDENT HELPERS\n// [SECTION] METRICS/DEBUGGER WINDOW\n// [SECTION] DEBUG LOG WINDOW\n// [SECTION] OTHER DEBUG TOOLS (ITEM PICKER, ID STACK TOOL)\n\n*/\n\n//-----------------------------------------------------------------------------\n// DOCUMENTATION\n//-----------------------------------------------------------------------------\n\n/*\n\n MISSION STATEMENT\n =================\n\n - Easy to use to create code-driven and data-driven tools.\n - Easy to use to create ad hoc short-lived tools and long-lived, more elaborate tools.\n - Easy to hack and improve.\n - Minimize setup and maintenance.\n - Minimize state storage on user side.\n - Minimize state synchronization.\n - Portable, minimize dependencies, run on target (consoles, phones, etc.).\n - Efficient runtime and memory consumption.\n\n Designed primarily for developers and content-creators, not the typical end-user!\n Some of the current weaknesses (which we aim to address in the future) includes:\n\n - Doesn't look fancy.\n - Limited layout features, intricate layouts are typically crafted in code.\n\n\n CONTROLS GUIDE\n ==============\n\n - MOUSE CONTROLS\n   - Mouse wheel:                   Scroll vertically.\n   - SHIFT+Mouse wheel:             Scroll horizontally.\n   - Click [X]:                     Close a window, available when 'bool* p_open' is passed to ImGui::Begin().\n   - Click ^, Double-Click title:   Collapse window.\n   - Drag on corner/border:         Resize window (double-click to auto fit window to its contents).\n   - Drag on any empty space:       Move window (unless io.ConfigWindowsMoveFromTitleBarOnly = true).\n   - Left-click outside popup:      Close popup stack (right-click over underlying popup: Partially close popup stack).\n\n - TEXT EDITOR\n   - Hold SHIFT or Drag Mouse:      Select text.\n   - CTRL+Left/Right:               Word jump.\n   - CTRL+Shift+Left/Right:         Select words.\n   - CTRL+A or Double-Click:        Select All.\n   - CTRL+X, CTRL+C, CTRL+V:        Use OS clipboard.\n   - CTRL+Z                         Undo.\n   - CTRL+Y or CTRL+Shift+Z:        Redo.\n   - ESCAPE:                        Revert text to its original value.\n   - On OSX, controls are automatically adjusted to match standard OSX text editing 2ts and behaviors.\n\n - KEYBOARD CONTROLS\n   - Basic:\n     - Tab, SHIFT+Tab               Cycle through text editable fields.\n     - CTRL+Tab, CTRL+Shift+Tab     Cycle through windows.\n     - CTRL+Click                   Input text into a Slider or Drag widget.\n   - Extended features with `io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard`:\n     - Tab, SHIFT+Tab:              Cycle through every items.\n     - Arrow keys                   Move through items using directional navigation. Tweak value.\n     - Arrow keys + Alt, Shift      Tweak slower, tweak faster (when using arrow keys).\n     - Enter                        Activate item (prefer text input when possible).\n     - Space                        Activate item (prefer tweaking with arrows when possible).\n     - Escape                       Deactivate item, leave child window, close popup.\n     - Page Up, Page Down           Previous page, next page.\n     - Home, End                    Scroll to top, scroll to bottom.\n     - Alt                          Toggle between scrolling layer and menu layer.\n     - CTRL+Tab then Ctrl+Arrows    Move window. Hold SHIFT to resize instead of moving.\n   - Output when ImGuiConfigFlags_NavEnableKeyboard set,\n     - io.WantCaptureKeyboard flag is set when keyboard is claimed.\n     - io.NavActive: true when a window is focused and it doesn't have the ImGuiWindowFlags_NoNavInputs flag set.\n     - io.NavVisible: true when the navigation cursor is visible (usually goes to back false when mouse is used).\n\n - GAMEPAD CONTROLS\n   - Enable with 'io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad'.\n   - Particularly useful to use Dear ImGui on a console system (e.g. PlayStation, Switch, Xbox) without a mouse!\n   - Download controller mapping PNG/PSD at http://dearimgui.com/controls_sheets\n   - Backend support: backend needs to:\n      - Set 'io.BackendFlags |= ImGuiBackendFlags_HasGamepad' + call io.AddKeyEvent/AddKeyAnalogEvent() with ImGuiKey_Gamepad_XXX keys.\n      - For analog values (0.0f to 1.0f), backend is responsible to handling a dead-zone and rescaling inputs accordingly.\n        Backend code will probably need to transform your raw inputs (such as e.g. remapping your 0.2..0.9 raw input range to 0.0..1.0 imgui range, etc.).\n   - If you need to share inputs between your game and the Dear ImGui interface, the easiest approach is to go all-or-nothing,\n     with a buttons combo to toggle the target. Please reach out if you think the game vs navigation input sharing could be improved.\n\n - REMOTE INPUTS SHARING & MOUSE EMULATION\n   - PS4/PS5 users: Consider emulating a mouse cursor with DualShock touch pad or a spare analog stick as a mouse-emulation fallback.\n   - Consoles/Tablet/Phone users: Consider using a Synergy 1.x server (on your PC) + run examples/libs/synergy/uSynergy.c (on your console/tablet/phone app)\n     in order to share your PC mouse/keyboard.\n   - See https://github.com/ocornut/imgui/wiki/Useful-Extensions#remoting for other remoting solutions.\n   - On a TV/console system where readability may be lower or mouse inputs may be awkward, you may want to set the io.ConfigNavMoveSetMousePos flag.\n     Enabling io.ConfigNavMoveSetMousePos + ImGuiBackendFlags_HasSetMousePos instructs Dear ImGui to move your mouse cursor along with navigation movements.\n     When enabled, the NewFrame() function may alter 'io.MousePos' and set 'io.WantSetMousePos' to notify you that it wants the mouse cursor to be moved.\n     When that happens your backend NEEDS to move the OS or underlying mouse cursor on the next frame. Some of the backends in examples/ do that.\n     (If you set the NavEnableSetMousePos flag but don't honor 'io.WantSetMousePos' properly, Dear ImGui will misbehave as it will see your mouse moving back & forth!)\n     (In a setup when you may not have easy control over the mouse cursor, e.g. uSynergy.c doesn't expose moving remote mouse cursor, you may want\n     to set a boolean to ignore your other external mouse positions until the external source is moved again.)\n\n\n PROGRAMMER GUIDE\n ================\n\n READ FIRST\n ----------\n - Remember to check the wonderful Wiki (https://github.com/ocornut/imgui/wiki)\n - Your code creates the UI every frame of your application loop, if your code doesn't run the UI is gone!\n   The UI can be highly dynamic, there are no construction or destruction steps, less superfluous\n   data retention on your side, less state duplication, less state synchronization, fewer bugs.\n - Call and read ImGui::ShowDemoWindow() for demo code demonstrating most features.\n   Or browse https://pthom.github.io/imgui_manual_online/manual/imgui_manual.html for interactive web version.\n - The library is designed to be built from sources. Avoid pre-compiled binaries and packaged versions. See imconfig.h to configure your build.\n - Dear ImGui is an implementation of the IMGUI paradigm (immediate-mode graphical user interface, a term coined by Casey Muratori).\n   You can learn about IMGUI principles at http://www.johno.se/book/imgui.html, http://mollyrocket.com/861 & more links in Wiki.\n - Dear ImGui is a \"single pass\" rasterizing implementation of the IMGUI paradigm, aimed at ease of use and high-performances.\n   For every application frame, your UI code will be called only once. This is in contrast to e.g. Unity's implementation of an IMGUI,\n   where the UI code is called multiple times (\"multiple passes\") from a single entry point. There are pros and cons to both approaches.\n - Our origin is on the top-left. In axis aligned bounding boxes, Min = top-left, Max = bottom-right.\n - Please make sure you have asserts enabled (IM_ASSERT redirects to assert() by default, but can be redirected).\n   If you get an assert, read the messages and comments around the assert.\n - This codebase aims to be highly optimized:\n   - A typical idle frame should never call malloc/free.\n   - We rely on a maximum of constant-time or O(N) algorithms. Limiting searches/scans as much as possible.\n   - We put particular energy in making sure performances are decent with typical \"Debug\" build settings as well.\n     Which mean we tend to avoid over-relying on \"zero-cost abstraction\" as they aren't zero-cost at all.\n - This codebase aims to be both highly opinionated and highly flexible:\n   - This code works because of the things it choose to solve or not solve.\n   - C++: this is a pragmatic C-ish codebase: we don't use fancy C++ features, we don't include C++ headers,\n     and ImGui:: is a namespace. We rarely use member functions (and when we did, I am mostly regretting it now).\n     This is to increase compatibility, increase maintainability and facilitate use from other languages.\n   - C++: ImVec2/ImVec4 do not expose math operators by default, because it is expected that you use your own math types.\n     See FAQ \"How can I use my own math types instead of ImVec2/ImVec4?\" for details about setting up imconfig.h for that.\n     We can can optionally export math operators for ImVec2/ImVec4 using IMGUI_DEFINE_MATH_OPERATORS, which we use internally.\n   - C++: pay attention that ImVector<> manipulates plain-old-data and does not honor construction/destruction\n     (so don't use ImVector in your code or at our own risk!).\n   - Building: We don't use nor mandate a build system for the main library.\n     This is in an effort to ensure that it works in the real world aka with any esoteric build setup.\n     This is also because providing a build system for the main library would be of little-value.\n     The build problems are almost never coming from the main library but from specific backends.\n\n\n HOW TO UPDATE TO A NEWER VERSION OF DEAR IMGUI\n ----------------------------------------------\n - Update submodule or copy/overwrite every file.\n - About imconfig.h:\n   - You may modify your copy of imconfig.h, in this case don't overwrite it.\n   - or you may locally branch to modify imconfig.h and merge/rebase latest.\n   - or you may '#define IMGUI_USER_CONFIG \"my_config_file.h\"' globally from your build system to\n     specify a custom path for your imconfig.h file and instead not have to modify the default one.\n\n - Overwrite all the sources files except for imconfig.h (if you have modified your copy of imconfig.h)\n - Or maintain your own branch where you have imconfig.h modified as a top-most commit which you can regularly rebase over \"master\".\n - You can also use '#define IMGUI_USER_CONFIG \"my_config_file.h\" to redirect configuration to your own file.\n - Read the \"API BREAKING CHANGES\" section (below). This is where we list occasional API breaking changes.\n   If a function/type has been renamed / or marked obsolete, try to fix the name in your code before it is permanently removed\n   from the public API. If you have a problem with a missing function/symbols, search for its name in the code, there will\n   likely be a comment about it. Please report any issue to the GitHub page!\n - To find out usage of old API, you can add '#define IMGUI_DISABLE_OBSOLETE_FUNCTIONS' in your configuration file.\n - Try to keep your copy of Dear ImGui reasonably up to date!\n\n\n GETTING STARTED WITH INTEGRATING DEAR IMGUI IN YOUR CODE/ENGINE\n ---------------------------------------------------------------\n - See https://github.com/ocornut/imgui/wiki/Getting-Started.\n - Run and study the examples and demo in imgui_demo.cpp to get acquainted with the library.\n - In the majority of cases you should be able to use unmodified backends files available in the backends/ folder.\n - Add the Dear ImGui source files + selected backend source files to your projects or using your preferred build system.\n   It is recommended you build and statically link the .cpp files as part of your project and NOT as a shared library (DLL).\n - You can later customize the imconfig.h file to tweak some compile-time behavior, such as integrating Dear ImGui types with your own maths types.\n - When using Dear ImGui, your programming IDE is your friend: follow the declaration of variables, functions and types to find comments about them.\n - Dear ImGui never touches or knows about your GPU state. The only function that knows about GPU is the draw function that you provide.\n   Effectively it means you can create widgets at any time in your code, regardless of considerations of being in \"update\" vs \"render\"\n   phases of your own application. All rendering information is stored into command-lists that you will retrieve after calling ImGui::Render().\n - Refer to the backends and demo applications in the examples/ folder for instruction on how to setup your code.\n - If you are running over a standard OS with a common graphics API, you should be able to use unmodified imgui_impl_*** files from the examples/ folder.\n\n\n HOW A SIMPLE APPLICATION MAY LOOK LIKE\n --------------------------------------\n EXHIBIT 1: USING THE EXAMPLE BACKENDS (= imgui_impl_XXX.cpp files from the backends/ folder).\n The sub-folders in examples/ contain examples applications following this structure.\n\n     // Application init: create a dear imgui context, setup some options, load fonts\n     ImGui::CreateContext();\n     ImGuiIO& io = ImGui::GetIO();\n     // TODO: Set optional io.ConfigFlags values, e.g. 'io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard' to enable keyboard controls.\n     // TODO: Fill optional fields of the io structure later.\n     // TODO: Load TTF/OTF fonts if you don't want to use the default font.\n\n     // Initialize helper Platform and Renderer backends (here we are using imgui_impl_win32.cpp and imgui_impl_dx11.cpp)\n     ImGui_ImplWin32_Init(hwnd);\n     ImGui_ImplDX11_Init(g_pd3dDevice, g_pd3dDeviceContext);\n\n     // Application main loop\n     while (true)\n     {\n         // Feed inputs to dear imgui, start new frame\n         ImGui_ImplDX11_NewFrame();\n         ImGui_ImplWin32_NewFrame();\n         ImGui::NewFrame();\n\n         // Any application code here\n         ImGui::Text(\"Hello, world!\");\n\n         // Render dear imgui into screen\n         ImGui::Render();\n         ImGui_ImplDX11_RenderDrawData(ImGui::GetDrawData());\n         g_pSwapChain->Present(1, 0);\n     }\n\n     // Shutdown\n     ImGui_ImplDX11_Shutdown();\n     ImGui_ImplWin32_Shutdown();\n     ImGui::DestroyContext();\n\n EXHIBIT 2: IMPLEMENTING CUSTOM BACKEND / CUSTOM ENGINE\n\n     // Application init: create a dear imgui context, setup some options, load fonts\n     ImGui::CreateContext();\n     ImGuiIO& io = ImGui::GetIO();\n     // TODO: Set optional io.ConfigFlags values, e.g. 'io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard' to enable keyboard controls.\n     // TODO: Fill optional fields of the io structure later.\n     // TODO: Load TTF/OTF fonts if you don't want to use the default font.\n\n     // Build and load the texture atlas into a texture\n     // (In the examples/ app this is usually done within the ImGui_ImplXXX_Init() function from one of the demo Renderer)\n     int width, height;\n     unsigned char* pixels = nullptr;\n     io.Fonts->GetTexDataAsRGBA32(&pixels, &width, &height);\n\n     // At this point you've got the texture data and you need to upload that to your graphic system:\n     // After we have created the texture, store its pointer/identifier (_in whichever format your engine uses_) in 'io.Fonts->TexID'.\n     // This will be passed back to your via the renderer. Basically ImTextureID == void*. Read FAQ for details about ImTextureID.\n     MyTexture* texture = MyEngine::CreateTextureFromMemoryPixels(pixels, width, height, TEXTURE_TYPE_RGBA32)\n     io.Fonts->SetTexID((void*)texture);\n\n     // Application main loop\n     while (true)\n     {\n        // Setup low-level inputs, e.g. on Win32: calling GetKeyboardState(), or write to those fields from your Windows message handlers, etc.\n        // (In the examples/ app this is usually done within the ImGui_ImplXXX_NewFrame() function from one of the demo Platform Backends)\n        io.DeltaTime = 1.0f/60.0f;              // set the time elapsed since the previous frame (in seconds)\n        io.DisplaySize.x = 1920.0f;             // set the current display width\n        io.DisplaySize.y = 1280.0f;             // set the current display height here\n        io.AddMousePosEvent(mouse_x, mouse_y);  // update mouse position\n        io.AddMouseButtonEvent(0, mouse_b[0]);  // update mouse button states\n        io.AddMouseButtonEvent(1, mouse_b[1]);  // update mouse button states\n\n        // Call NewFrame(), after this point you can use ImGui::* functions anytime\n        // (So you want to try calling NewFrame() as early as you can in your main loop to be able to use Dear ImGui everywhere)\n        ImGui::NewFrame();\n\n        // Most of your application code here\n        ImGui::Text(\"Hello, world!\");\n        MyGameUpdate(); // may use any Dear ImGui functions, e.g. ImGui::Begin(\"My window\"); ImGui::Text(\"Hello, world!\"); ImGui::End();\n        MyGameRender(); // may use any Dear ImGui functions as well!\n\n        // Render dear imgui, swap buffers\n        // (You want to try calling EndFrame/Render as late as you can, to be able to use Dear ImGui in your own game rendering code)\n        ImGui::EndFrame();\n        ImGui::Render();\n        ImDrawData* draw_data = ImGui::GetDrawData();\n        MyImGuiRenderFunction(draw_data);\n        SwapBuffers();\n     }\n\n     // Shutdown\n     ImGui::DestroyContext();\n\n To decide whether to dispatch mouse/keyboard inputs to Dear ImGui to the rest of your application,\n you should read the 'io.WantCaptureMouse', 'io.WantCaptureKeyboard' and 'io.WantTextInput' flags!\n Please read the FAQ entry \"How can I tell whether to dispatch mouse/keyboard to Dear ImGui or my application?\" about this.\n\n\n HOW A SIMPLE RENDERING FUNCTION MAY LOOK LIKE\n ---------------------------------------------\n The backends in impl_impl_XXX.cpp files contain many working implementations of a rendering function.\n\n    void MyImGuiRenderFunction(ImDrawData* draw_data)\n    {\n       // TODO: Setup render state: alpha-blending enabled, no face culling, no depth testing, scissor enabled\n       // TODO: Setup texture sampling state: sample with bilinear filtering (NOT point/nearest filtering). Use 'io.Fonts->Flags |= ImFontAtlasFlags_NoBakedLines;' to allow point/nearest filtering.\n       // TODO: Setup viewport covering draw_data->DisplayPos to draw_data->DisplayPos + draw_data->DisplaySize\n       // TODO: Setup orthographic projection matrix cover draw_data->DisplayPos to draw_data->DisplayPos + draw_data->DisplaySize\n       // TODO: Setup shader: vertex { float2 pos, float2 uv, u32 color }, fragment shader sample color from 1 texture, multiply by vertex color.\n       ImVec2 clip_off = draw_data->DisplayPos;\n       for (int n = 0; n < draw_data->CmdListsCount; n++)\n       {\n          const ImDrawList* cmd_list = draw_data->CmdLists[n];\n          const ImDrawVert* vtx_buffer = cmd_list->VtxBuffer.Data;  // vertex buffer generated by Dear ImGui\n          const ImDrawIdx* idx_buffer = cmd_list->IdxBuffer.Data;   // index buffer generated by Dear ImGui\n          for (int cmd_i = 0; cmd_i < cmd_list->CmdBuffer.Size; cmd_i++)\n          {\n             const ImDrawCmd* pcmd = &cmd_list->CmdBuffer[cmd_i];\n             if (pcmd->UserCallback)\n             {\n                 pcmd->UserCallback(cmd_list, pcmd);\n             }\n             else\n             {\n                 // Project scissor/clipping rectangles into framebuffer space\n                 ImVec2 clip_min(pcmd->ClipRect.x - clip_off.x, pcmd->ClipRect.y - clip_off.y);\n                 ImVec2 clip_max(pcmd->ClipRect.z - clip_off.x, pcmd->ClipRect.w - clip_off.y);\n                 if (clip_max.x <= clip_min.x || clip_max.y <= clip_min.y)\n                     continue;\n\n                 // We are using scissoring to clip some objects. All low-level graphics API should support it.\n                 // - If your engine doesn't support scissoring yet, you may ignore this at first. You will get some small glitches\n                 //   (some elements visible outside their bounds) but you can fix that once everything else works!\n                 // - Clipping coordinates are provided in imgui coordinates space:\n                 //   - For a given viewport, draw_data->DisplayPos == viewport->Pos and draw_data->DisplaySize == viewport->Size\n                 //   - In a single viewport application, draw_data->DisplayPos == (0,0) and draw_data->DisplaySize == io.DisplaySize, but always use GetMainViewport()->Pos/Size instead of hardcoding those values.\n                 //   - In the interest of supporting multi-viewport applications (see 'docking' branch on github),\n                 //     always subtract draw_data->DisplayPos from clipping bounds to convert them to your viewport space.\n                 // - Note that pcmd->ClipRect contains Min+Max bounds. Some graphics API may use Min+Max, other may use Min+Size (size being Max-Min)\n                 MyEngineSetScissor(clip_min.x, clip_min.y, clip_max.x, clip_max.y);\n\n                 // The texture for the draw call is specified by pcmd->GetTexID().\n                 // The vast majority of draw calls will use the Dear ImGui texture atlas, which value you have set yourself during initialization.\n                 MyEngineBindTexture((MyTexture*)pcmd->GetTexID());\n\n                 // Render 'pcmd->ElemCount/3' indexed triangles.\n                 // By default the indices ImDrawIdx are 16-bit, you can change them to 32-bit in imconfig.h if your engine doesn't support 16-bit indices.\n                 MyEngineDrawIndexedTriangles(pcmd->ElemCount, sizeof(ImDrawIdx) == 2 ? GL_UNSIGNED_SHORT : GL_UNSIGNED_INT, idx_buffer + pcmd->IdxOffset, vtx_buffer, pcmd->VtxOffset);\n             }\n          }\n       }\n    }\n\n\n API BREAKING CHANGES\n ====================\n\n Occasionally introducing changes that are breaking the API. We try to make the breakage minor and easy to fix.\n Below is a change-log of API breaking changes only. If you are using one of the functions listed, expect to have to fix some code.\n When you are not sure about an old symbol or function name, try using the Search/Find function of your IDE to look for comments or references in all imgui files.\n You can read releases logs https://github.com/ocornut/imgui/releases for more details.\n\n - 2025/03/05 (1.91.9) - BeginMenu(): Internals: reworked mangling of menu windows to use \"###Menu_00\" etc. instead of \"##Menu_00\", allowing them to also store the menu name before it. This shouldn't affect code unless directly accessing menu window from their mangled name.\n - 2025/02/27 (1.91.9) - Image(): removed 'tint_col' and 'border_col' parameter from Image() function. Added ImageWithBg() replacement. (#8131, #8238)\n                            - old: void Image      (ImTextureID tex_id, ImVec2 image_size, ImVec2 uv0 = (0,0), ImVec2 uv1 = (1,1), ImVec4 tint_col = (1,1,1,1), ImVec4 border_col = (0,0,0,0));\n                            - new: void Image      (ImTextureID tex_id, ImVec2 image_size, ImVec2 uv0 = (0,0), ImVec2 uv1 = (1,1));\n                            - new: void ImageWithBg(ImTextureID tex_id, ImVec2 image_size, ImVec2 uv0 = (0,0), ImVec2 uv1 = (1,1), ImVec4 bg_col = (0,0,0,0), ImVec4 tint_col = (1,1,1,1));\n                            - TL;DR: 'border_col' had misleading side-effect on layout, 'bg_col' was missing, parameter order couldn't be consistent with ImageButton().\n                            - new behavior always use ImGuiCol_Border color + style.ImageBorderSize / ImGuiStyleVar_ImageBorderSize.\n                            - old behavior altered border size (and therefore layout) based on border color's alpha, which caused variety of problems + old behavior a fixed 1.0f for border size which was not tweakable.\n                            - kept legacy signature (will obsolete), which mimics the old behavior,  but uses Max(1.0f, style.ImageBorderSize) when border_col is specified.\n                            - added ImageWithBg() function which has both 'bg_col' (which was missing) and 'tint_col'. It was impossible to add 'bg_col' to Image() with a parameter order consistent with other functions, so we decided to remove 'tint_col' and introduce ImageWithBg().\n - 2025/02/25 (1.91.9) - internals: fonts: ImFontAtlas::ConfigData[] has been renamed to ImFontAtlas::Sources[]. ImFont::ConfigData[], ConfigDataCount has been renamed to Sources[], SourcesCount.\n - 2025/02/06 (1.91.9) - renamed ImFontConfig::GlyphExtraSpacing.x to ImFontConfig::GlyphExtraAdvanceX.\n - 2025/01/22 (1.91.8) - removed ImGuiColorEditFlags_AlphaPreview (made value 0): it is now the default behavior.\n                         prior to 1.91.8: alpha was made opaque in the preview by default _unless_ using ImGuiColorEditFlags_AlphaPreview. We now display the preview as transparent by default. You can use ImGuiColorEditFlags_AlphaOpaque to use old behavior.\n                         the new flags (ImGuiColorEditFlags_AlphaOpaque, ImGuiColorEditFlags_AlphaNoBg + existing ImGuiColorEditFlags_AlphaPreviewHalf) may be combined better and allow finer controls:\n - 2025/01/14 (1.91.7) - renamed ImGuiTreeNodeFlags_SpanTextWidth to ImGuiTreeNodeFlags_SpanLabelWidth for consistency with other names. Kept redirection enum (will obsolete). (#6937)\n - 2024/11/27 (1.91.6) - changed CRC32 table from CRC32-adler to CRC32c polynomial in order to be compatible with the result of SSE 4.2 instructions.\n                         As a result, old .ini data may be partially lost (docking and tables information particularly).\n                         Because some users have crafted and storing .ini data as a way to workaround limitations of the docking API, we are providing a '#define IMGUI_USE_LEGACY_CRC32_ADLER' compile-time option to keep using old CRC32 tables if you cannot afford invalidating old .ini data.\n - 2024/11/06 (1.91.5) - commented/obsoleted out pre-1.87 IO system (equivalent to using IMGUI_DISABLE_OBSOLETE_KEYIO or IMGUI_DISABLE_OBSOLETE_FUNCTIONS before)\n                            - io.KeyMap[] and io.KeysDown[] are removed (obsoleted February 2022).\n                            - io.NavInputs[] and ImGuiNavInput are removed (obsoleted July 2022).\n                            - pre-1.87 backends are not supported:\n                               - backends need to call io.AddKeyEvent(), io.AddMouseEvent() instead of writing to io.KeysDown[], io.MouseDown[] fields.\n                               - backends need to call io.AddKeyAnalogEvent() for gamepad values instead of writing to io.NavInputs[] fields.\n                            - for more reference:\n                              - read 1.87 and 1.88 part of this section or read Changelog for 1.87 and 1.88.\n                              - read https://github.com/ocornut/imgui/issues/4921\n                            - if you have trouble updating a very old codebase using legacy backend-specific key codes: consider updating to 1.91.4 first, then #define IMGUI_DISABLE_OBSOLETE_KEYIO, then update to latest.\n                       - obsoleted ImGuiKey_COUNT (it is unusually error-prone/misleading since valid keys don't start at 0). probably use ImGuiKey_NamedKey_BEGIN/ImGuiKey_NamedKey_END?\n                       - fonts: removed const qualifiers from most font functions in prevision for upcoming font improvements.\n - 2024/10/18 (1.91.4) - renamed ImGuiCol_NavHighlight to ImGuiCol_NavCursor (for consistency with newly exposed and reworked features). Kept inline redirection enum (will obsolete).\n - 2024/10/14 (1.91.4) - moved ImGuiConfigFlags_NavEnableSetMousePos to standalone io.ConfigNavMoveSetMousePos bool.\n                         moved ImGuiConfigFlags_NavNoCaptureKeyboard to standalone io.ConfigNavCaptureKeyboard bool (note the inverted value!).\n                         kept legacy names (will obsolete) + code that copies settings once the first time. Dynamically changing the old value won't work. Switch to using the new value!\n - 2024/10/10 (1.91.4) - the typedef for ImTextureID now defaults to ImU64 instead of void*. (#1641)\n                         this removes the requirement to redefine it for backends which are e.g. storing descriptor sets or other 64-bits structures when building on 32-bits archs. It therefore simplify various building scripts/helpers.\n                         you may have compile-time issues if you were casting to 'void*' instead of 'ImTextureID' when passing your types to functions taking ImTextureID values, e.g. ImGui::Image().\n                         in doubt it is almost always better to do an intermediate intptr_t cast, since it allows casting any pointer/integer type without warning:\n                            - May warn:    ImGui::Image((void*)MyTextureData, ...);\n                            - May warn:    ImGui::Image((void*)(intptr_t)MyTextureData, ...);\n                            - Won't warn:  ImGui::Image((ImTextureID)(intptr_t)MyTextureData), ...);\n  -                      note that you can always define ImTextureID to be your own high-level structures (with dedicated constructors) if you like.\n - 2024/10/03 (1.91.3) - drags: treat v_min==v_max as a valid clamping range when != 0.0f. Zero is a still special value due to legacy reasons, unless using ImGuiSliderFlags_ClampZeroRange. (#7968, #3361, #76)\n                       - drags: extended behavior of ImGuiSliderFlags_AlwaysClamp to include _ClampZeroRange. It considers v_min==v_max==0.0f as a valid clamping range (aka edits not allowed).\n                         although unlikely, it you wish to only clamp on text input but want v_min==v_max==0.0f to mean unclamped drags, you can use _ClampOnInput instead of _AlwaysClamp. (#7968, #3361, #76)\n - 2024/09/10 (1.91.2) - internals: using multiple overlayed ButtonBehavior() with same ID will now have io.ConfigDebugHighlightIdConflicts=true feature emit a warning. (#8030)\n                         it was one of the rare case where using same ID is legal. workarounds: (1) use single ButtonBehavior() call with multiple _MouseButton flags, or (2) surround the calls with PushItemFlag(ImGuiItemFlags_AllowDuplicateId, true); ... PopItemFlag()\n - 2024/08/23 (1.91.1) - renamed ImGuiChildFlags_Border to ImGuiChildFlags_Borders for consistency. kept inline redirection flag.\n - 2024/08/22 (1.91.1) - moved some functions from ImGuiIO to ImGuiPlatformIO structure:\n                            - io.GetClipboardTextFn         -> platform_io.Platform_GetClipboardTextFn + changed 'void* user_data' to 'ImGuiContext* ctx'. Pull your user data from platform_io.ClipboardUserData.\n                            - io.SetClipboardTextFn         -> platform_io.Platform_SetClipboardTextFn + same as above line.\n                            - io.PlatformOpenInShellFn      -> platform_io.Platform_OpenInShellFn (#7660)\n                            - io.PlatformSetImeDataFn       -> platform_io.Platform_SetImeDataFn\n                            - io.PlatformLocaleDecimalPoint -> platform_io.Platform_LocaleDecimalPoint (#7389, #6719, #2278)\n                            - access those via GetPlatformIO() instead of GetIO().\n                         some were introduced very recently and often automatically setup by core library and backends, so for those we are exceptionally not maintaining a legacy redirection symbol.\n                       - commented the old ImageButton() signature obsoleted in 1.89 (~August 2022). As a reminder:\n                            - old ImageButton() before 1.89 used ImTextureId as item id (created issue with e.g. multiple buttons in same scope, transient texture id values, opaque computation of ID)\n                            - new ImageButton() since 1.89 requires an explicit 'const char* str_id'\n                            - old ImageButton() before 1.89 had frame_padding' override argument.\n                            - new ImageButton() since 1.89 always use style.FramePadding, which you can freely override with PushStyleVar()/PopStyleVar().\n - 2024/07/25 (1.91.0) - obsoleted GetContentRegionMax(), GetWindowContentRegionMin() and GetWindowContentRegionMax(). (see #7838 on GitHub for more info)\n                         you should never need those functions. you can do everything with GetCursorScreenPos() and GetContentRegionAvail() in a more simple way.\n                            - instead of:  GetWindowContentRegionMax().x - GetCursorPos().x\n                            - you can use: GetContentRegionAvail().x\n                            - instead of:  GetWindowContentRegionMax().x + GetWindowPos().x\n                            - you can use: GetCursorScreenPos().x + GetContentRegionAvail().x // when called from left edge of window\n                            - instead of:  GetContentRegionMax()\n                            - you can use: GetContentRegionAvail() + GetCursorScreenPos() - GetWindowPos() // right edge in local coordinates\n                            - instead of:  GetWindowContentRegionMax().x - GetWindowContentRegionMin().x\n                            - you can use: GetContentRegionAvail() // when called from left edge of window\n - 2024/07/15 (1.91.0) - renamed ImGuiSelectableFlags_DontClosePopups to ImGuiSelectableFlags_NoAutoClosePopups. (#1379, #1468, #2200, #4936, #5216, #7302, #7573)\n                         (internals: also renamed ImGuiItemFlags_SelectableDontClosePopup into ImGuiItemFlags_AutoClosePopups with inverted behaviors)\n - 2024/07/15 (1.91.0) - obsoleted PushButtonRepeat()/PopButtonRepeat() in favor of using new PushItemFlag(ImGuiItemFlags_ButtonRepeat, ...)/PopItemFlag().\n - 2024/07/02 (1.91.0) - commented out obsolete ImGuiModFlags (renamed to ImGuiKeyChord in 1.89). (#4921, #456)\n                       - commented out obsolete ImGuiModFlags_XXX values (renamed to ImGuiMod_XXX in 1.89). (#4921, #456)\n                            - ImGuiModFlags_Ctrl -> ImGuiMod_Ctrl, ImGuiModFlags_Shift -> ImGuiMod_Shift etc.\n - 2024/07/02 (1.91.0) - IO, IME: renamed platform IME hook and added explicit context for consistency and future-proofness.\n                            - old: io.SetPlatformImeDataFn(ImGuiViewport* viewport, ImGuiPlatformImeData* data);\n                            - new: io.PlatformSetImeDataFn(ImGuiContext* ctx, ImGuiViewport* viewport, ImGuiPlatformImeData* data);\n - 2024/06/21 (1.90.9) - BeginChild: added ImGuiChildFlags_NavFlattened as a replacement for the window flag ImGuiWindowFlags_NavFlattened: the feature only ever made sense for BeginChild() anyhow.\n                            - old: BeginChild(\"Name\", size, 0, ImGuiWindowFlags_NavFlattened);\n                            - new: BeginChild(\"Name\", size, ImGuiChildFlags_NavFlattened, 0);\n - 2024/06/21 (1.90.9) - io: ClearInputKeys() (first exposed in 1.89.8) doesn't clear mouse data, newly added ClearInputMouse() does.\n - 2024/06/20 (1.90.9) - renamed ImGuiDragDropFlags_SourceAutoExpirePayload to ImGuiDragDropFlags_PayloadAutoExpire.\n - 2024/06/18 (1.90.9) - style: renamed ImGuiCol_TabActive -> ImGuiCol_TabSelected, ImGuiCol_TabUnfocused -> ImGuiCol_TabDimmed, ImGuiCol_TabUnfocusedActive -> ImGuiCol_TabDimmedSelected.\n - 2024/06/10 (1.90.9) - removed old nested structure: renaming ImGuiStorage::ImGuiStoragePair type to ImGuiStoragePair (simpler for many languages).\n - 2024/06/06 (1.90.8) - reordered ImGuiInputTextFlags values. This should not be breaking unless you are using generated headers that have values not matching the main library.\n - 2024/06/06 (1.90.8) - removed 'ImGuiButtonFlags_MouseButtonDefault_ = ImGuiButtonFlags_MouseButtonLeft', was mostly unused and misleading.\n - 2024/05/27 (1.90.7) - commented out obsolete symbols marked obsolete in 1.88 (May 2022):\n                            - old: CaptureKeyboardFromApp(bool)\n                            - new: SetNextFrameWantCaptureKeyboard(bool)\n                            - old: CaptureMouseFromApp(bool)\n                            - new: SetNextFrameWantCaptureMouse(bool)\n - 2024/05/22 (1.90.7) - inputs (internals): renamed ImGuiKeyOwner_None to ImGuiKeyOwner_NoOwner, to make use more explicit and reduce confusion with the default it is a non-zero value and cannot be the default value (never made public, but disclosing as I expect a few users caught on owner-aware inputs).\n                       - inputs (internals): renamed ImGuiInputFlags_RouteGlobalLow -> ImGuiInputFlags_RouteGlobal, ImGuiInputFlags_RouteGlobal -> ImGuiInputFlags_RouteGlobalOverFocused, ImGuiInputFlags_RouteGlobalHigh -> ImGuiInputFlags_RouteGlobalHighest.\n                       - inputs (internals): Shortcut(), SetShortcutRouting(): swapped last two parameters order in function signatures:\n                            - old: Shortcut(ImGuiKeyChord key_chord, ImGuiID owner_id = 0, ImGuiInputFlags flags = 0);\n                            - new: Shortcut(ImGuiKeyChord key_chord, ImGuiInputFlags flags = 0, ImGuiID owner_id = 0);\n                       - inputs (internals): owner-aware versions of IsKeyPressed(), IsKeyChordPressed(), IsMouseClicked(): swapped last two parameters order in function signatures.\n                            - old: IsKeyPressed(ImGuiKey key, ImGuiID owner_id, ImGuiInputFlags flags = 0);\n                            - new: IsKeyPressed(ImGuiKey key, ImGuiInputFlags flags, ImGuiID owner_id = 0);\n                            - old: IsMouseClicked(ImGuiMouseButton button, ImGuiID owner_id, ImGuiInputFlags flags = 0);\n                            - new: IsMouseClicked(ImGuiMouseButton button, ImGuiInputFlags flags, ImGuiID owner_id = 0);\n                         for various reasons those changes makes sense. They are being made because making some of those API public.\n                         only past users of imgui_internal.h with the extra parameters will be affected. Added asserts for valid flags in various functions to detect _some_ misuses, BUT NOT ALL.\n - 2024/05/16 (1.90.7) - inputs: on macOS X, Cmd and Ctrl keys are now automatically swapped by io.AddKeyEvent() as this naturally align with how macOS X uses those keys.\n                           - it shouldn't really affect you unless you had custom shortcut swapping in place for macOS X apps.\n                           - removed ImGuiMod_Shortcut which was previously dynamically remapping to Ctrl or Cmd/Super. It is now unnecessary to specific cross-platform idiomatic shortcuts. (#2343, #4084, #5923, #456)\n - 2024/05/14 (1.90.7) - backends: SDL_Renderer2 and SDL_Renderer3 backend now take a SDL_Renderer* in their RenderDrawData() functions.\n - 2024/04/18 (1.90.6) - TreeNode: Fixed a layout inconsistency when using an empty/hidden label followed by a SameLine() call. (#7505, #282)\n                           - old: TreeNode(\"##Hidden\"); SameLine(); Text(\"Hello\");     // <-- This was actually incorrect! BUT appeared to look ok with the default style where ItemSpacing.x == FramePadding.x * 2 (it didn't look aligned otherwise).\n                           - new: TreeNode(\"##Hidden\"); SameLine(0, 0); Text(\"Hello\"); // <-- This is correct for all styles values.\n                         with the fix, IF you were successfully using TreeNode(\"\")+SameLine(); you will now have extra spacing between your TreeNode and the following item.\n                         You'll need to change the SameLine() call to SameLine(0,0) to remove this extraneous spacing. This seemed like the more sensible fix that's not making things less consistent.\n                         (Note: when using this idiom you are likely to also use ImGuiTreeNodeFlags_SpanAvailWidth).\n - 2024/03/18 (1.90.5) - merged the radius_x/radius_y parameters in ImDrawList::AddEllipse(), AddEllipseFilled() and PathEllipticalArcTo() into a single ImVec2 parameter. Exceptionally, because those functions were added in 1.90, we are not adding inline redirection functions. The transition is easy and should affect few users. (#2743, #7417)\n - 2024/03/08 (1.90.5) - inputs: more formally obsoleted GetKeyIndex() when IMGUI_DISABLE_OBSOLETE_FUNCTIONS is set. It has been unnecessary and a no-op since 1.87 (it returns the same value as passed when used with a 1.87+ backend using io.AddKeyEvent() function). (#4921)\n                           - IsKeyPressed(GetKeyIndex(ImGuiKey_XXX)) -> use IsKeyPressed(ImGuiKey_XXX)\n - 2024/01/15 (1.90.2) - commented out obsolete ImGuiIO::ImeWindowHandle marked obsolete in 1.87, favor of writing to 'void* ImGuiViewport::PlatformHandleRaw'.\n - 2023/12/19 (1.90.1) - commented out obsolete ImGuiKey_KeyPadEnter redirection to ImGuiKey_KeypadEnter.\n - 2023/11/06 (1.90.1) - removed CalcListClipping() marked obsolete in 1.86. Prefer using ImGuiListClipper which can return non-contiguous ranges.\n - 2023/11/05 (1.90.1) - imgui_freetype: commented out ImGuiFreeType::BuildFontAtlas() obsoleted in 1.81. prefer using #define IMGUI_ENABLE_FREETYPE or see commented code for manual calls.\n - 2023/11/05 (1.90.1) - internals,columns: commented out legacy ImGuiColumnsFlags_XXX symbols redirecting to ImGuiOldColumnsFlags_XXX, obsoleted from imgui_internal.h in 1.80.\n - 2023/11/09 (1.90.0) - removed IM_OFFSETOF() macro in favor of using offsetof() available in C++11. Kept redirection define (will obsolete).\n - 2023/11/07 (1.90.0) - removed BeginChildFrame()/EndChildFrame() in favor of using BeginChild() with the ImGuiChildFlags_FrameStyle flag. kept inline redirection function (will obsolete).\n                         those functions were merely PushStyle/PopStyle helpers, the removal isn't so much motivated by needing to add the feature in BeginChild(), but by the necessity to avoid BeginChildFrame() signature mismatching BeginChild() signature and features.\n - 2023/11/02 (1.90.0) - BeginChild: upgraded 'bool border = true' parameter to 'ImGuiChildFlags flags' type, added ImGuiChildFlags_Border equivalent. As with our prior \"bool-to-flags\" API updates, the ImGuiChildFlags_Border value is guaranteed to be == true forever to ensure a smoother transition, meaning all existing calls will still work.\n                           - old: BeginChild(\"Name\", size, true)\n                           - new: BeginChild(\"Name\", size, ImGuiChildFlags_Border)\n                           - old: BeginChild(\"Name\", size, false)\n                           - new: BeginChild(\"Name\", size) or BeginChild(\"Name\", 0) or BeginChild(\"Name\", size, ImGuiChildFlags_None)\n                         **AMEND FROM THE FUTURE: from 1.91.1, 'ImGuiChildFlags_Border' is called 'ImGuiChildFlags_Borders'**\n - 2023/11/02 (1.90.0) - BeginChild: added child-flag ImGuiChildFlags_AlwaysUseWindowPadding as a replacement for the window-flag ImGuiWindowFlags_AlwaysUseWindowPadding: the feature only ever made sense for BeginChild() anyhow.\n                           - old: BeginChild(\"Name\", size, 0, ImGuiWindowFlags_AlwaysUseWindowPadding);\n                           - new: BeginChild(\"Name\", size, ImGuiChildFlags_AlwaysUseWindowPadding, 0);\n - 2023/09/27 (1.90.0) - io: removed io.MetricsActiveAllocations introduced in 1.63. Same as 'g.DebugMemAllocCount - g.DebugMemFreeCount' (still displayed in Metrics, unlikely to be accessed by end-user).\n - 2023/09/26 (1.90.0) - debug tools: Renamed ShowStackToolWindow() (\"Stack Tool\") to ShowIDStackToolWindow() (\"ID Stack Tool\"), as earlier name was misleading. Kept inline redirection function. (#4631)\n - 2023/09/15 (1.90.0) - ListBox, Combo: changed signature of \"name getter\" callback in old one-liner ListBox()/Combo() apis. kept inline redirection function (will obsolete).\n                           - old: bool Combo(const char* label, int* current_item, bool (*getter)(void* user_data, int idx, const char** out_text), ...)\n                           - new: bool Combo(const char* label, int* current_item, const char* (*getter)(void* user_data, int idx), ...);\n                           - old: bool ListBox(const char* label, int* current_item, bool (*getting)(void* user_data, int idx, const char** out_text), ...);\n                           - new: bool ListBox(const char* label, int* current_item, const char* (*getter)(void* user_data, int idx), ...);\n - 2023/09/08 (1.90.0) - commented out obsolete redirecting functions:\n                           - GetWindowContentRegionWidth()  -> use GetWindowContentRegionMax().x - GetWindowContentRegionMin().x. Consider that generally 'GetContentRegionAvail().x' is more useful.\n                           - ImDrawCornerFlags_XXX          -> use ImDrawFlags_RoundCornersXXX flags. Read 1.82 Changelog for details + grep commented names in sources.\n                       - commented out runtime support for hardcoded ~0 or 0x01..0x0F rounding flags values for AddRect()/AddRectFilled()/PathRect()/AddImageRounded() -> use ImDrawFlags_RoundCornersXXX flags. Read 1.82 Changelog for details\n - 2023/08/25 (1.89.9) - clipper: Renamed IncludeRangeByIndices() (also called ForceDisplayRangeByIndices() before 1.89.6) to IncludeItemsByIndex(). Kept inline redirection function. Sorry!\n - 2023/07/12 (1.89.8) - ImDrawData: CmdLists now owned, changed from ImDrawList** to ImVector<ImDrawList*>. Majority of users shouldn't be affected, but you cannot compare to NULL nor reassign manually anymore. Instead use AddDrawList(). (#6406, #4879, #1878)\n - 2023/06/28 (1.89.7) - overlapping items: obsoleted 'SetItemAllowOverlap()' (called after item) in favor of calling 'SetNextItemAllowOverlap()' (called before item). 'SetItemAllowOverlap()' didn't and couldn't work reliably since 1.89 (2022-11-15).\n - 2023/06/28 (1.89.7) - overlapping items: renamed 'ImGuiTreeNodeFlags_AllowItemOverlap' to 'ImGuiTreeNodeFlags_AllowOverlap', 'ImGuiSelectableFlags_AllowItemOverlap' to 'ImGuiSelectableFlags_AllowOverlap'. Kept redirecting enums (will obsolete).\n - 2023/06/28 (1.89.7) - overlapping items: IsItemHovered() now by default return false when querying an item using AllowOverlap mode which is being overlapped. Use ImGuiHoveredFlags_AllowWhenOverlappedByItem to revert to old behavior.\n - 2023/06/28 (1.89.7) - overlapping items: Selectable and TreeNode don't allow overlap when active so overlapping widgets won't appear as hovered. While this fixes a common small visual issue, it also means that calling IsItemHovered() after a non-reactive elements - e.g. Text() - overlapping an active one may fail if you don't use IsItemHovered(ImGuiHoveredFlags_AllowWhenBlockedByActiveItem). (#6610)\n - 2023/06/20 (1.89.7) - moved io.HoverDelayShort/io.HoverDelayNormal to style.HoverDelayShort/style.HoverDelayNormal. As the fields were added in 1.89 and expected to be left unchanged by most users, or only tweaked once during app initialization, we are exceptionally accepting the breakage.\n - 2023/05/30 (1.89.6) - backends: renamed \"imgui_impl_sdlrenderer.cpp\" to \"imgui_impl_sdlrenderer2.cpp\" and \"imgui_impl_sdlrenderer.h\" to \"imgui_impl_sdlrenderer2.h\". This is in prevision for the future release of SDL3.\n - 2023/05/22 (1.89.6) - listbox: commented out obsolete/redirecting functions that were marked obsolete more than two years ago:\n                           - ListBoxHeader()  -> use BeginListBox() (note how two variants of ListBoxHeader() existed. Check commented versions in imgui.h for reference)\n                           - ListBoxFooter()  -> use EndListBox()\n - 2023/05/15 (1.89.6) - clipper: commented out obsolete redirection constructor 'ImGuiListClipper(int items_count, float items_height = -1.0f)' that was marked obsolete in 1.79. Use default constructor + clipper.Begin().\n - 2023/05/15 (1.89.6) - clipper: renamed ImGuiListClipper::ForceDisplayRangeByIndices() to ImGuiListClipper::IncludeRangeByIndices().\n - 2023/03/14 (1.89.4) - commented out redirecting enums/functions names that were marked obsolete two years ago:\n                           - ImGuiSliderFlags_ClampOnInput        -> use ImGuiSliderFlags_AlwaysClamp\n                           - ImGuiInputTextFlags_AlwaysInsertMode -> use ImGuiInputTextFlags_AlwaysOverwrite\n                           - ImDrawList::AddBezierCurve()         -> use ImDrawList::AddBezierCubic()\n                           - ImDrawList::PathBezierCurveTo()      -> use ImDrawList::PathBezierCubicCurveTo()\n - 2023/03/09 (1.89.4) - renamed PushAllowKeyboardFocus()/PopAllowKeyboardFocus() to PushTabStop()/PopTabStop(). Kept inline redirection functions (will obsolete).\n - 2023/03/09 (1.89.4) - tooltips: Added 'bool' return value to BeginTooltip() for API consistency. Please only submit contents and call EndTooltip() if BeginTooltip() returns true. In reality the function will _currently_ always return true, but further changes down the line may change this, best to clarify API sooner.\n - 2023/02/15 (1.89.4) - moved the optional \"courtesy maths operators\" implementation from imgui_internal.h in imgui.h.\n                         Even though we encourage using your own maths types and operators by setting up IM_VEC2_CLASS_EXTRA,\n                         it has been frequently requested by people to use our own. We had an opt-in define which was\n                         previously fulfilled in imgui_internal.h. It is now fulfilled in imgui.h. (#6164)\n                           - OK:     #define IMGUI_DEFINE_MATH_OPERATORS / #include \"imgui.h\" / #include \"imgui_internal.h\"\n                           - Error:  #include \"imgui.h\" / #define IMGUI_DEFINE_MATH_OPERATORS / #include \"imgui_internal.h\"\n - 2023/02/07 (1.89.3) - backends: renamed \"imgui_impl_sdl.cpp\" to \"imgui_impl_sdl2.cpp\" and \"imgui_impl_sdl.h\" to \"imgui_impl_sdl2.h\". (#6146) This is in prevision for the future release of SDL3.\n - 2022/10/26 (1.89)   - commented out redirecting OpenPopupContextItem() which was briefly the name of OpenPopupOnItemClick() from 1.77 to 1.79.\n - 2022/10/12 (1.89)   - removed runtime patching of invalid \"%f\"/\"%0.f\" format strings for DragInt()/SliderInt(). This was obsoleted in 1.61 (May 2018). See 1.61 changelog for details.\n - 2022/09/26 (1.89)   - renamed and merged keyboard modifiers key enums and flags into a same set. Kept inline redirection enums (will obsolete).\n                           - ImGuiKey_ModCtrl  and ImGuiModFlags_Ctrl  -> ImGuiMod_Ctrl\n                           - ImGuiKey_ModShift and ImGuiModFlags_Shift -> ImGuiMod_Shift\n                           - ImGuiKey_ModAlt   and ImGuiModFlags_Alt   -> ImGuiMod_Alt\n                           - ImGuiKey_ModSuper and ImGuiModFlags_Super -> ImGuiMod_Super\n                         the ImGuiKey_ModXXX were introduced in 1.87 and mostly used by backends.\n                         the ImGuiModFlags_XXX have been exposed in imgui.h but not really used by any public api only by third-party extensions.\n                         exceptionally commenting out the older ImGuiKeyModFlags_XXX names ahead of obsolescence schedule to reduce confusion and because they were not meant to be used anyway.\n - 2022/09/20 (1.89)   - ImGuiKey is now a typed enum, allowing ImGuiKey_XXX symbols to be named in debuggers.\n                         this will require uses of legacy backend-dependent indices to be casted, e.g.\n                            - with imgui_impl_glfw:  IsKeyPressed(GLFW_KEY_A) -> IsKeyPressed((ImGuiKey)GLFW_KEY_A);\n                            - with imgui_impl_win32: IsKeyPressed('A')        -> IsKeyPressed((ImGuiKey)'A')\n                            - etc. However if you are upgrading code you might well use the better, backend-agnostic IsKeyPressed(ImGuiKey_A) now!\n - 2022/09/12 (1.89) - removed the bizarre legacy default argument for 'TreePush(const void* ptr = NULL)', always pass a pointer value explicitly. NULL/nullptr is ok but require cast, e.g. TreePush((void*)nullptr);\n - 2022/09/05 (1.89) - commented out redirecting functions/enums names that were marked obsolete in 1.77 and 1.78 (June 2020):\n                         - DragScalar(), DragScalarN(), DragFloat(), DragFloat2(), DragFloat3(), DragFloat4(): For old signatures ending with (..., const char* format, float power = 1.0f) -> use (..., format ImGuiSliderFlags_Logarithmic) if power != 1.0f.\n                         - SliderScalar(), SliderScalarN(), SliderFloat(), SliderFloat2(), SliderFloat3(), SliderFloat4(): For old signatures ending with (..., const char* format, float power = 1.0f) -> use (..., format ImGuiSliderFlags_Logarithmic) if power != 1.0f.\n                         - BeginPopupContextWindow(const char*, ImGuiMouseButton, bool) -> use BeginPopupContextWindow(const char*, ImGuiPopupFlags)\n - 2022/09/02 (1.89) - obsoleted using SetCursorPos()/SetCursorScreenPos() to extend parent window/cell boundaries.\n                       this relates to when moving the cursor position beyond current boundaries WITHOUT submitting an item.\n                         - previously this would make the window content size ~200x200:\n                              Begin(...) + SetCursorScreenPos(GetCursorScreenPos() + ImVec2(200,200)) + End();\n                         - instead, please submit an item:\n                              Begin(...) + SetCursorScreenPos(GetCursorScreenPos() + ImVec2(200,200)) + Dummy(ImVec2(0,0)) + End();\n                         - alternative:\n                              Begin(...) + Dummy(ImVec2(200,200)) + End();\n                         - content size is now only extended when submitting an item!\n                         - with '#define IMGUI_DISABLE_OBSOLETE_FUNCTIONS' this will now be detected and assert.\n                         - without '#define IMGUI_DISABLE_OBSOLETE_FUNCTIONS' this will silently be fixed until we obsolete it.\n - 2022/08/03 (1.89) - changed signature of ImageButton() function. Kept redirection function (will obsolete).\n                        - added 'const char* str_id' parameter + removed 'int frame_padding = -1' parameter.\n                        - old signature: bool ImageButton(ImTextureID tex_id, ImVec2 size, ImVec2 uv0 = ImVec2(0,0), ImVec2 uv1 = ImVec2(1,1), int frame_padding = -1, ImVec4 bg_col = ImVec4(0,0,0,0), ImVec4 tint_col = ImVec4(1,1,1,1));\n                          - used the ImTextureID value to create an ID. This was inconsistent with other functions, led to ID conflicts, and caused problems with engines using transient ImTextureID values.\n                          - had a FramePadding override which was inconsistent with other functions and made the already-long signature even longer.\n                        - new signature: bool ImageButton(const char* str_id, ImTextureID tex_id, ImVec2 size, ImVec2 uv0 = ImVec2(0,0), ImVec2 uv1 = ImVec2(1,1), ImVec4 bg_col = ImVec4(0,0,0,0), ImVec4 tint_col = ImVec4(1,1,1,1));\n                          - requires an explicit identifier. You may still use e.g. PushID() calls and then pass an empty identifier.\n                          - always uses style.FramePadding for padding, to be consistent with other buttons. You may use PushStyleVar() to alter this.\n - 2022/07/08 (1.89) - inputs: removed io.NavInputs[] and ImGuiNavInput enum (following 1.87 changes).\n                        - Official backends from 1.87+                  -> no issue.\n                        - Official backends from 1.60 to 1.86           -> will build and convert gamepad inputs, unless IMGUI_DISABLE_OBSOLETE_KEYIO is defined. Need updating!\n                        - Custom backends not writing to io.NavInputs[] -> no issue.\n                        - Custom backends writing to io.NavInputs[]     -> will build and convert gamepad inputs, unless IMGUI_DISABLE_OBSOLETE_KEYIO is defined. Need fixing!\n                        - TL;DR: Backends should call io.AddKeyEvent()/io.AddKeyAnalogEvent() with ImGuiKey_GamepadXXX values instead of filling io.NavInput[].\n - 2022/06/15 (1.88) - renamed IMGUI_DISABLE_METRICS_WINDOW to IMGUI_DISABLE_DEBUG_TOOLS for correctness. kept support for old define (will obsolete).\n - 2022/05/03 (1.88) - backends: osx: removed ImGui_ImplOSX_HandleEvent() from backend API in favor of backend automatically handling event capture. All ImGui_ImplOSX_HandleEvent() calls should be removed as they are now unnecessary.\n - 2022/04/05 (1.88) - inputs: renamed ImGuiKeyModFlags to ImGuiModFlags. Kept inline redirection enums (will obsolete). This was never used in public API functions but technically present in imgui.h and ImGuiIO.\n - 2022/01/20 (1.87) - inputs: reworded gamepad IO.\n                        - Backend writing to io.NavInputs[]            -> backend should call io.AddKeyEvent()/io.AddKeyAnalogEvent() with ImGuiKey_GamepadXXX values.\n - 2022/01/19 (1.87) - sliders, drags: removed support for legacy arithmetic operators (+,+-,*,/) when inputting text. This doesn't break any api/code but a feature that used to be accessible by end-users (which seemingly no one used).\n - 2022/01/17 (1.87) - inputs: reworked mouse IO.\n                        - Backend writing to io.MousePos               -> backend should call io.AddMousePosEvent()\n                        - Backend writing to io.MouseDown[]            -> backend should call io.AddMouseButtonEvent()\n                        - Backend writing to io.MouseWheel             -> backend should call io.AddMouseWheelEvent()\n                        - Backend writing to io.MouseHoveredViewport   -> backend should call io.AddMouseViewportEvent() [Docking branch w/ multi-viewports only]\n                       note: for all calls to IO new functions, the Dear ImGui context should be bound/current.\n                       read https://github.com/ocornut/imgui/issues/4921 for details.\n - 2022/01/10 (1.87) - inputs: reworked keyboard IO. Removed io.KeyMap[], io.KeysDown[] in favor of calling io.AddKeyEvent(), ImGui::IsKeyDown(). Removed GetKeyIndex(), now unnecessary. All IsKeyXXX() functions now take ImGuiKey values. All features are still functional until IMGUI_DISABLE_OBSOLETE_KEYIO is defined. Read Changelog and Release Notes for details.\n                        - IsKeyPressed(MY_NATIVE_KEY_XXX)              -> use IsKeyPressed(ImGuiKey_XXX)\n                        - IsKeyPressed(GetKeyIndex(ImGuiKey_XXX))      -> use IsKeyPressed(ImGuiKey_XXX)\n                        - Backend writing to io.KeyMap[],io.KeysDown[] -> backend should call io.AddKeyEvent() (+ call io.SetKeyEventNativeData() if you want legacy user code to stil function with legacy key codes).\n                        - Backend writing to io.KeyCtrl, io.KeyShift.. -> backend should call io.AddKeyEvent() with ImGuiMod_XXX values. *IF YOU PULLED CODE BETWEEN 2021/01/10 and 2021/01/27: We used to have a io.AddKeyModsEvent() function which was now replaced by io.AddKeyEvent() with ImGuiMod_XXX values.*\n                     - one case won't work with backward compatibility: if your custom backend used ImGuiKey as mock native indices (e.g. \"io.KeyMap[ImGuiKey_A] = ImGuiKey_A\") because those values are now larger than the legacy KeyDown[] array. Will assert.\n                     - inputs: added ImGuiKey_ModCtrl/ImGuiKey_ModShift/ImGuiKey_ModAlt/ImGuiKey_ModSuper values to submit keyboard modifiers using io.AddKeyEvent(), instead of writing directly to io.KeyCtrl, io.KeyShift, io.KeyAlt, io.KeySuper.\n - 2022/01/05 (1.87) - inputs: renamed ImGuiKey_KeyPadEnter to ImGuiKey_KeypadEnter to align with new symbols. Kept redirection enum.\n - 2022/01/05 (1.87) - removed io.ImeSetInputScreenPosFn() in favor of more flexible io.SetPlatformImeDataFn(). Removed 'void* io.ImeWindowHandle' in favor of writing to 'void* ImGuiViewport::PlatformHandleRaw'.\n - 2022/01/01 (1.87) - commented out redirecting functions/enums names that were marked obsolete in 1.69, 1.70, 1.71, 1.72 (March-July 2019)\n                        - ImGui::SetNextTreeNodeOpen()        -> use ImGui::SetNextItemOpen()\n                        - ImGui::GetContentRegionAvailWidth() -> use ImGui::GetContentRegionAvail().x\n                        - ImGui::TreeAdvanceToLabelPos()      -> use ImGui::SetCursorPosX(ImGui::GetCursorPosX() + ImGui::GetTreeNodeToLabelSpacing());\n                        - ImFontAtlas::CustomRect             -> use ImFontAtlasCustomRect\n                        - ImGuiColorEditFlags_RGB/HSV/HEX     -> use ImGuiColorEditFlags_DisplayRGB/HSV/Hex\n - 2021/12/20 (1.86) - backends: removed obsolete Marmalade backend (imgui_impl_marmalade.cpp) + example. Find last supported version at https://github.com/ocornut/imgui/wiki/Bindings\n - 2021/11/04 (1.86) - removed CalcListClipping() function. Prefer using ImGuiListClipper which can return non-contiguous ranges. Please open an issue if you think you really need this function.\n - 2021/08/23 (1.85) - removed GetWindowContentRegionWidth() function. keep inline redirection helper. can use 'GetWindowContentRegionMax().x - GetWindowContentRegionMin().x' instead for generally 'GetContentRegionAvail().x' is more useful.\n - 2021/07/26 (1.84) - commented out redirecting functions/enums names that were marked obsolete in 1.67 and 1.69 (March 2019):\n                        - ImGui::GetOverlayDrawList() -> use ImGui::GetForegroundDrawList()\n                        - ImFont::GlyphRangesBuilder  -> use ImFontGlyphRangesBuilder\n - 2021/05/19 (1.83) - backends: obsoleted direct access to ImDrawCmd::TextureId in favor of calling ImDrawCmd::GetTexID().\n                        - if you are using official backends from the source tree: you have nothing to do.\n                        - if you have copied old backend code or using your own: change access to draw_cmd->TextureId to draw_cmd->GetTexID().\n - 2021/03/12 (1.82) - upgraded ImDrawList::AddRect(), AddRectFilled(), PathRect() to use ImDrawFlags instead of ImDrawCornersFlags.\n                        - ImDrawCornerFlags_TopLeft  -> use ImDrawFlags_RoundCornersTopLeft\n                        - ImDrawCornerFlags_BotRight -> use ImDrawFlags_RoundCornersBottomRight\n                        - ImDrawCornerFlags_None     -> use ImDrawFlags_RoundCornersNone etc.\n                       flags now sanely defaults to 0 instead of 0x0F, consistent with all other flags in the API.\n                       breaking: the default with rounding > 0.0f is now \"round all corners\" vs old implicit \"round no corners\":\n                        - rounding == 0.0f + flags == 0 --> meant no rounding  --> unchanged (common use)\n                        - rounding  > 0.0f + flags != 0 --> meant rounding     --> unchanged (common use)\n                        - rounding == 0.0f + flags != 0 --> meant no rounding  --> unchanged (unlikely use)\n                        - rounding  > 0.0f + flags == 0 --> meant no rounding  --> BREAKING (unlikely use): will now round all corners --> use ImDrawFlags_RoundCornersNone or rounding == 0.0f.\n                       this ONLY matters for hard coded use of 0 + rounding > 0.0f. Use of named ImDrawFlags_RoundCornersNone (new) or ImDrawCornerFlags_None (old) are ok.\n                       the old ImDrawCornersFlags used awkward default values of ~0 or 0xF (4 lower bits set) to signify \"round all corners\" and we sometimes encouraged using them as shortcuts.\n                       legacy path still support use of hard coded ~0 or any value from 0x1 or 0xF. They will behave the same with legacy paths enabled (will assert otherwise).\n - 2021/03/11 (1.82) - removed redirecting functions/enums names that were marked obsolete in 1.66 (September 2018):\n                        - ImGui::SetScrollHere()              -> use ImGui::SetScrollHereY()\n - 2021/03/11 (1.82) - clarified that ImDrawList::PathArcTo(), ImDrawList::PathArcToFast() won't render with radius < 0.0f. Previously it sorts of accidentally worked but would generally lead to counter-clockwise paths and have an effect on anti-aliasing.\n - 2021/03/10 (1.82) - upgraded ImDrawList::AddPolyline() and PathStroke() \"bool closed\" parameter to \"ImDrawFlags flags\". The matching ImDrawFlags_Closed value is guaranteed to always stay == 1 in the future.\n - 2021/02/22 (1.82) - (*undone in 1.84*) win32+mingw: Re-enabled IME functions by default even under MinGW. In July 2016, issue #738 had me incorrectly disable those default functions for MinGW. MinGW users should: either link with -limm32, either set their imconfig file  with '#define IMGUI_DISABLE_WIN32_DEFAULT_IME_FUNCTIONS'.\n - 2021/02/17 (1.82) - renamed rarely used style.CircleSegmentMaxError (old default = 1.60f) to style.CircleTessellationMaxError (new default = 0.30f) as the meaning of the value changed.\n - 2021/02/03 (1.81) - renamed ListBoxHeader(const char* label, ImVec2 size) to BeginListBox(). Kept inline redirection function (will obsolete).\n                     - removed ListBoxHeader(const char* label, int items_count, int height_in_items = -1) in favor of specifying size. Kept inline redirection function (will obsolete).\n                     - renamed ListBoxFooter() to EndListBox(). Kept inline redirection function (will obsolete).\n - 2021/01/26 (1.81) - removed ImGuiFreeType::BuildFontAtlas(). Kept inline redirection function. Prefer using '#define IMGUI_ENABLE_FREETYPE', but there's a runtime selection path available too. The shared extra flags parameters (very rarely used) are now stored in ImFontAtlas::FontBuilderFlags.\n                     - renamed ImFontConfig::RasterizerFlags (used by FreeType) to ImFontConfig::FontBuilderFlags.\n                     - renamed ImGuiFreeType::XXX flags to ImGuiFreeTypeBuilderFlags_XXX for consistency with other API.\n - 2020/10/12 (1.80) - removed redirecting functions/enums that were marked obsolete in 1.63 (August 2018):\n                        - ImGui::IsItemDeactivatedAfterChange() -> use ImGui::IsItemDeactivatedAfterEdit().\n                        - ImGuiCol_ModalWindowDarkening       -> use ImGuiCol_ModalWindowDimBg\n                        - ImGuiInputTextCallback              -> use ImGuiTextEditCallback\n                        - ImGuiInputTextCallbackData          -> use ImGuiTextEditCallbackData\n - 2020/12/21 (1.80) - renamed ImDrawList::AddBezierCurve() to AddBezierCubic(), and PathBezierCurveTo() to PathBezierCubicCurveTo(). Kept inline redirection function (will obsolete).\n - 2020/12/04 (1.80) - added imgui_tables.cpp file! Manually constructed project files will need the new file added!\n - 2020/11/18 (1.80) - renamed undocumented/internals ImGuiColumnsFlags_* to ImGuiOldColumnFlags_* in prevision of incoming Tables API.\n - 2020/11/03 (1.80) - renamed io.ConfigWindowsMemoryCompactTimer to io.ConfigMemoryCompactTimer as the feature will apply to other data structures\n - 2020/10/14 (1.80) - backends: moved all backends files (imgui_impl_XXXX.cpp, imgui_impl_XXXX.h) from examples/ to backends/.\n - 2020/10/12 (1.80) - removed redirecting functions/enums that were marked obsolete in 1.60 (April 2018):\n                        - io.RenderDrawListsFn pointer        -> use ImGui::GetDrawData() value and call the render function of your backend\n                        - ImGui::IsAnyWindowFocused()         -> use ImGui::IsWindowFocused(ImGuiFocusedFlags_AnyWindow)\n                        - ImGui::IsAnyWindowHovered()         -> use ImGui::IsWindowHovered(ImGuiHoveredFlags_AnyWindow)\n                        - ImGuiStyleVar_Count_                -> use ImGuiStyleVar_COUNT\n                        - ImGuiMouseCursor_Count_             -> use ImGuiMouseCursor_COUNT\n                      - removed redirecting functions names that were marked obsolete in 1.61 (May 2018):\n                        - InputFloat (... int decimal_precision ...) -> use InputFloat (... const char* format ...) with format = \"%.Xf\" where X is your value for decimal_precision.\n                        - same for InputFloat2()/InputFloat3()/InputFloat4() variants taking a `int decimal_precision` parameter.\n - 2020/10/05 (1.79) - removed ImGuiListClipper: Renamed constructor parameters which created an ambiguous alternative to using the ImGuiListClipper::Begin() function, with misleading edge cases (note: imgui_memory_editor <0.40 from imgui_club/ used this old clipper API. Update your copy if needed).\n - 2020/09/25 (1.79) - renamed ImGuiSliderFlags_ClampOnInput to ImGuiSliderFlags_AlwaysClamp. Kept redirection enum (will obsolete sooner because previous name was added recently).\n - 2020/09/25 (1.79) - renamed style.TabMinWidthForUnselectedCloseButton to style.TabMinWidthForCloseButton.\n - 2020/09/21 (1.79) - renamed OpenPopupContextItem() back to OpenPopupOnItemClick(), reverting the change from 1.77. For varieties of reason this is more self-explanatory.\n - 2020/09/21 (1.79) - removed return value from OpenPopupOnItemClick() - returned true on mouse release on an item - because it is inconsistent with other popup APIs and makes others misleading. It's also and unnecessary: you can use IsWindowAppearing() after BeginPopup() for a similar result.\n - 2020/09/17 (1.79) - removed ImFont::DisplayOffset in favor of ImFontConfig::GlyphOffset. DisplayOffset was applied after scaling and not very meaningful/useful outside of being needed by the default ProggyClean font. If you scaled this value after calling AddFontDefault(), this is now done automatically. It was also getting in the way of better font scaling, so let's get rid of it now!\n - 2020/08/17 (1.78) - obsoleted use of the trailing 'float power=1.0f' parameter for DragFloat(), DragFloat2(), DragFloat3(), DragFloat4(), DragFloatRange2(), DragScalar(), DragScalarN(), SliderFloat(), SliderFloat2(), SliderFloat3(), SliderFloat4(), SliderScalar(), SliderScalarN(), VSliderFloat() and VSliderScalar().\n                       replaced the 'float power=1.0f' argument with integer-based flags defaulting to 0 (as with all our flags).\n                       worked out a backward-compatibility scheme so hopefully most C++ codebase should not be affected. in short, when calling those functions:\n                       - if you omitted the 'power' parameter (likely!), you are not affected.\n                       - if you set the 'power' parameter to 1.0f (same as previous default value): 1/ your compiler may warn on float>int conversion, 2/ everything else will work. 3/ you can replace the 1.0f value with 0 to fix the warning, and be technically correct.\n                       - if you set the 'power' parameter to >1.0f (to enable non-linear editing): 1/ your compiler may warn on float>int conversion, 2/ code will assert at runtime, 3/ in case asserts are disabled, the code will not crash and enable the _Logarithmic flag. 4/ you can replace the >1.0f value with ImGuiSliderFlags_Logarithmic to fix the warning/assert and get a _similar_ effect as previous uses of power >1.0f.\n                       see https://github.com/ocornut/imgui/issues/3361 for all details.\n                       kept inline redirection functions (will obsolete) apart for: DragFloatRange2(), VSliderFloat(), VSliderScalar(). For those three the 'float power=1.0f' version was removed directly as they were most unlikely ever used.\n                       for shared code, you can version check at compile-time with `#if IMGUI_VERSION_NUM >= 17704`.\n                     - obsoleted use of v_min > v_max in DragInt, DragFloat, DragScalar to lock edits (introduced in 1.73, was not demoed nor documented very), will be replaced by a more generic ReadOnly feature. You may use the ImGuiSliderFlags_ReadOnly internal flag in the meantime.\n - 2020/06/23 (1.77) - removed BeginPopupContextWindow(const char*, int mouse_button, bool also_over_items) in favor of BeginPopupContextWindow(const char*, ImGuiPopupFlags flags) with ImGuiPopupFlags_NoOverItems.\n - 2020/06/15 (1.77) - renamed OpenPopupOnItemClick() to OpenPopupContextItem(). Kept inline redirection function (will obsolete). [NOTE: THIS WAS REVERTED IN 1.79]\n - 2020/06/15 (1.77) - removed CalcItemRectClosestPoint() entry point which was made obsolete and asserting in December 2017.\n - 2020/04/23 (1.77) - removed unnecessary ID (first arg) of ImFontAtlas::AddCustomRectRegular().\n - 2020/01/22 (1.75) - ImDrawList::AddCircle()/AddCircleFilled() functions don't accept negative radius any more.\n - 2019/12/17 (1.75) - [undid this change in 1.76] made Columns() limited to 64 columns by asserting above that limit. While the current code technically supports it, future code may not so we're putting the restriction ahead.\n - 2019/12/13 (1.75) - [imgui_internal.h] changed ImRect() default constructor initializes all fields to 0.0f instead of (FLT_MAX,FLT_MAX,-FLT_MAX,-FLT_MAX). If you used ImRect::Add() to create bounding boxes by adding multiple points into it, you may need to fix your initial value.\n - 2019/12/08 (1.75) - removed redirecting functions/enums that were marked obsolete in 1.53 (December 2017):\n                       - ShowTestWindow()                    -> use ShowDemoWindow()\n                       - IsRootWindowFocused()               -> use IsWindowFocused(ImGuiFocusedFlags_RootWindow)\n                       - IsRootWindowOrAnyChildFocused()     -> use IsWindowFocused(ImGuiFocusedFlags_RootAndChildWindows)\n                       - SetNextWindowContentWidth(w)        -> use SetNextWindowContentSize(ImVec2(w, 0.0f)\n                       - GetItemsLineHeightWithSpacing()     -> use GetFrameHeightWithSpacing()\n                       - ImGuiCol_ChildWindowBg              -> use ImGuiCol_ChildBg\n                       - ImGuiStyleVar_ChildWindowRounding   -> use ImGuiStyleVar_ChildRounding\n                       - ImGuiTreeNodeFlags_AllowOverlapMode -> use ImGuiTreeNodeFlags_AllowItemOverlap\n                       - IMGUI_DISABLE_TEST_WINDOWS          -> use IMGUI_DISABLE_DEMO_WINDOWS\n - 2019/12/08 (1.75) - obsoleted calling ImDrawList::PrimReserve() with a negative count (which was vaguely documented and rarely if ever used). Instead, we added an explicit PrimUnreserve() API.\n - 2019/12/06 (1.75) - removed implicit default parameter to IsMouseDragging(int button = 0) to be consistent with other mouse functions (none of the other functions have it).\n - 2019/11/21 (1.74) - ImFontAtlas::AddCustomRectRegular() now requires an ID larger than 0x110000 (instead of 0x10000) to conform with supporting Unicode planes 1-16 in a future update. ID below 0x110000 will now assert.\n - 2019/11/19 (1.74) - renamed IMGUI_DISABLE_FORMAT_STRING_FUNCTIONS to IMGUI_DISABLE_DEFAULT_FORMAT_FUNCTIONS for consistency.\n - 2019/11/19 (1.74) - renamed IMGUI_DISABLE_MATH_FUNCTIONS to IMGUI_DISABLE_DEFAULT_MATH_FUNCTIONS for consistency.\n - 2019/10/22 (1.74) - removed redirecting functions/enums that were marked obsolete in 1.52 (October 2017):\n                       - Begin() [old 5 args version]        -> use Begin() [3 args], use SetNextWindowSize() SetNextWindowBgAlpha() if needed\n                       - IsRootWindowOrAnyChildHovered()     -> use IsWindowHovered(ImGuiHoveredFlags_RootAndChildWindows)\n                       - AlignFirstTextHeightToWidgets()     -> use AlignTextToFramePadding()\n                       - SetNextWindowPosCenter()            -> use SetNextWindowPos() with a pivot of (0.5f, 0.5f)\n                       - ImFont::Glyph                       -> use ImFontGlyph\n - 2019/10/14 (1.74) - inputs: Fixed a miscalculation in the keyboard/mouse \"typematic\" repeat delay/rate calculation, used by keys and e.g. repeating mouse buttons as well as the GetKeyPressedAmount() function.\n                       if you were using a non-default value for io.KeyRepeatRate (previous default was 0.250), you can add +io.KeyRepeatDelay to it to compensate for the fix.\n                       The function was triggering on: 0.0 and (delay+rate*N) where (N>=1). Fixed formula responds to (N>=0). Effectively it made io.KeyRepeatRate behave like it was set to (io.KeyRepeatRate + io.KeyRepeatDelay).\n                       If you never altered io.KeyRepeatRate nor used GetKeyPressedAmount() this won't affect you.\n - 2019/07/15 (1.72) - removed TreeAdvanceToLabelPos() which is rarely used and only does SetCursorPosX(GetCursorPosX() + GetTreeNodeToLabelSpacing()). Kept redirection function (will obsolete).\n - 2019/07/12 (1.72) - renamed ImFontAtlas::CustomRect to ImFontAtlasCustomRect. Kept redirection typedef (will obsolete).\n - 2019/06/14 (1.72) - removed redirecting functions/enums names that were marked obsolete in 1.51 (June 2017): ImGuiCol_Column*, ImGuiSetCond_*, IsItemHoveredRect(), IsPosHoveringAnyWindow(), IsMouseHoveringAnyWindow(), IsMouseHoveringWindow(), IMGUI_ONCE_UPON_A_FRAME. Grep this log for details and new names, or see how they were implemented until 1.71.\n - 2019/06/07 (1.71) - rendering of child window outer decorations (bg color, border, scrollbars) is now performed as part of the parent window. If you have\n                       overlapping child windows in a same parent, and relied on their relative z-order to be mapped to their submission order, this will affect your rendering.\n                       This optimization is disabled if the parent window has no visual output, because it appears to be the most common situation leading to the creation of overlapping child windows.\n                       Please reach out if you are affected.\n - 2019/05/13 (1.71) - renamed SetNextTreeNodeOpen() to SetNextItemOpen(). Kept inline redirection function (will obsolete).\n - 2019/05/11 (1.71) - changed io.AddInputCharacter(unsigned short c) signature to io.AddInputCharacter(unsigned int c).\n - 2019/04/29 (1.70) - improved ImDrawList thick strokes (>1.0f) preserving correct thickness up to 90 degrees angles (e.g. rectangles). If you have custom rendering using thick lines, they will appear thicker now.\n - 2019/04/29 (1.70) - removed GetContentRegionAvailWidth(), use GetContentRegionAvail().x instead. Kept inline redirection function (will obsolete).\n - 2019/03/04 (1.69) - renamed GetOverlayDrawList() to GetForegroundDrawList(). Kept redirection function (will obsolete).\n - 2019/02/26 (1.69) - renamed ImGuiColorEditFlags_RGB/ImGuiColorEditFlags_HSV/ImGuiColorEditFlags_HEX to ImGuiColorEditFlags_DisplayRGB/ImGuiColorEditFlags_DisplayHSV/ImGuiColorEditFlags_DisplayHex. Kept redirection enums (will obsolete).\n - 2019/02/14 (1.68) - made it illegal/assert when io.DisplayTime == 0.0f (with an exception for the first frame). If for some reason your time step calculation gives you a zero value, replace it with an arbitrarily small value!\n - 2019/02/01 (1.68) - removed io.DisplayVisibleMin/DisplayVisibleMax (which were marked obsolete and removed from viewport/docking branch already).\n - 2019/01/06 (1.67) - renamed io.InputCharacters[], marked internal as was always intended. Please don't access directly, and use AddInputCharacter() instead!\n - 2019/01/06 (1.67) - renamed ImFontAtlas::GlyphRangesBuilder to ImFontGlyphRangesBuilder. Kept redirection typedef (will obsolete).\n - 2018/12/20 (1.67) - made it illegal to call Begin(\"\") with an empty string. This somehow half-worked before but had various undesirable side-effects.\n - 2018/12/10 (1.67) - renamed io.ConfigResizeWindowsFromEdges to io.ConfigWindowsResizeFromEdges as we are doing a large pass on configuration flags.\n - 2018/10/12 (1.66) - renamed misc/stl/imgui_stl.* to misc/cpp/imgui_stdlib.* in prevision for other C++ helper files.\n - 2018/09/28 (1.66) - renamed SetScrollHere() to SetScrollHereY(). Kept redirection function (will obsolete).\n - 2018/09/06 (1.65) - renamed stb_truetype.h to imstb_truetype.h, stb_textedit.h to imstb_textedit.h, and stb_rect_pack.h to imstb_rectpack.h.\n                       If you were conveniently using the imgui copy of those STB headers in your project you will have to update your include paths.\n - 2018/09/05 (1.65) - renamed io.OptCursorBlink/io.ConfigCursorBlink to io.ConfigInputTextCursorBlink. (#1427)\n - 2018/08/31 (1.64) - added imgui_widgets.cpp file, extracted and moved widgets code out of imgui.cpp into imgui_widgets.cpp. Re-ordered some of the code remaining in imgui.cpp.\n                       NONE OF THE FUNCTIONS HAVE CHANGED. THE CODE IS SEMANTICALLY 100% IDENTICAL, BUT _EVERY_ FUNCTION HAS BEEN MOVED.\n                       Because of this, any local modifications to imgui.cpp will likely conflict when you update. Read docs/CHANGELOG.txt for suggestions.\n - 2018/08/22 (1.63) - renamed IsItemDeactivatedAfterChange() to IsItemDeactivatedAfterEdit() for consistency with new IsItemEdited() API. Kept redirection function (will obsolete soonish as IsItemDeactivatedAfterChange() is very recent).\n - 2018/08/21 (1.63) - renamed ImGuiTextEditCallback to ImGuiInputTextCallback, ImGuiTextEditCallbackData to ImGuiInputTextCallbackData for consistency. Kept redirection types (will obsolete).\n - 2018/08/21 (1.63) - removed ImGuiInputTextCallbackData::ReadOnly since it is a duplication of (ImGuiInputTextCallbackData::Flags & ImGuiInputTextFlags_ReadOnly).\n - 2018/08/01 (1.63) - removed per-window ImGuiWindowFlags_ResizeFromAnySide beta flag in favor of a global io.ConfigResizeWindowsFromEdges [update 1.67 renamed to ConfigWindowsResizeFromEdges] to enable the feature.\n - 2018/08/01 (1.63) - renamed io.OptCursorBlink to io.ConfigCursorBlink [-> io.ConfigInputTextCursorBlink in 1.65], io.OptMacOSXBehaviors to ConfigMacOSXBehaviors for consistency.\n - 2018/07/22 (1.63) - changed ImGui::GetTime() return value from float to double to avoid accumulating floating point imprecisions over time.\n - 2018/07/08 (1.63) - style: renamed ImGuiCol_ModalWindowDarkening to ImGuiCol_ModalWindowDimBg for consistency with other features. Kept redirection enum (will obsolete).\n - 2018/06/08 (1.62) - examples: the imgui_impl_XXX files have been split to separate platform (Win32, GLFW, SDL2, etc.) from renderer (DX11, OpenGL, Vulkan,  etc.).\n                       old backends will still work as is, however prefer using the separated backends as they will be updated to support multi-viewports.\n                       when adopting new backends follow the main.cpp code of your preferred examples/ folder to know which functions to call.\n                       in particular, note that old backends called ImGui::NewFrame() at the end of their ImGui_ImplXXXX_NewFrame() function.\n - 2018/06/06 (1.62) - renamed GetGlyphRangesChinese() to GetGlyphRangesChineseFull() to distinguish other variants and discourage using the full set.\n - 2018/06/06 (1.62) - TreeNodeEx()/TreeNodeBehavior(): the ImGuiTreeNodeFlags_CollapsingHeader helper now include the ImGuiTreeNodeFlags_NoTreePushOnOpen flag. See Changelog for details.\n - 2018/05/03 (1.61) - DragInt(): the default compile-time format string has been changed from \"%.0f\" to \"%d\", as we are not using integers internally any more.\n                       If you used DragInt() with custom format strings, make sure you change them to use %d or an integer-compatible format.\n                       To honor backward-compatibility, the DragInt() code will currently parse and modify format strings to replace %*f with %d, giving time to users to upgrade their code.\n                       If you have IMGUI_DISABLE_OBSOLETE_FUNCTIONS enabled, the code will instead assert! You may run a reg-exp search on your codebase for e.g. \"DragInt.*%f\" to help you find them.\n - 2018/04/28 (1.61) - obsoleted InputFloat() functions taking an optional \"int decimal_precision\" in favor of an equivalent and more flexible \"const char* format\",\n                       consistent with other functions. Kept redirection functions (will obsolete).\n - 2018/04/09 (1.61) - IM_DELETE() helper function added in 1.60 doesn't clear the input _pointer_ reference, more consistent with expectation and allows passing r-value.\n - 2018/03/20 (1.60) - renamed io.WantMoveMouse to io.WantSetMousePos for consistency and ease of understanding (was added in 1.52, _not_ used by core and only honored by some backend ahead of merging the Nav branch).\n - 2018/03/12 (1.60) - removed ImGuiCol_CloseButton, ImGuiCol_CloseButtonActive, ImGuiCol_CloseButtonHovered as the closing cross uses regular button colors now.\n - 2018/03/08 (1.60) - changed ImFont::DisplayOffset.y to default to 0 instead of +1. Fixed rounding of Ascent/Descent to match TrueType renderer. If you were adding or subtracting to ImFont::DisplayOffset check if your fonts are correctly aligned vertically.\n - 2018/03/03 (1.60) - renamed ImGuiStyleVar_Count_ to ImGuiStyleVar_COUNT and ImGuiMouseCursor_Count_ to ImGuiMouseCursor_COUNT for consistency with other public enums.\n - 2018/02/18 (1.60) - BeginDragDropSource(): temporarily removed the optional mouse_button=0 parameter because it is not really usable in many situations at the moment.\n - 2018/02/16 (1.60) - obsoleted the io.RenderDrawListsFn callback, you can call your graphics engine render function after ImGui::Render(). Use ImGui::GetDrawData() to retrieve the ImDrawData* to display.\n - 2018/02/07 (1.60) - reorganized context handling to be more explicit,\n                       - YOU NOW NEED TO CALL ImGui::CreateContext() AT THE BEGINNING OF YOUR APP, AND CALL ImGui::DestroyContext() AT THE END.\n                       - removed Shutdown() function, as DestroyContext() serve this purpose.\n                       - you may pass a ImFontAtlas* pointer to CreateContext() to share a font atlas between contexts. Otherwise CreateContext() will create its own font atlas instance.\n                       - removed allocator parameters from CreateContext(), they are now setup with SetAllocatorFunctions(), and shared by all contexts.\n                       - removed the default global context and font atlas instance, which were confusing for users of DLL reloading and users of multiple contexts.\n - 2018/01/31 (1.60) - moved sample TTF files from extra_fonts/ to misc/fonts/. If you loaded files directly from the imgui repo you may need to update your paths.\n - 2018/01/11 (1.60) - obsoleted IsAnyWindowHovered() in favor of IsWindowHovered(ImGuiHoveredFlags_AnyWindow). Kept redirection function (will obsolete).\n - 2018/01/11 (1.60) - obsoleted IsAnyWindowFocused() in favor of IsWindowFocused(ImGuiFocusedFlags_AnyWindow). Kept redirection function (will obsolete).\n - 2018/01/03 (1.60) - renamed ImGuiSizeConstraintCallback to ImGuiSizeCallback, ImGuiSizeConstraintCallbackData to ImGuiSizeCallbackData.\n - 2017/12/29 (1.60) - removed CalcItemRectClosestPoint() which was weird and not really used by anyone except demo code. If you need it it's easy to replicate on your side.\n - 2017/12/24 (1.53) - renamed the emblematic ShowTestWindow() function to ShowDemoWindow(). Kept redirection function (will obsolete).\n - 2017/12/21 (1.53) - ImDrawList: renamed style.AntiAliasedShapes to style.AntiAliasedFill for consistency and as a way to explicitly break code that manipulate those flag at runtime. You can now manipulate ImDrawList::Flags\n - 2017/12/21 (1.53) - ImDrawList: removed 'bool anti_aliased = true' final parameter of ImDrawList::AddPolyline() and ImDrawList::AddConvexPolyFilled(). Prefer manipulating ImDrawList::Flags if you need to toggle them during the frame.\n - 2017/12/14 (1.53) - using the ImGuiWindowFlags_NoScrollWithMouse flag on a child window forwards the mouse wheel event to the parent window, unless either ImGuiWindowFlags_NoInputs or ImGuiWindowFlags_NoScrollbar are also set.\n - 2017/12/13 (1.53) - renamed GetItemsLineHeightWithSpacing() to GetFrameHeightWithSpacing(). Kept redirection function (will obsolete).\n - 2017/12/13 (1.53) - obsoleted IsRootWindowFocused() in favor of using IsWindowFocused(ImGuiFocusedFlags_RootWindow). Kept redirection function (will obsolete).\n                     - obsoleted IsRootWindowOrAnyChildFocused() in favor of using IsWindowFocused(ImGuiFocusedFlags_RootAndChildWindows). Kept redirection function (will obsolete).\n - 2017/12/12 (1.53) - renamed ImGuiTreeNodeFlags_AllowOverlapMode to ImGuiTreeNodeFlags_AllowItemOverlap. Kept redirection enum (will obsolete).\n - 2017/12/10 (1.53) - removed SetNextWindowContentWidth(), prefer using SetNextWindowContentSize(). Kept redirection function (will obsolete).\n - 2017/11/27 (1.53) - renamed ImGuiTextBuffer::append() helper to appendf(), appendv() to appendfv(). If you copied the 'Log' demo in your code, it uses appendv() so that needs to be renamed.\n - 2017/11/18 (1.53) - Style, Begin: removed ImGuiWindowFlags_ShowBorders window flag. Borders are now fully set up in the ImGuiStyle structure (see e.g. style.FrameBorderSize, style.WindowBorderSize). Use ImGui::ShowStyleEditor() to look them up.\n                       Please note that the style system will keep evolving (hopefully stabilizing in Q1 2018), and so custom styles will probably subtly break over time. It is recommended you use the StyleColorsClassic(), StyleColorsDark(), StyleColorsLight() functions.\n - 2017/11/18 (1.53) - Style: removed ImGuiCol_ComboBg in favor of combo boxes using ImGuiCol_PopupBg for consistency.\n - 2017/11/18 (1.53) - Style: renamed ImGuiCol_ChildWindowBg to ImGuiCol_ChildBg.\n - 2017/11/18 (1.53) - Style: renamed style.ChildWindowRounding to style.ChildRounding, ImGuiStyleVar_ChildWindowRounding to ImGuiStyleVar_ChildRounding.\n - 2017/11/02 (1.53) - obsoleted IsRootWindowOrAnyChildHovered() in favor of using IsWindowHovered(ImGuiHoveredFlags_RootAndChildWindows);\n - 2017/10/24 (1.52) - renamed IMGUI_DISABLE_WIN32_DEFAULT_CLIPBOARD_FUNCS/IMGUI_DISABLE_WIN32_DEFAULT_IME_FUNCS to IMGUI_DISABLE_WIN32_DEFAULT_CLIPBOARD_FUNCTIONS/IMGUI_DISABLE_WIN32_DEFAULT_IME_FUNCTIONS for consistency.\n - 2017/10/20 (1.52) - changed IsWindowHovered() default parameters behavior to return false if an item is active in another window (e.g. click-dragging item from another window to this window). You can use the newly introduced IsWindowHovered() flags to requests this specific behavior if you need it.\n - 2017/10/20 (1.52) - marked IsItemHoveredRect()/IsMouseHoveringWindow() as obsolete, in favor of using the newly introduced flags for IsItemHovered() and IsWindowHovered(). See https://github.com/ocornut/imgui/issues/1382 for details.\n                       removed the IsItemRectHovered()/IsWindowRectHovered() names introduced in 1.51 since they were merely more consistent names for the two functions we are now obsoleting.\n                         IsItemHoveredRect()        --> IsItemHovered(ImGuiHoveredFlags_RectOnly)\n                         IsMouseHoveringAnyWindow() --> IsWindowHovered(ImGuiHoveredFlags_AnyWindow)\n                         IsMouseHoveringWindow()    --> IsWindowHovered(ImGuiHoveredFlags_AllowWhenBlockedByPopup | ImGuiHoveredFlags_AllowWhenBlockedByActiveItem) [weird, old behavior]\n - 2017/10/17 (1.52) - marked the old 5-parameters version of Begin() as obsolete (still available). Use SetNextWindowSize()+Begin() instead!\n - 2017/10/11 (1.52) - renamed AlignFirstTextHeightToWidgets() to AlignTextToFramePadding(). Kept inline redirection function (will obsolete).\n - 2017/09/26 (1.52) - renamed ImFont::Glyph to ImFontGlyph. Kept redirection typedef (will obsolete).\n - 2017/09/25 (1.52) - removed SetNextWindowPosCenter() because SetNextWindowPos() now has the optional pivot information to do the same and more. Kept redirection function (will obsolete).\n - 2017/08/25 (1.52) - io.MousePos needs to be set to ImVec2(-FLT_MAX,-FLT_MAX) when mouse is unavailable/missing. Previously ImVec2(-1,-1) was enough but we now accept negative mouse coordinates. In your backend if you need to support unavailable mouse, make sure to replace \"io.MousePos = ImVec2(-1,-1)\" with \"io.MousePos = ImVec2(-FLT_MAX,-FLT_MAX)\".\n - 2017/08/22 (1.51) - renamed IsItemHoveredRect() to IsItemRectHovered(). Kept inline redirection function (will obsolete). -> (1.52) use IsItemHovered(ImGuiHoveredFlags_RectOnly)!\n                     - renamed IsMouseHoveringAnyWindow() to IsAnyWindowHovered() for consistency. Kept inline redirection function (will obsolete).\n                     - renamed IsMouseHoveringWindow() to IsWindowRectHovered() for consistency. Kept inline redirection function (will obsolete).\n - 2017/08/20 (1.51) - renamed GetStyleColName() to GetStyleColorName() for consistency.\n - 2017/08/20 (1.51) - added PushStyleColor(ImGuiCol idx, ImU32 col) overload, which _might_ cause an \"ambiguous call\" compilation error if you are using ImColor() with implicit cast. Cast to ImU32 or ImVec4 explicily to fix.\n - 2017/08/15 (1.51) - marked the weird IMGUI_ONCE_UPON_A_FRAME helper macro as obsolete. prefer using the more explicit ImGuiOnceUponAFrame type.\n - 2017/08/15 (1.51) - changed parameter order for BeginPopupContextWindow() from (const char*,int buttons,bool also_over_items) to (const char*,int buttons,bool also_over_items). Note that most calls relied on default parameters completely.\n - 2017/08/13 (1.51) - renamed ImGuiCol_Column to ImGuiCol_Separator, ImGuiCol_ColumnHovered to ImGuiCol_SeparatorHovered, ImGuiCol_ColumnActive to ImGuiCol_SeparatorActive. Kept redirection enums (will obsolete).\n - 2017/08/11 (1.51) - renamed ImGuiSetCond_Always to ImGuiCond_Always, ImGuiSetCond_Once to ImGuiCond_Once, ImGuiSetCond_FirstUseEver to ImGuiCond_FirstUseEver, ImGuiSetCond_Appearing to ImGuiCond_Appearing. Kept redirection enums (will obsolete).\n - 2017/08/09 (1.51) - removed ValueColor() helpers, they are equivalent to calling Text(label) + SameLine() + ColorButton().\n - 2017/08/08 (1.51) - removed ColorEditMode() and ImGuiColorEditMode in favor of ImGuiColorEditFlags and parameters to the various Color*() functions. The SetColorEditOptions() allows to initialize default but the user can still change them with right-click context menu.\n                     - changed prototype of 'ColorEdit4(const char* label, float col[4], bool show_alpha = true)' to 'ColorEdit4(const char* label, float col[4], ImGuiColorEditFlags flags = 0)', where passing flags = 0x01 is a safe no-op (hello dodgy backward compatibility!). - check and run the demo window, under \"Color/Picker Widgets\", to understand the various new options.\n                     - changed prototype of rarely used 'ColorButton(ImVec4 col, bool small_height = false, bool outline_border = true)' to 'ColorButton(const char* desc_id, ImVec4 col, ImGuiColorEditFlags flags = 0, ImVec2 size = ImVec2(0, 0))'\n - 2017/07/20 (1.51) - removed IsPosHoveringAnyWindow(ImVec2), which was partly broken and misleading. ASSERT + redirect user to io.WantCaptureMouse\n - 2017/05/26 (1.50) - removed ImFontConfig::MergeGlyphCenterV in favor of a more multipurpose ImFontConfig::GlyphOffset.\n - 2017/05/01 (1.50) - renamed ImDrawList::PathFill() (rarely used directly) to ImDrawList::PathFillConvex() for clarity.\n - 2016/11/06 (1.50) - BeginChild(const char*) now applies the stack id to the provided label, consistently with other functions as it should always have been. It shouldn't affect you unless (extremely unlikely) you were appending multiple times to a same child from different locations of the stack id. If that's the case, generate an id with GetID() and use it instead of passing string to BeginChild().\n - 2016/10/15 (1.50) - avoid 'void* user_data' parameter to io.SetClipboardTextFn/io.GetClipboardTextFn pointers. We pass io.ClipboardUserData to it.\n - 2016/09/25 (1.50) - style.WindowTitleAlign is now a ImVec2 (ImGuiAlign enum was removed). set to (0.5f,0.5f) for horizontal+vertical centering, (0.0f,0.0f) for upper-left, etc.\n - 2016/07/30 (1.50) - SameLine(x) with x>0.0f is now relative to left of column/group if any, and not always to left of window. This was sort of always the intent and hopefully, breakage should be minimal.\n - 2016/05/12 (1.49) - title bar (using ImGuiCol_TitleBg/ImGuiCol_TitleBgActive colors) isn't rendered over a window background (ImGuiCol_WindowBg color) anymore.\n                       If your TitleBg/TitleBgActive alpha was 1.0f or you are using the default theme it will not affect you, otherwise if <1.0f you need to tweak your custom theme to readjust for the fact that we don't draw a WindowBg background behind the title bar.\n                       This helper function will convert an old TitleBg/TitleBgActive color into a new one with the same visual output, given the OLD color and the OLD WindowBg color:\n                       ImVec4 ConvertTitleBgCol(const ImVec4& win_bg_col, const ImVec4& title_bg_col) { float new_a = 1.0f - ((1.0f - win_bg_col.w) * (1.0f - title_bg_col.w)), k = title_bg_col.w / new_a; return ImVec4((win_bg_col.x * win_bg_col.w + title_bg_col.x) * k, (win_bg_col.y * win_bg_col.w + title_bg_col.y) * k, (win_bg_col.z * win_bg_col.w + title_bg_col.z) * k, new_a); }\n                       If this is confusing, pick the RGB value from title bar from an old screenshot and apply this as TitleBg/TitleBgActive. Or you may just create TitleBgActive from a tweaked TitleBg color.\n - 2016/05/07 (1.49) - removed confusing set of GetInternalState(), GetInternalStateSize(), SetInternalState() functions. Now using CreateContext(), DestroyContext(), GetCurrentContext(), SetCurrentContext().\n - 2016/05/02 (1.49) - renamed SetNextTreeNodeOpened() to SetNextTreeNodeOpen(), no redirection.\n - 2016/05/01 (1.49) - obsoleted old signature of CollapsingHeader(const char* label, const char* str_id = NULL, bool display_frame = true, bool default_open = false) as extra parameters were badly designed and rarely used. You can replace the \"default_open = true\" flag in new API with CollapsingHeader(label, ImGuiTreeNodeFlags_DefaultOpen).\n - 2016/04/26 (1.49) - changed ImDrawList::PushClipRect(ImVec4 rect) to ImDrawList::PushClipRect(Imvec2 min,ImVec2 max,bool intersect_with_current_clip_rect=false). Note that higher-level ImGui::PushClipRect() is preferable because it will clip at logic/widget level, whereas ImDrawList::PushClipRect() only affect your renderer.\n - 2016/04/03 (1.48) - removed style.WindowFillAlphaDefault setting which was redundant. Bake default BG alpha inside style.Colors[ImGuiCol_WindowBg] and all other Bg color values. (ref GitHub issue #337).\n - 2016/04/03 (1.48) - renamed ImGuiCol_TooltipBg to ImGuiCol_PopupBg, used by popups/menus and tooltips. popups/menus were previously using ImGuiCol_WindowBg. (ref github issue #337)\n - 2016/03/21 (1.48) - renamed GetWindowFont() to GetFont(), GetWindowFontSize() to GetFontSize(). Kept inline redirection function (will obsolete).\n - 2016/03/02 (1.48) - InputText() completion/history/always callbacks: if you modify the text buffer manually (without using DeleteChars()/InsertChars() helper) you need to maintain the BufTextLen field. added an assert.\n - 2016/01/23 (1.48) - fixed not honoring exact width passed to PushItemWidth(), previously it would add extra FramePadding.x*2 over that width. if you had manual pixel-perfect alignment in place it might affect you.\n - 2015/12/27 (1.48) - fixed ImDrawList::AddRect() which used to render a rectangle 1 px too large on each axis.\n - 2015/12/04 (1.47) - renamed Color() helpers to ValueColor() - dangerously named, rarely used and probably to be made obsolete.\n - 2015/08/29 (1.45) - with the addition of horizontal scrollbar we made various fixes to inconsistencies with dealing with cursor position.\n                       GetCursorPos()/SetCursorPos() functions now include the scrolled amount. It shouldn't affect the majority of users, but take note that SetCursorPosX(100.0f) puts you at +100 from the starting x position which may include scrolling, not at +100 from the window left side.\n                       GetContentRegionMax()/GetWindowContentRegionMin()/GetWindowContentRegionMax() functions allow include the scrolled amount. Typically those were used in cases where no scrolling would happen so it may not be a problem, but watch out!\n - 2015/08/29 (1.45) - renamed style.ScrollbarWidth to style.ScrollbarSize\n - 2015/08/05 (1.44) - split imgui.cpp into extra files: imgui_demo.cpp imgui_draw.cpp imgui_internal.h that you need to add to your project.\n - 2015/07/18 (1.44) - fixed angles in ImDrawList::PathArcTo(), PathArcToFast() (introduced in 1.43) being off by an extra PI for no justifiable reason\n - 2015/07/14 (1.43) - add new ImFontAtlas::AddFont() API. For the old AddFont***, moved the 'font_no' parameter of ImFontAtlas::AddFont** functions to the ImFontConfig structure.\n                       you need to render your textured triangles with bilinear filtering to benefit from sub-pixel positioning of text.\n - 2015/07/08 (1.43) - switched rendering data to use indexed rendering. this is saving a fair amount of CPU/GPU and enables us to get anti-aliasing for a marginal cost.\n                       this necessary change will break your rendering function! the fix should be very easy. sorry for that :(\n                     - if you are using a vanilla copy of one of the imgui_impl_XXX.cpp provided in the example, you just need to update your copy and you can ignore the rest.\n                     - the signature of the io.RenderDrawListsFn handler has changed!\n                       old: ImGui_XXXX_RenderDrawLists(ImDrawList** const cmd_lists, int cmd_lists_count)\n                       new: ImGui_XXXX_RenderDrawLists(ImDrawData* draw_data).\n                         parameters: 'cmd_lists' becomes 'draw_data->CmdLists', 'cmd_lists_count' becomes 'draw_data->CmdListsCount'\n                         ImDrawList: 'commands' becomes 'CmdBuffer', 'vtx_buffer' becomes 'VtxBuffer', 'IdxBuffer' is new.\n                         ImDrawCmd:  'vtx_count' becomes 'ElemCount', 'clip_rect' becomes 'ClipRect', 'user_callback' becomes 'UserCallback', 'texture_id' becomes 'TextureId'.\n                     - each ImDrawList now contains both a vertex buffer and an index buffer. For each command, render ElemCount/3 triangles using indices from the index buffer.\n                     - if you REALLY cannot render indexed primitives, you can call the draw_data->DeIndexAllBuffers() method to de-index the buffers. This is slow and a waste of CPU/GPU. Prefer using indexed rendering!\n                     - refer to code in the examples/ folder or ask on the GitHub if you are unsure of how to upgrade. please upgrade!\n - 2015/07/10 (1.43) - changed SameLine() parameters from int to float.\n - 2015/07/02 (1.42) - renamed SetScrollPosHere() to SetScrollFromCursorPos(). Kept inline redirection function (will obsolete).\n - 2015/07/02 (1.42) - renamed GetScrollPosY() to GetScrollY(). Necessary to reduce confusion along with other scrolling functions, because positions (e.g. cursor position) are not equivalent to scrolling amount.\n - 2015/06/14 (1.41) - changed ImageButton() default bg_col parameter from (0,0,0,1) (black) to (0,0,0,0) (transparent) - makes a difference when texture have transparence\n - 2015/06/14 (1.41) - changed Selectable() API from (label, selected, size) to (label, selected, flags, size). Size override should have been rarely used. Sorry!\n - 2015/05/31 (1.40) - renamed GetWindowCollapsed() to IsWindowCollapsed() for consistency. Kept inline redirection function (will obsolete).\n - 2015/05/31 (1.40) - renamed IsRectClipped() to IsRectVisible() for consistency. Note that return value is opposite! Kept inline redirection function (will obsolete).\n - 2015/05/27 (1.40) - removed the third 'repeat_if_held' parameter from Button() - sorry! it was rarely used and inconsistent. Use PushButtonRepeat(true) / PopButtonRepeat() to enable repeat on desired buttons.\n - 2015/05/11 (1.40) - changed BeginPopup() API, takes a string identifier instead of a bool. ImGui needs to manage the open/closed state of popups. Call OpenPopup() to actually set the \"open\" state of a popup. BeginPopup() returns true if the popup is opened.\n - 2015/05/03 (1.40) - removed style.AutoFitPadding, using style.WindowPadding makes more sense (the default values were already the same).\n - 2015/04/13 (1.38) - renamed IsClipped() to IsRectClipped(). Kept inline redirection function until 1.50.\n - 2015/04/09 (1.38) - renamed ImDrawList::AddArc() to ImDrawList::AddArcFast() for compatibility with future API\n - 2015/04/03 (1.38) - removed ImGuiCol_CheckHovered, ImGuiCol_CheckActive, replaced with the more general ImGuiCol_FrameBgHovered, ImGuiCol_FrameBgActive.\n - 2014/04/03 (1.38) - removed support for passing -FLT_MAX..+FLT_MAX as the range for a SliderFloat(). Use DragFloat() or Inputfloat() instead.\n - 2015/03/17 (1.36) - renamed GetItemBoxMin()/GetItemBoxMax()/IsMouseHoveringBox() to GetItemRectMin()/GetItemRectMax()/IsMouseHoveringRect(). Kept inline redirection function until 1.50.\n - 2015/03/15 (1.36) - renamed style.TreeNodeSpacing to style.IndentSpacing, ImGuiStyleVar_TreeNodeSpacing to ImGuiStyleVar_IndentSpacing\n - 2015/03/13 (1.36) - renamed GetWindowIsFocused() to IsWindowFocused(). Kept inline redirection function until 1.50.\n - 2015/03/08 (1.35) - renamed style.ScrollBarWidth to style.ScrollbarWidth (casing)\n - 2015/02/27 (1.34) - renamed OpenNextNode(bool) to SetNextTreeNodeOpened(bool, ImGuiSetCond). Kept inline redirection function until 1.50.\n - 2015/02/27 (1.34) - renamed ImGuiSetCondition_*** to ImGuiSetCond_***, and _FirstUseThisSession becomes _Once.\n - 2015/02/11 (1.32) - changed text input callback ImGuiTextEditCallback return type from void-->int. reserved for future use, return 0 for now.\n - 2015/02/10 (1.32) - renamed GetItemWidth() to CalcItemWidth() to clarify its evolving behavior\n - 2015/02/08 (1.31) - renamed GetTextLineSpacing() to GetTextLineHeightWithSpacing()\n - 2015/02/01 (1.31) - removed IO.MemReallocFn (unused)\n - 2015/01/19 (1.30) - renamed ImGuiStorage::GetIntPtr()/GetFloatPtr() to GetIntRef()/GetIntRef() because Ptr was conflicting with actual pointer storage functions.\n - 2015/01/11 (1.30) - big font/image API change! now loads TTF file. allow for multiple fonts. no need for a PNG loader.\n - 2015/01/11 (1.30) - removed GetDefaultFontData(). uses io.Fonts->GetTextureData*() API to retrieve uncompressed pixels.\n                       - old:  const void* png_data; unsigned int png_size; ImGui::GetDefaultFontData(NULL, NULL, &png_data, &png_size); [..Upload texture to GPU..];\n                       - new:  unsigned char* pixels; int width, height; io.Fonts->GetTexDataAsRGBA32(&pixels, &width, &height); [..Upload texture to GPU..]; io.Fonts->SetTexID(YourTexIdentifier);\n                       you now have more flexibility to load multiple TTF fonts and manage the texture buffer for internal needs. It is now recommended that you sample the font texture with bilinear interpolation.\n - 2015/01/11 (1.30) - added texture identifier in ImDrawCmd passed to your render function (we can now render images). make sure to call io.Fonts->SetTexID()\n - 2015/01/11 (1.30) - removed IO.PixelCenterOffset (unnecessary, can be handled in user projection matrix)\n - 2015/01/11 (1.30) - removed ImGui::IsItemFocused() in favor of ImGui::IsItemActive() which handles all widgets\n - 2014/12/10 (1.18) - removed SetNewWindowDefaultPos() in favor of new generic API SetNextWindowPos(pos, ImGuiSetCondition_FirstUseEver)\n - 2014/11/28 (1.17) - moved IO.Font*** options to inside the IO.Font-> structure (FontYOffset, FontTexUvForWhite, FontBaseScale, FontFallbackGlyph)\n - 2014/11/26 (1.17) - reworked syntax of IMGUI_ONCE_UPON_A_FRAME helper macro to increase compiler compatibility\n - 2014/11/07 (1.15) - renamed IsHovered() to IsItemHovered()\n - 2014/10/02 (1.14) - renamed IMGUI_INCLUDE_IMGUI_USER_CPP to IMGUI_INCLUDE_IMGUI_USER_INL and imgui_user.cpp to imgui_user.inl (more IDE friendly)\n - 2014/09/25 (1.13) - removed 'text_end' parameter from IO.SetClipboardTextFn (the string is now always zero-terminated for simplicity)\n - 2014/09/24 (1.12) - renamed SetFontScale() to SetWindowFontScale()\n - 2014/09/24 (1.12) - moved IM_MALLOC/IM_REALLOC/IM_FREE preprocessor defines to IO.MemAllocFn/IO.MemReallocFn/IO.MemFreeFn\n - 2014/08/30 (1.09) - removed IO.FontHeight (now computed automatically)\n - 2014/08/30 (1.09) - moved IMGUI_FONT_TEX_UV_FOR_WHITE preprocessor define to IO.FontTexUvForWhite\n - 2014/08/28 (1.09) - changed the behavior of IO.PixelCenterOffset following various rendering fixes\n\n\n FREQUENTLY ASKED QUESTIONS (FAQ)\n ================================\n\n Read all answers online:\n   https://www.dearimgui.com/faq or https://github.com/ocornut/imgui/blob/master/docs/FAQ.md (same url)\n Read all answers locally (with a text editor or ideally a Markdown viewer):\n   docs/FAQ.md\n Some answers are copied down here to facilitate searching in code.\n\n Q&A: Basics\n ===========\n\n Q: Where is the documentation?\n A: This library is poorly documented at the moment and expects the user to be acquainted with C/C++.\n    - Run the examples/ applications and explore them.\n    - Read Getting Started (https://github.com/ocornut/imgui/wiki/Getting-Started) guide.\n    - See demo code in imgui_demo.cpp and particularly the ImGui::ShowDemoWindow() function.\n    - The demo covers most features of Dear ImGui, so you can read the code and see its output.\n    - See documentation and comments at the top of imgui.cpp + effectively imgui.h.\n    - 20+ standalone example applications using e.g. OpenGL/DirectX are provided in the\n      examples/ folder to explain how to integrate Dear ImGui with your own engine/application.\n    - The Wiki (https://github.com/ocornut/imgui/wiki) has many resources and links.\n    - The Glossary (https://github.com/ocornut/imgui/wiki/Glossary) page also may be useful.\n    - Your programming IDE is your friend, find the type or function declaration to find comments\n      associated with it.\n\n Q: What is this library called?\n Q: Which version should I get?\n >> This library is called \"Dear ImGui\", please don't call it \"ImGui\" :)\n >> See https://www.dearimgui.com/faq for details.\n\n Q&A: Integration\n ================\n\n Q: How to get started?\n A: Read https://github.com/ocornut/imgui/wiki/Getting-Started. Read 'PROGRAMMER GUIDE' above. Read examples/README.txt.\n\n Q: How can I tell whether to dispatch mouse/keyboard to Dear ImGui or my application?\n A: You should read the 'io.WantCaptureMouse', 'io.WantCaptureKeyboard' and 'io.WantTextInput' flags!\n >> See https://www.dearimgui.com/faq for a fully detailed answer. You really want to read this.\n\n Q. How can I enable keyboard or gamepad controls?\n Q: How can I use this on a machine without mouse, keyboard or screen? (input share, remote display)\n Q: I integrated Dear ImGui in my engine and little squares are showing instead of text...\n Q: I integrated Dear ImGui in my engine and some elements are clipping or disappearing when I move windows around...\n Q: I integrated Dear ImGui in my engine and some elements are displaying outside their expected windows boundaries...\n >> See https://www.dearimgui.com/faq\n\n Q&A: Usage\n ----------\n\n Q: About the ID Stack system..\n   - Why is my widget not reacting when I click on it?\n   - How can I have widgets with an empty label?\n   - How can I have multiple widgets with the same label?\n   - How can I have multiple windows with the same label?\n Q: How can I display an image? What is ImTextureID, how does it work?\n Q: How can I use my own math types instead of ImVec2?\n Q: How can I interact with standard C++ types (such as std::string and std::vector)?\n Q: How can I display custom shapes? (using low-level ImDrawList API)\n >> See https://www.dearimgui.com/faq\n\n Q&A: Fonts, Text\n ================\n\n Q: How should I handle DPI in my application?\n Q: How can I load a different font than the default?\n Q: How can I easily use icons in my application?\n Q: How can I load multiple fonts?\n Q: How can I display and input non-Latin characters such as Chinese, Japanese, Korean, Cyrillic?\n >> See https://www.dearimgui.com/faq and https://github.com/ocornut/imgui/blob/master/docs/FONTS.md\n\n Q&A: Concerns\n =============\n\n Q: Who uses Dear ImGui?\n Q: Can you create elaborate/serious tools with Dear ImGui?\n Q: Can you reskin the look of Dear ImGui?\n Q: Why using C++ (as opposed to C)?\n >> See https://www.dearimgui.com/faq\n\n Q&A: Community\n ==============\n\n Q: How can I help?\n A: - Businesses: please reach out to \"omar AT dearimgui DOT com\" if you work in a place using Dear ImGui!\n      We can discuss ways for your company to fund development via invoiced technical support, maintenance or sponsoring contacts.\n      This is among the most useful thing you can do for Dear ImGui. With increased funding, we sustain and grow work on this project.\n      >>> See https://github.com/ocornut/imgui/wiki/Funding\n    - Businesses: you can also purchase licenses for the Dear ImGui Automation/Test Engine.\n    - If you are experienced with Dear ImGui and C++, look at the GitHub issues, look at the Wiki, and see how you want to help and can help!\n    - Disclose your usage of Dear ImGui via a dev blog post, a tweet, a screenshot, a mention somewhere etc.\n      You may post screenshot or links in the gallery threads. Visuals are ideal as they inspire other programmers.\n      But even without visuals, disclosing your use of dear imgui helps the library grow credibility, and help other teams and programmers with taking decisions.\n    - If you have issues or if you need to hack into the library, even if you don't expect any support it is useful that you share your issues (on GitHub or privately).\n\n*/\n\n//-------------------------------------------------------------------------\n// [SECTION] INCLUDES\n//-------------------------------------------------------------------------\n\n#if defined(_MSC_VER) && !defined(_CRT_SECURE_NO_WARNINGS)\n#define _CRT_SECURE_NO_WARNINGS\n#endif\n\n#ifndef IMGUI_DEFINE_MATH_OPERATORS\n#define IMGUI_DEFINE_MATH_OPERATORS\n#endif\n\n#include \"imgui.h\"\n#ifndef IMGUI_DISABLE\n#include \"imgui_internal.h\"\n\n// System includes\n#include <stdio.h>      // vsnprintf, sscanf, printf\n#include <stdint.h>     // intptr_t\n\n// [Windows] On non-Visual Studio compilers, we default to IMGUI_DISABLE_WIN32_DEFAULT_IME_FUNCTIONS unless explicitly enabled\n#if defined(_WIN32) && !defined(_MSC_VER) && !defined(IMGUI_ENABLE_WIN32_DEFAULT_IME_FUNCTIONS) && !defined(IMGUI_DISABLE_WIN32_DEFAULT_IME_FUNCTIONS)\n#define IMGUI_DISABLE_WIN32_DEFAULT_IME_FUNCTIONS\n#endif\n\n// [Windows] OS specific includes (optional)\n#if defined(_WIN32) && defined(IMGUI_DISABLE_DEFAULT_FILE_FUNCTIONS) && defined(IMGUI_DISABLE_WIN32_DEFAULT_CLIPBOARD_FUNCTIONS) && defined(IMGUI_DISABLE_WIN32_DEFAULT_IME_FUNCTIONS) && defined(IMGUI_DISABLE_DEFAULT_SHELL_FUNCTIONS) && !defined(IMGUI_DISABLE_WIN32_FUNCTIONS)\n#define IMGUI_DISABLE_WIN32_FUNCTIONS\n#endif\n#if defined(_WIN32) && !defined(IMGUI_DISABLE_WIN32_FUNCTIONS)\n#ifndef WIN32_LEAN_AND_MEAN\n#define WIN32_LEAN_AND_MEAN\n#endif\n#ifndef NOMINMAX\n#define NOMINMAX\n#endif\n#ifndef __MINGW32__\n#include <Windows.h>        // _wfopen, OpenClipboard\n#else\n#include <windows.h>\n#endif\n#if defined(WINAPI_FAMILY) && ((defined(WINAPI_FAMILY_APP) && WINAPI_FAMILY == WINAPI_FAMILY_APP) || (defined(WINAPI_FAMILY_GAMES) && WINAPI_FAMILY == WINAPI_FAMILY_GAMES))\n// The UWP and GDK Win32 API subsets don't support clipboard nor IME functions\n#define IMGUI_DISABLE_WIN32_DEFAULT_CLIPBOARD_FUNCTIONS\n#define IMGUI_DISABLE_WIN32_DEFAULT_IME_FUNCTIONS\n#define IMGUI_DISABLE_DEFAULT_SHELL_FUNCTIONS\n#endif\n#endif\n\n// [Apple] OS specific includes\n#if defined(__APPLE__)\n#include <TargetConditionals.h>\n#endif\n\n// Visual Studio warnings\n#ifdef _MSC_VER\n#pragma warning (disable: 4127)             // condition expression is constant\n#pragma warning (disable: 4996)             // 'This function or variable may be unsafe': strcpy, strdup, sprintf, vsnprintf, sscanf, fopen\n#if defined(_MSC_VER) && _MSC_VER >= 1922   // MSVC 2019 16.2 or later\n#pragma warning (disable: 5054)             // operator '|': deprecated between enumerations of different types\n#endif\n#pragma warning (disable: 26451)            // [Static Analyzer] Arithmetic overflow : Using operator 'xxx' on a 4 byte value and then casting the result to an 8 byte value. Cast the value to the wider type before calling operator 'xxx' to avoid overflow(io.2).\n#pragma warning (disable: 26495)            // [Static Analyzer] Variable 'XXX' is uninitialized. Always initialize a member variable (type.6).\n#pragma warning (disable: 26812)            // [Static Analyzer] The enum type 'xxx' is unscoped. Prefer 'enum class' over 'enum' (Enum.3).\n#endif\n\n// Clang/GCC warnings with -Weverything\n#if defined(__clang__)\n#if __has_warning(\"-Wunknown-warning-option\")\n#pragma clang diagnostic ignored \"-Wunknown-warning-option\"         // warning: unknown warning group 'xxx'                      // not all warnings are known by all Clang versions and they tend to be rename-happy.. so ignoring warnings triggers new warnings on some configuration. Great!\n#endif\n#pragma clang diagnostic ignored \"-Wunknown-pragmas\"                // warning: unknown warning group 'xxx'\n#pragma clang diagnostic ignored \"-Wold-style-cast\"                 // warning: use of old-style cast                            // yes, they are more terse.\n#pragma clang diagnostic ignored \"-Wfloat-equal\"                    // warning: comparing floating point with == or != is unsafe // storing and comparing against same constants (typically 0.0f) is ok.\n#pragma clang diagnostic ignored \"-Wformat\"                         // warning: format specifies type 'int' but the argument has type 'unsigned int'\n#pragma clang diagnostic ignored \"-Wformat-nonliteral\"              // warning: format string is not a string literal            // passing non-literal to vsnformat(). yes, user passing incorrect format strings can crash the code.\n#pragma clang diagnostic ignored \"-Wformat-pedantic\"                // warning: format specifies type 'void *' but the argument has type 'xxxx *' // unreasonable, would lead to casting every %p arg to void*. probably enabled by -pedantic.\n#pragma clang diagnostic ignored \"-Wexit-time-destructors\"          // warning: declaration requires an exit-time destructor     // exit-time destruction order is undefined. if MemFree() leads to users code that has been disabled before exit it might cause problems. ImGui coding style welcomes static/globals.\n#pragma clang diagnostic ignored \"-Wglobal-constructors\"            // warning: declaration requires a global destructor         // similar to above, not sure what the exact difference is.\n#pragma clang diagnostic ignored \"-Wsign-conversion\"                // warning: implicit conversion changes signedness\n#pragma clang diagnostic ignored \"-Wint-to-void-pointer-cast\"       // warning: cast to 'void *' from smaller integer type 'int'\n#pragma clang diagnostic ignored \"-Wzero-as-null-pointer-constant\"  // warning: zero as null pointer constant                    // some standard header variations use #define NULL 0\n#pragma clang diagnostic ignored \"-Wdouble-promotion\"               // warning: implicit conversion from 'float' to 'double' when passing argument to function  // using printf() is a misery with this as C++ va_arg ellipsis changes float to double.\n#pragma clang diagnostic ignored \"-Wimplicit-int-float-conversion\"  // warning: implicit conversion from 'xxx' to 'float' may lose precision\n#pragma clang diagnostic ignored \"-Wunsafe-buffer-usage\"            // warning: 'xxx' is an unsafe pointer used for buffer access\n#pragma clang diagnostic ignored \"-Wnontrivial-memaccess\"           // warning: first argument in call to 'memset' is a pointer to non-trivially copyable type\n#pragma clang diagnostic ignored \"-Wswitch-default\"                 // warning: 'switch' missing 'default' label\n#elif defined(__GNUC__)\n// We disable -Wpragmas because GCC doesn't provide a has_warning equivalent and some forks/patches may not follow the warning/version association.\n#pragma GCC diagnostic ignored \"-Wpragmas\"                          // warning: unknown option after '#pragma GCC diagnostic' kind\n#pragma GCC diagnostic ignored \"-Wunused-function\"                  // warning: 'xxxx' defined but not used\n#pragma GCC diagnostic ignored \"-Wint-to-pointer-cast\"              // warning: cast to pointer from integer of different size\n#pragma GCC diagnostic ignored \"-Wfloat-equal\"                      // warning: comparing floating-point with '==' or '!=' is unsafe\n#pragma GCC diagnostic ignored \"-Wformat\"                           // warning: format '%p' expects argument of type 'int'/'void*', but argument X has type 'unsigned int'/'ImGuiWindow*'\n#pragma GCC diagnostic ignored \"-Wdouble-promotion\"                 // warning: implicit conversion from 'float' to 'double' when passing argument to function\n#pragma GCC diagnostic ignored \"-Wconversion\"                       // warning: conversion to 'xxxx' from 'xxxx' may alter its value\n#pragma GCC diagnostic ignored \"-Wformat-nonliteral\"                // warning: format not a string literal, format string not checked\n#pragma GCC diagnostic ignored \"-Wstrict-overflow\"                  // warning: assuming signed overflow does not occur when assuming that (X - c) > X is always false\n#pragma GCC diagnostic ignored \"-Wclass-memaccess\"                  // [__GNUC__ >= 8] warning: 'memset/memcpy' clearing/writing an object of type 'xxxx' with no trivial copy-assignment; use assignment or value-initialization instead\n#pragma GCC diagnostic ignored \"-Wcast-qual\"                        // warning: cast from type 'const xxxx *' to type 'xxxx *' casts away qualifiers\n#endif\n\n// Debug options\n#define IMGUI_DEBUG_NAV_SCORING     0   // Display navigation scoring preview when hovering items. Hold CTRL to display for all candidates. CTRL+Arrow to change last direction.\n#define IMGUI_DEBUG_NAV_RECTS       0   // Display the reference navigation rectangle for each window\n\n// When using CTRL+TAB (or Gamepad Square+L/R) we delay the visual a little in order to reduce visual noise doing a fast switch.\nstatic const float NAV_WINDOWING_HIGHLIGHT_DELAY            = 0.20f;    // Time before the highlight and screen dimming starts fading in\nstatic const float NAV_WINDOWING_LIST_APPEAR_DELAY          = 0.15f;    // Time before the window list starts to appear\nstatic const float NAV_ACTIVATE_HIGHLIGHT_TIMER             = 0.10f;    // Time to highlight an item activated by a shortcut.\nstatic const float WINDOWS_RESIZE_FROM_EDGES_FEEDBACK_TIMER = 0.04f;    // Reduce visual noise by only highlighting the border after a certain time.\nstatic const float WINDOWS_MOUSE_WHEEL_SCROLL_LOCK_TIMER    = 0.70f;    // Lock scrolled window (so it doesn't pick child windows that are scrolling through) for a certain time, unless mouse moved.\n\n// Tooltip offset\nstatic const ImVec2 TOOLTIP_DEFAULT_OFFSET_MOUSE = ImVec2(16, 10);      // Multiplied by g.Style.MouseCursorScale\nstatic const ImVec2 TOOLTIP_DEFAULT_OFFSET_TOUCH = ImVec2(0, -20);      // Multiplied by g.Style.MouseCursorScale\nstatic const ImVec2 TOOLTIP_DEFAULT_PIVOT_TOUCH = ImVec2(0.5f, 1.0f);   // Multiplied by g.Style.MouseCursorScale\n\n//-------------------------------------------------------------------------\n// [SECTION] FORWARD DECLARATIONS\n//-------------------------------------------------------------------------\n\nstatic void             SetCurrentWindow(ImGuiWindow* window);\nstatic ImGuiWindow*     CreateNewWindow(const char* name, ImGuiWindowFlags flags);\nstatic ImVec2           CalcNextScrollFromScrollTargetAndClamp(ImGuiWindow* window);\n\nstatic void             AddWindowToSortBuffer(ImVector<ImGuiWindow*>* out_sorted_windows, ImGuiWindow* window);\n\n// Settings\nstatic void             WindowSettingsHandler_ClearAll(ImGuiContext*, ImGuiSettingsHandler*);\nstatic void*            WindowSettingsHandler_ReadOpen(ImGuiContext*, ImGuiSettingsHandler*, const char* name);\nstatic void             WindowSettingsHandler_ReadLine(ImGuiContext*, ImGuiSettingsHandler*, void* entry, const char* line);\nstatic void             WindowSettingsHandler_ApplyAll(ImGuiContext*, ImGuiSettingsHandler*);\nstatic void             WindowSettingsHandler_WriteAll(ImGuiContext*, ImGuiSettingsHandler*, ImGuiTextBuffer* buf);\n\n// Platform Dependents default implementation for ImGuiPlatformIO functions\nstatic const char*      Platform_GetClipboardTextFn_DefaultImpl(ImGuiContext* ctx);\nstatic void             Platform_SetClipboardTextFn_DefaultImpl(ImGuiContext* ctx, const char* text);\nstatic void             Platform_SetImeDataFn_DefaultImpl(ImGuiContext* ctx, ImGuiViewport* viewport, ImGuiPlatformImeData* data);\nstatic bool             Platform_OpenInShellFn_DefaultImpl(ImGuiContext* ctx, const char* path);\n\nnamespace ImGui\n{\n// Item\nstatic void             ItemHandleShortcut(ImGuiID id);\n\n// Window Focus\nstatic int              FindWindowFocusIndex(ImGuiWindow* window);\nstatic void             UpdateWindowInFocusOrderList(ImGuiWindow* window, bool just_created, ImGuiWindowFlags new_flags);\n\n// Navigation\nstatic void             NavUpdate();\nstatic void             NavUpdateWindowing();\nstatic void             NavUpdateWindowingApplyFocus(ImGuiWindow* window);\nstatic void             NavUpdateWindowingOverlay();\nstatic void             NavUpdateCancelRequest();\nstatic void             NavUpdateCreateMoveRequest();\nstatic void             NavUpdateCreateTabbingRequest();\nstatic float            NavUpdatePageUpPageDown();\nstatic inline void      NavUpdateAnyRequestFlag();\nstatic void             NavUpdateCreateWrappingRequest();\nstatic void             NavEndFrame();\nstatic bool             NavScoreItem(ImGuiNavItemData* result);\nstatic void             NavApplyItemToResult(ImGuiNavItemData* result);\nstatic void             NavProcessItem();\nstatic void             NavProcessItemForTabbingRequest(ImGuiID id, ImGuiItemFlags item_flags, ImGuiNavMoveFlags move_flags);\nstatic ImGuiInputSource NavCalcPreferredRefPosSource();\nstatic ImVec2           NavCalcPreferredRefPos();\nstatic void             NavSaveLastChildNavWindowIntoParent(ImGuiWindow* nav_window);\nstatic ImGuiWindow*     NavRestoreLastChildNavWindow(ImGuiWindow* window);\nstatic void             NavRestoreLayer(ImGuiNavLayer layer);\n\n// Error Checking and Debug Tools\nstatic void             ErrorCheckNewFrameSanityChecks();\nstatic void             ErrorCheckEndFrameSanityChecks();\n#ifndef IMGUI_DISABLE_DEBUG_TOOLS\nstatic void             UpdateDebugToolItemPicker();\nstatic void             UpdateDebugToolStackQueries();\nstatic void             UpdateDebugToolFlashStyleColor();\n#endif\n\n// Inputs\nstatic void             UpdateKeyboardInputs();\nstatic void             UpdateMouseInputs();\nstatic void             UpdateMouseWheel();\nstatic void             UpdateKeyRoutingTable(ImGuiKeyRoutingTable* rt);\n\n// Misc\nstatic void             UpdateSettings();\nstatic int              UpdateWindowManualResize(ImGuiWindow* window, const ImVec2& size_auto_fit, int* border_hovered, int* border_held, int resize_grip_count, ImU32 resize_grip_col[4], const ImRect& visibility_rect);\nstatic void             RenderWindowOuterBorders(ImGuiWindow* window);\nstatic void             RenderWindowDecorations(ImGuiWindow* window, const ImRect& title_bar_rect, bool title_bar_is_highlight, bool handle_borders_and_resize_grips, int resize_grip_count, const ImU32 resize_grip_col[4], float resize_grip_draw_size);\nstatic void             RenderWindowTitleBarContents(ImGuiWindow* window, const ImRect& title_bar_rect, const char* name, bool* p_open);\nstatic void             RenderDimmedBackgroundBehindWindow(ImGuiWindow* window, ImU32 col);\nstatic void             RenderDimmedBackgrounds();\nstatic void             SetLastItemDataForWindow(ImGuiWindow* window, const ImRect& rect);\nstatic void             SetLastItemDataForChildWindowItem(ImGuiWindow* window, const ImRect& rect);\n\n// Viewports\nconst ImGuiID           IMGUI_VIEWPORT_DEFAULT_ID = 0x11111111; // Using an arbitrary constant instead of e.g. ImHashStr(\"ViewportDefault\", 0); so it's easier to spot in the debugger. The exact value doesn't matter.\nstatic void             UpdateViewportsNewFrame();\n\n}\n\n//-----------------------------------------------------------------------------\n// [SECTION] CONTEXT AND MEMORY ALLOCATORS\n//-----------------------------------------------------------------------------\n\n// DLL users:\n// - Heaps and globals are not shared across DLL boundaries!\n// - You will need to call SetCurrentContext() + SetAllocatorFunctions() for each static/DLL boundary you are calling from.\n// - Same applies for hot-reloading mechanisms that are reliant on reloading DLL (note that many hot-reloading mechanisms work without DLL).\n// - Using Dear ImGui via a shared library is not recommended, because of function call overhead and because we don't guarantee backward nor forward ABI compatibility.\n// - Confused? In a debugger: add GImGui to your watch window and notice how its value changes depending on your current location (which DLL boundary you are in).\n\n// Current context pointer. Implicitly used by all Dear ImGui functions. Always assumed to be != NULL.\n// - ImGui::CreateContext() will automatically set this pointer if it is NULL.\n//   Change to a different context by calling ImGui::SetCurrentContext().\n// - Important: Dear ImGui functions are not thread-safe because of this pointer.\n//   If you want thread-safety to allow N threads to access N different contexts:\n//   - Change this variable to use thread local storage so each thread can refer to a different context, in your imconfig.h:\n//         struct ImGuiContext;\n//         extern thread_local ImGuiContext* MyImGuiTLS;\n//         #define GImGui MyImGuiTLS\n//     And then define MyImGuiTLS in one of your cpp files. Note that thread_local is a C++11 keyword, earlier C++ uses compiler-specific keyword.\n//   - Future development aims to make this context pointer explicit to all calls. Also read https://github.com/ocornut/imgui/issues/586\n//   - If you need a finite number of contexts, you may compile and use multiple instances of the ImGui code from a different namespace.\n// - DLL users: read comments above.\n#ifndef GImGui\nImGuiContext*   GImGui = NULL;\n#endif\n\n// Memory Allocator functions. Use SetAllocatorFunctions() to change them.\n// - You probably don't want to modify that mid-program, and if you use global/static e.g. ImVector<> instances you may need to keep them accessible during program destruction.\n// - DLL users: read comments above.\n#ifndef IMGUI_DISABLE_DEFAULT_ALLOCATORS\nstatic void*   MallocWrapper(size_t size, void* user_data)    { IM_UNUSED(user_data); return malloc(size); }\nstatic void    FreeWrapper(void* ptr, void* user_data)        { IM_UNUSED(user_data); free(ptr); }\n#else\nstatic void*   MallocWrapper(size_t size, void* user_data)    { IM_UNUSED(user_data); IM_UNUSED(size); IM_ASSERT(0); return NULL; }\nstatic void    FreeWrapper(void* ptr, void* user_data)        { IM_UNUSED(user_data); IM_UNUSED(ptr); IM_ASSERT(0); }\n#endif\nstatic ImGuiMemAllocFunc    GImAllocatorAllocFunc = MallocWrapper;\nstatic ImGuiMemFreeFunc     GImAllocatorFreeFunc = FreeWrapper;\nstatic void*                GImAllocatorUserData = NULL;\n\n//-----------------------------------------------------------------------------\n// [SECTION] USER FACING STRUCTURES (ImGuiStyle, ImGuiIO, ImGuiPlatformIO)\n//-----------------------------------------------------------------------------\n\nImGuiStyle::ImGuiStyle()\n{\n    Alpha                       = 1.0f;             // Global alpha applies to everything in Dear ImGui.\n    DisabledAlpha               = 0.60f;            // Additional alpha multiplier applied by BeginDisabled(). Multiply over current value of Alpha.\n    WindowPadding               = ImVec2(8,8);      // Padding within a window\n    WindowRounding              = 0.0f;             // Radius of window corners rounding. Set to 0.0f to have rectangular windows. Large values tend to lead to variety of artifacts and are not recommended.\n    WindowBorderSize            = 1.0f;             // Thickness of border around windows. Generally set to 0.0f or 1.0f. Other values not well tested.\n    WindowBorderHoverPadding    = 4.0f;             // Hit-testing extent outside/inside resizing border. Also extend determination of hovered window. Generally meaningfully larger than WindowBorderSize to make it easy to reach borders.\n    WindowMinSize               = ImVec2(32,32);    // Minimum window size\n    WindowTitleAlign            = ImVec2(0.0f,0.5f);// Alignment for title bar text\n    WindowMenuButtonPosition    = ImGuiDir_Left;    // Position of the collapsing/docking button in the title bar (left/right). Defaults to ImGuiDir_Left.\n    ChildRounding               = 0.0f;             // Radius of child window corners rounding. Set to 0.0f to have rectangular child windows\n    ChildBorderSize             = 1.0f;             // Thickness of border around child windows. Generally set to 0.0f or 1.0f. Other values not well tested.\n    PopupRounding               = 0.0f;             // Radius of popup window corners rounding. Set to 0.0f to have rectangular child windows\n    PopupBorderSize             = 1.0f;             // Thickness of border around popup or tooltip windows. Generally set to 0.0f or 1.0f. Other values not well tested.\n    FramePadding                = ImVec2(4,3);      // Padding within a framed rectangle (used by most widgets)\n    FrameRounding               = 0.0f;             // Radius of frame corners rounding. Set to 0.0f to have rectangular frames (used by most widgets).\n    FrameBorderSize             = 0.0f;             // Thickness of border around frames. Generally set to 0.0f or 1.0f. Other values not well tested.\n    ItemSpacing                 = ImVec2(8,4);      // Horizontal and vertical spacing between widgets/lines\n    ItemInnerSpacing            = ImVec2(4,4);      // Horizontal and vertical spacing between within elements of a composed widget (e.g. a slider and its label)\n    CellPadding                 = ImVec2(4,2);      // Padding within a table cell. Cellpadding.x is locked for entire table. CellPadding.y may be altered between different rows.\n    TouchExtraPadding           = ImVec2(0,0);      // Expand reactive bounding box for touch-based system where touch position is not accurate enough. Unfortunately we don't sort widgets so priority on overlap will always be given to the first widget. So don't grow this too much!\n    IndentSpacing               = 21.0f;            // Horizontal spacing when e.g. entering a tree node. Generally == (FontSize + FramePadding.x*2).\n    ColumnsMinSpacing           = 6.0f;             // Minimum horizontal spacing between two columns. Preferably > (FramePadding.x + 1).\n    ScrollbarSize               = 14.0f;            // Width of the vertical scrollbar, Height of the horizontal scrollbar\n    ScrollbarRounding           = 9.0f;             // Radius of grab corners rounding for scrollbar\n    GrabMinSize                 = 12.0f;            // Minimum width/height of a grab box for slider/scrollbar\n    GrabRounding                = 0.0f;             // Radius of grabs corners rounding. Set to 0.0f to have rectangular slider grabs.\n    LogSliderDeadzone           = 4.0f;             // The size in pixels of the dead-zone around zero on logarithmic sliders that cross zero.\n    ImageBorderSize             = 0.0f;             // Thickness of border around tabs.\n    TabRounding                 = 5.0f;             // Radius of upper corners of a tab. Set to 0.0f to have rectangular tabs.\n    TabBorderSize               = 0.0f;             // Thickness of border around tabs.\n    TabCloseButtonMinWidthSelected   = -1.0f;       // -1: always visible. 0.0f: visible when hovered. >0.0f: visible when hovered if minimum width.\n    TabCloseButtonMinWidthUnselected = 0.0f;        // -1: always visible. 0.0f: visible when hovered. >0.0f: visible when hovered if minimum width. FLT_MAX: never show close button when unselected.\n    TabBarBorderSize            = 1.0f;             // Thickness of tab-bar separator, which takes on the tab active color to denote focus.\n    TabBarOverlineSize          = 1.0f;             // Thickness of tab-bar overline, which highlights the selected tab-bar.\n    TableAngledHeadersAngle     = 35.0f * (IM_PI / 180.0f); // Angle of angled headers (supported values range from -50 degrees to +50 degrees).\n    TableAngledHeadersTextAlign = ImVec2(0.5f,0.0f);// Alignment of angled headers within the cell\n    ColorButtonPosition         = ImGuiDir_Right;   // Side of the color button in the ColorEdit4 widget (left/right). Defaults to ImGuiDir_Right.\n    ButtonTextAlign             = ImVec2(0.5f,0.5f);// Alignment of button text when button is larger than text.\n    SelectableTextAlign         = ImVec2(0.0f,0.0f);// Alignment of selectable text. Defaults to (0.0f, 0.0f) (top-left aligned). It's generally important to keep this left-aligned if you want to lay multiple items on a same line.\n    SeparatorTextBorderSize     = 3.0f;             // Thickness of border in SeparatorText()\n    SeparatorTextAlign          = ImVec2(0.0f,0.5f);// Alignment of text within the separator. Defaults to (0.0f, 0.5f) (left aligned, center).\n    SeparatorTextPadding        = ImVec2(20.0f,3.f);// Horizontal offset of text from each edge of the separator + spacing on other axis. Generally small values. .y is recommended to be == FramePadding.y.\n    DisplayWindowPadding        = ImVec2(19,19);    // Window position are clamped to be visible within the display area or monitors by at least this amount. Only applies to regular windows.\n    DisplaySafeAreaPadding      = ImVec2(3,3);      // If you cannot see the edge of your screen (e.g. on a TV) increase the safe area padding. Covers popups/tooltips as well regular windows.\n    MouseCursorScale            = 1.0f;             // Scale software rendered mouse cursor (when io.MouseDrawCursor is enabled). May be removed later.\n    AntiAliasedLines            = true;             // Enable anti-aliased lines/borders. Disable if you are really tight on CPU/GPU.\n    AntiAliasedLinesUseTex      = true;             // Enable anti-aliased lines/borders using textures where possible. Require backend to render with bilinear filtering (NOT point/nearest filtering).\n    AntiAliasedFill             = true;             // Enable anti-aliased filled shapes (rounded rectangles, circles, etc.).\n    CurveTessellationTol        = 1.25f;            // Tessellation tolerance when using PathBezierCurveTo() without a specific number of segments. Decrease for highly tessellated curves (higher quality, more polygons), increase to reduce quality.\n    CircleTessellationMaxError  = 0.30f;            // Maximum error (in pixels) allowed when using AddCircle()/AddCircleFilled() or drawing rounded corner rectangles with no explicit segment count specified. Decrease for higher quality but more geometry.\n\n    // Behaviors\n    HoverStationaryDelay        = 0.15f;            // Delay for IsItemHovered(ImGuiHoveredFlags_Stationary). Time required to consider mouse stationary.\n    HoverDelayShort             = 0.15f;            // Delay for IsItemHovered(ImGuiHoveredFlags_DelayShort). Usually used along with HoverStationaryDelay.\n    HoverDelayNormal            = 0.40f;            // Delay for IsItemHovered(ImGuiHoveredFlags_DelayNormal). \"\n    HoverFlagsForTooltipMouse   = ImGuiHoveredFlags_Stationary | ImGuiHoveredFlags_DelayShort | ImGuiHoveredFlags_AllowWhenDisabled;    // Default flags when using IsItemHovered(ImGuiHoveredFlags_ForTooltip) or BeginItemTooltip()/SetItemTooltip() while using mouse.\n    HoverFlagsForTooltipNav     = ImGuiHoveredFlags_NoSharedDelay | ImGuiHoveredFlags_DelayNormal | ImGuiHoveredFlags_AllowWhenDisabled;  // Default flags when using IsItemHovered(ImGuiHoveredFlags_ForTooltip) or BeginItemTooltip()/SetItemTooltip() while using keyboard/gamepad.\n\n    // Default theme\n    ImGui::StyleColorsDark(this);\n}\n\n// To scale your entire UI (e.g. if you want your app to use High DPI or generally be DPI aware) you may use this helper function. Scaling the fonts is done separately and is up to you.\n// Important: This operation is lossy because we round all sizes to integer. If you need to change your scale multiples, call this over a freshly initialized ImGuiStyle structure rather than scaling multiple times.\nvoid ImGuiStyle::ScaleAllSizes(float scale_factor)\n{\n    WindowPadding = ImTrunc(WindowPadding * scale_factor);\n    WindowRounding = ImTrunc(WindowRounding * scale_factor);\n    WindowMinSize = ImTrunc(WindowMinSize * scale_factor);\n    WindowBorderHoverPadding = ImTrunc(WindowBorderHoverPadding * scale_factor);\n    ChildRounding = ImTrunc(ChildRounding * scale_factor);\n    PopupRounding = ImTrunc(PopupRounding * scale_factor);\n    FramePadding = ImTrunc(FramePadding * scale_factor);\n    FrameRounding = ImTrunc(FrameRounding * scale_factor);\n    ItemSpacing = ImTrunc(ItemSpacing * scale_factor);\n    ItemInnerSpacing = ImTrunc(ItemInnerSpacing * scale_factor);\n    CellPadding = ImTrunc(CellPadding * scale_factor);\n    TouchExtraPadding = ImTrunc(TouchExtraPadding * scale_factor);\n    IndentSpacing = ImTrunc(IndentSpacing * scale_factor);\n    ColumnsMinSpacing = ImTrunc(ColumnsMinSpacing * scale_factor);\n    ScrollbarSize = ImTrunc(ScrollbarSize * scale_factor);\n    ScrollbarRounding = ImTrunc(ScrollbarRounding * scale_factor);\n    GrabMinSize = ImTrunc(GrabMinSize * scale_factor);\n    GrabRounding = ImTrunc(GrabRounding * scale_factor);\n    LogSliderDeadzone = ImTrunc(LogSliderDeadzone * scale_factor);\n    ImageBorderSize = ImTrunc(ImageBorderSize * scale_factor);\n    TabRounding = ImTrunc(TabRounding * scale_factor);\n    TabCloseButtonMinWidthSelected = (TabCloseButtonMinWidthSelected > 0.0f && TabCloseButtonMinWidthSelected != FLT_MAX) ? ImTrunc(TabCloseButtonMinWidthSelected * scale_factor) : TabCloseButtonMinWidthSelected;\n    TabCloseButtonMinWidthUnselected = (TabCloseButtonMinWidthUnselected > 0.0f && TabCloseButtonMinWidthUnselected != FLT_MAX) ? ImTrunc(TabCloseButtonMinWidthUnselected * scale_factor) : TabCloseButtonMinWidthUnselected;\n    TabBarOverlineSize = ImTrunc(TabBarOverlineSize * scale_factor);\n    SeparatorTextPadding = ImTrunc(SeparatorTextPadding * scale_factor);\n    DisplayWindowPadding = ImTrunc(DisplayWindowPadding * scale_factor);\n    DisplaySafeAreaPadding = ImTrunc(DisplaySafeAreaPadding * scale_factor);\n    MouseCursorScale = ImTrunc(MouseCursorScale * scale_factor);\n}\n\nImGuiIO::ImGuiIO()\n{\n    // Most fields are initialized with zero\n    memset(this, 0, sizeof(*this));\n    IM_STATIC_ASSERT(IM_ARRAYSIZE(ImGuiIO::MouseDown) == ImGuiMouseButton_COUNT && IM_ARRAYSIZE(ImGuiIO::MouseClicked) == ImGuiMouseButton_COUNT);\n\n    // Settings\n    ConfigFlags = ImGuiConfigFlags_None;\n    BackendFlags = ImGuiBackendFlags_None;\n    DisplaySize = ImVec2(-1.0f, -1.0f);\n    DeltaTime = 1.0f / 60.0f;\n    IniSavingRate = 5.0f;\n    IniFilename = \"imgui.ini\"; // Important: \"imgui.ini\" is relative to current working dir, most apps will want to lock this to an absolute path (e.g. same path as executables).\n    LogFilename = \"imgui_log.txt\";\n    UserData = NULL;\n\n    Fonts = NULL;\n    FontGlobalScale = 1.0f;\n    FontDefault = NULL;\n    FontAllowUserScaling = false;\n    DisplayFramebufferScale = ImVec2(1.0f, 1.0f);\n\n    // Keyboard/Gamepad Navigation options\n    ConfigNavSwapGamepadButtons = false;\n    ConfigNavMoveSetMousePos = false;\n    ConfigNavCaptureKeyboard = true;\n    ConfigNavEscapeClearFocusItem = true;\n    ConfigNavEscapeClearFocusWindow = false;\n    ConfigNavCursorVisibleAuto = true;\n    ConfigNavCursorVisibleAlways = false;\n\n    // Miscellaneous options\n    MouseDrawCursor = false;\n#ifdef __APPLE__\n    ConfigMacOSXBehaviors = true;  // Set Mac OS X style defaults based on __APPLE__ compile time flag\n#else\n    ConfigMacOSXBehaviors = false;\n#endif\n    ConfigInputTrickleEventQueue = true;\n    ConfigInputTextCursorBlink = true;\n    ConfigInputTextEnterKeepActive = false;\n    ConfigDragClickToInputText = false;\n    ConfigWindowsResizeFromEdges = true;\n    ConfigWindowsMoveFromTitleBarOnly = false;\n    ConfigWindowsCopyContentsWithCtrlC = false;\n    ConfigScrollbarScrollByPage = true;\n    ConfigMemoryCompactTimer = 60.0f;\n    ConfigDebugIsDebuggerPresent = false;\n    ConfigDebugHighlightIdConflicts = true;\n    ConfigDebugHighlightIdConflictsShowItemPicker = true;\n    ConfigDebugBeginReturnValueOnce = false;\n    ConfigDebugBeginReturnValueLoop = false;\n\n    ConfigErrorRecovery = true;\n    ConfigErrorRecoveryEnableAssert = true;\n    ConfigErrorRecoveryEnableDebugLog = true;\n    ConfigErrorRecoveryEnableTooltip = true;\n\n    // Inputs Behaviors\n    MouseDoubleClickTime = 0.30f;\n    MouseDoubleClickMaxDist = 6.0f;\n    MouseDragThreshold = 6.0f;\n    KeyRepeatDelay = 0.275f;\n    KeyRepeatRate = 0.050f;\n\n    // Platform Functions\n    // Note: Initialize() will setup default clipboard/ime handlers.\n    BackendPlatformName = BackendRendererName = NULL;\n    BackendPlatformUserData = BackendRendererUserData = BackendLanguageUserData = NULL;\n\n    // Input (NB: we already have memset zero the entire structure!)\n    MousePos = ImVec2(-FLT_MAX, -FLT_MAX);\n    MousePosPrev = ImVec2(-FLT_MAX, -FLT_MAX);\n    MouseSource = ImGuiMouseSource_Mouse;\n    for (int i = 0; i < IM_ARRAYSIZE(MouseDownDuration); i++) MouseDownDuration[i] = MouseDownDurationPrev[i] = -1.0f;\n    for (int i = 0; i < IM_ARRAYSIZE(KeysData); i++) { KeysData[i].DownDuration = KeysData[i].DownDurationPrev = -1.0f; }\n    AppAcceptingEvents = true;\n}\n\n// Pass in translated ASCII characters for text input.\n// - with glfw you can get those from the callback set in glfwSetCharCallback()\n// - on Windows you can get those using ToAscii+keyboard state, or via the WM_CHAR message\n// FIXME: Should in theory be called \"AddCharacterEvent()\" to be consistent with new API\nvoid ImGuiIO::AddInputCharacter(unsigned int c)\n{\n    IM_ASSERT(Ctx != NULL);\n    ImGuiContext& g = *Ctx;\n    if (c == 0 || !AppAcceptingEvents)\n        return;\n\n    ImGuiInputEvent e;\n    e.Type = ImGuiInputEventType_Text;\n    e.Source = ImGuiInputSource_Keyboard;\n    e.EventId = g.InputEventsNextEventId++;\n    e.Text.Char = c;\n    g.InputEventsQueue.push_back(e);\n}\n\n// UTF16 strings use surrogate pairs to encode codepoints >= 0x10000, so\n// we should save the high surrogate.\nvoid ImGuiIO::AddInputCharacterUTF16(ImWchar16 c)\n{\n    if ((c == 0 && InputQueueSurrogate == 0) || !AppAcceptingEvents)\n        return;\n\n    if ((c & 0xFC00) == 0xD800) // High surrogate, must save\n    {\n        if (InputQueueSurrogate != 0)\n            AddInputCharacter(IM_UNICODE_CODEPOINT_INVALID);\n        InputQueueSurrogate = c;\n        return;\n    }\n\n    ImWchar cp = c;\n    if (InputQueueSurrogate != 0)\n    {\n        if ((c & 0xFC00) != 0xDC00) // Invalid low surrogate\n        {\n            AddInputCharacter(IM_UNICODE_CODEPOINT_INVALID);\n        }\n        else\n        {\n#if IM_UNICODE_CODEPOINT_MAX == 0xFFFF\n            cp = IM_UNICODE_CODEPOINT_INVALID; // Codepoint will not fit in ImWchar\n#else\n            cp = (ImWchar)(((InputQueueSurrogate - 0xD800) << 10) + (c - 0xDC00) + 0x10000);\n#endif\n        }\n\n        InputQueueSurrogate = 0;\n    }\n    AddInputCharacter((unsigned)cp);\n}\n\nvoid ImGuiIO::AddInputCharactersUTF8(const char* utf8_chars)\n{\n    if (!AppAcceptingEvents)\n        return;\n    while (*utf8_chars != 0)\n    {\n        unsigned int c = 0;\n        utf8_chars += ImTextCharFromUtf8(&c, utf8_chars, NULL);\n        AddInputCharacter(c);\n    }\n}\n\n// Clear all incoming events.\nvoid ImGuiIO::ClearEventsQueue()\n{\n    IM_ASSERT(Ctx != NULL);\n    ImGuiContext& g = *Ctx;\n    g.InputEventsQueue.clear();\n}\n\n// Clear current keyboard/gamepad state + current frame text input buffer. Equivalent to releasing all keys/buttons.\nvoid ImGuiIO::ClearInputKeys()\n{\n    ImGuiContext& g = *Ctx;\n    for (int key = ImGuiKey_NamedKey_BEGIN; key < ImGuiKey_NamedKey_END; key++)\n    {\n        if (ImGui::IsMouseKey((ImGuiKey)key))\n            continue;\n        ImGuiKeyData* key_data = &g.IO.KeysData[key - ImGuiKey_NamedKey_BEGIN];\n        key_data->Down = false;\n        key_data->DownDuration = -1.0f;\n        key_data->DownDurationPrev = -1.0f;\n    }\n    KeyCtrl = KeyShift = KeyAlt = KeySuper = false;\n    KeyMods = ImGuiMod_None;\n    InputQueueCharacters.resize(0); // Behavior of old ClearInputCharacters().\n}\n\nvoid ImGuiIO::ClearInputMouse()\n{\n    for (ImGuiKey key = ImGuiKey_Mouse_BEGIN; key < ImGuiKey_Mouse_END; key = (ImGuiKey)(key + 1))\n    {\n        ImGuiKeyData* key_data = &KeysData[key - ImGuiKey_NamedKey_BEGIN];\n        key_data->Down = false;\n        key_data->DownDuration = -1.0f;\n        key_data->DownDurationPrev = -1.0f;\n    }\n    MousePos = ImVec2(-FLT_MAX, -FLT_MAX);\n    for (int n = 0; n < IM_ARRAYSIZE(MouseDown); n++)\n    {\n        MouseDown[n] = false;\n        MouseDownDuration[n] = MouseDownDurationPrev[n] = -1.0f;\n    }\n    MouseWheel = MouseWheelH = 0.0f;\n}\n\n// Removed this as it is ambiguous/misleading and generally incorrect to use with the existence of a higher-level input queue.\n// Current frame character buffer is now also cleared by ClearInputKeys().\n#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS\nvoid ImGuiIO::ClearInputCharacters()\n{\n    InputQueueCharacters.resize(0);\n}\n#endif\n\nstatic ImGuiInputEvent* FindLatestInputEvent(ImGuiContext* ctx, ImGuiInputEventType type, int arg = -1)\n{\n    ImGuiContext& g = *ctx;\n    for (int n = g.InputEventsQueue.Size - 1; n >= 0; n--)\n    {\n        ImGuiInputEvent* e = &g.InputEventsQueue[n];\n        if (e->Type != type)\n            continue;\n        if (type == ImGuiInputEventType_Key && e->Key.Key != arg)\n            continue;\n        if (type == ImGuiInputEventType_MouseButton && e->MouseButton.Button != arg)\n            continue;\n        return e;\n    }\n    return NULL;\n}\n\n// Queue a new key down/up event.\n// - ImGuiKey key:       Translated key (as in, generally ImGuiKey_A matches the key end-user would use to emit an 'A' character)\n// - bool down:          Is the key down? use false to signify a key release.\n// - float analog_value: 0.0f..1.0f\n// IMPORTANT: THIS FUNCTION AND OTHER \"ADD\" GRABS THE CONTEXT FROM OUR INSTANCE.\n// WE NEED TO ENSURE THAT ALL FUNCTION CALLS ARE FULFILLING THIS, WHICH IS WHY GetKeyData() HAS AN EXPLICIT CONTEXT.\nvoid ImGuiIO::AddKeyAnalogEvent(ImGuiKey key, bool down, float analog_value)\n{\n    //if (e->Down) { IMGUI_DEBUG_LOG_IO(\"AddKeyEvent() Key='%s' %d, NativeKeycode = %d, NativeScancode = %d\\n\", ImGui::GetKeyName(e->Key), e->Down, e->NativeKeycode, e->NativeScancode); }\n    IM_ASSERT(Ctx != NULL);\n    if (key == ImGuiKey_None || !AppAcceptingEvents)\n        return;\n    ImGuiContext& g = *Ctx;\n    IM_ASSERT(ImGui::IsNamedKeyOrMod(key)); // Backend needs to pass a valid ImGuiKey_ constant. 0..511 values are legacy native key codes which are not accepted by this API.\n    IM_ASSERT(ImGui::IsAliasKey(key) == false); // Backend cannot submit ImGuiKey_MouseXXX values they are automatically inferred from AddMouseXXX() events.\n\n    // MacOS: swap Cmd(Super) and Ctrl\n    if (g.IO.ConfigMacOSXBehaviors)\n    {\n        if (key == ImGuiMod_Super)          { key = ImGuiMod_Ctrl; }\n        else if (key == ImGuiMod_Ctrl)      { key = ImGuiMod_Super; }\n        else if (key == ImGuiKey_LeftSuper) { key = ImGuiKey_LeftCtrl; }\n        else if (key == ImGuiKey_RightSuper){ key = ImGuiKey_RightCtrl; }\n        else if (key == ImGuiKey_LeftCtrl)  { key = ImGuiKey_LeftSuper; }\n        else if (key == ImGuiKey_RightCtrl) { key = ImGuiKey_RightSuper; }\n    }\n\n    // Filter duplicate (in particular: key mods and gamepad analog values are commonly spammed)\n    const ImGuiInputEvent* latest_event = FindLatestInputEvent(&g, ImGuiInputEventType_Key, (int)key);\n    const ImGuiKeyData* key_data = ImGui::GetKeyData(&g, key);\n    const bool latest_key_down = latest_event ? latest_event->Key.Down : key_data->Down;\n    const float latest_key_analog = latest_event ? latest_event->Key.AnalogValue : key_data->AnalogValue;\n    if (latest_key_down == down && latest_key_analog == analog_value)\n        return;\n\n    // Add event\n    ImGuiInputEvent e;\n    e.Type = ImGuiInputEventType_Key;\n    e.Source = ImGui::IsGamepadKey(key) ? ImGuiInputSource_Gamepad : ImGuiInputSource_Keyboard;\n    e.EventId = g.InputEventsNextEventId++;\n    e.Key.Key = key;\n    e.Key.Down = down;\n    e.Key.AnalogValue = analog_value;\n    g.InputEventsQueue.push_back(e);\n}\n\nvoid ImGuiIO::AddKeyEvent(ImGuiKey key, bool down)\n{\n    if (!AppAcceptingEvents)\n        return;\n    AddKeyAnalogEvent(key, down, down ? 1.0f : 0.0f);\n}\n\n// [Optional] Call after AddKeyEvent().\n// Specify native keycode, scancode + Specify index for legacy <1.87 IsKeyXXX() functions with native indices.\n// If you are writing a backend in 2022 or don't use IsKeyXXX() with native values that are not ImGuiKey values, you can avoid calling this.\nvoid ImGuiIO::SetKeyEventNativeData(ImGuiKey key, int native_keycode, int native_scancode, int native_legacy_index)\n{\n    if (key == ImGuiKey_None)\n        return;\n    IM_ASSERT(ImGui::IsNamedKey(key)); // >= 512\n    IM_ASSERT(native_legacy_index == -1 || ImGui::IsLegacyKey((ImGuiKey)native_legacy_index)); // >= 0 && <= 511\n    IM_UNUSED(key);                 // Yet unused\n    IM_UNUSED(native_keycode);      // Yet unused\n    IM_UNUSED(native_scancode);     // Yet unused\n    IM_UNUSED(native_legacy_index); // Yet unused\n}\n\n// Set master flag for accepting key/mouse/text events (default to true). Useful if you have native dialog boxes that are interrupting your application loop/refresh, and you want to disable events being queued while your app is frozen.\nvoid ImGuiIO::SetAppAcceptingEvents(bool accepting_events)\n{\n    AppAcceptingEvents = accepting_events;\n}\n\n// Queue a mouse move event\nvoid ImGuiIO::AddMousePosEvent(float x, float y)\n{\n    IM_ASSERT(Ctx != NULL);\n    ImGuiContext& g = *Ctx;\n    if (!AppAcceptingEvents)\n        return;\n\n    // Apply same flooring as UpdateMouseInputs()\n    ImVec2 pos((x > -FLT_MAX) ? ImFloor(x) : x, (y > -FLT_MAX) ? ImFloor(y) : y);\n\n    // Filter duplicate\n    const ImGuiInputEvent* latest_event = FindLatestInputEvent(&g, ImGuiInputEventType_MousePos);\n    const ImVec2 latest_pos = latest_event ? ImVec2(latest_event->MousePos.PosX, latest_event->MousePos.PosY) : g.IO.MousePos;\n    if (latest_pos.x == pos.x && latest_pos.y == pos.y)\n        return;\n\n    ImGuiInputEvent e;\n    e.Type = ImGuiInputEventType_MousePos;\n    e.Source = ImGuiInputSource_Mouse;\n    e.EventId = g.InputEventsNextEventId++;\n    e.MousePos.PosX = pos.x;\n    e.MousePos.PosY = pos.y;\n    e.MousePos.MouseSource = g.InputEventsNextMouseSource;\n    g.InputEventsQueue.push_back(e);\n}\n\nvoid ImGuiIO::AddMouseButtonEvent(int mouse_button, bool down)\n{\n    IM_ASSERT(Ctx != NULL);\n    ImGuiContext& g = *Ctx;\n    IM_ASSERT(mouse_button >= 0 && mouse_button < ImGuiMouseButton_COUNT);\n    if (!AppAcceptingEvents)\n        return;\n\n    // On MacOS X: Convert Ctrl(Super)+Left click into Right-click: handle held button.\n    if (ConfigMacOSXBehaviors && mouse_button == 0 && MouseCtrlLeftAsRightClick)\n    {\n        // Order of both statements matterns: this event will still release mouse button 1\n        mouse_button = 1;\n        if (!down)\n            MouseCtrlLeftAsRightClick = false;\n    }\n\n    // Filter duplicate\n    const ImGuiInputEvent* latest_event = FindLatestInputEvent(&g, ImGuiInputEventType_MouseButton, (int)mouse_button);\n    const bool latest_button_down = latest_event ? latest_event->MouseButton.Down : g.IO.MouseDown[mouse_button];\n    if (latest_button_down == down)\n        return;\n\n    // On MacOS X: Convert Ctrl(Super)+Left click into Right-click.\n    // - Note that this is actual physical Ctrl which is ImGuiMod_Super for us.\n    // - At this point we want from !down to down, so this is handling the initial press.\n    if (ConfigMacOSXBehaviors && mouse_button == 0 && down)\n    {\n        const ImGuiInputEvent* latest_super_event = FindLatestInputEvent(&g, ImGuiInputEventType_Key, (int)ImGuiMod_Super);\n        if (latest_super_event ? latest_super_event->Key.Down : g.IO.KeySuper)\n        {\n            IMGUI_DEBUG_LOG_IO(\"[io] Super+Left Click aliased into Right Click\\n\");\n            MouseCtrlLeftAsRightClick = true;\n            AddMouseButtonEvent(1, true); // This is just quicker to write that passing through, as we need to filter duplicate again.\n            return;\n        }\n    }\n\n    ImGuiInputEvent e;\n    e.Type = ImGuiInputEventType_MouseButton;\n    e.Source = ImGuiInputSource_Mouse;\n    e.EventId = g.InputEventsNextEventId++;\n    e.MouseButton.Button = mouse_button;\n    e.MouseButton.Down = down;\n    e.MouseButton.MouseSource = g.InputEventsNextMouseSource;\n    g.InputEventsQueue.push_back(e);\n}\n\n// Queue a mouse wheel event (some mouse/API may only have a Y component)\nvoid ImGuiIO::AddMouseWheelEvent(float wheel_x, float wheel_y)\n{\n    IM_ASSERT(Ctx != NULL);\n    ImGuiContext& g = *Ctx;\n\n    // Filter duplicate (unlike most events, wheel values are relative and easy to filter)\n    if (!AppAcceptingEvents || (wheel_x == 0.0f && wheel_y == 0.0f))\n        return;\n\n    ImGuiInputEvent e;\n    e.Type = ImGuiInputEventType_MouseWheel;\n    e.Source = ImGuiInputSource_Mouse;\n    e.EventId = g.InputEventsNextEventId++;\n    e.MouseWheel.WheelX = wheel_x;\n    e.MouseWheel.WheelY = wheel_y;\n    e.MouseWheel.MouseSource = g.InputEventsNextMouseSource;\n    g.InputEventsQueue.push_back(e);\n}\n\n// This is not a real event, the data is latched in order to be stored in actual Mouse events.\n// This is so that duplicate events (e.g. Windows sending extraneous WM_MOUSEMOVE) gets filtered and are not leading to actual source changes.\nvoid ImGuiIO::AddMouseSourceEvent(ImGuiMouseSource source)\n{\n    IM_ASSERT(Ctx != NULL);\n    ImGuiContext& g = *Ctx;\n    g.InputEventsNextMouseSource = source;\n}\n\nvoid ImGuiIO::AddFocusEvent(bool focused)\n{\n    IM_ASSERT(Ctx != NULL);\n    ImGuiContext& g = *Ctx;\n\n    // Filter duplicate\n    const ImGuiInputEvent* latest_event = FindLatestInputEvent(&g, ImGuiInputEventType_Focus);\n    const bool latest_focused = latest_event ? latest_event->AppFocused.Focused : !g.IO.AppFocusLost;\n    if (latest_focused == focused || (ConfigDebugIgnoreFocusLoss && !focused))\n        return;\n\n    ImGuiInputEvent e;\n    e.Type = ImGuiInputEventType_Focus;\n    e.EventId = g.InputEventsNextEventId++;\n    e.AppFocused.Focused = focused;\n    g.InputEventsQueue.push_back(e);\n}\n\nImGuiPlatformIO::ImGuiPlatformIO()\n{\n    // Most fields are initialized with zero\n    memset(this, 0, sizeof(*this));\n    Platform_LocaleDecimalPoint = '.';\n}\n\n//-----------------------------------------------------------------------------\n// [SECTION] MISC HELPERS/UTILITIES (Geometry functions)\n//-----------------------------------------------------------------------------\n\nImVec2 ImBezierCubicClosestPoint(const ImVec2& p1, const ImVec2& p2, const ImVec2& p3, const ImVec2& p4, const ImVec2& p, int num_segments)\n{\n    IM_ASSERT(num_segments > 0); // Use ImBezierCubicClosestPointCasteljau()\n    ImVec2 p_last = p1;\n    ImVec2 p_closest;\n    float p_closest_dist2 = FLT_MAX;\n    float t_step = 1.0f / (float)num_segments;\n    for (int i_step = 1; i_step <= num_segments; i_step++)\n    {\n        ImVec2 p_current = ImBezierCubicCalc(p1, p2, p3, p4, t_step * i_step);\n        ImVec2 p_line = ImLineClosestPoint(p_last, p_current, p);\n        float dist2 = ImLengthSqr(p - p_line);\n        if (dist2 < p_closest_dist2)\n        {\n            p_closest = p_line;\n            p_closest_dist2 = dist2;\n        }\n        p_last = p_current;\n    }\n    return p_closest;\n}\n\n// Closely mimics PathBezierToCasteljau() in imgui_draw.cpp\nstatic void ImBezierCubicClosestPointCasteljauStep(const ImVec2& p, ImVec2& p_closest, ImVec2& p_last, float& p_closest_dist2, float x1, float y1, float x2, float y2, float x3, float y3, float x4, float y4, float tess_tol, int level)\n{\n    float dx = x4 - x1;\n    float dy = y4 - y1;\n    float d2 = ((x2 - x4) * dy - (y2 - y4) * dx);\n    float d3 = ((x3 - x4) * dy - (y3 - y4) * dx);\n    d2 = (d2 >= 0) ? d2 : -d2;\n    d3 = (d3 >= 0) ? d3 : -d3;\n    if ((d2 + d3) * (d2 + d3) < tess_tol * (dx * dx + dy * dy))\n    {\n        ImVec2 p_current(x4, y4);\n        ImVec2 p_line = ImLineClosestPoint(p_last, p_current, p);\n        float dist2 = ImLengthSqr(p - p_line);\n        if (dist2 < p_closest_dist2)\n        {\n            p_closest = p_line;\n            p_closest_dist2 = dist2;\n        }\n        p_last = p_current;\n    }\n    else if (level < 10)\n    {\n        float x12 = (x1 + x2)*0.5f,       y12 = (y1 + y2)*0.5f;\n        float x23 = (x2 + x3)*0.5f,       y23 = (y2 + y3)*0.5f;\n        float x34 = (x3 + x4)*0.5f,       y34 = (y3 + y4)*0.5f;\n        float x123 = (x12 + x23)*0.5f,    y123 = (y12 + y23)*0.5f;\n        float x234 = (x23 + x34)*0.5f,    y234 = (y23 + y34)*0.5f;\n        float x1234 = (x123 + x234)*0.5f, y1234 = (y123 + y234)*0.5f;\n        ImBezierCubicClosestPointCasteljauStep(p, p_closest, p_last, p_closest_dist2, x1, y1, x12, y12, x123, y123, x1234, y1234, tess_tol, level + 1);\n        ImBezierCubicClosestPointCasteljauStep(p, p_closest, p_last, p_closest_dist2, x1234, y1234, x234, y234, x34, y34, x4, y4, tess_tol, level + 1);\n    }\n}\n\n// tess_tol is generally the same value you would find in ImGui::GetStyle().CurveTessellationTol\n// Because those ImXXX functions are lower-level than ImGui:: we cannot access this value automatically.\nImVec2 ImBezierCubicClosestPointCasteljau(const ImVec2& p1, const ImVec2& p2, const ImVec2& p3, const ImVec2& p4, const ImVec2& p, float tess_tol)\n{\n    IM_ASSERT(tess_tol > 0.0f);\n    ImVec2 p_last = p1;\n    ImVec2 p_closest;\n    float p_closest_dist2 = FLT_MAX;\n    ImBezierCubicClosestPointCasteljauStep(p, p_closest, p_last, p_closest_dist2, p1.x, p1.y, p2.x, p2.y, p3.x, p3.y, p4.x, p4.y, tess_tol, 0);\n    return p_closest;\n}\n\nImVec2 ImLineClosestPoint(const ImVec2& a, const ImVec2& b, const ImVec2& p)\n{\n    ImVec2 ap = p - a;\n    ImVec2 ab_dir = b - a;\n    float dot = ap.x * ab_dir.x + ap.y * ab_dir.y;\n    if (dot < 0.0f)\n        return a;\n    float ab_len_sqr = ab_dir.x * ab_dir.x + ab_dir.y * ab_dir.y;\n    if (dot > ab_len_sqr)\n        return b;\n    return a + ab_dir * dot / ab_len_sqr;\n}\n\nbool ImTriangleContainsPoint(const ImVec2& a, const ImVec2& b, const ImVec2& c, const ImVec2& p)\n{\n    bool b1 = ((p.x - b.x) * (a.y - b.y) - (p.y - b.y) * (a.x - b.x)) < 0.0f;\n    bool b2 = ((p.x - c.x) * (b.y - c.y) - (p.y - c.y) * (b.x - c.x)) < 0.0f;\n    bool b3 = ((p.x - a.x) * (c.y - a.y) - (p.y - a.y) * (c.x - a.x)) < 0.0f;\n    return ((b1 == b2) && (b2 == b3));\n}\n\nvoid ImTriangleBarycentricCoords(const ImVec2& a, const ImVec2& b, const ImVec2& c, const ImVec2& p, float& out_u, float& out_v, float& out_w)\n{\n    ImVec2 v0 = b - a;\n    ImVec2 v1 = c - a;\n    ImVec2 v2 = p - a;\n    const float denom = v0.x * v1.y - v1.x * v0.y;\n    out_v = (v2.x * v1.y - v1.x * v2.y) / denom;\n    out_w = (v0.x * v2.y - v2.x * v0.y) / denom;\n    out_u = 1.0f - out_v - out_w;\n}\n\nImVec2 ImTriangleClosestPoint(const ImVec2& a, const ImVec2& b, const ImVec2& c, const ImVec2& p)\n{\n    ImVec2 proj_ab = ImLineClosestPoint(a, b, p);\n    ImVec2 proj_bc = ImLineClosestPoint(b, c, p);\n    ImVec2 proj_ca = ImLineClosestPoint(c, a, p);\n    float dist2_ab = ImLengthSqr(p - proj_ab);\n    float dist2_bc = ImLengthSqr(p - proj_bc);\n    float dist2_ca = ImLengthSqr(p - proj_ca);\n    float m = ImMin(dist2_ab, ImMin(dist2_bc, dist2_ca));\n    if (m == dist2_ab)\n        return proj_ab;\n    if (m == dist2_bc)\n        return proj_bc;\n    return proj_ca;\n}\n\n//-----------------------------------------------------------------------------\n// [SECTION] MISC HELPERS/UTILITIES (String, Format, Hash functions)\n//-----------------------------------------------------------------------------\n\n// Consider using _stricmp/_strnicmp under Windows or strcasecmp/strncasecmp. We don't actually use either ImStricmp/ImStrnicmp in the codebase any more.\nint ImStricmp(const char* str1, const char* str2)\n{\n    int d;\n    while ((d = ImToUpper(*str2) - ImToUpper(*str1)) == 0 && *str1) { str1++; str2++; }\n    return d;\n}\n\nint ImStrnicmp(const char* str1, const char* str2, size_t count)\n{\n    int d = 0;\n    while (count > 0 && (d = ImToUpper(*str2) - ImToUpper(*str1)) == 0 && *str1) { str1++; str2++; count--; }\n    return d;\n}\n\nvoid ImStrncpy(char* dst, const char* src, size_t count)\n{\n    if (count < 1)\n        return;\n    if (count > 1)\n        strncpy(dst, src, count - 1);\n    dst[count - 1] = 0;\n}\n\nchar* ImStrdup(const char* str)\n{\n    size_t len = ImStrlen(str);\n    void* buf = IM_ALLOC(len + 1);\n    return (char*)memcpy(buf, (const void*)str, len + 1);\n}\n\nchar* ImStrdupcpy(char* dst, size_t* p_dst_size, const char* src)\n{\n    size_t dst_buf_size = p_dst_size ? *p_dst_size : ImStrlen(dst) + 1;\n    size_t src_size = ImStrlen(src) + 1;\n    if (dst_buf_size < src_size)\n    {\n        IM_FREE(dst);\n        dst = (char*)IM_ALLOC(src_size);\n        if (p_dst_size)\n            *p_dst_size = src_size;\n    }\n    return (char*)memcpy(dst, (const void*)src, src_size);\n}\n\nconst char* ImStrchrRange(const char* str, const char* str_end, char c)\n{\n    const char* p = (const char*)ImMemchr(str, (int)c, str_end - str);\n    return p;\n}\n\nint ImStrlenW(const ImWchar* str)\n{\n    //return (int)wcslen((const wchar_t*)str);  // FIXME-OPT: Could use this when wchar_t are 16-bit\n    int n = 0;\n    while (*str++) n++;\n    return n;\n}\n\n// Find end-of-line. Return pointer will point to either first \\n, either str_end.\nconst char* ImStreolRange(const char* str, const char* str_end)\n{\n    const char* p = (const char*)ImMemchr(str, '\\n', str_end - str);\n    return p ? p : str_end;\n}\n\nconst char* ImStrbol(const char* buf_mid_line, const char* buf_begin) // find beginning-of-line\n{\n    IM_ASSERT_PARANOID(buf_mid_line >= buf_begin && buf_mid_line <= buf_begin + ImStrlen(buf_begin));\n    while (buf_mid_line > buf_begin && buf_mid_line[-1] != '\\n')\n        buf_mid_line--;\n    return buf_mid_line;\n}\n\nconst char* ImStristr(const char* haystack, const char* haystack_end, const char* needle, const char* needle_end)\n{\n    if (!needle_end)\n        needle_end = needle + ImStrlen(needle);\n\n    const char un0 = (char)ImToUpper(*needle);\n    while ((!haystack_end && *haystack) || (haystack_end && haystack < haystack_end))\n    {\n        if (ImToUpper(*haystack) == un0)\n        {\n            const char* b = needle + 1;\n            for (const char* a = haystack + 1; b < needle_end; a++, b++)\n                if (ImToUpper(*a) != ImToUpper(*b))\n                    break;\n            if (b == needle_end)\n                return haystack;\n        }\n        haystack++;\n    }\n    return NULL;\n}\n\n// Trim str by offsetting contents when there's leading data + writing a \\0 at the trailing position. We use this in situation where the cost is negligible.\nvoid ImStrTrimBlanks(char* buf)\n{\n    char* p = buf;\n    while (p[0] == ' ' || p[0] == '\\t')     // Leading blanks\n        p++;\n    char* p_start = p;\n    while (*p != 0)                         // Find end of string\n        p++;\n    while (p > p_start && (p[-1] == ' ' || p[-1] == '\\t'))  // Trailing blanks\n        p--;\n    if (p_start != buf)                     // Copy memory if we had leading blanks\n        memmove(buf, p_start, p - p_start);\n    buf[p - p_start] = 0;                   // Zero terminate\n}\n\nconst char* ImStrSkipBlank(const char* str)\n{\n    while (str[0] == ' ' || str[0] == '\\t')\n        str++;\n    return str;\n}\n\n// A) MSVC version appears to return -1 on overflow, whereas glibc appears to return total count (which may be >= buf_size).\n// Ideally we would test for only one of those limits at runtime depending on the behavior the vsnprintf(), but trying to deduct it at compile time sounds like a pandora can of worm.\n// B) When buf==NULL vsnprintf() will return the output size.\n#ifndef IMGUI_DISABLE_DEFAULT_FORMAT_FUNCTIONS\n\n// We support stb_sprintf which is much faster (see: https://github.com/nothings/stb/blob/master/stb_sprintf.h)\n// You may set IMGUI_USE_STB_SPRINTF to use our default wrapper, or set IMGUI_DISABLE_DEFAULT_FORMAT_FUNCTIONS\n// and setup the wrapper yourself. (FIXME-OPT: Some of our high-level operations such as ImGuiTextBuffer::appendfv() are\n// designed using two-passes worst case, which probably could be improved using the stbsp_vsprintfcb() function.)\n#ifdef IMGUI_USE_STB_SPRINTF\n#ifndef IMGUI_DISABLE_STB_SPRINTF_IMPLEMENTATION\n#define STB_SPRINTF_IMPLEMENTATION\n#endif\n#ifdef IMGUI_STB_SPRINTF_FILENAME\n#include IMGUI_STB_SPRINTF_FILENAME\n#else\n#include \"stb_sprintf.h\"\n#endif\n#endif // #ifdef IMGUI_USE_STB_SPRINTF\n\n#if defined(_MSC_VER) && !defined(vsnprintf)\n#define vsnprintf _vsnprintf\n#endif\n\nint ImFormatString(char* buf, size_t buf_size, const char* fmt, ...)\n{\n    va_list args;\n    va_start(args, fmt);\n#ifdef IMGUI_USE_STB_SPRINTF\n    int w = stbsp_vsnprintf(buf, (int)buf_size, fmt, args);\n#else\n    int w = vsnprintf(buf, buf_size, fmt, args);\n#endif\n    va_end(args);\n    if (buf == NULL)\n        return w;\n    if (w == -1 || w >= (int)buf_size)\n        w = (int)buf_size - 1;\n    buf[w] = 0;\n    return w;\n}\n\nint ImFormatStringV(char* buf, size_t buf_size, const char* fmt, va_list args)\n{\n#ifdef IMGUI_USE_STB_SPRINTF\n    int w = stbsp_vsnprintf(buf, (int)buf_size, fmt, args);\n#else\n    int w = vsnprintf(buf, buf_size, fmt, args);\n#endif\n    if (buf == NULL)\n        return w;\n    if (w == -1 || w >= (int)buf_size)\n        w = (int)buf_size - 1;\n    buf[w] = 0;\n    return w;\n}\n#endif // #ifdef IMGUI_DISABLE_DEFAULT_FORMAT_FUNCTIONS\n\nvoid ImFormatStringToTempBuffer(const char** out_buf, const char** out_buf_end, const char* fmt, ...)\n{\n    va_list args;\n    va_start(args, fmt);\n    ImFormatStringToTempBufferV(out_buf, out_buf_end, fmt, args);\n    va_end(args);\n}\n\n// FIXME: Should rework API toward allowing multiple in-flight temp buffers (easier and safer for caller)\n// by making the caller acquire a temp buffer token, with either explicit or destructor release, e.g.\n//  ImGuiTempBufferToken token;\n//  ImFormatStringToTempBuffer(token, ...);\nvoid ImFormatStringToTempBufferV(const char** out_buf, const char** out_buf_end, const char* fmt, va_list args)\n{\n    ImGuiContext& g = *GImGui;\n    if (fmt[0] == '%' && fmt[1] == 's' && fmt[2] == 0)\n    {\n        const char* buf = va_arg(args, const char*); // Skip formatting when using \"%s\"\n        if (buf == NULL)\n            buf = \"(null)\";\n        *out_buf = buf;\n        if (out_buf_end) { *out_buf_end = buf + ImStrlen(buf); }\n    }\n    else if (fmt[0] == '%' && fmt[1] == '.' && fmt[2] == '*' && fmt[3] == 's' && fmt[4] == 0)\n    {\n        int buf_len = va_arg(args, int); // Skip formatting when using \"%.*s\"\n        const char* buf = va_arg(args, const char*);\n        if (buf == NULL)\n        {\n            buf = \"(null)\";\n            buf_len = ImMin(buf_len, 6);\n        }\n        *out_buf = buf;\n        *out_buf_end = buf + buf_len; // Disallow not passing 'out_buf_end' here. User is expected to use it.\n    }\n    else\n    {\n        int buf_len = ImFormatStringV(g.TempBuffer.Data, g.TempBuffer.Size, fmt, args);\n        *out_buf = g.TempBuffer.Data;\n        if (out_buf_end) { *out_buf_end = g.TempBuffer.Data + buf_len; }\n    }\n}\n\n#ifndef IMGUI_ENABLE_SSE4_2_CRC\n// CRC32 needs a 1KB lookup table (not cache friendly)\n// Although the code to generate the table is simple and shorter than the table itself, using a const table allows us to easily:\n// - avoid an unnecessary branch/memory tap, - keep the ImHashXXX functions usable by static constructors, - make it thread-safe.\nstatic const ImU32 GCrc32LookupTable[256] =\n{\n#ifdef IMGUI_USE_LEGACY_CRC32_ADLER\n    // Legacy CRC32-adler table used pre 1.91.6 (before 2024/11/27). Only use if you cannot afford invalidating old .ini data.\n    0x00000000,0x77073096,0xEE0E612C,0x990951BA,0x076DC419,0x706AF48F,0xE963A535,0x9E6495A3,0x0EDB8832,0x79DCB8A4,0xE0D5E91E,0x97D2D988,0x09B64C2B,0x7EB17CBD,0xE7B82D07,0x90BF1D91,\n    0x1DB71064,0x6AB020F2,0xF3B97148,0x84BE41DE,0x1ADAD47D,0x6DDDE4EB,0xF4D4B551,0x83D385C7,0x136C9856,0x646BA8C0,0xFD62F97A,0x8A65C9EC,0x14015C4F,0x63066CD9,0xFA0F3D63,0x8D080DF5,\n    0x3B6E20C8,0x4C69105E,0xD56041E4,0xA2677172,0x3C03E4D1,0x4B04D447,0xD20D85FD,0xA50AB56B,0x35B5A8FA,0x42B2986C,0xDBBBC9D6,0xACBCF940,0x32D86CE3,0x45DF5C75,0xDCD60DCF,0xABD13D59,\n    0x26D930AC,0x51DE003A,0xC8D75180,0xBFD06116,0x21B4F4B5,0x56B3C423,0xCFBA9599,0xB8BDA50F,0x2802B89E,0x5F058808,0xC60CD9B2,0xB10BE924,0x2F6F7C87,0x58684C11,0xC1611DAB,0xB6662D3D,\n    0x76DC4190,0x01DB7106,0x98D220BC,0xEFD5102A,0x71B18589,0x06B6B51F,0x9FBFE4A5,0xE8B8D433,0x7807C9A2,0x0F00F934,0x9609A88E,0xE10E9818,0x7F6A0DBB,0x086D3D2D,0x91646C97,0xE6635C01,\n    0x6B6B51F4,0x1C6C6162,0x856530D8,0xF262004E,0x6C0695ED,0x1B01A57B,0x8208F4C1,0xF50FC457,0x65B0D9C6,0x12B7E950,0x8BBEB8EA,0xFCB9887C,0x62DD1DDF,0x15DA2D49,0x8CD37CF3,0xFBD44C65,\n    0x4DB26158,0x3AB551CE,0xA3BC0074,0xD4BB30E2,0x4ADFA541,0x3DD895D7,0xA4D1C46D,0xD3D6F4FB,0x4369E96A,0x346ED9FC,0xAD678846,0xDA60B8D0,0x44042D73,0x33031DE5,0xAA0A4C5F,0xDD0D7CC9,\n    0x5005713C,0x270241AA,0xBE0B1010,0xC90C2086,0x5768B525,0x206F85B3,0xB966D409,0xCE61E49F,0x5EDEF90E,0x29D9C998,0xB0D09822,0xC7D7A8B4,0x59B33D17,0x2EB40D81,0xB7BD5C3B,0xC0BA6CAD,\n    0xEDB88320,0x9ABFB3B6,0x03B6E20C,0x74B1D29A,0xEAD54739,0x9DD277AF,0x04DB2615,0x73DC1683,0xE3630B12,0x94643B84,0x0D6D6A3E,0x7A6A5AA8,0xE40ECF0B,0x9309FF9D,0x0A00AE27,0x7D079EB1,\n    0xF00F9344,0x8708A3D2,0x1E01F268,0x6906C2FE,0xF762575D,0x806567CB,0x196C3671,0x6E6B06E7,0xFED41B76,0x89D32BE0,0x10DA7A5A,0x67DD4ACC,0xF9B9DF6F,0x8EBEEFF9,0x17B7BE43,0x60B08ED5,\n    0xD6D6A3E8,0xA1D1937E,0x38D8C2C4,0x4FDFF252,0xD1BB67F1,0xA6BC5767,0x3FB506DD,0x48B2364B,0xD80D2BDA,0xAF0A1B4C,0x36034AF6,0x41047A60,0xDF60EFC3,0xA867DF55,0x316E8EEF,0x4669BE79,\n    0xCB61B38C,0xBC66831A,0x256FD2A0,0x5268E236,0xCC0C7795,0xBB0B4703,0x220216B9,0x5505262F,0xC5BA3BBE,0xB2BD0B28,0x2BB45A92,0x5CB36A04,0xC2D7FFA7,0xB5D0CF31,0x2CD99E8B,0x5BDEAE1D,\n    0x9B64C2B0,0xEC63F226,0x756AA39C,0x026D930A,0x9C0906A9,0xEB0E363F,0x72076785,0x05005713,0x95BF4A82,0xE2B87A14,0x7BB12BAE,0x0CB61B38,0x92D28E9B,0xE5D5BE0D,0x7CDCEFB7,0x0BDBDF21,\n    0x86D3D2D4,0xF1D4E242,0x68DDB3F8,0x1FDA836E,0x81BE16CD,0xF6B9265B,0x6FB077E1,0x18B74777,0x88085AE6,0xFF0F6A70,0x66063BCA,0x11010B5C,0x8F659EFF,0xF862AE69,0x616BFFD3,0x166CCF45,\n    0xA00AE278,0xD70DD2EE,0x4E048354,0x3903B3C2,0xA7672661,0xD06016F7,0x4969474D,0x3E6E77DB,0xAED16A4A,0xD9D65ADC,0x40DF0B66,0x37D83BF0,0xA9BCAE53,0xDEBB9EC5,0x47B2CF7F,0x30B5FFE9,\n    0xBDBDF21C,0xCABAC28A,0x53B39330,0x24B4A3A6,0xBAD03605,0xCDD70693,0x54DE5729,0x23D967BF,0xB3667A2E,0xC4614AB8,0x5D681B02,0x2A6F2B94,0xB40BBE37,0xC30C8EA1,0x5A05DF1B,0x2D02EF8D,\n#else\n    // CRC32c table compatible with SSE 4.2 instructions\n    0x00000000,0xF26B8303,0xE13B70F7,0x1350F3F4,0xC79A971F,0x35F1141C,0x26A1E7E8,0xD4CA64EB,0x8AD958CF,0x78B2DBCC,0x6BE22838,0x9989AB3B,0x4D43CFD0,0xBF284CD3,0xAC78BF27,0x5E133C24,\n    0x105EC76F,0xE235446C,0xF165B798,0x030E349B,0xD7C45070,0x25AFD373,0x36FF2087,0xC494A384,0x9A879FA0,0x68EC1CA3,0x7BBCEF57,0x89D76C54,0x5D1D08BF,0xAF768BBC,0xBC267848,0x4E4DFB4B,\n    0x20BD8EDE,0xD2D60DDD,0xC186FE29,0x33ED7D2A,0xE72719C1,0x154C9AC2,0x061C6936,0xF477EA35,0xAA64D611,0x580F5512,0x4B5FA6E6,0xB93425E5,0x6DFE410E,0x9F95C20D,0x8CC531F9,0x7EAEB2FA,\n    0x30E349B1,0xC288CAB2,0xD1D83946,0x23B3BA45,0xF779DEAE,0x05125DAD,0x1642AE59,0xE4292D5A,0xBA3A117E,0x4851927D,0x5B016189,0xA96AE28A,0x7DA08661,0x8FCB0562,0x9C9BF696,0x6EF07595,\n    0x417B1DBC,0xB3109EBF,0xA0406D4B,0x522BEE48,0x86E18AA3,0x748A09A0,0x67DAFA54,0x95B17957,0xCBA24573,0x39C9C670,0x2A993584,0xD8F2B687,0x0C38D26C,0xFE53516F,0xED03A29B,0x1F682198,\n    0x5125DAD3,0xA34E59D0,0xB01EAA24,0x42752927,0x96BF4DCC,0x64D4CECF,0x77843D3B,0x85EFBE38,0xDBFC821C,0x2997011F,0x3AC7F2EB,0xC8AC71E8,0x1C661503,0xEE0D9600,0xFD5D65F4,0x0F36E6F7,\n    0x61C69362,0x93AD1061,0x80FDE395,0x72966096,0xA65C047D,0x5437877E,0x4767748A,0xB50CF789,0xEB1FCBAD,0x197448AE,0x0A24BB5A,0xF84F3859,0x2C855CB2,0xDEEEDFB1,0xCDBE2C45,0x3FD5AF46,\n    0x7198540D,0x83F3D70E,0x90A324FA,0x62C8A7F9,0xB602C312,0x44694011,0x5739B3E5,0xA55230E6,0xFB410CC2,0x092A8FC1,0x1A7A7C35,0xE811FF36,0x3CDB9BDD,0xCEB018DE,0xDDE0EB2A,0x2F8B6829,\n    0x82F63B78,0x709DB87B,0x63CD4B8F,0x91A6C88C,0x456CAC67,0xB7072F64,0xA457DC90,0x563C5F93,0x082F63B7,0xFA44E0B4,0xE9141340,0x1B7F9043,0xCFB5F4A8,0x3DDE77AB,0x2E8E845F,0xDCE5075C,\n    0x92A8FC17,0x60C37F14,0x73938CE0,0x81F80FE3,0x55326B08,0xA759E80B,0xB4091BFF,0x466298FC,0x1871A4D8,0xEA1A27DB,0xF94AD42F,0x0B21572C,0xDFEB33C7,0x2D80B0C4,0x3ED04330,0xCCBBC033,\n    0xA24BB5A6,0x502036A5,0x4370C551,0xB11B4652,0x65D122B9,0x97BAA1BA,0x84EA524E,0x7681D14D,0x2892ED69,0xDAF96E6A,0xC9A99D9E,0x3BC21E9D,0xEF087A76,0x1D63F975,0x0E330A81,0xFC588982,\n    0xB21572C9,0x407EF1CA,0x532E023E,0xA145813D,0x758FE5D6,0x87E466D5,0x94B49521,0x66DF1622,0x38CC2A06,0xCAA7A905,0xD9F75AF1,0x2B9CD9F2,0xFF56BD19,0x0D3D3E1A,0x1E6DCDEE,0xEC064EED,\n    0xC38D26C4,0x31E6A5C7,0x22B65633,0xD0DDD530,0x0417B1DB,0xF67C32D8,0xE52CC12C,0x1747422F,0x49547E0B,0xBB3FFD08,0xA86F0EFC,0x5A048DFF,0x8ECEE914,0x7CA56A17,0x6FF599E3,0x9D9E1AE0,\n    0xD3D3E1AB,0x21B862A8,0x32E8915C,0xC083125F,0x144976B4,0xE622F5B7,0xF5720643,0x07198540,0x590AB964,0xAB613A67,0xB831C993,0x4A5A4A90,0x9E902E7B,0x6CFBAD78,0x7FAB5E8C,0x8DC0DD8F,\n    0xE330A81A,0x115B2B19,0x020BD8ED,0xF0605BEE,0x24AA3F05,0xD6C1BC06,0xC5914FF2,0x37FACCF1,0x69E9F0D5,0x9B8273D6,0x88D28022,0x7AB90321,0xAE7367CA,0x5C18E4C9,0x4F48173D,0xBD23943E,\n    0xF36E6F75,0x0105EC76,0x12551F82,0xE03E9C81,0x34F4F86A,0xC69F7B69,0xD5CF889D,0x27A40B9E,0x79B737BA,0x8BDCB4B9,0x988C474D,0x6AE7C44E,0xBE2DA0A5,0x4C4623A6,0x5F16D052,0xAD7D5351\n#endif\n};\n#endif\n\n// Known size hash\n// It is ok to call ImHashData on a string with known length but the ### operator won't be supported.\n// FIXME-OPT: Replace with e.g. FNV1a hash? CRC32 pretty much randomly access 1KB. Need to do proper measurements.\nImGuiID ImHashData(const void* data_p, size_t data_size, ImGuiID seed)\n{\n    ImU32 crc = ~seed;\n    const unsigned char* data = (const unsigned char*)data_p;\n    const unsigned char *data_end = (const unsigned char*)data_p + data_size;\n#ifndef IMGUI_ENABLE_SSE4_2_CRC\n    const ImU32* crc32_lut = GCrc32LookupTable;\n    while (data < data_end)\n        crc = (crc >> 8) ^ crc32_lut[(crc & 0xFF) ^ *data++];\n    return ~crc;\n#else\n    while (data + 4 <= data_end)\n    {\n        crc = _mm_crc32_u32(crc, *(ImU32*)data);\n        data += 4;\n    }\n    while (data < data_end)\n        crc = _mm_crc32_u8(crc, *data++);\n    return ~crc;\n#endif\n}\n\n// Zero-terminated string hash, with support for ### to reset back to seed value\n// We support a syntax of \"label###id\" where only \"###id\" is included in the hash, and only \"label\" gets displayed.\n// Because this syntax is rarely used we are optimizing for the common case.\n// - If we reach ### in the string we discard the hash so far and reset to the seed.\n// - We don't do 'current += 2; continue;' after handling ### to keep the code smaller/faster (measured ~10% diff in Debug build)\n// FIXME-OPT: Replace with e.g. FNV1a hash? CRC32 pretty much randomly access 1KB. Need to do proper measurements.\nImGuiID ImHashStr(const char* data_p, size_t data_size, ImGuiID seed)\n{\n    seed = ~seed;\n    ImU32 crc = seed;\n    const unsigned char* data = (const unsigned char*)data_p;\n#ifndef IMGUI_ENABLE_SSE4_2_CRC\n    const ImU32* crc32_lut = GCrc32LookupTable;\n#endif\n    if (data_size != 0)\n    {\n        while (data_size-- != 0)\n        {\n            unsigned char c = *data++;\n            if (c == '#' && data_size >= 2 && data[0] == '#' && data[1] == '#')\n                crc = seed;\n#ifndef IMGUI_ENABLE_SSE4_2_CRC\n            crc = (crc >> 8) ^ crc32_lut[(crc & 0xFF) ^ c];\n#else\n            crc = _mm_crc32_u8(crc, c);\n#endif\n        }\n    }\n    else\n    {\n        while (unsigned char c = *data++)\n        {\n            if (c == '#' && data[0] == '#' && data[1] == '#')\n                crc = seed;\n#ifndef IMGUI_ENABLE_SSE4_2_CRC\n            crc = (crc >> 8) ^ crc32_lut[(crc & 0xFF) ^ c];\n#else\n            crc = _mm_crc32_u8(crc, c);\n#endif\n        }\n    }\n    return ~crc;\n}\n\n//-----------------------------------------------------------------------------\n// [SECTION] MISC HELPERS/UTILITIES (File functions)\n//-----------------------------------------------------------------------------\n\n// Default file functions\n#ifndef IMGUI_DISABLE_DEFAULT_FILE_FUNCTIONS\n\nImFileHandle ImFileOpen(const char* filename, const char* mode)\n{\n#if defined(_WIN32) && !defined(IMGUI_DISABLE_WIN32_FUNCTIONS) && (defined(__MINGW32__) || (!defined(__CYGWIN__) && !defined(__GNUC__)))\n    // We need a fopen() wrapper because MSVC/Windows fopen doesn't handle UTF-8 filenames.\n    // Previously we used ImTextCountCharsFromUtf8/ImTextStrFromUtf8 here but we now need to support ImWchar16 and ImWchar32!\n    const int filename_wsize = ::MultiByteToWideChar(CP_UTF8, 0, filename, -1, NULL, 0);\n    const int mode_wsize = ::MultiByteToWideChar(CP_UTF8, 0, mode, -1, NULL, 0);\n\n    // Use stack buffer if possible, otherwise heap buffer. Sizes include zero terminator.\n    // We don't rely on current ImGuiContext as this is implied to be a helper function which doesn't depend on it (see #7314).\n    wchar_t local_temp_stack[FILENAME_MAX];\n    ImVector<wchar_t> local_temp_heap;\n    if (filename_wsize + mode_wsize > IM_ARRAYSIZE(local_temp_stack))\n        local_temp_heap.resize(filename_wsize + mode_wsize);\n    wchar_t* filename_wbuf = local_temp_heap.Data ? local_temp_heap.Data : local_temp_stack;\n    wchar_t* mode_wbuf = filename_wbuf + filename_wsize;\n    ::MultiByteToWideChar(CP_UTF8, 0, filename, -1, filename_wbuf, filename_wsize);\n    ::MultiByteToWideChar(CP_UTF8, 0, mode, -1, mode_wbuf, mode_wsize);\n    return ::_wfopen(filename_wbuf, mode_wbuf);\n#else\n    return fopen(filename, mode);\n#endif\n}\n\n// We should in theory be using fseeko()/ftello() with off_t and _fseeki64()/_ftelli64() with __int64, waiting for the PR that does that in a very portable pre-C++11 zero-warnings way.\nbool    ImFileClose(ImFileHandle f)     { return fclose(f) == 0; }\nImU64   ImFileGetSize(ImFileHandle f)   { long off = 0, sz = 0; return ((off = ftell(f)) != -1 && !fseek(f, 0, SEEK_END) && (sz = ftell(f)) != -1 && !fseek(f, off, SEEK_SET)) ? (ImU64)sz : (ImU64)-1; }\nImU64   ImFileRead(void* data, ImU64 sz, ImU64 count, ImFileHandle f)           { return fread(data, (size_t)sz, (size_t)count, f); }\nImU64   ImFileWrite(const void* data, ImU64 sz, ImU64 count, ImFileHandle f)    { return fwrite(data, (size_t)sz, (size_t)count, f); }\n#endif // #ifndef IMGUI_DISABLE_DEFAULT_FILE_FUNCTIONS\n\n// Helper: Load file content into memory\n// Memory allocated with IM_ALLOC(), must be freed by user using IM_FREE() == ImGui::MemFree()\n// This can't really be used with \"rt\" because fseek size won't match read size.\nvoid*   ImFileLoadToMemory(const char* filename, const char* mode, size_t* out_file_size, int padding_bytes)\n{\n    IM_ASSERT(filename && mode);\n    if (out_file_size)\n        *out_file_size = 0;\n\n    ImFileHandle f;\n    if ((f = ImFileOpen(filename, mode)) == NULL)\n        return NULL;\n\n    size_t file_size = (size_t)ImFileGetSize(f);\n    if (file_size == (size_t)-1)\n    {\n        ImFileClose(f);\n        return NULL;\n    }\n\n    void* file_data = IM_ALLOC(file_size + padding_bytes);\n    if (file_data == NULL)\n    {\n        ImFileClose(f);\n        return NULL;\n    }\n    if (ImFileRead(file_data, 1, file_size, f) != file_size)\n    {\n        ImFileClose(f);\n        IM_FREE(file_data);\n        return NULL;\n    }\n    if (padding_bytes > 0)\n        memset((void*)(((char*)file_data) + file_size), 0, (size_t)padding_bytes);\n\n    ImFileClose(f);\n    if (out_file_size)\n        *out_file_size = file_size;\n\n    return file_data;\n}\n\n//-----------------------------------------------------------------------------\n// [SECTION] MISC HELPERS/UTILITIES (ImText* functions)\n//-----------------------------------------------------------------------------\n\nIM_MSVC_RUNTIME_CHECKS_OFF\n\n// Convert UTF-8 to 32-bit character, process single character input.\n// A nearly-branchless UTF-8 decoder, based on work of Christopher Wellons (https://github.com/skeeto/branchless-utf8).\n// We handle UTF-8 decoding error by skipping forward.\nint ImTextCharFromUtf8(unsigned int* out_char, const char* in_text, const char* in_text_end)\n{\n    static const char lengths[32] = { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 3, 3, 4, 0 };\n    static const int masks[]  = { 0x00, 0x7f, 0x1f, 0x0f, 0x07 };\n    static const uint32_t mins[] = { 0x400000, 0, 0x80, 0x800, 0x10000 };\n    static const int shiftc[] = { 0, 18, 12, 6, 0 };\n    static const int shifte[] = { 0, 6, 4, 2, 0 };\n    int len = lengths[*(const unsigned char*)in_text >> 3];\n    int wanted = len + (len ? 0 : 1);\n\n    if (in_text_end == NULL)\n        in_text_end = in_text + wanted; // Max length, nulls will be taken into account.\n\n    // Copy at most 'len' bytes, stop copying at 0 or past in_text_end. Branch predictor does a good job here,\n    // so it is fast even with excessive branching.\n    unsigned char s[4];\n    s[0] = in_text + 0 < in_text_end ? in_text[0] : 0;\n    s[1] = in_text + 1 < in_text_end ? in_text[1] : 0;\n    s[2] = in_text + 2 < in_text_end ? in_text[2] : 0;\n    s[3] = in_text + 3 < in_text_end ? in_text[3] : 0;\n\n    // Assume a four-byte character and load four bytes. Unused bits are shifted out.\n    *out_char  = (uint32_t)(s[0] & masks[len]) << 18;\n    *out_char |= (uint32_t)(s[1] & 0x3f) << 12;\n    *out_char |= (uint32_t)(s[2] & 0x3f) <<  6;\n    *out_char |= (uint32_t)(s[3] & 0x3f) <<  0;\n    *out_char >>= shiftc[len];\n\n    // Accumulate the various error conditions.\n    int e = 0;\n    e  = (*out_char < mins[len]) << 6; // non-canonical encoding\n    e |= ((*out_char >> 11) == 0x1b) << 7;  // surrogate half?\n    e |= (*out_char > IM_UNICODE_CODEPOINT_MAX) << 8;  // out of range we can store in ImWchar (FIXME: May evolve)\n    e |= (s[1] & 0xc0) >> 2;\n    e |= (s[2] & 0xc0) >> 4;\n    e |= (s[3]       ) >> 6;\n    e ^= 0x2a; // top two bits of each tail byte correct?\n    e >>= shifte[len];\n\n    if (e)\n    {\n        // No bytes are consumed when *in_text == 0 || in_text == in_text_end.\n        // One byte is consumed in case of invalid first byte of in_text.\n        // All available bytes (at most `len` bytes) are consumed on incomplete/invalid second to last bytes.\n        // Invalid or incomplete input may consume less bytes than wanted, therefore every byte has to be inspected in s.\n        wanted = ImMin(wanted, !!s[0] + !!s[1] + !!s[2] + !!s[3]);\n        *out_char = IM_UNICODE_CODEPOINT_INVALID;\n    }\n\n    return wanted;\n}\n\nint ImTextStrFromUtf8(ImWchar* buf, int buf_size, const char* in_text, const char* in_text_end, const char** in_text_remaining)\n{\n    ImWchar* buf_out = buf;\n    ImWchar* buf_end = buf + buf_size;\n    while (buf_out < buf_end - 1 && (!in_text_end || in_text < in_text_end) && *in_text)\n    {\n        unsigned int c;\n        in_text += ImTextCharFromUtf8(&c, in_text, in_text_end);\n        *buf_out++ = (ImWchar)c;\n    }\n    *buf_out = 0;\n    if (in_text_remaining)\n        *in_text_remaining = in_text;\n    return (int)(buf_out - buf);\n}\n\nint ImTextCountCharsFromUtf8(const char* in_text, const char* in_text_end)\n{\n    int char_count = 0;\n    while ((!in_text_end || in_text < in_text_end) && *in_text)\n    {\n        unsigned int c;\n        in_text += ImTextCharFromUtf8(&c, in_text, in_text_end);\n        char_count++;\n    }\n    return char_count;\n}\n\n// Based on stb_to_utf8() from github.com/nothings/stb/\nstatic inline int ImTextCharToUtf8_inline(char* buf, int buf_size, unsigned int c)\n{\n    if (c < 0x80)\n    {\n        buf[0] = (char)c;\n        return 1;\n    }\n    if (c < 0x800)\n    {\n        if (buf_size < 2) return 0;\n        buf[0] = (char)(0xc0 + (c >> 6));\n        buf[1] = (char)(0x80 + (c & 0x3f));\n        return 2;\n    }\n    if (c < 0x10000)\n    {\n        if (buf_size < 3) return 0;\n        buf[0] = (char)(0xe0 + (c >> 12));\n        buf[1] = (char)(0x80 + ((c >> 6) & 0x3f));\n        buf[2] = (char)(0x80 + ((c ) & 0x3f));\n        return 3;\n    }\n    if (c <= 0x10FFFF)\n    {\n        if (buf_size < 4) return 0;\n        buf[0] = (char)(0xf0 + (c >> 18));\n        buf[1] = (char)(0x80 + ((c >> 12) & 0x3f));\n        buf[2] = (char)(0x80 + ((c >> 6) & 0x3f));\n        buf[3] = (char)(0x80 + ((c ) & 0x3f));\n        return 4;\n    }\n    // Invalid code point, the max unicode is 0x10FFFF\n    return 0;\n}\n\nconst char* ImTextCharToUtf8(char out_buf[5], unsigned int c)\n{\n    int count = ImTextCharToUtf8_inline(out_buf, 5, c);\n    out_buf[count] = 0;\n    return out_buf;\n}\n\n// Not optimal but we very rarely use this function.\nint ImTextCountUtf8BytesFromChar(const char* in_text, const char* in_text_end)\n{\n    unsigned int unused = 0;\n    return ImTextCharFromUtf8(&unused, in_text, in_text_end);\n}\n\nstatic inline int ImTextCountUtf8BytesFromChar(unsigned int c)\n{\n    if (c < 0x80) return 1;\n    if (c < 0x800) return 2;\n    if (c < 0x10000) return 3;\n    if (c <= 0x10FFFF) return 4;\n    return 3;\n}\n\nint ImTextStrToUtf8(char* out_buf, int out_buf_size, const ImWchar* in_text, const ImWchar* in_text_end)\n{\n    char* buf_p = out_buf;\n    const char* buf_end = out_buf + out_buf_size;\n    while (buf_p < buf_end - 1 && (!in_text_end || in_text < in_text_end) && *in_text)\n    {\n        unsigned int c = (unsigned int)(*in_text++);\n        if (c < 0x80)\n            *buf_p++ = (char)c;\n        else\n            buf_p += ImTextCharToUtf8_inline(buf_p, (int)(buf_end - buf_p - 1), c);\n    }\n    *buf_p = 0;\n    return (int)(buf_p - out_buf);\n}\n\nint ImTextCountUtf8BytesFromStr(const ImWchar* in_text, const ImWchar* in_text_end)\n{\n    int bytes_count = 0;\n    while ((!in_text_end || in_text < in_text_end) && *in_text)\n    {\n        unsigned int c = (unsigned int)(*in_text++);\n        if (c < 0x80)\n            bytes_count++;\n        else\n            bytes_count += ImTextCountUtf8BytesFromChar(c);\n    }\n    return bytes_count;\n}\n\nconst char* ImTextFindPreviousUtf8Codepoint(const char* in_text_start, const char* in_text_curr)\n{\n    while (in_text_curr > in_text_start)\n    {\n        in_text_curr--;\n        if ((*in_text_curr & 0xC0) != 0x80)\n            return in_text_curr;\n    }\n    return in_text_start;\n}\n\nint ImTextCountLines(const char* in_text, const char* in_text_end)\n{\n    if (in_text_end == NULL)\n        in_text_end = in_text + ImStrlen(in_text); // FIXME-OPT: Not optimal approach, discourage use for now.\n    int count = 0;\n    while (in_text < in_text_end)\n    {\n        const char* line_end = (const char*)ImMemchr(in_text, '\\n', in_text_end - in_text);\n        in_text = line_end ? line_end + 1 : in_text_end;\n        count++;\n    }\n    return count;\n}\n\nIM_MSVC_RUNTIME_CHECKS_RESTORE\n\n//-----------------------------------------------------------------------------\n// [SECTION] MISC HELPERS/UTILITIES (Color functions)\n// Note: The Convert functions are early design which are not consistent with other API.\n//-----------------------------------------------------------------------------\n\nIMGUI_API ImU32 ImAlphaBlendColors(ImU32 col_a, ImU32 col_b)\n{\n    float t = ((col_b >> IM_COL32_A_SHIFT) & 0xFF) / 255.f;\n    int r = ImLerp((int)(col_a >> IM_COL32_R_SHIFT) & 0xFF, (int)(col_b >> IM_COL32_R_SHIFT) & 0xFF, t);\n    int g = ImLerp((int)(col_a >> IM_COL32_G_SHIFT) & 0xFF, (int)(col_b >> IM_COL32_G_SHIFT) & 0xFF, t);\n    int b = ImLerp((int)(col_a >> IM_COL32_B_SHIFT) & 0xFF, (int)(col_b >> IM_COL32_B_SHIFT) & 0xFF, t);\n    return IM_COL32(r, g, b, 0xFF);\n}\n\nImVec4 ImGui::ColorConvertU32ToFloat4(ImU32 in)\n{\n    float s = 1.0f / 255.0f;\n    return ImVec4(\n        ((in >> IM_COL32_R_SHIFT) & 0xFF) * s,\n        ((in >> IM_COL32_G_SHIFT) & 0xFF) * s,\n        ((in >> IM_COL32_B_SHIFT) & 0xFF) * s,\n        ((in >> IM_COL32_A_SHIFT) & 0xFF) * s);\n}\n\nImU32 ImGui::ColorConvertFloat4ToU32(const ImVec4& in)\n{\n    ImU32 out;\n    out  = ((ImU32)IM_F32_TO_INT8_SAT(in.x)) << IM_COL32_R_SHIFT;\n    out |= ((ImU32)IM_F32_TO_INT8_SAT(in.y)) << IM_COL32_G_SHIFT;\n    out |= ((ImU32)IM_F32_TO_INT8_SAT(in.z)) << IM_COL32_B_SHIFT;\n    out |= ((ImU32)IM_F32_TO_INT8_SAT(in.w)) << IM_COL32_A_SHIFT;\n    return out;\n}\n\n// Convert rgb floats ([0-1],[0-1],[0-1]) to hsv floats ([0-1],[0-1],[0-1]), from Foley & van Dam p592\n// Optimized http://lolengine.net/blog/2013/01/13/fast-rgb-to-hsv\nvoid ImGui::ColorConvertRGBtoHSV(float r, float g, float b, float& out_h, float& out_s, float& out_v)\n{\n    float K = 0.f;\n    if (g < b)\n    {\n        ImSwap(g, b);\n        K = -1.f;\n    }\n    if (r < g)\n    {\n        ImSwap(r, g);\n        K = -2.f / 6.f - K;\n    }\n\n    const float chroma = r - (g < b ? g : b);\n    out_h = ImFabs(K + (g - b) / (6.f * chroma + 1e-20f));\n    out_s = chroma / (r + 1e-20f);\n    out_v = r;\n}\n\n// Convert hsv floats ([0-1],[0-1],[0-1]) to rgb floats ([0-1],[0-1],[0-1]), from Foley & van Dam p593\n// also http://en.wikipedia.org/wiki/HSL_and_HSV\nvoid ImGui::ColorConvertHSVtoRGB(float h, float s, float v, float& out_r, float& out_g, float& out_b)\n{\n    if (s == 0.0f)\n    {\n        // gray\n        out_r = out_g = out_b = v;\n        return;\n    }\n\n    h = ImFmod(h, 1.0f) / (60.0f / 360.0f);\n    int   i = (int)h;\n    float f = h - (float)i;\n    float p = v * (1.0f - s);\n    float q = v * (1.0f - s * f);\n    float t = v * (1.0f - s * (1.0f - f));\n\n    switch (i)\n    {\n    case 0: out_r = v; out_g = t; out_b = p; break;\n    case 1: out_r = q; out_g = v; out_b = p; break;\n    case 2: out_r = p; out_g = v; out_b = t; break;\n    case 3: out_r = p; out_g = q; out_b = v; break;\n    case 4: out_r = t; out_g = p; out_b = v; break;\n    case 5: default: out_r = v; out_g = p; out_b = q; break;\n    }\n}\n\n//-----------------------------------------------------------------------------\n// [SECTION] ImGuiStorage\n// Helper: Key->value storage\n//-----------------------------------------------------------------------------\n\n// std::lower_bound but without the bullshit\nImGuiStoragePair* ImLowerBound(ImGuiStoragePair* in_begin, ImGuiStoragePair* in_end, ImGuiID key)\n{\n    ImGuiStoragePair* in_p = in_begin;\n    for (size_t count = (size_t)(in_end - in_p); count > 0; )\n    {\n        size_t count2 = count >> 1;\n        ImGuiStoragePair* mid = in_p + count2;\n        if (mid->key < key)\n        {\n            in_p = ++mid;\n            count -= count2 + 1;\n        }\n        else\n        {\n            count = count2;\n        }\n    }\n    return in_p;\n}\n\nIM_MSVC_RUNTIME_CHECKS_OFF\nstatic int IMGUI_CDECL PairComparerByID(const void* lhs, const void* rhs)\n{\n    // We can't just do a subtraction because qsort uses signed integers and subtracting our ID doesn't play well with that.\n    ImGuiID lhs_v = ((const ImGuiStoragePair*)lhs)->key;\n    ImGuiID rhs_v = ((const ImGuiStoragePair*)rhs)->key;\n    return (lhs_v > rhs_v ? +1 : lhs_v < rhs_v ? -1 : 0);\n}\n\n// For quicker full rebuild of a storage (instead of an incremental one), you may add all your contents and then sort once.\nvoid ImGuiStorage::BuildSortByKey()\n{\n    ImQsort(Data.Data, (size_t)Data.Size, sizeof(ImGuiStoragePair), PairComparerByID);\n}\n\nint ImGuiStorage::GetInt(ImGuiID key, int default_val) const\n{\n    ImGuiStoragePair* it = ImLowerBound(const_cast<ImGuiStoragePair*>(Data.Data), const_cast<ImGuiStoragePair*>(Data.Data + Data.Size), key);\n    if (it == Data.Data + Data.Size || it->key != key)\n        return default_val;\n    return it->val_i;\n}\n\nbool ImGuiStorage::GetBool(ImGuiID key, bool default_val) const\n{\n    return GetInt(key, default_val ? 1 : 0) != 0;\n}\n\nfloat ImGuiStorage::GetFloat(ImGuiID key, float default_val) const\n{\n    ImGuiStoragePair* it = ImLowerBound(const_cast<ImGuiStoragePair*>(Data.Data), const_cast<ImGuiStoragePair*>(Data.Data + Data.Size), key);\n    if (it == Data.Data + Data.Size || it->key != key)\n        return default_val;\n    return it->val_f;\n}\n\nvoid* ImGuiStorage::GetVoidPtr(ImGuiID key) const\n{\n    ImGuiStoragePair* it = ImLowerBound(const_cast<ImGuiStoragePair*>(Data.Data), const_cast<ImGuiStoragePair*>(Data.Data + Data.Size), key);\n    if (it == Data.Data + Data.Size || it->key != key)\n        return NULL;\n    return it->val_p;\n}\n\n// References are only valid until a new value is added to the storage. Calling a Set***() function or a Get***Ref() function invalidates the pointer.\nint* ImGuiStorage::GetIntRef(ImGuiID key, int default_val)\n{\n    ImGuiStoragePair* it = ImLowerBound(Data.Data, Data.Data + Data.Size, key);\n    if (it == Data.Data + Data.Size || it->key != key)\n        it = Data.insert(it, ImGuiStoragePair(key, default_val));\n    return &it->val_i;\n}\n\nbool* ImGuiStorage::GetBoolRef(ImGuiID key, bool default_val)\n{\n    return (bool*)GetIntRef(key, default_val ? 1 : 0);\n}\n\nfloat* ImGuiStorage::GetFloatRef(ImGuiID key, float default_val)\n{\n    ImGuiStoragePair* it = ImLowerBound(Data.Data, Data.Data + Data.Size, key);\n    if (it == Data.Data + Data.Size || it->key != key)\n        it = Data.insert(it, ImGuiStoragePair(key, default_val));\n    return &it->val_f;\n}\n\nvoid** ImGuiStorage::GetVoidPtrRef(ImGuiID key, void* default_val)\n{\n    ImGuiStoragePair* it = ImLowerBound(Data.Data, Data.Data + Data.Size, key);\n    if (it == Data.Data + Data.Size || it->key != key)\n        it = Data.insert(it, ImGuiStoragePair(key, default_val));\n    return &it->val_p;\n}\n\n// FIXME-OPT: Need a way to reuse the result of lower_bound when doing GetInt()/SetInt() - not too bad because it only happens on explicit interaction (maximum one a frame)\nvoid ImGuiStorage::SetInt(ImGuiID key, int val)\n{\n    ImGuiStoragePair* it = ImLowerBound(Data.Data, Data.Data + Data.Size, key);\n    if (it == Data.Data + Data.Size || it->key != key)\n        Data.insert(it, ImGuiStoragePair(key, val));\n    else\n        it->val_i = val;\n}\n\nvoid ImGuiStorage::SetBool(ImGuiID key, bool val)\n{\n    SetInt(key, val ? 1 : 0);\n}\n\nvoid ImGuiStorage::SetFloat(ImGuiID key, float val)\n{\n    ImGuiStoragePair* it = ImLowerBound(Data.Data, Data.Data + Data.Size, key);\n    if (it == Data.Data + Data.Size || it->key != key)\n        Data.insert(it, ImGuiStoragePair(key, val));\n    else\n        it->val_f = val;\n}\n\nvoid ImGuiStorage::SetVoidPtr(ImGuiID key, void* val)\n{\n    ImGuiStoragePair* it = ImLowerBound(Data.Data, Data.Data + Data.Size, key);\n    if (it == Data.Data + Data.Size || it->key != key)\n        Data.insert(it, ImGuiStoragePair(key, val));\n    else\n        it->val_p = val;\n}\n\nvoid ImGuiStorage::SetAllInt(int v)\n{\n    for (int i = 0; i < Data.Size; i++)\n        Data[i].val_i = v;\n}\nIM_MSVC_RUNTIME_CHECKS_RESTORE\n\n//-----------------------------------------------------------------------------\n// [SECTION] ImGuiTextFilter\n//-----------------------------------------------------------------------------\n\n// Helper: Parse and apply text filters. In format \"aaaaa[,bbbb][,ccccc]\"\nImGuiTextFilter::ImGuiTextFilter(const char* default_filter) //-V1077\n{\n    InputBuf[0] = 0;\n    CountGrep = 0;\n    if (default_filter)\n    {\n        ImStrncpy(InputBuf, default_filter, IM_ARRAYSIZE(InputBuf));\n        Build();\n    }\n}\n\nbool ImGuiTextFilter::Draw(const char* label, float width)\n{\n    if (width != 0.0f)\n        ImGui::SetNextItemWidth(width);\n    bool value_changed = ImGui::InputText(label, InputBuf, IM_ARRAYSIZE(InputBuf));\n    if (value_changed)\n        Build();\n    return value_changed;\n}\n\nvoid ImGuiTextFilter::ImGuiTextRange::split(char separator, ImVector<ImGuiTextRange>* out) const\n{\n    out->resize(0);\n    const char* wb = b;\n    const char* we = wb;\n    while (we < e)\n    {\n        if (*we == separator)\n        {\n            out->push_back(ImGuiTextRange(wb, we));\n            wb = we + 1;\n        }\n        we++;\n    }\n    if (wb != we)\n        out->push_back(ImGuiTextRange(wb, we));\n}\n\nvoid ImGuiTextFilter::Build()\n{\n    Filters.resize(0);\n    ImGuiTextRange input_range(InputBuf, InputBuf + ImStrlen(InputBuf));\n    input_range.split(',', &Filters);\n\n    CountGrep = 0;\n    for (ImGuiTextRange& f : Filters)\n    {\n        while (f.b < f.e && ImCharIsBlankA(f.b[0]))\n            f.b++;\n        while (f.e > f.b && ImCharIsBlankA(f.e[-1]))\n            f.e--;\n        if (f.empty())\n            continue;\n        if (f.b[0] != '-')\n            CountGrep += 1;\n    }\n}\n\nbool ImGuiTextFilter::PassFilter(const char* text, const char* text_end) const\n{\n    if (Filters.Size == 0)\n        return true;\n\n    if (text == NULL)\n        text = text_end = \"\";\n\n    for (const ImGuiTextRange& f : Filters)\n    {\n        if (f.b == f.e)\n            continue;\n        if (f.b[0] == '-')\n        {\n            // Subtract\n            if (ImStristr(text, text_end, f.b + 1, f.e) != NULL)\n                return false;\n        }\n        else\n        {\n            // Grep\n            if (ImStristr(text, text_end, f.b, f.e) != NULL)\n                return true;\n        }\n    }\n\n    // Implicit * grep\n    if (CountGrep == 0)\n        return true;\n\n    return false;\n}\n\n//-----------------------------------------------------------------------------\n// [SECTION] ImGuiTextBuffer, ImGuiTextIndex\n//-----------------------------------------------------------------------------\n\n// On some platform vsnprintf() takes va_list by reference and modifies it.\n// va_copy is the 'correct' way to copy a va_list but Visual Studio prior to 2013 doesn't have it.\n#ifndef va_copy\n#if defined(__GNUC__) || defined(__clang__)\n#define va_copy(dest, src) __builtin_va_copy(dest, src)\n#else\n#define va_copy(dest, src) (dest = src)\n#endif\n#endif\n\nchar ImGuiTextBuffer::EmptyString[1] = { 0 };\n\nvoid ImGuiTextBuffer::append(const char* str, const char* str_end)\n{\n    int len = str_end ? (int)(str_end - str) : (int)ImStrlen(str);\n\n    // Add zero-terminator the first time\n    const int write_off = (Buf.Size != 0) ? Buf.Size : 1;\n    const int needed_sz = write_off + len;\n    if (write_off + len >= Buf.Capacity)\n    {\n        int new_capacity = Buf.Capacity * 2;\n        Buf.reserve(needed_sz > new_capacity ? needed_sz : new_capacity);\n    }\n\n    Buf.resize(needed_sz);\n    memcpy(&Buf[write_off - 1], str, (size_t)len);\n    Buf[write_off - 1 + len] = 0;\n}\n\nvoid ImGuiTextBuffer::appendf(const char* fmt, ...)\n{\n    va_list args;\n    va_start(args, fmt);\n    appendfv(fmt, args);\n    va_end(args);\n}\n\n// Helper: Text buffer for logging/accumulating text\nvoid ImGuiTextBuffer::appendfv(const char* fmt, va_list args)\n{\n    va_list args_copy;\n    va_copy(args_copy, args);\n\n    int len = ImFormatStringV(NULL, 0, fmt, args);         // FIXME-OPT: could do a first pass write attempt, likely successful on first pass.\n    if (len <= 0)\n    {\n        va_end(args_copy);\n        return;\n    }\n\n    // Add zero-terminator the first time\n    const int write_off = (Buf.Size != 0) ? Buf.Size : 1;\n    const int needed_sz = write_off + len;\n    if (write_off + len >= Buf.Capacity)\n    {\n        int new_capacity = Buf.Capacity * 2;\n        Buf.reserve(needed_sz > new_capacity ? needed_sz : new_capacity);\n    }\n\n    Buf.resize(needed_sz);\n    ImFormatStringV(&Buf[write_off - 1], (size_t)len + 1, fmt, args_copy);\n    va_end(args_copy);\n}\n\nvoid ImGuiTextIndex::append(const char* base, int old_size, int new_size)\n{\n    IM_ASSERT(old_size >= 0 && new_size >= old_size && new_size >= EndOffset);\n    if (old_size == new_size)\n        return;\n    if (EndOffset == 0 || base[EndOffset - 1] == '\\n')\n        LineOffsets.push_back(EndOffset);\n    const char* base_end = base + new_size;\n    for (const char* p = base + old_size; (p = (const char*)ImMemchr(p, '\\n', base_end - p)) != 0; )\n        if (++p < base_end) // Don't push a trailing offset on last \\n\n            LineOffsets.push_back((int)(intptr_t)(p - base));\n    EndOffset = ImMax(EndOffset, new_size);\n}\n\n//-----------------------------------------------------------------------------\n// [SECTION] ImGuiListClipper\n//-----------------------------------------------------------------------------\n\n// FIXME-TABLE: This prevents us from using ImGuiListClipper _inside_ a table cell.\n// The problem we have is that without a Begin/End scheme for rows using the clipper is ambiguous.\nstatic bool GetSkipItemForListClipping()\n{\n    ImGuiContext& g = *GImGui;\n    return (g.CurrentTable ? g.CurrentTable->HostSkipItems : g.CurrentWindow->SkipItems);\n}\n\nstatic void ImGuiListClipper_SortAndFuseRanges(ImVector<ImGuiListClipperRange>& ranges, int offset = 0)\n{\n    if (ranges.Size - offset <= 1)\n        return;\n\n    // Helper to order ranges and fuse them together if possible (bubble sort is fine as we are only sorting 2-3 entries)\n    for (int sort_end = ranges.Size - offset - 1; sort_end > 0; --sort_end)\n        for (int i = offset; i < sort_end + offset; ++i)\n            if (ranges[i].Min > ranges[i + 1].Min)\n                ImSwap(ranges[i], ranges[i + 1]);\n\n    // Now fuse ranges together as much as possible.\n    for (int i = 1 + offset; i < ranges.Size; i++)\n    {\n        IM_ASSERT(!ranges[i].PosToIndexConvert && !ranges[i - 1].PosToIndexConvert);\n        if (ranges[i - 1].Max < ranges[i].Min)\n            continue;\n        ranges[i - 1].Min = ImMin(ranges[i - 1].Min, ranges[i].Min);\n        ranges[i - 1].Max = ImMax(ranges[i - 1].Max, ranges[i].Max);\n        ranges.erase(ranges.Data + i);\n        i--;\n    }\n}\n\nstatic void ImGuiListClipper_SeekCursorAndSetupPrevLine(float pos_y, float line_height)\n{\n    // Set cursor position and a few other things so that SetScrollHereY() and Columns() can work when seeking cursor.\n    // FIXME: It is problematic that we have to do that here, because custom/equivalent end-user code would stumble on the same issue.\n    // The clipper should probably have a final step to display the last item in a regular manner, maybe with an opt-out flag for data sets which may have costly seek?\n    ImGuiContext& g = *GImGui;\n    ImGuiWindow* window = g.CurrentWindow;\n    float off_y = pos_y - window->DC.CursorPos.y;\n    window->DC.CursorPos.y = pos_y;\n    window->DC.CursorMaxPos.y = ImMax(window->DC.CursorMaxPos.y, pos_y - g.Style.ItemSpacing.y);\n    window->DC.CursorPosPrevLine.y = window->DC.CursorPos.y - line_height;  // Setting those fields so that SetScrollHereY() can properly function after the end of our clipper usage.\n    window->DC.PrevLineSize.y = (line_height - g.Style.ItemSpacing.y);      // If we end up needing more accurate data (to e.g. use SameLine) we may as well make the clipper have a fourth step to let user process and display the last item in their list.\n    if (ImGuiOldColumns* columns = window->DC.CurrentColumns)\n        columns->LineMinY = window->DC.CursorPos.y;                         // Setting this so that cell Y position are set properly\n    if (ImGuiTable* table = g.CurrentTable)\n    {\n        if (table->IsInsideRow)\n            ImGui::TableEndRow(table);\n        table->RowPosY2 = window->DC.CursorPos.y;\n        const int row_increase = (int)((off_y / line_height) + 0.5f);\n        //table->CurrentRow += row_increase; // Can't do without fixing TableEndRow()\n        table->RowBgColorCounter += row_increase;\n    }\n}\n\nImGuiListClipper::ImGuiListClipper()\n{\n    memset(this, 0, sizeof(*this));\n}\n\nImGuiListClipper::~ImGuiListClipper()\n{\n    End();\n}\n\nvoid ImGuiListClipper::Begin(int items_count, float items_height)\n{\n    if (Ctx == NULL)\n        Ctx = ImGui::GetCurrentContext();\n\n    ImGuiContext& g = *Ctx;\n    ImGuiWindow* window = g.CurrentWindow;\n    IMGUI_DEBUG_LOG_CLIPPER(\"Clipper: Begin(%d,%.2f) in '%s'\\n\", items_count, items_height, window->Name);\n\n    if (ImGuiTable* table = g.CurrentTable)\n        if (table->IsInsideRow)\n            ImGui::TableEndRow(table);\n\n    StartPosY = window->DC.CursorPos.y;\n    ItemsHeight = items_height;\n    ItemsCount = items_count;\n    DisplayStart = -1;\n    DisplayEnd = 0;\n\n    // Acquire temporary buffer\n    if (++g.ClipperTempDataStacked > g.ClipperTempData.Size)\n        g.ClipperTempData.resize(g.ClipperTempDataStacked, ImGuiListClipperData());\n    ImGuiListClipperData* data = &g.ClipperTempData[g.ClipperTempDataStacked - 1];\n    data->Reset(this);\n    data->LossynessOffset = window->DC.CursorStartPosLossyness.y;\n    TempData = data;\n    StartSeekOffsetY = data->LossynessOffset;\n}\n\nvoid ImGuiListClipper::End()\n{\n    if (ImGuiListClipperData* data = (ImGuiListClipperData*)TempData)\n    {\n        // In theory here we should assert that we are already at the right position, but it seems saner to just seek at the end and not assert/crash the user.\n        ImGuiContext& g = *Ctx;\n        IMGUI_DEBUG_LOG_CLIPPER(\"Clipper: End() in '%s'\\n\", g.CurrentWindow->Name);\n        if (ItemsCount >= 0 && ItemsCount < INT_MAX && DisplayStart >= 0)\n            SeekCursorForItem(ItemsCount);\n\n        // Restore temporary buffer and fix back pointers which may be invalidated when nesting\n        IM_ASSERT(data->ListClipper == this);\n        data->StepNo = data->Ranges.Size;\n        if (--g.ClipperTempDataStacked > 0)\n        {\n            data = &g.ClipperTempData[g.ClipperTempDataStacked - 1];\n            data->ListClipper->TempData = data;\n        }\n        TempData = NULL;\n    }\n    ItemsCount = -1;\n}\n\nvoid ImGuiListClipper::IncludeItemsByIndex(int item_begin, int item_end)\n{\n    ImGuiListClipperData* data = (ImGuiListClipperData*)TempData;\n    IM_ASSERT(DisplayStart < 0); // Only allowed after Begin() and if there has not been a specified range yet.\n    IM_ASSERT(item_begin <= item_end);\n    if (item_begin < item_end)\n        data->Ranges.push_back(ImGuiListClipperRange::FromIndices(item_begin, item_end));\n}\n\n// This is already called while stepping.\n// The ONLY reason you may want to call this is if you passed INT_MAX to ImGuiListClipper::Begin() because you couldn't step item count beforehand.\nvoid ImGuiListClipper::SeekCursorForItem(int item_n)\n{\n    // - Perform the add and multiply with double to allow seeking through larger ranges.\n    // - StartPosY starts from ItemsFrozen, by adding SeekOffsetY we generally cancel that out (SeekOffsetY == LossynessOffset - ItemsFrozen * ItemsHeight).\n    // - The reason we store SeekOffsetY instead of inferring it, is because we want to allow user to perform Seek after the last step, where ImGuiListClipperData is already done.\n    float pos_y = (float)((double)StartPosY + StartSeekOffsetY + (double)item_n * ItemsHeight);\n    ImGuiListClipper_SeekCursorAndSetupPrevLine(pos_y, ItemsHeight);\n}\n\nstatic bool ImGuiListClipper_StepInternal(ImGuiListClipper* clipper)\n{\n    ImGuiContext& g = *clipper->Ctx;\n    ImGuiWindow* window = g.CurrentWindow;\n    ImGuiListClipperData* data = (ImGuiListClipperData*)clipper->TempData;\n    IM_ASSERT(data != NULL && \"Called ImGuiListClipper::Step() too many times, or before ImGuiListClipper::Begin() ?\");\n\n    ImGuiTable* table = g.CurrentTable;\n    if (table && table->IsInsideRow)\n        ImGui::TableEndRow(table);\n\n    // No items\n    if (clipper->ItemsCount == 0 || GetSkipItemForListClipping())\n        return false;\n\n    // While we are in frozen row state, keep displaying items one by one, unclipped\n    // FIXME: Could be stored as a table-agnostic state.\n    if (data->StepNo == 0 && table != NULL && !table->IsUnfrozenRows)\n    {\n        clipper->DisplayStart = data->ItemsFrozen;\n        clipper->DisplayEnd = ImMin(data->ItemsFrozen + 1, clipper->ItemsCount);\n        if (clipper->DisplayStart < clipper->DisplayEnd)\n            data->ItemsFrozen++;\n        return true;\n    }\n\n    // Step 0: Let you process the first element (regardless of it being visible or not, so we can measure the element height)\n    bool calc_clipping = false;\n    if (data->StepNo == 0)\n    {\n        clipper->StartPosY = window->DC.CursorPos.y;\n        if (clipper->ItemsHeight <= 0.0f)\n        {\n            // Submit the first item (or range) so we can measure its height (generally the first range is 0..1)\n            data->Ranges.push_front(ImGuiListClipperRange::FromIndices(data->ItemsFrozen, data->ItemsFrozen + 1));\n            clipper->DisplayStart = ImMax(data->Ranges[0].Min, data->ItemsFrozen);\n            clipper->DisplayEnd = ImMin(data->Ranges[0].Max, clipper->ItemsCount);\n            data->StepNo = 1;\n            return true;\n        }\n        calc_clipping = true;   // If on the first step with known item height, calculate clipping.\n    }\n\n    // Step 1: Let the clipper infer height from first range\n    if (clipper->ItemsHeight <= 0.0f)\n    {\n        IM_ASSERT(data->StepNo == 1);\n        if (table)\n            IM_ASSERT(table->RowPosY1 == clipper->StartPosY && table->RowPosY2 == window->DC.CursorPos.y);\n\n        clipper->ItemsHeight = (window->DC.CursorPos.y - clipper->StartPosY) / (float)(clipper->DisplayEnd - clipper->DisplayStart);\n        bool affected_by_floating_point_precision = ImIsFloatAboveGuaranteedIntegerPrecision(clipper->StartPosY) || ImIsFloatAboveGuaranteedIntegerPrecision(window->DC.CursorPos.y);\n        if (affected_by_floating_point_precision)\n            clipper->ItemsHeight = window->DC.PrevLineSize.y + g.Style.ItemSpacing.y; // FIXME: Technically wouldn't allow multi-line entries.\n        if (clipper->ItemsHeight == 0.0f && clipper->ItemsCount == INT_MAX) // Accept that no item have been submitted if in indeterminate mode.\n            return false;\n        IM_ASSERT(clipper->ItemsHeight > 0.0f && \"Unable to calculate item height! First item hasn't moved the cursor vertically!\");\n        calc_clipping = true;   // If item height had to be calculated, calculate clipping afterwards.\n    }\n\n    // Step 0 or 1: Calculate the actual ranges of visible elements.\n    const int already_submitted = clipper->DisplayEnd;\n    if (calc_clipping)\n    {\n        // Record seek offset, this is so ImGuiListClipper::Seek() can be called after ImGuiListClipperData is done\n        clipper->StartSeekOffsetY = (double)data->LossynessOffset - data->ItemsFrozen * (double)clipper->ItemsHeight;\n\n        if (g.LogEnabled)\n        {\n            // If logging is active, do not perform any clipping\n            data->Ranges.push_back(ImGuiListClipperRange::FromIndices(0, clipper->ItemsCount));\n        }\n        else\n        {\n            // Add range selected to be included for navigation\n            const bool is_nav_request = (g.NavMoveScoringItems && g.NavWindow && g.NavWindow->RootWindowForNav == window->RootWindowForNav);\n            if (is_nav_request)\n                data->Ranges.push_back(ImGuiListClipperRange::FromPositions(g.NavScoringNoClipRect.Min.y, g.NavScoringNoClipRect.Max.y, 0, 0));\n            if (is_nav_request && (g.NavMoveFlags & ImGuiNavMoveFlags_IsTabbing) && g.NavTabbingDir == -1)\n                data->Ranges.push_back(ImGuiListClipperRange::FromIndices(clipper->ItemsCount - 1, clipper->ItemsCount));\n\n            // Add focused/active item\n            ImRect nav_rect_abs = ImGui::WindowRectRelToAbs(window, window->NavRectRel[0]);\n            if (g.NavId != 0 && window->NavLastIds[0] == g.NavId)\n                data->Ranges.push_back(ImGuiListClipperRange::FromPositions(nav_rect_abs.Min.y, nav_rect_abs.Max.y, 0, 0));\n\n            // Add visible range\n            float min_y = window->ClipRect.Min.y;\n            float max_y = window->ClipRect.Max.y;\n\n            // Add box selection range\n            ImGuiBoxSelectState* bs = &g.BoxSelectState;\n            if (bs->IsActive && bs->Window == window)\n            {\n                // FIXME: Selectable() use of half-ItemSpacing isn't consistent in matter of layout, as ItemAdd(bb) stray above ItemSize()'s CursorPos.\n                // RangeSelect's BoxSelect relies on comparing overlap of previous and current rectangle and is sensitive to that.\n                // As a workaround we currently half ItemSpacing worth on each side.\n                min_y -= g.Style.ItemSpacing.y;\n                max_y += g.Style.ItemSpacing.y;\n\n                // Box-select on 2D area requires different clipping.\n                if (bs->UnclipMode)\n                    data->Ranges.push_back(ImGuiListClipperRange::FromPositions(bs->UnclipRect.Min.y, bs->UnclipRect.Max.y, 0, 0));\n            }\n\n            const int off_min = (is_nav_request && g.NavMoveClipDir == ImGuiDir_Up) ? -1 : 0;\n            const int off_max = (is_nav_request && g.NavMoveClipDir == ImGuiDir_Down) ? 1 : 0;\n            data->Ranges.push_back(ImGuiListClipperRange::FromPositions(min_y, max_y, off_min, off_max));\n        }\n\n        // Convert position ranges to item index ranges\n        // - Very important: when a starting position is after our maximum item, we set Min to (ItemsCount - 1). This allows us to handle most forms of wrapping.\n        // - Due to how Selectable extra padding they tend to be \"unaligned\" with exact unit in the item list,\n        //   which with the flooring/ceiling tend to lead to 2 items instead of one being submitted.\n        for (ImGuiListClipperRange& range : data->Ranges)\n            if (range.PosToIndexConvert)\n            {\n                int m1 = (int)(((double)range.Min - window->DC.CursorPos.y - data->LossynessOffset) / clipper->ItemsHeight);\n                int m2 = (int)((((double)range.Max - window->DC.CursorPos.y - data->LossynessOffset) / clipper->ItemsHeight) + 0.999999f);\n                range.Min = ImClamp(already_submitted + m1 + range.PosToIndexOffsetMin, already_submitted, clipper->ItemsCount - 1);\n                range.Max = ImClamp(already_submitted + m2 + range.PosToIndexOffsetMax, range.Min + 1, clipper->ItemsCount);\n                range.PosToIndexConvert = false;\n            }\n        ImGuiListClipper_SortAndFuseRanges(data->Ranges, data->StepNo);\n    }\n\n    // Step 0+ (if item height is given in advance) or 1+: Display the next range in line.\n    while (data->StepNo < data->Ranges.Size)\n    {\n        clipper->DisplayStart = ImMax(data->Ranges[data->StepNo].Min, already_submitted);\n        clipper->DisplayEnd = ImMin(data->Ranges[data->StepNo].Max, clipper->ItemsCount);\n        data->StepNo++;\n        if (clipper->DisplayStart >= clipper->DisplayEnd)\n            continue;\n        if (clipper->DisplayStart > already_submitted)\n            clipper->SeekCursorForItem(clipper->DisplayStart);\n        return true;\n    }\n\n    // After the last step: Let the clipper validate that we have reached the expected Y position (corresponding to element DisplayEnd),\n    // Advance the cursor to the end of the list and then returns 'false' to end the loop.\n    if (clipper->ItemsCount < INT_MAX)\n        clipper->SeekCursorForItem(clipper->ItemsCount);\n\n    return false;\n}\n\nbool ImGuiListClipper::Step()\n{\n    ImGuiContext& g = *Ctx;\n    bool need_items_height = (ItemsHeight <= 0.0f);\n    bool ret = ImGuiListClipper_StepInternal(this);\n    if (ret && (DisplayStart >= DisplayEnd))\n        ret = false;\n    if (g.CurrentTable && g.CurrentTable->IsUnfrozenRows == false)\n        IMGUI_DEBUG_LOG_CLIPPER(\"Clipper: Step(): inside frozen table row.\\n\");\n    if (need_items_height && ItemsHeight > 0.0f)\n        IMGUI_DEBUG_LOG_CLIPPER(\"Clipper: Step(): computed ItemsHeight: %.2f.\\n\", ItemsHeight);\n    if (ret)\n    {\n        IMGUI_DEBUG_LOG_CLIPPER(\"Clipper: Step(): display %d to %d.\\n\", DisplayStart, DisplayEnd);\n    }\n    else\n    {\n        IMGUI_DEBUG_LOG_CLIPPER(\"Clipper: Step(): End.\\n\");\n        End();\n    }\n    return ret;\n}\n\n//-----------------------------------------------------------------------------\n// [SECTION] STYLING\n//-----------------------------------------------------------------------------\n\nImGuiStyle& ImGui::GetStyle()\n{\n    IM_ASSERT(GImGui != NULL && \"No current context. Did you call ImGui::CreateContext() and ImGui::SetCurrentContext() ?\");\n    return GImGui->Style;\n}\n\nImU32 ImGui::GetColorU32(ImGuiCol idx, float alpha_mul)\n{\n    ImGuiStyle& style = GImGui->Style;\n    ImVec4 c = style.Colors[idx];\n    c.w *= style.Alpha * alpha_mul;\n    return ColorConvertFloat4ToU32(c);\n}\n\nImU32 ImGui::GetColorU32(const ImVec4& col)\n{\n    ImGuiStyle& style = GImGui->Style;\n    ImVec4 c = col;\n    c.w *= style.Alpha;\n    return ColorConvertFloat4ToU32(c);\n}\n\nconst ImVec4& ImGui::GetStyleColorVec4(ImGuiCol idx)\n{\n    ImGuiStyle& style = GImGui->Style;\n    return style.Colors[idx];\n}\n\nImU32 ImGui::GetColorU32(ImU32 col, float alpha_mul)\n{\n    ImGuiStyle& style = GImGui->Style;\n    alpha_mul *= style.Alpha;\n    if (alpha_mul >= 1.0f)\n        return col;\n    ImU32 a = (col & IM_COL32_A_MASK) >> IM_COL32_A_SHIFT;\n    a = (ImU32)(a * alpha_mul); // We don't need to clamp 0..255 because alpha is in 0..1 range.\n    return (col & ~IM_COL32_A_MASK) | (a << IM_COL32_A_SHIFT);\n}\n\n// FIXME: This may incur a round-trip (if the end user got their data from a float4) but eventually we aim to store the in-flight colors as ImU32\nvoid ImGui::PushStyleColor(ImGuiCol idx, ImU32 col)\n{\n    ImGuiContext& g = *GImGui;\n    ImGuiColorMod backup;\n    backup.Col = idx;\n    backup.BackupValue = g.Style.Colors[idx];\n    g.ColorStack.push_back(backup);\n    if (g.DebugFlashStyleColorIdx != idx)\n        g.Style.Colors[idx] = ColorConvertU32ToFloat4(col);\n}\n\nvoid ImGui::PushStyleColor(ImGuiCol idx, const ImVec4& col)\n{\n    ImGuiContext& g = *GImGui;\n    ImGuiColorMod backup;\n    backup.Col = idx;\n    backup.BackupValue = g.Style.Colors[idx];\n    g.ColorStack.push_back(backup);\n    if (g.DebugFlashStyleColorIdx != idx)\n        g.Style.Colors[idx] = col;\n}\n\nvoid ImGui::PopStyleColor(int count)\n{\n    ImGuiContext& g = *GImGui;\n    if (g.ColorStack.Size < count)\n    {\n        IM_ASSERT_USER_ERROR(0, \"Calling PopStyleColor() too many times!\");\n        count = g.ColorStack.Size;\n    }\n    while (count > 0)\n    {\n        ImGuiColorMod& backup = g.ColorStack.back();\n        g.Style.Colors[backup.Col] = backup.BackupValue;\n        g.ColorStack.pop_back();\n        count--;\n    }\n}\n\nstatic const ImGuiStyleVarInfo GStyleVarsInfo[] =\n{\n    { 1, ImGuiDataType_Float, (ImU32)offsetof(ImGuiStyle, Alpha) },                     // ImGuiStyleVar_Alpha\n    { 1, ImGuiDataType_Float, (ImU32)offsetof(ImGuiStyle, DisabledAlpha) },             // ImGuiStyleVar_DisabledAlpha\n    { 2, ImGuiDataType_Float, (ImU32)offsetof(ImGuiStyle, WindowPadding) },             // ImGuiStyleVar_WindowPadding\n    { 1, ImGuiDataType_Float, (ImU32)offsetof(ImGuiStyle, WindowRounding) },            // ImGuiStyleVar_WindowRounding\n    { 1, ImGuiDataType_Float, (ImU32)offsetof(ImGuiStyle, WindowBorderSize) },          // ImGuiStyleVar_WindowBorderSize\n    { 2, ImGuiDataType_Float, (ImU32)offsetof(ImGuiStyle, WindowMinSize) },             // ImGuiStyleVar_WindowMinSize\n    { 2, ImGuiDataType_Float, (ImU32)offsetof(ImGuiStyle, WindowTitleAlign) },          // ImGuiStyleVar_WindowTitleAlign\n    { 1, ImGuiDataType_Float, (ImU32)offsetof(ImGuiStyle, ChildRounding) },             // ImGuiStyleVar_ChildRounding\n    { 1, ImGuiDataType_Float, (ImU32)offsetof(ImGuiStyle, ChildBorderSize) },           // ImGuiStyleVar_ChildBorderSize\n    { 1, ImGuiDataType_Float, (ImU32)offsetof(ImGuiStyle, PopupRounding) },             // ImGuiStyleVar_PopupRounding\n    { 1, ImGuiDataType_Float, (ImU32)offsetof(ImGuiStyle, PopupBorderSize) },           // ImGuiStyleVar_PopupBorderSize\n    { 2, ImGuiDataType_Float, (ImU32)offsetof(ImGuiStyle, FramePadding) },              // ImGuiStyleVar_FramePadding\n    { 1, ImGuiDataType_Float, (ImU32)offsetof(ImGuiStyle, FrameRounding) },             // ImGuiStyleVar_FrameRounding\n    { 1, ImGuiDataType_Float, (ImU32)offsetof(ImGuiStyle, FrameBorderSize) },           // ImGuiStyleVar_FrameBorderSize\n    { 2, ImGuiDataType_Float, (ImU32)offsetof(ImGuiStyle, ItemSpacing) },               // ImGuiStyleVar_ItemSpacing\n    { 2, ImGuiDataType_Float, (ImU32)offsetof(ImGuiStyle, ItemInnerSpacing) },          // ImGuiStyleVar_ItemInnerSpacing\n    { 1, ImGuiDataType_Float, (ImU32)offsetof(ImGuiStyle, IndentSpacing) },             // ImGuiStyleVar_IndentSpacing\n    { 2, ImGuiDataType_Float, (ImU32)offsetof(ImGuiStyle, CellPadding) },               // ImGuiStyleVar_CellPadding\n    { 1, ImGuiDataType_Float, (ImU32)offsetof(ImGuiStyle, ScrollbarSize) },             // ImGuiStyleVar_ScrollbarSize\n    { 1, ImGuiDataType_Float, (ImU32)offsetof(ImGuiStyle, ScrollbarRounding) },         // ImGuiStyleVar_ScrollbarRounding\n    { 1, ImGuiDataType_Float, (ImU32)offsetof(ImGuiStyle, GrabMinSize) },               // ImGuiStyleVar_GrabMinSize\n    { 1, ImGuiDataType_Float, (ImU32)offsetof(ImGuiStyle, GrabRounding) },              // ImGuiStyleVar_GrabRounding\n    { 1, ImGuiDataType_Float, (ImU32)offsetof(ImGuiStyle, ImageBorderSize) },           // ImGuiStyleVar_ImageBorderSize\n    { 1, ImGuiDataType_Float, (ImU32)offsetof(ImGuiStyle, TabRounding) },               // ImGuiStyleVar_TabRounding\n    { 1, ImGuiDataType_Float, (ImU32)offsetof(ImGuiStyle, TabBorderSize) },             // ImGuiStyleVar_TabBorderSize\n    { 1, ImGuiDataType_Float, (ImU32)offsetof(ImGuiStyle, TabBarBorderSize) },          // ImGuiStyleVar_TabBarBorderSize\n    { 1, ImGuiDataType_Float, (ImU32)offsetof(ImGuiStyle, TabBarOverlineSize) },        // ImGuiStyleVar_TabBarOverlineSize\n    { 1, ImGuiDataType_Float, (ImU32)offsetof(ImGuiStyle, TableAngledHeadersAngle)},    // ImGuiStyleVar_TableAngledHeadersAngle\n    { 2, ImGuiDataType_Float, (ImU32)offsetof(ImGuiStyle, TableAngledHeadersTextAlign)},// ImGuiStyleVar_TableAngledHeadersTextAlign\n    { 2, ImGuiDataType_Float, (ImU32)offsetof(ImGuiStyle, ButtonTextAlign) },           // ImGuiStyleVar_ButtonTextAlign\n    { 2, ImGuiDataType_Float, (ImU32)offsetof(ImGuiStyle, SelectableTextAlign) },       // ImGuiStyleVar_SelectableTextAlign\n    { 1, ImGuiDataType_Float, (ImU32)offsetof(ImGuiStyle, SeparatorTextBorderSize)},    // ImGuiStyleVar_SeparatorTextBorderSize\n    { 2, ImGuiDataType_Float, (ImU32)offsetof(ImGuiStyle, SeparatorTextAlign) },        // ImGuiStyleVar_SeparatorTextAlign\n    { 2, ImGuiDataType_Float, (ImU32)offsetof(ImGuiStyle, SeparatorTextPadding) },      // ImGuiStyleVar_SeparatorTextPadding\n};\n\nconst ImGuiStyleVarInfo* ImGui::GetStyleVarInfo(ImGuiStyleVar idx)\n{\n    IM_ASSERT(idx >= 0 && idx < ImGuiStyleVar_COUNT);\n    IM_STATIC_ASSERT(IM_ARRAYSIZE(GStyleVarsInfo) == ImGuiStyleVar_COUNT);\n    return &GStyleVarsInfo[idx];\n}\n\nvoid ImGui::PushStyleVar(ImGuiStyleVar idx, float val)\n{\n    ImGuiContext& g = *GImGui;\n    const ImGuiStyleVarInfo* var_info = GetStyleVarInfo(idx);\n    if (var_info->DataType != ImGuiDataType_Float || var_info->Count != 1)\n    {\n        IM_ASSERT_USER_ERROR(0, \"Calling PushStyleVar() variant with wrong type!\");\n        return;\n    }\n    float* pvar = (float*)var_info->GetVarPtr(&g.Style);\n    g.StyleVarStack.push_back(ImGuiStyleMod(idx, *pvar));\n    *pvar = val;\n}\n\nvoid ImGui::PushStyleVarX(ImGuiStyleVar idx, float val_x)\n{\n    ImGuiContext& g = *GImGui;\n    const ImGuiStyleVarInfo* var_info = GetStyleVarInfo(idx);\n    if (var_info->DataType != ImGuiDataType_Float || var_info->Count != 2)\n    {\n        IM_ASSERT_USER_ERROR(0, \"Calling PushStyleVar() variant with wrong type!\");\n        return;\n    }\n    ImVec2* pvar = (ImVec2*)var_info->GetVarPtr(&g.Style);\n    g.StyleVarStack.push_back(ImGuiStyleMod(idx, *pvar));\n    pvar->x = val_x;\n}\n\nvoid ImGui::PushStyleVarY(ImGuiStyleVar idx, float val_y)\n{\n    ImGuiContext& g = *GImGui;\n    const ImGuiStyleVarInfo* var_info = GetStyleVarInfo(idx);\n    if (var_info->DataType != ImGuiDataType_Float || var_info->Count != 2)\n    {\n        IM_ASSERT_USER_ERROR(0, \"Calling PushStyleVar() variant with wrong type!\");\n        return;\n    }\n    ImVec2* pvar = (ImVec2*)var_info->GetVarPtr(&g.Style);\n    g.StyleVarStack.push_back(ImGuiStyleMod(idx, *pvar));\n    pvar->y = val_y;\n}\n\nvoid ImGui::PushStyleVar(ImGuiStyleVar idx, const ImVec2& val)\n{\n    ImGuiContext& g = *GImGui;\n    const ImGuiStyleVarInfo* var_info = GetStyleVarInfo(idx);\n    if (var_info->DataType != ImGuiDataType_Float || var_info->Count != 2)\n    {\n        IM_ASSERT_USER_ERROR(0, \"Calling PushStyleVar() variant with wrong type!\");\n        return;\n    }\n    ImVec2* pvar = (ImVec2*)var_info->GetVarPtr(&g.Style);\n    g.StyleVarStack.push_back(ImGuiStyleMod(idx, *pvar));\n    *pvar = val;\n}\n\nvoid ImGui::PopStyleVar(int count)\n{\n    ImGuiContext& g = *GImGui;\n    if (g.StyleVarStack.Size < count)\n    {\n        IM_ASSERT_USER_ERROR(0, \"Calling PopStyleVar() too many times!\");\n        count = g.StyleVarStack.Size;\n    }\n    while (count > 0)\n    {\n        // We avoid a generic memcpy(data, &backup.Backup.., GDataTypeSize[info->Type] * info->Count), the overhead in Debug is not worth it.\n        ImGuiStyleMod& backup = g.StyleVarStack.back();\n        const ImGuiStyleVarInfo* var_info = GetStyleVarInfo(backup.VarIdx);\n        void* data = var_info->GetVarPtr(&g.Style);\n        if (var_info->DataType == ImGuiDataType_Float && var_info->Count == 1)      { ((float*)data)[0] = backup.BackupFloat[0]; }\n        else if (var_info->DataType == ImGuiDataType_Float && var_info->Count == 2) { ((float*)data)[0] = backup.BackupFloat[0]; ((float*)data)[1] = backup.BackupFloat[1]; }\n        g.StyleVarStack.pop_back();\n        count--;\n    }\n}\n\nconst char* ImGui::GetStyleColorName(ImGuiCol idx)\n{\n    // Create switch-case from enum with regexp: ImGuiCol_{.*}, --> case ImGuiCol_\\1: return \"\\1\";\n    switch (idx)\n    {\n    case ImGuiCol_Text: return \"Text\";\n    case ImGuiCol_TextDisabled: return \"TextDisabled\";\n    case ImGuiCol_WindowBg: return \"WindowBg\";\n    case ImGuiCol_ChildBg: return \"ChildBg\";\n    case ImGuiCol_PopupBg: return \"PopupBg\";\n    case ImGuiCol_Border: return \"Border\";\n    case ImGuiCol_BorderShadow: return \"BorderShadow\";\n    case ImGuiCol_FrameBg: return \"FrameBg\";\n    case ImGuiCol_FrameBgHovered: return \"FrameBgHovered\";\n    case ImGuiCol_FrameBgActive: return \"FrameBgActive\";\n    case ImGuiCol_TitleBg: return \"TitleBg\";\n    case ImGuiCol_TitleBgActive: return \"TitleBgActive\";\n    case ImGuiCol_TitleBgCollapsed: return \"TitleBgCollapsed\";\n    case ImGuiCol_MenuBarBg: return \"MenuBarBg\";\n    case ImGuiCol_ScrollbarBg: return \"ScrollbarBg\";\n    case ImGuiCol_ScrollbarGrab: return \"ScrollbarGrab\";\n    case ImGuiCol_ScrollbarGrabHovered: return \"ScrollbarGrabHovered\";\n    case ImGuiCol_ScrollbarGrabActive: return \"ScrollbarGrabActive\";\n    case ImGuiCol_CheckMark: return \"CheckMark\";\n    case ImGuiCol_SliderGrab: return \"SliderGrab\";\n    case ImGuiCol_SliderGrabActive: return \"SliderGrabActive\";\n    case ImGuiCol_Button: return \"Button\";\n    case ImGuiCol_ButtonHovered: return \"ButtonHovered\";\n    case ImGuiCol_ButtonActive: return \"ButtonActive\";\n    case ImGuiCol_Header: return \"Header\";\n    case ImGuiCol_HeaderHovered: return \"HeaderHovered\";\n    case ImGuiCol_HeaderActive: return \"HeaderActive\";\n    case ImGuiCol_Separator: return \"Separator\";\n    case ImGuiCol_SeparatorHovered: return \"SeparatorHovered\";\n    case ImGuiCol_SeparatorActive: return \"SeparatorActive\";\n    case ImGuiCol_ResizeGrip: return \"ResizeGrip\";\n    case ImGuiCol_ResizeGripHovered: return \"ResizeGripHovered\";\n    case ImGuiCol_ResizeGripActive: return \"ResizeGripActive\";\n    case ImGuiCol_TabHovered: return \"TabHovered\";\n    case ImGuiCol_Tab: return \"Tab\";\n    case ImGuiCol_TabSelected: return \"TabSelected\";\n    case ImGuiCol_TabSelectedOverline: return \"TabSelectedOverline\";\n    case ImGuiCol_TabDimmed: return \"TabDimmed\";\n    case ImGuiCol_TabDimmedSelected: return \"TabDimmedSelected\";\n    case ImGuiCol_TabDimmedSelectedOverline: return \"TabDimmedSelectedOverline\";\n    case ImGuiCol_PlotLines: return \"PlotLines\";\n    case ImGuiCol_PlotLinesHovered: return \"PlotLinesHovered\";\n    case ImGuiCol_PlotHistogram: return \"PlotHistogram\";\n    case ImGuiCol_PlotHistogramHovered: return \"PlotHistogramHovered\";\n    case ImGuiCol_TableHeaderBg: return \"TableHeaderBg\";\n    case ImGuiCol_TableBorderStrong: return \"TableBorderStrong\";\n    case ImGuiCol_TableBorderLight: return \"TableBorderLight\";\n    case ImGuiCol_TableRowBg: return \"TableRowBg\";\n    case ImGuiCol_TableRowBgAlt: return \"TableRowBgAlt\";\n    case ImGuiCol_TextLink: return \"TextLink\";\n    case ImGuiCol_TextSelectedBg: return \"TextSelectedBg\";\n    case ImGuiCol_DragDropTarget: return \"DragDropTarget\";\n    case ImGuiCol_NavCursor: return \"NavCursor\";\n    case ImGuiCol_NavWindowingHighlight: return \"NavWindowingHighlight\";\n    case ImGuiCol_NavWindowingDimBg: return \"NavWindowingDimBg\";\n    case ImGuiCol_ModalWindowDimBg: return \"ModalWindowDimBg\";\n    }\n    IM_ASSERT(0);\n    return \"Unknown\";\n}\n\n//-----------------------------------------------------------------------------\n// [SECTION] RENDER HELPERS\n// Some of those (internal) functions are currently quite a legacy mess - their signature and behavior will change,\n// we need a nicer separation between low-level functions and high-level functions relying on the ImGui context.\n// Also see imgui_draw.cpp for some more which have been reworked to not rely on ImGui:: context.\n//-----------------------------------------------------------------------------\n\nconst char* ImGui::FindRenderedTextEnd(const char* text, const char* text_end)\n{\n    const char* text_display_end = text;\n    if (!text_end)\n        text_end = (const char*)-1;\n\n    while (text_display_end < text_end && *text_display_end != '\\0' && (text_display_end[0] != '#' || text_display_end[1] != '#'))\n        text_display_end++;\n    return text_display_end;\n}\n\n// Internal ImGui functions to render text\n// RenderText***() functions calls ImDrawList::AddText() calls ImBitmapFont::RenderText()\nvoid ImGui::RenderText(ImVec2 pos, const char* text, const char* text_end, bool hide_text_after_hash)\n{\n    ImGuiContext& g = *GImGui;\n    ImGuiWindow* window = g.CurrentWindow;\n\n    // Hide anything after a '##' string\n    const char* text_display_end;\n    if (hide_text_after_hash)\n    {\n        text_display_end = FindRenderedTextEnd(text, text_end);\n    }\n    else\n    {\n        if (!text_end)\n            text_end = text + ImStrlen(text); // FIXME-OPT\n        text_display_end = text_end;\n    }\n\n    if (text != text_display_end)\n    {\n        window->DrawList->AddText(g.Font, g.FontSize, pos, GetColorU32(ImGuiCol_Text), text, text_display_end);\n        if (g.LogEnabled)\n            LogRenderedText(&pos, text, text_display_end);\n    }\n}\n\nvoid ImGui::RenderTextWrapped(ImVec2 pos, const char* text, const char* text_end, float wrap_width)\n{\n    ImGuiContext& g = *GImGui;\n    ImGuiWindow* window = g.CurrentWindow;\n\n    if (!text_end)\n        text_end = text + ImStrlen(text); // FIXME-OPT\n\n    if (text != text_end)\n    {\n        window->DrawList->AddText(g.Font, g.FontSize, pos, GetColorU32(ImGuiCol_Text), text, text_end, wrap_width);\n        if (g.LogEnabled)\n            LogRenderedText(&pos, text, text_end);\n    }\n}\n\n// Default clip_rect uses (pos_min,pos_max)\n// Handle clipping on CPU immediately (vs typically let the GPU clip the triangles that are overlapping the clipping rectangle edges)\n// FIXME-OPT: Since we have or calculate text_size we could coarse clip whole block immediately, especally for text above draw_list->DrawList.\n// Effectively as this is called from widget doing their own coarse clipping it's not very valuable presently. Next time function will take\n// better advantage of the render function taking size into account for coarse clipping.\nvoid ImGui::RenderTextClippedEx(ImDrawList* draw_list, const ImVec2& pos_min, const ImVec2& pos_max, const char* text, const char* text_display_end, const ImVec2* text_size_if_known, const ImVec2& align, const ImRect* clip_rect)\n{\n    // Perform CPU side clipping for single clipped element to avoid using scissor state\n    ImVec2 pos = pos_min;\n    const ImVec2 text_size = text_size_if_known ? *text_size_if_known : CalcTextSize(text, text_display_end, false, 0.0f);\n\n    const ImVec2* clip_min = clip_rect ? &clip_rect->Min : &pos_min;\n    const ImVec2* clip_max = clip_rect ? &clip_rect->Max : &pos_max;\n    bool need_clipping = (pos.x + text_size.x >= clip_max->x) || (pos.y + text_size.y >= clip_max->y);\n    if (clip_rect) // If we had no explicit clipping rectangle then pos==clip_min\n        need_clipping |= (pos.x < clip_min->x) || (pos.y < clip_min->y);\n\n    // Align whole block. We should defer that to the better rendering function when we'll have support for individual line alignment.\n    if (align.x > 0.0f) pos.x = ImMax(pos.x, pos.x + (pos_max.x - pos.x - text_size.x) * align.x);\n    if (align.y > 0.0f) pos.y = ImMax(pos.y, pos.y + (pos_max.y - pos.y - text_size.y) * align.y);\n\n    // Render\n    if (need_clipping)\n    {\n        ImVec4 fine_clip_rect(clip_min->x, clip_min->y, clip_max->x, clip_max->y);\n        draw_list->AddText(NULL, 0.0f, pos, GetColorU32(ImGuiCol_Text), text, text_display_end, 0.0f, &fine_clip_rect);\n    }\n    else\n    {\n        draw_list->AddText(NULL, 0.0f, pos, GetColorU32(ImGuiCol_Text), text, text_display_end, 0.0f, NULL);\n    }\n}\n\nvoid ImGui::RenderTextClipped(const ImVec2& pos_min, const ImVec2& pos_max, const char* text, const char* text_end, const ImVec2* text_size_if_known, const ImVec2& align, const ImRect* clip_rect)\n{\n    // Hide anything after a '##' string\n    const char* text_display_end = FindRenderedTextEnd(text, text_end);\n    const int text_len = (int)(text_display_end - text);\n    if (text_len == 0)\n        return;\n\n    ImGuiContext& g = *GImGui;\n    ImGuiWindow* window = g.CurrentWindow;\n    RenderTextClippedEx(window->DrawList, pos_min, pos_max, text, text_display_end, text_size_if_known, align, clip_rect);\n    if (g.LogEnabled)\n        LogRenderedText(&pos_min, text, text_display_end);\n}\n\n// Another overly complex function until we reorganize everything into a nice all-in-one helper.\n// This is made more complex because we have dissociated the layout rectangle (pos_min..pos_max) which define _where_ the ellipsis is, from actual clipping of text and limit of the ellipsis display.\n// This is because in the context of tabs we selectively hide part of the text when the Close Button appears, but we don't want the ellipsis to move.\nvoid ImGui::RenderTextEllipsis(ImDrawList* draw_list, const ImVec2& pos_min, const ImVec2& pos_max, float clip_max_x, float ellipsis_max_x, const char* text, const char* text_end_full, const ImVec2* text_size_if_known)\n{\n    ImGuiContext& g = *GImGui;\n    if (text_end_full == NULL)\n        text_end_full = FindRenderedTextEnd(text);\n    const ImVec2 text_size = text_size_if_known ? *text_size_if_known : CalcTextSize(text, text_end_full, false, 0.0f);\n\n    //draw_list->AddLine(ImVec2(pos_max.x, pos_min.y - 4), ImVec2(pos_max.x, pos_max.y + 4), IM_COL32(0, 0, 255, 255));\n    //draw_list->AddLine(ImVec2(ellipsis_max_x, pos_min.y-2), ImVec2(ellipsis_max_x, pos_max.y+2), IM_COL32(0, 255, 0, 255));\n    //draw_list->AddLine(ImVec2(clip_max_x, pos_min.y), ImVec2(clip_max_x, pos_max.y), IM_COL32(255, 0, 0, 255));\n    // FIXME: We could technically remove (last_glyph->AdvanceX - last_glyph->X1) from text_size.x here and save a few pixels.\n    if (text_size.x > pos_max.x - pos_min.x)\n    {\n        // Hello wo...\n        // |       |   |\n        // min   max   ellipsis_max\n        //          <-> this is generally some padding value\n\n        ImFont* font = draw_list->_Data->Font;\n        const float font_size = draw_list->_Data->FontSize;\n        const float font_scale = draw_list->_Data->FontScale;\n        const char* text_end_ellipsis = NULL;\n        const float ellipsis_width = font->EllipsisWidth * font_scale;\n\n        // We can now claim the space between pos_max.x and ellipsis_max.x\n        const float text_avail_width = ImMax((ImMax(pos_max.x, ellipsis_max_x) - ellipsis_width) - pos_min.x, 1.0f);\n        float text_size_clipped_x = font->CalcTextSizeA(font_size, text_avail_width, 0.0f, text, text_end_full, &text_end_ellipsis).x;\n        if (text == text_end_ellipsis && text_end_ellipsis < text_end_full)\n        {\n            // Always display at least 1 character if there's no room for character + ellipsis\n            text_end_ellipsis = text + ImTextCountUtf8BytesFromChar(text, text_end_full);\n            text_size_clipped_x = font->CalcTextSizeA(font_size, FLT_MAX, 0.0f, text, text_end_ellipsis).x;\n        }\n        while (text_end_ellipsis > text && ImCharIsBlankA(text_end_ellipsis[-1]))\n        {\n            // Trim trailing space before ellipsis (FIXME: Supporting non-ascii blanks would be nice, for this we need a function to backtrack in UTF-8 text)\n            text_end_ellipsis--;\n            text_size_clipped_x -= font->CalcTextSizeA(font_size, FLT_MAX, 0.0f, text_end_ellipsis, text_end_ellipsis + 1).x; // Ascii blanks are always 1 byte\n        }\n\n        // Render text, render ellipsis\n        RenderTextClippedEx(draw_list, pos_min, ImVec2(clip_max_x, pos_max.y), text, text_end_ellipsis, &text_size, ImVec2(0.0f, 0.0f));\n        ImVec2 ellipsis_pos = ImTrunc(ImVec2(pos_min.x + text_size_clipped_x, pos_min.y));\n        if (ellipsis_pos.x + ellipsis_width <= ellipsis_max_x)\n            for (int i = 0; i < font->EllipsisCharCount; i++, ellipsis_pos.x += font->EllipsisCharStep * font_scale)\n                font->RenderChar(draw_list, font_size, ellipsis_pos, GetColorU32(ImGuiCol_Text), font->EllipsisChar);\n    }\n    else\n    {\n        RenderTextClippedEx(draw_list, pos_min, ImVec2(clip_max_x, pos_max.y), text, text_end_full, &text_size, ImVec2(0.0f, 0.0f));\n    }\n\n    if (g.LogEnabled)\n        LogRenderedText(&pos_min, text, text_end_full);\n}\n\n// Render a rectangle shaped with optional rounding and borders\nvoid ImGui::RenderFrame(ImVec2 p_min, ImVec2 p_max, ImU32 fill_col, bool borders, float rounding)\n{\n    ImGuiContext& g = *GImGui;\n    ImGuiWindow* window = g.CurrentWindow;\n    window->DrawList->AddRectFilled(p_min, p_max, fill_col, rounding);\n    const float border_size = g.Style.FrameBorderSize;\n    if (borders && border_size > 0.0f)\n    {\n        window->DrawList->AddRect(p_min + ImVec2(1, 1), p_max + ImVec2(1, 1), GetColorU32(ImGuiCol_BorderShadow), rounding, 0, border_size);\n        window->DrawList->AddRect(p_min, p_max, GetColorU32(ImGuiCol_Border), rounding, 0, border_size);\n    }\n}\n\nvoid ImGui::RenderFrameBorder(ImVec2 p_min, ImVec2 p_max, float rounding)\n{\n    ImGuiContext& g = *GImGui;\n    ImGuiWindow* window = g.CurrentWindow;\n    const float border_size = g.Style.FrameBorderSize;\n    if (border_size > 0.0f)\n    {\n        window->DrawList->AddRect(p_min + ImVec2(1, 1), p_max + ImVec2(1, 1), GetColorU32(ImGuiCol_BorderShadow), rounding, 0, border_size);\n        window->DrawList->AddRect(p_min, p_max, GetColorU32(ImGuiCol_Border), rounding, 0, border_size);\n    }\n}\n\nvoid ImGui::RenderNavCursor(const ImRect& bb, ImGuiID id, ImGuiNavRenderCursorFlags flags)\n{\n    ImGuiContext& g = *GImGui;\n    if (id != g.NavId)\n        return;\n    if (!g.NavCursorVisible && !(flags & ImGuiNavRenderCursorFlags_AlwaysDraw))\n        return;\n    if (id == g.LastItemData.ID && (g.LastItemData.ItemFlags & ImGuiItemFlags_NoNav))\n        return;\n    ImGuiWindow* window = g.CurrentWindow;\n    if (window->DC.NavHideHighlightOneFrame)\n        return;\n\n    float rounding = (flags & ImGuiNavRenderCursorFlags_NoRounding) ? 0.0f : g.Style.FrameRounding;\n    ImRect display_rect = bb;\n    display_rect.ClipWith(window->ClipRect);\n    const float thickness = 2.0f;\n    if (flags & ImGuiNavRenderCursorFlags_Compact)\n    {\n        window->DrawList->AddRect(display_rect.Min, display_rect.Max, GetColorU32(ImGuiCol_NavCursor), rounding, 0, thickness);\n    }\n    else\n    {\n        const float distance = 3.0f + thickness * 0.5f;\n        display_rect.Expand(ImVec2(distance, distance));\n        bool fully_visible = window->ClipRect.Contains(display_rect);\n        if (!fully_visible)\n            window->DrawList->PushClipRect(display_rect.Min, display_rect.Max);\n        window->DrawList->AddRect(display_rect.Min, display_rect.Max, GetColorU32(ImGuiCol_NavCursor), rounding, 0, thickness);\n        if (!fully_visible)\n            window->DrawList->PopClipRect();\n    }\n}\n\nvoid ImGui::RenderMouseCursor(ImVec2 base_pos, float base_scale, ImGuiMouseCursor mouse_cursor, ImU32 col_fill, ImU32 col_border, ImU32 col_shadow)\n{\n    ImGuiContext& g = *GImGui;\n    if (mouse_cursor <= ImGuiMouseCursor_None || mouse_cursor >= ImGuiMouseCursor_COUNT) // We intentionally accept out of bound values.\n        mouse_cursor = ImGuiMouseCursor_Arrow;\n    ImFontAtlas* font_atlas = g.DrawListSharedData.Font->ContainerAtlas;\n    for (ImGuiViewportP* viewport : g.Viewports)\n    {\n        // We scale cursor with current viewport/monitor, however Windows 10 for its own hardware cursor seems to be using a different scale factor.\n        ImVec2 offset, size, uv[4];\n        if (!ImFontAtlasGetMouseCursorTexData(font_atlas, mouse_cursor, &offset, &size, &uv[0], &uv[2]))\n            continue;\n        const ImVec2 pos = base_pos - offset;\n        const float scale = base_scale;\n        if (!viewport->GetMainRect().Overlaps(ImRect(pos, pos + ImVec2(size.x + 2, size.y + 2) * scale)))\n            continue;\n        ImDrawList* draw_list = GetForegroundDrawList(viewport);\n        ImTextureID tex_id = font_atlas->TexID;\n        draw_list->PushTextureID(tex_id);\n        draw_list->AddImage(tex_id, pos + ImVec2(1, 0) * scale, pos + (ImVec2(1, 0) + size) * scale, uv[2], uv[3], col_shadow);\n        draw_list->AddImage(tex_id, pos + ImVec2(2, 0) * scale, pos + (ImVec2(2, 0) + size) * scale, uv[2], uv[3], col_shadow);\n        draw_list->AddImage(tex_id, pos,                        pos + size * scale,                  uv[2], uv[3], col_border);\n        draw_list->AddImage(tex_id, pos,                        pos + size * scale,                  uv[0], uv[1], col_fill);\n        if (mouse_cursor == ImGuiMouseCursor_Wait || mouse_cursor == ImGuiMouseCursor_Progress)\n        {\n            float a_min = ImFmod((float)g.Time * 5.0f, 2.0f * IM_PI);\n            float a_max = a_min + IM_PI * 1.65f;\n            draw_list->PathArcTo(pos + ImVec2(14, -1) * scale, 6.0f * scale, a_min, a_max);\n            draw_list->PathStroke(col_fill, ImDrawFlags_None, 3.0f * scale);\n        }\n        draw_list->PopTextureID();\n    }\n}\n\n//-----------------------------------------------------------------------------\n// [SECTION] INITIALIZATION, SHUTDOWN\n//-----------------------------------------------------------------------------\n\n// Internal state access - if you want to share Dear ImGui state between modules (e.g. DLL) or allocate it yourself\n// Note that we still point to some static data and members (such as GFontAtlas), so the state instance you end up using will point to the static data within its module\nImGuiContext* ImGui::GetCurrentContext()\n{\n    return GImGui;\n}\n\nvoid ImGui::SetCurrentContext(ImGuiContext* ctx)\n{\n#ifdef IMGUI_SET_CURRENT_CONTEXT_FUNC\n    IMGUI_SET_CURRENT_CONTEXT_FUNC(ctx); // For custom thread-based hackery you may want to have control over this.\n#else\n    GImGui = ctx;\n#endif\n}\n\nvoid ImGui::SetAllocatorFunctions(ImGuiMemAllocFunc alloc_func, ImGuiMemFreeFunc free_func, void* user_data)\n{\n    GImAllocatorAllocFunc = alloc_func;\n    GImAllocatorFreeFunc = free_func;\n    GImAllocatorUserData = user_data;\n}\n\n// This is provided to facilitate copying allocators from one static/DLL boundary to another (e.g. retrieve default allocator of your executable address space)\nvoid ImGui::GetAllocatorFunctions(ImGuiMemAllocFunc* p_alloc_func, ImGuiMemFreeFunc* p_free_func, void** p_user_data)\n{\n    *p_alloc_func = GImAllocatorAllocFunc;\n    *p_free_func = GImAllocatorFreeFunc;\n    *p_user_data = GImAllocatorUserData;\n}\n\nImGuiContext* ImGui::CreateContext(ImFontAtlas* shared_font_atlas)\n{\n    ImGuiContext* prev_ctx = GetCurrentContext();\n    ImGuiContext* ctx = IM_NEW(ImGuiContext)(shared_font_atlas);\n    SetCurrentContext(ctx);\n    Initialize();\n    if (prev_ctx != NULL)\n        SetCurrentContext(prev_ctx); // Restore previous context if any, else keep new one.\n    return ctx;\n}\n\nvoid ImGui::DestroyContext(ImGuiContext* ctx)\n{\n    ImGuiContext* prev_ctx = GetCurrentContext();\n    if (ctx == NULL) //-V1051\n        ctx = prev_ctx;\n    SetCurrentContext(ctx);\n    Shutdown();\n    SetCurrentContext((prev_ctx != ctx) ? prev_ctx : NULL);\n    IM_DELETE(ctx);\n}\n\n// IMPORTANT: interactive elements requires a fixed ###xxx suffix, it must be same in ALL languages to allow for automation.\nstatic const ImGuiLocEntry GLocalizationEntriesEnUS[] =\n{\n    { ImGuiLocKey_VersionStr,           \"Dear ImGui \" IMGUI_VERSION \" (\" IM_STRINGIFY(IMGUI_VERSION_NUM) \")\" },\n    { ImGuiLocKey_TableSizeOne,         \"Size column to fit###SizeOne\"          },\n    { ImGuiLocKey_TableSizeAllFit,      \"Size all columns to fit###SizeAll\"     },\n    { ImGuiLocKey_TableSizeAllDefault,  \"Size all columns to default###SizeAll\" },\n    { ImGuiLocKey_TableResetOrder,      \"Reset order###ResetOrder\"              },\n    { ImGuiLocKey_WindowingMainMenuBar, \"(Main menu bar)\"                       },\n    { ImGuiLocKey_WindowingPopup,       \"(Popup)\"                               },\n    { ImGuiLocKey_WindowingUntitled,    \"(Untitled)\"                            },\n    { ImGuiLocKey_OpenLink_s,           \"Open '%s'\"                             },\n    { ImGuiLocKey_CopyLink,             \"Copy Link###CopyLink\"                  },\n};\n\nImGuiContext::ImGuiContext(ImFontAtlas* shared_font_atlas)\n{\n    IO.Ctx = this;\n    InputTextState.Ctx = this;\n\n    Initialized = false;\n    FontAtlasOwnedByContext = shared_font_atlas ? false : true;\n    Font = NULL;\n    FontSize = FontBaseSize = FontScale = CurrentDpiScale = 0.0f;\n    IO.Fonts = shared_font_atlas ? shared_font_atlas : IM_NEW(ImFontAtlas)();\n    Time = 0.0f;\n    FrameCount = 0;\n    FrameCountEnded = FrameCountRendered = -1;\n    WithinEndChildID = 0;\n    WithinFrameScope = WithinFrameScopeWithImplicitWindow = false;\n    GcCompactAll = false;\n    TestEngineHookItems = false;\n    TestEngine = NULL;\n    memset(ContextName, 0, sizeof(ContextName));\n\n    InputEventsNextMouseSource = ImGuiMouseSource_Mouse;\n    InputEventsNextEventId = 1;\n\n    WindowsActiveCount = 0;\n    WindowsBorderHoverPadding = 0.0f;\n    CurrentWindow = NULL;\n    HoveredWindow = NULL;\n    HoveredWindowUnderMovingWindow = NULL;\n    HoveredWindowBeforeClear = NULL;\n    MovingWindow = NULL;\n    WheelingWindow = NULL;\n    WheelingWindowStartFrame = WheelingWindowScrolledFrame = -1;\n    WheelingWindowReleaseTimer = 0.0f;\n\n    DebugDrawIdConflicts = 0;\n    DebugHookIdInfo = 0;\n    HoveredId = HoveredIdPreviousFrame = 0;\n    HoveredIdPreviousFrameItemCount = 0;\n    HoveredIdAllowOverlap = false;\n    HoveredIdIsDisabled = false;\n    HoveredIdTimer = HoveredIdNotActiveTimer = 0.0f;\n    ItemUnclipByLog = false;\n    ActiveId = 0;\n    ActiveIdIsAlive = 0;\n    ActiveIdTimer = 0.0f;\n    ActiveIdIsJustActivated = false;\n    ActiveIdAllowOverlap = false;\n    ActiveIdNoClearOnFocusLoss = false;\n    ActiveIdHasBeenPressedBefore = false;\n    ActiveIdHasBeenEditedBefore = false;\n    ActiveIdHasBeenEditedThisFrame = false;\n    ActiveIdFromShortcut = false;\n    ActiveIdClickOffset = ImVec2(-1, -1);\n    ActiveIdWindow = NULL;\n    ActiveIdSource = ImGuiInputSource_None;\n    ActiveIdMouseButton = -1;\n    ActiveIdPreviousFrame = 0;\n    memset(&DeactivatedItemData, 0, sizeof(DeactivatedItemData));\n    memset(&ActiveIdValueOnActivation, 0, sizeof(ActiveIdValueOnActivation));\n    LastActiveId = 0;\n    LastActiveIdTimer = 0.0f;\n\n    LastKeyboardKeyPressTime = LastKeyModsChangeTime = LastKeyModsChangeFromNoneTime = -1.0;\n\n    ActiveIdUsingNavDirMask = 0x00;\n    ActiveIdUsingAllKeyboardKeys = false;\n\n    CurrentFocusScopeId = 0;\n    CurrentItemFlags = ImGuiItemFlags_None;\n    DebugShowGroupRects = false;\n\n    NavCursorVisible = false;\n    NavHighlightItemUnderNav = false;\n    NavMousePosDirty = false;\n    NavIdIsAlive = false;\n    NavId = 0;\n    NavWindow = NULL;\n    NavFocusScopeId = NavActivateId = NavActivateDownId = NavActivatePressedId = 0;\n    NavLayer = ImGuiNavLayer_Main;\n    NavNextActivateId = 0;\n    NavActivateFlags = NavNextActivateFlags = ImGuiActivateFlags_None;\n    NavHighlightActivatedId = 0;\n    NavHighlightActivatedTimer = 0.0f;\n    NavInputSource = ImGuiInputSource_Keyboard;\n    NavLastValidSelectionUserData = ImGuiSelectionUserData_Invalid;\n    NavCursorHideFrames = 0;\n\n    NavAnyRequest = false;\n    NavInitRequest = false;\n    NavInitRequestFromMove = false;\n    NavMoveSubmitted = false;\n    NavMoveScoringItems = false;\n    NavMoveForwardToNextFrame = false;\n    NavMoveFlags = ImGuiNavMoveFlags_None;\n    NavMoveScrollFlags = ImGuiScrollFlags_None;\n    NavMoveKeyMods = ImGuiMod_None;\n    NavMoveDir = NavMoveDirForDebug = NavMoveClipDir = ImGuiDir_None;\n    NavScoringDebugCount = 0;\n    NavTabbingDir = 0;\n    NavTabbingCounter = 0;\n\n    NavJustMovedFromFocusScopeId = NavJustMovedToId = NavJustMovedToFocusScopeId = 0;\n    NavJustMovedToKeyMods = ImGuiMod_None;\n    NavJustMovedToIsTabbing = false;\n    NavJustMovedToHasSelectionData = false;\n\n    // All platforms use Ctrl+Tab but Ctrl<>Super are swapped on Mac...\n    // FIXME: Because this value is stored, it annoyingly interfere with toggling io.ConfigMacOSXBehaviors updating this..\n    ConfigNavWindowingKeyNext = IO.ConfigMacOSXBehaviors ? (ImGuiMod_Super | ImGuiKey_Tab) : (ImGuiMod_Ctrl | ImGuiKey_Tab);\n    ConfigNavWindowingKeyPrev = IO.ConfigMacOSXBehaviors ? (ImGuiMod_Super | ImGuiMod_Shift | ImGuiKey_Tab) : (ImGuiMod_Ctrl | ImGuiMod_Shift | ImGuiKey_Tab);\n    NavWindowingTarget = NavWindowingTargetAnim = NavWindowingListWindow = NULL;\n    NavWindowingTimer = NavWindowingHighlightAlpha = 0.0f;\n    NavWindowingToggleLayer = false;\n    NavWindowingToggleKey = ImGuiKey_None;\n\n    DimBgRatio = 0.0f;\n\n    DragDropActive = DragDropWithinSource = DragDropWithinTarget = false;\n    DragDropSourceFlags = ImGuiDragDropFlags_None;\n    DragDropSourceFrameCount = -1;\n    DragDropMouseButton = -1;\n    DragDropTargetId = 0;\n    DragDropAcceptFlags = ImGuiDragDropFlags_None;\n    DragDropAcceptIdCurrRectSurface = 0.0f;\n    DragDropAcceptIdPrev = DragDropAcceptIdCurr = 0;\n    DragDropAcceptFrameCount = -1;\n    DragDropHoldJustPressedId = 0;\n    memset(DragDropPayloadBufLocal, 0, sizeof(DragDropPayloadBufLocal));\n\n    ClipperTempDataStacked = 0;\n\n    CurrentTable = NULL;\n    TablesTempDataStacked = 0;\n    CurrentTabBar = NULL;\n    CurrentMultiSelect = NULL;\n    MultiSelectTempDataStacked = 0;\n\n    HoverItemDelayId = HoverItemDelayIdPreviousFrame = HoverItemUnlockedStationaryId = HoverWindowUnlockedStationaryId = 0;\n    HoverItemDelayTimer = HoverItemDelayClearTimer = 0.0f;\n\n    MouseCursor = ImGuiMouseCursor_Arrow;\n    MouseStationaryTimer = 0.0f;\n\n    TempInputId = 0;\n    memset(&DataTypeZeroValue, 0, sizeof(DataTypeZeroValue));\n    BeginMenuDepth = BeginComboDepth = 0;\n    ColorEditOptions = ImGuiColorEditFlags_DefaultOptions_;\n    ColorEditCurrentID = ColorEditSavedID = 0;\n    ColorEditSavedHue = ColorEditSavedSat = 0.0f;\n    ColorEditSavedColor = 0;\n    WindowResizeRelativeMode = false;\n    ScrollbarSeekMode = 0;\n    ScrollbarClickDeltaToGrabCenter = 0.0f;\n    SliderGrabClickOffset = 0.0f;\n    SliderCurrentAccum = 0.0f;\n    SliderCurrentAccumDirty = false;\n    DragCurrentAccumDirty = false;\n    DragCurrentAccum = 0.0f;\n    DragSpeedDefaultRatio = 1.0f / 100.0f;\n    DisabledAlphaBackup = 0.0f;\n    DisabledStackSize = 0;\n    TooltipOverrideCount = 0;\n    TooltipPreviousWindow = NULL;\n\n    PlatformImeData.InputPos = ImVec2(0.0f, 0.0f);\n    PlatformImeDataPrev.InputPos = ImVec2(-1.0f, -1.0f); // Different to ensure initial submission\n\n    SettingsLoaded = false;\n    SettingsDirtyTimer = 0.0f;\n    HookIdNext = 0;\n\n    memset(LocalizationTable, 0, sizeof(LocalizationTable));\n\n    LogEnabled = false;\n    LogFlags = ImGuiLogFlags_None;\n    LogWindow = NULL;\n    LogNextPrefix = LogNextSuffix = NULL;\n    LogFile = NULL;\n    LogLinePosY = FLT_MAX;\n    LogLineFirstItem = false;\n    LogDepthRef = 0;\n    LogDepthToExpand = LogDepthToExpandDefault = 2;\n\n    ErrorCallback = NULL;\n    ErrorCallbackUserData = NULL;\n    ErrorFirst = true;\n    ErrorCountCurrentFrame = 0;\n    StackSizesInBeginForCurrentWindow = NULL;\n\n    DebugDrawIdConflictsCount = 0;\n    DebugLogFlags = ImGuiDebugLogFlags_EventError | ImGuiDebugLogFlags_OutputToTTY;\n    DebugLocateId = 0;\n    DebugLogSkippedErrors = 0;\n    DebugLogAutoDisableFlags = ImGuiDebugLogFlags_None;\n    DebugLogAutoDisableFrames = 0;\n    DebugLocateFrames = 0;\n    DebugBeginReturnValueCullDepth = -1;\n    DebugItemPickerActive = false;\n    DebugItemPickerMouseButton = ImGuiMouseButton_Left;\n    DebugItemPickerBreakId = 0;\n    DebugFlashStyleColorTime = 0.0f;\n    DebugFlashStyleColorIdx = ImGuiCol_COUNT;\n\n    // Same as DebugBreakClearData(). Those fields are scattered in their respective subsystem to stay in hot-data locations\n    DebugBreakInWindow = 0;\n    DebugBreakInTable = 0;\n    DebugBreakInLocateId = false;\n    DebugBreakKeyChord = ImGuiKey_Pause;\n    DebugBreakInShortcutRouting = ImGuiKey_None;\n\n    memset(FramerateSecPerFrame, 0, sizeof(FramerateSecPerFrame));\n    FramerateSecPerFrameIdx = FramerateSecPerFrameCount = 0;\n    FramerateSecPerFrameAccum = 0.0f;\n    WantCaptureMouseNextFrame = WantCaptureKeyboardNextFrame = WantTextInputNextFrame = -1;\n    memset(TempKeychordName, 0, sizeof(TempKeychordName));\n}\n\nvoid ImGui::Initialize()\n{\n    ImGuiContext& g = *GImGui;\n    IM_ASSERT(!g.Initialized && !g.SettingsLoaded);\n\n    // Add .ini handle for ImGuiWindow and ImGuiTable types\n    {\n        ImGuiSettingsHandler ini_handler;\n        ini_handler.TypeName = \"Window\";\n        ini_handler.TypeHash = ImHashStr(\"Window\");\n        ini_handler.ClearAllFn = WindowSettingsHandler_ClearAll;\n        ini_handler.ReadOpenFn = WindowSettingsHandler_ReadOpen;\n        ini_handler.ReadLineFn = WindowSettingsHandler_ReadLine;\n        ini_handler.ApplyAllFn = WindowSettingsHandler_ApplyAll;\n        ini_handler.WriteAllFn = WindowSettingsHandler_WriteAll;\n        AddSettingsHandler(&ini_handler);\n    }\n    TableSettingsAddSettingsHandler();\n\n    // Setup default localization table\n    LocalizeRegisterEntries(GLocalizationEntriesEnUS, IM_ARRAYSIZE(GLocalizationEntriesEnUS));\n\n    // Setup default ImGuiPlatformIO clipboard/IME handlers.\n    g.PlatformIO.Platform_GetClipboardTextFn = Platform_GetClipboardTextFn_DefaultImpl;    // Platform dependent default implementations\n    g.PlatformIO.Platform_SetClipboardTextFn = Platform_SetClipboardTextFn_DefaultImpl;\n    g.PlatformIO.Platform_OpenInShellFn = Platform_OpenInShellFn_DefaultImpl;\n    g.PlatformIO.Platform_SetImeDataFn = Platform_SetImeDataFn_DefaultImpl;\n\n    // Create default viewport\n    ImGuiViewportP* viewport = IM_NEW(ImGuiViewportP)();\n    viewport->ID = IMGUI_VIEWPORT_DEFAULT_ID;\n    g.Viewports.push_back(viewport);\n    g.TempBuffer.resize(1024 * 3 + 1, 0);\n\n    // Build KeysMayBeCharInput[] lookup table (1 bool per named key)\n    for (ImGuiKey key = ImGuiKey_NamedKey_BEGIN; key < ImGuiKey_NamedKey_END; key = (ImGuiKey)(key + 1))\n        if ((key >= ImGuiKey_0 && key <= ImGuiKey_9) || (key >= ImGuiKey_A && key <= ImGuiKey_Z) || (key >= ImGuiKey_Keypad0 && key <= ImGuiKey_Keypad9)\n            || key == ImGuiKey_Tab || key == ImGuiKey_Space || key == ImGuiKey_Apostrophe || key == ImGuiKey_Comma || key == ImGuiKey_Minus || key == ImGuiKey_Period\n            || key == ImGuiKey_Slash || key == ImGuiKey_Semicolon || key == ImGuiKey_Equal || key == ImGuiKey_LeftBracket || key == ImGuiKey_RightBracket || key == ImGuiKey_GraveAccent\n            || key == ImGuiKey_KeypadDecimal || key == ImGuiKey_KeypadDivide || key == ImGuiKey_KeypadMultiply || key == ImGuiKey_KeypadSubtract || key == ImGuiKey_KeypadAdd || key == ImGuiKey_KeypadEqual)\n            g.KeysMayBeCharInput.SetBit(key);\n\n#ifdef IMGUI_HAS_DOCK\n#endif\n\n    g.Initialized = true;\n}\n\n// This function is merely here to free heap allocations.\nvoid ImGui::Shutdown()\n{\n    ImGuiContext& g = *GImGui;\n    IM_ASSERT_USER_ERROR(g.IO.BackendPlatformUserData == NULL, \"Forgot to shutdown Platform backend?\");\n    IM_ASSERT_USER_ERROR(g.IO.BackendRendererUserData == NULL, \"Forgot to shutdown Renderer backend?\");\n\n    // The fonts atlas can be used prior to calling NewFrame(), so we clear it even if g.Initialized is FALSE (which would happen if we never called NewFrame)\n    if (g.IO.Fonts && g.FontAtlasOwnedByContext)\n    {\n        g.IO.Fonts->Locked = false;\n        IM_DELETE(g.IO.Fonts);\n    }\n    g.IO.Fonts = NULL;\n    g.DrawListSharedData.TempBuffer.clear();\n\n    // Cleanup of other data are conditional on actually having initialized Dear ImGui.\n    if (!g.Initialized)\n        return;\n\n    // Save settings (unless we haven't attempted to load them: CreateContext/DestroyContext without a call to NewFrame shouldn't save an empty file)\n    if (g.SettingsLoaded && g.IO.IniFilename != NULL)\n        SaveIniSettingsToDisk(g.IO.IniFilename);\n\n    CallContextHooks(&g, ImGuiContextHookType_Shutdown);\n\n    // Clear everything else\n    g.Windows.clear_delete();\n    g.WindowsFocusOrder.clear();\n    g.WindowsTempSortBuffer.clear();\n    g.CurrentWindow = NULL;\n    g.CurrentWindowStack.clear();\n    g.WindowsById.Clear();\n    g.NavWindow = NULL;\n    g.HoveredWindow = g.HoveredWindowUnderMovingWindow = NULL;\n    g.ActiveIdWindow = NULL;\n    g.MovingWindow = NULL;\n\n    g.KeysRoutingTable.Clear();\n\n    g.ColorStack.clear();\n    g.StyleVarStack.clear();\n    g.FontStack.clear();\n    g.OpenPopupStack.clear();\n    g.BeginPopupStack.clear();\n    g.TreeNodeStack.clear();\n\n    g.Viewports.clear_delete();\n\n    g.TabBars.Clear();\n    g.CurrentTabBarStack.clear();\n    g.ShrinkWidthBuffer.clear();\n\n    g.ClipperTempData.clear_destruct();\n\n    g.Tables.Clear();\n    g.TablesTempData.clear_destruct();\n    g.DrawChannelsTempMergeBuffer.clear();\n\n    g.MultiSelectStorage.Clear();\n    g.MultiSelectTempData.clear_destruct();\n\n    g.ClipboardHandlerData.clear();\n    g.MenusIdSubmittedThisFrame.clear();\n    g.InputTextState.ClearFreeMemory();\n    g.InputTextDeactivatedState.ClearFreeMemory();\n\n    g.SettingsWindows.clear();\n    g.SettingsHandlers.clear();\n\n    if (g.LogFile)\n    {\n#ifndef IMGUI_DISABLE_TTY_FUNCTIONS\n        if (g.LogFile != stdout)\n#endif\n            ImFileClose(g.LogFile);\n        g.LogFile = NULL;\n    }\n    g.LogBuffer.clear();\n    g.DebugLogBuf.clear();\n    g.DebugLogIndex.clear();\n\n    g.Initialized = false;\n}\n\n// No specific ordering/dependency support, will see as needed\nImGuiID ImGui::AddContextHook(ImGuiContext* ctx, const ImGuiContextHook* hook)\n{\n    ImGuiContext& g = *ctx;\n    IM_ASSERT(hook->Callback != NULL && hook->HookId == 0 && hook->Type != ImGuiContextHookType_PendingRemoval_);\n    g.Hooks.push_back(*hook);\n    g.Hooks.back().HookId = ++g.HookIdNext;\n    return g.HookIdNext;\n}\n\n// Deferred removal, avoiding issue with changing vector while iterating it\nvoid ImGui::RemoveContextHook(ImGuiContext* ctx, ImGuiID hook_id)\n{\n    ImGuiContext& g = *ctx;\n    IM_ASSERT(hook_id != 0);\n    for (ImGuiContextHook& hook : g.Hooks)\n        if (hook.HookId == hook_id)\n            hook.Type = ImGuiContextHookType_PendingRemoval_;\n}\n\n// Call context hooks (used by e.g. test engine)\n// We assume a small number of hooks so all stored in same array\nvoid ImGui::CallContextHooks(ImGuiContext* ctx, ImGuiContextHookType hook_type)\n{\n    ImGuiContext& g = *ctx;\n    for (ImGuiContextHook& hook : g.Hooks)\n        if (hook.Type == hook_type)\n            hook.Callback(&g, &hook);\n}\n\n//-----------------------------------------------------------------------------\n// [SECTION] MAIN CODE (most of the code! lots of stuff, needs tidying up!)\n//-----------------------------------------------------------------------------\n\n// ImGuiWindow is mostly a dumb struct. It merely has a constructor and a few helper methods\nImGuiWindow::ImGuiWindow(ImGuiContext* ctx, const char* name) : DrawListInst(NULL)\n{\n    memset(this, 0, sizeof(*this));\n    Ctx = ctx;\n    Name = ImStrdup(name);\n    NameBufLen = (int)ImStrlen(name) + 1;\n    ID = ImHashStr(name);\n    IDStack.push_back(ID);\n    MoveId = GetID(\"#MOVE\");\n    ScrollTarget = ImVec2(FLT_MAX, FLT_MAX);\n    ScrollTargetCenterRatio = ImVec2(0.5f, 0.5f);\n    AutoFitFramesX = AutoFitFramesY = -1;\n    AutoPosLastDirection = ImGuiDir_None;\n    SetWindowPosAllowFlags = SetWindowSizeAllowFlags = SetWindowCollapsedAllowFlags = 0;\n    SetWindowPosVal = SetWindowPosPivot = ImVec2(FLT_MAX, FLT_MAX);\n    LastFrameActive = -1;\n    LastTimeActive = -1.0f;\n    FontRefSize = 0.0f;\n    FontWindowScale = FontWindowScaleParents = 1.0f;\n    SettingsOffset = -1;\n    DrawList = &DrawListInst;\n    DrawList->_OwnerName = Name;\n    DrawList->_Data = &Ctx->DrawListSharedData;\n    NavPreferredScoringPosRel[0] = NavPreferredScoringPosRel[1] = ImVec2(FLT_MAX, FLT_MAX);\n}\n\nImGuiWindow::~ImGuiWindow()\n{\n    IM_ASSERT(DrawList == &DrawListInst);\n    IM_DELETE(Name);\n    ColumnsStorage.clear_destruct();\n}\n\nstatic void SetCurrentWindow(ImGuiWindow* window)\n{\n    ImGuiContext& g = *GImGui;\n    g.CurrentWindow = window;\n    g.StackSizesInBeginForCurrentWindow = g.CurrentWindow ? &g.CurrentWindowStack.back().StackSizesInBegin : NULL;\n    g.CurrentTable = window && window->DC.CurrentTableIdx != -1 ? g.Tables.GetByIndex(window->DC.CurrentTableIdx) : NULL;\n    g.CurrentDpiScale = 1.0f; // FIXME-DPI: WIP this is modified in docking\n    if (window)\n    {\n        g.FontSize = g.DrawListSharedData.FontSize = window->CalcFontSize();\n        g.FontScale = g.DrawListSharedData.FontScale = g.FontSize / g.Font->FontSize;\n        ImGui::NavUpdateCurrentWindowIsScrollPushableX();\n    }\n}\n\nvoid ImGui::GcCompactTransientMiscBuffers()\n{\n    ImGuiContext& g = *GImGui;\n    g.ItemFlagsStack.clear();\n    g.GroupStack.clear();\n    g.MultiSelectTempDataStacked = 0;\n    g.MultiSelectTempData.clear_destruct();\n    TableGcCompactSettings();\n}\n\n// Free up/compact internal window buffers, we can use this when a window becomes unused.\n// Not freed:\n// - ImGuiWindow, ImGuiWindowSettings, Name, StateStorage, ColumnsStorage (may hold useful data)\n// This should have no noticeable visual effect. When the window reappear however, expect new allocation/buffer growth/copy cost.\nvoid ImGui::GcCompactTransientWindowBuffers(ImGuiWindow* window)\n{\n    window->MemoryCompacted = true;\n    window->MemoryDrawListIdxCapacity = window->DrawList->IdxBuffer.Capacity;\n    window->MemoryDrawListVtxCapacity = window->DrawList->VtxBuffer.Capacity;\n    window->IDStack.clear();\n    window->DrawList->_ClearFreeMemory();\n    window->DC.ChildWindows.clear();\n    window->DC.ItemWidthStack.clear();\n    window->DC.TextWrapPosStack.clear();\n}\n\nvoid ImGui::GcAwakeTransientWindowBuffers(ImGuiWindow* window)\n{\n    // We stored capacity of the ImDrawList buffer to reduce growth-caused allocation/copy when awakening.\n    // The other buffers tends to amortize much faster.\n    window->MemoryCompacted = false;\n    window->DrawList->IdxBuffer.reserve(window->MemoryDrawListIdxCapacity);\n    window->DrawList->VtxBuffer.reserve(window->MemoryDrawListVtxCapacity);\n    window->MemoryDrawListIdxCapacity = window->MemoryDrawListVtxCapacity = 0;\n}\n\nvoid ImGui::SetActiveID(ImGuiID id, ImGuiWindow* window)\n{\n    ImGuiContext& g = *GImGui;\n\n    // Clear previous active id\n    if (g.ActiveId != 0)\n    {\n        // While most behaved code would make an effort to not steal active id during window move/drag operations,\n        // we at least need to be resilient to it. Canceling the move is rather aggressive and users of 'master' branch\n        // may prefer the weird ill-defined half working situation ('docking' did assert), so may need to rework that.\n        if (g.MovingWindow != NULL && g.ActiveId == g.MovingWindow->MoveId)\n        {\n            IMGUI_DEBUG_LOG_ACTIVEID(\"SetActiveID() cancel MovingWindow\\n\");\n            g.MovingWindow = NULL;\n        }\n\n        // Store deactivate data\n        ImGuiDeactivatedItemData* deactivated_data = &g.DeactivatedItemData;\n        deactivated_data->ID = g.ActiveId;\n        deactivated_data->ElapseFrame = (g.LastItemData.ID == g.ActiveId) ? g.FrameCount : g.FrameCount + 1; // FIXME: OK to use LastItemData?\n        deactivated_data->HasBeenEditedBefore = g.ActiveIdHasBeenEditedBefore;\n        deactivated_data->IsAlive = (g.ActiveIdIsAlive == g.ActiveId);\n\n        // This could be written in a more general way (e.g associate a hook to ActiveId),\n        // but since this is currently quite an exception we'll leave it as is.\n        // One common scenario leading to this is: pressing Key ->NavMoveRequestApplyResult() -> ClearActiveID()\n        if (g.InputTextState.ID == g.ActiveId)\n            InputTextDeactivateHook(g.ActiveId);\n    }\n\n    // Set active id\n    g.ActiveIdIsJustActivated = (g.ActiveId != id);\n    if (g.ActiveIdIsJustActivated)\n    {\n        IMGUI_DEBUG_LOG_ACTIVEID(\"SetActiveID() old:0x%08X (window \\\"%s\\\") -> new:0x%08X (window \\\"%s\\\")\\n\", g.ActiveId, g.ActiveIdWindow ? g.ActiveIdWindow->Name : \"\", id, window ? window->Name : \"\");\n        g.ActiveIdTimer = 0.0f;\n        g.ActiveIdHasBeenPressedBefore = false;\n        g.ActiveIdHasBeenEditedBefore = false;\n        g.ActiveIdMouseButton = -1;\n        if (id != 0)\n        {\n            g.LastActiveId = id;\n            g.LastActiveIdTimer = 0.0f;\n        }\n    }\n    g.ActiveId = id;\n    g.ActiveIdAllowOverlap = false;\n    g.ActiveIdNoClearOnFocusLoss = false;\n    g.ActiveIdWindow = window;\n    g.ActiveIdHasBeenEditedThisFrame = false;\n    g.ActiveIdFromShortcut = false;\n    if (id)\n    {\n        g.ActiveIdIsAlive = id;\n        g.ActiveIdSource = (g.NavActivateId == id || g.NavJustMovedToId == id) ? g.NavInputSource : ImGuiInputSource_Mouse;\n        IM_ASSERT(g.ActiveIdSource != ImGuiInputSource_None);\n    }\n\n    // Clear declaration of inputs claimed by the widget\n    // (Please note that this is WIP and not all keys/inputs are thoroughly declared by all widgets yet)\n    g.ActiveIdUsingNavDirMask = 0x00;\n    g.ActiveIdUsingAllKeyboardKeys = false;\n}\n\nvoid ImGui::ClearActiveID()\n{\n    SetActiveID(0, NULL); // g.ActiveId = 0;\n}\n\nvoid ImGui::SetHoveredID(ImGuiID id)\n{\n    ImGuiContext& g = *GImGui;\n    g.HoveredId = id;\n    g.HoveredIdAllowOverlap = false;\n    if (id != 0 && g.HoveredIdPreviousFrame != id)\n        g.HoveredIdTimer = g.HoveredIdNotActiveTimer = 0.0f;\n}\n\nImGuiID ImGui::GetHoveredID()\n{\n    ImGuiContext& g = *GImGui;\n    return g.HoveredId ? g.HoveredId : g.HoveredIdPreviousFrame;\n}\n\nvoid ImGui::MarkItemEdited(ImGuiID id)\n{\n    // This marking is to be able to provide info for IsItemDeactivatedAfterEdit().\n    // ActiveId might have been released by the time we call this (as in the typical press/release button behavior) but still need to fill the data.\n    ImGuiContext& g = *GImGui;\n    if (g.LastItemData.ItemFlags & ImGuiItemFlags_NoMarkEdited)\n        return;\n    if (g.ActiveId == id || g.ActiveId == 0)\n    {\n        // FIXME: Can't we fully rely on LastItemData yet?\n        g.ActiveIdHasBeenEditedThisFrame = true;\n        g.ActiveIdHasBeenEditedBefore = true;\n        if (g.DeactivatedItemData.ID == id)\n            g.DeactivatedItemData.HasBeenEditedBefore = true;\n    }\n\n    // We accept a MarkItemEdited() on drag and drop targets (see https://github.com/ocornut/imgui/issues/1875#issuecomment-978243343)\n    // We accept 'ActiveIdPreviousFrame == id' for InputText() returning an edit after it has been taken ActiveId away (#4714)\n    IM_ASSERT(g.DragDropActive || g.ActiveId == id || g.ActiveId == 0 || g.ActiveIdPreviousFrame == id || (g.CurrentMultiSelect != NULL && g.BoxSelectState.IsActive));\n\n    //IM_ASSERT(g.CurrentWindow->DC.LastItemId == id);\n    g.LastItemData.StatusFlags |= ImGuiItemStatusFlags_Edited;\n}\n\nbool ImGui::IsWindowContentHoverable(ImGuiWindow* window, ImGuiHoveredFlags flags)\n{\n    // An active popup disable hovering on other windows (apart from its own children)\n    // FIXME-OPT: This could be cached/stored within the window.\n    ImGuiContext& g = *GImGui;\n    if (g.NavWindow)\n        if (ImGuiWindow* focused_root_window = g.NavWindow->RootWindow)\n            if (focused_root_window->WasActive && focused_root_window != window->RootWindow)\n            {\n                // For the purpose of those flags we differentiate \"standard popup\" from \"modal popup\"\n                // NB: The 'else' is important because Modal windows are also Popups.\n                bool want_inhibit = false;\n                if (focused_root_window->Flags & ImGuiWindowFlags_Modal)\n                    want_inhibit = true;\n                else if ((focused_root_window->Flags & ImGuiWindowFlags_Popup) && !(flags & ImGuiHoveredFlags_AllowWhenBlockedByPopup))\n                    want_inhibit = true;\n\n                // Inhibit hover unless the window is within the stack of our modal/popup\n                if (want_inhibit)\n                    if (!IsWindowWithinBeginStackOf(window->RootWindow, focused_root_window))\n                        return false;\n            }\n    return true;\n}\n\nstatic inline float CalcDelayFromHoveredFlags(ImGuiHoveredFlags flags)\n{\n    ImGuiContext& g = *GImGui;\n    if (flags & ImGuiHoveredFlags_DelayNormal)\n        return g.Style.HoverDelayNormal;\n    if (flags & ImGuiHoveredFlags_DelayShort)\n        return g.Style.HoverDelayShort;\n    return 0.0f;\n}\n\nstatic ImGuiHoveredFlags ApplyHoverFlagsForTooltip(ImGuiHoveredFlags user_flags, ImGuiHoveredFlags shared_flags)\n{\n    // Allow instance flags to override shared flags\n    if (user_flags & (ImGuiHoveredFlags_DelayNone | ImGuiHoveredFlags_DelayShort | ImGuiHoveredFlags_DelayNormal))\n        shared_flags &= ~(ImGuiHoveredFlags_DelayNone | ImGuiHoveredFlags_DelayShort | ImGuiHoveredFlags_DelayNormal);\n    return user_flags | shared_flags;\n}\n\n// This is roughly matching the behavior of internal-facing ItemHoverable()\n// - we allow hovering to be true when ActiveId==window->MoveID, so that clicking on non-interactive items such as a Text() item still returns true with IsItemHovered()\n// - this should work even for non-interactive items that have no ID, so we cannot use LastItemId\nbool ImGui::IsItemHovered(ImGuiHoveredFlags flags)\n{\n    ImGuiContext& g = *GImGui;\n    ImGuiWindow* window = g.CurrentWindow;\n    IM_ASSERT_USER_ERROR((flags & ~ImGuiHoveredFlags_AllowedMaskForIsItemHovered) == 0, \"Invalid flags for IsItemHovered()!\");\n\n    if (g.NavHighlightItemUnderNav && g.NavCursorVisible && !(flags & ImGuiHoveredFlags_NoNavOverride))\n    {\n        if (!IsItemFocused())\n            return false;\n        if ((g.LastItemData.ItemFlags & ImGuiItemFlags_Disabled) && !(flags & ImGuiHoveredFlags_AllowWhenDisabled))\n            return false;\n\n        if (flags & ImGuiHoveredFlags_ForTooltip)\n            flags = ApplyHoverFlagsForTooltip(flags, g.Style.HoverFlagsForTooltipNav);\n    }\n    else\n    {\n        // Test for bounding box overlap, as updated as ItemAdd()\n        ImGuiItemStatusFlags status_flags = g.LastItemData.StatusFlags;\n        if (!(status_flags & ImGuiItemStatusFlags_HoveredRect))\n            return false;\n\n        if (flags & ImGuiHoveredFlags_ForTooltip)\n            flags = ApplyHoverFlagsForTooltip(flags, g.Style.HoverFlagsForTooltipMouse);\n\n        // Done with rectangle culling so we can perform heavier checks now\n        // Test if we are hovering the right window (our window could be behind another window)\n        // [2021/03/02] Reworked / reverted the revert, finally. Note we want e.g. BeginGroup/ItemAdd/EndGroup to work as well. (#3851)\n        // [2017/10/16] Reverted commit 344d48be3 and testing RootWindow instead. I believe it is correct to NOT test for RootWindow but this leaves us unable\n        // to use IsItemHovered() after EndChild() itself. Until a solution is found I believe reverting to the test from 2017/09/27 is safe since this was\n        // the test that has been running for a long while.\n        if (g.HoveredWindow != window && (status_flags & ImGuiItemStatusFlags_HoveredWindow) == 0)\n            if ((flags & ImGuiHoveredFlags_AllowWhenOverlappedByWindow) == 0)\n                return false;\n\n        // Test if another item is active (e.g. being dragged)\n        const ImGuiID id = g.LastItemData.ID;\n        if ((flags & ImGuiHoveredFlags_AllowWhenBlockedByActiveItem) == 0)\n            if (g.ActiveId != 0 && g.ActiveId != id && !g.ActiveIdAllowOverlap)\n                if (g.ActiveId != window->MoveId)\n                    return false;\n\n        // Test if interactions on this window are blocked by an active popup or modal.\n        // The ImGuiHoveredFlags_AllowWhenBlockedByPopup flag will be tested here.\n        if (!IsWindowContentHoverable(window, flags) && !(g.LastItemData.ItemFlags & ImGuiItemFlags_NoWindowHoverableCheck))\n            return false;\n\n        // Test if the item is disabled\n        if ((g.LastItemData.ItemFlags & ImGuiItemFlags_Disabled) && !(flags & ImGuiHoveredFlags_AllowWhenDisabled))\n            return false;\n\n        // Special handling for calling after Begin() which represent the title bar or tab.\n        // When the window is skipped/collapsed (SkipItems==true) that last item (always ->MoveId submitted by Begin)\n        // will never be overwritten so we need to detect the case.\n        if (id == window->MoveId && window->WriteAccessed)\n            return false;\n\n        // Test if using AllowOverlap and overlapped\n        if ((g.LastItemData.ItemFlags & ImGuiItemFlags_AllowOverlap) && id != 0)\n            if ((flags & ImGuiHoveredFlags_AllowWhenOverlappedByItem) == 0)\n                if (g.HoveredIdPreviousFrame != g.LastItemData.ID)\n                    return false;\n    }\n\n    // Handle hover delay\n    // (some ideas: https://www.nngroup.com/articles/timing-exposing-content)\n    const float delay = CalcDelayFromHoveredFlags(flags);\n    if (delay > 0.0f || (flags & ImGuiHoveredFlags_Stationary))\n    {\n        ImGuiID hover_delay_id = (g.LastItemData.ID != 0) ? g.LastItemData.ID : window->GetIDFromPos(g.LastItemData.Rect.Min);\n        if ((flags & ImGuiHoveredFlags_NoSharedDelay) && (g.HoverItemDelayIdPreviousFrame != hover_delay_id))\n            g.HoverItemDelayTimer = 0.0f;\n        g.HoverItemDelayId = hover_delay_id;\n\n        // When changing hovered item we requires a bit of stationary delay before activating hover timer,\n        // but once unlocked on a given item we also moving.\n        //if (g.HoverDelayTimer >= delay && (g.HoverDelayTimer - g.IO.DeltaTime < delay || g.MouseStationaryTimer - g.IO.DeltaTime < g.Style.HoverStationaryDelay)) { IMGUI_DEBUG_LOG(\"HoverDelayTimer = %f/%f, MouseStationaryTimer = %f\\n\", g.HoverDelayTimer, delay, g.MouseStationaryTimer); }\n        if ((flags & ImGuiHoveredFlags_Stationary) != 0 && g.HoverItemUnlockedStationaryId != hover_delay_id)\n            return false;\n\n        if (g.HoverItemDelayTimer < delay)\n            return false;\n    }\n\n    return true;\n}\n\n// Internal facing ItemHoverable() used when submitting widgets. Differs slightly from IsItemHovered().\n// (this does not rely on LastItemData it can be called from a ButtonBehavior() call not following an ItemAdd() call)\n// FIXME-LEGACY: the 'ImGuiItemFlags item_flags' parameter was added on 2023-06-28.\n// If you used this in your legacy/custom widgets code:\n// - Commonly: if your ItemHoverable() call comes after an ItemAdd() call: pass 'item_flags = g.LastItemData.ItemFlags'.\n// - Rare: otherwise you may pass 'item_flags = 0' (ImGuiItemFlags_None) unless you want to benefit from special behavior handled by ItemHoverable.\nbool ImGui::ItemHoverable(const ImRect& bb, ImGuiID id, ImGuiItemFlags item_flags)\n{\n    ImGuiContext& g = *GImGui;\n    ImGuiWindow* window = g.CurrentWindow;\n\n    // Detect ID conflicts\n#ifndef IMGUI_DISABLE_DEBUG_TOOLS\n    if (id != 0 && g.HoveredIdPreviousFrame == id && (item_flags & ImGuiItemFlags_AllowDuplicateId) == 0)\n    {\n        g.HoveredIdPreviousFrameItemCount++;\n        if (g.DebugDrawIdConflicts == id)\n            window->DrawList->AddRect(bb.Min - ImVec2(1,1), bb.Max + ImVec2(1,1), IM_COL32(255, 0, 0, 255), 0.0f, ImDrawFlags_None, 2.0f);\n    }\n#endif\n\n    if (g.HoveredWindow != window)\n        return false;\n    if (!IsMouseHoveringRect(bb.Min, bb.Max))\n        return false;\n\n    if (g.HoveredId != 0 && g.HoveredId != id && !g.HoveredIdAllowOverlap)\n        return false;\n    if (g.ActiveId != 0 && g.ActiveId != id && !g.ActiveIdAllowOverlap)\n        if (!g.ActiveIdFromShortcut)\n            return false;\n\n    // Done with rectangle culling so we can perform heavier checks now.\n    if (!(item_flags & ImGuiItemFlags_NoWindowHoverableCheck) && !IsWindowContentHoverable(window, ImGuiHoveredFlags_None))\n    {\n        g.HoveredIdIsDisabled = true;\n        return false;\n    }\n\n    // We exceptionally allow this function to be called with id==0 to allow using it for easy high-level\n    // hover test in widgets code. We could also decide to split this function is two.\n    if (id != 0)\n    {\n        // Drag source doesn't report as hovered\n        if (g.DragDropActive && g.DragDropPayload.SourceId == id && !(g.DragDropSourceFlags & ImGuiDragDropFlags_SourceNoDisableHover))\n            return false;\n\n        SetHoveredID(id);\n\n        // AllowOverlap mode (rarely used) requires previous frame HoveredId to be null or to match.\n        // This allows using patterns where a later submitted widget overlaps a previous one. Generally perceived as a front-to-back hit-test.\n        if (item_flags & ImGuiItemFlags_AllowOverlap)\n        {\n            g.HoveredIdAllowOverlap = true;\n            if (g.HoveredIdPreviousFrame != id)\n                return false;\n        }\n\n        // Display shortcut (only works with mouse)\n        // (ImGuiItemStatusFlags_HasShortcut in LastItemData denotes we want a tooltip)\n        if (id == g.LastItemData.ID && (g.LastItemData.StatusFlags & ImGuiItemStatusFlags_HasShortcut) && g.ActiveId != id)\n            if (IsItemHovered(ImGuiHoveredFlags_ForTooltip | ImGuiHoveredFlags_DelayNormal))\n                SetTooltip(\"%s\", GetKeyChordName(g.LastItemData.Shortcut));\n    }\n\n    // When disabled we'll return false but still set HoveredId\n    if (item_flags & ImGuiItemFlags_Disabled)\n    {\n        // Release active id if turning disabled\n        if (g.ActiveId == id && id != 0)\n            ClearActiveID();\n        g.HoveredIdIsDisabled = true;\n        return false;\n    }\n\n#ifndef IMGUI_DISABLE_DEBUG_TOOLS\n    if (id != 0)\n    {\n        // [DEBUG] Item Picker tool!\n        // We perform the check here because reaching is path is rare (1~ time a frame),\n        // making the cost of this tool near-zero! We could get better call-stack and support picking non-hovered\n        // items if we performed the test in ItemAdd(), but that would incur a bigger runtime cost.\n        if (g.DebugItemPickerActive && g.HoveredIdPreviousFrame == id)\n            GetForegroundDrawList()->AddRect(bb.Min, bb.Max, IM_COL32(255, 255, 0, 255));\n        if (g.DebugItemPickerBreakId == id)\n            IM_DEBUG_BREAK();\n    }\n#endif\n\n    if (g.NavHighlightItemUnderNav && (item_flags & ImGuiItemFlags_NoNavDisableMouseHover) == 0)\n        return false;\n\n    return true;\n}\n\n// FIXME: This is inlined/duplicated in ItemAdd()\n// FIXME: The id != 0 path is not used by our codebase, may get rid of it?\nbool ImGui::IsClippedEx(const ImRect& bb, ImGuiID id)\n{\n    ImGuiContext& g = *GImGui;\n    ImGuiWindow* window = g.CurrentWindow;\n    if (!bb.Overlaps(window->ClipRect))\n        if (id == 0 || (id != g.ActiveId && id != g.ActiveIdPreviousFrame && id != g.NavId && id != g.NavActivateId))\n            if (!g.ItemUnclipByLog)\n                return true;\n    return false;\n}\n\n// This is also inlined in ItemAdd()\n// Note: if ImGuiItemStatusFlags_HasDisplayRect is set, user needs to set g.LastItemData.DisplayRect.\nvoid ImGui::SetLastItemData(ImGuiID item_id, ImGuiItemFlags item_flags, ImGuiItemStatusFlags status_flags, const ImRect& item_rect)\n{\n    ImGuiContext& g = *GImGui;\n    g.LastItemData.ID = item_id;\n    g.LastItemData.ItemFlags = item_flags;\n    g.LastItemData.StatusFlags = status_flags;\n    g.LastItemData.Rect = g.LastItemData.NavRect = item_rect;\n}\n\nstatic void ImGui::SetLastItemDataForWindow(ImGuiWindow* window, const ImRect& rect)\n{\n    ImGuiContext& g = *GImGui;\n    SetLastItemData(window->MoveId, g.CurrentItemFlags, window->DC.WindowItemStatusFlags, rect);\n}\n\nstatic void ImGui::SetLastItemDataForChildWindowItem(ImGuiWindow* window, const ImRect& rect)\n{\n    ImGuiContext& g = *GImGui;\n    SetLastItemData(window->ChildId, g.CurrentItemFlags, window->DC.ChildItemStatusFlags, rect);\n}\n\nfloat ImGui::CalcWrapWidthForPos(const ImVec2& pos, float wrap_pos_x)\n{\n    if (wrap_pos_x < 0.0f)\n        return 0.0f;\n\n    ImGuiContext& g = *GImGui;\n    ImGuiWindow* window = g.CurrentWindow;\n    if (wrap_pos_x == 0.0f)\n    {\n        // We could decide to setup a default wrapping max point for auto-resizing windows,\n        // or have auto-wrap (with unspecified wrapping pos) behave as a ContentSize extending function?\n        //if (window->Hidden && (window->Flags & ImGuiWindowFlags_AlwaysAutoResize))\n        //    wrap_pos_x = ImMax(window->WorkRect.Min.x + g.FontSize * 10.0f, window->WorkRect.Max.x);\n        //else\n        wrap_pos_x = window->WorkRect.Max.x;\n    }\n    else if (wrap_pos_x > 0.0f)\n    {\n        wrap_pos_x += window->Pos.x - window->Scroll.x; // wrap_pos_x is provided is window local space\n    }\n\n    return ImMax(wrap_pos_x - pos.x, 1.0f);\n}\n\n// IM_ALLOC() == ImGui::MemAlloc()\nvoid* ImGui::MemAlloc(size_t size)\n{\n    void* ptr = (*GImAllocatorAllocFunc)(size, GImAllocatorUserData);\n#ifndef IMGUI_DISABLE_DEBUG_TOOLS\n    if (ImGuiContext* ctx = GImGui)\n        DebugAllocHook(&ctx->DebugAllocInfo, ctx->FrameCount, ptr, size);\n#endif\n    return ptr;\n}\n\n// IM_FREE() == ImGui::MemFree()\nvoid ImGui::MemFree(void* ptr)\n{\n#ifndef IMGUI_DISABLE_DEBUG_TOOLS\n    if (ptr != NULL)\n        if (ImGuiContext* ctx = GImGui)\n            DebugAllocHook(&ctx->DebugAllocInfo, ctx->FrameCount, ptr, (size_t)-1);\n#endif\n    return (*GImAllocatorFreeFunc)(ptr, GImAllocatorUserData);\n}\n\n// We record the number of allocation in recent frames, as a way to audit/sanitize our guiding principles of \"no allocations on idle/repeating frames\"\nvoid ImGui::DebugAllocHook(ImGuiDebugAllocInfo* info, int frame_count, void* ptr, size_t size)\n{\n    ImGuiDebugAllocEntry* entry = &info->LastEntriesBuf[info->LastEntriesIdx];\n    IM_UNUSED(ptr);\n    if (entry->FrameCount != frame_count)\n    {\n        info->LastEntriesIdx = (info->LastEntriesIdx + 1) % IM_ARRAYSIZE(info->LastEntriesBuf);\n        entry = &info->LastEntriesBuf[info->LastEntriesIdx];\n        entry->FrameCount = frame_count;\n        entry->AllocCount = entry->FreeCount = 0;\n    }\n    if (size != (size_t)-1)\n    {\n        //printf(\"[%05d] MemAlloc(%d) -> 0x%p\\n\", frame_count, (int)size, ptr);\n        entry->AllocCount++;\n        info->TotalAllocCount++;\n    }\n    else\n    {\n        //printf(\"[%05d] MemFree(0x%p)\\n\", frame_count, ptr);\n        entry->FreeCount++;\n        info->TotalFreeCount++;\n    }\n}\n\nconst char* ImGui::GetClipboardText()\n{\n    ImGuiContext& g = *GImGui;\n    return g.PlatformIO.Platform_GetClipboardTextFn ? g.PlatformIO.Platform_GetClipboardTextFn(&g) : \"\";\n}\n\nvoid ImGui::SetClipboardText(const char* text)\n{\n    ImGuiContext& g = *GImGui;\n    if (g.PlatformIO.Platform_SetClipboardTextFn != NULL)\n        g.PlatformIO.Platform_SetClipboardTextFn(&g, text);\n}\n\nconst char* ImGui::GetVersion()\n{\n    return IMGUI_VERSION;\n}\n\nImGuiIO& ImGui::GetIO()\n{\n    IM_ASSERT(GImGui != NULL && \"No current context. Did you call ImGui::CreateContext() and ImGui::SetCurrentContext() ?\");\n    return GImGui->IO;\n}\n\n// This variant exists to facilitate backends experimenting with multi-threaded parallel context. (#8069, #6293, #5856)\nImGuiIO& ImGui::GetIO(ImGuiContext* ctx)\n{\n    IM_ASSERT(ctx != NULL);\n    return ctx->IO;\n}\n\nImGuiPlatformIO& ImGui::GetPlatformIO()\n{\n    IM_ASSERT(GImGui != NULL && \"No current context. Did you call ImGui::CreateContext() and ImGui::SetCurrentContext()?\");\n    return GImGui->PlatformIO;\n}\n\n// This variant exists to facilitate backends experimenting with multi-threaded parallel context. (#8069, #6293, #5856)\nImGuiPlatformIO& ImGui::GetPlatformIO(ImGuiContext* ctx)\n{\n    IM_ASSERT(ctx != NULL);\n    return ctx->PlatformIO;\n}\n\n// Pass this to your backend rendering function! Valid after Render() and until the next call to NewFrame()\nImDrawData* ImGui::GetDrawData()\n{\n    ImGuiContext& g = *GImGui;\n    ImGuiViewportP* viewport = g.Viewports[0];\n    return viewport->DrawDataP.Valid ? &viewport->DrawDataP : NULL;\n}\n\ndouble ImGui::GetTime()\n{\n    return GImGui->Time;\n}\n\nint ImGui::GetFrameCount()\n{\n    return GImGui->FrameCount;\n}\n\nstatic ImDrawList* GetViewportBgFgDrawList(ImGuiViewportP* viewport, size_t drawlist_no, const char* drawlist_name)\n{\n    // Create the draw list on demand, because they are not frequently used for all viewports\n    ImGuiContext& g = *GImGui;\n    IM_ASSERT(drawlist_no < IM_ARRAYSIZE(viewport->BgFgDrawLists));\n    ImDrawList* draw_list = viewport->BgFgDrawLists[drawlist_no];\n    if (draw_list == NULL)\n    {\n        draw_list = IM_NEW(ImDrawList)(&g.DrawListSharedData);\n        draw_list->_OwnerName = drawlist_name;\n        viewport->BgFgDrawLists[drawlist_no] = draw_list;\n    }\n\n    // Our ImDrawList system requires that there is always a command\n    if (viewport->BgFgDrawListsLastFrame[drawlist_no] != g.FrameCount)\n    {\n        draw_list->_ResetForNewFrame();\n        draw_list->PushTextureID(g.IO.Fonts->TexID);\n        draw_list->PushClipRect(viewport->Pos, viewport->Pos + viewport->Size, false);\n        viewport->BgFgDrawListsLastFrame[drawlist_no] = g.FrameCount;\n    }\n    return draw_list;\n}\n\nImDrawList* ImGui::GetBackgroundDrawList(ImGuiViewport* viewport)\n{\n    return GetViewportBgFgDrawList((ImGuiViewportP*)viewport, 0, \"##Background\");\n}\n\nImDrawList* ImGui::GetBackgroundDrawList()\n{\n    ImGuiContext& g = *GImGui;\n    return GetBackgroundDrawList(g.Viewports[0]);\n}\n\nImDrawList* ImGui::GetForegroundDrawList(ImGuiViewport* viewport)\n{\n    return GetViewportBgFgDrawList((ImGuiViewportP*)viewport, 1, \"##Foreground\");\n}\n\nImDrawList* ImGui::GetForegroundDrawList()\n{\n    ImGuiContext& g = *GImGui;\n    return GetForegroundDrawList(g.Viewports[0]);\n}\n\nImDrawListSharedData* ImGui::GetDrawListSharedData()\n{\n    return &GImGui->DrawListSharedData;\n}\n\nvoid ImGui::StartMouseMovingWindow(ImGuiWindow* window)\n{\n    // Set ActiveId even if the _NoMove flag is set. Without it, dragging away from a window with _NoMove would activate hover on other windows.\n    // We _also_ call this when clicking in a window empty space when io.ConfigWindowsMoveFromTitleBarOnly is set, but clear g.MovingWindow afterward.\n    // This is because we want ActiveId to be set even when the window is not permitted to move.\n    ImGuiContext& g = *GImGui;\n    FocusWindow(window);\n    SetActiveID(window->MoveId, window);\n    if (g.IO.ConfigNavCursorVisibleAuto)\n        g.NavCursorVisible = false;\n    g.ActiveIdClickOffset = g.IO.MouseClickedPos[0] - window->RootWindow->Pos;\n    g.ActiveIdNoClearOnFocusLoss = true;\n    SetActiveIdUsingAllKeyboardKeys();\n\n    bool can_move_window = true;\n    if ((window->Flags & ImGuiWindowFlags_NoMove) || (window->RootWindow->Flags & ImGuiWindowFlags_NoMove))\n        can_move_window = false;\n    if (can_move_window)\n        g.MovingWindow = window;\n}\n\n// Handle mouse moving window\n// Note: moving window with the navigation keys (Square + d-pad / CTRL+TAB + Arrows) are processed in NavUpdateWindowing()\n// FIXME: We don't have strong guarantee that g.MovingWindow stay synced with g.ActiveId == g.MovingWindow->MoveId.\n// This is currently enforced by the fact that BeginDragDropSource() is setting all g.ActiveIdUsingXXXX flags to inhibit navigation inputs,\n// but if we should more thoroughly test cases where g.ActiveId or g.MovingWindow gets changed and not the other.\nvoid ImGui::UpdateMouseMovingWindowNewFrame()\n{\n    ImGuiContext& g = *GImGui;\n    if (g.MovingWindow != NULL)\n    {\n        // We actually want to move the root window. g.MovingWindow == window we clicked on (could be a child window).\n        // We track it to preserve Focus and so that generally ActiveIdWindow == MovingWindow and ActiveId == MovingWindow->MoveId for consistency.\n        KeepAliveID(g.ActiveId);\n        IM_ASSERT(g.MovingWindow && g.MovingWindow->RootWindow);\n        ImGuiWindow* moving_window = g.MovingWindow->RootWindow;\n        if (g.IO.MouseDown[0] && IsMousePosValid(&g.IO.MousePos))\n        {\n            ImVec2 pos = g.IO.MousePos - g.ActiveIdClickOffset;\n            SetWindowPos(moving_window, pos, ImGuiCond_Always);\n            FocusWindow(g.MovingWindow);\n        }\n        else\n        {\n            g.MovingWindow = NULL;\n            ClearActiveID();\n        }\n    }\n    else\n    {\n        // When clicking/dragging from a window that has the _NoMove flag, we still set the ActiveId in order to prevent hovering others.\n        if (g.ActiveIdWindow && g.ActiveIdWindow->MoveId == g.ActiveId)\n        {\n            KeepAliveID(g.ActiveId);\n            if (!g.IO.MouseDown[0])\n                ClearActiveID();\n        }\n    }\n}\n\n// Initiate focusing and moving window when clicking on empty space or title bar.\n// Initiate focusing window when clicking on a disabled item.\n// Handle left-click and right-click focus.\nvoid ImGui::UpdateMouseMovingWindowEndFrame()\n{\n    ImGuiContext& g = *GImGui;\n    if (g.ActiveId != 0 || (g.HoveredId != 0 && !g.HoveredIdIsDisabled))\n        return;\n\n    // Unless we just made a window/popup appear\n    if (g.NavWindow && g.NavWindow->Appearing)\n        return;\n\n    // Click on empty space to focus window and start moving\n    // (after we're done with all our widgets)\n    if (g.IO.MouseClicked[0])\n    {\n        // Handle the edge case of a popup being closed while clicking in its empty space.\n        // If we try to focus it, FocusWindow() > ClosePopupsOverWindow() will accidentally close any parent popups because they are not linked together any more.\n        ImGuiWindow* root_window = g.HoveredWindow ? g.HoveredWindow->RootWindow : NULL;\n        const bool is_closed_popup = root_window && (root_window->Flags & ImGuiWindowFlags_Popup) && !IsPopupOpen(root_window->PopupId, ImGuiPopupFlags_AnyPopupLevel);\n\n        if (root_window != NULL && !is_closed_popup)\n        {\n            StartMouseMovingWindow(g.HoveredWindow); //-V595\n\n            // Cancel moving if clicked outside of title bar\n            if (g.IO.ConfigWindowsMoveFromTitleBarOnly)\n                if (!(root_window->Flags & ImGuiWindowFlags_NoTitleBar))\n                    if (!root_window->TitleBarRect().Contains(g.IO.MouseClickedPos[0]))\n                        g.MovingWindow = NULL;\n\n            // Cancel moving if clicked over an item which was disabled or inhibited by popups\n            // (when g.HoveredIdIsDisabled == true && g.HoveredId == 0 we are inhibited by popups, when g.HoveredIdIsDisabled == true && g.HoveredId != 0 we are over a disabled item)0 already)\n            if (g.HoveredIdIsDisabled)\n                g.MovingWindow = NULL;\n        }\n        else if (root_window == NULL && g.NavWindow != NULL)\n        {\n            // Clicking on void disable focus\n            FocusWindow(NULL, ImGuiFocusRequestFlags_UnlessBelowModal);\n        }\n    }\n\n    // With right mouse button we close popups without changing focus based on where the mouse is aimed\n    // Instead, focus will be restored to the window under the bottom-most closed popup.\n    // (The left mouse button path calls FocusWindow on the hovered window, which will lead NewFrame->ClosePopupsOverWindow to trigger)\n    if (g.IO.MouseClicked[1] && g.HoveredId == 0)\n    {\n        // Find the top-most window between HoveredWindow and the top-most Modal Window.\n        // This is where we can trim the popup stack.\n        ImGuiWindow* modal = GetTopMostPopupModal();\n        bool hovered_window_above_modal = g.HoveredWindow && (modal == NULL || IsWindowAbove(g.HoveredWindow, modal));\n        ClosePopupsOverWindow(hovered_window_above_modal ? g.HoveredWindow : modal, true);\n    }\n}\n\nstatic bool IsWindowActiveAndVisible(ImGuiWindow* window)\n{\n    return (window->Active) && (!window->Hidden);\n}\n\n// The reason this is exposed in imgui_internal.h is: on touch-based system that don't have hovering, we want to dispatch inputs to the right target (imgui vs imgui+app)\nvoid ImGui::UpdateHoveredWindowAndCaptureFlags()\n{\n    ImGuiContext& g = *GImGui;\n    ImGuiIO& io = g.IO;\n\n    // FIXME-DPI: This storage was added on 2021/03/31 for test engine, but if we want to multiply WINDOWS_HOVER_PADDING\n    // by DpiScale, we need to make this window-agnostic anyhow, maybe need storing inside ImGuiWindow.\n    g.WindowsBorderHoverPadding = ImMax(ImMax(g.Style.TouchExtraPadding.x, g.Style.TouchExtraPadding.y), g.Style.WindowBorderHoverPadding);\n\n    // Find the window hovered by mouse:\n    // - Child windows can extend beyond the limit of their parent so we need to derive HoveredRootWindow from HoveredWindow.\n    // - When moving a window we can skip the search, which also conveniently bypasses the fact that window->WindowRectClipped is lagging as this point of the frame.\n    // - We also support the moved window toggling the NoInputs flag after moving has started in order to be able to detect windows below it, which is useful for e.g. docking mechanisms.\n    bool clear_hovered_windows = false;\n    FindHoveredWindowEx(g.IO.MousePos, false, &g.HoveredWindow, &g.HoveredWindowUnderMovingWindow);\n    g.HoveredWindowBeforeClear = g.HoveredWindow;\n\n    // Modal windows prevents mouse from hovering behind them.\n    ImGuiWindow* modal_window = GetTopMostPopupModal();\n    if (modal_window && g.HoveredWindow && !IsWindowWithinBeginStackOf(g.HoveredWindow->RootWindow, modal_window))\n        clear_hovered_windows = true;\n\n    // Disabled mouse hovering (we don't currently clear MousePos, we could)\n    if (io.ConfigFlags & ImGuiConfigFlags_NoMouse)\n        clear_hovered_windows = true;\n\n    // We track click ownership. When clicked outside of a window the click is owned by the application and\n    // won't report hovering nor request capture even while dragging over our windows afterward.\n    const bool has_open_popup = (g.OpenPopupStack.Size > 0);\n    const bool has_open_modal = (modal_window != NULL);\n    int mouse_earliest_down = -1;\n    bool mouse_any_down = false;\n    for (int i = 0; i < IM_ARRAYSIZE(io.MouseDown); i++)\n    {\n        if (io.MouseClicked[i])\n        {\n            io.MouseDownOwned[i] = (g.HoveredWindow != NULL) || has_open_popup;\n            io.MouseDownOwnedUnlessPopupClose[i] = (g.HoveredWindow != NULL) || has_open_modal;\n        }\n        mouse_any_down |= io.MouseDown[i];\n        if (io.MouseDown[i] || io.MouseReleased[i]) // Increase release frame for our evaluation of earliest button (#1392)\n            if (mouse_earliest_down == -1 || io.MouseClickedTime[i] < io.MouseClickedTime[mouse_earliest_down])\n                mouse_earliest_down = i;\n    }\n    const bool mouse_avail = (mouse_earliest_down == -1) || io.MouseDownOwned[mouse_earliest_down];\n    const bool mouse_avail_unless_popup_close = (mouse_earliest_down == -1) || io.MouseDownOwnedUnlessPopupClose[mouse_earliest_down];\n\n    // If mouse was first clicked outside of ImGui bounds we also cancel out hovering.\n    // FIXME: For patterns of drag and drop across OS windows, we may need to rework/remove this test (first committed 311c0ca9 on 2015/02)\n    const bool mouse_dragging_extern_payload = g.DragDropActive && (g.DragDropSourceFlags & ImGuiDragDropFlags_SourceExtern) != 0;\n    if (!mouse_avail && !mouse_dragging_extern_payload)\n        clear_hovered_windows = true;\n\n    if (clear_hovered_windows)\n        g.HoveredWindow = g.HoveredWindowUnderMovingWindow = NULL;\n\n    // Update io.WantCaptureMouse for the user application (true = dispatch mouse info to Dear ImGui only, false = dispatch mouse to Dear ImGui + underlying app)\n    // Update io.WantCaptureMouseAllowPopupClose (experimental) to give a chance for app to react to popup closure with a drag\n    if (g.WantCaptureMouseNextFrame != -1)\n    {\n        io.WantCaptureMouse = io.WantCaptureMouseUnlessPopupClose = (g.WantCaptureMouseNextFrame != 0);\n    }\n    else\n    {\n        io.WantCaptureMouse = (mouse_avail && (g.HoveredWindow != NULL || mouse_any_down)) || has_open_popup;\n        io.WantCaptureMouseUnlessPopupClose = (mouse_avail_unless_popup_close && (g.HoveredWindow != NULL || mouse_any_down)) || has_open_modal;\n    }\n\n    // Update io.WantCaptureKeyboard for the user application (true = dispatch keyboard info to Dear ImGui only, false = dispatch keyboard info to Dear ImGui + underlying app)\n    io.WantCaptureKeyboard = false;\n    if ((io.ConfigFlags & ImGuiConfigFlags_NoKeyboard) == 0)\n    {\n        if ((g.ActiveId != 0) || (modal_window != NULL))\n            io.WantCaptureKeyboard = true;\n        else if (io.NavActive && (io.ConfigFlags & ImGuiConfigFlags_NavEnableKeyboard) && io.ConfigNavCaptureKeyboard)\n            io.WantCaptureKeyboard = true;\n    }\n    if (g.WantCaptureKeyboardNextFrame != -1) // Manual override\n        io.WantCaptureKeyboard = (g.WantCaptureKeyboardNextFrame != 0);\n\n    // Update io.WantTextInput flag, this is to allow systems without a keyboard (e.g. mobile, hand-held) to show a software keyboard if possible\n    io.WantTextInput = (g.WantTextInputNextFrame != -1) ? (g.WantTextInputNextFrame != 0) : false;\n}\n\n// Called once a frame. Followed by SetCurrentFont() which sets up the remaining data.\n// FIXME-VIEWPORT: the concept of a single ClipRectFullscreen is not ideal!\nstatic void SetupDrawListSharedData()\n{\n    ImGuiContext& g = *GImGui;\n    ImRect virtual_space(FLT_MAX, FLT_MAX, -FLT_MAX, -FLT_MAX);\n    for (ImGuiViewportP* viewport : g.Viewports)\n        virtual_space.Add(viewport->GetMainRect());\n    g.DrawListSharedData.ClipRectFullscreen = virtual_space.ToVec4();\n    g.DrawListSharedData.CurveTessellationTol = g.Style.CurveTessellationTol;\n    g.DrawListSharedData.SetCircleTessellationMaxError(g.Style.CircleTessellationMaxError);\n    g.DrawListSharedData.InitialFlags = ImDrawListFlags_None;\n    if (g.Style.AntiAliasedLines)\n        g.DrawListSharedData.InitialFlags |= ImDrawListFlags_AntiAliasedLines;\n    if (g.Style.AntiAliasedLinesUseTex && !(g.IO.Fonts->Flags & ImFontAtlasFlags_NoBakedLines))\n        g.DrawListSharedData.InitialFlags |= ImDrawListFlags_AntiAliasedLinesUseTex;\n    if (g.Style.AntiAliasedFill)\n        g.DrawListSharedData.InitialFlags |= ImDrawListFlags_AntiAliasedFill;\n    if (g.IO.BackendFlags & ImGuiBackendFlags_RendererHasVtxOffset)\n        g.DrawListSharedData.InitialFlags |= ImDrawListFlags_AllowVtxOffset;\n    g.DrawListSharedData.InitialFringeScale = 1.0f; // FIXME-DPI: Change this for some DPI scaling experiments.\n}\n\nvoid ImGui::NewFrame()\n{\n    IM_ASSERT(GImGui != NULL && \"No current context. Did you call ImGui::CreateContext() and ImGui::SetCurrentContext() ?\");\n    ImGuiContext& g = *GImGui;\n\n    // Remove pending delete hooks before frame start.\n    // This deferred removal avoid issues of removal while iterating the hook vector\n    for (int n = g.Hooks.Size - 1; n >= 0; n--)\n        if (g.Hooks[n].Type == ImGuiContextHookType_PendingRemoval_)\n            g.Hooks.erase(&g.Hooks[n]);\n\n    CallContextHooks(&g, ImGuiContextHookType_NewFramePre);\n\n    // Check and assert for various common IO and Configuration mistakes\n    ErrorCheckNewFrameSanityChecks();\n\n    // Load settings on first frame, save settings when modified (after a delay)\n    UpdateSettings();\n\n    g.Time += g.IO.DeltaTime;\n    g.WithinFrameScope = true;\n    g.FrameCount += 1;\n    g.TooltipOverrideCount = 0;\n    g.WindowsActiveCount = 0;\n    g.MenusIdSubmittedThisFrame.resize(0);\n\n    // Calculate frame-rate for the user, as a purely luxurious feature\n    g.FramerateSecPerFrameAccum += g.IO.DeltaTime - g.FramerateSecPerFrame[g.FramerateSecPerFrameIdx];\n    g.FramerateSecPerFrame[g.FramerateSecPerFrameIdx] = g.IO.DeltaTime;\n    g.FramerateSecPerFrameIdx = (g.FramerateSecPerFrameIdx + 1) % IM_ARRAYSIZE(g.FramerateSecPerFrame);\n    g.FramerateSecPerFrameCount = ImMin(g.FramerateSecPerFrameCount + 1, IM_ARRAYSIZE(g.FramerateSecPerFrame));\n    g.IO.Framerate = (g.FramerateSecPerFrameAccum > 0.0f) ? (1.0f / (g.FramerateSecPerFrameAccum / (float)g.FramerateSecPerFrameCount)) : FLT_MAX;\n\n    // Process input queue (trickle as many events as possible), turn events into writes to IO structure\n    g.InputEventsTrail.resize(0);\n    UpdateInputEvents(g.IO.ConfigInputTrickleEventQueue);\n\n    // Update viewports (after processing input queue, so io.MouseHoveredViewport is set)\n    UpdateViewportsNewFrame();\n\n    // Setup current font and draw list shared data\n    g.IO.Fonts->Locked = true;\n    SetupDrawListSharedData();\n    SetCurrentFont(GetDefaultFont());\n    IM_ASSERT(g.Font->IsLoaded());\n\n    // Mark rendering data as invalid to prevent user who may have a handle on it to use it.\n    for (ImGuiViewportP* viewport : g.Viewports)\n        viewport->DrawDataP.Valid = false;\n\n    // Drag and drop keep the source ID alive so even if the source disappear our state is consistent\n    if (g.DragDropActive && g.DragDropPayload.SourceId == g.ActiveId)\n        KeepAliveID(g.DragDropPayload.SourceId);\n\n    // [DEBUG]\n    if (!g.IO.ConfigDebugHighlightIdConflicts || !g.IO.KeyCtrl) // Count is locked while holding CTRL\n        g.DebugDrawIdConflicts = 0;\n    if (g.IO.ConfigDebugHighlightIdConflicts && g.HoveredIdPreviousFrameItemCount > 1)\n        g.DebugDrawIdConflicts = g.HoveredIdPreviousFrame;\n\n    // Update HoveredId data\n    if (!g.HoveredIdPreviousFrame)\n        g.HoveredIdTimer = 0.0f;\n    if (!g.HoveredIdPreviousFrame || (g.HoveredId && g.ActiveId == g.HoveredId))\n        g.HoveredIdNotActiveTimer = 0.0f;\n    if (g.HoveredId)\n        g.HoveredIdTimer += g.IO.DeltaTime;\n    if (g.HoveredId && g.ActiveId != g.HoveredId)\n        g.HoveredIdNotActiveTimer += g.IO.DeltaTime;\n    g.HoveredIdPreviousFrame = g.HoveredId;\n    g.HoveredIdPreviousFrameItemCount = 0;\n    g.HoveredId = 0;\n    g.HoveredIdAllowOverlap = false;\n    g.HoveredIdIsDisabled = false;\n\n    // Clear ActiveID if the item is not alive anymore.\n    // In 1.87, the common most call to KeepAliveID() was moved from GetID() to ItemAdd().\n    // As a result, custom widget using ButtonBehavior() _without_ ItemAdd() need to call KeepAliveID() themselves.\n    if (g.ActiveId != 0 && g.ActiveIdIsAlive != g.ActiveId && g.ActiveIdPreviousFrame == g.ActiveId)\n    {\n        IMGUI_DEBUG_LOG_ACTIVEID(\"NewFrame(): ClearActiveID() because it isn't marked alive anymore!\\n\");\n        ClearActiveID();\n    }\n\n    // Update ActiveId data (clear reference to active widget if the widget isn't alive anymore)\n    if (g.ActiveId)\n        g.ActiveIdTimer += g.IO.DeltaTime;\n    g.LastActiveIdTimer += g.IO.DeltaTime;\n    g.ActiveIdPreviousFrame = g.ActiveId;\n    g.ActiveIdIsAlive = 0;\n    g.ActiveIdHasBeenEditedThisFrame = false;\n    g.ActiveIdIsJustActivated = false;\n    if (g.TempInputId != 0 && g.ActiveId != g.TempInputId)\n        g.TempInputId = 0;\n    if (g.ActiveId == 0)\n    {\n        g.ActiveIdUsingNavDirMask = 0x00;\n        g.ActiveIdUsingAllKeyboardKeys = false;\n    }\n    if (g.DeactivatedItemData.ElapseFrame < g.FrameCount)\n        g.DeactivatedItemData.ID = 0;\n    g.DeactivatedItemData.IsAlive = false;\n\n    // Record when we have been stationary as this state is preserved while over same item.\n    // FIXME: The way this is expressed means user cannot alter HoverStationaryDelay during the frame to use varying values.\n    // To allow this we should store HoverItemMaxStationaryTime+ID and perform the >= check in IsItemHovered() function.\n    if (g.HoverItemDelayId != 0 && g.MouseStationaryTimer >= g.Style.HoverStationaryDelay)\n        g.HoverItemUnlockedStationaryId = g.HoverItemDelayId;\n    else if (g.HoverItemDelayId == 0)\n        g.HoverItemUnlockedStationaryId = 0;\n    if (g.HoveredWindow != NULL && g.MouseStationaryTimer >= g.Style.HoverStationaryDelay)\n        g.HoverWindowUnlockedStationaryId = g.HoveredWindow->ID;\n    else if (g.HoveredWindow == NULL)\n        g.HoverWindowUnlockedStationaryId = 0;\n\n    // Update hover delay for IsItemHovered() with delays and tooltips\n    g.HoverItemDelayIdPreviousFrame = g.HoverItemDelayId;\n    if (g.HoverItemDelayId != 0)\n    {\n        g.HoverItemDelayTimer += g.IO.DeltaTime;\n        g.HoverItemDelayClearTimer = 0.0f;\n        g.HoverItemDelayId = 0;\n    }\n    else if (g.HoverItemDelayTimer > 0.0f)\n    {\n        // This gives a little bit of leeway before clearing the hover timer, allowing mouse to cross gaps\n        // We could expose 0.25f as style.HoverClearDelay but I am not sure of the logic yet, this is particularly subtle.\n        g.HoverItemDelayClearTimer += g.IO.DeltaTime;\n        if (g.HoverItemDelayClearTimer >= ImMax(0.25f, g.IO.DeltaTime * 2.0f)) // ~7 frames at 30 Hz + allow for low framerate\n            g.HoverItemDelayTimer = g.HoverItemDelayClearTimer = 0.0f; // May want a decaying timer, in which case need to clamp at max first, based on max of caller last requested timer.\n    }\n\n    // Drag and drop\n    g.DragDropAcceptIdPrev = g.DragDropAcceptIdCurr;\n    g.DragDropAcceptIdCurr = 0;\n    g.DragDropAcceptIdCurrRectSurface = FLT_MAX;\n    g.DragDropWithinSource = false;\n    g.DragDropWithinTarget = false;\n    g.DragDropHoldJustPressedId = 0;\n    g.TooltipPreviousWindow = NULL;\n\n    // Close popups on focus lost (currently wip/opt-in)\n    //if (g.IO.AppFocusLost)\n    //    ClosePopupsExceptModals();\n\n    // Update keyboard input state\n    UpdateKeyboardInputs();\n\n    //IM_ASSERT(g.IO.KeyCtrl == IsKeyDown(ImGuiKey_LeftCtrl) || IsKeyDown(ImGuiKey_RightCtrl));\n    //IM_ASSERT(g.IO.KeyShift == IsKeyDown(ImGuiKey_LeftShift) || IsKeyDown(ImGuiKey_RightShift));\n    //IM_ASSERT(g.IO.KeyAlt == IsKeyDown(ImGuiKey_LeftAlt) || IsKeyDown(ImGuiKey_RightAlt));\n    //IM_ASSERT(g.IO.KeySuper == IsKeyDown(ImGuiKey_LeftSuper) || IsKeyDown(ImGuiKey_RightSuper));\n\n    // Update keyboard/gamepad navigation\n    NavUpdate();\n\n    // Update mouse input state\n    UpdateMouseInputs();\n\n    // Mark all windows as not visible and compact unused memory.\n    IM_ASSERT(g.WindowsFocusOrder.Size <= g.Windows.Size);\n    const float memory_compact_start_time = (g.GcCompactAll || g.IO.ConfigMemoryCompactTimer < 0.0f) ? FLT_MAX : (float)g.Time - g.IO.ConfigMemoryCompactTimer;\n    for (ImGuiWindow* window : g.Windows)\n    {\n        window->WasActive = window->Active;\n        window->Active = false;\n        window->WriteAccessed = false;\n        window->BeginCountPreviousFrame = window->BeginCount;\n        window->BeginCount = 0;\n\n        // Garbage collect transient buffers of recently unused windows\n        if (!window->WasActive && !window->MemoryCompacted && window->LastTimeActive < memory_compact_start_time)\n            GcCompactTransientWindowBuffers(window);\n    }\n\n    // Find hovered window\n    // (needs to be before UpdateMouseMovingWindowNewFrame so we fill g.HoveredWindowUnderMovingWindow on the mouse release frame)\n    // (currently needs to be done after the WasActive=Active loop and FindHoveredWindowEx uses ->Active)\n    UpdateHoveredWindowAndCaptureFlags();\n\n    // Handle user moving window with mouse (at the beginning of the frame to avoid input lag or sheering)\n    UpdateMouseMovingWindowNewFrame();\n\n    // Background darkening/whitening\n    if (GetTopMostPopupModal() != NULL || (g.NavWindowingTarget != NULL && g.NavWindowingHighlightAlpha > 0.0f))\n        g.DimBgRatio = ImMin(g.DimBgRatio + g.IO.DeltaTime * 6.0f, 1.0f);\n    else\n        g.DimBgRatio = ImMax(g.DimBgRatio - g.IO.DeltaTime * 10.0f, 0.0f);\n\n    g.MouseCursor = ImGuiMouseCursor_Arrow;\n    g.WantCaptureMouseNextFrame = g.WantCaptureKeyboardNextFrame = g.WantTextInputNextFrame = -1;\n\n    // Platform IME data: reset for the frame\n    g.PlatformImeDataPrev = g.PlatformImeData;\n    g.PlatformImeData.WantVisible = false;\n\n    // Mouse wheel scrolling, scale\n    UpdateMouseWheel();\n\n    // Garbage collect transient buffers of recently unused tables\n    for (int i = 0; i < g.TablesLastTimeActive.Size; i++)\n        if (g.TablesLastTimeActive[i] >= 0.0f && g.TablesLastTimeActive[i] < memory_compact_start_time)\n            TableGcCompactTransientBuffers(g.Tables.GetByIndex(i));\n    for (ImGuiTableTempData& table_temp_data : g.TablesTempData)\n        if (table_temp_data.LastTimeActive >= 0.0f && table_temp_data.LastTimeActive < memory_compact_start_time)\n            TableGcCompactTransientBuffers(&table_temp_data);\n    if (g.GcCompactAll)\n        GcCompactTransientMiscBuffers();\n    g.GcCompactAll = false;\n\n    // Closing the focused window restore focus to the first active root window in descending z-order\n    if (g.NavWindow && !g.NavWindow->WasActive)\n        FocusTopMostWindowUnderOne(NULL, NULL, NULL, ImGuiFocusRequestFlags_RestoreFocusedChild);\n\n    // No window should be open at the beginning of the frame.\n    // But in order to allow the user to call NewFrame() multiple times without calling Render(), we are doing an explicit clear.\n    g.CurrentWindowStack.resize(0);\n    g.BeginPopupStack.resize(0);\n    g.ItemFlagsStack.resize(0);\n    g.ItemFlagsStack.push_back(ImGuiItemFlags_AutoClosePopups); // Default flags\n    g.CurrentItemFlags = g.ItemFlagsStack.back();\n    g.GroupStack.resize(0);\n\n    // [DEBUG] Update debug features\n#ifndef IMGUI_DISABLE_DEBUG_TOOLS\n    UpdateDebugToolItemPicker();\n    UpdateDebugToolStackQueries();\n    UpdateDebugToolFlashStyleColor();\n    if (g.DebugLocateFrames > 0 && --g.DebugLocateFrames == 0)\n    {\n        g.DebugLocateId = 0;\n        g.DebugBreakInLocateId = false;\n    }\n    if (g.DebugLogAutoDisableFrames > 0 && --g.DebugLogAutoDisableFrames == 0)\n    {\n        DebugLog(\"(Debug Log: Auto-disabled some ImGuiDebugLogFlags after 2 frames)\\n\");\n        g.DebugLogFlags &= ~g.DebugLogAutoDisableFlags;\n        g.DebugLogAutoDisableFlags = ImGuiDebugLogFlags_None;\n    }\n#endif\n\n    // Create implicit/fallback window - which we will only render it if the user has added something to it.\n    // We don't use \"Debug\" to avoid colliding with user trying to create a \"Debug\" window with custom flags.\n    // This fallback is particularly important as it prevents ImGui:: calls from crashing.\n    g.WithinFrameScopeWithImplicitWindow = true;\n    SetNextWindowSize(ImVec2(400, 400), ImGuiCond_FirstUseEver);\n    Begin(\"Debug##Default\");\n    IM_ASSERT(g.CurrentWindow->IsFallbackWindow == true);\n\n    // Store stack sizes\n    g.ErrorCountCurrentFrame = 0;\n    ErrorRecoveryStoreState(&g.StackSizesInNewFrame);\n\n    // [DEBUG] When io.ConfigDebugBeginReturnValue is set, we make Begin()/BeginChild() return false at different level of the window-stack,\n    // allowing to validate correct Begin/End behavior in user code.\n#ifndef IMGUI_DISABLE_DEBUG_TOOLS\n    if (g.IO.ConfigDebugBeginReturnValueLoop)\n        g.DebugBeginReturnValueCullDepth = (g.DebugBeginReturnValueCullDepth == -1) ? 0 : ((g.DebugBeginReturnValueCullDepth + ((g.FrameCount % 4) == 0 ? 1 : 0)) % 10);\n    else\n        g.DebugBeginReturnValueCullDepth = -1;\n#endif\n\n    CallContextHooks(&g, ImGuiContextHookType_NewFramePost);\n}\n\n// FIXME: Add a more explicit sort order in the window structure.\nstatic int IMGUI_CDECL ChildWindowComparer(const void* lhs, const void* rhs)\n{\n    const ImGuiWindow* const a = *(const ImGuiWindow* const *)lhs;\n    const ImGuiWindow* const b = *(const ImGuiWindow* const *)rhs;\n    if (int d = (a->Flags & ImGuiWindowFlags_Popup) - (b->Flags & ImGuiWindowFlags_Popup))\n        return d;\n    if (int d = (a->Flags & ImGuiWindowFlags_Tooltip) - (b->Flags & ImGuiWindowFlags_Tooltip))\n        return d;\n    return (a->BeginOrderWithinParent - b->BeginOrderWithinParent);\n}\n\nstatic void AddWindowToSortBuffer(ImVector<ImGuiWindow*>* out_sorted_windows, ImGuiWindow* window)\n{\n    out_sorted_windows->push_back(window);\n    if (window->Active)\n    {\n        int count = window->DC.ChildWindows.Size;\n        ImQsort(window->DC.ChildWindows.Data, (size_t)count, sizeof(ImGuiWindow*), ChildWindowComparer);\n        for (int i = 0; i < count; i++)\n        {\n            ImGuiWindow* child = window->DC.ChildWindows[i];\n            if (child->Active)\n                AddWindowToSortBuffer(out_sorted_windows, child);\n        }\n    }\n}\n\nstatic void AddWindowToDrawData(ImGuiWindow* window, int layer)\n{\n    ImGuiContext& g = *GImGui;\n    ImGuiViewportP* viewport = g.Viewports[0];\n    g.IO.MetricsRenderWindows++;\n    if (window->DrawList->_Splitter._Count > 1)\n        window->DrawList->ChannelsMerge(); // Merge if user forgot to merge back. Also required in Docking branch for ImGuiWindowFlags_DockNodeHost windows.\n    ImGui::AddDrawListToDrawDataEx(&viewport->DrawDataP, viewport->DrawDataBuilder.Layers[layer], window->DrawList);\n    for (ImGuiWindow* child : window->DC.ChildWindows)\n        if (IsWindowActiveAndVisible(child)) // Clipped children may have been marked not active\n            AddWindowToDrawData(child, layer);\n}\n\nstatic inline int GetWindowDisplayLayer(ImGuiWindow* window)\n{\n    return (window->Flags & ImGuiWindowFlags_Tooltip) ? 1 : 0;\n}\n\n// Layer is locked for the root window, however child windows may use a different viewport (e.g. extruding menu)\nstatic inline void AddRootWindowToDrawData(ImGuiWindow* window)\n{\n    AddWindowToDrawData(window, GetWindowDisplayLayer(window));\n}\n\nstatic void FlattenDrawDataIntoSingleLayer(ImDrawDataBuilder* builder)\n{\n    int n = builder->Layers[0]->Size;\n    int full_size = n;\n    for (int i = 1; i < IM_ARRAYSIZE(builder->Layers); i++)\n        full_size += builder->Layers[i]->Size;\n    builder->Layers[0]->resize(full_size);\n    for (int layer_n = 1; layer_n < IM_ARRAYSIZE(builder->Layers); layer_n++)\n    {\n        ImVector<ImDrawList*>* layer = builder->Layers[layer_n];\n        if (layer->empty())\n            continue;\n        memcpy(builder->Layers[0]->Data + n, layer->Data, layer->Size * sizeof(ImDrawList*));\n        n += layer->Size;\n        layer->resize(0);\n    }\n}\n\nstatic void InitViewportDrawData(ImGuiViewportP* viewport)\n{\n    ImGuiIO& io = ImGui::GetIO();\n    ImDrawData* draw_data = &viewport->DrawDataP;\n\n    viewport->DrawDataBuilder.Layers[0] = &draw_data->CmdLists;\n    viewport->DrawDataBuilder.Layers[1] = &viewport->DrawDataBuilder.LayerData1;\n    viewport->DrawDataBuilder.Layers[0]->resize(0);\n    viewport->DrawDataBuilder.Layers[1]->resize(0);\n\n    draw_data->Valid = true;\n    draw_data->CmdListsCount = 0;\n    draw_data->TotalVtxCount = draw_data->TotalIdxCount = 0;\n    draw_data->DisplayPos = viewport->Pos;\n    draw_data->DisplaySize = viewport->Size;\n    draw_data->FramebufferScale = io.DisplayFramebufferScale;\n    draw_data->OwnerViewport = viewport;\n}\n\n// Push a clipping rectangle for both ImGui logic (hit-testing etc.) and low-level ImDrawList rendering.\n// - When using this function it is sane to ensure that float are perfectly rounded to integer values,\n//   so that e.g. (int)(max.x-min.x) in user's render produce correct result.\n// - If the code here changes, may need to update code of functions like NextColumn() and PushColumnClipRect():\n//   some frequently called functions which to modify both channels and clipping simultaneously tend to use the\n//   more specialized SetWindowClipRectBeforeSetChannel() to avoid extraneous updates of underlying ImDrawCmds.\n// - This is analoguous to PushFont()/PopFont() in the sense that are a mixing a global stack and a window stack,\n//   which in the case of ClipRect is not so problematic but tends to be more restrictive for fonts.\nvoid ImGui::PushClipRect(const ImVec2& clip_rect_min, const ImVec2& clip_rect_max, bool intersect_with_current_clip_rect)\n{\n    ImGuiWindow* window = GetCurrentWindow();\n    window->DrawList->PushClipRect(clip_rect_min, clip_rect_max, intersect_with_current_clip_rect);\n    window->ClipRect = window->DrawList->_ClipRectStack.back();\n}\n\nvoid ImGui::PopClipRect()\n{\n    ImGuiWindow* window = GetCurrentWindow();\n    window->DrawList->PopClipRect();\n    window->ClipRect = window->DrawList->_ClipRectStack.back();\n}\n\nstatic void ImGui::RenderDimmedBackgroundBehindWindow(ImGuiWindow* window, ImU32 col)\n{\n    if ((col & IM_COL32_A_MASK) == 0)\n        return;\n\n    ImGuiViewportP* viewport = (ImGuiViewportP*)GetMainViewport();\n    ImRect viewport_rect = viewport->GetMainRect();\n\n    // Draw behind window by moving the draw command at the FRONT of the draw list\n    {\n        // We've already called AddWindowToDrawData() which called DrawList->ChannelsMerge() on DockNodeHost windows,\n        // and draw list have been trimmed already, hence the explicit recreation of a draw command if missing.\n        // FIXME: This is creating complication, might be simpler if we could inject a drawlist in drawdata at a given position and not attempt to manipulate ImDrawCmd order.\n        ImDrawList* draw_list = window->RootWindow->DrawList;\n        if (draw_list->CmdBuffer.Size == 0)\n            draw_list->AddDrawCmd();\n        draw_list->PushClipRect(viewport_rect.Min - ImVec2(1, 1), viewport_rect.Max + ImVec2(1, 1), false); // FIXME: Need to stricty ensure ImDrawCmd are not merged (ElemCount==6 checks below will verify that)\n        draw_list->AddRectFilled(viewport_rect.Min, viewport_rect.Max, col);\n        ImDrawCmd cmd = draw_list->CmdBuffer.back();\n        IM_ASSERT(cmd.ElemCount == 6);\n        draw_list->CmdBuffer.pop_back();\n        draw_list->CmdBuffer.push_front(cmd);\n        draw_list->AddDrawCmd(); // We need to create a command as CmdBuffer.back().IdxOffset won't be correct if we append to same command.\n        draw_list->PopClipRect();\n    }\n}\n\nImGuiWindow* ImGui::FindBottomMostVisibleWindowWithinBeginStack(ImGuiWindow* parent_window)\n{\n    ImGuiContext& g = *GImGui;\n    ImGuiWindow* bottom_most_visible_window = parent_window;\n    for (int i = FindWindowDisplayIndex(parent_window); i >= 0; i--)\n    {\n        ImGuiWindow* window = g.Windows[i];\n        if (window->Flags & ImGuiWindowFlags_ChildWindow)\n            continue;\n        if (!IsWindowWithinBeginStackOf(window, parent_window))\n            break;\n        if (IsWindowActiveAndVisible(window) && GetWindowDisplayLayer(window) <= GetWindowDisplayLayer(parent_window))\n            bottom_most_visible_window = window;\n    }\n    return bottom_most_visible_window;\n}\n\nstatic void ImGui::RenderDimmedBackgrounds()\n{\n    ImGuiContext& g = *GImGui;\n    ImGuiWindow* modal_window = GetTopMostAndVisiblePopupModal();\n    if (g.DimBgRatio <= 0.0f && g.NavWindowingHighlightAlpha <= 0.0f)\n        return;\n    const bool dim_bg_for_modal = (modal_window != NULL);\n    const bool dim_bg_for_window_list = (g.NavWindowingTargetAnim != NULL && g.NavWindowingTargetAnim->Active);\n    if (!dim_bg_for_modal && !dim_bg_for_window_list)\n        return;\n\n    if (dim_bg_for_modal)\n    {\n        // Draw dimming behind modal or a begin stack child, whichever comes first in draw order.\n        ImGuiWindow* dim_behind_window = FindBottomMostVisibleWindowWithinBeginStack(modal_window);\n        RenderDimmedBackgroundBehindWindow(dim_behind_window, GetColorU32(modal_window->DC.ModalDimBgColor, g.DimBgRatio));\n    }\n    else if (dim_bg_for_window_list)\n    {\n        // Draw dimming behind CTRL+Tab target window and behind CTRL+Tab UI window\n        RenderDimmedBackgroundBehindWindow(g.NavWindowingTargetAnim, GetColorU32(ImGuiCol_NavWindowingDimBg, g.DimBgRatio));\n\n        // Draw border around CTRL+Tab target window\n        ImGuiWindow* window = g.NavWindowingTargetAnim;\n        ImGuiViewport* viewport = GetMainViewport();\n        float distance = g.FontSize;\n        ImRect bb = window->Rect();\n        bb.Expand(distance);\n        if (bb.GetWidth() >= viewport->Size.x && bb.GetHeight() >= viewport->Size.y)\n            bb.Expand(-distance - 1.0f); // If a window fits the entire viewport, adjust its highlight inward\n        if (window->DrawList->CmdBuffer.Size == 0)\n            window->DrawList->AddDrawCmd();\n        window->DrawList->PushClipRect(viewport->Pos, viewport->Pos + viewport->Size);\n        window->DrawList->AddRect(bb.Min, bb.Max, GetColorU32(ImGuiCol_NavWindowingHighlight, g.NavWindowingHighlightAlpha), window->WindowRounding, 0, 3.0f);\n        window->DrawList->PopClipRect();\n    }\n}\n\n// This is normally called by Render(). You may want to call it directly if you want to avoid calling Render() but the gain will be very minimal.\nvoid ImGui::EndFrame()\n{\n    ImGuiContext& g = *GImGui;\n    IM_ASSERT(g.Initialized);\n\n    // Don't process EndFrame() multiple times.\n    if (g.FrameCountEnded == g.FrameCount)\n        return;\n    IM_ASSERT(g.WithinFrameScope && \"Forgot to call ImGui::NewFrame()?\");\n\n    CallContextHooks(&g, ImGuiContextHookType_EndFramePre);\n\n    // [EXPERIMENTAL] Recover from errors\n    if (g.IO.ConfigErrorRecovery)\n        ErrorRecoveryTryToRecoverState(&g.StackSizesInNewFrame);\n    ErrorCheckEndFrameSanityChecks();\n    ErrorCheckEndFrameFinalizeErrorTooltip();\n\n    // Notify Platform/OS when our Input Method Editor cursor has moved (e.g. CJK inputs using Microsoft IME)\n    ImGuiPlatformImeData* ime_data = &g.PlatformImeData;\n    if (g.PlatformIO.Platform_SetImeDataFn != NULL && memcmp(ime_data, &g.PlatformImeDataPrev, sizeof(ImGuiPlatformImeData)) != 0)\n    {\n        IMGUI_DEBUG_LOG_IO(\"[io] Calling Platform_SetImeDataFn(): WantVisible: %d, InputPos (%.2f,%.2f)\\n\", ime_data->WantVisible, ime_data->InputPos.x, ime_data->InputPos.y);\n        ImGuiViewport* viewport = GetMainViewport();\n        g.PlatformIO.Platform_SetImeDataFn(&g, viewport, ime_data);\n    }\n\n    // Hide implicit/fallback \"Debug\" window if it hasn't been used\n    g.WithinFrameScopeWithImplicitWindow = false;\n    if (g.CurrentWindow && !g.CurrentWindow->WriteAccessed)\n        g.CurrentWindow->Active = false;\n    End();\n\n    // Update navigation: CTRL+Tab, wrap-around requests\n    NavEndFrame();\n\n    // Drag and Drop: Elapse payload (if delivered, or if source stops being submitted)\n    if (g.DragDropActive)\n    {\n        bool is_delivered = g.DragDropPayload.Delivery;\n        bool is_elapsed = (g.DragDropSourceFrameCount + 1 < g.FrameCount) && ((g.DragDropSourceFlags & ImGuiDragDropFlags_PayloadAutoExpire) || g.DragDropMouseButton == -1 || !IsMouseDown(g.DragDropMouseButton));\n        if (is_delivered || is_elapsed)\n            ClearDragDrop();\n    }\n\n    // Drag and Drop: Fallback for missing source tooltip. This is not ideal but better than nothing.\n    // If you want to handle source item disappearing: instead of submitting your description tooltip\n    // in the BeginDragDropSource() block of the dragged item, you can submit them from a safe single spot\n    // (e.g. end of your item loop, or before EndFrame) by reading payload data.\n    // In the typical case, the contents of drag tooltip should be possible to infer solely from payload data.\n    if (g.DragDropActive && g.DragDropSourceFrameCount + 1 < g.FrameCount && !(g.DragDropSourceFlags & ImGuiDragDropFlags_SourceNoPreviewTooltip))\n    {\n        g.DragDropWithinSource = true;\n        SetTooltip(\"...\");\n        g.DragDropWithinSource = false;\n    }\n\n    // End frame\n    g.WithinFrameScope = false;\n    g.FrameCountEnded = g.FrameCount;\n\n    // Initiate moving window + handle left-click and right-click focus\n    UpdateMouseMovingWindowEndFrame();\n\n    // Sort the window list so that all child windows are after their parent\n    // We cannot do that on FocusWindow() because children may not exist yet\n    g.WindowsTempSortBuffer.resize(0);\n    g.WindowsTempSortBuffer.reserve(g.Windows.Size);\n    for (ImGuiWindow* window : g.Windows)\n    {\n        if (window->Active && (window->Flags & ImGuiWindowFlags_ChildWindow))       // if a child is active its parent will add it\n            continue;\n        AddWindowToSortBuffer(&g.WindowsTempSortBuffer, window);\n    }\n\n    // This usually assert if there is a mismatch between the ImGuiWindowFlags_ChildWindow / ParentWindow values and DC.ChildWindows[] in parents, aka we've done something wrong.\n    IM_ASSERT(g.Windows.Size == g.WindowsTempSortBuffer.Size);\n    g.Windows.swap(g.WindowsTempSortBuffer);\n    g.IO.MetricsActiveWindows = g.WindowsActiveCount;\n\n    // Unlock font atlas\n    g.IO.Fonts->Locked = false;\n\n    // Clear Input data for next frame\n    g.IO.MousePosPrev = g.IO.MousePos;\n    g.IO.AppFocusLost = false;\n    g.IO.MouseWheel = g.IO.MouseWheelH = 0.0f;\n    g.IO.InputQueueCharacters.resize(0);\n\n    CallContextHooks(&g, ImGuiContextHookType_EndFramePost);\n}\n\n// Prepare the data for rendering so you can call GetDrawData()\n// (As with anything within the ImGui:: namspace this doesn't touch your GPU or graphics API at all:\n// it is the role of the ImGui_ImplXXXX_RenderDrawData() function provided by the renderer backend)\nvoid ImGui::Render()\n{\n    ImGuiContext& g = *GImGui;\n    IM_ASSERT(g.Initialized);\n\n    if (g.FrameCountEnded != g.FrameCount)\n        EndFrame();\n    if (g.FrameCountRendered == g.FrameCount)\n        return;\n    g.FrameCountRendered = g.FrameCount;\n\n    g.IO.MetricsRenderWindows = 0;\n    CallContextHooks(&g, ImGuiContextHookType_RenderPre);\n\n    // Add background ImDrawList (for each active viewport)\n    for (ImGuiViewportP* viewport : g.Viewports)\n    {\n        InitViewportDrawData(viewport);\n        if (viewport->BgFgDrawLists[0] != NULL)\n            AddDrawListToDrawDataEx(&viewport->DrawDataP, viewport->DrawDataBuilder.Layers[0], GetBackgroundDrawList(viewport));\n    }\n\n    // Draw modal/window whitening backgrounds\n    RenderDimmedBackgrounds();\n\n    // Add ImDrawList to render\n    ImGuiWindow* windows_to_render_top_most[2];\n    windows_to_render_top_most[0] = (g.NavWindowingTarget && !(g.NavWindowingTarget->Flags & ImGuiWindowFlags_NoBringToFrontOnFocus)) ? g.NavWindowingTarget->RootWindow : NULL;\n    windows_to_render_top_most[1] = (g.NavWindowingTarget ? g.NavWindowingListWindow : NULL);\n    for (ImGuiWindow* window : g.Windows)\n    {\n        IM_MSVC_WARNING_SUPPRESS(6011); // Static Analysis false positive \"warning C6011: Dereferencing NULL pointer 'window'\"\n        if (IsWindowActiveAndVisible(window) && (window->Flags & ImGuiWindowFlags_ChildWindow) == 0 && window != windows_to_render_top_most[0] && window != windows_to_render_top_most[1])\n            AddRootWindowToDrawData(window);\n    }\n    for (int n = 0; n < IM_ARRAYSIZE(windows_to_render_top_most); n++)\n        if (windows_to_render_top_most[n] && IsWindowActiveAndVisible(windows_to_render_top_most[n])) // NavWindowingTarget is always temporarily displayed as the top-most window\n            AddRootWindowToDrawData(windows_to_render_top_most[n]);\n\n    // Draw software mouse cursor if requested by io.MouseDrawCursor flag\n    if (g.IO.MouseDrawCursor && g.MouseCursor != ImGuiMouseCursor_None)\n        RenderMouseCursor(g.IO.MousePos, g.Style.MouseCursorScale, g.MouseCursor, IM_COL32_WHITE, IM_COL32_BLACK, IM_COL32(0, 0, 0, 48));\n\n    // Setup ImDrawData structures for end-user\n    g.IO.MetricsRenderVertices = g.IO.MetricsRenderIndices = 0;\n    for (ImGuiViewportP* viewport : g.Viewports)\n    {\n        FlattenDrawDataIntoSingleLayer(&viewport->DrawDataBuilder);\n\n        // Add foreground ImDrawList (for each active viewport)\n        if (viewport->BgFgDrawLists[1] != NULL)\n            AddDrawListToDrawDataEx(&viewport->DrawDataP, viewport->DrawDataBuilder.Layers[0], GetForegroundDrawList(viewport));\n\n        // We call _PopUnusedDrawCmd() last thing, as RenderDimmedBackgrounds() rely on a valid command being there (especially in docking branch).\n        ImDrawData* draw_data = &viewport->DrawDataP;\n        IM_ASSERT(draw_data->CmdLists.Size == draw_data->CmdListsCount);\n        for (ImDrawList* draw_list : draw_data->CmdLists)\n            draw_list->_PopUnusedDrawCmd();\n\n        g.IO.MetricsRenderVertices += draw_data->TotalVtxCount;\n        g.IO.MetricsRenderIndices += draw_data->TotalIdxCount;\n    }\n\n    CallContextHooks(&g, ImGuiContextHookType_RenderPost);\n}\n\n// Calculate text size. Text can be multi-line. Optionally ignore text after a ## marker.\n// CalcTextSize(\"\") should return ImVec2(0.0f, g.FontSize)\nImVec2 ImGui::CalcTextSize(const char* text, const char* text_end, bool hide_text_after_double_hash, float wrap_width)\n{\n    ImGuiContext& g = *GImGui;\n\n    const char* text_display_end;\n    if (hide_text_after_double_hash)\n        text_display_end = FindRenderedTextEnd(text, text_end);      // Hide anything after a '##' string\n    else\n        text_display_end = text_end;\n\n    ImFont* font = g.Font;\n    const float font_size = g.FontSize;\n    if (text == text_display_end)\n        return ImVec2(0.0f, font_size);\n    ImVec2 text_size = font->CalcTextSizeA(font_size, FLT_MAX, wrap_width, text, text_display_end, NULL);\n\n    // Round\n    // FIXME: This has been here since Dec 2015 (7b0bf230) but down the line we want this out.\n    // FIXME: Investigate using ceilf or e.g.\n    // - https://git.musl-libc.org/cgit/musl/tree/src/math/ceilf.c\n    // - https://embarkstudios.github.io/rust-gpu/api/src/libm/math/ceilf.rs.html\n    text_size.x = IM_TRUNC(text_size.x + 0.99999f);\n\n    return text_size;\n}\n\n// Find window given position, search front-to-back\n// - Typically write output back to g.HoveredWindow and g.HoveredWindowUnderMovingWindow.\n// - FIXME: Note that we have an inconsequential lag here: OuterRectClipped is updated in Begin(), so windows moved programmatically\n//   with SetWindowPos() and not SetNextWindowPos() will have that rectangle lagging by a frame at the time FindHoveredWindow() is\n//   called, aka before the next Begin(). Moving window isn't affected.\n// - The 'find_first_and_in_any_viewport = true' mode is only used by TestEngine. It is simpler to maintain here.\nvoid ImGui::FindHoveredWindowEx(const ImVec2& pos, bool find_first_and_in_any_viewport, ImGuiWindow** out_hovered_window, ImGuiWindow** out_hovered_window_under_moving_window)\n{\n    ImGuiContext& g = *GImGui;\n    ImGuiWindow* hovered_window = NULL;\n    ImGuiWindow* hovered_window_under_moving_window = NULL;\n\n    if (find_first_and_in_any_viewport == false && g.MovingWindow && !(g.MovingWindow->Flags & ImGuiWindowFlags_NoMouseInputs))\n        hovered_window = g.MovingWindow;\n\n    ImVec2 padding_regular = g.Style.TouchExtraPadding;\n    ImVec2 padding_for_resize = ImMax(g.Style.TouchExtraPadding, ImVec2(g.Style.WindowBorderHoverPadding, g.Style.WindowBorderHoverPadding));\n    for (int i = g.Windows.Size - 1; i >= 0; i--)\n    {\n        ImGuiWindow* window = g.Windows[i];\n        IM_MSVC_WARNING_SUPPRESS(28182); // [Static Analyzer] Dereferencing NULL pointer.\n        if (!window->WasActive || window->Hidden)\n            continue;\n        if (window->Flags & ImGuiWindowFlags_NoMouseInputs)\n            continue;\n\n        // Using the clipped AABB, a child window will typically be clipped by its parent (not always)\n        ImVec2 hit_padding = (window->Flags & (ImGuiWindowFlags_NoResize | ImGuiWindowFlags_AlwaysAutoResize)) ? padding_regular : padding_for_resize;\n        if (!window->OuterRectClipped.ContainsWithPad(pos, hit_padding))\n            continue;\n\n        // Support for one rectangular hole in any given window\n        // FIXME: Consider generalizing hit-testing override (with more generic data, callback, etc.) (#1512)\n        if (window->HitTestHoleSize.x != 0)\n        {\n            ImVec2 hole_pos(window->Pos.x + (float)window->HitTestHoleOffset.x, window->Pos.y + (float)window->HitTestHoleOffset.y);\n            ImVec2 hole_size((float)window->HitTestHoleSize.x, (float)window->HitTestHoleSize.y);\n            if (ImRect(hole_pos, hole_pos + hole_size).Contains(pos))\n                continue;\n        }\n\n        if (find_first_and_in_any_viewport)\n        {\n            hovered_window = window;\n            break;\n        }\n        else\n        {\n            if (hovered_window == NULL)\n                hovered_window = window;\n            IM_MSVC_WARNING_SUPPRESS(28182); // [Static Analyzer] Dereferencing NULL pointer.\n            if (hovered_window_under_moving_window == NULL && (!g.MovingWindow || window->RootWindow != g.MovingWindow->RootWindow))\n                hovered_window_under_moving_window = window;\n            if (hovered_window && hovered_window_under_moving_window)\n                break;\n        }\n    }\n\n    *out_hovered_window = hovered_window;\n    if (out_hovered_window_under_moving_window != NULL)\n        *out_hovered_window_under_moving_window = hovered_window_under_moving_window;\n}\n\nbool ImGui::IsItemActive()\n{\n    ImGuiContext& g = *GImGui;\n    if (g.ActiveId)\n        return g.ActiveId == g.LastItemData.ID;\n    return false;\n}\n\nbool ImGui::IsItemActivated()\n{\n    ImGuiContext& g = *GImGui;\n    if (g.ActiveId)\n        if (g.ActiveId == g.LastItemData.ID && g.ActiveIdPreviousFrame != g.LastItemData.ID)\n            return true;\n    return false;\n}\n\nbool ImGui::IsItemDeactivated()\n{\n    ImGuiContext& g = *GImGui;\n    if (g.LastItemData.StatusFlags & ImGuiItemStatusFlags_HasDeactivated)\n        return (g.LastItemData.StatusFlags & ImGuiItemStatusFlags_Deactivated) != 0;\n    return (g.DeactivatedItemData.ID == g.LastItemData.ID && g.LastItemData.ID != 0 && g.DeactivatedItemData.ElapseFrame >= g.FrameCount);\n}\n\nbool ImGui::IsItemDeactivatedAfterEdit()\n{\n    ImGuiContext& g = *GImGui;\n    return IsItemDeactivated() && g.DeactivatedItemData.HasBeenEditedBefore;\n}\n\n// == (GetItemID() == GetFocusID() && GetFocusID() != 0)\nbool ImGui::IsItemFocused()\n{\n    ImGuiContext& g = *GImGui;\n    return g.NavId == g.LastItemData.ID && g.NavId != 0;\n}\n\n// Important: this can be useful but it is NOT equivalent to the behavior of e.g.Button()!\n// Most widgets have specific reactions based on mouse-up/down state, mouse position etc.\nbool ImGui::IsItemClicked(ImGuiMouseButton mouse_button)\n{\n    return IsMouseClicked(mouse_button) && IsItemHovered(ImGuiHoveredFlags_None);\n}\n\nbool ImGui::IsItemToggledOpen()\n{\n    ImGuiContext& g = *GImGui;\n    return (g.LastItemData.StatusFlags & ImGuiItemStatusFlags_ToggledOpen) ? true : false;\n}\n\n// Call after a Selectable() or TreeNode() involved in multi-selection.\n// Useful if you need the per-item information before reaching EndMultiSelect(), e.g. for rendering purpose.\n// This is only meant to be called inside a BeginMultiSelect()/EndMultiSelect() block.\n// (Outside of multi-select, it would be misleading/ambiguous to report this signal, as widgets\n// return e.g. a pressed event and user code is in charge of altering selection in ways we cannot predict.)\nbool ImGui::IsItemToggledSelection()\n{\n    ImGuiContext& g = *GImGui;\n    IM_ASSERT(g.CurrentMultiSelect != NULL); // Can only be used inside a BeginMultiSelect()/EndMultiSelect()\n    return (g.LastItemData.StatusFlags & ImGuiItemStatusFlags_ToggledSelection) ? true : false;\n}\n\n// IMPORTANT: If you are trying to check whether your mouse should be dispatched to Dear ImGui or to your underlying app,\n// you should not use this function! Use the 'io.WantCaptureMouse' boolean for that!\n// Refer to FAQ entry \"How can I tell whether to dispatch mouse/keyboard to Dear ImGui or my application?\" for details.\nbool ImGui::IsAnyItemHovered()\n{\n    ImGuiContext& g = *GImGui;\n    return g.HoveredId != 0 || g.HoveredIdPreviousFrame != 0;\n}\n\nbool ImGui::IsAnyItemActive()\n{\n    ImGuiContext& g = *GImGui;\n    return g.ActiveId != 0;\n}\n\nbool ImGui::IsAnyItemFocused()\n{\n    ImGuiContext& g = *GImGui;\n    return g.NavId != 0 && g.NavCursorVisible;\n}\n\nbool ImGui::IsItemVisible()\n{\n    ImGuiContext& g = *GImGui;\n    return (g.LastItemData.StatusFlags & ImGuiItemStatusFlags_Visible) != 0;\n}\n\nbool ImGui::IsItemEdited()\n{\n    ImGuiContext& g = *GImGui;\n    return (g.LastItemData.StatusFlags & ImGuiItemStatusFlags_Edited) != 0;\n}\n\n// Allow next item to be overlapped by subsequent items.\n// This works by requiring HoveredId to match for two subsequent frames,\n// so if a following items overwrite it our interactions will naturally be disabled.\nvoid ImGui::SetNextItemAllowOverlap()\n{\n    ImGuiContext& g = *GImGui;\n    g.NextItemData.ItemFlags |= ImGuiItemFlags_AllowOverlap;\n}\n\n#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS\n// Allow last item to be overlapped by a subsequent item. Both may be activated during the same frame before the later one takes priority.\n// FIXME-LEGACY: Use SetNextItemAllowOverlap() *before* your item instead.\nvoid ImGui::SetItemAllowOverlap()\n{\n    ImGuiContext& g = *GImGui;\n    ImGuiID id = g.LastItemData.ID;\n    if (g.HoveredId == id)\n        g.HoveredIdAllowOverlap = true;\n    if (g.ActiveId == id) // Before we made this obsolete, most calls to SetItemAllowOverlap() used to avoid this path by testing g.ActiveId != id.\n        g.ActiveIdAllowOverlap = true;\n}\n#endif\n\n// This is a shortcut for not taking ownership of 100+ keys, frequently used by drag operations.\n// FIXME: It might be undesirable that this will likely disable KeyOwner-aware shortcuts systems. Consider a more fine-tuned version if needed?\nvoid ImGui::SetActiveIdUsingAllKeyboardKeys()\n{\n    ImGuiContext& g = *GImGui;\n    IM_ASSERT(g.ActiveId != 0);\n    g.ActiveIdUsingNavDirMask = (1 << ImGuiDir_COUNT) - 1;\n    g.ActiveIdUsingAllKeyboardKeys = true;\n    NavMoveRequestCancel();\n}\n\nImGuiID ImGui::GetItemID()\n{\n    ImGuiContext& g = *GImGui;\n    return g.LastItemData.ID;\n}\n\nImVec2 ImGui::GetItemRectMin()\n{\n    ImGuiContext& g = *GImGui;\n    return g.LastItemData.Rect.Min;\n}\n\nImVec2 ImGui::GetItemRectMax()\n{\n    ImGuiContext& g = *GImGui;\n    return g.LastItemData.Rect.Max;\n}\n\nImVec2 ImGui::GetItemRectSize()\n{\n    ImGuiContext& g = *GImGui;\n    return g.LastItemData.Rect.GetSize();\n}\n\n// Prior to v1.90 2023/10/16, the BeginChild() function took a 'bool border = false' parameter instead of 'ImGuiChildFlags child_flags = 0'.\n// ImGuiChildFlags_Borders is defined as always == 1 in order to allow old code passing 'true'. Read comments in imgui.h for details!\nbool ImGui::BeginChild(const char* str_id, const ImVec2& size_arg, ImGuiChildFlags child_flags, ImGuiWindowFlags window_flags)\n{\n    ImGuiID id = GetCurrentWindow()->GetID(str_id);\n    return BeginChildEx(str_id, id, size_arg, child_flags, window_flags);\n}\n\nbool ImGui::BeginChild(ImGuiID id, const ImVec2& size_arg, ImGuiChildFlags child_flags, ImGuiWindowFlags window_flags)\n{\n    return BeginChildEx(NULL, id, size_arg, child_flags, window_flags);\n}\n\nbool ImGui::BeginChildEx(const char* name, ImGuiID id, const ImVec2& size_arg, ImGuiChildFlags child_flags, ImGuiWindowFlags window_flags)\n{\n    ImGuiContext& g = *GImGui;\n    ImGuiWindow* parent_window = g.CurrentWindow;\n    IM_ASSERT(id != 0);\n\n    // Sanity check as it is likely that some user will accidentally pass ImGuiWindowFlags into the ImGuiChildFlags argument.\n    const ImGuiChildFlags ImGuiChildFlags_SupportedMask_ = ImGuiChildFlags_Borders | ImGuiChildFlags_AlwaysUseWindowPadding | ImGuiChildFlags_ResizeX | ImGuiChildFlags_ResizeY | ImGuiChildFlags_AutoResizeX | ImGuiChildFlags_AutoResizeY | ImGuiChildFlags_AlwaysAutoResize | ImGuiChildFlags_FrameStyle | ImGuiChildFlags_NavFlattened;\n    IM_UNUSED(ImGuiChildFlags_SupportedMask_);\n    IM_ASSERT((child_flags & ~ImGuiChildFlags_SupportedMask_) == 0 && \"Illegal ImGuiChildFlags value. Did you pass ImGuiWindowFlags values instead of ImGuiChildFlags?\");\n    IM_ASSERT((window_flags & ImGuiWindowFlags_AlwaysAutoResize) == 0 && \"Cannot specify ImGuiWindowFlags_AlwaysAutoResize for BeginChild(). Use ImGuiChildFlags_AlwaysAutoResize!\");\n    if (child_flags & ImGuiChildFlags_AlwaysAutoResize)\n    {\n        IM_ASSERT((child_flags & (ImGuiChildFlags_ResizeX | ImGuiChildFlags_ResizeY)) == 0 && \"Cannot use ImGuiChildFlags_ResizeX or ImGuiChildFlags_ResizeY with ImGuiChildFlags_AlwaysAutoResize!\");\n        IM_ASSERT((child_flags & (ImGuiChildFlags_AutoResizeX | ImGuiChildFlags_AutoResizeY)) != 0 && \"Must use ImGuiChildFlags_AutoResizeX or ImGuiChildFlags_AutoResizeY with ImGuiChildFlags_AlwaysAutoResize!\");\n    }\n#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS\n    if (window_flags & ImGuiWindowFlags_AlwaysUseWindowPadding)\n        child_flags |= ImGuiChildFlags_AlwaysUseWindowPadding;\n    if (window_flags & ImGuiWindowFlags_NavFlattened)\n        child_flags |= ImGuiChildFlags_NavFlattened;\n#endif\n    if (child_flags & ImGuiChildFlags_AutoResizeX)\n        child_flags &= ~ImGuiChildFlags_ResizeX;\n    if (child_flags & ImGuiChildFlags_AutoResizeY)\n        child_flags &= ~ImGuiChildFlags_ResizeY;\n\n    // Set window flags\n    window_flags |= ImGuiWindowFlags_ChildWindow | ImGuiWindowFlags_NoTitleBar;\n    window_flags |= (parent_window->Flags & ImGuiWindowFlags_NoMove); // Inherit the NoMove flag\n    if (child_flags & (ImGuiChildFlags_AutoResizeX | ImGuiChildFlags_AutoResizeY | ImGuiChildFlags_AlwaysAutoResize))\n        window_flags |= ImGuiWindowFlags_AlwaysAutoResize;\n    if ((child_flags & (ImGuiChildFlags_ResizeX | ImGuiChildFlags_ResizeY)) == 0)\n        window_flags |= ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoSavedSettings;\n\n    // Special framed style\n    if (child_flags & ImGuiChildFlags_FrameStyle)\n    {\n        PushStyleColor(ImGuiCol_ChildBg, g.Style.Colors[ImGuiCol_FrameBg]);\n        PushStyleVar(ImGuiStyleVar_ChildRounding, g.Style.FrameRounding);\n        PushStyleVar(ImGuiStyleVar_ChildBorderSize, g.Style.FrameBorderSize);\n        PushStyleVar(ImGuiStyleVar_WindowPadding, g.Style.FramePadding);\n        child_flags |= ImGuiChildFlags_Borders | ImGuiChildFlags_AlwaysUseWindowPadding;\n        window_flags |= ImGuiWindowFlags_NoMove;\n    }\n\n    // Forward size\n    // Important: Begin() has special processing to switch condition to ImGuiCond_FirstUseEver for a given axis when ImGuiChildFlags_ResizeXXX is set.\n    // (the alternative would to store conditional flags per axis, which is possible but more code)\n    const ImVec2 size_avail = GetContentRegionAvail();\n    const ImVec2 size_default((child_flags & ImGuiChildFlags_AutoResizeX) ? 0.0f : size_avail.x, (child_flags & ImGuiChildFlags_AutoResizeY) ? 0.0f : size_avail.y);\n    ImVec2 size = CalcItemSize(size_arg, size_default.x, size_default.y);\n\n    // A SetNextWindowSize() call always has priority (#8020)\n    // (since the code in Begin() never supported SizeVal==0.0f aka auto-resize via SetNextWindowSize() call, we don't support it here for now)\n    // FIXME: We only support ImGuiCond_Always in this path. Supporting other paths would requires to obtain window pointer.\n    if ((g.NextWindowData.HasFlags & ImGuiNextWindowDataFlags_HasSize) != 0 && (g.NextWindowData.SizeCond & ImGuiCond_Always) != 0)\n    {\n        if (g.NextWindowData.SizeVal.x > 0.0f)\n        {\n            size.x = g.NextWindowData.SizeVal.x;\n            child_flags &= ~ImGuiChildFlags_ResizeX;\n        }\n        if (g.NextWindowData.SizeVal.y > 0.0f)\n        {\n            size.y = g.NextWindowData.SizeVal.y;\n            child_flags &= ~ImGuiChildFlags_ResizeY;\n        }\n    }\n    SetNextWindowSize(size);\n\n    // Forward child flags (we allow prior settings to merge but it'll only work for adding flags)\n    if (g.NextWindowData.HasFlags & ImGuiNextWindowDataFlags_HasChildFlags)\n        g.NextWindowData.ChildFlags |= child_flags;\n    else\n        g.NextWindowData.ChildFlags = child_flags;\n    g.NextWindowData.HasFlags |= ImGuiNextWindowDataFlags_HasChildFlags;\n\n    // Build up name. If you need to append to a same child from multiple location in the ID stack, use BeginChild(ImGuiID id) with a stable value.\n    // FIXME: 2023/11/14: commented out shorted version. We had an issue with multiple ### in child window path names, which the trailing hash helped workaround.\n    // e.g. \"ParentName###ParentIdentifier/ChildName###ChildIdentifier\" would get hashed incorrectly by ImHashStr(), trailing _%08X somehow fixes it.\n    const char* temp_window_name;\n    /*if (name && parent_window->IDStack.back() == parent_window->ID)\n        ImFormatStringToTempBuffer(&temp_window_name, NULL, \"%s/%s\", parent_window->Name, name); // May omit ID if in root of ID stack\n    else*/\n    if (name)\n        ImFormatStringToTempBuffer(&temp_window_name, NULL, \"%s/%s_%08X\", parent_window->Name, name, id);\n    else\n        ImFormatStringToTempBuffer(&temp_window_name, NULL, \"%s/%08X\", parent_window->Name, id);\n\n    // Set style\n    const float backup_border_size = g.Style.ChildBorderSize;\n    if ((child_flags & ImGuiChildFlags_Borders) == 0)\n        g.Style.ChildBorderSize = 0.0f;\n\n    // Begin into window\n    const bool ret = Begin(temp_window_name, NULL, window_flags);\n\n    // Restore style\n    g.Style.ChildBorderSize = backup_border_size;\n    if (child_flags & ImGuiChildFlags_FrameStyle)\n    {\n        PopStyleVar(3);\n        PopStyleColor();\n    }\n\n    ImGuiWindow* child_window = g.CurrentWindow;\n    child_window->ChildId = id;\n\n    // Set the cursor to handle case where the user called SetNextWindowPos()+BeginChild() manually.\n    // While this is not really documented/defined, it seems that the expected thing to do.\n    if (child_window->BeginCount == 1)\n        parent_window->DC.CursorPos = child_window->Pos;\n\n    // Process navigation-in immediately so NavInit can run on first frame\n    // Can enter a child if (A) it has navigable items or (B) it can be scrolled.\n    const ImGuiID temp_id_for_activation = ImHashStr(\"##Child\", 0, id);\n    if (g.ActiveId == temp_id_for_activation)\n        ClearActiveID();\n    if (g.NavActivateId == id && !(child_flags & ImGuiChildFlags_NavFlattened) && (child_window->DC.NavLayersActiveMask != 0 || child_window->DC.NavWindowHasScrollY))\n    {\n        FocusWindow(child_window);\n        NavInitWindow(child_window, false);\n        SetActiveID(temp_id_for_activation, child_window); // Steal ActiveId with another arbitrary id so that key-press won't activate child item\n        g.ActiveIdSource = g.NavInputSource;\n    }\n    return ret;\n}\n\nvoid ImGui::EndChild()\n{\n    ImGuiContext& g = *GImGui;\n    ImGuiWindow* child_window = g.CurrentWindow;\n\n    const ImGuiID backup_within_end_child_id = g.WithinEndChildID;\n    IM_ASSERT(child_window->Flags & ImGuiWindowFlags_ChildWindow);   // Mismatched BeginChild()/EndChild() calls\n\n    g.WithinEndChildID = child_window->ID;\n    ImVec2 child_size = child_window->Size;\n    End();\n    if (child_window->BeginCount == 1)\n    {\n        ImGuiWindow* parent_window = g.CurrentWindow;\n        ImRect bb(parent_window->DC.CursorPos, parent_window->DC.CursorPos + child_size);\n        ItemSize(child_size);\n        const bool nav_flattened = (child_window->ChildFlags & ImGuiChildFlags_NavFlattened) != 0;\n        if ((child_window->DC.NavLayersActiveMask != 0 || child_window->DC.NavWindowHasScrollY) && !nav_flattened)\n        {\n            ItemAdd(bb, child_window->ChildId);\n            RenderNavCursor(bb, child_window->ChildId);\n\n            // When browsing a window that has no activable items (scroll only) we keep a highlight on the child (pass g.NavId to trick into always displaying)\n            if (child_window->DC.NavLayersActiveMask == 0 && child_window == g.NavWindow)\n                RenderNavCursor(ImRect(bb.Min - ImVec2(2, 2), bb.Max + ImVec2(2, 2)), g.NavId, ImGuiNavRenderCursorFlags_Compact);\n        }\n        else\n        {\n            // Not navigable into\n            // - This is a bit of a fringe use case, mostly useful for undecorated, non-scrolling contents childs, or empty childs.\n            // - We could later decide to not apply this path if ImGuiChildFlags_FrameStyle or ImGuiChildFlags_Borders is set.\n            ItemAdd(bb, child_window->ChildId, NULL, ImGuiItemFlags_NoNav);\n\n            // But when flattened we directly reach items, adjust active layer mask accordingly\n            if (nav_flattened)\n                parent_window->DC.NavLayersActiveMaskNext |= child_window->DC.NavLayersActiveMaskNext;\n        }\n        if (g.HoveredWindow == child_window)\n            g.LastItemData.StatusFlags |= ImGuiItemStatusFlags_HoveredWindow;\n        child_window->DC.ChildItemStatusFlags = g.LastItemData.StatusFlags;\n        //SetLastItemDataForChildWindowItem(child_window, child_window->Rect()); // Not needed, effectively done by ItemAdd()\n    }\n    else\n    {\n        SetLastItemDataForChildWindowItem(child_window, child_window->Rect());\n    }\n\n    g.WithinEndChildID = backup_within_end_child_id;\n    g.LogLinePosY = -FLT_MAX; // To enforce a carriage return\n}\n\nstatic void SetWindowConditionAllowFlags(ImGuiWindow* window, ImGuiCond flags, bool enabled)\n{\n    window->SetWindowPosAllowFlags       = enabled ? (window->SetWindowPosAllowFlags       | flags) : (window->SetWindowPosAllowFlags       & ~flags);\n    window->SetWindowSizeAllowFlags      = enabled ? (window->SetWindowSizeAllowFlags      | flags) : (window->SetWindowSizeAllowFlags      & ~flags);\n    window->SetWindowCollapsedAllowFlags = enabled ? (window->SetWindowCollapsedAllowFlags | flags) : (window->SetWindowCollapsedAllowFlags & ~flags);\n}\n\nImGuiWindow* ImGui::FindWindowByID(ImGuiID id)\n{\n    ImGuiContext& g = *GImGui;\n    return (ImGuiWindow*)g.WindowsById.GetVoidPtr(id);\n}\n\nImGuiWindow* ImGui::FindWindowByName(const char* name)\n{\n    ImGuiID id = ImHashStr(name);\n    return FindWindowByID(id);\n}\n\nstatic void ApplyWindowSettings(ImGuiWindow* window, ImGuiWindowSettings* settings)\n{\n    window->Pos = ImTrunc(ImVec2(settings->Pos.x, settings->Pos.y));\n    if (settings->Size.x > 0 && settings->Size.y > 0)\n        window->Size = window->SizeFull = ImTrunc(ImVec2(settings->Size.x, settings->Size.y));\n    window->Collapsed = settings->Collapsed;\n}\n\nstatic void InitOrLoadWindowSettings(ImGuiWindow* window, ImGuiWindowSettings* settings)\n{\n    // Initial window state with e.g. default/arbitrary window position\n    // Use SetNextWindowPos() with the appropriate condition flag to change the initial position of a window.\n    const ImGuiViewport* main_viewport = ImGui::GetMainViewport();\n    window->Pos = main_viewport->Pos + ImVec2(60, 60);\n    window->Size = window->SizeFull = ImVec2(0, 0);\n    window->SetWindowPosAllowFlags = window->SetWindowSizeAllowFlags = window->SetWindowCollapsedAllowFlags = ImGuiCond_Always | ImGuiCond_Once | ImGuiCond_FirstUseEver | ImGuiCond_Appearing;\n\n    if (settings != NULL)\n    {\n        SetWindowConditionAllowFlags(window, ImGuiCond_FirstUseEver, false);\n        ApplyWindowSettings(window, settings);\n    }\n    window->DC.CursorStartPos = window->DC.CursorMaxPos = window->DC.IdealMaxPos = window->Pos; // So first call to CalcWindowContentSizes() doesn't return crazy values\n\n    if ((window->Flags & ImGuiWindowFlags_AlwaysAutoResize) != 0)\n    {\n        window->AutoFitFramesX = window->AutoFitFramesY = 2;\n        window->AutoFitOnlyGrows = false;\n    }\n    else\n    {\n        if (window->Size.x <= 0.0f)\n            window->AutoFitFramesX = 2;\n        if (window->Size.y <= 0.0f)\n            window->AutoFitFramesY = 2;\n        window->AutoFitOnlyGrows = (window->AutoFitFramesX > 0) || (window->AutoFitFramesY > 0);\n    }\n}\n\nstatic ImGuiWindow* CreateNewWindow(const char* name, ImGuiWindowFlags flags)\n{\n    // Create window the first time\n    //IMGUI_DEBUG_LOG(\"CreateNewWindow '%s', flags = 0x%08X\\n\", name, flags);\n    ImGuiContext& g = *GImGui;\n    ImGuiWindow* window = IM_NEW(ImGuiWindow)(&g, name);\n    window->Flags = flags;\n    g.WindowsById.SetVoidPtr(window->ID, window);\n\n    ImGuiWindowSettings* settings = NULL;\n    if (!(flags & ImGuiWindowFlags_NoSavedSettings))\n        if ((settings = ImGui::FindWindowSettingsByWindow(window)) != 0)\n            window->SettingsOffset = g.SettingsWindows.offset_from_ptr(settings);\n\n    InitOrLoadWindowSettings(window, settings);\n\n    if (flags & ImGuiWindowFlags_NoBringToFrontOnFocus)\n        g.Windows.push_front(window); // Quite slow but rare and only once\n    else\n        g.Windows.push_back(window);\n\n    return window;\n}\n\nstatic inline ImVec2 CalcWindowMinSize(ImGuiWindow* window)\n{\n    // We give windows non-zero minimum size to facilitate understanding problematic cases (e.g. empty popups)\n    // FIXME: Essentially we want to restrict manual resizing to WindowMinSize+Decoration, and allow api resizing to be smaller.\n    // Perhaps should tend further a neater test for this.\n    ImGuiContext& g = *GImGui;\n    ImVec2 size_min;\n    if ((window->Flags & ImGuiWindowFlags_ChildWindow) && !(window->Flags & ImGuiWindowFlags_Popup))\n    {\n        size_min.x = (window->ChildFlags & ImGuiChildFlags_ResizeX) ? g.Style.WindowMinSize.x : 4.0f;\n        size_min.y = (window->ChildFlags & ImGuiChildFlags_ResizeY) ? g.Style.WindowMinSize.y : 4.0f;\n    }\n    else\n    {\n        size_min.x = ((window->Flags & ImGuiWindowFlags_AlwaysAutoResize) == 0) ? g.Style.WindowMinSize.x : 4.0f;\n        size_min.y = ((window->Flags & ImGuiWindowFlags_AlwaysAutoResize) == 0) ? g.Style.WindowMinSize.y : 4.0f;\n    }\n\n    // Reduce artifacts with very small windows\n    ImGuiWindow* window_for_height = window;\n    size_min.y = ImMax(size_min.y, window_for_height->TitleBarHeight + window_for_height->MenuBarHeight + ImMax(0.0f, g.Style.WindowRounding - 1.0f));\n    return size_min;\n}\n\nstatic ImVec2 CalcWindowSizeAfterConstraint(ImGuiWindow* window, const ImVec2& size_desired)\n{\n    ImGuiContext& g = *GImGui;\n    ImVec2 new_size = size_desired;\n    if (g.NextWindowData.HasFlags & ImGuiNextWindowDataFlags_HasSizeConstraint)\n    {\n        // See comments in SetNextWindowSizeConstraints() for details about setting size_min an size_max.\n        ImRect cr = g.NextWindowData.SizeConstraintRect;\n        new_size.x = (cr.Min.x >= 0 && cr.Max.x >= 0) ? ImClamp(new_size.x, cr.Min.x, cr.Max.x) : window->SizeFull.x;\n        new_size.y = (cr.Min.y >= 0 && cr.Max.y >= 0) ? ImClamp(new_size.y, cr.Min.y, cr.Max.y) : window->SizeFull.y;\n        if (g.NextWindowData.SizeCallback)\n        {\n            ImGuiSizeCallbackData data;\n            data.UserData = g.NextWindowData.SizeCallbackUserData;\n            data.Pos = window->Pos;\n            data.CurrentSize = window->SizeFull;\n            data.DesiredSize = new_size;\n            g.NextWindowData.SizeCallback(&data);\n            new_size = data.DesiredSize;\n        }\n        new_size.x = IM_TRUNC(new_size.x);\n        new_size.y = IM_TRUNC(new_size.y);\n    }\n\n    // Minimum size\n    ImVec2 size_min = CalcWindowMinSize(window);\n    return ImMax(new_size, size_min);\n}\n\nstatic void CalcWindowContentSizes(ImGuiWindow* window, ImVec2* content_size_current, ImVec2* content_size_ideal)\n{\n    bool preserve_old_content_sizes = false;\n    if (window->Collapsed && window->AutoFitFramesX <= 0 && window->AutoFitFramesY <= 0)\n        preserve_old_content_sizes = true;\n    else if (window->Hidden && window->HiddenFramesCannotSkipItems == 0 && window->HiddenFramesCanSkipItems > 0)\n        preserve_old_content_sizes = true;\n    if (preserve_old_content_sizes)\n    {\n        *content_size_current = window->ContentSize;\n        *content_size_ideal = window->ContentSizeIdeal;\n        return;\n    }\n\n    content_size_current->x = (window->ContentSizeExplicit.x != 0.0f) ? window->ContentSizeExplicit.x : IM_TRUNC(window->DC.CursorMaxPos.x - window->DC.CursorStartPos.x);\n    content_size_current->y = (window->ContentSizeExplicit.y != 0.0f) ? window->ContentSizeExplicit.y : IM_TRUNC(window->DC.CursorMaxPos.y - window->DC.CursorStartPos.y);\n    content_size_ideal->x = (window->ContentSizeExplicit.x != 0.0f) ? window->ContentSizeExplicit.x : IM_TRUNC(ImMax(window->DC.CursorMaxPos.x, window->DC.IdealMaxPos.x) - window->DC.CursorStartPos.x);\n    content_size_ideal->y = (window->ContentSizeExplicit.y != 0.0f) ? window->ContentSizeExplicit.y : IM_TRUNC(ImMax(window->DC.CursorMaxPos.y, window->DC.IdealMaxPos.y) - window->DC.CursorStartPos.y);\n}\n\nstatic ImVec2 CalcWindowAutoFitSize(ImGuiWindow* window, const ImVec2& size_contents)\n{\n    ImGuiContext& g = *GImGui;\n    ImGuiStyle& style = g.Style;\n    const float decoration_w_without_scrollbars = window->DecoOuterSizeX1 + window->DecoOuterSizeX2 - window->ScrollbarSizes.x;\n    const float decoration_h_without_scrollbars = window->DecoOuterSizeY1 + window->DecoOuterSizeY2 - window->ScrollbarSizes.y;\n    ImVec2 size_pad = window->WindowPadding * 2.0f;\n    ImVec2 size_desired = size_contents + size_pad + ImVec2(decoration_w_without_scrollbars, decoration_h_without_scrollbars);\n    if (window->Flags & ImGuiWindowFlags_Tooltip)\n    {\n        // Tooltip always resize\n        return size_desired;\n    }\n    else\n    {\n        // Maximum window size is determined by the viewport size or monitor size\n        ImVec2 size_min = CalcWindowMinSize(window);\n        ImVec2 size_max = ((window->Flags & ImGuiWindowFlags_ChildWindow) && !(window->Flags & ImGuiWindowFlags_Popup)) ? ImVec2(FLT_MAX, FLT_MAX) : ImGui::GetMainViewport()->WorkSize - style.DisplaySafeAreaPadding * 2.0f;\n        ImVec2 size_auto_fit = ImClamp(size_desired, size_min, size_max);\n\n        // FIXME: CalcWindowAutoFitSize() doesn't take into account that only one axis may be auto-fit when calculating scrollbars,\n        // we may need to compute/store three variants of size_auto_fit, for x/y/xy.\n        // Here we implement a workaround for child windows only, but a full solution would apply to normal windows as well:\n        if ((window->ChildFlags & ImGuiChildFlags_ResizeX) && !(window->ChildFlags & ImGuiChildFlags_ResizeY))\n            size_auto_fit.y = window->SizeFull.y;\n        else if (!(window->ChildFlags & ImGuiChildFlags_ResizeX) && (window->ChildFlags & ImGuiChildFlags_ResizeY))\n            size_auto_fit.x = window->SizeFull.x;\n\n        // When the window cannot fit all contents (either because of constraints, either because screen is too small),\n        // we are growing the size on the other axis to compensate for expected scrollbar. FIXME: Might turn bigger than ViewportSize-WindowPadding.\n        ImVec2 size_auto_fit_after_constraint = CalcWindowSizeAfterConstraint(window, size_auto_fit);\n        bool will_have_scrollbar_x = (size_auto_fit_after_constraint.x - size_pad.x - decoration_w_without_scrollbars < size_contents.x && !(window->Flags & ImGuiWindowFlags_NoScrollbar) && (window->Flags & ImGuiWindowFlags_HorizontalScrollbar)) || (window->Flags & ImGuiWindowFlags_AlwaysHorizontalScrollbar);\n        bool will_have_scrollbar_y = (size_auto_fit_after_constraint.y - size_pad.y - decoration_h_without_scrollbars < size_contents.y && !(window->Flags & ImGuiWindowFlags_NoScrollbar)) || (window->Flags & ImGuiWindowFlags_AlwaysVerticalScrollbar);\n        if (will_have_scrollbar_x)\n            size_auto_fit.y += style.ScrollbarSize;\n        if (will_have_scrollbar_y)\n            size_auto_fit.x += style.ScrollbarSize;\n        return size_auto_fit;\n    }\n}\n\nImVec2 ImGui::CalcWindowNextAutoFitSize(ImGuiWindow* window)\n{\n    ImVec2 size_contents_current;\n    ImVec2 size_contents_ideal;\n    CalcWindowContentSizes(window, &size_contents_current, &size_contents_ideal);\n    ImVec2 size_auto_fit = CalcWindowAutoFitSize(window, size_contents_ideal);\n    ImVec2 size_final = CalcWindowSizeAfterConstraint(window, size_auto_fit);\n    return size_final;\n}\n\nstatic ImGuiCol GetWindowBgColorIdx(ImGuiWindow* window)\n{\n    if (window->Flags & (ImGuiWindowFlags_Tooltip | ImGuiWindowFlags_Popup))\n        return ImGuiCol_PopupBg;\n    if (window->Flags & ImGuiWindowFlags_ChildWindow)\n        return ImGuiCol_ChildBg;\n    return ImGuiCol_WindowBg;\n}\n\nstatic void CalcResizePosSizeFromAnyCorner(ImGuiWindow* window, const ImVec2& corner_target, const ImVec2& corner_norm, ImVec2* out_pos, ImVec2* out_size)\n{\n    ImVec2 pos_min = ImLerp(corner_target, window->Pos, corner_norm);                // Expected window upper-left\n    ImVec2 pos_max = ImLerp(window->Pos + window->Size, corner_target, corner_norm); // Expected window lower-right\n    ImVec2 size_expected = pos_max - pos_min;\n    ImVec2 size_constrained = CalcWindowSizeAfterConstraint(window, size_expected);\n    *out_pos = pos_min;\n    if (corner_norm.x == 0.0f)\n        out_pos->x -= (size_constrained.x - size_expected.x);\n    if (corner_norm.y == 0.0f)\n        out_pos->y -= (size_constrained.y - size_expected.y);\n    *out_size = size_constrained;\n}\n\n// Data for resizing from resize grip / corner\nstruct ImGuiResizeGripDef\n{\n    ImVec2  CornerPosN;\n    ImVec2  InnerDir;\n    int     AngleMin12, AngleMax12;\n};\nstatic const ImGuiResizeGripDef resize_grip_def[4] =\n{\n    { ImVec2(1, 1), ImVec2(-1, -1), 0, 3 },  // Lower-right\n    { ImVec2(0, 1), ImVec2(+1, -1), 3, 6 },  // Lower-left\n    { ImVec2(0, 0), ImVec2(+1, +1), 6, 9 },  // Upper-left (Unused)\n    { ImVec2(1, 0), ImVec2(-1, +1), 9, 12 }  // Upper-right (Unused)\n};\n\n// Data for resizing from borders\nstruct ImGuiResizeBorderDef\n{\n    ImVec2  InnerDir;               // Normal toward inside\n    ImVec2  SegmentN1, SegmentN2;   // End positions, normalized (0,0: upper left)\n    float   OuterAngle;             // Angle toward outside\n};\nstatic const ImGuiResizeBorderDef resize_border_def[4] =\n{\n    { ImVec2(+1, 0), ImVec2(0, 1), ImVec2(0, 0), IM_PI * 1.00f }, // Left\n    { ImVec2(-1, 0), ImVec2(1, 0), ImVec2(1, 1), IM_PI * 0.00f }, // Right\n    { ImVec2(0, +1), ImVec2(0, 0), ImVec2(1, 0), IM_PI * 1.50f }, // Up\n    { ImVec2(0, -1), ImVec2(1, 1), ImVec2(0, 1), IM_PI * 0.50f }  // Down\n};\n\nstatic ImRect GetResizeBorderRect(ImGuiWindow* window, int border_n, float perp_padding, float thickness)\n{\n    ImRect rect = window->Rect();\n    if (thickness == 0.0f)\n        rect.Max -= ImVec2(1, 1);\n    if (border_n == ImGuiDir_Left)  { return ImRect(rect.Min.x - thickness,    rect.Min.y + perp_padding, rect.Min.x + thickness,    rect.Max.y - perp_padding); }\n    if (border_n == ImGuiDir_Right) { return ImRect(rect.Max.x - thickness,    rect.Min.y + perp_padding, rect.Max.x + thickness,    rect.Max.y - perp_padding); }\n    if (border_n == ImGuiDir_Up)    { return ImRect(rect.Min.x + perp_padding, rect.Min.y - thickness,    rect.Max.x - perp_padding, rect.Min.y + thickness);    }\n    if (border_n == ImGuiDir_Down)  { return ImRect(rect.Min.x + perp_padding, rect.Max.y - thickness,    rect.Max.x - perp_padding, rect.Max.y + thickness);    }\n    IM_ASSERT(0);\n    return ImRect();\n}\n\n// 0..3: corners (Lower-right, Lower-left, Unused, Unused)\nImGuiID ImGui::GetWindowResizeCornerID(ImGuiWindow* window, int n)\n{\n    IM_ASSERT(n >= 0 && n < 4);\n    ImGuiID id = window->ID;\n    id = ImHashStr(\"#RESIZE\", 0, id);\n    id = ImHashData(&n, sizeof(int), id);\n    return id;\n}\n\n// Borders (Left, Right, Up, Down)\nImGuiID ImGui::GetWindowResizeBorderID(ImGuiWindow* window, ImGuiDir dir)\n{\n    IM_ASSERT(dir >= 0 && dir < 4);\n    int n = (int)dir + 4;\n    ImGuiID id = window->ID;\n    id = ImHashStr(\"#RESIZE\", 0, id);\n    id = ImHashData(&n, sizeof(int), id);\n    return id;\n}\n\n// Handle resize for: Resize Grips, Borders, Gamepad\n// Return true when using auto-fit (double-click on resize grip)\nstatic int ImGui::UpdateWindowManualResize(ImGuiWindow* window, const ImVec2& size_auto_fit, int* border_hovered, int* border_held, int resize_grip_count, ImU32 resize_grip_col[4], const ImRect& visibility_rect)\n{\n    ImGuiContext& g = *GImGui;\n    ImGuiWindowFlags flags = window->Flags;\n\n    if ((flags & ImGuiWindowFlags_NoResize) || (flags & ImGuiWindowFlags_AlwaysAutoResize) || window->AutoFitFramesX > 0 || window->AutoFitFramesY > 0)\n        return false;\n    if (window->WasActive == false) // Early out to avoid running this code for e.g. a hidden implicit/fallback Debug window.\n        return false;\n\n    int ret_auto_fit_mask = 0x00;\n    const float grip_draw_size = IM_TRUNC(ImMax(g.FontSize * 1.35f, window->WindowRounding + 1.0f + g.FontSize * 0.2f));\n    const float grip_hover_inner_size = (resize_grip_count > 0) ? IM_TRUNC(grip_draw_size * 0.75f) : 0.0f;\n    const float grip_hover_outer_size = g.WindowsBorderHoverPadding;\n\n    ImRect clamp_rect = visibility_rect;\n    const bool window_move_from_title_bar = g.IO.ConfigWindowsMoveFromTitleBarOnly && !(window->Flags & ImGuiWindowFlags_NoTitleBar);\n    if (window_move_from_title_bar)\n        clamp_rect.Min.y -= window->TitleBarHeight;\n\n    ImVec2 pos_target(FLT_MAX, FLT_MAX);\n    ImVec2 size_target(FLT_MAX, FLT_MAX);\n\n    // Resize grips and borders are on layer 1\n    window->DC.NavLayerCurrent = ImGuiNavLayer_Menu;\n\n    // Manual resize grips\n    PushID(\"#RESIZE\");\n    for (int resize_grip_n = 0; resize_grip_n < resize_grip_count; resize_grip_n++)\n    {\n        const ImGuiResizeGripDef& def = resize_grip_def[resize_grip_n];\n        const ImVec2 corner = ImLerp(window->Pos, window->Pos + window->Size, def.CornerPosN);\n\n        // Using the FlattenChilds button flag we make the resize button accessible even if we are hovering over a child window\n        bool hovered, held;\n        ImRect resize_rect(corner - def.InnerDir * grip_hover_outer_size, corner + def.InnerDir * grip_hover_inner_size);\n        if (resize_rect.Min.x > resize_rect.Max.x) ImSwap(resize_rect.Min.x, resize_rect.Max.x);\n        if (resize_rect.Min.y > resize_rect.Max.y) ImSwap(resize_rect.Min.y, resize_rect.Max.y);\n        ImGuiID resize_grip_id = window->GetID(resize_grip_n); // == GetWindowResizeCornerID()\n        ItemAdd(resize_rect, resize_grip_id, NULL, ImGuiItemFlags_NoNav);\n        ButtonBehavior(resize_rect, resize_grip_id, &hovered, &held, ImGuiButtonFlags_FlattenChildren | ImGuiButtonFlags_NoNavFocus);\n        //GetForegroundDrawList(window)->AddRect(resize_rect.Min, resize_rect.Max, IM_COL32(255, 255, 0, 255));\n        if (hovered || held)\n            SetMouseCursor((resize_grip_n & 1) ? ImGuiMouseCursor_ResizeNESW : ImGuiMouseCursor_ResizeNWSE);\n\n        if (held && g.IO.MouseDoubleClicked[0])\n        {\n            // Auto-fit when double-clicking\n            size_target = CalcWindowSizeAfterConstraint(window, size_auto_fit);\n            ret_auto_fit_mask = 0x03; // Both axes\n            ClearActiveID();\n        }\n        else if (held)\n        {\n            // Resize from any of the four corners\n            // We don't use an incremental MouseDelta but rather compute an absolute target size based on mouse position\n            ImVec2 clamp_min = ImVec2(def.CornerPosN.x == 1.0f ? clamp_rect.Min.x : -FLT_MAX, (def.CornerPosN.y == 1.0f || (def.CornerPosN.y == 0.0f && window_move_from_title_bar)) ? clamp_rect.Min.y : -FLT_MAX);\n            ImVec2 clamp_max = ImVec2(def.CornerPosN.x == 0.0f ? clamp_rect.Max.x : +FLT_MAX, def.CornerPosN.y == 0.0f ? clamp_rect.Max.y : +FLT_MAX);\n            ImVec2 corner_target = g.IO.MousePos - g.ActiveIdClickOffset + ImLerp(def.InnerDir * grip_hover_outer_size, def.InnerDir * -grip_hover_inner_size, def.CornerPosN); // Corner of the window corresponding to our corner grip\n            corner_target = ImClamp(corner_target, clamp_min, clamp_max);\n            CalcResizePosSizeFromAnyCorner(window, corner_target, def.CornerPosN, &pos_target, &size_target);\n        }\n\n        // Only lower-left grip is visible before hovering/activating\n        if (resize_grip_n == 0 || held || hovered)\n            resize_grip_col[resize_grip_n] = GetColorU32(held ? ImGuiCol_ResizeGripActive : hovered ? ImGuiCol_ResizeGripHovered : ImGuiCol_ResizeGrip);\n    }\n\n    int resize_border_mask = 0x00;\n    if (window->Flags & ImGuiWindowFlags_ChildWindow)\n        resize_border_mask |= ((window->ChildFlags & ImGuiChildFlags_ResizeX) ? 0x02 : 0) | ((window->ChildFlags & ImGuiChildFlags_ResizeY) ? 0x08 : 0);\n    else\n        resize_border_mask = g.IO.ConfigWindowsResizeFromEdges ? 0x0F : 0x00;\n    for (int border_n = 0; border_n < 4; border_n++)\n    {\n        if ((resize_border_mask & (1 << border_n)) == 0)\n            continue;\n        const ImGuiResizeBorderDef& def = resize_border_def[border_n];\n        const ImGuiAxis axis = (border_n == ImGuiDir_Left || border_n == ImGuiDir_Right) ? ImGuiAxis_X : ImGuiAxis_Y;\n\n        bool hovered, held;\n        ImRect border_rect = GetResizeBorderRect(window, border_n, grip_hover_inner_size, g.WindowsBorderHoverPadding);\n        ImGuiID border_id = window->GetID(border_n + 4); // == GetWindowResizeBorderID()\n        ItemAdd(border_rect, border_id, NULL, ImGuiItemFlags_NoNav);\n        ButtonBehavior(border_rect, border_id, &hovered, &held, ImGuiButtonFlags_FlattenChildren | ImGuiButtonFlags_NoNavFocus);\n        //GetForegroundDrawList(window)->AddRect(border_rect.Min, border_rect.Max, IM_COL32(255, 255, 0, 255));\n        if (hovered && g.HoveredIdTimer <= WINDOWS_RESIZE_FROM_EDGES_FEEDBACK_TIMER)\n            hovered = false;\n        if (hovered || held)\n            SetMouseCursor((axis == ImGuiAxis_X) ? ImGuiMouseCursor_ResizeEW : ImGuiMouseCursor_ResizeNS);\n        if (held && g.IO.MouseDoubleClicked[0])\n        {\n            // Double-clicking bottom or right border auto-fit on this axis\n            // FIXME: CalcWindowAutoFitSize() doesn't take into account that only one side may be auto-fit when calculating scrollbars.\n            // FIXME: Support top and right borders: rework CalcResizePosSizeFromAnyCorner() to be reusable in both cases.\n            if (border_n == 1 || border_n == 3) // Right and bottom border\n            {\n                size_target[axis] = CalcWindowSizeAfterConstraint(window, size_auto_fit)[axis];\n                ret_auto_fit_mask |= (1 << axis);\n                hovered = held = false; // So border doesn't show highlighted at new position\n            }\n            ClearActiveID();\n        }\n        else if (held)\n        {\n            // Switch to relative resizing mode when border geometry moved (e.g. resizing a child altering parent scroll), in order to avoid resizing feedback loop.\n            // Currently only using relative mode on resizable child windows, as the problem to solve is more likely noticeable for them, but could apply for all windows eventually.\n            // FIXME: May want to generalize this idiom at lower-level, so more widgets can use it!\n            const bool just_scrolled_manually_while_resizing = (g.WheelingWindow != NULL && g.WheelingWindowScrolledFrame == g.FrameCount && IsWindowChildOf(window, g.WheelingWindow, false));\n            if (g.ActiveIdIsJustActivated || just_scrolled_manually_while_resizing)\n            {\n                g.WindowResizeBorderExpectedRect = border_rect;\n                g.WindowResizeRelativeMode = false;\n            }\n            if ((window->Flags & ImGuiWindowFlags_ChildWindow) && memcmp(&g.WindowResizeBorderExpectedRect, &border_rect, sizeof(ImRect)) != 0)\n                g.WindowResizeRelativeMode = true;\n\n            const ImVec2 border_curr = (window->Pos + ImMin(def.SegmentN1, def.SegmentN2) * window->Size);\n            const float border_target_rel_mode_for_axis = border_curr[axis] + g.IO.MouseDelta[axis];\n            const float border_target_abs_mode_for_axis = g.IO.MousePos[axis] - g.ActiveIdClickOffset[axis] + g.WindowsBorderHoverPadding; // Match ButtonBehavior() padding above.\n\n            // Use absolute mode position\n            ImVec2 border_target = window->Pos;\n            border_target[axis] = border_target_abs_mode_for_axis;\n\n            // Use relative mode target for child window, ignore resize when moving back toward the ideal absolute position.\n            bool ignore_resize = false;\n            if (g.WindowResizeRelativeMode)\n            {\n                //GetForegroundDrawList()->AddText(GetMainViewport()->WorkPos, IM_COL32_WHITE, \"Relative Mode\");\n                border_target[axis] = border_target_rel_mode_for_axis;\n                if (g.IO.MouseDelta[axis] == 0.0f || (g.IO.MouseDelta[axis] > 0.0f) == (border_target_rel_mode_for_axis > border_target_abs_mode_for_axis))\n                    ignore_resize = true;\n            }\n\n            // Clamp, apply\n            ImVec2 clamp_min(border_n == ImGuiDir_Right ? clamp_rect.Min.x : -FLT_MAX, border_n == ImGuiDir_Down || (border_n == ImGuiDir_Up && window_move_from_title_bar) ? clamp_rect.Min.y : -FLT_MAX);\n            ImVec2 clamp_max(border_n == ImGuiDir_Left ? clamp_rect.Max.x : +FLT_MAX, border_n == ImGuiDir_Up ? clamp_rect.Max.y : +FLT_MAX);\n            border_target = ImClamp(border_target, clamp_min, clamp_max);\n            if (flags & ImGuiWindowFlags_ChildWindow) // Clamp resizing of childs within parent\n            {\n                ImGuiWindow* parent_window = window->ParentWindow;\n                ImGuiWindowFlags parent_flags = parent_window->Flags;\n                ImRect border_limit_rect = parent_window->InnerRect;\n                border_limit_rect.Expand(ImVec2(-ImMax(parent_window->WindowPadding.x, parent_window->WindowBorderSize), -ImMax(parent_window->WindowPadding.y, parent_window->WindowBorderSize)));\n                if ((axis == ImGuiAxis_X) && ((parent_flags & (ImGuiWindowFlags_HorizontalScrollbar | ImGuiWindowFlags_AlwaysHorizontalScrollbar)) == 0 || (parent_flags & ImGuiWindowFlags_NoScrollbar)))\n                    border_target.x = ImClamp(border_target.x, border_limit_rect.Min.x, border_limit_rect.Max.x);\n                if ((axis == ImGuiAxis_Y) && (parent_flags & ImGuiWindowFlags_NoScrollbar))\n                    border_target.y = ImClamp(border_target.y, border_limit_rect.Min.y, border_limit_rect.Max.y);\n            }\n            if (!ignore_resize)\n                CalcResizePosSizeFromAnyCorner(window, border_target, ImMin(def.SegmentN1, def.SegmentN2), &pos_target, &size_target);\n        }\n        if (hovered)\n            *border_hovered = border_n;\n        if (held)\n            *border_held = border_n;\n    }\n    PopID();\n\n    // Restore nav layer\n    window->DC.NavLayerCurrent = ImGuiNavLayer_Main;\n\n    // Navigation resize (keyboard/gamepad)\n    // FIXME: This cannot be moved to NavUpdateWindowing() because CalcWindowSizeAfterConstraint() need to callback into user.\n    // Not even sure the callback works here.\n    if (g.NavWindowingTarget && g.NavWindowingTarget->RootWindow == window)\n    {\n        ImVec2 nav_resize_dir;\n        if (g.NavInputSource == ImGuiInputSource_Keyboard && g.IO.KeyShift)\n            nav_resize_dir = GetKeyMagnitude2d(ImGuiKey_LeftArrow, ImGuiKey_RightArrow, ImGuiKey_UpArrow, ImGuiKey_DownArrow);\n        if (g.NavInputSource == ImGuiInputSource_Gamepad)\n            nav_resize_dir = GetKeyMagnitude2d(ImGuiKey_GamepadDpadLeft, ImGuiKey_GamepadDpadRight, ImGuiKey_GamepadDpadUp, ImGuiKey_GamepadDpadDown);\n        if (nav_resize_dir.x != 0.0f || nav_resize_dir.y != 0.0f)\n        {\n            const float NAV_RESIZE_SPEED = 600.0f;\n            const float resize_step = NAV_RESIZE_SPEED * g.IO.DeltaTime * ImMin(g.IO.DisplayFramebufferScale.x, g.IO.DisplayFramebufferScale.y);\n            g.NavWindowingAccumDeltaSize += nav_resize_dir * resize_step;\n            g.NavWindowingAccumDeltaSize = ImMax(g.NavWindowingAccumDeltaSize, clamp_rect.Min - window->Pos - window->Size); // We need Pos+Size >= clmap_rect.Min, so Size >= clmap_rect.Min - Pos, so size_delta >= clmap_rect.Min - window->Pos - window->Size\n            g.NavWindowingToggleLayer = false;\n            g.NavHighlightItemUnderNav = true;\n            resize_grip_col[0] = GetColorU32(ImGuiCol_ResizeGripActive);\n            ImVec2 accum_floored = ImTrunc(g.NavWindowingAccumDeltaSize);\n            if (accum_floored.x != 0.0f || accum_floored.y != 0.0f)\n            {\n                // FIXME-NAV: Should store and accumulate into a separate size buffer to handle sizing constraints properly, right now a constraint will make us stuck.\n                size_target = CalcWindowSizeAfterConstraint(window, window->SizeFull + accum_floored);\n                g.NavWindowingAccumDeltaSize -= accum_floored;\n            }\n        }\n    }\n\n    // Apply back modified position/size to window\n    const ImVec2 curr_pos = window->Pos;\n    const ImVec2 curr_size = window->SizeFull;\n    if (size_target.x != FLT_MAX && (window->Size.x != size_target.x || window->SizeFull.x != size_target.x))\n        window->Size.x = window->SizeFull.x = size_target.x;\n    if (size_target.y != FLT_MAX && (window->Size.y != size_target.y || window->SizeFull.y != size_target.y))\n        window->Size.y = window->SizeFull.y = size_target.y;\n    if (pos_target.x != FLT_MAX && window->Pos.x != ImTrunc(pos_target.x))\n        window->Pos.x = ImTrunc(pos_target.x);\n    if (pos_target.y != FLT_MAX && window->Pos.y != ImTrunc(pos_target.y))\n        window->Pos.y = ImTrunc(pos_target.y);\n    if (curr_pos.x != window->Pos.x || curr_pos.y != window->Pos.y || curr_size.x != window->SizeFull.x || curr_size.y != window->SizeFull.y)\n        MarkIniSettingsDirty(window);\n\n    // Recalculate next expected border expected coordinates\n    if (*border_held != -1)\n        g.WindowResizeBorderExpectedRect = GetResizeBorderRect(window, *border_held, grip_hover_inner_size, g.WindowsBorderHoverPadding);\n\n    return ret_auto_fit_mask;\n}\n\nstatic inline void ClampWindowPos(ImGuiWindow* window, const ImRect& visibility_rect)\n{\n    ImGuiContext& g = *GImGui;\n    ImVec2 size_for_clamping = window->Size;\n    if (g.IO.ConfigWindowsMoveFromTitleBarOnly && !(window->Flags & ImGuiWindowFlags_NoTitleBar))\n        size_for_clamping.y = window->TitleBarHeight;\n    window->Pos = ImClamp(window->Pos, visibility_rect.Min - size_for_clamping, visibility_rect.Max);\n}\n\nstatic void RenderWindowOuterSingleBorder(ImGuiWindow* window, int border_n, ImU32 border_col, float border_size)\n{\n    const ImGuiResizeBorderDef& def = resize_border_def[border_n];\n    const float rounding = window->WindowRounding;\n    const ImRect border_r = GetResizeBorderRect(window, border_n, rounding, 0.0f);\n    window->DrawList->PathArcTo(ImLerp(border_r.Min, border_r.Max, def.SegmentN1) + ImVec2(0.5f, 0.5f) + def.InnerDir * rounding, rounding, def.OuterAngle - IM_PI * 0.25f, def.OuterAngle);\n    window->DrawList->PathArcTo(ImLerp(border_r.Min, border_r.Max, def.SegmentN2) + ImVec2(0.5f, 0.5f) + def.InnerDir * rounding, rounding, def.OuterAngle, def.OuterAngle + IM_PI * 0.25f);\n    window->DrawList->PathStroke(border_col, ImDrawFlags_None, border_size);\n}\n\nstatic void ImGui::RenderWindowOuterBorders(ImGuiWindow* window)\n{\n    ImGuiContext& g = *GImGui;\n    const float border_size = window->WindowBorderSize;\n    const ImU32 border_col = GetColorU32(ImGuiCol_Border);\n    if (border_size > 0.0f && (window->Flags & ImGuiWindowFlags_NoBackground) == 0)\n        window->DrawList->AddRect(window->Pos, window->Pos + window->Size, border_col, window->WindowRounding, 0, window->WindowBorderSize);\n    else if (border_size > 0.0f)\n    {\n        if (window->ChildFlags & ImGuiChildFlags_ResizeX) // Similar code as 'resize_border_mask' computation in UpdateWindowManualResize() but we specifically only always draw explicit child resize border.\n            RenderWindowOuterSingleBorder(window, 1, border_col, border_size);\n        if (window->ChildFlags & ImGuiChildFlags_ResizeY)\n            RenderWindowOuterSingleBorder(window, 3, border_col, border_size);\n    }\n    if (window->ResizeBorderHovered != -1 || window->ResizeBorderHeld != -1)\n    {\n        const int border_n = (window->ResizeBorderHeld != -1) ? window->ResizeBorderHeld : window->ResizeBorderHovered;\n        const ImU32 border_col_resizing = GetColorU32((window->ResizeBorderHeld != -1) ? ImGuiCol_SeparatorActive : ImGuiCol_SeparatorHovered);\n        RenderWindowOuterSingleBorder(window, border_n, border_col_resizing, ImMax(2.0f, window->WindowBorderSize)); // Thicker than usual\n    }\n    if (g.Style.FrameBorderSize > 0 && !(window->Flags & ImGuiWindowFlags_NoTitleBar))\n    {\n        float y = window->Pos.y + window->TitleBarHeight - 1;\n        window->DrawList->AddLine(ImVec2(window->Pos.x + border_size * 0.5f, y), ImVec2(window->Pos.x + window->Size.x - border_size * 0.5f, y), border_col, g.Style.FrameBorderSize);\n    }\n}\n\n// Draw background and borders\n// Draw and handle scrollbars\nvoid ImGui::RenderWindowDecorations(ImGuiWindow* window, const ImRect& title_bar_rect, bool title_bar_is_highlight, bool handle_borders_and_resize_grips, int resize_grip_count, const ImU32 resize_grip_col[4], float resize_grip_draw_size)\n{\n    ImGuiContext& g = *GImGui;\n    ImGuiStyle& style = g.Style;\n    ImGuiWindowFlags flags = window->Flags;\n\n    // Ensure that Scrollbar() doesn't read last frame's SkipItems\n    IM_ASSERT(window->BeginCount == 0);\n    window->SkipItems = false;\n    window->DC.NavLayerCurrent = ImGuiNavLayer_Menu;\n\n    // Draw window + handle manual resize\n    // As we highlight the title bar when want_focus is set, multiple reappearing windows will have their title bar highlighted on their reappearing frame.\n    const float window_rounding = window->WindowRounding;\n    const float window_border_size = window->WindowBorderSize;\n    if (window->Collapsed)\n    {\n        // Title bar only\n        const float backup_border_size = style.FrameBorderSize;\n        g.Style.FrameBorderSize = window->WindowBorderSize;\n        ImU32 title_bar_col = GetColorU32((title_bar_is_highlight && g.NavCursorVisible) ? ImGuiCol_TitleBgActive : ImGuiCol_TitleBgCollapsed);\n        RenderFrame(title_bar_rect.Min, title_bar_rect.Max, title_bar_col, true, window_rounding);\n        g.Style.FrameBorderSize = backup_border_size;\n    }\n    else\n    {\n        // Window background\n        if (!(flags & ImGuiWindowFlags_NoBackground))\n        {\n            ImU32 bg_col = GetColorU32(GetWindowBgColorIdx(window));\n            bool override_alpha = false;\n            float alpha = 1.0f;\n            if (g.NextWindowData.HasFlags & ImGuiNextWindowDataFlags_HasBgAlpha)\n            {\n                alpha = g.NextWindowData.BgAlphaVal;\n                override_alpha = true;\n            }\n            if (override_alpha)\n                bg_col = (bg_col & ~IM_COL32_A_MASK) | (IM_F32_TO_INT8_SAT(alpha) << IM_COL32_A_SHIFT);\n            window->DrawList->AddRectFilled(window->Pos + ImVec2(0, window->TitleBarHeight), window->Pos + window->Size, bg_col, window_rounding, (flags & ImGuiWindowFlags_NoTitleBar) ? 0 : ImDrawFlags_RoundCornersBottom);\n        }\n\n        // Title bar\n        if (!(flags & ImGuiWindowFlags_NoTitleBar))\n        {\n            ImU32 title_bar_col = GetColorU32(title_bar_is_highlight ? ImGuiCol_TitleBgActive : ImGuiCol_TitleBg);\n            window->DrawList->AddRectFilled(title_bar_rect.Min, title_bar_rect.Max, title_bar_col, window_rounding, ImDrawFlags_RoundCornersTop);\n        }\n\n        // Menu bar\n        if (flags & ImGuiWindowFlags_MenuBar)\n        {\n            ImRect menu_bar_rect = window->MenuBarRect();\n            menu_bar_rect.ClipWith(window->Rect());  // Soft clipping, in particular child window don't have minimum size covering the menu bar so this is useful for them.\n            window->DrawList->AddRectFilled(menu_bar_rect.Min, menu_bar_rect.Max, GetColorU32(ImGuiCol_MenuBarBg), (flags & ImGuiWindowFlags_NoTitleBar) ? window_rounding : 0.0f, ImDrawFlags_RoundCornersTop);\n            if (style.FrameBorderSize > 0.0f && menu_bar_rect.Max.y < window->Pos.y + window->Size.y)\n                window->DrawList->AddLine(menu_bar_rect.GetBL() + ImVec2(window_border_size * 0.5f, 0.0f), menu_bar_rect.GetBR() - ImVec2(window_border_size * 0.5f, 0.0f), GetColorU32(ImGuiCol_Border), style.FrameBorderSize);\n        }\n\n        // Scrollbars\n        if (window->ScrollbarX)\n            Scrollbar(ImGuiAxis_X);\n        if (window->ScrollbarY)\n            Scrollbar(ImGuiAxis_Y);\n\n        // Render resize grips (after their input handling so we don't have a frame of latency)\n        if (handle_borders_and_resize_grips && !(flags & ImGuiWindowFlags_NoResize))\n        {\n            for (int resize_grip_n = 0; resize_grip_n < resize_grip_count; resize_grip_n++)\n            {\n                const ImU32 col = resize_grip_col[resize_grip_n];\n                if ((col & IM_COL32_A_MASK) == 0)\n                    continue;\n                const ImGuiResizeGripDef& grip = resize_grip_def[resize_grip_n];\n                const ImVec2 corner = ImLerp(window->Pos, window->Pos + window->Size, grip.CornerPosN);\n                const float border_inner = IM_ROUND(window_border_size * 0.5f);\n                window->DrawList->PathLineTo(corner + grip.InnerDir * ((resize_grip_n & 1) ? ImVec2(border_inner, resize_grip_draw_size) : ImVec2(resize_grip_draw_size, border_inner)));\n                window->DrawList->PathLineTo(corner + grip.InnerDir * ((resize_grip_n & 1) ? ImVec2(resize_grip_draw_size, border_inner) : ImVec2(border_inner, resize_grip_draw_size)));\n                window->DrawList->PathArcToFast(ImVec2(corner.x + grip.InnerDir.x * (window_rounding + border_inner), corner.y + grip.InnerDir.y * (window_rounding + border_inner)), window_rounding, grip.AngleMin12, grip.AngleMax12);\n                window->DrawList->PathFillConvex(col);\n            }\n        }\n\n        // Borders\n        if (handle_borders_and_resize_grips)\n            RenderWindowOuterBorders(window);\n    }\n    window->DC.NavLayerCurrent = ImGuiNavLayer_Main;\n}\n\n// Render title text, collapse button, close button\nvoid ImGui::RenderWindowTitleBarContents(ImGuiWindow* window, const ImRect& title_bar_rect, const char* name, bool* p_open)\n{\n    ImGuiContext& g = *GImGui;\n    ImGuiStyle& style = g.Style;\n    ImGuiWindowFlags flags = window->Flags;\n\n    const bool has_close_button = (p_open != NULL);\n    const bool has_collapse_button = !(flags & ImGuiWindowFlags_NoCollapse) && (style.WindowMenuButtonPosition != ImGuiDir_None);\n\n    // Close & Collapse button are on the Menu NavLayer and don't default focus (unless there's nothing else on that layer)\n    // FIXME-NAV: Might want (or not?) to set the equivalent of ImGuiButtonFlags_NoNavFocus so that mouse clicks on standard title bar items don't necessarily set nav/keyboard ref?\n    const ImGuiItemFlags item_flags_backup = g.CurrentItemFlags;\n    g.CurrentItemFlags |= ImGuiItemFlags_NoNavDefaultFocus;\n    window->DC.NavLayerCurrent = ImGuiNavLayer_Menu;\n\n    // Layout buttons\n    // FIXME: Would be nice to generalize the subtleties expressed here into reusable code.\n    float pad_l = style.FramePadding.x;\n    float pad_r = style.FramePadding.x;\n    float button_sz = g.FontSize;\n    ImVec2 close_button_pos;\n    ImVec2 collapse_button_pos;\n    if (has_close_button)\n    {\n        close_button_pos = ImVec2(title_bar_rect.Max.x - pad_r - button_sz, title_bar_rect.Min.y + style.FramePadding.y);\n        pad_r += button_sz + style.ItemInnerSpacing.x;\n    }\n    if (has_collapse_button && style.WindowMenuButtonPosition == ImGuiDir_Right)\n    {\n        collapse_button_pos = ImVec2(title_bar_rect.Max.x - pad_r - button_sz, title_bar_rect.Min.y + style.FramePadding.y);\n        pad_r += button_sz + style.ItemInnerSpacing.x;\n    }\n    if (has_collapse_button && style.WindowMenuButtonPosition == ImGuiDir_Left)\n    {\n        collapse_button_pos = ImVec2(title_bar_rect.Min.x + pad_l, title_bar_rect.Min.y + style.FramePadding.y);\n        pad_l += button_sz + style.ItemInnerSpacing.x;\n    }\n\n    // Collapse button (submitting first so it gets priority when choosing a navigation init fallback)\n    if (has_collapse_button)\n        if (CollapseButton(window->GetID(\"#COLLAPSE\"), collapse_button_pos))\n            window->WantCollapseToggle = true; // Defer actual collapsing to next frame as we are too far in the Begin() function\n\n    // Close button\n    if (has_close_button)\n        if (CloseButton(window->GetID(\"#CLOSE\"), close_button_pos))\n            *p_open = false;\n\n    window->DC.NavLayerCurrent = ImGuiNavLayer_Main;\n    g.CurrentItemFlags = item_flags_backup;\n\n    // Title bar text (with: horizontal alignment, avoiding collapse/close button, optional \"unsaved document\" marker)\n    // FIXME: Refactor text alignment facilities along with RenderText helpers, this is WAY too much messy code..\n    const float marker_size_x = (flags & ImGuiWindowFlags_UnsavedDocument) ? button_sz * 0.80f : 0.0f;\n    const ImVec2 text_size = CalcTextSize(name, NULL, true) + ImVec2(marker_size_x, 0.0f);\n\n    // As a nice touch we try to ensure that centered title text doesn't get affected by visibility of Close/Collapse button,\n    // while uncentered title text will still reach edges correctly.\n    if (pad_l > style.FramePadding.x)\n        pad_l += g.Style.ItemInnerSpacing.x;\n    if (pad_r > style.FramePadding.x)\n        pad_r += g.Style.ItemInnerSpacing.x;\n    if (style.WindowTitleAlign.x > 0.0f && style.WindowTitleAlign.x < 1.0f)\n    {\n        float centerness = ImSaturate(1.0f - ImFabs(style.WindowTitleAlign.x - 0.5f) * 2.0f); // 0.0f on either edges, 1.0f on center\n        float pad_extend = ImMin(ImMax(pad_l, pad_r), title_bar_rect.GetWidth() - pad_l - pad_r - text_size.x);\n        pad_l = ImMax(pad_l, pad_extend * centerness);\n        pad_r = ImMax(pad_r, pad_extend * centerness);\n    }\n\n    ImRect layout_r(title_bar_rect.Min.x + pad_l, title_bar_rect.Min.y, title_bar_rect.Max.x - pad_r, title_bar_rect.Max.y);\n    ImRect clip_r(layout_r.Min.x, layout_r.Min.y, ImMin(layout_r.Max.x + g.Style.ItemInnerSpacing.x, title_bar_rect.Max.x), layout_r.Max.y);\n    if (flags & ImGuiWindowFlags_UnsavedDocument)\n    {\n        ImVec2 marker_pos;\n        marker_pos.x = ImClamp(layout_r.Min.x + (layout_r.GetWidth() - text_size.x) * style.WindowTitleAlign.x + text_size.x, layout_r.Min.x, layout_r.Max.x);\n        marker_pos.y = (layout_r.Min.y + layout_r.Max.y) * 0.5f;\n        if (marker_pos.x > layout_r.Min.x)\n        {\n            RenderBullet(window->DrawList, marker_pos, GetColorU32(ImGuiCol_Text));\n            clip_r.Max.x = ImMin(clip_r.Max.x, marker_pos.x - (int)(marker_size_x * 0.5f));\n        }\n    }\n    //if (g.IO.KeyShift) window->DrawList->AddRect(layout_r.Min, layout_r.Max, IM_COL32(255, 128, 0, 255)); // [DEBUG]\n    //if (g.IO.KeyCtrl) window->DrawList->AddRect(clip_r.Min, clip_r.Max, IM_COL32(255, 128, 0, 255)); // [DEBUG]\n    RenderTextClipped(layout_r.Min, layout_r.Max, name, NULL, &text_size, style.WindowTitleAlign, &clip_r);\n}\n\nvoid ImGui::UpdateWindowParentAndRootLinks(ImGuiWindow* window, ImGuiWindowFlags flags, ImGuiWindow* parent_window)\n{\n    window->ParentWindow = parent_window;\n    window->RootWindow = window->RootWindowPopupTree = window->RootWindowForTitleBarHighlight = window->RootWindowForNav = window;\n    if (parent_window && (flags & ImGuiWindowFlags_ChildWindow) && !(flags & ImGuiWindowFlags_Tooltip))\n        window->RootWindow = parent_window->RootWindow;\n    if (parent_window && (flags & ImGuiWindowFlags_Popup))\n        window->RootWindowPopupTree = parent_window->RootWindowPopupTree;\n    if (parent_window && !(flags & ImGuiWindowFlags_Modal) && (flags & (ImGuiWindowFlags_ChildWindow | ImGuiWindowFlags_Popup)))\n        window->RootWindowForTitleBarHighlight = parent_window->RootWindowForTitleBarHighlight;\n    while (window->RootWindowForNav->ChildFlags & ImGuiChildFlags_NavFlattened)\n    {\n        IM_ASSERT(window->RootWindowForNav->ParentWindow != NULL);\n        window->RootWindowForNav = window->RootWindowForNav->ParentWindow;\n    }\n}\n\n// [EXPERIMENTAL] Called by Begin(). NextWindowData is valid at this point.\n// This is designed as a toy/test-bed for\nvoid ImGui::UpdateWindowSkipRefresh(ImGuiWindow* window)\n{\n    ImGuiContext& g = *GImGui;\n    window->SkipRefresh = false;\n    if ((g.NextWindowData.HasFlags & ImGuiNextWindowDataFlags_HasRefreshPolicy) == 0)\n        return;\n    if (g.NextWindowData.RefreshFlagsVal & ImGuiWindowRefreshFlags_TryToAvoidRefresh)\n    {\n        // FIXME-IDLE: Tests for e.g. mouse clicks or keyboard while focused.\n        if (window->Appearing) // If currently appearing\n            return;\n        if (window->Hidden) // If was hidden (previous frame)\n            return;\n        if ((g.NextWindowData.RefreshFlagsVal & ImGuiWindowRefreshFlags_RefreshOnHover) && g.HoveredWindow)\n            if (window->RootWindow == g.HoveredWindow->RootWindow || IsWindowWithinBeginStackOf(g.HoveredWindow->RootWindow, window))\n                return;\n        if ((g.NextWindowData.RefreshFlagsVal & ImGuiWindowRefreshFlags_RefreshOnFocus) && g.NavWindow)\n            if (window->RootWindow == g.NavWindow->RootWindow || IsWindowWithinBeginStackOf(g.NavWindow->RootWindow, window))\n                return;\n        window->DrawList = NULL;\n        window->SkipRefresh = true;\n    }\n}\n\nstatic void SetWindowActiveForSkipRefresh(ImGuiWindow* window)\n{\n    window->Active = true;\n    for (ImGuiWindow* child : window->DC.ChildWindows)\n        if (!child->Hidden)\n        {\n            child->Active = child->SkipRefresh = true;\n            SetWindowActiveForSkipRefresh(child);\n        }\n}\n\n// Push a new Dear ImGui window to add widgets to.\n// - A default window called \"Debug\" is automatically stacked at the beginning of every frame so you can use widgets without explicitly calling a Begin/End pair.\n// - Begin/End can be called multiple times during the frame with the same window name to append content.\n// - The window name is used as a unique identifier to preserve window information across frames (and save rudimentary information to the .ini file).\n//   You can use the \"##\" or \"###\" markers to use the same label with different id, or same id with different label. See documentation at the top of this file.\n// - Return false when window is collapsed, so you can early out in your code. You always need to call ImGui::End() even if false is returned.\n// - Passing 'bool* p_open' displays a Close button on the upper-right corner of the window, the pointed value will be set to false when the button is pressed.\nbool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags)\n{\n    ImGuiContext& g = *GImGui;\n    const ImGuiStyle& style = g.Style;\n    IM_ASSERT(name != NULL && name[0] != '\\0');     // Window name required\n    IM_ASSERT(g.WithinFrameScope);                  // Forgot to call ImGui::NewFrame()\n    IM_ASSERT(g.FrameCountEnded != g.FrameCount);   // Called ImGui::Render() or ImGui::EndFrame() and haven't called ImGui::NewFrame() again yet\n\n    // Find or create\n    ImGuiWindow* window = FindWindowByName(name);\n    const bool window_just_created = (window == NULL);\n    if (window_just_created)\n        window = CreateNewWindow(name, flags);\n\n    // [DEBUG] Debug break requested by user\n    if (g.DebugBreakInWindow == window->ID)\n        IM_DEBUG_BREAK();\n\n    // Automatically disable manual moving/resizing when NoInputs is set\n    if ((flags & ImGuiWindowFlags_NoInputs) == ImGuiWindowFlags_NoInputs)\n        flags |= ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize;\n\n    const int current_frame = g.FrameCount;\n    const bool first_begin_of_the_frame = (window->LastFrameActive != current_frame);\n    window->IsFallbackWindow = (g.CurrentWindowStack.Size == 0 && g.WithinFrameScopeWithImplicitWindow);\n\n    // Update the Appearing flag\n    bool window_just_activated_by_user = (window->LastFrameActive < current_frame - 1);   // Not using !WasActive because the implicit \"Debug\" window would always toggle off->on\n    if (flags & ImGuiWindowFlags_Popup)\n    {\n        ImGuiPopupData& popup_ref = g.OpenPopupStack[g.BeginPopupStack.Size];\n        window_just_activated_by_user |= (window->PopupId != popup_ref.PopupId); // We recycle popups so treat window as activated if popup id changed\n        window_just_activated_by_user |= (window != popup_ref.Window);\n    }\n    window->Appearing = window_just_activated_by_user;\n    if (window->Appearing)\n        SetWindowConditionAllowFlags(window, ImGuiCond_Appearing, true);\n\n    // Update Flags, LastFrameActive, BeginOrderXXX fields\n    if (first_begin_of_the_frame)\n    {\n        UpdateWindowInFocusOrderList(window, window_just_created, flags);\n        window->Flags = (ImGuiWindowFlags)flags;\n        window->ChildFlags = (g.NextWindowData.HasFlags & ImGuiNextWindowDataFlags_HasChildFlags) ? g.NextWindowData.ChildFlags : 0;\n        window->LastFrameActive = current_frame;\n        window->LastTimeActive = (float)g.Time;\n        window->BeginOrderWithinParent = 0;\n        window->BeginOrderWithinContext = (short)(g.WindowsActiveCount++);\n    }\n    else\n    {\n        flags = window->Flags;\n    }\n\n    // Parent window is latched only on the first call to Begin() of the frame, so further append-calls can be done from a different window stack\n    ImGuiWindow* parent_window_in_stack = g.CurrentWindowStack.empty() ? NULL : g.CurrentWindowStack.back().Window;\n    ImGuiWindow* parent_window = first_begin_of_the_frame ? ((flags & (ImGuiWindowFlags_ChildWindow | ImGuiWindowFlags_Popup)) ? parent_window_in_stack : NULL) : window->ParentWindow;\n    IM_ASSERT(parent_window != NULL || !(flags & ImGuiWindowFlags_ChildWindow));\n\n    // We allow window memory to be compacted so recreate the base stack when needed.\n    if (window->IDStack.Size == 0)\n        window->IDStack.push_back(window->ID);\n\n    // Add to stack\n    g.CurrentWindow = window;\n    g.CurrentWindowStack.resize(g.CurrentWindowStack.Size + 1);\n    ImGuiWindowStackData& window_stack_data = g.CurrentWindowStack.back();\n    window_stack_data.Window = window;\n    window_stack_data.ParentLastItemDataBackup = g.LastItemData;\n    window_stack_data.DisabledOverrideReenable = (flags & ImGuiWindowFlags_Tooltip) && (g.CurrentItemFlags & ImGuiItemFlags_Disabled);\n    window_stack_data.DisabledOverrideReenableAlphaBackup = 0.0f;\n    ErrorRecoveryStoreState(&window_stack_data.StackSizesInBegin);\n    g.StackSizesInBeginForCurrentWindow = &window_stack_data.StackSizesInBegin;\n    if (flags & ImGuiWindowFlags_ChildMenu)\n        g.BeginMenuDepth++;\n\n    // Update ->RootWindow and others pointers (before any possible call to FocusWindow)\n    if (first_begin_of_the_frame)\n    {\n        UpdateWindowParentAndRootLinks(window, flags, parent_window);\n        window->ParentWindowInBeginStack = parent_window_in_stack;\n\n        // There's little point to expose a flag to set this: because the interesting cases won't be using parent_window_in_stack,\n        // e.g. linking a tool window in a standalone viewport to a document window, regardless of their Begin() stack parenting. (#6798)\n        window->ParentWindowForFocusRoute = (flags & ImGuiWindowFlags_ChildWindow) ? parent_window_in_stack : NULL;\n\n        // Inherent SetWindowFontScale() from parent until we fix this system...\n        window->FontWindowScaleParents = parent_window ? parent_window->FontWindowScaleParents * parent_window->FontWindowScale : 1.0f;\n    }\n\n    // Add to focus scope stack\n    PushFocusScope((window->ChildFlags & ImGuiChildFlags_NavFlattened) ? g.CurrentFocusScopeId : window->ID);\n    window->NavRootFocusScopeId = g.CurrentFocusScopeId;\n\n    // Add to popup stacks: update OpenPopupStack[] data, push to BeginPopupStack[]\n    if (flags & ImGuiWindowFlags_Popup)\n    {\n        ImGuiPopupData& popup_ref = g.OpenPopupStack[g.BeginPopupStack.Size];\n        popup_ref.Window = window;\n        popup_ref.ParentNavLayer = parent_window_in_stack->DC.NavLayerCurrent;\n        g.BeginPopupStack.push_back(popup_ref);\n        window->PopupId = popup_ref.PopupId;\n    }\n\n    // Process SetNextWindow***() calls\n    // (FIXME: Consider splitting the HasXXX flags into X/Y components\n    bool window_pos_set_by_api = false;\n    bool window_size_x_set_by_api = false, window_size_y_set_by_api = false;\n    if (g.NextWindowData.HasFlags & ImGuiNextWindowDataFlags_HasPos)\n    {\n        window_pos_set_by_api = (window->SetWindowPosAllowFlags & g.NextWindowData.PosCond) != 0;\n        if (window_pos_set_by_api && ImLengthSqr(g.NextWindowData.PosPivotVal) > 0.00001f)\n        {\n            // May be processed on the next frame if this is our first frame and we are measuring size\n            // FIXME: Look into removing the branch so everything can go through this same code path for consistency.\n            window->SetWindowPosVal = g.NextWindowData.PosVal;\n            window->SetWindowPosPivot = g.NextWindowData.PosPivotVal;\n            window->SetWindowPosAllowFlags &= ~(ImGuiCond_Once | ImGuiCond_FirstUseEver | ImGuiCond_Appearing);\n        }\n        else\n        {\n            SetWindowPos(window, g.NextWindowData.PosVal, g.NextWindowData.PosCond);\n        }\n    }\n    if (g.NextWindowData.HasFlags & ImGuiNextWindowDataFlags_HasSize)\n    {\n        window_size_x_set_by_api = (window->SetWindowSizeAllowFlags & g.NextWindowData.SizeCond) != 0 && (g.NextWindowData.SizeVal.x > 0.0f);\n        window_size_y_set_by_api = (window->SetWindowSizeAllowFlags & g.NextWindowData.SizeCond) != 0 && (g.NextWindowData.SizeVal.y > 0.0f);\n        if ((window->ChildFlags & ImGuiChildFlags_ResizeX) && (window->SetWindowSizeAllowFlags & ImGuiCond_FirstUseEver) == 0) // Axis-specific conditions for BeginChild()\n            g.NextWindowData.SizeVal.x = window->SizeFull.x;\n        if ((window->ChildFlags & ImGuiChildFlags_ResizeY) && (window->SetWindowSizeAllowFlags & ImGuiCond_FirstUseEver) == 0)\n            g.NextWindowData.SizeVal.y = window->SizeFull.y;\n        SetWindowSize(window, g.NextWindowData.SizeVal, g.NextWindowData.SizeCond);\n    }\n    if (g.NextWindowData.HasFlags & ImGuiNextWindowDataFlags_HasScroll)\n    {\n        if (g.NextWindowData.ScrollVal.x >= 0.0f)\n        {\n            window->ScrollTarget.x = g.NextWindowData.ScrollVal.x;\n            window->ScrollTargetCenterRatio.x = 0.0f;\n        }\n        if (g.NextWindowData.ScrollVal.y >= 0.0f)\n        {\n            window->ScrollTarget.y = g.NextWindowData.ScrollVal.y;\n            window->ScrollTargetCenterRatio.y = 0.0f;\n        }\n    }\n    if (g.NextWindowData.HasFlags & ImGuiNextWindowDataFlags_HasContentSize)\n        window->ContentSizeExplicit = g.NextWindowData.ContentSizeVal;\n    else if (first_begin_of_the_frame)\n        window->ContentSizeExplicit = ImVec2(0.0f, 0.0f);\n    if (g.NextWindowData.HasFlags & ImGuiNextWindowDataFlags_HasCollapsed)\n        SetWindowCollapsed(window, g.NextWindowData.CollapsedVal, g.NextWindowData.CollapsedCond);\n    if (g.NextWindowData.HasFlags & ImGuiNextWindowDataFlags_HasFocus)\n        FocusWindow(window);\n    if (window->Appearing)\n        SetWindowConditionAllowFlags(window, ImGuiCond_Appearing, false);\n\n    // [EXPERIMENTAL] Skip Refresh mode\n    UpdateWindowSkipRefresh(window);\n\n    // Nested root windows (typically tooltips) override disabled state\n    if (window_stack_data.DisabledOverrideReenable && window->RootWindow == window)\n        BeginDisabledOverrideReenable();\n\n    // We intentionally set g.CurrentWindow to NULL to prevent usage until when the viewport is set, then will call SetCurrentWindow()\n    g.CurrentWindow = NULL;\n\n    // When reusing window again multiple times a frame, just append content (don't need to setup again)\n    if (first_begin_of_the_frame && !window->SkipRefresh)\n    {\n        // Initialize\n        const bool window_is_child_tooltip = (flags & ImGuiWindowFlags_ChildWindow) && (flags & ImGuiWindowFlags_Tooltip); // FIXME-WIP: Undocumented behavior of Child+Tooltip for pinned tooltip (#1345)\n        const bool window_just_appearing_after_hidden_for_resize = (window->HiddenFramesCannotSkipItems > 0);\n        window->Active = true;\n        window->HasCloseButton = (p_open != NULL);\n        window->ClipRect = ImVec4(-FLT_MAX, -FLT_MAX, +FLT_MAX, +FLT_MAX);\n        window->IDStack.resize(1);\n        window->DrawList->_ResetForNewFrame();\n        window->DC.CurrentTableIdx = -1;\n\n        // Restore buffer capacity when woken from a compacted state, to avoid\n        if (window->MemoryCompacted)\n            GcAwakeTransientWindowBuffers(window);\n\n        // Update stored window name when it changes (which can _only_ happen with the \"###\" operator, so the ID would stay unchanged).\n        // The title bar always display the 'name' parameter, so we only update the string storage if it needs to be visible to the end-user elsewhere.\n        bool window_title_visible_elsewhere = false;\n        if (g.NavWindowingListWindow != NULL && (flags & ImGuiWindowFlags_NoNavFocus) == 0)   // Window titles visible when using CTRL+TAB\n            window_title_visible_elsewhere = true;\n        if (flags & ImGuiWindowFlags_ChildMenu)\n            window_title_visible_elsewhere = true;\n        if (window_title_visible_elsewhere && !window_just_created && strcmp(name, window->Name) != 0)\n        {\n            size_t buf_len = (size_t)window->NameBufLen;\n            window->Name = ImStrdupcpy(window->Name, &buf_len, name);\n            window->NameBufLen = (int)buf_len;\n        }\n\n        // UPDATE CONTENTS SIZE, UPDATE HIDDEN STATUS\n\n        // Update contents size from last frame for auto-fitting (or use explicit size)\n        CalcWindowContentSizes(window, &window->ContentSize, &window->ContentSizeIdeal);\n        if (window->HiddenFramesCanSkipItems > 0)\n            window->HiddenFramesCanSkipItems--;\n        if (window->HiddenFramesCannotSkipItems > 0)\n            window->HiddenFramesCannotSkipItems--;\n        if (window->HiddenFramesForRenderOnly > 0)\n            window->HiddenFramesForRenderOnly--;\n\n        // Hide new windows for one frame until they calculate their size\n        if (window_just_created && (!window_size_x_set_by_api || !window_size_y_set_by_api))\n            window->HiddenFramesCannotSkipItems = 1;\n\n        // Hide popup/tooltip window when re-opening while we measure size (because we recycle the windows)\n        // We reset Size/ContentSize for reappearing popups/tooltips early in this function, so further code won't be tempted to use the old size.\n        if (window_just_activated_by_user && (flags & (ImGuiWindowFlags_Popup | ImGuiWindowFlags_Tooltip)) != 0)\n        {\n            window->HiddenFramesCannotSkipItems = 1;\n            if (flags & ImGuiWindowFlags_AlwaysAutoResize)\n            {\n                if (!window_size_x_set_by_api)\n                    window->Size.x = window->SizeFull.x = 0.f;\n                if (!window_size_y_set_by_api)\n                    window->Size.y = window->SizeFull.y = 0.f;\n                window->ContentSize = window->ContentSizeIdeal = ImVec2(0.f, 0.f);\n            }\n        }\n\n        // SELECT VIEWPORT\n        // FIXME-VIEWPORT: In the docking/viewport branch, this is the point where we select the current viewport (which may affect the style)\n\n        ImGuiViewportP* viewport = (ImGuiViewportP*)(void*)GetMainViewport();\n        SetWindowViewport(window, viewport);\n        SetCurrentWindow(window);\n\n        // LOCK BORDER SIZE AND PADDING FOR THE FRAME (so that altering them doesn't cause inconsistencies)\n\n        if (flags & ImGuiWindowFlags_ChildWindow)\n            window->WindowBorderSize = style.ChildBorderSize;\n        else\n            window->WindowBorderSize = ((flags & (ImGuiWindowFlags_Popup | ImGuiWindowFlags_Tooltip)) && !(flags & ImGuiWindowFlags_Modal)) ? style.PopupBorderSize : style.WindowBorderSize;\n        window->WindowPadding = style.WindowPadding;\n        if ((flags & ImGuiWindowFlags_ChildWindow) && !(flags & ImGuiWindowFlags_Popup) && !(window->ChildFlags & ImGuiChildFlags_AlwaysUseWindowPadding) && window->WindowBorderSize == 0.0f)\n            window->WindowPadding = ImVec2(0.0f, (flags & ImGuiWindowFlags_MenuBar) ? style.WindowPadding.y : 0.0f);\n\n        // Lock menu offset so size calculation can use it as menu-bar windows need a minimum size.\n        window->DC.MenuBarOffset.x = ImMax(ImMax(window->WindowPadding.x, style.ItemSpacing.x), g.NextWindowData.MenuBarOffsetMinVal.x);\n        window->DC.MenuBarOffset.y = g.NextWindowData.MenuBarOffsetMinVal.y;\n        window->TitleBarHeight = (flags & ImGuiWindowFlags_NoTitleBar) ? 0.0f : g.FontSize + g.Style.FramePadding.y * 2.0f;\n        window->MenuBarHeight = (flags & ImGuiWindowFlags_MenuBar) ? window->DC.MenuBarOffset.y + g.FontSize + g.Style.FramePadding.y * 2.0f : 0.0f;\n        window->FontRefSize = g.FontSize; // Lock this to discourage calling window->CalcFontSize() outside of current window.\n\n        // Depending on condition we use previous or current window size to compare against contents size to decide if a scrollbar should be visible.\n        // Those flags will be altered further down in the function depending on more conditions.\n        bool use_current_size_for_scrollbar_x = window_just_created;\n        bool use_current_size_for_scrollbar_y = window_just_created;\n        if (window_size_x_set_by_api && window->ContentSizeExplicit.x != 0.0f)\n            use_current_size_for_scrollbar_x = true;\n        if (window_size_y_set_by_api && window->ContentSizeExplicit.y != 0.0f) // #7252\n            use_current_size_for_scrollbar_y = true;\n\n        // Collapse window by double-clicking on title bar\n        // At this point we don't have a clipping rectangle setup yet, so we can use the title bar area for hit detection and drawing\n        if (!(flags & ImGuiWindowFlags_NoTitleBar) && !(flags & ImGuiWindowFlags_NoCollapse))\n        {\n            // We don't use a regular button+id to test for double-click on title bar (mostly due to legacy reason, could be fixed),\n            // so verify that we don't have items over the title bar.\n            ImRect title_bar_rect = window->TitleBarRect();\n            if (g.HoveredWindow == window && g.HoveredId == 0 && g.HoveredIdPreviousFrame == 0 && g.ActiveId == 0 && IsMouseHoveringRect(title_bar_rect.Min, title_bar_rect.Max))\n                if (g.IO.MouseClickedCount[0] == 2 && GetKeyOwner(ImGuiKey_MouseLeft) == ImGuiKeyOwner_NoOwner)\n                    window->WantCollapseToggle = true;\n            if (window->WantCollapseToggle)\n            {\n                window->Collapsed = !window->Collapsed;\n                if (!window->Collapsed)\n                    use_current_size_for_scrollbar_y = true;\n                MarkIniSettingsDirty(window);\n            }\n        }\n        else\n        {\n            window->Collapsed = false;\n        }\n        window->WantCollapseToggle = false;\n\n        // SIZE\n\n        // Outer Decoration Sizes\n        // (we need to clear ScrollbarSize immediately as CalcWindowAutoFitSize() needs it and can be called from other locations).\n        const ImVec2 scrollbar_sizes_from_last_frame = window->ScrollbarSizes;\n        window->DecoOuterSizeX1 = 0.0f;\n        window->DecoOuterSizeX2 = 0.0f;\n        window->DecoOuterSizeY1 = window->TitleBarHeight + window->MenuBarHeight;\n        window->DecoOuterSizeY2 = 0.0f;\n        window->ScrollbarSizes = ImVec2(0.0f, 0.0f);\n\n        // Calculate auto-fit size, handle automatic resize\n        const ImVec2 size_auto_fit = CalcWindowAutoFitSize(window, window->ContentSizeIdeal);\n        if ((flags & ImGuiWindowFlags_AlwaysAutoResize) && !window->Collapsed)\n        {\n            // Using SetNextWindowSize() overrides ImGuiWindowFlags_AlwaysAutoResize, so it can be used on tooltips/popups, etc.\n            if (!window_size_x_set_by_api)\n            {\n                window->SizeFull.x = size_auto_fit.x;\n                use_current_size_for_scrollbar_x = true;\n            }\n            if (!window_size_y_set_by_api)\n            {\n                window->SizeFull.y = size_auto_fit.y;\n                use_current_size_for_scrollbar_y = true;\n            }\n        }\n        else if (window->AutoFitFramesX > 0 || window->AutoFitFramesY > 0)\n        {\n            // Auto-fit may only grow window during the first few frames\n            // We still process initial auto-fit on collapsed windows to get a window width, but otherwise don't honor ImGuiWindowFlags_AlwaysAutoResize when collapsed.\n            if (!window_size_x_set_by_api && window->AutoFitFramesX > 0)\n            {\n                window->SizeFull.x = window->AutoFitOnlyGrows ? ImMax(window->SizeFull.x, size_auto_fit.x) : size_auto_fit.x;\n                use_current_size_for_scrollbar_x = true;\n            }\n            if (!window_size_y_set_by_api && window->AutoFitFramesY > 0)\n            {\n                window->SizeFull.y = window->AutoFitOnlyGrows ? ImMax(window->SizeFull.y, size_auto_fit.y) : size_auto_fit.y;\n                use_current_size_for_scrollbar_y = true;\n            }\n            if (!window->Collapsed)\n                MarkIniSettingsDirty(window);\n        }\n\n        // Apply minimum/maximum window size constraints and final size\n        window->SizeFull = CalcWindowSizeAfterConstraint(window, window->SizeFull);\n        window->Size = window->Collapsed && !(flags & ImGuiWindowFlags_ChildWindow) ? window->TitleBarRect().GetSize() : window->SizeFull;\n\n        // POSITION\n\n        // Popup latch its initial position, will position itself when it appears next frame\n        if (window_just_activated_by_user)\n        {\n            window->AutoPosLastDirection = ImGuiDir_None;\n            if ((flags & ImGuiWindowFlags_Popup) != 0 && !(flags & ImGuiWindowFlags_Modal) && !window_pos_set_by_api) // FIXME: BeginPopup() could use SetNextWindowPos()\n                window->Pos = g.BeginPopupStack.back().OpenPopupPos;\n        }\n\n        // Position child window\n        if (flags & ImGuiWindowFlags_ChildWindow)\n        {\n            IM_ASSERT(parent_window && parent_window->Active);\n            window->BeginOrderWithinParent = (short)parent_window->DC.ChildWindows.Size;\n            parent_window->DC.ChildWindows.push_back(window);\n            if (!(flags & ImGuiWindowFlags_Popup) && !window_pos_set_by_api && !window_is_child_tooltip)\n                window->Pos = parent_window->DC.CursorPos;\n        }\n\n        const bool window_pos_with_pivot = (window->SetWindowPosVal.x != FLT_MAX && window->HiddenFramesCannotSkipItems == 0);\n        if (window_pos_with_pivot)\n            SetWindowPos(window, window->SetWindowPosVal - window->Size * window->SetWindowPosPivot, 0); // Position given a pivot (e.g. for centering)\n        else if ((flags & ImGuiWindowFlags_ChildMenu) != 0)\n            window->Pos = FindBestWindowPosForPopup(window);\n        else if ((flags & ImGuiWindowFlags_Popup) != 0 && !window_pos_set_by_api && window_just_appearing_after_hidden_for_resize)\n            window->Pos = FindBestWindowPosForPopup(window);\n        else if ((flags & ImGuiWindowFlags_Tooltip) != 0 && !window_pos_set_by_api && !window_is_child_tooltip)\n            window->Pos = FindBestWindowPosForPopup(window);\n\n        // Calculate the range of allowed position for that window (to be movable and visible past safe area padding)\n        // When clamping to stay visible, we will enforce that window->Pos stays inside of visibility_rect.\n        ImRect viewport_rect(viewport->GetMainRect());\n        ImRect viewport_work_rect(viewport->GetWorkRect());\n        ImVec2 visibility_padding = ImMax(style.DisplayWindowPadding, style.DisplaySafeAreaPadding);\n        ImRect visibility_rect(viewport_work_rect.Min + visibility_padding, viewport_work_rect.Max - visibility_padding);\n\n        // Clamp position/size so window stays visible within its viewport or monitor\n        // Ignore zero-sized display explicitly to avoid losing positions if a window manager reports zero-sized window when initializing or minimizing.\n        if (!window_pos_set_by_api && !(flags & ImGuiWindowFlags_ChildWindow))\n            if (viewport_rect.GetWidth() > 0.0f && viewport_rect.GetHeight() > 0.0f)\n                ClampWindowPos(window, visibility_rect);\n        window->Pos = ImTrunc(window->Pos);\n\n        // Lock window rounding for the frame (so that altering them doesn't cause inconsistencies)\n        // Large values tend to lead to variety of artifacts and are not recommended.\n        window->WindowRounding = (flags & ImGuiWindowFlags_ChildWindow) ? style.ChildRounding : ((flags & ImGuiWindowFlags_Popup) && !(flags & ImGuiWindowFlags_Modal)) ? style.PopupRounding : style.WindowRounding;\n\n        // For windows with title bar or menu bar, we clamp to FrameHeight(FontSize + FramePadding.y * 2.0f) to completely hide artifacts.\n        //if ((window->Flags & ImGuiWindowFlags_MenuBar) || !(window->Flags & ImGuiWindowFlags_NoTitleBar))\n        //    window->WindowRounding = ImMin(window->WindowRounding, g.FontSize + style.FramePadding.y * 2.0f);\n\n        // Apply window focus (new and reactivated windows are moved to front)\n        bool want_focus = false;\n        if (window_just_activated_by_user && !(flags & ImGuiWindowFlags_NoFocusOnAppearing))\n        {\n            if (flags & ImGuiWindowFlags_Popup)\n                want_focus = true;\n            else if ((flags & (ImGuiWindowFlags_ChildWindow | ImGuiWindowFlags_Tooltip)) == 0)\n                want_focus = true;\n        }\n\n        // [Test Engine] Register whole window in the item system (before submitting further decorations)\n#ifdef IMGUI_ENABLE_TEST_ENGINE\n        if (g.TestEngineHookItems)\n        {\n            IM_ASSERT(window->IDStack.Size == 1);\n            window->IDStack.Size = 0; // As window->IDStack[0] == window->ID here, make sure TestEngine doesn't erroneously see window as parent of itself.\n            window->DC.NavLayerCurrent = ImGuiNavLayer_Menu;\n            IMGUI_TEST_ENGINE_ITEM_ADD(window->ID, window->Rect(), NULL);\n            IMGUI_TEST_ENGINE_ITEM_INFO(window->ID, window->Name, (g.HoveredWindow == window) ? ImGuiItemStatusFlags_HoveredRect : 0);\n            window->IDStack.Size = 1;\n            window->DC.NavLayerCurrent = ImGuiNavLayer_Main;\n\n        }\n#endif\n\n        // Handle manual resize: Resize Grips, Borders, Gamepad\n        int border_hovered = -1, border_held = -1;\n        ImU32 resize_grip_col[4] = {};\n        const int resize_grip_count = ((flags & ImGuiWindowFlags_ChildWindow) && !(flags & ImGuiWindowFlags_Popup)) ? 0 : g.IO.ConfigWindowsResizeFromEdges ? 2 : 1; // Allow resize from lower-left if we have the mouse cursor feedback for it.\n        const float resize_grip_draw_size = IM_TRUNC(ImMax(g.FontSize * 1.10f, window->WindowRounding + 1.0f + g.FontSize * 0.2f));\n        if (!window->Collapsed)\n            if (int auto_fit_mask = UpdateWindowManualResize(window, size_auto_fit, &border_hovered, &border_held, resize_grip_count, &resize_grip_col[0], visibility_rect))\n            {\n                if (auto_fit_mask & (1 << ImGuiAxis_X))\n                    use_current_size_for_scrollbar_x = true;\n                if (auto_fit_mask & (1 << ImGuiAxis_Y))\n                    use_current_size_for_scrollbar_y = true;\n            }\n        window->ResizeBorderHovered = (signed char)border_hovered;\n        window->ResizeBorderHeld = (signed char)border_held;\n\n        // SCROLLBAR VISIBILITY\n\n        // Update scrollbar visibility (based on the Size that was effective during last frame or the auto-resized Size).\n        if (!window->Collapsed)\n        {\n            // When reading the current size we need to read it after size constraints have been applied.\n            // Intentionally use previous frame values for InnerRect and ScrollbarSizes.\n            // And when we use window->DecorationUp here it doesn't have ScrollbarSizes.y applied yet.\n            ImVec2 avail_size_from_current_frame = ImVec2(window->SizeFull.x, window->SizeFull.y - (window->DecoOuterSizeY1 + window->DecoOuterSizeY2));\n            ImVec2 avail_size_from_last_frame = window->InnerRect.GetSize() + scrollbar_sizes_from_last_frame;\n            ImVec2 needed_size_from_last_frame = window_just_created ? ImVec2(0, 0) : window->ContentSize + window->WindowPadding * 2.0f;\n            float size_x_for_scrollbars = use_current_size_for_scrollbar_x ? avail_size_from_current_frame.x : avail_size_from_last_frame.x;\n            float size_y_for_scrollbars = use_current_size_for_scrollbar_y ? avail_size_from_current_frame.y : avail_size_from_last_frame.y;\n            bool scrollbar_x_prev = window->ScrollbarX;\n            //bool scrollbar_y_from_last_frame = window->ScrollbarY; // FIXME: May want to use that in the ScrollbarX expression? How many pros vs cons?\n            window->ScrollbarY = (flags & ImGuiWindowFlags_AlwaysVerticalScrollbar) || ((needed_size_from_last_frame.y > size_y_for_scrollbars) && !(flags & ImGuiWindowFlags_NoScrollbar));\n            window->ScrollbarX = (flags & ImGuiWindowFlags_AlwaysHorizontalScrollbar) || ((needed_size_from_last_frame.x > size_x_for_scrollbars - (window->ScrollbarY ? style.ScrollbarSize : 0.0f)) && !(flags & ImGuiWindowFlags_NoScrollbar) && (flags & ImGuiWindowFlags_HorizontalScrollbar));\n\n            // Track when ScrollbarX visibility keeps toggling, which is a sign of a feedback loop, and stabilize by enforcing visibility (#3285, #8488)\n            // (Feedback loops of this sort can manifest in various situations, but combining horizontal + vertical scrollbar + using a clipper with varying width items is one frequent cause.\n            //  The better solution is to, either (1) enforce visibility by using ImGuiWindowFlags_AlwaysHorizontalScrollbar or (2) declare stable contents width with SetNextWindowContentSize(), if you can compute it)\n            window->ScrollbarXStabilizeToggledHistory <<= 1;\n            window->ScrollbarXStabilizeToggledHistory |= (scrollbar_x_prev != window->ScrollbarX) ? 0x01 : 0x00;\n            const bool scrollbar_x_stabilize = (window->ScrollbarXStabilizeToggledHistory != 0) && ImCountSetBits(window->ScrollbarXStabilizeToggledHistory) >= 4; // 4 == half of bits in our U8 history.\n            if (scrollbar_x_stabilize)\n                window->ScrollbarX = true;\n            //if (scrollbar_x_stabilize && !window->ScrollbarXStabilizeEnabled)\n            //    IMGUI_DEBUG_LOG(\"[scroll] Stabilize ScrollbarX for Window '%s'\\n\", window->Name);\n            window->ScrollbarXStabilizeEnabled = scrollbar_x_stabilize;\n\n            if (window->ScrollbarX && !window->ScrollbarY)\n                window->ScrollbarY = (needed_size_from_last_frame.y > size_y_for_scrollbars - style.ScrollbarSize) && !(flags & ImGuiWindowFlags_NoScrollbar);\n            window->ScrollbarSizes = ImVec2(window->ScrollbarY ? style.ScrollbarSize : 0.0f, window->ScrollbarX ? style.ScrollbarSize : 0.0f);\n\n            // Amend the partially filled window->DecorationXXX values.\n            window->DecoOuterSizeX2 += window->ScrollbarSizes.x;\n            window->DecoOuterSizeY2 += window->ScrollbarSizes.y;\n        }\n\n        // UPDATE RECTANGLES (1- THOSE NOT AFFECTED BY SCROLLING)\n        // Update various regions. Variables they depend on should be set above in this function.\n        // We set this up after processing the resize grip so that our rectangles doesn't lag by a frame.\n\n        // Outer rectangle\n        // Not affected by window border size. Used by:\n        // - FindHoveredWindow() (w/ extra padding when border resize is enabled)\n        // - Begin() initial clipping rect for drawing window background and borders.\n        // - Begin() clipping whole child\n        const ImRect host_rect = ((flags & ImGuiWindowFlags_ChildWindow) && !(flags & ImGuiWindowFlags_Popup) && !window_is_child_tooltip) ? parent_window->ClipRect : viewport_rect;\n        const ImRect outer_rect = window->Rect();\n        const ImRect title_bar_rect = window->TitleBarRect();\n        window->OuterRectClipped = outer_rect;\n        window->OuterRectClipped.ClipWith(host_rect);\n\n        // Inner rectangle\n        // Not affected by window border size. Used by:\n        // - InnerClipRect\n        // - ScrollToRectEx()\n        // - NavUpdatePageUpPageDown()\n        // - Scrollbar()\n        window->InnerRect.Min.x = window->Pos.x + window->DecoOuterSizeX1;\n        window->InnerRect.Min.y = window->Pos.y + window->DecoOuterSizeY1;\n        window->InnerRect.Max.x = window->Pos.x + window->Size.x - window->DecoOuterSizeX2;\n        window->InnerRect.Max.y = window->Pos.y + window->Size.y - window->DecoOuterSizeY2;\n\n        // Inner clipping rectangle.\n        // - Extend a outside of normal work region up to borders.\n        // - This is to allow e.g. Selectable or CollapsingHeader or some separators to cover that space.\n        // - It also makes clipped items be more noticeable.\n        // - And is consistent on both axis (prior to 2024/05/03 ClipRect used WindowPadding.x * 0.5f on left and right edge), see #3312\n        // - Force round operator last to ensure that e.g. (int)(max.x-min.x) in user's render code produce correct result.\n        // Note that if our window is collapsed we will end up with an inverted (~null) clipping rectangle which is the correct behavior.\n        // Affected by window/frame border size. Used by:\n        // - Begin() initial clip rect\n        float top_border_size = (((flags & ImGuiWindowFlags_MenuBar) || !(flags & ImGuiWindowFlags_NoTitleBar)) ? style.FrameBorderSize : window->WindowBorderSize);\n\n        // Try to match the fact that our border is drawn centered over the window rectangle, rather than inner.\n        // This is why we do a *0.5f here. We don't currently even technically support large values for WindowBorderSize,\n        // see e.g #7887 #7888, but may do after we move the window border to become an inner border (and then we can remove the 0.5f here).\n        window->InnerClipRect.Min.x = ImFloor(0.5f + window->InnerRect.Min.x + window->WindowBorderSize * 0.5f);\n        window->InnerClipRect.Min.y = ImFloor(0.5f + window->InnerRect.Min.y + top_border_size * 0.5f);\n        window->InnerClipRect.Max.x = ImFloor(window->InnerRect.Max.x - window->WindowBorderSize * 0.5f);\n        window->InnerClipRect.Max.y = ImFloor(window->InnerRect.Max.y - window->WindowBorderSize * 0.5f);\n        window->InnerClipRect.ClipWithFull(host_rect);\n\n        // Default item width. Make it proportional to window size if window manually resizes\n        if (window->Size.x > 0.0f && !(flags & ImGuiWindowFlags_Tooltip) && !(flags & ImGuiWindowFlags_AlwaysAutoResize))\n            window->ItemWidthDefault = ImTrunc(window->Size.x * 0.65f);\n        else\n            window->ItemWidthDefault = ImTrunc(g.FontSize * 16.0f);\n\n        // SCROLLING\n\n        // Lock down maximum scrolling\n        // The value of ScrollMax are ahead from ScrollbarX/ScrollbarY which is intentionally using InnerRect from previous rect in order to accommodate\n        // for right/bottom aligned items without creating a scrollbar.\n        window->ScrollMax.x = ImMax(0.0f, window->ContentSize.x + window->WindowPadding.x * 2.0f - window->InnerRect.GetWidth());\n        window->ScrollMax.y = ImMax(0.0f, window->ContentSize.y + window->WindowPadding.y * 2.0f - window->InnerRect.GetHeight());\n\n        // Apply scrolling\n        window->Scroll = CalcNextScrollFromScrollTargetAndClamp(window);\n        window->ScrollTarget = ImVec2(FLT_MAX, FLT_MAX);\n        window->DecoInnerSizeX1 = window->DecoInnerSizeY1 = 0.0f;\n\n        // DRAWING\n\n        // Setup draw list and outer clipping rectangle\n        IM_ASSERT(window->DrawList->CmdBuffer.Size == 1 && window->DrawList->CmdBuffer[0].ElemCount == 0);\n        window->DrawList->PushTextureID(g.Font->ContainerAtlas->TexID);\n        PushClipRect(host_rect.Min, host_rect.Max, false);\n\n        // Child windows can render their decoration (bg color, border, scrollbars, etc.) within their parent to save a draw call (since 1.71)\n        // When using overlapping child windows, this will break the assumption that child z-order is mapped to submission order.\n        // FIXME: User code may rely on explicit sorting of overlapping child window and would need to disable this somehow. Please get in contact if you are affected (github #4493)\n        {\n            bool render_decorations_in_parent = false;\n            if ((flags & ImGuiWindowFlags_ChildWindow) && !(flags & ImGuiWindowFlags_Popup) && !window_is_child_tooltip)\n            {\n                // - We test overlap with the previous child window only (testing all would end up being O(log N) not a good investment here)\n                // - We disable this when the parent window has zero vertices, which is a common pattern leading to laying out multiple overlapping childs\n                ImGuiWindow* previous_child = parent_window->DC.ChildWindows.Size >= 2 ? parent_window->DC.ChildWindows[parent_window->DC.ChildWindows.Size - 2] : NULL;\n                bool previous_child_overlapping = previous_child ? previous_child->Rect().Overlaps(window->Rect()) : false;\n                bool parent_is_empty = (parent_window->DrawList->VtxBuffer.Size == 0);\n                if (window->DrawList->CmdBuffer.back().ElemCount == 0 && !parent_is_empty && !previous_child_overlapping)\n                    render_decorations_in_parent = true;\n            }\n            if (render_decorations_in_parent)\n                window->DrawList = parent_window->DrawList;\n\n            // Handle title bar, scrollbar, resize grips and resize borders\n            const ImGuiWindow* window_to_highlight = g.NavWindowingTarget ? g.NavWindowingTarget : g.NavWindow;\n            const bool title_bar_is_highlight = want_focus || (window_to_highlight && window->RootWindowForTitleBarHighlight == window_to_highlight->RootWindowForTitleBarHighlight);\n            const bool handle_borders_and_resize_grips = true; // This exists to facilitate merge with 'docking' branch.\n            RenderWindowDecorations(window, title_bar_rect, title_bar_is_highlight, handle_borders_and_resize_grips, resize_grip_count, resize_grip_col, resize_grip_draw_size);\n\n            if (render_decorations_in_parent)\n                window->DrawList = &window->DrawListInst;\n        }\n\n        // UPDATE RECTANGLES (2- THOSE AFFECTED BY SCROLLING)\n\n        // Work rectangle.\n        // Affected by window padding and border size. Used by:\n        // - Columns() for right-most edge\n        // - TreeNode(), CollapsingHeader() for right-most edge\n        // - BeginTabBar() for right-most edge\n        const bool allow_scrollbar_x = !(flags & ImGuiWindowFlags_NoScrollbar) && (flags & ImGuiWindowFlags_HorizontalScrollbar);\n        const bool allow_scrollbar_y = !(flags & ImGuiWindowFlags_NoScrollbar);\n        const float work_rect_size_x = (window->ContentSizeExplicit.x != 0.0f ? window->ContentSizeExplicit.x : ImMax(allow_scrollbar_x ? window->ContentSize.x : 0.0f, window->Size.x - window->WindowPadding.x * 2.0f - (window->DecoOuterSizeX1 + window->DecoOuterSizeX2)));\n        const float work_rect_size_y = (window->ContentSizeExplicit.y != 0.0f ? window->ContentSizeExplicit.y : ImMax(allow_scrollbar_y ? window->ContentSize.y : 0.0f, window->Size.y - window->WindowPadding.y * 2.0f - (window->DecoOuterSizeY1 + window->DecoOuterSizeY2)));\n        window->WorkRect.Min.x = ImTrunc(window->InnerRect.Min.x - window->Scroll.x + ImMax(window->WindowPadding.x, window->WindowBorderSize));\n        window->WorkRect.Min.y = ImTrunc(window->InnerRect.Min.y - window->Scroll.y + ImMax(window->WindowPadding.y, window->WindowBorderSize));\n        window->WorkRect.Max.x = window->WorkRect.Min.x + work_rect_size_x;\n        window->WorkRect.Max.y = window->WorkRect.Min.y + work_rect_size_y;\n        window->ParentWorkRect = window->WorkRect;\n\n        // [LEGACY] Content Region\n        // FIXME-OBSOLETE: window->ContentRegionRect.Max is currently very misleading / partly faulty, but some BeginChild() patterns relies on it.\n        // Unless explicit content size is specified by user, this currently represent the region leading to no scrolling.\n        // Used by:\n        // - Mouse wheel scrolling + many other things\n        window->ContentRegionRect.Min.x = window->Pos.x - window->Scroll.x + window->WindowPadding.x + window->DecoOuterSizeX1;\n        window->ContentRegionRect.Min.y = window->Pos.y - window->Scroll.y + window->WindowPadding.y + window->DecoOuterSizeY1;\n        window->ContentRegionRect.Max.x = window->ContentRegionRect.Min.x + (window->ContentSizeExplicit.x != 0.0f ? window->ContentSizeExplicit.x : (window->Size.x - window->WindowPadding.x * 2.0f - (window->DecoOuterSizeX1 + window->DecoOuterSizeX2)));\n        window->ContentRegionRect.Max.y = window->ContentRegionRect.Min.y + (window->ContentSizeExplicit.y != 0.0f ? window->ContentSizeExplicit.y : (window->Size.y - window->WindowPadding.y * 2.0f - (window->DecoOuterSizeY1 + window->DecoOuterSizeY2)));\n\n        // Setup drawing context\n        // (NB: That term \"drawing context / DC\" lost its meaning a long time ago. Initially was meant to hold transient data only. Nowadays difference between window-> and window->DC-> is dubious.)\n        window->DC.Indent.x = window->DecoOuterSizeX1 + window->WindowPadding.x - window->Scroll.x;\n        window->DC.GroupOffset.x = 0.0f;\n        window->DC.ColumnsOffset.x = 0.0f;\n\n        // Record the loss of precision of CursorStartPos which can happen due to really large scrolling amount.\n        // This is used by clipper to compensate and fix the most common use case of large scroll area. Easy and cheap, next best thing compared to switching everything to double or ImU64.\n        double start_pos_highp_x = (double)window->Pos.x + window->WindowPadding.x - (double)window->Scroll.x + window->DecoOuterSizeX1 + window->DC.ColumnsOffset.x;\n        double start_pos_highp_y = (double)window->Pos.y + window->WindowPadding.y - (double)window->Scroll.y + window->DecoOuterSizeY1;\n        window->DC.CursorStartPos  = ImVec2((float)start_pos_highp_x, (float)start_pos_highp_y);\n        window->DC.CursorStartPosLossyness = ImVec2((float)(start_pos_highp_x - window->DC.CursorStartPos.x), (float)(start_pos_highp_y - window->DC.CursorStartPos.y));\n        window->DC.CursorPos = window->DC.CursorStartPos;\n        window->DC.CursorPosPrevLine = window->DC.CursorPos;\n        window->DC.CursorMaxPos = window->DC.CursorStartPos;\n        window->DC.IdealMaxPos = window->DC.CursorStartPos;\n        window->DC.CurrLineSize = window->DC.PrevLineSize = ImVec2(0.0f, 0.0f);\n        window->DC.CurrLineTextBaseOffset = window->DC.PrevLineTextBaseOffset = 0.0f;\n        window->DC.IsSameLine = window->DC.IsSetPos = false;\n\n        window->DC.NavLayerCurrent = ImGuiNavLayer_Main;\n        window->DC.NavLayersActiveMask = window->DC.NavLayersActiveMaskNext;\n        window->DC.NavLayersActiveMaskNext = 0x00;\n        window->DC.NavIsScrollPushableX = true;\n        window->DC.NavHideHighlightOneFrame = false;\n        window->DC.NavWindowHasScrollY = (window->ScrollMax.y > 0.0f);\n\n        window->DC.MenuBarAppending = false;\n        window->DC.MenuColumns.Update(style.ItemSpacing.x, window_just_activated_by_user);\n        window->DC.TreeDepth = 0;\n        window->DC.TreeHasStackDataDepthMask = 0x00;\n        window->DC.ChildWindows.resize(0);\n        window->DC.StateStorage = &window->StateStorage;\n        window->DC.CurrentColumns = NULL;\n        window->DC.LayoutType = ImGuiLayoutType_Vertical;\n        window->DC.ParentLayoutType = parent_window ? parent_window->DC.LayoutType : ImGuiLayoutType_Vertical;\n\n        window->DC.ItemWidth = window->ItemWidthDefault;\n        window->DC.TextWrapPos = -1.0f; // disabled\n        window->DC.ItemWidthStack.resize(0);\n        window->DC.TextWrapPosStack.resize(0);\n        if (flags & ImGuiWindowFlags_Modal)\n            window->DC.ModalDimBgColor = ColorConvertFloat4ToU32(GetStyleColorVec4(ImGuiCol_ModalWindowDimBg));\n\n        if (window->AutoFitFramesX > 0)\n            window->AutoFitFramesX--;\n        if (window->AutoFitFramesY > 0)\n            window->AutoFitFramesY--;\n\n        // Apply focus (we need to call FocusWindow() AFTER setting DC.CursorStartPos so our initial navigation reference rectangle can start around there)\n        // We ImGuiFocusRequestFlags_UnlessBelowModal to:\n        // - Avoid focusing a window that is created outside of a modal. This will prevent active modal from being closed.\n        // - Position window behind the modal that is not a begin-parent of this window.\n        if (want_focus)\n            FocusWindow(window, ImGuiFocusRequestFlags_UnlessBelowModal);\n        if (want_focus && window == g.NavWindow)\n            NavInitWindow(window, false); // <-- this is in the way for us to be able to defer and sort reappearing FocusWindow() calls\n\n        // Pressing CTRL+C copy window content into the clipboard\n        // [EXPERIMENTAL] Breaks on nested Begin/End pairs. We need to work that out and add better logging scope.\n        // [EXPERIMENTAL] Text outputs has many issues.\n        if (g.IO.ConfigWindowsCopyContentsWithCtrlC)\n            if (g.NavWindow && g.NavWindow->RootWindow == window && g.ActiveId == 0 && Shortcut(ImGuiMod_Ctrl | ImGuiKey_C))\n                LogToClipboard(0);\n\n        // Title bar\n        if (!(flags & ImGuiWindowFlags_NoTitleBar))\n            RenderWindowTitleBarContents(window, ImRect(title_bar_rect.Min.x + window->WindowBorderSize, title_bar_rect.Min.y, title_bar_rect.Max.x - window->WindowBorderSize, title_bar_rect.Max.y), name, p_open);\n\n        // Clear hit test shape every frame\n        window->HitTestHoleSize.x = window->HitTestHoleSize.y = 0;\n\n        if (flags & ImGuiWindowFlags_Tooltip)\n            g.TooltipPreviousWindow = window;\n\n        // We fill last item data based on Title Bar/Tab, in order for IsItemHovered() and IsItemActive() to be usable after Begin().\n        // This is useful to allow creating context menus on title bar only, etc.\n        window->DC.WindowItemStatusFlags = ImGuiItemStatusFlags_None;\n        window->DC.WindowItemStatusFlags |= IsMouseHoveringRect(title_bar_rect.Min, title_bar_rect.Max, false) ? ImGuiItemStatusFlags_HoveredRect : 0;\n        SetLastItemDataForWindow(window, title_bar_rect);\n\n        // [DEBUG]\n#ifndef IMGUI_DISABLE_DEBUG_TOOLS\n        if (g.DebugLocateId != 0 && (window->ID == g.DebugLocateId || window->MoveId == g.DebugLocateId))\n            DebugLocateItemResolveWithLastItem();\n#endif\n\n        // [Test Engine] Register title bar / tab with MoveId.\n#ifdef IMGUI_ENABLE_TEST_ENGINE\n        if (!(window->Flags & ImGuiWindowFlags_NoTitleBar))\n        {\n            window->DC.NavLayerCurrent = ImGuiNavLayer_Menu;\n            IMGUI_TEST_ENGINE_ITEM_ADD(g.LastItemData.ID, g.LastItemData.Rect, &g.LastItemData);\n            window->DC.NavLayerCurrent = ImGuiNavLayer_Main;\n        }\n#endif\n    }\n    else\n    {\n        // Skip refresh always mark active\n        if (window->SkipRefresh)\n            SetWindowActiveForSkipRefresh(window);\n\n        // Append\n        SetCurrentWindow(window);\n        SetLastItemDataForWindow(window, window->TitleBarRect());\n    }\n\n    if (!window->SkipRefresh)\n        PushClipRect(window->InnerClipRect.Min, window->InnerClipRect.Max, true);\n\n    // Clear 'accessed' flag last thing (After PushClipRect which will set the flag. We want the flag to stay false when the default \"Debug\" window is unused)\n    window->WriteAccessed = false;\n    window->BeginCount++;\n    g.NextWindowData.ClearFlags();\n\n    // Update visibility\n    if (first_begin_of_the_frame && !window->SkipRefresh)\n    {\n        if ((flags & ImGuiWindowFlags_ChildWindow) && !(flags & ImGuiWindowFlags_ChildMenu))\n        {\n            // Child window can be out of sight and have \"negative\" clip windows.\n            // Mark them as collapsed so commands are skipped earlier (we can't manually collapse them because they have no title bar).\n            IM_ASSERT((flags & ImGuiWindowFlags_NoTitleBar) != 0);\n            const bool nav_request = (window->ChildFlags & ImGuiChildFlags_NavFlattened) && (g.NavAnyRequest && g.NavWindow && g.NavWindow->RootWindowForNav == window->RootWindowForNav);\n            if (!g.LogEnabled && !nav_request)\n                if (window->OuterRectClipped.Min.x >= window->OuterRectClipped.Max.x || window->OuterRectClipped.Min.y >= window->OuterRectClipped.Max.y)\n                {\n                    if (window->AutoFitFramesX > 0 || window->AutoFitFramesY > 0)\n                        window->HiddenFramesCannotSkipItems = 1;\n                    else\n                        window->HiddenFramesCanSkipItems = 1;\n                }\n\n            // Hide along with parent or if parent is collapsed\n            if (parent_window && (parent_window->Collapsed || parent_window->HiddenFramesCanSkipItems > 0))\n                window->HiddenFramesCanSkipItems = 1;\n            if (parent_window && parent_window->HiddenFramesCannotSkipItems > 0)\n                window->HiddenFramesCannotSkipItems = 1;\n        }\n\n        // Don't render if style alpha is 0.0 at the time of Begin(). This is arbitrary and inconsistent but has been there for a long while (may remove at some point)\n        if (style.Alpha <= 0.0f)\n            window->HiddenFramesCanSkipItems = 1;\n\n        // Update the Hidden flag\n        bool hidden_regular = (window->HiddenFramesCanSkipItems > 0) || (window->HiddenFramesCannotSkipItems > 0);\n        window->Hidden = hidden_regular || (window->HiddenFramesForRenderOnly > 0);\n\n        // Disable inputs for requested number of frames\n        if (window->DisableInputsFrames > 0)\n        {\n            window->DisableInputsFrames--;\n            window->Flags |= ImGuiWindowFlags_NoInputs;\n        }\n\n        // Update the SkipItems flag, used to early out of all items functions (no layout required)\n        bool skip_items = false;\n        if (window->Collapsed || !window->Active || hidden_regular)\n            if (window->AutoFitFramesX <= 0 && window->AutoFitFramesY <= 0 && window->HiddenFramesCannotSkipItems <= 0)\n                skip_items = true;\n        window->SkipItems = skip_items;\n    }\n    else if (first_begin_of_the_frame)\n    {\n        // Skip refresh mode\n        window->SkipItems = true;\n    }\n\n    // [DEBUG] io.ConfigDebugBeginReturnValue override return value to test Begin/End and BeginChild/EndChild behaviors.\n    // (The implicit fallback window is NOT automatically ended allowing it to always be able to receive commands without crashing)\n#ifndef IMGUI_DISABLE_DEBUG_TOOLS\n    if (!window->IsFallbackWindow)\n        if ((g.IO.ConfigDebugBeginReturnValueOnce && window_just_created) || (g.IO.ConfigDebugBeginReturnValueLoop && g.DebugBeginReturnValueCullDepth == g.CurrentWindowStack.Size))\n        {\n            if (window->AutoFitFramesX > 0) { window->AutoFitFramesX++; }\n            if (window->AutoFitFramesY > 0) { window->AutoFitFramesY++; }\n            return false;\n        }\n#endif\n\n    return !window->SkipItems;\n}\n\nvoid ImGui::End()\n{\n    ImGuiContext& g = *GImGui;\n    ImGuiWindow* window = g.CurrentWindow;\n\n    // Error checking: verify that user hasn't called End() too many times!\n    if (g.CurrentWindowStack.Size <= 1 && g.WithinFrameScopeWithImplicitWindow)\n    {\n        IM_ASSERT_USER_ERROR(g.CurrentWindowStack.Size > 1, \"Calling End() too many times!\");\n        return;\n    }\n    ImGuiWindowStackData& window_stack_data = g.CurrentWindowStack.back();\n\n    // Error checking: verify that user doesn't directly call End() on a child window.\n    if (window->Flags & ImGuiWindowFlags_ChildWindow)\n        IM_ASSERT_USER_ERROR(g.WithinEndChildID == window->ID, \"Must call EndChild() and not End()!\");\n\n    // Close anything that is open\n    if (window->DC.CurrentColumns)\n        EndColumns();\n    if (!window->SkipRefresh)\n        PopClipRect();   // Inner window clip rectangle\n    PopFocusScope();\n    if (window_stack_data.DisabledOverrideReenable && window->RootWindow == window)\n        EndDisabledOverrideReenable();\n\n    if (window->SkipRefresh)\n    {\n        IM_ASSERT(window->DrawList == NULL);\n        window->DrawList = &window->DrawListInst;\n    }\n\n    // Stop logging\n    if (g.LogWindow == window) // FIXME: add more options for scope of logging\n        LogFinish();\n\n    if (window->DC.IsSetPos)\n        ErrorCheckUsingSetCursorPosToExtendParentBoundaries();\n\n    // Pop from window stack\n    g.LastItemData = window_stack_data.ParentLastItemDataBackup;\n    if (window->Flags & ImGuiWindowFlags_ChildMenu)\n        g.BeginMenuDepth--;\n    if (window->Flags & ImGuiWindowFlags_Popup)\n        g.BeginPopupStack.pop_back();\n\n    // Error handling, state recovery\n    if (g.IO.ConfigErrorRecovery)\n        ErrorRecoveryTryToRecoverWindowState(&window_stack_data.StackSizesInBegin);\n\n    g.CurrentWindowStack.pop_back();\n    SetCurrentWindow(g.CurrentWindowStack.Size == 0 ? NULL : g.CurrentWindowStack.back().Window);\n}\n\n// Important: this alone doesn't alter current ImDrawList state. This is called by PushFont/PopFont only.\nvoid ImGui::SetCurrentFont(ImFont* font)\n{\n    ImGuiContext& g = *GImGui;\n    IM_ASSERT(font && font->IsLoaded());    // Font Atlas not created. Did you call io.Fonts->GetTexDataAsRGBA32 / GetTexDataAsAlpha8 ?\n    IM_ASSERT(font->Scale > 0.0f);\n    g.Font = font;\n    g.FontBaseSize = ImMax(1.0f, g.IO.FontGlobalScale * g.Font->FontSize * g.Font->Scale);\n    g.FontSize = g.CurrentWindow ? g.CurrentWindow->CalcFontSize() : 0.0f;\n    g.FontScale = g.FontSize / g.Font->FontSize;\n\n    ImFontAtlas* atlas = g.Font->ContainerAtlas;\n    g.DrawListSharedData.TexUvWhitePixel = atlas->TexUvWhitePixel;\n    g.DrawListSharedData.TexUvLines = atlas->TexUvLines;\n    g.DrawListSharedData.Font = g.Font;\n    g.DrawListSharedData.FontSize = g.FontSize;\n    g.DrawListSharedData.FontScale = g.FontScale;\n}\n\n// Use ImDrawList::_SetTextureID(), making our shared g.FontStack[] authorative against window-local ImDrawList.\n// - Whereas ImDrawList::PushTextureID()/PopTextureID() is not to be used across Begin() calls.\n// - Note that we don't propagate current texture id when e.g. Begin()-ing into a new window, we never really did...\n//   - Some code paths never really fully worked with multiple atlas textures.\n//   - The right-ish solution may be to remove _SetTextureID() and make AddText/RenderText lazily call PushTextureID()/PopTextureID()\n//     the same way AddImage() does, but then all other primitives would also need to? I don't think we should tackle this problem\n//     because we have a concrete need and a test bed for multiple atlas textures.\nvoid ImGui::PushFont(ImFont* font)\n{\n    ImGuiContext& g = *GImGui;\n    if (font == NULL)\n        font = GetDefaultFont();\n    g.FontStack.push_back(font);\n    SetCurrentFont(font);\n    g.CurrentWindow->DrawList->_SetTextureID(font->ContainerAtlas->TexID);\n}\n\nvoid  ImGui::PopFont()\n{\n    ImGuiContext& g = *GImGui;\n    if (g.FontStack.Size <= 0)\n    {\n        IM_ASSERT_USER_ERROR(0, \"Calling PopFont() too many times!\");\n        return;\n    }\n    g.FontStack.pop_back();\n    ImFont* font = g.FontStack.Size == 0 ? GetDefaultFont() : g.FontStack.back();\n    SetCurrentFont(font);\n    g.CurrentWindow->DrawList->_SetTextureID(font->ContainerAtlas->TexID);\n}\n\nvoid ImGui::PushItemFlag(ImGuiItemFlags option, bool enabled)\n{\n    ImGuiContext& g = *GImGui;\n    ImGuiItemFlags item_flags = g.CurrentItemFlags;\n    IM_ASSERT(item_flags == g.ItemFlagsStack.back());\n    if (enabled)\n        item_flags |= option;\n    else\n        item_flags &= ~option;\n    g.CurrentItemFlags = item_flags;\n    g.ItemFlagsStack.push_back(item_flags);\n}\n\nvoid ImGui::PopItemFlag()\n{\n    ImGuiContext& g = *GImGui;\n    if (g.ItemFlagsStack.Size <= 1)\n    {\n        IM_ASSERT_USER_ERROR(0, \"Calling PopItemFlag() too many times!\");\n        return;\n    }\n    g.ItemFlagsStack.pop_back();\n    g.CurrentItemFlags = g.ItemFlagsStack.back();\n}\n\n// BeginDisabled()/EndDisabled()\n// - Those can be nested but it cannot be used to enable an already disabled section (a single BeginDisabled(true) in the stack is enough to keep everything disabled)\n// - Visually this is currently altering alpha, but it is expected that in a future styling system this would work differently.\n// - Feedback welcome at https://github.com/ocornut/imgui/issues/211\n// - BeginDisabled(false)/EndDisabled() essentially does nothing but is provided to facilitate use of boolean expressions.\n//   (as a micro-optimization: if you have tens of thousands of BeginDisabled(false)/EndDisabled() pairs, you might want to reformulate your code to avoid making those calls)\n// - Note: mixing up BeginDisabled() and PushItemFlag(ImGuiItemFlags_Disabled) is currently NOT SUPPORTED.\nvoid ImGui::BeginDisabled(bool disabled)\n{\n    ImGuiContext& g = *GImGui;\n    bool was_disabled = (g.CurrentItemFlags & ImGuiItemFlags_Disabled) != 0;\n    if (!was_disabled && disabled)\n    {\n        g.DisabledAlphaBackup = g.Style.Alpha;\n        g.Style.Alpha *= g.Style.DisabledAlpha; // PushStyleVar(ImGuiStyleVar_Alpha, g.Style.Alpha * g.Style.DisabledAlpha);\n    }\n    if (was_disabled || disabled)\n        g.CurrentItemFlags |= ImGuiItemFlags_Disabled;\n    g.ItemFlagsStack.push_back(g.CurrentItemFlags); // FIXME-OPT: can we simply skip this and use DisabledStackSize?\n    g.DisabledStackSize++;\n}\n\nvoid ImGui::EndDisabled()\n{\n    ImGuiContext& g = *GImGui;\n    if (g.DisabledStackSize <= 0)\n    {\n        IM_ASSERT_USER_ERROR(0, \"Calling EndDisabled() too many times!\");\n        return;\n    }\n    g.DisabledStackSize--;\n    bool was_disabled = (g.CurrentItemFlags & ImGuiItemFlags_Disabled) != 0;\n    //PopItemFlag();\n    g.ItemFlagsStack.pop_back();\n    g.CurrentItemFlags = g.ItemFlagsStack.back();\n    if (was_disabled && (g.CurrentItemFlags & ImGuiItemFlags_Disabled) == 0)\n        g.Style.Alpha = g.DisabledAlphaBackup; //PopStyleVar();\n}\n\n// Could have been called BeginDisabledDisable() but it didn't want to be award nominated for most awkward function name.\n// Ideally we would use a shared e.g. BeginDisabled()->BeginDisabledEx() but earlier needs to be optimal.\n// The whole code for this is awkward, will reevaluate if we find a way to implement SetNextItemDisabled().\nvoid ImGui::BeginDisabledOverrideReenable()\n{\n    ImGuiContext& g = *GImGui;\n    IM_ASSERT(g.CurrentItemFlags & ImGuiItemFlags_Disabled);\n    g.CurrentWindowStack.back().DisabledOverrideReenableAlphaBackup = g.Style.Alpha;\n    g.Style.Alpha = g.DisabledAlphaBackup;\n    g.CurrentItemFlags &= ~ImGuiItemFlags_Disabled;\n    g.ItemFlagsStack.push_back(g.CurrentItemFlags);\n    g.DisabledStackSize++;\n}\n\nvoid ImGui::EndDisabledOverrideReenable()\n{\n    ImGuiContext& g = *GImGui;\n    g.DisabledStackSize--;\n    IM_ASSERT(g.DisabledStackSize > 0);\n    g.ItemFlagsStack.pop_back();\n    g.CurrentItemFlags = g.ItemFlagsStack.back();\n    g.Style.Alpha = g.CurrentWindowStack.back().DisabledOverrideReenableAlphaBackup;\n}\n\nvoid ImGui::PushTextWrapPos(float wrap_pos_x)\n{\n    ImGuiContext& g = *GImGui;\n    ImGuiWindow* window = g.CurrentWindow;\n    window->DC.TextWrapPosStack.push_back(window->DC.TextWrapPos);\n    window->DC.TextWrapPos = wrap_pos_x;\n}\n\nvoid ImGui::PopTextWrapPos()\n{\n    ImGuiContext& g = *GImGui;\n    ImGuiWindow* window = g.CurrentWindow;\n    if (window->DC.TextWrapPosStack.Size <= 0)\n    {\n        IM_ASSERT_USER_ERROR(0, \"Calling PopTextWrapPos() too many times!\");\n        return;\n    }\n    window->DC.TextWrapPos = window->DC.TextWrapPosStack.back();\n    window->DC.TextWrapPosStack.pop_back();\n}\n\nstatic ImGuiWindow* GetCombinedRootWindow(ImGuiWindow* window, bool popup_hierarchy)\n{\n    ImGuiWindow* last_window = NULL;\n    while (last_window != window)\n    {\n        last_window = window;\n        window = window->RootWindow;\n        if (popup_hierarchy)\n            window = window->RootWindowPopupTree;\n    }\n    return window;\n}\n\nbool ImGui::IsWindowChildOf(ImGuiWindow* window, ImGuiWindow* potential_parent, bool popup_hierarchy)\n{\n    ImGuiWindow* window_root = GetCombinedRootWindow(window, popup_hierarchy);\n    if (window_root == potential_parent)\n        return true;\n    while (window != NULL)\n    {\n        if (window == potential_parent)\n            return true;\n        if (window == window_root) // end of chain\n            return false;\n        window = window->ParentWindow;\n    }\n    return false;\n}\n\nbool ImGui::IsWindowWithinBeginStackOf(ImGuiWindow* window, ImGuiWindow* potential_parent)\n{\n    if (window->RootWindow == potential_parent)\n        return true;\n    while (window != NULL)\n    {\n        if (window == potential_parent)\n            return true;\n        window = window->ParentWindowInBeginStack;\n    }\n    return false;\n}\n\nbool ImGui::IsWindowAbove(ImGuiWindow* potential_above, ImGuiWindow* potential_below)\n{\n    ImGuiContext& g = *GImGui;\n\n    // It would be saner to ensure that display layer is always reflected in the g.Windows[] order, which would likely requires altering all manipulations of that array\n    const int display_layer_delta = GetWindowDisplayLayer(potential_above) - GetWindowDisplayLayer(potential_below);\n    if (display_layer_delta != 0)\n        return display_layer_delta > 0;\n\n    for (int i = g.Windows.Size - 1; i >= 0; i--)\n    {\n        ImGuiWindow* candidate_window = g.Windows[i];\n        if (candidate_window == potential_above)\n            return true;\n        if (candidate_window == potential_below)\n            return false;\n    }\n    return false;\n}\n\n// Is current window hovered and hoverable (e.g. not blocked by a popup/modal)? See ImGuiHoveredFlags_ for options.\n// IMPORTANT: If you are trying to check whether your mouse should be dispatched to Dear ImGui or to your underlying app,\n// you should not use this function! Use the 'io.WantCaptureMouse' boolean for that!\n// Refer to FAQ entry \"How can I tell whether to dispatch mouse/keyboard to Dear ImGui or my application?\" for details.\nbool ImGui::IsWindowHovered(ImGuiHoveredFlags flags)\n{\n    ImGuiContext& g = *GImGui;\n    IM_ASSERT_USER_ERROR((flags & ~ImGuiHoveredFlags_AllowedMaskForIsWindowHovered) == 0, \"Invalid flags for IsWindowHovered()!\");\n\n    ImGuiWindow* ref_window = g.HoveredWindow;\n    ImGuiWindow* cur_window = g.CurrentWindow;\n    if (ref_window == NULL)\n        return false;\n\n    if ((flags & ImGuiHoveredFlags_AnyWindow) == 0)\n    {\n        IM_ASSERT(cur_window); // Not inside a Begin()/End()\n        const bool popup_hierarchy = (flags & ImGuiHoveredFlags_NoPopupHierarchy) == 0;\n        if (flags & ImGuiHoveredFlags_RootWindow)\n            cur_window = GetCombinedRootWindow(cur_window, popup_hierarchy);\n\n        bool result;\n        if (flags & ImGuiHoveredFlags_ChildWindows)\n            result = IsWindowChildOf(ref_window, cur_window, popup_hierarchy);\n        else\n            result = (ref_window == cur_window);\n        if (!result)\n            return false;\n    }\n\n    if (!IsWindowContentHoverable(ref_window, flags))\n        return false;\n    if (!(flags & ImGuiHoveredFlags_AllowWhenBlockedByActiveItem))\n        if (g.ActiveId != 0 && !g.ActiveIdAllowOverlap && g.ActiveId != ref_window->MoveId)\n            return false;\n\n    // When changing hovered window we requires a bit of stationary delay before activating hover timer.\n    // FIXME: We don't support delay other than stationary one for now, other delay would need a way\n    // to fulfill the possibility that multiple IsWindowHovered() with varying flag could return true\n    // for different windows of the hierarchy. Possibly need a Hash(Current+Flags) ==> (Timer) cache.\n    // We can implement this for _Stationary because the data is linked to HoveredWindow rather than CurrentWindow.\n    if (flags & ImGuiHoveredFlags_ForTooltip)\n        flags = ApplyHoverFlagsForTooltip(flags, g.Style.HoverFlagsForTooltipMouse);\n    if ((flags & ImGuiHoveredFlags_Stationary) != 0 && g.HoverWindowUnlockedStationaryId != ref_window->ID)\n        return false;\n\n    return true;\n}\n\nfloat ImGui::GetWindowWidth()\n{\n    ImGuiWindow* window = GImGui->CurrentWindow;\n    return window->Size.x;\n}\n\nfloat ImGui::GetWindowHeight()\n{\n    ImGuiWindow* window = GImGui->CurrentWindow;\n    return window->Size.y;\n}\n\nImVec2 ImGui::GetWindowPos()\n{\n    ImGuiContext& g = *GImGui;\n    ImGuiWindow* window = g.CurrentWindow;\n    return window->Pos;\n}\n\nvoid ImGui::SetWindowPos(ImGuiWindow* window, const ImVec2& pos, ImGuiCond cond)\n{\n    // Test condition (NB: bit 0 is always true) and clear flags for next time\n    if (cond && (window->SetWindowPosAllowFlags & cond) == 0)\n        return;\n\n    IM_ASSERT(cond == 0 || ImIsPowerOfTwo(cond)); // Make sure the user doesn't attempt to combine multiple condition flags.\n    window->SetWindowPosAllowFlags &= ~(ImGuiCond_Once | ImGuiCond_FirstUseEver | ImGuiCond_Appearing);\n    window->SetWindowPosVal = ImVec2(FLT_MAX, FLT_MAX);\n\n    // Set\n    const ImVec2 old_pos = window->Pos;\n    window->Pos = ImTrunc(pos);\n    ImVec2 offset = window->Pos - old_pos;\n    if (offset.x == 0.0f && offset.y == 0.0f)\n        return;\n    MarkIniSettingsDirty(window);\n    window->DC.CursorPos += offset;         // As we happen to move the window while it is being appended to (which is a bad idea - will smear) let's at least offset the cursor\n    window->DC.CursorMaxPos += offset;      // And more importantly we need to offset CursorMaxPos/CursorStartPos this so ContentSize calculation doesn't get affected.\n    window->DC.IdealMaxPos += offset;\n    window->DC.CursorStartPos += offset;\n}\n\nvoid ImGui::SetWindowPos(const ImVec2& pos, ImGuiCond cond)\n{\n    ImGuiWindow* window = GetCurrentWindowRead();\n    SetWindowPos(window, pos, cond);\n}\n\nvoid ImGui::SetWindowPos(const char* name, const ImVec2& pos, ImGuiCond cond)\n{\n    if (ImGuiWindow* window = FindWindowByName(name))\n        SetWindowPos(window, pos, cond);\n}\n\nImVec2 ImGui::GetWindowSize()\n{\n    ImGuiWindow* window = GetCurrentWindowRead();\n    return window->Size;\n}\n\nvoid ImGui::SetWindowSize(ImGuiWindow* window, const ImVec2& size, ImGuiCond cond)\n{\n    // Test condition (NB: bit 0 is always true) and clear flags for next time\n    if (cond && (window->SetWindowSizeAllowFlags & cond) == 0)\n        return;\n\n    IM_ASSERT(cond == 0 || ImIsPowerOfTwo(cond)); // Make sure the user doesn't attempt to combine multiple condition flags.\n    window->SetWindowSizeAllowFlags &= ~(ImGuiCond_Once | ImGuiCond_FirstUseEver | ImGuiCond_Appearing);\n\n    // Enable auto-fit (not done in BeginChild() path unless appearing or combined with ImGuiChildFlags_AlwaysAutoResize)\n    if ((window->Flags & ImGuiWindowFlags_ChildWindow) == 0 || window->Appearing || (window->ChildFlags & ImGuiChildFlags_AlwaysAutoResize) != 0)\n        window->AutoFitFramesX = (size.x <= 0.0f) ? 2 : 0;\n    if ((window->Flags & ImGuiWindowFlags_ChildWindow) == 0 || window->Appearing || (window->ChildFlags & ImGuiChildFlags_AlwaysAutoResize) != 0)\n        window->AutoFitFramesY = (size.y <= 0.0f) ? 2 : 0;\n\n    // Set\n    ImVec2 old_size = window->SizeFull;\n    if (size.x <= 0.0f)\n        window->AutoFitOnlyGrows = false;\n    else\n        window->SizeFull.x = IM_TRUNC(size.x);\n    if (size.y <= 0.0f)\n        window->AutoFitOnlyGrows = false;\n    else\n        window->SizeFull.y = IM_TRUNC(size.y);\n    if (old_size.x != window->SizeFull.x || old_size.y != window->SizeFull.y)\n        MarkIniSettingsDirty(window);\n}\n\nvoid ImGui::SetWindowSize(const ImVec2& size, ImGuiCond cond)\n{\n    SetWindowSize(GImGui->CurrentWindow, size, cond);\n}\n\nvoid ImGui::SetWindowSize(const char* name, const ImVec2& size, ImGuiCond cond)\n{\n    if (ImGuiWindow* window = FindWindowByName(name))\n        SetWindowSize(window, size, cond);\n}\n\nvoid ImGui::SetWindowCollapsed(ImGuiWindow* window, bool collapsed, ImGuiCond cond)\n{\n    // Test condition (NB: bit 0 is always true) and clear flags for next time\n    if (cond && (window->SetWindowCollapsedAllowFlags & cond) == 0)\n        return;\n    window->SetWindowCollapsedAllowFlags &= ~(ImGuiCond_Once | ImGuiCond_FirstUseEver | ImGuiCond_Appearing);\n\n    // Set\n    window->Collapsed = collapsed;\n}\n\nvoid ImGui::SetWindowHitTestHole(ImGuiWindow* window, const ImVec2& pos, const ImVec2& size)\n{\n    IM_ASSERT(window->HitTestHoleSize.x == 0);     // We don't support multiple holes/hit test filters\n    window->HitTestHoleSize = ImVec2ih(size);\n    window->HitTestHoleOffset = ImVec2ih(pos - window->Pos);\n}\n\nvoid ImGui::SetWindowHiddenAndSkipItemsForCurrentFrame(ImGuiWindow* window)\n{\n    window->Hidden = window->SkipItems = true;\n    window->HiddenFramesCanSkipItems = 1;\n}\n\nvoid ImGui::SetWindowCollapsed(bool collapsed, ImGuiCond cond)\n{\n    SetWindowCollapsed(GImGui->CurrentWindow, collapsed, cond);\n}\n\nbool ImGui::IsWindowCollapsed()\n{\n    ImGuiWindow* window = GetCurrentWindowRead();\n    return window->Collapsed;\n}\n\nbool ImGui::IsWindowAppearing()\n{\n    ImGuiWindow* window = GetCurrentWindowRead();\n    return window->Appearing;\n}\n\nvoid ImGui::SetWindowCollapsed(const char* name, bool collapsed, ImGuiCond cond)\n{\n    if (ImGuiWindow* window = FindWindowByName(name))\n        SetWindowCollapsed(window, collapsed, cond);\n}\n\nvoid ImGui::SetNextWindowPos(const ImVec2& pos, ImGuiCond cond, const ImVec2& pivot)\n{\n    ImGuiContext& g = *GImGui;\n    IM_ASSERT(cond == 0 || ImIsPowerOfTwo(cond)); // Make sure the user doesn't attempt to combine multiple condition flags.\n    g.NextWindowData.HasFlags |= ImGuiNextWindowDataFlags_HasPos;\n    g.NextWindowData.PosVal = pos;\n    g.NextWindowData.PosPivotVal = pivot;\n    g.NextWindowData.PosCond = cond ? cond : ImGuiCond_Always;\n}\n\nvoid ImGui::SetNextWindowSize(const ImVec2& size, ImGuiCond cond)\n{\n    ImGuiContext& g = *GImGui;\n    IM_ASSERT(cond == 0 || ImIsPowerOfTwo(cond)); // Make sure the user doesn't attempt to combine multiple condition flags.\n    g.NextWindowData.HasFlags |= ImGuiNextWindowDataFlags_HasSize;\n    g.NextWindowData.SizeVal = size;\n    g.NextWindowData.SizeCond = cond ? cond : ImGuiCond_Always;\n}\n\n// For each axis:\n// - Use 0.0f as min or FLT_MAX as max if you don't want limits, e.g. size_min = (500.0f, 0.0f), size_max = (FLT_MAX, FLT_MAX) sets a minimum width.\n// - Use -1 for both min and max of same axis to preserve current size which itself is a constraint.\n// - See \"Demo->Examples->Constrained-resizing window\" for examples.\nvoid ImGui::SetNextWindowSizeConstraints(const ImVec2& size_min, const ImVec2& size_max, ImGuiSizeCallback custom_callback, void* custom_callback_user_data)\n{\n    ImGuiContext& g = *GImGui;\n    g.NextWindowData.HasFlags |= ImGuiNextWindowDataFlags_HasSizeConstraint;\n    g.NextWindowData.SizeConstraintRect = ImRect(size_min, size_max);\n    g.NextWindowData.SizeCallback = custom_callback;\n    g.NextWindowData.SizeCallbackUserData = custom_callback_user_data;\n}\n\n// Content size = inner scrollable rectangle, padded with WindowPadding.\n// SetNextWindowContentSize(ImVec2(100,100) + ImGuiWindowFlags_AlwaysAutoResize will always allow submitting a 100x100 item.\nvoid ImGui::SetNextWindowContentSize(const ImVec2& size)\n{\n    ImGuiContext& g = *GImGui;\n    g.NextWindowData.HasFlags |= ImGuiNextWindowDataFlags_HasContentSize;\n    g.NextWindowData.ContentSizeVal = ImTrunc(size);\n}\n\nvoid ImGui::SetNextWindowScroll(const ImVec2& scroll)\n{\n    ImGuiContext& g = *GImGui;\n    g.NextWindowData.HasFlags |= ImGuiNextWindowDataFlags_HasScroll;\n    g.NextWindowData.ScrollVal = scroll;\n}\n\nvoid ImGui::SetNextWindowCollapsed(bool collapsed, ImGuiCond cond)\n{\n    ImGuiContext& g = *GImGui;\n    IM_ASSERT(cond == 0 || ImIsPowerOfTwo(cond)); // Make sure the user doesn't attempt to combine multiple condition flags.\n    g.NextWindowData.HasFlags |= ImGuiNextWindowDataFlags_HasCollapsed;\n    g.NextWindowData.CollapsedVal = collapsed;\n    g.NextWindowData.CollapsedCond = cond ? cond : ImGuiCond_Always;\n}\n\nvoid ImGui::SetNextWindowBgAlpha(float alpha)\n{\n    ImGuiContext& g = *GImGui;\n    g.NextWindowData.HasFlags |= ImGuiNextWindowDataFlags_HasBgAlpha;\n    g.NextWindowData.BgAlphaVal = alpha;\n}\n\n// This is experimental and meant to be a toy for exploring a future/wider range of features.\nvoid ImGui::SetNextWindowRefreshPolicy(ImGuiWindowRefreshFlags flags)\n{\n    ImGuiContext& g = *GImGui;\n    g.NextWindowData.HasFlags |= ImGuiNextWindowDataFlags_HasRefreshPolicy;\n    g.NextWindowData.RefreshFlagsVal = flags;\n}\n\nImDrawList* ImGui::GetWindowDrawList()\n{\n    ImGuiWindow* window = GetCurrentWindow();\n    return window->DrawList;\n}\n\nImFont* ImGui::GetFont()\n{\n    return GImGui->Font;\n}\n\nfloat ImGui::GetFontSize()\n{\n    return GImGui->FontSize;\n}\n\nImVec2 ImGui::GetFontTexUvWhitePixel()\n{\n    return GImGui->DrawListSharedData.TexUvWhitePixel;\n}\n\nvoid ImGui::SetWindowFontScale(float scale)\n{\n    IM_ASSERT(scale > 0.0f);\n    ImGuiContext& g = *GImGui;\n    ImGuiWindow* window = GetCurrentWindow();\n    window->FontWindowScale = scale;\n    g.FontSize = g.DrawListSharedData.FontSize = window->CalcFontSize();\n    g.FontScale = g.DrawListSharedData.FontScale = g.FontSize / g.Font->FontSize;\n}\n\nvoid ImGui::PushFocusScope(ImGuiID id)\n{\n    ImGuiContext& g = *GImGui;\n    ImGuiFocusScopeData data;\n    data.ID = id;\n    data.WindowID = g.CurrentWindow->ID;\n    g.FocusScopeStack.push_back(data);\n    g.CurrentFocusScopeId = id;\n}\n\nvoid ImGui::PopFocusScope()\n{\n    ImGuiContext& g = *GImGui;\n    if (g.FocusScopeStack.Size <= g.StackSizesInBeginForCurrentWindow->SizeOfFocusScopeStack)\n    {\n        IM_ASSERT_USER_ERROR(0, \"Calling PopFocusScope() too many times!\");\n        return;\n    }\n    g.FocusScopeStack.pop_back();\n    g.CurrentFocusScopeId = g.FocusScopeStack.Size ? g.FocusScopeStack.back().ID : 0;\n}\n\nvoid ImGui::SetNavFocusScope(ImGuiID focus_scope_id)\n{\n    ImGuiContext& g = *GImGui;\n    g.NavFocusScopeId = focus_scope_id;\n    g.NavFocusRoute.resize(0); // Invalidate\n    if (focus_scope_id == 0)\n        return;\n    IM_ASSERT(g.NavWindow != NULL);\n\n    // Store current path (in reverse order)\n    if (focus_scope_id == g.CurrentFocusScopeId)\n    {\n        // Top of focus stack contains local focus scopes inside current window\n        for (int n = g.FocusScopeStack.Size - 1; n >= 0 && g.FocusScopeStack.Data[n].WindowID == g.CurrentWindow->ID; n--)\n            g.NavFocusRoute.push_back(g.FocusScopeStack.Data[n]);\n    }\n    else if (focus_scope_id == g.NavWindow->NavRootFocusScopeId)\n        g.NavFocusRoute.push_back({ focus_scope_id, g.NavWindow->ID });\n    else\n        return;\n\n    // Then follow on manually set ParentWindowForFocusRoute field (#6798)\n    for (ImGuiWindow* window = g.NavWindow->ParentWindowForFocusRoute; window != NULL; window = window->ParentWindowForFocusRoute)\n        g.NavFocusRoute.push_back({ window->NavRootFocusScopeId, window->ID });\n    IM_ASSERT(g.NavFocusRoute.Size < 100); // Maximum depth is technically 251 as per CalcRoutingScore(): 254 - 3\n}\n\n// Focus = move navigation cursor, set scrolling, set focus window.\nvoid ImGui::FocusItem()\n{\n    ImGuiContext& g = *GImGui;\n    ImGuiWindow* window = g.CurrentWindow;\n    IMGUI_DEBUG_LOG_FOCUS(\"FocusItem(0x%08x) in window \\\"%s\\\"\\n\", g.LastItemData.ID, window->Name);\n    if (g.DragDropActive || g.MovingWindow != NULL) // FIXME: Opt-in flags for this?\n    {\n        IMGUI_DEBUG_LOG_FOCUS(\"FocusItem() ignored while DragDropActive!\\n\");\n        return;\n    }\n\n    ImGuiNavMoveFlags move_flags = ImGuiNavMoveFlags_IsTabbing | ImGuiNavMoveFlags_FocusApi | ImGuiNavMoveFlags_NoSetNavCursorVisible | ImGuiNavMoveFlags_NoSelect;\n    ImGuiScrollFlags scroll_flags = window->Appearing ? ImGuiScrollFlags_KeepVisibleEdgeX | ImGuiScrollFlags_AlwaysCenterY : ImGuiScrollFlags_KeepVisibleEdgeX | ImGuiScrollFlags_KeepVisibleEdgeY;\n    SetNavWindow(window);\n    NavMoveRequestSubmit(ImGuiDir_None, ImGuiDir_Up, move_flags, scroll_flags);\n    NavMoveRequestResolveWithLastItem(&g.NavMoveResultLocal);\n}\n\nvoid ImGui::ActivateItemByID(ImGuiID id)\n{\n    ImGuiContext& g = *GImGui;\n    g.NavNextActivateId = id;\n    g.NavNextActivateFlags = ImGuiActivateFlags_None;\n}\n\n// Note: this will likely be called ActivateItem() once we rework our Focus/Activation system!\n// But ActivateItem() should function without altering scroll/focus?\nvoid ImGui::SetKeyboardFocusHere(int offset)\n{\n    ImGuiContext& g = *GImGui;\n    ImGuiWindow* window = g.CurrentWindow;\n    IM_ASSERT(offset >= -1);    // -1 is allowed but not below\n    IMGUI_DEBUG_LOG_FOCUS(\"SetKeyboardFocusHere(%d) in window \\\"%s\\\"\\n\", offset, window->Name);\n\n    // It makes sense in the vast majority of cases to never interrupt a drag and drop.\n    // When we refactor this function into ActivateItem() we may want to make this an option.\n    // MovingWindow is protected from most user inputs using SetActiveIdUsingNavAndKeys(), but\n    // is also automatically dropped in the event g.ActiveId is stolen.\n    if (g.DragDropActive || g.MovingWindow != NULL)\n    {\n        IMGUI_DEBUG_LOG_FOCUS(\"SetKeyboardFocusHere() ignored while DragDropActive!\\n\");\n        return;\n    }\n\n    SetNavWindow(window);\n\n    ImGuiNavMoveFlags move_flags = ImGuiNavMoveFlags_IsTabbing | ImGuiNavMoveFlags_Activate | ImGuiNavMoveFlags_FocusApi | ImGuiNavMoveFlags_NoSetNavCursorVisible;\n    ImGuiScrollFlags scroll_flags = window->Appearing ? ImGuiScrollFlags_KeepVisibleEdgeX | ImGuiScrollFlags_AlwaysCenterY : ImGuiScrollFlags_KeepVisibleEdgeX | ImGuiScrollFlags_KeepVisibleEdgeY;\n    NavMoveRequestSubmit(ImGuiDir_None, offset < 0 ? ImGuiDir_Up : ImGuiDir_Down, move_flags, scroll_flags); // FIXME-NAV: Once we refactor tabbing, add LegacyApi flag to not activate non-inputable.\n    if (offset == -1)\n    {\n        NavMoveRequestResolveWithLastItem(&g.NavMoveResultLocal);\n    }\n    else\n    {\n        g.NavTabbingDir = 1;\n        g.NavTabbingCounter = offset + 1;\n    }\n}\n\nvoid ImGui::SetItemDefaultFocus()\n{\n    ImGuiContext& g = *GImGui;\n    ImGuiWindow* window = g.CurrentWindow;\n    if (!window->Appearing)\n        return;\n    if (g.NavWindow != window->RootWindowForNav || (!g.NavInitRequest && g.NavInitResult.ID == 0) || g.NavLayer != window->DC.NavLayerCurrent)\n        return;\n\n    g.NavInitRequest = false;\n    NavApplyItemToResult(&g.NavInitResult);\n    NavUpdateAnyRequestFlag();\n\n    // Scroll could be done in NavInitRequestApplyResult() via an opt-in flag (we however don't want regular init requests to scroll)\n    if (!window->ClipRect.Contains(g.LastItemData.Rect))\n        ScrollToRectEx(window, g.LastItemData.Rect, ImGuiScrollFlags_None);\n}\n\nvoid ImGui::SetStateStorage(ImGuiStorage* tree)\n{\n    ImGuiWindow* window = GImGui->CurrentWindow;\n    window->DC.StateStorage = tree ? tree : &window->StateStorage;\n}\n\nImGuiStorage* ImGui::GetStateStorage()\n{\n    ImGuiWindow* window = GImGui->CurrentWindow;\n    return window->DC.StateStorage;\n}\n\nbool ImGui::IsRectVisible(const ImVec2& size)\n{\n    ImGuiWindow* window = GImGui->CurrentWindow;\n    return window->ClipRect.Overlaps(ImRect(window->DC.CursorPos, window->DC.CursorPos + size));\n}\n\nbool ImGui::IsRectVisible(const ImVec2& rect_min, const ImVec2& rect_max)\n{\n    ImGuiWindow* window = GImGui->CurrentWindow;\n    return window->ClipRect.Overlaps(ImRect(rect_min, rect_max));\n}\n\n//-----------------------------------------------------------------------------\n// [SECTION] ID STACK\n//-----------------------------------------------------------------------------\n\n// This is one of the very rare legacy case where we use ImGuiWindow methods,\n// it should ideally be flattened at some point but it's been used a lots by widgets.\nIM_MSVC_RUNTIME_CHECKS_OFF\nImGuiID ImGuiWindow::GetID(const char* str, const char* str_end)\n{\n    ImGuiID seed = IDStack.back();\n    ImGuiID id = ImHashStr(str, str_end ? (str_end - str) : 0, seed);\n#ifndef IMGUI_DISABLE_DEBUG_TOOLS\n    ImGuiContext& g = *Ctx;\n    if (g.DebugHookIdInfo == id)\n        ImGui::DebugHookIdInfo(id, ImGuiDataType_String, str, str_end);\n#endif\n    return id;\n}\n\nImGuiID ImGuiWindow::GetID(const void* ptr)\n{\n    ImGuiID seed = IDStack.back();\n    ImGuiID id = ImHashData(&ptr, sizeof(void*), seed);\n#ifndef IMGUI_DISABLE_DEBUG_TOOLS\n    ImGuiContext& g = *Ctx;\n    if (g.DebugHookIdInfo == id)\n        ImGui::DebugHookIdInfo(id, ImGuiDataType_Pointer, ptr, NULL);\n#endif\n    return id;\n}\n\nImGuiID ImGuiWindow::GetID(int n)\n{\n    ImGuiID seed = IDStack.back();\n    ImGuiID id = ImHashData(&n, sizeof(n), seed);\n#ifndef IMGUI_DISABLE_DEBUG_TOOLS\n    ImGuiContext& g = *Ctx;\n    if (g.DebugHookIdInfo == id)\n        ImGui::DebugHookIdInfo(id, ImGuiDataType_S32, (void*)(intptr_t)n, NULL);\n#endif\n    return id;\n}\n\n// This is only used in rare/specific situations to manufacture an ID out of nowhere.\n// FIXME: Consider instead storing last non-zero ID + count of successive zero-ID, and combine those?\nImGuiID ImGuiWindow::GetIDFromPos(const ImVec2& p_abs)\n{\n    ImGuiID seed = IDStack.back();\n    ImVec2 p_rel = ImGui::WindowPosAbsToRel(this, p_abs);\n    ImGuiID id = ImHashData(&p_rel, sizeof(p_rel), seed);\n    return id;\n}\n\n// \"\nImGuiID ImGuiWindow::GetIDFromRectangle(const ImRect& r_abs)\n{\n    ImGuiID seed = IDStack.back();\n    ImRect r_rel = ImGui::WindowRectAbsToRel(this, r_abs);\n    ImGuiID id = ImHashData(&r_rel, sizeof(r_rel), seed);\n    return id;\n}\n\nvoid ImGui::PushID(const char* str_id)\n{\n    ImGuiContext& g = *GImGui;\n    ImGuiWindow* window = g.CurrentWindow;\n    ImGuiID id = window->GetID(str_id);\n    window->IDStack.push_back(id);\n}\n\nvoid ImGui::PushID(const char* str_id_begin, const char* str_id_end)\n{\n    ImGuiContext& g = *GImGui;\n    ImGuiWindow* window = g.CurrentWindow;\n    ImGuiID id = window->GetID(str_id_begin, str_id_end);\n    window->IDStack.push_back(id);\n}\n\nvoid ImGui::PushID(const void* ptr_id)\n{\n    ImGuiContext& g = *GImGui;\n    ImGuiWindow* window = g.CurrentWindow;\n    ImGuiID id = window->GetID(ptr_id);\n    window->IDStack.push_back(id);\n}\n\nvoid ImGui::PushID(int int_id)\n{\n    ImGuiContext& g = *GImGui;\n    ImGuiWindow* window = g.CurrentWindow;\n    ImGuiID id = window->GetID(int_id);\n    window->IDStack.push_back(id);\n}\n\n// Push a given id value ignoring the ID stack as a seed.\nvoid ImGui::PushOverrideID(ImGuiID id)\n{\n    ImGuiContext& g = *GImGui;\n    ImGuiWindow* window = g.CurrentWindow;\n#ifndef IMGUI_DISABLE_DEBUG_TOOLS\n    if (g.DebugHookIdInfo == id)\n        DebugHookIdInfo(id, ImGuiDataType_ID, NULL, NULL);\n#endif\n    window->IDStack.push_back(id);\n}\n\n// Helper to avoid a common series of PushOverrideID -> GetID() -> PopID() call\n// (note that when using this pattern, ID Stack Tool will tend to not display the intermediate stack level.\n//  for that to work we would need to do PushOverrideID() -> ItemAdd() -> PopID() which would alter widget code a little more)\nImGuiID ImGui::GetIDWithSeed(const char* str, const char* str_end, ImGuiID seed)\n{\n    ImGuiID id = ImHashStr(str, str_end ? (str_end - str) : 0, seed);\n#ifndef IMGUI_DISABLE_DEBUG_TOOLS\n    ImGuiContext& g = *GImGui;\n    if (g.DebugHookIdInfo == id)\n        DebugHookIdInfo(id, ImGuiDataType_String, str, str_end);\n#endif\n    return id;\n}\n\nImGuiID ImGui::GetIDWithSeed(int n, ImGuiID seed)\n{\n    ImGuiID id = ImHashData(&n, sizeof(n), seed);\n#ifndef IMGUI_DISABLE_DEBUG_TOOLS\n    ImGuiContext& g = *GImGui;\n    if (g.DebugHookIdInfo == id)\n        DebugHookIdInfo(id, ImGuiDataType_S32, (void*)(intptr_t)n, NULL);\n#endif\n    return id;\n}\n\nvoid ImGui::PopID()\n{\n    ImGuiWindow* window = GImGui->CurrentWindow;\n    if (window->IDStack.Size <= 1)\n    {\n        IM_ASSERT_USER_ERROR(0, \"Calling PopID() too many times!\");\n        return;\n    }\n    window->IDStack.pop_back();\n}\n\nImGuiID ImGui::GetID(const char* str_id)\n{\n    ImGuiWindow* window = GImGui->CurrentWindow;\n    return window->GetID(str_id);\n}\n\nImGuiID ImGui::GetID(const char* str_id_begin, const char* str_id_end)\n{\n    ImGuiWindow* window = GImGui->CurrentWindow;\n    return window->GetID(str_id_begin, str_id_end);\n}\n\nImGuiID ImGui::GetID(const void* ptr_id)\n{\n    ImGuiWindow* window = GImGui->CurrentWindow;\n    return window->GetID(ptr_id);\n}\n\nImGuiID ImGui::GetID(int int_id)\n{\n    ImGuiWindow* window = GImGui->CurrentWindow;\n    return window->GetID(int_id);\n}\nIM_MSVC_RUNTIME_CHECKS_RESTORE\n\n//-----------------------------------------------------------------------------\n// [SECTION] INPUTS\n//-----------------------------------------------------------------------------\n// - GetModForLRModKey() [Internal]\n// - FixupKeyChord() [Internal]\n// - GetKeyData() [Internal]\n// - GetKeyIndex() [Internal]\n// - GetKeyName()\n// - GetKeyChordName() [Internal]\n// - CalcTypematicRepeatAmount() [Internal]\n// - GetTypematicRepeatRate() [Internal]\n// - GetKeyPressedAmount() [Internal]\n// - GetKeyMagnitude2d() [Internal]\n//-----------------------------------------------------------------------------\n// - UpdateKeyRoutingTable() [Internal]\n// - GetRoutingIdFromOwnerId() [Internal]\n// - GetShortcutRoutingData() [Internal]\n// - CalcRoutingScore() [Internal]\n// - SetShortcutRouting() [Internal]\n// - TestShortcutRouting() [Internal]\n//-----------------------------------------------------------------------------\n// - IsKeyDown()\n// - IsKeyPressed()\n// - IsKeyReleased()\n//-----------------------------------------------------------------------------\n// - IsMouseDown()\n// - IsMouseClicked()\n// - IsMouseReleased()\n// - IsMouseDoubleClicked()\n// - GetMouseClickedCount()\n// - IsMouseHoveringRect() [Internal]\n// - IsMouseDragPastThreshold() [Internal]\n// - IsMouseDragging()\n// - GetMousePos()\n// - SetMousePos() [Internal]\n// - GetMousePosOnOpeningCurrentPopup()\n// - IsMousePosValid()\n// - IsAnyMouseDown()\n// - GetMouseDragDelta()\n// - ResetMouseDragDelta()\n// - GetMouseCursor()\n// - SetMouseCursor()\n//-----------------------------------------------------------------------------\n// - UpdateAliasKey()\n// - GetMergedModsFromKeys()\n// - UpdateKeyboardInputs()\n// - UpdateMouseInputs()\n//-----------------------------------------------------------------------------\n// - LockWheelingWindow [Internal]\n// - FindBestWheelingWindow [Internal]\n// - UpdateMouseWheel() [Internal]\n//-----------------------------------------------------------------------------\n// - SetNextFrameWantCaptureKeyboard()\n// - SetNextFrameWantCaptureMouse()\n//-----------------------------------------------------------------------------\n// - GetInputSourceName() [Internal]\n// - DebugPrintInputEvent() [Internal]\n// - UpdateInputEvents() [Internal]\n//-----------------------------------------------------------------------------\n// - GetKeyOwner() [Internal]\n// - TestKeyOwner() [Internal]\n// - SetKeyOwner() [Internal]\n// - SetItemKeyOwner() [Internal]\n// - Shortcut() [Internal]\n//-----------------------------------------------------------------------------\n\nstatic ImGuiKeyChord GetModForLRModKey(ImGuiKey key)\n{\n    if (key == ImGuiKey_LeftCtrl || key == ImGuiKey_RightCtrl)\n        return ImGuiMod_Ctrl;\n    if (key == ImGuiKey_LeftShift || key == ImGuiKey_RightShift)\n        return ImGuiMod_Shift;\n    if (key == ImGuiKey_LeftAlt || key == ImGuiKey_RightAlt)\n        return ImGuiMod_Alt;\n    if (key == ImGuiKey_LeftSuper || key == ImGuiKey_RightSuper)\n        return ImGuiMod_Super;\n    return ImGuiMod_None;\n}\n\nImGuiKeyChord ImGui::FixupKeyChord(ImGuiKeyChord key_chord)\n{\n    // Add ImGuiMod_XXXX when a corresponding ImGuiKey_LeftXXX/ImGuiKey_RightXXX is specified.\n    ImGuiKey key = (ImGuiKey)(key_chord & ~ImGuiMod_Mask_);\n    if (IsLRModKey(key))\n        key_chord |= GetModForLRModKey(key);\n    return key_chord;\n}\n\nImGuiKeyData* ImGui::GetKeyData(ImGuiContext* ctx, ImGuiKey key)\n{\n    ImGuiContext& g = *ctx;\n\n    // Special storage location for mods\n    if (key & ImGuiMod_Mask_)\n        key = ConvertSingleModFlagToKey(key);\n\n    IM_ASSERT(IsNamedKey(key) && \"Support for user key indices was dropped in favor of ImGuiKey. Please update backend & user code.\");\n    return &g.IO.KeysData[key - ImGuiKey_NamedKey_BEGIN];\n}\n\n// Those names are provided for debugging purpose and are not meant to be saved persistently nor compared.\nstatic const char* const GKeyNames[] =\n{\n    \"Tab\", \"LeftArrow\", \"RightArrow\", \"UpArrow\", \"DownArrow\", \"PageUp\", \"PageDown\",\n    \"Home\", \"End\", \"Insert\", \"Delete\", \"Backspace\", \"Space\", \"Enter\", \"Escape\",\n    \"LeftCtrl\", \"LeftShift\", \"LeftAlt\", \"LeftSuper\", \"RightCtrl\", \"RightShift\", \"RightAlt\", \"RightSuper\", \"Menu\",\n    \"0\", \"1\", \"2\", \"3\", \"4\", \"5\", \"6\", \"7\", \"8\", \"9\", \"A\", \"B\", \"C\", \"D\", \"E\", \"F\", \"G\", \"H\",\n    \"I\", \"J\", \"K\", \"L\", \"M\", \"N\", \"O\", \"P\", \"Q\", \"R\", \"S\", \"T\", \"U\", \"V\", \"W\", \"X\", \"Y\", \"Z\",\n    \"F1\", \"F2\", \"F3\", \"F4\", \"F5\", \"F6\", \"F7\", \"F8\", \"F9\", \"F10\", \"F11\", \"F12\",\n    \"F13\", \"F14\", \"F15\", \"F16\", \"F17\", \"F18\", \"F19\", \"F20\", \"F21\", \"F22\", \"F23\", \"F24\",\n    \"Apostrophe\", \"Comma\", \"Minus\", \"Period\", \"Slash\", \"Semicolon\", \"Equal\", \"LeftBracket\",\n    \"Backslash\", \"RightBracket\", \"GraveAccent\", \"CapsLock\", \"ScrollLock\", \"NumLock\", \"PrintScreen\",\n    \"Pause\", \"Keypad0\", \"Keypad1\", \"Keypad2\", \"Keypad3\", \"Keypad4\", \"Keypad5\", \"Keypad6\",\n    \"Keypad7\", \"Keypad8\", \"Keypad9\", \"KeypadDecimal\", \"KeypadDivide\", \"KeypadMultiply\",\n    \"KeypadSubtract\", \"KeypadAdd\", \"KeypadEnter\", \"KeypadEqual\",\n    \"AppBack\", \"AppForward\", \"Oem102\",\n    \"GamepadStart\", \"GamepadBack\",\n    \"GamepadFaceLeft\", \"GamepadFaceRight\", \"GamepadFaceUp\", \"GamepadFaceDown\",\n    \"GamepadDpadLeft\", \"GamepadDpadRight\", \"GamepadDpadUp\", \"GamepadDpadDown\",\n    \"GamepadL1\", \"GamepadR1\", \"GamepadL2\", \"GamepadR2\", \"GamepadL3\", \"GamepadR3\",\n    \"GamepadLStickLeft\", \"GamepadLStickRight\", \"GamepadLStickUp\", \"GamepadLStickDown\",\n    \"GamepadRStickLeft\", \"GamepadRStickRight\", \"GamepadRStickUp\", \"GamepadRStickDown\",\n    \"MouseLeft\", \"MouseRight\", \"MouseMiddle\", \"MouseX1\", \"MouseX2\", \"MouseWheelX\", \"MouseWheelY\",\n    \"ModCtrl\", \"ModShift\", \"ModAlt\", \"ModSuper\", // ReservedForModXXX are showing the ModXXX names.\n};\nIM_STATIC_ASSERT(ImGuiKey_NamedKey_COUNT == IM_ARRAYSIZE(GKeyNames));\n\nconst char* ImGui::GetKeyName(ImGuiKey key)\n{\n    if (key == ImGuiKey_None)\n        return \"None\";\n    IM_ASSERT(IsNamedKeyOrMod(key) && \"Support for user key indices was dropped in favor of ImGuiKey. Please update backend and user code.\");\n    if (key & ImGuiMod_Mask_)\n        key = ConvertSingleModFlagToKey(key);\n    if (!IsNamedKey(key))\n        return \"Unknown\";\n\n    return GKeyNames[key - ImGuiKey_NamedKey_BEGIN];\n}\n\n// Return untranslated names: on macOS, Cmd key will show as Ctrl, Ctrl key will show as super.\n// Lifetime of return value: valid until next call to same function.\nconst char* ImGui::GetKeyChordName(ImGuiKeyChord key_chord)\n{\n    ImGuiContext& g = *GImGui;\n\n    const ImGuiKey key = (ImGuiKey)(key_chord & ~ImGuiMod_Mask_);\n    if (IsLRModKey(key))\n        key_chord &= ~GetModForLRModKey(key); // Return \"Ctrl+LeftShift\" instead of \"Ctrl+Shift+LeftShift\"\n    ImFormatString(g.TempKeychordName, IM_ARRAYSIZE(g.TempKeychordName), \"%s%s%s%s%s\",\n        (key_chord & ImGuiMod_Ctrl) ? \"Ctrl+\" : \"\",\n        (key_chord & ImGuiMod_Shift) ? \"Shift+\" : \"\",\n        (key_chord & ImGuiMod_Alt) ? \"Alt+\" : \"\",\n        (key_chord & ImGuiMod_Super) ? \"Super+\" : \"\",\n        (key != ImGuiKey_None || key_chord == ImGuiKey_None) ? GetKeyName(key) : \"\");\n    size_t len;\n    if (key == ImGuiKey_None && key_chord != 0)\n        if ((len = ImStrlen(g.TempKeychordName)) != 0) // Remove trailing '+'\n            g.TempKeychordName[len - 1] = 0;\n    return g.TempKeychordName;\n}\n\n// t0 = previous time (e.g.: g.Time - g.IO.DeltaTime)\n// t1 = current time (e.g.: g.Time)\n// An event is triggered at:\n//  t = 0.0f     t = repeat_delay,    t = repeat_delay + repeat_rate*N\nint ImGui::CalcTypematicRepeatAmount(float t0, float t1, float repeat_delay, float repeat_rate)\n{\n    if (t1 == 0.0f)\n        return 1;\n    if (t0 >= t1)\n        return 0;\n    if (repeat_rate <= 0.0f)\n        return (t0 < repeat_delay) && (t1 >= repeat_delay);\n    const int count_t0 = (t0 < repeat_delay) ? -1 : (int)((t0 - repeat_delay) / repeat_rate);\n    const int count_t1 = (t1 < repeat_delay) ? -1 : (int)((t1 - repeat_delay) / repeat_rate);\n    const int count = count_t1 - count_t0;\n    return count;\n}\n\nvoid ImGui::GetTypematicRepeatRate(ImGuiInputFlags flags, float* repeat_delay, float* repeat_rate)\n{\n    ImGuiContext& g = *GImGui;\n    switch (flags & ImGuiInputFlags_RepeatRateMask_)\n    {\n    case ImGuiInputFlags_RepeatRateNavMove:             *repeat_delay = g.IO.KeyRepeatDelay * 0.72f; *repeat_rate = g.IO.KeyRepeatRate * 0.80f; return;\n    case ImGuiInputFlags_RepeatRateNavTweak:            *repeat_delay = g.IO.KeyRepeatDelay * 0.72f; *repeat_rate = g.IO.KeyRepeatRate * 0.30f; return;\n    case ImGuiInputFlags_RepeatRateDefault: default:    *repeat_delay = g.IO.KeyRepeatDelay * 1.00f; *repeat_rate = g.IO.KeyRepeatRate * 1.00f; return;\n    }\n}\n\n// Return value representing the number of presses in the last time period, for the given repeat rate\n// (most often returns 0 or 1. The result is generally only >1 when RepeatRate is smaller than DeltaTime, aka large DeltaTime or fast RepeatRate)\nint ImGui::GetKeyPressedAmount(ImGuiKey key, float repeat_delay, float repeat_rate)\n{\n    ImGuiContext& g = *GImGui;\n    const ImGuiKeyData* key_data = GetKeyData(key);\n    if (!key_data->Down) // In theory this should already be encoded as (DownDuration < 0.0f), but testing this facilitates eating mechanism (until we finish work on key ownership)\n        return 0;\n    const float t = key_data->DownDuration;\n    return CalcTypematicRepeatAmount(t - g.IO.DeltaTime, t, repeat_delay, repeat_rate);\n}\n\n// Return 2D vector representing the combination of four cardinal direction, with analog value support (for e.g. ImGuiKey_GamepadLStick* values).\nImVec2 ImGui::GetKeyMagnitude2d(ImGuiKey key_left, ImGuiKey key_right, ImGuiKey key_up, ImGuiKey key_down)\n{\n    return ImVec2(\n        GetKeyData(key_right)->AnalogValue - GetKeyData(key_left)->AnalogValue,\n        GetKeyData(key_down)->AnalogValue - GetKeyData(key_up)->AnalogValue);\n}\n\n// Rewrite routing data buffers to strip old entries + sort by key to make queries not touch scattered data.\n//   Entries   D,A,B,B,A,C,B     --> A,A,B,B,B,C,D\n//   Index     A:1 B:2 C:5 D:0   --> A:0 B:2 C:5 D:6\n// See 'Metrics->Key Owners & Shortcut Routing' to visualize the result of that operation.\nstatic void ImGui::UpdateKeyRoutingTable(ImGuiKeyRoutingTable* rt)\n{\n    ImGuiContext& g = *GImGui;\n    rt->EntriesNext.resize(0);\n    for (ImGuiKey key = ImGuiKey_NamedKey_BEGIN; key < ImGuiKey_NamedKey_END; key = (ImGuiKey)(key + 1))\n    {\n        const int new_routing_start_idx = rt->EntriesNext.Size;\n        ImGuiKeyRoutingData* routing_entry;\n        for (int old_routing_idx = rt->Index[key - ImGuiKey_NamedKey_BEGIN]; old_routing_idx != -1; old_routing_idx = routing_entry->NextEntryIndex)\n        {\n            routing_entry = &rt->Entries[old_routing_idx];\n            routing_entry->RoutingCurrScore = routing_entry->RoutingNextScore;\n            routing_entry->RoutingCurr = routing_entry->RoutingNext; // Update entry\n            routing_entry->RoutingNext = ImGuiKeyOwner_NoOwner;\n            routing_entry->RoutingNextScore = 255;\n            if (routing_entry->RoutingCurr == ImGuiKeyOwner_NoOwner)\n                continue;\n            rt->EntriesNext.push_back(*routing_entry); // Write alive ones into new buffer\n\n            // Apply routing to owner if there's no owner already (RoutingCurr == None at this point)\n            // This is the result of previous frame's SetShortcutRouting() call.\n            if (routing_entry->Mods == g.IO.KeyMods)\n            {\n                ImGuiKeyOwnerData* owner_data = GetKeyOwnerData(&g, key);\n                if (owner_data->OwnerCurr == ImGuiKeyOwner_NoOwner)\n                {\n                    owner_data->OwnerCurr = routing_entry->RoutingCurr;\n                    //IMGUI_DEBUG_LOG(\"SetKeyOwner(%s, owner_id=0x%08X) via Routing\\n\", GetKeyName(key), routing_entry->RoutingCurr);\n                }\n            }\n        }\n\n        // Rewrite linked-list\n        rt->Index[key - ImGuiKey_NamedKey_BEGIN] = (ImGuiKeyRoutingIndex)(new_routing_start_idx < rt->EntriesNext.Size ? new_routing_start_idx : -1);\n        for (int n = new_routing_start_idx; n < rt->EntriesNext.Size; n++)\n            rt->EntriesNext[n].NextEntryIndex = (ImGuiKeyRoutingIndex)((n + 1 < rt->EntriesNext.Size) ? n + 1 : -1);\n    }\n    rt->Entries.swap(rt->EntriesNext); // Swap new and old indexes\n}\n\n// owner_id may be None/Any, but routing_id needs to be always be set, so we default to GetCurrentFocusScope().\nstatic inline ImGuiID GetRoutingIdFromOwnerId(ImGuiID owner_id)\n{\n    ImGuiContext& g = *GImGui;\n    return (owner_id != ImGuiKeyOwner_NoOwner && owner_id != ImGuiKeyOwner_Any) ? owner_id : g.CurrentFocusScopeId;\n}\n\nImGuiKeyRoutingData* ImGui::GetShortcutRoutingData(ImGuiKeyChord key_chord)\n{\n    // Majority of shortcuts will be Key + any number of Mods\n    // We accept _Single_ mod with ImGuiKey_None.\n    //  - Shortcut(ImGuiKey_S | ImGuiMod_Ctrl);                    // Legal\n    //  - Shortcut(ImGuiKey_S | ImGuiMod_Ctrl | ImGuiMod_Shift);   // Legal\n    //  - Shortcut(ImGuiMod_Ctrl);                                 // Legal\n    //  - Shortcut(ImGuiMod_Ctrl | ImGuiMod_Shift);                // Not legal\n    ImGuiContext& g = *GImGui;\n    ImGuiKeyRoutingTable* rt = &g.KeysRoutingTable;\n    ImGuiKeyRoutingData* routing_data;\n    ImGuiKey key = (ImGuiKey)(key_chord & ~ImGuiMod_Mask_);\n    ImGuiKey mods = (ImGuiKey)(key_chord & ImGuiMod_Mask_);\n    if (key == ImGuiKey_None)\n        key = ConvertSingleModFlagToKey(mods);\n    IM_ASSERT(IsNamedKey(key));\n\n    // Get (in the majority of case, the linked list will have one element so this should be 2 reads.\n    // Subsequent elements will be contiguous in memory as list is sorted/rebuilt in NewFrame).\n    for (ImGuiKeyRoutingIndex idx = rt->Index[key - ImGuiKey_NamedKey_BEGIN]; idx != -1; idx = routing_data->NextEntryIndex)\n    {\n        routing_data = &rt->Entries[idx];\n        if (routing_data->Mods == mods)\n            return routing_data;\n    }\n\n    // Add to linked-list\n    ImGuiKeyRoutingIndex routing_data_idx = (ImGuiKeyRoutingIndex)rt->Entries.Size;\n    rt->Entries.push_back(ImGuiKeyRoutingData());\n    routing_data = &rt->Entries[routing_data_idx];\n    routing_data->Mods = (ImU16)mods;\n    routing_data->NextEntryIndex = rt->Index[key - ImGuiKey_NamedKey_BEGIN]; // Setup linked list\n    rt->Index[key - ImGuiKey_NamedKey_BEGIN] = routing_data_idx;\n    return routing_data;\n}\n\n// Current score encoding (lower is highest priority):\n//  -   0: ImGuiInputFlags_RouteGlobal | ImGuiInputFlags_RouteOverActive\n//  -   1: ImGuiInputFlags_ActiveItem or ImGuiInputFlags_RouteFocused (if item active)\n//  -   2: ImGuiInputFlags_RouteGlobal | ImGuiInputFlags_RouteOverFocused\n//  -  3+: ImGuiInputFlags_RouteFocused (if window in focus-stack)\n//  - 254: ImGuiInputFlags_RouteGlobal\n//  - 255: never route\n// 'flags' should include an explicit routing policy\nstatic int CalcRoutingScore(ImGuiID focus_scope_id, ImGuiID owner_id, ImGuiInputFlags flags)\n{\n    ImGuiContext& g = *GImGui;\n    if (flags & ImGuiInputFlags_RouteFocused)\n    {\n        // ActiveID gets top priority\n        // (we don't check g.ActiveIdUsingAllKeys here. Routing is applied but if input ownership is tested later it may discard it)\n        if (owner_id != 0 && g.ActiveId == owner_id)\n            return 1;\n\n        // Score based on distance to focused window (lower is better)\n        // Assuming both windows are submitting a routing request,\n        // - When Window....... is focused -> Window scores 3 (best), Window/ChildB scores 255 (no match)\n        // - When Window/ChildB is focused -> Window scores 4,        Window/ChildB scores 3 (best)\n        // Assuming only WindowA is submitting a routing request,\n        // - When Window/ChildB is focused -> Window scores 4 (best), Window/ChildB doesn't have a score.\n        // This essentially follow the window->ParentWindowForFocusRoute chain.\n        if (focus_scope_id == 0)\n            return 255;\n        for (int index_in_focus_path = 0; index_in_focus_path < g.NavFocusRoute.Size; index_in_focus_path++)\n            if (g.NavFocusRoute.Data[index_in_focus_path].ID == focus_scope_id)\n                return 3 + index_in_focus_path;\n        return 255;\n    }\n    else if (flags & ImGuiInputFlags_RouteActive)\n    {\n        if (owner_id != 0 && g.ActiveId == owner_id)\n            return 1;\n        return 255;\n    }\n    else if (flags & ImGuiInputFlags_RouteGlobal)\n    {\n        if (flags & ImGuiInputFlags_RouteOverActive)\n            return 0;\n        if (flags & ImGuiInputFlags_RouteOverFocused)\n            return 2;\n        return 254;\n    }\n    IM_ASSERT(0);\n    return 0;\n}\n\n// - We need this to filter some Shortcut() routes when an item e.g. an InputText() is active\n//   e.g. ImGuiKey_G won't be considered a shortcut when item is active, but ImGuiMod|ImGuiKey_G can be.\n// - This is also used by UpdateInputEvents() to avoid trickling in the most common case of e.g. pressing ImGuiKey_G also emitting a G character.\nstatic bool IsKeyChordPotentiallyCharInput(ImGuiKeyChord key_chord)\n{\n    // Mimic 'ignore_char_inputs' logic in InputText()\n    ImGuiContext& g = *GImGui;\n\n    // When the right mods are pressed it cannot be a char input so we won't filter the shortcut out.\n    ImGuiKey mods = (ImGuiKey)(key_chord & ImGuiMod_Mask_);\n    const bool ignore_char_inputs = ((mods & ImGuiMod_Ctrl) && !(mods & ImGuiMod_Alt)) || (g.IO.ConfigMacOSXBehaviors && (mods & ImGuiMod_Ctrl));\n    if (ignore_char_inputs)\n        return false;\n\n    // Return true for A-Z, 0-9 and other keys associated to char inputs. Other keys such as F1-F12 won't be filtered.\n    ImGuiKey key = (ImGuiKey)(key_chord & ~ImGuiMod_Mask_);\n    if (key == ImGuiKey_None)\n        return false;\n    return g.KeysMayBeCharInput.TestBit(key);\n}\n\n// Request a desired route for an input chord (key + mods).\n// Return true if the route is available this frame.\n// - Routes and key ownership are attributed at the beginning of next frame based on best score and mod state.\n//   (Conceptually this does a \"Submit for next frame\" + \"Test for current frame\".\n//   As such, it could be called TrySetXXX or SubmitXXX, or the Submit and Test operations should be separate.)\nbool ImGui::SetShortcutRouting(ImGuiKeyChord key_chord, ImGuiInputFlags flags, ImGuiID owner_id)\n{\n    ImGuiContext& g = *GImGui;\n    if ((flags & ImGuiInputFlags_RouteTypeMask_) == 0)\n        flags |= ImGuiInputFlags_RouteGlobal | ImGuiInputFlags_RouteOverFocused | ImGuiInputFlags_RouteOverActive; // IMPORTANT: This is the default for SetShortcutRouting() but NOT Shortcut()\n    else\n        IM_ASSERT(ImIsPowerOfTwo(flags & ImGuiInputFlags_RouteTypeMask_)); // Check that only 1 routing flag is used\n    IM_ASSERT(owner_id != ImGuiKeyOwner_Any && owner_id != ImGuiKeyOwner_NoOwner);\n    if (flags & (ImGuiInputFlags_RouteOverFocused | ImGuiInputFlags_RouteOverActive | ImGuiInputFlags_RouteUnlessBgFocused))\n        IM_ASSERT(flags & ImGuiInputFlags_RouteGlobal);\n\n    // Add ImGuiMod_XXXX when a corresponding ImGuiKey_LeftXXX/ImGuiKey_RightXXX is specified.\n    key_chord = FixupKeyChord(key_chord);\n\n    // [DEBUG] Debug break requested by user\n    if (g.DebugBreakInShortcutRouting == key_chord)\n        IM_DEBUG_BREAK();\n\n    if (flags & ImGuiInputFlags_RouteUnlessBgFocused)\n        if (g.NavWindow == NULL)\n            return false;\n\n    // Note how ImGuiInputFlags_RouteAlways won't set routing and thus won't set owner. May want to rework this?\n    if (flags & ImGuiInputFlags_RouteAlways)\n    {\n        IMGUI_DEBUG_LOG_INPUTROUTING(\"SetShortcutRouting(%s, flags=%04X, owner_id=0x%08X) -> always, no register\\n\", GetKeyChordName(key_chord), flags, owner_id);\n        return true;\n    }\n\n    // Specific culling when there's an active item.\n    if (g.ActiveId != 0 && g.ActiveId != owner_id)\n    {\n        if (flags & ImGuiInputFlags_RouteActive)\n            return false;\n\n        // Cull shortcuts with no modifiers when it could generate a character.\n        // e.g. Shortcut(ImGuiKey_G) also generates 'g' character, should not trigger when InputText() is active.\n        // but  Shortcut(Ctrl+G) should generally trigger when InputText() is active.\n        // TL;DR: lettered shortcut with no mods or with only Alt mod will not trigger while an item reading text input is active.\n        // (We cannot filter based on io.InputQueueCharacters[] contents because of trickling and key<>chars submission order are undefined)\n        if (g.IO.WantTextInput && IsKeyChordPotentiallyCharInput(key_chord))\n        {\n            IMGUI_DEBUG_LOG_INPUTROUTING(\"SetShortcutRouting(%s, flags=%04X, owner_id=0x%08X) -> filtered as potential char input\\n\", GetKeyChordName(key_chord), flags, owner_id);\n            return false;\n        }\n\n        // ActiveIdUsingAllKeyboardKeys trumps all for ActiveId\n        if ((flags & ImGuiInputFlags_RouteOverActive) == 0 && g.ActiveIdUsingAllKeyboardKeys)\n        {\n            ImGuiKey key = (ImGuiKey)(key_chord & ~ImGuiMod_Mask_);\n            if (key == ImGuiKey_None)\n                key = ConvertSingleModFlagToKey((ImGuiKey)(key_chord & ImGuiMod_Mask_));\n            if (key >= ImGuiKey_Keyboard_BEGIN && key < ImGuiKey_Keyboard_END)\n                return false;\n        }\n    }\n\n    // Where do we evaluate route for?\n    ImGuiID focus_scope_id = g.CurrentFocusScopeId;\n    if (flags & ImGuiInputFlags_RouteFromRootWindow)\n        focus_scope_id = g.CurrentWindow->RootWindow->ID; // See PushFocusScope() call in Begin()\n\n    const int score = CalcRoutingScore(focus_scope_id, owner_id, flags);\n    IMGUI_DEBUG_LOG_INPUTROUTING(\"SetShortcutRouting(%s, flags=%04X, owner_id=0x%08X) -> score %d\\n\", GetKeyChordName(key_chord), flags, owner_id, score);\n    if (score == 255)\n        return false;\n\n    // Submit routing for NEXT frame (assuming score is sufficient)\n    // FIXME: Could expose a way to use a \"serve last\" policy for same score resolution (using <= instead of <).\n    ImGuiKeyRoutingData* routing_data = GetShortcutRoutingData(key_chord);\n    //const bool set_route = (flags & ImGuiInputFlags_ServeLast) ? (score <= routing_data->RoutingNextScore) : (score < routing_data->RoutingNextScore);\n    if (score < routing_data->RoutingNextScore)\n    {\n        routing_data->RoutingNext = owner_id;\n        routing_data->RoutingNextScore = (ImU8)score;\n    }\n\n    // Return routing state for CURRENT frame\n    if (routing_data->RoutingCurr == owner_id)\n        IMGUI_DEBUG_LOG_INPUTROUTING(\"--> granting current route\\n\");\n    return routing_data->RoutingCurr == owner_id;\n}\n\n// Currently unused by core (but used by tests)\n// Note: this cannot be turned into GetShortcutRouting() because we do the owner_id->routing_id translation, name would be more misleading.\nbool ImGui::TestShortcutRouting(ImGuiKeyChord key_chord, ImGuiID owner_id)\n{\n    const ImGuiID routing_id = GetRoutingIdFromOwnerId(owner_id);\n    key_chord = FixupKeyChord(key_chord);\n    ImGuiKeyRoutingData* routing_data = GetShortcutRoutingData(key_chord); // FIXME: Could avoid creating entry.\n    return routing_data->RoutingCurr == routing_id;\n}\n\n// Note that Dear ImGui doesn't know the meaning/semantic of ImGuiKey from 0..511: they are legacy native keycodes.\n// Consider transitioning from 'IsKeyDown(MY_ENGINE_KEY_A)' (<1.87) to IsKeyDown(ImGuiKey_A) (>= 1.87)\nbool ImGui::IsKeyDown(ImGuiKey key)\n{\n    return IsKeyDown(key, ImGuiKeyOwner_Any);\n}\n\nbool ImGui::IsKeyDown(ImGuiKey key, ImGuiID owner_id)\n{\n    const ImGuiKeyData* key_data = GetKeyData(key);\n    if (!key_data->Down)\n        return false;\n    if (!TestKeyOwner(key, owner_id))\n        return false;\n    return true;\n}\n\nbool ImGui::IsKeyPressed(ImGuiKey key, bool repeat)\n{\n    return IsKeyPressed(key, repeat ? ImGuiInputFlags_Repeat : ImGuiInputFlags_None, ImGuiKeyOwner_Any);\n}\n\n// Important: unlike legacy IsKeyPressed(ImGuiKey, bool repeat=true) which DEFAULT to repeat, this requires EXPLICIT repeat.\nbool ImGui::IsKeyPressed(ImGuiKey key, ImGuiInputFlags flags, ImGuiID owner_id)\n{\n    const ImGuiKeyData* key_data = GetKeyData(key);\n    if (!key_data->Down) // In theory this should already be encoded as (DownDuration < 0.0f), but testing this facilitates eating mechanism (until we finish work on key ownership)\n        return false;\n    const float t = key_data->DownDuration;\n    if (t < 0.0f)\n        return false;\n    IM_ASSERT((flags & ~ImGuiInputFlags_SupportedByIsKeyPressed) == 0); // Passing flags not supported by this function!\n    if (flags & (ImGuiInputFlags_RepeatRateMask_ | ImGuiInputFlags_RepeatUntilMask_)) // Setting any _RepeatXXX option enables _Repeat\n        flags |= ImGuiInputFlags_Repeat;\n\n    bool pressed = (t == 0.0f);\n    if (!pressed && (flags & ImGuiInputFlags_Repeat) != 0)\n    {\n        float repeat_delay, repeat_rate;\n        GetTypematicRepeatRate(flags, &repeat_delay, &repeat_rate);\n        pressed = (t > repeat_delay) && GetKeyPressedAmount(key, repeat_delay, repeat_rate) > 0;\n        if (pressed && (flags & ImGuiInputFlags_RepeatUntilMask_))\n        {\n            // Slightly bias 'key_pressed_time' as DownDuration is an accumulation of DeltaTime which we compare to an absolute time value.\n            // Ideally we'd replace DownDuration with KeyPressedTime but it would break user's code.\n            ImGuiContext& g = *GImGui;\n            double key_pressed_time = g.Time - t + 0.00001f;\n            if ((flags & ImGuiInputFlags_RepeatUntilKeyModsChange) && (g.LastKeyModsChangeTime > key_pressed_time))\n                pressed = false;\n            if ((flags & ImGuiInputFlags_RepeatUntilKeyModsChangeFromNone) && (g.LastKeyModsChangeFromNoneTime > key_pressed_time))\n                pressed = false;\n            if ((flags & ImGuiInputFlags_RepeatUntilOtherKeyPress) && (g.LastKeyboardKeyPressTime > key_pressed_time))\n                pressed = false;\n        }\n    }\n    if (!pressed)\n        return false;\n    if (!TestKeyOwner(key, owner_id))\n        return false;\n    return true;\n}\n\nbool ImGui::IsKeyReleased(ImGuiKey key)\n{\n    return IsKeyReleased(key, ImGuiKeyOwner_Any);\n}\n\nbool ImGui::IsKeyReleased(ImGuiKey key, ImGuiID owner_id)\n{\n    const ImGuiKeyData* key_data = GetKeyData(key);\n    if (key_data->DownDurationPrev < 0.0f || key_data->Down)\n        return false;\n    if (!TestKeyOwner(key, owner_id))\n        return false;\n    return true;\n}\n\nbool ImGui::IsMouseDown(ImGuiMouseButton button)\n{\n    ImGuiContext& g = *GImGui;\n    IM_ASSERT(button >= 0 && button < IM_ARRAYSIZE(g.IO.MouseDown));\n    return g.IO.MouseDown[button] && TestKeyOwner(MouseButtonToKey(button), ImGuiKeyOwner_Any); // should be same as IsKeyDown(MouseButtonToKey(button), ImGuiKeyOwner_Any), but this allows legacy code hijacking the io.Mousedown[] array.\n}\n\nbool ImGui::IsMouseDown(ImGuiMouseButton button, ImGuiID owner_id)\n{\n    ImGuiContext& g = *GImGui;\n    IM_ASSERT(button >= 0 && button < IM_ARRAYSIZE(g.IO.MouseDown));\n    return g.IO.MouseDown[button] && TestKeyOwner(MouseButtonToKey(button), owner_id); // Should be same as IsKeyDown(MouseButtonToKey(button), owner_id), but this allows legacy code hijacking the io.Mousedown[] array.\n}\n\nbool ImGui::IsMouseClicked(ImGuiMouseButton button, bool repeat)\n{\n    return IsMouseClicked(button, repeat ? ImGuiInputFlags_Repeat : ImGuiInputFlags_None, ImGuiKeyOwner_Any);\n}\n\nbool ImGui::IsMouseClicked(ImGuiMouseButton button, ImGuiInputFlags flags, ImGuiID owner_id)\n{\n    ImGuiContext& g = *GImGui;\n    IM_ASSERT(button >= 0 && button < IM_ARRAYSIZE(g.IO.MouseDown));\n    if (!g.IO.MouseDown[button]) // In theory this should already be encoded as (DownDuration < 0.0f), but testing this facilitates eating mechanism (until we finish work on key ownership)\n        return false;\n    const float t = g.IO.MouseDownDuration[button];\n    if (t < 0.0f)\n        return false;\n    IM_ASSERT((flags & ~ImGuiInputFlags_SupportedByIsMouseClicked) == 0); // Passing flags not supported by this function! // FIXME: Could support RepeatRate and RepeatUntil flags here.\n\n    const bool repeat = (flags & ImGuiInputFlags_Repeat) != 0;\n    const bool pressed = (t == 0.0f) || (repeat && t > g.IO.KeyRepeatDelay && CalcTypematicRepeatAmount(t - g.IO.DeltaTime, t, g.IO.KeyRepeatDelay, g.IO.KeyRepeatRate) > 0);\n    if (!pressed)\n        return false;\n\n    if (!TestKeyOwner(MouseButtonToKey(button), owner_id))\n        return false;\n\n    return true;\n}\n\nbool ImGui::IsMouseReleased(ImGuiMouseButton button)\n{\n    ImGuiContext& g = *GImGui;\n    IM_ASSERT(button >= 0 && button < IM_ARRAYSIZE(g.IO.MouseDown));\n    return g.IO.MouseReleased[button] && TestKeyOwner(MouseButtonToKey(button), ImGuiKeyOwner_Any); // Should be same as IsKeyReleased(MouseButtonToKey(button), ImGuiKeyOwner_Any)\n}\n\nbool ImGui::IsMouseReleased(ImGuiMouseButton button, ImGuiID owner_id)\n{\n    ImGuiContext& g = *GImGui;\n    IM_ASSERT(button >= 0 && button < IM_ARRAYSIZE(g.IO.MouseDown));\n    return g.IO.MouseReleased[button] && TestKeyOwner(MouseButtonToKey(button), owner_id); // Should be same as IsKeyReleased(MouseButtonToKey(button), owner_id)\n}\n\n// Use if you absolutely need to distinguish single-click from double-click by introducing a delay.\n// Generally use with 'delay >= io.MouseDoubleClickTime' + combined with a 'io.MouseClickedLastCount == 1' test.\n// This is a very rarely used UI idiom, but some apps use this: e.g. MS Explorer single click on an icon to rename.\nbool ImGui::IsMouseReleasedWithDelay(ImGuiMouseButton button, float delay)\n{\n    ImGuiContext& g = *GImGui;\n    IM_ASSERT(button >= 0 && button < IM_ARRAYSIZE(g.IO.MouseDown));\n    const float time_since_release = (float)(g.Time - g.IO.MouseReleasedTime[button]);\n    return !IsMouseDown(button) && (time_since_release - g.IO.DeltaTime < delay) && (time_since_release >= delay);\n}\n\nbool ImGui::IsMouseDoubleClicked(ImGuiMouseButton button)\n{\n    ImGuiContext& g = *GImGui;\n    IM_ASSERT(button >= 0 && button < IM_ARRAYSIZE(g.IO.MouseDown));\n    return g.IO.MouseClickedCount[button] == 2 && TestKeyOwner(MouseButtonToKey(button), ImGuiKeyOwner_Any);\n}\n\nbool ImGui::IsMouseDoubleClicked(ImGuiMouseButton button, ImGuiID owner_id)\n{\n    ImGuiContext& g = *GImGui;\n    IM_ASSERT(button >= 0 && button < IM_ARRAYSIZE(g.IO.MouseDown));\n    return g.IO.MouseClickedCount[button] == 2 && TestKeyOwner(MouseButtonToKey(button), owner_id);\n}\n\nint ImGui::GetMouseClickedCount(ImGuiMouseButton button)\n{\n    ImGuiContext& g = *GImGui;\n    IM_ASSERT(button >= 0 && button < IM_ARRAYSIZE(g.IO.MouseDown));\n    return g.IO.MouseClickedCount[button];\n}\n\n// Test if mouse cursor is hovering given rectangle\n// NB- Rectangle is clipped by our current clip setting\n// NB- Expand the rectangle to be generous on imprecise inputs systems (g.Style.TouchExtraPadding)\nbool ImGui::IsMouseHoveringRect(const ImVec2& r_min, const ImVec2& r_max, bool clip)\n{\n    ImGuiContext& g = *GImGui;\n\n    // Clip\n    ImRect rect_clipped(r_min, r_max);\n    if (clip)\n        rect_clipped.ClipWith(g.CurrentWindow->ClipRect);\n\n    // Hit testing, expanded for touch input\n    if (!rect_clipped.ContainsWithPad(g.IO.MousePos, g.Style.TouchExtraPadding))\n        return false;\n    return true;\n}\n\n// Return if a mouse click/drag went past the given threshold. Valid to call during the MouseReleased frame.\n// [Internal] This doesn't test if the button is pressed\nbool ImGui::IsMouseDragPastThreshold(ImGuiMouseButton button, float lock_threshold)\n{\n    ImGuiContext& g = *GImGui;\n    IM_ASSERT(button >= 0 && button < IM_ARRAYSIZE(g.IO.MouseDown));\n    if (lock_threshold < 0.0f)\n        lock_threshold = g.IO.MouseDragThreshold;\n    return g.IO.MouseDragMaxDistanceSqr[button] >= lock_threshold * lock_threshold;\n}\n\nbool ImGui::IsMouseDragging(ImGuiMouseButton button, float lock_threshold)\n{\n    ImGuiContext& g = *GImGui;\n    IM_ASSERT(button >= 0 && button < IM_ARRAYSIZE(g.IO.MouseDown));\n    if (!g.IO.MouseDown[button])\n        return false;\n    return IsMouseDragPastThreshold(button, lock_threshold);\n}\n\nImVec2 ImGui::GetMousePos()\n{\n    ImGuiContext& g = *GImGui;\n    return g.IO.MousePos;\n}\n\n// This is called TeleportMousePos() and not SetMousePos() to emphasis that setting MousePosPrev will effectively clear mouse delta as well.\n// It is expected you only call this if (io.BackendFlags & ImGuiBackendFlags_HasSetMousePos) is set and supported by backend.\nvoid ImGui::TeleportMousePos(const ImVec2& pos)\n{\n    ImGuiContext& g = *GImGui;\n    g.IO.MousePos = g.IO.MousePosPrev = pos;\n    g.IO.MouseDelta = ImVec2(0.0f, 0.0f);\n    g.IO.WantSetMousePos = true;\n    //IMGUI_DEBUG_LOG_IO(\"TeleportMousePos: (%.1f,%.1f)\\n\", io.MousePos.x, io.MousePos.y);\n}\n\n// NB: prefer to call right after BeginPopup(). At the time Selectable/MenuItem is activated, the popup is already closed!\nImVec2 ImGui::GetMousePosOnOpeningCurrentPopup()\n{\n    ImGuiContext& g = *GImGui;\n    if (g.BeginPopupStack.Size > 0)\n        return g.OpenPopupStack[g.BeginPopupStack.Size - 1].OpenMousePos;\n    return g.IO.MousePos;\n}\n\n// We typically use ImVec2(-FLT_MAX,-FLT_MAX) to denote an invalid mouse position.\nbool ImGui::IsMousePosValid(const ImVec2* mouse_pos)\n{\n    // The assert is only to silence a false-positive in XCode Static Analysis.\n    // Because GImGui is not dereferenced in every code path, the static analyzer assume that it may be NULL (which it doesn't for other functions).\n    IM_ASSERT(GImGui != NULL);\n    const float MOUSE_INVALID = -256000.0f;\n    ImVec2 p = mouse_pos ? *mouse_pos : GImGui->IO.MousePos;\n    return p.x >= MOUSE_INVALID && p.y >= MOUSE_INVALID;\n}\n\n// [WILL OBSOLETE] This was designed for backends, but prefer having backend maintain a mask of held mouse buttons, because upcoming input queue system will make this invalid.\nbool ImGui::IsAnyMouseDown()\n{\n    ImGuiContext& g = *GImGui;\n    for (int n = 0; n < IM_ARRAYSIZE(g.IO.MouseDown); n++)\n        if (g.IO.MouseDown[n])\n            return true;\n    return false;\n}\n\n// Return the delta from the initial clicking position while the mouse button is clicked or was just released.\n// This is locked and return 0.0f until the mouse moves past a distance threshold at least once.\n// NB: This is only valid if IsMousePosValid(). backends in theory should always keep mouse position valid when dragging even outside the client window.\nImVec2 ImGui::GetMouseDragDelta(ImGuiMouseButton button, float lock_threshold)\n{\n    ImGuiContext& g = *GImGui;\n    IM_ASSERT(button >= 0 && button < IM_ARRAYSIZE(g.IO.MouseDown));\n    if (lock_threshold < 0.0f)\n        lock_threshold = g.IO.MouseDragThreshold;\n    if (g.IO.MouseDown[button] || g.IO.MouseReleased[button])\n        if (g.IO.MouseDragMaxDistanceSqr[button] >= lock_threshold * lock_threshold)\n            if (IsMousePosValid(&g.IO.MousePos) && IsMousePosValid(&g.IO.MouseClickedPos[button]))\n                return g.IO.MousePos - g.IO.MouseClickedPos[button];\n    return ImVec2(0.0f, 0.0f);\n}\n\nvoid ImGui::ResetMouseDragDelta(ImGuiMouseButton button)\n{\n    ImGuiContext& g = *GImGui;\n    IM_ASSERT(button >= 0 && button < IM_ARRAYSIZE(g.IO.MouseDown));\n    // NB: We don't need to reset g.IO.MouseDragMaxDistanceSqr\n    g.IO.MouseClickedPos[button] = g.IO.MousePos;\n}\n\n// Get desired mouse cursor shape.\n// Important: this is meant to be used by a platform backend, it is reset in ImGui::NewFrame(),\n// updated during the frame, and locked in EndFrame()/Render().\n// If you use software rendering by setting io.MouseDrawCursor then Dear ImGui will render those for you\nImGuiMouseCursor ImGui::GetMouseCursor()\n{\n    ImGuiContext& g = *GImGui;\n    return g.MouseCursor;\n}\n\n// We intentionally accept values of ImGuiMouseCursor that are outside our bounds, in case users needs to hack-in a custom cursor value.\n// Custom cursors may be handled by custom backends. If you are using a standard backend and want to hack in a custom cursor, you may\n// handle it before the backend _NewFrame() call and temporarily set ImGuiConfigFlags_NoMouseCursorChange during the backend _NewFrame() call.\nvoid ImGui::SetMouseCursor(ImGuiMouseCursor cursor_type)\n{\n    ImGuiContext& g = *GImGui;\n    g.MouseCursor = cursor_type;\n}\n\nstatic void UpdateAliasKey(ImGuiKey key, bool v, float analog_value)\n{\n    IM_ASSERT(ImGui::IsAliasKey(key));\n    ImGuiKeyData* key_data = ImGui::GetKeyData(key);\n    key_data->Down = v;\n    key_data->AnalogValue = analog_value;\n}\n\n// [Internal] Do not use directly\nstatic ImGuiKeyChord GetMergedModsFromKeys()\n{\n    ImGuiKeyChord mods = 0;\n    if (ImGui::IsKeyDown(ImGuiMod_Ctrl))     { mods |= ImGuiMod_Ctrl; }\n    if (ImGui::IsKeyDown(ImGuiMod_Shift))    { mods |= ImGuiMod_Shift; }\n    if (ImGui::IsKeyDown(ImGuiMod_Alt))      { mods |= ImGuiMod_Alt; }\n    if (ImGui::IsKeyDown(ImGuiMod_Super))    { mods |= ImGuiMod_Super; }\n    return mods;\n}\n\nstatic void ImGui::UpdateKeyboardInputs()\n{\n    ImGuiContext& g = *GImGui;\n    ImGuiIO& io = g.IO;\n\n    if (io.ConfigFlags & ImGuiConfigFlags_NoKeyboard)\n        io.ClearInputKeys();\n\n    // Update aliases\n    for (int n = 0; n < ImGuiMouseButton_COUNT; n++)\n        UpdateAliasKey(MouseButtonToKey(n), io.MouseDown[n], io.MouseDown[n] ? 1.0f : 0.0f);\n    UpdateAliasKey(ImGuiKey_MouseWheelX, io.MouseWheelH != 0.0f, io.MouseWheelH);\n    UpdateAliasKey(ImGuiKey_MouseWheelY, io.MouseWheel != 0.0f, io.MouseWheel);\n\n    // Synchronize io.KeyMods and io.KeyCtrl/io.KeyShift/etc. values.\n    // - New backends (1.87+): send io.AddKeyEvent(ImGuiMod_XXX) ->                                      -> (here) deriving io.KeyMods + io.KeyXXX from key array.\n    // - Legacy backends:      set io.KeyXXX bools               -> (above) set key array from io.KeyXXX -> (here) deriving io.KeyMods + io.KeyXXX from key array.\n    // So with legacy backends the 4 values will do a unnecessary back-and-forth but it makes the code simpler and future facing.\n    const ImGuiKeyChord prev_key_mods = io.KeyMods;\n    io.KeyMods = GetMergedModsFromKeys();\n    io.KeyCtrl = (io.KeyMods & ImGuiMod_Ctrl) != 0;\n    io.KeyShift = (io.KeyMods & ImGuiMod_Shift) != 0;\n    io.KeyAlt = (io.KeyMods & ImGuiMod_Alt) != 0;\n    io.KeySuper = (io.KeyMods & ImGuiMod_Super) != 0;\n    if (prev_key_mods != io.KeyMods)\n        g.LastKeyModsChangeTime = g.Time;\n    if (prev_key_mods != io.KeyMods && prev_key_mods == 0)\n        g.LastKeyModsChangeFromNoneTime = g.Time;\n\n    // Clear gamepad data if disabled\n    if ((io.BackendFlags & ImGuiBackendFlags_HasGamepad) == 0)\n        for (int key = ImGuiKey_Gamepad_BEGIN; key < ImGuiKey_Gamepad_END; key++)\n        {\n            io.KeysData[key - ImGuiKey_NamedKey_BEGIN].Down = false;\n            io.KeysData[key - ImGuiKey_NamedKey_BEGIN].AnalogValue = 0.0f;\n        }\n\n    // Update keys\n    for (int key = ImGuiKey_NamedKey_BEGIN; key < ImGuiKey_NamedKey_END; key++)\n    {\n        ImGuiKeyData* key_data = &io.KeysData[key - ImGuiKey_NamedKey_BEGIN];\n        key_data->DownDurationPrev = key_data->DownDuration;\n        key_data->DownDuration = key_data->Down ? (key_data->DownDuration < 0.0f ? 0.0f : key_data->DownDuration + io.DeltaTime) : -1.0f;\n        if (key_data->DownDuration == 0.0f)\n        {\n            if (IsKeyboardKey((ImGuiKey)key))\n                g.LastKeyboardKeyPressTime = g.Time;\n            else if (key == ImGuiKey_ReservedForModCtrl || key == ImGuiKey_ReservedForModShift || key == ImGuiKey_ReservedForModAlt || key == ImGuiKey_ReservedForModSuper)\n                g.LastKeyboardKeyPressTime = g.Time;\n        }\n    }\n\n    // Update keys/input owner (named keys only): one entry per key\n    for (ImGuiKey key = ImGuiKey_NamedKey_BEGIN; key < ImGuiKey_NamedKey_END; key = (ImGuiKey)(key + 1))\n    {\n        ImGuiKeyData* key_data = &io.KeysData[key - ImGuiKey_NamedKey_BEGIN];\n        ImGuiKeyOwnerData* owner_data = &g.KeysOwnerData[key - ImGuiKey_NamedKey_BEGIN];\n        owner_data->OwnerCurr = owner_data->OwnerNext;\n        if (!key_data->Down) // Important: ownership is released on the frame after a release. Ensure a 'MouseDown -> CloseWindow -> MouseUp' chain doesn't lead to someone else seeing the MouseUp.\n            owner_data->OwnerNext = ImGuiKeyOwner_NoOwner;\n        owner_data->LockThisFrame = owner_data->LockUntilRelease = owner_data->LockUntilRelease && key_data->Down;  // Clear LockUntilRelease when key is not Down anymore\n    }\n\n    // Update key routing (for e.g. shortcuts)\n    UpdateKeyRoutingTable(&g.KeysRoutingTable);\n}\n\nstatic void ImGui::UpdateMouseInputs()\n{\n    ImGuiContext& g = *GImGui;\n    ImGuiIO& io = g.IO;\n\n    // Mouse Wheel swapping flag\n    // As a standard behavior holding SHIFT while using Vertical Mouse Wheel triggers Horizontal scroll instead\n    // - We avoid doing it on OSX as it the OS input layer handles this already.\n    // - FIXME: However this means when running on OSX over Emscripten, Shift+WheelY will incur two swapping (1 in OS, 1 here), canceling the feature.\n    // - FIXME: When we can distinguish e.g. touchpad scroll events from mouse ones, we'll set this accordingly based on input source.\n    io.MouseWheelRequestAxisSwap = io.KeyShift && !io.ConfigMacOSXBehaviors;\n\n    // Round mouse position to avoid spreading non-rounded position (e.g. UpdateManualResize doesn't support them well)\n    if (IsMousePosValid(&io.MousePos))\n        io.MousePos = g.MouseLastValidPos = ImFloor(io.MousePos);\n\n    // If mouse just appeared or disappeared (usually denoted by -FLT_MAX components) we cancel out movement in MouseDelta\n    if (IsMousePosValid(&io.MousePos) && IsMousePosValid(&io.MousePosPrev))\n        io.MouseDelta = io.MousePos - io.MousePosPrev;\n    else\n        io.MouseDelta = ImVec2(0.0f, 0.0f);\n\n    // Update stationary timer.\n    // FIXME: May need to rework again to have some tolerance for occasional small movement, while being functional on high-framerates.\n    const float mouse_stationary_threshold = (io.MouseSource == ImGuiMouseSource_Mouse) ? 2.0f : 3.0f; // Slightly higher threshold for ImGuiMouseSource_TouchScreen/ImGuiMouseSource_Pen, may need rework.\n    const bool mouse_stationary = (ImLengthSqr(io.MouseDelta) <= mouse_stationary_threshold * mouse_stationary_threshold);\n    g.MouseStationaryTimer = mouse_stationary ? (g.MouseStationaryTimer + io.DeltaTime) : 0.0f;\n    //IMGUI_DEBUG_LOG(\"%.4f\\n\", g.MouseStationaryTimer);\n\n    // If mouse moved we re-enable mouse hovering in case it was disabled by keyboard/gamepad. In theory should use a >0.0f threshold but would need to reset in everywhere we set this to true.\n    if (io.MouseDelta.x != 0.0f || io.MouseDelta.y != 0.0f)\n        g.NavHighlightItemUnderNav = false;\n\n    for (int i = 0; i < IM_ARRAYSIZE(io.MouseDown); i++)\n    {\n        io.MouseClicked[i] = io.MouseDown[i] && io.MouseDownDuration[i] < 0.0f;\n        io.MouseClickedCount[i] = 0; // Will be filled below\n        io.MouseReleased[i] = !io.MouseDown[i] && io.MouseDownDuration[i] >= 0.0f;\n        if (io.MouseReleased[i])\n            io.MouseReleasedTime[i] = g.Time;\n        io.MouseDownDurationPrev[i] = io.MouseDownDuration[i];\n        io.MouseDownDuration[i] = io.MouseDown[i] ? (io.MouseDownDuration[i] < 0.0f ? 0.0f : io.MouseDownDuration[i] + io.DeltaTime) : -1.0f;\n        if (io.MouseClicked[i])\n        {\n            bool is_repeated_click = false;\n            if ((float)(g.Time - io.MouseClickedTime[i]) < io.MouseDoubleClickTime)\n            {\n                ImVec2 delta_from_click_pos = IsMousePosValid(&io.MousePos) ? (io.MousePos - io.MouseClickedPos[i]) : ImVec2(0.0f, 0.0f);\n                if (ImLengthSqr(delta_from_click_pos) < io.MouseDoubleClickMaxDist * io.MouseDoubleClickMaxDist)\n                    is_repeated_click = true;\n            }\n            if (is_repeated_click)\n                io.MouseClickedLastCount[i]++;\n            else\n                io.MouseClickedLastCount[i] = 1;\n            io.MouseClickedTime[i] = g.Time;\n            io.MouseClickedPos[i] = io.MousePos;\n            io.MouseClickedCount[i] = io.MouseClickedLastCount[i];\n            io.MouseDragMaxDistanceSqr[i] = 0.0f;\n        }\n        else if (io.MouseDown[i])\n        {\n            // Maintain the maximum distance we reaching from the initial click position, which is used with dragging threshold\n            float delta_sqr_click_pos = IsMousePosValid(&io.MousePos) ? ImLengthSqr(io.MousePos - io.MouseClickedPos[i]) : 0.0f;\n            io.MouseDragMaxDistanceSqr[i] = ImMax(io.MouseDragMaxDistanceSqr[i], delta_sqr_click_pos);\n        }\n\n        // We provide io.MouseDoubleClicked[] as a legacy service\n        io.MouseDoubleClicked[i] = (io.MouseClickedCount[i] == 2);\n\n        // Clicking any mouse button reactivate mouse hovering which may have been deactivated by keyboard/gamepad navigation\n        if (io.MouseClicked[i])\n            g.NavHighlightItemUnderNav = false;\n    }\n}\n\nstatic void LockWheelingWindow(ImGuiWindow* window, float wheel_amount)\n{\n    ImGuiContext& g = *GImGui;\n    if (window)\n        g.WheelingWindowReleaseTimer = ImMin(g.WheelingWindowReleaseTimer + ImAbs(wheel_amount) * WINDOWS_MOUSE_WHEEL_SCROLL_LOCK_TIMER, WINDOWS_MOUSE_WHEEL_SCROLL_LOCK_TIMER);\n    else\n        g.WheelingWindowReleaseTimer = 0.0f;\n    if (g.WheelingWindow == window)\n        return;\n    IMGUI_DEBUG_LOG_IO(\"[io] LockWheelingWindow() \\\"%s\\\"\\n\", window ? window->Name : \"NULL\");\n    g.WheelingWindow = window;\n    g.WheelingWindowRefMousePos = g.IO.MousePos;\n    if (window == NULL)\n    {\n        g.WheelingWindowStartFrame = -1;\n        g.WheelingAxisAvg = ImVec2(0.0f, 0.0f);\n    }\n}\n\nstatic ImGuiWindow* FindBestWheelingWindow(const ImVec2& wheel)\n{\n    // For each axis, find window in the hierarchy that may want to use scrolling\n    ImGuiContext& g = *GImGui;\n    ImGuiWindow* windows[2] = { NULL, NULL };\n    for (int axis = 0; axis < 2; axis++)\n        if (wheel[axis] != 0.0f)\n            for (ImGuiWindow* window = windows[axis] = g.HoveredWindow; window->Flags & ImGuiWindowFlags_ChildWindow; window = windows[axis] = window->ParentWindow)\n            {\n                // Bubble up into parent window if:\n                // - a child window doesn't allow any scrolling.\n                // - a child window has the ImGuiWindowFlags_NoScrollWithMouse flag.\n                //// - a child window doesn't need scrolling because it is already at the edge for the direction we are going in (FIXME-WIP)\n                const bool has_scrolling = (window->ScrollMax[axis] != 0.0f);\n                const bool inputs_disabled = (window->Flags & ImGuiWindowFlags_NoScrollWithMouse) && !(window->Flags & ImGuiWindowFlags_NoMouseInputs);\n                //const bool scrolling_past_limits = (wheel_v < 0.0f) ? (window->Scroll[axis] <= 0.0f) : (window->Scroll[axis] >= window->ScrollMax[axis]);\n                if (has_scrolling && !inputs_disabled) // && !scrolling_past_limits)\n                    break; // select this window\n            }\n    if (windows[0] == NULL && windows[1] == NULL)\n        return NULL;\n\n    // If there's only one window or only one axis then there's no ambiguity\n    if (windows[0] == windows[1] || windows[0] == NULL || windows[1] == NULL)\n        return windows[1] ? windows[1] : windows[0];\n\n    // If candidate are different windows we need to decide which one to prioritize\n    // - First frame: only find a winner if one axis is zero.\n    // - Subsequent frames: only find a winner when one is more than the other.\n    if (g.WheelingWindowStartFrame == -1)\n        g.WheelingWindowStartFrame = g.FrameCount;\n    if ((g.WheelingWindowStartFrame == g.FrameCount && wheel.x != 0.0f && wheel.y != 0.0f) || (g.WheelingAxisAvg.x == g.WheelingAxisAvg.y))\n    {\n        g.WheelingWindowWheelRemainder = wheel;\n        return NULL;\n    }\n    return (g.WheelingAxisAvg.x > g.WheelingAxisAvg.y) ? windows[0] : windows[1];\n}\n\n// Called by NewFrame()\nvoid ImGui::UpdateMouseWheel()\n{\n    // Reset the locked window if we move the mouse or after the timer elapses.\n    // FIXME: Ideally we could refactor to have one timer for \"changing window w/ same axis\" and a shorter timer for \"changing window or axis w/ other axis\" (#3795)\n    ImGuiContext& g = *GImGui;\n    if (g.WheelingWindow != NULL)\n    {\n        g.WheelingWindowReleaseTimer -= g.IO.DeltaTime;\n        if (IsMousePosValid() && ImLengthSqr(g.IO.MousePos - g.WheelingWindowRefMousePos) > g.IO.MouseDragThreshold * g.IO.MouseDragThreshold)\n            g.WheelingWindowReleaseTimer = 0.0f;\n        if (g.WheelingWindowReleaseTimer <= 0.0f)\n            LockWheelingWindow(NULL, 0.0f);\n    }\n\n    ImVec2 wheel;\n    wheel.x = TestKeyOwner(ImGuiKey_MouseWheelX, ImGuiKeyOwner_NoOwner) ? g.IO.MouseWheelH : 0.0f;\n    wheel.y = TestKeyOwner(ImGuiKey_MouseWheelY, ImGuiKeyOwner_NoOwner) ? g.IO.MouseWheel : 0.0f;\n\n    //IMGUI_DEBUG_LOG(\"MouseWheel X:%.3f Y:%.3f\\n\", wheel_x, wheel_y);\n    ImGuiWindow* mouse_window = g.WheelingWindow ? g.WheelingWindow : g.HoveredWindow;\n    if (!mouse_window || mouse_window->Collapsed)\n        return;\n\n    // Zoom / Scale window\n    // FIXME-OBSOLETE: This is an old feature, it still works but pretty much nobody is using it and may be best redesigned.\n    if (wheel.y != 0.0f && g.IO.KeyCtrl && g.IO.FontAllowUserScaling)\n    {\n        LockWheelingWindow(mouse_window, wheel.y);\n        ImGuiWindow* window = mouse_window;\n        const float new_font_scale = ImClamp(window->FontWindowScale + g.IO.MouseWheel * 0.10f, 0.50f, 2.50f);\n        const float scale = new_font_scale / window->FontWindowScale;\n        window->FontWindowScale = new_font_scale;\n        if (window == window->RootWindow)\n        {\n            const ImVec2 offset = window->Size * (1.0f - scale) * (g.IO.MousePos - window->Pos) / window->Size;\n            SetWindowPos(window, window->Pos + offset, 0);\n            window->Size = ImTrunc(window->Size * scale);\n            window->SizeFull = ImTrunc(window->SizeFull * scale);\n        }\n        return;\n    }\n    if (g.IO.KeyCtrl)\n        return;\n\n    // Mouse wheel scrolling\n    // Read about io.MouseWheelRequestAxisSwap and its issue on Mac+Emscripten in UpdateMouseInputs()\n    if (g.IO.MouseWheelRequestAxisSwap)\n        wheel = ImVec2(wheel.y, 0.0f);\n\n    // Maintain a rough average of moving magnitude on both axes\n    // FIXME: should by based on wall clock time rather than frame-counter\n    g.WheelingAxisAvg.x = ImExponentialMovingAverage(g.WheelingAxisAvg.x, ImAbs(wheel.x), 30);\n    g.WheelingAxisAvg.y = ImExponentialMovingAverage(g.WheelingAxisAvg.y, ImAbs(wheel.y), 30);\n\n    // In the rare situation where FindBestWheelingWindow() had to defer first frame of wheeling due to ambiguous main axis, reinject it now.\n    wheel += g.WheelingWindowWheelRemainder;\n    g.WheelingWindowWheelRemainder = ImVec2(0.0f, 0.0f);\n    if (wheel.x == 0.0f && wheel.y == 0.0f)\n        return;\n\n    // Mouse wheel scrolling: find target and apply\n    // - don't renew lock if axis doesn't apply on the window.\n    // - select a main axis when both axes are being moved.\n    if (ImGuiWindow* window = (g.WheelingWindow ? g.WheelingWindow : FindBestWheelingWindow(wheel)))\n        if (!(window->Flags & ImGuiWindowFlags_NoScrollWithMouse) && !(window->Flags & ImGuiWindowFlags_NoMouseInputs))\n        {\n            bool do_scroll[2] = { wheel.x != 0.0f && window->ScrollMax.x != 0.0f, wheel.y != 0.0f && window->ScrollMax.y != 0.0f };\n            if (do_scroll[ImGuiAxis_X] && do_scroll[ImGuiAxis_Y])\n                do_scroll[(g.WheelingAxisAvg.x > g.WheelingAxisAvg.y) ? ImGuiAxis_Y : ImGuiAxis_X] = false;\n            if (do_scroll[ImGuiAxis_X])\n            {\n                LockWheelingWindow(window, wheel.x);\n                float max_step = window->InnerRect.GetWidth() * 0.67f;\n                float scroll_step = ImTrunc(ImMin(2 * window->FontRefSize, max_step));\n                SetScrollX(window, window->Scroll.x - wheel.x * scroll_step);\n                g.WheelingWindowScrolledFrame = g.FrameCount;\n            }\n            if (do_scroll[ImGuiAxis_Y])\n            {\n                LockWheelingWindow(window, wheel.y);\n                float max_step = window->InnerRect.GetHeight() * 0.67f;\n                float scroll_step = ImTrunc(ImMin(5 * window->FontRefSize, max_step));\n                SetScrollY(window, window->Scroll.y - wheel.y * scroll_step);\n                g.WheelingWindowScrolledFrame = g.FrameCount;\n            }\n        }\n}\n\nvoid ImGui::SetNextFrameWantCaptureKeyboard(bool want_capture_keyboard)\n{\n    ImGuiContext& g = *GImGui;\n    g.WantCaptureKeyboardNextFrame = want_capture_keyboard ? 1 : 0;\n}\n\nvoid ImGui::SetNextFrameWantCaptureMouse(bool want_capture_mouse)\n{\n    ImGuiContext& g = *GImGui;\n    g.WantCaptureMouseNextFrame = want_capture_mouse ? 1 : 0;\n}\n\n#ifndef IMGUI_DISABLE_DEBUG_TOOLS\nstatic const char* GetInputSourceName(ImGuiInputSource source)\n{\n    const char* input_source_names[] = { \"None\", \"Mouse\", \"Keyboard\", \"Gamepad\" };\n    IM_ASSERT(IM_ARRAYSIZE(input_source_names) == ImGuiInputSource_COUNT && source >= 0 && source < ImGuiInputSource_COUNT);\n    return input_source_names[source];\n}\nstatic const char* GetMouseSourceName(ImGuiMouseSource source)\n{\n    const char* mouse_source_names[] = { \"Mouse\", \"TouchScreen\", \"Pen\" };\n    IM_ASSERT(IM_ARRAYSIZE(mouse_source_names) == ImGuiMouseSource_COUNT && source >= 0 && source < ImGuiMouseSource_COUNT);\n    return mouse_source_names[source];\n}\nstatic void DebugPrintInputEvent(const char* prefix, const ImGuiInputEvent* e)\n{\n    ImGuiContext& g = *GImGui;\n    if (e->Type == ImGuiInputEventType_MousePos)    { if (e->MousePos.PosX == -FLT_MAX && e->MousePos.PosY == -FLT_MAX) IMGUI_DEBUG_LOG_IO(\"[io] %s: MousePos (-FLT_MAX, -FLT_MAX)\\n\", prefix); else IMGUI_DEBUG_LOG_IO(\"[io] %s: MousePos (%.1f, %.1f) (%s)\\n\", prefix, e->MousePos.PosX, e->MousePos.PosY, GetMouseSourceName(e->MousePos.MouseSource)); return; }\n    if (e->Type == ImGuiInputEventType_MouseButton) { IMGUI_DEBUG_LOG_IO(\"[io] %s: MouseButton %d %s (%s)\\n\", prefix, e->MouseButton.Button, e->MouseButton.Down ? \"Down\" : \"Up\", GetMouseSourceName(e->MouseButton.MouseSource)); return; }\n    if (e->Type == ImGuiInputEventType_MouseWheel)  { IMGUI_DEBUG_LOG_IO(\"[io] %s: MouseWheel (%.3f, %.3f) (%s)\\n\", prefix, e->MouseWheel.WheelX, e->MouseWheel.WheelY, GetMouseSourceName(e->MouseWheel.MouseSource)); return; }\n    if (e->Type == ImGuiInputEventType_Key)         { IMGUI_DEBUG_LOG_IO(\"[io] %s: Key \\\"%s\\\" %s\\n\", prefix, ImGui::GetKeyName(e->Key.Key), e->Key.Down ? \"Down\" : \"Up\"); return; }\n    if (e->Type == ImGuiInputEventType_Text)        { IMGUI_DEBUG_LOG_IO(\"[io] %s: Text: %c (U+%08X)\\n\", prefix, e->Text.Char, e->Text.Char); return; }\n    if (e->Type == ImGuiInputEventType_Focus)       { IMGUI_DEBUG_LOG_IO(\"[io] %s: AppFocused %d\\n\", prefix, e->AppFocused.Focused); return; }\n}\n#endif\n\n// Process input queue\n// We always call this with the value of 'bool g.IO.ConfigInputTrickleEventQueue'.\n// - trickle_fast_inputs = false : process all events, turn into flattened input state (e.g. successive down/up/down/up will be lost)\n// - trickle_fast_inputs = true  : process as many events as possible (successive down/up/down/up will be trickled over several frames so nothing is lost) (new feature in 1.87)\nvoid ImGui::UpdateInputEvents(bool trickle_fast_inputs)\n{\n    ImGuiContext& g = *GImGui;\n    ImGuiIO& io = g.IO;\n\n    // Only trickle chars<>key when working with InputText()\n    // FIXME: InputText() could parse event trail?\n    // FIXME: Could specialize chars<>keys trickling rules for control keys (those not typically associated to characters)\n    const bool trickle_interleaved_nonchar_keys_and_text = (trickle_fast_inputs && g.WantTextInputNextFrame == 1);\n\n    bool mouse_moved = false, mouse_wheeled = false, key_changed = false, key_changed_nonchar = false, text_inputted = false;\n    int  mouse_button_changed = 0x00;\n    ImBitArray<ImGuiKey_NamedKey_COUNT> key_changed_mask;\n\n    int event_n = 0;\n    for (; event_n < g.InputEventsQueue.Size; event_n++)\n    {\n        ImGuiInputEvent* e = &g.InputEventsQueue[event_n];\n        if (e->Type == ImGuiInputEventType_MousePos)\n        {\n            if (g.IO.WantSetMousePos)\n                continue;\n            // Trickling Rule: Stop processing queued events if we already handled a mouse button change\n            ImVec2 event_pos(e->MousePos.PosX, e->MousePos.PosY);\n            if (trickle_fast_inputs && (mouse_button_changed != 0 || mouse_wheeled || key_changed || text_inputted))\n                break;\n            io.MousePos = event_pos;\n            io.MouseSource = e->MousePos.MouseSource;\n            mouse_moved = true;\n        }\n        else if (e->Type == ImGuiInputEventType_MouseButton)\n        {\n            // Trickling Rule: Stop processing queued events if we got multiple action on the same button\n            const ImGuiMouseButton button = e->MouseButton.Button;\n            IM_ASSERT(button >= 0 && button < ImGuiMouseButton_COUNT);\n            if (trickle_fast_inputs && ((mouse_button_changed & (1 << button)) || mouse_wheeled))\n                break;\n            if (trickle_fast_inputs && e->MouseButton.MouseSource == ImGuiMouseSource_TouchScreen && mouse_moved) // #2702: TouchScreen have no initial hover.\n                break;\n            io.MouseDown[button] = e->MouseButton.Down;\n            io.MouseSource = e->MouseButton.MouseSource;\n            mouse_button_changed |= (1 << button);\n        }\n        else if (e->Type == ImGuiInputEventType_MouseWheel)\n        {\n            // Trickling Rule: Stop processing queued events if we got multiple action on the event\n            if (trickle_fast_inputs && (mouse_moved || mouse_button_changed != 0))\n                break;\n            io.MouseWheelH += e->MouseWheel.WheelX;\n            io.MouseWheel += e->MouseWheel.WheelY;\n            io.MouseSource = e->MouseWheel.MouseSource;\n            mouse_wheeled = true;\n        }\n        else if (e->Type == ImGuiInputEventType_Key)\n        {\n            // Trickling Rule: Stop processing queued events if we got multiple action on the same button\n            if (io.ConfigFlags & ImGuiConfigFlags_NoKeyboard)\n                continue;\n            ImGuiKey key = e->Key.Key;\n            IM_ASSERT(key != ImGuiKey_None);\n            ImGuiKeyData* key_data = GetKeyData(key);\n            const int key_data_index = (int)(key_data - g.IO.KeysData);\n            if (trickle_fast_inputs && key_data->Down != e->Key.Down && (key_changed_mask.TestBit(key_data_index) || mouse_button_changed != 0))\n                break;\n\n            const bool key_is_potentially_for_char_input = IsKeyChordPotentiallyCharInput(GetMergedModsFromKeys() | key);\n            if (trickle_interleaved_nonchar_keys_and_text && (text_inputted && !key_is_potentially_for_char_input))\n                break;\n\n            key_data->Down = e->Key.Down;\n            key_data->AnalogValue = e->Key.AnalogValue;\n            key_changed = true;\n            key_changed_mask.SetBit(key_data_index);\n            if (trickle_interleaved_nonchar_keys_and_text && !key_is_potentially_for_char_input)\n                key_changed_nonchar = true;\n        }\n        else if (e->Type == ImGuiInputEventType_Text)\n        {\n            if (io.ConfigFlags & ImGuiConfigFlags_NoKeyboard)\n                continue;\n            // Trickling Rule: Stop processing queued events if keys/mouse have been interacted with\n            if (trickle_fast_inputs && (mouse_button_changed != 0 || mouse_moved || mouse_wheeled))\n                break;\n            if (trickle_interleaved_nonchar_keys_and_text && key_changed_nonchar)\n                break;\n            unsigned int c = e->Text.Char;\n            io.InputQueueCharacters.push_back(c <= IM_UNICODE_CODEPOINT_MAX ? (ImWchar)c : IM_UNICODE_CODEPOINT_INVALID);\n            if (trickle_interleaved_nonchar_keys_and_text)\n                text_inputted = true;\n        }\n        else if (e->Type == ImGuiInputEventType_Focus)\n        {\n            // We intentionally overwrite this and process in NewFrame(), in order to give a chance\n            // to multi-viewports backends to queue AddFocusEvent(false) + AddFocusEvent(true) in same frame.\n            const bool focus_lost = !e->AppFocused.Focused;\n            io.AppFocusLost = focus_lost;\n        }\n        else\n        {\n            IM_ASSERT(0 && \"Unknown event!\");\n        }\n    }\n\n    // Record trail (for domain-specific applications wanting to access a precise trail)\n    //if (event_n != 0) IMGUI_DEBUG_LOG_IO(\"Processed: %d / Remaining: %d\\n\", event_n, g.InputEventsQueue.Size - event_n);\n    for (int n = 0; n < event_n; n++)\n        g.InputEventsTrail.push_back(g.InputEventsQueue[n]);\n\n    // [DEBUG]\n#ifndef IMGUI_DISABLE_DEBUG_TOOLS\n    if (event_n != 0 && (g.DebugLogFlags & ImGuiDebugLogFlags_EventIO))\n        for (int n = 0; n < g.InputEventsQueue.Size; n++)\n            DebugPrintInputEvent(n < event_n ? \"Processed\" : \"Remaining\", &g.InputEventsQueue[n]);\n#endif\n\n    // Remaining events will be processed on the next frame\n    if (event_n == g.InputEventsQueue.Size)\n        g.InputEventsQueue.resize(0);\n    else\n        g.InputEventsQueue.erase(g.InputEventsQueue.Data, g.InputEventsQueue.Data + event_n);\n\n    // Clear buttons state when focus is lost\n    // - this is useful so e.g. releasing Alt after focus loss on Alt-Tab doesn't trigger the Alt menu toggle.\n    // - we clear in EndFrame() and not now in order allow application/user code polling this flag\n    //   (e.g. custom backend may want to clear additional data, custom widgets may want to react with a \"canceling\" event).\n    if (g.IO.AppFocusLost)\n    {\n        g.IO.ClearInputKeys();\n        g.IO.ClearInputMouse();\n    }\n}\n\nImGuiID ImGui::GetKeyOwner(ImGuiKey key)\n{\n    if (!IsNamedKeyOrMod(key))\n        return ImGuiKeyOwner_NoOwner;\n\n    ImGuiContext& g = *GImGui;\n    ImGuiKeyOwnerData* owner_data = GetKeyOwnerData(&g, key);\n    ImGuiID owner_id = owner_data->OwnerCurr;\n\n    if (g.ActiveIdUsingAllKeyboardKeys && owner_id != g.ActiveId && owner_id != ImGuiKeyOwner_Any)\n        if (key >= ImGuiKey_Keyboard_BEGIN && key < ImGuiKey_Keyboard_END)\n            return ImGuiKeyOwner_NoOwner;\n\n    return owner_id;\n}\n\n// TestKeyOwner(..., ID)   : (owner == None || owner == ID)\n// TestKeyOwner(..., None) : (owner == None)\n// TestKeyOwner(..., Any)  : no owner test\n// All paths are also testing for key not being locked, for the rare cases that key have been locked with using ImGuiInputFlags_LockXXX flags.\nbool ImGui::TestKeyOwner(ImGuiKey key, ImGuiID owner_id)\n{\n    if (!IsNamedKeyOrMod(key))\n        return true;\n\n    ImGuiContext& g = *GImGui;\n    if (g.ActiveIdUsingAllKeyboardKeys && owner_id != g.ActiveId && owner_id != ImGuiKeyOwner_Any)\n        if (key >= ImGuiKey_Keyboard_BEGIN && key < ImGuiKey_Keyboard_END)\n            return false;\n\n    ImGuiKeyOwnerData* owner_data = GetKeyOwnerData(&g, key);\n    if (owner_id == ImGuiKeyOwner_Any)\n        return (owner_data->LockThisFrame == false);\n\n    // Note: SetKeyOwner() sets OwnerCurr. It is not strictly required for most mouse routing overlap (because of ActiveId/HoveredId\n    // are acting as filter before this has a chance to filter), but sane as soon as user tries to look into things.\n    // Setting OwnerCurr in SetKeyOwner() is more consistent than testing OwnerNext here: would be inconsistent with getter and other functions.\n    if (owner_data->OwnerCurr != owner_id)\n    {\n        if (owner_data->LockThisFrame)\n            return false;\n        if (owner_data->OwnerCurr != ImGuiKeyOwner_NoOwner)\n            return false;\n    }\n\n    return true;\n}\n\n// _LockXXX flags are useful to lock keys away from code which is not input-owner aware.\n// When using _LockXXX flags, you can use ImGuiKeyOwner_Any to lock keys from everyone.\n// - SetKeyOwner(..., None)              : clears owner\n// - SetKeyOwner(..., Any, !Lock)        : illegal (assert)\n// - SetKeyOwner(..., Any or None, Lock) : set lock\nvoid ImGui::SetKeyOwner(ImGuiKey key, ImGuiID owner_id, ImGuiInputFlags flags)\n{\n    ImGuiContext& g = *GImGui;\n    IM_ASSERT(IsNamedKeyOrMod(key) && (owner_id != ImGuiKeyOwner_Any || (flags & (ImGuiInputFlags_LockThisFrame | ImGuiInputFlags_LockUntilRelease)))); // Can only use _Any with _LockXXX flags (to eat a key away without an ID to retrieve it)\n    IM_ASSERT((flags & ~ImGuiInputFlags_SupportedBySetKeyOwner) == 0); // Passing flags not supported by this function!\n    //IMGUI_DEBUG_LOG(\"SetKeyOwner(%s, owner_id=0x%08X, flags=%08X)\\n\", GetKeyName(key), owner_id, flags);\n\n    ImGuiKeyOwnerData* owner_data = GetKeyOwnerData(&g, key);\n    owner_data->OwnerCurr = owner_data->OwnerNext = owner_id;\n\n    // We cannot lock by default as it would likely break lots of legacy code.\n    // In the case of using LockUntilRelease while key is not down we still lock during the frame (no key_data->Down test)\n    owner_data->LockUntilRelease = (flags & ImGuiInputFlags_LockUntilRelease) != 0;\n    owner_data->LockThisFrame = (flags & ImGuiInputFlags_LockThisFrame) != 0 || (owner_data->LockUntilRelease);\n}\n\n// Rarely used helper\nvoid ImGui::SetKeyOwnersForKeyChord(ImGuiKeyChord key_chord, ImGuiID owner_id, ImGuiInputFlags flags)\n{\n    if (key_chord & ImGuiMod_Ctrl)      { SetKeyOwner(ImGuiMod_Ctrl, owner_id, flags); }\n    if (key_chord & ImGuiMod_Shift)     { SetKeyOwner(ImGuiMod_Shift, owner_id, flags); }\n    if (key_chord & ImGuiMod_Alt)       { SetKeyOwner(ImGuiMod_Alt, owner_id, flags); }\n    if (key_chord & ImGuiMod_Super)     { SetKeyOwner(ImGuiMod_Super, owner_id, flags); }\n    if (key_chord & ~ImGuiMod_Mask_)    { SetKeyOwner((ImGuiKey)(key_chord & ~ImGuiMod_Mask_), owner_id, flags); }\n}\n\n// This is more or less equivalent to:\n//   if (IsItemHovered() || IsItemActive())\n//       SetKeyOwner(key, GetItemID());\n// Extensive uses of that (e.g. many calls for a single item) may want to manually perform the tests once and then call SetKeyOwner() multiple times.\n// More advanced usage scenarios may want to call SetKeyOwner() manually based on different condition.\n// Worth noting is that only one item can be hovered and only one item can be active, therefore this usage pattern doesn't need to bother with routing and priority.\nvoid ImGui::SetItemKeyOwner(ImGuiKey key, ImGuiInputFlags flags)\n{\n    ImGuiContext& g = *GImGui;\n    ImGuiID id = g.LastItemData.ID;\n    if (id == 0 || (g.HoveredId != id && g.ActiveId != id))\n        return;\n    if ((flags & ImGuiInputFlags_CondMask_) == 0)\n        flags |= ImGuiInputFlags_CondDefault_;\n    if ((g.HoveredId == id && (flags & ImGuiInputFlags_CondHovered)) || (g.ActiveId == id && (flags & ImGuiInputFlags_CondActive)))\n    {\n        IM_ASSERT((flags & ~ImGuiInputFlags_SupportedBySetItemKeyOwner) == 0); // Passing flags not supported by this function!\n        SetKeyOwner(key, id, flags & ~ImGuiInputFlags_CondMask_);\n    }\n}\n\nvoid ImGui::SetItemKeyOwner(ImGuiKey key)\n{\n    SetItemKeyOwner(key, ImGuiInputFlags_None);\n}\n\n// This is the only public API until we expose owner_id versions of the API as replacements.\nbool ImGui::IsKeyChordPressed(ImGuiKeyChord key_chord)\n{\n    return IsKeyChordPressed(key_chord, ImGuiInputFlags_None, ImGuiKeyOwner_Any);\n}\n\n// This is equivalent to comparing KeyMods + doing a IsKeyPressed()\nbool ImGui::IsKeyChordPressed(ImGuiKeyChord key_chord, ImGuiInputFlags flags, ImGuiID owner_id)\n{\n    ImGuiContext& g = *GImGui;\n    key_chord = FixupKeyChord(key_chord);\n    ImGuiKey mods = (ImGuiKey)(key_chord & ImGuiMod_Mask_);\n    if (g.IO.KeyMods != mods)\n        return false;\n\n    // Special storage location for mods\n    ImGuiKey key = (ImGuiKey)(key_chord & ~ImGuiMod_Mask_);\n    if (key == ImGuiKey_None)\n        key = ConvertSingleModFlagToKey(mods);\n    if (!IsKeyPressed(key, (flags & ImGuiInputFlags_RepeatMask_), owner_id))\n        return false;\n    return true;\n}\n\nvoid ImGui::SetNextItemShortcut(ImGuiKeyChord key_chord, ImGuiInputFlags flags)\n{\n    ImGuiContext& g = *GImGui;\n    g.NextItemData.HasFlags |= ImGuiNextItemDataFlags_HasShortcut;\n    g.NextItemData.Shortcut = key_chord;\n    g.NextItemData.ShortcutFlags = flags;\n}\n\n// Called from within ItemAdd: at this point we can read from NextItemData and write to LastItemData\nvoid ImGui::ItemHandleShortcut(ImGuiID id)\n{\n    ImGuiContext& g = *GImGui;\n    ImGuiInputFlags flags = g.NextItemData.ShortcutFlags;\n    IM_ASSERT((flags & ~ImGuiInputFlags_SupportedBySetNextItemShortcut) == 0); // Passing flags not supported by SetNextItemShortcut()!\n\n    if (g.LastItemData.ItemFlags & ImGuiItemFlags_Disabled)\n        return;\n    if (flags & ImGuiInputFlags_Tooltip)\n    {\n        g.LastItemData.StatusFlags |= ImGuiItemStatusFlags_HasShortcut;\n        g.LastItemData.Shortcut = g.NextItemData.Shortcut;\n    }\n    if (!Shortcut(g.NextItemData.Shortcut, flags & ImGuiInputFlags_SupportedByShortcut, id) || g.NavActivateId != 0)\n        return;\n\n    // FIXME: Generalize Activation queue?\n    g.NavActivateId = id; // Will effectively disable clipping.\n    g.NavActivateFlags = ImGuiActivateFlags_PreferInput | ImGuiActivateFlags_FromShortcut;\n    //if (g.ActiveId == 0 || g.ActiveId == id)\n    g.NavActivateDownId = g.NavActivatePressedId = id;\n    NavHighlightActivated(id);\n}\n\nbool ImGui::Shortcut(ImGuiKeyChord key_chord, ImGuiInputFlags flags)\n{\n    return Shortcut(key_chord, flags, ImGuiKeyOwner_Any);\n}\n\nbool ImGui::Shortcut(ImGuiKeyChord key_chord, ImGuiInputFlags flags, ImGuiID owner_id)\n{\n    ImGuiContext& g = *GImGui;\n    //IMGUI_DEBUG_LOG(\"Shortcut(%s, flags=%X, owner_id=0x%08X)\\n\", GetKeyChordName(key_chord, g.TempBuffer.Data, g.TempBuffer.Size), flags, owner_id);\n\n    // When using (owner_id == 0/Any): SetShortcutRouting() will use CurrentFocusScopeId and filter with this, so IsKeyPressed() is fine with he 0/Any.\n    if ((flags & ImGuiInputFlags_RouteTypeMask_) == 0)\n        flags |= ImGuiInputFlags_RouteFocused;\n\n    // Using 'owner_id == ImGuiKeyOwner_Any/0': auto-assign an owner based on current focus scope (each window has its focus scope by default)\n    // Effectively makes Shortcut() always input-owner aware.\n    if (owner_id == ImGuiKeyOwner_Any || owner_id == ImGuiKeyOwner_NoOwner)\n        owner_id = GetRoutingIdFromOwnerId(owner_id);\n\n    if (g.CurrentItemFlags & ImGuiItemFlags_Disabled)\n        return false;\n\n    // Submit route\n    if (!SetShortcutRouting(key_chord, flags, owner_id))\n        return false;\n\n    // Default repeat behavior for Shortcut()\n    // So e.g. pressing Ctrl+W and releasing Ctrl while holding W will not trigger the W shortcut.\n    if ((flags & ImGuiInputFlags_Repeat) != 0 && (flags & ImGuiInputFlags_RepeatUntilMask_) == 0)\n        flags |= ImGuiInputFlags_RepeatUntilKeyModsChange;\n\n    if (!IsKeyChordPressed(key_chord, flags, owner_id))\n        return false;\n\n    // Claim mods during the press\n    SetKeyOwnersForKeyChord(key_chord & ImGuiMod_Mask_, owner_id);\n\n    IM_ASSERT((flags & ~ImGuiInputFlags_SupportedByShortcut) == 0); // Passing flags not supported by this function!\n    return true;\n}\n\n//-----------------------------------------------------------------------------\n// [SECTION] ERROR CHECKING, STATE RECOVERY\n//-----------------------------------------------------------------------------\n// - DebugCheckVersionAndDataLayout() (called via IMGUI_CHECKVERSION() macros)\n// - ErrorCheckUsingSetCursorPosToExtendParentBoundaries()\n// - ErrorCheckNewFrameSanityChecks()\n// - ErrorCheckEndFrameSanityChecks()\n// - ErrorRecoveryStoreState()\n// - ErrorRecoveryTryToRecoverState()\n// - ErrorRecoveryTryToRecoverWindowState()\n// - ErrorLog()\n//-----------------------------------------------------------------------------\n\n// Verify ABI compatibility between caller code and compiled version of Dear ImGui. This helps detects some build issues.\n// Called by IMGUI_CHECKVERSION().\n// Verify that the type sizes are matching between the calling file's compilation unit and imgui.cpp's compilation unit\n// If this triggers you have mismatched headers and compiled code versions.\n// - It could be because of a build issue (using new headers with old compiled code)\n// - It could be because of mismatched configuration #define, compilation settings, packing pragma etc.\n//   THE CONFIGURATION SETTINGS MENTIONED IN imconfig.h MUST BE SET FOR ALL COMPILATION UNITS INVOLVED WITH DEAR IMGUI.\n//   Which is why it is required you put them in your imconfig file (and NOT only before including imgui.h).\n//   Otherwise it is possible that different compilation units would see different structure layout.\n//   If you don't want to modify imconfig.h you can use the IMGUI_USER_CONFIG define to change filename.\nbool ImGui::DebugCheckVersionAndDataLayout(const char* version, size_t sz_io, size_t sz_style, size_t sz_vec2, size_t sz_vec4, size_t sz_vert, size_t sz_idx)\n{\n    bool error = false;\n    if (strcmp(version, IMGUI_VERSION) != 0) { error = true; IM_ASSERT(strcmp(version, IMGUI_VERSION) == 0 && \"Mismatched version string!\"); }\n    if (sz_io    != sizeof(ImGuiIO))    { error = true; IM_ASSERT(sz_io == sizeof(ImGuiIO) && \"Mismatched struct layout!\"); }\n    if (sz_style != sizeof(ImGuiStyle)) { error = true; IM_ASSERT(sz_style == sizeof(ImGuiStyle) && \"Mismatched struct layout!\"); }\n    if (sz_vec2  != sizeof(ImVec2))     { error = true; IM_ASSERT(sz_vec2 == sizeof(ImVec2) && \"Mismatched struct layout!\"); }\n    if (sz_vec4  != sizeof(ImVec4))     { error = true; IM_ASSERT(sz_vec4 == sizeof(ImVec4) && \"Mismatched struct layout!\"); }\n    if (sz_vert  != sizeof(ImDrawVert)) { error = true; IM_ASSERT(sz_vert == sizeof(ImDrawVert) && \"Mismatched struct layout!\"); }\n    if (sz_idx   != sizeof(ImDrawIdx))  { error = true; IM_ASSERT(sz_idx == sizeof(ImDrawIdx) && \"Mismatched struct layout!\"); }\n    return !error;\n}\n\n// Until 1.89 (IMGUI_VERSION_NUM < 18814) it was legal to use SetCursorPos() to extend the boundary of a parent (e.g. window or table cell)\n// This is causing issues and ambiguity and we need to retire that.\n// See https://github.com/ocornut/imgui/issues/5548 for more details.\n// [Scenario 1]\n//  Previously this would make the window content size ~200x200:\n//    Begin(...) + SetCursorScreenPos(GetCursorScreenPos() + ImVec2(200,200)) + End();  // NOT OK\n//  Instead, please submit an item:\n//    Begin(...) + SetCursorScreenPos(GetCursorScreenPos() + ImVec2(200,200)) + Dummy(ImVec2(0,0)) + End(); // OK\n//  Alternative:\n//    Begin(...) + Dummy(ImVec2(200,200)) + End(); // OK\n// [Scenario 2]\n//  For reference this is one of the issue what we aim to fix with this change:\n//    BeginGroup() + SomeItem(\"foobar\") + SetCursorScreenPos(GetCursorScreenPos()) + EndGroup()\n//  The previous logic made SetCursorScreenPos(GetCursorScreenPos()) have a side-effect! It would erroneously incorporate ItemSpacing.y after the item into content size, making the group taller!\n//  While this code is a little twisted, no-one would expect SetXXX(GetXXX()) to have a side-effect. Using vertical alignment patterns could trigger this issue.\nvoid ImGui::ErrorCheckUsingSetCursorPosToExtendParentBoundaries()\n{\n    ImGuiContext& g = *GImGui;\n    ImGuiWindow* window = g.CurrentWindow;\n    IM_ASSERT(window->DC.IsSetPos);\n    window->DC.IsSetPos = false;\n#ifdef IMGUI_DISABLE_OBSOLETE_FUNCTIONS\n    if (window->DC.CursorPos.x <= window->DC.CursorMaxPos.x && window->DC.CursorPos.y <= window->DC.CursorMaxPos.y)\n        return;\n    if (window->SkipItems)\n        return;\n    IM_ASSERT(0 && \"Code uses SetCursorPos()/SetCursorScreenPos() to extend window/parent boundaries. Please submit an item e.g. Dummy() to validate extent.\");\n#else\n    window->DC.CursorMaxPos = ImMax(window->DC.CursorMaxPos, window->DC.CursorPos);\n#endif\n}\n\nstatic void ImGui::ErrorCheckNewFrameSanityChecks()\n{\n    ImGuiContext& g = *GImGui;\n\n    // Check user IM_ASSERT macro\n    // (IF YOU GET A WARNING OR COMPILE ERROR HERE: it means your assert macro is incorrectly defined!\n    //  If your macro uses multiple statements, it NEEDS to be surrounded by a 'do { ... } while (0)' block.\n    //  This is a common C/C++ idiom to allow multiple statements macros to be used in control flow blocks.)\n    // #define IM_ASSERT(EXPR)   if (SomeCode(EXPR)) SomeMoreCode();                    // Wrong!\n    // #define IM_ASSERT(EXPR)   do { if (SomeCode(EXPR)) SomeMoreCode(); } while (0)   // Correct!\n    if (true) IM_ASSERT(1); else IM_ASSERT(0);\n\n    // Emscripten backends are often imprecise in their submission of DeltaTime. (#6114, #3644)\n    // Ideally the Emscripten app/backend should aim to fix or smooth this value and avoid feeding zero, but we tolerate it.\n#ifdef __EMSCRIPTEN__\n    if (g.IO.DeltaTime <= 0.0f && g.FrameCount > 0)\n        g.IO.DeltaTime = 0.00001f;\n#endif\n\n    // Check user data\n    // (We pass an error message in the assert expression to make it visible to programmers who are not using a debugger, as most assert handlers display their argument)\n    IM_ASSERT(g.Initialized);\n    IM_ASSERT((g.IO.DeltaTime > 0.0f || g.FrameCount == 0)              && \"Need a positive DeltaTime!\");\n    IM_ASSERT((g.FrameCount == 0 || g.FrameCountEnded == g.FrameCount)  && \"Forgot to call Render() or EndFrame() at the end of the previous frame?\");\n    IM_ASSERT(g.IO.DisplaySize.x >= 0.0f && g.IO.DisplaySize.y >= 0.0f  && \"Invalid DisplaySize value!\");\n    IM_ASSERT(g.IO.Fonts->IsBuilt()                                     && \"Font Atlas not built! Make sure you called ImGui_ImplXXXX_NewFrame() function for renderer backend, which should call io.Fonts->GetTexDataAsRGBA32() / GetTexDataAsAlpha8()\");\n    IM_ASSERT(g.Style.CurveTessellationTol > 0.0f                       && \"Invalid style setting!\");\n    IM_ASSERT(g.Style.CircleTessellationMaxError > 0.0f                 && \"Invalid style setting!\");\n    IM_ASSERT(g.Style.Alpha >= 0.0f && g.Style.Alpha <= 1.0f            && \"Invalid style setting!\"); // Allows us to avoid a few clamps in color computations\n    IM_ASSERT(g.Style.WindowMinSize.x >= 1.0f && g.Style.WindowMinSize.y >= 1.0f && \"Invalid style setting!\");\n    IM_ASSERT(g.Style.WindowBorderHoverPadding > 0.0f                   && \"Invalid style setting!\"); // Required otherwise cannot resize from borders.\n    IM_ASSERT(g.Style.WindowMenuButtonPosition == ImGuiDir_None || g.Style.WindowMenuButtonPosition == ImGuiDir_Left || g.Style.WindowMenuButtonPosition == ImGuiDir_Right);\n    IM_ASSERT(g.Style.ColorButtonPosition == ImGuiDir_Left || g.Style.ColorButtonPosition == ImGuiDir_Right);\n\n    // Error handling: we do not accept 100% silent recovery! Please contact me if you feel this is getting in your way.\n    if (g.IO.ConfigErrorRecovery)\n        IM_ASSERT(g.IO.ConfigErrorRecoveryEnableAssert || g.IO.ConfigErrorRecoveryEnableDebugLog || g.IO.ConfigErrorRecoveryEnableTooltip || g.ErrorCallback != NULL);\n\n#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS\n    // Remap legacy names\n    if (g.IO.ConfigFlags & ImGuiConfigFlags_NavEnableSetMousePos)\n    {\n        g.IO.ConfigNavMoveSetMousePos = true;\n        g.IO.ConfigFlags &= ~ImGuiConfigFlags_NavEnableSetMousePos;\n    }\n    if (g.IO.ConfigFlags & ImGuiConfigFlags_NavNoCaptureKeyboard)\n    {\n        g.IO.ConfigNavCaptureKeyboard = false;\n        g.IO.ConfigFlags &= ~ImGuiConfigFlags_NavNoCaptureKeyboard;\n    }\n\n    // Remap legacy clipboard handlers (OBSOLETED in 1.91.1, August 2024)\n    if (g.IO.GetClipboardTextFn != NULL && (g.PlatformIO.Platform_GetClipboardTextFn == NULL || g.PlatformIO.Platform_GetClipboardTextFn == Platform_GetClipboardTextFn_DefaultImpl))\n        g.PlatformIO.Platform_GetClipboardTextFn = [](ImGuiContext* ctx) { return ctx->IO.GetClipboardTextFn(ctx->IO.ClipboardUserData); };\n    if (g.IO.SetClipboardTextFn != NULL && (g.PlatformIO.Platform_SetClipboardTextFn == NULL || g.PlatformIO.Platform_SetClipboardTextFn == Platform_SetClipboardTextFn_DefaultImpl))\n        g.PlatformIO.Platform_SetClipboardTextFn = [](ImGuiContext* ctx, const char* text) { return ctx->IO.SetClipboardTextFn(ctx->IO.ClipboardUserData, text); };\n#endif\n}\n\nstatic void ImGui::ErrorCheckEndFrameSanityChecks()\n{\n    // Verify that io.KeyXXX fields haven't been tampered with. Key mods should not be modified between NewFrame() and EndFrame()\n    // One possible reason leading to this assert is that your backends update inputs _AFTER_ NewFrame().\n    // It is known that when some modal native windows called mid-frame takes focus away, some backends such as GLFW will\n    // send key release events mid-frame. This would normally trigger this assertion and lead to sheared inputs.\n    // We silently accommodate for this case by ignoring the case where all io.KeyXXX modifiers were released (aka key_mod_flags == 0),\n    // while still correctly asserting on mid-frame key press events.\n    ImGuiContext& g = *GImGui;\n    const ImGuiKeyChord key_mods = GetMergedModsFromKeys();\n    IM_UNUSED(g);\n    IM_UNUSED(key_mods);\n    IM_ASSERT((key_mods == 0 || g.IO.KeyMods == key_mods) && \"Mismatching io.KeyCtrl/io.KeyShift/io.KeyAlt/io.KeySuper vs io.KeyMods\");\n    IM_UNUSED(key_mods);\n\n    IM_ASSERT(g.CurrentWindowStack.Size == 1);\n    IM_ASSERT(g.CurrentWindowStack[0].Window->IsFallbackWindow);\n}\n\n// Save current stack sizes. Called e.g. by NewFrame() and by Begin() but may be called for manual recovery.\nvoid ImGui::ErrorRecoveryStoreState(ImGuiErrorRecoveryState* state_out)\n{\n    ImGuiContext& g = *GImGui;\n    state_out->SizeOfWindowStack = (short)g.CurrentWindowStack.Size;\n    state_out->SizeOfIDStack = (short)g.CurrentWindow->IDStack.Size;\n    state_out->SizeOfTreeStack = (short)g.CurrentWindow->DC.TreeDepth; // NOT g.TreeNodeStack.Size which is a partial stack!\n    state_out->SizeOfColorStack = (short)g.ColorStack.Size;\n    state_out->SizeOfStyleVarStack = (short)g.StyleVarStack.Size;\n    state_out->SizeOfFontStack = (short)g.FontStack.Size;\n    state_out->SizeOfFocusScopeStack = (short)g.FocusScopeStack.Size;\n    state_out->SizeOfGroupStack = (short)g.GroupStack.Size;\n    state_out->SizeOfItemFlagsStack = (short)g.ItemFlagsStack.Size;\n    state_out->SizeOfBeginPopupStack = (short)g.BeginPopupStack.Size;\n    state_out->SizeOfDisabledStack = (short)g.DisabledStackSize;\n}\n\n// Chosen name \"Try to recover\" over e.g. \"Restore\" to suggest this is not a 100% guaranteed recovery.\n// Called by e.g. EndFrame() but may be called for manual recovery.\n// Attempt to recover full window stack.\nvoid ImGui::ErrorRecoveryTryToRecoverState(const ImGuiErrorRecoveryState* state_in)\n{\n    // PVS-Studio V1044 is \"Loop break conditions do not depend on the number of iterations\"\n    ImGuiContext& g = *GImGui;\n    while (g.CurrentWindowStack.Size > state_in->SizeOfWindowStack) //-V1044\n    {\n        // Recap:\n        // - Begin()/BeginChild() return false to indicate the window is collapsed or fully clipped.\n        // - Always call a matching End() for each Begin() call, regardless of its return value!\n        // - Begin/End and BeginChild/EndChild logic is KNOWN TO BE INCONSISTENT WITH ALL OTHER BEGIN/END FUNCTIONS.\n        // - We will fix that in a future major update.\n        ImGuiWindow* window = g.CurrentWindow;\n        if (window->Flags & ImGuiWindowFlags_ChildWindow)\n        {\n            if (g.CurrentTable != NULL && g.CurrentTable->InnerWindow == g.CurrentWindow)\n            {\n                IM_ASSERT_USER_ERROR(0, \"Missing EndTable()\");\n                EndTable();\n            }\n            else\n            {\n                IM_ASSERT_USER_ERROR(0, \"Missing EndChild()\");\n                EndChild();\n            }\n        }\n        else\n        {\n            IM_ASSERT_USER_ERROR(0, \"Missing End()\");\n            End();\n        }\n    }\n    if (g.CurrentWindowStack.Size == state_in->SizeOfWindowStack)\n        ErrorRecoveryTryToRecoverWindowState(state_in);\n}\n\n// Called by e.g. End() but may be called for manual recovery.\n// Read '// Error Handling [BETA]' block in imgui_internal.h for details.\n// Attempt to recover from incorrect usage of BeginXXX/EndXXX/PushXXX/PopXXX calls.\nvoid    ImGui::ErrorRecoveryTryToRecoverWindowState(const ImGuiErrorRecoveryState* state_in)\n{\n    ImGuiContext& g = *GImGui;\n\n    while (g.CurrentTable != NULL && g.CurrentTable->InnerWindow == g.CurrentWindow) //-V1044\n    {\n        IM_ASSERT_USER_ERROR(0, \"Missing EndTable()\");\n        EndTable();\n    }\n\n    ImGuiWindow* window = g.CurrentWindow;\n\n    // FIXME: Can't recover from inside BeginTabItem/EndTabItem yet.\n    while (g.CurrentTabBar != NULL && g.CurrentTabBar->Window == window) //-V1044\n    {\n        IM_ASSERT_USER_ERROR(0, \"Missing EndTabBar()\");\n        EndTabBar();\n    }\n    while (g.CurrentMultiSelect != NULL && g.CurrentMultiSelect->Storage->Window == window) //-V1044\n    {\n        IM_ASSERT_USER_ERROR(0, \"Missing EndMultiSelect()\");\n        EndMultiSelect();\n    }\n    if (window->DC.MenuBarAppending) //-V1044\n    {\n        IM_ASSERT_USER_ERROR(0, \"Missing EndMenuBar()\");\n        EndMenuBar();\n    }\n    while (window->DC.TreeDepth > state_in->SizeOfTreeStack) //-V1044\n    {\n        IM_ASSERT_USER_ERROR(0, \"Missing TreePop()\");\n        TreePop();\n    }\n    while (g.GroupStack.Size > state_in->SizeOfGroupStack) //-V1044\n    {\n        IM_ASSERT_USER_ERROR(0, \"Missing EndGroup()\");\n        EndGroup();\n    }\n    IM_ASSERT(g.GroupStack.Size == state_in->SizeOfGroupStack);\n    while (window->IDStack.Size > state_in->SizeOfIDStack) //-V1044\n    {\n        IM_ASSERT_USER_ERROR(0, \"Missing PopID()\");\n        PopID();\n    }\n    while (g.DisabledStackSize > state_in->SizeOfDisabledStack) //-V1044\n    {\n        IM_ASSERT_USER_ERROR(0, \"Missing EndDisabled()\");\n        if (g.CurrentItemFlags & ImGuiItemFlags_Disabled)\n            EndDisabled();\n        else\n        {\n            EndDisabledOverrideReenable();\n            g.CurrentWindowStack.back().DisabledOverrideReenable = false;\n        }\n    }\n    IM_ASSERT(g.DisabledStackSize == state_in->SizeOfDisabledStack);\n    while (g.ColorStack.Size > state_in->SizeOfColorStack) //-V1044\n    {\n        IM_ASSERT_USER_ERROR(0, \"Missing PopStyleColor()\");\n        PopStyleColor();\n    }\n    while (g.ItemFlagsStack.Size > state_in->SizeOfItemFlagsStack) //-V1044\n    {\n        IM_ASSERT_USER_ERROR(0, \"Missing PopItemFlag()\");\n        PopItemFlag();\n    }\n    while (g.StyleVarStack.Size > state_in->SizeOfStyleVarStack) //-V1044\n    {\n        IM_ASSERT_USER_ERROR(0, \"Missing PopStyleVar()\");\n        PopStyleVar();\n    }\n    while (g.FontStack.Size > state_in->SizeOfFontStack) //-V1044\n    {\n        IM_ASSERT_USER_ERROR(0, \"Missing PopFont()\");\n        PopFont();\n    }\n    while (g.FocusScopeStack.Size > state_in->SizeOfFocusScopeStack) //-V1044\n    {\n        IM_ASSERT_USER_ERROR(0, \"Missing PopFocusScope()\");\n        PopFocusScope();\n    }\n    //IM_ASSERT(g.FocusScopeStack.Size == state_in->SizeOfFocusScopeStack);\n}\n\nbool    ImGui::ErrorLog(const char* msg)\n{\n    ImGuiContext& g = *GImGui;\n\n    // Output to debug log\n#ifndef IMGUI_DISABLE_DEBUG_TOOLS\n    ImGuiWindow* window = g.CurrentWindow;\n\n    if (g.IO.ConfigErrorRecoveryEnableDebugLog)\n    {\n        if (g.ErrorFirst)\n            IMGUI_DEBUG_LOG_ERROR(\"[imgui-error] (current settings: Assert=%d, Log=%d, Tooltip=%d)\\n\",\n                g.IO.ConfigErrorRecoveryEnableAssert, g.IO.ConfigErrorRecoveryEnableDebugLog, g.IO.ConfigErrorRecoveryEnableTooltip);\n        IMGUI_DEBUG_LOG_ERROR(\"[imgui-error] In window '%s': %s\\n\", window ? window->Name : \"NULL\", msg);\n    }\n    g.ErrorFirst = false;\n\n    // Output to tooltip\n    if (g.IO.ConfigErrorRecoveryEnableTooltip)\n    {\n        if (g.WithinFrameScope && BeginErrorTooltip())\n        {\n            if (g.ErrorCountCurrentFrame < 20)\n            {\n                Text(\"In window '%s': %s\", window ? window->Name : \"NULL\", msg);\n                if (window && (!window->IsFallbackWindow || window->WasActive))\n                    GetForegroundDrawList(window)->AddRect(window->Pos, window->Pos + window->Size, IM_COL32(255, 0, 0, 255));\n            }\n            if (g.ErrorCountCurrentFrame == 20)\n                Text(\"(and more errors)\");\n            // EndFrame() will amend debug buttons to this window, after all errors have been submitted.\n            EndErrorTooltip();\n        }\n        g.ErrorCountCurrentFrame++;\n    }\n#endif\n\n    // Output to callback\n    if (g.ErrorCallback != NULL)\n        g.ErrorCallback(&g, g.ErrorCallbackUserData, msg);\n\n    // Return whether we should assert\n    return g.IO.ConfigErrorRecoveryEnableAssert;\n}\n\nvoid ImGui::ErrorCheckEndFrameFinalizeErrorTooltip()\n{\n#ifndef IMGUI_DISABLE_DEBUG_TOOLS\n    ImGuiContext& g = *GImGui;\n    if (g.DebugDrawIdConflicts != 0 && g.IO.KeyCtrl == false)\n        g.DebugDrawIdConflictsCount = g.HoveredIdPreviousFrameItemCount;\n    if (g.DebugDrawIdConflicts != 0 && g.DebugItemPickerActive == false && BeginErrorTooltip())\n    {\n        Text(\"Programmer error: %d visible items with conflicting ID!\", g.DebugDrawIdConflictsCount);\n        BulletText(\"Code should use PushID()/PopID() in loops, or append \\\"##xx\\\" to same-label identifiers!\");\n        BulletText(\"Empty label e.g. Button(\\\"\\\") == same ID as parent widget/node. Use Button(\\\"##xx\\\") instead!\");\n        //BulletText(\"Code intending to use duplicate ID may use e.g. PushItemFlag(ImGuiItemFlags_AllowDuplicateId, true); ... PopItemFlag()\"); // Not making this too visible for fear of it being abused.\n        BulletText(\"Set io.ConfigDebugHighlightIdConflicts=false to disable this warning in non-programmers builds.\");\n        Separator();\n        if (g.IO.ConfigDebugHighlightIdConflictsShowItemPicker)\n        {\n            Text(\"(Hold CTRL to: use \");\n            SameLine(0.0f, 0.0f);\n            if (SmallButton(\"Item Picker\"))\n                DebugStartItemPicker();\n            SameLine(0.0f, 0.0f);\n            Text(\" to break in item call-stack, or \");\n        }\n        else\n        {\n            Text(\"(Hold CTRL to \");\n        }\n        SameLine(0.0f, 0.0f);\n        if (SmallButton(\"Open FAQ->About ID Stack System\") && g.PlatformIO.Platform_OpenInShellFn != NULL)\n            g.PlatformIO.Platform_OpenInShellFn(&g, \"https://github.com/ocornut/imgui/blob/master/docs/FAQ.md#qa-usage\");\n        SameLine(0.0f, 0.0f);\n        Text(\")\");\n        EndErrorTooltip();\n    }\n\n    if (g.ErrorCountCurrentFrame > 0 && BeginErrorTooltip()) // Amend at end of frame\n    {\n        Separator();\n        Text(\"(Hold CTRL to:\");\n        SameLine();\n        if (SmallButton(\"Enable Asserts\"))\n            g.IO.ConfigErrorRecoveryEnableAssert = true;\n        //SameLine();\n        //if (SmallButton(\"Hide Error Tooltips\"))\n        //    g.IO.ConfigErrorRecoveryEnableTooltip = false; // Too dangerous\n        SameLine(0, 0);\n        Text(\")\");\n        EndErrorTooltip();\n    }\n#endif\n}\n\n// Pseudo-tooltip. Follow mouse until CTRL is held. When CTRL is held we lock position, allowing to click it.\nbool ImGui::BeginErrorTooltip()\n{\n    ImGuiContext& g = *GImGui;\n    ImGuiWindow* window = FindWindowByName(\"##Tooltip_Error\");\n    const bool use_locked_pos = (g.IO.KeyCtrl && window && window->WasActive);\n    PushStyleColor(ImGuiCol_PopupBg, ImLerp(g.Style.Colors[ImGuiCol_PopupBg], ImVec4(1.0f, 0.0f, 0.0f, 1.0f), 0.15f));\n    if (use_locked_pos)\n        SetNextWindowPos(g.ErrorTooltipLockedPos);\n    bool is_visible = Begin(\"##Tooltip_Error\", NULL, ImGuiWindowFlags_Tooltip | ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_AlwaysAutoResize);\n    PopStyleColor();\n    if (is_visible && g.CurrentWindow->BeginCount == 1)\n    {\n        SeparatorText(\"MESSAGE FROM DEAR IMGUI\");\n        BringWindowToDisplayFront(g.CurrentWindow);\n        BringWindowToFocusFront(g.CurrentWindow);\n        g.ErrorTooltipLockedPos = GetWindowPos();\n    }\n    else if (!is_visible)\n    {\n        End();\n    }\n    return is_visible;\n}\n\nvoid ImGui::EndErrorTooltip()\n{\n    End();\n}\n\n//-----------------------------------------------------------------------------\n// [SECTION] ITEM SUBMISSION\n//-----------------------------------------------------------------------------\n// - KeepAliveID()\n// - ItemAdd()\n//-----------------------------------------------------------------------------\n\n// Code not using ItemAdd() may need to call this manually otherwise ActiveId will be cleared. In IMGUI_VERSION_NUM < 18717 this was called by GetID().\nvoid ImGui::KeepAliveID(ImGuiID id)\n{\n    ImGuiContext& g = *GImGui;\n    if (g.ActiveId == id)\n        g.ActiveIdIsAlive = id;\n    if (g.DeactivatedItemData.ID == id)\n        g.DeactivatedItemData.IsAlive = true;\n}\n\n// Declare item bounding box for clipping and interaction.\n// Note that the size can be different than the one provided to ItemSize(). Typically, widgets that spread over available surface\n// declare their minimum size requirement to ItemSize() and provide a larger region to ItemAdd() which is used drawing/interaction.\n// THIS IS IN THE PERFORMANCE CRITICAL PATH (UNTIL THE CLIPPING TEST AND EARLY-RETURN)\nIM_MSVC_RUNTIME_CHECKS_OFF\nbool ImGui::ItemAdd(const ImRect& bb, ImGuiID id, const ImRect* nav_bb_arg, ImGuiItemFlags extra_flags)\n{\n    ImGuiContext& g = *GImGui;\n    ImGuiWindow* window = g.CurrentWindow;\n\n    // Set item data\n    // (DisplayRect is left untouched, made valid when ImGuiItemStatusFlags_HasDisplayRect is set)\n    g.LastItemData.ID = id;\n    g.LastItemData.Rect = bb;\n    g.LastItemData.NavRect = nav_bb_arg ? *nav_bb_arg : bb;\n    g.LastItemData.ItemFlags = g.CurrentItemFlags | g.NextItemData.ItemFlags | extra_flags;\n    g.LastItemData.StatusFlags = ImGuiItemStatusFlags_None;\n    // Note: we don't copy 'g.NextItemData.SelectionUserData' to an hypothetical g.LastItemData.SelectionUserData: since the former is not cleared.\n\n    if (id != 0)\n    {\n        KeepAliveID(id);\n\n        // Directional navigation processing\n        // Runs prior to clipping early-out\n        //  (a) So that NavInitRequest can be honored, for newly opened windows to select a default widget\n        //  (b) So that we can scroll up/down past clipped items. This adds a small O(N) cost to regular navigation requests\n        //      unfortunately, but it is still limited to one window. It may not scale very well for windows with ten of\n        //      thousands of item, but at least NavMoveRequest is only set on user interaction, aka maximum once a frame.\n        //      We could early out with \"if (is_clipped && !g.NavInitRequest) return false;\" but when we wouldn't be able\n        //      to reach unclipped widgets. This would work if user had explicit scrolling control (e.g. mapped on a stick).\n        // We intentionally don't check if g.NavWindow != NULL because g.NavAnyRequest should only be set when it is non null.\n        // If we crash on a NULL g.NavWindow we need to fix the bug elsewhere.\n        if (!(g.LastItemData.ItemFlags & ImGuiItemFlags_NoNav))\n        {\n            // FIMXE-NAV: investigate changing the window tests into a simple 'if (g.NavFocusScopeId == g.CurrentFocusScopeId)' test.\n            window->DC.NavLayersActiveMaskNext |= (1 << window->DC.NavLayerCurrent);\n            if (g.NavId == id || g.NavAnyRequest)\n                if (g.NavWindow->RootWindowForNav == window->RootWindowForNav)\n                    if (window == g.NavWindow || ((window->ChildFlags | g.NavWindow->ChildFlags) & ImGuiChildFlags_NavFlattened))\n                        NavProcessItem();\n        }\n\n        if (g.NextItemData.HasFlags & ImGuiNextItemDataFlags_HasShortcut)\n            ItemHandleShortcut(id);\n    }\n\n    // Lightweight clear of SetNextItemXXX data.\n    g.NextItemData.HasFlags = ImGuiNextItemDataFlags_None;\n    g.NextItemData.ItemFlags = ImGuiItemFlags_None;\n\n#ifdef IMGUI_ENABLE_TEST_ENGINE\n    if (id != 0)\n        IMGUI_TEST_ENGINE_ITEM_ADD(id, g.LastItemData.NavRect, &g.LastItemData);\n#endif\n\n    // Clipping test\n    // (this is an inline copy of IsClippedEx() so we can reuse the is_rect_visible value, otherwise we'd do 'if (IsClippedEx(bb, id)) return false')\n    // g.NavActivateId is not necessarily == g.NavId, in the case of remote activation (e.g. shortcuts)\n    const bool is_rect_visible = bb.Overlaps(window->ClipRect);\n    if (!is_rect_visible)\n        if (id == 0 || (id != g.ActiveId && id != g.ActiveIdPreviousFrame && id != g.NavId && id != g.NavActivateId))\n            if (!g.ItemUnclipByLog)\n                return false;\n\n    // [DEBUG]\n#ifndef IMGUI_DISABLE_DEBUG_TOOLS\n    if (id != 0)\n    {\n        if (id == g.DebugLocateId)\n            DebugLocateItemResolveWithLastItem();\n\n        // [DEBUG] People keep stumbling on this problem and using \"\" as identifier in the root of a window instead of \"##something\".\n        // Empty identifier are valid and useful in a small amount of cases, but 99.9% of the time you want to use \"##something\".\n        // READ THE FAQ: https://dearimgui.com/faq\n        IM_ASSERT(id != window->ID && \"Cannot have an empty ID at the root of a window. If you need an empty label, use ## and read the FAQ about how the ID Stack works!\");\n    }\n    //if (g.IO.KeyAlt) window->DrawList->AddRect(bb.Min, bb.Max, IM_COL32(255,255,0,120)); // [DEBUG]\n    //if ((g.LastItemData.ItemFlags & ImGuiItemFlags_NoNav) == 0)\n    //    window->DrawList->AddRect(g.LastItemData.NavRect.Min, g.LastItemData.NavRect.Max, IM_COL32(255,255,0,255)); // [DEBUG]\n#endif\n\n    if (id != 0 && g.DeactivatedItemData.ID == id)\n        g.DeactivatedItemData.ElapseFrame = g.FrameCount;\n\n    // We need to calculate this now to take account of the current clipping rectangle (as items like Selectable may change them)\n    if (is_rect_visible)\n        g.LastItemData.StatusFlags |= ImGuiItemStatusFlags_Visible;\n    if (IsMouseHoveringRect(bb.Min, bb.Max))\n        g.LastItemData.StatusFlags |= ImGuiItemStatusFlags_HoveredRect;\n    return true;\n}\nIM_MSVC_RUNTIME_CHECKS_RESTORE\n\n//-----------------------------------------------------------------------------\n// [SECTION] LAYOUT\n//-----------------------------------------------------------------------------\n// - ItemSize()\n// - SameLine()\n// - GetCursorScreenPos()\n// - SetCursorScreenPos()\n// - GetCursorPos(), GetCursorPosX(), GetCursorPosY()\n// - SetCursorPos(), SetCursorPosX(), SetCursorPosY()\n// - GetCursorStartPos()\n// - Indent()\n// - Unindent()\n// - SetNextItemWidth()\n// - PushItemWidth()\n// - PushMultiItemsWidths()\n// - PopItemWidth()\n// - CalcItemWidth()\n// - CalcItemSize()\n// - GetTextLineHeight()\n// - GetTextLineHeightWithSpacing()\n// - GetFrameHeight()\n// - GetFrameHeightWithSpacing()\n// - GetContentRegionMax()\n// - GetContentRegionAvail(),\n// - BeginGroup()\n// - EndGroup()\n// Also see in imgui_widgets: tab bars, and in imgui_tables: tables, columns.\n//-----------------------------------------------------------------------------\n\n// Advance cursor given item size for layout.\n// Register minimum needed size so it can extend the bounding box used for auto-fit calculation.\n// See comments in ItemAdd() about how/why the size provided to ItemSize() vs ItemAdd() may often different.\n// THIS IS IN THE PERFORMANCE CRITICAL PATH.\nIM_MSVC_RUNTIME_CHECKS_OFF\nvoid ImGui::ItemSize(const ImVec2& size, float text_baseline_y)\n{\n    ImGuiContext& g = *GImGui;\n    ImGuiWindow* window = g.CurrentWindow;\n    if (window->SkipItems)\n        return;\n\n    // We increase the height in this function to accommodate for baseline offset.\n    // In theory we should be offsetting the starting position (window->DC.CursorPos), that will be the topic of a larger refactor,\n    // but since ItemSize() is not yet an API that moves the cursor (to handle e.g. wrapping) enlarging the height has the same effect.\n    const float offset_to_match_baseline_y = (text_baseline_y >= 0) ? ImMax(0.0f, window->DC.CurrLineTextBaseOffset - text_baseline_y) : 0.0f;\n\n    const float line_y1 = window->DC.IsSameLine ? window->DC.CursorPosPrevLine.y : window->DC.CursorPos.y;\n    const float line_height = ImMax(window->DC.CurrLineSize.y, /*ImMax(*/window->DC.CursorPos.y - line_y1/*, 0.0f)*/ + size.y + offset_to_match_baseline_y);\n\n    // Always align ourselves on pixel boundaries\n    //if (g.IO.KeyAlt) window->DrawList->AddRect(window->DC.CursorPos, window->DC.CursorPos + ImVec2(size.x, line_height), IM_COL32(255,0,0,200)); // [DEBUG]\n    window->DC.CursorPosPrevLine.x = window->DC.CursorPos.x + size.x;\n    window->DC.CursorPosPrevLine.y = line_y1;\n    window->DC.CursorPos.x = IM_TRUNC(window->Pos.x + window->DC.Indent.x + window->DC.ColumnsOffset.x);    // Next line\n    window->DC.CursorPos.y = IM_TRUNC(line_y1 + line_height + g.Style.ItemSpacing.y);                       // Next line\n    window->DC.CursorMaxPos.x = ImMax(window->DC.CursorMaxPos.x, window->DC.CursorPosPrevLine.x);\n    window->DC.CursorMaxPos.y = ImMax(window->DC.CursorMaxPos.y, window->DC.CursorPos.y - g.Style.ItemSpacing.y);\n    //if (g.IO.KeyAlt) window->DrawList->AddCircle(window->DC.CursorMaxPos, 3.0f, IM_COL32(255,0,0,255), 4); // [DEBUG]\n\n    window->DC.PrevLineSize.y = line_height;\n    window->DC.CurrLineSize.y = 0.0f;\n    window->DC.PrevLineTextBaseOffset = ImMax(window->DC.CurrLineTextBaseOffset, text_baseline_y);\n    window->DC.CurrLineTextBaseOffset = 0.0f;\n    window->DC.IsSameLine = window->DC.IsSetPos = false;\n\n    // Horizontal layout mode\n    if (window->DC.LayoutType == ImGuiLayoutType_Horizontal)\n        SameLine();\n}\nIM_MSVC_RUNTIME_CHECKS_RESTORE\n\n// Gets back to previous line and continue with horizontal layout\n//      offset_from_start_x == 0 : follow right after previous item\n//      offset_from_start_x != 0 : align to specified x position (relative to window/group left)\n//      spacing_w < 0            : use default spacing if offset_from_start_x == 0, no spacing if offset_from_start_x != 0\n//      spacing_w >= 0           : enforce spacing amount\nvoid ImGui::SameLine(float offset_from_start_x, float spacing_w)\n{\n    ImGuiContext& g = *GImGui;\n    ImGuiWindow* window = g.CurrentWindow;\n    if (window->SkipItems)\n        return;\n\n    if (offset_from_start_x != 0.0f)\n    {\n        if (spacing_w < 0.0f)\n            spacing_w = 0.0f;\n        window->DC.CursorPos.x = window->Pos.x - window->Scroll.x + offset_from_start_x + spacing_w + window->DC.GroupOffset.x + window->DC.ColumnsOffset.x;\n        window->DC.CursorPos.y = window->DC.CursorPosPrevLine.y;\n    }\n    else\n    {\n        if (spacing_w < 0.0f)\n            spacing_w = g.Style.ItemSpacing.x;\n        window->DC.CursorPos.x = window->DC.CursorPosPrevLine.x + spacing_w;\n        window->DC.CursorPos.y = window->DC.CursorPosPrevLine.y;\n    }\n    window->DC.CurrLineSize = window->DC.PrevLineSize;\n    window->DC.CurrLineTextBaseOffset = window->DC.PrevLineTextBaseOffset;\n    window->DC.IsSameLine = true;\n}\n\nImVec2 ImGui::GetCursorScreenPos()\n{\n    ImGuiWindow* window = GetCurrentWindowRead();\n    return window->DC.CursorPos;\n}\n\nvoid ImGui::SetCursorScreenPos(const ImVec2& pos)\n{\n    ImGuiWindow* window = GetCurrentWindow();\n    window->DC.CursorPos = pos;\n    //window->DC.CursorMaxPos = ImMax(window->DC.CursorMaxPos, window->DC.CursorPos);\n    window->DC.IsSetPos = true;\n}\n\n// User generally sees positions in window coordinates. Internally we store CursorPos in absolute screen coordinates because it is more convenient.\n// Conversion happens as we pass the value to user, but it makes our naming convention confusing because GetCursorPos() == (DC.CursorPos - window.Pos). May want to rename 'DC.CursorPos'.\nImVec2 ImGui::GetCursorPos()\n{\n    ImGuiWindow* window = GetCurrentWindowRead();\n    return window->DC.CursorPos - window->Pos + window->Scroll;\n}\n\nfloat ImGui::GetCursorPosX()\n{\n    ImGuiWindow* window = GetCurrentWindowRead();\n    return window->DC.CursorPos.x - window->Pos.x + window->Scroll.x;\n}\n\nfloat ImGui::GetCursorPosY()\n{\n    ImGuiWindow* window = GetCurrentWindowRead();\n    return window->DC.CursorPos.y - window->Pos.y + window->Scroll.y;\n}\n\nvoid ImGui::SetCursorPos(const ImVec2& local_pos)\n{\n    ImGuiWindow* window = GetCurrentWindow();\n    window->DC.CursorPos = window->Pos - window->Scroll + local_pos;\n    //window->DC.CursorMaxPos = ImMax(window->DC.CursorMaxPos, window->DC.CursorPos);\n    window->DC.IsSetPos = true;\n}\n\nvoid ImGui::SetCursorPosX(float x)\n{\n    ImGuiWindow* window = GetCurrentWindow();\n    window->DC.CursorPos.x = window->Pos.x - window->Scroll.x + x;\n    //window->DC.CursorMaxPos.x = ImMax(window->DC.CursorMaxPos.x, window->DC.CursorPos.x);\n    window->DC.IsSetPos = true;\n}\n\nvoid ImGui::SetCursorPosY(float y)\n{\n    ImGuiWindow* window = GetCurrentWindow();\n    window->DC.CursorPos.y = window->Pos.y - window->Scroll.y + y;\n    //window->DC.CursorMaxPos.y = ImMax(window->DC.CursorMaxPos.y, window->DC.CursorPos.y);\n    window->DC.IsSetPos = true;\n}\n\nImVec2 ImGui::GetCursorStartPos()\n{\n    ImGuiWindow* window = GetCurrentWindowRead();\n    return window->DC.CursorStartPos - window->Pos;\n}\n\nvoid ImGui::Indent(float indent_w)\n{\n    ImGuiContext& g = *GImGui;\n    ImGuiWindow* window = GetCurrentWindow();\n    window->DC.Indent.x += (indent_w != 0.0f) ? indent_w : g.Style.IndentSpacing;\n    window->DC.CursorPos.x = window->Pos.x + window->DC.Indent.x + window->DC.ColumnsOffset.x;\n}\n\nvoid ImGui::Unindent(float indent_w)\n{\n    ImGuiContext& g = *GImGui;\n    ImGuiWindow* window = GetCurrentWindow();\n    window->DC.Indent.x -= (indent_w != 0.0f) ? indent_w : g.Style.IndentSpacing;\n    window->DC.CursorPos.x = window->Pos.x + window->DC.Indent.x + window->DC.ColumnsOffset.x;\n}\n\n// Affect large frame+labels widgets only.\nvoid ImGui::SetNextItemWidth(float item_width)\n{\n    ImGuiContext& g = *GImGui;\n    g.NextItemData.HasFlags |= ImGuiNextItemDataFlags_HasWidth;\n    g.NextItemData.Width = item_width;\n}\n\n// FIXME: Remove the == 0.0f behavior?\nvoid ImGui::PushItemWidth(float item_width)\n{\n    ImGuiContext& g = *GImGui;\n    ImGuiWindow* window = g.CurrentWindow;\n    window->DC.ItemWidthStack.push_back(window->DC.ItemWidth); // Backup current width\n    window->DC.ItemWidth = (item_width == 0.0f ? window->ItemWidthDefault : item_width);\n    g.NextItemData.HasFlags &= ~ImGuiNextItemDataFlags_HasWidth;\n}\n\nvoid ImGui::PushMultiItemsWidths(int components, float w_full)\n{\n    ImGuiContext& g = *GImGui;\n    ImGuiWindow* window = g.CurrentWindow;\n    IM_ASSERT(components > 0);\n    const ImGuiStyle& style = g.Style;\n    window->DC.ItemWidthStack.push_back(window->DC.ItemWidth); // Backup current width\n    float w_items = w_full - style.ItemInnerSpacing.x * (components - 1);\n    float prev_split = w_items;\n    for (int i = components - 1; i > 0; i--)\n    {\n        float next_split = IM_TRUNC(w_items * i / components);\n        window->DC.ItemWidthStack.push_back(ImMax(prev_split - next_split, 1.0f));\n        prev_split = next_split;\n    }\n    window->DC.ItemWidth = ImMax(prev_split, 1.0f);\n    g.NextItemData.HasFlags &= ~ImGuiNextItemDataFlags_HasWidth;\n}\n\nvoid ImGui::PopItemWidth()\n{\n    ImGuiContext& g = *GImGui;\n    ImGuiWindow* window = g.CurrentWindow;\n    if (window->DC.ItemWidthStack.Size <= 0)\n    {\n        IM_ASSERT_USER_ERROR(0, \"Calling PopItemWidth() too many times!\");\n        return;\n    }\n    window->DC.ItemWidth = window->DC.ItemWidthStack.back();\n    window->DC.ItemWidthStack.pop_back();\n}\n\n// Calculate default item width given value passed to PushItemWidth() or SetNextItemWidth().\n// The SetNextItemWidth() data is generally cleared/consumed by ItemAdd() or NextItemData.ClearFlags()\nfloat ImGui::CalcItemWidth()\n{\n    ImGuiContext& g = *GImGui;\n    ImGuiWindow* window = g.CurrentWindow;\n    float w;\n    if (g.NextItemData.HasFlags & ImGuiNextItemDataFlags_HasWidth)\n        w = g.NextItemData.Width;\n    else\n        w = window->DC.ItemWidth;\n    if (w < 0.0f)\n    {\n        float region_avail_x = GetContentRegionAvail().x;\n        w = ImMax(1.0f, region_avail_x + w);\n    }\n    w = IM_TRUNC(w);\n    return w;\n}\n\n// [Internal] Calculate full item size given user provided 'size' parameter and default width/height. Default width is often == CalcItemWidth().\n// Those two functions CalcItemWidth vs CalcItemSize are awkwardly named because they are not fully symmetrical.\n// Note that only CalcItemWidth() is publicly exposed.\n// The 4.0f here may be changed to match CalcItemWidth() and/or BeginChild() (right now we have a mismatch which is harmless but undesirable)\nImVec2 ImGui::CalcItemSize(ImVec2 size, float default_w, float default_h)\n{\n    ImVec2 avail;\n    if (size.x < 0.0f || size.y < 0.0f)\n        avail = GetContentRegionAvail();\n\n    if (size.x == 0.0f)\n        size.x = default_w;\n    else if (size.x < 0.0f)\n        size.x = ImMax(4.0f, avail.x + size.x); // <-- size.x is negative here so we are subtracting\n\n    if (size.y == 0.0f)\n        size.y = default_h;\n    else if (size.y < 0.0f)\n        size.y = ImMax(4.0f, avail.y + size.y); // <-- size.y is negative here so we are subtracting\n\n    return size;\n}\n\nfloat ImGui::GetTextLineHeight()\n{\n    ImGuiContext& g = *GImGui;\n    return g.FontSize;\n}\n\nfloat ImGui::GetTextLineHeightWithSpacing()\n{\n    ImGuiContext& g = *GImGui;\n    return g.FontSize + g.Style.ItemSpacing.y;\n}\n\nfloat ImGui::GetFrameHeight()\n{\n    ImGuiContext& g = *GImGui;\n    return g.FontSize + g.Style.FramePadding.y * 2.0f;\n}\n\nfloat ImGui::GetFrameHeightWithSpacing()\n{\n    ImGuiContext& g = *GImGui;\n    return g.FontSize + g.Style.FramePadding.y * 2.0f + g.Style.ItemSpacing.y;\n}\n\nImVec2 ImGui::GetContentRegionAvail()\n{\n    ImGuiContext& g = *GImGui;\n    ImGuiWindow* window = g.CurrentWindow;\n    ImVec2 mx = (window->DC.CurrentColumns || g.CurrentTable) ? window->WorkRect.Max : window->ContentRegionRect.Max;\n    return mx - window->DC.CursorPos;\n}\n\n#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS\n\n// You should never need those functions. Always use GetCursorScreenPos() and GetContentRegionAvail()!\n// They are bizarre local-coordinates which don't play well with scrolling.\nImVec2 ImGui::GetContentRegionMax()\n{\n    return GetContentRegionAvail() + GetCursorScreenPos() - GetWindowPos();\n}\n\nImVec2 ImGui::GetWindowContentRegionMin()\n{\n    ImGuiWindow* window = GImGui->CurrentWindow;\n    return window->ContentRegionRect.Min - window->Pos;\n}\n\nImVec2 ImGui::GetWindowContentRegionMax()\n{\n    ImGuiWindow* window = GImGui->CurrentWindow;\n    return window->ContentRegionRect.Max - window->Pos;\n}\n#endif\n\n// Lock horizontal starting position + capture group bounding box into one \"item\" (so you can use IsItemHovered() or layout primitives such as SameLine() on whole group, etc.)\n// Groups are currently a mishmash of functionalities which should perhaps be clarified and separated.\n// FIXME-OPT: Could we safely early out on ->SkipItems?\nvoid ImGui::BeginGroup()\n{\n    ImGuiContext& g = *GImGui;\n    ImGuiWindow* window = g.CurrentWindow;\n\n    g.GroupStack.resize(g.GroupStack.Size + 1);\n    ImGuiGroupData& group_data = g.GroupStack.back();\n    group_data.WindowID = window->ID;\n    group_data.BackupCursorPos = window->DC.CursorPos;\n    group_data.BackupCursorPosPrevLine = window->DC.CursorPosPrevLine;\n    group_data.BackupCursorMaxPos = window->DC.CursorMaxPos;\n    group_data.BackupIndent = window->DC.Indent;\n    group_data.BackupGroupOffset = window->DC.GroupOffset;\n    group_data.BackupCurrLineSize = window->DC.CurrLineSize;\n    group_data.BackupCurrLineTextBaseOffset = window->DC.CurrLineTextBaseOffset;\n    group_data.BackupActiveIdIsAlive = g.ActiveIdIsAlive;\n    group_data.BackupHoveredIdIsAlive = g.HoveredId != 0;\n    group_data.BackupIsSameLine = window->DC.IsSameLine;\n    group_data.BackupDeactivatedIdIsAlive = g.DeactivatedItemData.IsAlive;\n    group_data.EmitItem = true;\n\n    window->DC.GroupOffset.x = window->DC.CursorPos.x - window->Pos.x - window->DC.ColumnsOffset.x;\n    window->DC.Indent = window->DC.GroupOffset;\n    window->DC.CursorMaxPos = window->DC.CursorPos;\n    window->DC.CurrLineSize = ImVec2(0.0f, 0.0f);\n    if (g.LogEnabled)\n        g.LogLinePosY = -FLT_MAX; // To enforce a carriage return\n}\n\nvoid ImGui::EndGroup()\n{\n    ImGuiContext& g = *GImGui;\n    ImGuiWindow* window = g.CurrentWindow;\n    IM_ASSERT(g.GroupStack.Size > 0); // Mismatched BeginGroup()/EndGroup() calls\n\n    ImGuiGroupData& group_data = g.GroupStack.back();\n    IM_ASSERT(group_data.WindowID == window->ID); // EndGroup() in wrong window?\n\n    if (window->DC.IsSetPos)\n        ErrorCheckUsingSetCursorPosToExtendParentBoundaries();\n\n    // Include LastItemData.Rect.Max as a workaround for e.g. EndTable() undershooting with CursorMaxPos report. (#7543)\n    ImRect group_bb(group_data.BackupCursorPos, ImMax(ImMax(window->DC.CursorMaxPos, g.LastItemData.Rect.Max), group_data.BackupCursorPos));\n    window->DC.CursorPos = group_data.BackupCursorPos;\n    window->DC.CursorPosPrevLine = group_data.BackupCursorPosPrevLine;\n    window->DC.CursorMaxPos = ImMax(group_data.BackupCursorMaxPos, group_bb.Max);\n    window->DC.Indent = group_data.BackupIndent;\n    window->DC.GroupOffset = group_data.BackupGroupOffset;\n    window->DC.CurrLineSize = group_data.BackupCurrLineSize;\n    window->DC.CurrLineTextBaseOffset = group_data.BackupCurrLineTextBaseOffset;\n    window->DC.IsSameLine = group_data.BackupIsSameLine;\n    if (g.LogEnabled)\n        g.LogLinePosY = -FLT_MAX; // To enforce a carriage return\n\n    if (!group_data.EmitItem)\n    {\n        g.GroupStack.pop_back();\n        return;\n    }\n\n    window->DC.CurrLineTextBaseOffset = ImMax(window->DC.PrevLineTextBaseOffset, group_data.BackupCurrLineTextBaseOffset); // FIXME: Incorrect, we should grab the base offset from the *first line* of the group but it is hard to obtain now.\n    ItemSize(group_bb.GetSize());\n    ItemAdd(group_bb, 0, NULL, ImGuiItemFlags_NoTabStop);\n\n    // If the current ActiveId was declared within the boundary of our group, we copy it to LastItemId so IsItemActive(), IsItemDeactivated() etc. will be functional on the entire group.\n    // It would be neater if we replaced window.DC.LastItemId by e.g. 'bool LastItemIsActive', but would put a little more burden on individual widgets.\n    // Also if you grep for LastItemId you'll notice it is only used in that context.\n    // (The two tests not the same because ActiveIdIsAlive is an ID itself, in order to be able to handle ActiveId being overwritten during the frame.)\n    const bool group_contains_curr_active_id = (group_data.BackupActiveIdIsAlive != g.ActiveId) && (g.ActiveIdIsAlive == g.ActiveId) && g.ActiveId;\n    const bool group_contains_deactivated_id = (group_data.BackupDeactivatedIdIsAlive == false) && (g.DeactivatedItemData.IsAlive == true);\n    if (group_contains_curr_active_id)\n        g.LastItemData.ID = g.ActiveId;\n    else if (group_contains_deactivated_id)\n        g.LastItemData.ID = g.DeactivatedItemData.ID;\n    g.LastItemData.Rect = group_bb;\n\n    // Forward Hovered flag\n    const bool group_contains_curr_hovered_id = (group_data.BackupHoveredIdIsAlive == false) && g.HoveredId != 0;\n    if (group_contains_curr_hovered_id)\n        g.LastItemData.StatusFlags |= ImGuiItemStatusFlags_HoveredWindow;\n\n    // Forward Edited flag\n    if (group_contains_curr_active_id && g.ActiveIdHasBeenEditedThisFrame)\n        g.LastItemData.StatusFlags |= ImGuiItemStatusFlags_Edited;\n\n    // Forward Deactivated flag\n    g.LastItemData.StatusFlags |= ImGuiItemStatusFlags_HasDeactivated;\n    if (group_contains_deactivated_id)\n        g.LastItemData.StatusFlags |= ImGuiItemStatusFlags_Deactivated;\n\n    g.GroupStack.pop_back();\n    if (g.DebugShowGroupRects)\n        window->DrawList->AddRect(group_bb.Min, group_bb.Max, IM_COL32(255,0,255,255));   // [Debug]\n}\n\n\n//-----------------------------------------------------------------------------\n// [SECTION] SCROLLING\n//-----------------------------------------------------------------------------\n\n// Helper to snap on edges when aiming at an item very close to the edge,\n// So the difference between WindowPadding and ItemSpacing will be in the visible area after scrolling.\n// When we refactor the scrolling API this may be configurable with a flag?\n// Note that the effect for this won't be visible on X axis with default Style settings as WindowPadding.x == ItemSpacing.x by default.\nstatic float CalcScrollEdgeSnap(float target, float snap_min, float snap_max, float snap_threshold, float center_ratio)\n{\n    if (target <= snap_min + snap_threshold)\n        return ImLerp(snap_min, target, center_ratio);\n    if (target >= snap_max - snap_threshold)\n        return ImLerp(target, snap_max, center_ratio);\n    return target;\n}\n\nstatic ImVec2 CalcNextScrollFromScrollTargetAndClamp(ImGuiWindow* window)\n{\n    ImVec2 scroll = window->Scroll;\n    ImVec2 decoration_size(window->DecoOuterSizeX1 + window->DecoInnerSizeX1 + window->DecoOuterSizeX2, window->DecoOuterSizeY1 + window->DecoInnerSizeY1 + window->DecoOuterSizeY2);\n    for (int axis = 0; axis < 2; axis++)\n    {\n        if (window->ScrollTarget[axis] < FLT_MAX)\n        {\n            float center_ratio = window->ScrollTargetCenterRatio[axis];\n            float scroll_target = window->ScrollTarget[axis];\n            if (window->ScrollTargetEdgeSnapDist[axis] > 0.0f)\n            {\n                float snap_min = 0.0f;\n                float snap_max = window->ScrollMax[axis] + window->SizeFull[axis] - decoration_size[axis];\n                scroll_target = CalcScrollEdgeSnap(scroll_target, snap_min, snap_max, window->ScrollTargetEdgeSnapDist[axis], center_ratio);\n            }\n            scroll[axis] = scroll_target - center_ratio * (window->SizeFull[axis] - decoration_size[axis]);\n        }\n        scroll[axis] = IM_ROUND(ImMax(scroll[axis], 0.0f));\n        if (!window->Collapsed && !window->SkipItems)\n            scroll[axis] = ImMin(scroll[axis], window->ScrollMax[axis]);\n    }\n    return scroll;\n}\n\nvoid ImGui::ScrollToItem(ImGuiScrollFlags flags)\n{\n    ImGuiContext& g = *GImGui;\n    ImGuiWindow* window = g.CurrentWindow;\n    ScrollToRectEx(window, g.LastItemData.NavRect, flags);\n}\n\nvoid ImGui::ScrollToRect(ImGuiWindow* window, const ImRect& item_rect, ImGuiScrollFlags flags)\n{\n    ScrollToRectEx(window, item_rect, flags);\n}\n\n// Scroll to keep newly navigated item fully into view\nImVec2 ImGui::ScrollToRectEx(ImGuiWindow* window, const ImRect& item_rect, ImGuiScrollFlags flags)\n{\n    ImGuiContext& g = *GImGui;\n    ImRect scroll_rect(window->InnerRect.Min - ImVec2(1, 1), window->InnerRect.Max + ImVec2(1, 1));\n    scroll_rect.Min.x = ImMin(scroll_rect.Min.x + window->DecoInnerSizeX1, scroll_rect.Max.x);\n    scroll_rect.Min.y = ImMin(scroll_rect.Min.y + window->DecoInnerSizeY1, scroll_rect.Max.y);\n    //GetForegroundDrawList(window)->AddRect(item_rect.Min, item_rect.Max, IM_COL32(255,0,0,255), 0.0f, 0, 5.0f); // [DEBUG]\n    //GetForegroundDrawList(window)->AddRect(scroll_rect.Min, scroll_rect.Max, IM_COL32_WHITE); // [DEBUG]\n\n    // Check that only one behavior is selected per axis\n    IM_ASSERT((flags & ImGuiScrollFlags_MaskX_) == 0 || ImIsPowerOfTwo(flags & ImGuiScrollFlags_MaskX_));\n    IM_ASSERT((flags & ImGuiScrollFlags_MaskY_) == 0 || ImIsPowerOfTwo(flags & ImGuiScrollFlags_MaskY_));\n\n    // Defaults\n    ImGuiScrollFlags in_flags = flags;\n    if ((flags & ImGuiScrollFlags_MaskX_) == 0 && window->ScrollbarX)\n        flags |= ImGuiScrollFlags_KeepVisibleEdgeX;\n    if ((flags & ImGuiScrollFlags_MaskY_) == 0)\n        flags |= window->Appearing ? ImGuiScrollFlags_AlwaysCenterY : ImGuiScrollFlags_KeepVisibleEdgeY;\n\n    const bool fully_visible_x = item_rect.Min.x >= scroll_rect.Min.x && item_rect.Max.x <= scroll_rect.Max.x;\n    const bool fully_visible_y = item_rect.Min.y >= scroll_rect.Min.y && item_rect.Max.y <= scroll_rect.Max.y;\n    const bool can_be_fully_visible_x = (item_rect.GetWidth() + g.Style.ItemSpacing.x * 2.0f) <= scroll_rect.GetWidth() || (window->AutoFitFramesX > 0) || (window->Flags & ImGuiWindowFlags_AlwaysAutoResize) != 0;\n    const bool can_be_fully_visible_y = (item_rect.GetHeight() + g.Style.ItemSpacing.y * 2.0f) <= scroll_rect.GetHeight() || (window->AutoFitFramesY > 0) || (window->Flags & ImGuiWindowFlags_AlwaysAutoResize) != 0;\n\n    if ((flags & ImGuiScrollFlags_KeepVisibleEdgeX) && !fully_visible_x)\n    {\n        if (item_rect.Min.x < scroll_rect.Min.x || !can_be_fully_visible_x)\n            SetScrollFromPosX(window, item_rect.Min.x - g.Style.ItemSpacing.x - window->Pos.x, 0.0f);\n        else if (item_rect.Max.x >= scroll_rect.Max.x)\n            SetScrollFromPosX(window, item_rect.Max.x + g.Style.ItemSpacing.x - window->Pos.x, 1.0f);\n    }\n    else if (((flags & ImGuiScrollFlags_KeepVisibleCenterX) && !fully_visible_x) || (flags & ImGuiScrollFlags_AlwaysCenterX))\n    {\n        if (can_be_fully_visible_x)\n            SetScrollFromPosX(window, ImTrunc((item_rect.Min.x + item_rect.Max.x) * 0.5f) - window->Pos.x, 0.5f);\n        else\n            SetScrollFromPosX(window, item_rect.Min.x - window->Pos.x, 0.0f);\n    }\n\n    if ((flags & ImGuiScrollFlags_KeepVisibleEdgeY) && !fully_visible_y)\n    {\n        if (item_rect.Min.y < scroll_rect.Min.y || !can_be_fully_visible_y)\n            SetScrollFromPosY(window, item_rect.Min.y - g.Style.ItemSpacing.y - window->Pos.y, 0.0f);\n        else if (item_rect.Max.y >= scroll_rect.Max.y)\n            SetScrollFromPosY(window, item_rect.Max.y + g.Style.ItemSpacing.y - window->Pos.y, 1.0f);\n    }\n    else if (((flags & ImGuiScrollFlags_KeepVisibleCenterY) && !fully_visible_y) || (flags & ImGuiScrollFlags_AlwaysCenterY))\n    {\n        if (can_be_fully_visible_y)\n            SetScrollFromPosY(window, ImTrunc((item_rect.Min.y + item_rect.Max.y) * 0.5f) - window->Pos.y, 0.5f);\n        else\n            SetScrollFromPosY(window, item_rect.Min.y - window->Pos.y, 0.0f);\n    }\n\n    ImVec2 next_scroll = CalcNextScrollFromScrollTargetAndClamp(window);\n    ImVec2 delta_scroll = next_scroll - window->Scroll;\n\n    // Also scroll parent window to keep us into view if necessary\n    if (!(flags & ImGuiScrollFlags_NoScrollParent) && (window->Flags & ImGuiWindowFlags_ChildWindow))\n    {\n        // FIXME-SCROLL: May be an option?\n        if ((in_flags & (ImGuiScrollFlags_AlwaysCenterX | ImGuiScrollFlags_KeepVisibleCenterX)) != 0)\n            in_flags = (in_flags & ~ImGuiScrollFlags_MaskX_) | ImGuiScrollFlags_KeepVisibleEdgeX;\n        if ((in_flags & (ImGuiScrollFlags_AlwaysCenterY | ImGuiScrollFlags_KeepVisibleCenterY)) != 0)\n            in_flags = (in_flags & ~ImGuiScrollFlags_MaskY_) | ImGuiScrollFlags_KeepVisibleEdgeY;\n        delta_scroll += ScrollToRectEx(window->ParentWindow, ImRect(item_rect.Min - delta_scroll, item_rect.Max - delta_scroll), in_flags);\n    }\n\n    return delta_scroll;\n}\n\nfloat ImGui::GetScrollX()\n{\n    ImGuiWindow* window = GImGui->CurrentWindow;\n    return window->Scroll.x;\n}\n\nfloat ImGui::GetScrollY()\n{\n    ImGuiWindow* window = GImGui->CurrentWindow;\n    return window->Scroll.y;\n}\n\nfloat ImGui::GetScrollMaxX()\n{\n    ImGuiWindow* window = GImGui->CurrentWindow;\n    return window->ScrollMax.x;\n}\n\nfloat ImGui::GetScrollMaxY()\n{\n    ImGuiWindow* window = GImGui->CurrentWindow;\n    return window->ScrollMax.y;\n}\n\nvoid ImGui::SetScrollX(ImGuiWindow* window, float scroll_x)\n{\n    window->ScrollTarget.x = scroll_x;\n    window->ScrollTargetCenterRatio.x = 0.0f;\n    window->ScrollTargetEdgeSnapDist.x = 0.0f;\n}\n\nvoid ImGui::SetScrollY(ImGuiWindow* window, float scroll_y)\n{\n    window->ScrollTarget.y = scroll_y;\n    window->ScrollTargetCenterRatio.y = 0.0f;\n    window->ScrollTargetEdgeSnapDist.y = 0.0f;\n}\n\nvoid ImGui::SetScrollX(float scroll_x)\n{\n    ImGuiContext& g = *GImGui;\n    SetScrollX(g.CurrentWindow, scroll_x);\n}\n\nvoid ImGui::SetScrollY(float scroll_y)\n{\n    ImGuiContext& g = *GImGui;\n    SetScrollY(g.CurrentWindow, scroll_y);\n}\n\n// Note that a local position will vary depending on initial scroll value,\n// This is a little bit confusing so bear with us:\n//  - local_pos = (absolution_pos - window->Pos)\n//  - So local_x/local_y are 0.0f for a position at the upper-left corner of a window,\n//    and generally local_x/local_y are >(padding+decoration) && <(size-padding-decoration) when in the visible area.\n//  - They mostly exist because of legacy API.\n// Following the rules above, when trying to work with scrolling code, consider that:\n//  - SetScrollFromPosY(0.0f) == SetScrollY(0.0f + scroll.y) == has no effect!\n//  - SetScrollFromPosY(-scroll.y) == SetScrollY(-scroll.y + scroll.y) == SetScrollY(0.0f) == reset scroll. Of course writing SetScrollY(0.0f) directly then makes more sense\n// We store a target position so centering and clamping can occur on the next frame when we are guaranteed to have a known window size\nvoid ImGui::SetScrollFromPosX(ImGuiWindow* window, float local_x, float center_x_ratio)\n{\n    IM_ASSERT(center_x_ratio >= 0.0f && center_x_ratio <= 1.0f);\n    window->ScrollTarget.x = IM_TRUNC(local_x - window->DecoOuterSizeX1 - window->DecoInnerSizeX1 + window->Scroll.x); // Convert local position to scroll offset\n    window->ScrollTargetCenterRatio.x = center_x_ratio;\n    window->ScrollTargetEdgeSnapDist.x = 0.0f;\n}\n\nvoid ImGui::SetScrollFromPosY(ImGuiWindow* window, float local_y, float center_y_ratio)\n{\n    IM_ASSERT(center_y_ratio >= 0.0f && center_y_ratio <= 1.0f);\n    window->ScrollTarget.y = IM_TRUNC(local_y - window->DecoOuterSizeY1 - window->DecoInnerSizeY1 + window->Scroll.y); // Convert local position to scroll offset\n    window->ScrollTargetCenterRatio.y = center_y_ratio;\n    window->ScrollTargetEdgeSnapDist.y = 0.0f;\n}\n\nvoid ImGui::SetScrollFromPosX(float local_x, float center_x_ratio)\n{\n    ImGuiContext& g = *GImGui;\n    SetScrollFromPosX(g.CurrentWindow, local_x, center_x_ratio);\n}\n\nvoid ImGui::SetScrollFromPosY(float local_y, float center_y_ratio)\n{\n    ImGuiContext& g = *GImGui;\n    SetScrollFromPosY(g.CurrentWindow, local_y, center_y_ratio);\n}\n\n// center_x_ratio: 0.0f left of last item, 0.5f horizontal center of last item, 1.0f right of last item.\nvoid ImGui::SetScrollHereX(float center_x_ratio)\n{\n    ImGuiContext& g = *GImGui;\n    ImGuiWindow* window = g.CurrentWindow;\n    float spacing_x = ImMax(window->WindowPadding.x, g.Style.ItemSpacing.x);\n    float target_pos_x = ImLerp(g.LastItemData.Rect.Min.x - spacing_x, g.LastItemData.Rect.Max.x + spacing_x, center_x_ratio);\n    SetScrollFromPosX(window, target_pos_x - window->Pos.x, center_x_ratio); // Convert from absolute to local pos\n\n    // Tweak: snap on edges when aiming at an item very close to the edge\n    window->ScrollTargetEdgeSnapDist.x = ImMax(0.0f, window->WindowPadding.x - spacing_x);\n}\n\n// center_y_ratio: 0.0f top of last item, 0.5f vertical center of last item, 1.0f bottom of last item.\nvoid ImGui::SetScrollHereY(float center_y_ratio)\n{\n    ImGuiContext& g = *GImGui;\n    ImGuiWindow* window = g.CurrentWindow;\n    float spacing_y = ImMax(window->WindowPadding.y, g.Style.ItemSpacing.y);\n    float target_pos_y = ImLerp(window->DC.CursorPosPrevLine.y - spacing_y, window->DC.CursorPosPrevLine.y + window->DC.PrevLineSize.y + spacing_y, center_y_ratio);\n    SetScrollFromPosY(window, target_pos_y - window->Pos.y, center_y_ratio); // Convert from absolute to local pos\n\n    // Tweak: snap on edges when aiming at an item very close to the edge\n    window->ScrollTargetEdgeSnapDist.y = ImMax(0.0f, window->WindowPadding.y - spacing_y);\n}\n\n//-----------------------------------------------------------------------------\n// [SECTION] TOOLTIPS\n//-----------------------------------------------------------------------------\n\nbool ImGui::BeginTooltip()\n{\n    return BeginTooltipEx(ImGuiTooltipFlags_None, ImGuiWindowFlags_None);\n}\n\nbool ImGui::BeginItemTooltip()\n{\n    if (!IsItemHovered(ImGuiHoveredFlags_ForTooltip))\n        return false;\n    return BeginTooltipEx(ImGuiTooltipFlags_None, ImGuiWindowFlags_None);\n}\n\nbool ImGui::BeginTooltipEx(ImGuiTooltipFlags tooltip_flags, ImGuiWindowFlags extra_window_flags)\n{\n    ImGuiContext& g = *GImGui;\n\n    const bool is_dragdrop_tooltip = g.DragDropWithinSource || g.DragDropWithinTarget;\n    if (is_dragdrop_tooltip)\n    {\n        // Drag and Drop tooltips are positioning differently than other tooltips:\n        // - offset visibility to increase visibility around mouse.\n        // - never clamp within outer viewport boundary.\n        // We call SetNextWindowPos() to enforce position and disable clamping.\n        // See FindBestWindowPosForPopup() for positionning logic of other tooltips (not drag and drop ones).\n        //ImVec2 tooltip_pos = g.IO.MousePos - g.ActiveIdClickOffset - g.Style.WindowPadding;\n        const bool is_touchscreen = (g.IO.MouseSource == ImGuiMouseSource_TouchScreen);\n        if ((g.NextWindowData.HasFlags & ImGuiNextWindowDataFlags_HasPos) == 0)\n        {\n            ImVec2 tooltip_pos = is_touchscreen ? (g.IO.MousePos + TOOLTIP_DEFAULT_OFFSET_TOUCH * g.Style.MouseCursorScale) : (g.IO.MousePos + TOOLTIP_DEFAULT_OFFSET_MOUSE * g.Style.MouseCursorScale);\n            ImVec2 tooltip_pivot = is_touchscreen ? TOOLTIP_DEFAULT_PIVOT_TOUCH : ImVec2(0.0f, 0.0f);\n            SetNextWindowPos(tooltip_pos, ImGuiCond_None, tooltip_pivot);\n        }\n\n        SetNextWindowBgAlpha(g.Style.Colors[ImGuiCol_PopupBg].w * 0.60f);\n        //PushStyleVar(ImGuiStyleVar_Alpha, g.Style.Alpha * 0.60f); // This would be nice but e.g ColorButton with checkboard has issue with transparent colors :(\n        tooltip_flags |= ImGuiTooltipFlags_OverridePrevious;\n    }\n\n    const char* window_name_template = is_dragdrop_tooltip ? \"##Tooltip_DragDrop_%02d\" : \"##Tooltip_%02d\";\n    char window_name[32];\n    ImFormatString(window_name, IM_ARRAYSIZE(window_name), window_name_template, g.TooltipOverrideCount);\n    if ((tooltip_flags & ImGuiTooltipFlags_OverridePrevious) && g.TooltipPreviousWindow != NULL && g.TooltipPreviousWindow->Active)\n    {\n        // Hide previous tooltip from being displayed. We can't easily \"reset\" the content of a window so we create a new one.\n        //IMGUI_DEBUG_LOG(\"[tooltip] '%s' already active, using +1 for this frame\\n\", window_name);\n        SetWindowHiddenAndSkipItemsForCurrentFrame(g.TooltipPreviousWindow);\n        ImFormatString(window_name, IM_ARRAYSIZE(window_name), window_name_template, ++g.TooltipOverrideCount);\n    }\n    ImGuiWindowFlags flags = ImGuiWindowFlags_Tooltip | ImGuiWindowFlags_NoInputs | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_AlwaysAutoResize;\n    Begin(window_name, NULL, flags | extra_window_flags);\n    // 2023-03-09: Added bool return value to the API, but currently always returning true.\n    // If this ever returns false we need to update BeginDragDropSource() accordingly.\n    //if (!ret)\n    //    End();\n    //return ret;\n    return true;\n}\n\nvoid ImGui::EndTooltip()\n{\n    IM_ASSERT(GetCurrentWindowRead()->Flags & ImGuiWindowFlags_Tooltip);   // Mismatched BeginTooltip()/EndTooltip() calls\n    End();\n}\n\nvoid ImGui::SetTooltip(const char* fmt, ...)\n{\n    va_list args;\n    va_start(args, fmt);\n    SetTooltipV(fmt, args);\n    va_end(args);\n}\n\nvoid ImGui::SetTooltipV(const char* fmt, va_list args)\n{\n    if (!BeginTooltipEx(ImGuiTooltipFlags_OverridePrevious, ImGuiWindowFlags_None))\n        return;\n    TextV(fmt, args);\n    EndTooltip();\n}\n\n// Shortcut to use 'style.HoverFlagsForTooltipMouse' or 'style.HoverFlagsForTooltipNav'.\n// Defaults to == ImGuiHoveredFlags_Stationary | ImGuiHoveredFlags_DelayShort when using the mouse.\nvoid ImGui::SetItemTooltip(const char* fmt, ...)\n{\n    va_list args;\n    va_start(args, fmt);\n    if (IsItemHovered(ImGuiHoveredFlags_ForTooltip))\n        SetTooltipV(fmt, args);\n    va_end(args);\n}\n\nvoid ImGui::SetItemTooltipV(const char* fmt, va_list args)\n{\n    if (IsItemHovered(ImGuiHoveredFlags_ForTooltip))\n        SetTooltipV(fmt, args);\n}\n\n\n//-----------------------------------------------------------------------------\n// [SECTION] POPUPS\n//-----------------------------------------------------------------------------\n\n// Supported flags: ImGuiPopupFlags_AnyPopupId, ImGuiPopupFlags_AnyPopupLevel\nbool ImGui::IsPopupOpen(ImGuiID id, ImGuiPopupFlags popup_flags)\n{\n    ImGuiContext& g = *GImGui;\n    if (popup_flags & ImGuiPopupFlags_AnyPopupId)\n    {\n        // Return true if any popup is open at the current BeginPopup() level of the popup stack\n        // This may be used to e.g. test for another popups already opened to handle popups priorities at the same level.\n        IM_ASSERT(id == 0);\n        if (popup_flags & ImGuiPopupFlags_AnyPopupLevel)\n            return g.OpenPopupStack.Size > 0;\n        else\n            return g.OpenPopupStack.Size > g.BeginPopupStack.Size;\n    }\n    else\n    {\n        if (popup_flags & ImGuiPopupFlags_AnyPopupLevel)\n        {\n            // Return true if the popup is open anywhere in the popup stack\n            for (int n = 0; n < g.OpenPopupStack.Size; n++)\n                if (g.OpenPopupStack[n].PopupId == id)\n                    return true;\n            return false;\n        }\n        else\n        {\n            // Return true if the popup is open at the current BeginPopup() level of the popup stack (this is the most-common query)\n            return g.OpenPopupStack.Size > g.BeginPopupStack.Size && g.OpenPopupStack[g.BeginPopupStack.Size].PopupId == id;\n        }\n    }\n}\n\nbool ImGui::IsPopupOpen(const char* str_id, ImGuiPopupFlags popup_flags)\n{\n    ImGuiContext& g = *GImGui;\n    ImGuiID id = (popup_flags & ImGuiPopupFlags_AnyPopupId) ? 0 : g.CurrentWindow->GetID(str_id);\n    if ((popup_flags & ImGuiPopupFlags_AnyPopupLevel) && id != 0)\n        IM_ASSERT(0 && \"Cannot use IsPopupOpen() with a string id and ImGuiPopupFlags_AnyPopupLevel.\"); // But non-string version is legal and used internally\n    return IsPopupOpen(id, popup_flags);\n}\n\n// Also see FindBlockingModal(NULL)\nImGuiWindow* ImGui::GetTopMostPopupModal()\n{\n    ImGuiContext& g = *GImGui;\n    for (int n = g.OpenPopupStack.Size - 1; n >= 0; n--)\n        if (ImGuiWindow* popup = g.OpenPopupStack.Data[n].Window)\n            if (popup->Flags & ImGuiWindowFlags_Modal)\n                return popup;\n    return NULL;\n}\n\n// See Demo->Stacked Modal to confirm what this is for.\nImGuiWindow* ImGui::GetTopMostAndVisiblePopupModal()\n{\n    ImGuiContext& g = *GImGui;\n    for (int n = g.OpenPopupStack.Size - 1; n >= 0; n--)\n        if (ImGuiWindow* popup = g.OpenPopupStack.Data[n].Window)\n            if ((popup->Flags & ImGuiWindowFlags_Modal) && IsWindowActiveAndVisible(popup))\n                return popup;\n    return NULL;\n}\n\n\n// When a modal popup is open, newly created windows that want focus (i.e. are not popups and do not specify ImGuiWindowFlags_NoFocusOnAppearing)\n// should be positioned behind that modal window, unless the window was created inside the modal begin-stack.\n// In case of multiple stacked modals newly created window honors begin stack order and does not go below its own modal parent.\n// - WindowA            // FindBlockingModal() returns Modal1\n//   - WindowB          //                  .. returns Modal1\n//   - Modal1           //                  .. returns Modal2\n//      - WindowC       //                  .. returns Modal2\n//          - WindowD   //                  .. returns Modal2\n//          - Modal2    //                  .. returns Modal2\n//            - WindowE //                  .. returns NULL\n// Notes:\n// - FindBlockingModal(NULL) == NULL is generally equivalent to GetTopMostPopupModal() == NULL.\n//   Only difference is here we check for ->Active/WasActive but it may be unnecessary.\nImGuiWindow* ImGui::FindBlockingModal(ImGuiWindow* window)\n{\n    ImGuiContext& g = *GImGui;\n    if (g.OpenPopupStack.Size <= 0)\n        return NULL;\n\n    // Find a modal that has common parent with specified window. Specified window should be positioned behind that modal.\n    for (ImGuiPopupData& popup_data : g.OpenPopupStack)\n    {\n        ImGuiWindow* popup_window = popup_data.Window;\n        if (popup_window == NULL || !(popup_window->Flags & ImGuiWindowFlags_Modal))\n            continue;\n        if (!popup_window->Active && !popup_window->WasActive)  // Check WasActive, because this code may run before popup renders on current frame, also check Active to handle newly created windows.\n            continue;\n        if (window == NULL)                                     // FindBlockingModal(NULL) test for if FocusWindow(NULL) is naturally possible via a mouse click.\n            return popup_window;\n        if (IsWindowWithinBeginStackOf(window, popup_window))   // Window may be over modal\n            continue;\n        return popup_window;                                    // Place window right below first block modal\n    }\n    return NULL;\n}\n\nvoid ImGui::OpenPopup(const char* str_id, ImGuiPopupFlags popup_flags)\n{\n    ImGuiContext& g = *GImGui;\n    ImGuiID id = g.CurrentWindow->GetID(str_id);\n    IMGUI_DEBUG_LOG_POPUP(\"[popup] OpenPopup(\\\"%s\\\" -> 0x%08X)\\n\", str_id, id);\n    OpenPopupEx(id, popup_flags);\n}\n\nvoid ImGui::OpenPopup(ImGuiID id, ImGuiPopupFlags popup_flags)\n{\n    OpenPopupEx(id, popup_flags);\n}\n\n// Mark popup as open (toggle toward open state).\n// Popups are closed when user click outside, or activate a pressable item, or CloseCurrentPopup() is called within a BeginPopup()/EndPopup() block.\n// Popup identifiers are relative to the current ID-stack (so OpenPopup and BeginPopup needs to be at the same level).\n// One open popup per level of the popup hierarchy (NB: when assigning we reset the Window member of ImGuiPopupRef to NULL)\nvoid ImGui::OpenPopupEx(ImGuiID id, ImGuiPopupFlags popup_flags)\n{\n    ImGuiContext& g = *GImGui;\n    ImGuiWindow* parent_window = g.CurrentWindow;\n    const int current_stack_size = g.BeginPopupStack.Size;\n\n    if (popup_flags & ImGuiPopupFlags_NoOpenOverExistingPopup)\n        if (IsPopupOpen((ImGuiID)0, ImGuiPopupFlags_AnyPopupId))\n            return;\n\n    ImGuiPopupData popup_ref; // Tagged as new ref as Window will be set back to NULL if we write this into OpenPopupStack.\n    popup_ref.PopupId = id;\n    popup_ref.Window = NULL;\n    popup_ref.RestoreNavWindow = g.NavWindow;           // When popup closes focus may be restored to NavWindow (depend on window type).\n    popup_ref.OpenFrameCount = g.FrameCount;\n    popup_ref.OpenParentId = parent_window->IDStack.back();\n    popup_ref.OpenPopupPos = NavCalcPreferredRefPos();\n    popup_ref.OpenMousePos = IsMousePosValid(&g.IO.MousePos) ? g.IO.MousePos : popup_ref.OpenPopupPos;\n\n    IMGUI_DEBUG_LOG_POPUP(\"[popup] OpenPopupEx(0x%08X)\\n\", id);\n    if (g.OpenPopupStack.Size < current_stack_size + 1)\n    {\n        g.OpenPopupStack.push_back(popup_ref);\n    }\n    else\n    {\n        // Gently handle the user mistakenly calling OpenPopup() every frames: it is likely a programming mistake!\n        // However, if we were to run the regular code path, the ui would become completely unusable because the popup will always be\n        // in hidden-while-calculating-size state _while_ claiming focus. Which is extremely confusing situation for the programmer.\n        // Instead, for successive frames calls to OpenPopup(), we silently avoid reopening even if ImGuiPopupFlags_NoReopen is not specified.\n        bool keep_existing = false;\n        if (g.OpenPopupStack[current_stack_size].PopupId == id)\n            if ((g.OpenPopupStack[current_stack_size].OpenFrameCount == g.FrameCount - 1) || (popup_flags & ImGuiPopupFlags_NoReopen))\n                keep_existing = true;\n        if (keep_existing)\n        {\n            // No reopen\n            g.OpenPopupStack[current_stack_size].OpenFrameCount = popup_ref.OpenFrameCount;\n        }\n        else\n        {\n            // Reopen: close child popups if any, then flag popup for open/reopen (set position, focus, init navigation)\n            ClosePopupToLevel(current_stack_size, true);\n            g.OpenPopupStack.push_back(popup_ref);\n        }\n\n        // When reopening a popup we first refocus its parent, otherwise if its parent is itself a popup it would get closed by ClosePopupsOverWindow().\n        // This is equivalent to what ClosePopupToLevel() does.\n        //if (g.OpenPopupStack[current_stack_size].PopupId == id)\n        //    FocusWindow(parent_window);\n    }\n}\n\n// When popups are stacked, clicking on a lower level popups puts focus back to it and close popups above it.\n// This function closes any popups that are over 'ref_window'.\nvoid ImGui::ClosePopupsOverWindow(ImGuiWindow* ref_window, bool restore_focus_to_window_under_popup)\n{\n    ImGuiContext& g = *GImGui;\n    if (g.OpenPopupStack.Size == 0)\n        return;\n\n    // Don't close our own child popup windows.\n    //IMGUI_DEBUG_LOG_POPUP(\"[popup] ClosePopupsOverWindow(\\\"%s\\\") restore_under=%d\\n\", ref_window ? ref_window->Name : \"<NULL>\", restore_focus_to_window_under_popup);\n    int popup_count_to_keep = 0;\n    if (ref_window)\n    {\n        // Find the highest popup which is a descendant of the reference window (generally reference window = NavWindow)\n        for (; popup_count_to_keep < g.OpenPopupStack.Size; popup_count_to_keep++)\n        {\n            ImGuiPopupData& popup = g.OpenPopupStack[popup_count_to_keep];\n            if (!popup.Window)\n                continue;\n            IM_ASSERT((popup.Window->Flags & ImGuiWindowFlags_Popup) != 0);\n\n            // Trim the stack unless the popup is a direct parent of the reference window (the reference window is often the NavWindow)\n            // - Clicking/Focusing Window2 won't close Popup1:\n            //     Window -> Popup1 -> Window2(Ref)\n            // - Clicking/focusing Popup1 will close Popup2 and Popup3:\n            //     Window -> Popup1(Ref) -> Popup2 -> Popup3\n            // - Each popups may contain child windows, which is why we compare ->RootWindow!\n            //     Window -> Popup1 -> Popup1_Child -> Popup2 -> Popup2_Child\n            // We step through every popup from bottom to top to validate their position relative to reference window.\n            bool ref_window_is_descendent_of_popup = false;\n            for (int n = popup_count_to_keep; n < g.OpenPopupStack.Size; n++)\n                if (ImGuiWindow* popup_window = g.OpenPopupStack[n].Window)\n                    if (IsWindowWithinBeginStackOf(ref_window, popup_window))\n                    {\n                        ref_window_is_descendent_of_popup = true;\n                        break;\n                    }\n            if (!ref_window_is_descendent_of_popup)\n                break;\n        }\n    }\n    if (popup_count_to_keep < g.OpenPopupStack.Size) // This test is not required but it allows to set a convenient breakpoint on the statement below\n    {\n        IMGUI_DEBUG_LOG_POPUP(\"[popup] ClosePopupsOverWindow(\\\"%s\\\")\\n\", ref_window ? ref_window->Name : \"<NULL>\");\n        ClosePopupToLevel(popup_count_to_keep, restore_focus_to_window_under_popup);\n    }\n}\n\nvoid ImGui::ClosePopupsExceptModals()\n{\n    ImGuiContext& g = *GImGui;\n\n    int popup_count_to_keep;\n    for (popup_count_to_keep = g.OpenPopupStack.Size; popup_count_to_keep > 0; popup_count_to_keep--)\n    {\n        ImGuiWindow* window = g.OpenPopupStack[popup_count_to_keep - 1].Window;\n        if (!window || (window->Flags & ImGuiWindowFlags_Modal))\n            break;\n    }\n    if (popup_count_to_keep < g.OpenPopupStack.Size) // This test is not required but it allows to set a convenient breakpoint on the statement below\n        ClosePopupToLevel(popup_count_to_keep, true);\n}\n\nvoid ImGui::ClosePopupToLevel(int remaining, bool restore_focus_to_window_under_popup)\n{\n    ImGuiContext& g = *GImGui;\n    IMGUI_DEBUG_LOG_POPUP(\"[popup] ClosePopupToLevel(%d), restore_under=%d\\n\", remaining, restore_focus_to_window_under_popup);\n    IM_ASSERT(remaining >= 0 && remaining < g.OpenPopupStack.Size);\n    if (g.DebugLogFlags & ImGuiDebugLogFlags_EventPopup)\n        for (int n = remaining; n < g.OpenPopupStack.Size; n++)\n            IMGUI_DEBUG_LOG_POPUP(\"[popup] - Closing PopupID 0x%08X Window \\\"%s\\\"\\n\", g.OpenPopupStack[n].PopupId, g.OpenPopupStack[n].Window ? g.OpenPopupStack[n].Window->Name : NULL);\n\n    // Trim open popup stack\n    ImGuiPopupData prev_popup = g.OpenPopupStack[remaining];\n    g.OpenPopupStack.resize(remaining);\n\n    // Restore focus (unless popup window was not yet submitted, and didn't have a chance to take focus anyhow. See #7325 for an edge case)\n    if (restore_focus_to_window_under_popup && prev_popup.Window)\n    {\n        ImGuiWindow* popup_window = prev_popup.Window;\n        ImGuiWindow* focus_window = (popup_window->Flags & ImGuiWindowFlags_ChildMenu) ? popup_window->ParentWindow : prev_popup.RestoreNavWindow;\n        if (focus_window && !focus_window->WasActive)\n            FocusTopMostWindowUnderOne(popup_window, NULL, NULL, ImGuiFocusRequestFlags_RestoreFocusedChild); // Fallback\n        else\n            FocusWindow(focus_window, (g.NavLayer == ImGuiNavLayer_Main) ? ImGuiFocusRequestFlags_RestoreFocusedChild : ImGuiFocusRequestFlags_None);\n    }\n}\n\n// Close the popup we have begin-ed into.\nvoid ImGui::CloseCurrentPopup()\n{\n    ImGuiContext& g = *GImGui;\n    int popup_idx = g.BeginPopupStack.Size - 1;\n    if (popup_idx < 0 || popup_idx >= g.OpenPopupStack.Size || g.BeginPopupStack[popup_idx].PopupId != g.OpenPopupStack[popup_idx].PopupId)\n        return;\n\n    // Closing a menu closes its top-most parent popup (unless a modal)\n    while (popup_idx > 0)\n    {\n        ImGuiWindow* popup_window = g.OpenPopupStack[popup_idx].Window;\n        ImGuiWindow* parent_popup_window = g.OpenPopupStack[popup_idx - 1].Window;\n        bool close_parent = false;\n        if (popup_window && (popup_window->Flags & ImGuiWindowFlags_ChildMenu))\n            if (parent_popup_window && !(parent_popup_window->Flags & ImGuiWindowFlags_MenuBar))\n                close_parent = true;\n        if (!close_parent)\n            break;\n        popup_idx--;\n    }\n    IMGUI_DEBUG_LOG_POPUP(\"[popup] CloseCurrentPopup %d -> %d\\n\", g.BeginPopupStack.Size - 1, popup_idx);\n    ClosePopupToLevel(popup_idx, true);\n\n    // A common pattern is to close a popup when selecting a menu item/selectable that will open another window.\n    // To improve this usage pattern, we avoid nav highlight for a single frame in the parent window.\n    // Similarly, we could avoid mouse hover highlight in this window but it is less visually problematic.\n    if (ImGuiWindow* window = g.NavWindow)\n        window->DC.NavHideHighlightOneFrame = true;\n}\n\n// Attention! BeginPopup() adds default flags when calling BeginPopupEx()!\nbool ImGui::BeginPopupEx(ImGuiID id, ImGuiWindowFlags extra_window_flags)\n{\n    ImGuiContext& g = *GImGui;\n    if (!IsPopupOpen(id, ImGuiPopupFlags_None))\n    {\n        g.NextWindowData.ClearFlags(); // We behave like Begin() and need to consume those values\n        return false;\n    }\n\n    char name[20];\n    IM_ASSERT((extra_window_flags & ImGuiWindowFlags_ChildMenu) == 0); // Use BeginPopupMenuEx()\n    ImFormatString(name, IM_ARRAYSIZE(name), \"##Popup_%08x\", id); // No recycling, so we can close/open during the same frame\n\n    bool is_open = Begin(name, NULL, extra_window_flags | ImGuiWindowFlags_Popup);\n    if (!is_open) // NB: Begin can return false when the popup is completely clipped (e.g. zero size display)\n        EndPopup();\n    //g.CurrentWindow->FocusRouteParentWindow = g.CurrentWindow->ParentWindowInBeginStack;\n    return is_open;\n}\n\nbool ImGui::BeginPopupMenuEx(ImGuiID id, const char* label, ImGuiWindowFlags extra_window_flags)\n{\n    ImGuiContext& g = *GImGui;\n    if (!IsPopupOpen(id, ImGuiPopupFlags_None))\n    {\n        g.NextWindowData.ClearFlags(); // We behave like Begin() and need to consume those values\n        return false;\n    }\n\n    char name[128];\n    IM_ASSERT(extra_window_flags & ImGuiWindowFlags_ChildMenu);\n    ImFormatString(name, IM_ARRAYSIZE(name), \"%s###Menu_%02d\", label, g.BeginMenuDepth); // Recycle windows based on depth\n    bool is_open = Begin(name, NULL, extra_window_flags | ImGuiWindowFlags_Popup);\n    if (!is_open) // NB: Begin can return false when the popup is completely clipped (e.g. zero size display)\n        EndPopup();\n    //g.CurrentWindow->FocusRouteParentWindow = g.CurrentWindow->ParentWindowInBeginStack;\n    return is_open;\n}\n\nbool ImGui::BeginPopup(const char* str_id, ImGuiWindowFlags flags)\n{\n    ImGuiContext& g = *GImGui;\n    if (g.OpenPopupStack.Size <= g.BeginPopupStack.Size) // Early out for performance\n    {\n        g.NextWindowData.ClearFlags(); // We behave like Begin() and need to consume those values\n        return false;\n    }\n    flags |= ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoSavedSettings;\n    ImGuiID id = g.CurrentWindow->GetID(str_id);\n    return BeginPopupEx(id, flags);\n}\n\n// If 'p_open' is specified for a modal popup window, the popup will have a regular close button which will close the popup.\n// Note that popup visibility status is owned by Dear ImGui (and manipulated with e.g. OpenPopup).\n// - *p_open set back to false in BeginPopupModal() when popup is not open.\n// - if you set *p_open to false before calling BeginPopupModal(), it will close the popup.\nbool ImGui::BeginPopupModal(const char* name, bool* p_open, ImGuiWindowFlags flags)\n{\n    ImGuiContext& g = *GImGui;\n    ImGuiWindow* window = g.CurrentWindow;\n    const ImGuiID id = window->GetID(name);\n    if (!IsPopupOpen(id, ImGuiPopupFlags_None))\n    {\n        g.NextWindowData.ClearFlags(); // We behave like Begin() and need to consume those values\n        if (p_open && *p_open)\n            *p_open = false;\n        return false;\n    }\n\n    // Center modal windows by default for increased visibility\n    // (this won't really last as settings will kick in, and is mostly for backward compatibility. user may do the same themselves)\n    // FIXME: Should test for (PosCond & window->SetWindowPosAllowFlags) with the upcoming window.\n    if ((g.NextWindowData.HasFlags & ImGuiNextWindowDataFlags_HasPos) == 0)\n    {\n        const ImGuiViewport* viewport = GetMainViewport();\n        SetNextWindowPos(viewport->GetCenter(), ImGuiCond_FirstUseEver, ImVec2(0.5f, 0.5f));\n    }\n\n    flags |= ImGuiWindowFlags_Popup | ImGuiWindowFlags_Modal | ImGuiWindowFlags_NoCollapse;\n    const bool is_open = Begin(name, p_open, flags);\n    if (!is_open || (p_open && !*p_open)) // NB: is_open can be 'false' when the popup is completely clipped (e.g. zero size display)\n    {\n        EndPopup();\n        if (is_open)\n            ClosePopupToLevel(g.BeginPopupStack.Size, true);\n        return false;\n    }\n    return is_open;\n}\n\nvoid ImGui::EndPopup()\n{\n    ImGuiContext& g = *GImGui;\n    ImGuiWindow* window = g.CurrentWindow;\n    IM_ASSERT(window->Flags & ImGuiWindowFlags_Popup);  // Mismatched BeginPopup()/EndPopup() calls\n    IM_ASSERT(g.BeginPopupStack.Size > 0);\n\n    // Make all menus and popups wrap around for now, may need to expose that policy (e.g. focus scope could include wrap/loop policy flags used by new move requests)\n    if (g.NavWindow == window)\n        NavMoveRequestTryWrapping(window, ImGuiNavMoveFlags_LoopY);\n\n    // Child-popups don't need to be laid out\n    const ImGuiID backup_within_end_child_id = g.WithinEndChildID;\n    if (window->Flags & ImGuiWindowFlags_ChildWindow)\n        g.WithinEndChildID = window->ID;\n    End();\n    g.WithinEndChildID = backup_within_end_child_id;\n}\n\n// Helper to open a popup if mouse button is released over the item\n// - This is essentially the same as BeginPopupContextItem() but without the trailing BeginPopup()\nvoid ImGui::OpenPopupOnItemClick(const char* str_id, ImGuiPopupFlags popup_flags)\n{\n    ImGuiContext& g = *GImGui;\n    ImGuiWindow* window = g.CurrentWindow;\n    int mouse_button = (popup_flags & ImGuiPopupFlags_MouseButtonMask_);\n    if (IsMouseReleased(mouse_button) && IsItemHovered(ImGuiHoveredFlags_AllowWhenBlockedByPopup))\n    {\n        ImGuiID id = str_id ? window->GetID(str_id) : g.LastItemData.ID;    // If user hasn't passed an ID, we can use the LastItemID. Using LastItemID as a Popup ID won't conflict!\n        IM_ASSERT(id != 0);                                             // You cannot pass a NULL str_id if the last item has no identifier (e.g. a Text() item)\n        OpenPopupEx(id, popup_flags);\n    }\n}\n\n// This is a helper to handle the simplest case of associating one named popup to one given widget.\n// - To create a popup associated to the last item, you generally want to pass a NULL value to str_id.\n// - To create a popup with a specific identifier, pass it in str_id.\n//    - This is useful when using using BeginPopupContextItem() on an item which doesn't have an identifier, e.g. a Text() call.\n//    - This is useful when multiple code locations may want to manipulate/open the same popup, given an explicit id.\n// - You may want to handle the whole on user side if you have specific needs (e.g. tweaking IsItemHovered() parameters).\n//   This is essentially the same as:\n//       id = str_id ? GetID(str_id) : GetItemID();\n//       OpenPopupOnItemClick(str_id, ImGuiPopupFlags_MouseButtonRight);\n//       return BeginPopup(id);\n//   Which is essentially the same as:\n//       id = str_id ? GetID(str_id) : GetItemID();\n//       if (IsItemHovered() && IsMouseReleased(ImGuiMouseButton_Right))\n//           OpenPopup(id);\n//       return BeginPopup(id);\n//   The main difference being that this is tweaked to avoid computing the ID twice.\nbool ImGui::BeginPopupContextItem(const char* str_id, ImGuiPopupFlags popup_flags)\n{\n    ImGuiContext& g = *GImGui;\n    ImGuiWindow* window = g.CurrentWindow;\n    if (window->SkipItems)\n        return false;\n    ImGuiID id = str_id ? window->GetID(str_id) : g.LastItemData.ID;    // If user hasn't passed an ID, we can use the LastItemID. Using LastItemID as a Popup ID won't conflict!\n    IM_ASSERT(id != 0);                                             // You cannot pass a NULL str_id if the last item has no identifier (e.g. a Text() item)\n    int mouse_button = (popup_flags & ImGuiPopupFlags_MouseButtonMask_);\n    if (IsMouseReleased(mouse_button) && IsItemHovered(ImGuiHoveredFlags_AllowWhenBlockedByPopup))\n        OpenPopupEx(id, popup_flags);\n    return BeginPopupEx(id, ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoSavedSettings);\n}\n\nbool ImGui::BeginPopupContextWindow(const char* str_id, ImGuiPopupFlags popup_flags)\n{\n    ImGuiContext& g = *GImGui;\n    ImGuiWindow* window = g.CurrentWindow;\n    if (!str_id)\n        str_id = \"window_context\";\n    ImGuiID id = window->GetID(str_id);\n    int mouse_button = (popup_flags & ImGuiPopupFlags_MouseButtonMask_);\n    if (IsMouseReleased(mouse_button) && IsWindowHovered(ImGuiHoveredFlags_AllowWhenBlockedByPopup))\n        if (!(popup_flags & ImGuiPopupFlags_NoOpenOverItems) || !IsAnyItemHovered())\n            OpenPopupEx(id, popup_flags);\n    return BeginPopupEx(id, ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoSavedSettings);\n}\n\nbool ImGui::BeginPopupContextVoid(const char* str_id, ImGuiPopupFlags popup_flags)\n{\n    ImGuiContext& g = *GImGui;\n    ImGuiWindow* window = g.CurrentWindow;\n    if (!str_id)\n        str_id = \"void_context\";\n    ImGuiID id = window->GetID(str_id);\n    int mouse_button = (popup_flags & ImGuiPopupFlags_MouseButtonMask_);\n    if (IsMouseReleased(mouse_button) && !IsWindowHovered(ImGuiHoveredFlags_AnyWindow))\n        if (GetTopMostPopupModal() == NULL)\n            OpenPopupEx(id, popup_flags);\n    return BeginPopupEx(id, ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoSavedSettings);\n}\n\n// r_avoid = the rectangle to avoid (e.g. for tooltip it is a rectangle around the mouse cursor which we want to avoid. for popups it's a small point around the cursor.)\n// r_outer = the visible area rectangle, minus safe area padding. If our popup size won't fit because of safe area padding we ignore it.\n// (r_outer is usually equivalent to the viewport rectangle minus padding, but when multi-viewports are enabled and monitor\n//  information are available, it may represent the entire platform monitor from the frame of reference of the current viewport.\n//  this allows us to have tooltips/popups displayed out of the parent viewport.)\nImVec2 ImGui::FindBestWindowPosForPopupEx(const ImVec2& ref_pos, const ImVec2& size, ImGuiDir* last_dir, const ImRect& r_outer, const ImRect& r_avoid, ImGuiPopupPositionPolicy policy)\n{\n    ImVec2 base_pos_clamped = ImClamp(ref_pos, r_outer.Min, r_outer.Max - size);\n    //GetForegroundDrawList()->AddRect(r_avoid.Min, r_avoid.Max, IM_COL32(255,0,0,255));\n    //GetForegroundDrawList()->AddRect(r_outer.Min, r_outer.Max, IM_COL32(0,255,0,255));\n\n    // Combo Box policy (we want a connecting edge)\n    if (policy == ImGuiPopupPositionPolicy_ComboBox)\n    {\n        const ImGuiDir dir_prefered_order[ImGuiDir_COUNT] = { ImGuiDir_Down, ImGuiDir_Right, ImGuiDir_Left, ImGuiDir_Up };\n        for (int n = (*last_dir != ImGuiDir_None) ? -1 : 0; n < ImGuiDir_COUNT; n++)\n        {\n            const ImGuiDir dir = (n == -1) ? *last_dir : dir_prefered_order[n];\n            if (n != -1 && dir == *last_dir) // Already tried this direction?\n                continue;\n            ImVec2 pos;\n            if (dir == ImGuiDir_Down)  pos = ImVec2(r_avoid.Min.x, r_avoid.Max.y);          // Below, Toward Right (default)\n            if (dir == ImGuiDir_Right) pos = ImVec2(r_avoid.Min.x, r_avoid.Min.y - size.y); // Above, Toward Right\n            if (dir == ImGuiDir_Left)  pos = ImVec2(r_avoid.Max.x - size.x, r_avoid.Max.y); // Below, Toward Left\n            if (dir == ImGuiDir_Up)    pos = ImVec2(r_avoid.Max.x - size.x, r_avoid.Min.y - size.y); // Above, Toward Left\n            if (!r_outer.Contains(ImRect(pos, pos + size)))\n                continue;\n            *last_dir = dir;\n            return pos;\n        }\n    }\n\n    // Tooltip and Default popup policy\n    // (Always first try the direction we used on the last frame, if any)\n    if (policy == ImGuiPopupPositionPolicy_Tooltip || policy == ImGuiPopupPositionPolicy_Default)\n    {\n        const ImGuiDir dir_prefered_order[ImGuiDir_COUNT] = { ImGuiDir_Right, ImGuiDir_Down, ImGuiDir_Up, ImGuiDir_Left };\n        for (int n = (*last_dir != ImGuiDir_None) ? -1 : 0; n < ImGuiDir_COUNT; n++)\n        {\n            const ImGuiDir dir = (n == -1) ? *last_dir : dir_prefered_order[n];\n            if (n != -1 && dir == *last_dir) // Already tried this direction?\n                continue;\n\n            const float avail_w = (dir == ImGuiDir_Left ? r_avoid.Min.x : r_outer.Max.x) - (dir == ImGuiDir_Right ? r_avoid.Max.x : r_outer.Min.x);\n            const float avail_h = (dir == ImGuiDir_Up ? r_avoid.Min.y : r_outer.Max.y) - (dir == ImGuiDir_Down ? r_avoid.Max.y : r_outer.Min.y);\n\n            // If there's not enough room on one axis, there's no point in positioning on a side on this axis (e.g. when not enough width, use a top/bottom position to maximize available width)\n            if (avail_w < size.x && (dir == ImGuiDir_Left || dir == ImGuiDir_Right))\n                continue;\n            if (avail_h < size.y && (dir == ImGuiDir_Up || dir == ImGuiDir_Down))\n                continue;\n\n            ImVec2 pos;\n            pos.x = (dir == ImGuiDir_Left) ? r_avoid.Min.x - size.x : (dir == ImGuiDir_Right) ? r_avoid.Max.x : base_pos_clamped.x;\n            pos.y = (dir == ImGuiDir_Up) ? r_avoid.Min.y - size.y : (dir == ImGuiDir_Down) ? r_avoid.Max.y : base_pos_clamped.y;\n\n            // Clamp top-left corner of popup\n            pos.x = ImMax(pos.x, r_outer.Min.x);\n            pos.y = ImMax(pos.y, r_outer.Min.y);\n\n            *last_dir = dir;\n            return pos;\n        }\n    }\n\n    // Fallback when not enough room:\n    *last_dir = ImGuiDir_None;\n\n    // For tooltip we prefer avoiding the cursor at all cost even if it means that part of the tooltip won't be visible.\n    if (policy == ImGuiPopupPositionPolicy_Tooltip)\n        return ref_pos + ImVec2(2, 2);\n\n    // Otherwise try to keep within display\n    ImVec2 pos = ref_pos;\n    pos.x = ImMax(ImMin(pos.x + size.x, r_outer.Max.x) - size.x, r_outer.Min.x);\n    pos.y = ImMax(ImMin(pos.y + size.y, r_outer.Max.y) - size.y, r_outer.Min.y);\n    return pos;\n}\n\n// Note that this is used for popups, which can overlap the non work-area of individual viewports.\nImRect ImGui::GetPopupAllowedExtentRect(ImGuiWindow* window)\n{\n    ImGuiContext& g = *GImGui;\n    IM_UNUSED(window);\n    ImRect r_screen = ((ImGuiViewportP*)(void*)GetMainViewport())->GetMainRect();\n    ImVec2 padding = g.Style.DisplaySafeAreaPadding;\n    r_screen.Expand(ImVec2((r_screen.GetWidth() > padding.x * 2) ? -padding.x : 0.0f, (r_screen.GetHeight() > padding.y * 2) ? -padding.y : 0.0f));\n    return r_screen;\n}\n\nImVec2 ImGui::FindBestWindowPosForPopup(ImGuiWindow* window)\n{\n    ImGuiContext& g = *GImGui;\n\n    ImRect r_outer = GetPopupAllowedExtentRect(window);\n    if (window->Flags & ImGuiWindowFlags_ChildMenu)\n    {\n        // Child menus typically request _any_ position within the parent menu item, and then we move the new menu outside the parent bounds.\n        // This is how we end up with child menus appearing (most-commonly) on the right of the parent menu.\n        IM_ASSERT(g.CurrentWindow == window);\n        ImGuiWindow* parent_window = g.CurrentWindowStack[g.CurrentWindowStack.Size - 2].Window;\n        float horizontal_overlap = g.Style.ItemInnerSpacing.x; // We want some overlap to convey the relative depth of each menu (currently the amount of overlap is hard-coded to style.ItemSpacing.x).\n        ImRect r_avoid;\n        if (parent_window->DC.MenuBarAppending)\n            r_avoid = ImRect(-FLT_MAX, parent_window->ClipRect.Min.y, FLT_MAX, parent_window->ClipRect.Max.y); // Avoid parent menu-bar. If we wanted multi-line menu-bar, we may instead want to have the calling window setup e.g. a NextWindowData.PosConstraintAvoidRect field\n        else\n            r_avoid = ImRect(parent_window->Pos.x + horizontal_overlap, -FLT_MAX, parent_window->Pos.x + parent_window->Size.x - horizontal_overlap - parent_window->ScrollbarSizes.x, FLT_MAX);\n        return FindBestWindowPosForPopupEx(window->Pos, window->Size, &window->AutoPosLastDirection, r_outer, r_avoid, ImGuiPopupPositionPolicy_Default);\n    }\n    if (window->Flags & ImGuiWindowFlags_Popup)\n    {\n        return FindBestWindowPosForPopupEx(window->Pos, window->Size, &window->AutoPosLastDirection, r_outer, ImRect(window->Pos, window->Pos), ImGuiPopupPositionPolicy_Default); // Ideally we'd disable r_avoid here\n    }\n    if (window->Flags & ImGuiWindowFlags_Tooltip)\n    {\n        // Position tooltip (always follows mouse + clamp within outer boundaries)\n        // FIXME:\n        // - Too many paths. One problem is that FindBestWindowPosForPopupEx() doesn't allow passing a suggested position (so touch screen path doesn't use it by default).\n        // - Drag and drop tooltips are not using this path either: BeginTooltipEx() manually sets their position.\n        // - Require some tidying up. In theory we could handle both cases in same location, but requires a bit of shuffling\n        //   as drag and drop tooltips are calling SetNextWindowPos() leading to 'window_pos_set_by_api' being set in Begin().\n        IM_ASSERT(g.CurrentWindow == window);\n        const float scale = g.Style.MouseCursorScale;\n        const ImVec2 ref_pos = NavCalcPreferredRefPos();\n\n        if (g.IO.MouseSource == ImGuiMouseSource_TouchScreen && NavCalcPreferredRefPosSource() == ImGuiInputSource_Mouse)\n        {\n            ImVec2 tooltip_pos = ref_pos + TOOLTIP_DEFAULT_OFFSET_TOUCH * scale - (TOOLTIP_DEFAULT_PIVOT_TOUCH * window->Size);\n            if (r_outer.Contains(ImRect(tooltip_pos, tooltip_pos + window->Size)))\n                return tooltip_pos;\n        }\n\n        ImVec2 tooltip_pos = ref_pos + TOOLTIP_DEFAULT_OFFSET_MOUSE * scale;\n        ImRect r_avoid;\n        if (g.NavCursorVisible && g.NavHighlightItemUnderNav && !g.IO.ConfigNavMoveSetMousePos)\n            r_avoid = ImRect(ref_pos.x - 16, ref_pos.y - 8, ref_pos.x + 16, ref_pos.y + 8);\n        else\n            r_avoid = ImRect(ref_pos.x - 16, ref_pos.y - 8, ref_pos.x + 24 * scale, ref_pos.y + 24 * scale); // FIXME: Hard-coded based on mouse cursor shape expectation. Exact dimension not very important.\n        //GetForegroundDrawList()->AddRect(r_avoid.Min, r_avoid.Max, IM_COL32(255, 0, 255, 255));\n\n        return FindBestWindowPosForPopupEx(tooltip_pos, window->Size, &window->AutoPosLastDirection, r_outer, r_avoid, ImGuiPopupPositionPolicy_Tooltip);\n    }\n    IM_ASSERT(0);\n    return window->Pos;\n}\n\n//-----------------------------------------------------------------------------\n// [SECTION] WINDOW FOCUS\n//----------------------------------------------------------------------------\n// - SetWindowFocus()\n// - SetNextWindowFocus()\n// - IsWindowFocused()\n// - UpdateWindowInFocusOrderList() [Internal]\n// - BringWindowToFocusFront() [Internal]\n// - BringWindowToDisplayFront() [Internal]\n// - BringWindowToDisplayBack() [Internal]\n// - BringWindowToDisplayBehind() [Internal]\n// - FindWindowDisplayIndex() [Internal]\n// - FocusWindow() [Internal]\n// - FocusTopMostWindowUnderOne() [Internal]\n//-----------------------------------------------------------------------------\n\nvoid ImGui::SetWindowFocus()\n{\n    FocusWindow(GImGui->CurrentWindow);\n}\n\nvoid ImGui::SetWindowFocus(const char* name)\n{\n    if (name)\n    {\n        if (ImGuiWindow* window = FindWindowByName(name))\n            FocusWindow(window);\n    }\n    else\n    {\n        FocusWindow(NULL);\n    }\n}\n\nvoid ImGui::SetNextWindowFocus()\n{\n    ImGuiContext& g = *GImGui;\n    g.NextWindowData.HasFlags |= ImGuiNextWindowDataFlags_HasFocus;\n}\n\n// Similar to IsWindowHovered()\nbool ImGui::IsWindowFocused(ImGuiFocusedFlags flags)\n{\n    ImGuiContext& g = *GImGui;\n    ImGuiWindow* ref_window = g.NavWindow;\n    ImGuiWindow* cur_window = g.CurrentWindow;\n\n    if (ref_window == NULL)\n        return false;\n    if (flags & ImGuiFocusedFlags_AnyWindow)\n        return true;\n\n    IM_ASSERT(cur_window); // Not inside a Begin()/End()\n    const bool popup_hierarchy = (flags & ImGuiFocusedFlags_NoPopupHierarchy) == 0;\n    if (flags & ImGuiHoveredFlags_RootWindow)\n        cur_window = GetCombinedRootWindow(cur_window, popup_hierarchy);\n\n    if (flags & ImGuiHoveredFlags_ChildWindows)\n        return IsWindowChildOf(ref_window, cur_window, popup_hierarchy);\n    else\n        return (ref_window == cur_window);\n}\n\nstatic int ImGui::FindWindowFocusIndex(ImGuiWindow* window)\n{\n    ImGuiContext& g = *GImGui;\n    IM_UNUSED(g);\n    int order = window->FocusOrder;\n    IM_ASSERT(window->RootWindow == window); // No child window (not testing _ChildWindow because of docking)\n    IM_ASSERT(g.WindowsFocusOrder[order] == window);\n    return order;\n}\n\nstatic void ImGui::UpdateWindowInFocusOrderList(ImGuiWindow* window, bool just_created, ImGuiWindowFlags new_flags)\n{\n    ImGuiContext& g = *GImGui;\n\n    const bool new_is_explicit_child = (new_flags & ImGuiWindowFlags_ChildWindow) != 0 && ((new_flags & ImGuiWindowFlags_Popup) == 0 || (new_flags & ImGuiWindowFlags_ChildMenu) != 0);\n    const bool child_flag_changed = new_is_explicit_child != window->IsExplicitChild;\n    if ((just_created || child_flag_changed) && !new_is_explicit_child)\n    {\n        IM_ASSERT(!g.WindowsFocusOrder.contains(window));\n        g.WindowsFocusOrder.push_back(window);\n        window->FocusOrder = (short)(g.WindowsFocusOrder.Size - 1);\n    }\n    else if (!just_created && child_flag_changed && new_is_explicit_child)\n    {\n        IM_ASSERT(g.WindowsFocusOrder[window->FocusOrder] == window);\n        for (int n = window->FocusOrder + 1; n < g.WindowsFocusOrder.Size; n++)\n            g.WindowsFocusOrder[n]->FocusOrder--;\n        g.WindowsFocusOrder.erase(g.WindowsFocusOrder.Data + window->FocusOrder);\n        window->FocusOrder = -1;\n    }\n    window->IsExplicitChild = new_is_explicit_child;\n}\n\nvoid ImGui::BringWindowToFocusFront(ImGuiWindow* window)\n{\n    ImGuiContext& g = *GImGui;\n    IM_ASSERT(window == window->RootWindow);\n\n    const int cur_order = window->FocusOrder;\n    IM_ASSERT(g.WindowsFocusOrder[cur_order] == window);\n    if (g.WindowsFocusOrder.back() == window)\n        return;\n\n    const int new_order = g.WindowsFocusOrder.Size - 1;\n    for (int n = cur_order; n < new_order; n++)\n    {\n        g.WindowsFocusOrder[n] = g.WindowsFocusOrder[n + 1];\n        g.WindowsFocusOrder[n]->FocusOrder--;\n        IM_ASSERT(g.WindowsFocusOrder[n]->FocusOrder == n);\n    }\n    g.WindowsFocusOrder[new_order] = window;\n    window->FocusOrder = (short)new_order;\n}\n\n// Note technically focus related but rather adjacent and close to BringWindowToFocusFront()\nvoid ImGui::BringWindowToDisplayFront(ImGuiWindow* window)\n{\n    ImGuiContext& g = *GImGui;\n    ImGuiWindow* current_front_window = g.Windows.back();\n    if (current_front_window == window || current_front_window->RootWindow == window) // Cheap early out (could be better)\n        return;\n    for (int i = g.Windows.Size - 2; i >= 0; i--) // We can ignore the top-most window\n        if (g.Windows[i] == window)\n        {\n            memmove(&g.Windows[i], &g.Windows[i + 1], (size_t)(g.Windows.Size - i - 1) * sizeof(ImGuiWindow*));\n            g.Windows[g.Windows.Size - 1] = window;\n            break;\n        }\n}\n\nvoid ImGui::BringWindowToDisplayBack(ImGuiWindow* window)\n{\n    ImGuiContext& g = *GImGui;\n    if (g.Windows[0] == window)\n        return;\n    for (int i = 0; i < g.Windows.Size; i++)\n        if (g.Windows[i] == window)\n        {\n            memmove(&g.Windows[1], &g.Windows[0], (size_t)i * sizeof(ImGuiWindow*));\n            g.Windows[0] = window;\n            break;\n        }\n}\n\nvoid ImGui::BringWindowToDisplayBehind(ImGuiWindow* window, ImGuiWindow* behind_window)\n{\n    IM_ASSERT(window != NULL && behind_window != NULL);\n    ImGuiContext& g = *GImGui;\n    window = window->RootWindow;\n    behind_window = behind_window->RootWindow;\n    int pos_wnd = FindWindowDisplayIndex(window);\n    int pos_beh = FindWindowDisplayIndex(behind_window);\n    if (pos_wnd < pos_beh)\n    {\n        size_t copy_bytes = (pos_beh - pos_wnd - 1) * sizeof(ImGuiWindow*);\n        memmove(&g.Windows.Data[pos_wnd], &g.Windows.Data[pos_wnd + 1], copy_bytes);\n        g.Windows[pos_beh - 1] = window;\n    }\n    else\n    {\n        size_t copy_bytes = (pos_wnd - pos_beh) * sizeof(ImGuiWindow*);\n        memmove(&g.Windows.Data[pos_beh + 1], &g.Windows.Data[pos_beh], copy_bytes);\n        g.Windows[pos_beh] = window;\n    }\n}\n\nint ImGui::FindWindowDisplayIndex(ImGuiWindow* window)\n{\n    ImGuiContext& g = *GImGui;\n    return g.Windows.index_from_ptr(g.Windows.find(window));\n}\n\n// Moving window to front of display and set focus (which happens to be back of our sorted list)\nvoid ImGui::FocusWindow(ImGuiWindow* window, ImGuiFocusRequestFlags flags)\n{\n    ImGuiContext& g = *GImGui;\n\n    // Modal check?\n    if ((flags & ImGuiFocusRequestFlags_UnlessBelowModal) && (g.NavWindow != window)) // Early out in common case.\n        if (ImGuiWindow* blocking_modal = FindBlockingModal(window))\n        {\n            // This block would typically be reached in two situations:\n            // - API call to FocusWindow() with a window under a modal and ImGuiFocusRequestFlags_UnlessBelowModal flag.\n            // - User clicking on void or anything behind a modal while a modal is open (window == NULL)\n            IMGUI_DEBUG_LOG_FOCUS(\"[focus] FocusWindow(\\\"%s\\\", UnlessBelowModal): prevented by \\\"%s\\\".\\n\", window ? window->Name : \"<NULL>\", blocking_modal->Name);\n            if (window && window == window->RootWindow && (window->Flags & ImGuiWindowFlags_NoBringToFrontOnFocus) == 0)\n                BringWindowToDisplayBehind(window, blocking_modal); // Still bring right under modal. (FIXME: Could move in focus list too?)\n            ClosePopupsOverWindow(GetTopMostPopupModal(), false); // Note how we need to use GetTopMostPopupModal() aad NOT blocking_modal, to handle nested modals\n            return;\n        }\n\n    // Find last focused child (if any) and focus it instead.\n    if ((flags & ImGuiFocusRequestFlags_RestoreFocusedChild) && window != NULL)\n        window = NavRestoreLastChildNavWindow(window);\n\n    // Apply focus\n    if (g.NavWindow != window)\n    {\n        SetNavWindow(window);\n        if (window && g.NavHighlightItemUnderNav)\n            g.NavMousePosDirty = true;\n        g.NavId = window ? window->NavLastIds[0] : 0; // Restore NavId\n        g.NavLayer = ImGuiNavLayer_Main;\n        SetNavFocusScope(window ? window->NavRootFocusScopeId : 0);\n        g.NavIdIsAlive = false;\n        g.NavLastValidSelectionUserData = ImGuiSelectionUserData_Invalid;\n\n        // Close popups if any\n        ClosePopupsOverWindow(window, false);\n    }\n\n    // Move the root window to the top of the pile\n    IM_ASSERT(window == NULL || window->RootWindow != NULL);\n    ImGuiWindow* focus_front_window = window ? window->RootWindow : NULL; // NB: In docking branch this is window->RootWindowDockStop\n    ImGuiWindow* display_front_window = window ? window->RootWindow : NULL;\n\n    // Steal active widgets. Some of the cases it triggers includes:\n    // - Focus a window while an InputText in another window is active, if focus happens before the old InputText can run.\n    // - When using Nav to activate menu items (due to timing of activating on press->new window appears->losing ActiveId)\n    if (g.ActiveId != 0 && g.ActiveIdWindow && g.ActiveIdWindow->RootWindow != focus_front_window)\n        if (!g.ActiveIdNoClearOnFocusLoss)\n            ClearActiveID();\n\n    // Passing NULL allow to disable keyboard focus\n    if (!window)\n        return;\n\n    // Bring to front\n    BringWindowToFocusFront(focus_front_window);\n    if (((window->Flags | display_front_window->Flags) & ImGuiWindowFlags_NoBringToFrontOnFocus) == 0)\n        BringWindowToDisplayFront(display_front_window);\n}\n\nvoid ImGui::FocusTopMostWindowUnderOne(ImGuiWindow* under_this_window, ImGuiWindow* ignore_window, ImGuiViewport* filter_viewport, ImGuiFocusRequestFlags flags)\n{\n    ImGuiContext& g = *GImGui;\n    IM_UNUSED(filter_viewport); // Unused in master branch.\n    int start_idx = g.WindowsFocusOrder.Size - 1;\n    if (under_this_window != NULL)\n    {\n        // Aim at root window behind us, if we are in a child window that's our own root (see #4640)\n        int offset = -1;\n        while (under_this_window->Flags & ImGuiWindowFlags_ChildWindow)\n        {\n            under_this_window = under_this_window->ParentWindow;\n            offset = 0;\n        }\n        start_idx = FindWindowFocusIndex(under_this_window) + offset;\n    }\n    for (int i = start_idx; i >= 0; i--)\n    {\n        // We may later decide to test for different NoXXXInputs based on the active navigation input (mouse vs nav) but that may feel more confusing to the user.\n        ImGuiWindow* window = g.WindowsFocusOrder[i];\n        if (window == ignore_window || !window->WasActive)\n            continue;\n        if ((window->Flags & (ImGuiWindowFlags_NoMouseInputs | ImGuiWindowFlags_NoNavInputs)) != (ImGuiWindowFlags_NoMouseInputs | ImGuiWindowFlags_NoNavInputs))\n        {\n            FocusWindow(window, flags);\n            return;\n        }\n    }\n    FocusWindow(NULL, flags);\n}\n\n//-----------------------------------------------------------------------------\n// [SECTION] KEYBOARD/GAMEPAD NAVIGATION\n//-----------------------------------------------------------------------------\n\n// FIXME-NAV: The existence of SetNavID vs SetFocusID vs FocusWindow() needs to be clarified/reworked.\n// In our terminology those should be interchangeable, yet right now this is super confusing.\n// Those two functions are merely a legacy artifact, so at minimum naming should be clarified.\n\nvoid ImGui::SetNavCursorVisible(bool visible)\n{\n    ImGuiContext& g = *GImGui;\n    if (g.IO.ConfigNavCursorVisibleAlways)\n        visible = true;\n    g.NavCursorVisible = visible;\n}\n\n// (was called NavRestoreHighlightAfterMove() before 1.91.4)\nvoid ImGui::SetNavCursorVisibleAfterMove()\n{\n    ImGuiContext& g = *GImGui;\n    if (g.IO.ConfigNavCursorVisibleAuto)\n        g.NavCursorVisible = true;\n    g.NavHighlightItemUnderNav = g.NavMousePosDirty = true;\n}\n\nvoid ImGui::SetNavWindow(ImGuiWindow* window)\n{\n    ImGuiContext& g = *GImGui;\n    if (g.NavWindow != window)\n    {\n        IMGUI_DEBUG_LOG_FOCUS(\"[focus] SetNavWindow(\\\"%s\\\")\\n\", window ? window->Name : \"<NULL>\");\n        g.NavWindow = window;\n        g.NavLastValidSelectionUserData = ImGuiSelectionUserData_Invalid;\n    }\n    g.NavInitRequest = g.NavMoveSubmitted = g.NavMoveScoringItems = false;\n    NavUpdateAnyRequestFlag();\n}\n\nvoid ImGui::NavHighlightActivated(ImGuiID id)\n{\n    ImGuiContext& g = *GImGui;\n    g.NavHighlightActivatedId = id;\n    g.NavHighlightActivatedTimer = NAV_ACTIVATE_HIGHLIGHT_TIMER;\n}\n\nvoid ImGui::NavClearPreferredPosForAxis(ImGuiAxis axis)\n{\n    ImGuiContext& g = *GImGui;\n    g.NavWindow->RootWindowForNav->NavPreferredScoringPosRel[g.NavLayer][axis] = FLT_MAX;\n}\n\nvoid ImGui::SetNavID(ImGuiID id, ImGuiNavLayer nav_layer, ImGuiID focus_scope_id, const ImRect& rect_rel)\n{\n    ImGuiContext& g = *GImGui;\n    IM_ASSERT(g.NavWindow != NULL);\n    IM_ASSERT(nav_layer == ImGuiNavLayer_Main || nav_layer == ImGuiNavLayer_Menu);\n    g.NavId = id;\n    g.NavLayer = nav_layer;\n    SetNavFocusScope(focus_scope_id);\n    g.NavWindow->NavLastIds[nav_layer] = id;\n    g.NavWindow->NavRectRel[nav_layer] = rect_rel;\n\n    // Clear preferred scoring position (NavMoveRequestApplyResult() will tend to restore it)\n    NavClearPreferredPosForAxis(ImGuiAxis_X);\n    NavClearPreferredPosForAxis(ImGuiAxis_Y);\n}\n\nvoid ImGui::SetFocusID(ImGuiID id, ImGuiWindow* window)\n{\n    ImGuiContext& g = *GImGui;\n    IM_ASSERT(id != 0);\n\n    if (g.NavWindow != window)\n       SetNavWindow(window);\n\n    // Assume that SetFocusID() is called in the context where its window->DC.NavLayerCurrent and g.CurrentFocusScopeId are valid.\n    // Note that window may be != g.CurrentWindow (e.g. SetFocusID call in InputTextEx for multi-line text)\n    const ImGuiNavLayer nav_layer = window->DC.NavLayerCurrent;\n    g.NavId = id;\n    g.NavLayer = nav_layer;\n    SetNavFocusScope(g.CurrentFocusScopeId);\n    window->NavLastIds[nav_layer] = id;\n    if (g.LastItemData.ID == id)\n        window->NavRectRel[nav_layer] = WindowRectAbsToRel(window, g.LastItemData.NavRect);\n\n    if (g.ActiveIdSource == ImGuiInputSource_Keyboard || g.ActiveIdSource == ImGuiInputSource_Gamepad)\n        g.NavHighlightItemUnderNav = true;\n    else if (g.IO.ConfigNavCursorVisibleAuto)\n        g.NavCursorVisible = false;\n\n    // Clear preferred scoring position (NavMoveRequestApplyResult() will tend to restore it)\n    NavClearPreferredPosForAxis(ImGuiAxis_X);\n    NavClearPreferredPosForAxis(ImGuiAxis_Y);\n}\n\nstatic ImGuiDir ImGetDirQuadrantFromDelta(float dx, float dy)\n{\n    if (ImFabs(dx) > ImFabs(dy))\n        return (dx > 0.0f) ? ImGuiDir_Right : ImGuiDir_Left;\n    return (dy > 0.0f) ? ImGuiDir_Down : ImGuiDir_Up;\n}\n\nstatic float inline NavScoreItemDistInterval(float cand_min, float cand_max, float curr_min, float curr_max)\n{\n    if (cand_max < curr_min)\n        return cand_max - curr_min;\n    if (curr_max < cand_min)\n        return cand_min - curr_max;\n    return 0.0f;\n}\n\n// Scoring function for keyboard/gamepad directional navigation. Based on https://gist.github.com/rygorous/6981057\nstatic bool ImGui::NavScoreItem(ImGuiNavItemData* result)\n{\n    ImGuiContext& g = *GImGui;\n    ImGuiWindow* window = g.CurrentWindow;\n    if (g.NavLayer != window->DC.NavLayerCurrent)\n        return false;\n\n    // FIXME: Those are not good variables names\n    ImRect cand = g.LastItemData.NavRect;   // Current item nav rectangle\n    const ImRect curr = g.NavScoringRect;   // Current modified source rect (NB: we've applied Max.x = Min.x in NavUpdate() to inhibit the effect of having varied item width)\n    g.NavScoringDebugCount++;\n\n    // When entering through a NavFlattened border, we consider child window items as fully clipped for scoring\n    if (window->ParentWindow == g.NavWindow)\n    {\n        IM_ASSERT((window->ChildFlags | g.NavWindow->ChildFlags) & ImGuiChildFlags_NavFlattened);\n        if (!window->ClipRect.Overlaps(cand))\n            return false;\n        cand.ClipWithFull(window->ClipRect); // This allows the scored item to not overlap other candidates in the parent window\n    }\n\n    // Compute distance between boxes\n    // FIXME-NAV: Introducing biases for vertical navigation, needs to be removed.\n    float dbx = NavScoreItemDistInterval(cand.Min.x, cand.Max.x, curr.Min.x, curr.Max.x);\n    float dby = NavScoreItemDistInterval(ImLerp(cand.Min.y, cand.Max.y, 0.2f), ImLerp(cand.Min.y, cand.Max.y, 0.8f), ImLerp(curr.Min.y, curr.Max.y, 0.2f), ImLerp(curr.Min.y, curr.Max.y, 0.8f)); // Scale down on Y to keep using box-distance for vertically touching items\n    if (dby != 0.0f && dbx != 0.0f)\n        dbx = (dbx / 1000.0f) + ((dbx > 0.0f) ? +1.0f : -1.0f);\n    float dist_box = ImFabs(dbx) + ImFabs(dby);\n\n    // Compute distance between centers (this is off by a factor of 2, but we only compare center distances with each other so it doesn't matter)\n    float dcx = (cand.Min.x + cand.Max.x) - (curr.Min.x + curr.Max.x);\n    float dcy = (cand.Min.y + cand.Max.y) - (curr.Min.y + curr.Max.y);\n    float dist_center = ImFabs(dcx) + ImFabs(dcy); // L1 metric (need this for our connectedness guarantee)\n\n    // Determine which quadrant of 'curr' our candidate item 'cand' lies in based on distance\n    ImGuiDir quadrant;\n    float dax = 0.0f, day = 0.0f, dist_axial = 0.0f;\n    if (dbx != 0.0f || dby != 0.0f)\n    {\n        // For non-overlapping boxes, use distance between boxes\n        // FIXME-NAV: Quadrant may be incorrect because of (1) dbx bias and (2) curr.Max.y bias applied by NavBiasScoringRect() where typically curr.Max.y==curr.Min.y\n        // One typical case where this happens, with style.WindowMenuButtonPosition == ImGuiDir_Right, pressing Left to navigate from Close to Collapse tends to fail.\n        // Also see #6344. Calling ImGetDirQuadrantFromDelta() with unbiased values may be good but side-effects are plenty.\n        dax = dbx;\n        day = dby;\n        dist_axial = dist_box;\n        quadrant = ImGetDirQuadrantFromDelta(dbx, dby);\n    }\n    else if (dcx != 0.0f || dcy != 0.0f)\n    {\n        // For overlapping boxes with different centers, use distance between centers\n        dax = dcx;\n        day = dcy;\n        dist_axial = dist_center;\n        quadrant = ImGetDirQuadrantFromDelta(dcx, dcy);\n    }\n    else\n    {\n        // Degenerate case: two overlapping buttons with same center, break ties arbitrarily (note that LastItemId here is really the _previous_ item order, but it doesn't matter)\n        quadrant = (g.LastItemData.ID < g.NavId) ? ImGuiDir_Left : ImGuiDir_Right;\n    }\n\n    const ImGuiDir move_dir = g.NavMoveDir;\n#if IMGUI_DEBUG_NAV_SCORING\n    char buf[200];\n    if (g.IO.KeyCtrl) // Hold CTRL to preview score in matching quadrant. CTRL+Arrow to rotate.\n    {\n        if (quadrant == move_dir)\n        {\n            ImFormatString(buf, IM_ARRAYSIZE(buf), \"%.0f/%.0f\", dist_box, dist_center);\n            ImDrawList* draw_list = GetForegroundDrawList(window);\n            draw_list->AddRectFilled(cand.Min, cand.Max, IM_COL32(255, 0, 0, 80));\n            draw_list->AddRectFilled(cand.Min, cand.Min + CalcTextSize(buf), IM_COL32(255, 0, 0, 200));\n            draw_list->AddText(cand.Min, IM_COL32(255, 255, 255, 255), buf);\n        }\n    }\n    const bool debug_hovering = IsMouseHoveringRect(cand.Min, cand.Max);\n    const bool debug_tty = (g.IO.KeyCtrl && IsKeyPressed(ImGuiKey_Space));\n    if (debug_hovering || debug_tty)\n    {\n        ImFormatString(buf, IM_ARRAYSIZE(buf),\n            \"d-box    (%7.3f,%7.3f) -> %7.3f\\nd-center (%7.3f,%7.3f) -> %7.3f\\nd-axial  (%7.3f,%7.3f) -> %7.3f\\nnav %c, quadrant %c\",\n            dbx, dby, dist_box, dcx, dcy, dist_center, dax, day, dist_axial, \"-WENS\"[move_dir+1], \"-WENS\"[quadrant+1]);\n        if (debug_hovering)\n        {\n            ImDrawList* draw_list = GetForegroundDrawList(window);\n            draw_list->AddRect(curr.Min, curr.Max, IM_COL32(255, 200, 0, 100));\n            draw_list->AddRect(cand.Min, cand.Max, IM_COL32(255, 255, 0, 200));\n            draw_list->AddRectFilled(cand.Max - ImVec2(4, 4), cand.Max + CalcTextSize(buf) + ImVec2(4, 4), IM_COL32(40, 0, 0, 200));\n            draw_list->AddText(cand.Max, ~0U, buf);\n        }\n        if (debug_tty) { IMGUI_DEBUG_LOG_NAV(\"id 0x%08X\\n%s\\n\", g.LastItemData.ID, buf); }\n    }\n#endif\n\n    // Is it in the quadrant we're interested in moving to?\n    bool new_best = false;\n    if (quadrant == move_dir)\n    {\n        // Does it beat the current best candidate?\n        if (dist_box < result->DistBox)\n        {\n            result->DistBox = dist_box;\n            result->DistCenter = dist_center;\n            return true;\n        }\n        if (dist_box == result->DistBox)\n        {\n            // Try using distance between center points to break ties\n            if (dist_center < result->DistCenter)\n            {\n                result->DistCenter = dist_center;\n                new_best = true;\n            }\n            else if (dist_center == result->DistCenter)\n            {\n                // Still tied! we need to be extra-careful to make sure everything gets linked properly. We consistently break ties by symbolically moving \"later\" items\n                // (with higher index) to the right/downwards by an infinitesimal amount since we the current \"best\" button already (so it must have a lower index),\n                // this is fairly easy. This rule ensures that all buttons with dx==dy==0 will end up being linked in order of appearance along the x axis.\n                if (((move_dir == ImGuiDir_Up || move_dir == ImGuiDir_Down) ? dby : dbx) < 0.0f) // moving bj to the right/down decreases distance\n                    new_best = true;\n            }\n        }\n    }\n\n    // Axial check: if 'curr' has no link at all in some direction and 'cand' lies roughly in that direction, add a tentative link. This will only be kept if no \"real\" matches\n    // are found, so it only augments the graph produced by the above method using extra links. (important, since it doesn't guarantee strong connectedness)\n    // This is just to avoid buttons having no links in a particular direction when there's a suitable neighbor. you get good graphs without this too.\n    // 2017/09/29: FIXME: This now currently only enabled inside menu bars, ideally we'd disable it everywhere. Menus in particular need to catch failure. For general navigation it feels awkward.\n    // Disabling it may lead to disconnected graphs when nodes are very spaced out on different axis. Perhaps consider offering this as an option?\n    if (result->DistBox == FLT_MAX && dist_axial < result->DistAxial)  // Check axial match\n        if (g.NavLayer == ImGuiNavLayer_Menu && !(g.NavWindow->Flags & ImGuiWindowFlags_ChildMenu))\n            if ((move_dir == ImGuiDir_Left && dax < 0.0f) || (move_dir == ImGuiDir_Right && dax > 0.0f) || (move_dir == ImGuiDir_Up && day < 0.0f) || (move_dir == ImGuiDir_Down && day > 0.0f))\n            {\n                result->DistAxial = dist_axial;\n                new_best = true;\n            }\n\n    return new_best;\n}\n\nstatic void ImGui::NavApplyItemToResult(ImGuiNavItemData* result)\n{\n    ImGuiContext& g = *GImGui;\n    ImGuiWindow* window = g.CurrentWindow;\n    result->Window = window;\n    result->ID = g.LastItemData.ID;\n    result->FocusScopeId = g.CurrentFocusScopeId;\n    result->ItemFlags = g.LastItemData.ItemFlags;\n    result->RectRel = WindowRectAbsToRel(window, g.LastItemData.NavRect);\n    if (result->ItemFlags & ImGuiItemFlags_HasSelectionUserData)\n    {\n        IM_ASSERT(g.NextItemData.SelectionUserData != ImGuiSelectionUserData_Invalid);\n        result->SelectionUserData = g.NextItemData.SelectionUserData; // INTENTIONAL: At this point this field is not cleared in NextItemData. Avoid unnecessary copy to LastItemData.\n    }\n}\n\n// True when current work location may be scrolled horizontally when moving left / right.\n// This is generally always true UNLESS within a column. We don't have a vertical equivalent.\nvoid ImGui::NavUpdateCurrentWindowIsScrollPushableX()\n{\n    ImGuiContext& g = *GImGui;\n    ImGuiWindow* window = g.CurrentWindow;\n    window->DC.NavIsScrollPushableX = (g.CurrentTable == NULL && window->DC.CurrentColumns == NULL);\n}\n\n// We get there when either NavId == id, or when g.NavAnyRequest is set (which is updated by NavUpdateAnyRequestFlag above)\n// This is called after LastItemData is set, but NextItemData is also still valid.\nstatic void ImGui::NavProcessItem()\n{\n    ImGuiContext& g = *GImGui;\n    ImGuiWindow* window = g.CurrentWindow;\n    const ImGuiID id = g.LastItemData.ID;\n    const ImGuiItemFlags item_flags = g.LastItemData.ItemFlags;\n\n    // When inside a container that isn't scrollable with Left<>Right, clip NavRect accordingly (#2221)\n    if (window->DC.NavIsScrollPushableX == false)\n    {\n        g.LastItemData.NavRect.Min.x = ImClamp(g.LastItemData.NavRect.Min.x, window->ClipRect.Min.x, window->ClipRect.Max.x);\n        g.LastItemData.NavRect.Max.x = ImClamp(g.LastItemData.NavRect.Max.x, window->ClipRect.Min.x, window->ClipRect.Max.x);\n    }\n    const ImRect nav_bb = g.LastItemData.NavRect;\n\n    // Process Init Request\n    if (g.NavInitRequest && g.NavLayer == window->DC.NavLayerCurrent && (item_flags & ImGuiItemFlags_Disabled) == 0)\n    {\n        // Even if 'ImGuiItemFlags_NoNavDefaultFocus' is on (typically collapse/close button) we record the first ResultId so they can be used as a fallback\n        const bool candidate_for_nav_default_focus = (item_flags & ImGuiItemFlags_NoNavDefaultFocus) == 0;\n        if (candidate_for_nav_default_focus || g.NavInitResult.ID == 0)\n        {\n            NavApplyItemToResult(&g.NavInitResult);\n        }\n        if (candidate_for_nav_default_focus)\n        {\n            g.NavInitRequest = false; // Found a match, clear request\n            NavUpdateAnyRequestFlag();\n        }\n    }\n\n    // Process Move Request (scoring for navigation)\n    // FIXME-NAV: Consider policy for double scoring (scoring from NavScoringRect + scoring from a rect wrapped according to current wrapping policy)\n    if (g.NavMoveScoringItems && (item_flags & ImGuiItemFlags_Disabled) == 0)\n    {\n        if ((g.NavMoveFlags & ImGuiNavMoveFlags_FocusApi) || (window->Flags & ImGuiWindowFlags_NoNavInputs) == 0)\n        {\n            const bool is_tabbing = (g.NavMoveFlags & ImGuiNavMoveFlags_IsTabbing) != 0;\n            if (is_tabbing)\n            {\n                NavProcessItemForTabbingRequest(id, item_flags, g.NavMoveFlags);\n            }\n            else if (g.NavId != id || (g.NavMoveFlags & ImGuiNavMoveFlags_AllowCurrentNavId))\n            {\n                ImGuiNavItemData* result = (window == g.NavWindow) ? &g.NavMoveResultLocal : &g.NavMoveResultOther;\n                if (NavScoreItem(result))\n                    NavApplyItemToResult(result);\n\n                // Features like PageUp/PageDown need to maintain a separate score for the visible set of items.\n                const float VISIBLE_RATIO = 0.70f;\n                if ((g.NavMoveFlags & ImGuiNavMoveFlags_AlsoScoreVisibleSet) && window->ClipRect.Overlaps(nav_bb))\n                    if (ImClamp(nav_bb.Max.y, window->ClipRect.Min.y, window->ClipRect.Max.y) - ImClamp(nav_bb.Min.y, window->ClipRect.Min.y, window->ClipRect.Max.y) >= (nav_bb.Max.y - nav_bb.Min.y) * VISIBLE_RATIO)\n                        if (NavScoreItem(&g.NavMoveResultLocalVisible))\n                            NavApplyItemToResult(&g.NavMoveResultLocalVisible);\n            }\n        }\n    }\n\n    // Update information for currently focused/navigated item\n    if (g.NavId == id)\n    {\n        if (g.NavWindow != window)\n            SetNavWindow(window); // Always refresh g.NavWindow, because some operations such as FocusItem() may not have a window.\n        g.NavLayer = window->DC.NavLayerCurrent;\n        SetNavFocusScope(g.CurrentFocusScopeId); // Will set g.NavFocusScopeId AND store g.NavFocusScopePath\n        g.NavFocusScopeId = g.CurrentFocusScopeId;\n        g.NavIdIsAlive = true;\n        if (g.LastItemData.ItemFlags & ImGuiItemFlags_HasSelectionUserData)\n        {\n            IM_ASSERT(g.NextItemData.SelectionUserData != ImGuiSelectionUserData_Invalid);\n            g.NavLastValidSelectionUserData = g.NextItemData.SelectionUserData; // INTENTIONAL: At this point this field is not cleared in NextItemData. Avoid unnecessary copy to LastItemData.\n        }\n        window->NavRectRel[window->DC.NavLayerCurrent] = WindowRectAbsToRel(window, nav_bb); // Store item bounding box (relative to window position)\n    }\n}\n\n// Handle \"scoring\" of an item for a tabbing/focusing request initiated by NavUpdateCreateTabbingRequest().\n// Note that SetKeyboardFocusHere() API calls are considered tabbing requests!\n// - Case 1: no nav/active id:    set result to first eligible item, stop storing.\n// - Case 2: tab forward:         on ref id set counter, on counter elapse store result\n// - Case 3: tab forward wrap:    set result to first eligible item (preemptively), on ref id set counter, on next frame if counter hasn't elapsed store result. // FIXME-TABBING: Could be done as a next-frame forwarded request\n// - Case 4: tab backward:        store all results, on ref id pick prev, stop storing\n// - Case 5: tab backward wrap:   store all results, on ref id if no result keep storing until last // FIXME-TABBING: Could be done as next-frame forwarded requested\nvoid ImGui::NavProcessItemForTabbingRequest(ImGuiID id, ImGuiItemFlags item_flags, ImGuiNavMoveFlags move_flags)\n{\n    ImGuiContext& g = *GImGui;\n\n    if ((move_flags & ImGuiNavMoveFlags_FocusApi) == 0)\n    {\n        if (g.NavLayer != g.CurrentWindow->DC.NavLayerCurrent)\n            return;\n        if (g.NavFocusScopeId != g.CurrentFocusScopeId)\n            return;\n    }\n\n    // - Can always land on an item when using API call.\n    // - Tabbing with _NavEnableKeyboard (space/enter/arrows): goes through every item.\n    // - Tabbing without _NavEnableKeyboard: goes through inputable items only.\n    bool can_stop;\n    if (move_flags & ImGuiNavMoveFlags_FocusApi)\n        can_stop = true;\n    else\n        can_stop = (item_flags & ImGuiItemFlags_NoTabStop) == 0 && ((g.IO.ConfigFlags & ImGuiConfigFlags_NavEnableKeyboard) || (item_flags & ImGuiItemFlags_Inputable));\n\n    // Always store in NavMoveResultLocal (unlike directional request which uses NavMoveResultOther on sibling/flattened windows)\n    ImGuiNavItemData* result = &g.NavMoveResultLocal;\n    if (g.NavTabbingDir == +1)\n    {\n        // Tab Forward or SetKeyboardFocusHere() with >= 0\n        if (can_stop && g.NavTabbingResultFirst.ID == 0)\n            NavApplyItemToResult(&g.NavTabbingResultFirst);\n        if (can_stop && g.NavTabbingCounter > 0 && --g.NavTabbingCounter == 0)\n            NavMoveRequestResolveWithLastItem(result);\n        else if (g.NavId == id)\n            g.NavTabbingCounter = 1;\n    }\n    else if (g.NavTabbingDir == -1)\n    {\n        // Tab Backward\n        if (g.NavId == id)\n        {\n            if (result->ID)\n            {\n                g.NavMoveScoringItems = false;\n                NavUpdateAnyRequestFlag();\n            }\n        }\n        else if (can_stop)\n        {\n            // Keep applying until reaching NavId\n            NavApplyItemToResult(result);\n        }\n    }\n    else if (g.NavTabbingDir == 0)\n    {\n        if (can_stop && g.NavId == id)\n            NavMoveRequestResolveWithLastItem(result);\n        if (can_stop && g.NavTabbingResultFirst.ID == 0) // Tab init\n            NavApplyItemToResult(&g.NavTabbingResultFirst);\n    }\n}\n\nbool ImGui::NavMoveRequestButNoResultYet()\n{\n    ImGuiContext& g = *GImGui;\n    return g.NavMoveScoringItems && g.NavMoveResultLocal.ID == 0 && g.NavMoveResultOther.ID == 0;\n}\n\n// FIXME: ScoringRect is not set\nvoid ImGui::NavMoveRequestSubmit(ImGuiDir move_dir, ImGuiDir clip_dir, ImGuiNavMoveFlags move_flags, ImGuiScrollFlags scroll_flags)\n{\n    ImGuiContext& g = *GImGui;\n    IM_ASSERT(g.NavWindow != NULL);\n    //IMGUI_DEBUG_LOG_NAV(\"[nav] NavMoveRequestSubmit: dir %c, window \\\"%s\\\"\\n\", \"-WENS\"[move_dir + 1], g.NavWindow->Name);\n\n    if (move_flags & ImGuiNavMoveFlags_IsTabbing)\n        move_flags |= ImGuiNavMoveFlags_AllowCurrentNavId;\n\n    g.NavMoveSubmitted = g.NavMoveScoringItems = true;\n    g.NavMoveDir = move_dir;\n    g.NavMoveDirForDebug = move_dir;\n    g.NavMoveClipDir = clip_dir;\n    g.NavMoveFlags = move_flags;\n    g.NavMoveScrollFlags = scroll_flags;\n    g.NavMoveForwardToNextFrame = false;\n    g.NavMoveKeyMods = (move_flags & ImGuiNavMoveFlags_FocusApi) ? 0 : g.IO.KeyMods;\n    g.NavMoveResultLocal.Clear();\n    g.NavMoveResultLocalVisible.Clear();\n    g.NavMoveResultOther.Clear();\n    g.NavTabbingCounter = 0;\n    g.NavTabbingResultFirst.Clear();\n    NavUpdateAnyRequestFlag();\n}\n\nvoid ImGui::NavMoveRequestResolveWithLastItem(ImGuiNavItemData* result)\n{\n    ImGuiContext& g = *GImGui;\n    g.NavMoveScoringItems = false; // Ensure request doesn't need more processing\n    NavApplyItemToResult(result);\n    NavUpdateAnyRequestFlag();\n}\n\n// Called by TreePop() to implement ImGuiTreeNodeFlags_NavLeftJumpsBackHere\nvoid ImGui::NavMoveRequestResolveWithPastTreeNode(ImGuiNavItemData* result, ImGuiTreeNodeStackData* tree_node_data)\n{\n    ImGuiContext& g = *GImGui;\n    g.NavMoveScoringItems = false;\n    g.LastItemData.ID = tree_node_data->ID;\n    g.LastItemData.ItemFlags = tree_node_data->ItemFlags & ~ImGuiItemFlags_HasSelectionUserData; // Losing SelectionUserData, recovered next-frame (cheaper).\n    g.LastItemData.NavRect = tree_node_data->NavRect;\n    NavApplyItemToResult(result); // Result this instead of implementing a NavApplyPastTreeNodeToResult()\n    NavClearPreferredPosForAxis(ImGuiAxis_Y);\n    NavUpdateAnyRequestFlag();\n}\n\nvoid ImGui::NavMoveRequestCancel()\n{\n    ImGuiContext& g = *GImGui;\n    g.NavMoveSubmitted = g.NavMoveScoringItems = false;\n    NavUpdateAnyRequestFlag();\n}\n\n// Forward will reuse the move request again on the next frame (generally with modifications done to it)\nvoid ImGui::NavMoveRequestForward(ImGuiDir move_dir, ImGuiDir clip_dir, ImGuiNavMoveFlags move_flags, ImGuiScrollFlags scroll_flags)\n{\n    ImGuiContext& g = *GImGui;\n    IM_ASSERT(g.NavMoveForwardToNextFrame == false);\n    NavMoveRequestCancel();\n    g.NavMoveForwardToNextFrame = true;\n    g.NavMoveDir = move_dir;\n    g.NavMoveClipDir = clip_dir;\n    g.NavMoveFlags = move_flags | ImGuiNavMoveFlags_Forwarded;\n    g.NavMoveScrollFlags = scroll_flags;\n}\n\n// Navigation wrap-around logic is delayed to the end of the frame because this operation is only valid after entire\n// popup is assembled and in case of appended popups it is not clear which EndPopup() call is final.\nvoid ImGui::NavMoveRequestTryWrapping(ImGuiWindow* window, ImGuiNavMoveFlags wrap_flags)\n{\n    ImGuiContext& g = *GImGui;\n    IM_ASSERT((wrap_flags & ImGuiNavMoveFlags_WrapMask_ ) != 0 && (wrap_flags & ~ImGuiNavMoveFlags_WrapMask_) == 0); // Call with _WrapX, _WrapY, _LoopX, _LoopY\n\n    // In theory we should test for NavMoveRequestButNoResultYet() but there's no point doing it:\n    // as NavEndFrame() will do the same test. It will end up calling NavUpdateCreateWrappingRequest().\n    if (g.NavWindow == window && g.NavMoveScoringItems && g.NavLayer == ImGuiNavLayer_Main)\n        g.NavMoveFlags = (g.NavMoveFlags & ~ImGuiNavMoveFlags_WrapMask_) | wrap_flags;\n}\n\n// FIXME: This could be replaced by updating a frame number in each window when (window == NavWindow) and (NavLayer == 0).\n// This way we could find the last focused window among our children. It would be much less confusing this way?\nstatic void ImGui::NavSaveLastChildNavWindowIntoParent(ImGuiWindow* nav_window)\n{\n    ImGuiWindow* parent = nav_window;\n    while (parent && parent->RootWindow != parent && (parent->Flags & (ImGuiWindowFlags_Popup | ImGuiWindowFlags_ChildMenu)) == 0)\n        parent = parent->ParentWindow;\n    if (parent && parent != nav_window)\n        parent->NavLastChildNavWindow = nav_window;\n}\n\n// Restore the last focused child.\n// Call when we are expected to land on the Main Layer (0) after FocusWindow()\nstatic ImGuiWindow* ImGui::NavRestoreLastChildNavWindow(ImGuiWindow* window)\n{\n    if (window->NavLastChildNavWindow && window->NavLastChildNavWindow->WasActive)\n        return window->NavLastChildNavWindow;\n    return window;\n}\n\nvoid ImGui::NavRestoreLayer(ImGuiNavLayer layer)\n{\n    ImGuiContext& g = *GImGui;\n    if (layer == ImGuiNavLayer_Main)\n    {\n        ImGuiWindow* prev_nav_window = g.NavWindow;\n        g.NavWindow = NavRestoreLastChildNavWindow(g.NavWindow);    // FIXME-NAV: Should clear ongoing nav requests?\n        g.NavLastValidSelectionUserData = ImGuiSelectionUserData_Invalid;\n        if (prev_nav_window)\n            IMGUI_DEBUG_LOG_FOCUS(\"[focus] NavRestoreLayer: from \\\"%s\\\" to SetNavWindow(\\\"%s\\\")\\n\", prev_nav_window->Name, g.NavWindow->Name);\n    }\n    ImGuiWindow* window = g.NavWindow;\n    if (window->NavLastIds[layer] != 0)\n    {\n        SetNavID(window->NavLastIds[layer], layer, 0, window->NavRectRel[layer]);\n    }\n    else\n    {\n        g.NavLayer = layer;\n        NavInitWindow(window, true);\n    }\n}\n\nstatic inline void ImGui::NavUpdateAnyRequestFlag()\n{\n    ImGuiContext& g = *GImGui;\n    g.NavAnyRequest = g.NavMoveScoringItems || g.NavInitRequest || (IMGUI_DEBUG_NAV_SCORING && g.NavWindow != NULL);\n    if (g.NavAnyRequest)\n        IM_ASSERT(g.NavWindow != NULL);\n}\n\n// This needs to be called before we submit any widget (aka in or before Begin)\nvoid ImGui::NavInitWindow(ImGuiWindow* window, bool force_reinit)\n{\n    ImGuiContext& g = *GImGui;\n    IM_ASSERT(window == g.NavWindow);\n\n    if (window->Flags & ImGuiWindowFlags_NoNavInputs)\n    {\n        g.NavId = 0;\n        SetNavFocusScope(window->NavRootFocusScopeId);\n        return;\n    }\n\n    bool init_for_nav = false;\n    if (window == window->RootWindow || (window->Flags & ImGuiWindowFlags_Popup) || (window->NavLastIds[0] == 0) || force_reinit)\n        init_for_nav = true;\n    IMGUI_DEBUG_LOG_NAV(\"[nav] NavInitRequest: from NavInitWindow(), init_for_nav=%d, window=\\\"%s\\\", layer=%d\\n\", init_for_nav, window->Name, g.NavLayer);\n    if (init_for_nav)\n    {\n        SetNavID(0, g.NavLayer, window->NavRootFocusScopeId, ImRect());\n        g.NavInitRequest = true;\n        g.NavInitRequestFromMove = false;\n        g.NavInitResult.ID = 0;\n        NavUpdateAnyRequestFlag();\n    }\n    else\n    {\n        g.NavId = window->NavLastIds[0];\n        SetNavFocusScope(window->NavRootFocusScopeId);\n    }\n}\n\nstatic ImGuiInputSource ImGui::NavCalcPreferredRefPosSource()\n{\n    ImGuiContext& g = *GImGui;\n    ImGuiWindow* window = g.NavWindow;\n    const bool activated_shortcut = g.ActiveId != 0 && g.ActiveIdFromShortcut && g.ActiveId == g.LastItemData.ID;\n\n    // Testing for !activated_shortcut here could in theory be removed if we decided that activating a remote shortcut altered one of the g.NavDisableXXX flag.\n    if ((!g.NavCursorVisible || !g.NavHighlightItemUnderNav || !window) && !activated_shortcut)\n        return ImGuiInputSource_Mouse;\n    else\n        return ImGuiInputSource_Keyboard; // or Nav in general\n}\n\nstatic ImVec2 ImGui::NavCalcPreferredRefPos()\n{\n    ImGuiContext& g = *GImGui;\n    ImGuiWindow* window = g.NavWindow;\n    ImGuiInputSource source = NavCalcPreferredRefPosSource();\n\n    const bool activated_shortcut = g.ActiveId != 0 && g.ActiveIdFromShortcut && g.ActiveId == g.LastItemData.ID;\n\n    // Testing for !activated_shortcut here could in theory be removed if we decided that activating a remote shortcut altered one of the g.NavDisableXXX flag.\n    if (source == ImGuiInputSource_Mouse)\n    {\n        // Mouse (we need a fallback in case the mouse becomes invalid after being used)\n        // The +1.0f offset when stored by OpenPopupEx() allows reopening this or another popup (same or another mouse button) while not moving the mouse, it is pretty standard.\n        // In theory we could move that +1.0f offset in OpenPopupEx()\n        ImVec2 p = IsMousePosValid(&g.IO.MousePos) ? g.IO.MousePos : g.MouseLastValidPos;\n        return ImVec2(p.x + 1.0f, p.y);\n    }\n    else\n    {\n        // When navigation is active and mouse is disabled, pick a position around the bottom left of the currently navigated item\n        ImRect ref_rect;\n        if (activated_shortcut)\n            ref_rect = g.LastItemData.NavRect;\n        else\n            ref_rect = WindowRectRelToAbs(window, window->NavRectRel[g.NavLayer]);\n\n        // Take account of upcoming scrolling (maybe set mouse pos should be done in EndFrame?)\n        if (window->LastFrameActive != g.FrameCount && (window->ScrollTarget.x != FLT_MAX || window->ScrollTarget.y != FLT_MAX))\n        {\n            ImVec2 next_scroll = CalcNextScrollFromScrollTargetAndClamp(window);\n            ref_rect.Translate(window->Scroll - next_scroll);\n        }\n        ImVec2 pos = ImVec2(ref_rect.Min.x + ImMin(g.Style.FramePadding.x * 4, ref_rect.GetWidth()), ref_rect.Max.y - ImMin(g.Style.FramePadding.y, ref_rect.GetHeight()));\n        ImGuiViewport* viewport = GetMainViewport();\n        return ImTrunc(ImClamp(pos, viewport->Pos, viewport->Pos + viewport->Size)); // ImTrunc() is important because non-integer mouse position application in backend might be lossy and result in undesirable non-zero delta.\n    }\n}\n\nfloat ImGui::GetNavTweakPressedAmount(ImGuiAxis axis)\n{\n    ImGuiContext& g = *GImGui;\n    float repeat_delay, repeat_rate;\n    GetTypematicRepeatRate(ImGuiInputFlags_RepeatRateNavTweak, &repeat_delay, &repeat_rate);\n\n    ImGuiKey key_less, key_more;\n    if (g.NavInputSource == ImGuiInputSource_Gamepad)\n    {\n        key_less = (axis == ImGuiAxis_X) ? ImGuiKey_GamepadDpadLeft : ImGuiKey_GamepadDpadUp;\n        key_more = (axis == ImGuiAxis_X) ? ImGuiKey_GamepadDpadRight : ImGuiKey_GamepadDpadDown;\n    }\n    else\n    {\n        key_less = (axis == ImGuiAxis_X) ? ImGuiKey_LeftArrow : ImGuiKey_UpArrow;\n        key_more = (axis == ImGuiAxis_X) ? ImGuiKey_RightArrow : ImGuiKey_DownArrow;\n    }\n    float amount = (float)GetKeyPressedAmount(key_more, repeat_delay, repeat_rate) - (float)GetKeyPressedAmount(key_less, repeat_delay, repeat_rate);\n    if (amount != 0.0f && IsKeyDown(key_less) && IsKeyDown(key_more)) // Cancel when opposite directions are held, regardless of repeat phase\n        amount = 0.0f;\n    return amount;\n}\n\nstatic void ImGui::NavUpdate()\n{\n    ImGuiContext& g = *GImGui;\n    ImGuiIO& io = g.IO;\n\n    io.WantSetMousePos = false;\n    //if (g.NavScoringDebugCount > 0) IMGUI_DEBUG_LOG_NAV(\"[nav] NavScoringDebugCount %d for '%s' layer %d (Init:%d, Move:%d)\\n\", g.NavScoringDebugCount, g.NavWindow ? g.NavWindow->Name : \"NULL\", g.NavLayer, g.NavInitRequest || g.NavInitResultId != 0, g.NavMoveRequest);\n\n    // Set input source based on which keys are last pressed (as some features differs when used with Gamepad vs Keyboard)\n    // FIXME-NAV: Now that keys are separated maybe we can get rid of NavInputSource?\n    const bool nav_gamepad_active = (io.ConfigFlags & ImGuiConfigFlags_NavEnableGamepad) != 0 && (io.BackendFlags & ImGuiBackendFlags_HasGamepad) != 0;\n    const ImGuiKey nav_gamepad_keys_to_change_source[] = { ImGuiKey_GamepadFaceRight, ImGuiKey_GamepadFaceLeft, ImGuiKey_GamepadFaceUp, ImGuiKey_GamepadFaceDown, ImGuiKey_GamepadDpadRight, ImGuiKey_GamepadDpadLeft, ImGuiKey_GamepadDpadUp, ImGuiKey_GamepadDpadDown };\n    if (nav_gamepad_active)\n        for (ImGuiKey key : nav_gamepad_keys_to_change_source)\n            if (IsKeyDown(key))\n                g.NavInputSource = ImGuiInputSource_Gamepad;\n    const bool nav_keyboard_active = (io.ConfigFlags & ImGuiConfigFlags_NavEnableKeyboard) != 0;\n    const ImGuiKey nav_keyboard_keys_to_change_source[] = { ImGuiKey_Space, ImGuiKey_Enter, ImGuiKey_Escape, ImGuiKey_RightArrow, ImGuiKey_LeftArrow, ImGuiKey_UpArrow, ImGuiKey_DownArrow };\n    if (nav_keyboard_active)\n        for (ImGuiKey key : nav_keyboard_keys_to_change_source)\n            if (IsKeyDown(key))\n                g.NavInputSource = ImGuiInputSource_Keyboard;\n\n    // Process navigation init request (select first/default focus)\n    g.NavJustMovedToId = 0;\n    g.NavJustMovedToFocusScopeId = g.NavJustMovedFromFocusScopeId = 0;\n    if (g.NavInitResult.ID != 0)\n        NavInitRequestApplyResult();\n    g.NavInitRequest = false;\n    g.NavInitRequestFromMove = false;\n    g.NavInitResult.ID = 0;\n\n    // Process navigation move request\n    if (g.NavMoveSubmitted)\n        NavMoveRequestApplyResult();\n    g.NavTabbingCounter = 0;\n    g.NavMoveSubmitted = g.NavMoveScoringItems = false;\n    if (g.NavCursorHideFrames > 0)\n        if (--g.NavCursorHideFrames == 0)\n            g.NavCursorVisible = true;\n\n    // Schedule mouse position update (will be done at the bottom of this function, after 1) processing all move requests and 2) updating scrolling)\n    bool set_mouse_pos = false;\n    if (g.NavMousePosDirty && g.NavIdIsAlive)\n        if (g.NavCursorVisible && g.NavHighlightItemUnderNav && g.NavWindow)\n            set_mouse_pos = true;\n    g.NavMousePosDirty = false;\n    IM_ASSERT(g.NavLayer == ImGuiNavLayer_Main || g.NavLayer == ImGuiNavLayer_Menu);\n\n    // Store our return window (for returning from Menu Layer to Main Layer) and clear it as soon as we step back in our own Layer 0\n    if (g.NavWindow)\n        NavSaveLastChildNavWindowIntoParent(g.NavWindow);\n    if (g.NavWindow && g.NavWindow->NavLastChildNavWindow != NULL && g.NavLayer == ImGuiNavLayer_Main)\n        g.NavWindow->NavLastChildNavWindow = NULL;\n\n    // Update CTRL+TAB and Windowing features (hold Square to move/resize/etc.)\n    NavUpdateWindowing();\n\n    // Set output flags for user application\n    io.NavActive = (nav_keyboard_active || nav_gamepad_active) && g.NavWindow && !(g.NavWindow->Flags & ImGuiWindowFlags_NoNavInputs);\n    io.NavVisible = (io.NavActive && g.NavId != 0 && g.NavCursorVisible) || (g.NavWindowingTarget != NULL);\n\n    // Process NavCancel input (to close a popup, get back to parent, clear focus)\n    NavUpdateCancelRequest();\n\n    // Process manual activation request\n    g.NavActivateId = g.NavActivateDownId = g.NavActivatePressedId = 0;\n    g.NavActivateFlags = ImGuiActivateFlags_None;\n    if (g.NavId != 0 && g.NavCursorVisible && !g.NavWindowingTarget && g.NavWindow && !(g.NavWindow->Flags & ImGuiWindowFlags_NoNavInputs))\n    {\n        const bool activate_down = (nav_keyboard_active && IsKeyDown(ImGuiKey_Space, ImGuiKeyOwner_NoOwner)) || (nav_gamepad_active && IsKeyDown(ImGuiKey_NavGamepadActivate, ImGuiKeyOwner_NoOwner));\n        const bool activate_pressed = activate_down && ((nav_keyboard_active && IsKeyPressed(ImGuiKey_Space, 0, ImGuiKeyOwner_NoOwner)) || (nav_gamepad_active && IsKeyPressed(ImGuiKey_NavGamepadActivate, 0, ImGuiKeyOwner_NoOwner)));\n        const bool input_down = (nav_keyboard_active && (IsKeyDown(ImGuiKey_Enter, ImGuiKeyOwner_NoOwner) || IsKeyDown(ImGuiKey_KeypadEnter, ImGuiKeyOwner_NoOwner))) || (nav_gamepad_active && IsKeyDown(ImGuiKey_NavGamepadInput, ImGuiKeyOwner_NoOwner));\n        const bool input_pressed = input_down && ((nav_keyboard_active && (IsKeyPressed(ImGuiKey_Enter, 0, ImGuiKeyOwner_NoOwner) || IsKeyPressed(ImGuiKey_KeypadEnter, 0, ImGuiKeyOwner_NoOwner))) || (nav_gamepad_active && IsKeyPressed(ImGuiKey_NavGamepadInput, 0, ImGuiKeyOwner_NoOwner)));\n        if (g.ActiveId == 0 && activate_pressed)\n        {\n            g.NavActivateId = g.NavId;\n            g.NavActivateFlags = ImGuiActivateFlags_PreferTweak;\n        }\n        if ((g.ActiveId == 0 || g.ActiveId == g.NavId) && input_pressed)\n        {\n            g.NavActivateId = g.NavId;\n            g.NavActivateFlags = ImGuiActivateFlags_PreferInput;\n        }\n        if ((g.ActiveId == 0 || g.ActiveId == g.NavId) && (activate_down || input_down))\n            g.NavActivateDownId = g.NavId;\n        if ((g.ActiveId == 0 || g.ActiveId == g.NavId) && (activate_pressed || input_pressed))\n        {\n            g.NavActivatePressedId = g.NavId;\n            NavHighlightActivated(g.NavId);\n        }\n    }\n    if (g.NavWindow && (g.NavWindow->Flags & ImGuiWindowFlags_NoNavInputs))\n        g.NavCursorVisible = false;\n    else if (g.IO.ConfigNavCursorVisibleAlways && g.NavCursorHideFrames == 0)\n        g.NavCursorVisible = true;\n    if (g.NavActivateId != 0)\n        IM_ASSERT(g.NavActivateDownId == g.NavActivateId);\n\n    // Highlight\n    if (g.NavHighlightActivatedTimer > 0.0f)\n        g.NavHighlightActivatedTimer = ImMax(0.0f, g.NavHighlightActivatedTimer - io.DeltaTime);\n    if (g.NavHighlightActivatedTimer == 0.0f)\n        g.NavHighlightActivatedId = 0;\n\n    // Process programmatic activation request\n    // FIXME-NAV: Those should eventually be queued (unlike focus they don't cancel each others)\n    if (g.NavNextActivateId != 0)\n    {\n        g.NavActivateId = g.NavActivateDownId = g.NavActivatePressedId = g.NavNextActivateId;\n        g.NavActivateFlags = g.NavNextActivateFlags;\n    }\n    g.NavNextActivateId = 0;\n\n    // Process move requests\n    NavUpdateCreateMoveRequest();\n    if (g.NavMoveDir == ImGuiDir_None)\n        NavUpdateCreateTabbingRequest();\n    NavUpdateAnyRequestFlag();\n    g.NavIdIsAlive = false;\n\n    // Scrolling\n    if (g.NavWindow && !(g.NavWindow->Flags & ImGuiWindowFlags_NoNavInputs) && !g.NavWindowingTarget)\n    {\n        // *Fallback* manual-scroll with Nav directional keys when window has no navigable item\n        ImGuiWindow* window = g.NavWindow;\n        const float scroll_speed = IM_ROUND(window->FontRefSize * 100 * io.DeltaTime); // We need round the scrolling speed because sub-pixel scroll isn't reliably supported.\n        const ImGuiDir move_dir = g.NavMoveDir;\n        if (window->DC.NavLayersActiveMask == 0x00 && window->DC.NavWindowHasScrollY && move_dir != ImGuiDir_None)\n        {\n            if (move_dir == ImGuiDir_Left || move_dir == ImGuiDir_Right)\n                SetScrollX(window, ImTrunc(window->Scroll.x + ((move_dir == ImGuiDir_Left) ? -1.0f : +1.0f) * scroll_speed));\n            if (move_dir == ImGuiDir_Up || move_dir == ImGuiDir_Down)\n                SetScrollY(window, ImTrunc(window->Scroll.y + ((move_dir == ImGuiDir_Up) ? -1.0f : +1.0f) * scroll_speed));\n        }\n\n        // *Normal* Manual scroll with LStick\n        // Next movement request will clamp the NavId reference rectangle to the visible area, so navigation will resume within those bounds.\n        if (nav_gamepad_active)\n        {\n            const ImVec2 scroll_dir = GetKeyMagnitude2d(ImGuiKey_GamepadLStickLeft, ImGuiKey_GamepadLStickRight, ImGuiKey_GamepadLStickUp, ImGuiKey_GamepadLStickDown);\n            const float tweak_factor = IsKeyDown(ImGuiKey_NavGamepadTweakSlow) ? 1.0f / 10.0f : IsKeyDown(ImGuiKey_NavGamepadTweakFast) ? 10.0f : 1.0f;\n            if (scroll_dir.x != 0.0f && window->ScrollbarX)\n                SetScrollX(window, ImTrunc(window->Scroll.x + scroll_dir.x * scroll_speed * tweak_factor));\n            if (scroll_dir.y != 0.0f)\n                SetScrollY(window, ImTrunc(window->Scroll.y + scroll_dir.y * scroll_speed * tweak_factor));\n        }\n    }\n\n    // Always prioritize mouse highlight if navigation is disabled\n    if (!nav_keyboard_active && !nav_gamepad_active)\n    {\n        g.NavCursorVisible = false;\n        g.NavHighlightItemUnderNav = set_mouse_pos = false;\n    }\n\n    // Update mouse position if requested\n    // (This will take into account the possibility that a Scroll was queued in the window to offset our absolute mouse position before scroll has been applied)\n    if (set_mouse_pos && io.ConfigNavMoveSetMousePos && (io.BackendFlags & ImGuiBackendFlags_HasSetMousePos))\n        TeleportMousePos(NavCalcPreferredRefPos());\n\n    // [DEBUG]\n    g.NavScoringDebugCount = 0;\n#if IMGUI_DEBUG_NAV_RECTS\n    if (ImGuiWindow* debug_window = g.NavWindow)\n    {\n        ImDrawList* draw_list = GetForegroundDrawList(debug_window);\n        int layer = g.NavLayer; /* for (int layer = 0; layer < 2; layer++)*/ { ImRect r = WindowRectRelToAbs(debug_window, debug_window->NavRectRel[layer]); draw_list->AddRect(r.Min, r.Max, IM_COL32(255, 200, 0, 255)); }\n        //if (1) { ImU32 col = (!debug_window->Hidden) ? IM_COL32(255,0,255,255) : IM_COL32(255,0,0,255); ImVec2 p = NavCalcPreferredRefPos(); char buf[32]; ImFormatString(buf, 32, \"%d\", g.NavLayer); draw_list->AddCircleFilled(p, 3.0f, col); draw_list->AddText(NULL, 13.0f, p + ImVec2(8,-4), col, buf); }\n    }\n#endif\n}\n\nvoid ImGui::NavInitRequestApplyResult()\n{\n    // In very rare cases g.NavWindow may be null (e.g. clearing focus after requesting an init request, which does happen when releasing Alt while clicking on void)\n    ImGuiContext& g = *GImGui;\n    if (!g.NavWindow)\n        return;\n\n    ImGuiNavItemData* result = &g.NavInitResult;\n    if (g.NavId != result->ID)\n    {\n        g.NavJustMovedFromFocusScopeId = g.NavFocusScopeId;\n        g.NavJustMovedToId = result->ID;\n        g.NavJustMovedToFocusScopeId = result->FocusScopeId;\n        g.NavJustMovedToKeyMods = 0;\n        g.NavJustMovedToIsTabbing = false;\n        g.NavJustMovedToHasSelectionData = (result->ItemFlags & ImGuiItemFlags_HasSelectionUserData) != 0;\n    }\n\n    // Apply result from previous navigation init request (will typically select the first item, unless SetItemDefaultFocus() has been called)\n    // FIXME-NAV: On _NavFlattened windows, g.NavWindow will only be updated during subsequent frame. Not a problem currently.\n    IMGUI_DEBUG_LOG_NAV(\"[nav] NavInitRequest: ApplyResult: NavID 0x%08X in Layer %d Window \\\"%s\\\"\\n\", result->ID, g.NavLayer, g.NavWindow->Name);\n    SetNavID(result->ID, g.NavLayer, result->FocusScopeId, result->RectRel);\n    g.NavIdIsAlive = true; // Mark as alive from previous frame as we got a result\n    if (result->SelectionUserData != ImGuiSelectionUserData_Invalid)\n        g.NavLastValidSelectionUserData = result->SelectionUserData;\n    if (g.NavInitRequestFromMove)\n        SetNavCursorVisibleAfterMove();\n}\n\n// Bias scoring rect ahead of scoring + update preferred pos (if missing) using source position\nstatic void NavBiasScoringRect(ImRect& r, ImVec2& preferred_pos_rel, ImGuiDir move_dir, ImGuiNavMoveFlags move_flags)\n{\n    // Bias initial rect\n    ImGuiContext& g = *GImGui;\n    const ImVec2 rel_to_abs_offset = g.NavWindow->DC.CursorStartPos;\n\n    // Initialize bias on departure if we don't have any. So mouse-click + arrow will record bias.\n    // - We default to L/U bias, so moving down from a large source item into several columns will land on left-most column.\n    // - But each successful move sets new bias on one axis, only cleared when using mouse.\n    if ((move_flags & ImGuiNavMoveFlags_Forwarded) == 0)\n    {\n        if (preferred_pos_rel.x == FLT_MAX)\n            preferred_pos_rel.x = ImMin(r.Min.x + 1.0f, r.Max.x) - rel_to_abs_offset.x;\n        if (preferred_pos_rel.y == FLT_MAX)\n            preferred_pos_rel.y = r.GetCenter().y - rel_to_abs_offset.y;\n    }\n\n    // Apply general bias on the other axis\n    if ((move_dir == ImGuiDir_Up || move_dir == ImGuiDir_Down) && preferred_pos_rel.x != FLT_MAX)\n        r.Min.x = r.Max.x = preferred_pos_rel.x + rel_to_abs_offset.x;\n    else if ((move_dir == ImGuiDir_Left || move_dir == ImGuiDir_Right) && preferred_pos_rel.y != FLT_MAX)\n        r.Min.y = r.Max.y = preferred_pos_rel.y + rel_to_abs_offset.y;\n}\n\nvoid ImGui::NavUpdateCreateMoveRequest()\n{\n    ImGuiContext& g = *GImGui;\n    ImGuiIO& io = g.IO;\n    ImGuiWindow* window = g.NavWindow;\n    const bool nav_gamepad_active = (io.ConfigFlags & ImGuiConfigFlags_NavEnableGamepad) != 0 && (io.BackendFlags & ImGuiBackendFlags_HasGamepad) != 0;\n    const bool nav_keyboard_active = (io.ConfigFlags & ImGuiConfigFlags_NavEnableKeyboard) != 0;\n\n    if (g.NavMoveForwardToNextFrame && window != NULL)\n    {\n        // Forwarding previous request (which has been modified, e.g. wrap around menus rewrite the requests with a starting rectangle at the other side of the window)\n        // (preserve most state, which were already set by the NavMoveRequestForward() function)\n        IM_ASSERT(g.NavMoveDir != ImGuiDir_None && g.NavMoveClipDir != ImGuiDir_None);\n        IM_ASSERT(g.NavMoveFlags & ImGuiNavMoveFlags_Forwarded);\n        IMGUI_DEBUG_LOG_NAV(\"[nav] NavMoveRequestForward %d\\n\", g.NavMoveDir);\n    }\n    else\n    {\n        // Initiate directional inputs request\n        g.NavMoveDir = ImGuiDir_None;\n        g.NavMoveFlags = ImGuiNavMoveFlags_None;\n        g.NavMoveScrollFlags = ImGuiScrollFlags_None;\n        if (window && !g.NavWindowingTarget && !(window->Flags & ImGuiWindowFlags_NoNavInputs))\n        {\n            const ImGuiInputFlags repeat_mode = ImGuiInputFlags_Repeat | (ImGuiInputFlags)ImGuiInputFlags_RepeatRateNavMove;\n            if (!IsActiveIdUsingNavDir(ImGuiDir_Left)  && ((nav_gamepad_active && IsKeyPressed(ImGuiKey_GamepadDpadLeft,  repeat_mode, ImGuiKeyOwner_NoOwner)) || (nav_keyboard_active && IsKeyPressed(ImGuiKey_LeftArrow,  repeat_mode, ImGuiKeyOwner_NoOwner)))) { g.NavMoveDir = ImGuiDir_Left; }\n            if (!IsActiveIdUsingNavDir(ImGuiDir_Right) && ((nav_gamepad_active && IsKeyPressed(ImGuiKey_GamepadDpadRight, repeat_mode, ImGuiKeyOwner_NoOwner)) || (nav_keyboard_active && IsKeyPressed(ImGuiKey_RightArrow, repeat_mode, ImGuiKeyOwner_NoOwner)))) { g.NavMoveDir = ImGuiDir_Right; }\n            if (!IsActiveIdUsingNavDir(ImGuiDir_Up)    && ((nav_gamepad_active && IsKeyPressed(ImGuiKey_GamepadDpadUp,    repeat_mode, ImGuiKeyOwner_NoOwner)) || (nav_keyboard_active && IsKeyPressed(ImGuiKey_UpArrow,    repeat_mode, ImGuiKeyOwner_NoOwner)))) { g.NavMoveDir = ImGuiDir_Up; }\n            if (!IsActiveIdUsingNavDir(ImGuiDir_Down)  && ((nav_gamepad_active && IsKeyPressed(ImGuiKey_GamepadDpadDown,  repeat_mode, ImGuiKeyOwner_NoOwner)) || (nav_keyboard_active && IsKeyPressed(ImGuiKey_DownArrow,  repeat_mode, ImGuiKeyOwner_NoOwner)))) { g.NavMoveDir = ImGuiDir_Down; }\n        }\n        g.NavMoveClipDir = g.NavMoveDir;\n        g.NavScoringNoClipRect = ImRect(+FLT_MAX, +FLT_MAX, -FLT_MAX, -FLT_MAX);\n    }\n\n    // Update PageUp/PageDown/Home/End scroll\n    // FIXME-NAV: Consider enabling those keys even without the master ImGuiConfigFlags_NavEnableKeyboard flag?\n    float scoring_rect_offset_y = 0.0f;\n    if (window && g.NavMoveDir == ImGuiDir_None && nav_keyboard_active)\n        scoring_rect_offset_y = NavUpdatePageUpPageDown();\n    if (scoring_rect_offset_y != 0.0f)\n    {\n        g.NavScoringNoClipRect = window->InnerRect;\n        g.NavScoringNoClipRect.TranslateY(scoring_rect_offset_y);\n    }\n\n    // [DEBUG] Always send a request when holding CTRL. Hold CTRL + Arrow change the direction.\n#if IMGUI_DEBUG_NAV_SCORING\n    //if (io.KeyCtrl && IsKeyPressed(ImGuiKey_C))\n    //    g.NavMoveDirForDebug = (ImGuiDir)((g.NavMoveDirForDebug + 1) & 3);\n    if (io.KeyCtrl)\n    {\n        if (g.NavMoveDir == ImGuiDir_None)\n            g.NavMoveDir = g.NavMoveDirForDebug;\n        g.NavMoveClipDir = g.NavMoveDir;\n        g.NavMoveFlags |= ImGuiNavMoveFlags_DebugNoResult;\n    }\n#endif\n\n    // Submit\n    g.NavMoveForwardToNextFrame = false;\n    if (g.NavMoveDir != ImGuiDir_None)\n        NavMoveRequestSubmit(g.NavMoveDir, g.NavMoveClipDir, g.NavMoveFlags, g.NavMoveScrollFlags);\n\n    // Moving with no reference triggers an init request (will be used as a fallback if the direction fails to find a match)\n    if (g.NavMoveSubmitted && g.NavId == 0)\n    {\n        IMGUI_DEBUG_LOG_NAV(\"[nav] NavInitRequest: from move, window \\\"%s\\\", layer=%d\\n\", window ? window->Name : \"<NULL>\", g.NavLayer);\n        g.NavInitRequest = g.NavInitRequestFromMove = true;\n        g.NavInitResult.ID = 0;\n        if (g.IO.ConfigNavCursorVisibleAuto)\n            g.NavCursorVisible = true;\n    }\n\n    // When using gamepad, we project the reference nav bounding box into window visible area.\n    // This is to allow resuming navigation inside the visible area after doing a large amount of scrolling,\n    // since with gamepad all movements are relative (can't focus a visible object like we can with the mouse).\n    if (g.NavMoveSubmitted && g.NavInputSource == ImGuiInputSource_Gamepad && g.NavLayer == ImGuiNavLayer_Main && window != NULL)// && (g.NavMoveFlags & ImGuiNavMoveFlags_Forwarded))\n    {\n        bool clamp_x = (g.NavMoveFlags & (ImGuiNavMoveFlags_LoopX | ImGuiNavMoveFlags_WrapX)) == 0;\n        bool clamp_y = (g.NavMoveFlags & (ImGuiNavMoveFlags_LoopY | ImGuiNavMoveFlags_WrapY)) == 0;\n        ImRect inner_rect_rel = WindowRectAbsToRel(window, ImRect(window->InnerRect.Min - ImVec2(1, 1), window->InnerRect.Max + ImVec2(1, 1)));\n\n        // Take account of changing scroll to handle triggering a new move request on a scrolling frame. (#6171)\n        // Otherwise 'inner_rect_rel' would be off on the move result frame.\n        inner_rect_rel.Translate(CalcNextScrollFromScrollTargetAndClamp(window) - window->Scroll);\n\n        if ((clamp_x || clamp_y) && !inner_rect_rel.Contains(window->NavRectRel[g.NavLayer]))\n        {\n            IMGUI_DEBUG_LOG_NAV(\"[nav] NavMoveRequest: clamp NavRectRel for gamepad move\\n\");\n            float pad_x = ImMin(inner_rect_rel.GetWidth(), window->FontRefSize * 0.5f);\n            float pad_y = ImMin(inner_rect_rel.GetHeight(), window->FontRefSize * 0.5f); // Terrible approximation for the intent of starting navigation from first fully visible item\n            inner_rect_rel.Min.x = clamp_x ? (inner_rect_rel.Min.x + pad_x) : -FLT_MAX;\n            inner_rect_rel.Max.x = clamp_x ? (inner_rect_rel.Max.x - pad_x) : +FLT_MAX;\n            inner_rect_rel.Min.y = clamp_y ? (inner_rect_rel.Min.y + pad_y) : -FLT_MAX;\n            inner_rect_rel.Max.y = clamp_y ? (inner_rect_rel.Max.y - pad_y) : +FLT_MAX;\n            window->NavRectRel[g.NavLayer].ClipWithFull(inner_rect_rel);\n            g.NavId = 0;\n        }\n    }\n\n    // For scoring we use a single segment on the left side our current item bounding box (not touching the edge to avoid box overlap with zero-spaced items)\n    ImRect scoring_rect;\n    if (window != NULL)\n    {\n        ImRect nav_rect_rel = !window->NavRectRel[g.NavLayer].IsInverted() ? window->NavRectRel[g.NavLayer] : ImRect(0, 0, 0, 0);\n        scoring_rect = WindowRectRelToAbs(window, nav_rect_rel);\n        scoring_rect.TranslateY(scoring_rect_offset_y);\n        if (g.NavMoveSubmitted)\n            NavBiasScoringRect(scoring_rect, window->RootWindowForNav->NavPreferredScoringPosRel[g.NavLayer], g.NavMoveDir, g.NavMoveFlags);\n        IM_ASSERT(!scoring_rect.IsInverted()); // Ensure we have a non-inverted bounding box here will allow us to remove extraneous ImFabs() calls in NavScoreItem().\n        //GetForegroundDrawList()->AddRect(scoring_rect.Min, scoring_rect.Max, IM_COL32(255,200,0,255)); // [DEBUG]\n        //if (!g.NavScoringNoClipRect.IsInverted()) { GetForegroundDrawList()->AddRect(g.NavScoringNoClipRect.Min, g.NavScoringNoClipRect.Max, IM_COL32(255, 200, 0, 255)); } // [DEBUG]\n    }\n    g.NavScoringRect = scoring_rect;\n    g.NavScoringNoClipRect.Add(scoring_rect);\n}\n\nvoid ImGui::NavUpdateCreateTabbingRequest()\n{\n    ImGuiContext& g = *GImGui;\n    ImGuiWindow* window = g.NavWindow;\n    IM_ASSERT(g.NavMoveDir == ImGuiDir_None);\n    if (window == NULL || g.NavWindowingTarget != NULL || (window->Flags & ImGuiWindowFlags_NoNavInputs))\n        return;\n\n    const bool tab_pressed = IsKeyPressed(ImGuiKey_Tab, ImGuiInputFlags_Repeat, ImGuiKeyOwner_NoOwner) && !g.IO.KeyCtrl && !g.IO.KeyAlt;\n    if (!tab_pressed)\n        return;\n\n    // Initiate tabbing request\n    // (this is ALWAYS ENABLED, regardless of ImGuiConfigFlags_NavEnableKeyboard flag!)\n    // See NavProcessItemForTabbingRequest() for a description of the various forward/backward tabbing cases with and without wrapping.\n    const bool nav_keyboard_active = (g.IO.ConfigFlags & ImGuiConfigFlags_NavEnableKeyboard) != 0;\n    if (nav_keyboard_active)\n        g.NavTabbingDir = g.IO.KeyShift ? -1 : (g.NavCursorVisible == false && g.ActiveId == 0) ? 0 : +1;\n    else\n        g.NavTabbingDir = g.IO.KeyShift ? -1 : (g.ActiveId == 0) ? 0 : +1;\n    ImGuiNavMoveFlags move_flags = ImGuiNavMoveFlags_IsTabbing | ImGuiNavMoveFlags_Activate;\n    ImGuiScrollFlags scroll_flags = window->Appearing ? ImGuiScrollFlags_KeepVisibleEdgeX | ImGuiScrollFlags_AlwaysCenterY : ImGuiScrollFlags_KeepVisibleEdgeX | ImGuiScrollFlags_KeepVisibleEdgeY;\n    ImGuiDir clip_dir = (g.NavTabbingDir < 0) ? ImGuiDir_Up : ImGuiDir_Down;\n    NavMoveRequestSubmit(ImGuiDir_None, clip_dir, move_flags, scroll_flags); // FIXME-NAV: Once we refactor tabbing, add LegacyApi flag to not activate non-inputable.\n    g.NavTabbingCounter = -1;\n}\n\n// Apply result from previous frame navigation directional move request. Always called from NavUpdate()\nvoid ImGui::NavMoveRequestApplyResult()\n{\n    ImGuiContext& g = *GImGui;\n#if IMGUI_DEBUG_NAV_SCORING\n    if (g.NavMoveFlags & ImGuiNavMoveFlags_DebugNoResult) // [DEBUG] Scoring all items in NavWindow at all times\n        return;\n#endif\n\n    // Select which result to use\n    ImGuiNavItemData* result = (g.NavMoveResultLocal.ID != 0) ? &g.NavMoveResultLocal : (g.NavMoveResultOther.ID != 0) ? &g.NavMoveResultOther : NULL;\n\n    // Tabbing forward wrap\n    if ((g.NavMoveFlags & ImGuiNavMoveFlags_IsTabbing) && result == NULL)\n        if ((g.NavTabbingCounter == 1 || g.NavTabbingDir == 0) && g.NavTabbingResultFirst.ID)\n            result = &g.NavTabbingResultFirst;\n\n    // In a situation when there are no results but NavId != 0, re-enable the Navigation highlight (because g.NavId is not considered as a possible result)\n    const ImGuiAxis axis = (g.NavMoveDir == ImGuiDir_Up || g.NavMoveDir == ImGuiDir_Down) ? ImGuiAxis_Y : ImGuiAxis_X;\n    if (result == NULL)\n    {\n        if (g.NavMoveFlags & ImGuiNavMoveFlags_IsTabbing)\n            g.NavMoveFlags |= ImGuiNavMoveFlags_NoSetNavCursorVisible;\n        if (g.NavId != 0 && (g.NavMoveFlags & ImGuiNavMoveFlags_NoSetNavCursorVisible) == 0)\n            SetNavCursorVisibleAfterMove();\n        NavClearPreferredPosForAxis(axis); // On a failed move, clear preferred pos for this axis.\n        IMGUI_DEBUG_LOG_NAV(\"[nav] NavMoveSubmitted but not led to a result!\\n\");\n        return;\n    }\n\n    // PageUp/PageDown behavior first jumps to the bottom/top mostly visible item, _otherwise_ use the result from the previous/next page.\n    if (g.NavMoveFlags & ImGuiNavMoveFlags_AlsoScoreVisibleSet)\n        if (g.NavMoveResultLocalVisible.ID != 0 && g.NavMoveResultLocalVisible.ID != g.NavId)\n            result = &g.NavMoveResultLocalVisible;\n\n    // Maybe entering a flattened child from the outside? In this case solve the tie using the regular scoring rules.\n    if (result != &g.NavMoveResultOther && g.NavMoveResultOther.ID != 0 && g.NavMoveResultOther.Window->ParentWindow == g.NavWindow)\n        if ((g.NavMoveResultOther.DistBox < result->DistBox) || (g.NavMoveResultOther.DistBox == result->DistBox && g.NavMoveResultOther.DistCenter < result->DistCenter))\n            result = &g.NavMoveResultOther;\n    IM_ASSERT(g.NavWindow && result->Window);\n\n    // Scroll to keep newly navigated item fully into view.\n    if (g.NavLayer == ImGuiNavLayer_Main)\n    {\n        ImRect rect_abs = WindowRectRelToAbs(result->Window, result->RectRel);\n        ScrollToRectEx(result->Window, rect_abs, g.NavMoveScrollFlags);\n\n        if (g.NavMoveFlags & ImGuiNavMoveFlags_ScrollToEdgeY)\n        {\n            // FIXME: Should remove this? Or make more precise: use ScrollToRectEx() with edge?\n            float scroll_target = (g.NavMoveDir == ImGuiDir_Up) ? result->Window->ScrollMax.y : 0.0f;\n            SetScrollY(result->Window, scroll_target);\n        }\n    }\n\n    if (g.NavWindow != result->Window)\n    {\n        IMGUI_DEBUG_LOG_FOCUS(\"[focus] NavMoveRequest: SetNavWindow(\\\"%s\\\")\\n\", result->Window->Name);\n        g.NavWindow = result->Window;\n        g.NavLastValidSelectionUserData = ImGuiSelectionUserData_Invalid;\n    }\n\n    // Clear active id unless requested not to\n    // FIXME: ImGuiNavMoveFlags_NoClearActiveId is currently unused as we don't have a clear strategy to preserve active id after interaction,\n    // so this is mostly provided as a gateway for further experiments (see #1418, #2890)\n    if (g.ActiveId != result->ID && (g.NavMoveFlags & ImGuiNavMoveFlags_NoClearActiveId) == 0)\n        ClearActiveID();\n\n    // Don't set NavJustMovedToId if just landed on the same spot (which may happen with ImGuiNavMoveFlags_AllowCurrentNavId)\n    // PageUp/PageDown however sets always set NavJustMovedTo (vs Home/End which doesn't) mimicking Windows behavior.\n    if ((g.NavId != result->ID || (g.NavMoveFlags & ImGuiNavMoveFlags_IsPageMove)) && (g.NavMoveFlags & ImGuiNavMoveFlags_NoSelect) == 0)\n    {\n        g.NavJustMovedFromFocusScopeId = g.NavFocusScopeId;\n        g.NavJustMovedToId = result->ID;\n        g.NavJustMovedToFocusScopeId = result->FocusScopeId;\n        g.NavJustMovedToKeyMods = g.NavMoveKeyMods;\n        g.NavJustMovedToIsTabbing = (g.NavMoveFlags & ImGuiNavMoveFlags_IsTabbing) != 0;\n        g.NavJustMovedToHasSelectionData = (result->ItemFlags & ImGuiItemFlags_HasSelectionUserData) != 0;\n        //IMGUI_DEBUG_LOG_NAV(\"[nav] NavJustMovedFromFocusScopeId = 0x%08X, NavJustMovedToFocusScopeId = 0x%08X\\n\", g.NavJustMovedFromFocusScopeId, g.NavJustMovedToFocusScopeId);\n    }\n\n    // Apply new NavID/Focus\n    IMGUI_DEBUG_LOG_NAV(\"[nav] NavMoveRequest: result NavID 0x%08X in Layer %d Window \\\"%s\\\"\\n\", result->ID, g.NavLayer, g.NavWindow->Name);\n    ImVec2 preferred_scoring_pos_rel = g.NavWindow->RootWindowForNav->NavPreferredScoringPosRel[g.NavLayer];\n    SetNavID(result->ID, g.NavLayer, result->FocusScopeId, result->RectRel);\n    if (result->SelectionUserData != ImGuiSelectionUserData_Invalid)\n        g.NavLastValidSelectionUserData = result->SelectionUserData;\n\n    // Restore last preferred position for current axis\n    // (storing in RootWindowForNav-> as the info is desirable at the beginning of a Move Request. In theory all storage should use RootWindowForNav..)\n    if ((g.NavMoveFlags & ImGuiNavMoveFlags_IsTabbing) == 0)\n    {\n        preferred_scoring_pos_rel[axis] = result->RectRel.GetCenter()[axis];\n        g.NavWindow->RootWindowForNav->NavPreferredScoringPosRel[g.NavLayer] = preferred_scoring_pos_rel;\n    }\n\n    // Tabbing: Activates Inputable, otherwise only Focus\n    if ((g.NavMoveFlags & ImGuiNavMoveFlags_IsTabbing) && (result->ItemFlags & ImGuiItemFlags_Inputable) == 0)\n        g.NavMoveFlags &= ~ImGuiNavMoveFlags_Activate;\n\n    // Activate\n    if (g.NavMoveFlags & ImGuiNavMoveFlags_Activate)\n    {\n        g.NavNextActivateId = result->ID;\n        g.NavNextActivateFlags = ImGuiActivateFlags_None;\n        if (g.NavMoveFlags & ImGuiNavMoveFlags_IsTabbing)\n            g.NavNextActivateFlags |= ImGuiActivateFlags_PreferInput | ImGuiActivateFlags_TryToPreserveState | ImGuiActivateFlags_FromTabbing;\n    }\n\n    // Make nav cursor visible\n    if ((g.NavMoveFlags & ImGuiNavMoveFlags_NoSetNavCursorVisible) == 0)\n        SetNavCursorVisibleAfterMove();\n}\n\n// Process Escape/NavCancel input (to close a popup, get back to parent, clear focus)\n// FIXME: In order to support e.g. Escape to clear a selection we'll need:\n// - either to store the equivalent of ActiveIdUsingKeyInputMask for a FocusScope and test for it.\n// - either to move most/all of those tests to the epilogue/end functions of the scope they are dealing with (e.g. exit child window in EndChild()) or in EndFrame(), to allow an earlier intercept\nstatic void ImGui::NavUpdateCancelRequest()\n{\n    ImGuiContext& g = *GImGui;\n    const bool nav_gamepad_active = (g.IO.ConfigFlags & ImGuiConfigFlags_NavEnableGamepad) != 0 && (g.IO.BackendFlags & ImGuiBackendFlags_HasGamepad) != 0;\n    const bool nav_keyboard_active = (g.IO.ConfigFlags & ImGuiConfigFlags_NavEnableKeyboard) != 0;\n    if (!(nav_keyboard_active && IsKeyPressed(ImGuiKey_Escape, 0, ImGuiKeyOwner_NoOwner)) && !(nav_gamepad_active && IsKeyPressed(ImGuiKey_NavGamepadCancel, 0, ImGuiKeyOwner_NoOwner)))\n        return;\n\n    IMGUI_DEBUG_LOG_NAV(\"[nav] NavUpdateCancelRequest()\\n\");\n    if (g.ActiveId != 0)\n    {\n        ClearActiveID();\n    }\n    else if (g.NavLayer != ImGuiNavLayer_Main)\n    {\n        // Leave the \"menu\" layer\n        NavRestoreLayer(ImGuiNavLayer_Main);\n        SetNavCursorVisibleAfterMove();\n    }\n    else if (g.NavWindow && g.NavWindow != g.NavWindow->RootWindow && !(g.NavWindow->RootWindowForNav->Flags & ImGuiWindowFlags_Popup) && g.NavWindow->RootWindowForNav->ParentWindow)\n    {\n        // Exit child window\n        ImGuiWindow* child_window = g.NavWindow->RootWindowForNav;\n        ImGuiWindow* parent_window = child_window->ParentWindow;\n        IM_ASSERT(child_window->ChildId != 0);\n        FocusWindow(parent_window);\n        SetNavID(child_window->ChildId, ImGuiNavLayer_Main, 0, WindowRectAbsToRel(parent_window, child_window->Rect()));\n        SetNavCursorVisibleAfterMove();\n    }\n    else if (g.OpenPopupStack.Size > 0 && g.OpenPopupStack.back().Window != NULL && !(g.OpenPopupStack.back().Window->Flags & ImGuiWindowFlags_Modal))\n    {\n        // Close open popup/menu\n        ClosePopupToLevel(g.OpenPopupStack.Size - 1, true);\n    }\n    else\n    {\n        // Clear NavLastId for popups but keep it for regular child window so we can leave one and come back where we were\n        // FIXME-NAV: This should happen on window appearing.\n        if (g.IO.ConfigNavEscapeClearFocusItem || g.IO.ConfigNavEscapeClearFocusWindow)\n            if (g.NavWindow && ((g.NavWindow->Flags & ImGuiWindowFlags_Popup)))// || !(g.NavWindow->Flags & ImGuiWindowFlags_ChildWindow)))\n                g.NavWindow->NavLastIds[0] = 0;\n\n        // Clear nav focus\n        if (g.IO.ConfigNavEscapeClearFocusItem || g.IO.ConfigNavEscapeClearFocusWindow)\n            g.NavId = 0;\n        if (g.IO.ConfigNavEscapeClearFocusWindow)\n            FocusWindow(NULL);\n    }\n}\n\n// Handle PageUp/PageDown/Home/End keys\n// Called from NavUpdateCreateMoveRequest() which will use our output to create a move request\n// FIXME-NAV: This doesn't work properly with NavFlattened siblings as we use NavWindow rectangle for reference\n// FIXME-NAV: how to get Home/End to aim at the beginning/end of a 2D grid?\nstatic float ImGui::NavUpdatePageUpPageDown()\n{\n    ImGuiContext& g = *GImGui;\n    ImGuiWindow* window = g.NavWindow;\n    if ((window->Flags & ImGuiWindowFlags_NoNavInputs) || g.NavWindowingTarget != NULL)\n        return 0.0f;\n\n    const bool page_up_held = IsKeyDown(ImGuiKey_PageUp, ImGuiKeyOwner_NoOwner);\n    const bool page_down_held = IsKeyDown(ImGuiKey_PageDown, ImGuiKeyOwner_NoOwner);\n    const bool home_pressed = IsKeyPressed(ImGuiKey_Home, ImGuiInputFlags_Repeat, ImGuiKeyOwner_NoOwner);\n    const bool end_pressed = IsKeyPressed(ImGuiKey_End, ImGuiInputFlags_Repeat, ImGuiKeyOwner_NoOwner);\n    if (page_up_held == page_down_held && home_pressed == end_pressed) // Proceed if either (not both) are pressed, otherwise early out\n        return 0.0f;\n\n    if (g.NavLayer != ImGuiNavLayer_Main)\n        NavRestoreLayer(ImGuiNavLayer_Main);\n\n    if (window->DC.NavLayersActiveMask == 0x00 && window->DC.NavWindowHasScrollY)\n    {\n        // Fallback manual-scroll when window has no navigable item\n        if (IsKeyPressed(ImGuiKey_PageUp, ImGuiInputFlags_Repeat, ImGuiKeyOwner_NoOwner))\n            SetScrollY(window, window->Scroll.y - window->InnerRect.GetHeight());\n        else if (IsKeyPressed(ImGuiKey_PageDown, ImGuiInputFlags_Repeat, ImGuiKeyOwner_NoOwner))\n            SetScrollY(window, window->Scroll.y + window->InnerRect.GetHeight());\n        else if (home_pressed)\n            SetScrollY(window, 0.0f);\n        else if (end_pressed)\n            SetScrollY(window, window->ScrollMax.y);\n    }\n    else\n    {\n        ImRect& nav_rect_rel = window->NavRectRel[g.NavLayer];\n        const float page_offset_y = ImMax(0.0f, window->InnerRect.GetHeight() - window->FontRefSize * 1.0f + nav_rect_rel.GetHeight());\n        float nav_scoring_rect_offset_y = 0.0f;\n        if (IsKeyPressed(ImGuiKey_PageUp, true))\n        {\n            nav_scoring_rect_offset_y = -page_offset_y;\n            g.NavMoveDir = ImGuiDir_Down; // Because our scoring rect is offset up, we request the down direction (so we can always land on the last item)\n            g.NavMoveClipDir = ImGuiDir_Up;\n            g.NavMoveFlags = ImGuiNavMoveFlags_AllowCurrentNavId | ImGuiNavMoveFlags_AlsoScoreVisibleSet | ImGuiNavMoveFlags_IsPageMove;\n        }\n        else if (IsKeyPressed(ImGuiKey_PageDown, true))\n        {\n            nav_scoring_rect_offset_y = +page_offset_y;\n            g.NavMoveDir = ImGuiDir_Up; // Because our scoring rect is offset down, we request the up direction (so we can always land on the last item)\n            g.NavMoveClipDir = ImGuiDir_Down;\n            g.NavMoveFlags = ImGuiNavMoveFlags_AllowCurrentNavId | ImGuiNavMoveFlags_AlsoScoreVisibleSet | ImGuiNavMoveFlags_IsPageMove;\n        }\n        else if (home_pressed)\n        {\n            // FIXME-NAV: handling of Home/End is assuming that the top/bottom most item will be visible with Scroll.y == 0/ScrollMax.y\n            // Scrolling will be handled via the ImGuiNavMoveFlags_ScrollToEdgeY flag, we don't scroll immediately to avoid scrolling happening before nav result.\n            // Preserve current horizontal position if we have any.\n            nav_rect_rel.Min.y = nav_rect_rel.Max.y = 0.0f;\n            if (nav_rect_rel.IsInverted())\n                nav_rect_rel.Min.x = nav_rect_rel.Max.x = 0.0f;\n            g.NavMoveDir = ImGuiDir_Down;\n            g.NavMoveFlags = ImGuiNavMoveFlags_AllowCurrentNavId | ImGuiNavMoveFlags_ScrollToEdgeY;\n            // FIXME-NAV: MoveClipDir left to _None, intentional?\n        }\n        else if (end_pressed)\n        {\n            nav_rect_rel.Min.y = nav_rect_rel.Max.y = window->ContentSize.y;\n            if (nav_rect_rel.IsInverted())\n                nav_rect_rel.Min.x = nav_rect_rel.Max.x = 0.0f;\n            g.NavMoveDir = ImGuiDir_Up;\n            g.NavMoveFlags = ImGuiNavMoveFlags_AllowCurrentNavId | ImGuiNavMoveFlags_ScrollToEdgeY;\n            // FIXME-NAV: MoveClipDir left to _None, intentional?\n        }\n        return nav_scoring_rect_offset_y;\n    }\n    return 0.0f;\n}\n\nstatic void ImGui::NavEndFrame()\n{\n    ImGuiContext& g = *GImGui;\n\n    // Show CTRL+TAB list window\n    if (g.NavWindowingTarget != NULL)\n        NavUpdateWindowingOverlay();\n\n    // Perform wrap-around in menus\n    // FIXME-NAV: Wrap may need to apply a weight bias on the other axis. e.g. 4x4 grid with 2 last items missing on last item won't handle LoopY/WrapY correctly.\n    // FIXME-NAV: Wrap (not Loop) support could be handled by the scoring function and then WrapX would function without an extra frame.\n    if (g.NavWindow && NavMoveRequestButNoResultYet() && (g.NavMoveFlags & ImGuiNavMoveFlags_WrapMask_) && (g.NavMoveFlags & ImGuiNavMoveFlags_Forwarded) == 0)\n        NavUpdateCreateWrappingRequest();\n}\n\nstatic void ImGui::NavUpdateCreateWrappingRequest()\n{\n    ImGuiContext& g = *GImGui;\n    ImGuiWindow* window = g.NavWindow;\n\n    bool do_forward = false;\n    ImRect bb_rel = window->NavRectRel[g.NavLayer];\n    ImGuiDir clip_dir = g.NavMoveDir;\n\n    const ImGuiNavMoveFlags move_flags = g.NavMoveFlags;\n    //const ImGuiAxis move_axis = (g.NavMoveDir == ImGuiDir_Up || g.NavMoveDir == ImGuiDir_Down) ? ImGuiAxis_Y : ImGuiAxis_X;\n    if (g.NavMoveDir == ImGuiDir_Left && (move_flags & (ImGuiNavMoveFlags_WrapX | ImGuiNavMoveFlags_LoopX)))\n    {\n        bb_rel.Min.x = bb_rel.Max.x = window->ContentSize.x + window->WindowPadding.x;\n        if (move_flags & ImGuiNavMoveFlags_WrapX)\n        {\n            bb_rel.TranslateY(-bb_rel.GetHeight()); // Previous row\n            clip_dir = ImGuiDir_Up;\n        }\n        do_forward = true;\n    }\n    if (g.NavMoveDir == ImGuiDir_Right && (move_flags & (ImGuiNavMoveFlags_WrapX | ImGuiNavMoveFlags_LoopX)))\n    {\n        bb_rel.Min.x = bb_rel.Max.x = -window->WindowPadding.x;\n        if (move_flags & ImGuiNavMoveFlags_WrapX)\n        {\n            bb_rel.TranslateY(+bb_rel.GetHeight()); // Next row\n            clip_dir = ImGuiDir_Down;\n        }\n        do_forward = true;\n    }\n    if (g.NavMoveDir == ImGuiDir_Up && (move_flags & (ImGuiNavMoveFlags_WrapY | ImGuiNavMoveFlags_LoopY)))\n    {\n        bb_rel.Min.y = bb_rel.Max.y = window->ContentSize.y + window->WindowPadding.y;\n        if (move_flags & ImGuiNavMoveFlags_WrapY)\n        {\n            bb_rel.TranslateX(-bb_rel.GetWidth()); // Previous column\n            clip_dir = ImGuiDir_Left;\n        }\n        do_forward = true;\n    }\n    if (g.NavMoveDir == ImGuiDir_Down && (move_flags & (ImGuiNavMoveFlags_WrapY | ImGuiNavMoveFlags_LoopY)))\n    {\n        bb_rel.Min.y = bb_rel.Max.y = -window->WindowPadding.y;\n        if (move_flags & ImGuiNavMoveFlags_WrapY)\n        {\n            bb_rel.TranslateX(+bb_rel.GetWidth()); // Next column\n            clip_dir = ImGuiDir_Right;\n        }\n        do_forward = true;\n    }\n    if (!do_forward)\n        return;\n    window->NavRectRel[g.NavLayer] = bb_rel;\n    NavClearPreferredPosForAxis(ImGuiAxis_X);\n    NavClearPreferredPosForAxis(ImGuiAxis_Y);\n    NavMoveRequestForward(g.NavMoveDir, clip_dir, move_flags, g.NavMoveScrollFlags);\n}\n\n// Can we focus this window with CTRL+TAB (or PadMenu + PadFocusPrev/PadFocusNext)\n// Note that NoNavFocus makes the window not reachable with CTRL+TAB but it can still be focused with mouse or programmatically.\n// If you want a window to never be focused, you may use the e.g. NoInputs flag.\nbool ImGui::IsWindowNavFocusable(ImGuiWindow* window)\n{\n    return window->WasActive && window == window->RootWindow && !(window->Flags & ImGuiWindowFlags_NoNavFocus);\n}\n\nstatic ImGuiWindow* FindWindowNavFocusable(int i_start, int i_stop, int dir) // FIXME-OPT O(N)\n{\n    ImGuiContext& g = *GImGui;\n    for (int i = i_start; i >= 0 && i < g.WindowsFocusOrder.Size && i != i_stop; i += dir)\n        if (ImGui::IsWindowNavFocusable(g.WindowsFocusOrder[i]))\n            return g.WindowsFocusOrder[i];\n    return NULL;\n}\n\nstatic void NavUpdateWindowingTarget(int focus_change_dir)\n{\n    ImGuiContext& g = *GImGui;\n    IM_ASSERT(g.NavWindowingTarget);\n    if (g.NavWindowingTarget->Flags & ImGuiWindowFlags_Modal)\n        return;\n\n    const int i_current = ImGui::FindWindowFocusIndex(g.NavWindowingTarget);\n    ImGuiWindow* window_target = FindWindowNavFocusable(i_current + focus_change_dir, -INT_MAX, focus_change_dir);\n    if (!window_target)\n        window_target = FindWindowNavFocusable((focus_change_dir < 0) ? (g.WindowsFocusOrder.Size - 1) : 0, i_current, focus_change_dir);\n    if (window_target) // Don't reset windowing target if there's a single window in the list\n    {\n        g.NavWindowingTarget = g.NavWindowingTargetAnim = window_target;\n        g.NavWindowingAccumDeltaPos = g.NavWindowingAccumDeltaSize = ImVec2(0.0f, 0.0f);\n    }\n    g.NavWindowingToggleLayer = false;\n}\n\n// Apply focus and close overlay\nstatic void ImGui::NavUpdateWindowingApplyFocus(ImGuiWindow* apply_focus_window)\n{\n    ImGuiContext& g = *GImGui;\n    if (g.NavWindow == NULL || apply_focus_window != g.NavWindow->RootWindow)\n    {\n        ClearActiveID();\n        SetNavCursorVisibleAfterMove();\n        ClosePopupsOverWindow(apply_focus_window, false);\n        FocusWindow(apply_focus_window, ImGuiFocusRequestFlags_RestoreFocusedChild);\n        apply_focus_window = g.NavWindow;\n        if (apply_focus_window->NavLastIds[0] == 0)\n            NavInitWindow(apply_focus_window, false);\n\n        // If the window has ONLY a menu layer (no main layer), select it directly\n        // Use NavLayersActiveMaskNext since windows didn't have a chance to be Begin()-ed on this frame,\n        // so CTRL+Tab where the keys are only held for 1 frame will be able to use correct layers mask since\n        // the target window as already been previewed once.\n        // FIXME-NAV: This should be done in NavInit.. or in FocusWindow... However in both of those cases,\n        // we won't have a guarantee that windows has been visible before and therefore NavLayersActiveMask*\n        // won't be valid.\n        if (apply_focus_window->DC.NavLayersActiveMaskNext == (1 << ImGuiNavLayer_Menu))\n            g.NavLayer = ImGuiNavLayer_Menu;\n    }\n    g.NavWindowingTarget = NULL;\n}\n\n// Windowing management mode\n// Keyboard: CTRL+Tab (change focus/move/resize), Alt (toggle menu layer)\n// Gamepad:  Hold Menu/Square (change focus/move/resize), Tap Menu/Square (toggle menu layer)\nstatic void ImGui::NavUpdateWindowing()\n{\n    ImGuiContext& g = *GImGui;\n    ImGuiIO& io = g.IO;\n\n    ImGuiWindow* apply_focus_window = NULL;\n    bool apply_toggle_layer = false;\n\n    ImGuiWindow* modal_window = GetTopMostPopupModal();\n    bool allow_windowing = (modal_window == NULL); // FIXME: This prevent CTRL+TAB from being usable with windows that are inside the Begin-stack of that modal.\n    if (!allow_windowing)\n        g.NavWindowingTarget = NULL;\n\n    // Fade out\n    if (g.NavWindowingTargetAnim && g.NavWindowingTarget == NULL)\n    {\n        g.NavWindowingHighlightAlpha = ImMax(g.NavWindowingHighlightAlpha - io.DeltaTime * 10.0f, 0.0f);\n        if (g.DimBgRatio <= 0.0f && g.NavWindowingHighlightAlpha <= 0.0f)\n            g.NavWindowingTargetAnim = NULL;\n    }\n\n    // Start CTRL+Tab or Square+L/R window selection\n    // (g.ConfigNavWindowingKeyNext/g.ConfigNavWindowingKeyPrev defaults are ImGuiMod_Ctrl|ImGuiKey_Tab and ImGuiMod_Ctrl|ImGuiMod_Shift|ImGuiKey_Tab)\n    const ImGuiID owner_id = ImHashStr(\"##NavUpdateWindowing\");\n    const bool nav_gamepad_active = (io.ConfigFlags & ImGuiConfigFlags_NavEnableGamepad) != 0 && (io.BackendFlags & ImGuiBackendFlags_HasGamepad) != 0;\n    const bool nav_keyboard_active = (io.ConfigFlags & ImGuiConfigFlags_NavEnableKeyboard) != 0;\n    const bool keyboard_next_window = allow_windowing && g.ConfigNavWindowingKeyNext && Shortcut(g.ConfigNavWindowingKeyNext, ImGuiInputFlags_Repeat | ImGuiInputFlags_RouteAlways, owner_id);\n    const bool keyboard_prev_window = allow_windowing && g.ConfigNavWindowingKeyPrev && Shortcut(g.ConfigNavWindowingKeyPrev, ImGuiInputFlags_Repeat | ImGuiInputFlags_RouteAlways, owner_id);\n    const bool start_windowing_with_gamepad = allow_windowing && nav_gamepad_active && !g.NavWindowingTarget && IsKeyPressed(ImGuiKey_NavGamepadMenu, ImGuiInputFlags_None);\n    const bool start_windowing_with_keyboard = allow_windowing && !g.NavWindowingTarget && (keyboard_next_window || keyboard_prev_window); // Note: enabled even without NavEnableKeyboard!\n    bool just_started_windowing_from_null_focus = false;\n    if (start_windowing_with_gamepad || start_windowing_with_keyboard)\n        if (ImGuiWindow* window = g.NavWindow ? g.NavWindow : FindWindowNavFocusable(g.WindowsFocusOrder.Size - 1, -INT_MAX, -1))\n        {\n            g.NavWindowingTarget = g.NavWindowingTargetAnim = window->RootWindow; // Current location\n            g.NavWindowingTimer = g.NavWindowingHighlightAlpha = 0.0f;\n            g.NavWindowingAccumDeltaPos = g.NavWindowingAccumDeltaSize = ImVec2(0.0f, 0.0f);\n            g.NavWindowingToggleLayer = start_windowing_with_gamepad ? true : false; // Gamepad starts toggling layer\n            g.NavInputSource = start_windowing_with_keyboard ? ImGuiInputSource_Keyboard : ImGuiInputSource_Gamepad;\n            if (g.NavWindow == NULL)\n                just_started_windowing_from_null_focus = true;\n\n            // Manually register ownership of our mods. Using a global route in the Shortcut() calls instead would probably be correct but may have more side-effects.\n            if (keyboard_next_window || keyboard_prev_window)\n                SetKeyOwnersForKeyChord((g.ConfigNavWindowingKeyNext | g.ConfigNavWindowingKeyPrev) & ImGuiMod_Mask_, owner_id);\n        }\n\n    // Gamepad update\n    g.NavWindowingTimer += io.DeltaTime;\n    if (g.NavWindowingTarget && g.NavInputSource == ImGuiInputSource_Gamepad)\n    {\n        // Highlight only appears after a brief time holding the button, so that a fast tap on PadMenu (to toggle NavLayer) doesn't add visual noise\n        g.NavWindowingHighlightAlpha = ImMax(g.NavWindowingHighlightAlpha, ImSaturate((g.NavWindowingTimer - NAV_WINDOWING_HIGHLIGHT_DELAY) / 0.05f));\n\n        // Select window to focus\n        const int focus_change_dir = (int)IsKeyPressed(ImGuiKey_GamepadL1) - (int)IsKeyPressed(ImGuiKey_GamepadR1);\n        if (focus_change_dir != 0 && !just_started_windowing_from_null_focus)\n        {\n            NavUpdateWindowingTarget(focus_change_dir);\n            g.NavWindowingHighlightAlpha = 1.0f;\n        }\n\n        // Single press toggles NavLayer, long press with L/R apply actual focus on release (until then the window was merely rendered top-most)\n        if (!IsKeyDown(ImGuiKey_NavGamepadMenu))\n        {\n            g.NavWindowingToggleLayer &= (g.NavWindowingHighlightAlpha < 1.0f); // Once button was held long enough we don't consider it a tap-to-toggle-layer press anymore.\n            if (g.NavWindowingToggleLayer && g.NavWindow)\n                apply_toggle_layer = true;\n            else if (!g.NavWindowingToggleLayer)\n                apply_focus_window = g.NavWindowingTarget;\n            g.NavWindowingTarget = NULL;\n        }\n    }\n\n    // Keyboard: Focus\n    if (g.NavWindowingTarget && g.NavInputSource == ImGuiInputSource_Keyboard)\n    {\n        // Visuals only appears after a brief time after pressing TAB the first time, so that a fast CTRL+TAB doesn't add visual noise\n        ImGuiKeyChord shared_mods = ((g.ConfigNavWindowingKeyNext ? g.ConfigNavWindowingKeyNext : ImGuiMod_Mask_) & (g.ConfigNavWindowingKeyPrev ? g.ConfigNavWindowingKeyPrev : ImGuiMod_Mask_)) & ImGuiMod_Mask_;\n        IM_ASSERT(shared_mods != 0); // Next/Prev shortcut currently needs a shared modifier to \"hold\", otherwise Prev actions would keep cycling between two windows.\n        g.NavWindowingHighlightAlpha = ImMax(g.NavWindowingHighlightAlpha, ImSaturate((g.NavWindowingTimer - NAV_WINDOWING_HIGHLIGHT_DELAY) / 0.05f)); // 1.0f\n        if ((keyboard_next_window || keyboard_prev_window) && !just_started_windowing_from_null_focus)\n            NavUpdateWindowingTarget(keyboard_next_window ? -1 : +1);\n        else if ((io.KeyMods & shared_mods) != shared_mods)\n            apply_focus_window = g.NavWindowingTarget;\n    }\n\n    // Keyboard: Press and Release ALT to toggle menu layer\n    const ImGuiKey windowing_toggle_keys[] = { ImGuiKey_LeftAlt, ImGuiKey_RightAlt };\n    bool windowing_toggle_layer_start = false;\n    if (g.NavWindow != NULL && !(g.NavWindow->Flags & ImGuiWindowFlags_NoNavInputs))\n        for (ImGuiKey windowing_toggle_key : windowing_toggle_keys)\n            if (nav_keyboard_active && IsKeyPressed(windowing_toggle_key, 0, ImGuiKeyOwner_NoOwner))\n            {\n                windowing_toggle_layer_start = true;\n                g.NavWindowingToggleLayer = true;\n                g.NavWindowingToggleKey = windowing_toggle_key;\n                g.NavInputSource = ImGuiInputSource_Keyboard;\n                break;\n            }\n    if (g.NavWindowingToggleLayer && g.NavInputSource == ImGuiInputSource_Keyboard)\n    {\n        // We cancel toggling nav layer when any text has been typed (generally while holding Alt). (See #370)\n        // We cancel toggling nav layer when other modifiers are pressed. (See #4439)\n        // - AltGR is Alt+Ctrl on some layout but we can't reliably detect it (not all backends/systems/layout emit it as Alt+Ctrl).\n        // We cancel toggling nav layer if an owner has claimed the key.\n        if (io.InputQueueCharacters.Size > 0 || io.KeyCtrl || io.KeyShift || io.KeySuper)\n            g.NavWindowingToggleLayer = false;\n        else if (windowing_toggle_layer_start == false && g.LastKeyboardKeyPressTime == g.Time)\n            g.NavWindowingToggleLayer = false;\n        else if (TestKeyOwner(g.NavWindowingToggleKey, ImGuiKeyOwner_NoOwner) == false || TestKeyOwner(ImGuiMod_Alt, ImGuiKeyOwner_NoOwner) == false)\n            g.NavWindowingToggleLayer = false;\n\n        // Apply layer toggle on Alt release\n        // Important: as before version <18314 we lacked an explicit IO event for focus gain/loss, we also compare mouse validity to detect old backends clearing mouse pos on focus loss.\n        if (IsKeyReleased(g.NavWindowingToggleKey) && g.NavWindowingToggleLayer)\n            if (g.ActiveId == 0 || g.ActiveIdAllowOverlap)\n                if (IsMousePosValid(&io.MousePos) == IsMousePosValid(&io.MousePosPrev))\n                    apply_toggle_layer = true;\n        if (!IsKeyDown(g.NavWindowingToggleKey))\n            g.NavWindowingToggleLayer = false;\n    }\n\n    // Move window\n    if (g.NavWindowingTarget && !(g.NavWindowingTarget->Flags & ImGuiWindowFlags_NoMove))\n    {\n        ImVec2 nav_move_dir;\n        if (g.NavInputSource == ImGuiInputSource_Keyboard && !io.KeyShift)\n            nav_move_dir = GetKeyMagnitude2d(ImGuiKey_LeftArrow, ImGuiKey_RightArrow, ImGuiKey_UpArrow, ImGuiKey_DownArrow);\n        if (g.NavInputSource == ImGuiInputSource_Gamepad)\n            nav_move_dir = GetKeyMagnitude2d(ImGuiKey_GamepadLStickLeft, ImGuiKey_GamepadLStickRight, ImGuiKey_GamepadLStickUp, ImGuiKey_GamepadLStickDown);\n        if (nav_move_dir.x != 0.0f || nav_move_dir.y != 0.0f)\n        {\n            const float NAV_MOVE_SPEED = 800.0f;\n            const float move_step = NAV_MOVE_SPEED * io.DeltaTime * ImMin(io.DisplayFramebufferScale.x, io.DisplayFramebufferScale.y);\n            g.NavWindowingAccumDeltaPos += nav_move_dir * move_step;\n            g.NavHighlightItemUnderNav = true;\n            ImVec2 accum_floored = ImTrunc(g.NavWindowingAccumDeltaPos);\n            if (accum_floored.x != 0.0f || accum_floored.y != 0.0f)\n            {\n                ImGuiWindow* moving_window = g.NavWindowingTarget->RootWindow;\n                SetWindowPos(moving_window, moving_window->Pos + accum_floored, ImGuiCond_Always);\n                g.NavWindowingAccumDeltaPos -= accum_floored;\n            }\n        }\n    }\n\n    // Apply final focus\n    if (apply_focus_window)\n        NavUpdateWindowingApplyFocus(apply_focus_window);\n\n    // Apply menu/layer toggle\n    if (apply_toggle_layer && g.NavWindow)\n    {\n        ClearActiveID();\n\n        // Move to parent menu if necessary\n        ImGuiWindow* new_nav_window = g.NavWindow;\n        while (new_nav_window->ParentWindow\n            && (new_nav_window->DC.NavLayersActiveMask & (1 << ImGuiNavLayer_Menu)) == 0\n            && (new_nav_window->Flags & ImGuiWindowFlags_ChildWindow) != 0\n            && (new_nav_window->Flags & (ImGuiWindowFlags_Popup | ImGuiWindowFlags_ChildMenu)) == 0)\n            new_nav_window = new_nav_window->ParentWindow;\n        if (new_nav_window != g.NavWindow)\n        {\n            ImGuiWindow* old_nav_window = g.NavWindow;\n            FocusWindow(new_nav_window);\n            new_nav_window->NavLastChildNavWindow = old_nav_window;\n        }\n\n        // Toggle layer\n        const ImGuiNavLayer new_nav_layer = (g.NavWindow->DC.NavLayersActiveMask & (1 << ImGuiNavLayer_Menu)) ? (ImGuiNavLayer)((int)g.NavLayer ^ 1) : ImGuiNavLayer_Main;\n        if (new_nav_layer != g.NavLayer)\n        {\n            // Reinitialize navigation when entering menu bar with the Alt key (FIXME: could be a properly of the layer?)\n            if (new_nav_layer == ImGuiNavLayer_Menu)\n                g.NavWindow->NavLastIds[new_nav_layer] = 0;\n            NavRestoreLayer(new_nav_layer);\n            SetNavCursorVisibleAfterMove();\n        }\n    }\n}\n\n// Window has already passed the IsWindowNavFocusable()\nstatic const char* GetFallbackWindowNameForWindowingList(ImGuiWindow* window)\n{\n    if (window->Flags & ImGuiWindowFlags_Popup)\n        return ImGui::LocalizeGetMsg(ImGuiLocKey_WindowingPopup);\n    if ((window->Flags & ImGuiWindowFlags_MenuBar) && strcmp(window->Name, \"##MainMenuBar\") == 0)\n        return ImGui::LocalizeGetMsg(ImGuiLocKey_WindowingMainMenuBar);\n    return ImGui::LocalizeGetMsg(ImGuiLocKey_WindowingUntitled);\n}\n\n// Overlay displayed when using CTRL+TAB. Called by EndFrame().\nvoid ImGui::NavUpdateWindowingOverlay()\n{\n    ImGuiContext& g = *GImGui;\n    IM_ASSERT(g.NavWindowingTarget != NULL);\n\n    if (g.NavWindowingTimer < NAV_WINDOWING_LIST_APPEAR_DELAY)\n        return;\n\n    if (g.NavWindowingListWindow == NULL)\n        g.NavWindowingListWindow = FindWindowByName(\"##NavWindowingOverlay\");\n    const ImGuiViewport* viewport = GetMainViewport();\n    SetNextWindowSizeConstraints(ImVec2(viewport->Size.x * 0.20f, viewport->Size.y * 0.20f), ImVec2(FLT_MAX, FLT_MAX));\n    SetNextWindowPos(viewport->GetCenter(), ImGuiCond_Always, ImVec2(0.5f, 0.5f));\n    PushStyleVar(ImGuiStyleVar_WindowPadding, g.Style.WindowPadding * 2.0f);\n    Begin(\"##NavWindowingOverlay\", NULL, ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoFocusOnAppearing | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoInputs | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoSavedSettings);\n    if (g.ContextName[0] != 0)\n        SeparatorText(g.ContextName);\n    for (int n = g.WindowsFocusOrder.Size - 1; n >= 0; n--)\n    {\n        ImGuiWindow* window = g.WindowsFocusOrder[n];\n        IM_ASSERT(window != NULL); // Fix static analyzers\n        if (!IsWindowNavFocusable(window))\n            continue;\n        const char* label = window->Name;\n        if (label == FindRenderedTextEnd(label))\n            label = GetFallbackWindowNameForWindowingList(window);\n        Selectable(label, g.NavWindowingTarget == window);\n    }\n    End();\n    PopStyleVar();\n}\n\n//-----------------------------------------------------------------------------\n// [SECTION] DRAG AND DROP\n//-----------------------------------------------------------------------------\n\nbool ImGui::IsDragDropActive()\n{\n    ImGuiContext& g = *GImGui;\n    return g.DragDropActive;\n}\n\nvoid ImGui::ClearDragDrop()\n{\n    ImGuiContext& g = *GImGui;\n    if (g.DragDropActive)\n        IMGUI_DEBUG_LOG_ACTIVEID(\"[dragdrop] ClearDragDrop()\\n\");\n    g.DragDropActive = false;\n    g.DragDropPayload.Clear();\n    g.DragDropAcceptFlags = ImGuiDragDropFlags_None;\n    g.DragDropAcceptIdCurr = g.DragDropAcceptIdPrev = 0;\n    g.DragDropAcceptIdCurrRectSurface = FLT_MAX;\n    g.DragDropAcceptFrameCount = -1;\n\n    g.DragDropPayloadBufHeap.clear();\n    memset(&g.DragDropPayloadBufLocal, 0, sizeof(g.DragDropPayloadBufLocal));\n}\n\nbool ImGui::BeginTooltipHidden()\n{\n    ImGuiContext& g = *GImGui;\n    bool ret = Begin(\"##Tooltip_Hidden\", NULL, ImGuiWindowFlags_Tooltip | ImGuiWindowFlags_NoInputs | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_AlwaysAutoResize);\n    SetWindowHiddenAndSkipItemsForCurrentFrame(g.CurrentWindow);\n    return ret;\n}\n\n// When this returns true you need to: a) call SetDragDropPayload() exactly once, b) you may render the payload visual/description, c) call EndDragDropSource()\n// If the item has an identifier:\n// - This assume/require the item to be activated (typically via ButtonBehavior).\n// - Therefore if you want to use this with a mouse button other than left mouse button, it is up to the item itself to activate with another button.\n// - We then pull and use the mouse button that was used to activate the item and use it to carry on the drag.\n// If the item has no identifier:\n// - Currently always assume left mouse button.\nbool ImGui::BeginDragDropSource(ImGuiDragDropFlags flags)\n{\n    ImGuiContext& g = *GImGui;\n    ImGuiWindow* window = g.CurrentWindow;\n\n    // FIXME-DRAGDROP: While in the common-most \"drag from non-zero active id\" case we can tell the mouse button,\n    // in both SourceExtern and id==0 cases we may requires something else (explicit flags or some heuristic).\n    ImGuiMouseButton mouse_button = ImGuiMouseButton_Left;\n\n    bool source_drag_active = false;\n    ImGuiID source_id = 0;\n    ImGuiID source_parent_id = 0;\n    if ((flags & ImGuiDragDropFlags_SourceExtern) == 0)\n    {\n        source_id = g.LastItemData.ID;\n        if (source_id != 0)\n        {\n            // Common path: items with ID\n            if (g.ActiveId != source_id)\n                return false;\n            if (g.ActiveIdMouseButton != -1)\n                mouse_button = g.ActiveIdMouseButton;\n            if (g.IO.MouseDown[mouse_button] == false || window->SkipItems)\n                return false;\n            g.ActiveIdAllowOverlap = false;\n        }\n        else\n        {\n            // Uncommon path: items without ID\n            if (g.IO.MouseDown[mouse_button] == false || window->SkipItems)\n                return false;\n            if ((g.LastItemData.StatusFlags & ImGuiItemStatusFlags_HoveredRect) == 0 && (g.ActiveId == 0 || g.ActiveIdWindow != window))\n                return false;\n\n            // If you want to use BeginDragDropSource() on an item with no unique identifier for interaction, such as Text() or Image(), you need to:\n            // A) Read the explanation below, B) Use the ImGuiDragDropFlags_SourceAllowNullID flag.\n            if (!(flags & ImGuiDragDropFlags_SourceAllowNullID))\n            {\n                IM_ASSERT(0);\n                return false;\n            }\n\n            // Magic fallback to handle items with no assigned ID, e.g. Text(), Image()\n            // We build a throwaway ID based on current ID stack + relative AABB of items in window.\n            // THE IDENTIFIER WON'T SURVIVE ANY REPOSITIONING/RESIZINGG OF THE WIDGET, so if your widget moves your dragging operation will be canceled.\n            // We don't need to maintain/call ClearActiveID() as releasing the button will early out this function and trigger !ActiveIdIsAlive.\n            // Rely on keeping other window->LastItemXXX fields intact.\n            source_id = g.LastItemData.ID = window->GetIDFromRectangle(g.LastItemData.Rect);\n            KeepAliveID(source_id);\n            bool is_hovered = ItemHoverable(g.LastItemData.Rect, source_id, g.LastItemData.ItemFlags);\n            if (is_hovered && g.IO.MouseClicked[mouse_button])\n            {\n                SetActiveID(source_id, window);\n                FocusWindow(window);\n            }\n            if (g.ActiveId == source_id) // Allow the underlying widget to display/return hovered during the mouse release frame, else we would get a flicker.\n                g.ActiveIdAllowOverlap = is_hovered;\n        }\n        if (g.ActiveId != source_id)\n            return false;\n        source_parent_id = window->IDStack.back();\n        source_drag_active = IsMouseDragging(mouse_button);\n\n        // Disable navigation and key inputs while dragging + cancel existing request if any\n        SetActiveIdUsingAllKeyboardKeys();\n    }\n    else\n    {\n        // When ImGuiDragDropFlags_SourceExtern is set:\n        window = NULL;\n        source_id = ImHashStr(\"#SourceExtern\");\n        source_drag_active = true;\n        mouse_button = g.IO.MouseDown[0] ? 0 : -1;\n        KeepAliveID(source_id);\n        SetActiveID(source_id, NULL);\n    }\n\n    IM_ASSERT(g.DragDropWithinTarget == false); // Can't nest BeginDragDropSource() and BeginDragDropTarget()\n    if (!source_drag_active)\n        return false;\n\n    // Activate drag and drop\n    if (!g.DragDropActive)\n    {\n        IM_ASSERT(source_id != 0);\n        ClearDragDrop();\n        IMGUI_DEBUG_LOG_ACTIVEID(\"[dragdrop] BeginDragDropSource() DragDropActive = true, source_id = 0x%08X%s\\n\",\n            source_id, (flags & ImGuiDragDropFlags_SourceExtern) ? \" (EXTERN)\" : \"\");\n        ImGuiPayload& payload = g.DragDropPayload;\n        payload.SourceId = source_id;\n        payload.SourceParentId = source_parent_id;\n        g.DragDropActive = true;\n        g.DragDropSourceFlags = flags;\n        g.DragDropMouseButton = mouse_button;\n        if (payload.SourceId == g.ActiveId)\n            g.ActiveIdNoClearOnFocusLoss = true;\n    }\n    g.DragDropSourceFrameCount = g.FrameCount;\n    g.DragDropWithinSource = true;\n\n    if (!(flags & ImGuiDragDropFlags_SourceNoPreviewTooltip))\n    {\n        // Target can request the Source to not display its tooltip (we use a dedicated flag to make this request explicit)\n        // We unfortunately can't just modify the source flags and skip the call to BeginTooltip, as caller may be emitting contents.\n        bool ret;\n        if (g.DragDropAcceptIdPrev && (g.DragDropAcceptFlags & ImGuiDragDropFlags_AcceptNoPreviewTooltip))\n            ret = BeginTooltipHidden();\n        else\n            ret = BeginTooltip();\n        IM_ASSERT(ret); // FIXME-NEWBEGIN: If this ever becomes false, we need to Begin(\"##Hidden\", NULL, ImGuiWindowFlags_NoSavedSettings) + SetWindowHiddendAndSkipItemsForCurrentFrame().\n        IM_UNUSED(ret);\n    }\n\n    if (!(flags & ImGuiDragDropFlags_SourceNoDisableHover) && !(flags & ImGuiDragDropFlags_SourceExtern))\n        g.LastItemData.StatusFlags &= ~ImGuiItemStatusFlags_HoveredRect;\n\n    return true;\n}\n\nvoid ImGui::EndDragDropSource()\n{\n    ImGuiContext& g = *GImGui;\n    IM_ASSERT(g.DragDropActive);\n    IM_ASSERT(g.DragDropWithinSource && \"Not after a BeginDragDropSource()?\");\n\n    if (!(g.DragDropSourceFlags & ImGuiDragDropFlags_SourceNoPreviewTooltip))\n        EndTooltip();\n\n    // Discard the drag if have not called SetDragDropPayload()\n    if (g.DragDropPayload.DataFrameCount == -1)\n        ClearDragDrop();\n    g.DragDropWithinSource = false;\n}\n\n// Use 'cond' to choose to submit payload on drag start or every frame\nbool ImGui::SetDragDropPayload(const char* type, const void* data, size_t data_size, ImGuiCond cond)\n{\n    ImGuiContext& g = *GImGui;\n    ImGuiPayload& payload = g.DragDropPayload;\n    if (cond == 0)\n        cond = ImGuiCond_Always;\n\n    IM_ASSERT(type != NULL);\n    IM_ASSERT(ImStrlen(type) < IM_ARRAYSIZE(payload.DataType) && \"Payload type can be at most 32 characters long\");\n    IM_ASSERT((data != NULL && data_size > 0) || (data == NULL && data_size == 0));\n    IM_ASSERT(cond == ImGuiCond_Always || cond == ImGuiCond_Once);\n    IM_ASSERT(payload.SourceId != 0); // Not called between BeginDragDropSource() and EndDragDropSource()\n\n    if (cond == ImGuiCond_Always || payload.DataFrameCount == -1)\n    {\n        // Copy payload\n        ImStrncpy(payload.DataType, type, IM_ARRAYSIZE(payload.DataType));\n        g.DragDropPayloadBufHeap.resize(0);\n        if (data_size > sizeof(g.DragDropPayloadBufLocal))\n        {\n            // Store in heap\n            g.DragDropPayloadBufHeap.resize((int)data_size);\n            payload.Data = g.DragDropPayloadBufHeap.Data;\n            memcpy(payload.Data, data, data_size);\n        }\n        else if (data_size > 0)\n        {\n            // Store locally\n            memset(&g.DragDropPayloadBufLocal, 0, sizeof(g.DragDropPayloadBufLocal));\n            payload.Data = g.DragDropPayloadBufLocal;\n            memcpy(payload.Data, data, data_size);\n        }\n        else\n        {\n            payload.Data = NULL;\n        }\n        payload.DataSize = (int)data_size;\n    }\n    payload.DataFrameCount = g.FrameCount;\n\n    // Return whether the payload has been accepted\n    return (g.DragDropAcceptFrameCount == g.FrameCount) || (g.DragDropAcceptFrameCount == g.FrameCount - 1);\n}\n\nbool ImGui::BeginDragDropTargetCustom(const ImRect& bb, ImGuiID id)\n{\n    ImGuiContext& g = *GImGui;\n    if (!g.DragDropActive)\n        return false;\n\n    ImGuiWindow* window = g.CurrentWindow;\n    ImGuiWindow* hovered_window = g.HoveredWindowUnderMovingWindow;\n    if (hovered_window == NULL || window->RootWindow != hovered_window->RootWindow)\n        return false;\n    IM_ASSERT(id != 0);\n    if (!IsMouseHoveringRect(bb.Min, bb.Max) || (id == g.DragDropPayload.SourceId))\n        return false;\n    if (window->SkipItems)\n        return false;\n\n    IM_ASSERT(g.DragDropWithinTarget == false && g.DragDropWithinSource == false); // Can't nest BeginDragDropSource() and BeginDragDropTarget()\n    g.DragDropTargetRect = bb;\n    g.DragDropTargetClipRect = window->ClipRect; // May want to be overridden by user depending on use case?\n    g.DragDropTargetId = id;\n    g.DragDropWithinTarget = true;\n    return true;\n}\n\n// We don't use BeginDragDropTargetCustom() and duplicate its code because:\n// 1) we use LastItemData's ImGuiItemStatusFlags_HoveredRect which handles items that push a temporarily clip rectangle in their code. Calling BeginDragDropTargetCustom(LastItemRect) would not handle them.\n// 2) and it's faster. as this code may be very frequently called, we want to early out as fast as we can.\n// Also note how the HoveredWindow test is positioned differently in both functions (in both functions we optimize for the cheapest early out case)\nbool ImGui::BeginDragDropTarget()\n{\n    ImGuiContext& g = *GImGui;\n    if (!g.DragDropActive)\n        return false;\n\n    ImGuiWindow* window = g.CurrentWindow;\n    if (!(g.LastItemData.StatusFlags & ImGuiItemStatusFlags_HoveredRect))\n        return false;\n    ImGuiWindow* hovered_window = g.HoveredWindowUnderMovingWindow;\n    if (hovered_window == NULL || window->RootWindow != hovered_window->RootWindow || window->SkipItems)\n        return false;\n\n    const ImRect& display_rect = (g.LastItemData.StatusFlags & ImGuiItemStatusFlags_HasDisplayRect) ? g.LastItemData.DisplayRect : g.LastItemData.Rect;\n    ImGuiID id = g.LastItemData.ID;\n    if (id == 0)\n    {\n        id = window->GetIDFromRectangle(display_rect);\n        KeepAliveID(id);\n    }\n    if (g.DragDropPayload.SourceId == id)\n        return false;\n\n    IM_ASSERT(g.DragDropWithinTarget == false && g.DragDropWithinSource == false); // Can't nest BeginDragDropSource() and BeginDragDropTarget()\n    g.DragDropTargetRect = display_rect;\n    g.DragDropTargetClipRect = (g.LastItemData.StatusFlags & ImGuiItemStatusFlags_HasClipRect) ? g.LastItemData.ClipRect : window->ClipRect;\n    g.DragDropTargetId = id;\n    g.DragDropWithinTarget = true;\n    return true;\n}\n\nbool ImGui::IsDragDropPayloadBeingAccepted()\n{\n    ImGuiContext& g = *GImGui;\n    return g.DragDropActive && g.DragDropAcceptIdPrev != 0;\n}\n\nconst ImGuiPayload* ImGui::AcceptDragDropPayload(const char* type, ImGuiDragDropFlags flags)\n{\n    ImGuiContext& g = *GImGui;\n    ImGuiPayload& payload = g.DragDropPayload;\n    IM_ASSERT(g.DragDropActive);                        // Not called between BeginDragDropTarget() and EndDragDropTarget() ?\n    IM_ASSERT(payload.DataFrameCount != -1);            // Forgot to call EndDragDropTarget() ?\n    if (type != NULL && !payload.IsDataType(type))\n        return NULL;\n\n    // Accept smallest drag target bounding box, this allows us to nest drag targets conveniently without ordering constraints.\n    // NB: We currently accept NULL id as target. However, overlapping targets requires a unique ID to function!\n    const bool was_accepted_previously = (g.DragDropAcceptIdPrev == g.DragDropTargetId);\n    ImRect r = g.DragDropTargetRect;\n    float r_surface = r.GetWidth() * r.GetHeight();\n    if (r_surface > g.DragDropAcceptIdCurrRectSurface)\n        return NULL;\n\n    g.DragDropAcceptFlags = flags;\n    g.DragDropAcceptIdCurr = g.DragDropTargetId;\n    g.DragDropAcceptIdCurrRectSurface = r_surface;\n    //IMGUI_DEBUG_LOG(\"AcceptDragDropPayload(): %08X: accept\\n\", g.DragDropTargetId);\n\n    // Render default drop visuals\n    payload.Preview = was_accepted_previously;\n    flags |= (g.DragDropSourceFlags & ImGuiDragDropFlags_AcceptNoDrawDefaultRect); // Source can also inhibit the preview (useful for external sources that live for 1 frame)\n    if (!(flags & ImGuiDragDropFlags_AcceptNoDrawDefaultRect) && payload.Preview)\n        RenderDragDropTargetRect(r, g.DragDropTargetClipRect);\n\n    g.DragDropAcceptFrameCount = g.FrameCount;\n    if ((g.DragDropSourceFlags & ImGuiDragDropFlags_SourceExtern) && g.DragDropMouseButton == -1)\n        payload.Delivery = was_accepted_previously && (g.DragDropSourceFrameCount < g.FrameCount);\n    else\n        payload.Delivery = was_accepted_previously && !IsMouseDown(g.DragDropMouseButton); // For extern drag sources affecting OS window focus, it's easier to just test !IsMouseDown() instead of IsMouseReleased()\n    if (!payload.Delivery && !(flags & ImGuiDragDropFlags_AcceptBeforeDelivery))\n        return NULL;\n\n    if (payload.Delivery)\n        IMGUI_DEBUG_LOG_ACTIVEID(\"[dragdrop] AcceptDragDropPayload(): 0x%08X: payload delivery\\n\", g.DragDropTargetId);\n    return &payload;\n}\n\n// FIXME-STYLE FIXME-DRAGDROP: Settle on a proper default visuals for drop target.\nvoid ImGui::RenderDragDropTargetRect(const ImRect& bb, const ImRect& item_clip_rect)\n{\n    ImGuiContext& g = *GImGui;\n    ImGuiWindow* window = g.CurrentWindow;\n    ImRect bb_display = bb;\n    bb_display.ClipWith(item_clip_rect); // Clip THEN expand so we have a way to visualize that target is not entirely visible.\n    bb_display.Expand(3.5f);\n    bool push_clip_rect = !window->ClipRect.Contains(bb_display);\n    if (push_clip_rect)\n        window->DrawList->PushClipRectFullScreen();\n    window->DrawList->AddRect(bb_display.Min, bb_display.Max, GetColorU32(ImGuiCol_DragDropTarget), 0.0f, 0, 2.0f);\n    if (push_clip_rect)\n        window->DrawList->PopClipRect();\n}\n\nconst ImGuiPayload* ImGui::GetDragDropPayload()\n{\n    ImGuiContext& g = *GImGui;\n    return (g.DragDropActive && g.DragDropPayload.DataFrameCount != -1) ? &g.DragDropPayload : NULL;\n}\n\nvoid ImGui::EndDragDropTarget()\n{\n    ImGuiContext& g = *GImGui;\n    IM_ASSERT(g.DragDropActive);\n    IM_ASSERT(g.DragDropWithinTarget);\n    g.DragDropWithinTarget = false;\n\n    // Clear drag and drop state payload right after delivery\n    if (g.DragDropPayload.Delivery)\n        ClearDragDrop();\n}\n\n//-----------------------------------------------------------------------------\n// [SECTION] LOGGING/CAPTURING\n//-----------------------------------------------------------------------------\n// All text output from the interface can be captured into tty/file/clipboard.\n// By default, tree nodes are automatically opened during logging.\n//-----------------------------------------------------------------------------\n\n// Pass text data straight to log (without being displayed)\nstatic inline void LogTextV(ImGuiContext& g, const char* fmt, va_list args)\n{\n    if (g.LogFile)\n    {\n        g.LogBuffer.Buf.resize(0);\n        g.LogBuffer.appendfv(fmt, args);\n        ImFileWrite(g.LogBuffer.c_str(), sizeof(char), (ImU64)g.LogBuffer.size(), g.LogFile);\n    }\n    else\n    {\n        g.LogBuffer.appendfv(fmt, args);\n    }\n}\n\nvoid ImGui::LogText(const char* fmt, ...)\n{\n    ImGuiContext& g = *GImGui;\n    if (!g.LogEnabled)\n        return;\n\n    va_list args;\n    va_start(args, fmt);\n    LogTextV(g, fmt, args);\n    va_end(args);\n}\n\nvoid ImGui::LogTextV(const char* fmt, va_list args)\n{\n    ImGuiContext& g = *GImGui;\n    if (!g.LogEnabled)\n        return;\n\n    LogTextV(g, fmt, args);\n}\n\n// Internal version that takes a position to decide on newline placement and pad items according to their depth.\n// We split text into individual lines to add current tree level padding\n// FIXME: This code is a little complicated perhaps, considering simplifying the whole system.\nvoid ImGui::LogRenderedText(const ImVec2* ref_pos, const char* text, const char* text_end)\n{\n    ImGuiContext& g = *GImGui;\n    ImGuiWindow* window = g.CurrentWindow;\n\n    const char* prefix = g.LogNextPrefix;\n    const char* suffix = g.LogNextSuffix;\n    g.LogNextPrefix = g.LogNextSuffix = NULL;\n\n    if (!text_end)\n        text_end = FindRenderedTextEnd(text, text_end);\n\n    const bool log_new_line = ref_pos && (ref_pos->y > g.LogLinePosY + g.Style.FramePadding.y + 1);\n    if (ref_pos)\n        g.LogLinePosY = ref_pos->y;\n    if (log_new_line)\n    {\n        LogText(IM_NEWLINE);\n        g.LogLineFirstItem = true;\n    }\n\n    if (prefix)\n        LogRenderedText(ref_pos, prefix, prefix + ImStrlen(prefix)); // Calculate end ourself to ensure \"##\" are included here.\n\n    // Re-adjust padding if we have popped out of our starting depth\n    if (g.LogDepthRef > window->DC.TreeDepth)\n        g.LogDepthRef = window->DC.TreeDepth;\n    const int tree_depth = (window->DC.TreeDepth - g.LogDepthRef);\n\n    const char* text_remaining = text;\n    for (;;)\n    {\n        // Split the string. Each new line (after a '\\n') is followed by indentation corresponding to the current depth of our log entry.\n        // We don't add a trailing \\n yet to allow a subsequent item on the same line to be captured.\n        const char* line_start = text_remaining;\n        const char* line_end = ImStreolRange(line_start, text_end);\n        const bool is_last_line = (line_end == text_end);\n        if (line_start != line_end || !is_last_line)\n        {\n            const int line_length = (int)(line_end - line_start);\n            const int indentation = g.LogLineFirstItem ? tree_depth * 4 : 1;\n            LogText(\"%*s%.*s\", indentation, \"\", line_length, line_start);\n            g.LogLineFirstItem = false;\n            if (*line_end == '\\n')\n            {\n                LogText(IM_NEWLINE);\n                g.LogLineFirstItem = true;\n            }\n        }\n        if (is_last_line)\n            break;\n        text_remaining = line_end + 1;\n    }\n\n    if (suffix)\n        LogRenderedText(ref_pos, suffix, suffix + ImStrlen(suffix));\n}\n\n// Start logging/capturing text output\nvoid ImGui::LogBegin(ImGuiLogFlags flags, int auto_open_depth)\n{\n    ImGuiContext& g = *GImGui;\n    ImGuiWindow* window = g.CurrentWindow;\n    IM_ASSERT(g.LogEnabled == false);\n    IM_ASSERT(g.LogFile == NULL && g.LogBuffer.empty());\n    IM_ASSERT(ImIsPowerOfTwo(flags & ImGuiLogFlags_OutputMask_)); // Check that only 1 type flag is used\n\n    g.LogEnabled = g.ItemUnclipByLog = true;\n    g.LogFlags = flags;\n    g.LogWindow = window;\n    g.LogNextPrefix = g.LogNextSuffix = NULL;\n    g.LogDepthRef = window->DC.TreeDepth;\n    g.LogDepthToExpand = ((auto_open_depth >= 0) ? auto_open_depth : g.LogDepthToExpandDefault);\n    g.LogLinePosY = FLT_MAX;\n    g.LogLineFirstItem = true;\n}\n\n// Important: doesn't copy underlying data, use carefully (prefix/suffix must be in scope at the time of the next LogRenderedText)\nvoid ImGui::LogSetNextTextDecoration(const char* prefix, const char* suffix)\n{\n    ImGuiContext& g = *GImGui;\n    g.LogNextPrefix = prefix;\n    g.LogNextSuffix = suffix;\n}\n\nvoid ImGui::LogToTTY(int auto_open_depth)\n{\n    ImGuiContext& g = *GImGui;\n    if (g.LogEnabled)\n        return;\n    IM_UNUSED(auto_open_depth);\n#ifndef IMGUI_DISABLE_TTY_FUNCTIONS\n    LogBegin(ImGuiLogFlags_OutputTTY, auto_open_depth);\n    g.LogFile = stdout;\n#endif\n}\n\n// Start logging/capturing text output to given file\nvoid ImGui::LogToFile(int auto_open_depth, const char* filename)\n{\n    ImGuiContext& g = *GImGui;\n    if (g.LogEnabled)\n        return;\n\n    // FIXME: We could probably open the file in text mode \"at\", however note that clipboard/buffer logging will still\n    // be subject to outputting OS-incompatible carriage return if within strings the user doesn't use IM_NEWLINE.\n    // By opening the file in binary mode \"ab\" we have consistent output everywhere.\n    if (!filename)\n        filename = g.IO.LogFilename;\n    if (!filename || !filename[0])\n        return;\n    ImFileHandle f = ImFileOpen(filename, \"ab\");\n    if (!f)\n    {\n        IM_ASSERT(0);\n        return;\n    }\n\n    LogBegin(ImGuiLogFlags_OutputFile, auto_open_depth);\n    g.LogFile = f;\n}\n\n// Start logging/capturing text output to clipboard\nvoid ImGui::LogToClipboard(int auto_open_depth)\n{\n    ImGuiContext& g = *GImGui;\n    if (g.LogEnabled)\n        return;\n    LogBegin(ImGuiLogFlags_OutputClipboard, auto_open_depth);\n}\n\nvoid ImGui::LogToBuffer(int auto_open_depth)\n{\n    ImGuiContext& g = *GImGui;\n    if (g.LogEnabled)\n        return;\n    LogBegin(ImGuiLogFlags_OutputBuffer, auto_open_depth);\n}\n\nvoid ImGui::LogFinish()\n{\n    ImGuiContext& g = *GImGui;\n    if (!g.LogEnabled)\n        return;\n\n    LogText(IM_NEWLINE);\n    switch (g.LogFlags & ImGuiLogFlags_OutputMask_)\n    {\n    case ImGuiLogFlags_OutputTTY:\n#ifndef IMGUI_DISABLE_TTY_FUNCTIONS\n        fflush(g.LogFile);\n#endif\n        break;\n    case ImGuiLogFlags_OutputFile:\n        ImFileClose(g.LogFile);\n        break;\n    case ImGuiLogFlags_OutputBuffer:\n        break;\n    case ImGuiLogFlags_OutputClipboard:\n        if (!g.LogBuffer.empty())\n            SetClipboardText(g.LogBuffer.begin());\n        break;\n    default:\n        IM_ASSERT(0);\n        break;\n    }\n\n    g.LogEnabled = g.ItemUnclipByLog = false;\n    g.LogFlags = ImGuiLogFlags_None;\n    g.LogFile = NULL;\n    g.LogBuffer.clear();\n}\n\n// Helper to display logging buttons\n// FIXME-OBSOLETE: We should probably obsolete this and let the user have their own helper (this is one of the oldest function alive!)\nvoid ImGui::LogButtons()\n{\n    ImGuiContext& g = *GImGui;\n\n    PushID(\"LogButtons\");\n#ifndef IMGUI_DISABLE_TTY_FUNCTIONS\n    const bool log_to_tty = Button(\"Log To TTY\"); SameLine();\n#else\n    const bool log_to_tty = false;\n#endif\n    const bool log_to_file = Button(\"Log To File\"); SameLine();\n    const bool log_to_clipboard = Button(\"Log To Clipboard\"); SameLine();\n    PushItemFlag(ImGuiItemFlags_NoTabStop, true);\n    SetNextItemWidth(80.0f);\n    SliderInt(\"Default Depth\", &g.LogDepthToExpandDefault, 0, 9, NULL);\n    PopItemFlag();\n    PopID();\n\n    // Start logging at the end of the function so that the buttons don't appear in the log\n    if (log_to_tty)\n        LogToTTY();\n    if (log_to_file)\n        LogToFile();\n    if (log_to_clipboard)\n        LogToClipboard();\n}\n\n//-----------------------------------------------------------------------------\n// [SECTION] SETTINGS\n//-----------------------------------------------------------------------------\n// - UpdateSettings() [Internal]\n// - MarkIniSettingsDirty() [Internal]\n// - FindSettingsHandler() [Internal]\n// - ClearIniSettings() [Internal]\n// - LoadIniSettingsFromDisk()\n// - LoadIniSettingsFromMemory()\n// - SaveIniSettingsToDisk()\n// - SaveIniSettingsToMemory()\n//-----------------------------------------------------------------------------\n// - CreateNewWindowSettings() [Internal]\n// - FindWindowSettingsByID() [Internal]\n// - FindWindowSettingsByWindow() [Internal]\n// - ClearWindowSettings() [Internal]\n// - WindowSettingsHandler_***() [Internal]\n//-----------------------------------------------------------------------------\n\n// Called by NewFrame()\nvoid ImGui::UpdateSettings()\n{\n    // Load settings on first frame (if not explicitly loaded manually before)\n    ImGuiContext& g = *GImGui;\n    if (!g.SettingsLoaded)\n    {\n        IM_ASSERT(g.SettingsWindows.empty());\n        if (g.IO.IniFilename)\n            LoadIniSettingsFromDisk(g.IO.IniFilename);\n        g.SettingsLoaded = true;\n    }\n\n    // Save settings (with a delay after the last modification, so we don't spam disk too much)\n    if (g.SettingsDirtyTimer > 0.0f)\n    {\n        g.SettingsDirtyTimer -= g.IO.DeltaTime;\n        if (g.SettingsDirtyTimer <= 0.0f)\n        {\n            if (g.IO.IniFilename != NULL)\n                SaveIniSettingsToDisk(g.IO.IniFilename);\n            else\n                g.IO.WantSaveIniSettings = true;  // Let user know they can call SaveIniSettingsToMemory(). user will need to clear io.WantSaveIniSettings themselves.\n            g.SettingsDirtyTimer = 0.0f;\n        }\n    }\n}\n\nvoid ImGui::MarkIniSettingsDirty()\n{\n    ImGuiContext& g = *GImGui;\n    if (g.SettingsDirtyTimer <= 0.0f)\n        g.SettingsDirtyTimer = g.IO.IniSavingRate;\n}\n\nvoid ImGui::MarkIniSettingsDirty(ImGuiWindow* window)\n{\n    ImGuiContext& g = *GImGui;\n    if (!(window->Flags & ImGuiWindowFlags_NoSavedSettings))\n        if (g.SettingsDirtyTimer <= 0.0f)\n            g.SettingsDirtyTimer = g.IO.IniSavingRate;\n}\n\nvoid ImGui::AddSettingsHandler(const ImGuiSettingsHandler* handler)\n{\n    ImGuiContext& g = *GImGui;\n    IM_ASSERT(FindSettingsHandler(handler->TypeName) == NULL);\n    g.SettingsHandlers.push_back(*handler);\n}\n\nvoid ImGui::RemoveSettingsHandler(const char* type_name)\n{\n    ImGuiContext& g = *GImGui;\n    if (ImGuiSettingsHandler* handler = FindSettingsHandler(type_name))\n        g.SettingsHandlers.erase(handler);\n}\n\nImGuiSettingsHandler* ImGui::FindSettingsHandler(const char* type_name)\n{\n    ImGuiContext& g = *GImGui;\n    const ImGuiID type_hash = ImHashStr(type_name);\n    for (ImGuiSettingsHandler& handler : g.SettingsHandlers)\n        if (handler.TypeHash == type_hash)\n            return &handler;\n    return NULL;\n}\n\n// Clear all settings (windows, tables, docking etc.)\nvoid ImGui::ClearIniSettings()\n{\n    ImGuiContext& g = *GImGui;\n    g.SettingsIniData.clear();\n    for (ImGuiSettingsHandler& handler : g.SettingsHandlers)\n        if (handler.ClearAllFn != NULL)\n            handler.ClearAllFn(&g, &handler);\n}\n\nvoid ImGui::LoadIniSettingsFromDisk(const char* ini_filename)\n{\n    size_t file_data_size = 0;\n    char* file_data = (char*)ImFileLoadToMemory(ini_filename, \"rb\", &file_data_size);\n    if (!file_data)\n        return;\n    if (file_data_size > 0)\n        LoadIniSettingsFromMemory(file_data, (size_t)file_data_size);\n    IM_FREE(file_data);\n}\n\n// Zero-tolerance, no error reporting, cheap .ini parsing\n// Set ini_size==0 to let us use strlen(ini_data). Do not call this function with a 0 if your buffer is actually empty!\nvoid ImGui::LoadIniSettingsFromMemory(const char* ini_data, size_t ini_size)\n{\n    ImGuiContext& g = *GImGui;\n    IM_ASSERT(g.Initialized);\n    //IM_ASSERT(!g.WithinFrameScope && \"Cannot be called between NewFrame() and EndFrame()\");\n    //IM_ASSERT(g.SettingsLoaded == false && g.FrameCount == 0);\n\n    // For user convenience, we allow passing a non zero-terminated string (hence the ini_size parameter).\n    // For our convenience and to make the code simpler, we'll also write zero-terminators within the buffer. So let's create a writable copy..\n    if (ini_size == 0)\n        ini_size = ImStrlen(ini_data);\n    g.SettingsIniData.Buf.resize((int)ini_size + 1);\n    char* const buf = g.SettingsIniData.Buf.Data;\n    char* const buf_end = buf + ini_size;\n    memcpy(buf, ini_data, ini_size);\n    buf_end[0] = 0;\n\n    // Call pre-read handlers\n    // Some types will clear their data (e.g. dock information) some types will allow merge/override (window)\n    for (ImGuiSettingsHandler& handler : g.SettingsHandlers)\n        if (handler.ReadInitFn != NULL)\n            handler.ReadInitFn(&g, &handler);\n\n    void* entry_data = NULL;\n    ImGuiSettingsHandler* entry_handler = NULL;\n\n    char* line_end = NULL;\n    for (char* line = buf; line < buf_end; line = line_end + 1)\n    {\n        // Skip new lines markers, then find end of the line\n        while (*line == '\\n' || *line == '\\r')\n            line++;\n        line_end = line;\n        while (line_end < buf_end && *line_end != '\\n' && *line_end != '\\r')\n            line_end++;\n        line_end[0] = 0;\n        if (line[0] == ';')\n            continue;\n        if (line[0] == '[' && line_end > line && line_end[-1] == ']')\n        {\n            // Parse \"[Type][Name]\". Note that 'Name' can itself contains [] characters, which is acceptable with the current format and parsing code.\n            line_end[-1] = 0;\n            const char* name_end = line_end - 1;\n            const char* type_start = line + 1;\n            char* type_end = (char*)(void*)ImStrchrRange(type_start, name_end, ']');\n            const char* name_start = type_end ? ImStrchrRange(type_end + 1, name_end, '[') : NULL;\n            if (!type_end || !name_start)\n                continue;\n            *type_end = 0; // Overwrite first ']'\n            name_start++;  // Skip second '['\n            entry_handler = FindSettingsHandler(type_start);\n            entry_data = entry_handler ? entry_handler->ReadOpenFn(&g, entry_handler, name_start) : NULL;\n        }\n        else if (entry_handler != NULL && entry_data != NULL)\n        {\n            // Let type handler parse the line\n            entry_handler->ReadLineFn(&g, entry_handler, entry_data, line);\n        }\n    }\n    g.SettingsLoaded = true;\n\n    // [DEBUG] Restore untouched copy so it can be browsed in Metrics (not strictly necessary)\n    memcpy(buf, ini_data, ini_size);\n\n    // Call post-read handlers\n    for (ImGuiSettingsHandler& handler : g.SettingsHandlers)\n        if (handler.ApplyAllFn != NULL)\n            handler.ApplyAllFn(&g, &handler);\n}\n\nvoid ImGui::SaveIniSettingsToDisk(const char* ini_filename)\n{\n    ImGuiContext& g = *GImGui;\n    g.SettingsDirtyTimer = 0.0f;\n    if (!ini_filename)\n        return;\n\n    size_t ini_data_size = 0;\n    const char* ini_data = SaveIniSettingsToMemory(&ini_data_size);\n    ImFileHandle f = ImFileOpen(ini_filename, \"wt\");\n    if (!f)\n        return;\n    ImFileWrite(ini_data, sizeof(char), ini_data_size, f);\n    ImFileClose(f);\n}\n\n// Call registered handlers (e.g. SettingsHandlerWindow_WriteAll() + custom handlers) to write their stuff into a text buffer\nconst char* ImGui::SaveIniSettingsToMemory(size_t* out_size)\n{\n    ImGuiContext& g = *GImGui;\n    g.SettingsDirtyTimer = 0.0f;\n    g.SettingsIniData.Buf.resize(0);\n    g.SettingsIniData.Buf.push_back(0);\n    for (ImGuiSettingsHandler& handler : g.SettingsHandlers)\n        handler.WriteAllFn(&g, &handler, &g.SettingsIniData);\n    if (out_size)\n        *out_size = (size_t)g.SettingsIniData.size();\n    return g.SettingsIniData.c_str();\n}\n\nImGuiWindowSettings* ImGui::CreateNewWindowSettings(const char* name)\n{\n    ImGuiContext& g = *GImGui;\n\n    if (g.IO.ConfigDebugIniSettings == false)\n    {\n        // Skip to the \"###\" marker if any. We don't skip past to match the behavior of GetID()\n        // Preserve the full string when ConfigDebugVerboseIniSettings is set to make .ini inspection easier.\n        if (const char* p = strstr(name, \"###\"))\n            name = p;\n    }\n    const size_t name_len = ImStrlen(name);\n\n    // Allocate chunk\n    const size_t chunk_size = sizeof(ImGuiWindowSettings) + name_len + 1;\n    ImGuiWindowSettings* settings = g.SettingsWindows.alloc_chunk(chunk_size);\n    IM_PLACEMENT_NEW(settings) ImGuiWindowSettings();\n    settings->ID = ImHashStr(name, name_len);\n    memcpy(settings->GetName(), name, name_len + 1);   // Store with zero terminator\n\n    return settings;\n}\n\n// We don't provide a FindWindowSettingsByName() because Docking system doesn't always hold on names.\n// This is called once per window .ini entry + once per newly instantiated window.\nImGuiWindowSettings* ImGui::FindWindowSettingsByID(ImGuiID id)\n{\n    ImGuiContext& g = *GImGui;\n    for (ImGuiWindowSettings* settings = g.SettingsWindows.begin(); settings != NULL; settings = g.SettingsWindows.next_chunk(settings))\n        if (settings->ID == id && !settings->WantDelete)\n            return settings;\n    return NULL;\n}\n\n// This is faster if you are holding on a Window already as we don't need to perform a search.\nImGuiWindowSettings* ImGui::FindWindowSettingsByWindow(ImGuiWindow* window)\n{\n    ImGuiContext& g = *GImGui;\n    if (window->SettingsOffset != -1)\n        return g.SettingsWindows.ptr_from_offset(window->SettingsOffset);\n    return FindWindowSettingsByID(window->ID);\n}\n\n// This will revert window to its initial state, including enabling the ImGuiCond_FirstUseEver/ImGuiCond_Once conditions once more.\nvoid ImGui::ClearWindowSettings(const char* name)\n{\n    //IMGUI_DEBUG_LOG(\"ClearWindowSettings('%s')\\n\", name);\n    ImGuiWindow* window = FindWindowByName(name);\n    if (window != NULL)\n    {\n        window->Flags |= ImGuiWindowFlags_NoSavedSettings;\n        InitOrLoadWindowSettings(window, NULL);\n    }\n    if (ImGuiWindowSettings* settings = window ? FindWindowSettingsByWindow(window) : FindWindowSettingsByID(ImHashStr(name)))\n        settings->WantDelete = true;\n}\n\nstatic void WindowSettingsHandler_ClearAll(ImGuiContext* ctx, ImGuiSettingsHandler*)\n{\n    ImGuiContext& g = *ctx;\n    for (ImGuiWindow* window : g.Windows)\n        window->SettingsOffset = -1;\n    g.SettingsWindows.clear();\n}\n\nstatic void* WindowSettingsHandler_ReadOpen(ImGuiContext*, ImGuiSettingsHandler*, const char* name)\n{\n    ImGuiID id = ImHashStr(name);\n    ImGuiWindowSettings* settings = ImGui::FindWindowSettingsByID(id);\n    if (settings)\n        *settings = ImGuiWindowSettings(); // Clear existing if recycling previous entry\n    else\n        settings = ImGui::CreateNewWindowSettings(name);\n    settings->ID = id;\n    settings->WantApply = true;\n    return (void*)settings;\n}\n\nstatic void WindowSettingsHandler_ReadLine(ImGuiContext*, ImGuiSettingsHandler*, void* entry, const char* line)\n{\n    ImGuiWindowSettings* settings = (ImGuiWindowSettings*)entry;\n    int x, y;\n    int i;\n    if (sscanf(line, \"Pos=%i,%i\", &x, &y) == 2)         { settings->Pos = ImVec2ih((short)x, (short)y); }\n    else if (sscanf(line, \"Size=%i,%i\", &x, &y) == 2)   { settings->Size = ImVec2ih((short)x, (short)y); }\n    else if (sscanf(line, \"Collapsed=%d\", &i) == 1)     { settings->Collapsed = (i != 0); }\n    else if (sscanf(line, \"IsChild=%d\", &i) == 1)       { settings->IsChild = (i != 0); }\n}\n\n// Apply to existing windows (if any)\nstatic void WindowSettingsHandler_ApplyAll(ImGuiContext* ctx, ImGuiSettingsHandler*)\n{\n    ImGuiContext& g = *ctx;\n    for (ImGuiWindowSettings* settings = g.SettingsWindows.begin(); settings != NULL; settings = g.SettingsWindows.next_chunk(settings))\n        if (settings->WantApply)\n        {\n            if (ImGuiWindow* window = ImGui::FindWindowByID(settings->ID))\n                ApplyWindowSettings(window, settings);\n            settings->WantApply = false;\n        }\n}\n\nstatic void WindowSettingsHandler_WriteAll(ImGuiContext* ctx, ImGuiSettingsHandler* handler, ImGuiTextBuffer* buf)\n{\n    // Gather data from windows that were active during this session\n    // (if a window wasn't opened in this session we preserve its settings)\n    ImGuiContext& g = *ctx;\n    for (ImGuiWindow* window : g.Windows)\n    {\n        if (window->Flags & ImGuiWindowFlags_NoSavedSettings)\n            continue;\n\n        ImGuiWindowSettings* settings = ImGui::FindWindowSettingsByWindow(window);\n        if (!settings)\n        {\n            settings = ImGui::CreateNewWindowSettings(window->Name);\n            window->SettingsOffset = g.SettingsWindows.offset_from_ptr(settings);\n        }\n        IM_ASSERT(settings->ID == window->ID);\n        settings->Pos = ImVec2ih(window->Pos);\n        settings->Size = ImVec2ih(window->SizeFull);\n        settings->IsChild = (window->Flags & ImGuiWindowFlags_ChildWindow) != 0;\n        settings->Collapsed = window->Collapsed;\n        settings->WantDelete = false;\n    }\n\n    // Write to text buffer\n    buf->reserve(buf->size() + g.SettingsWindows.size() * 6); // ballpark reserve\n    for (ImGuiWindowSettings* settings = g.SettingsWindows.begin(); settings != NULL; settings = g.SettingsWindows.next_chunk(settings))\n    {\n        if (settings->WantDelete)\n            continue;\n        const char* settings_name = settings->GetName();\n        buf->appendf(\"[%s][%s]\\n\", handler->TypeName, settings_name);\n        if (settings->IsChild)\n        {\n            buf->appendf(\"IsChild=1\\n\");\n            buf->appendf(\"Size=%d,%d\\n\", settings->Size.x, settings->Size.y);\n        }\n        else\n        {\n            buf->appendf(\"Pos=%d,%d\\n\", settings->Pos.x, settings->Pos.y);\n            buf->appendf(\"Size=%d,%d\\n\", settings->Size.x, settings->Size.y);\n            if (settings->Collapsed)\n                buf->appendf(\"Collapsed=1\\n\");\n        }\n        buf->append(\"\\n\");\n    }\n}\n\n//-----------------------------------------------------------------------------\n// [SECTION] LOCALIZATION\n//-----------------------------------------------------------------------------\n\nvoid ImGui::LocalizeRegisterEntries(const ImGuiLocEntry* entries, int count)\n{\n    ImGuiContext& g = *GImGui;\n    for (int n = 0; n < count; n++)\n        g.LocalizationTable[entries[n].Key] = entries[n].Text;\n}\n\n//-----------------------------------------------------------------------------\n// [SECTION] VIEWPORTS, PLATFORM WINDOWS\n//-----------------------------------------------------------------------------\n// - GetMainViewport()\n// - SetWindowViewport() [Internal]\n// - ScaleWindowsInViewport() [Internal]\n// - UpdateViewportsNewFrame() [Internal]\n// (this section is more complete in the 'docking' branch)\n//-----------------------------------------------------------------------------\n\nImGuiViewport* ImGui::GetMainViewport()\n{\n    ImGuiContext& g = *GImGui;\n    return g.Viewports[0];\n}\n\nvoid ImGui::SetWindowViewport(ImGuiWindow* window, ImGuiViewportP* viewport)\n{\n    window->Viewport = viewport;\n}\n\nstatic void ScaleWindow(ImGuiWindow* window, float scale)\n{\n    ImVec2 origin = window->Viewport->Pos;\n    window->Pos = ImFloor((window->Pos - origin) * scale + origin);\n    window->Size = ImTrunc(window->Size * scale);\n    window->SizeFull = ImTrunc(window->SizeFull * scale);\n    window->ContentSize = ImTrunc(window->ContentSize * scale);\n}\n\n// Scale all windows (position, size). Use when e.g. changing DPI. (This is a lossy operation!)\nvoid ImGui::ScaleWindowsInViewport(ImGuiViewportP* viewport, float scale)\n{\n    ImGuiContext& g = *GImGui;\n    for (ImGuiWindow* window : g.Windows)\n        if (window->Viewport == viewport)\n            ScaleWindow(window, scale);\n}\n\n// Update viewports and monitor infos\nstatic void ImGui::UpdateViewportsNewFrame()\n{\n    ImGuiContext& g = *GImGui;\n    IM_ASSERT(g.Viewports.Size == 1);\n\n    // Update main viewport with current platform position.\n    // FIXME-VIEWPORT: Size is driven by backend/user code for backward-compatibility but we should aim to make this more consistent.\n    ImGuiViewportP* main_viewport = g.Viewports[0];\n    main_viewport->Flags = ImGuiViewportFlags_IsPlatformWindow | ImGuiViewportFlags_OwnedByApp;\n    main_viewport->Pos = ImVec2(0.0f, 0.0f);\n    main_viewport->Size = g.IO.DisplaySize;\n\n    for (ImGuiViewportP* viewport : g.Viewports)\n    {\n        // Lock down space taken by menu bars and status bars\n        // Setup initial value for functions like BeginMainMenuBar(), DockSpaceOverViewport() etc.\n        viewport->WorkInsetMin = viewport->BuildWorkInsetMin;\n        viewport->WorkInsetMax = viewport->BuildWorkInsetMax;\n        viewport->BuildWorkInsetMin = viewport->BuildWorkInsetMax = ImVec2(0.0f, 0.0f);\n        viewport->UpdateWorkRect();\n    }\n}\n\n//-----------------------------------------------------------------------------\n// [SECTION] DOCKING\n//-----------------------------------------------------------------------------\n\n// (this section is filled in the 'docking' branch)\n\n\n//-----------------------------------------------------------------------------\n// [SECTION] PLATFORM DEPENDENT HELPERS\n//-----------------------------------------------------------------------------\n// - Default clipboard handlers\n// - Default shell function handlers\n// - Default IME handlers\n//-----------------------------------------------------------------------------\n\n#if defined(_WIN32) && !defined(IMGUI_DISABLE_WIN32_FUNCTIONS) && !defined(IMGUI_DISABLE_WIN32_DEFAULT_CLIPBOARD_FUNCTIONS)\n\n#ifdef _MSC_VER\n#pragma comment(lib, \"user32\")\n#pragma comment(lib, \"kernel32\")\n#endif\n\n// Win32 clipboard implementation\n// We use g.ClipboardHandlerData for temporary storage to ensure it is freed on Shutdown()\nstatic const char* Platform_GetClipboardTextFn_DefaultImpl(ImGuiContext* ctx)\n{\n    ImGuiContext& g = *ctx;\n    g.ClipboardHandlerData.clear();\n    if (!::OpenClipboard(NULL))\n        return NULL;\n    HANDLE wbuf_handle = ::GetClipboardData(CF_UNICODETEXT);\n    if (wbuf_handle == NULL)\n    {\n        ::CloseClipboard();\n        return NULL;\n    }\n    if (const WCHAR* wbuf_global = (const WCHAR*)::GlobalLock(wbuf_handle))\n    {\n        int buf_len = ::WideCharToMultiByte(CP_UTF8, 0, wbuf_global, -1, NULL, 0, NULL, NULL);\n        g.ClipboardHandlerData.resize(buf_len);\n        ::WideCharToMultiByte(CP_UTF8, 0, wbuf_global, -1, g.ClipboardHandlerData.Data, buf_len, NULL, NULL);\n    }\n    ::GlobalUnlock(wbuf_handle);\n    ::CloseClipboard();\n    return g.ClipboardHandlerData.Data;\n}\n\nstatic void Platform_SetClipboardTextFn_DefaultImpl(ImGuiContext*, const char* text)\n{\n    if (!::OpenClipboard(NULL))\n        return;\n    const int wbuf_length = ::MultiByteToWideChar(CP_UTF8, 0, text, -1, NULL, 0);\n    HGLOBAL wbuf_handle = ::GlobalAlloc(GMEM_MOVEABLE, (SIZE_T)wbuf_length * sizeof(WCHAR));\n    if (wbuf_handle == NULL)\n    {\n        ::CloseClipboard();\n        return;\n    }\n    WCHAR* wbuf_global = (WCHAR*)::GlobalLock(wbuf_handle);\n    ::MultiByteToWideChar(CP_UTF8, 0, text, -1, wbuf_global, wbuf_length);\n    ::GlobalUnlock(wbuf_handle);\n    ::EmptyClipboard();\n    if (::SetClipboardData(CF_UNICODETEXT, wbuf_handle) == NULL)\n        ::GlobalFree(wbuf_handle);\n    ::CloseClipboard();\n}\n\n#elif defined(__APPLE__) && TARGET_OS_OSX && defined(IMGUI_ENABLE_OSX_DEFAULT_CLIPBOARD_FUNCTIONS)\n\n#include <Carbon/Carbon.h>  // Use old API to avoid need for separate .mm file\nstatic PasteboardRef main_clipboard = 0;\n\n// OSX clipboard implementation\n// If you enable this you will need to add '-framework ApplicationServices' to your linker command-line!\nstatic void Platform_SetClipboardTextFn_DefaultImpl(ImGuiContext*, const char* text)\n{\n    if (!main_clipboard)\n        PasteboardCreate(kPasteboardClipboard, &main_clipboard);\n    PasteboardClear(main_clipboard);\n    CFDataRef cf_data = CFDataCreate(kCFAllocatorDefault, (const UInt8*)text, ImStrlen(text));\n    if (cf_data)\n    {\n        PasteboardPutItemFlavor(main_clipboard, (PasteboardItemID)1, CFSTR(\"public.utf8-plain-text\"), cf_data, 0);\n        CFRelease(cf_data);\n    }\n}\n\nstatic const char* Platform_GetClipboardTextFn_DefaultImpl(ImGuiContext* ctx)\n{\n    ImGuiContext& g = *ctx;\n    if (!main_clipboard)\n        PasteboardCreate(kPasteboardClipboard, &main_clipboard);\n    PasteboardSynchronize(main_clipboard);\n\n    ItemCount item_count = 0;\n    PasteboardGetItemCount(main_clipboard, &item_count);\n    for (ItemCount i = 0; i < item_count; i++)\n    {\n        PasteboardItemID item_id = 0;\n        PasteboardGetItemIdentifier(main_clipboard, i + 1, &item_id);\n        CFArrayRef flavor_type_array = 0;\n        PasteboardCopyItemFlavors(main_clipboard, item_id, &flavor_type_array);\n        for (CFIndex j = 0, nj = CFArrayGetCount(flavor_type_array); j < nj; j++)\n        {\n            CFDataRef cf_data;\n            if (PasteboardCopyItemFlavorData(main_clipboard, item_id, CFSTR(\"public.utf8-plain-text\"), &cf_data) == noErr)\n            {\n                g.ClipboardHandlerData.clear();\n                int length = (int)CFDataGetLength(cf_data);\n                g.ClipboardHandlerData.resize(length + 1);\n                CFDataGetBytes(cf_data, CFRangeMake(0, length), (UInt8*)g.ClipboardHandlerData.Data);\n                g.ClipboardHandlerData[length] = 0;\n                CFRelease(cf_data);\n                return g.ClipboardHandlerData.Data;\n            }\n        }\n    }\n    return NULL;\n}\n\n#else\n\n// Local Dear ImGui-only clipboard implementation, if user hasn't defined better clipboard handlers.\nstatic const char* Platform_GetClipboardTextFn_DefaultImpl(ImGuiContext* ctx)\n{\n    ImGuiContext& g = *ctx;\n    return g.ClipboardHandlerData.empty() ? NULL : g.ClipboardHandlerData.begin();\n}\n\nstatic void Platform_SetClipboardTextFn_DefaultImpl(ImGuiContext* ctx, const char* text)\n{\n    ImGuiContext& g = *ctx;\n    g.ClipboardHandlerData.clear();\n    const char* text_end = text + ImStrlen(text);\n    g.ClipboardHandlerData.resize((int)(text_end - text) + 1);\n    memcpy(&g.ClipboardHandlerData[0], text, (size_t)(text_end - text));\n    g.ClipboardHandlerData[(int)(text_end - text)] = 0;\n}\n\n#endif // Default clipboard handlers\n\n//-----------------------------------------------------------------------------\n\n#ifndef IMGUI_DISABLE_DEFAULT_SHELL_FUNCTIONS\n#if defined(__APPLE__) && TARGET_OS_IPHONE\n#define IMGUI_DISABLE_DEFAULT_SHELL_FUNCTIONS\n#endif\n#if defined(__3DS__)\n#define IMGUI_DISABLE_DEFAULT_SHELL_FUNCTIONS\n#endif\n#if defined(_WIN32) && defined(IMGUI_DISABLE_WIN32_FUNCTIONS)\n#define IMGUI_DISABLE_DEFAULT_SHELL_FUNCTIONS\n#endif\n#endif // #ifndef IMGUI_DISABLE_DEFAULT_SHELL_FUNCTIONS\n\n#ifndef IMGUI_DISABLE_DEFAULT_SHELL_FUNCTIONS\n#ifdef _WIN32\n#include <shellapi.h>   // ShellExecuteA()\n#ifdef _MSC_VER\n#pragma comment(lib, \"shell32\")\n#endif\nstatic bool Platform_OpenInShellFn_DefaultImpl(ImGuiContext*, const char* path)\n{\n    const int path_wsize = ::MultiByteToWideChar(CP_UTF8, 0, path, -1, NULL, 0);\n    ImVector<wchar_t> path_wbuf;\n    path_wbuf.resize(path_wsize);\n    ::MultiByteToWideChar(CP_UTF8, 0, path, -1, path_wbuf.Data, path_wsize);\n    return (INT_PTR)::ShellExecuteW(NULL, L\"open\", path_wbuf.Data, NULL, NULL, SW_SHOWDEFAULT) > 32;\n}\n#else\n#include <sys/wait.h>\n#include <unistd.h>\nstatic bool Platform_OpenInShellFn_DefaultImpl(ImGuiContext*, const char* path)\n{\n#if defined(__APPLE__)\n    const char* args[] { \"open\", \"--\", path, NULL };\n#else\n    const char* args[] { \"xdg-open\", path, NULL };\n#endif\n    pid_t pid = fork();\n    if (pid < 0)\n        return false;\n    if (!pid)\n    {\n        execvp(args[0], const_cast<char **>(args));\n        exit(-1);\n    }\n    else\n    {\n        int status;\n        waitpid(pid, &status, 0);\n        return WEXITSTATUS(status) == 0;\n    }\n}\n#endif\n#else\nstatic bool Platform_OpenInShellFn_DefaultImpl(ImGuiContext*, const char*) { return false; }\n#endif // Default shell handlers\n\n//-----------------------------------------------------------------------------\n\n// Win32 API IME support (for Asian languages, etc.)\n#if defined(_WIN32) && !defined(IMGUI_DISABLE_WIN32_FUNCTIONS) && !defined(IMGUI_DISABLE_WIN32_DEFAULT_IME_FUNCTIONS)\n\n#include <imm.h>\n#ifdef _MSC_VER\n#pragma comment(lib, \"imm32\")\n#endif\n\nstatic void Platform_SetImeDataFn_DefaultImpl(ImGuiContext*, ImGuiViewport* viewport, ImGuiPlatformImeData* data)\n{\n    // Notify OS Input Method Editor of text input position\n    HWND hwnd = (HWND)viewport->PlatformHandleRaw;\n    if (hwnd == 0)\n        return;\n\n    //::ImmAssociateContextEx(hwnd, NULL, data->WantVisible ? IACE_DEFAULT : 0);\n    if (HIMC himc = ::ImmGetContext(hwnd))\n    {\n        COMPOSITIONFORM composition_form = {};\n        composition_form.ptCurrentPos.x = (LONG)data->InputPos.x;\n        composition_form.ptCurrentPos.y = (LONG)data->InputPos.y;\n        composition_form.dwStyle = CFS_FORCE_POSITION;\n        ::ImmSetCompositionWindow(himc, &composition_form);\n        CANDIDATEFORM candidate_form = {};\n        candidate_form.dwStyle = CFS_CANDIDATEPOS;\n        candidate_form.ptCurrentPos.x = (LONG)data->InputPos.x;\n        candidate_form.ptCurrentPos.y = (LONG)data->InputPos.y;\n        ::ImmSetCandidateWindow(himc, &candidate_form);\n        ::ImmReleaseContext(hwnd, himc);\n    }\n}\n\n#else\n\nstatic void Platform_SetImeDataFn_DefaultImpl(ImGuiContext*, ImGuiViewport*, ImGuiPlatformImeData*) {}\n\n#endif // Default IME handlers\n\n//-----------------------------------------------------------------------------\n// [SECTION] METRICS/DEBUGGER WINDOW\n//-----------------------------------------------------------------------------\n// - DebugRenderViewportThumbnail() [Internal]\n// - RenderViewportsThumbnails() [Internal]\n// - DebugTextEncoding()\n// - MetricsHelpMarker() [Internal]\n// - ShowFontAtlas() [Internal]\n// - ShowMetricsWindow()\n// - DebugNodeColumns() [Internal]\n// - DebugNodeDrawList() [Internal]\n// - DebugNodeDrawCmdShowMeshAndBoundingBox() [Internal]\n// - DebugNodeFont() [Internal]\n// - DebugNodeFontGlyph() [Internal]\n// - DebugNodeStorage() [Internal]\n// - DebugNodeTabBar() [Internal]\n// - DebugNodeViewport() [Internal]\n// - DebugNodeWindow() [Internal]\n// - DebugNodeWindowSettings() [Internal]\n// - DebugNodeWindowsList() [Internal]\n// - DebugNodeWindowsListByBeginStackParent() [Internal]\n//-----------------------------------------------------------------------------\n\n#ifndef IMGUI_DISABLE_DEBUG_TOOLS\n\nvoid ImGui::DebugRenderViewportThumbnail(ImDrawList* draw_list, ImGuiViewportP* viewport, const ImRect& bb)\n{\n    ImGuiContext& g = *GImGui;\n    ImGuiWindow* window = g.CurrentWindow;\n\n    ImVec2 scale = bb.GetSize() / viewport->Size;\n    ImVec2 off = bb.Min - viewport->Pos * scale;\n    float alpha_mul = 1.0f;\n    window->DrawList->AddRectFilled(bb.Min, bb.Max, GetColorU32(ImGuiCol_Border, alpha_mul * 0.40f));\n    for (ImGuiWindow* thumb_window : g.Windows)\n    {\n        if (!thumb_window->WasActive || (thumb_window->Flags & ImGuiWindowFlags_ChildWindow))\n            continue;\n\n        ImRect thumb_r = thumb_window->Rect();\n        ImRect title_r = thumb_window->TitleBarRect();\n        thumb_r = ImRect(ImTrunc(off + thumb_r.Min * scale), ImTrunc(off +  thumb_r.Max * scale));\n        title_r = ImRect(ImTrunc(off + title_r.Min * scale), ImTrunc(off +  ImVec2(title_r.Max.x, title_r.Min.y + title_r.GetHeight() * 3.0f) * scale)); // Exaggerate title bar height\n        thumb_r.ClipWithFull(bb);\n        title_r.ClipWithFull(bb);\n        const bool window_is_focused = (g.NavWindow && thumb_window->RootWindowForTitleBarHighlight == g.NavWindow->RootWindowForTitleBarHighlight);\n        window->DrawList->AddRectFilled(thumb_r.Min, thumb_r.Max, GetColorU32(ImGuiCol_WindowBg, alpha_mul));\n        window->DrawList->AddRectFilled(title_r.Min, title_r.Max, GetColorU32(window_is_focused ? ImGuiCol_TitleBgActive : ImGuiCol_TitleBg, alpha_mul));\n        window->DrawList->AddRect(thumb_r.Min, thumb_r.Max, GetColorU32(ImGuiCol_Border, alpha_mul));\n        window->DrawList->AddText(g.Font, g.FontSize * 1.0f, title_r.Min, GetColorU32(ImGuiCol_Text, alpha_mul), thumb_window->Name, FindRenderedTextEnd(thumb_window->Name));\n    }\n    draw_list->AddRect(bb.Min, bb.Max, GetColorU32(ImGuiCol_Border, alpha_mul));\n    if (viewport->ID == g.DebugMetricsConfig.HighlightViewportID)\n        window->DrawList->AddRect(bb.Min, bb.Max, IM_COL32(255, 255, 0, 255));\n}\n\nstatic void RenderViewportsThumbnails()\n{\n    ImGuiContext& g = *GImGui;\n    ImGuiWindow* window = g.CurrentWindow;\n\n    float SCALE = 1.0f / 8.0f;\n    ImRect bb_full(g.Viewports[0]->Pos, g.Viewports[0]->Pos + g.Viewports[0]->Size);\n    ImVec2 p = window->DC.CursorPos;\n    ImVec2 off = p - bb_full.Min * SCALE;\n\n    // Draw viewports\n    for (ImGuiViewportP* viewport : g.Viewports)\n    {\n        ImRect viewport_draw_bb(off + (viewport->Pos) * SCALE, off + (viewport->Pos + viewport->Size) * SCALE);\n        ImGui::DebugRenderViewportThumbnail(window->DrawList, viewport, viewport_draw_bb);\n    }\n    ImGui::Dummy(bb_full.GetSize() * SCALE);\n}\n\n// Draw an arbitrary US keyboard layout to visualize translated keys\nvoid ImGui::DebugRenderKeyboardPreview(ImDrawList* draw_list)\n{\n    const float scale = ImGui::GetFontSize() / 13.0f;\n    const ImVec2 key_size = ImVec2(35.0f, 35.0f) * scale;\n    const float  key_rounding = 3.0f * scale;\n    const ImVec2 key_face_size = ImVec2(25.0f, 25.0f) * scale;\n    const ImVec2 key_face_pos = ImVec2(5.0f, 3.0f) * scale;\n    const float  key_face_rounding = 2.0f * scale;\n    const ImVec2 key_label_pos = ImVec2(7.0f, 4.0f) * scale;\n    const ImVec2 key_step = ImVec2(key_size.x - 1.0f, key_size.y - 1.0f);\n    const float  key_row_offset = 9.0f * scale;\n\n    ImVec2 board_min = GetCursorScreenPos();\n    ImVec2 board_max = ImVec2(board_min.x + 3 * key_step.x + 2 * key_row_offset + 10.0f, board_min.y + 3 * key_step.y + 10.0f);\n    ImVec2 start_pos = ImVec2(board_min.x + 5.0f - key_step.x, board_min.y);\n\n    struct KeyLayoutData { int Row, Col; const char* Label; ImGuiKey Key; };\n    const KeyLayoutData keys_to_display[] =\n    {\n        { 0, 0, \"\", ImGuiKey_Tab },      { 0, 1, \"Q\", ImGuiKey_Q }, { 0, 2, \"W\", ImGuiKey_W }, { 0, 3, \"E\", ImGuiKey_E }, { 0, 4, \"R\", ImGuiKey_R },\n        { 1, 0, \"\", ImGuiKey_CapsLock }, { 1, 1, \"A\", ImGuiKey_A }, { 1, 2, \"S\", ImGuiKey_S }, { 1, 3, \"D\", ImGuiKey_D }, { 1, 4, \"F\", ImGuiKey_F },\n        { 2, 0, \"\", ImGuiKey_LeftShift },{ 2, 1, \"Z\", ImGuiKey_Z }, { 2, 2, \"X\", ImGuiKey_X }, { 2, 3, \"C\", ImGuiKey_C }, { 2, 4, \"V\", ImGuiKey_V }\n    };\n\n    // Elements rendered manually via ImDrawList API are not clipped automatically.\n    // While not strictly necessary, here IsItemVisible() is used to avoid rendering these shapes when they are out of view.\n    Dummy(board_max - board_min);\n    if (!IsItemVisible())\n        return;\n    draw_list->PushClipRect(board_min, board_max, true);\n    for (int n = 0; n < IM_ARRAYSIZE(keys_to_display); n++)\n    {\n        const KeyLayoutData* key_data = &keys_to_display[n];\n        ImVec2 key_min = ImVec2(start_pos.x + key_data->Col * key_step.x + key_data->Row * key_row_offset, start_pos.y + key_data->Row * key_step.y);\n        ImVec2 key_max = key_min + key_size;\n        draw_list->AddRectFilled(key_min, key_max, IM_COL32(204, 204, 204, 255), key_rounding);\n        draw_list->AddRect(key_min, key_max, IM_COL32(24, 24, 24, 255), key_rounding);\n        ImVec2 face_min = ImVec2(key_min.x + key_face_pos.x, key_min.y + key_face_pos.y);\n        ImVec2 face_max = ImVec2(face_min.x + key_face_size.x, face_min.y + key_face_size.y);\n        draw_list->AddRect(face_min, face_max, IM_COL32(193, 193, 193, 255), key_face_rounding, ImDrawFlags_None, 2.0f);\n        draw_list->AddRectFilled(face_min, face_max, IM_COL32(252, 252, 252, 255), key_face_rounding);\n        ImVec2 label_min = ImVec2(key_min.x + key_label_pos.x, key_min.y + key_label_pos.y);\n        draw_list->AddText(label_min, IM_COL32(64, 64, 64, 255), key_data->Label);\n        if (IsKeyDown(key_data->Key))\n            draw_list->AddRectFilled(key_min, key_max, IM_COL32(255, 0, 0, 128), key_rounding);\n    }\n    draw_list->PopClipRect();\n}\n\n// Helper tool to diagnose between text encoding issues and font loading issues. Pass your UTF-8 string and verify that there are correct.\nvoid ImGui::DebugTextEncoding(const char* str)\n{\n    Text(\"Text: \\\"%s\\\"\", str);\n    if (!BeginTable(\"##DebugTextEncoding\", 4, ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg | ImGuiTableFlags_SizingFixedFit | ImGuiTableFlags_Resizable))\n        return;\n    TableSetupColumn(\"Offset\");\n    TableSetupColumn(\"UTF-8\");\n    TableSetupColumn(\"Glyph\");\n    TableSetupColumn(\"Codepoint\");\n    TableHeadersRow();\n    for (const char* p = str; *p != 0; )\n    {\n        unsigned int c;\n        const int c_utf8_len = ImTextCharFromUtf8(&c, p, NULL);\n        TableNextColumn();\n        Text(\"%d\", (int)(p - str));\n        TableNextColumn();\n        for (int byte_index = 0; byte_index < c_utf8_len; byte_index++)\n        {\n            if (byte_index > 0)\n                SameLine();\n            Text(\"0x%02X\", (int)(unsigned char)p[byte_index]);\n        }\n        TableNextColumn();\n        if (GetFont()->FindGlyphNoFallback((ImWchar)c))\n            TextUnformatted(p, p + c_utf8_len);\n        else\n            TextUnformatted((c == IM_UNICODE_CODEPOINT_INVALID) ? \"[invalid]\" : \"[missing]\");\n        TableNextColumn();\n        Text(\"U+%04X\", (int)c);\n        p += c_utf8_len;\n    }\n    EndTable();\n}\n\nstatic void DebugFlashStyleColorStop()\n{\n    ImGuiContext& g = *GImGui;\n    if (g.DebugFlashStyleColorIdx != ImGuiCol_COUNT)\n        g.Style.Colors[g.DebugFlashStyleColorIdx] = g.DebugFlashStyleColorBackup;\n    g.DebugFlashStyleColorIdx = ImGuiCol_COUNT;\n}\n\n// Flash a given style color for some + inhibit modifications of this color via PushStyleColor() calls.\nvoid ImGui::DebugFlashStyleColor(ImGuiCol idx)\n{\n    ImGuiContext& g = *GImGui;\n    DebugFlashStyleColorStop();\n    g.DebugFlashStyleColorTime = 0.5f;\n    g.DebugFlashStyleColorIdx = idx;\n    g.DebugFlashStyleColorBackup = g.Style.Colors[idx];\n}\n\nvoid ImGui::UpdateDebugToolFlashStyleColor()\n{\n    ImGuiContext& g = *GImGui;\n    if (g.DebugFlashStyleColorTime <= 0.0f)\n        return;\n    ColorConvertHSVtoRGB(ImCos(g.DebugFlashStyleColorTime * 6.0f) * 0.5f + 0.5f, 0.5f, 0.5f, g.Style.Colors[g.DebugFlashStyleColorIdx].x, g.Style.Colors[g.DebugFlashStyleColorIdx].y, g.Style.Colors[g.DebugFlashStyleColorIdx].z);\n    g.Style.Colors[g.DebugFlashStyleColorIdx].w = 1.0f;\n    if ((g.DebugFlashStyleColorTime -= g.IO.DeltaTime) <= 0.0f)\n        DebugFlashStyleColorStop();\n}\n\nstatic const char* FormatTextureIDForDebugDisplay(char* buf, int buf_size, ImTextureID tex_id)\n{\n    union { void* ptr; int integer; } tex_id_opaque;\n    memcpy(&tex_id_opaque, &tex_id, ImMin(sizeof(void*), sizeof(tex_id)));\n    if (sizeof(tex_id) >= sizeof(void*))\n        ImFormatString(buf, buf_size, \"0x%p\", tex_id_opaque.ptr);\n    else\n        ImFormatString(buf, buf_size, \"0x%04X\", tex_id_opaque.integer);\n    return buf;\n}\n\n// Avoid naming collision with imgui_demo.cpp's HelpMarker() for unity builds.\nstatic void MetricsHelpMarker(const char* desc)\n{\n    ImGui::TextDisabled(\"(?)\");\n    if (ImGui::BeginItemTooltip())\n    {\n        ImGui::PushTextWrapPos(ImGui::GetFontSize() * 35.0f);\n        ImGui::TextUnformatted(desc);\n        ImGui::PopTextWrapPos();\n        ImGui::EndTooltip();\n    }\n}\n\n// [DEBUG] List fonts in a font atlas and display its texture\nvoid ImGui::ShowFontAtlas(ImFontAtlas* atlas)\n{\n    for (ImFont* font : atlas->Fonts)\n    {\n        PushID(font);\n        DebugNodeFont(font);\n        PopID();\n    }\n    if (TreeNode(\"Font Atlas\", \"Font Atlas (%dx%d pixels)\", atlas->TexWidth, atlas->TexHeight))\n    {\n        ImGuiContext& g = *GImGui;\n        PushStyleVar(ImGuiStyleVar_ImageBorderSize, ImMax(1.0f, g.Style.ImageBorderSize));\n        ImageWithBg(atlas->TexID, ImVec2((float)atlas->TexWidth, (float)atlas->TexHeight), ImVec2(0.0f, 0.0f), ImVec2(1.0f, 1.0f), ImVec4(0.0f, 0.0f, 0.0f, 1.0f));\n        PopStyleVar();\n        TreePop();\n    }\n}\n\nvoid ImGui::ShowMetricsWindow(bool* p_open)\n{\n    ImGuiContext& g = *GImGui;\n    ImGuiIO& io = g.IO;\n    ImGuiMetricsConfig* cfg = &g.DebugMetricsConfig;\n    if (cfg->ShowDebugLog)\n        ShowDebugLogWindow(&cfg->ShowDebugLog);\n    if (cfg->ShowIDStackTool)\n        ShowIDStackToolWindow(&cfg->ShowIDStackTool);\n\n    if (!Begin(\"Dear ImGui Metrics/Debugger\", p_open) || GetCurrentWindow()->BeginCount > 1)\n    {\n        End();\n        return;\n    }\n\n    // [DEBUG] Clear debug breaks hooks after exactly one cycle.\n    DebugBreakClearData();\n\n    // Basic info\n    Text(\"Dear ImGui %s (%d)\", IMGUI_VERSION, IMGUI_VERSION_NUM);\n    if (g.ContextName[0] != 0)\n    {\n        SameLine();\n        Text(\"(Context Name: \\\"%s\\\")\", g.ContextName);\n    }\n    Text(\"Application average %.3f ms/frame (%.1f FPS)\", 1000.0f / io.Framerate, io.Framerate);\n    Text(\"%d vertices, %d indices (%d triangles)\", io.MetricsRenderVertices, io.MetricsRenderIndices, io.MetricsRenderIndices / 3);\n    Text(\"%d visible windows, %d current allocations\", io.MetricsRenderWindows, g.DebugAllocInfo.TotalAllocCount - g.DebugAllocInfo.TotalFreeCount);\n    //SameLine(); if (SmallButton(\"GC\")) { g.GcCompactAll = true; }\n\n    Separator();\n\n    // Debugging enums\n    enum { WRT_OuterRect, WRT_OuterRectClipped, WRT_InnerRect, WRT_InnerClipRect, WRT_WorkRect, WRT_Content, WRT_ContentIdeal, WRT_ContentRegionRect, WRT_Count }; // Windows Rect Type\n    const char* wrt_rects_names[WRT_Count] = { \"OuterRect\", \"OuterRectClipped\", \"InnerRect\", \"InnerClipRect\", \"WorkRect\", \"Content\", \"ContentIdeal\", \"ContentRegionRect\" };\n    enum { TRT_OuterRect, TRT_InnerRect, TRT_WorkRect, TRT_HostClipRect, TRT_InnerClipRect, TRT_BackgroundClipRect, TRT_ColumnsRect, TRT_ColumnsWorkRect, TRT_ColumnsClipRect, TRT_ColumnsContentHeadersUsed, TRT_ColumnsContentHeadersIdeal, TRT_ColumnsContentFrozen, TRT_ColumnsContentUnfrozen, TRT_Count }; // Tables Rect Type\n    const char* trt_rects_names[TRT_Count] = { \"OuterRect\", \"InnerRect\", \"WorkRect\", \"HostClipRect\", \"InnerClipRect\", \"BackgroundClipRect\", \"ColumnsRect\", \"ColumnsWorkRect\", \"ColumnsClipRect\", \"ColumnsContentHeadersUsed\", \"ColumnsContentHeadersIdeal\", \"ColumnsContentFrozen\", \"ColumnsContentUnfrozen\" };\n    if (cfg->ShowWindowsRectsType < 0)\n        cfg->ShowWindowsRectsType = WRT_WorkRect;\n    if (cfg->ShowTablesRectsType < 0)\n        cfg->ShowTablesRectsType = TRT_WorkRect;\n\n    struct Funcs\n    {\n        static ImRect GetTableRect(ImGuiTable* table, int rect_type, int n)\n        {\n            ImGuiTableInstanceData* table_instance = TableGetInstanceData(table, table->InstanceCurrent); // Always using last submitted instance\n            if (rect_type == TRT_OuterRect)                     { return table->OuterRect; }\n            else if (rect_type == TRT_InnerRect)                { return table->InnerRect; }\n            else if (rect_type == TRT_WorkRect)                 { return table->WorkRect; }\n            else if (rect_type == TRT_HostClipRect)             { return table->HostClipRect; }\n            else if (rect_type == TRT_InnerClipRect)            { return table->InnerClipRect; }\n            else if (rect_type == TRT_BackgroundClipRect)       { return table->BgClipRect; }\n            else if (rect_type == TRT_ColumnsRect)              { ImGuiTableColumn* c = &table->Columns[n]; return ImRect(c->MinX, table->InnerClipRect.Min.y, c->MaxX, table->InnerClipRect.Min.y + table_instance->LastOuterHeight); }\n            else if (rect_type == TRT_ColumnsWorkRect)          { ImGuiTableColumn* c = &table->Columns[n]; return ImRect(c->WorkMinX, table->WorkRect.Min.y, c->WorkMaxX, table->WorkRect.Max.y); }\n            else if (rect_type == TRT_ColumnsClipRect)          { ImGuiTableColumn* c = &table->Columns[n]; return c->ClipRect; }\n            else if (rect_type == TRT_ColumnsContentHeadersUsed){ ImGuiTableColumn* c = &table->Columns[n]; return ImRect(c->WorkMinX, table->InnerClipRect.Min.y, c->ContentMaxXHeadersUsed, table->InnerClipRect.Min.y + table_instance->LastTopHeadersRowHeight); } // Note: y1/y2 not always accurate\n            else if (rect_type == TRT_ColumnsContentHeadersIdeal){ImGuiTableColumn* c = &table->Columns[n]; return ImRect(c->WorkMinX, table->InnerClipRect.Min.y, c->ContentMaxXHeadersIdeal, table->InnerClipRect.Min.y + table_instance->LastTopHeadersRowHeight); }\n            else if (rect_type == TRT_ColumnsContentFrozen)     { ImGuiTableColumn* c = &table->Columns[n]; return ImRect(c->WorkMinX, table->InnerClipRect.Min.y, c->ContentMaxXFrozen, table->InnerClipRect.Min.y + table_instance->LastFrozenHeight); }\n            else if (rect_type == TRT_ColumnsContentUnfrozen)   { ImGuiTableColumn* c = &table->Columns[n]; return ImRect(c->WorkMinX, table->InnerClipRect.Min.y + table_instance->LastFrozenHeight, c->ContentMaxXUnfrozen, table->InnerClipRect.Max.y); }\n            IM_ASSERT(0);\n            return ImRect();\n        }\n\n        static ImRect GetWindowRect(ImGuiWindow* window, int rect_type)\n        {\n            if (rect_type == WRT_OuterRect)                 { return window->Rect(); }\n            else if (rect_type == WRT_OuterRectClipped)     { return window->OuterRectClipped; }\n            else if (rect_type == WRT_InnerRect)            { return window->InnerRect; }\n            else if (rect_type == WRT_InnerClipRect)        { return window->InnerClipRect; }\n            else if (rect_type == WRT_WorkRect)             { return window->WorkRect; }\n            else if (rect_type == WRT_Content)              { ImVec2 min = window->InnerRect.Min - window->Scroll + window->WindowPadding; return ImRect(min, min + window->ContentSize); }\n            else if (rect_type == WRT_ContentIdeal)         { ImVec2 min = window->InnerRect.Min - window->Scroll + window->WindowPadding; return ImRect(min, min + window->ContentSizeIdeal); }\n            else if (rect_type == WRT_ContentRegionRect)    { return window->ContentRegionRect; }\n            IM_ASSERT(0);\n            return ImRect();\n        }\n    };\n\n    // Tools\n    if (TreeNode(\"Tools\"))\n    {\n        // Debug Break features\n        // The Item Picker tool is super useful to visually select an item and break into the call-stack of where it was submitted.\n        SeparatorTextEx(0, \"Debug breaks\", NULL, CalcTextSize(\"(?)\").x + g.Style.SeparatorTextPadding.x);\n        SameLine();\n        MetricsHelpMarker(\"Will call the IM_DEBUG_BREAK() macro to break in debugger.\\nWarning: If you don't have a debugger attached, this will probably crash.\");\n        if (Checkbox(\"Show Item Picker\", &g.DebugItemPickerActive) && g.DebugItemPickerActive)\n            DebugStartItemPicker();\n        Checkbox(\"Show \\\"Debug Break\\\" buttons in other sections (io.ConfigDebugIsDebuggerPresent)\", &g.IO.ConfigDebugIsDebuggerPresent);\n\n        SeparatorText(\"Visualize\");\n\n        Checkbox(\"Show Debug Log\", &cfg->ShowDebugLog);\n        SameLine();\n        MetricsHelpMarker(\"You can also call ImGui::ShowDebugLogWindow() from your code.\");\n\n        Checkbox(\"Show ID Stack Tool\", &cfg->ShowIDStackTool);\n        SameLine();\n        MetricsHelpMarker(\"You can also call ImGui::ShowIDStackToolWindow() from your code.\");\n\n        Checkbox(\"Show windows begin order\", &cfg->ShowWindowsBeginOrder);\n        Checkbox(\"Show windows rectangles\", &cfg->ShowWindowsRects);\n        SameLine();\n        SetNextItemWidth(GetFontSize() * 12);\n        cfg->ShowWindowsRects |= Combo(\"##show_windows_rect_type\", &cfg->ShowWindowsRectsType, wrt_rects_names, WRT_Count, WRT_Count);\n        if (cfg->ShowWindowsRects && g.NavWindow != NULL)\n        {\n            BulletText(\"'%s':\", g.NavWindow->Name);\n            Indent();\n            for (int rect_n = 0; rect_n < WRT_Count; rect_n++)\n            {\n                ImRect r = Funcs::GetWindowRect(g.NavWindow, rect_n);\n                Text(\"(%6.1f,%6.1f) (%6.1f,%6.1f) Size (%6.1f,%6.1f) %s\", r.Min.x, r.Min.y, r.Max.x, r.Max.y, r.GetWidth(), r.GetHeight(), wrt_rects_names[rect_n]);\n            }\n            Unindent();\n        }\n\n        Checkbox(\"Show tables rectangles\", &cfg->ShowTablesRects);\n        SameLine();\n        SetNextItemWidth(GetFontSize() * 12);\n        cfg->ShowTablesRects |= Combo(\"##show_table_rects_type\", &cfg->ShowTablesRectsType, trt_rects_names, TRT_Count, TRT_Count);\n        if (cfg->ShowTablesRects && g.NavWindow != NULL)\n        {\n            for (int table_n = 0; table_n < g.Tables.GetMapSize(); table_n++)\n            {\n                ImGuiTable* table = g.Tables.TryGetMapData(table_n);\n                if (table == NULL || table->LastFrameActive < g.FrameCount - 1 || (table->OuterWindow != g.NavWindow && table->InnerWindow != g.NavWindow))\n                    continue;\n\n                BulletText(\"Table 0x%08X (%d columns, in '%s')\", table->ID, table->ColumnsCount, table->OuterWindow->Name);\n                if (IsItemHovered())\n                    GetForegroundDrawList()->AddRect(table->OuterRect.Min - ImVec2(1, 1), table->OuterRect.Max + ImVec2(1, 1), IM_COL32(255, 255, 0, 255), 0.0f, 0, 2.0f);\n                Indent();\n                char buf[128];\n                for (int rect_n = 0; rect_n < TRT_Count; rect_n++)\n                {\n                    if (rect_n >= TRT_ColumnsRect)\n                    {\n                        if (rect_n != TRT_ColumnsRect && rect_n != TRT_ColumnsClipRect)\n                            continue;\n                        for (int column_n = 0; column_n < table->ColumnsCount; column_n++)\n                        {\n                            ImRect r = Funcs::GetTableRect(table, rect_n, column_n);\n                            ImFormatString(buf, IM_ARRAYSIZE(buf), \"(%6.1f,%6.1f) (%6.1f,%6.1f) Size (%6.1f,%6.1f) Col %d %s\", r.Min.x, r.Min.y, r.Max.x, r.Max.y, r.GetWidth(), r.GetHeight(), column_n, trt_rects_names[rect_n]);\n                            Selectable(buf);\n                            if (IsItemHovered())\n                                GetForegroundDrawList()->AddRect(r.Min - ImVec2(1, 1), r.Max + ImVec2(1, 1), IM_COL32(255, 255, 0, 255), 0.0f, 0, 2.0f);\n                        }\n                    }\n                    else\n                    {\n                        ImRect r = Funcs::GetTableRect(table, rect_n, -1);\n                        ImFormatString(buf, IM_ARRAYSIZE(buf), \"(%6.1f,%6.1f) (%6.1f,%6.1f) Size (%6.1f,%6.1f) %s\", r.Min.x, r.Min.y, r.Max.x, r.Max.y, r.GetWidth(), r.GetHeight(), trt_rects_names[rect_n]);\n                        Selectable(buf);\n                        if (IsItemHovered())\n                            GetForegroundDrawList()->AddRect(r.Min - ImVec2(1, 1), r.Max + ImVec2(1, 1), IM_COL32(255, 255, 0, 255), 0.0f, 0, 2.0f);\n                    }\n                }\n                Unindent();\n            }\n        }\n        Checkbox(\"Show groups rectangles\", &g.DebugShowGroupRects); // Storing in context as this is used by group code and prefers to be in hot-data\n\n        SeparatorText(\"Validate\");\n\n        Checkbox(\"Debug Begin/BeginChild return value\", &io.ConfigDebugBeginReturnValueLoop);\n        SameLine();\n        MetricsHelpMarker(\"Some calls to Begin()/BeginChild() will return false.\\n\\nWill cycle through window depths then repeat. Windows should be flickering while running.\");\n\n        Checkbox(\"UTF-8 Encoding viewer\", &cfg->ShowTextEncodingViewer);\n        SameLine();\n        MetricsHelpMarker(\"You can also call ImGui::DebugTextEncoding() from your code with a given string to test that your UTF-8 encoding settings are correct.\");\n        if (cfg->ShowTextEncodingViewer)\n        {\n            static char buf[64] = \"\";\n            SetNextItemWidth(-FLT_MIN);\n            InputText(\"##DebugTextEncodingBuf\", buf, IM_ARRAYSIZE(buf));\n            if (buf[0] != 0)\n                DebugTextEncoding(buf);\n        }\n\n        TreePop();\n    }\n\n    // Windows\n    if (TreeNode(\"Windows\", \"Windows (%d)\", g.Windows.Size))\n    {\n        //SetNextItemOpen(true, ImGuiCond_Once);\n        DebugNodeWindowsList(&g.Windows, \"By display order\");\n        DebugNodeWindowsList(&g.WindowsFocusOrder, \"By focus order (root windows)\");\n        if (TreeNode(\"By submission order (begin stack)\"))\n        {\n            // Here we display windows in their submitted order/hierarchy, however note that the Begin stack doesn't constitute a Parent<>Child relationship!\n            ImVector<ImGuiWindow*>& temp_buffer = g.WindowsTempSortBuffer;\n            temp_buffer.resize(0);\n            for (ImGuiWindow* window : g.Windows)\n                if (window->LastFrameActive + 1 >= g.FrameCount)\n                    temp_buffer.push_back(window);\n            struct Func { static int IMGUI_CDECL WindowComparerByBeginOrder(const void* lhs, const void* rhs) { return ((int)(*(const ImGuiWindow* const *)lhs)->BeginOrderWithinContext - (*(const ImGuiWindow* const*)rhs)->BeginOrderWithinContext); } };\n            ImQsort(temp_buffer.Data, (size_t)temp_buffer.Size, sizeof(ImGuiWindow*), Func::WindowComparerByBeginOrder);\n            DebugNodeWindowsListByBeginStackParent(temp_buffer.Data, temp_buffer.Size, NULL);\n            TreePop();\n        }\n\n        TreePop();\n    }\n\n    // DrawLists\n    int drawlist_count = 0;\n    for (ImGuiViewportP* viewport : g.Viewports)\n        drawlist_count += viewport->DrawDataP.CmdLists.Size;\n    if (TreeNode(\"DrawLists\", \"DrawLists (%d)\", drawlist_count))\n    {\n        Checkbox(\"Show ImDrawCmd mesh when hovering\", &cfg->ShowDrawCmdMesh);\n        Checkbox(\"Show ImDrawCmd bounding boxes when hovering\", &cfg->ShowDrawCmdBoundingBoxes);\n        for (ImGuiViewportP* viewport : g.Viewports)\n            for (ImDrawList* draw_list : viewport->DrawDataP.CmdLists)\n                DebugNodeDrawList(NULL, viewport, draw_list, \"DrawList\");\n        TreePop();\n    }\n\n    // Viewports\n    if (TreeNode(\"Viewports\", \"Viewports (%d)\", g.Viewports.Size))\n    {\n        SetNextItemOpen(true, ImGuiCond_Once);\n        if (TreeNode(\"Windows Minimap\"))\n        {\n            RenderViewportsThumbnails();\n            TreePop();\n        }\n        cfg->HighlightViewportID = 0;\n\n        for (ImGuiViewportP* viewport : g.Viewports)\n            DebugNodeViewport(viewport);\n        TreePop();\n    }\n\n    // Details for Popups\n    if (TreeNode(\"Popups\", \"Popups (%d)\", g.OpenPopupStack.Size))\n    {\n        for (const ImGuiPopupData& popup_data : g.OpenPopupStack)\n        {\n            // As it's difficult to interact with tree nodes while popups are open, we display everything inline.\n            ImGuiWindow* window = popup_data.Window;\n            BulletText(\"PopupID: %08x, Window: '%s' (%s%s), RestoreNavWindow '%s', ParentWindow '%s'\",\n                popup_data.PopupId, window ? window->Name : \"NULL\", window && (window->Flags & ImGuiWindowFlags_ChildWindow) ? \"Child;\" : \"\", window && (window->Flags & ImGuiWindowFlags_ChildMenu) ? \"Menu;\" : \"\",\n                popup_data.RestoreNavWindow ? popup_data.RestoreNavWindow->Name : \"NULL\", window && window->ParentWindow ? window->ParentWindow->Name : \"NULL\");\n        }\n        TreePop();\n    }\n\n    // Details for TabBars\n    if (TreeNode(\"TabBars\", \"Tab Bars (%d)\", g.TabBars.GetAliveCount()))\n    {\n        for (int n = 0; n < g.TabBars.GetMapSize(); n++)\n            if (ImGuiTabBar* tab_bar = g.TabBars.TryGetMapData(n))\n            {\n                PushID(tab_bar);\n                DebugNodeTabBar(tab_bar, \"TabBar\");\n                PopID();\n            }\n        TreePop();\n    }\n\n    // Details for Tables\n    if (TreeNode(\"Tables\", \"Tables (%d)\", g.Tables.GetAliveCount()))\n    {\n        for (int n = 0; n < g.Tables.GetMapSize(); n++)\n            if (ImGuiTable* table = g.Tables.TryGetMapData(n))\n                DebugNodeTable(table);\n        TreePop();\n    }\n\n    // Details for Fonts\n    ImFontAtlas* atlas = g.IO.Fonts;\n    if (TreeNode(\"Fonts\", \"Fonts (%d)\", atlas->Fonts.Size))\n    {\n        ShowFontAtlas(atlas);\n        TreePop();\n    }\n\n    // Details for InputText\n    if (TreeNode(\"InputText\"))\n    {\n        DebugNodeInputTextState(&g.InputTextState);\n        TreePop();\n    }\n\n    // Details for TypingSelect\n    if (TreeNode(\"TypingSelect\", \"TypingSelect (%d)\", g.TypingSelectState.SearchBuffer[0] != 0 ? 1 : 0))\n    {\n        DebugNodeTypingSelectState(&g.TypingSelectState);\n        TreePop();\n    }\n\n    // Details for MultiSelect\n    if (TreeNode(\"MultiSelect\", \"MultiSelect (%d)\", g.MultiSelectStorage.GetAliveCount()))\n    {\n        ImGuiBoxSelectState* bs = &g.BoxSelectState;\n        BulletText(\"BoxSelect ID=0x%08X, Starting = %d, Active %d\", bs->ID, bs->IsStarting, bs->IsActive);\n        for (int n = 0; n < g.MultiSelectStorage.GetMapSize(); n++)\n            if (ImGuiMultiSelectState* state = g.MultiSelectStorage.TryGetMapData(n))\n                DebugNodeMultiSelectState(state);\n        TreePop();\n    }\n\n    // Details for Docking\n#ifdef IMGUI_HAS_DOCK\n    if (TreeNode(\"Docking\"))\n    {\n        TreePop();\n    }\n#endif // #ifdef IMGUI_HAS_DOCK\n\n    // Settings\n    if (TreeNode(\"Settings\"))\n    {\n        if (SmallButton(\"Clear\"))\n            ClearIniSettings();\n        SameLine();\n        if (SmallButton(\"Save to memory\"))\n            SaveIniSettingsToMemory();\n        SameLine();\n        if (SmallButton(\"Save to disk\"))\n            SaveIniSettingsToDisk(g.IO.IniFilename);\n        SameLine();\n        if (g.IO.IniFilename)\n            Text(\"\\\"%s\\\"\", g.IO.IniFilename);\n        else\n            TextUnformatted(\"<NULL>\");\n        Checkbox(\"io.ConfigDebugIniSettings\", &io.ConfigDebugIniSettings);\n        Text(\"SettingsDirtyTimer %.2f\", g.SettingsDirtyTimer);\n        if (TreeNode(\"SettingsHandlers\", \"Settings handlers: (%d)\", g.SettingsHandlers.Size))\n        {\n            for (ImGuiSettingsHandler& handler : g.SettingsHandlers)\n                BulletText(\"\\\"%s\\\"\", handler.TypeName);\n            TreePop();\n        }\n        if (TreeNode(\"SettingsWindows\", \"Settings packed data: Windows: %d bytes\", g.SettingsWindows.size()))\n        {\n            for (ImGuiWindowSettings* settings = g.SettingsWindows.begin(); settings != NULL; settings = g.SettingsWindows.next_chunk(settings))\n                DebugNodeWindowSettings(settings);\n            TreePop();\n        }\n\n        if (TreeNode(\"SettingsTables\", \"Settings packed data: Tables: %d bytes\", g.SettingsTables.size()))\n        {\n            for (ImGuiTableSettings* settings = g.SettingsTables.begin(); settings != NULL; settings = g.SettingsTables.next_chunk(settings))\n                DebugNodeTableSettings(settings);\n            TreePop();\n        }\n\n#ifdef IMGUI_HAS_DOCK\n#endif // #ifdef IMGUI_HAS_DOCK\n\n        if (TreeNode(\"SettingsIniData\", \"Settings unpacked data (.ini): %d bytes\", g.SettingsIniData.size()))\n        {\n            InputTextMultiline(\"##Ini\", (char*)(void*)g.SettingsIniData.c_str(), g.SettingsIniData.Buf.Size, ImVec2(-FLT_MIN, GetTextLineHeight() * 20), ImGuiInputTextFlags_ReadOnly);\n            TreePop();\n        }\n        TreePop();\n    }\n\n    // Settings\n    if (TreeNode(\"Memory allocations\"))\n    {\n        ImGuiDebugAllocInfo* info = &g.DebugAllocInfo;\n        Text(\"%d current allocations\", info->TotalAllocCount - info->TotalFreeCount);\n        if (SmallButton(\"GC now\")) { g.GcCompactAll = true; }\n        Text(\"Recent frames with allocations:\");\n        int buf_size = IM_ARRAYSIZE(info->LastEntriesBuf);\n        for (int n = buf_size - 1; n >= 0; n--)\n        {\n            ImGuiDebugAllocEntry* entry = &info->LastEntriesBuf[(info->LastEntriesIdx - n + buf_size) % buf_size];\n            BulletText(\"Frame %06d: %+3d ( %2d alloc, %2d free )\", entry->FrameCount, entry->AllocCount - entry->FreeCount, entry->AllocCount, entry->FreeCount);\n            if (n == 0)\n            {\n                SameLine();\n                Text(\"<- %d frames ago\", g.FrameCount - entry->FrameCount);\n            }\n        }\n        TreePop();\n    }\n\n    if (TreeNode(\"Inputs\"))\n    {\n        Text(\"KEYBOARD/GAMEPAD/MOUSE KEYS\");\n        {\n            // User code should never have to go through such hoops! You can generally iterate between ImGuiKey_NamedKey_BEGIN and ImGuiKey_NamedKey_END.\n            Indent();\n            Text(\"Keys down:\");         for (ImGuiKey key = ImGuiKey_NamedKey_BEGIN; key < ImGuiKey_NamedKey_END; key = (ImGuiKey)(key + 1)) { if (!IsKeyDown(key)) continue;     SameLine(); Text(IsNamedKey(key) ? \"\\\"%s\\\"\" : \"\\\"%s\\\" %d\", GetKeyName(key), key); SameLine(); Text(\"(%.02f)\", GetKeyData(key)->DownDuration); }\n            Text(\"Keys pressed:\");      for (ImGuiKey key = ImGuiKey_NamedKey_BEGIN; key < ImGuiKey_NamedKey_END; key = (ImGuiKey)(key + 1)) { if (!IsKeyPressed(key)) continue;  SameLine(); Text(IsNamedKey(key) ? \"\\\"%s\\\"\" : \"\\\"%s\\\" %d\", GetKeyName(key), key); }\n            Text(\"Keys released:\");     for (ImGuiKey key = ImGuiKey_NamedKey_BEGIN; key < ImGuiKey_NamedKey_END; key = (ImGuiKey)(key + 1)) { if (!IsKeyReleased(key)) continue; SameLine(); Text(IsNamedKey(key) ? \"\\\"%s\\\"\" : \"\\\"%s\\\" %d\", GetKeyName(key), key); }\n            Text(\"Keys mods: %s%s%s%s\", io.KeyCtrl ? \"CTRL \" : \"\", io.KeyShift ? \"SHIFT \" : \"\", io.KeyAlt ? \"ALT \" : \"\", io.KeySuper ? \"SUPER \" : \"\");\n            Text(\"Chars queue:\");       for (int i = 0; i < io.InputQueueCharacters.Size; i++) { ImWchar c = io.InputQueueCharacters[i]; SameLine(); Text(\"\\'%c\\' (0x%04X)\", (c > ' ' && c <= 255) ? (char)c : '?', c); } // FIXME: We should convert 'c' to UTF-8 here but the functions are not public.\n            DebugRenderKeyboardPreview(GetWindowDrawList());\n            Unindent();\n        }\n\n        Text(\"MOUSE STATE\");\n        {\n            Indent();\n            if (IsMousePosValid())\n                Text(\"Mouse pos: (%g, %g)\", io.MousePos.x, io.MousePos.y);\n            else\n                Text(\"Mouse pos: <INVALID>\");\n            Text(\"Mouse delta: (%g, %g)\", io.MouseDelta.x, io.MouseDelta.y);\n            int count = IM_ARRAYSIZE(io.MouseDown);\n            Text(\"Mouse down:\");     for (int i = 0; i < count; i++) if (IsMouseDown(i)) { SameLine(); Text(\"b%d (%.02f secs)\", i, io.MouseDownDuration[i]); }\n            Text(\"Mouse clicked:\");  for (int i = 0; i < count; i++) if (IsMouseClicked(i)) { SameLine(); Text(\"b%d (%d)\", i, io.MouseClickedCount[i]); }\n            Text(\"Mouse released:\"); for (int i = 0; i < count; i++) if (IsMouseReleased(i)) { SameLine(); Text(\"b%d\", i); }\n            Text(\"Mouse wheel: %.1f\", io.MouseWheel);\n            Text(\"MouseStationaryTimer: %.2f\", g.MouseStationaryTimer);\n            Text(\"Mouse source: %s\", GetMouseSourceName(io.MouseSource));\n            Text(\"Pen Pressure: %.1f\", io.PenPressure); // Note: currently unused\n            Unindent();\n        }\n\n        Text(\"MOUSE WHEELING\");\n        {\n            Indent();\n            Text(\"WheelingWindow: '%s'\", g.WheelingWindow ? g.WheelingWindow->Name : \"NULL\");\n            Text(\"WheelingWindowReleaseTimer: %.2f\", g.WheelingWindowReleaseTimer);\n            Text(\"WheelingAxisAvg[] = { %.3f, %.3f }, Main Axis: %s\", g.WheelingAxisAvg.x, g.WheelingAxisAvg.y, (g.WheelingAxisAvg.x > g.WheelingAxisAvg.y) ? \"X\" : (g.WheelingAxisAvg.x < g.WheelingAxisAvg.y) ? \"Y\" : \"<none>\");\n            Unindent();\n        }\n\n        Text(\"KEY OWNERS\");\n        {\n            Indent();\n            if (BeginChild(\"##owners\", ImVec2(-FLT_MIN, GetTextLineHeightWithSpacing() * 8), ImGuiChildFlags_FrameStyle | ImGuiChildFlags_ResizeY, ImGuiWindowFlags_NoSavedSettings))\n                for (ImGuiKey key = ImGuiKey_NamedKey_BEGIN; key < ImGuiKey_NamedKey_END; key = (ImGuiKey)(key + 1))\n                {\n                    ImGuiKeyOwnerData* owner_data = GetKeyOwnerData(&g, key);\n                    if (owner_data->OwnerCurr == ImGuiKeyOwner_NoOwner)\n                        continue;\n                    Text(\"%s: 0x%08X%s\", GetKeyName(key), owner_data->OwnerCurr,\n                        owner_data->LockUntilRelease ? \" LockUntilRelease\" : owner_data->LockThisFrame ? \" LockThisFrame\" : \"\");\n                    DebugLocateItemOnHover(owner_data->OwnerCurr);\n                }\n            EndChild();\n            Unindent();\n        }\n        Text(\"SHORTCUT ROUTING\");\n        SameLine();\n        MetricsHelpMarker(\"Declared shortcut routes automatically set key owner when mods matches.\");\n        {\n            Indent();\n            if (BeginChild(\"##routes\", ImVec2(-FLT_MIN, GetTextLineHeightWithSpacing() * 8), ImGuiChildFlags_FrameStyle | ImGuiChildFlags_ResizeY, ImGuiWindowFlags_NoSavedSettings))\n                for (ImGuiKey key = ImGuiKey_NamedKey_BEGIN; key < ImGuiKey_NamedKey_END; key = (ImGuiKey)(key + 1))\n                {\n                    ImGuiKeyRoutingTable* rt = &g.KeysRoutingTable;\n                    for (ImGuiKeyRoutingIndex idx = rt->Index[key - ImGuiKey_NamedKey_BEGIN]; idx != -1; )\n                    {\n                        ImGuiKeyRoutingData* routing_data = &rt->Entries[idx];\n                        ImGuiKeyChord key_chord = key | routing_data->Mods;\n                        Text(\"%s: 0x%08X (scored %d)\", GetKeyChordName(key_chord), routing_data->RoutingCurr, routing_data->RoutingCurrScore);\n                        DebugLocateItemOnHover(routing_data->RoutingCurr);\n                        if (g.IO.ConfigDebugIsDebuggerPresent)\n                        {\n                            SameLine();\n                            if (DebugBreakButton(\"**DebugBreak**\", \"in SetShortcutRouting() for this KeyChord\"))\n                                g.DebugBreakInShortcutRouting = key_chord;\n                        }\n                        idx = routing_data->NextEntryIndex;\n                    }\n                }\n            EndChild();\n            Text(\"(ActiveIdUsing: AllKeyboardKeys: %d, NavDirMask: 0x%X)\", g.ActiveIdUsingAllKeyboardKeys, g.ActiveIdUsingNavDirMask);\n            Unindent();\n        }\n        TreePop();\n    }\n\n    if (TreeNode(\"Internal state\"))\n    {\n        Text(\"WINDOWING\");\n        Indent();\n        Text(\"HoveredWindow: '%s'\", g.HoveredWindow ? g.HoveredWindow->Name : \"NULL\");\n        Text(\"HoveredWindow->Root: '%s'\", g.HoveredWindow ? g.HoveredWindow->RootWindow->Name : \"NULL\");\n        Text(\"HoveredWindowUnderMovingWindow: '%s'\", g.HoveredWindowUnderMovingWindow ? g.HoveredWindowUnderMovingWindow->Name : \"NULL\");\n        Text(\"MovingWindow: '%s'\", g.MovingWindow ? g.MovingWindow->Name : \"NULL\");\n        Unindent();\n\n        Text(\"ITEMS\");\n        Indent();\n        Text(\"ActiveId: 0x%08X/0x%08X (%.2f sec), AllowOverlap: %d, Source: %s\", g.ActiveId, g.ActiveIdPreviousFrame, g.ActiveIdTimer, g.ActiveIdAllowOverlap, GetInputSourceName(g.ActiveIdSource));\n        DebugLocateItemOnHover(g.ActiveId);\n        Text(\"ActiveIdWindow: '%s'\", g.ActiveIdWindow ? g.ActiveIdWindow->Name : \"NULL\");\n        Text(\"ActiveIdUsing: AllKeyboardKeys: %d, NavDirMask: %X\", g.ActiveIdUsingAllKeyboardKeys, g.ActiveIdUsingNavDirMask);\n        Text(\"HoveredId: 0x%08X (%.2f sec), AllowOverlap: %d\", g.HoveredIdPreviousFrame, g.HoveredIdTimer, g.HoveredIdAllowOverlap); // Not displaying g.HoveredId as it is update mid-frame\n        Text(\"HoverItemDelayId: 0x%08X, Timer: %.2f, ClearTimer: %.2f\", g.HoverItemDelayId, g.HoverItemDelayTimer, g.HoverItemDelayClearTimer);\n        Text(\"DragDrop: %d, SourceId = 0x%08X, Payload \\\"%s\\\" (%d bytes)\", g.DragDropActive, g.DragDropPayload.SourceId, g.DragDropPayload.DataType, g.DragDropPayload.DataSize);\n        DebugLocateItemOnHover(g.DragDropPayload.SourceId);\n        Unindent();\n\n        Text(\"NAV,FOCUS\");\n        Indent();\n        Text(\"NavWindow: '%s'\", g.NavWindow ? g.NavWindow->Name : \"NULL\");\n        Text(\"NavId: 0x%08X, NavLayer: %d\", g.NavId, g.NavLayer);\n        DebugLocateItemOnHover(g.NavId);\n        Text(\"NavInputSource: %s\", GetInputSourceName(g.NavInputSource));\n        Text(\"NavLastValidSelectionUserData = %\" IM_PRId64 \" (0x%\" IM_PRIX64 \")\", g.NavLastValidSelectionUserData, g.NavLastValidSelectionUserData);\n        Text(\"NavActive: %d, NavVisible: %d\", g.IO.NavActive, g.IO.NavVisible);\n        Text(\"NavActivateId/DownId/PressedId: %08X/%08X/%08X\", g.NavActivateId, g.NavActivateDownId, g.NavActivatePressedId);\n        Text(\"NavActivateFlags: %04X\", g.NavActivateFlags);\n        Text(\"NavCursorVisible: %d, NavHighlightItemUnderNav: %d\", g.NavCursorVisible, g.NavHighlightItemUnderNav);\n        Text(\"NavFocusScopeId = 0x%08X\", g.NavFocusScopeId);\n        Text(\"NavFocusRoute[] = \");\n        for (int path_n = g.NavFocusRoute.Size - 1; path_n >= 0; path_n--)\n        {\n            const ImGuiFocusScopeData& focus_scope = g.NavFocusRoute[path_n];\n            SameLine(0.0f, 0.0f);\n            Text(\"0x%08X/\", focus_scope.ID);\n            SetItemTooltip(\"In window \\\"%s\\\"\", FindWindowByID(focus_scope.WindowID)->Name);\n        }\n        Text(\"NavWindowingTarget: '%s'\", g.NavWindowingTarget ? g.NavWindowingTarget->Name : \"NULL\");\n        Unindent();\n\n        TreePop();\n    }\n\n    // Overlay: Display windows Rectangles and Begin Order\n    if (cfg->ShowWindowsRects || cfg->ShowWindowsBeginOrder)\n    {\n        for (ImGuiWindow* window : g.Windows)\n        {\n            if (!window->WasActive)\n                continue;\n            ImDrawList* draw_list = GetForegroundDrawList(window);\n            if (cfg->ShowWindowsRects)\n            {\n                ImRect r = Funcs::GetWindowRect(window, cfg->ShowWindowsRectsType);\n                draw_list->AddRect(r.Min, r.Max, IM_COL32(255, 0, 128, 255));\n            }\n            if (cfg->ShowWindowsBeginOrder && !(window->Flags & ImGuiWindowFlags_ChildWindow))\n            {\n                char buf[32];\n                ImFormatString(buf, IM_ARRAYSIZE(buf), \"%d\", window->BeginOrderWithinContext);\n                float font_size = GetFontSize();\n                draw_list->AddRectFilled(window->Pos, window->Pos + ImVec2(font_size, font_size), IM_COL32(200, 100, 100, 255));\n                draw_list->AddText(window->Pos, IM_COL32(255, 255, 255, 255), buf);\n            }\n        }\n    }\n\n    // Overlay: Display Tables Rectangles\n    if (cfg->ShowTablesRects)\n    {\n        for (int table_n = 0; table_n < g.Tables.GetMapSize(); table_n++)\n        {\n            ImGuiTable* table = g.Tables.TryGetMapData(table_n);\n            if (table == NULL || table->LastFrameActive < g.FrameCount - 1)\n                continue;\n            ImDrawList* draw_list = GetForegroundDrawList(table->OuterWindow);\n            if (cfg->ShowTablesRectsType >= TRT_ColumnsRect)\n            {\n                for (int column_n = 0; column_n < table->ColumnsCount; column_n++)\n                {\n                    ImRect r = Funcs::GetTableRect(table, cfg->ShowTablesRectsType, column_n);\n                    ImU32 col = (table->HoveredColumnBody == column_n) ? IM_COL32(255, 255, 128, 255) : IM_COL32(255, 0, 128, 255);\n                    float thickness = (table->HoveredColumnBody == column_n) ? 3.0f : 1.0f;\n                    draw_list->AddRect(r.Min, r.Max, col, 0.0f, 0, thickness);\n                }\n            }\n            else\n            {\n                ImRect r = Funcs::GetTableRect(table, cfg->ShowTablesRectsType, -1);\n                draw_list->AddRect(r.Min, r.Max, IM_COL32(255, 0, 128, 255));\n            }\n        }\n    }\n\n#ifdef IMGUI_HAS_DOCK\n    // Overlay: Display Docking info\n    if (show_docking_nodes && g.IO.KeyCtrl)\n    {\n    }\n#endif // #ifdef IMGUI_HAS_DOCK\n\n    End();\n}\n\nvoid ImGui::DebugBreakClearData()\n{\n    // Those fields are scattered in their respective subsystem to stay in hot-data locations\n    ImGuiContext& g = *GImGui;\n    g.DebugBreakInWindow = 0;\n    g.DebugBreakInTable = 0;\n    g.DebugBreakInShortcutRouting = ImGuiKey_None;\n}\n\nvoid ImGui::DebugBreakButtonTooltip(bool keyboard_only, const char* description_of_location)\n{\n    if (!BeginItemTooltip())\n        return;\n    Text(\"To call IM_DEBUG_BREAK() %s:\", description_of_location);\n    Separator();\n    TextUnformatted(keyboard_only ? \"- Press 'Pause/Break' on keyboard.\" : \"- Press 'Pause/Break' on keyboard.\\n- or Click (may alter focus/active id).\\n- or navigate using keyboard and press space.\");\n    Separator();\n    TextUnformatted(\"Choose one way that doesn't interfere with what you are trying to debug!\\nYou need a debugger attached or this will crash!\");\n    EndTooltip();\n}\n\n// Special button that doesn't take focus, doesn't take input owner, and can be activated without a click etc.\n// In order to reduce interferences with the contents we are trying to debug into.\nbool ImGui::DebugBreakButton(const char* label, const char* description_of_location)\n{\n    ImGuiWindow* window = GetCurrentWindow();\n    if (window->SkipItems)\n        return false;\n\n    ImGuiContext& g = *GImGui;\n    const ImGuiID id = window->GetID(label);\n    const ImVec2 label_size = CalcTextSize(label, NULL, true);\n    ImVec2 pos = window->DC.CursorPos + ImVec2(0.0f, window->DC.CurrLineTextBaseOffset);\n    ImVec2 size = ImVec2(label_size.x + g.Style.FramePadding.x * 2.0f, label_size.y);\n\n    const ImRect bb(pos, pos + size);\n    ItemSize(size, 0.0f);\n    if (!ItemAdd(bb, id))\n        return false;\n\n    // WE DO NOT USE ButtonEx() or ButtonBehavior() in order to reduce our side-effects.\n    bool hovered = ItemHoverable(bb, id, g.CurrentItemFlags);\n    bool pressed = hovered && (IsKeyChordPressed(g.DebugBreakKeyChord) || IsMouseClicked(0) || g.NavActivateId == id);\n    DebugBreakButtonTooltip(false, description_of_location);\n\n    ImVec4 col4f = GetStyleColorVec4(hovered ? ImGuiCol_ButtonHovered : ImGuiCol_Button);\n    ImVec4 hsv;\n    ColorConvertRGBtoHSV(col4f.x, col4f.y, col4f.z, hsv.x, hsv.y, hsv.z);\n    ColorConvertHSVtoRGB(hsv.x + 0.20f, hsv.y, hsv.z, col4f.x, col4f.y, col4f.z);\n\n    RenderNavCursor(bb, id);\n    RenderFrame(bb.Min, bb.Max, GetColorU32(col4f), true, g.Style.FrameRounding);\n    RenderTextClipped(bb.Min, bb.Max, label, NULL, &label_size, g.Style.ButtonTextAlign, &bb);\n\n    IMGUI_TEST_ENGINE_ITEM_INFO(id, label, g.LastItemData.StatusFlags);\n    return pressed;\n}\n\n// [DEBUG] Display contents of Columns\nvoid ImGui::DebugNodeColumns(ImGuiOldColumns* columns)\n{\n    if (!TreeNode((void*)(uintptr_t)columns->ID, \"Columns Id: 0x%08X, Count: %d, Flags: 0x%04X\", columns->ID, columns->Count, columns->Flags))\n        return;\n    BulletText(\"Width: %.1f (MinX: %.1f, MaxX: %.1f)\", columns->OffMaxX - columns->OffMinX, columns->OffMinX, columns->OffMaxX);\n    for (ImGuiOldColumnData& column : columns->Columns)\n        BulletText(\"Column %02d: OffsetNorm %.3f (= %.1f px)\", (int)columns->Columns.index_from_ptr(&column), column.OffsetNorm, GetColumnOffsetFromNorm(columns, column.OffsetNorm));\n    TreePop();\n}\n\n// [DEBUG] Display contents of ImDrawList\nvoid ImGui::DebugNodeDrawList(ImGuiWindow* window, ImGuiViewportP* viewport, const ImDrawList* draw_list, const char* label)\n{\n    ImGuiContext& g = *GImGui;\n    IM_UNUSED(viewport); // Used in docking branch\n    ImGuiMetricsConfig* cfg = &g.DebugMetricsConfig;\n    int cmd_count = draw_list->CmdBuffer.Size;\n    if (cmd_count > 0 && draw_list->CmdBuffer.back().ElemCount == 0 && draw_list->CmdBuffer.back().UserCallback == NULL)\n        cmd_count--;\n    bool node_open = TreeNode(draw_list, \"%s: '%s' %d vtx, %d indices, %d cmds\", label, draw_list->_OwnerName ? draw_list->_OwnerName : \"\", draw_list->VtxBuffer.Size, draw_list->IdxBuffer.Size, cmd_count);\n    if (draw_list == GetWindowDrawList())\n    {\n        SameLine();\n        TextColored(ImVec4(1.0f, 0.4f, 0.4f, 1.0f), \"CURRENTLY APPENDING\"); // Can't display stats for active draw list! (we don't have the data double-buffered)\n        if (node_open)\n            TreePop();\n        return;\n    }\n\n    ImDrawList* fg_draw_list = GetForegroundDrawList(window); // Render additional visuals into the top-most draw list\n    if (window && IsItemHovered() && fg_draw_list)\n        fg_draw_list->AddRect(window->Pos, window->Pos + window->Size, IM_COL32(255, 255, 0, 255));\n    if (!node_open)\n        return;\n\n    if (window && !window->WasActive)\n        TextDisabled(\"Warning: owning Window is inactive. This DrawList is not being rendered!\");\n\n    for (const ImDrawCmd* pcmd = draw_list->CmdBuffer.Data; pcmd < draw_list->CmdBuffer.Data + cmd_count; pcmd++)\n    {\n        if (pcmd->UserCallback)\n        {\n            BulletText(\"Callback %p, user_data %p\", pcmd->UserCallback, pcmd->UserCallbackData);\n            continue;\n        }\n\n        char texid_desc[20];\n        FormatTextureIDForDebugDisplay(texid_desc, IM_ARRAYSIZE(texid_desc), pcmd->TextureId);\n        char buf[300];\n        ImFormatString(buf, IM_ARRAYSIZE(buf), \"DrawCmd:%5d tris, Tex %s, ClipRect (%4.0f,%4.0f)-(%4.0f,%4.0f)\",\n            pcmd->ElemCount / 3, texid_desc, pcmd->ClipRect.x, pcmd->ClipRect.y, pcmd->ClipRect.z, pcmd->ClipRect.w);\n        bool pcmd_node_open = TreeNode((void*)(pcmd - draw_list->CmdBuffer.begin()), \"%s\", buf);\n        if (IsItemHovered() && (cfg->ShowDrawCmdMesh || cfg->ShowDrawCmdBoundingBoxes) && fg_draw_list)\n            DebugNodeDrawCmdShowMeshAndBoundingBox(fg_draw_list, draw_list, pcmd, cfg->ShowDrawCmdMesh, cfg->ShowDrawCmdBoundingBoxes);\n        if (!pcmd_node_open)\n            continue;\n\n        // Calculate approximate coverage area (touched pixel count)\n        // This will be in pixels squared as long there's no post-scaling happening to the renderer output.\n        const ImDrawIdx* idx_buffer = (draw_list->IdxBuffer.Size > 0) ? draw_list->IdxBuffer.Data : NULL;\n        const ImDrawVert* vtx_buffer = draw_list->VtxBuffer.Data + pcmd->VtxOffset;\n        float total_area = 0.0f;\n        for (unsigned int idx_n = pcmd->IdxOffset; idx_n < pcmd->IdxOffset + pcmd->ElemCount; )\n        {\n            ImVec2 triangle[3];\n            for (int n = 0; n < 3; n++, idx_n++)\n                triangle[n] = vtx_buffer[idx_buffer ? idx_buffer[idx_n] : idx_n].pos;\n            total_area += ImTriangleArea(triangle[0], triangle[1], triangle[2]);\n        }\n\n        // Display vertex information summary. Hover to get all triangles drawn in wire-frame\n        ImFormatString(buf, IM_ARRAYSIZE(buf), \"Mesh: ElemCount: %d, VtxOffset: +%d, IdxOffset: +%d, Area: ~%0.f px\", pcmd->ElemCount, pcmd->VtxOffset, pcmd->IdxOffset, total_area);\n        Selectable(buf);\n        if (IsItemHovered() && fg_draw_list)\n            DebugNodeDrawCmdShowMeshAndBoundingBox(fg_draw_list, draw_list, pcmd, true, false);\n\n        // Display individual triangles/vertices. Hover on to get the corresponding triangle highlighted.\n        ImGuiListClipper clipper;\n        clipper.Begin(pcmd->ElemCount / 3); // Manually coarse clip our print out of individual vertices to save CPU, only items that may be visible.\n        while (clipper.Step())\n            for (int prim = clipper.DisplayStart, idx_i = pcmd->IdxOffset + clipper.DisplayStart * 3; prim < clipper.DisplayEnd; prim++)\n            {\n                char* buf_p = buf, * buf_end = buf + IM_ARRAYSIZE(buf);\n                ImVec2 triangle[3];\n                for (int n = 0; n < 3; n++, idx_i++)\n                {\n                    const ImDrawVert& v = vtx_buffer[idx_buffer ? idx_buffer[idx_i] : idx_i];\n                    triangle[n] = v.pos;\n                    buf_p += ImFormatString(buf_p, buf_end - buf_p, \"%s %04d: pos (%8.2f,%8.2f), uv (%.6f,%.6f), col %08X\\n\",\n                        (n == 0) ? \"Vert:\" : \"     \", idx_i, v.pos.x, v.pos.y, v.uv.x, v.uv.y, v.col);\n                }\n\n                Selectable(buf, false);\n                if (fg_draw_list && IsItemHovered())\n                {\n                    ImDrawListFlags backup_flags = fg_draw_list->Flags;\n                    fg_draw_list->Flags &= ~ImDrawListFlags_AntiAliasedLines; // Disable AA on triangle outlines is more readable for very large and thin triangles.\n                    fg_draw_list->AddPolyline(triangle, 3, IM_COL32(255, 255, 0, 255), ImDrawFlags_Closed, 1.0f);\n                    fg_draw_list->Flags = backup_flags;\n                }\n            }\n        TreePop();\n    }\n    TreePop();\n}\n\n// [DEBUG] Display mesh/aabb of a ImDrawCmd\nvoid ImGui::DebugNodeDrawCmdShowMeshAndBoundingBox(ImDrawList* out_draw_list, const ImDrawList* draw_list, const ImDrawCmd* draw_cmd, bool show_mesh, bool show_aabb)\n{\n    IM_ASSERT(show_mesh || show_aabb);\n\n    // Draw wire-frame version of all triangles\n    ImRect clip_rect = draw_cmd->ClipRect;\n    ImRect vtxs_rect(FLT_MAX, FLT_MAX, -FLT_MAX, -FLT_MAX);\n    ImDrawListFlags backup_flags = out_draw_list->Flags;\n    out_draw_list->Flags &= ~ImDrawListFlags_AntiAliasedLines; // Disable AA on triangle outlines is more readable for very large and thin triangles.\n    for (unsigned int idx_n = draw_cmd->IdxOffset, idx_end = draw_cmd->IdxOffset + draw_cmd->ElemCount; idx_n < idx_end; )\n    {\n        ImDrawIdx* idx_buffer = (draw_list->IdxBuffer.Size > 0) ? draw_list->IdxBuffer.Data : NULL; // We don't hold on those pointers past iterations as ->AddPolyline() may invalidate them if out_draw_list==draw_list\n        ImDrawVert* vtx_buffer = draw_list->VtxBuffer.Data + draw_cmd->VtxOffset;\n\n        ImVec2 triangle[3];\n        for (int n = 0; n < 3; n++, idx_n++)\n            vtxs_rect.Add((triangle[n] = vtx_buffer[idx_buffer ? idx_buffer[idx_n] : idx_n].pos));\n        if (show_mesh)\n            out_draw_list->AddPolyline(triangle, 3, IM_COL32(255, 255, 0, 255), ImDrawFlags_Closed, 1.0f); // In yellow: mesh triangles\n    }\n    // Draw bounding boxes\n    if (show_aabb)\n    {\n        out_draw_list->AddRect(ImTrunc(clip_rect.Min), ImTrunc(clip_rect.Max), IM_COL32(255, 0, 255, 255)); // In pink: clipping rectangle submitted to GPU\n        out_draw_list->AddRect(ImTrunc(vtxs_rect.Min), ImTrunc(vtxs_rect.Max), IM_COL32(0, 255, 255, 255)); // In cyan: bounding box of triangles\n    }\n    out_draw_list->Flags = backup_flags;\n}\n\n// [DEBUG] Display details for a single font, called by ShowStyleEditor().\nvoid ImGui::DebugNodeFont(ImFont* font)\n{\n    bool opened = TreeNode(font, \"Font: \\\"%s\\\": %.2f px, %d glyphs, %d sources(s)\",\n        font->Sources ? font->Sources[0].Name : \"\", font->FontSize, font->Glyphs.Size, font->SourcesCount);\n\n    // Display preview text\n    if (!opened)\n        Indent();\n    Indent();\n    PushFont(font);\n    Text(\"The quick brown fox jumps over the lazy dog\");\n    PopFont();\n    if (!opened)\n    {\n        Unindent();\n        Unindent();\n        return;\n    }\n    if (SmallButton(\"Set as default\"))\n        GetIO().FontDefault = font;\n\n    // Display details\n    SetNextItemWidth(GetFontSize() * 8);\n    DragFloat(\"Font scale\", &font->Scale, 0.005f, 0.3f, 2.0f, \"%.1f\");\n    SameLine(); MetricsHelpMarker(\n        \"Note that the default embedded font is NOT meant to be scaled.\\n\\n\"\n        \"Font are currently rendered into bitmaps at a given size at the time of building the atlas. \"\n        \"You may oversample them to get some flexibility with scaling. \"\n        \"You can also render at multiple sizes and select which one to use at runtime.\\n\\n\"\n        \"(Glimmer of hope: the atlas system will be rewritten in the future to make scaling more flexible.)\");\n    Text(\"Ascent: %f, Descent: %f, Height: %f\", font->Ascent, font->Descent, font->Ascent - font->Descent);\n    char c_str[5];\n    Text(\"Fallback character: '%s' (U+%04X)\", ImTextCharToUtf8(c_str, font->FallbackChar), font->FallbackChar);\n    Text(\"Ellipsis character: '%s' (U+%04X)\", ImTextCharToUtf8(c_str, font->EllipsisChar), font->EllipsisChar);\n    const int surface_sqrt = (int)ImSqrt((float)font->MetricsTotalSurface);\n    Text(\"Texture Area: about %d px ~%dx%d px\", font->MetricsTotalSurface, surface_sqrt, surface_sqrt);\n    for (int config_i = 0; config_i < font->SourcesCount; config_i++)\n        if (font->Sources)\n        {\n            const ImFontConfig* src = &font->Sources[config_i];\n            int oversample_h, oversample_v;\n            ImFontAtlasBuildGetOversampleFactors(src, &oversample_h, &oversample_v);\n            BulletText(\"Input %d: \\'%s\\', Oversample: (%d=>%d,%d=>%d), PixelSnapH: %d, Offset: (%.1f,%.1f)\",\n                config_i, src->Name, src->OversampleH, oversample_h, src->OversampleV, oversample_v, src->PixelSnapH, src->GlyphOffset.x, src->GlyphOffset.y);\n        }\n\n    // Display all glyphs of the fonts in separate pages of 256 characters\n    {\n        if (TreeNode(\"Glyphs\", \"Glyphs (%d)\", font->Glyphs.Size))\n        {\n            ImDrawList* draw_list = GetWindowDrawList();\n            const ImU32 glyph_col = GetColorU32(ImGuiCol_Text);\n            const float cell_size = font->FontSize * 1;\n            const float cell_spacing = GetStyle().ItemSpacing.y;\n            for (unsigned int base = 0; base <= IM_UNICODE_CODEPOINT_MAX; base += 256)\n            {\n                // Skip ahead if a large bunch of glyphs are not present in the font (test in chunks of 4k)\n                // This is only a small optimization to reduce the number of iterations when IM_UNICODE_MAX_CODEPOINT\n                // is large // (if ImWchar==ImWchar32 we will do at least about 272 queries here)\n                if (!(base & 8191) && font->IsGlyphRangeUnused(base, base + 8191))\n                {\n                    base += 8192 - 256;\n                    continue;\n                }\n\n                int count = 0;\n                for (unsigned int n = 0; n < 256; n++)\n                    if (font->FindGlyphNoFallback((ImWchar)(base + n)))\n                        count++;\n                if (count <= 0)\n                    continue;\n                if (!TreeNode((void*)(intptr_t)base, \"U+%04X..U+%04X (%d %s)\", base, base + 255, count, count > 1 ? \"glyphs\" : \"glyph\"))\n                    continue;\n\n                // Draw a 16x16 grid of glyphs\n                ImVec2 base_pos = GetCursorScreenPos();\n                for (unsigned int n = 0; n < 256; n++)\n                {\n                    // We use ImFont::RenderChar as a shortcut because we don't have UTF-8 conversion functions\n                    // available here and thus cannot easily generate a zero-terminated UTF-8 encoded string.\n                    ImVec2 cell_p1(base_pos.x + (n % 16) * (cell_size + cell_spacing), base_pos.y + (n / 16) * (cell_size + cell_spacing));\n                    ImVec2 cell_p2(cell_p1.x + cell_size, cell_p1.y + cell_size);\n                    const ImFontGlyph* glyph = font->FindGlyphNoFallback((ImWchar)(base + n));\n                    draw_list->AddRect(cell_p1, cell_p2, glyph ? IM_COL32(255, 255, 255, 100) : IM_COL32(255, 255, 255, 50));\n                    if (!glyph)\n                        continue;\n                    font->RenderChar(draw_list, cell_size, cell_p1, glyph_col, (ImWchar)(base + n));\n                    if (IsMouseHoveringRect(cell_p1, cell_p2) && BeginTooltip())\n                    {\n                        DebugNodeFontGlyph(font, glyph);\n                        EndTooltip();\n                    }\n                }\n                Dummy(ImVec2((cell_size + cell_spacing) * 16, (cell_size + cell_spacing) * 16));\n                TreePop();\n            }\n            TreePop();\n        }\n    }\n    TreePop();\n    Unindent();\n}\n\nvoid ImGui::DebugNodeFontGlyph(ImFont*, const ImFontGlyph* glyph)\n{\n    Text(\"Codepoint: U+%04X\", glyph->Codepoint);\n    Separator();\n    Text(\"Visible: %d\", glyph->Visible);\n    Text(\"AdvanceX: %.1f\", glyph->AdvanceX);\n    Text(\"Pos: (%.2f,%.2f)->(%.2f,%.2f)\", glyph->X0, glyph->Y0, glyph->X1, glyph->Y1);\n    Text(\"UV: (%.3f,%.3f)->(%.3f,%.3f)\", glyph->U0, glyph->V0, glyph->U1, glyph->V1);\n}\n\n// [DEBUG] Display contents of ImGuiStorage\nvoid ImGui::DebugNodeStorage(ImGuiStorage* storage, const char* label)\n{\n    if (!TreeNode(label, \"%s: %d entries, %d bytes\", label, storage->Data.Size, storage->Data.size_in_bytes()))\n        return;\n    for (const ImGuiStoragePair& p : storage->Data)\n    {\n        BulletText(\"Key 0x%08X Value { i: %d }\", p.key, p.val_i); // Important: we currently don't store a type, real value may not be integer.\n        DebugLocateItemOnHover(p.key);\n    }\n    TreePop();\n}\n\n// [DEBUG] Display contents of ImGuiTabBar\nvoid ImGui::DebugNodeTabBar(ImGuiTabBar* tab_bar, const char* label)\n{\n    // Standalone tab bars (not associated to docking/windows functionality) currently hold no discernible strings.\n    char buf[256];\n    char* p = buf;\n    const char* buf_end = buf + IM_ARRAYSIZE(buf);\n    const bool is_active = (tab_bar->PrevFrameVisible >= GetFrameCount() - 2);\n    p += ImFormatString(p, buf_end - p, \"%s 0x%08X (%d tabs)%s  {\", label, tab_bar->ID, tab_bar->Tabs.Size, is_active ? \"\" : \" *Inactive*\");\n    for (int tab_n = 0; tab_n < ImMin(tab_bar->Tabs.Size, 3); tab_n++)\n    {\n        ImGuiTabItem* tab = &tab_bar->Tabs[tab_n];\n        p += ImFormatString(p, buf_end - p, \"%s'%s'\", tab_n > 0 ? \", \" : \"\", TabBarGetTabName(tab_bar, tab));\n    }\n    p += ImFormatString(p, buf_end - p, (tab_bar->Tabs.Size > 3) ? \" ... }\" : \" } \");\n    if (!is_active) { PushStyleColor(ImGuiCol_Text, GetStyleColorVec4(ImGuiCol_TextDisabled)); }\n    bool open = TreeNode(label, \"%s\", buf);\n    if (!is_active) { PopStyleColor(); }\n    if (is_active && IsItemHovered())\n    {\n        ImDrawList* draw_list = GetForegroundDrawList();\n        draw_list->AddRect(tab_bar->BarRect.Min, tab_bar->BarRect.Max, IM_COL32(255, 255, 0, 255));\n        draw_list->AddLine(ImVec2(tab_bar->ScrollingRectMinX, tab_bar->BarRect.Min.y), ImVec2(tab_bar->ScrollingRectMinX, tab_bar->BarRect.Max.y), IM_COL32(0, 255, 0, 255));\n        draw_list->AddLine(ImVec2(tab_bar->ScrollingRectMaxX, tab_bar->BarRect.Min.y), ImVec2(tab_bar->ScrollingRectMaxX, tab_bar->BarRect.Max.y), IM_COL32(0, 255, 0, 255));\n    }\n    if (open)\n    {\n        for (int tab_n = 0; tab_n < tab_bar->Tabs.Size; tab_n++)\n        {\n            ImGuiTabItem* tab = &tab_bar->Tabs[tab_n];\n            PushID(tab);\n            if (SmallButton(\"<\")) { TabBarQueueReorder(tab_bar, tab, -1); } SameLine(0, 2);\n            if (SmallButton(\">\")) { TabBarQueueReorder(tab_bar, tab, +1); } SameLine();\n            Text(\"%02d%c Tab 0x%08X '%s' Offset: %.2f, Width: %.2f/%.2f\",\n                tab_n, (tab->ID == tab_bar->SelectedTabId) ? '*' : ' ', tab->ID, TabBarGetTabName(tab_bar, tab), tab->Offset, tab->Width, tab->ContentWidth);\n            PopID();\n        }\n        TreePop();\n    }\n}\n\nvoid ImGui::DebugNodeViewport(ImGuiViewportP* viewport)\n{\n    ImGuiContext& g = *GImGui;\n    SetNextItemOpen(true, ImGuiCond_Once);\n    bool open = TreeNode(\"viewport0\", \"Viewport #%d\", 0);\n    if (IsItemHovered())\n        g.DebugMetricsConfig.HighlightViewportID = viewport->ID;\n    if (open)\n    {\n        ImGuiWindowFlags flags = viewport->Flags;\n        BulletText(\"Main Pos: (%.0f,%.0f), Size: (%.0f,%.0f)\\nWorkArea Inset Left: %.0f Top: %.0f, Right: %.0f, Bottom: %.0f\",\n            viewport->Pos.x, viewport->Pos.y, viewport->Size.x, viewport->Size.y,\n            viewport->WorkInsetMin.x, viewport->WorkInsetMin.y, viewport->WorkInsetMax.x, viewport->WorkInsetMax.y);\n        BulletText(\"Flags: 0x%04X =%s%s%s\", viewport->Flags,\n            (flags & ImGuiViewportFlags_IsPlatformWindow)  ? \" IsPlatformWindow\"  : \"\",\n            (flags & ImGuiViewportFlags_IsPlatformMonitor) ? \" IsPlatformMonitor\" : \"\",\n            (flags & ImGuiViewportFlags_OwnedByApp)        ? \" OwnedByApp\"        : \"\");\n        for (ImDrawList* draw_list : viewport->DrawDataP.CmdLists)\n            DebugNodeDrawList(NULL, viewport, draw_list, \"DrawList\");\n        TreePop();\n    }\n}\n\nvoid ImGui::DebugNodeWindow(ImGuiWindow* window, const char* label)\n{\n    if (window == NULL)\n    {\n        BulletText(\"%s: NULL\", label);\n        return;\n    }\n\n    ImGuiContext& g = *GImGui;\n    const bool is_active = window->WasActive;\n    ImGuiTreeNodeFlags tree_node_flags = (window == g.NavWindow) ? ImGuiTreeNodeFlags_Selected : ImGuiTreeNodeFlags_None;\n    if (!is_active) { PushStyleColor(ImGuiCol_Text, GetStyleColorVec4(ImGuiCol_TextDisabled)); }\n    const bool open = TreeNodeEx(label, tree_node_flags, \"%s '%s'%s\", label, window->Name, is_active ? \"\" : \" *Inactive*\");\n    if (!is_active) { PopStyleColor(); }\n    if (IsItemHovered() && is_active)\n        GetForegroundDrawList(window)->AddRect(window->Pos, window->Pos + window->Size, IM_COL32(255, 255, 0, 255));\n    if (!open)\n        return;\n\n    if (window->MemoryCompacted)\n        TextDisabled(\"Note: some memory buffers have been compacted/freed.\");\n\n    if (g.IO.ConfigDebugIsDebuggerPresent && DebugBreakButton(\"**DebugBreak**\", \"in Begin()\"))\n        g.DebugBreakInWindow = window->ID;\n\n    ImGuiWindowFlags flags = window->Flags;\n    DebugNodeDrawList(window, window->Viewport, window->DrawList, \"DrawList\");\n    BulletText(\"Pos: (%.1f,%.1f), Size: (%.1f,%.1f), ContentSize (%.1f,%.1f) Ideal (%.1f,%.1f)\", window->Pos.x, window->Pos.y, window->Size.x, window->Size.y, window->ContentSize.x, window->ContentSize.y, window->ContentSizeIdeal.x, window->ContentSizeIdeal.y);\n    BulletText(\"Flags: 0x%08X (%s%s%s%s%s%s%s%s%s..)\", flags,\n        (flags & ImGuiWindowFlags_ChildWindow)  ? \"Child \" : \"\",      (flags & ImGuiWindowFlags_Tooltip)     ? \"Tooltip \"   : \"\",  (flags & ImGuiWindowFlags_Popup) ? \"Popup \" : \"\",\n        (flags & ImGuiWindowFlags_Modal)        ? \"Modal \" : \"\",      (flags & ImGuiWindowFlags_ChildMenu)   ? \"ChildMenu \" : \"\",  (flags & ImGuiWindowFlags_NoSavedSettings) ? \"NoSavedSettings \" : \"\",\n        (flags & ImGuiWindowFlags_NoMouseInputs)? \"NoMouseInputs\":\"\", (flags & ImGuiWindowFlags_NoNavInputs) ? \"NoNavInputs\" : \"\", (flags & ImGuiWindowFlags_AlwaysAutoResize) ? \"AlwaysAutoResize\" : \"\");\n    if (flags & ImGuiWindowFlags_ChildWindow)\n        BulletText(\"ChildFlags: 0x%08X (%s%s%s%s..)\", window->ChildFlags,\n            (window->ChildFlags & ImGuiChildFlags_Borders) ? \"Borders \" : \"\",\n            (window->ChildFlags & ImGuiChildFlags_ResizeX) ? \"ResizeX \" : \"\",\n            (window->ChildFlags & ImGuiChildFlags_ResizeY) ? \"ResizeY \" : \"\",\n            (window->ChildFlags & ImGuiChildFlags_NavFlattened) ? \"NavFlattened \" : \"\");\n    BulletText(\"Scroll: (%.2f/%.2f,%.2f/%.2f) Scrollbar:%s%s\", window->Scroll.x, window->ScrollMax.x, window->Scroll.y, window->ScrollMax.y, window->ScrollbarX ? \"X\" : \"\", window->ScrollbarY ? \"Y\" : \"\");\n    BulletText(\"Active: %d/%d, WriteAccessed: %d, BeginOrderWithinContext: %d\", window->Active, window->WasActive, window->WriteAccessed, (window->Active || window->WasActive) ? window->BeginOrderWithinContext : -1);\n    BulletText(\"Appearing: %d, Hidden: %d (CanSkip %d Cannot %d), SkipItems: %d\", window->Appearing, window->Hidden, window->HiddenFramesCanSkipItems, window->HiddenFramesCannotSkipItems, window->SkipItems);\n    for (int layer = 0; layer < ImGuiNavLayer_COUNT; layer++)\n    {\n        ImRect r = window->NavRectRel[layer];\n        if (r.Min.x >= r.Max.x && r.Min.y >= r.Max.y)\n            BulletText(\"NavLastIds[%d]: 0x%08X\", layer, window->NavLastIds[layer]);\n        else\n            BulletText(\"NavLastIds[%d]: 0x%08X at +(%.1f,%.1f)(%.1f,%.1f)\", layer, window->NavLastIds[layer], r.Min.x, r.Min.y, r.Max.x, r.Max.y);\n        DebugLocateItemOnHover(window->NavLastIds[layer]);\n    }\n    const ImVec2* pr = window->NavPreferredScoringPosRel;\n    for (int layer = 0; layer < ImGuiNavLayer_COUNT; layer++)\n        BulletText(\"NavPreferredScoringPosRel[%d] = {%.1f,%.1f)\", layer, (pr[layer].x == FLT_MAX ? -99999.0f : pr[layer].x), (pr[layer].y == FLT_MAX ? -99999.0f : pr[layer].y)); // Display as 99999.0f so it looks neater.\n    BulletText(\"NavLayersActiveMask: %X, NavLastChildNavWindow: %s\", window->DC.NavLayersActiveMask, window->NavLastChildNavWindow ? window->NavLastChildNavWindow->Name : \"NULL\");\n    if (window->RootWindow != window)               { DebugNodeWindow(window->RootWindow, \"RootWindow\"); }\n    if (window->ParentWindow != NULL)               { DebugNodeWindow(window->ParentWindow, \"ParentWindow\"); }\n    if (window->ParentWindowForFocusRoute != NULL)  { DebugNodeWindow(window->ParentWindowForFocusRoute, \"ParentWindowForFocusRoute\"); }\n    if (window->DC.ChildWindows.Size > 0)           { DebugNodeWindowsList(&window->DC.ChildWindows, \"ChildWindows\"); }\n    if (window->ColumnsStorage.Size > 0 && TreeNode(\"Columns\", \"Columns sets (%d)\", window->ColumnsStorage.Size))\n    {\n        for (ImGuiOldColumns& columns : window->ColumnsStorage)\n            DebugNodeColumns(&columns);\n        TreePop();\n    }\n    DebugNodeStorage(&window->StateStorage, \"Storage\");\n    TreePop();\n}\n\nvoid ImGui::DebugNodeWindowSettings(ImGuiWindowSettings* settings)\n{\n    if (settings->WantDelete)\n        BeginDisabled();\n    Text(\"0x%08X \\\"%s\\\" Pos (%d,%d) Size (%d,%d) Collapsed=%d\",\n        settings->ID, settings->GetName(), settings->Pos.x, settings->Pos.y, settings->Size.x, settings->Size.y, settings->Collapsed);\n    if (settings->WantDelete)\n        EndDisabled();\n}\n\nvoid ImGui::DebugNodeWindowsList(ImVector<ImGuiWindow*>* windows, const char* label)\n{\n    if (!TreeNode(label, \"%s (%d)\", label, windows->Size))\n        return;\n    for (int i = windows->Size - 1; i >= 0; i--) // Iterate front to back\n    {\n        PushID((*windows)[i]);\n        DebugNodeWindow((*windows)[i], \"Window\");\n        PopID();\n    }\n    TreePop();\n}\n\n// FIXME-OPT: This is technically suboptimal, but it is simpler this way.\nvoid ImGui::DebugNodeWindowsListByBeginStackParent(ImGuiWindow** windows, int windows_size, ImGuiWindow* parent_in_begin_stack)\n{\n    for (int i = 0; i < windows_size; i++)\n    {\n        ImGuiWindow* window = windows[i];\n        if (window->ParentWindowInBeginStack != parent_in_begin_stack)\n            continue;\n        char buf[20];\n        ImFormatString(buf, IM_ARRAYSIZE(buf), \"[%04d] Window\", window->BeginOrderWithinContext);\n        //BulletText(\"[%04d] Window '%s'\", window->BeginOrderWithinContext, window->Name);\n        DebugNodeWindow(window, buf);\n        Indent();\n        DebugNodeWindowsListByBeginStackParent(windows + i + 1, windows_size - i - 1, window);\n        Unindent();\n    }\n}\n\n//-----------------------------------------------------------------------------\n// [SECTION] DEBUG LOG WINDOW\n//-----------------------------------------------------------------------------\n\nvoid ImGui::DebugLog(const char* fmt, ...)\n{\n    va_list args;\n    va_start(args, fmt);\n    DebugLogV(fmt, args);\n    va_end(args);\n}\n\nvoid ImGui::DebugLogV(const char* fmt, va_list args)\n{\n    ImGuiContext& g = *GImGui;\n    const int old_size = g.DebugLogBuf.size();\n    if (g.ContextName[0] != 0)\n        g.DebugLogBuf.appendf(\"[%s] [%05d] \", g.ContextName, g.FrameCount);\n    else\n        g.DebugLogBuf.appendf(\"[%05d] \", g.FrameCount);\n    g.DebugLogBuf.appendfv(fmt, args);\n    g.DebugLogIndex.append(g.DebugLogBuf.c_str(), old_size, g.DebugLogBuf.size());\n    if (g.DebugLogFlags & ImGuiDebugLogFlags_OutputToTTY)\n        IMGUI_DEBUG_PRINTF(\"%s\", g.DebugLogBuf.begin() + old_size);\n#ifdef IMGUI_ENABLE_TEST_ENGINE\n    // IMGUI_TEST_ENGINE_LOG() adds a trailing \\n automatically\n    const int new_size = g.DebugLogBuf.size();\n    const bool trailing_carriage_return = (g.DebugLogBuf[new_size - 1] == '\\n');\n    if (g.DebugLogFlags & ImGuiDebugLogFlags_OutputToTestEngine)\n        IMGUI_TEST_ENGINE_LOG(\"%.*s\", new_size - old_size - (trailing_carriage_return ? 1 : 0), g.DebugLogBuf.begin() + old_size);\n#endif\n}\n\n// FIXME-LAYOUT: To be done automatically via layout mode once we rework ItemSize/ItemAdd into ItemLayout.\nstatic void SameLineOrWrap(const ImVec2& size)\n{\n    ImGuiContext& g = *GImGui;\n    ImGuiWindow* window = g.CurrentWindow;\n    ImVec2 pos(window->DC.CursorPosPrevLine.x + g.Style.ItemSpacing.x, window->DC.CursorPosPrevLine.y);\n    if (window->WorkRect.Contains(ImRect(pos, pos + size)))\n        ImGui::SameLine();\n}\n\nstatic void ShowDebugLogFlag(const char* name, ImGuiDebugLogFlags flags)\n{\n    ImGuiContext& g = *GImGui;\n    ImVec2 size(ImGui::GetFrameHeight() + g.Style.ItemInnerSpacing.x + ImGui::CalcTextSize(name).x, ImGui::GetFrameHeight());\n    SameLineOrWrap(size); // FIXME-LAYOUT: To be done automatically once we rework ItemSize/ItemAdd into ItemLayout.\n\n    bool highlight_errors = (flags == ImGuiDebugLogFlags_EventError && g.DebugLogSkippedErrors > 0);\n    if (highlight_errors)\n        ImGui::PushStyleColor(ImGuiCol_Text, ImLerp(g.Style.Colors[ImGuiCol_Text], ImVec4(1.0f, 0.0f, 0.0f, 1.0f), 0.30f));\n    if (ImGui::CheckboxFlags(name, &g.DebugLogFlags, flags) && g.IO.KeyShift && (g.DebugLogFlags & flags) != 0)\n    {\n        g.DebugLogAutoDisableFrames = 2;\n        g.DebugLogAutoDisableFlags |= flags;\n    }\n    if (highlight_errors)\n    {\n        ImGui::PopStyleColor();\n        ImGui::SetItemTooltip(\"%d past errors skipped.\", g.DebugLogSkippedErrors);\n    }\n    else\n    {\n        ImGui::SetItemTooltip(\"Hold SHIFT when clicking to enable for 2 frames only (useful for spammy log entries)\");\n    }\n}\n\nvoid ImGui::ShowDebugLogWindow(bool* p_open)\n{\n    ImGuiContext& g = *GImGui;\n    if ((g.NextWindowData.HasFlags & ImGuiNextWindowDataFlags_HasSize) == 0)\n        SetNextWindowSize(ImVec2(0.0f, GetFontSize() * 12.0f), ImGuiCond_FirstUseEver);\n    if (!Begin(\"Dear ImGui Debug Log\", p_open) || GetCurrentWindow()->BeginCount > 1)\n    {\n        End();\n        return;\n    }\n\n    ImGuiDebugLogFlags all_enable_flags = ImGuiDebugLogFlags_EventMask_ & ~ImGuiDebugLogFlags_EventInputRouting;\n    CheckboxFlags(\"All\", &g.DebugLogFlags, all_enable_flags);\n    SetItemTooltip(\"(except InputRouting which is spammy)\");\n\n    ShowDebugLogFlag(\"Errors\", ImGuiDebugLogFlags_EventError);\n    ShowDebugLogFlag(\"ActiveId\", ImGuiDebugLogFlags_EventActiveId);\n    ShowDebugLogFlag(\"Clipper\", ImGuiDebugLogFlags_EventClipper);\n    ShowDebugLogFlag(\"Focus\", ImGuiDebugLogFlags_EventFocus);\n    ShowDebugLogFlag(\"IO\", ImGuiDebugLogFlags_EventIO);\n    //ShowDebugLogFlag(\"Font\", ImGuiDebugLogFlags_EventFont);\n    ShowDebugLogFlag(\"Nav\", ImGuiDebugLogFlags_EventNav);\n    ShowDebugLogFlag(\"Popup\", ImGuiDebugLogFlags_EventPopup);\n    ShowDebugLogFlag(\"Selection\", ImGuiDebugLogFlags_EventSelection);\n    ShowDebugLogFlag(\"InputRouting\", ImGuiDebugLogFlags_EventInputRouting);\n\n    if (SmallButton(\"Clear\"))\n    {\n        g.DebugLogBuf.clear();\n        g.DebugLogIndex.clear();\n        g.DebugLogSkippedErrors = 0;\n    }\n    SameLine();\n    if (SmallButton(\"Copy\"))\n        SetClipboardText(g.DebugLogBuf.c_str());\n    SameLine();\n    if (SmallButton(\"Configure Outputs..\"))\n        OpenPopup(\"Outputs\");\n    if (BeginPopup(\"Outputs\"))\n    {\n        CheckboxFlags(\"OutputToTTY\", &g.DebugLogFlags, ImGuiDebugLogFlags_OutputToTTY);\n#ifndef IMGUI_ENABLE_TEST_ENGINE\n        BeginDisabled();\n#endif\n        CheckboxFlags(\"OutputToTestEngine\", &g.DebugLogFlags, ImGuiDebugLogFlags_OutputToTestEngine);\n#ifndef IMGUI_ENABLE_TEST_ENGINE\n        EndDisabled();\n#endif\n        EndPopup();\n    }\n\n    BeginChild(\"##log\", ImVec2(0.0f, 0.0f), ImGuiChildFlags_Borders, ImGuiWindowFlags_AlwaysVerticalScrollbar | ImGuiWindowFlags_AlwaysHorizontalScrollbar);\n\n    const ImGuiDebugLogFlags backup_log_flags = g.DebugLogFlags;\n    g.DebugLogFlags &= ~ImGuiDebugLogFlags_EventClipper;\n\n    ImGuiListClipper clipper;\n    clipper.Begin(g.DebugLogIndex.size());\n    while (clipper.Step())\n        for (int line_no = clipper.DisplayStart; line_no < clipper.DisplayEnd; line_no++)\n            DebugTextUnformattedWithLocateItem(g.DebugLogIndex.get_line_begin(g.DebugLogBuf.c_str(), line_no), g.DebugLogIndex.get_line_end(g.DebugLogBuf.c_str(), line_no));\n    g.DebugLogFlags = backup_log_flags;\n    if (GetScrollY() >= GetScrollMaxY())\n        SetScrollHereY(1.0f);\n    EndChild();\n\n    End();\n}\n\n// Display line, search for 0xXXXXXXXX identifiers and call DebugLocateItemOnHover() when hovered.\nvoid ImGui::DebugTextUnformattedWithLocateItem(const char* line_begin, const char* line_end)\n{\n    TextUnformatted(line_begin, line_end);\n    if (!IsItemHovered(ImGuiHoveredFlags_AllowWhenBlockedByPopup | ImGuiHoveredFlags_AllowWhenBlockedByActiveItem))\n        return;\n    ImGuiContext& g = *GImGui;\n    ImRect text_rect = g.LastItemData.Rect;\n    for (const char* p = line_begin; p <= line_end - 10; p++)\n    {\n        ImGuiID id = 0;\n        if (p[0] != '0' || (p[1] != 'x' && p[1] != 'X') || sscanf(p + 2, \"%X\", &id) != 1 || ImCharIsXdigitA(p[10]))\n            continue;\n        ImVec2 p0 = CalcTextSize(line_begin, p);\n        ImVec2 p1 = CalcTextSize(p, p + 10);\n        g.LastItemData.Rect = ImRect(text_rect.Min + ImVec2(p0.x, 0.0f), text_rect.Min + ImVec2(p0.x + p1.x, p1.y));\n        if (IsMouseHoveringRect(g.LastItemData.Rect.Min, g.LastItemData.Rect.Max, true))\n            DebugLocateItemOnHover(id);\n        p += 10;\n    }\n}\n\n//-----------------------------------------------------------------------------\n// [SECTION] OTHER DEBUG TOOLS (ITEM PICKER, ID STACK TOOL)\n//-----------------------------------------------------------------------------\n\n// Draw a small cross at current CursorPos in current window's DrawList\nvoid ImGui::DebugDrawCursorPos(ImU32 col)\n{\n    ImGuiContext& g = *GImGui;\n    ImGuiWindow* window = g.CurrentWindow;\n    ImVec2 pos = window->DC.CursorPos;\n    window->DrawList->AddLine(ImVec2(pos.x, pos.y - 3.0f), ImVec2(pos.x, pos.y + 4.0f), col, 1.0f);\n    window->DrawList->AddLine(ImVec2(pos.x - 3.0f, pos.y), ImVec2(pos.x + 4.0f, pos.y), col, 1.0f);\n}\n\n// Draw a 10px wide rectangle around CurposPos.x using Line Y1/Y2 in current window's DrawList\nvoid ImGui::DebugDrawLineExtents(ImU32 col)\n{\n    ImGuiContext& g = *GImGui;\n    ImGuiWindow* window = g.CurrentWindow;\n    float curr_x = window->DC.CursorPos.x;\n    float line_y1 = (window->DC.IsSameLine ? window->DC.CursorPosPrevLine.y : window->DC.CursorPos.y);\n    float line_y2 = line_y1 + (window->DC.IsSameLine ? window->DC.PrevLineSize.y : window->DC.CurrLineSize.y);\n    window->DrawList->AddLine(ImVec2(curr_x - 5.0f, line_y1), ImVec2(curr_x + 5.0f, line_y1), col, 1.0f);\n    window->DrawList->AddLine(ImVec2(curr_x - 0.5f, line_y1), ImVec2(curr_x - 0.5f, line_y2), col, 1.0f);\n    window->DrawList->AddLine(ImVec2(curr_x - 5.0f, line_y2), ImVec2(curr_x + 5.0f, line_y2), col, 1.0f);\n}\n\n// Draw last item rect in ForegroundDrawList (so it is always visible)\nvoid ImGui::DebugDrawItemRect(ImU32 col)\n{\n    ImGuiContext& g = *GImGui;\n    ImGuiWindow* window = g.CurrentWindow;\n    GetForegroundDrawList(window)->AddRect(g.LastItemData.Rect.Min, g.LastItemData.Rect.Max, col);\n}\n\n// [DEBUG] Locate item position/rectangle given an ID.\nstatic const ImU32 DEBUG_LOCATE_ITEM_COLOR = IM_COL32(0, 255, 0, 255);  // Green\n\nvoid ImGui::DebugLocateItem(ImGuiID target_id)\n{\n    ImGuiContext& g = *GImGui;\n    g.DebugLocateId = target_id;\n    g.DebugLocateFrames = 2;\n    g.DebugBreakInLocateId = false;\n}\n\n// FIXME: Doesn't work over through a modal window, because they clear HoveredWindow.\nvoid ImGui::DebugLocateItemOnHover(ImGuiID target_id)\n{\n    if (target_id == 0 || !IsItemHovered(ImGuiHoveredFlags_AllowWhenBlockedByActiveItem | ImGuiHoveredFlags_AllowWhenBlockedByPopup))\n        return;\n    ImGuiContext& g = *GImGui;\n    DebugLocateItem(target_id);\n    GetForegroundDrawList(g.CurrentWindow)->AddRect(g.LastItemData.Rect.Min - ImVec2(3.0f, 3.0f), g.LastItemData.Rect.Max + ImVec2(3.0f, 3.0f), DEBUG_LOCATE_ITEM_COLOR);\n\n    // Can't easily use a context menu here because it will mess with focus, active id etc.\n    if (g.IO.ConfigDebugIsDebuggerPresent && g.MouseStationaryTimer > 1.0f)\n    {\n        DebugBreakButtonTooltip(false, \"in ItemAdd()\");\n        if (IsKeyChordPressed(g.DebugBreakKeyChord))\n            g.DebugBreakInLocateId = true;\n    }\n}\n\nvoid ImGui::DebugLocateItemResolveWithLastItem()\n{\n    ImGuiContext& g = *GImGui;\n\n    // [DEBUG] Debug break requested by user\n    if (g.DebugBreakInLocateId)\n        IM_DEBUG_BREAK();\n\n    ImGuiLastItemData item_data = g.LastItemData;\n    g.DebugLocateId = 0;\n    ImDrawList* draw_list = GetForegroundDrawList(g.CurrentWindow);\n    ImRect r = item_data.Rect;\n    r.Expand(3.0f);\n    ImVec2 p1 = g.IO.MousePos;\n    ImVec2 p2 = ImVec2((p1.x < r.Min.x) ? r.Min.x : (p1.x > r.Max.x) ? r.Max.x : p1.x, (p1.y < r.Min.y) ? r.Min.y : (p1.y > r.Max.y) ? r.Max.y : p1.y);\n    draw_list->AddRect(r.Min, r.Max, DEBUG_LOCATE_ITEM_COLOR);\n    draw_list->AddLine(p1, p2, DEBUG_LOCATE_ITEM_COLOR);\n}\n\nvoid ImGui::DebugStartItemPicker()\n{\n    ImGuiContext& g = *GImGui;\n    g.DebugItemPickerActive = true;\n}\n\n// [DEBUG] Item picker tool - start with DebugStartItemPicker() - useful to visually select an item and break into its call-stack.\nvoid ImGui::UpdateDebugToolItemPicker()\n{\n    ImGuiContext& g = *GImGui;\n    g.DebugItemPickerBreakId = 0;\n    if (!g.DebugItemPickerActive)\n        return;\n\n    const ImGuiID hovered_id = g.HoveredIdPreviousFrame;\n    SetMouseCursor(ImGuiMouseCursor_Hand);\n    if (IsKeyPressed(ImGuiKey_Escape))\n        g.DebugItemPickerActive = false;\n    const bool change_mapping = g.IO.KeyMods == (ImGuiMod_Ctrl | ImGuiMod_Shift);\n    if (!change_mapping && IsMouseClicked(g.DebugItemPickerMouseButton) && hovered_id)\n    {\n        g.DebugItemPickerBreakId = hovered_id;\n        g.DebugItemPickerActive = false;\n    }\n    for (int mouse_button = 0; mouse_button < 3; mouse_button++)\n        if (change_mapping && IsMouseClicked(mouse_button))\n            g.DebugItemPickerMouseButton = (ImU8)mouse_button;\n    SetNextWindowBgAlpha(0.70f);\n    if (!BeginTooltip())\n        return;\n    Text(\"HoveredId: 0x%08X\", hovered_id);\n    Text(\"Press ESC to abort picking.\");\n    const char* mouse_button_names[] = { \"Left\", \"Right\", \"Middle\" };\n    if (change_mapping)\n        Text(\"Remap w/ Ctrl+Shift: click anywhere to select new mouse button.\");\n    else\n        TextColored(GetStyleColorVec4(hovered_id ? ImGuiCol_Text : ImGuiCol_TextDisabled), \"Click %s Button to break in debugger! (remap w/ Ctrl+Shift)\", mouse_button_names[g.DebugItemPickerMouseButton]);\n    EndTooltip();\n}\n\n// [DEBUG] ID Stack Tool: update queries. Called by NewFrame()\nvoid ImGui::UpdateDebugToolStackQueries()\n{\n    ImGuiContext& g = *GImGui;\n    ImGuiIDStackTool* tool = &g.DebugIDStackTool;\n\n    // Clear hook when id stack tool is not visible\n    g.DebugHookIdInfo = 0;\n    if (g.FrameCount != tool->LastActiveFrame + 1)\n        return;\n\n    // Update queries. The steps are: -1: query Stack, >= 0: query each stack item\n    // We can only perform 1 ID Info query every frame. This is designed so the GetID() tests are cheap and constant-time\n    const ImGuiID query_id = g.HoveredIdPreviousFrame ? g.HoveredIdPreviousFrame : g.ActiveId;\n    if (tool->QueryId != query_id)\n    {\n        tool->QueryId = query_id;\n        tool->StackLevel = -1;\n        tool->Results.resize(0);\n    }\n    if (query_id == 0)\n        return;\n\n    // Advance to next stack level when we got our result, or after 2 frames (in case we never get a result)\n    int stack_level = tool->StackLevel;\n    if (stack_level >= 0 && stack_level < tool->Results.Size)\n        if (tool->Results[stack_level].QuerySuccess || tool->Results[stack_level].QueryFrameCount > 2)\n            tool->StackLevel++;\n\n    // Update hook\n    stack_level = tool->StackLevel;\n    if (stack_level == -1)\n        g.DebugHookIdInfo = query_id;\n    if (stack_level >= 0 && stack_level < tool->Results.Size)\n    {\n        g.DebugHookIdInfo = tool->Results[stack_level].ID;\n        tool->Results[stack_level].QueryFrameCount++;\n    }\n}\n\n// [DEBUG] ID Stack tool: hooks called by GetID() family functions\nvoid ImGui::DebugHookIdInfo(ImGuiID id, ImGuiDataType data_type, const void* data_id, const void* data_id_end)\n{\n    ImGuiContext& g = *GImGui;\n    ImGuiWindow* window = g.CurrentWindow;\n    ImGuiIDStackTool* tool = &g.DebugIDStackTool;\n\n    // Step 0: stack query\n    // This assumes that the ID was computed with the current ID stack, which tends to be the case for our widget.\n    if (tool->StackLevel == -1)\n    {\n        tool->StackLevel++;\n        tool->Results.resize(window->IDStack.Size + 1, ImGuiStackLevelInfo());\n        for (int n = 0; n < window->IDStack.Size + 1; n++)\n            tool->Results[n].ID = (n < window->IDStack.Size) ? window->IDStack[n] : id;\n        return;\n    }\n\n    // Step 1+: query for individual level\n    IM_ASSERT(tool->StackLevel >= 0);\n    if (tool->StackLevel != window->IDStack.Size)\n        return;\n    ImGuiStackLevelInfo* info = &tool->Results[tool->StackLevel];\n    IM_ASSERT(info->ID == id && info->QueryFrameCount > 0);\n\n    switch (data_type)\n    {\n    case ImGuiDataType_S32:\n        ImFormatString(info->Desc, IM_ARRAYSIZE(info->Desc), \"%d\", (int)(intptr_t)data_id);\n        break;\n    case ImGuiDataType_String:\n        ImFormatString(info->Desc, IM_ARRAYSIZE(info->Desc), \"%.*s\", data_id_end ? (int)((const char*)data_id_end - (const char*)data_id) : (int)ImStrlen((const char*)data_id), (const char*)data_id);\n        break;\n    case ImGuiDataType_Pointer:\n        ImFormatString(info->Desc, IM_ARRAYSIZE(info->Desc), \"(void*)0x%p\", data_id);\n        break;\n    case ImGuiDataType_ID:\n        if (info->Desc[0] != 0) // PushOverrideID() is often used to avoid hashing twice, which would lead to 2 calls to DebugHookIdInfo(). We prioritize the first one.\n            return;\n        ImFormatString(info->Desc, IM_ARRAYSIZE(info->Desc), \"0x%08X [override]\", id);\n        break;\n    default:\n        IM_ASSERT(0);\n    }\n    info->QuerySuccess = true;\n    info->DataType = data_type;\n}\n\nstatic int StackToolFormatLevelInfo(ImGuiIDStackTool* tool, int n, bool format_for_ui, char* buf, size_t buf_size)\n{\n    ImGuiStackLevelInfo* info = &tool->Results[n];\n    ImGuiWindow* window = (info->Desc[0] == 0 && n == 0) ? ImGui::FindWindowByID(info->ID) : NULL;\n    if (window)                                                                 // Source: window name (because the root ID don't call GetID() and so doesn't get hooked)\n        return ImFormatString(buf, buf_size, format_for_ui ? \"\\\"%s\\\" [window]\" : \"%s\", window->Name);\n    if (info->QuerySuccess)                                                     // Source: GetID() hooks (prioritize over ItemInfo() because we frequently use patterns like: PushID(str), Button(\"\") where they both have same id)\n        return ImFormatString(buf, buf_size, (format_for_ui && info->DataType == ImGuiDataType_String) ? \"\\\"%s\\\"\" : \"%s\", info->Desc);\n    if (tool->StackLevel < tool->Results.Size)                                  // Only start using fallback below when all queries are done, so during queries we don't flickering ??? markers.\n        return (*buf = 0);\n#ifdef IMGUI_ENABLE_TEST_ENGINE\n    if (const char* label = ImGuiTestEngine_FindItemDebugLabel(GImGui, info->ID))   // Source: ImGuiTestEngine's ItemInfo()\n        return ImFormatString(buf, buf_size, format_for_ui ? \"??? \\\"%s\\\"\" : \"%s\", label);\n#endif\n    return ImFormatString(buf, buf_size, \"???\");\n}\n\n// ID Stack Tool: Display UI\nvoid ImGui::ShowIDStackToolWindow(bool* p_open)\n{\n    ImGuiContext& g = *GImGui;\n    if ((g.NextWindowData.HasFlags & ImGuiNextWindowDataFlags_HasSize) == 0)\n        SetNextWindowSize(ImVec2(0.0f, GetFontSize() * 8.0f), ImGuiCond_FirstUseEver);\n    if (!Begin(\"Dear ImGui ID Stack Tool\", p_open) || GetCurrentWindow()->BeginCount > 1)\n    {\n        End();\n        return;\n    }\n\n    // Display hovered/active status\n    ImGuiIDStackTool* tool = &g.DebugIDStackTool;\n\n    // Build and display path\n    tool->ResultPathBuf.resize(0);\n    for (int stack_n = 0; stack_n < tool->Results.Size; stack_n++)\n    {\n        char level_desc[256];\n        StackToolFormatLevelInfo(tool, stack_n, false, level_desc, IM_ARRAYSIZE(level_desc));\n        tool->ResultPathBuf.append(stack_n == 0 ? \"//\" : \"/\");\n        for (int n = 0; level_desc[n]; n++)\n        {\n            if (level_desc[n] == '/')\n                tool->ResultPathBuf.append(\"\\\\\");\n            tool->ResultPathBuf.append(level_desc + n, level_desc + n + 1);\n        }\n    }\n    Text(\"0x%08X\", tool->QueryId);\n    SameLine();\n    MetricsHelpMarker(\"Hover an item with the mouse to display elements of the ID Stack leading to the item's final ID.\\nEach level of the stack correspond to a PushID() call.\\nAll levels of the stack are hashed together to make the final ID of a widget (ID displayed at the bottom level of the stack).\\nRead FAQ entry about the ID stack for details.\");\n\n    // CTRL+C to copy path\n    const float time_since_copy = (float)g.Time - tool->CopyToClipboardLastTime;\n    SameLine();\n    PushStyleVarY(ImGuiStyleVar_FramePadding, 0.0f); Checkbox(\"Ctrl+C: copy path\", &tool->CopyToClipboardOnCtrlC); PopStyleVar();\n    SameLine();\n    TextColored((time_since_copy >= 0.0f && time_since_copy < 0.75f && ImFmod(time_since_copy, 0.25f) < 0.25f * 0.5f) ? ImVec4(1.f, 1.f, 0.3f, 1.f) : ImVec4(), \"*COPIED*\");\n    if (tool->CopyToClipboardOnCtrlC && Shortcut(ImGuiMod_Ctrl | ImGuiKey_C, ImGuiInputFlags_RouteGlobal | ImGuiInputFlags_RouteOverFocused))\n    {\n        tool->CopyToClipboardLastTime = (float)g.Time;\n        SetClipboardText(tool->ResultPathBuf.c_str());\n    }\n\n    Text(\"- Path \\\"%s\\\"\", tool->ResultPathBuf.c_str());\n#ifdef IMGUI_ENABLE_TEST_ENGINE\n    Text(\"- Label \\\"%s\\\"\", tool->QueryId ? ImGuiTestEngine_FindItemDebugLabel(&g, tool->QueryId) : \"\");\n#endif\n\n    Separator();\n\n    // Display decorated stack\n    tool->LastActiveFrame = g.FrameCount;\n    if (tool->Results.Size > 0 && BeginTable(\"##table\", 3, ImGuiTableFlags_Borders))\n    {\n        const float id_width = CalcTextSize(\"0xDDDDDDDD\").x;\n        TableSetupColumn(\"Seed\", ImGuiTableColumnFlags_WidthFixed, id_width);\n        TableSetupColumn(\"PushID\", ImGuiTableColumnFlags_WidthStretch);\n        TableSetupColumn(\"Result\", ImGuiTableColumnFlags_WidthFixed, id_width);\n        TableHeadersRow();\n        for (int n = 0; n < tool->Results.Size; n++)\n        {\n            ImGuiStackLevelInfo* info = &tool->Results[n];\n            TableNextColumn();\n            Text(\"0x%08X\", (n > 0) ? tool->Results[n - 1].ID : 0);\n            TableNextColumn();\n            StackToolFormatLevelInfo(tool, n, true, g.TempBuffer.Data, g.TempBuffer.Size);\n            TextUnformatted(g.TempBuffer.Data);\n            TableNextColumn();\n            Text(\"0x%08X\", info->ID);\n            if (n == tool->Results.Size - 1)\n                TableSetBgColor(ImGuiTableBgTarget_CellBg, GetColorU32(ImGuiCol_Header));\n        }\n        EndTable();\n    }\n    End();\n}\n\n#else\n\nvoid ImGui::ShowMetricsWindow(bool*) {}\nvoid ImGui::ShowFontAtlas(ImFontAtlas*) {}\nvoid ImGui::DebugNodeColumns(ImGuiOldColumns*) {}\nvoid ImGui::DebugNodeDrawList(ImGuiWindow*, ImGuiViewportP*, const ImDrawList*, const char*) {}\nvoid ImGui::DebugNodeDrawCmdShowMeshAndBoundingBox(ImDrawList*, const ImDrawList*, const ImDrawCmd*, bool, bool) {}\nvoid ImGui::DebugNodeFont(ImFont*) {}\nvoid ImGui::DebugNodeStorage(ImGuiStorage*, const char*) {}\nvoid ImGui::DebugNodeTabBar(ImGuiTabBar*, const char*) {}\nvoid ImGui::DebugNodeWindow(ImGuiWindow*, const char*) {}\nvoid ImGui::DebugNodeWindowSettings(ImGuiWindowSettings*) {}\nvoid ImGui::DebugNodeWindowsList(ImVector<ImGuiWindow*>*, const char*) {}\nvoid ImGui::DebugNodeViewport(ImGuiViewportP*) {}\n\nvoid ImGui::ShowDebugLogWindow(bool*) {}\nvoid ImGui::ShowIDStackToolWindow(bool*) {}\nvoid ImGui::DebugStartItemPicker() {}\nvoid ImGui::DebugHookIdInfo(ImGuiID, ImGuiDataType, const void*, const void*) {}\n\n#endif // #ifndef IMGUI_DISABLE_DEBUG_TOOLS\n\n//-----------------------------------------------------------------------------\n\n// Include imgui_user.inl at the end of imgui.cpp to access private data/functions that aren't exposed.\n// Prefer just including imgui_internal.h from your code rather than using this define. If a declaration is missing from imgui_internal.h add it or request it on the github.\n#ifdef IMGUI_INCLUDE_IMGUI_USER_INL\n#include \"imgui_user.inl\"\n#endif\n\n//-----------------------------------------------------------------------------\n\n#endif // #ifndef IMGUI_DISABLE\n"
  },
  {
    "path": "src/DesktopPlusUI/imgui/imgui.h",
    "content": "// dear imgui, v1.91.9b\n// (headers)\n\n// Help:\n// - See links below.\n// - Call and read ImGui::ShowDemoWindow() in imgui_demo.cpp. All applications in examples/ are doing that.\n// - Read top of imgui.cpp for more details, links and comments.\n// - Add '#define IMGUI_DEFINE_MATH_OPERATORS' before including this file (or in imconfig.h) to access courtesy maths operators for ImVec2 and ImVec4.\n\n// Resources:\n// - FAQ ........................ https://dearimgui.com/faq (in repository as docs/FAQ.md)\n// - Homepage ................... https://github.com/ocornut/imgui\n// - Releases & changelog ....... https://github.com/ocornut/imgui/releases\n// - Gallery .................... https://github.com/ocornut/imgui/issues?q=label%3Agallery (please post your screenshots/video there!)\n// - Wiki ....................... https://github.com/ocornut/imgui/wiki (lots of good stuff there)\n//   - Getting Started            https://github.com/ocornut/imgui/wiki/Getting-Started (how to integrate in an existing app by adding ~25 lines of code)\n//   - Third-party Extensions     https://github.com/ocornut/imgui/wiki/Useful-Extensions (ImPlot & many more)\n//   - Bindings/Backends          https://github.com/ocornut/imgui/wiki/Bindings (language bindings, backends for various tech/engines)\n//   - Glossary                   https://github.com/ocornut/imgui/wiki/Glossary\n//   - Debug Tools                https://github.com/ocornut/imgui/wiki/Debug-Tools\n//   - Software using Dear ImGui  https://github.com/ocornut/imgui/wiki/Software-using-dear-imgui\n// - Issues & support ........... https://github.com/ocornut/imgui/issues\n// - Test Engine & Automation ... https://github.com/ocornut/imgui_test_engine (test suite, test engine to automate your apps)\n\n// For first-time users having issues compiling/linking/running/loading fonts:\n// please post in https://github.com/ocornut/imgui/discussions if you cannot find a solution in resources above.\n// Everything else should be asked in 'Issues'! We are building a database of cross-linked knowledge there.\n\n// Library Version\n// (Integer encoded as XYYZZ for use in #if preprocessor conditionals, e.g. '#if IMGUI_VERSION_NUM >= 12345')\n#define IMGUI_VERSION       \"1.91.9b\"\n#define IMGUI_VERSION_NUM   19191\n#define IMGUI_HAS_TABLE\n\n/*\n\nIndex of this file:\n// [SECTION] Header mess\n// [SECTION] Forward declarations and basic types\n// [SECTION] Texture identifier (ImTextureID)\n// [SECTION] Dear ImGui end-user API functions\n// [SECTION] Flags & Enumerations\n// [SECTION] Tables API flags and structures (ImGuiTableFlags, ImGuiTableColumnFlags, ImGuiTableRowFlags, ImGuiTableBgTarget, ImGuiTableSortSpecs, ImGuiTableColumnSortSpecs)\n// [SECTION] Helpers: Debug log, Memory allocations macros, ImVector<>\n// [SECTION] ImGuiStyle\n// [SECTION] ImGuiIO\n// [SECTION] Misc data structures (ImGuiInputTextCallbackData, ImGuiSizeCallbackData, ImGuiPayload)\n// [SECTION] Helpers (ImGuiOnceUponAFrame, ImGuiTextFilter, ImGuiTextBuffer, ImGuiStorage, ImGuiListClipper, Math Operators, ImColor)\n// [SECTION] Multi-Select API flags and structures (ImGuiMultiSelectFlags, ImGuiMultiSelectIO, ImGuiSelectionRequest, ImGuiSelectionBasicStorage, ImGuiSelectionExternalStorage)\n// [SECTION] Drawing API (ImDrawCallback, ImDrawCmd, ImDrawIdx, ImDrawVert, ImDrawChannel, ImDrawListSplitter, ImDrawFlags, ImDrawListFlags, ImDrawList, ImDrawData)\n// [SECTION] Font API (ImFontConfig, ImFontGlyph, ImFontGlyphRangesBuilder, ImFontAtlasFlags, ImFontAtlas, ImFont)\n// [SECTION] Viewports (ImGuiViewportFlags, ImGuiViewport)\n// [SECTION] ImGuiPlatformIO + other Platform Dependent Interfaces (ImGuiPlatformImeData)\n// [SECTION] Obsolete functions and types\n\n*/\n\n#pragma once\n\n// Configuration file with compile-time options\n// (edit imconfig.h or '#define IMGUI_USER_CONFIG \"myfilename.h\" from your build system)\n#ifdef IMGUI_USER_CONFIG\n#include IMGUI_USER_CONFIG\n#endif\n#include \"imconfig.h\"\n\n#ifndef IMGUI_DISABLE\n\n//-----------------------------------------------------------------------------\n// [SECTION] Header mess\n//-----------------------------------------------------------------------------\n\n// Includes\n#include <float.h>                  // FLT_MIN, FLT_MAX\n#include <stdarg.h>                 // va_list, va_start, va_end\n#include <stddef.h>                 // ptrdiff_t, NULL\n#include <string.h>                 // memset, memmove, memcpy, strlen, strchr, strcpy, strcmp\n\n// Define attributes of all API symbols declarations (e.g. for DLL under Windows)\n// IMGUI_API is used for core imgui functions, IMGUI_IMPL_API is used for the default backends files (imgui_impl_xxx.h)\n// Using dear imgui via a shared library is not recommended: we don't guarantee backward nor forward ABI compatibility + this is a call-heavy library and function call overhead adds up.\n#ifndef IMGUI_API\n#define IMGUI_API\n#endif\n#ifndef IMGUI_IMPL_API\n#define IMGUI_IMPL_API              IMGUI_API\n#endif\n\n// Helper Macros\n#ifndef IM_ASSERT\n#include <assert.h>\n#define IM_ASSERT(_EXPR)            assert(_EXPR)                               // You can override the default assert handler by editing imconfig.h\n#endif\n#define IM_ARRAYSIZE(_ARR)          ((int)(sizeof(_ARR) / sizeof(*(_ARR))))     // Size of a static C-style array. Don't use on pointers!\n#define IM_UNUSED(_VAR)             ((void)(_VAR))                              // Used to silence \"unused variable warnings\". Often useful as asserts may be stripped out from final builds.\n\n// Check that version and structures layouts are matching between compiled imgui code and caller. Read comments above DebugCheckVersionAndDataLayout() for details.\n#define IMGUI_CHECKVERSION()        ImGui::DebugCheckVersionAndDataLayout(IMGUI_VERSION, sizeof(ImGuiIO), sizeof(ImGuiStyle), sizeof(ImVec2), sizeof(ImVec4), sizeof(ImDrawVert), sizeof(ImDrawIdx))\n\n// Helper Macros - IM_FMTARGS, IM_FMTLIST: Apply printf-style warnings to our formatting functions.\n// (MSVC provides an equivalent mechanism via SAL Annotations but it would require the macros in a different\n//  location. e.g. #include <sal.h> + void myprintf(_Printf_format_string_ const char* format, ...))\n#if !defined(IMGUI_USE_STB_SPRINTF) && defined(__MINGW32__) && !defined(__clang__)\n#define IM_FMTARGS(FMT)             __attribute__((format(gnu_printf, FMT, FMT+1)))\n#define IM_FMTLIST(FMT)             __attribute__((format(gnu_printf, FMT, 0)))\n#elif !defined(IMGUI_USE_STB_SPRINTF) && (defined(__clang__) || defined(__GNUC__))\n#define IM_FMTARGS(FMT)             __attribute__((format(printf, FMT, FMT+1)))\n#define IM_FMTLIST(FMT)             __attribute__((format(printf, FMT, 0)))\n#else\n#define IM_FMTARGS(FMT)\n#define IM_FMTLIST(FMT)\n#endif\n\n// Disable some of MSVC most aggressive Debug runtime checks in function header/footer (used in some simple/low-level functions)\n#if defined(_MSC_VER) && !defined(__clang__)  && !defined(__INTEL_COMPILER) && !defined(IMGUI_DEBUG_PARANOID)\n#define IM_MSVC_RUNTIME_CHECKS_OFF      __pragma(runtime_checks(\"\",off))     __pragma(check_stack(off)) __pragma(strict_gs_check(push,off))\n#define IM_MSVC_RUNTIME_CHECKS_RESTORE  __pragma(runtime_checks(\"\",restore)) __pragma(check_stack())    __pragma(strict_gs_check(pop))\n#else\n#define IM_MSVC_RUNTIME_CHECKS_OFF\n#define IM_MSVC_RUNTIME_CHECKS_RESTORE\n#endif\n\n// Warnings\n#ifdef _MSC_VER\n#pragma warning (push)\n#pragma warning (disable: 26495)    // [Static Analyzer] Variable 'XXX' is uninitialized. Always initialize a member variable (type.6).\n#endif\n#if defined(__clang__)\n#pragma clang diagnostic push\n#if __has_warning(\"-Wunknown-warning-option\")\n#pragma clang diagnostic ignored \"-Wunknown-warning-option\"         // warning: unknown warning group 'xxx'\n#endif\n#pragma clang diagnostic ignored \"-Wunknown-pragmas\"                // warning: unknown warning group 'xxx'\n#pragma clang diagnostic ignored \"-Wold-style-cast\"                 // warning: use of old-style cast\n#pragma clang diagnostic ignored \"-Wfloat-equal\"                    // warning: comparing floating point with == or != is unsafe\n#pragma clang diagnostic ignored \"-Wzero-as-null-pointer-constant\"  // warning: zero as null pointer constant\n#pragma clang diagnostic ignored \"-Wreserved-identifier\"            // warning: identifier '_Xxx' is reserved because it starts with '_' followed by a capital letter\n#pragma clang diagnostic ignored \"-Wunsafe-buffer-usage\"            // warning: 'xxx' is an unsafe pointer used for buffer access\n#pragma clang diagnostic ignored \"-Wnontrivial-memaccess\"           // warning: first argument in call to 'memset' is a pointer to non-trivially copyable type\n#elif defined(__GNUC__)\n#pragma GCC diagnostic push\n#pragma GCC diagnostic ignored \"-Wpragmas\"                          // warning: unknown option after '#pragma GCC diagnostic' kind\n#pragma GCC diagnostic ignored \"-Wfloat-equal\"                      // warning: comparing floating-point with '==' or '!=' is unsafe\n#pragma GCC diagnostic ignored \"-Wclass-memaccess\"                  // [__GNUC__ >= 8] warning: 'memset/memcpy' clearing/writing an object of type 'xxxx' with no trivial copy-assignment; use assignment or value-initialization instead\n#endif\n\n//-----------------------------------------------------------------------------\n// [SECTION] Forward declarations and basic types\n//-----------------------------------------------------------------------------\n\n// Scalar data types\ntypedef unsigned int        ImGuiID;// A unique ID used by widgets (typically the result of hashing a stack of string)\ntypedef signed char         ImS8;   // 8-bit signed integer\ntypedef unsigned char       ImU8;   // 8-bit unsigned integer\ntypedef signed short        ImS16;  // 16-bit signed integer\ntypedef unsigned short      ImU16;  // 16-bit unsigned integer\ntypedef signed int          ImS32;  // 32-bit signed integer == int\ntypedef unsigned int        ImU32;  // 32-bit unsigned integer (often used to store packed colors)\ntypedef signed   long long  ImS64;  // 64-bit signed integer\ntypedef unsigned long long  ImU64;  // 64-bit unsigned integer\n\n// Forward declarations: ImDrawList, ImFontAtlas layer\nstruct ImDrawChannel;               // Temporary storage to output draw commands out of order, used by ImDrawListSplitter and ImDrawList::ChannelsSplit()\nstruct ImDrawCmd;                   // A single draw command within a parent ImDrawList (generally maps to 1 GPU draw call, unless it is a callback)\nstruct ImDrawData;                  // All draw command lists required to render the frame + pos/size coordinates to use for the projection matrix.\nstruct ImDrawList;                  // A single draw command list (generally one per window, conceptually you may see this as a dynamic \"mesh\" builder)\nstruct ImDrawListSharedData;        // Data shared among multiple draw lists (typically owned by parent ImGui context, but you may create one yourself)\nstruct ImDrawListSplitter;          // Helper to split a draw list into different layers which can be drawn into out of order, then flattened back.\nstruct ImDrawVert;                  // A single vertex (pos + uv + col = 20 bytes by default. Override layout with IMGUI_OVERRIDE_DRAWVERT_STRUCT_LAYOUT)\nstruct ImFont;                      // Runtime data for a single font within a parent ImFontAtlas\nstruct ImFontAtlas;                 // Runtime data for multiple fonts, bake multiple fonts into a single texture, TTF/OTF font loader\nstruct ImFontBuilderIO;             // Opaque interface to a font builder (stb_truetype or FreeType).\nstruct ImFontConfig;                // Configuration data when adding a font or merging fonts\nstruct ImFontGlyph;                 // A single font glyph (code point + coordinates within in ImFontAtlas + offset)\nstruct ImFontGlyphRangesBuilder;    // Helper to build glyph ranges from text/string data\nstruct ImColor;                     // Helper functions to create a color that can be converted to either u32 or float4 (*OBSOLETE* please avoid using)\n\n// Forward declarations: ImGui layer\nstruct ImGuiContext;                // Dear ImGui context (opaque structure, unless including imgui_internal.h)\nstruct ImGuiIO;                     // Main configuration and I/O between your application and ImGui (also see: ImGuiPlatformIO)\nstruct ImGuiInputTextCallbackData;  // Shared state of InputText() when using custom ImGuiInputTextCallback (rare/advanced use)\nstruct ImGuiKeyData;                // Storage for ImGuiIO and IsKeyDown(), IsKeyPressed() etc functions.\nstruct ImGuiListClipper;            // Helper to manually clip large list of items\nstruct ImGuiMultiSelectIO;          // Structure to interact with a BeginMultiSelect()/EndMultiSelect() block\nstruct ImGuiOnceUponAFrame;         // Helper for running a block of code not more than once a frame\nstruct ImGuiPayload;                // User data payload for drag and drop operations\nstruct ImGuiPlatformIO;             // Interface between platform/renderer backends and ImGui (e.g. Clipboard, IME hooks). Extends ImGuiIO. In docking branch, this gets extended to support multi-viewports.\nstruct ImGuiPlatformImeData;        // Platform IME data for io.PlatformSetImeDataFn() function.\nstruct ImGuiSelectionBasicStorage;  // Optional helper to store multi-selection state + apply multi-selection requests.\nstruct ImGuiSelectionExternalStorage;//Optional helper to apply multi-selection requests to existing randomly accessible storage.\nstruct ImGuiSelectionRequest;       // A selection request (stored in ImGuiMultiSelectIO)\nstruct ImGuiSizeCallbackData;       // Callback data when using SetNextWindowSizeConstraints() (rare/advanced use)\nstruct ImGuiStorage;                // Helper for key->value storage (container sorted by key)\nstruct ImGuiStoragePair;            // Helper for key->value storage (pair)\nstruct ImGuiStyle;                  // Runtime data for styling/colors\nstruct ImGuiTableSortSpecs;         // Sorting specifications for a table (often handling sort specs for a single column, occasionally more)\nstruct ImGuiTableColumnSortSpecs;   // Sorting specification for one column of a table\nstruct ImGuiTextBuffer;             // Helper to hold and append into a text buffer (~string builder)\nstruct ImGuiTextFilter;             // Helper to parse and apply text filters (e.g. \"aaaaa[,bbbbb][,ccccc]\")\nstruct ImGuiViewport;               // A Platform Window (always only one in 'master' branch), in the future may represent Platform Monitor\n\n// Enumerations\n// - We don't use strongly typed enums much because they add constraints (can't extend in private code, can't store typed in bit fields, extra casting on iteration)\n// - Tip: Use your programming IDE navigation facilities on the names in the _central column_ below to find the actual flags/enum lists!\n//   - In Visual Studio: CTRL+comma (\"Edit.GoToAll\") can follow symbols inside comments, whereas CTRL+F12 (\"Edit.GoToImplementation\") cannot.\n//   - In Visual Studio w/ Visual Assist installed: ALT+G (\"VAssistX.GoToImplementation\") can also follow symbols inside comments.\n//   - In VS Code, CLion, etc.: CTRL+click can follow symbols inside comments.\nenum ImGuiDir : int;                // -> enum ImGuiDir              // Enum: A cardinal direction (Left, Right, Up, Down)\nenum ImGuiKey : int;                // -> enum ImGuiKey              // Enum: A key identifier (ImGuiKey_XXX or ImGuiMod_XXX value)\nenum ImGuiMouseSource : int;        // -> enum ImGuiMouseSource      // Enum; A mouse input source identifier (Mouse, TouchScreen, Pen)\nenum ImGuiSortDirection : ImU8;     // -> enum ImGuiSortDirection    // Enum: A sorting direction (ascending or descending)\ntypedef int ImGuiCol;               // -> enum ImGuiCol_             // Enum: A color identifier for styling\ntypedef int ImGuiCond;              // -> enum ImGuiCond_            // Enum: A condition for many Set*() functions\ntypedef int ImGuiDataType;          // -> enum ImGuiDataType_        // Enum: A primary data type\ntypedef int ImGuiMouseButton;       // -> enum ImGuiMouseButton_     // Enum: A mouse button identifier (0=left, 1=right, 2=middle)\ntypedef int ImGuiMouseCursor;       // -> enum ImGuiMouseCursor_     // Enum: A mouse cursor shape\ntypedef int ImGuiStyleVar;          // -> enum ImGuiStyleVar_        // Enum: A variable identifier for styling\ntypedef int ImGuiTableBgTarget;     // -> enum ImGuiTableBgTarget_   // Enum: A color target for TableSetBgColor()\n\n// Flags (declared as int to allow using as flags without overhead, and to not pollute the top of this file)\n// - Tip: Use your programming IDE navigation facilities on the names in the _central column_ below to find the actual flags/enum lists!\n//   - In Visual Studio: CTRL+comma (\"Edit.GoToAll\") can follow symbols inside comments, whereas CTRL+F12 (\"Edit.GoToImplementation\") cannot.\n//   - In Visual Studio w/ Visual Assist installed: ALT+G (\"VAssistX.GoToImplementation\") can also follow symbols inside comments.\n//   - In VS Code, CLion, etc.: CTRL+click can follow symbols inside comments.\ntypedef int ImDrawFlags;            // -> enum ImDrawFlags_          // Flags: for ImDrawList functions\ntypedef int ImDrawListFlags;        // -> enum ImDrawListFlags_      // Flags: for ImDrawList instance\ntypedef int ImFontAtlasFlags;       // -> enum ImFontAtlasFlags_     // Flags: for ImFontAtlas build\ntypedef int ImGuiBackendFlags;      // -> enum ImGuiBackendFlags_    // Flags: for io.BackendFlags\ntypedef int ImGuiButtonFlags;       // -> enum ImGuiButtonFlags_     // Flags: for InvisibleButton()\ntypedef int ImGuiChildFlags;        // -> enum ImGuiChildFlags_      // Flags: for BeginChild()\ntypedef int ImGuiColorEditFlags;    // -> enum ImGuiColorEditFlags_  // Flags: for ColorEdit4(), ColorPicker4() etc.\ntypedef int ImGuiConfigFlags;       // -> enum ImGuiConfigFlags_     // Flags: for io.ConfigFlags\ntypedef int ImGuiComboFlags;        // -> enum ImGuiComboFlags_      // Flags: for BeginCombo()\ntypedef int ImGuiDragDropFlags;     // -> enum ImGuiDragDropFlags_   // Flags: for BeginDragDropSource(), AcceptDragDropPayload()\ntypedef int ImGuiFocusedFlags;      // -> enum ImGuiFocusedFlags_    // Flags: for IsWindowFocused()\ntypedef int ImGuiHoveredFlags;      // -> enum ImGuiHoveredFlags_    // Flags: for IsItemHovered(), IsWindowHovered() etc.\ntypedef int ImGuiInputFlags;        // -> enum ImGuiInputFlags_      // Flags: for Shortcut(), SetNextItemShortcut()\ntypedef int ImGuiInputTextFlags;    // -> enum ImGuiInputTextFlags_  // Flags: for InputText(), InputTextMultiline()\ntypedef int ImGuiItemFlags;         // -> enum ImGuiItemFlags_       // Flags: for PushItemFlag(), shared by all items\ntypedef int ImGuiKeyChord;          // -> ImGuiKey | ImGuiMod_XXX    // Flags: for IsKeyChordPressed(), Shortcut() etc. an ImGuiKey optionally OR-ed with one or more ImGuiMod_XXX values.\ntypedef int ImGuiPopupFlags;        // -> enum ImGuiPopupFlags_      // Flags: for OpenPopup*(), BeginPopupContext*(), IsPopupOpen()\ntypedef int ImGuiMultiSelectFlags;  // -> enum ImGuiMultiSelectFlags_// Flags: for BeginMultiSelect()\ntypedef int ImGuiSelectableFlags;   // -> enum ImGuiSelectableFlags_ // Flags: for Selectable()\ntypedef int ImGuiSliderFlags;       // -> enum ImGuiSliderFlags_     // Flags: for DragFloat(), DragInt(), SliderFloat(), SliderInt() etc.\ntypedef int ImGuiTabBarFlags;       // -> enum ImGuiTabBarFlags_     // Flags: for BeginTabBar()\ntypedef int ImGuiTabItemFlags;      // -> enum ImGuiTabItemFlags_    // Flags: for BeginTabItem()\ntypedef int ImGuiTableFlags;        // -> enum ImGuiTableFlags_      // Flags: For BeginTable()\ntypedef int ImGuiTableColumnFlags;  // -> enum ImGuiTableColumnFlags_// Flags: For TableSetupColumn()\ntypedef int ImGuiTableRowFlags;     // -> enum ImGuiTableRowFlags_   // Flags: For TableNextRow()\ntypedef int ImGuiTreeNodeFlags;     // -> enum ImGuiTreeNodeFlags_   // Flags: for TreeNode(), TreeNodeEx(), CollapsingHeader()\ntypedef int ImGuiViewportFlags;     // -> enum ImGuiViewportFlags_   // Flags: for ImGuiViewport\ntypedef int ImGuiWindowFlags;       // -> enum ImGuiWindowFlags_     // Flags: for Begin(), BeginChild()\n\n// Character types\n// (we generally use UTF-8 encoded string in the API. This is storage specifically for a decoded character used for keyboard input and display)\ntypedef unsigned int ImWchar32;     // A single decoded U32 character/code point. We encode them as multi bytes UTF-8 when used in strings.\ntypedef unsigned short ImWchar16;   // A single decoded U16 character/code point. We encode them as multi bytes UTF-8 when used in strings.\n#ifdef IMGUI_USE_WCHAR32            // ImWchar [configurable type: override in imconfig.h with '#define IMGUI_USE_WCHAR32' to support Unicode planes 1-16]\ntypedef ImWchar32 ImWchar;\n#else\ntypedef ImWchar16 ImWchar;\n#endif\n\n// Multi-Selection item index or identifier when using BeginMultiSelect()\n// - Used by SetNextItemSelectionUserData() + and inside ImGuiMultiSelectIO structure.\n// - Most users are likely to use this store an item INDEX but this may be used to store a POINTER/ID as well. Read comments near ImGuiMultiSelectIO for details.\ntypedef ImS64 ImGuiSelectionUserData;\n\n// Callback and functions types\ntypedef int     (*ImGuiInputTextCallback)(ImGuiInputTextCallbackData* data);    // Callback function for ImGui::InputText()\ntypedef void    (*ImGuiSizeCallback)(ImGuiSizeCallbackData* data);              // Callback function for ImGui::SetNextWindowSizeConstraints()\ntypedef void*   (*ImGuiMemAllocFunc)(size_t sz, void* user_data);               // Function signature for ImGui::SetAllocatorFunctions()\ntypedef void    (*ImGuiMemFreeFunc)(void* ptr, void* user_data);                // Function signature for ImGui::SetAllocatorFunctions()\n\n// ImVec2: 2D vector used to store positions, sizes etc. [Compile-time configurable type]\n// - This is a frequently used type in the API. Consider using IM_VEC2_CLASS_EXTRA to create implicit cast from/to our preferred type.\n// - Add '#define IMGUI_DEFINE_MATH_OPERATORS' before including this file (or in imconfig.h) to access courtesy maths operators for ImVec2 and ImVec4.\nIM_MSVC_RUNTIME_CHECKS_OFF\nstruct ImVec2\n{\n    float                                   x, y;\n    constexpr ImVec2()                      : x(0.0f), y(0.0f) { }\n    constexpr ImVec2(float _x, float _y)    : x(_x), y(_y) { }\n    float& operator[] (size_t idx)          { IM_ASSERT(idx == 0 || idx == 1); return ((float*)(void*)(char*)this)[idx]; } // We very rarely use this [] operator, so the assert overhead is fine.\n    float  operator[] (size_t idx) const    { IM_ASSERT(idx == 0 || idx == 1); return ((const float*)(const void*)(const char*)this)[idx]; }\n#ifdef IM_VEC2_CLASS_EXTRA\n    IM_VEC2_CLASS_EXTRA     // Define additional constructors and implicit cast operators in imconfig.h to convert back and forth between your math types and ImVec2.\n#endif\n};\n\n// ImVec4: 4D vector used to store clipping rectangles, colors etc. [Compile-time configurable type]\nstruct ImVec4\n{\n    float                                                     x, y, z, w;\n    constexpr ImVec4()                                        : x(0.0f), y(0.0f), z(0.0f), w(0.0f) { }\n    constexpr ImVec4(float _x, float _y, float _z, float _w)  : x(_x), y(_y), z(_z), w(_w) { }\n#ifdef IM_VEC4_CLASS_EXTRA\n    IM_VEC4_CLASS_EXTRA     // Define additional constructors and implicit cast operators in imconfig.h to convert back and forth between your math types and ImVec4.\n#endif\n};\nIM_MSVC_RUNTIME_CHECKS_RESTORE\n\n//-----------------------------------------------------------------------------\n// [SECTION] Texture identifier (ImTextureID)\n//-----------------------------------------------------------------------------\n\n// ImTexture: user data for renderer backend to identify a texture [Compile-time configurable type]\n// - To use something else than an opaque void* pointer: override with e.g. '#define ImTextureID MyTextureType*' in your imconfig.h file.\n// - This can be whatever to you want it to be! read the FAQ about ImTextureID for details.\n// - You can make this a structure with various constructors if you need. You will have to implement ==/!= operators.\n// - (note: before v1.91.4 (2024/10/08) the default type for ImTextureID was void*. Use intermediary intptr_t cast and read FAQ if you have casting warnings)\n#ifndef ImTextureID\ntypedef ImU64 ImTextureID;          // Default: store a pointer or an integer fitting in a pointer (most renderer backends are ok with that)\n#endif\n\n//-----------------------------------------------------------------------------\n// [SECTION] Dear ImGui end-user API functions\n// (Note that ImGui:: being a namespace, you can add extra ImGui:: functions in your own separate file. Please don't modify imgui source files!)\n//-----------------------------------------------------------------------------\n\nnamespace ImGui\n{\n    // Context creation and access\n    // - Each context create its own ImFontAtlas by default. You may instance one yourself and pass it to CreateContext() to share a font atlas between contexts.\n    // - DLL users: heaps and globals are not shared across DLL boundaries! You will need to call SetCurrentContext() + SetAllocatorFunctions()\n    //   for each static/DLL boundary you are calling from. Read \"Context and Memory Allocators\" section of imgui.cpp for details.\n    IMGUI_API ImGuiContext* CreateContext(ImFontAtlas* shared_font_atlas = NULL);\n    IMGUI_API void          DestroyContext(ImGuiContext* ctx = NULL);   // NULL = destroy current context\n    IMGUI_API ImGuiContext* GetCurrentContext();\n    IMGUI_API void          SetCurrentContext(ImGuiContext* ctx);\n\n    // Main\n    IMGUI_API ImGuiIO&      GetIO();                                    // access the ImGuiIO structure (mouse/keyboard/gamepad inputs, time, various configuration options/flags)\n    IMGUI_API ImGuiPlatformIO& GetPlatformIO();                         // access the ImGuiPlatformIO structure (mostly hooks/functions to connect to platform/renderer and OS Clipboard, IME etc.)\n    IMGUI_API ImGuiStyle&   GetStyle();                                 // access the Style structure (colors, sizes). Always use PushStyleColor(), PushStyleVar() to modify style mid-frame!\n    IMGUI_API void          NewFrame();                                 // start a new Dear ImGui frame, you can submit any command from this point until Render()/EndFrame().\n    IMGUI_API void          EndFrame();                                 // ends the Dear ImGui frame. automatically called by Render(). If you don't need to render data (skipping rendering) you may call EndFrame() without Render()... but you'll have wasted CPU already! If you don't need to render, better to not create any windows and not call NewFrame() at all!\n    IMGUI_API void          Render();                                   // ends the Dear ImGui frame, finalize the draw data. You can then get call GetDrawData().\n    IMGUI_API ImDrawData*   GetDrawData();                              // valid after Render() and until the next call to NewFrame(). this is what you have to render.\n\n    // Demo, Debug, Information\n    IMGUI_API void          ShowDemoWindow(bool* p_open = NULL);        // create Demo window. demonstrate most ImGui features. call this to learn about the library! try to make it always available in your application!\n    IMGUI_API void          ShowMetricsWindow(bool* p_open = NULL);     // create Metrics/Debugger window. display Dear ImGui internals: windows, draw commands, various internal state, etc.\n    IMGUI_API void          ShowDebugLogWindow(bool* p_open = NULL);    // create Debug Log window. display a simplified log of important dear imgui events.\n    IMGUI_API void          ShowIDStackToolWindow(bool* p_open = NULL); // create Stack Tool window. hover items with mouse to query information about the source of their unique ID.\n    IMGUI_API void          ShowAboutWindow(bool* p_open = NULL);       // create About window. display Dear ImGui version, credits and build/system information.\n    IMGUI_API void          ShowStyleEditor(ImGuiStyle* ref = NULL);    // add style editor block (not a window). you can pass in a reference ImGuiStyle structure to compare to, revert to and save to (else it uses the default style)\n    IMGUI_API bool          ShowStyleSelector(const char* label);       // add style selector block (not a window), essentially a combo listing the default styles.\n    IMGUI_API void          ShowFontSelector(const char* label);        // add font selector block (not a window), essentially a combo listing the loaded fonts.\n    IMGUI_API void          ShowUserGuide();                            // add basic help/info block (not a window): how to manipulate ImGui as an end-user (mouse/keyboard controls).\n    IMGUI_API const char*   GetVersion();                               // get the compiled version string e.g. \"1.80 WIP\" (essentially the value for IMGUI_VERSION from the compiled version of imgui.cpp)\n\n    // Styles\n    IMGUI_API void          StyleColorsDark(ImGuiStyle* dst = NULL);    // new, recommended style (default)\n    IMGUI_API void          StyleColorsLight(ImGuiStyle* dst = NULL);   // best used with borders and a custom, thicker font\n    IMGUI_API void          StyleColorsClassic(ImGuiStyle* dst = NULL); // classic imgui style\n\n    // Windows\n    // - Begin() = push window to the stack and start appending to it. End() = pop window from the stack.\n    // - Passing 'bool* p_open != NULL' shows a window-closing widget in the upper-right corner of the window,\n    //   which clicking will set the boolean to false when clicked.\n    // - You may append multiple times to the same window during the same frame by calling Begin()/End() pairs multiple times.\n    //   Some information such as 'flags' or 'p_open' will only be considered by the first call to Begin().\n    // - Begin() return false to indicate the window is collapsed or fully clipped, so you may early out and omit submitting\n    //   anything to the window. Always call a matching End() for each Begin() call, regardless of its return value!\n    //   [Important: due to legacy reason, Begin/End and BeginChild/EndChild are inconsistent with all other functions\n    //    such as BeginMenu/EndMenu, BeginPopup/EndPopup, etc. where the EndXXX call should only be called if the corresponding\n    //    BeginXXX function returned true. Begin and BeginChild are the only odd ones out. Will be fixed in a future update.]\n    // - Note that the bottom of window stack always contains a window called \"Debug\".\n    IMGUI_API bool          Begin(const char* name, bool* p_open = NULL, ImGuiWindowFlags flags = 0);\n    IMGUI_API void          End();\n\n    // Child Windows\n    // - Use child windows to begin into a self-contained independent scrolling/clipping regions within a host window. Child windows can embed their own child.\n    // - Before 1.90 (November 2023), the \"ImGuiChildFlags child_flags = 0\" parameter was \"bool border = false\".\n    //   This API is backward compatible with old code, as we guarantee that ImGuiChildFlags_Borders == true.\n    //   Consider updating your old code:\n    //      BeginChild(\"Name\", size, false)   -> Begin(\"Name\", size, 0); or Begin(\"Name\", size, ImGuiChildFlags_None);\n    //      BeginChild(\"Name\", size, true)    -> Begin(\"Name\", size, ImGuiChildFlags_Borders);\n    // - Manual sizing (each axis can use a different setting e.g. ImVec2(0.0f, 400.0f)):\n    //     == 0.0f: use remaining parent window size for this axis.\n    //      > 0.0f: use specified size for this axis.\n    //      < 0.0f: right/bottom-align to specified distance from available content boundaries.\n    // - Specifying ImGuiChildFlags_AutoResizeX or ImGuiChildFlags_AutoResizeY makes the sizing automatic based on child contents.\n    //   Combining both ImGuiChildFlags_AutoResizeX _and_ ImGuiChildFlags_AutoResizeY defeats purpose of a scrolling region and is NOT recommended.\n    // - BeginChild() returns false to indicate the window is collapsed or fully clipped, so you may early out and omit submitting\n    //   anything to the window. Always call a matching EndChild() for each BeginChild() call, regardless of its return value.\n    //   [Important: due to legacy reason, Begin/End and BeginChild/EndChild are inconsistent with all other functions\n    //    such as BeginMenu/EndMenu, BeginPopup/EndPopup, etc. where the EndXXX call should only be called if the corresponding\n    //    BeginXXX function returned true. Begin and BeginChild are the only odd ones out. Will be fixed in a future update.]\n    IMGUI_API bool          BeginChild(const char* str_id, const ImVec2& size = ImVec2(0, 0), ImGuiChildFlags child_flags = 0, ImGuiWindowFlags window_flags = 0);\n    IMGUI_API bool          BeginChild(ImGuiID id, const ImVec2& size = ImVec2(0, 0), ImGuiChildFlags child_flags = 0, ImGuiWindowFlags window_flags = 0);\n    IMGUI_API void          EndChild();\n\n    // Windows Utilities\n    // - 'current window' = the window we are appending into while inside a Begin()/End() block. 'next window' = next window we will Begin() into.\n    IMGUI_API bool          IsWindowAppearing();\n    IMGUI_API bool          IsWindowCollapsed();\n    IMGUI_API bool          IsWindowFocused(ImGuiFocusedFlags flags=0); // is current window focused? or its root/child, depending on flags. see flags for options.\n    IMGUI_API bool          IsWindowHovered(ImGuiHoveredFlags flags=0); // is current window hovered and hoverable (e.g. not blocked by a popup/modal)? See ImGuiHoveredFlags_ for options. IMPORTANT: If you are trying to check whether your mouse should be dispatched to Dear ImGui or to your underlying app, you should not use this function! Use the 'io.WantCaptureMouse' boolean for that! Refer to FAQ entry \"How can I tell whether to dispatch mouse/keyboard to Dear ImGui or my application?\" for details.\n    IMGUI_API ImDrawList*   GetWindowDrawList();                        // get draw list associated to the current window, to append your own drawing primitives\n    IMGUI_API ImVec2        GetWindowPos();                             // get current window position in screen space (IT IS UNLIKELY YOU EVER NEED TO USE THIS. Consider always using GetCursorScreenPos() and GetContentRegionAvail() instead)\n    IMGUI_API ImVec2        GetWindowSize();                            // get current window size (IT IS UNLIKELY YOU EVER NEED TO USE THIS. Consider always using GetCursorScreenPos() and GetContentRegionAvail() instead)\n    IMGUI_API float         GetWindowWidth();                           // get current window width (IT IS UNLIKELY YOU EVER NEED TO USE THIS). Shortcut for GetWindowSize().x.\n    IMGUI_API float         GetWindowHeight();                          // get current window height (IT IS UNLIKELY YOU EVER NEED TO USE THIS). Shortcut for GetWindowSize().y.\n\n    // Window manipulation\n    // - Prefer using SetNextXXX functions (before Begin) rather that SetXXX functions (after Begin).\n    IMGUI_API void          SetNextWindowPos(const ImVec2& pos, ImGuiCond cond = 0, const ImVec2& pivot = ImVec2(0, 0)); // set next window position. call before Begin(). use pivot=(0.5f,0.5f) to center on given point, etc.\n    IMGUI_API void          SetNextWindowSize(const ImVec2& size, ImGuiCond cond = 0);                  // set next window size. set axis to 0.0f to force an auto-fit on this axis. call before Begin()\n    IMGUI_API void          SetNextWindowSizeConstraints(const ImVec2& size_min, const ImVec2& size_max, ImGuiSizeCallback custom_callback = NULL, void* custom_callback_data = NULL); // set next window size limits. use 0.0f or FLT_MAX if you don't want limits. Use -1 for both min and max of same axis to preserve current size (which itself is a constraint). Use callback to apply non-trivial programmatic constraints.\n    IMGUI_API void          SetNextWindowContentSize(const ImVec2& size);                               // set next window content size (~ scrollable client area, which enforce the range of scrollbars). Not including window decorations (title bar, menu bar, etc.) nor WindowPadding. set an axis to 0.0f to leave it automatic. call before Begin()\n    IMGUI_API void          SetNextWindowCollapsed(bool collapsed, ImGuiCond cond = 0);                 // set next window collapsed state. call before Begin()\n    IMGUI_API void          SetNextWindowFocus();                                                       // set next window to be focused / top-most. call before Begin()\n    IMGUI_API void          SetNextWindowScroll(const ImVec2& scroll);                                  // set next window scrolling value (use < 0.0f to not affect a given axis).\n    IMGUI_API void          SetNextWindowBgAlpha(float alpha);                                          // set next window background color alpha. helper to easily override the Alpha component of ImGuiCol_WindowBg/ChildBg/PopupBg. you may also use ImGuiWindowFlags_NoBackground.\n    IMGUI_API void          SetWindowPos(const ImVec2& pos, ImGuiCond cond = 0);                        // (not recommended) set current window position - call within Begin()/End(). prefer using SetNextWindowPos(), as this may incur tearing and side-effects.\n    IMGUI_API void          SetWindowSize(const ImVec2& size, ImGuiCond cond = 0);                      // (not recommended) set current window size - call within Begin()/End(). set to ImVec2(0, 0) to force an auto-fit. prefer using SetNextWindowSize(), as this may incur tearing and minor side-effects.\n    IMGUI_API void          SetWindowCollapsed(bool collapsed, ImGuiCond cond = 0);                     // (not recommended) set current window collapsed state. prefer using SetNextWindowCollapsed().\n    IMGUI_API void          SetWindowFocus();                                                           // (not recommended) set current window to be focused / top-most. prefer using SetNextWindowFocus().\n    IMGUI_API void          SetWindowFontScale(float scale);                                            // [OBSOLETE] set font scale. Adjust IO.FontGlobalScale if you want to scale all windows. This is an old API! For correct scaling, prefer to reload font + rebuild ImFontAtlas + call style.ScaleAllSizes().\n    IMGUI_API void          SetWindowPos(const char* name, const ImVec2& pos, ImGuiCond cond = 0);      // set named window position.\n    IMGUI_API void          SetWindowSize(const char* name, const ImVec2& size, ImGuiCond cond = 0);    // set named window size. set axis to 0.0f to force an auto-fit on this axis.\n    IMGUI_API void          SetWindowCollapsed(const char* name, bool collapsed, ImGuiCond cond = 0);   // set named window collapsed state\n    IMGUI_API void          SetWindowFocus(const char* name);                                           // set named window to be focused / top-most. use NULL to remove focus.\n\n    // Windows Scrolling\n    // - Any change of Scroll will be applied at the beginning of next frame in the first call to Begin().\n    // - You may instead use SetNextWindowScroll() prior to calling Begin() to avoid this delay, as an alternative to using SetScrollX()/SetScrollY().\n    IMGUI_API float         GetScrollX();                                                   // get scrolling amount [0 .. GetScrollMaxX()]\n    IMGUI_API float         GetScrollY();                                                   // get scrolling amount [0 .. GetScrollMaxY()]\n    IMGUI_API void          SetScrollX(float scroll_x);                                     // set scrolling amount [0 .. GetScrollMaxX()]\n    IMGUI_API void          SetScrollY(float scroll_y);                                     // set scrolling amount [0 .. GetScrollMaxY()]\n    IMGUI_API float         GetScrollMaxX();                                                // get maximum scrolling amount ~~ ContentSize.x - WindowSize.x - DecorationsSize.x\n    IMGUI_API float         GetScrollMaxY();                                                // get maximum scrolling amount ~~ ContentSize.y - WindowSize.y - DecorationsSize.y\n    IMGUI_API void          SetScrollHereX(float center_x_ratio = 0.5f);                    // adjust scrolling amount to make current cursor position visible. center_x_ratio=0.0: left, 0.5: center, 1.0: right. When using to make a \"default/current item\" visible, consider using SetItemDefaultFocus() instead.\n    IMGUI_API void          SetScrollHereY(float center_y_ratio = 0.5f);                    // adjust scrolling amount to make current cursor position visible. center_y_ratio=0.0: top, 0.5: center, 1.0: bottom. When using to make a \"default/current item\" visible, consider using SetItemDefaultFocus() instead.\n    IMGUI_API void          SetScrollFromPosX(float local_x, float center_x_ratio = 0.5f);  // adjust scrolling amount to make given position visible. Generally GetCursorStartPos() + offset to compute a valid position.\n    IMGUI_API void          SetScrollFromPosY(float local_y, float center_y_ratio = 0.5f);  // adjust scrolling amount to make given position visible. Generally GetCursorStartPos() + offset to compute a valid position.\n\n    // Parameters stacks (shared)\n    IMGUI_API void          PushFont(ImFont* font);                                         // use NULL as a shortcut to push default font\n    IMGUI_API void          PopFont();\n    IMGUI_API void          PushStyleColor(ImGuiCol idx, ImU32 col);                        // modify a style color. always use this if you modify the style after NewFrame().\n    IMGUI_API void          PushStyleColor(ImGuiCol idx, const ImVec4& col);\n    IMGUI_API void          PopStyleColor(int count = 1);\n    IMGUI_API void          PushStyleVar(ImGuiStyleVar idx, float val);                     // modify a style float variable. always use this if you modify the style after NewFrame()!\n    IMGUI_API void          PushStyleVar(ImGuiStyleVar idx, const ImVec2& val);             // modify a style ImVec2 variable. \"\n    IMGUI_API void          PushStyleVarX(ImGuiStyleVar idx, float val_x);                  // modify X component of a style ImVec2 variable. \"\n    IMGUI_API void          PushStyleVarY(ImGuiStyleVar idx, float val_y);                  // modify Y component of a style ImVec2 variable. \"\n    IMGUI_API void          PopStyleVar(int count = 1);\n    IMGUI_API void          PushItemFlag(ImGuiItemFlags option, bool enabled);              // modify specified shared item flag, e.g. PushItemFlag(ImGuiItemFlags_NoTabStop, true)\n    IMGUI_API void          PopItemFlag();\n\n    // Parameters stacks (current window)\n    IMGUI_API void          PushItemWidth(float item_width);                                // push width of items for common large \"item+label\" widgets. >0.0f: width in pixels, <0.0f align xx pixels to the right of window (so -FLT_MIN always align width to the right side).\n    IMGUI_API void          PopItemWidth();\n    IMGUI_API void          SetNextItemWidth(float item_width);                             // set width of the _next_ common large \"item+label\" widget. >0.0f: width in pixels, <0.0f align xx pixels to the right of window (so -FLT_MIN always align width to the right side)\n    IMGUI_API float         CalcItemWidth();                                                // width of item given pushed settings and current cursor position. NOT necessarily the width of last item unlike most 'Item' functions.\n    IMGUI_API void          PushTextWrapPos(float wrap_local_pos_x = 0.0f);                 // push word-wrapping position for Text*() commands. < 0.0f: no wrapping; 0.0f: wrap to end of window (or column); > 0.0f: wrap at 'wrap_pos_x' position in window local space\n    IMGUI_API void          PopTextWrapPos();\n\n    // Style read access\n    // - Use the ShowStyleEditor() function to interactively see/edit the colors.\n    IMGUI_API ImFont*       GetFont();                                                      // get current font\n    IMGUI_API float         GetFontSize();                                                  // get current font size (= height in pixels) of current font with current scale applied\n    IMGUI_API ImVec2        GetFontTexUvWhitePixel();                                       // get UV coordinate for a white pixel, useful to draw custom shapes via the ImDrawList API\n    IMGUI_API ImU32         GetColorU32(ImGuiCol idx, float alpha_mul = 1.0f);              // retrieve given style color with style alpha applied and optional extra alpha multiplier, packed as a 32-bit value suitable for ImDrawList\n    IMGUI_API ImU32         GetColorU32(const ImVec4& col);                                 // retrieve given color with style alpha applied, packed as a 32-bit value suitable for ImDrawList\n    IMGUI_API ImU32         GetColorU32(ImU32 col, float alpha_mul = 1.0f);                 // retrieve given color with style alpha applied, packed as a 32-bit value suitable for ImDrawList\n    IMGUI_API const ImVec4& GetStyleColorVec4(ImGuiCol idx);                                // retrieve style color as stored in ImGuiStyle structure. use to feed back into PushStyleColor(), otherwise use GetColorU32() to get style color with style alpha baked in.\n\n    // Layout cursor positioning\n    // - By \"cursor\" we mean the current output position.\n    // - The typical widget behavior is to output themselves at the current cursor position, then move the cursor one line down.\n    // - You can call SameLine() between widgets to undo the last carriage return and output at the right of the preceding widget.\n    // - YOU CAN DO 99% OF WHAT YOU NEED WITH ONLY GetCursorScreenPos() and GetContentRegionAvail().\n    // - Attention! We currently have inconsistencies between window-local and absolute positions we will aim to fix with future API:\n    //    - Absolute coordinate:        GetCursorScreenPos(), SetCursorScreenPos(), all ImDrawList:: functions. -> this is the preferred way forward.\n    //    - Window-local coordinates:   SameLine(offset), GetCursorPos(), SetCursorPos(), GetCursorStartPos(), PushTextWrapPos()\n    //    - Window-local coordinates:   GetContentRegionMax(), GetWindowContentRegionMin(), GetWindowContentRegionMax() --> all obsoleted. YOU DON'T NEED THEM.\n    // - GetCursorScreenPos() = GetCursorPos() + GetWindowPos(). GetWindowPos() is almost only ever useful to convert from window-local to absolute coordinates. Try not to use it.\n    IMGUI_API ImVec2        GetCursorScreenPos();                                           // cursor position, absolute coordinates. THIS IS YOUR BEST FRIEND (prefer using this rather than GetCursorPos(), also more useful to work with ImDrawList API).\n    IMGUI_API void          SetCursorScreenPos(const ImVec2& pos);                          // cursor position, absolute coordinates. THIS IS YOUR BEST FRIEND.\n    IMGUI_API ImVec2        GetContentRegionAvail();                                        // available space from current position. THIS IS YOUR BEST FRIEND.\n    IMGUI_API ImVec2        GetCursorPos();                                                 // [window-local] cursor position in window-local coordinates. This is not your best friend.\n    IMGUI_API float         GetCursorPosX();                                                // [window-local] \"\n    IMGUI_API float         GetCursorPosY();                                                // [window-local] \"\n    IMGUI_API void          SetCursorPos(const ImVec2& local_pos);                          // [window-local] \"\n    IMGUI_API void          SetCursorPosX(float local_x);                                   // [window-local] \"\n    IMGUI_API void          SetCursorPosY(float local_y);                                   // [window-local] \"\n    IMGUI_API ImVec2        GetCursorStartPos();                                            // [window-local] initial cursor position, in window-local coordinates. Call GetCursorScreenPos() after Begin() to get the absolute coordinates version.\n\n    // Other layout functions\n    IMGUI_API void          Separator();                                                    // separator, generally horizontal. inside a menu bar or in horizontal layout mode, this becomes a vertical separator.\n    IMGUI_API void          SameLine(float offset_from_start_x=0.0f, float spacing=-1.0f);  // call between widgets or groups to layout them horizontally. X position given in window coordinates.\n    IMGUI_API void          NewLine();                                                      // undo a SameLine() or force a new line when in a horizontal-layout context.\n    IMGUI_API void          Spacing();                                                      // add vertical spacing.\n    IMGUI_API void          Dummy(const ImVec2& size);                                      // add a dummy item of given size. unlike InvisibleButton(), Dummy() won't take the mouse click or be navigable into.\n    IMGUI_API void          Indent(float indent_w = 0.0f);                                  // move content position toward the right, by indent_w, or style.IndentSpacing if indent_w <= 0\n    IMGUI_API void          Unindent(float indent_w = 0.0f);                                // move content position back to the left, by indent_w, or style.IndentSpacing if indent_w <= 0\n    IMGUI_API void          BeginGroup();                                                   // lock horizontal starting position\n    IMGUI_API void          EndGroup();                                                     // unlock horizontal starting position + capture the whole group bounding box into one \"item\" (so you can use IsItemHovered() or layout primitives such as SameLine() on whole group, etc.)\n    IMGUI_API void          AlignTextToFramePadding();                                      // vertically align upcoming text baseline to FramePadding.y so that it will align properly to regularly framed items (call if you have text on a line before a framed item)\n    IMGUI_API float         GetTextLineHeight();                                            // ~ FontSize\n    IMGUI_API float         GetTextLineHeightWithSpacing();                                 // ~ FontSize + style.ItemSpacing.y (distance in pixels between 2 consecutive lines of text)\n    IMGUI_API float         GetFrameHeight();                                               // ~ FontSize + style.FramePadding.y * 2\n    IMGUI_API float         GetFrameHeightWithSpacing();                                    // ~ FontSize + style.FramePadding.y * 2 + style.ItemSpacing.y (distance in pixels between 2 consecutive lines of framed widgets)\n\n    // ID stack/scopes\n    // Read the FAQ (docs/FAQ.md or http://dearimgui.com/faq) for more details about how ID are handled in dear imgui.\n    // - Those questions are answered and impacted by understanding of the ID stack system:\n    //   - \"Q: Why is my widget not reacting when I click on it?\"\n    //   - \"Q: How can I have widgets with an empty label?\"\n    //   - \"Q: How can I have multiple widgets with the same label?\"\n    // - Short version: ID are hashes of the entire ID stack. If you are creating widgets in a loop you most likely\n    //   want to push a unique identifier (e.g. object pointer, loop index) to uniquely differentiate them.\n    // - You can also use the \"Label##foobar\" syntax within widget label to distinguish them from each others.\n    // - In this header file we use the \"label\"/\"name\" terminology to denote a string that will be displayed + used as an ID,\n    //   whereas \"str_id\" denote a string that is only used as an ID and not normally displayed.\n    IMGUI_API void          PushID(const char* str_id);                                     // push string into the ID stack (will hash string).\n    IMGUI_API void          PushID(const char* str_id_begin, const char* str_id_end);       // push string into the ID stack (will hash string).\n    IMGUI_API void          PushID(const void* ptr_id);                                     // push pointer into the ID stack (will hash pointer).\n    IMGUI_API void          PushID(int int_id);                                             // push integer into the ID stack (will hash integer).\n    IMGUI_API void          PopID();                                                        // pop from the ID stack.\n    IMGUI_API ImGuiID       GetID(const char* str_id);                                      // calculate unique ID (hash of whole ID stack + given parameter). e.g. if you want to query into ImGuiStorage yourself\n    IMGUI_API ImGuiID       GetID(const char* str_id_begin, const char* str_id_end);\n    IMGUI_API ImGuiID       GetID(const void* ptr_id);\n    IMGUI_API ImGuiID       GetID(int int_id);\n\n    // Widgets: Text\n    IMGUI_API void          TextUnformatted(const char* text, const char* text_end = NULL); // raw text without formatting. Roughly equivalent to Text(\"%s\", text) but: A) doesn't require null terminated string if 'text_end' is specified, B) it's faster, no memory copy is done, no buffer size limits, recommended for long chunks of text.\n    IMGUI_API void          Text(const char* fmt, ...)                                      IM_FMTARGS(1); // formatted text\n    IMGUI_API void          TextV(const char* fmt, va_list args)                            IM_FMTLIST(1);\n    IMGUI_API void          TextColored(const ImVec4& col, const char* fmt, ...)            IM_FMTARGS(2); // shortcut for PushStyleColor(ImGuiCol_Text, col); Text(fmt, ...); PopStyleColor();\n    IMGUI_API void          TextColoredV(const ImVec4& col, const char* fmt, va_list args)  IM_FMTLIST(2);\n    IMGUI_API void          TextDisabled(const char* fmt, ...)                              IM_FMTARGS(1); // shortcut for PushStyleColor(ImGuiCol_Text, style.Colors[ImGuiCol_TextDisabled]); Text(fmt, ...); PopStyleColor();\n    IMGUI_API void          TextDisabledV(const char* fmt, va_list args)                    IM_FMTLIST(1);\n    IMGUI_API void          TextWrapped(const char* fmt, ...)                               IM_FMTARGS(1); // shortcut for PushTextWrapPos(0.0f); Text(fmt, ...); PopTextWrapPos();. Note that this won't work on an auto-resizing window if there's no other widgets to extend the window width, yoy may need to set a size using SetNextWindowSize().\n    IMGUI_API void          TextWrappedV(const char* fmt, va_list args)                     IM_FMTLIST(1);\n    IMGUI_API void          LabelText(const char* label, const char* fmt, ...)              IM_FMTARGS(2); // display text+label aligned the same way as value+label widgets\n    IMGUI_API void          LabelTextV(const char* label, const char* fmt, va_list args)    IM_FMTLIST(2);\n    IMGUI_API void          BulletText(const char* fmt, ...)                                IM_FMTARGS(1); // shortcut for Bullet()+Text()\n    IMGUI_API void          BulletTextV(const char* fmt, va_list args)                      IM_FMTLIST(1);\n    IMGUI_API void          SeparatorText(const char* label);                               // currently: formatted text with a horizontal line\n\n    // Widgets: Main\n    // - Most widgets return true when the value has been changed or when pressed/selected\n    // - You may also use one of the many IsItemXXX functions (e.g. IsItemActive, IsItemHovered, etc.) to query widget state.\n    IMGUI_API bool          Button(const char* label, const ImVec2& size = ImVec2(0, 0));   // button\n    IMGUI_API bool          SmallButton(const char* label);                                 // button with (FramePadding.y == 0) to easily embed within text\n    IMGUI_API bool          InvisibleButton(const char* str_id, const ImVec2& size, ImGuiButtonFlags flags = 0); // flexible button behavior without the visuals, frequently useful to build custom behaviors using the public api (along with IsItemActive, IsItemHovered, etc.)\n    IMGUI_API bool          ArrowButton(const char* str_id, ImGuiDir dir);                  // square button with an arrow shape\n    IMGUI_API bool          Checkbox(const char* label, bool* v);\n    IMGUI_API bool          CheckboxFlags(const char* label, int* flags, int flags_value);\n    IMGUI_API bool          CheckboxFlags(const char* label, unsigned int* flags, unsigned int flags_value);\n    IMGUI_API bool          RadioButton(const char* label, bool active);                    // use with e.g. if (RadioButton(\"one\", my_value==1)) { my_value = 1; }\n    IMGUI_API bool          RadioButton(const char* label, int* v, int v_button);           // shortcut to handle the above pattern when value is an integer\n    IMGUI_API void          ProgressBar(float fraction, const ImVec2& size_arg = ImVec2(-FLT_MIN, 0), const char* overlay = NULL);\n    IMGUI_API void          Bullet();                                                       // draw a small circle + keep the cursor on the same line. advance cursor x position by GetTreeNodeToLabelSpacing(), same distance that TreeNode() uses\n    IMGUI_API bool          TextLink(const char* label);                                    // hyperlink text button, return true when clicked\n    IMGUI_API void          TextLinkOpenURL(const char* label, const char* url = NULL);     // hyperlink text button, automatically open file/url when clicked\n\n    // Widgets: Images\n    // - Read about ImTextureID here: https://github.com/ocornut/imgui/wiki/Image-Loading-and-Displaying-Examples\n    // - 'uv0' and 'uv1' are texture coordinates. Read about them from the same link above.\n    // - Image() pads adds style.ImageBorderSize on each side, ImageButton() adds style.FramePadding on each side.\n    // - ImageButton() draws a background based on regular Button() color + optionally an inner background if specified.\n    IMGUI_API void          Image(ImTextureID user_texture_id, const ImVec2& image_size, const ImVec2& uv0 = ImVec2(0, 0), const ImVec2& uv1 = ImVec2(1, 1));\n    IMGUI_API void          ImageWithBg(ImTextureID user_texture_id, const ImVec2& image_size, const ImVec2& uv0 = ImVec2(0, 0), const ImVec2& uv1 = ImVec2(1, 1), const ImVec4& bg_col = ImVec4(0, 0, 0, 0), const ImVec4& tint_col = ImVec4(1, 1, 1, 1));\n    IMGUI_API bool          ImageButton(const char* str_id, ImTextureID user_texture_id, const ImVec2& image_size, const ImVec2& uv0 = ImVec2(0, 0), const ImVec2& uv1 = ImVec2(1, 1), const ImVec4& bg_col = ImVec4(0, 0, 0, 0), const ImVec4& tint_col = ImVec4(1, 1, 1, 1));\n\n    // Widgets: Combo Box (Dropdown)\n    // - The BeginCombo()/EndCombo() api allows you to manage your contents and selection state however you want it, by creating e.g. Selectable() items.\n    // - The old Combo() api are helpers over BeginCombo()/EndCombo() which are kept available for convenience purpose. This is analogous to how ListBox are created.\n    IMGUI_API bool          BeginCombo(const char* label, const char* preview_value, ImGuiComboFlags flags = 0);\n    IMGUI_API void          EndCombo(); // only call EndCombo() if BeginCombo() returns true!\n    IMGUI_API bool          Combo(const char* label, int* current_item, const char* const items[], int items_count, int popup_max_height_in_items = -1);\n    IMGUI_API bool          Combo(const char* label, int* current_item, const char* items_separated_by_zeros, int popup_max_height_in_items = -1);      // Separate items with \\0 within a string, end item-list with \\0\\0. e.g. \"One\\0Two\\0Three\\0\"\n    IMGUI_API bool          Combo(const char* label, int* current_item, const char* (*getter)(void* user_data, int idx), void* user_data, int items_count, int popup_max_height_in_items = -1);\n\n    // Widgets: Drag Sliders\n    // - CTRL+Click on any drag box to turn them into an input box. Manually input values aren't clamped by default and can go off-bounds. Use ImGuiSliderFlags_AlwaysClamp to always clamp.\n    // - For all the Float2/Float3/Float4/Int2/Int3/Int4 versions of every function, note that a 'float v[X]' function argument is the same as 'float* v',\n    //   the array syntax is just a way to document the number of elements that are expected to be accessible. You can pass address of your first element out of a contiguous set, e.g. &myvector.x\n    // - Adjust format string to decorate the value with a prefix, a suffix, or adapt the editing and display precision e.g. \"%.3f\" -> 1.234; \"%5.2f secs\" -> 01.23 secs; \"Biscuit: %.0f\" -> Biscuit: 1; etc.\n    // - Format string may also be set to NULL or use the default format (\"%f\" or \"%d\").\n    // - Speed are per-pixel of mouse movement (v_speed=0.2f: mouse needs to move by 5 pixels to increase value by 1). For keyboard/gamepad navigation, minimum speed is Max(v_speed, minimum_step_at_given_precision).\n    // - Use v_min < v_max to clamp edits to given limits. Note that CTRL+Click manual input can override those limits if ImGuiSliderFlags_AlwaysClamp is not used.\n    // - Use v_max = FLT_MAX / INT_MAX etc to avoid clamping to a maximum, same with v_min = -FLT_MAX / INT_MIN to avoid clamping to a minimum.\n    // - We use the same sets of flags for DragXXX() and SliderXXX() functions as the features are the same and it makes it easier to swap them.\n    // - Legacy: Pre-1.78 there are DragXXX() function signatures that take a final `float power=1.0f' argument instead of the `ImGuiSliderFlags flags=0' argument.\n    //   If you get a warning converting a float to ImGuiSliderFlags, read https://github.com/ocornut/imgui/issues/3361\n    IMGUI_API bool          DragFloat(const char* label, float* v, float v_speed = 1.0f, float v_min = 0.0f, float v_max = 0.0f, const char* format = \"%.3f\", ImGuiSliderFlags flags = 0);     // If v_min >= v_max we have no bound\n    IMGUI_API bool          DragFloat2(const char* label, float v[2], float v_speed = 1.0f, float v_min = 0.0f, float v_max = 0.0f, const char* format = \"%.3f\", ImGuiSliderFlags flags = 0);\n    IMGUI_API bool          DragFloat3(const char* label, float v[3], float v_speed = 1.0f, float v_min = 0.0f, float v_max = 0.0f, const char* format = \"%.3f\", ImGuiSliderFlags flags = 0);\n    IMGUI_API bool          DragFloat4(const char* label, float v[4], float v_speed = 1.0f, float v_min = 0.0f, float v_max = 0.0f, const char* format = \"%.3f\", ImGuiSliderFlags flags = 0);\n    IMGUI_API bool          DragFloatRange2(const char* label, float* v_current_min, float* v_current_max, float v_speed = 1.0f, float v_min = 0.0f, float v_max = 0.0f, const char* format = \"%.3f\", const char* format_max = NULL, ImGuiSliderFlags flags = 0);\n    IMGUI_API bool          DragInt(const char* label, int* v, float v_speed = 1.0f, int v_min = 0, int v_max = 0, const char* format = \"%d\", ImGuiSliderFlags flags = 0);  // If v_min >= v_max we have no bound\n    IMGUI_API bool          DragInt2(const char* label, int v[2], float v_speed = 1.0f, int v_min = 0, int v_max = 0, const char* format = \"%d\", ImGuiSliderFlags flags = 0);\n    IMGUI_API bool          DragInt3(const char* label, int v[3], float v_speed = 1.0f, int v_min = 0, int v_max = 0, const char* format = \"%d\", ImGuiSliderFlags flags = 0);\n    IMGUI_API bool          DragInt4(const char* label, int v[4], float v_speed = 1.0f, int v_min = 0, int v_max = 0, const char* format = \"%d\", ImGuiSliderFlags flags = 0);\n    IMGUI_API bool          DragIntRange2(const char* label, int* v_current_min, int* v_current_max, float v_speed = 1.0f, int v_min = 0, int v_max = 0, const char* format = \"%d\", const char* format_max = NULL, ImGuiSliderFlags flags = 0);\n    IMGUI_API bool          DragScalar(const char* label, ImGuiDataType data_type, void* p_data, float v_speed = 1.0f, const void* p_min = NULL, const void* p_max = NULL, const char* format = NULL, ImGuiSliderFlags flags = 0);\n    IMGUI_API bool          DragScalarN(const char* label, ImGuiDataType data_type, void* p_data, int components, float v_speed = 1.0f, const void* p_min = NULL, const void* p_max = NULL, const char* format = NULL, ImGuiSliderFlags flags = 0);\n\n    // Widgets: Regular Sliders\n    // - CTRL+Click on any slider to turn them into an input box. Manually input values aren't clamped by default and can go off-bounds. Use ImGuiSliderFlags_AlwaysClamp to always clamp.\n    // - Adjust format string to decorate the value with a prefix, a suffix, or adapt the editing and display precision e.g. \"%.3f\" -> 1.234; \"%5.2f secs\" -> 01.23 secs; \"Biscuit: %.0f\" -> Biscuit: 1; etc.\n    // - Format string may also be set to NULL or use the default format (\"%f\" or \"%d\").\n    // - Legacy: Pre-1.78 there are SliderXXX() function signatures that take a final `float power=1.0f' argument instead of the `ImGuiSliderFlags flags=0' argument.\n    //   If you get a warning converting a float to ImGuiSliderFlags, read https://github.com/ocornut/imgui/issues/3361\n    IMGUI_API bool          SliderFloat(const char* label, float* v, float v_min, float v_max, const char* format = \"%.3f\", ImGuiSliderFlags flags = 0);     // adjust format to decorate the value with a prefix or a suffix for in-slider labels or unit display.\n    IMGUI_API bool          SliderFloat2(const char* label, float v[2], float v_min, float v_max, const char* format = \"%.3f\", ImGuiSliderFlags flags = 0);\n    IMGUI_API bool          SliderFloat3(const char* label, float v[3], float v_min, float v_max, const char* format = \"%.3f\", ImGuiSliderFlags flags = 0);\n    IMGUI_API bool          SliderFloat4(const char* label, float v[4], float v_min, float v_max, const char* format = \"%.3f\", ImGuiSliderFlags flags = 0);\n    IMGUI_API bool          SliderAngle(const char* label, float* v_rad, float v_degrees_min = -360.0f, float v_degrees_max = +360.0f, const char* format = \"%.0f deg\", ImGuiSliderFlags flags = 0);\n    IMGUI_API bool          SliderInt(const char* label, int* v, int v_min, int v_max, const char* format = \"%d\", ImGuiSliderFlags flags = 0);\n    IMGUI_API bool          SliderInt2(const char* label, int v[2], int v_min, int v_max, const char* format = \"%d\", ImGuiSliderFlags flags = 0);\n    IMGUI_API bool          SliderInt3(const char* label, int v[3], int v_min, int v_max, const char* format = \"%d\", ImGuiSliderFlags flags = 0);\n    IMGUI_API bool          SliderInt4(const char* label, int v[4], int v_min, int v_max, const char* format = \"%d\", ImGuiSliderFlags flags = 0);\n    IMGUI_API bool          SliderScalar(const char* label, ImGuiDataType data_type, void* p_data, const void* p_min, const void* p_max, const char* format = NULL, ImGuiSliderFlags flags = 0);\n    IMGUI_API bool          SliderScalarN(const char* label, ImGuiDataType data_type, void* p_data, int components, const void* p_min, const void* p_max, const char* format = NULL, ImGuiSliderFlags flags = 0);\n    IMGUI_API bool          VSliderFloat(const char* label, const ImVec2& size, float* v, float v_min, float v_max, const char* format = \"%.3f\", ImGuiSliderFlags flags = 0);\n    IMGUI_API bool          VSliderInt(const char* label, const ImVec2& size, int* v, int v_min, int v_max, const char* format = \"%d\", ImGuiSliderFlags flags = 0);\n    IMGUI_API bool          VSliderScalar(const char* label, const ImVec2& size, ImGuiDataType data_type, void* p_data, const void* p_min, const void* p_max, const char* format = NULL, ImGuiSliderFlags flags = 0);\n\n    // Widgets: Input with Keyboard\n    // - If you want to use InputText() with std::string or any custom dynamic string type, see misc/cpp/imgui_stdlib.h and comments in imgui_demo.cpp.\n    // - Most of the ImGuiInputTextFlags flags are only useful for InputText() and not for InputFloatX, InputIntX, InputDouble etc.\n    IMGUI_API bool          InputText(const char* label, char* buf, size_t buf_size, ImGuiInputTextFlags flags = 0, ImGuiInputTextCallback callback = NULL, void* user_data = NULL);\n    IMGUI_API bool          InputTextMultiline(const char* label, char* buf, size_t buf_size, const ImVec2& size = ImVec2(0, 0), ImGuiInputTextFlags flags = 0, ImGuiInputTextCallback callback = NULL, void* user_data = NULL);\n    IMGUI_API bool          InputTextWithHint(const char* label, const char* hint, char* buf, size_t buf_size, ImGuiInputTextFlags flags = 0, ImGuiInputTextCallback callback = NULL, void* user_data = NULL);\n    IMGUI_API bool          InputFloat(const char* label, float* v, float step = 0.0f, float step_fast = 0.0f, const char* format = \"%.3f\", ImGuiInputTextFlags flags = 0);\n    IMGUI_API bool          InputFloat2(const char* label, float v[2], const char* format = \"%.3f\", ImGuiInputTextFlags flags = 0);\n    IMGUI_API bool          InputFloat3(const char* label, float v[3], const char* format = \"%.3f\", ImGuiInputTextFlags flags = 0);\n    IMGUI_API bool          InputFloat4(const char* label, float v[4], const char* format = \"%.3f\", ImGuiInputTextFlags flags = 0);\n    IMGUI_API bool          InputInt(const char* label, int* v, int step = 1, int step_fast = 100, ImGuiInputTextFlags flags = 0);\n    IMGUI_API bool          InputInt2(const char* label, int v[2], ImGuiInputTextFlags flags = 0);\n    IMGUI_API bool          InputInt3(const char* label, int v[3], ImGuiInputTextFlags flags = 0);\n    IMGUI_API bool          InputInt4(const char* label, int v[4], ImGuiInputTextFlags flags = 0);\n    IMGUI_API bool          InputDouble(const char* label, double* v, double step = 0.0, double step_fast = 0.0, const char* format = \"%.6f\", ImGuiInputTextFlags flags = 0);\n    IMGUI_API bool          InputScalar(const char* label, ImGuiDataType data_type, void* p_data, const void* p_step = NULL, const void* p_step_fast = NULL, const char* format = NULL, ImGuiInputTextFlags flags = 0);\n    IMGUI_API bool          InputScalarN(const char* label, ImGuiDataType data_type, void* p_data, int components, const void* p_step = NULL, const void* p_step_fast = NULL, const char* format = NULL, ImGuiInputTextFlags flags = 0);\n\n    // Widgets: Color Editor/Picker (tip: the ColorEdit* functions have a little color square that can be left-clicked to open a picker, and right-clicked to open an option menu.)\n    // - Note that in C++ a 'float v[X]' function argument is the _same_ as 'float* v', the array syntax is just a way to document the number of elements that are expected to be accessible.\n    // - You can pass the address of a first float element out of a contiguous structure, e.g. &myvector.x\n    IMGUI_API bool          ColorEdit3(const char* label, float col[3], ImGuiColorEditFlags flags = 0);\n    IMGUI_API bool          ColorEdit4(const char* label, float col[4], ImGuiColorEditFlags flags = 0);\n    IMGUI_API bool          ColorPicker3(const char* label, float col[3], ImGuiColorEditFlags flags = 0);\n    IMGUI_API bool          ColorPicker4(const char* label, float col[4], ImGuiColorEditFlags flags = 0, const float* ref_col = NULL);\n    IMGUI_API bool          ColorButton(const char* desc_id, const ImVec4& col, ImGuiColorEditFlags flags = 0, const ImVec2& size = ImVec2(0, 0)); // display a color square/button, hover for details, return true when pressed.\n    IMGUI_API void          SetColorEditOptions(ImGuiColorEditFlags flags);                     // initialize current options (generally on application startup) if you want to select a default format, picker type, etc. User will be able to change many settings, unless you pass the _NoOptions flag to your calls.\n\n    // Widgets: Trees\n    // - TreeNode functions return true when the node is open, in which case you need to also call TreePop() when you are finished displaying the tree node contents.\n    IMGUI_API bool          TreeNode(const char* label);\n    IMGUI_API bool          TreeNode(const char* str_id, const char* fmt, ...) IM_FMTARGS(2);   // helper variation to easily decorelate the id from the displayed string. Read the FAQ about why and how to use ID. to align arbitrary text at the same level as a TreeNode() you can use Bullet().\n    IMGUI_API bool          TreeNode(const void* ptr_id, const char* fmt, ...) IM_FMTARGS(2);   // \"\n    IMGUI_API bool          TreeNodeV(const char* str_id, const char* fmt, va_list args) IM_FMTLIST(2);\n    IMGUI_API bool          TreeNodeV(const void* ptr_id, const char* fmt, va_list args) IM_FMTLIST(2);\n    IMGUI_API bool          TreeNodeEx(const char* label, ImGuiTreeNodeFlags flags = 0);\n    IMGUI_API bool          TreeNodeEx(const char* str_id, ImGuiTreeNodeFlags flags, const char* fmt, ...) IM_FMTARGS(3);\n    IMGUI_API bool          TreeNodeEx(const void* ptr_id, ImGuiTreeNodeFlags flags, const char* fmt, ...) IM_FMTARGS(3);\n    IMGUI_API bool          TreeNodeExV(const char* str_id, ImGuiTreeNodeFlags flags, const char* fmt, va_list args) IM_FMTLIST(3);\n    IMGUI_API bool          TreeNodeExV(const void* ptr_id, ImGuiTreeNodeFlags flags, const char* fmt, va_list args) IM_FMTLIST(3);\n    IMGUI_API void          TreePush(const char* str_id);                                       // ~ Indent()+PushID(). Already called by TreeNode() when returning true, but you can call TreePush/TreePop yourself if desired.\n    IMGUI_API void          TreePush(const void* ptr_id);                                       // \"\n    IMGUI_API void          TreePop();                                                          // ~ Unindent()+PopID()\n    IMGUI_API float         GetTreeNodeToLabelSpacing();                                        // horizontal distance preceding label when using TreeNode*() or Bullet() == (g.FontSize + style.FramePadding.x*2) for a regular unframed TreeNode\n    IMGUI_API bool          CollapsingHeader(const char* label, ImGuiTreeNodeFlags flags = 0);  // if returning 'true' the header is open. doesn't indent nor push on ID stack. user doesn't have to call TreePop().\n    IMGUI_API bool          CollapsingHeader(const char* label, bool* p_visible, ImGuiTreeNodeFlags flags = 0); // when 'p_visible != NULL': if '*p_visible==true' display an additional small close button on upper right of the header which will set the bool to false when clicked, if '*p_visible==false' don't display the header.\n    IMGUI_API void          SetNextItemOpen(bool is_open, ImGuiCond cond = 0);                  // set next TreeNode/CollapsingHeader open state.\n    IMGUI_API void          SetNextItemStorageID(ImGuiID storage_id);                           // set id to use for open/close storage (default to same as item id).\n\n    // Widgets: Selectables\n    // - A selectable highlights when hovered, and can display another color when selected.\n    // - Neighbors selectable extend their highlight bounds in order to leave no gap between them. This is so a series of selected Selectable appear contiguous.\n    IMGUI_API bool          Selectable(const char* label, bool selected = false, ImGuiSelectableFlags flags = 0, const ImVec2& size = ImVec2(0, 0)); // \"bool selected\" carry the selection state (read-only). Selectable() is clicked is returns true so you can modify your selection state. size.x==0.0: use remaining width, size.x>0.0: specify width. size.y==0.0: use label height, size.y>0.0: specify height\n    IMGUI_API bool          Selectable(const char* label, bool* p_selected, ImGuiSelectableFlags flags = 0, const ImVec2& size = ImVec2(0, 0));      // \"bool* p_selected\" point to the selection state (read-write), as a convenient helper.\n\n    // Multi-selection system for Selectable(), Checkbox(), TreeNode() functions [BETA]\n    // - This enables standard multi-selection/range-selection idioms (CTRL+Mouse/Keyboard, SHIFT+Mouse/Keyboard, etc.) in a way that also allow a clipper to be used.\n    // - ImGuiSelectionUserData is often used to store your item index within the current view (but may store something else).\n    // - Read comments near ImGuiMultiSelectIO for instructions/details and see 'Demo->Widgets->Selection State & Multi-Select' for demo.\n    // - TreeNode() is technically supported but... using this correctly is more complicated. You need some sort of linear/random access to your tree,\n    //   which is suited to advanced trees setups already implementing filters and clipper. We will work simplifying the current demo.\n    // - 'selection_size' and 'items_count' parameters are optional and used by a few features. If they are costly for you to compute, you may avoid them.\n    IMGUI_API ImGuiMultiSelectIO*   BeginMultiSelect(ImGuiMultiSelectFlags flags, int selection_size = -1, int items_count = -1);\n    IMGUI_API ImGuiMultiSelectIO*   EndMultiSelect();\n    IMGUI_API void                  SetNextItemSelectionUserData(ImGuiSelectionUserData selection_user_data);\n    IMGUI_API bool                  IsItemToggledSelection();                                   // Was the last item selection state toggled? Useful if you need the per-item information _before_ reaching EndMultiSelect(). We only returns toggle _event_ in order to handle clipping correctly.\n\n    // Widgets: List Boxes\n    // - This is essentially a thin wrapper to using BeginChild/EndChild with the ImGuiChildFlags_FrameStyle flag for stylistic changes + displaying a label.\n    // - If you don't need a label you can probably simply use BeginChild() with the ImGuiChildFlags_FrameStyle flag for the same result.\n    // - You can submit contents and manage your selection state however you want it, by creating e.g. Selectable() or any other items.\n    // - The simplified/old ListBox() api are helpers over BeginListBox()/EndListBox() which are kept available for convenience purpose. This is analoguous to how Combos are created.\n    // - Choose frame width:   size.x > 0.0f: custom  /  size.x < 0.0f or -FLT_MIN: right-align   /  size.x = 0.0f (default): use current ItemWidth\n    // - Choose frame height:  size.y > 0.0f: custom  /  size.y < 0.0f or -FLT_MIN: bottom-align  /  size.y = 0.0f (default): arbitrary default height which can fit ~7 items\n    IMGUI_API bool          BeginListBox(const char* label, const ImVec2& size = ImVec2(0, 0)); // open a framed scrolling region\n    IMGUI_API void          EndListBox();                                                       // only call EndListBox() if BeginListBox() returned true!\n    IMGUI_API bool          ListBox(const char* label, int* current_item, const char* const items[], int items_count, int height_in_items = -1);\n    IMGUI_API bool          ListBox(const char* label, int* current_item, const char* (*getter)(void* user_data, int idx), void* user_data, int items_count, int height_in_items = -1);\n\n    // Widgets: Data Plotting\n    // - Consider using ImPlot (https://github.com/epezent/implot) which is much better!\n    IMGUI_API void          PlotLines(const char* label, const float* values, int values_count, int values_offset = 0, const char* overlay_text = NULL, float scale_min = FLT_MAX, float scale_max = FLT_MAX, ImVec2 graph_size = ImVec2(0, 0), int stride = sizeof(float));\n    IMGUI_API void          PlotLines(const char* label, float(*values_getter)(void* data, int idx), void* data, int values_count, int values_offset = 0, const char* overlay_text = NULL, float scale_min = FLT_MAX, float scale_max = FLT_MAX, ImVec2 graph_size = ImVec2(0, 0));\n    IMGUI_API void          PlotHistogram(const char* label, const float* values, int values_count, int values_offset = 0, const char* overlay_text = NULL, float scale_min = FLT_MAX, float scale_max = FLT_MAX, ImVec2 graph_size = ImVec2(0, 0), int stride = sizeof(float));\n    IMGUI_API void          PlotHistogram(const char* label, float (*values_getter)(void* data, int idx), void* data, int values_count, int values_offset = 0, const char* overlay_text = NULL, float scale_min = FLT_MAX, float scale_max = FLT_MAX, ImVec2 graph_size = ImVec2(0, 0));\n\n    // Widgets: Value() Helpers.\n    // - Those are merely shortcut to calling Text() with a format string. Output single value in \"name: value\" format (tip: freely declare more in your code to handle your types. you can add functions to the ImGui namespace)\n    IMGUI_API void          Value(const char* prefix, bool b);\n    IMGUI_API void          Value(const char* prefix, int v);\n    IMGUI_API void          Value(const char* prefix, unsigned int v);\n    IMGUI_API void          Value(const char* prefix, float v, const char* float_format = NULL);\n\n    // Widgets: Menus\n    // - Use BeginMenuBar() on a window ImGuiWindowFlags_MenuBar to append to its menu bar.\n    // - Use BeginMainMenuBar() to create a menu bar at the top of the screen and append to it.\n    // - Use BeginMenu() to create a menu. You can call BeginMenu() multiple time with the same identifier to append more items to it.\n    // - Not that MenuItem() keyboardshortcuts are displayed as a convenience but _not processed_ by Dear ImGui at the moment.\n    IMGUI_API bool          BeginMenuBar();                                                     // append to menu-bar of current window (requires ImGuiWindowFlags_MenuBar flag set on parent window).\n    IMGUI_API void          EndMenuBar();                                                       // only call EndMenuBar() if BeginMenuBar() returns true!\n    IMGUI_API bool          BeginMainMenuBar();                                                 // create and append to a full screen menu-bar.\n    IMGUI_API void          EndMainMenuBar();                                                   // only call EndMainMenuBar() if BeginMainMenuBar() returns true!\n    IMGUI_API bool          BeginMenu(const char* label, bool enabled = true);                  // create a sub-menu entry. only call EndMenu() if this returns true!\n    IMGUI_API void          EndMenu();                                                          // only call EndMenu() if BeginMenu() returns true!\n    IMGUI_API bool          MenuItem(const char* label, const char* shortcut = NULL, bool selected = false, bool enabled = true);  // return true when activated.\n    IMGUI_API bool          MenuItem(const char* label, const char* shortcut, bool* p_selected, bool enabled = true);              // return true when activated + toggle (*p_selected) if p_selected != NULL\n\n    // Tooltips\n    // - Tooltips are windows following the mouse. They do not take focus away.\n    // - A tooltip window can contain items of any types.\n    // - SetTooltip() is more or less a shortcut for the 'if (BeginTooltip()) { Text(...); EndTooltip(); }' idiom (with a subtlety that it discard any previously submitted tooltip)\n    IMGUI_API bool          BeginTooltip();                                                     // begin/append a tooltip window.\n    IMGUI_API void          EndTooltip();                                                       // only call EndTooltip() if BeginTooltip()/BeginItemTooltip() returns true!\n    IMGUI_API void          SetTooltip(const char* fmt, ...) IM_FMTARGS(1);                     // set a text-only tooltip. Often used after a ImGui::IsItemHovered() check. Override any previous call to SetTooltip().\n    IMGUI_API void          SetTooltipV(const char* fmt, va_list args) IM_FMTLIST(1);\n\n    // Tooltips: helpers for showing a tooltip when hovering an item\n    // - BeginItemTooltip() is a shortcut for the 'if (IsItemHovered(ImGuiHoveredFlags_ForTooltip) && BeginTooltip())' idiom.\n    // - SetItemTooltip() is a shortcut for the 'if (IsItemHovered(ImGuiHoveredFlags_ForTooltip)) { SetTooltip(...); }' idiom.\n    // - Where 'ImGuiHoveredFlags_ForTooltip' itself is a shortcut to use 'style.HoverFlagsForTooltipMouse' or 'style.HoverFlagsForTooltipNav' depending on active input type. For mouse it defaults to 'ImGuiHoveredFlags_Stationary | ImGuiHoveredFlags_DelayShort'.\n    IMGUI_API bool          BeginItemTooltip();                                                 // begin/append a tooltip window if preceding item was hovered.\n    IMGUI_API void          SetItemTooltip(const char* fmt, ...) IM_FMTARGS(1);                 // set a text-only tooltip if preceding item was hovered. override any previous call to SetTooltip().\n    IMGUI_API void          SetItemTooltipV(const char* fmt, va_list args) IM_FMTLIST(1);\n\n    // Popups, Modals\n    //  - They block normal mouse hovering detection (and therefore most mouse interactions) behind them.\n    //  - If not modal: they can be closed by clicking anywhere outside them, or by pressing ESCAPE.\n    //  - Their visibility state (~bool) is held internally instead of being held by the programmer as we are used to with regular Begin*() calls.\n    //  - The 3 properties above are related: we need to retain popup visibility state in the library because popups may be closed as any time.\n    //  - You can bypass the hovering restriction by using ImGuiHoveredFlags_AllowWhenBlockedByPopup when calling IsItemHovered() or IsWindowHovered().\n    //  - IMPORTANT: Popup identifiers are relative to the current ID stack, so OpenPopup and BeginPopup generally needs to be at the same level of the stack.\n    //    This is sometimes leading to confusing mistakes. May rework this in the future.\n    //  - BeginPopup(): query popup state, if open start appending into the window. Call EndPopup() afterwards if returned true. ImGuiWindowFlags are forwarded to the window.\n    //  - BeginPopupModal(): block every interaction behind the window, cannot be closed by user, add a dimming background, has a title bar.\n    IMGUI_API bool          BeginPopup(const char* str_id, ImGuiWindowFlags flags = 0);                         // return true if the popup is open, and you can start outputting to it.\n    IMGUI_API bool          BeginPopupModal(const char* name, bool* p_open = NULL, ImGuiWindowFlags flags = 0); // return true if the modal is open, and you can start outputting to it.\n    IMGUI_API void          EndPopup();                                                                         // only call EndPopup() if BeginPopupXXX() returns true!\n\n    // Popups: open/close functions\n    //  - OpenPopup(): set popup state to open. ImGuiPopupFlags are available for opening options.\n    //  - If not modal: they can be closed by clicking anywhere outside them, or by pressing ESCAPE.\n    //  - CloseCurrentPopup(): use inside the BeginPopup()/EndPopup() scope to close manually.\n    //  - CloseCurrentPopup() is called by default by Selectable()/MenuItem() when activated (FIXME: need some options).\n    //  - Use ImGuiPopupFlags_NoOpenOverExistingPopup to avoid opening a popup if there's already one at the same level. This is equivalent to e.g. testing for !IsAnyPopupOpen() prior to OpenPopup().\n    //  - Use IsWindowAppearing() after BeginPopup() to tell if a window just opened.\n    //  - IMPORTANT: Notice that for OpenPopupOnItemClick() we exceptionally default flags to 1 (== ImGuiPopupFlags_MouseButtonRight) for backward compatibility with older API taking 'int mouse_button = 1' parameter\n    IMGUI_API void          OpenPopup(const char* str_id, ImGuiPopupFlags popup_flags = 0);                     // call to mark popup as open (don't call every frame!).\n    IMGUI_API void          OpenPopup(ImGuiID id, ImGuiPopupFlags popup_flags = 0);                             // id overload to facilitate calling from nested stacks\n    IMGUI_API void          OpenPopupOnItemClick(const char* str_id = NULL, ImGuiPopupFlags popup_flags = 1);   // helper to open popup when clicked on last item. Default to ImGuiPopupFlags_MouseButtonRight == 1. (note: actually triggers on the mouse _released_ event to be consistent with popup behaviors)\n    IMGUI_API void          CloseCurrentPopup();                                                                // manually close the popup we have begin-ed into.\n\n    // Popups: open+begin combined functions helpers\n    //  - Helpers to do OpenPopup+BeginPopup where the Open action is triggered by e.g. hovering an item and right-clicking.\n    //  - They are convenient to easily create context menus, hence the name.\n    //  - IMPORTANT: Notice that BeginPopupContextXXX takes ImGuiPopupFlags just like OpenPopup() and unlike BeginPopup(). For full consistency, we may add ImGuiWindowFlags to the BeginPopupContextXXX functions in the future.\n    //  - IMPORTANT: Notice that we exceptionally default their flags to 1 (== ImGuiPopupFlags_MouseButtonRight) for backward compatibility with older API taking 'int mouse_button = 1' parameter, so if you add other flags remember to re-add the ImGuiPopupFlags_MouseButtonRight.\n    IMGUI_API bool          BeginPopupContextItem(const char* str_id = NULL, ImGuiPopupFlags popup_flags = 1);  // open+begin popup when clicked on last item. Use str_id==NULL to associate the popup to previous item. If you want to use that on a non-interactive item such as Text() you need to pass in an explicit ID here. read comments in .cpp!\n    IMGUI_API bool          BeginPopupContextWindow(const char* str_id = NULL, ImGuiPopupFlags popup_flags = 1);// open+begin popup when clicked on current window.\n    IMGUI_API bool          BeginPopupContextVoid(const char* str_id = NULL, ImGuiPopupFlags popup_flags = 1);  // open+begin popup when clicked in void (where there are no windows).\n\n    // Popups: query functions\n    //  - IsPopupOpen(): return true if the popup is open at the current BeginPopup() level of the popup stack.\n    //  - IsPopupOpen() with ImGuiPopupFlags_AnyPopupId: return true if any popup is open at the current BeginPopup() level of the popup stack.\n    //  - IsPopupOpen() with ImGuiPopupFlags_AnyPopupId + ImGuiPopupFlags_AnyPopupLevel: return true if any popup is open.\n    IMGUI_API bool          IsPopupOpen(const char* str_id, ImGuiPopupFlags flags = 0);                         // return true if the popup is open.\n\n    // Tables\n    // - Full-featured replacement for old Columns API.\n    // - See Demo->Tables for demo code. See top of imgui_tables.cpp for general commentary.\n    // - See ImGuiTableFlags_ and ImGuiTableColumnFlags_ enums for a description of available flags.\n    // The typical call flow is:\n    // - 1. Call BeginTable(), early out if returning false.\n    // - 2. Optionally call TableSetupColumn() to submit column name/flags/defaults.\n    // - 3. Optionally call TableSetupScrollFreeze() to request scroll freezing of columns/rows.\n    // - 4. Optionally call TableHeadersRow() to submit a header row. Names are pulled from TableSetupColumn() data.\n    // - 5. Populate contents:\n    //    - In most situations you can use TableNextRow() + TableSetColumnIndex(N) to start appending into a column.\n    //    - If you are using tables as a sort of grid, where every column is holding the same type of contents,\n    //      you may prefer using TableNextColumn() instead of TableNextRow() + TableSetColumnIndex().\n    //      TableNextColumn() will automatically wrap-around into the next row if needed.\n    //    - IMPORTANT: Comparatively to the old Columns() API, we need to call TableNextColumn() for the first column!\n    //    - Summary of possible call flow:\n    //        - TableNextRow() -> TableSetColumnIndex(0) -> Text(\"Hello 0\") -> TableSetColumnIndex(1) -> Text(\"Hello 1\")  // OK\n    //        - TableNextRow() -> TableNextColumn()      -> Text(\"Hello 0\") -> TableNextColumn()      -> Text(\"Hello 1\")  // OK\n    //        -                   TableNextColumn()      -> Text(\"Hello 0\") -> TableNextColumn()      -> Text(\"Hello 1\")  // OK: TableNextColumn() automatically gets to next row!\n    //        - TableNextRow()                           -> Text(\"Hello 0\")                                               // Not OK! Missing TableSetColumnIndex() or TableNextColumn()! Text will not appear!\n    // - 5. Call EndTable()\n    IMGUI_API bool          BeginTable(const char* str_id, int columns, ImGuiTableFlags flags = 0, const ImVec2& outer_size = ImVec2(0.0f, 0.0f), float inner_width = 0.0f);\n    IMGUI_API void          EndTable();                                         // only call EndTable() if BeginTable() returns true!\n    IMGUI_API void          TableNextRow(ImGuiTableRowFlags row_flags = 0, float min_row_height = 0.0f); // append into the first cell of a new row.\n    IMGUI_API bool          TableNextColumn();                                  // append into the next column (or first column of next row if currently in last column). Return true when column is visible.\n    IMGUI_API bool          TableSetColumnIndex(int column_n);                  // append into the specified column. Return true when column is visible.\n\n    // Tables: Headers & Columns declaration\n    // - Use TableSetupColumn() to specify label, resizing policy, default width/weight, id, various other flags etc.\n    // - Use TableHeadersRow() to create a header row and automatically submit a TableHeader() for each column.\n    //   Headers are required to perform: reordering, sorting, and opening the context menu.\n    //   The context menu can also be made available in columns body using ImGuiTableFlags_ContextMenuInBody.\n    // - You may manually submit headers using TableNextRow() + TableHeader() calls, but this is only useful in\n    //   some advanced use cases (e.g. adding custom widgets in header row).\n    // - Use TableSetupScrollFreeze() to lock columns/rows so they stay visible when scrolled.\n    IMGUI_API void          TableSetupColumn(const char* label, ImGuiTableColumnFlags flags = 0, float init_width_or_weight = 0.0f, ImGuiID user_id = 0);\n    IMGUI_API void          TableSetupScrollFreeze(int cols, int rows);         // lock columns/rows so they stay visible when scrolled.\n    IMGUI_API void          TableHeader(const char* label);                     // submit one header cell manually (rarely used)\n    IMGUI_API void          TableHeadersRow();                                  // submit a row with headers cells based on data provided to TableSetupColumn() + submit context menu\n    IMGUI_API void          TableAngledHeadersRow();                            // submit a row with angled headers for every column with the ImGuiTableColumnFlags_AngledHeader flag. MUST BE FIRST ROW.\n\n    // Tables: Sorting & Miscellaneous functions\n    // - Sorting: call TableGetSortSpecs() to retrieve latest sort specs for the table. NULL when not sorting.\n    //   When 'sort_specs->SpecsDirty == true' you should sort your data. It will be true when sorting specs have\n    //   changed since last call, or the first time. Make sure to set 'SpecsDirty = false' after sorting,\n    //   else you may wastefully sort your data every frame!\n    // - Functions args 'int column_n' treat the default value of -1 as the same as passing the current column index.\n    IMGUI_API ImGuiTableSortSpecs*  TableGetSortSpecs();                        // get latest sort specs for the table (NULL if not sorting).  Lifetime: don't hold on this pointer over multiple frames or past any subsequent call to BeginTable().\n    IMGUI_API int                   TableGetColumnCount();                      // return number of columns (value passed to BeginTable)\n    IMGUI_API int                   TableGetColumnIndex();                      // return current column index.\n    IMGUI_API int                   TableGetRowIndex();                         // return current row index.\n    IMGUI_API const char*           TableGetColumnName(int column_n = -1);      // return \"\" if column didn't have a name declared by TableSetupColumn(). Pass -1 to use current column.\n    IMGUI_API ImGuiTableColumnFlags TableGetColumnFlags(int column_n = -1);     // return column flags so you can query their Enabled/Visible/Sorted/Hovered status flags. Pass -1 to use current column.\n    IMGUI_API void                  TableSetColumnEnabled(int column_n, bool v);// change user accessible enabled/disabled state of a column. Set to false to hide the column. User can use the context menu to change this themselves (right-click in headers, or right-click in columns body with ImGuiTableFlags_ContextMenuInBody)\n    IMGUI_API int                   TableGetHoveredColumn();                    // return hovered column. return -1 when table is not hovered. return columns_count if the unused space at the right of visible columns is hovered. Can also use (TableGetColumnFlags() & ImGuiTableColumnFlags_IsHovered) instead.\n    IMGUI_API void                  TableSetBgColor(ImGuiTableBgTarget target, ImU32 color, int column_n = -1);  // change the color of a cell, row, or column. See ImGuiTableBgTarget_ flags for details.\n\n    // Legacy Columns API (prefer using Tables!)\n    // - You can also use SameLine(pos_x) to mimic simplified columns.\n    IMGUI_API void          Columns(int count = 1, const char* id = NULL, bool borders = true);\n    IMGUI_API void          NextColumn();                                                       // next column, defaults to current row or next row if the current row is finished\n    IMGUI_API int           GetColumnIndex();                                                   // get current column index\n    IMGUI_API float         GetColumnWidth(int column_index = -1);                              // get column width (in pixels). pass -1 to use current column\n    IMGUI_API void          SetColumnWidth(int column_index, float width);                      // set column width (in pixels). pass -1 to use current column\n    IMGUI_API float         GetColumnOffset(int column_index = -1);                             // get position of column line (in pixels, from the left side of the contents region). pass -1 to use current column, otherwise 0..GetColumnsCount() inclusive. column 0 is typically 0.0f\n    IMGUI_API void          SetColumnOffset(int column_index, float offset_x);                  // set position of column line (in pixels, from the left side of the contents region). pass -1 to use current column\n    IMGUI_API int           GetColumnsCount();\n\n    // Tab Bars, Tabs\n    // - Note: Tabs are automatically created by the docking system (when in 'docking' branch). Use this to create tab bars/tabs yourself.\n    IMGUI_API bool          BeginTabBar(const char* str_id, ImGuiTabBarFlags flags = 0);        // create and append into a TabBar\n    IMGUI_API void          EndTabBar();                                                        // only call EndTabBar() if BeginTabBar() returns true!\n    IMGUI_API bool          BeginTabItem(const char* label, bool* p_open = NULL, ImGuiTabItemFlags flags = 0); // create a Tab. Returns true if the Tab is selected.\n    IMGUI_API void          EndTabItem();                                                       // only call EndTabItem() if BeginTabItem() returns true!\n    IMGUI_API bool          TabItemButton(const char* label, ImGuiTabItemFlags flags = 0);      // create a Tab behaving like a button. return true when clicked. cannot be selected in the tab bar.\n    IMGUI_API void          SetTabItemClosed(const char* tab_or_docked_window_label);           // notify TabBar or Docking system of a closed tab/window ahead (useful to reduce visual flicker on reorderable tab bars). For tab-bar: call after BeginTabBar() and before Tab submissions. Otherwise call with a window name.\n\n    // Logging/Capture\n    // - All text output from the interface can be captured into tty/file/clipboard. By default, tree nodes are automatically opened during logging.\n    IMGUI_API void          LogToTTY(int auto_open_depth = -1);                                 // start logging to tty (stdout)\n    IMGUI_API void          LogToFile(int auto_open_depth = -1, const char* filename = NULL);   // start logging to file\n    IMGUI_API void          LogToClipboard(int auto_open_depth = -1);                           // start logging to OS clipboard\n    IMGUI_API void          LogFinish();                                                        // stop logging (close file, etc.)\n    IMGUI_API void          LogButtons();                                                       // helper to display buttons for logging to tty/file/clipboard\n    IMGUI_API void          LogText(const char* fmt, ...) IM_FMTARGS(1);                        // pass text data straight to log (without being displayed)\n    IMGUI_API void          LogTextV(const char* fmt, va_list args) IM_FMTLIST(1);\n\n    // Drag and Drop\n    // - On source items, call BeginDragDropSource(), if it returns true also call SetDragDropPayload() + EndDragDropSource().\n    // - On target candidates, call BeginDragDropTarget(), if it returns true also call AcceptDragDropPayload() + EndDragDropTarget().\n    // - If you stop calling BeginDragDropSource() the payload is preserved however it won't have a preview tooltip (we currently display a fallback \"...\" tooltip, see #1725)\n    // - An item can be both drag source and drop target.\n    IMGUI_API bool          BeginDragDropSource(ImGuiDragDropFlags flags = 0);                                      // call after submitting an item which may be dragged. when this return true, you can call SetDragDropPayload() + EndDragDropSource()\n    IMGUI_API bool          SetDragDropPayload(const char* type, const void* data, size_t sz, ImGuiCond cond = 0);  // type is a user defined string of maximum 32 characters. Strings starting with '_' are reserved for dear imgui internal types. Data is copied and held by imgui. Return true when payload has been accepted.\n    IMGUI_API void          EndDragDropSource();                                                                    // only call EndDragDropSource() if BeginDragDropSource() returns true!\n    IMGUI_API bool                  BeginDragDropTarget();                                                          // call after submitting an item that may receive a payload. If this returns true, you can call AcceptDragDropPayload() + EndDragDropTarget()\n    IMGUI_API const ImGuiPayload*   AcceptDragDropPayload(const char* type, ImGuiDragDropFlags flags = 0);          // accept contents of a given type. If ImGuiDragDropFlags_AcceptBeforeDelivery is set you can peek into the payload before the mouse button is released.\n    IMGUI_API void                  EndDragDropTarget();                                                            // only call EndDragDropTarget() if BeginDragDropTarget() returns true!\n    IMGUI_API const ImGuiPayload*   GetDragDropPayload();                                                           // peek directly into the current payload from anywhere. returns NULL when drag and drop is finished or inactive. use ImGuiPayload::IsDataType() to test for the payload type.\n\n    // Disabling [BETA API]\n    // - Disable all user interactions and dim items visuals (applying style.DisabledAlpha over current colors)\n    // - Those can be nested but it cannot be used to enable an already disabled section (a single BeginDisabled(true) in the stack is enough to keep everything disabled)\n    // - Tooltips windows by exception are opted out of disabling.\n    // - BeginDisabled(false)/EndDisabled() essentially does nothing but is provided to facilitate use of boolean expressions (as a micro-optimization: if you have tens of thousands of BeginDisabled(false)/EndDisabled() pairs, you might want to reformulate your code to avoid making those calls)\n    IMGUI_API void          BeginDisabled(bool disabled = true);\n    IMGUI_API void          EndDisabled();\n\n    // Clipping\n    // - Mouse hovering is affected by ImGui::PushClipRect() calls, unlike direct calls to ImDrawList::PushClipRect() which are render only.\n    IMGUI_API void          PushClipRect(const ImVec2& clip_rect_min, const ImVec2& clip_rect_max, bool intersect_with_current_clip_rect);\n    IMGUI_API void          PopClipRect();\n\n    // Focus, Activation\n    IMGUI_API void          SetItemDefaultFocus();                                              // make last item the default focused item of a newly appearing window.\n    IMGUI_API void          SetKeyboardFocusHere(int offset = 0);                               // focus keyboard on the next widget. Use positive 'offset' to access sub components of a multiple component widget. Use -1 to access previous widget.\n\n    // Keyboard/Gamepad Navigation\n    IMGUI_API void          SetNavCursorVisible(bool visible);                                  // alter visibility of keyboard/gamepad cursor. by default: show when using an arrow key, hide when clicking with mouse.\n\n    // Overlapping mode\n    IMGUI_API void          SetNextItemAllowOverlap();                                          // allow next item to be overlapped by a subsequent item. Useful with invisible buttons, selectable, treenode covering an area where subsequent items may need to be added. Note that both Selectable() and TreeNode() have dedicated flags doing this.\n\n    // Item/Widgets Utilities and Query Functions\n    // - Most of the functions are referring to the previous Item that has been submitted.\n    // - See Demo Window under \"Widgets->Querying Status\" for an interactive visualization of most of those functions.\n    IMGUI_API bool          IsItemHovered(ImGuiHoveredFlags flags = 0);                         // is the last item hovered? (and usable, aka not blocked by a popup, etc.). See ImGuiHoveredFlags for more options.\n    IMGUI_API bool          IsItemActive();                                                     // is the last item active? (e.g. button being held, text field being edited. This will continuously return true while holding mouse button on an item. Items that don't interact will always return false)\n    IMGUI_API bool          IsItemFocused();                                                    // is the last item focused for keyboard/gamepad navigation?\n    IMGUI_API bool          IsItemClicked(ImGuiMouseButton mouse_button = 0);                   // is the last item hovered and mouse clicked on? (**)  == IsMouseClicked(mouse_button) && IsItemHovered()Important. (**) this is NOT equivalent to the behavior of e.g. Button(). Read comments in function definition.\n    IMGUI_API bool          IsItemVisible();                                                    // is the last item visible? (items may be out of sight because of clipping/scrolling)\n    IMGUI_API bool          IsItemEdited();                                                     // did the last item modify its underlying value this frame? or was pressed? This is generally the same as the \"bool\" return value of many widgets.\n    IMGUI_API bool          IsItemActivated();                                                  // was the last item just made active (item was previously inactive).\n    IMGUI_API bool          IsItemDeactivated();                                                // was the last item just made inactive (item was previously active). Useful for Undo/Redo patterns with widgets that require continuous editing.\n    IMGUI_API bool          IsItemDeactivatedAfterEdit();                                       // was the last item just made inactive and made a value change when it was active? (e.g. Slider/Drag moved). Useful for Undo/Redo patterns with widgets that require continuous editing. Note that you may get false positives (some widgets such as Combo()/ListBox()/Selectable() will return true even when clicking an already selected item).\n    IMGUI_API bool          IsItemToggledOpen();                                                // was the last item open state toggled? set by TreeNode().\n    IMGUI_API bool          IsAnyItemHovered();                                                 // is any item hovered?\n    IMGUI_API bool          IsAnyItemActive();                                                  // is any item active?\n    IMGUI_API bool          IsAnyItemFocused();                                                 // is any item focused?\n    IMGUI_API ImGuiID       GetItemID();                                                        // get ID of last item (~~ often same ImGui::GetID(label) beforehand)\n    IMGUI_API ImVec2        GetItemRectMin();                                                   // get upper-left bounding rectangle of the last item (screen space)\n    IMGUI_API ImVec2        GetItemRectMax();                                                   // get lower-right bounding rectangle of the last item (screen space)\n    IMGUI_API ImVec2        GetItemRectSize();                                                  // get size of last item\n\n    // Viewports\n    // - Currently represents the Platform Window created by the application which is hosting our Dear ImGui windows.\n    // - In 'docking' branch with multi-viewport enabled, we extend this concept to have multiple active viewports.\n    // - In the future we will extend this concept further to also represent Platform Monitor and support a \"no main platform window\" operation mode.\n    IMGUI_API ImGuiViewport* GetMainViewport();                                                 // return primary/default viewport. This can never be NULL.\n\n    // Background/Foreground Draw Lists\n    IMGUI_API ImDrawList*   GetBackgroundDrawList();                                            // this draw list will be the first rendered one. Useful to quickly draw shapes/text behind dear imgui contents.\n    IMGUI_API ImDrawList*   GetForegroundDrawList();                                            // this draw list will be the last rendered one. Useful to quickly draw shapes/text over dear imgui contents.\n\n    // Miscellaneous Utilities\n    IMGUI_API bool          IsRectVisible(const ImVec2& size);                                  // test if rectangle (of given size, starting from cursor position) is visible / not clipped.\n    IMGUI_API bool          IsRectVisible(const ImVec2& rect_min, const ImVec2& rect_max);      // test if rectangle (in screen space) is visible / not clipped. to perform coarse clipping on user's side.\n    IMGUI_API double        GetTime();                                                          // get global imgui time. incremented by io.DeltaTime every frame.\n    IMGUI_API int           GetFrameCount();                                                    // get global imgui frame count. incremented by 1 every frame.\n    IMGUI_API ImDrawListSharedData* GetDrawListSharedData();                                    // you may use this when creating your own ImDrawList instances.\n    IMGUI_API const char*   GetStyleColorName(ImGuiCol idx);                                    // get a string corresponding to the enum value (for display, saving, etc.).\n    IMGUI_API void          SetStateStorage(ImGuiStorage* storage);                             // replace current window storage with our own (if you want to manipulate it yourself, typically clear subsection of it)\n    IMGUI_API ImGuiStorage* GetStateStorage();\n\n    // Text Utilities\n    IMGUI_API ImVec2        CalcTextSize(const char* text, const char* text_end = NULL, bool hide_text_after_double_hash = false, float wrap_width = -1.0f);\n\n    // Color Utilities\n    IMGUI_API ImVec4        ColorConvertU32ToFloat4(ImU32 in);\n    IMGUI_API ImU32         ColorConvertFloat4ToU32(const ImVec4& in);\n    IMGUI_API void          ColorConvertRGBtoHSV(float r, float g, float b, float& out_h, float& out_s, float& out_v);\n    IMGUI_API void          ColorConvertHSVtoRGB(float h, float s, float v, float& out_r, float& out_g, float& out_b);\n\n    // Inputs Utilities: Keyboard/Mouse/Gamepad\n    // - the ImGuiKey enum contains all possible keyboard, mouse and gamepad inputs (e.g. ImGuiKey_A, ImGuiKey_MouseLeft, ImGuiKey_GamepadDpadUp...).\n    // - (legacy: before v1.87, we used ImGuiKey to carry native/user indices as defined by each backends. This was obsoleted in 1.87 (2022-02) and completely removed in 1.91.5 (2024-11). See https://github.com/ocornut/imgui/issues/4921)\n    // - (legacy: any use of ImGuiKey will assert when key < 512 to detect passing legacy native/user indices)\n    IMGUI_API bool          IsKeyDown(ImGuiKey key);                                            // is key being held.\n    IMGUI_API bool          IsKeyPressed(ImGuiKey key, bool repeat = true);                     // was key pressed (went from !Down to Down)? if repeat=true, uses io.KeyRepeatDelay / KeyRepeatRate\n    IMGUI_API bool          IsKeyReleased(ImGuiKey key);                                        // was key released (went from Down to !Down)?\n    IMGUI_API bool          IsKeyChordPressed(ImGuiKeyChord key_chord);                         // was key chord (mods + key) pressed, e.g. you can pass 'ImGuiMod_Ctrl | ImGuiKey_S' as a key-chord. This doesn't do any routing or focus check, please consider using Shortcut() function instead.\n    IMGUI_API int           GetKeyPressedAmount(ImGuiKey key, float repeat_delay, float rate);  // uses provided repeat rate/delay. return a count, most often 0 or 1 but might be >1 if RepeatRate is small enough that DeltaTime > RepeatRate\n    IMGUI_API const char*   GetKeyName(ImGuiKey key);                                           // [DEBUG] returns English name of the key. Those names are provided for debugging purpose and are not meant to be saved persistently nor compared.\n    IMGUI_API void          SetNextFrameWantCaptureKeyboard(bool want_capture_keyboard);        // Override io.WantCaptureKeyboard flag next frame (said flag is left for your application to handle, typically when true it instructs your app to ignore inputs). e.g. force capture keyboard when your widget is being hovered. This is equivalent to setting \"io.WantCaptureKeyboard = want_capture_keyboard\"; after the next NewFrame() call.\n\n    // Inputs Utilities: Shortcut Testing & Routing [BETA]\n    // - ImGuiKeyChord = a ImGuiKey + optional ImGuiMod_Alt/ImGuiMod_Ctrl/ImGuiMod_Shift/ImGuiMod_Super.\n    //       ImGuiKey_C                          // Accepted by functions taking ImGuiKey or ImGuiKeyChord arguments)\n    //       ImGuiMod_Ctrl | ImGuiKey_C          // Accepted by functions taking ImGuiKeyChord arguments)\n    //   only ImGuiMod_XXX values are legal to combine with an ImGuiKey. You CANNOT combine two ImGuiKey values.\n    // - The general idea is that several callers may register interest in a shortcut, and only one owner gets it.\n    //      Parent   -> call Shortcut(Ctrl+S)    // When Parent is focused, Parent gets the shortcut.\n    //        Child1 -> call Shortcut(Ctrl+S)    // When Child1 is focused, Child1 gets the shortcut (Child1 overrides Parent shortcuts)\n    //        Child2 -> no call                  // When Child2 is focused, Parent gets the shortcut.\n    //   The whole system is order independent, so if Child1 makes its calls before Parent, results will be identical.\n    //   This is an important property as it facilitate working with foreign code or larger codebase.\n    // - To understand the difference:\n    //   - IsKeyChordPressed() compares mods and call IsKeyPressed() -> function has no side-effect.\n    //   - Shortcut() submits a route, routes are resolved, if it currently can be routed it calls IsKeyChordPressed() -> function has (desirable) side-effects as it can prevents another call from getting the route.\n    // - Visualize registered routes in 'Metrics/Debugger->Inputs'.\n    IMGUI_API bool          Shortcut(ImGuiKeyChord key_chord, ImGuiInputFlags flags = 0);\n    IMGUI_API void          SetNextItemShortcut(ImGuiKeyChord key_chord, ImGuiInputFlags flags = 0);\n\n    // Inputs Utilities: Key/Input Ownership [BETA]\n    // - One common use case would be to allow your items to disable standard inputs behaviors such\n    //   as Tab or Alt key handling, Mouse Wheel scrolling, etc.\n    //   e.g. Button(...); SetItemKeyOwner(ImGuiKey_MouseWheelY); to make hovering/activating a button disable wheel for scrolling.\n    // - Reminder ImGuiKey enum include access to mouse buttons and gamepad, so key ownership can apply to them.\n    // - Many related features are still in imgui_internal.h. For instance, most IsKeyXXX()/IsMouseXXX() functions have an owner-id-aware version.\n    IMGUI_API void          SetItemKeyOwner(ImGuiKey key);                                      // Set key owner to last item ID if it is hovered or active. Equivalent to 'if (IsItemHovered() || IsItemActive()) { SetKeyOwner(key, GetItemID());'.\n\n    // Inputs Utilities: Mouse\n    // - To refer to a mouse button, you may use named enums in your code e.g. ImGuiMouseButton_Left, ImGuiMouseButton_Right.\n    // - You can also use regular integer: it is forever guaranteed that 0=Left, 1=Right, 2=Middle.\n    // - Dragging operations are only reported after mouse has moved a certain distance away from the initial clicking position (see 'lock_threshold' and 'io.MouseDraggingThreshold')\n    IMGUI_API bool          IsMouseDown(ImGuiMouseButton button);                               // is mouse button held?\n    IMGUI_API bool          IsMouseClicked(ImGuiMouseButton button, bool repeat = false);       // did mouse button clicked? (went from !Down to Down). Same as GetMouseClickedCount() == 1.\n    IMGUI_API bool          IsMouseReleased(ImGuiMouseButton button);                           // did mouse button released? (went from Down to !Down)\n    IMGUI_API bool          IsMouseDoubleClicked(ImGuiMouseButton button);                      // did mouse button double-clicked? Same as GetMouseClickedCount() == 2. (note that a double-click will also report IsMouseClicked() == true)\n    IMGUI_API bool          IsMouseReleasedWithDelay(ImGuiMouseButton button, float delay);     // delayed mouse release (use very sparingly!). Generally used with 'delay >= io.MouseDoubleClickTime' + combined with a 'io.MouseClickedLastCount==1' test. This is a very rarely used UI idiom, but some apps use this: e.g. MS Explorer single click on an icon to rename.\n    IMGUI_API int           GetMouseClickedCount(ImGuiMouseButton button);                      // return the number of successive mouse-clicks at the time where a click happen (otherwise 0).\n    IMGUI_API bool          IsMouseHoveringRect(const ImVec2& r_min, const ImVec2& r_max, bool clip = true);// is mouse hovering given bounding rect (in screen space). clipped by current clipping settings, but disregarding of other consideration of focus/window ordering/popup-block.\n    IMGUI_API bool          IsMousePosValid(const ImVec2* mouse_pos = NULL);                    // by convention we use (-FLT_MAX,-FLT_MAX) to denote that there is no mouse available\n    IMGUI_API bool          IsAnyMouseDown();                                                   // [WILL OBSOLETE] is any mouse button held? This was designed for backends, but prefer having backend maintain a mask of held mouse buttons, because upcoming input queue system will make this invalid.\n    IMGUI_API ImVec2        GetMousePos();                                                      // shortcut to ImGui::GetIO().MousePos provided by user, to be consistent with other calls\n    IMGUI_API ImVec2        GetMousePosOnOpeningCurrentPopup();                                 // retrieve mouse position at the time of opening popup we have BeginPopup() into (helper to avoid user backing that value themselves)\n    IMGUI_API bool          IsMouseDragging(ImGuiMouseButton button, float lock_threshold = -1.0f);         // is mouse dragging? (uses io.MouseDraggingThreshold if lock_threshold < 0.0f)\n    IMGUI_API ImVec2        GetMouseDragDelta(ImGuiMouseButton button = 0, float lock_threshold = -1.0f);   // return the delta from the initial clicking position while the mouse button is pressed or was just released. This is locked and return 0.0f until the mouse moves past a distance threshold at least once (uses io.MouseDraggingThreshold if lock_threshold < 0.0f)\n    IMGUI_API void          ResetMouseDragDelta(ImGuiMouseButton button = 0);                   //\n    IMGUI_API ImGuiMouseCursor GetMouseCursor();                                                // get desired mouse cursor shape. Important: reset in ImGui::NewFrame(), this is updated during the frame. valid before Render(). If you use software rendering by setting io.MouseDrawCursor ImGui will render those for you\n    IMGUI_API void          SetMouseCursor(ImGuiMouseCursor cursor_type);                       // set desired mouse cursor shape\n    IMGUI_API void          SetNextFrameWantCaptureMouse(bool want_capture_mouse);              // Override io.WantCaptureMouse flag next frame (said flag is left for your application to handle, typical when true it instucts your app to ignore inputs). This is equivalent to setting \"io.WantCaptureMouse = want_capture_mouse;\" after the next NewFrame() call.\n\n    // Clipboard Utilities\n    // - Also see the LogToClipboard() function to capture GUI into clipboard, or easily output text data to the clipboard.\n    IMGUI_API const char*   GetClipboardText();\n    IMGUI_API void          SetClipboardText(const char* text);\n\n    // Settings/.Ini Utilities\n    // - The disk functions are automatically called if io.IniFilename != NULL (default is \"imgui.ini\").\n    // - Set io.IniFilename to NULL to load/save manually. Read io.WantSaveIniSettings description about handling .ini saving manually.\n    // - Important: default value \"imgui.ini\" is relative to current working dir! Most apps will want to lock this to an absolute path (e.g. same path as executables).\n    IMGUI_API void          LoadIniSettingsFromDisk(const char* ini_filename);                  // call after CreateContext() and before the first call to NewFrame(). NewFrame() automatically calls LoadIniSettingsFromDisk(io.IniFilename).\n    IMGUI_API void          LoadIniSettingsFromMemory(const char* ini_data, size_t ini_size=0); // call after CreateContext() and before the first call to NewFrame() to provide .ini data from your own data source.\n    IMGUI_API void          SaveIniSettingsToDisk(const char* ini_filename);                    // this is automatically called (if io.IniFilename is not empty) a few seconds after any modification that should be reflected in the .ini file (and also by DestroyContext).\n    IMGUI_API const char*   SaveIniSettingsToMemory(size_t* out_ini_size = NULL);               // return a zero-terminated string with the .ini data which you can save by your own mean. call when io.WantSaveIniSettings is set, then save data by your own mean and clear io.WantSaveIniSettings.\n\n    // Debug Utilities\n    // - Your main debugging friend is the ShowMetricsWindow() function, which is also accessible from Demo->Tools->Metrics Debugger\n    IMGUI_API void          DebugTextEncoding(const char* text);\n    IMGUI_API void          DebugFlashStyleColor(ImGuiCol idx);\n    IMGUI_API void          DebugStartItemPicker();\n    IMGUI_API bool          DebugCheckVersionAndDataLayout(const char* version_str, size_t sz_io, size_t sz_style, size_t sz_vec2, size_t sz_vec4, size_t sz_drawvert, size_t sz_drawidx); // This is called by IMGUI_CHECKVERSION() macro.\n#ifndef IMGUI_DISABLE_DEBUG_TOOLS\n    IMGUI_API void          DebugLog(const char* fmt, ...)           IM_FMTARGS(1); // Call via IMGUI_DEBUG_LOG() for maximum stripping in caller code!\n    IMGUI_API void          DebugLogV(const char* fmt, va_list args) IM_FMTLIST(1);\n#endif\n\n    // Memory Allocators\n    // - Those functions are not reliant on the current context.\n    // - DLL users: heaps and globals are not shared across DLL boundaries! You will need to call SetCurrentContext() + SetAllocatorFunctions()\n    //   for each static/DLL boundary you are calling from. Read \"Context and Memory Allocators\" section of imgui.cpp for more details.\n    IMGUI_API void          SetAllocatorFunctions(ImGuiMemAllocFunc alloc_func, ImGuiMemFreeFunc free_func, void* user_data = NULL);\n    IMGUI_API void          GetAllocatorFunctions(ImGuiMemAllocFunc* p_alloc_func, ImGuiMemFreeFunc* p_free_func, void** p_user_data);\n    IMGUI_API void*         MemAlloc(size_t size);\n    IMGUI_API void          MemFree(void* ptr);\n\n} // namespace ImGui\n\n//-----------------------------------------------------------------------------\n// [SECTION] Flags & Enumerations\n//-----------------------------------------------------------------------------\n\n// Flags for ImGui::Begin()\n// (Those are per-window flags. There are shared flags in ImGuiIO: io.ConfigWindowsResizeFromEdges and io.ConfigWindowsMoveFromTitleBarOnly)\nenum ImGuiWindowFlags_\n{\n    ImGuiWindowFlags_None                   = 0,\n    ImGuiWindowFlags_NoTitleBar             = 1 << 0,   // Disable title-bar\n    ImGuiWindowFlags_NoResize               = 1 << 1,   // Disable user resizing with the lower-right grip\n    ImGuiWindowFlags_NoMove                 = 1 << 2,   // Disable user moving the window\n    ImGuiWindowFlags_NoScrollbar            = 1 << 3,   // Disable scrollbars (window can still scroll with mouse or programmatically)\n    ImGuiWindowFlags_NoScrollWithMouse      = 1 << 4,   // Disable user vertically scrolling with mouse wheel. On child window, mouse wheel will be forwarded to the parent unless NoScrollbar is also set.\n    ImGuiWindowFlags_NoCollapse             = 1 << 5,   // Disable user collapsing window by double-clicking on it. Also referred to as Window Menu Button (e.g. within a docking node).\n    ImGuiWindowFlags_AlwaysAutoResize       = 1 << 6,   // Resize every window to its content every frame\n    ImGuiWindowFlags_NoBackground           = 1 << 7,   // Disable drawing background color (WindowBg, etc.) and outside border. Similar as using SetNextWindowBgAlpha(0.0f).\n    ImGuiWindowFlags_NoSavedSettings        = 1 << 8,   // Never load/save settings in .ini file\n    ImGuiWindowFlags_NoMouseInputs          = 1 << 9,   // Disable catching mouse, hovering test with pass through.\n    ImGuiWindowFlags_MenuBar                = 1 << 10,  // Has a menu-bar\n    ImGuiWindowFlags_HorizontalScrollbar    = 1 << 11,  // Allow horizontal scrollbar to appear (off by default). You may use SetNextWindowContentSize(ImVec2(width,0.0f)); prior to calling Begin() to specify width. Read code in imgui_demo in the \"Horizontal Scrolling\" section.\n    ImGuiWindowFlags_NoFocusOnAppearing     = 1 << 12,  // Disable taking focus when transitioning from hidden to visible state\n    ImGuiWindowFlags_NoBringToFrontOnFocus  = 1 << 13,  // Disable bringing window to front when taking focus (e.g. clicking on it or programmatically giving it focus)\n    ImGuiWindowFlags_AlwaysVerticalScrollbar= 1 << 14,  // Always show vertical scrollbar (even if ContentSize.y < Size.y)\n    ImGuiWindowFlags_AlwaysHorizontalScrollbar=1<< 15,  // Always show horizontal scrollbar (even if ContentSize.x < Size.x)\n    ImGuiWindowFlags_NoNavInputs            = 1 << 16,  // No keyboard/gamepad navigation within the window\n    ImGuiWindowFlags_NoNavFocus             = 1 << 17,  // No focusing toward this window with keyboard/gamepad navigation (e.g. skipped by CTRL+TAB)\n    ImGuiWindowFlags_UnsavedDocument        = 1 << 18,  // Display a dot next to the title. When used in a tab/docking context, tab is selected when clicking the X + closure is not assumed (will wait for user to stop submitting the tab). Otherwise closure is assumed when pressing the X, so if you keep submitting the tab may reappear at end of tab bar.\n    ImGuiWindowFlags_NoNav                  = ImGuiWindowFlags_NoNavInputs | ImGuiWindowFlags_NoNavFocus,\n    ImGuiWindowFlags_NoDecoration           = ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoCollapse,\n    ImGuiWindowFlags_NoInputs               = ImGuiWindowFlags_NoMouseInputs | ImGuiWindowFlags_NoNavInputs | ImGuiWindowFlags_NoNavFocus,\n\n    // [Internal]\n    ImGuiWindowFlags_ChildWindow            = 1 << 24,  // Don't use! For internal use by BeginChild()\n    ImGuiWindowFlags_Tooltip                = 1 << 25,  // Don't use! For internal use by BeginTooltip()\n    ImGuiWindowFlags_Popup                  = 1 << 26,  // Don't use! For internal use by BeginPopup()\n    ImGuiWindowFlags_Modal                  = 1 << 27,  // Don't use! For internal use by BeginPopupModal()\n    ImGuiWindowFlags_ChildMenu              = 1 << 28,  // Don't use! For internal use by BeginMenu()\n\n    // Obsolete names\n#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS\n    ImGuiWindowFlags_NavFlattened           = 1 << 29,  // Obsoleted in 1.90.9: Use ImGuiChildFlags_NavFlattened in BeginChild() call.\n    ImGuiWindowFlags_AlwaysUseWindowPadding = 1 << 30,  // Obsoleted in 1.90.0: Use ImGuiChildFlags_AlwaysUseWindowPadding in BeginChild() call.\n#endif\n};\n\n// Flags for ImGui::BeginChild()\n// (Legacy: bit 0 must always correspond to ImGuiChildFlags_Borders to be backward compatible with old API using 'bool border = false'.\n// About using AutoResizeX/AutoResizeY flags:\n// - May be combined with SetNextWindowSizeConstraints() to set a min/max size for each axis (see \"Demo->Child->Auto-resize with Constraints\").\n// - Size measurement for a given axis is only performed when the child window is within visible boundaries, or is just appearing.\n//   - This allows BeginChild() to return false when not within boundaries (e.g. when scrolling), which is more optimal. BUT it won't update its auto-size while clipped.\n//     While not perfect, it is a better default behavior as the always-on performance gain is more valuable than the occasional \"resizing after becoming visible again\" glitch.\n//   - You may also use ImGuiChildFlags_AlwaysAutoResize to force an update even when child window is not in view.\n//     HOWEVER PLEASE UNDERSTAND THAT DOING SO WILL PREVENT BeginChild() FROM EVER RETURNING FALSE, disabling benefits of coarse clipping.\nenum ImGuiChildFlags_\n{\n    ImGuiChildFlags_None                    = 0,\n    ImGuiChildFlags_Borders                 = 1 << 0,   // Show an outer border and enable WindowPadding. (IMPORTANT: this is always == 1 == true for legacy reason)\n    ImGuiChildFlags_AlwaysUseWindowPadding  = 1 << 1,   // Pad with style.WindowPadding even if no border are drawn (no padding by default for non-bordered child windows because it makes more sense)\n    ImGuiChildFlags_ResizeX                 = 1 << 2,   // Allow resize from right border (layout direction). Enable .ini saving (unless ImGuiWindowFlags_NoSavedSettings passed to window flags)\n    ImGuiChildFlags_ResizeY                 = 1 << 3,   // Allow resize from bottom border (layout direction). \"\n    ImGuiChildFlags_AutoResizeX             = 1 << 4,   // Enable auto-resizing width. Read \"IMPORTANT: Size measurement\" details above.\n    ImGuiChildFlags_AutoResizeY             = 1 << 5,   // Enable auto-resizing height. Read \"IMPORTANT: Size measurement\" details above.\n    ImGuiChildFlags_AlwaysAutoResize        = 1 << 6,   // Combined with AutoResizeX/AutoResizeY. Always measure size even when child is hidden, always return true, always disable clipping optimization! NOT RECOMMENDED.\n    ImGuiChildFlags_FrameStyle              = 1 << 7,   // Style the child window like a framed item: use FrameBg, FrameRounding, FrameBorderSize, FramePadding instead of ChildBg, ChildRounding, ChildBorderSize, WindowPadding.\n    ImGuiChildFlags_NavFlattened            = 1 << 8,   // [BETA] Share focus scope, allow keyboard/gamepad navigation to cross over parent border to this child or between sibling child windows.\n\n    // Obsolete names\n#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS\n    ImGuiChildFlags_Border                  = ImGuiChildFlags_Borders,  // Renamed in 1.91.1 (August 2024) for consistency.\n#endif\n};\n\n// Flags for ImGui::PushItemFlag()\n// (Those are shared by all items)\nenum ImGuiItemFlags_\n{\n    ImGuiItemFlags_None                     = 0,        // (Default)\n    ImGuiItemFlags_NoTabStop                = 1 << 0,   // false    // Disable keyboard tabbing. This is a \"lighter\" version of ImGuiItemFlags_NoNav.\n    ImGuiItemFlags_NoNav                    = 1 << 1,   // false    // Disable any form of focusing (keyboard/gamepad directional navigation and SetKeyboardFocusHere() calls).\n    ImGuiItemFlags_NoNavDefaultFocus        = 1 << 2,   // false    // Disable item being a candidate for default focus (e.g. used by title bar items).\n    ImGuiItemFlags_ButtonRepeat             = 1 << 3,   // false    // Any button-like behavior will have repeat mode enabled (based on io.KeyRepeatDelay and io.KeyRepeatRate values). Note that you can also call IsItemActive() after any button to tell if it is being held.\n    ImGuiItemFlags_AutoClosePopups          = 1 << 4,   // true     // MenuItem()/Selectable() automatically close their parent popup window.\n    ImGuiItemFlags_AllowDuplicateId         = 1 << 5,   // false    // Allow submitting an item with the same identifier as an item already submitted this frame without triggering a warning tooltip if io.ConfigDebugHighlightIdConflicts is set.\n};\n\n// Flags for ImGui::InputText()\n// (Those are per-item flags. There are shared flags in ImGuiIO: io.ConfigInputTextCursorBlink and io.ConfigInputTextEnterKeepActive)\nenum ImGuiInputTextFlags_\n{\n    // Basic filters (also see ImGuiInputTextFlags_CallbackCharFilter)\n    ImGuiInputTextFlags_None                = 0,\n    ImGuiInputTextFlags_CharsDecimal        = 1 << 0,   // Allow 0123456789.+-*/\n    ImGuiInputTextFlags_CharsHexadecimal    = 1 << 1,   // Allow 0123456789ABCDEFabcdef\n    ImGuiInputTextFlags_CharsScientific     = 1 << 2,   // Allow 0123456789.+-*/eE (Scientific notation input)\n    ImGuiInputTextFlags_CharsUppercase      = 1 << 3,   // Turn a..z into A..Z\n    ImGuiInputTextFlags_CharsNoBlank        = 1 << 4,   // Filter out spaces, tabs\n\n    // Inputs\n    ImGuiInputTextFlags_AllowTabInput       = 1 << 5,   // Pressing TAB input a '\\t' character into the text field\n    ImGuiInputTextFlags_EnterReturnsTrue    = 1 << 6,   // Return 'true' when Enter is pressed (as opposed to every time the value was modified). Consider using IsItemDeactivatedAfterEdit() instead!\n    ImGuiInputTextFlags_EscapeClearsAll     = 1 << 7,   // Escape key clears content if not empty, and deactivate otherwise (contrast to default behavior of Escape to revert)\n    ImGuiInputTextFlags_CtrlEnterForNewLine = 1 << 8,   // In multi-line mode, validate with Enter, add new line with Ctrl+Enter (default is opposite: validate with Ctrl+Enter, add line with Enter).\n\n    // Other options\n    ImGuiInputTextFlags_ReadOnly            = 1 << 9,   // Read-only mode\n    ImGuiInputTextFlags_Password            = 1 << 10,  // Password mode, display all characters as '*', disable copy\n    ImGuiInputTextFlags_AlwaysOverwrite     = 1 << 11,  // Overwrite mode\n    ImGuiInputTextFlags_AutoSelectAll       = 1 << 12,  // Select entire text when first taking mouse focus\n    ImGuiInputTextFlags_ParseEmptyRefVal    = 1 << 13,  // InputFloat(), InputInt(), InputScalar() etc. only: parse empty string as zero value.\n    ImGuiInputTextFlags_DisplayEmptyRefVal  = 1 << 14,  // InputFloat(), InputInt(), InputScalar() etc. only: when value is zero, do not display it. Generally used with ImGuiInputTextFlags_ParseEmptyRefVal.\n    ImGuiInputTextFlags_NoHorizontalScroll  = 1 << 15,  // Disable following the cursor horizontally\n    ImGuiInputTextFlags_NoUndoRedo          = 1 << 16,  // Disable undo/redo. Note that input text owns the text data while active, if you want to provide your own undo/redo stack you need e.g. to call ClearActiveID().\n\n    // Elide display / Alignment\n    ImGuiInputTextFlags_ElideLeft           = 1 << 17,  // When text doesn't fit, elide left side to ensure right side stays visible. Useful for path/filenames. Single-line only!\n\n    // Callback features\n    ImGuiInputTextFlags_CallbackCompletion  = 1 << 18,  // Callback on pressing TAB (for completion handling)\n    ImGuiInputTextFlags_CallbackHistory     = 1 << 19,  // Callback on pressing Up/Down arrows (for history handling)\n    ImGuiInputTextFlags_CallbackAlways      = 1 << 20,  // Callback on each iteration. User code may query cursor position, modify text buffer.\n    ImGuiInputTextFlags_CallbackCharFilter  = 1 << 21,  // Callback on character inputs to replace or discard them. Modify 'EventChar' to replace or discard, or return 1 in callback to discard.\n    ImGuiInputTextFlags_CallbackResize      = 1 << 22,  // Callback on buffer capacity changes request (beyond 'buf_size' parameter value), allowing the string to grow. Notify when the string wants to be resized (for string types which hold a cache of their Size). You will be provided a new BufSize in the callback and NEED to honor it. (see misc/cpp/imgui_stdlib.h for an example of using this)\n    ImGuiInputTextFlags_CallbackEdit        = 1 << 23,  // Callback on any edit. Note that InputText() already returns true on edit + you can always use IsItemEdited(). The callback is useful to manipulate the underlying buffer while focus is active.\n\n    // Obsolete names\n    //ImGuiInputTextFlags_AlwaysInsertMode  = ImGuiInputTextFlags_AlwaysOverwrite   // [renamed in 1.82] name was not matching behavior\n};\n\n// Flags for ImGui::TreeNodeEx(), ImGui::CollapsingHeader*()\nenum ImGuiTreeNodeFlags_\n{\n    ImGuiTreeNodeFlags_None                 = 0,\n    ImGuiTreeNodeFlags_Selected             = 1 << 0,   // Draw as selected\n    ImGuiTreeNodeFlags_Framed               = 1 << 1,   // Draw frame with background (e.g. for CollapsingHeader)\n    ImGuiTreeNodeFlags_AllowOverlap         = 1 << 2,   // Hit testing to allow subsequent widgets to overlap this one\n    ImGuiTreeNodeFlags_NoTreePushOnOpen     = 1 << 3,   // Don't do a TreePush() when open (e.g. for CollapsingHeader) = no extra indent nor pushing on ID stack\n    ImGuiTreeNodeFlags_NoAutoOpenOnLog      = 1 << 4,   // Don't automatically and temporarily open node when Logging is active (by default logging will automatically open tree nodes)\n    ImGuiTreeNodeFlags_DefaultOpen          = 1 << 5,   // Default node to be open\n    ImGuiTreeNodeFlags_OpenOnDoubleClick    = 1 << 6,   // Open on double-click instead of simple click (default for multi-select unless any _OpenOnXXX behavior is set explicitly). Both behaviors may be combined.\n    ImGuiTreeNodeFlags_OpenOnArrow          = 1 << 7,   // Open when clicking on the arrow part (default for multi-select unless any _OpenOnXXX behavior is set explicitly). Both behaviors may be combined.\n    ImGuiTreeNodeFlags_Leaf                 = 1 << 8,   // No collapsing, no arrow (use as a convenience for leaf nodes).\n    ImGuiTreeNodeFlags_Bullet               = 1 << 9,   // Display a bullet instead of arrow. IMPORTANT: node can still be marked open/close if you don't set the _Leaf flag!\n    ImGuiTreeNodeFlags_FramePadding         = 1 << 10,  // Use FramePadding (even for an unframed text node) to vertically align text baseline to regular widget height. Equivalent to calling AlignTextToFramePadding() before the node.\n    ImGuiTreeNodeFlags_SpanAvailWidth       = 1 << 11,  // Extend hit box to the right-most edge, even if not framed. This is not the default in order to allow adding other items on the same line without using AllowOverlap mode.\n    ImGuiTreeNodeFlags_SpanFullWidth        = 1 << 12,  // Extend hit box to the left-most and right-most edges (cover the indent area).\n    ImGuiTreeNodeFlags_SpanLabelWidth       = 1 << 13,  // Narrow hit box + narrow hovering highlight, will only cover the label text.\n    ImGuiTreeNodeFlags_SpanAllColumns       = 1 << 14,  // Frame will span all columns of its container table (label will still fit in current column)\n    ImGuiTreeNodeFlags_LabelSpanAllColumns  = 1 << 15,  // Label will span all columns of its container table\n    //ImGuiTreeNodeFlags_NoScrollOnOpen     = 1 << 16,  // FIXME: TODO: Disable automatic scroll on TreePop() if node got just open and contents is not visible\n    ImGuiTreeNodeFlags_NavLeftJumpsBackHere = 1 << 17,  // (WIP) Nav: left direction may move to this TreeNode() from any of its child (items submitted between TreeNode and TreePop)\n    ImGuiTreeNodeFlags_CollapsingHeader     = ImGuiTreeNodeFlags_Framed | ImGuiTreeNodeFlags_NoTreePushOnOpen | ImGuiTreeNodeFlags_NoAutoOpenOnLog,\n\n#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS\n    ImGuiTreeNodeFlags_AllowItemOverlap     = ImGuiTreeNodeFlags_AllowOverlap,  // Renamed in 1.89.7\n    ImGuiTreeNodeFlags_SpanTextWidth        = ImGuiTreeNodeFlags_SpanLabelWidth,// Renamed in 1.90.7\n#endif\n};\n\n// Flags for OpenPopup*(), BeginPopupContext*(), IsPopupOpen() functions.\n// - To be backward compatible with older API which took an 'int mouse_button = 1' argument instead of 'ImGuiPopupFlags flags',\n//   we need to treat small flags values as a mouse button index, so we encode the mouse button in the first few bits of the flags.\n//   It is therefore guaranteed to be legal to pass a mouse button index in ImGuiPopupFlags.\n// - For the same reason, we exceptionally default the ImGuiPopupFlags argument of BeginPopupContextXXX functions to 1 instead of 0.\n//   IMPORTANT: because the default parameter is 1 (==ImGuiPopupFlags_MouseButtonRight), if you rely on the default parameter\n//   and want to use another flag, you need to pass in the ImGuiPopupFlags_MouseButtonRight flag explicitly.\n// - Multiple buttons currently cannot be combined/or-ed in those functions (we could allow it later).\nenum ImGuiPopupFlags_\n{\n    ImGuiPopupFlags_None                    = 0,\n    ImGuiPopupFlags_MouseButtonLeft         = 0,        // For BeginPopupContext*(): open on Left Mouse release. Guaranteed to always be == 0 (same as ImGuiMouseButton_Left)\n    ImGuiPopupFlags_MouseButtonRight        = 1,        // For BeginPopupContext*(): open on Right Mouse release. Guaranteed to always be == 1 (same as ImGuiMouseButton_Right)\n    ImGuiPopupFlags_MouseButtonMiddle       = 2,        // For BeginPopupContext*(): open on Middle Mouse release. Guaranteed to always be == 2 (same as ImGuiMouseButton_Middle)\n    ImGuiPopupFlags_MouseButtonMask_        = 0x1F,\n    ImGuiPopupFlags_MouseButtonDefault_     = 1,\n    ImGuiPopupFlags_NoReopen                = 1 << 5,   // For OpenPopup*(), BeginPopupContext*(): don't reopen same popup if already open (won't reposition, won't reinitialize navigation)\n    //ImGuiPopupFlags_NoReopenAlwaysNavInit = 1 << 6,   // For OpenPopup*(), BeginPopupContext*(): focus and initialize navigation even when not reopening.\n    ImGuiPopupFlags_NoOpenOverExistingPopup = 1 << 7,   // For OpenPopup*(), BeginPopupContext*(): don't open if there's already a popup at the same level of the popup stack\n    ImGuiPopupFlags_NoOpenOverItems         = 1 << 8,   // For BeginPopupContextWindow(): don't return true when hovering items, only when hovering empty space\n    ImGuiPopupFlags_AnyPopupId              = 1 << 10,  // For IsPopupOpen(): ignore the ImGuiID parameter and test for any popup.\n    ImGuiPopupFlags_AnyPopupLevel           = 1 << 11,  // For IsPopupOpen(): search/test at any level of the popup stack (default test in the current level)\n    ImGuiPopupFlags_AnyPopup                = ImGuiPopupFlags_AnyPopupId | ImGuiPopupFlags_AnyPopupLevel,\n};\n\n// Flags for ImGui::Selectable()\nenum ImGuiSelectableFlags_\n{\n    ImGuiSelectableFlags_None               = 0,\n    ImGuiSelectableFlags_NoAutoClosePopups  = 1 << 0,   // Clicking this doesn't close parent popup window (overrides ImGuiItemFlags_AutoClosePopups)\n    ImGuiSelectableFlags_SpanAllColumns     = 1 << 1,   // Frame will span all columns of its container table (text will still fit in current column)\n    ImGuiSelectableFlags_AllowDoubleClick   = 1 << 2,   // Generate press events on double clicks too\n    ImGuiSelectableFlags_Disabled           = 1 << 3,   // Cannot be selected, display grayed out text\n    ImGuiSelectableFlags_AllowOverlap       = 1 << 4,   // (WIP) Hit testing to allow subsequent widgets to overlap this one\n    ImGuiSelectableFlags_Highlight          = 1 << 5,   // Make the item be displayed as if it is hovered\n\n#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS\n    ImGuiSelectableFlags_DontClosePopups    = ImGuiSelectableFlags_NoAutoClosePopups,   // Renamed in 1.91.0\n    ImGuiSelectableFlags_AllowItemOverlap   = ImGuiSelectableFlags_AllowOverlap,        // Renamed in 1.89.7\n#endif\n};\n\n// Flags for ImGui::BeginCombo()\nenum ImGuiComboFlags_\n{\n    ImGuiComboFlags_None                    = 0,\n    ImGuiComboFlags_PopupAlignLeft          = 1 << 0,   // Align the popup toward the left by default\n    ImGuiComboFlags_HeightSmall             = 1 << 1,   // Max ~4 items visible. Tip: If you want your combo popup to be a specific size you can use SetNextWindowSizeConstraints() prior to calling BeginCombo()\n    ImGuiComboFlags_HeightRegular           = 1 << 2,   // Max ~8 items visible (default)\n    ImGuiComboFlags_HeightLarge             = 1 << 3,   // Max ~20 items visible\n    ImGuiComboFlags_HeightLargest           = 1 << 4,   // As many fitting items as possible\n    ImGuiComboFlags_NoArrowButton           = 1 << 5,   // Display on the preview box without the square arrow button\n    ImGuiComboFlags_NoPreview               = 1 << 6,   // Display only a square arrow button\n    ImGuiComboFlags_WidthFitPreview         = 1 << 7,   // Width dynamically calculated from preview contents\n    ImGuiComboFlags_HeightMask_             = ImGuiComboFlags_HeightSmall | ImGuiComboFlags_HeightRegular | ImGuiComboFlags_HeightLarge | ImGuiComboFlags_HeightLargest,\n};\n\n// Flags for ImGui::BeginTabBar()\nenum ImGuiTabBarFlags_\n{\n    ImGuiTabBarFlags_None                           = 0,\n    ImGuiTabBarFlags_Reorderable                    = 1 << 0,   // Allow manually dragging tabs to re-order them + New tabs are appended at the end of list\n    ImGuiTabBarFlags_AutoSelectNewTabs              = 1 << 1,   // Automatically select new tabs when they appear\n    ImGuiTabBarFlags_TabListPopupButton             = 1 << 2,   // Disable buttons to open the tab list popup\n    ImGuiTabBarFlags_NoCloseWithMiddleMouseButton   = 1 << 3,   // Disable behavior of closing tabs (that are submitted with p_open != NULL) with middle mouse button. You may handle this behavior manually on user's side with if (IsItemHovered() && IsMouseClicked(2)) *p_open = false.\n    ImGuiTabBarFlags_NoTabListScrollingButtons      = 1 << 4,   // Disable scrolling buttons (apply when fitting policy is ImGuiTabBarFlags_FittingPolicyScroll)\n    ImGuiTabBarFlags_NoTooltip                      = 1 << 5,   // Disable tooltips when hovering a tab\n    ImGuiTabBarFlags_DrawSelectedOverline           = 1 << 6,   // Draw selected overline markers over selected tab\n    ImGuiTabBarFlags_FittingPolicyResizeDown        = 1 << 7,   // Resize tabs when they don't fit\n    ImGuiTabBarFlags_FittingPolicyScroll            = 1 << 8,   // Add scroll buttons when tabs don't fit\n    ImGuiTabBarFlags_FittingPolicyMask_             = ImGuiTabBarFlags_FittingPolicyResizeDown | ImGuiTabBarFlags_FittingPolicyScroll,\n    ImGuiTabBarFlags_FittingPolicyDefault_          = ImGuiTabBarFlags_FittingPolicyResizeDown,\n};\n\n// Flags for ImGui::BeginTabItem()\nenum ImGuiTabItemFlags_\n{\n    ImGuiTabItemFlags_None                          = 0,\n    ImGuiTabItemFlags_UnsavedDocument               = 1 << 0,   // Display a dot next to the title + set ImGuiTabItemFlags_NoAssumedClosure.\n    ImGuiTabItemFlags_SetSelected                   = 1 << 1,   // Trigger flag to programmatically make the tab selected when calling BeginTabItem()\n    ImGuiTabItemFlags_NoCloseWithMiddleMouseButton  = 1 << 2,   // Disable behavior of closing tabs (that are submitted with p_open != NULL) with middle mouse button. You may handle this behavior manually on user's side with if (IsItemHovered() && IsMouseClicked(2)) *p_open = false.\n    ImGuiTabItemFlags_NoPushId                      = 1 << 3,   // Don't call PushID()/PopID() on BeginTabItem()/EndTabItem()\n    ImGuiTabItemFlags_NoTooltip                     = 1 << 4,   // Disable tooltip for the given tab\n    ImGuiTabItemFlags_NoReorder                     = 1 << 5,   // Disable reordering this tab or having another tab cross over this tab\n    ImGuiTabItemFlags_Leading                       = 1 << 6,   // Enforce the tab position to the left of the tab bar (after the tab list popup button)\n    ImGuiTabItemFlags_Trailing                      = 1 << 7,   // Enforce the tab position to the right of the tab bar (before the scrolling buttons)\n    ImGuiTabItemFlags_NoAssumedClosure              = 1 << 8,   // Tab is selected when trying to close + closure is not immediately assumed (will wait for user to stop submitting the tab). Otherwise closure is assumed when pressing the X, so if you keep submitting the tab may reappear at end of tab bar.\n};\n\n// Flags for ImGui::IsWindowFocused()\nenum ImGuiFocusedFlags_\n{\n    ImGuiFocusedFlags_None                          = 0,\n    ImGuiFocusedFlags_ChildWindows                  = 1 << 0,   // Return true if any children of the window is focused\n    ImGuiFocusedFlags_RootWindow                    = 1 << 1,   // Test from root window (top most parent of the current hierarchy)\n    ImGuiFocusedFlags_AnyWindow                     = 1 << 2,   // Return true if any window is focused. Important: If you are trying to tell how to dispatch your low-level inputs, do NOT use this. Use 'io.WantCaptureMouse' instead! Please read the FAQ!\n    ImGuiFocusedFlags_NoPopupHierarchy              = 1 << 3,   // Do not consider popup hierarchy (do not treat popup emitter as parent of popup) (when used with _ChildWindows or _RootWindow)\n    //ImGuiFocusedFlags_DockHierarchy               = 1 << 4,   // Consider docking hierarchy (treat dockspace host as parent of docked window) (when used with _ChildWindows or _RootWindow)\n    ImGuiFocusedFlags_RootAndChildWindows           = ImGuiFocusedFlags_RootWindow | ImGuiFocusedFlags_ChildWindows,\n};\n\n// Flags for ImGui::IsItemHovered(), ImGui::IsWindowHovered()\n// Note: if you are trying to check whether your mouse should be dispatched to Dear ImGui or to your app, you should use 'io.WantCaptureMouse' instead! Please read the FAQ!\n// Note: windows with the ImGuiWindowFlags_NoInputs flag are ignored by IsWindowHovered() calls.\nenum ImGuiHoveredFlags_\n{\n    ImGuiHoveredFlags_None                          = 0,        // Return true if directly over the item/window, not obstructed by another window, not obstructed by an active popup or modal blocking inputs under them.\n    ImGuiHoveredFlags_ChildWindows                  = 1 << 0,   // IsWindowHovered() only: Return true if any children of the window is hovered\n    ImGuiHoveredFlags_RootWindow                    = 1 << 1,   // IsWindowHovered() only: Test from root window (top most parent of the current hierarchy)\n    ImGuiHoveredFlags_AnyWindow                     = 1 << 2,   // IsWindowHovered() only: Return true if any window is hovered\n    ImGuiHoveredFlags_NoPopupHierarchy              = 1 << 3,   // IsWindowHovered() only: Do not consider popup hierarchy (do not treat popup emitter as parent of popup) (when used with _ChildWindows or _RootWindow)\n    //ImGuiHoveredFlags_DockHierarchy               = 1 << 4,   // IsWindowHovered() only: Consider docking hierarchy (treat dockspace host as parent of docked window) (when used with _ChildWindows or _RootWindow)\n    ImGuiHoveredFlags_AllowWhenBlockedByPopup       = 1 << 5,   // Return true even if a popup window is normally blocking access to this item/window\n    //ImGuiHoveredFlags_AllowWhenBlockedByModal     = 1 << 6,   // Return true even if a modal popup window is normally blocking access to this item/window. FIXME-TODO: Unavailable yet.\n    ImGuiHoveredFlags_AllowWhenBlockedByActiveItem  = 1 << 7,   // Return true even if an active item is blocking access to this item/window. Useful for Drag and Drop patterns.\n    ImGuiHoveredFlags_AllowWhenOverlappedByItem     = 1 << 8,   // IsItemHovered() only: Return true even if the item uses AllowOverlap mode and is overlapped by another hoverable item.\n    ImGuiHoveredFlags_AllowWhenOverlappedByWindow   = 1 << 9,   // IsItemHovered() only: Return true even if the position is obstructed or overlapped by another window.\n    ImGuiHoveredFlags_AllowWhenDisabled             = 1 << 10,  // IsItemHovered() only: Return true even if the item is disabled\n    ImGuiHoveredFlags_NoNavOverride                 = 1 << 11,  // IsItemHovered() only: Disable using keyboard/gamepad navigation state when active, always query mouse\n    ImGuiHoveredFlags_AllowWhenOverlapped           = ImGuiHoveredFlags_AllowWhenOverlappedByItem | ImGuiHoveredFlags_AllowWhenOverlappedByWindow,\n    ImGuiHoveredFlags_RectOnly                      = ImGuiHoveredFlags_AllowWhenBlockedByPopup | ImGuiHoveredFlags_AllowWhenBlockedByActiveItem | ImGuiHoveredFlags_AllowWhenOverlapped,\n    ImGuiHoveredFlags_RootAndChildWindows           = ImGuiHoveredFlags_RootWindow | ImGuiHoveredFlags_ChildWindows,\n\n    // Tooltips mode\n    // - typically used in IsItemHovered() + SetTooltip() sequence.\n    // - this is a shortcut to pull flags from 'style.HoverFlagsForTooltipMouse' or 'style.HoverFlagsForTooltipNav' where you can reconfigure desired behavior.\n    //   e.g. 'TooltipHoveredFlagsForMouse' defaults to 'ImGuiHoveredFlags_Stationary | ImGuiHoveredFlags_DelayShort'.\n    // - for frequently actioned or hovered items providing a tooltip, you want may to use ImGuiHoveredFlags_ForTooltip (stationary + delay) so the tooltip doesn't show too often.\n    // - for items which main purpose is to be hovered, or items with low affordance, or in less consistent apps, prefer no delay or shorter delay.\n    ImGuiHoveredFlags_ForTooltip                    = 1 << 12,  // Shortcut for standard flags when using IsItemHovered() + SetTooltip() sequence.\n\n    // (Advanced) Mouse Hovering delays.\n    // - generally you can use ImGuiHoveredFlags_ForTooltip to use application-standardized flags.\n    // - use those if you need specific overrides.\n    ImGuiHoveredFlags_Stationary                    = 1 << 13,  // Require mouse to be stationary for style.HoverStationaryDelay (~0.15 sec) _at least one time_. After this, can move on same item/window. Using the stationary test tends to reduces the need for a long delay.\n    ImGuiHoveredFlags_DelayNone                     = 1 << 14,  // IsItemHovered() only: Return true immediately (default). As this is the default you generally ignore this.\n    ImGuiHoveredFlags_DelayShort                    = 1 << 15,  // IsItemHovered() only: Return true after style.HoverDelayShort elapsed (~0.15 sec) (shared between items) + requires mouse to be stationary for style.HoverStationaryDelay (once per item).\n    ImGuiHoveredFlags_DelayNormal                   = 1 << 16,  // IsItemHovered() only: Return true after style.HoverDelayNormal elapsed (~0.40 sec) (shared between items) + requires mouse to be stationary for style.HoverStationaryDelay (once per item).\n    ImGuiHoveredFlags_NoSharedDelay                 = 1 << 17,  // IsItemHovered() only: Disable shared delay system where moving from one item to the next keeps the previous timer for a short time (standard for tooltips with long delays)\n};\n\n// Flags for ImGui::BeginDragDropSource(), ImGui::AcceptDragDropPayload()\nenum ImGuiDragDropFlags_\n{\n    ImGuiDragDropFlags_None                         = 0,\n    // BeginDragDropSource() flags\n    ImGuiDragDropFlags_SourceNoPreviewTooltip       = 1 << 0,   // Disable preview tooltip. By default, a successful call to BeginDragDropSource opens a tooltip so you can display a preview or description of the source contents. This flag disables this behavior.\n    ImGuiDragDropFlags_SourceNoDisableHover         = 1 << 1,   // By default, when dragging we clear data so that IsItemHovered() will return false, to avoid subsequent user code submitting tooltips. This flag disables this behavior so you can still call IsItemHovered() on the source item.\n    ImGuiDragDropFlags_SourceNoHoldToOpenOthers     = 1 << 2,   // Disable the behavior that allows to open tree nodes and collapsing header by holding over them while dragging a source item.\n    ImGuiDragDropFlags_SourceAllowNullID            = 1 << 3,   // Allow items such as Text(), Image() that have no unique identifier to be used as drag source, by manufacturing a temporary identifier based on their window-relative position. This is extremely unusual within the dear imgui ecosystem and so we made it explicit.\n    ImGuiDragDropFlags_SourceExtern                 = 1 << 4,   // External source (from outside of dear imgui), won't attempt to read current item/window info. Will always return true. Only one Extern source can be active simultaneously.\n    ImGuiDragDropFlags_PayloadAutoExpire            = 1 << 5,   // Automatically expire the payload if the source cease to be submitted (otherwise payloads are persisting while being dragged)\n    ImGuiDragDropFlags_PayloadNoCrossContext        = 1 << 6,   // Hint to specify that the payload may not be copied outside current dear imgui context.\n    ImGuiDragDropFlags_PayloadNoCrossProcess        = 1 << 7,   // Hint to specify that the payload may not be copied outside current process.\n    // AcceptDragDropPayload() flags\n    ImGuiDragDropFlags_AcceptBeforeDelivery         = 1 << 10,  // AcceptDragDropPayload() will returns true even before the mouse button is released. You can then call IsDelivery() to test if the payload needs to be delivered.\n    ImGuiDragDropFlags_AcceptNoDrawDefaultRect      = 1 << 11,  // Do not draw the default highlight rectangle when hovering over target.\n    ImGuiDragDropFlags_AcceptNoPreviewTooltip       = 1 << 12,  // Request hiding the BeginDragDropSource tooltip from the BeginDragDropTarget site.\n    ImGuiDragDropFlags_AcceptPeekOnly               = ImGuiDragDropFlags_AcceptBeforeDelivery | ImGuiDragDropFlags_AcceptNoDrawDefaultRect, // For peeking ahead and inspecting the payload before delivery.\n\n#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS\n    ImGuiDragDropFlags_SourceAutoExpirePayload = ImGuiDragDropFlags_PayloadAutoExpire, // Renamed in 1.90.9\n#endif\n};\n\n// Standard Drag and Drop payload types. You can define you own payload types using short strings. Types starting with '_' are defined by Dear ImGui.\n#define IMGUI_PAYLOAD_TYPE_COLOR_3F     \"_COL3F\"    // float[3]: Standard type for colors, without alpha. User code may use this type.\n#define IMGUI_PAYLOAD_TYPE_COLOR_4F     \"_COL4F\"    // float[4]: Standard type for colors. User code may use this type.\n\n// A primary data type\nenum ImGuiDataType_\n{\n    ImGuiDataType_S8,       // signed char / char (with sensible compilers)\n    ImGuiDataType_U8,       // unsigned char\n    ImGuiDataType_S16,      // short\n    ImGuiDataType_U16,      // unsigned short\n    ImGuiDataType_S32,      // int\n    ImGuiDataType_U32,      // unsigned int\n    ImGuiDataType_S64,      // long long / __int64\n    ImGuiDataType_U64,      // unsigned long long / unsigned __int64\n    ImGuiDataType_Float,    // float\n    ImGuiDataType_Double,   // double\n    ImGuiDataType_Bool,     // bool (provided for user convenience, not supported by scalar widgets)\n    ImGuiDataType_String,   // char* (provided for user convenience, not supported by scalar widgets)\n    ImGuiDataType_COUNT\n};\n\n// A cardinal direction\nenum ImGuiDir : int\n{\n    ImGuiDir_None    = -1,\n    ImGuiDir_Left    = 0,\n    ImGuiDir_Right   = 1,\n    ImGuiDir_Up      = 2,\n    ImGuiDir_Down    = 3,\n    ImGuiDir_COUNT\n};\n\n// A sorting direction\nenum ImGuiSortDirection : ImU8\n{\n    ImGuiSortDirection_None         = 0,\n    ImGuiSortDirection_Ascending    = 1,    // Ascending = 0->9, A->Z etc.\n    ImGuiSortDirection_Descending   = 2     // Descending = 9->0, Z->A etc.\n};\n\n// A key identifier (ImGuiKey_XXX or ImGuiMod_XXX value): can represent Keyboard, Mouse and Gamepad values.\n// All our named keys are >= 512. Keys value 0 to 511 are left unused and were legacy native/opaque key values (< 1.87).\n// Support for legacy keys was completely removed in 1.91.5.\n// Read details about the 1.87+ transition : https://github.com/ocornut/imgui/issues/4921\n// Note that \"Keys\" related to physical keys and are not the same concept as input \"Characters\", the later are submitted via io.AddInputCharacter().\n// The keyboard key enum values are named after the keys on a standard US keyboard, and on other keyboard types the keys reported may not match the keycaps.\nenum ImGuiKey : int\n{\n    // Keyboard\n    ImGuiKey_None = 0,\n    ImGuiKey_NamedKey_BEGIN = 512,  // First valid key value (other than 0)\n\n    ImGuiKey_Tab = 512,             // == ImGuiKey_NamedKey_BEGIN\n    ImGuiKey_LeftArrow,\n    ImGuiKey_RightArrow,\n    ImGuiKey_UpArrow,\n    ImGuiKey_DownArrow,\n    ImGuiKey_PageUp,\n    ImGuiKey_PageDown,\n    ImGuiKey_Home,\n    ImGuiKey_End,\n    ImGuiKey_Insert,\n    ImGuiKey_Delete,\n    ImGuiKey_Backspace,\n    ImGuiKey_Space,\n    ImGuiKey_Enter,\n    ImGuiKey_Escape,\n    ImGuiKey_LeftCtrl, ImGuiKey_LeftShift, ImGuiKey_LeftAlt, ImGuiKey_LeftSuper,\n    ImGuiKey_RightCtrl, ImGuiKey_RightShift, ImGuiKey_RightAlt, ImGuiKey_RightSuper,\n    ImGuiKey_Menu,\n    ImGuiKey_0, ImGuiKey_1, ImGuiKey_2, ImGuiKey_3, ImGuiKey_4, ImGuiKey_5, ImGuiKey_6, ImGuiKey_7, ImGuiKey_8, ImGuiKey_9,\n    ImGuiKey_A, ImGuiKey_B, ImGuiKey_C, ImGuiKey_D, ImGuiKey_E, ImGuiKey_F, ImGuiKey_G, ImGuiKey_H, ImGuiKey_I, ImGuiKey_J,\n    ImGuiKey_K, ImGuiKey_L, ImGuiKey_M, ImGuiKey_N, ImGuiKey_O, ImGuiKey_P, ImGuiKey_Q, ImGuiKey_R, ImGuiKey_S, ImGuiKey_T,\n    ImGuiKey_U, ImGuiKey_V, ImGuiKey_W, ImGuiKey_X, ImGuiKey_Y, ImGuiKey_Z,\n    ImGuiKey_F1, ImGuiKey_F2, ImGuiKey_F3, ImGuiKey_F4, ImGuiKey_F5, ImGuiKey_F6,\n    ImGuiKey_F7, ImGuiKey_F8, ImGuiKey_F9, ImGuiKey_F10, ImGuiKey_F11, ImGuiKey_F12,\n    ImGuiKey_F13, ImGuiKey_F14, ImGuiKey_F15, ImGuiKey_F16, ImGuiKey_F17, ImGuiKey_F18,\n    ImGuiKey_F19, ImGuiKey_F20, ImGuiKey_F21, ImGuiKey_F22, ImGuiKey_F23, ImGuiKey_F24,\n    ImGuiKey_Apostrophe,        // '\n    ImGuiKey_Comma,             // ,\n    ImGuiKey_Minus,             // -\n    ImGuiKey_Period,            // .\n    ImGuiKey_Slash,             // /\n    ImGuiKey_Semicolon,         // ;\n    ImGuiKey_Equal,             // =\n    ImGuiKey_LeftBracket,       // [\n    ImGuiKey_Backslash,         // \\ (this text inhibit multiline comment caused by backslash)\n    ImGuiKey_RightBracket,      // ]\n    ImGuiKey_GraveAccent,       // `\n    ImGuiKey_CapsLock,\n    ImGuiKey_ScrollLock,\n    ImGuiKey_NumLock,\n    ImGuiKey_PrintScreen,\n    ImGuiKey_Pause,\n    ImGuiKey_Keypad0, ImGuiKey_Keypad1, ImGuiKey_Keypad2, ImGuiKey_Keypad3, ImGuiKey_Keypad4,\n    ImGuiKey_Keypad5, ImGuiKey_Keypad6, ImGuiKey_Keypad7, ImGuiKey_Keypad8, ImGuiKey_Keypad9,\n    ImGuiKey_KeypadDecimal,\n    ImGuiKey_KeypadDivide,\n    ImGuiKey_KeypadMultiply,\n    ImGuiKey_KeypadSubtract,\n    ImGuiKey_KeypadAdd,\n    ImGuiKey_KeypadEnter,\n    ImGuiKey_KeypadEqual,\n    ImGuiKey_AppBack,               // Available on some keyboard/mouses. Often referred as \"Browser Back\"\n    ImGuiKey_AppForward,\n    ImGuiKey_Oem102,                // Non-US backslash.\n\n    // Gamepad (some of those are analog values, 0.0f to 1.0f)                          // NAVIGATION ACTION\n    // (download controller mapping PNG/PSD at http://dearimgui.com/controls_sheets)\n    ImGuiKey_GamepadStart,          // Menu (Xbox)      + (Switch)   Start/Options (PS)\n    ImGuiKey_GamepadBack,           // View (Xbox)      - (Switch)   Share (PS)\n    ImGuiKey_GamepadFaceLeft,       // X (Xbox)         Y (Switch)   Square (PS)        // Tap: Toggle Menu. Hold: Windowing mode (Focus/Move/Resize windows)\n    ImGuiKey_GamepadFaceRight,      // B (Xbox)         A (Switch)   Circle (PS)        // Cancel / Close / Exit\n    ImGuiKey_GamepadFaceUp,         // Y (Xbox)         X (Switch)   Triangle (PS)      // Text Input / On-screen Keyboard\n    ImGuiKey_GamepadFaceDown,       // A (Xbox)         B (Switch)   Cross (PS)         // Activate / Open / Toggle / Tweak\n    ImGuiKey_GamepadDpadLeft,       // D-pad Left                                       // Move / Tweak / Resize Window (in Windowing mode)\n    ImGuiKey_GamepadDpadRight,      // D-pad Right                                      // Move / Tweak / Resize Window (in Windowing mode)\n    ImGuiKey_GamepadDpadUp,         // D-pad Up                                         // Move / Tweak / Resize Window (in Windowing mode)\n    ImGuiKey_GamepadDpadDown,       // D-pad Down                                       // Move / Tweak / Resize Window (in Windowing mode)\n    ImGuiKey_GamepadL1,             // L Bumper (Xbox)  L (Switch)   L1 (PS)            // Tweak Slower / Focus Previous (in Windowing mode)\n    ImGuiKey_GamepadR1,             // R Bumper (Xbox)  R (Switch)   R1 (PS)            // Tweak Faster / Focus Next (in Windowing mode)\n    ImGuiKey_GamepadL2,             // L Trig. (Xbox)   ZL (Switch)  L2 (PS) [Analog]\n    ImGuiKey_GamepadR2,             // R Trig. (Xbox)   ZR (Switch)  R2 (PS) [Analog]\n    ImGuiKey_GamepadL3,             // L Stick (Xbox)   L3 (Switch)  L3 (PS)\n    ImGuiKey_GamepadR3,             // R Stick (Xbox)   R3 (Switch)  R3 (PS)\n    ImGuiKey_GamepadLStickLeft,     // [Analog]                                         // Move Window (in Windowing mode)\n    ImGuiKey_GamepadLStickRight,    // [Analog]                                         // Move Window (in Windowing mode)\n    ImGuiKey_GamepadLStickUp,       // [Analog]                                         // Move Window (in Windowing mode)\n    ImGuiKey_GamepadLStickDown,     // [Analog]                                         // Move Window (in Windowing mode)\n    ImGuiKey_GamepadRStickLeft,     // [Analog]\n    ImGuiKey_GamepadRStickRight,    // [Analog]\n    ImGuiKey_GamepadRStickUp,       // [Analog]\n    ImGuiKey_GamepadRStickDown,     // [Analog]\n\n    // Aliases: Mouse Buttons (auto-submitted from AddMouseButtonEvent() calls)\n    // - This is mirroring the data also written to io.MouseDown[], io.MouseWheel, in a format allowing them to be accessed via standard key API.\n    ImGuiKey_MouseLeft, ImGuiKey_MouseRight, ImGuiKey_MouseMiddle, ImGuiKey_MouseX1, ImGuiKey_MouseX2, ImGuiKey_MouseWheelX, ImGuiKey_MouseWheelY,\n\n    // [Internal] Reserved for mod storage\n    ImGuiKey_ReservedForModCtrl, ImGuiKey_ReservedForModShift, ImGuiKey_ReservedForModAlt, ImGuiKey_ReservedForModSuper,\n    ImGuiKey_NamedKey_END,\n\n    // Keyboard Modifiers (explicitly submitted by backend via AddKeyEvent() calls)\n    // - This is mirroring the data also written to io.KeyCtrl, io.KeyShift, io.KeyAlt, io.KeySuper, in a format allowing\n    //   them to be accessed via standard key API, allowing calls such as IsKeyPressed(), IsKeyReleased(), querying duration etc.\n    // - Code polling every key (e.g. an interface to detect a key press for input mapping) might want to ignore those\n    //   and prefer using the real keys (e.g. ImGuiKey_LeftCtrl, ImGuiKey_RightCtrl instead of ImGuiMod_Ctrl).\n    // - In theory the value of keyboard modifiers should be roughly equivalent to a logical or of the equivalent left/right keys.\n    //   In practice: it's complicated; mods are often provided from different sources. Keyboard layout, IME, sticky keys and\n    //   backends tend to interfere and break that equivalence. The safer decision is to relay that ambiguity down to the end-user...\n    // - On macOS, we swap Cmd(Super) and Ctrl keys at the time of the io.AddKeyEvent() call.\n    ImGuiMod_None                   = 0,\n    ImGuiMod_Ctrl                   = 1 << 12, // Ctrl (non-macOS), Cmd (macOS)\n    ImGuiMod_Shift                  = 1 << 13, // Shift\n    ImGuiMod_Alt                    = 1 << 14, // Option/Menu\n    ImGuiMod_Super                  = 1 << 15, // Windows/Super (non-macOS), Ctrl (macOS)\n    ImGuiMod_Mask_                  = 0xF000,  // 4-bits\n\n    // [Internal] If you need to iterate all keys (for e.g. an input mapper) you may use ImGuiKey_NamedKey_BEGIN..ImGuiKey_NamedKey_END.\n    ImGuiKey_NamedKey_COUNT         = ImGuiKey_NamedKey_END - ImGuiKey_NamedKey_BEGIN,\n    //ImGuiKey_KeysData_SIZE        = ImGuiKey_NamedKey_COUNT,  // Size of KeysData[]: only hold named keys\n    //ImGuiKey_KeysData_OFFSET      = ImGuiKey_NamedKey_BEGIN,  // Accesses to io.KeysData[] must use (key - ImGuiKey_NamedKey_BEGIN) index.\n\n#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS\n    ImGuiKey_COUNT                  = ImGuiKey_NamedKey_END,    // Obsoleted in 1.91.5 because it was extremely misleading (since named keys don't start at 0 anymore)\n    ImGuiMod_Shortcut               = ImGuiMod_Ctrl,            // Removed in 1.90.7, you can now simply use ImGuiMod_Ctrl\n    ImGuiKey_ModCtrl = ImGuiMod_Ctrl, ImGuiKey_ModShift = ImGuiMod_Shift, ImGuiKey_ModAlt = ImGuiMod_Alt, ImGuiKey_ModSuper = ImGuiMod_Super, // Renamed in 1.89\n    //ImGuiKey_KeyPadEnter = ImGuiKey_KeypadEnter,              // Renamed in 1.87\n#endif\n};\n\n// Flags for Shortcut(), SetNextItemShortcut(),\n// (and for upcoming extended versions of IsKeyPressed(), IsMouseClicked(), Shortcut(), SetKeyOwner(), SetItemKeyOwner() that are still in imgui_internal.h)\n// Don't mistake with ImGuiInputTextFlags! (which is for ImGui::InputText() function)\nenum ImGuiInputFlags_\n{\n    ImGuiInputFlags_None                    = 0,\n    ImGuiInputFlags_Repeat                  = 1 << 0,   // Enable repeat. Return true on successive repeats. Default for legacy IsKeyPressed(). NOT Default for legacy IsMouseClicked(). MUST BE == 1.\n\n    // Flags for Shortcut(), SetNextItemShortcut()\n    // - Routing policies: RouteGlobal+OverActive >> RouteActive or RouteFocused (if owner is active item) >> RouteGlobal+OverFocused >> RouteFocused (if in focused window stack) >> RouteGlobal.\n    // - Default policy is RouteFocused. Can select only 1 policy among all available.\n    ImGuiInputFlags_RouteActive             = 1 << 10,  // Route to active item only.\n    ImGuiInputFlags_RouteFocused            = 1 << 11,  // Route to windows in the focus stack (DEFAULT). Deep-most focused window takes inputs. Active item takes inputs over deep-most focused window.\n    ImGuiInputFlags_RouteGlobal             = 1 << 12,  // Global route (unless a focused window or active item registered the route).\n    ImGuiInputFlags_RouteAlways             = 1 << 13,  // Do not register route, poll keys directly.\n    // - Routing options\n    ImGuiInputFlags_RouteOverFocused        = 1 << 14,  // Option: global route: higher priority than focused route (unless active item in focused route).\n    ImGuiInputFlags_RouteOverActive         = 1 << 15,  // Option: global route: higher priority than active item. Unlikely you need to use that: will interfere with every active items, e.g. CTRL+A registered by InputText will be overridden by this. May not be fully honored as user/internal code is likely to always assume they can access keys when active.\n    ImGuiInputFlags_RouteUnlessBgFocused    = 1 << 16,  // Option: global route: will not be applied if underlying background/void is focused (== no Dear ImGui windows are focused). Useful for overlay applications.\n    ImGuiInputFlags_RouteFromRootWindow     = 1 << 17,  // Option: route evaluated from the point of view of root window rather than current window.\n\n    // Flags for SetNextItemShortcut()\n    ImGuiInputFlags_Tooltip                 = 1 << 18,  // Automatically display a tooltip when hovering item [BETA] Unsure of right api (opt-in/opt-out)\n};\n\n// Configuration flags stored in io.ConfigFlags. Set by user/application.\nenum ImGuiConfigFlags_\n{\n    ImGuiConfigFlags_None                   = 0,\n    ImGuiConfigFlags_NavEnableKeyboard      = 1 << 0,   // Master keyboard navigation enable flag. Enable full Tabbing + directional arrows + space/enter to activate.\n    ImGuiConfigFlags_NavEnableGamepad       = 1 << 1,   // Master gamepad navigation enable flag. Backend also needs to set ImGuiBackendFlags_HasGamepad.\n    ImGuiConfigFlags_NoMouse                = 1 << 4,   // Instruct dear imgui to disable mouse inputs and interactions.\n    ImGuiConfigFlags_NoMouseCursorChange    = 1 << 5,   // Instruct backend to not alter mouse cursor shape and visibility. Use if the backend cursor changes are interfering with yours and you don't want to use SetMouseCursor() to change mouse cursor. You may want to honor requests from imgui by reading GetMouseCursor() yourself instead.\n    ImGuiConfigFlags_NoKeyboard             = 1 << 6,   // Instruct dear imgui to disable keyboard inputs and interactions. This is done by ignoring keyboard events and clearing existing states.\n\n    // User storage (to allow your backend/engine to communicate to code that may be shared between multiple projects. Those flags are NOT used by core Dear ImGui)\n    ImGuiConfigFlags_IsSRGB                 = 1 << 20,  // Application is SRGB-aware.\n    ImGuiConfigFlags_IsTouchScreen          = 1 << 21,  // Application is using a touch screen instead of a mouse.\n\n#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS\n    ImGuiConfigFlags_NavEnableSetMousePos   = 1 << 2,   // [moved/renamed in 1.91.4] -> use bool io.ConfigNavMoveSetMousePos\n    ImGuiConfigFlags_NavNoCaptureKeyboard   = 1 << 3,   // [moved/renamed in 1.91.4] -> use bool io.ConfigNavCaptureKeyboard\n#endif\n};\n\n// Backend capabilities flags stored in io.BackendFlags. Set by imgui_impl_xxx or custom backend.\nenum ImGuiBackendFlags_\n{\n    ImGuiBackendFlags_None                  = 0,\n    ImGuiBackendFlags_HasGamepad            = 1 << 0,   // Backend Platform supports gamepad and currently has one connected.\n    ImGuiBackendFlags_HasMouseCursors       = 1 << 1,   // Backend Platform supports honoring GetMouseCursor() value to change the OS cursor shape.\n    ImGuiBackendFlags_HasSetMousePos        = 1 << 2,   // Backend Platform supports io.WantSetMousePos requests to reposition the OS mouse position (only used if io.ConfigNavMoveSetMousePos is set).\n    ImGuiBackendFlags_RendererHasVtxOffset  = 1 << 3,   // Backend Renderer supports ImDrawCmd::VtxOffset. This enables output of large meshes (64K+ vertices) while still using 16-bit indices.\n};\n\n// Enumeration for PushStyleColor() / PopStyleColor()\nenum ImGuiCol_\n{\n    ImGuiCol_Text,\n    ImGuiCol_TextDisabled,\n    ImGuiCol_WindowBg,              // Background of normal windows\n    ImGuiCol_ChildBg,               // Background of child windows\n    ImGuiCol_PopupBg,               // Background of popups, menus, tooltips windows\n    ImGuiCol_Border,\n    ImGuiCol_BorderShadow,\n    ImGuiCol_FrameBg,               // Background of checkbox, radio button, plot, slider, text input\n    ImGuiCol_FrameBgHovered,\n    ImGuiCol_FrameBgActive,\n    ImGuiCol_TitleBg,               // Title bar\n    ImGuiCol_TitleBgActive,         // Title bar when focused\n    ImGuiCol_TitleBgCollapsed,      // Title bar when collapsed\n    ImGuiCol_MenuBarBg,\n    ImGuiCol_ScrollbarBg,\n    ImGuiCol_ScrollbarGrab,\n    ImGuiCol_ScrollbarGrabHovered,\n    ImGuiCol_ScrollbarGrabActive,\n    ImGuiCol_CheckMark,             // Checkbox tick and RadioButton circle\n    ImGuiCol_SliderGrab,\n    ImGuiCol_SliderGrabActive,\n    ImGuiCol_Button,\n    ImGuiCol_ButtonHovered,\n    ImGuiCol_ButtonActive,\n    ImGuiCol_Header,                // Header* colors are used for CollapsingHeader, TreeNode, Selectable, MenuItem\n    ImGuiCol_HeaderHovered,\n    ImGuiCol_HeaderActive,\n    ImGuiCol_Separator,\n    ImGuiCol_SeparatorHovered,\n    ImGuiCol_SeparatorActive,\n    ImGuiCol_ResizeGrip,            // Resize grip in lower-right and lower-left corners of windows.\n    ImGuiCol_ResizeGripHovered,\n    ImGuiCol_ResizeGripActive,\n    ImGuiCol_TabHovered,            // Tab background, when hovered\n    ImGuiCol_Tab,                   // Tab background, when tab-bar is focused & tab is unselected\n    ImGuiCol_TabSelected,           // Tab background, when tab-bar is focused & tab is selected\n    ImGuiCol_TabSelectedOverline,   // Tab horizontal overline, when tab-bar is focused & tab is selected\n    ImGuiCol_TabDimmed,             // Tab background, when tab-bar is unfocused & tab is unselected\n    ImGuiCol_TabDimmedSelected,     // Tab background, when tab-bar is unfocused & tab is selected\n    ImGuiCol_TabDimmedSelectedOverline,//..horizontal overline, when tab-bar is unfocused & tab is selected\n    ImGuiCol_PlotLines,\n    ImGuiCol_PlotLinesHovered,\n    ImGuiCol_PlotHistogram,\n    ImGuiCol_PlotHistogramHovered,\n    ImGuiCol_TableHeaderBg,         // Table header background\n    ImGuiCol_TableBorderStrong,     // Table outer and header borders (prefer using Alpha=1.0 here)\n    ImGuiCol_TableBorderLight,      // Table inner borders (prefer using Alpha=1.0 here)\n    ImGuiCol_TableRowBg,            // Table row background (even rows)\n    ImGuiCol_TableRowBgAlt,         // Table row background (odd rows)\n    ImGuiCol_TextLink,              // Hyperlink color\n    ImGuiCol_TextSelectedBg,\n    ImGuiCol_DragDropTarget,        // Rectangle highlighting a drop target\n    ImGuiCol_NavCursor,             // Color of keyboard/gamepad navigation cursor/rectangle, when visible\n    ImGuiCol_NavWindowingHighlight, // Highlight window when using CTRL+TAB\n    ImGuiCol_NavWindowingDimBg,     // Darken/colorize entire screen behind the CTRL+TAB window list, when active\n    ImGuiCol_ModalWindowDimBg,      // Darken/colorize entire screen behind a modal window, when one is active\n    ImGuiCol_COUNT,\n\n#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS\n    ImGuiCol_TabActive = ImGuiCol_TabSelected,                  // [renamed in 1.90.9]\n    ImGuiCol_TabUnfocused = ImGuiCol_TabDimmed,                 // [renamed in 1.90.9]\n    ImGuiCol_TabUnfocusedActive = ImGuiCol_TabDimmedSelected,   // [renamed in 1.90.9]\n    ImGuiCol_NavHighlight = ImGuiCol_NavCursor,                 // [renamed in 1.91.4]\n#endif\n};\n\n// Enumeration for PushStyleVar() / PopStyleVar() to temporarily modify the ImGuiStyle structure.\n// - The enum only refers to fields of ImGuiStyle which makes sense to be pushed/popped inside UI code.\n//   During initialization or between frames, feel free to just poke into ImGuiStyle directly.\n// - Tip: Use your programming IDE navigation facilities on the names in the _second column_ below to find the actual members and their description.\n//   - In Visual Studio: CTRL+comma (\"Edit.GoToAll\") can follow symbols inside comments, whereas CTRL+F12 (\"Edit.GoToImplementation\") cannot.\n//   - In Visual Studio w/ Visual Assist installed: ALT+G (\"VAssistX.GoToImplementation\") can also follow symbols inside comments.\n//   - In VS Code, CLion, etc.: CTRL+click can follow symbols inside comments.\n// - When changing this enum, you need to update the associated internal table GStyleVarInfo[] accordingly. This is where we link enum values to members offset/type.\nenum ImGuiStyleVar_\n{\n    // Enum name -------------------------- // Member in ImGuiStyle structure (see ImGuiStyle for descriptions)\n    ImGuiStyleVar_Alpha,                    // float     Alpha\n    ImGuiStyleVar_DisabledAlpha,            // float     DisabledAlpha\n    ImGuiStyleVar_WindowPadding,            // ImVec2    WindowPadding\n    ImGuiStyleVar_WindowRounding,           // float     WindowRounding\n    ImGuiStyleVar_WindowBorderSize,         // float     WindowBorderSize\n    ImGuiStyleVar_WindowMinSize,            // ImVec2    WindowMinSize\n    ImGuiStyleVar_WindowTitleAlign,         // ImVec2    WindowTitleAlign\n    ImGuiStyleVar_ChildRounding,            // float     ChildRounding\n    ImGuiStyleVar_ChildBorderSize,          // float     ChildBorderSize\n    ImGuiStyleVar_PopupRounding,            // float     PopupRounding\n    ImGuiStyleVar_PopupBorderSize,          // float     PopupBorderSize\n    ImGuiStyleVar_FramePadding,             // ImVec2    FramePadding\n    ImGuiStyleVar_FrameRounding,            // float     FrameRounding\n    ImGuiStyleVar_FrameBorderSize,          // float     FrameBorderSize\n    ImGuiStyleVar_ItemSpacing,              // ImVec2    ItemSpacing\n    ImGuiStyleVar_ItemInnerSpacing,         // ImVec2    ItemInnerSpacing\n    ImGuiStyleVar_IndentSpacing,            // float     IndentSpacing\n    ImGuiStyleVar_CellPadding,              // ImVec2    CellPadding\n    ImGuiStyleVar_ScrollbarSize,            // float     ScrollbarSize\n    ImGuiStyleVar_ScrollbarRounding,        // float     ScrollbarRounding\n    ImGuiStyleVar_GrabMinSize,              // float     GrabMinSize\n    ImGuiStyleVar_GrabRounding,             // float     GrabRounding\n    ImGuiStyleVar_ImageBorderSize,          // float     ImageBorderSize\n    ImGuiStyleVar_TabRounding,              // float     TabRounding\n    ImGuiStyleVar_TabBorderSize,            // float     TabBorderSize\n    ImGuiStyleVar_TabBarBorderSize,         // float     TabBarBorderSize\n    ImGuiStyleVar_TabBarOverlineSize,       // float     TabBarOverlineSize\n    ImGuiStyleVar_TableAngledHeadersAngle,  // float     TableAngledHeadersAngle\n    ImGuiStyleVar_TableAngledHeadersTextAlign,// ImVec2  TableAngledHeadersTextAlign\n    ImGuiStyleVar_ButtonTextAlign,          // ImVec2    ButtonTextAlign\n    ImGuiStyleVar_SelectableTextAlign,      // ImVec2    SelectableTextAlign\n    ImGuiStyleVar_SeparatorTextBorderSize,  // float     SeparatorTextBorderSize\n    ImGuiStyleVar_SeparatorTextAlign,       // ImVec2    SeparatorTextAlign\n    ImGuiStyleVar_SeparatorTextPadding,     // ImVec2    SeparatorTextPadding\n    ImGuiStyleVar_COUNT\n};\n\n// Flags for InvisibleButton() [extended in imgui_internal.h]\nenum ImGuiButtonFlags_\n{\n    ImGuiButtonFlags_None                   = 0,\n    ImGuiButtonFlags_MouseButtonLeft        = 1 << 0,   // React on left mouse button (default)\n    ImGuiButtonFlags_MouseButtonRight       = 1 << 1,   // React on right mouse button\n    ImGuiButtonFlags_MouseButtonMiddle      = 1 << 2,   // React on center mouse button\n    ImGuiButtonFlags_MouseButtonMask_       = ImGuiButtonFlags_MouseButtonLeft | ImGuiButtonFlags_MouseButtonRight | ImGuiButtonFlags_MouseButtonMiddle, // [Internal]\n    ImGuiButtonFlags_EnableNav              = 1 << 3,   // InvisibleButton(): do not disable navigation/tabbing. Otherwise disabled by default.\n};\n\n// Flags for ColorEdit3() / ColorEdit4() / ColorPicker3() / ColorPicker4() / ColorButton()\nenum ImGuiColorEditFlags_\n{\n    ImGuiColorEditFlags_None            = 0,\n    ImGuiColorEditFlags_NoAlpha         = 1 << 1,   //              // ColorEdit, ColorPicker, ColorButton: ignore Alpha component (will only read 3 components from the input pointer).\n    ImGuiColorEditFlags_NoPicker        = 1 << 2,   //              // ColorEdit: disable picker when clicking on color square.\n    ImGuiColorEditFlags_NoOptions       = 1 << 3,   //              // ColorEdit: disable toggling options menu when right-clicking on inputs/small preview.\n    ImGuiColorEditFlags_NoSmallPreview  = 1 << 4,   //              // ColorEdit, ColorPicker: disable color square preview next to the inputs. (e.g. to show only the inputs)\n    ImGuiColorEditFlags_NoInputs        = 1 << 5,   //              // ColorEdit, ColorPicker: disable inputs sliders/text widgets (e.g. to show only the small preview color square).\n    ImGuiColorEditFlags_NoTooltip       = 1 << 6,   //              // ColorEdit, ColorPicker, ColorButton: disable tooltip when hovering the preview.\n    ImGuiColorEditFlags_NoLabel         = 1 << 7,   //              // ColorEdit, ColorPicker: disable display of inline text label (the label is still forwarded to the tooltip and picker).\n    ImGuiColorEditFlags_NoSidePreview   = 1 << 8,   //              // ColorPicker: disable bigger color preview on right side of the picker, use small color square preview instead.\n    ImGuiColorEditFlags_NoDragDrop      = 1 << 9,   //              // ColorEdit: disable drag and drop target. ColorButton: disable drag and drop source.\n    ImGuiColorEditFlags_NoBorder        = 1 << 10,  //              // ColorButton: disable border (which is enforced by default)\n\n    // Alpha preview\n    // - Prior to 1.91.8 (2025/01/21): alpha was made opaque in the preview by default using old name ImGuiColorEditFlags_AlphaPreview.\n    // - We now display the preview as transparent by default. You can use ImGuiColorEditFlags_AlphaOpaque to use old behavior.\n    // - The new flags may be combined better and allow finer controls.\n    ImGuiColorEditFlags_AlphaOpaque     = 1 << 11,  //              // ColorEdit, ColorPicker, ColorButton: disable alpha in the preview,. Contrary to _NoAlpha it may still be edited when calling ColorEdit4()/ColorPicker4(). For ColorButton() this does the same as _NoAlpha.\n    ImGuiColorEditFlags_AlphaNoBg       = 1 << 12,  //              // ColorEdit, ColorPicker, ColorButton: disable rendering a checkerboard background behind transparent color.\n    ImGuiColorEditFlags_AlphaPreviewHalf= 1 << 13,  //              // ColorEdit, ColorPicker, ColorButton: display half opaque / half transparent preview.\n\n    // User Options (right-click on widget to change some of them).\n    ImGuiColorEditFlags_AlphaBar        = 1 << 16,  //              // ColorEdit, ColorPicker: show vertical alpha bar/gradient in picker.\n    ImGuiColorEditFlags_HDR             = 1 << 19,  //              // (WIP) ColorEdit: Currently only disable 0.0f..1.0f limits in RGBA edition (note: you probably want to use ImGuiColorEditFlags_Float flag as well).\n    ImGuiColorEditFlags_DisplayRGB      = 1 << 20,  // [Display]    // ColorEdit: override _display_ type among RGB/HSV/Hex. ColorPicker: select any combination using one or more of RGB/HSV/Hex.\n    ImGuiColorEditFlags_DisplayHSV      = 1 << 21,  // [Display]    // \"\n    ImGuiColorEditFlags_DisplayHex      = 1 << 22,  // [Display]    // \"\n    ImGuiColorEditFlags_Uint8           = 1 << 23,  // [DataType]   // ColorEdit, ColorPicker, ColorButton: _display_ values formatted as 0..255.\n    ImGuiColorEditFlags_Float           = 1 << 24,  // [DataType]   // ColorEdit, ColorPicker, ColorButton: _display_ values formatted as 0.0f..1.0f floats instead of 0..255 integers. No round-trip of value via integers.\n    ImGuiColorEditFlags_PickerHueBar    = 1 << 25,  // [Picker]     // ColorPicker: bar for Hue, rectangle for Sat/Value.\n    ImGuiColorEditFlags_PickerHueWheel  = 1 << 26,  // [Picker]     // ColorPicker: wheel for Hue, triangle for Sat/Value.\n    ImGuiColorEditFlags_InputRGB        = 1 << 27,  // [Input]      // ColorEdit, ColorPicker: input and output data in RGB format.\n    ImGuiColorEditFlags_InputHSV        = 1 << 28,  // [Input]      // ColorEdit, ColorPicker: input and output data in HSV format.\n\n    // Defaults Options. You can set application defaults using SetColorEditOptions(). The intent is that you probably don't want to\n    // override them in most of your calls. Let the user choose via the option menu and/or call SetColorEditOptions() once during startup.\n    ImGuiColorEditFlags_DefaultOptions_ = ImGuiColorEditFlags_Uint8 | ImGuiColorEditFlags_DisplayRGB | ImGuiColorEditFlags_InputRGB | ImGuiColorEditFlags_PickerHueBar,\n\n    // [Internal] Masks\n    ImGuiColorEditFlags_AlphaMask_      = ImGuiColorEditFlags_NoAlpha | ImGuiColorEditFlags_AlphaOpaque | ImGuiColorEditFlags_AlphaNoBg | ImGuiColorEditFlags_AlphaPreviewHalf,\n    ImGuiColorEditFlags_DisplayMask_    = ImGuiColorEditFlags_DisplayRGB | ImGuiColorEditFlags_DisplayHSV | ImGuiColorEditFlags_DisplayHex,\n    ImGuiColorEditFlags_DataTypeMask_   = ImGuiColorEditFlags_Uint8 | ImGuiColorEditFlags_Float,\n    ImGuiColorEditFlags_PickerMask_     = ImGuiColorEditFlags_PickerHueWheel | ImGuiColorEditFlags_PickerHueBar,\n    ImGuiColorEditFlags_InputMask_      = ImGuiColorEditFlags_InputRGB | ImGuiColorEditFlags_InputHSV,\n\n    // Obsolete names\n#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS\n    ImGuiColorEditFlags_AlphaPreview = 0,         // [Removed in 1.91.8] This is the default now. Will display a checkerboard unless ImGuiColorEditFlags_AlphaNoBg is set.\n#endif\n    //ImGuiColorEditFlags_RGB = ImGuiColorEditFlags_DisplayRGB, ImGuiColorEditFlags_HSV = ImGuiColorEditFlags_DisplayHSV, ImGuiColorEditFlags_HEX = ImGuiColorEditFlags_DisplayHex  // [renamed in 1.69]\n};\n\n// Flags for DragFloat(), DragInt(), SliderFloat(), SliderInt() etc.\n// We use the same sets of flags for DragXXX() and SliderXXX() functions as the features are the same and it makes it easier to swap them.\n// (Those are per-item flags. There is shared behavior flag too: ImGuiIO: io.ConfigDragClickToInputText)\nenum ImGuiSliderFlags_\n{\n    ImGuiSliderFlags_None               = 0,\n    ImGuiSliderFlags_Logarithmic        = 1 << 5,       // Make the widget logarithmic (linear otherwise). Consider using ImGuiSliderFlags_NoRoundToFormat with this if using a format-string with small amount of digits.\n    ImGuiSliderFlags_NoRoundToFormat    = 1 << 6,       // Disable rounding underlying value to match precision of the display format string (e.g. %.3f values are rounded to those 3 digits).\n    ImGuiSliderFlags_NoInput            = 1 << 7,       // Disable CTRL+Click or Enter key allowing to input text directly into the widget.\n    ImGuiSliderFlags_WrapAround         = 1 << 8,       // Enable wrapping around from max to min and from min to max. Only supported by DragXXX() functions for now.\n    ImGuiSliderFlags_ClampOnInput       = 1 << 9,       // Clamp value to min/max bounds when input manually with CTRL+Click. By default CTRL+Click allows going out of bounds.\n    ImGuiSliderFlags_ClampZeroRange     = 1 << 10,      // Clamp even if min==max==0.0f. Otherwise due to legacy reason DragXXX functions don't clamp with those values. When your clamping limits are dynamic you almost always want to use it.\n    ImGuiSliderFlags_NoSpeedTweaks      = 1 << 11,      // Disable keyboard modifiers altering tweak speed. Useful if you want to alter tweak speed yourself based on your own logic.\n    ImGuiSliderFlags_AlwaysClamp        = ImGuiSliderFlags_ClampOnInput | ImGuiSliderFlags_ClampZeroRange,\n    ImGuiSliderFlags_InvalidMask_       = 0x7000000F,   // [Internal] We treat using those bits as being potentially a 'float power' argument from the previous API that has got miscast to this enum, and will trigger an assert if needed.\n};\n\n// Identify a mouse button.\n// Those values are guaranteed to be stable and we frequently use 0/1 directly. Named enums provided for convenience.\nenum ImGuiMouseButton_\n{\n    ImGuiMouseButton_Left = 0,\n    ImGuiMouseButton_Right = 1,\n    ImGuiMouseButton_Middle = 2,\n    ImGuiMouseButton_COUNT = 5\n};\n\n// Enumeration for GetMouseCursor()\n// User code may request backend to display given cursor by calling SetMouseCursor(), which is why we have some cursors that are marked unused here\nenum ImGuiMouseCursor_\n{\n    ImGuiMouseCursor_None = -1,\n    ImGuiMouseCursor_Arrow = 0,\n    ImGuiMouseCursor_TextInput,         // When hovering over InputText, etc.\n    ImGuiMouseCursor_ResizeAll,         // (Unused by Dear ImGui functions)\n    ImGuiMouseCursor_ResizeNS,          // When hovering over a horizontal border\n    ImGuiMouseCursor_ResizeEW,          // When hovering over a vertical border or a column\n    ImGuiMouseCursor_ResizeNESW,        // When hovering over the bottom-left corner of a window\n    ImGuiMouseCursor_ResizeNWSE,        // When hovering over the bottom-right corner of a window\n    ImGuiMouseCursor_Hand,              // (Unused by Dear ImGui functions. Use for e.g. hyperlinks)\n    ImGuiMouseCursor_Wait,              // When waiting for something to process/load.\n    ImGuiMouseCursor_Progress,          // When waiting for something to process/load, but application is still interactive.\n    ImGuiMouseCursor_NotAllowed,        // When hovering something with disallowed interaction. Usually a crossed circle.\n    ImGuiMouseCursor_COUNT\n};\n\n// Enumeration for AddMouseSourceEvent() actual source of Mouse Input data.\n// Historically we use \"Mouse\" terminology everywhere to indicate pointer data, e.g. MousePos, IsMousePressed(), io.AddMousePosEvent()\n// But that \"Mouse\" data can come from different source which occasionally may be useful for application to know about.\n// You can submit a change of pointer type using io.AddMouseSourceEvent().\nenum ImGuiMouseSource : int\n{\n    ImGuiMouseSource_Mouse = 0,         // Input is coming from an actual mouse.\n    ImGuiMouseSource_TouchScreen,       // Input is coming from a touch screen (no hovering prior to initial press, less precise initial press aiming, dual-axis wheeling possible).\n    ImGuiMouseSource_Pen,               // Input is coming from a pressure/magnetic pen (often used in conjunction with high-sampling rates).\n    ImGuiMouseSource_COUNT\n};\n\n// Enumeration for ImGui::SetNextWindow***(), SetWindow***(), SetNextItem***() functions\n// Represent a condition.\n// Important: Treat as a regular enum! Do NOT combine multiple values using binary operators! All the functions above treat 0 as a shortcut to ImGuiCond_Always.\nenum ImGuiCond_\n{\n    ImGuiCond_None          = 0,        // No condition (always set the variable), same as _Always\n    ImGuiCond_Always        = 1 << 0,   // No condition (always set the variable), same as _None\n    ImGuiCond_Once          = 1 << 1,   // Set the variable once per runtime session (only the first call will succeed)\n    ImGuiCond_FirstUseEver  = 1 << 2,   // Set the variable if the object/window has no persistently saved data (no entry in .ini file)\n    ImGuiCond_Appearing     = 1 << 3,   // Set the variable if the object/window is appearing after being hidden/inactive (or the first time)\n};\n\n//-----------------------------------------------------------------------------\n// [SECTION] Tables API flags and structures (ImGuiTableFlags, ImGuiTableColumnFlags, ImGuiTableRowFlags, ImGuiTableBgTarget, ImGuiTableSortSpecs, ImGuiTableColumnSortSpecs)\n//-----------------------------------------------------------------------------\n\n// Flags for ImGui::BeginTable()\n// - Important! Sizing policies have complex and subtle side effects, much more so than you would expect.\n//   Read comments/demos carefully + experiment with live demos to get acquainted with them.\n// - The DEFAULT sizing policies are:\n//    - Default to ImGuiTableFlags_SizingFixedFit    if ScrollX is on, or if host window has ImGuiWindowFlags_AlwaysAutoResize.\n//    - Default to ImGuiTableFlags_SizingStretchSame if ScrollX is off.\n// - When ScrollX is off:\n//    - Table defaults to ImGuiTableFlags_SizingStretchSame -> all Columns defaults to ImGuiTableColumnFlags_WidthStretch with same weight.\n//    - Columns sizing policy allowed: Stretch (default), Fixed/Auto.\n//    - Fixed Columns (if any) will generally obtain their requested width (unless the table cannot fit them all).\n//    - Stretch Columns will share the remaining width according to their respective weight.\n//    - Mixed Fixed/Stretch columns is possible but has various side-effects on resizing behaviors.\n//      The typical use of mixing sizing policies is: any number of LEADING Fixed columns, followed by one or two TRAILING Stretch columns.\n//      (this is because the visible order of columns have subtle but necessary effects on how they react to manual resizing).\n// - When ScrollX is on:\n//    - Table defaults to ImGuiTableFlags_SizingFixedFit -> all Columns defaults to ImGuiTableColumnFlags_WidthFixed\n//    - Columns sizing policy allowed: Fixed/Auto mostly.\n//    - Fixed Columns can be enlarged as needed. Table will show a horizontal scrollbar if needed.\n//    - When using auto-resizing (non-resizable) fixed columns, querying the content width to use item right-alignment e.g. SetNextItemWidth(-FLT_MIN) doesn't make sense, would create a feedback loop.\n//    - Using Stretch columns OFTEN DOES NOT MAKE SENSE if ScrollX is on, UNLESS you have specified a value for 'inner_width' in BeginTable().\n//      If you specify a value for 'inner_width' then effectively the scrolling space is known and Stretch or mixed Fixed/Stretch columns become meaningful again.\n// - Read on documentation at the top of imgui_tables.cpp for details.\nenum ImGuiTableFlags_\n{\n    // Features\n    ImGuiTableFlags_None                       = 0,\n    ImGuiTableFlags_Resizable                  = 1 << 0,   // Enable resizing columns.\n    ImGuiTableFlags_Reorderable                = 1 << 1,   // Enable reordering columns in header row (need calling TableSetupColumn() + TableHeadersRow() to display headers)\n    ImGuiTableFlags_Hideable                   = 1 << 2,   // Enable hiding/disabling columns in context menu.\n    ImGuiTableFlags_Sortable                   = 1 << 3,   // Enable sorting. Call TableGetSortSpecs() to obtain sort specs. Also see ImGuiTableFlags_SortMulti and ImGuiTableFlags_SortTristate.\n    ImGuiTableFlags_NoSavedSettings            = 1 << 4,   // Disable persisting columns order, width and sort settings in the .ini file.\n    ImGuiTableFlags_ContextMenuInBody          = 1 << 5,   // Right-click on columns body/contents will display table context menu. By default it is available in TableHeadersRow().\n    // Decorations\n    ImGuiTableFlags_RowBg                      = 1 << 6,   // Set each RowBg color with ImGuiCol_TableRowBg or ImGuiCol_TableRowBgAlt (equivalent of calling TableSetBgColor with ImGuiTableBgFlags_RowBg0 on each row manually)\n    ImGuiTableFlags_BordersInnerH              = 1 << 7,   // Draw horizontal borders between rows.\n    ImGuiTableFlags_BordersOuterH              = 1 << 8,   // Draw horizontal borders at the top and bottom.\n    ImGuiTableFlags_BordersInnerV              = 1 << 9,   // Draw vertical borders between columns.\n    ImGuiTableFlags_BordersOuterV              = 1 << 10,  // Draw vertical borders on the left and right sides.\n    ImGuiTableFlags_BordersH                   = ImGuiTableFlags_BordersInnerH | ImGuiTableFlags_BordersOuterH, // Draw horizontal borders.\n    ImGuiTableFlags_BordersV                   = ImGuiTableFlags_BordersInnerV | ImGuiTableFlags_BordersOuterV, // Draw vertical borders.\n    ImGuiTableFlags_BordersInner               = ImGuiTableFlags_BordersInnerV | ImGuiTableFlags_BordersInnerH, // Draw inner borders.\n    ImGuiTableFlags_BordersOuter               = ImGuiTableFlags_BordersOuterV | ImGuiTableFlags_BordersOuterH, // Draw outer borders.\n    ImGuiTableFlags_Borders                    = ImGuiTableFlags_BordersInner | ImGuiTableFlags_BordersOuter,   // Draw all borders.\n    ImGuiTableFlags_NoBordersInBody            = 1 << 11,  // [ALPHA] Disable vertical borders in columns Body (borders will always appear in Headers). -> May move to style\n    ImGuiTableFlags_NoBordersInBodyUntilResize = 1 << 12,  // [ALPHA] Disable vertical borders in columns Body until hovered for resize (borders will always appear in Headers). -> May move to style\n    // Sizing Policy (read above for defaults)\n    ImGuiTableFlags_SizingFixedFit             = 1 << 13,  // Columns default to _WidthFixed or _WidthAuto (if resizable or not resizable), matching contents width.\n    ImGuiTableFlags_SizingFixedSame            = 2 << 13,  // Columns default to _WidthFixed or _WidthAuto (if resizable or not resizable), matching the maximum contents width of all columns. Implicitly enable ImGuiTableFlags_NoKeepColumnsVisible.\n    ImGuiTableFlags_SizingStretchProp          = 3 << 13,  // Columns default to _WidthStretch with default weights proportional to each columns contents widths.\n    ImGuiTableFlags_SizingStretchSame          = 4 << 13,  // Columns default to _WidthStretch with default weights all equal, unless overridden by TableSetupColumn().\n    // Sizing Extra Options\n    ImGuiTableFlags_NoHostExtendX              = 1 << 16,  // Make outer width auto-fit to columns, overriding outer_size.x value. Only available when ScrollX/ScrollY are disabled and Stretch columns are not used.\n    ImGuiTableFlags_NoHostExtendY              = 1 << 17,  // Make outer height stop exactly at outer_size.y (prevent auto-extending table past the limit). Only available when ScrollX/ScrollY are disabled. Data below the limit will be clipped and not visible.\n    ImGuiTableFlags_NoKeepColumnsVisible       = 1 << 18,  // Disable keeping column always minimally visible when ScrollX is off and table gets too small. Not recommended if columns are resizable.\n    ImGuiTableFlags_PreciseWidths              = 1 << 19,  // Disable distributing remainder width to stretched columns (width allocation on a 100-wide table with 3 columns: Without this flag: 33,33,34. With this flag: 33,33,33). With larger number of columns, resizing will appear to be less smooth.\n    // Clipping\n    ImGuiTableFlags_NoClip                     = 1 << 20,  // Disable clipping rectangle for every individual columns (reduce draw command count, items will be able to overflow into other columns). Generally incompatible with TableSetupScrollFreeze().\n    // Padding\n    ImGuiTableFlags_PadOuterX                  = 1 << 21,  // Default if BordersOuterV is on. Enable outermost padding. Generally desirable if you have headers.\n    ImGuiTableFlags_NoPadOuterX                = 1 << 22,  // Default if BordersOuterV is off. Disable outermost padding.\n    ImGuiTableFlags_NoPadInnerX                = 1 << 23,  // Disable inner padding between columns (double inner padding if BordersOuterV is on, single inner padding if BordersOuterV is off).\n    // Scrolling\n    ImGuiTableFlags_ScrollX                    = 1 << 24,  // Enable horizontal scrolling. Require 'outer_size' parameter of BeginTable() to specify the container size. Changes default sizing policy. Because this creates a child window, ScrollY is currently generally recommended when using ScrollX.\n    ImGuiTableFlags_ScrollY                    = 1 << 25,  // Enable vertical scrolling. Require 'outer_size' parameter of BeginTable() to specify the container size.\n    // Sorting\n    ImGuiTableFlags_SortMulti                  = 1 << 26,  // Hold shift when clicking headers to sort on multiple column. TableGetSortSpecs() may return specs where (SpecsCount > 1).\n    ImGuiTableFlags_SortTristate               = 1 << 27,  // Allow no sorting, disable default sorting. TableGetSortSpecs() may return specs where (SpecsCount == 0).\n    // Miscellaneous\n    ImGuiTableFlags_HighlightHoveredColumn     = 1 << 28,  // Highlight column headers when hovered (may evolve into a fuller highlight)\n\n    // [Internal] Combinations and masks\n    ImGuiTableFlags_SizingMask_                = ImGuiTableFlags_SizingFixedFit | ImGuiTableFlags_SizingFixedSame | ImGuiTableFlags_SizingStretchProp | ImGuiTableFlags_SizingStretchSame,\n};\n\n// Flags for ImGui::TableSetupColumn()\nenum ImGuiTableColumnFlags_\n{\n    // Input configuration flags\n    ImGuiTableColumnFlags_None                  = 0,\n    ImGuiTableColumnFlags_Disabled              = 1 << 0,   // Overriding/master disable flag: hide column, won't show in context menu (unlike calling TableSetColumnEnabled() which manipulates the user accessible state)\n    ImGuiTableColumnFlags_DefaultHide           = 1 << 1,   // Default as a hidden/disabled column.\n    ImGuiTableColumnFlags_DefaultSort           = 1 << 2,   // Default as a sorting column.\n    ImGuiTableColumnFlags_WidthStretch          = 1 << 3,   // Column will stretch. Preferable with horizontal scrolling disabled (default if table sizing policy is _SizingStretchSame or _SizingStretchProp).\n    ImGuiTableColumnFlags_WidthFixed            = 1 << 4,   // Column will not stretch. Preferable with horizontal scrolling enabled (default if table sizing policy is _SizingFixedFit and table is resizable).\n    ImGuiTableColumnFlags_NoResize              = 1 << 5,   // Disable manual resizing.\n    ImGuiTableColumnFlags_NoReorder             = 1 << 6,   // Disable manual reordering this column, this will also prevent other columns from crossing over this column.\n    ImGuiTableColumnFlags_NoHide                = 1 << 7,   // Disable ability to hide/disable this column.\n    ImGuiTableColumnFlags_NoClip                = 1 << 8,   // Disable clipping for this column (all NoClip columns will render in a same draw command).\n    ImGuiTableColumnFlags_NoSort                = 1 << 9,   // Disable ability to sort on this field (even if ImGuiTableFlags_Sortable is set on the table).\n    ImGuiTableColumnFlags_NoSortAscending       = 1 << 10,  // Disable ability to sort in the ascending direction.\n    ImGuiTableColumnFlags_NoSortDescending      = 1 << 11,  // Disable ability to sort in the descending direction.\n    ImGuiTableColumnFlags_NoHeaderLabel         = 1 << 12,  // TableHeadersRow() will submit an empty label for this column. Convenient for some small columns. Name will still appear in context menu or in angled headers. You may append into this cell by calling TableSetColumnIndex() right after the TableHeadersRow() call.\n    ImGuiTableColumnFlags_NoHeaderWidth         = 1 << 13,  // Disable header text width contribution to automatic column width.\n    ImGuiTableColumnFlags_PreferSortAscending   = 1 << 14,  // Make the initial sort direction Ascending when first sorting on this column (default).\n    ImGuiTableColumnFlags_PreferSortDescending  = 1 << 15,  // Make the initial sort direction Descending when first sorting on this column.\n    ImGuiTableColumnFlags_IndentEnable          = 1 << 16,  // Use current Indent value when entering cell (default for column 0).\n    ImGuiTableColumnFlags_IndentDisable         = 1 << 17,  // Ignore current Indent value when entering cell (default for columns > 0). Indentation changes _within_ the cell will still be honored.\n    ImGuiTableColumnFlags_AngledHeader          = 1 << 18,  // TableHeadersRow() will submit an angled header row for this column. Note this will add an extra row.\n\n    // Output status flags, read-only via TableGetColumnFlags()\n    ImGuiTableColumnFlags_IsEnabled             = 1 << 24,  // Status: is enabled == not hidden by user/api (referred to as \"Hide\" in _DefaultHide and _NoHide) flags.\n    ImGuiTableColumnFlags_IsVisible             = 1 << 25,  // Status: is visible == is enabled AND not clipped by scrolling.\n    ImGuiTableColumnFlags_IsSorted              = 1 << 26,  // Status: is currently part of the sort specs\n    ImGuiTableColumnFlags_IsHovered             = 1 << 27,  // Status: is hovered by mouse\n\n    // [Internal] Combinations and masks\n    ImGuiTableColumnFlags_WidthMask_            = ImGuiTableColumnFlags_WidthStretch | ImGuiTableColumnFlags_WidthFixed,\n    ImGuiTableColumnFlags_IndentMask_           = ImGuiTableColumnFlags_IndentEnable | ImGuiTableColumnFlags_IndentDisable,\n    ImGuiTableColumnFlags_StatusMask_           = ImGuiTableColumnFlags_IsEnabled | ImGuiTableColumnFlags_IsVisible | ImGuiTableColumnFlags_IsSorted | ImGuiTableColumnFlags_IsHovered,\n    ImGuiTableColumnFlags_NoDirectResize_       = 1 << 30,  // [Internal] Disable user resizing this column directly (it may however we resized indirectly from its left edge)\n};\n\n// Flags for ImGui::TableNextRow()\nenum ImGuiTableRowFlags_\n{\n    ImGuiTableRowFlags_None                     = 0,\n    ImGuiTableRowFlags_Headers                  = 1 << 0,   // Identify header row (set default background color + width of its contents accounted differently for auto column width)\n};\n\n// Enum for ImGui::TableSetBgColor()\n// Background colors are rendering in 3 layers:\n//  - Layer 0: draw with RowBg0 color if set, otherwise draw with ColumnBg0 if set.\n//  - Layer 1: draw with RowBg1 color if set, otherwise draw with ColumnBg1 if set.\n//  - Layer 2: draw with CellBg color if set.\n// The purpose of the two row/columns layers is to let you decide if a background color change should override or blend with the existing color.\n// When using ImGuiTableFlags_RowBg on the table, each row has the RowBg0 color automatically set for odd/even rows.\n// If you set the color of RowBg0 target, your color will override the existing RowBg0 color.\n// If you set the color of RowBg1 or ColumnBg1 target, your color will blend over the RowBg0 color.\nenum ImGuiTableBgTarget_\n{\n    ImGuiTableBgTarget_None                     = 0,\n    ImGuiTableBgTarget_RowBg0                   = 1,        // Set row background color 0 (generally used for background, automatically set when ImGuiTableFlags_RowBg is used)\n    ImGuiTableBgTarget_RowBg1                   = 2,        // Set row background color 1 (generally used for selection marking)\n    ImGuiTableBgTarget_CellBg                   = 3,        // Set cell background color (top-most color)\n};\n\n// Sorting specifications for a table (often handling sort specs for a single column, occasionally more)\n// Obtained by calling TableGetSortSpecs().\n// When 'SpecsDirty == true' you can sort your data. It will be true with sorting specs have changed since last call, or the first time.\n// Make sure to set 'SpecsDirty = false' after sorting, else you may wastefully sort your data every frame!\nstruct ImGuiTableSortSpecs\n{\n    const ImGuiTableColumnSortSpecs* Specs;     // Pointer to sort spec array.\n    int                         SpecsCount;     // Sort spec count. Most often 1. May be > 1 when ImGuiTableFlags_SortMulti is enabled. May be == 0 when ImGuiTableFlags_SortTristate is enabled.\n    bool                        SpecsDirty;     // Set to true when specs have changed since last time! Use this to sort again, then clear the flag.\n\n    ImGuiTableSortSpecs()       { memset(this, 0, sizeof(*this)); }\n};\n\n// Sorting specification for one column of a table (sizeof == 12 bytes)\nstruct ImGuiTableColumnSortSpecs\n{\n    ImGuiID                     ColumnUserID;       // User id of the column (if specified by a TableSetupColumn() call)\n    ImS16                       ColumnIndex;        // Index of the column\n    ImS16                       SortOrder;          // Index within parent ImGuiTableSortSpecs (always stored in order starting from 0, tables sorted on a single criteria will always have a 0 here)\n    ImGuiSortDirection          SortDirection;      // ImGuiSortDirection_Ascending or ImGuiSortDirection_Descending\n\n    ImGuiTableColumnSortSpecs() { memset(this, 0, sizeof(*this)); }\n};\n\n//-----------------------------------------------------------------------------\n// [SECTION] Helpers: Debug log, memory allocations macros, ImVector<>\n//-----------------------------------------------------------------------------\n\n//-----------------------------------------------------------------------------\n// Debug Logging into ShowDebugLogWindow(), tty and more.\n//-----------------------------------------------------------------------------\n\n#ifndef IMGUI_DISABLE_DEBUG_TOOLS\n#define IMGUI_DEBUG_LOG(...)        ImGui::DebugLog(__VA_ARGS__)\n#else\n#define IMGUI_DEBUG_LOG(...)        ((void)0)\n#endif\n\n//-----------------------------------------------------------------------------\n// IM_MALLOC(), IM_FREE(), IM_NEW(), IM_PLACEMENT_NEW(), IM_DELETE()\n// We call C++ constructor on own allocated memory via the placement \"new(ptr) Type()\" syntax.\n// Defining a custom placement new() with a custom parameter allows us to bypass including <new> which on some platforms complains when user has disabled exceptions.\n//-----------------------------------------------------------------------------\n\nstruct ImNewWrapper {};\ninline void* operator new(size_t, ImNewWrapper, void* ptr) { return ptr; }\ninline void  operator delete(void*, ImNewWrapper, void*)   {} // This is only required so we can use the symmetrical new()\n#define IM_ALLOC(_SIZE)                     ImGui::MemAlloc(_SIZE)\n#define IM_FREE(_PTR)                       ImGui::MemFree(_PTR)\n#define IM_PLACEMENT_NEW(_PTR)              new(ImNewWrapper(), _PTR)\n#define IM_NEW(_TYPE)                       new(ImNewWrapper(), ImGui::MemAlloc(sizeof(_TYPE))) _TYPE\ntemplate<typename T> void IM_DELETE(T* p)   { if (p) { p->~T(); ImGui::MemFree(p); } }\n\n//-----------------------------------------------------------------------------\n// ImVector<>\n// Lightweight std::vector<>-like class to avoid dragging dependencies (also, some implementations of STL with debug enabled are absurdly slow, we bypass it so our code runs fast in debug).\n//-----------------------------------------------------------------------------\n// - You generally do NOT need to care or use this ever. But we need to make it available in imgui.h because some of our public structures are relying on it.\n// - We use std-like naming convention here, which is a little unusual for this codebase.\n// - Important: clear() frees memory, resize(0) keep the allocated buffer. We use resize(0) a lot to intentionally recycle allocated buffers across frames and amortize our costs.\n// - Important: our implementation does NOT call C++ constructors/destructors, we treat everything as raw data! This is intentional but be extra mindful of that,\n//   Do NOT use this class as a std::vector replacement in your own code! Many of the structures used by dear imgui can be safely initialized by a zero-memset.\n//-----------------------------------------------------------------------------\n\nIM_MSVC_RUNTIME_CHECKS_OFF\ntemplate<typename T>\nstruct ImVector\n{\n    int                 Size;\n    int                 Capacity;\n    T*                  Data;\n\n    // Provide standard typedefs but we don't use them ourselves.\n    typedef T                   value_type;\n    typedef value_type*         iterator;\n    typedef const value_type*   const_iterator;\n\n    // Constructors, destructor\n    inline ImVector()                                       { Size = Capacity = 0; Data = NULL; }\n    inline ImVector(const ImVector<T>& src)                 { Size = Capacity = 0; Data = NULL; operator=(src); }\n    inline ImVector<T>& operator=(const ImVector<T>& src)   { clear(); resize(src.Size); if (src.Data) memcpy(Data, src.Data, (size_t)Size * sizeof(T)); return *this; }\n    inline ~ImVector()                                      { if (Data) IM_FREE(Data); } // Important: does not destruct anything\n\n    inline void         clear()                             { if (Data) { Size = Capacity = 0; IM_FREE(Data); Data = NULL; } }  // Important: does not destruct anything\n    inline void         clear_delete()                      { for (int n = 0; n < Size; n++) IM_DELETE(Data[n]); clear(); }     // Important: never called automatically! always explicit.\n    inline void         clear_destruct()                    { for (int n = 0; n < Size; n++) Data[n].~T(); clear(); }           // Important: never called automatically! always explicit.\n\n    inline bool         empty() const                       { return Size == 0; }\n    inline int          size() const                        { return Size; }\n    inline int          size_in_bytes() const               { return Size * (int)sizeof(T); }\n    inline int          max_size() const                    { return 0x7FFFFFFF / (int)sizeof(T); }\n    inline int          capacity() const                    { return Capacity; }\n    inline T&           operator[](int i)                   { IM_ASSERT(i >= 0 && i < Size); return Data[i]; }\n    inline const T&     operator[](int i) const             { IM_ASSERT(i >= 0 && i < Size); return Data[i]; }\n\n    inline T*           begin()                             { return Data; }\n    inline const T*     begin() const                       { return Data; }\n    inline T*           end()                               { return Data + Size; }\n    inline const T*     end() const                         { return Data + Size; }\n    inline T&           front()                             { IM_ASSERT(Size > 0); return Data[0]; }\n    inline const T&     front() const                       { IM_ASSERT(Size > 0); return Data[0]; }\n    inline T&           back()                              { IM_ASSERT(Size > 0); return Data[Size - 1]; }\n    inline const T&     back() const                        { IM_ASSERT(Size > 0); return Data[Size - 1]; }\n    inline void         swap(ImVector<T>& rhs)              { int rhs_size = rhs.Size; rhs.Size = Size; Size = rhs_size; int rhs_cap = rhs.Capacity; rhs.Capacity = Capacity; Capacity = rhs_cap; T* rhs_data = rhs.Data; rhs.Data = Data; Data = rhs_data; }\n\n    inline int          _grow_capacity(int sz) const        { int new_capacity = Capacity ? (Capacity + Capacity / 2) : 8; return new_capacity > sz ? new_capacity : sz; }\n    inline void         resize(int new_size)                { if (new_size > Capacity) reserve(_grow_capacity(new_size)); Size = new_size; }\n    inline void         resize(int new_size, const T& v)    { if (new_size > Capacity) reserve(_grow_capacity(new_size)); if (new_size > Size) for (int n = Size; n < new_size; n++) memcpy(&Data[n], &v, sizeof(v)); Size = new_size; }\n    inline void         shrink(int new_size)                { IM_ASSERT(new_size <= Size); Size = new_size; } // Resize a vector to a smaller size, guaranteed not to cause a reallocation\n    inline void         reserve(int new_capacity)           { if (new_capacity <= Capacity) return; T* new_data = (T*)IM_ALLOC((size_t)new_capacity * sizeof(T)); if (Data) { memcpy(new_data, Data, (size_t)Size * sizeof(T)); IM_FREE(Data); } Data = new_data; Capacity = new_capacity; }\n    inline void         reserve_discard(int new_capacity)   { if (new_capacity <= Capacity) return; if (Data) IM_FREE(Data); Data = (T*)IM_ALLOC((size_t)new_capacity * sizeof(T)); Capacity = new_capacity; }\n\n    // NB: It is illegal to call push_back/push_front/insert with a reference pointing inside the ImVector data itself! e.g. v.push_back(v[10]) is forbidden.\n    inline void         push_back(const T& v)               { if (Size == Capacity) reserve(_grow_capacity(Size + 1)); memcpy(&Data[Size], &v, sizeof(v)); Size++; }\n    inline void         pop_back()                          { IM_ASSERT(Size > 0); Size--; }\n    inline void         push_front(const T& v)              { if (Size == 0) push_back(v); else insert(Data, v); }\n    inline T*           erase(const T* it)                  { IM_ASSERT(it >= Data && it < Data + Size); const ptrdiff_t off = it - Data; memmove(Data + off, Data + off + 1, ((size_t)Size - (size_t)off - 1) * sizeof(T)); Size--; return Data + off; }\n    inline T*           erase(const T* it, const T* it_last){ IM_ASSERT(it >= Data && it < Data + Size && it_last >= it && it_last <= Data + Size); const ptrdiff_t count = it_last - it; const ptrdiff_t off = it - Data; memmove(Data + off, Data + off + count, ((size_t)Size - (size_t)off - (size_t)count) * sizeof(T)); Size -= (int)count; return Data + off; }\n    inline T*           erase_unsorted(const T* it)         { IM_ASSERT(it >= Data && it < Data + Size);  const ptrdiff_t off = it - Data; if (it < Data + Size - 1) memcpy(Data + off, Data + Size - 1, sizeof(T)); Size--; return Data + off; }\n    inline T*           insert(const T* it, const T& v)     { IM_ASSERT(it >= Data && it <= Data + Size); const ptrdiff_t off = it - Data; if (Size == Capacity) reserve(_grow_capacity(Size + 1)); if (off < (int)Size) memmove(Data + off + 1, Data + off, ((size_t)Size - (size_t)off) * sizeof(T)); memcpy(&Data[off], &v, sizeof(v)); Size++; return Data + off; }\n    inline bool         contains(const T& v) const          { const T* data = Data;  const T* data_end = Data + Size; while (data < data_end) if (*data++ == v) return true; return false; }\n    inline T*           find(const T& v)                    { T* data = Data;  const T* data_end = Data + Size; while (data < data_end) if (*data == v) break; else ++data; return data; }\n    inline const T*     find(const T& v) const              { const T* data = Data;  const T* data_end = Data + Size; while (data < data_end) if (*data == v) break; else ++data; return data; }\n    inline int          find_index(const T& v) const        { const T* data_end = Data + Size; const T* it = find(v); if (it == data_end) return -1; const ptrdiff_t off = it - Data; return (int)off; }\n    inline bool         find_erase(const T& v)              { const T* it = find(v); if (it < Data + Size) { erase(it); return true; } return false; }\n    inline bool         find_erase_unsorted(const T& v)     { const T* it = find(v); if (it < Data + Size) { erase_unsorted(it); return true; } return false; }\n    inline int          index_from_ptr(const T* it) const   { IM_ASSERT(it >= Data && it < Data + Size); const ptrdiff_t off = it - Data; return (int)off; }\n};\nIM_MSVC_RUNTIME_CHECKS_RESTORE\n\n//-----------------------------------------------------------------------------\n// [SECTION] ImGuiStyle\n//-----------------------------------------------------------------------------\n// You may modify the ImGui::GetStyle() main instance during initialization and before NewFrame().\n// During the frame, use ImGui::PushStyleVar(ImGuiStyleVar_XXXX)/PopStyleVar() to alter the main style values,\n// and ImGui::PushStyleColor(ImGuiCol_XXX)/PopStyleColor() for colors.\n//-----------------------------------------------------------------------------\n\nstruct ImGuiStyle\n{\n    float       Alpha;                      // Global alpha applies to everything in Dear ImGui.\n    float       DisabledAlpha;              // Additional alpha multiplier applied by BeginDisabled(). Multiply over current value of Alpha.\n    ImVec2      WindowPadding;              // Padding within a window.\n    float       WindowRounding;             // Radius of window corners rounding. Set to 0.0f to have rectangular windows. Large values tend to lead to variety of artifacts and are not recommended.\n    float       WindowBorderSize;           // Thickness of border around windows. Generally set to 0.0f or 1.0f. (Other values are not well tested and more CPU/GPU costly).\n    float       WindowBorderHoverPadding;   // Hit-testing extent outside/inside resizing border. Also extend determination of hovered window. Generally meaningfully larger than WindowBorderSize to make it easy to reach borders.\n    ImVec2      WindowMinSize;              // Minimum window size. This is a global setting. If you want to constrain individual windows, use SetNextWindowSizeConstraints().\n    ImVec2      WindowTitleAlign;           // Alignment for title bar text. Defaults to (0.0f,0.5f) for left-aligned,vertically centered.\n    ImGuiDir    WindowMenuButtonPosition;   // Side of the collapsing/docking button in the title bar (None/Left/Right). Defaults to ImGuiDir_Left.\n    float       ChildRounding;              // Radius of child window corners rounding. Set to 0.0f to have rectangular windows.\n    float       ChildBorderSize;            // Thickness of border around child windows. Generally set to 0.0f or 1.0f. (Other values are not well tested and more CPU/GPU costly).\n    float       PopupRounding;              // Radius of popup window corners rounding. (Note that tooltip windows use WindowRounding)\n    float       PopupBorderSize;            // Thickness of border around popup/tooltip windows. Generally set to 0.0f or 1.0f. (Other values are not well tested and more CPU/GPU costly).\n    ImVec2      FramePadding;               // Padding within a framed rectangle (used by most widgets).\n    float       FrameRounding;              // Radius of frame corners rounding. Set to 0.0f to have rectangular frame (used by most widgets).\n    float       FrameBorderSize;            // Thickness of border around frames. Generally set to 0.0f or 1.0f. (Other values are not well tested and more CPU/GPU costly).\n    ImVec2      ItemSpacing;                // Horizontal and vertical spacing between widgets/lines.\n    ImVec2      ItemInnerSpacing;           // Horizontal and vertical spacing between within elements of a composed widget (e.g. a slider and its label).\n    ImVec2      CellPadding;                // Padding within a table cell. Cellpadding.x is locked for entire table. CellPadding.y may be altered between different rows.\n    ImVec2      TouchExtraPadding;          // Expand reactive bounding box for touch-based system where touch position is not accurate enough. Unfortunately we don't sort widgets so priority on overlap will always be given to the first widget. So don't grow this too much!\n    float       IndentSpacing;              // Horizontal indentation when e.g. entering a tree node. Generally == (FontSize + FramePadding.x*2).\n    float       ColumnsMinSpacing;          // Minimum horizontal spacing between two columns. Preferably > (FramePadding.x + 1).\n    float       ScrollbarSize;              // Width of the vertical scrollbar, Height of the horizontal scrollbar.\n    float       ScrollbarRounding;          // Radius of grab corners for scrollbar.\n    float       GrabMinSize;                // Minimum width/height of a grab box for slider/scrollbar.\n    float       GrabRounding;               // Radius of grabs corners rounding. Set to 0.0f to have rectangular slider grabs.\n    float       LogSliderDeadzone;          // The size in pixels of the dead-zone around zero on logarithmic sliders that cross zero.\n    float       ImageBorderSize;            // Thickness of border around Image() calls.\n    float       TabRounding;                // Radius of upper corners of a tab. Set to 0.0f to have rectangular tabs.\n    float       TabBorderSize;              // Thickness of border around tabs.\n    float       TabCloseButtonMinWidthSelected;     // -1: always visible. 0.0f: visible when hovered. >0.0f: visible when hovered if minimum width.\n    float       TabCloseButtonMinWidthUnselected;   // -1: always visible. 0.0f: visible when hovered. >0.0f: visible when hovered if minimum width. FLT_MAX: never show close button when unselected.\n    float       TabBarBorderSize;           // Thickness of tab-bar separator, which takes on the tab active color to denote focus.\n    float       TabBarOverlineSize;         // Thickness of tab-bar overline, which highlights the selected tab-bar.\n    float       TableAngledHeadersAngle;    // Angle of angled headers (supported values range from -50.0f degrees to +50.0f degrees).\n    ImVec2      TableAngledHeadersTextAlign;// Alignment of angled headers within the cell\n    ImGuiDir    ColorButtonPosition;        // Side of the color button in the ColorEdit4 widget (left/right). Defaults to ImGuiDir_Right.\n    ImVec2      ButtonTextAlign;            // Alignment of button text when button is larger than text. Defaults to (0.5f, 0.5f) (centered).\n    ImVec2      SelectableTextAlign;        // Alignment of selectable text. Defaults to (0.0f, 0.0f) (top-left aligned). It's generally important to keep this left-aligned if you want to lay multiple items on a same line.\n    float       SeparatorTextBorderSize;    // Thickness of border in SeparatorText()\n    ImVec2      SeparatorTextAlign;         // Alignment of text within the separator. Defaults to (0.0f, 0.5f) (left aligned, center).\n    ImVec2      SeparatorTextPadding;       // Horizontal offset of text from each edge of the separator + spacing on other axis. Generally small values. .y is recommended to be == FramePadding.y.\n    ImVec2      DisplayWindowPadding;       // Apply to regular windows: amount which we enforce to keep visible when moving near edges of your screen.\n    ImVec2      DisplaySafeAreaPadding;     // Apply to every windows, menus, popups, tooltips: amount where we avoid displaying contents. Adjust if you cannot see the edges of your screen (e.g. on a TV where scaling has not been configured).\n    float       MouseCursorScale;           // Scale software rendered mouse cursor (when io.MouseDrawCursor is enabled). We apply per-monitor DPI scaling over this scale. May be removed later.\n    bool        AntiAliasedLines;           // Enable anti-aliased lines/borders. Disable if you are really tight on CPU/GPU. Latched at the beginning of the frame (copied to ImDrawList).\n    bool        AntiAliasedLinesUseTex;     // Enable anti-aliased lines/borders using textures where possible. Require backend to render with bilinear filtering (NOT point/nearest filtering). Latched at the beginning of the frame (copied to ImDrawList).\n    bool        AntiAliasedFill;            // Enable anti-aliased edges around filled shapes (rounded rectangles, circles, etc.). Disable if you are really tight on CPU/GPU. Latched at the beginning of the frame (copied to ImDrawList).\n    float       CurveTessellationTol;       // Tessellation tolerance when using PathBezierCurveTo() without a specific number of segments. Decrease for highly tessellated curves (higher quality, more polygons), increase to reduce quality.\n    float       CircleTessellationMaxError; // Maximum error (in pixels) allowed when using AddCircle()/AddCircleFilled() or drawing rounded corner rectangles with no explicit segment count specified. Decrease for higher quality but more geometry.\n\n    // Colors\n    ImVec4      Colors[ImGuiCol_COUNT];\n\n    // Behaviors\n    // (It is possible to modify those fields mid-frame if specific behavior need it, unlike e.g. configuration fields in ImGuiIO)\n    float             HoverStationaryDelay;     // Delay for IsItemHovered(ImGuiHoveredFlags_Stationary). Time required to consider mouse stationary.\n    float             HoverDelayShort;          // Delay for IsItemHovered(ImGuiHoveredFlags_DelayShort). Usually used along with HoverStationaryDelay.\n    float             HoverDelayNormal;         // Delay for IsItemHovered(ImGuiHoveredFlags_DelayNormal). \"\n    ImGuiHoveredFlags HoverFlagsForTooltipMouse;// Default flags when using IsItemHovered(ImGuiHoveredFlags_ForTooltip) or BeginItemTooltip()/SetItemTooltip() while using mouse.\n    ImGuiHoveredFlags HoverFlagsForTooltipNav;  // Default flags when using IsItemHovered(ImGuiHoveredFlags_ForTooltip) or BeginItemTooltip()/SetItemTooltip() while using keyboard/gamepad.\n\n    IMGUI_API ImGuiStyle();\n    IMGUI_API void ScaleAllSizes(float scale_factor);\n\n    // Obsolete names\n#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS\n    // TabMinWidthForCloseButton = TabCloseButtonMinWidthUnselected // Renamed in 1.91.9.\n#endif\n};\n\n//-----------------------------------------------------------------------------\n// [SECTION] ImGuiIO\n//-----------------------------------------------------------------------------\n// Communicate most settings and inputs/outputs to Dear ImGui using this structure.\n// Access via ImGui::GetIO(). Read 'Programmer guide' section in .cpp file for general usage.\n// It is generally expected that:\n// - initialization: backends and user code writes to ImGuiIO.\n// - main loop: backends writes to ImGuiIO, user code and imgui code reads from ImGuiIO.\n//-----------------------------------------------------------------------------\n// Also see ImGui::GetPlatformIO() and ImGuiPlatformIO struct for OS/platform related functions: clipboard, IME etc.\n//-----------------------------------------------------------------------------\n\n// [Internal] Storage used by IsKeyDown(), IsKeyPressed() etc functions.\n// If prior to 1.87 you used io.KeysDownDuration[] (which was marked as internal), you should use GetKeyData(key)->DownDuration and *NOT* io.KeysData[key]->DownDuration.\nstruct ImGuiKeyData\n{\n    bool        Down;               // True for if key is down\n    float       DownDuration;       // Duration the key has been down (<0.0f: not pressed, 0.0f: just pressed, >0.0f: time held)\n    float       DownDurationPrev;   // Last frame duration the key has been down\n    float       AnalogValue;        // 0.0f..1.0f for gamepad values\n};\n\nstruct ImGuiIO\n{\n    //------------------------------------------------------------------\n    // Configuration                            // Default value\n    //------------------------------------------------------------------\n\n    ImGuiConfigFlags   ConfigFlags;             // = 0              // See ImGuiConfigFlags_ enum. Set by user/application. Keyboard/Gamepad navigation options, etc.\n    ImGuiBackendFlags  BackendFlags;            // = 0              // See ImGuiBackendFlags_ enum. Set by backend (imgui_impl_xxx files or custom backend) to communicate features supported by the backend.\n    ImVec2      DisplaySize;                    // <unset>          // Main display size, in pixels (generally == GetMainViewport()->Size). May change every frame.\n    float       DeltaTime;                      // = 1.0f/60.0f     // Time elapsed since last frame, in seconds. May change every frame.\n    float       IniSavingRate;                  // = 5.0f           // Minimum time between saving positions/sizes to .ini file, in seconds.\n    const char* IniFilename;                    // = \"imgui.ini\"    // Path to .ini file (important: default \"imgui.ini\" is relative to current working dir!). Set NULL to disable automatic .ini loading/saving or if you want to manually call LoadIniSettingsXXX() / SaveIniSettingsXXX() functions.\n    const char* LogFilename;                    // = \"imgui_log.txt\"// Path to .log file (default parameter to ImGui::LogToFile when no file is specified).\n    void*       UserData;                       // = NULL           // Store your own data.\n\n    // Font system\n    ImFontAtlas*Fonts;                          // <auto>           // Font atlas: load, rasterize and pack one or more fonts into a single texture.\n    float       FontGlobalScale;                // = 1.0f           // Global scale all fonts\n    bool        FontAllowUserScaling;           // = false          // [OBSOLETE] Allow user scaling text of individual window with CTRL+Wheel.\n    ImFont*     FontDefault;                    // = NULL           // Font to use on NewFrame(). Use NULL to uses Fonts->Fonts[0].\n    ImVec2      DisplayFramebufferScale;        // = (1, 1)         // For retina display or other situations where window coordinates are different from framebuffer coordinates. This generally ends up in ImDrawData::FramebufferScale.\n\n    // Keyboard/Gamepad Navigation options\n    bool        ConfigNavSwapGamepadButtons;    // = false          // Swap Activate<>Cancel (A<>B) buttons, matching typical \"Nintendo/Japanese style\" gamepad layout.\n    bool        ConfigNavMoveSetMousePos;       // = false          // Directional/tabbing navigation teleports the mouse cursor. May be useful on TV/console systems where moving a virtual mouse is difficult. Will update io.MousePos and set io.WantSetMousePos=true.\n    bool        ConfigNavCaptureKeyboard;       // = true           // Sets io.WantCaptureKeyboard when io.NavActive is set.\n    bool        ConfigNavEscapeClearFocusItem;  // = true           // Pressing Escape can clear focused item + navigation id/highlight. Set to false if you want to always keep highlight on.\n    bool        ConfigNavEscapeClearFocusWindow;// = false          // Pressing Escape can clear focused window as well (super set of io.ConfigNavEscapeClearFocusItem).\n    bool        ConfigNavCursorVisibleAuto;     // = true           // Using directional navigation key makes the cursor visible. Mouse click hides the cursor.\n    bool        ConfigNavCursorVisibleAlways;   // = false          // Navigation cursor is always visible.\n\n    // Miscellaneous options\n    // (you can visualize and interact with all options in 'Demo->Configuration')\n    bool        MouseDrawCursor;                // = false          // Request ImGui to draw a mouse cursor for you (if you are on a platform without a mouse cursor). Cannot be easily renamed to 'io.ConfigXXX' because this is frequently used by backend implementations.\n    bool        ConfigMacOSXBehaviors;          // = defined(__APPLE__) // Swap Cmd<>Ctrl keys + OS X style text editing cursor movement using Alt instead of Ctrl, Shortcuts using Cmd/Super instead of Ctrl, Line/Text Start and End using Cmd+Arrows instead of Home/End, Double click selects by word instead of selecting whole text, Multi-selection in lists uses Cmd/Super instead of Ctrl.\n    bool        ConfigInputTrickleEventQueue;   // = true           // Enable input queue trickling: some types of events submitted during the same frame (e.g. button down + up) will be spread over multiple frames, improving interactions with low framerates.\n    bool        ConfigInputTextCursorBlink;     // = true           // Enable blinking cursor (optional as some users consider it to be distracting).\n    bool        ConfigInputTextEnterKeepActive; // = false          // [BETA] Pressing Enter will keep item active and select contents (single-line only).\n    bool        ConfigDragClickToInputText;     // = false          // [BETA] Enable turning DragXXX widgets into text input with a simple mouse click-release (without moving). Not desirable on devices without a keyboard.\n    bool        ConfigWindowsResizeFromEdges;   // = true           // Enable resizing of windows from their edges and from the lower-left corner. This requires ImGuiBackendFlags_HasMouseCursors for better mouse cursor feedback. (This used to be a per-window ImGuiWindowFlags_ResizeFromAnySide flag)\n    bool        ConfigWindowsMoveFromTitleBarOnly;  // = false      // Enable allowing to move windows only when clicking on their title bar. Does not apply to windows without a title bar.\n    bool        ConfigWindowsCopyContentsWithCtrlC; // = false      // [EXPERIMENTAL] CTRL+C copy the contents of focused window into the clipboard. Experimental because: (1) has known issues with nested Begin/End pairs (2) text output quality varies (3) text output is in submission order rather than spatial order.\n    bool        ConfigScrollbarScrollByPage;    // = true           // Enable scrolling page by page when clicking outside the scrollbar grab. When disabled, always scroll to clicked location. When enabled, Shift+Click scrolls to clicked location.\n    float       ConfigMemoryCompactTimer;       // = 60.0f          // Timer (in seconds) to free transient windows/tables memory buffers when unused. Set to -1.0f to disable.\n\n    // Inputs Behaviors\n    // (other variables, ones which are expected to be tweaked within UI code, are exposed in ImGuiStyle)\n    float       MouseDoubleClickTime;           // = 0.30f          // Time for a double-click, in seconds.\n    float       MouseDoubleClickMaxDist;        // = 6.0f           // Distance threshold to stay in to validate a double-click, in pixels.\n    float       MouseDragThreshold;             // = 6.0f           // Distance threshold before considering we are dragging.\n    float       KeyRepeatDelay;                 // = 0.275f         // When holding a key/button, time before it starts repeating, in seconds (for buttons in Repeat mode, etc.).\n    float       KeyRepeatRate;                  // = 0.050f         // When holding a key/button, rate at which it repeats, in seconds.\n\n    //------------------------------------------------------------------\n    // Debug options\n    //------------------------------------------------------------------\n\n    // Options to configure Error Handling and how we handle recoverable errors [EXPERIMENTAL]\n    // - Error recovery is provided as a way to facilitate:\n    //    - Recovery after a programming error (native code or scripting language - the later tends to facilitate iterating on code while running).\n    //    - Recovery after running an exception handler or any error processing which may skip code after an error has been detected.\n    // - Error recovery is not perfect nor guaranteed! It is a feature to ease development.\n    //   You not are not supposed to rely on it in the course of a normal application run.\n    // - Functions that support error recovery are using IM_ASSERT_USER_ERROR() instead of IM_ASSERT().\n    // - By design, we do NOT allow error recovery to be 100% silent. One of the three options needs to be checked!\n    // - Always ensure that on programmers seats you have at minimum Asserts or Tooltips enabled when making direct imgui API calls!\n    //   Otherwise it would severely hinder your ability to catch and correct mistakes!\n    // Read https://github.com/ocornut/imgui/wiki/Error-Handling for details.\n    // - Programmer seats: keep asserts (default), or disable asserts and keep error tooltips (new and nice!)\n    // - Non-programmer seats: maybe disable asserts, but make sure errors are resurfaced (tooltips, visible log entries, use callback etc.)\n    // - Recovery after error/exception: record stack sizes with ErrorRecoveryStoreState(), disable assert, set log callback (to e.g. trigger high-level breakpoint), recover with ErrorRecoveryTryToRecoverState(), restore settings.\n    bool        ConfigErrorRecovery;                // = true       // Enable error recovery support. Some errors won't be detected and lead to direct crashes if recovery is disabled.\n    bool        ConfigErrorRecoveryEnableAssert;    // = true       // Enable asserts on recoverable error. By default call IM_ASSERT() when returning from a failing IM_ASSERT_USER_ERROR()\n    bool        ConfigErrorRecoveryEnableDebugLog;  // = true       // Enable debug log output on recoverable errors.\n    bool        ConfigErrorRecoveryEnableTooltip;   // = true       // Enable tooltip on recoverable errors. The tooltip include a way to enable asserts if they were disabled.\n\n    // Option to enable various debug tools showing buttons that will call the IM_DEBUG_BREAK() macro.\n    // - The Item Picker tool will be available regardless of this being enabled, in order to maximize its discoverability.\n    // - Requires a debugger being attached, otherwise IM_DEBUG_BREAK() options will appear to crash your application.\n    //   e.g. io.ConfigDebugIsDebuggerPresent = ::IsDebuggerPresent() on Win32, or refer to ImOsIsDebuggerPresent() imgui_test_engine/imgui_te_utils.cpp for a Unix compatible version).\n    bool        ConfigDebugIsDebuggerPresent;   // = false          // Enable various tools calling IM_DEBUG_BREAK().\n\n    // Tools to detect code submitting items with conflicting/duplicate IDs\n    // - Code should use PushID()/PopID() in loops, or append \"##xx\" to same-label identifiers.\n    // - Empty label e.g. Button(\"\") == same ID as parent widget/node. Use Button(\"##xx\") instead!\n    // - See FAQ https://github.com/ocornut/imgui/blob/master/docs/FAQ.md#q-about-the-id-stack-system\n    bool        ConfigDebugHighlightIdConflicts;// = true           // Highlight and show an error message popup when multiple items have conflicting identifiers.\n    bool        ConfigDebugHighlightIdConflictsShowItemPicker;//=true // Show \"Item Picker\" button in aforementioned popup.\n\n    // Tools to test correct Begin/End and BeginChild/EndChild behaviors.\n    // - Presently Begin()/End() and BeginChild()/EndChild() needs to ALWAYS be called in tandem, regardless of return value of BeginXXX()\n    // - This is inconsistent with other BeginXXX functions and create confusion for many users.\n    // - We expect to update the API eventually. In the meanwhile we provide tools to facilitate checking user-code behavior.\n    bool        ConfigDebugBeginReturnValueOnce;// = false          // First-time calls to Begin()/BeginChild() will return false. NEEDS TO BE SET AT APPLICATION BOOT TIME if you don't want to miss windows.\n    bool        ConfigDebugBeginReturnValueLoop;// = false          // Some calls to Begin()/BeginChild() will return false. Will cycle through window depths then repeat. Suggested use: add \"io.ConfigDebugBeginReturnValue = io.KeyShift\" in your main loop then occasionally press SHIFT. Windows should be flickering while running.\n\n    // Option to deactivate io.AddFocusEvent(false) handling.\n    // - May facilitate interactions with a debugger when focus loss leads to clearing inputs data.\n    // - Backends may have other side-effects on focus loss, so this will reduce side-effects but not necessary remove all of them.\n    bool        ConfigDebugIgnoreFocusLoss;     // = false          // Ignore io.AddFocusEvent(false), consequently not calling io.ClearInputKeys()/io.ClearInputMouse() in input processing.\n\n    // Option to audit .ini data\n    bool        ConfigDebugIniSettings;         // = false          // Save .ini data with extra comments (particularly helpful for Docking, but makes saving slower)\n\n    //------------------------------------------------------------------\n    // Platform Identifiers\n    // (the imgui_impl_xxxx backend files are setting those up for you)\n    //------------------------------------------------------------------\n\n    // Nowadays those would be stored in ImGuiPlatformIO but we are leaving them here for legacy reasons.\n    // Optional: Platform/Renderer backend name (informational only! will be displayed in About Window) + User data for backend/wrappers to store their own stuff.\n    const char* BackendPlatformName;            // = NULL\n    const char* BackendRendererName;            // = NULL\n    void*       BackendPlatformUserData;        // = NULL           // User data for platform backend\n    void*       BackendRendererUserData;        // = NULL           // User data for renderer backend\n    void*       BackendLanguageUserData;        // = NULL           // User data for non C++ programming language backend\n\n    //------------------------------------------------------------------\n    // Input - Call before calling NewFrame()\n    //------------------------------------------------------------------\n\n    // Input Functions\n    IMGUI_API void  AddKeyEvent(ImGuiKey key, bool down);                   // Queue a new key down/up event. Key should be \"translated\" (as in, generally ImGuiKey_A matches the key end-user would use to emit an 'A' character)\n    IMGUI_API void  AddKeyAnalogEvent(ImGuiKey key, bool down, float v);    // Queue a new key down/up event for analog values (e.g. ImGuiKey_Gamepad_ values). Dead-zones should be handled by the backend.\n    IMGUI_API void  AddMousePosEvent(float x, float y);                     // Queue a mouse position update. Use -FLT_MAX,-FLT_MAX to signify no mouse (e.g. app not focused and not hovered)\n    IMGUI_API void  AddMouseButtonEvent(int button, bool down);             // Queue a mouse button change\n    IMGUI_API void  AddMouseWheelEvent(float wheel_x, float wheel_y);       // Queue a mouse wheel update. wheel_y<0: scroll down, wheel_y>0: scroll up, wheel_x<0: scroll right, wheel_x>0: scroll left.\n    IMGUI_API void  AddMouseSourceEvent(ImGuiMouseSource source);           // Queue a mouse source change (Mouse/TouchScreen/Pen)\n    IMGUI_API void  AddFocusEvent(bool focused);                            // Queue a gain/loss of focus for the application (generally based on OS/platform focus of your window)\n    IMGUI_API void  AddInputCharacter(unsigned int c);                      // Queue a new character input\n    IMGUI_API void  AddInputCharacterUTF16(ImWchar16 c);                    // Queue a new character input from a UTF-16 character, it can be a surrogate\n    IMGUI_API void  AddInputCharactersUTF8(const char* str);                // Queue a new characters input from a UTF-8 string\n\n    IMGUI_API void  SetKeyEventNativeData(ImGuiKey key, int native_keycode, int native_scancode, int native_legacy_index = -1); // [Optional] Specify index for legacy <1.87 IsKeyXXX() functions with native indices + specify native keycode, scancode.\n    IMGUI_API void  SetAppAcceptingEvents(bool accepting_events);           // Set master flag for accepting key/mouse/text events (default to true). Useful if you have native dialog boxes that are interrupting your application loop/refresh, and you want to disable events being queued while your app is frozen.\n    IMGUI_API void  ClearEventsQueue();                                     // Clear all incoming events.\n    IMGUI_API void  ClearInputKeys();                                       // Clear current keyboard/gamepad state + current frame text input buffer. Equivalent to releasing all keys/buttons.\n    IMGUI_API void  ClearInputMouse();                                      // Clear current mouse state.\n#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS\n    IMGUI_API void  ClearInputCharacters();                                 // [Obsoleted in 1.89.8] Clear the current frame text input buffer. Now included within ClearInputKeys().\n#endif\n\n    //------------------------------------------------------------------\n    // Output - Updated by NewFrame() or EndFrame()/Render()\n    // (when reading from the io.WantCaptureMouse, io.WantCaptureKeyboard flags to dispatch your inputs, it is\n    //  generally easier and more correct to use their state BEFORE calling NewFrame(). See FAQ for details!)\n    //------------------------------------------------------------------\n\n    bool        WantCaptureMouse;                   // Set when Dear ImGui will use mouse inputs, in this case do not dispatch them to your main game/application (either way, always pass on mouse inputs to imgui). (e.g. unclicked mouse is hovering over an imgui window, widget is active, mouse was clicked over an imgui window, etc.).\n    bool        WantCaptureKeyboard;                // Set when Dear ImGui will use keyboard inputs, in this case do not dispatch them to your main game/application (either way, always pass keyboard inputs to imgui). (e.g. InputText active, or an imgui window is focused and navigation is enabled, etc.).\n    bool        WantTextInput;                      // Mobile/console: when set, you may display an on-screen keyboard. This is set by Dear ImGui when it wants textual keyboard input to happen (e.g. when a InputText widget is active).\n    bool        WantSetMousePos;                    // MousePos has been altered, backend should reposition mouse on next frame. Rarely used! Set only when io.ConfigNavMoveSetMousePos is enabled.\n    bool        WantSaveIniSettings;                // When manual .ini load/save is active (io.IniFilename == NULL), this will be set to notify your application that you can call SaveIniSettingsToMemory() and save yourself. Important: clear io.WantSaveIniSettings yourself after saving!\n    bool        NavActive;                          // Keyboard/Gamepad navigation is currently allowed (will handle ImGuiKey_NavXXX events) = a window is focused and it doesn't use the ImGuiWindowFlags_NoNavInputs flag.\n    bool        NavVisible;                         // Keyboard/Gamepad navigation highlight is visible and allowed (will handle ImGuiKey_NavXXX events).\n    float       Framerate;                          // Estimate of application framerate (rolling average over 60 frames, based on io.DeltaTime), in frame per second. Solely for convenience. Slow applications may not want to use a moving average or may want to reset underlying buffers occasionally.\n    int         MetricsRenderVertices;              // Vertices output during last call to Render()\n    int         MetricsRenderIndices;               // Indices output during last call to Render() = number of triangles * 3\n    int         MetricsRenderWindows;               // Number of visible windows\n    int         MetricsActiveWindows;               // Number of active windows\n    ImVec2      MouseDelta;                         // Mouse delta. Note that this is zero if either current or previous position are invalid (-FLT_MAX,-FLT_MAX), so a disappearing/reappearing mouse won't have a huge delta.\n\n    //------------------------------------------------------------------\n    // [Internal] Dear ImGui will maintain those fields. Forward compatibility not guaranteed!\n    //------------------------------------------------------------------\n\n    ImGuiContext* Ctx;                              // Parent UI context (needs to be set explicitly by parent).\n\n    // Main Input State\n    // (this block used to be written by backend, since 1.87 it is best to NOT write to those directly, call the AddXXX functions above instead)\n    // (reading from those variables is fair game, as they are extremely unlikely to be moving anywhere)\n    ImVec2      MousePos;                           // Mouse position, in pixels. Set to ImVec2(-FLT_MAX, -FLT_MAX) if mouse is unavailable (on another screen, etc.)\n    bool        MouseDown[5];                       // Mouse buttons: 0=left, 1=right, 2=middle + extras (ImGuiMouseButton_COUNT == 5). Dear ImGui mostly uses left and right buttons. Other buttons allow us to track if the mouse is being used by your application + available to user as a convenience via IsMouse** API.\n    float       MouseWheel;                         // Mouse wheel Vertical: 1 unit scrolls about 5 lines text. >0 scrolls Up, <0 scrolls Down. Hold SHIFT to turn vertical scroll into horizontal scroll.\n    float       MouseWheelH;                        // Mouse wheel Horizontal. >0 scrolls Left, <0 scrolls Right. Most users don't have a mouse with a horizontal wheel, may not be filled by all backends.\n    ImGuiMouseSource MouseSource;                   // Mouse actual input peripheral (Mouse/TouchScreen/Pen).\n    bool        KeyCtrl;                            // Keyboard modifier down: Control\n    bool        KeyShift;                           // Keyboard modifier down: Shift\n    bool        KeyAlt;                             // Keyboard modifier down: Alt\n    bool        KeySuper;                           // Keyboard modifier down: Cmd/Super/Windows\n\n    // Other state maintained from data above + IO function calls\n    ImGuiKeyChord KeyMods;                          // Key mods flags (any of ImGuiMod_Ctrl/ImGuiMod_Shift/ImGuiMod_Alt/ImGuiMod_Super flags, same as io.KeyCtrl/KeyShift/KeyAlt/KeySuper but merged into flags. Read-only, updated by NewFrame()\n    ImGuiKeyData  KeysData[ImGuiKey_NamedKey_COUNT];// Key state for all known keys. Use IsKeyXXX() functions to access this.\n    bool        WantCaptureMouseUnlessPopupClose;   // Alternative to WantCaptureMouse: (WantCaptureMouse == true && WantCaptureMouseUnlessPopupClose == false) when a click over void is expected to close a popup.\n    ImVec2      MousePosPrev;                       // Previous mouse position (note that MouseDelta is not necessary == MousePos-MousePosPrev, in case either position is invalid)\n    ImVec2      MouseClickedPos[5];                 // Position at time of clicking\n    double      MouseClickedTime[5];                // Time of last click (used to figure out double-click)\n    bool        MouseClicked[5];                    // Mouse button went from !Down to Down (same as MouseClickedCount[x] != 0)\n    bool        MouseDoubleClicked[5];              // Has mouse button been double-clicked? (same as MouseClickedCount[x] == 2)\n    ImU16       MouseClickedCount[5];               // == 0 (not clicked), == 1 (same as MouseClicked[]), == 2 (double-clicked), == 3 (triple-clicked) etc. when going from !Down to Down\n    ImU16       MouseClickedLastCount[5];           // Count successive number of clicks. Stays valid after mouse release. Reset after another click is done.\n    bool        MouseReleased[5];                   // Mouse button went from Down to !Down\n    double      MouseReleasedTime[5];               // Time of last released (rarely used! but useful to handle delayed single-click when trying to disambiguate them from double-click).\n    bool        MouseDownOwned[5];                  // Track if button was clicked inside a dear imgui window or over void blocked by a popup. We don't request mouse capture from the application if click started outside ImGui bounds.\n    bool        MouseDownOwnedUnlessPopupClose[5];  // Track if button was clicked inside a dear imgui window.\n    bool        MouseWheelRequestAxisSwap;          // On a non-Mac system, holding SHIFT requests WheelY to perform the equivalent of a WheelX event. On a Mac system this is already enforced by the system.\n    bool        MouseCtrlLeftAsRightClick;          // (OSX) Set to true when the current click was a Ctrl+click that spawned a simulated right click\n    float       MouseDownDuration[5];               // Duration the mouse button has been down (0.0f == just clicked)\n    float       MouseDownDurationPrev[5];           // Previous time the mouse button has been down\n    float       MouseDragMaxDistanceSqr[5];         // Squared maximum distance of how much mouse has traveled from the clicking point (used for moving thresholds)\n    float       PenPressure;                        // Touch/Pen pressure (0.0f to 1.0f, should be >0.0f only when MouseDown[0] == true). Helper storage currently unused by Dear ImGui.\n    bool        AppFocusLost;                       // Only modify via AddFocusEvent()\n    bool        AppAcceptingEvents;                 // Only modify via SetAppAcceptingEvents()\n    ImWchar16   InputQueueSurrogate;                // For AddInputCharacterUTF16()\n    ImVector<ImWchar> InputQueueCharacters;         // Queue of _characters_ input (obtained by platform backend). Fill using AddInputCharacter() helper.\n\n    // Legacy: before 1.87, we required backend to fill io.KeyMap[] (imgui->native map) during initialization and io.KeysDown[] (native indices) every frame.\n    // This is still temporarily supported as a legacy feature. However the new preferred scheme is for backend to call io.AddKeyEvent().\n    //   Old (<1.87):  ImGui::IsKeyPressed(ImGui::GetIO().KeyMap[ImGuiKey_Space]) --> New (1.87+) ImGui::IsKeyPressed(ImGuiKey_Space)\n    //   Old (<1.87):  ImGui::IsKeyPressed(MYPLATFORM_KEY_SPACE)                  --> New (1.87+) ImGui::IsKeyPressed(ImGuiKey_Space)\n    // Read https://github.com/ocornut/imgui/issues/4921 for details.\n    //int       KeyMap[ImGuiKey_COUNT];             // [LEGACY] Input: map of indices into the KeysDown[512] entries array which represent your \"native\" keyboard state. The first 512 are now unused and should be kept zero. Legacy backend will write into KeyMap[] using ImGuiKey_ indices which are always >512.\n    //bool      KeysDown[ImGuiKey_COUNT];           // [LEGACY] Input: Keyboard keys that are pressed (ideally left in the \"native\" order your engine has access to keyboard keys, so you can use your own defines/enums for keys). This used to be [512] sized. It is now ImGuiKey_COUNT to allow legacy io.KeysDown[GetKeyIndex(...)] to work without an overflow.\n    //float     NavInputs[ImGuiNavInput_COUNT];     // [LEGACY] Since 1.88, NavInputs[] was removed. Backends from 1.60 to 1.86 won't build. Feed gamepad inputs via io.AddKeyEvent() and ImGuiKey_GamepadXXX enums.\n    //void*     ImeWindowHandle;                    // [Obsoleted in 1.87] Set ImGuiViewport::PlatformHandleRaw instead. Set this to your HWND to get automatic IME cursor positioning.\n\n    // Legacy: before 1.91.1, clipboard functions were stored in ImGuiIO instead of ImGuiPlatformIO.\n    // As this is will affect all users of custom engines/backends, we are providing proper legacy redirection (will obsolete).\n#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS\n    const char* (*GetClipboardTextFn)(void* user_data);\n    void        (*SetClipboardTextFn)(void* user_data, const char* text);\n    void*       ClipboardUserData;\n#endif\n\n    IMGUI_API   ImGuiIO();\n};\n\n//-----------------------------------------------------------------------------\n// [SECTION] Misc data structures (ImGuiInputTextCallbackData, ImGuiSizeCallbackData, ImGuiPayload)\n//-----------------------------------------------------------------------------\n\n// Shared state of InputText(), passed as an argument to your callback when a ImGuiInputTextFlags_Callback* flag is used.\n// The callback function should return 0 by default.\n// Callbacks (follow a flag name and see comments in ImGuiInputTextFlags_ declarations for more details)\n// - ImGuiInputTextFlags_CallbackEdit:        Callback on buffer edit. Note that InputText() already returns true on edit + you can always use IsItemEdited(). The callback is useful to manipulate the underlying buffer while focus is active.\n// - ImGuiInputTextFlags_CallbackAlways:      Callback on each iteration\n// - ImGuiInputTextFlags_CallbackCompletion:  Callback on pressing TAB\n// - ImGuiInputTextFlags_CallbackHistory:     Callback on pressing Up/Down arrows\n// - ImGuiInputTextFlags_CallbackCharFilter:  Callback on character inputs to replace or discard them. Modify 'EventChar' to replace or discard, or return 1 in callback to discard.\n// - ImGuiInputTextFlags_CallbackResize:      Callback on buffer capacity changes request (beyond 'buf_size' parameter value), allowing the string to grow.\nstruct ImGuiInputTextCallbackData\n{\n    ImGuiContext*       Ctx;            // Parent UI context\n    ImGuiInputTextFlags EventFlag;      // One ImGuiInputTextFlags_Callback*    // Read-only\n    ImGuiInputTextFlags Flags;          // What user passed to InputText()      // Read-only\n    void*               UserData;       // What user passed to InputText()      // Read-only\n\n    // Arguments for the different callback events\n    // - During Resize callback, Buf will be same as your input buffer.\n    // - However, during Completion/History/Always callback, Buf always points to our own internal data (it is not the same as your buffer)! Changes to it will be reflected into your own buffer shortly after the callback.\n    // - To modify the text buffer in a callback, prefer using the InsertChars() / DeleteChars() function. InsertChars() will take care of calling the resize callback if necessary.\n    // - If you know your edits are not going to resize the underlying buffer allocation, you may modify the contents of 'Buf[]' directly. You need to update 'BufTextLen' accordingly (0 <= BufTextLen < BufSize) and set 'BufDirty'' to true so InputText can update its internal state.\n    ImWchar             EventChar;      // Character input                      // Read-write   // [CharFilter] Replace character with another one, or set to zero to drop. return 1 is equivalent to setting EventChar=0;\n    ImGuiKey            EventKey;       // Key pressed (Up/Down/TAB)            // Read-only    // [Completion,History]\n    char*               Buf;            // Text buffer                          // Read-write   // [Resize] Can replace pointer / [Completion,History,Always] Only write to pointed data, don't replace the actual pointer!\n    int                 BufTextLen;     // Text length (in bytes)               // Read-write   // [Resize,Completion,History,Always] Exclude zero-terminator storage. In C land: == strlen(some_text), in C++ land: string.length()\n    int                 BufSize;        // Buffer size (in bytes) = capacity+1  // Read-only    // [Resize,Completion,History,Always] Include zero-terminator storage. In C land == ARRAYSIZE(my_char_array), in C++ land: string.capacity()+1\n    bool                BufDirty;       // Set if you modify Buf/BufTextLen!    // Write        // [Completion,History,Always]\n    int                 CursorPos;      //                                      // Read-write   // [Completion,History,Always]\n    int                 SelectionStart; //                                      // Read-write   // [Completion,History,Always] == to SelectionEnd when no selection)\n    int                 SelectionEnd;   //                                      // Read-write   // [Completion,History,Always]\n\n    // Helper functions for text manipulation.\n    // Use those function to benefit from the CallbackResize behaviors. Calling those function reset the selection.\n    IMGUI_API ImGuiInputTextCallbackData();\n    IMGUI_API void      DeleteChars(int pos, int bytes_count);\n    IMGUI_API void      InsertChars(int pos, const char* text, const char* text_end = NULL);\n    void                SelectAll()             { SelectionStart = 0; SelectionEnd = BufTextLen; }\n    void                ClearSelection()        { SelectionStart = SelectionEnd = BufTextLen; }\n    bool                HasSelection() const    { return SelectionStart != SelectionEnd; }\n};\n\n// Resizing callback data to apply custom constraint. As enabled by SetNextWindowSizeConstraints(). Callback is called during the next Begin().\n// NB: For basic min/max size constraint on each axis you don't need to use the callback! The SetNextWindowSizeConstraints() parameters are enough.\nstruct ImGuiSizeCallbackData\n{\n    void*   UserData;       // Read-only.   What user passed to SetNextWindowSizeConstraints(). Generally store an integer or float in here (need reinterpret_cast<>).\n    ImVec2  Pos;            // Read-only.   Window position, for reference.\n    ImVec2  CurrentSize;    // Read-only.   Current window size.\n    ImVec2  DesiredSize;    // Read-write.  Desired size, based on user's mouse position. Write to this field to restrain resizing.\n};\n\n// Data payload for Drag and Drop operations: AcceptDragDropPayload(), GetDragDropPayload()\nstruct ImGuiPayload\n{\n    // Members\n    void*           Data;               // Data (copied and owned by dear imgui)\n    int             DataSize;           // Data size\n\n    // [Internal]\n    ImGuiID         SourceId;           // Source item id\n    ImGuiID         SourceParentId;     // Source parent id (if available)\n    int             DataFrameCount;     // Data timestamp\n    char            DataType[32 + 1];   // Data type tag (short user-supplied string, 32 characters max)\n    bool            Preview;            // Set when AcceptDragDropPayload() was called and mouse has been hovering the target item (nb: handle overlapping drag targets)\n    bool            Delivery;           // Set when AcceptDragDropPayload() was called and mouse button is released over the target item.\n\n    ImGuiPayload()  { Clear(); }\n    void Clear()    { SourceId = SourceParentId = 0; Data = NULL; DataSize = 0; memset(DataType, 0, sizeof(DataType)); DataFrameCount = -1; Preview = Delivery = false; }\n    bool IsDataType(const char* type) const { return DataFrameCount != -1 && strcmp(type, DataType) == 0; }\n    bool IsPreview() const                  { return Preview; }\n    bool IsDelivery() const                 { return Delivery; }\n};\n\n//-----------------------------------------------------------------------------\n// [SECTION] Helpers (ImGuiOnceUponAFrame, ImGuiTextFilter, ImGuiTextBuffer, ImGuiStorage, ImGuiListClipper, Math Operators, ImColor)\n//-----------------------------------------------------------------------------\n\n// Helper: Unicode defines\n#define IM_UNICODE_CODEPOINT_INVALID 0xFFFD     // Invalid Unicode code point (standard value).\n#ifdef IMGUI_USE_WCHAR32\n#define IM_UNICODE_CODEPOINT_MAX     0x10FFFF   // Maximum Unicode code point supported by this build.\n#else\n#define IM_UNICODE_CODEPOINT_MAX     0xFFFF     // Maximum Unicode code point supported by this build.\n#endif\n\n// Helper: Execute a block of code at maximum once a frame. Convenient if you want to quickly create a UI within deep-nested code that runs multiple times every frame.\n// Usage: static ImGuiOnceUponAFrame oaf; if (oaf) ImGui::Text(\"This will be called only once per frame\");\nstruct ImGuiOnceUponAFrame\n{\n    ImGuiOnceUponAFrame() { RefFrame = -1; }\n    mutable int RefFrame;\n    operator bool() const { int current_frame = ImGui::GetFrameCount(); if (RefFrame == current_frame) return false; RefFrame = current_frame; return true; }\n};\n\n// Helper: Parse and apply text filters. In format \"aaaaa[,bbbb][,ccccc]\"\nstruct ImGuiTextFilter\n{\n    IMGUI_API           ImGuiTextFilter(const char* default_filter = \"\");\n    IMGUI_API bool      Draw(const char* label = \"Filter (inc,-exc)\", float width = 0.0f);  // Helper calling InputText+Build\n    IMGUI_API bool      PassFilter(const char* text, const char* text_end = NULL) const;\n    IMGUI_API void      Build();\n    void                Clear()          { InputBuf[0] = 0; Build(); }\n    bool                IsActive() const { return !Filters.empty(); }\n\n    // [Internal]\n    struct ImGuiTextRange\n    {\n        const char*     b;\n        const char*     e;\n\n        ImGuiTextRange()                                { b = e = NULL; }\n        ImGuiTextRange(const char* _b, const char* _e)  { b = _b; e = _e; }\n        bool            empty() const                   { return b == e; }\n        IMGUI_API void  split(char separator, ImVector<ImGuiTextRange>* out) const;\n    };\n    char                    InputBuf[256];\n    ImVector<ImGuiTextRange>Filters;\n    int                     CountGrep;\n};\n\n// Helper: Growable text buffer for logging/accumulating text\n// (this could be called 'ImGuiTextBuilder' / 'ImGuiStringBuilder')\nstruct ImGuiTextBuffer\n{\n    ImVector<char>      Buf;\n    IMGUI_API static char EmptyString[1];\n\n    ImGuiTextBuffer()   { }\n    inline char         operator[](int i) const { IM_ASSERT(Buf.Data != NULL); return Buf.Data[i]; }\n    const char*         begin() const           { return Buf.Data ? &Buf.front() : EmptyString; }\n    const char*         end() const             { return Buf.Data ? &Buf.back() : EmptyString; } // Buf is zero-terminated, so end() will point on the zero-terminator\n    int                 size() const            { return Buf.Size ? Buf.Size - 1 : 0; }\n    bool                empty() const           { return Buf.Size <= 1; }\n    void                clear()                 { Buf.clear(); }\n    void                resize(int size)        { if (Buf.Size > size) Buf.Data[size] = 0; Buf.resize(size ? size + 1 : 0, 0); } // Similar to resize(0) on ImVector: empty string but don't free buffer.\n    void                reserve(int capacity)   { Buf.reserve(capacity); }\n    const char*         c_str() const           { return Buf.Data ? Buf.Data : EmptyString; }\n    IMGUI_API void      append(const char* str, const char* str_end = NULL);\n    IMGUI_API void      appendf(const char* fmt, ...) IM_FMTARGS(2);\n    IMGUI_API void      appendfv(const char* fmt, va_list args) IM_FMTLIST(2);\n};\n\n// [Internal] Key+Value for ImGuiStorage\nstruct ImGuiStoragePair\n{\n    ImGuiID     key;\n    union       { int val_i; float val_f; void* val_p; };\n    ImGuiStoragePair(ImGuiID _key, int _val)    { key = _key; val_i = _val; }\n    ImGuiStoragePair(ImGuiID _key, float _val)  { key = _key; val_f = _val; }\n    ImGuiStoragePair(ImGuiID _key, void* _val)  { key = _key; val_p = _val; }\n};\n\n// Helper: Key->Value storage\n// Typically you don't have to worry about this since a storage is held within each Window.\n// We use it to e.g. store collapse state for a tree (Int 0/1)\n// This is optimized for efficient lookup (dichotomy into a contiguous buffer) and rare insertion (typically tied to user interactions aka max once a frame)\n// You can use it as custom user storage for temporary values. Declare your own storage if, for example:\n// - You want to manipulate the open/close state of a particular sub-tree in your interface (tree node uses Int 0/1 to store their state).\n// - You want to store custom debug data easily without adding or editing structures in your code (probably not efficient, but convenient)\n// Types are NOT stored, so it is up to you to make sure your Key don't collide with different types.\nstruct ImGuiStorage\n{\n    // [Internal]\n    ImVector<ImGuiStoragePair>      Data;\n\n    // - Get***() functions find pair, never add/allocate. Pairs are sorted so a query is O(log N)\n    // - Set***() functions find pair, insertion on demand if missing.\n    // - Sorted insertion is costly, paid once. A typical frame shouldn't need to insert any new pair.\n    void                Clear() { Data.clear(); }\n    IMGUI_API int       GetInt(ImGuiID key, int default_val = 0) const;\n    IMGUI_API void      SetInt(ImGuiID key, int val);\n    IMGUI_API bool      GetBool(ImGuiID key, bool default_val = false) const;\n    IMGUI_API void      SetBool(ImGuiID key, bool val);\n    IMGUI_API float     GetFloat(ImGuiID key, float default_val = 0.0f) const;\n    IMGUI_API void      SetFloat(ImGuiID key, float val);\n    IMGUI_API void*     GetVoidPtr(ImGuiID key) const; // default_val is NULL\n    IMGUI_API void      SetVoidPtr(ImGuiID key, void* val);\n\n    // - Get***Ref() functions finds pair, insert on demand if missing, return pointer. Useful if you intend to do Get+Set.\n    // - References are only valid until a new value is added to the storage. Calling a Set***() function or a Get***Ref() function invalidates the pointer.\n    // - A typical use case where this is convenient for quick hacking (e.g. add storage during a live Edit&Continue session if you can't modify existing struct)\n    //      float* pvar = ImGui::GetFloatRef(key); ImGui::SliderFloat(\"var\", pvar, 0, 100.0f); some_var += *pvar;\n    IMGUI_API int*      GetIntRef(ImGuiID key, int default_val = 0);\n    IMGUI_API bool*     GetBoolRef(ImGuiID key, bool default_val = false);\n    IMGUI_API float*    GetFloatRef(ImGuiID key, float default_val = 0.0f);\n    IMGUI_API void**    GetVoidPtrRef(ImGuiID key, void* default_val = NULL);\n\n    // Advanced: for quicker full rebuild of a storage (instead of an incremental one), you may add all your contents and then sort once.\n    IMGUI_API void      BuildSortByKey();\n    // Obsolete: use on your own storage if you know only integer are being stored (open/close all tree nodes)\n    IMGUI_API void      SetAllInt(int val);\n\n#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS\n    //typedef ::ImGuiStoragePair ImGuiStoragePair;  // 1.90.8: moved type outside struct\n#endif\n};\n\n// Helper: Manually clip large list of items.\n// If you have lots evenly spaced items and you have random access to the list, you can perform coarse\n// clipping based on visibility to only submit items that are in view.\n// The clipper calculates the range of visible items and advance the cursor to compensate for the non-visible items we have skipped.\n// (Dear ImGui already clip items based on their bounds but: it needs to first layout the item to do so, and generally\n//  fetching/submitting your own data incurs additional cost. Coarse clipping using ImGuiListClipper allows you to easily\n//  scale using lists with tens of thousands of items without a problem)\n// Usage:\n//   ImGuiListClipper clipper;\n//   clipper.Begin(1000);         // We have 1000 elements, evenly spaced.\n//   while (clipper.Step())\n//       for (int i = clipper.DisplayStart; i < clipper.DisplayEnd; i++)\n//           ImGui::Text(\"line number %d\", i);\n// Generally what happens is:\n// - Clipper lets you process the first element (DisplayStart = 0, DisplayEnd = 1) regardless of it being visible or not.\n// - User code submit that one element.\n// - Clipper can measure the height of the first element\n// - Clipper calculate the actual range of elements to display based on the current clipping rectangle, position the cursor before the first visible element.\n// - User code submit visible elements.\n// - The clipper also handles various subtleties related to keyboard/gamepad navigation, wrapping etc.\nstruct ImGuiListClipper\n{\n    ImGuiContext*   Ctx;                // Parent UI context\n    int             DisplayStart;       // First item to display, updated by each call to Step()\n    int             DisplayEnd;         // End of items to display (exclusive)\n    int             ItemsCount;         // [Internal] Number of items\n    float           ItemsHeight;        // [Internal] Height of item after a first step and item submission can calculate it\n    float           StartPosY;          // [Internal] Cursor position at the time of Begin() or after table frozen rows are all processed\n    double          StartSeekOffsetY;   // [Internal] Account for frozen rows in a table and initial loss of precision in very large windows.\n    void*           TempData;           // [Internal] Internal data\n\n    // items_count: Use INT_MAX if you don't know how many items you have (in which case the cursor won't be advanced in the final step, and you can call SeekCursorForItem() manually if you need)\n    // items_height: Use -1.0f to be calculated automatically on first step. Otherwise pass in the distance between your items, typically GetTextLineHeightWithSpacing() or GetFrameHeightWithSpacing().\n    IMGUI_API ImGuiListClipper();\n    IMGUI_API ~ImGuiListClipper();\n    IMGUI_API void  Begin(int items_count, float items_height = -1.0f);\n    IMGUI_API void  End();             // Automatically called on the last call of Step() that returns false.\n    IMGUI_API bool  Step();            // Call until it returns false. The DisplayStart/DisplayEnd fields will be set and you can process/draw those items.\n\n    // Call IncludeItemByIndex() or IncludeItemsByIndex() *BEFORE* first call to Step() if you need a range of items to not be clipped, regardless of their visibility.\n    // (Due to alignment / padding of certain items it is possible that an extra item may be included on either end of the display range).\n    inline void     IncludeItemByIndex(int item_index)                  { IncludeItemsByIndex(item_index, item_index + 1); }\n    IMGUI_API void  IncludeItemsByIndex(int item_begin, int item_end);  // item_end is exclusive e.g. use (42, 42+1) to make item 42 never clipped.\n\n    // Seek cursor toward given item. This is automatically called while stepping.\n    // - The only reason to call this is: you can use ImGuiListClipper::Begin(INT_MAX) if you don't know item count ahead of time.\n    // - In this case, after all steps are done, you'll want to call SeekCursorForItem(item_count).\n    IMGUI_API void  SeekCursorForItem(int item_index);\n\n#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS\n    inline void IncludeRangeByIndices(int item_begin, int item_end)      { IncludeItemsByIndex(item_begin, item_end); } // [renamed in 1.89.9]\n    inline void ForceDisplayRangeByIndices(int item_begin, int item_end) { IncludeItemsByIndex(item_begin, item_end); } // [renamed in 1.89.6]\n    //inline ImGuiListClipper(int items_count, float items_height = -1.0f) { memset(this, 0, sizeof(*this)); ItemsCount = -1; Begin(items_count, items_height); } // [removed in 1.79]\n#endif\n};\n\n// Helpers: ImVec2/ImVec4 operators\n// - It is important that we are keeping those disabled by default so they don't leak in user space.\n// - This is in order to allow user enabling implicit cast operators between ImVec2/ImVec4 and their own types (using IM_VEC2_CLASS_EXTRA in imconfig.h)\n// - Add '#define IMGUI_DEFINE_MATH_OPERATORS' before including this file (or in imconfig.h) to access courtesy maths operators for ImVec2 and ImVec4.\n// - We intentionally provide ImVec2*float but not float*ImVec2: this is rare enough and we want to reduce the surface for possible user mistake.\n#ifdef IMGUI_DEFINE_MATH_OPERATORS\n#define IMGUI_DEFINE_MATH_OPERATORS_IMPLEMENTED\nIM_MSVC_RUNTIME_CHECKS_OFF\nstatic inline ImVec2  operator*(const ImVec2& lhs, const float rhs)     { return ImVec2(lhs.x * rhs, lhs.y * rhs); }\nstatic inline ImVec2  operator/(const ImVec2& lhs, const float rhs)     { return ImVec2(lhs.x / rhs, lhs.y / rhs); }\nstatic inline ImVec2  operator+(const ImVec2& lhs, const ImVec2& rhs)   { return ImVec2(lhs.x + rhs.x, lhs.y + rhs.y); }\nstatic inline ImVec2  operator-(const ImVec2& lhs, const ImVec2& rhs)   { return ImVec2(lhs.x - rhs.x, lhs.y - rhs.y); }\nstatic inline ImVec2  operator*(const ImVec2& lhs, const ImVec2& rhs)   { return ImVec2(lhs.x * rhs.x, lhs.y * rhs.y); }\nstatic inline ImVec2  operator/(const ImVec2& lhs, const ImVec2& rhs)   { return ImVec2(lhs.x / rhs.x, lhs.y / rhs.y); }\nstatic inline ImVec2  operator-(const ImVec2& lhs)                      { return ImVec2(-lhs.x, -lhs.y); }\nstatic inline ImVec2& operator*=(ImVec2& lhs, const float rhs)          { lhs.x *= rhs; lhs.y *= rhs; return lhs; }\nstatic inline ImVec2& operator/=(ImVec2& lhs, const float rhs)          { lhs.x /= rhs; lhs.y /= rhs; return lhs; }\nstatic inline ImVec2& operator+=(ImVec2& lhs, const ImVec2& rhs)        { lhs.x += rhs.x; lhs.y += rhs.y; return lhs; }\nstatic inline ImVec2& operator-=(ImVec2& lhs, const ImVec2& rhs)        { lhs.x -= rhs.x; lhs.y -= rhs.y; return lhs; }\nstatic inline ImVec2& operator*=(ImVec2& lhs, const ImVec2& rhs)        { lhs.x *= rhs.x; lhs.y *= rhs.y; return lhs; }\nstatic inline ImVec2& operator/=(ImVec2& lhs, const ImVec2& rhs)        { lhs.x /= rhs.x; lhs.y /= rhs.y; return lhs; }\nstatic inline bool    operator==(const ImVec2& lhs, const ImVec2& rhs)  { return lhs.x == rhs.x && lhs.y == rhs.y; }\nstatic inline bool    operator!=(const ImVec2& lhs, const ImVec2& rhs)  { return lhs.x != rhs.x || lhs.y != rhs.y; }\nstatic inline ImVec4  operator+(const ImVec4& lhs, const ImVec4& rhs)   { return ImVec4(lhs.x + rhs.x, lhs.y + rhs.y, lhs.z + rhs.z, lhs.w + rhs.w); }\nstatic inline ImVec4  operator-(const ImVec4& lhs, const ImVec4& rhs)   { return ImVec4(lhs.x - rhs.x, lhs.y - rhs.y, lhs.z - rhs.z, lhs.w - rhs.w); }\nstatic inline ImVec4  operator*(const ImVec4& lhs, const ImVec4& rhs)   { return ImVec4(lhs.x * rhs.x, lhs.y * rhs.y, lhs.z * rhs.z, lhs.w * rhs.w); }\nstatic inline bool    operator==(const ImVec4& lhs, const ImVec4& rhs)  { return lhs.x == rhs.x && lhs.y == rhs.y && lhs.z == rhs.z && lhs.w == rhs.w; }\nstatic inline bool    operator!=(const ImVec4& lhs, const ImVec4& rhs)  { return lhs.x != rhs.x || lhs.y != rhs.y || lhs.z != rhs.z || lhs.w != rhs.w; }\nIM_MSVC_RUNTIME_CHECKS_RESTORE\n#endif\n\n// Helpers macros to generate 32-bit encoded colors\n// - User can declare their own format by #defining the 5 _SHIFT/_MASK macros in their imconfig file.\n// - Any setting other than the default will need custom backend support. The only standard backend that supports anything else than the default is DirectX9.\n#ifndef IM_COL32_R_SHIFT\n#ifdef IMGUI_USE_BGRA_PACKED_COLOR\n#define IM_COL32_R_SHIFT    16\n#define IM_COL32_G_SHIFT    8\n#define IM_COL32_B_SHIFT    0\n#define IM_COL32_A_SHIFT    24\n#define IM_COL32_A_MASK     0xFF000000\n#else\n#define IM_COL32_R_SHIFT    0\n#define IM_COL32_G_SHIFT    8\n#define IM_COL32_B_SHIFT    16\n#define IM_COL32_A_SHIFT    24\n#define IM_COL32_A_MASK     0xFF000000\n#endif\n#endif\n#define IM_COL32(R,G,B,A)    (((ImU32)(A)<<IM_COL32_A_SHIFT) | ((ImU32)(B)<<IM_COL32_B_SHIFT) | ((ImU32)(G)<<IM_COL32_G_SHIFT) | ((ImU32)(R)<<IM_COL32_R_SHIFT))\n#define IM_COL32_WHITE       IM_COL32(255,255,255,255)  // Opaque white = 0xFFFFFFFF\n#define IM_COL32_BLACK       IM_COL32(0,0,0,255)        // Opaque black\n#define IM_COL32_BLACK_TRANS IM_COL32(0,0,0,0)          // Transparent black = 0x00000000\n\n// Helper: ImColor() implicitly converts colors to either ImU32 (packed 4x1 byte) or ImVec4 (4x1 float)\n// Prefer using IM_COL32() macros if you want a guaranteed compile-time ImU32 for usage with ImDrawList API.\n// **Avoid storing ImColor! Store either u32 of ImVec4. This is not a full-featured color class. MAY OBSOLETE.\n// **None of the ImGui API are using ImColor directly but you can use it as a convenience to pass colors in either ImU32 or ImVec4 formats. Explicitly cast to ImU32 or ImVec4 if needed.\nstruct ImColor\n{\n    ImVec4          Value;\n\n    constexpr ImColor()                                             { }\n    constexpr ImColor(float r, float g, float b, float a = 1.0f)    : Value(r, g, b, a) { }\n    constexpr ImColor(const ImVec4& col)                            : Value(col) {}\n    constexpr ImColor(int r, int g, int b, int a = 255)             : Value((float)r * (1.0f / 255.0f), (float)g * (1.0f / 255.0f), (float)b * (1.0f / 255.0f), (float)a* (1.0f / 255.0f)) {}\n    constexpr ImColor(ImU32 rgba)                                   : Value((float)((rgba >> IM_COL32_R_SHIFT) & 0xFF) * (1.0f / 255.0f), (float)((rgba >> IM_COL32_G_SHIFT) & 0xFF) * (1.0f / 255.0f), (float)((rgba >> IM_COL32_B_SHIFT) & 0xFF) * (1.0f / 255.0f), (float)((rgba >> IM_COL32_A_SHIFT) & 0xFF) * (1.0f / 255.0f)) {}\n    inline operator ImU32() const                                   { return ImGui::ColorConvertFloat4ToU32(Value); }\n    inline operator ImVec4() const                                  { return Value; }\n\n    // FIXME-OBSOLETE: May need to obsolete/cleanup those helpers.\n    inline void    SetHSV(float h, float s, float v, float a = 1.0f){ ImGui::ColorConvertHSVtoRGB(h, s, v, Value.x, Value.y, Value.z); Value.w = a; }\n    static ImColor HSV(float h, float s, float v, float a = 1.0f)   { float r, g, b; ImGui::ColorConvertHSVtoRGB(h, s, v, r, g, b); return ImColor(r, g, b, a); }\n};\n\n//-----------------------------------------------------------------------------\n// [SECTION] Multi-Select API flags and structures (ImGuiMultiSelectFlags, ImGuiSelectionRequestType, ImGuiSelectionRequest, ImGuiMultiSelectIO, ImGuiSelectionBasicStorage)\n//-----------------------------------------------------------------------------\n\n// Multi-selection system\n// Documentation at: https://github.com/ocornut/imgui/wiki/Multi-Select\n// - Refer to 'Demo->Widgets->Selection State & Multi-Select' for demos using this.\n// - This system implements standard multi-selection idioms (CTRL+Mouse/Keyboard, SHIFT+Mouse/Keyboard, etc)\n//   with support for clipper (skipping non-visible items), box-select and many other details.\n// - Selectable(), Checkbox() are supported but custom widgets may use it as well.\n// - TreeNode() is technically supported but... using this correctly is more complicated: you need some sort of linear/random access to your tree,\n//   which is suited to advanced trees setups also implementing filters and clipper. We will work toward simplifying and demoing it.\n// - In the spirit of Dear ImGui design, your code owns actual selection data.\n//   This is designed to allow all kinds of selection storage you may use in your application e.g. set/map/hash.\n// About ImGuiSelectionBasicStorage:\n// - This is an optional helper to store a selection state and apply selection requests.\n// - It is used by our demos and provided as a convenience to quickly implement multi-selection.\n// Usage:\n// - Identify submitted items with SetNextItemSelectionUserData(), most likely using an index into your current data-set.\n// - Store and maintain actual selection data using persistent object identifiers.\n// - Usage flow:\n//     BEGIN - (1) Call BeginMultiSelect() and retrieve the ImGuiMultiSelectIO* result.\n//           - (2) Honor request list (SetAll/SetRange requests) by updating your selection data. Same code as Step 6.\n//           - (3) [If using clipper] You need to make sure RangeSrcItem is always submitted. Calculate its index and pass to clipper.IncludeItemByIndex(). If storing indices in ImGuiSelectionUserData, a simple clipper.IncludeItemByIndex(ms_io->RangeSrcItem) call will work.\n//     LOOP  - (4) Submit your items with SetNextItemSelectionUserData() + Selectable()/TreeNode() calls.\n//     END   - (5) Call EndMultiSelect() and retrieve the ImGuiMultiSelectIO* result.\n//           - (6) Honor request list (SetAll/SetRange requests) by updating your selection data. Same code as Step 2.\n//     If you submit all items (no clipper), Step 2 and 3 are optional and will be handled by each item themselves. It is fine to always honor those steps.\n// About ImGuiSelectionUserData:\n// - This can store an application-defined identifier (e.g. index or pointer) submitted via SetNextItemSelectionUserData().\n// - In return we store them into RangeSrcItem/RangeFirstItem/RangeLastItem and other fields in ImGuiMultiSelectIO.\n// - Most applications will store an object INDEX, hence the chosen name and type. Storing an index is natural, because\n//   SetRange requests will give you two end-points and you will need to iterate/interpolate between them to update your selection.\n// - However it is perfectly possible to store a POINTER or another IDENTIFIER inside ImGuiSelectionUserData.\n//   Our system never assume that you identify items by indices, it never attempts to interpolate between two values.\n// - If you enable ImGuiMultiSelectFlags_NoRangeSelect then it is guaranteed that you will never have to interpolate\n//   between two ImGuiSelectionUserData, which may be a convenient way to use part of the feature with less code work.\n// - As most users will want to store an index, for convenience and to reduce confusion we use ImS64 instead of void*,\n//   being syntactically easier to downcast. Feel free to reinterpret_cast and store a pointer inside.\n\n// Flags for BeginMultiSelect()\nenum ImGuiMultiSelectFlags_\n{\n    ImGuiMultiSelectFlags_None                  = 0,\n    ImGuiMultiSelectFlags_SingleSelect          = 1 << 0,   // Disable selecting more than one item. This is available to allow single-selection code to share same code/logic if desired. It essentially disables the main purpose of BeginMultiSelect() tho!\n    ImGuiMultiSelectFlags_NoSelectAll           = 1 << 1,   // Disable CTRL+A shortcut to select all.\n    ImGuiMultiSelectFlags_NoRangeSelect         = 1 << 2,   // Disable Shift+selection mouse/keyboard support (useful for unordered 2D selection). With BoxSelect is also ensure contiguous SetRange requests are not combined into one. This allows not handling interpolation in SetRange requests.\n    ImGuiMultiSelectFlags_NoAutoSelect          = 1 << 3,   // Disable selecting items when navigating (useful for e.g. supporting range-select in a list of checkboxes).\n    ImGuiMultiSelectFlags_NoAutoClear           = 1 << 4,   // Disable clearing selection when navigating or selecting another one (generally used with ImGuiMultiSelectFlags_NoAutoSelect. useful for e.g. supporting range-select in a list of checkboxes).\n    ImGuiMultiSelectFlags_NoAutoClearOnReselect = 1 << 5,   // Disable clearing selection when clicking/selecting an already selected item.\n    ImGuiMultiSelectFlags_BoxSelect1d           = 1 << 6,   // Enable box-selection with same width and same x pos items (e.g. full row Selectable()). Box-selection works better with little bit of spacing between items hit-box in order to be able to aim at empty space.\n    ImGuiMultiSelectFlags_BoxSelect2d           = 1 << 7,   // Enable box-selection with varying width or varying x pos items support (e.g. different width labels, or 2D layout/grid). This is slower: alters clipping logic so that e.g. horizontal movements will update selection of normally clipped items.\n    ImGuiMultiSelectFlags_BoxSelectNoScroll     = 1 << 8,   // Disable scrolling when box-selecting near edges of scope.\n    ImGuiMultiSelectFlags_ClearOnEscape         = 1 << 9,   // Clear selection when pressing Escape while scope is focused.\n    ImGuiMultiSelectFlags_ClearOnClickVoid      = 1 << 10,  // Clear selection when clicking on empty location within scope.\n    ImGuiMultiSelectFlags_ScopeWindow           = 1 << 11,  // Scope for _BoxSelect and _ClearOnClickVoid is whole window (Default). Use if BeginMultiSelect() covers a whole window or used a single time in same window.\n    ImGuiMultiSelectFlags_ScopeRect             = 1 << 12,  // Scope for _BoxSelect and _ClearOnClickVoid is rectangle encompassing BeginMultiSelect()/EndMultiSelect(). Use if BeginMultiSelect() is called multiple times in same window.\n    ImGuiMultiSelectFlags_SelectOnClick         = 1 << 13,  // Apply selection on mouse down when clicking on unselected item. (Default)\n    ImGuiMultiSelectFlags_SelectOnClickRelease  = 1 << 14,  // Apply selection on mouse release when clicking an unselected item. Allow dragging an unselected item without altering selection.\n    //ImGuiMultiSelectFlags_RangeSelect2d       = 1 << 15,  // Shift+Selection uses 2d geometry instead of linear sequence, so possible to use Shift+up/down to select vertically in grid. Analogous to what BoxSelect does.\n    ImGuiMultiSelectFlags_NavWrapX              = 1 << 16,  // [Temporary] Enable navigation wrapping on X axis. Provided as a convenience because we don't have a design for the general Nav API for this yet. When the more general feature be public we may obsolete this flag in favor of new one.\n};\n\n// Main IO structure returned by BeginMultiSelect()/EndMultiSelect().\n// This mainly contains a list of selection requests.\n// - Use 'Demo->Tools->Debug Log->Selection' to see requests as they happen.\n// - Some fields are only useful if your list is dynamic and allows deletion (getting post-deletion focus/state right is shown in the demo)\n// - Below: who reads/writes each fields? 'r'=read, 'w'=write, 'ms'=multi-select code, 'app'=application/user code.\nstruct ImGuiMultiSelectIO\n{\n    //------------------------------------------// BeginMultiSelect / EndMultiSelect\n    ImVector<ImGuiSelectionRequest> Requests;   //  ms:w, app:r     /  ms:w  app:r   // Requests to apply to your selection data.\n    ImGuiSelectionUserData      RangeSrcItem;   //  ms:w  app:r     /                // (If using clipper) Begin: Source item (often the first selected item) must never be clipped: use clipper.IncludeItemByIndex() to ensure it is submitted.\n    ImGuiSelectionUserData      NavIdItem;      //  ms:w, app:r     /                // (If using deletion) Last known SetNextItemSelectionUserData() value for NavId (if part of submitted items).\n    bool                        NavIdSelected;  //  ms:w, app:r     /        app:r   // (If using deletion) Last known selection state for NavId (if part of submitted items).\n    bool                        RangeSrcReset;  //        app:w     /  ms:r          // (If using deletion) Set before EndMultiSelect() to reset ResetSrcItem (e.g. if deleted selection).\n    int                         ItemsCount;     //  ms:w, app:r     /        app:r   // 'int items_count' parameter to BeginMultiSelect() is copied here for convenience, allowing simpler calls to your ApplyRequests handler. Not used internally.\n};\n\n// Selection request type\nenum ImGuiSelectionRequestType\n{\n    ImGuiSelectionRequestType_None = 0,\n    ImGuiSelectionRequestType_SetAll,           // Request app to clear selection (if Selected==false) or select all items (if Selected==true). We cannot set RangeFirstItem/RangeLastItem as its contents is entirely up to user (not necessarily an index)\n    ImGuiSelectionRequestType_SetRange,         // Request app to select/unselect [RangeFirstItem..RangeLastItem] items (inclusive) based on value of Selected. Only EndMultiSelect() request this, app code can read after BeginMultiSelect() and it will always be false.\n};\n\n// Selection request item\nstruct ImGuiSelectionRequest\n{\n    //------------------------------------------// BeginMultiSelect / EndMultiSelect\n    ImGuiSelectionRequestType   Type;           //  ms:w, app:r     /  ms:w, app:r   // Request type. You'll most often receive 1 Clear + 1 SetRange with a single-item range.\n    bool                        Selected;       //  ms:w, app:r     /  ms:w, app:r   // Parameter for SetAll/SetRange requests (true = select, false = unselect)\n    ImS8                        RangeDirection; //                  /  ms:w  app:r   // Parameter for SetRange request: +1 when RangeFirstItem comes before RangeLastItem, -1 otherwise. Useful if you want to preserve selection order on a backward Shift+Click.\n    ImGuiSelectionUserData      RangeFirstItem; //                  /  ms:w, app:r   // Parameter for SetRange request (this is generally == RangeSrcItem when shift selecting from top to bottom).\n    ImGuiSelectionUserData      RangeLastItem;  //                  /  ms:w, app:r   // Parameter for SetRange request (this is generally == RangeSrcItem when shift selecting from bottom to top). Inclusive!\n};\n\n// Optional helper to store multi-selection state + apply multi-selection requests.\n// - Used by our demos and provided as a convenience to easily implement basic multi-selection.\n// - Iterate selection with 'void* it = NULL; ImGuiID id; while (selection.GetNextSelectedItem(&it, &id)) { ... }'\n//   Or you can check 'if (Contains(id)) { ... }' for each possible object if their number is not too high to iterate.\n// - USING THIS IS NOT MANDATORY. This is only a helper and not a required API.\n// To store a multi-selection, in your application you could:\n// - Use this helper as a convenience. We use our simple key->value ImGuiStorage as a std::set<ImGuiID> replacement.\n// - Use your own external storage: e.g. std::set<MyObjectId>, std::vector<MyObjectId>, interval trees, intrusively stored selection etc.\n// In ImGuiSelectionBasicStorage we:\n// - always use indices in the multi-selection API (passed to SetNextItemSelectionUserData(), retrieved in ImGuiMultiSelectIO)\n// - use the AdapterIndexToStorageId() indirection layer to abstract how persistent selection data is derived from an index.\n// - use decently optimized logic to allow queries and insertion of very large selection sets.\n// - do not preserve selection order.\n// Many combinations are possible depending on how you prefer to store your items and how you prefer to store your selection.\n// Large applications are likely to eventually want to get rid of this indirection layer and do their own thing.\n// See https://github.com/ocornut/imgui/wiki/Multi-Select for details and pseudo-code using this helper.\nstruct ImGuiSelectionBasicStorage\n{\n    // Members\n    int             Size;           //          // Number of selected items, maintained by this helper.\n    bool            PreserveOrder;  // = false  // GetNextSelectedItem() will return ordered selection (currently implemented by two additional sorts of selection. Could be improved)\n    void*           UserData;       // = NULL   // User data for use by adapter function        // e.g. selection.UserData = (void*)my_items;\n    ImGuiID         (*AdapterIndexToStorageId)(ImGuiSelectionBasicStorage* self, int idx);      // e.g. selection.AdapterIndexToStorageId = [](ImGuiSelectionBasicStorage* self, int idx) { return ((MyItems**)self->UserData)[idx]->ID; };\n    int             _SelectionOrder;// [Internal] Increasing counter to store selection order\n    ImGuiStorage    _Storage;       // [Internal] Selection set. Think of this as similar to e.g. std::set<ImGuiID>. Prefer not accessing directly: iterate with GetNextSelectedItem().\n\n    // Methods\n    IMGUI_API ImGuiSelectionBasicStorage();\n    IMGUI_API void  ApplyRequests(ImGuiMultiSelectIO* ms_io);   // Apply selection requests coming from BeginMultiSelect() and EndMultiSelect() functions. It uses 'items_count' passed to BeginMultiSelect()\n    IMGUI_API bool  Contains(ImGuiID id) const;                 // Query if an item id is in selection.\n    IMGUI_API void  Clear();                                    // Clear selection\n    IMGUI_API void  Swap(ImGuiSelectionBasicStorage& r);        // Swap two selections\n    IMGUI_API void  SetItemSelected(ImGuiID id, bool selected); // Add/remove an item from selection (generally done by ApplyRequests() function)\n    IMGUI_API bool  GetNextSelectedItem(void** opaque_it, ImGuiID* out_id); // Iterate selection with 'void* it = NULL; ImGuiID id; while (selection.GetNextSelectedItem(&it, &id)) { ... }'\n    inline ImGuiID  GetStorageIdFromIndex(int idx)              { return AdapterIndexToStorageId(this, idx); }  // Convert index to item id based on provided adapter.\n};\n\n// Optional helper to apply multi-selection requests to existing randomly accessible storage.\n// Convenient if you want to quickly wire multi-select API on e.g. an array of bool or items storing their own selection state.\nstruct ImGuiSelectionExternalStorage\n{\n    // Members\n    void*           UserData;       // User data for use by adapter function                                // e.g. selection.UserData = (void*)my_items;\n    void            (*AdapterSetItemSelected)(ImGuiSelectionExternalStorage* self, int idx, bool selected); // e.g. AdapterSetItemSelected = [](ImGuiSelectionExternalStorage* self, int idx, bool selected) { ((MyItems**)self->UserData)[idx]->Selected = selected; }\n\n    // Methods\n    IMGUI_API ImGuiSelectionExternalStorage();\n    IMGUI_API void  ApplyRequests(ImGuiMultiSelectIO* ms_io);   // Apply selection requests by using AdapterSetItemSelected() calls\n};\n\n//-----------------------------------------------------------------------------\n// [SECTION] Drawing API (ImDrawCmd, ImDrawIdx, ImDrawVert, ImDrawChannel, ImDrawListSplitter, ImDrawListFlags, ImDrawList, ImDrawData)\n// Hold a series of drawing commands. The user provides a renderer for ImDrawData which essentially contains an array of ImDrawList.\n//-----------------------------------------------------------------------------\n\n// The maximum line width to bake anti-aliased textures for. Build atlas with ImFontAtlasFlags_NoBakedLines to disable baking.\n#ifndef IM_DRAWLIST_TEX_LINES_WIDTH_MAX\n#define IM_DRAWLIST_TEX_LINES_WIDTH_MAX     (32)\n#endif\n\n// ImDrawIdx: vertex index. [Compile-time configurable type]\n// - To use 16-bit indices + allow large meshes: backend need to set 'io.BackendFlags |= ImGuiBackendFlags_RendererHasVtxOffset' and handle ImDrawCmd::VtxOffset (recommended).\n// - To use 32-bit indices: override with '#define ImDrawIdx unsigned int' in your imconfig.h file.\n#ifndef ImDrawIdx\ntypedef unsigned short ImDrawIdx;   // Default: 16-bit (for maximum compatibility with renderer backends)\n#endif\n\n// ImDrawCallback: Draw callbacks for advanced uses [configurable type: override in imconfig.h]\n// NB: You most likely do NOT need to use draw callbacks just to create your own widget or customized UI rendering,\n// you can poke into the draw list for that! Draw callback may be useful for example to:\n//  A) Change your GPU render state,\n//  B) render a complex 3D scene inside a UI element without an intermediate texture/render target, etc.\n// The expected behavior from your rendering function is 'if (cmd.UserCallback != NULL) { cmd.UserCallback(parent_list, cmd); } else { RenderTriangles() }'\n// If you want to override the signature of ImDrawCallback, you can simply use e.g. '#define ImDrawCallback MyDrawCallback' (in imconfig.h) + update rendering backend accordingly.\n#ifndef ImDrawCallback\ntypedef void (*ImDrawCallback)(const ImDrawList* parent_list, const ImDrawCmd* cmd);\n#endif\n\n// Special Draw callback value to request renderer backend to reset the graphics/render state.\n// The renderer backend needs to handle this special value, otherwise it will crash trying to call a function at this address.\n// This is useful, for example, if you submitted callbacks which you know have altered the render state and you want it to be restored.\n// Render state is not reset by default because they are many perfectly useful way of altering render state (e.g. changing shader/blending settings before an Image call).\n#define ImDrawCallback_ResetRenderState     (ImDrawCallback)(-8)\n\n// Typically, 1 command = 1 GPU draw call (unless command is a callback)\n// - VtxOffset: When 'io.BackendFlags & ImGuiBackendFlags_RendererHasVtxOffset' is enabled,\n//   this fields allow us to render meshes larger than 64K vertices while keeping 16-bit indices.\n//   Backends made for <1.71. will typically ignore the VtxOffset fields.\n// - The ClipRect/TextureId/VtxOffset fields must be contiguous as we memcmp() them together (this is asserted for).\nstruct ImDrawCmd\n{\n    ImVec4          ClipRect;           // 4*4  // Clipping rectangle (x1, y1, x2, y2). Subtract ImDrawData->DisplayPos to get clipping rectangle in \"viewport\" coordinates\n    ImTextureID     TextureId;          // 4-8  // User-provided texture ID. Set by user in ImfontAtlas::SetTexID() for fonts or passed to Image*() functions. Ignore if never using images or multiple fonts atlas.\n    unsigned int    VtxOffset;          // 4    // Start offset in vertex buffer. ImGuiBackendFlags_RendererHasVtxOffset: always 0, otherwise may be >0 to support meshes larger than 64K vertices with 16-bit indices.\n    unsigned int    IdxOffset;          // 4    // Start offset in index buffer.\n    unsigned int    ElemCount;          // 4    // Number of indices (multiple of 3) to be rendered as triangles. Vertices are stored in the callee ImDrawList's vtx_buffer[] array, indices in idx_buffer[].\n    ImDrawCallback  UserCallback;       // 4-8  // If != NULL, call the function instead of rendering the vertices. clip_rect and texture_id will be set normally.\n    void*           UserCallbackData;   // 4-8  // Callback user data (when UserCallback != NULL). If called AddCallback() with size == 0, this is a copy of the AddCallback() argument. If called AddCallback() with size > 0, this is pointing to a buffer where data is stored.\n    int             UserCallbackDataSize;  // 4 // Size of callback user data when using storage, otherwise 0.\n    int             UserCallbackDataOffset;// 4 // [Internal] Offset of callback user data when using storage, otherwise -1.\n\n    ImDrawCmd()     { memset(this, 0, sizeof(*this)); } // Also ensure our padding fields are zeroed\n\n    // Since 1.83: returns ImTextureID associated with this draw call. Warning: DO NOT assume this is always same as 'TextureId' (we will change this function for an upcoming feature)\n    inline ImTextureID GetTexID() const { return TextureId; }\n};\n\n// Vertex layout\n#ifndef IMGUI_OVERRIDE_DRAWVERT_STRUCT_LAYOUT\nstruct ImDrawVert\n{\n    ImVec2  pos;\n    ImVec2  uv;\n    ImU32   col;\n};\n#else\n// You can override the vertex format layout by defining IMGUI_OVERRIDE_DRAWVERT_STRUCT_LAYOUT in imconfig.h\n// The code expect ImVec2 pos (8 bytes), ImVec2 uv (8 bytes), ImU32 col (4 bytes), but you can re-order them or add other fields as needed to simplify integration in your engine.\n// The type has to be described within the macro (you can either declare the struct or use a typedef). This is because ImVec2/ImU32 are likely not declared at the time you'd want to set your type up.\n// NOTE: IMGUI DOESN'T CLEAR THE STRUCTURE AND DOESN'T CALL A CONSTRUCTOR SO ANY CUSTOM FIELD WILL BE UNINITIALIZED. IF YOU ADD EXTRA FIELDS (SUCH AS A 'Z' COORDINATES) YOU WILL NEED TO CLEAR THEM DURING RENDER OR TO IGNORE THEM.\nIMGUI_OVERRIDE_DRAWVERT_STRUCT_LAYOUT;\n#endif\n\n// [Internal] For use by ImDrawList\nstruct ImDrawCmdHeader\n{\n    ImVec4          ClipRect;\n    ImTextureID     TextureId;\n    unsigned int    VtxOffset;\n};\n\n// [Internal] For use by ImDrawListSplitter\nstruct ImDrawChannel\n{\n    ImVector<ImDrawCmd>         _CmdBuffer;\n    ImVector<ImDrawIdx>         _IdxBuffer;\n};\n\n// Split/Merge functions are used to split the draw list into different layers which can be drawn into out of order.\n// This is used by the Columns/Tables API, so items of each column can be batched together in a same draw call.\nstruct ImDrawListSplitter\n{\n    int                         _Current;    // Current channel number (0)\n    int                         _Count;      // Number of active channels (1+)\n    ImVector<ImDrawChannel>     _Channels;   // Draw channels (not resized down so _Count might be < Channels.Size)\n\n    inline ImDrawListSplitter()  { memset(this, 0, sizeof(*this)); }\n    inline ~ImDrawListSplitter() { ClearFreeMemory(); }\n    inline void                 Clear() { _Current = 0; _Count = 1; } // Do not clear Channels[] so our allocations are reused next frame\n    IMGUI_API void              ClearFreeMemory();\n    IMGUI_API void              Split(ImDrawList* draw_list, int count);\n    IMGUI_API void              Merge(ImDrawList* draw_list);\n    IMGUI_API void              SetCurrentChannel(ImDrawList* draw_list, int channel_idx);\n};\n\n// Flags for ImDrawList functions\n// (Legacy: bit 0 must always correspond to ImDrawFlags_Closed to be backward compatible with old API using a bool. Bits 1..3 must be unused)\nenum ImDrawFlags_\n{\n    ImDrawFlags_None                        = 0,\n    ImDrawFlags_Closed                      = 1 << 0, // PathStroke(), AddPolyline(): specify that shape should be closed (Important: this is always == 1 for legacy reason)\n    ImDrawFlags_RoundCornersTopLeft         = 1 << 4, // AddRect(), AddRectFilled(), PathRect(): enable rounding top-left corner only (when rounding > 0.0f, we default to all corners). Was 0x01.\n    ImDrawFlags_RoundCornersTopRight        = 1 << 5, // AddRect(), AddRectFilled(), PathRect(): enable rounding top-right corner only (when rounding > 0.0f, we default to all corners). Was 0x02.\n    ImDrawFlags_RoundCornersBottomLeft      = 1 << 6, // AddRect(), AddRectFilled(), PathRect(): enable rounding bottom-left corner only (when rounding > 0.0f, we default to all corners). Was 0x04.\n    ImDrawFlags_RoundCornersBottomRight     = 1 << 7, // AddRect(), AddRectFilled(), PathRect(): enable rounding bottom-right corner only (when rounding > 0.0f, we default to all corners). Wax 0x08.\n    ImDrawFlags_RoundCornersNone            = 1 << 8, // AddRect(), AddRectFilled(), PathRect(): disable rounding on all corners (when rounding > 0.0f). This is NOT zero, NOT an implicit flag!\n    ImDrawFlags_RoundCornersTop             = ImDrawFlags_RoundCornersTopLeft | ImDrawFlags_RoundCornersTopRight,\n    ImDrawFlags_RoundCornersBottom          = ImDrawFlags_RoundCornersBottomLeft | ImDrawFlags_RoundCornersBottomRight,\n    ImDrawFlags_RoundCornersLeft            = ImDrawFlags_RoundCornersBottomLeft | ImDrawFlags_RoundCornersTopLeft,\n    ImDrawFlags_RoundCornersRight           = ImDrawFlags_RoundCornersBottomRight | ImDrawFlags_RoundCornersTopRight,\n    ImDrawFlags_RoundCornersAll             = ImDrawFlags_RoundCornersTopLeft | ImDrawFlags_RoundCornersTopRight | ImDrawFlags_RoundCornersBottomLeft | ImDrawFlags_RoundCornersBottomRight,\n    ImDrawFlags_RoundCornersDefault_        = ImDrawFlags_RoundCornersAll, // Default to ALL corners if none of the _RoundCornersXX flags are specified.\n    ImDrawFlags_RoundCornersMask_           = ImDrawFlags_RoundCornersAll | ImDrawFlags_RoundCornersNone,\n};\n\n// Flags for ImDrawList instance. Those are set automatically by ImGui:: functions from ImGuiIO settings, and generally not manipulated directly.\n// It is however possible to temporarily alter flags between calls to ImDrawList:: functions.\nenum ImDrawListFlags_\n{\n    ImDrawListFlags_None                    = 0,\n    ImDrawListFlags_AntiAliasedLines        = 1 << 0,  // Enable anti-aliased lines/borders (*2 the number of triangles for 1.0f wide line or lines thin enough to be drawn using textures, otherwise *3 the number of triangles)\n    ImDrawListFlags_AntiAliasedLinesUseTex  = 1 << 1,  // Enable anti-aliased lines/borders using textures when possible. Require backend to render with bilinear filtering (NOT point/nearest filtering).\n    ImDrawListFlags_AntiAliasedFill         = 1 << 2,  // Enable anti-aliased edge around filled shapes (rounded rectangles, circles).\n    ImDrawListFlags_AllowVtxOffset          = 1 << 3,  // Can emit 'VtxOffset > 0' to allow large meshes. Set when 'ImGuiBackendFlags_RendererHasVtxOffset' is enabled.\n};\n\n// Draw command list\n// This is the low-level list of polygons that ImGui:: functions are filling. At the end of the frame,\n// all command lists are passed to your ImGuiIO::RenderDrawListFn function for rendering.\n// Each dear imgui window contains its own ImDrawList. You can use ImGui::GetWindowDrawList() to\n// access the current window draw list and draw custom primitives.\n// You can interleave normal ImGui:: calls and adding primitives to the current draw list.\n// In single viewport mode, top-left is == GetMainViewport()->Pos (generally 0,0), bottom-right is == GetMainViewport()->Pos+Size (generally io.DisplaySize).\n// You are totally free to apply whatever transformation matrix you want to the data (depending on the use of the transformation you may want to apply it to ClipRect as well!)\n// Important: Primitives are always added to the list and not culled (culling is done at higher-level by ImGui:: functions), if you use this API a lot consider coarse culling your drawn objects.\nstruct ImDrawList\n{\n    // This is what you have to render\n    ImVector<ImDrawCmd>     CmdBuffer;          // Draw commands. Typically 1 command = 1 GPU draw call, unless the command is a callback.\n    ImVector<ImDrawIdx>     IdxBuffer;          // Index buffer. Each command consume ImDrawCmd::ElemCount of those\n    ImVector<ImDrawVert>    VtxBuffer;          // Vertex buffer.\n    ImDrawListFlags         Flags;              // Flags, you may poke into these to adjust anti-aliasing settings per-primitive.\n\n    // [Internal, used while building lists]\n    unsigned int            _VtxCurrentIdx;     // [Internal] generally == VtxBuffer.Size unless we are past 64K vertices, in which case this gets reset to 0.\n    ImDrawListSharedData*   _Data;              // Pointer to shared draw data (you can use ImGui::GetDrawListSharedData() to get the one from current ImGui context)\n    ImDrawVert*             _VtxWritePtr;       // [Internal] point within VtxBuffer.Data after each add command (to avoid using the ImVector<> operators too much)\n    ImDrawIdx*              _IdxWritePtr;       // [Internal] point within IdxBuffer.Data after each add command (to avoid using the ImVector<> operators too much)\n    ImVector<ImVec2>        _Path;              // [Internal] current path building\n    ImDrawCmdHeader         _CmdHeader;         // [Internal] template of active commands. Fields should match those of CmdBuffer.back().\n    ImDrawListSplitter      _Splitter;          // [Internal] for channels api (note: prefer using your own persistent instance of ImDrawListSplitter!)\n    ImVector<ImVec4>        _ClipRectStack;     // [Internal]\n    ImVector<ImTextureID>   _TextureIdStack;    // [Internal]\n    ImVector<ImU8>          _CallbacksDataBuf;  // [Internal]\n    float                   _FringeScale;       // [Internal] anti-alias fringe is scaled by this value, this helps to keep things sharp while zooming at vertex buffer content\n    const char*             _OwnerName;         // Pointer to owner window's name for debugging\n\n    // If you want to create ImDrawList instances, pass them ImGui::GetDrawListSharedData().\n    // (advanced: you may create and use your own ImDrawListSharedData so you can use ImDrawList without ImGui, but that's more involved)\n    IMGUI_API ImDrawList(ImDrawListSharedData* shared_data);\n    IMGUI_API ~ImDrawList();\n\n    IMGUI_API void  PushClipRect(const ImVec2& clip_rect_min, const ImVec2& clip_rect_max, bool intersect_with_current_clip_rect = false);  // Render-level scissoring. This is passed down to your render function but not used for CPU-side coarse clipping. Prefer using higher-level ImGui::PushClipRect() to affect logic (hit-testing and widget culling)\n    IMGUI_API void  PushClipRectFullScreen();\n    IMGUI_API void  PopClipRect();\n    IMGUI_API void  PushTextureID(ImTextureID texture_id);\n    IMGUI_API void  PopTextureID();\n    inline ImVec2   GetClipRectMin() const { const ImVec4& cr = _ClipRectStack.back(); return ImVec2(cr.x, cr.y); }\n    inline ImVec2   GetClipRectMax() const { const ImVec4& cr = _ClipRectStack.back(); return ImVec2(cr.z, cr.w); }\n\n    // Primitives\n    // - Filled shapes must always use clockwise winding order. The anti-aliasing fringe depends on it. Counter-clockwise shapes will have \"inward\" anti-aliasing.\n    // - For rectangular primitives, \"p_min\" and \"p_max\" represent the upper-left and lower-right corners.\n    // - For circle primitives, use \"num_segments == 0\" to automatically calculate tessellation (preferred).\n    //   In older versions (until Dear ImGui 1.77) the AddCircle functions defaulted to num_segments == 12.\n    //   In future versions we will use textures to provide cheaper and higher-quality circles.\n    //   Use AddNgon() and AddNgonFilled() functions if you need to guarantee a specific number of sides.\n    IMGUI_API void  AddLine(const ImVec2& p1, const ImVec2& p2, ImU32 col, float thickness = 1.0f);\n    IMGUI_API void  AddRect(const ImVec2& p_min, const ImVec2& p_max, ImU32 col, float rounding = 0.0f, ImDrawFlags flags = 0, float thickness = 1.0f);   // a: upper-left, b: lower-right (== upper-left + size)\n    IMGUI_API void  AddRectFilled(const ImVec2& p_min, const ImVec2& p_max, ImU32 col, float rounding = 0.0f, ImDrawFlags flags = 0);                     // a: upper-left, b: lower-right (== upper-left + size)\n    IMGUI_API void  AddRectFilledMultiColor(const ImVec2& p_min, const ImVec2& p_max, ImU32 col_upr_left, ImU32 col_upr_right, ImU32 col_bot_right, ImU32 col_bot_left);\n    IMGUI_API void  AddQuad(const ImVec2& p1, const ImVec2& p2, const ImVec2& p3, const ImVec2& p4, ImU32 col, float thickness = 1.0f);\n    IMGUI_API void  AddQuadFilled(const ImVec2& p1, const ImVec2& p2, const ImVec2& p3, const ImVec2& p4, ImU32 col);\n    IMGUI_API void  AddTriangle(const ImVec2& p1, const ImVec2& p2, const ImVec2& p3, ImU32 col, float thickness = 1.0f);\n    IMGUI_API void  AddTriangleFilled(const ImVec2& p1, const ImVec2& p2, const ImVec2& p3, ImU32 col);\n    IMGUI_API void  AddCircle(const ImVec2& center, float radius, ImU32 col, int num_segments = 0, float thickness = 1.0f);\n    IMGUI_API void  AddCircleFilled(const ImVec2& center, float radius, ImU32 col, int num_segments = 0);\n    IMGUI_API void  AddNgon(const ImVec2& center, float radius, ImU32 col, int num_segments, float thickness = 1.0f);\n    IMGUI_API void  AddNgonFilled(const ImVec2& center, float radius, ImU32 col, int num_segments);\n    IMGUI_API void  AddEllipse(const ImVec2& center, const ImVec2& radius, ImU32 col, float rot = 0.0f, int num_segments = 0, float thickness = 1.0f);\n    IMGUI_API void  AddEllipseFilled(const ImVec2& center, const ImVec2& radius, ImU32 col, float rot = 0.0f, int num_segments = 0);\n    IMGUI_API void  AddText(const ImVec2& pos, ImU32 col, const char* text_begin, const char* text_end = NULL);\n    IMGUI_API void  AddText(ImFont* font, float font_size, const ImVec2& pos, ImU32 col, const char* text_begin, const char* text_end = NULL, float wrap_width = 0.0f, const ImVec4* cpu_fine_clip_rect = NULL);\n    IMGUI_API void  AddBezierCubic(const ImVec2& p1, const ImVec2& p2, const ImVec2& p3, const ImVec2& p4, ImU32 col, float thickness, int num_segments = 0); // Cubic Bezier (4 control points)\n    IMGUI_API void  AddBezierQuadratic(const ImVec2& p1, const ImVec2& p2, const ImVec2& p3, ImU32 col, float thickness, int num_segments = 0);               // Quadratic Bezier (3 control points)\n\n    // General polygon\n    // - Only simple polygons are supported by filling functions (no self-intersections, no holes).\n    // - Concave polygon fill is more expensive than convex one: it has O(N^2) complexity. Provided as a convenience for the user but not used by the main library.\n    IMGUI_API void  AddPolyline(const ImVec2* points, int num_points, ImU32 col, ImDrawFlags flags, float thickness);\n    IMGUI_API void  AddConvexPolyFilled(const ImVec2* points, int num_points, ImU32 col);\n    IMGUI_API void  AddConcavePolyFilled(const ImVec2* points, int num_points, ImU32 col);\n\n    // Image primitives\n    // - Read FAQ to understand what ImTextureID is.\n    // - \"p_min\" and \"p_max\" represent the upper-left and lower-right corners of the rectangle.\n    // - \"uv_min\" and \"uv_max\" represent the normalized texture coordinates to use for those corners. Using (0,0)->(1,1) texture coordinates will generally display the entire texture.\n    IMGUI_API void  AddImage(ImTextureID user_texture_id, const ImVec2& p_min, const ImVec2& p_max, const ImVec2& uv_min = ImVec2(0, 0), const ImVec2& uv_max = ImVec2(1, 1), ImU32 col = IM_COL32_WHITE);\n    IMGUI_API void  AddImageQuad(ImTextureID user_texture_id, const ImVec2& p1, const ImVec2& p2, const ImVec2& p3, const ImVec2& p4, const ImVec2& uv1 = ImVec2(0, 0), const ImVec2& uv2 = ImVec2(1, 0), const ImVec2& uv3 = ImVec2(1, 1), const ImVec2& uv4 = ImVec2(0, 1), ImU32 col = IM_COL32_WHITE);\n    IMGUI_API void  AddImageRounded(ImTextureID user_texture_id, const ImVec2& p_min, const ImVec2& p_max, const ImVec2& uv_min, const ImVec2& uv_max, ImU32 col, float rounding, ImDrawFlags flags = 0);\n\n    // Stateful path API, add points then finish with PathFillConvex() or PathStroke()\n    // - Important: filled shapes must always use clockwise winding order! The anti-aliasing fringe depends on it. Counter-clockwise shapes will have \"inward\" anti-aliasing.\n    //   so e.g. 'PathArcTo(center, radius, PI * -0.5f, PI)' is ok, whereas 'PathArcTo(center, radius, PI, PI * -0.5f)' won't have correct anti-aliasing when followed by PathFillConvex().\n    inline    void  PathClear()                                                 { _Path.Size = 0; }\n    inline    void  PathLineTo(const ImVec2& pos)                               { _Path.push_back(pos); }\n    inline    void  PathLineToMergeDuplicate(const ImVec2& pos)                 { if (_Path.Size == 0 || memcmp(&_Path.Data[_Path.Size - 1], &pos, 8) != 0) _Path.push_back(pos); }\n    inline    void  PathFillConvex(ImU32 col)                                   { AddConvexPolyFilled(_Path.Data, _Path.Size, col); _Path.Size = 0; }\n    inline    void  PathFillConcave(ImU32 col)                                  { AddConcavePolyFilled(_Path.Data, _Path.Size, col); _Path.Size = 0; }\n    inline    void  PathStroke(ImU32 col, ImDrawFlags flags = 0, float thickness = 1.0f) { AddPolyline(_Path.Data, _Path.Size, col, flags, thickness); _Path.Size = 0; }\n    IMGUI_API void  PathArcTo(const ImVec2& center, float radius, float a_min, float a_max, int num_segments = 0);\n    IMGUI_API void  PathArcToFast(const ImVec2& center, float radius, int a_min_of_12, int a_max_of_12);                // Use precomputed angles for a 12 steps circle\n    IMGUI_API void  PathEllipticalArcTo(const ImVec2& center, const ImVec2& radius, float rot, float a_min, float a_max, int num_segments = 0); // Ellipse\n    IMGUI_API void  PathBezierCubicCurveTo(const ImVec2& p2, const ImVec2& p3, const ImVec2& p4, int num_segments = 0); // Cubic Bezier (4 control points)\n    IMGUI_API void  PathBezierQuadraticCurveTo(const ImVec2& p2, const ImVec2& p3, int num_segments = 0);               // Quadratic Bezier (3 control points)\n    IMGUI_API void  PathRect(const ImVec2& rect_min, const ImVec2& rect_max, float rounding = 0.0f, ImDrawFlags flags = 0);\n\n    // Advanced: Draw Callbacks\n    // - May be used to alter render state (change sampler, blending, current shader). May be used to emit custom rendering commands (difficult to do correctly, but possible).\n    // - Use special ImDrawCallback_ResetRenderState callback to instruct backend to reset its render state to the default.\n    // - Your rendering loop must check for 'UserCallback' in ImDrawCmd and call the function instead of rendering triangles. All standard backends are honoring this.\n    // - For some backends, the callback may access selected render-states exposed by the backend in a ImGui_ImplXXXX_RenderState structure pointed to by platform_io.Renderer_RenderState.\n    // - IMPORTANT: please be mindful of the different level of indirection between using size==0 (copying argument) and using size>0 (copying pointed data into a buffer).\n    //   - If userdata_size == 0: we copy/store the 'userdata' argument as-is. It will be available unmodified in ImDrawCmd::UserCallbackData during render.\n    //   - If userdata_size > 0,  we copy/store 'userdata_size' bytes pointed to by 'userdata'. We store them in a buffer stored inside the drawlist. ImDrawCmd::UserCallbackData will point inside that buffer so you have to retrieve data from there. Your callback may need to use ImDrawCmd::UserCallbackDataSize if you expect dynamically-sized data.\n    //   - Support for userdata_size > 0 was added in v1.91.4, October 2024. So earlier code always only allowed to copy/store a simple void*.\n    IMGUI_API void  AddCallback(ImDrawCallback callback, void* userdata, size_t userdata_size = 0);\n\n    // Advanced: Miscellaneous\n    IMGUI_API void  AddDrawCmd();                                               // This is useful if you need to forcefully create a new draw call (to allow for dependent rendering / blending). Otherwise primitives are merged into the same draw-call as much as possible\n    IMGUI_API ImDrawList* CloneOutput() const;                                  // Create a clone of the CmdBuffer/IdxBuffer/VtxBuffer.\n\n    // Advanced: Channels\n    // - Use to split render into layers. By switching channels to can render out-of-order (e.g. submit FG primitives before BG primitives)\n    // - Use to minimize draw calls (e.g. if going back-and-forth between multiple clipping rectangles, prefer to append into separate channels then merge at the end)\n    // - This API shouldn't have been in ImDrawList in the first place!\n    //   Prefer using your own persistent instance of ImDrawListSplitter as you can stack them.\n    //   Using the ImDrawList::ChannelsXXXX you cannot stack a split over another.\n    inline void     ChannelsSplit(int count)    { _Splitter.Split(this, count); }\n    inline void     ChannelsMerge()             { _Splitter.Merge(this); }\n    inline void     ChannelsSetCurrent(int n)   { _Splitter.SetCurrentChannel(this, n); }\n\n    // Advanced: Primitives allocations\n    // - We render triangles (three vertices)\n    // - All primitives needs to be reserved via PrimReserve() beforehand.\n    IMGUI_API void  PrimReserve(int idx_count, int vtx_count);\n    IMGUI_API void  PrimUnreserve(int idx_count, int vtx_count);\n    IMGUI_API void  PrimRect(const ImVec2& a, const ImVec2& b, ImU32 col);      // Axis aligned rectangle (composed of two triangles)\n    IMGUI_API void  PrimRectUV(const ImVec2& a, const ImVec2& b, const ImVec2& uv_a, const ImVec2& uv_b, ImU32 col);\n    IMGUI_API void  PrimQuadUV(const ImVec2& a, const ImVec2& b, const ImVec2& c, const ImVec2& d, const ImVec2& uv_a, const ImVec2& uv_b, const ImVec2& uv_c, const ImVec2& uv_d, ImU32 col);\n    inline    void  PrimWriteVtx(const ImVec2& pos, const ImVec2& uv, ImU32 col)    { _VtxWritePtr->pos = pos; _VtxWritePtr->uv = uv; _VtxWritePtr->col = col; _VtxWritePtr++; _VtxCurrentIdx++; }\n    inline    void  PrimWriteIdx(ImDrawIdx idx)                                     { *_IdxWritePtr = idx; _IdxWritePtr++; }\n    inline    void  PrimVtx(const ImVec2& pos, const ImVec2& uv, ImU32 col)         { PrimWriteIdx((ImDrawIdx)_VtxCurrentIdx); PrimWriteVtx(pos, uv, col); } // Write vertex with unique index\n\n    // Obsolete names\n    //inline  void  AddEllipse(const ImVec2& center, float radius_x, float radius_y, ImU32 col, float rot = 0.0f, int num_segments = 0, float thickness = 1.0f) { AddEllipse(center, ImVec2(radius_x, radius_y), col, rot, num_segments, thickness); } // OBSOLETED in 1.90.5 (Mar 2024)\n    //inline  void  AddEllipseFilled(const ImVec2& center, float radius_x, float radius_y, ImU32 col, float rot = 0.0f, int num_segments = 0) { AddEllipseFilled(center, ImVec2(radius_x, radius_y), col, rot, num_segments); }                        // OBSOLETED in 1.90.5 (Mar 2024)\n    //inline  void  PathEllipticalArcTo(const ImVec2& center, float radius_x, float radius_y, float rot, float a_min, float a_max, int num_segments = 0) { PathEllipticalArcTo(center, ImVec2(radius_x, radius_y), rot, a_min, a_max, num_segments); } // OBSOLETED in 1.90.5 (Mar 2024)\n    //inline  void  AddBezierCurve(const ImVec2& p1, const ImVec2& p2, const ImVec2& p3, const ImVec2& p4, ImU32 col, float thickness, int num_segments = 0) { AddBezierCubic(p1, p2, p3, p4, col, thickness, num_segments); }                         // OBSOLETED in 1.80 (Jan 2021)\n    //inline  void  PathBezierCurveTo(const ImVec2& p2, const ImVec2& p3, const ImVec2& p4, int num_segments = 0) { PathBezierCubicCurveTo(p2, p3, p4, num_segments); }                                                                                // OBSOLETED in 1.80 (Jan 2021)\n\n    // [Internal helpers]\n    IMGUI_API void  _ResetForNewFrame();\n    IMGUI_API void  _ClearFreeMemory();\n    IMGUI_API void  _PopUnusedDrawCmd();\n    IMGUI_API void  _TryMergeDrawCmds();\n    IMGUI_API void  _OnChangedClipRect();\n    IMGUI_API void  _OnChangedTextureID();\n    IMGUI_API void  _OnChangedVtxOffset();\n    IMGUI_API void  _SetTextureID(ImTextureID texture_id);\n    IMGUI_API int   _CalcCircleAutoSegmentCount(float radius) const;\n    IMGUI_API void  _PathArcToFastEx(const ImVec2& center, float radius, int a_min_sample, int a_max_sample, int a_step);\n    IMGUI_API void  _PathArcToN(const ImVec2& center, float radius, float a_min, float a_max, int num_segments);\n};\n\n// All draw data to render a Dear ImGui frame\n// (NB: the style and the naming convention here is a little inconsistent, we currently preserve them for backward compatibility purpose,\n// as this is one of the oldest structure exposed by the library! Basically, ImDrawList == CmdList)\nstruct ImDrawData\n{\n    bool                Valid;              // Only valid after Render() is called and before the next NewFrame() is called.\n    int                 CmdListsCount;      // Number of ImDrawList* to render (should always be == CmdLists.size)\n    int                 TotalIdxCount;      // For convenience, sum of all ImDrawList's IdxBuffer.Size\n    int                 TotalVtxCount;      // For convenience, sum of all ImDrawList's VtxBuffer.Size\n    ImVector<ImDrawList*> CmdLists;         // Array of ImDrawList* to render. The ImDrawLists are owned by ImGuiContext and only pointed to from here.\n    ImVec2              DisplayPos;         // Top-left position of the viewport to render (== top-left of the orthogonal projection matrix to use) (== GetMainViewport()->Pos for the main viewport, == (0.0) in most single-viewport applications)\n    ImVec2              DisplaySize;        // Size of the viewport to render (== GetMainViewport()->Size for the main viewport, == io.DisplaySize in most single-viewport applications)\n    ImVec2              FramebufferScale;   // Amount of pixels for each unit of DisplaySize. Based on io.DisplayFramebufferScale. Generally (1,1) on normal display, (2,2) on OSX with Retina display.\n    ImGuiViewport*      OwnerViewport;      // Viewport carrying the ImDrawData instance, might be of use to the renderer (generally not).\n\n    // Functions\n    ImDrawData()    { Clear(); }\n    IMGUI_API void  Clear();\n    IMGUI_API void  AddDrawList(ImDrawList* draw_list);     // Helper to add an external draw list into an existing ImDrawData.\n    IMGUI_API void  DeIndexAllBuffers();                    // Helper to convert all buffers from indexed to non-indexed, in case you cannot render indexed. Note: this is slow and most likely a waste of resources. Always prefer indexed rendering!\n    IMGUI_API void  ScaleClipRects(const ImVec2& fb_scale); // Helper to scale the ClipRect field of each ImDrawCmd. Use if your final output buffer is at a different scale than Dear ImGui expects, or if there is a difference between your window resolution and framebuffer resolution.\n};\n\n//-----------------------------------------------------------------------------\n// [SECTION] Font API (ImFontConfig, ImFontGlyph, ImFontAtlasFlags, ImFontAtlas, ImFontGlyphRangesBuilder, ImFont)\n//-----------------------------------------------------------------------------\n\n// A font input/source (we may rename this to ImFontSource in the future)\nstruct ImFontConfig\n{\n    void*           FontData;               //          // TTF/OTF data\n    int             FontDataSize;           //          // TTF/OTF data size\n    bool            FontDataOwnedByAtlas;   // true     // TTF/OTF data ownership taken by the container ImFontAtlas (will delete memory itself).\n    bool            MergeMode;              // false    // Merge into previous ImFont, so you can combine multiple inputs font into one ImFont (e.g. ASCII font + icons + Japanese glyphs). You may want to use GlyphOffset.y when merge font of different heights.\n    bool            PixelSnapH;             // false    // Align every glyph AdvanceX to pixel boundaries. Useful e.g. if you are merging a non-pixel aligned font with the default font. If enabled, you can set OversampleH/V to 1.\n    int             FontNo;                 // 0        // Index of font within TTF/OTF file\n    int             OversampleH;            // 0 (2)    // Rasterize at higher quality for sub-pixel positioning. 0 == auto == 1 or 2 depending on size. Note the difference between 2 and 3 is minimal. You can reduce this to 1 for large glyphs save memory. Read https://github.com/nothings/stb/blob/master/tests/oversample/README.md for details.\n    int             OversampleV;            // 0 (1)    // Rasterize at higher quality for sub-pixel positioning. 0 == auto == 1. This is not really useful as we don't use sub-pixel positions on the Y axis.\n    float           SizePixels;             //          // Size in pixels for rasterizer (more or less maps to the resulting font height).\n    //ImVec2        GlyphExtraSpacing;      // 0, 0     // (REMOVED IN 1.91.9: use GlyphExtraAdvanceX)\n    ImVec2          GlyphOffset;            // 0, 0     // Offset all glyphs from this font input.\n    const ImWchar*  GlyphRanges;            // NULL     // THE ARRAY DATA NEEDS TO PERSIST AS LONG AS THE FONT IS ALIVE. Pointer to a user-provided list of Unicode range (2 value per range, values are inclusive, zero-terminated list).\n    float           GlyphMinAdvanceX;       // 0        // Minimum AdvanceX for glyphs, set Min to align font icons, set both Min/Max to enforce mono-space font\n    float           GlyphMaxAdvanceX;       // FLT_MAX  // Maximum AdvanceX for glyphs\n    float           GlyphExtraAdvanceX;     // 0        // Extra spacing (in pixels) between glyphs. Please contact us if you are using this.\n    unsigned int    FontBuilderFlags;       // 0        // Settings for custom font builder. THIS IS BUILDER IMPLEMENTATION DEPENDENT. Leave as zero if unsure.\n    float           RasterizerMultiply;     // 1.0f     // Linearly brighten (>1.0f) or darken (<1.0f) font output. Brightening small fonts may be a good workaround to make them more readable. This is a silly thing we may remove in the future.\n    float           RasterizerDensity;      // 1.0f     // DPI scale for rasterization, not altering other font metrics: make it easy to swap between e.g. a 100% and a 400% fonts for a zooming display. IMPORTANT: If you increase this it is expected that you increase font scale accordingly, otherwise quality may look lowered.\n    ImWchar         EllipsisChar;           // 0        // Explicitly specify Unicode codepoint of ellipsis character. When fonts are being merged first specified ellipsis will be used.\n\n    // [Internal]\n    char            Name[40];               // Name (strictly to ease debugging)\n    ImFont*         DstFont;\n\n    IMGUI_API ImFontConfig();\n};\n\n// Hold rendering data for one glyph.\n// (Note: some language parsers may fail to convert the 31+1 bitfield members, in this case maybe drop store a single u32 or we can rework this)\nstruct ImFontGlyph\n{\n    unsigned int    Colored : 1;        // Flag to indicate glyph is colored and should generally ignore tinting (make it usable with no shift on little-endian as this is used in loops)\n    unsigned int    Visible : 1;        // Flag to indicate glyph has no visible pixels (e.g. space). Allow early out when rendering.\n    unsigned int    Codepoint : 30;     // 0x0000..0x10FFFF\n    float           AdvanceX;           // Horizontal distance to advance layout with\n    float           X0, Y0, X1, Y1;     // Glyph corners\n    float           U0, V0, U1, V1;     // Texture coordinates\n};\n\n// Helper to build glyph ranges from text/string data. Feed your application strings/characters to it then call BuildRanges().\n// This is essentially a tightly packed of vector of 64k booleans = 8KB storage.\nstruct ImFontGlyphRangesBuilder\n{\n    ImVector<ImU32> UsedChars;            // Store 1-bit per Unicode code point (0=unused, 1=used)\n\n    ImFontGlyphRangesBuilder()              { Clear(); }\n    inline void     Clear()                 { int size_in_bytes = (IM_UNICODE_CODEPOINT_MAX + 1) / 8; UsedChars.resize(size_in_bytes / (int)sizeof(ImU32)); memset(UsedChars.Data, 0, (size_t)size_in_bytes); }\n    inline bool     GetBit(size_t n) const  { int off = (int)(n >> 5); ImU32 mask = 1u << (n & 31); return (UsedChars[off] & mask) != 0; }  // Get bit n in the array\n    inline void     SetBit(size_t n)        { int off = (int)(n >> 5); ImU32 mask = 1u << (n & 31); UsedChars[off] |= mask; }               // Set bit n in the array\n    inline void     AddChar(ImWchar c)      { SetBit(c); }                      // Add character\n    IMGUI_API void  AddText(const char* text, const char* text_end = NULL);     // Add string (each character of the UTF-8 string are added)\n    IMGUI_API void  AddRanges(const ImWchar* ranges);                           // Add ranges, e.g. builder.AddRanges(ImFontAtlas::GetGlyphRangesDefault()) to force add all of ASCII/Latin+Ext\n    IMGUI_API void  BuildRanges(ImVector<ImWchar>* out_ranges);                 // Output new ranges\n};\n\n// See ImFontAtlas::AddCustomRectXXX functions.\nstruct ImFontAtlasCustomRect\n{\n    unsigned short  X, Y;           // Output   // Packed position in Atlas\n\n    // [Internal]\n    unsigned short  Width, Height;  // Input    // Desired rectangle dimension\n    unsigned int    GlyphID : 31;   // Input    // For custom font glyphs only (ID < 0x110000)\n    unsigned int    GlyphColored : 1; // Input  // For custom font glyphs only: glyph is colored, removed tinting.\n    float           GlyphAdvanceX;  // Input    // For custom font glyphs only: glyph xadvance\n    ImVec2          GlyphOffset;    // Input    // For custom font glyphs only: glyph display offset\n    ImFont*         Font;           // Input    // For custom font glyphs only: target font\n    ImFontAtlasCustomRect()         { X = Y = 0xFFFF; Width = Height = 0; GlyphID = 0; GlyphColored = 0; GlyphAdvanceX = 0.0f; GlyphOffset = ImVec2(0, 0); Font = NULL; }\n    bool IsPacked() const           { return X != 0xFFFF; }\n};\n\n// Flags for ImFontAtlas build\nenum ImFontAtlasFlags_\n{\n    ImFontAtlasFlags_None               = 0,\n    ImFontAtlasFlags_NoPowerOfTwoHeight = 1 << 0,   // Don't round the height to next power of two\n    ImFontAtlasFlags_NoMouseCursors     = 1 << 1,   // Don't build software mouse cursors into the atlas (save a little texture memory)\n    ImFontAtlasFlags_NoBakedLines       = 1 << 2,   // Don't build thick line textures into the atlas (save a little texture memory, allow support for point/nearest filtering). The AntiAliasedLinesUseTex features uses them, otherwise they will be rendered using polygons (more expensive for CPU/GPU).\n};\n\n// Load and rasterize multiple TTF/OTF fonts into a same texture. The font atlas will build a single texture holding:\n//  - One or more fonts.\n//  - Custom graphics data needed to render the shapes needed by Dear ImGui.\n//  - Mouse cursor shapes for software cursor rendering (unless setting 'Flags |= ImFontAtlasFlags_NoMouseCursors' in the font atlas).\n// It is the user-code responsibility to setup/build the atlas, then upload the pixel data into a texture accessible by your graphics api.\n//  - Optionally, call any of the AddFont*** functions. If you don't call any, the default font embedded in the code will be loaded for you.\n//  - Call GetTexDataAsAlpha8() or GetTexDataAsRGBA32() to build and retrieve pixels data.\n//  - Upload the pixels data into a texture within your graphics system (see imgui_impl_xxxx.cpp examples)\n//  - Call SetTexID(my_tex_id); and pass the pointer/identifier to your texture in a format natural to your graphics API.\n//    This value will be passed back to you during rendering to identify the texture. Read FAQ entry about ImTextureID for more details.\n// Common pitfalls:\n// - If you pass a 'glyph_ranges' array to AddFont*** functions, you need to make sure that your array persist up until the\n//   atlas is build (when calling GetTexData*** or Build()). We only copy the pointer, not the data.\n// - Important: By default, AddFontFromMemoryTTF() takes ownership of the data. Even though we are not writing to it, we will free the pointer on destruction.\n//   You can set font_cfg->FontDataOwnedByAtlas=false to keep ownership of your data and it won't be freed,\n// - Even though many functions are suffixed with \"TTF\", OTF data is supported just as well.\n// - This is an old API and it is currently awkward for those and various other reasons! We will address them in the future!\nstruct ImFontAtlas\n{\n    IMGUI_API ImFontAtlas();\n    IMGUI_API ~ImFontAtlas();\n    IMGUI_API ImFont*           AddFont(const ImFontConfig* font_cfg);\n    IMGUI_API ImFont*           AddFontDefault(const ImFontConfig* font_cfg = NULL);\n    IMGUI_API ImFont*           AddFontFromFileTTF(const char* filename, float size_pixels, const ImFontConfig* font_cfg = NULL, const ImWchar* glyph_ranges = NULL);\n    IMGUI_API ImFont*           AddFontFromMemoryTTF(void* font_data, int font_data_size, float size_pixels, const ImFontConfig* font_cfg = NULL, const ImWchar* glyph_ranges = NULL); // Note: Transfer ownership of 'ttf_data' to ImFontAtlas! Will be deleted after destruction of the atlas. Set font_cfg->FontDataOwnedByAtlas=false to keep ownership of your data and it won't be freed.\n    IMGUI_API ImFont*           AddFontFromMemoryCompressedTTF(const void* compressed_font_data, int compressed_font_data_size, float size_pixels, const ImFontConfig* font_cfg = NULL, const ImWchar* glyph_ranges = NULL); // 'compressed_font_data' still owned by caller. Compress with binary_to_compressed_c.cpp.\n    IMGUI_API ImFont*           AddFontFromMemoryCompressedBase85TTF(const char* compressed_font_data_base85, float size_pixels, const ImFontConfig* font_cfg = NULL, const ImWchar* glyph_ranges = NULL);              // 'compressed_font_data_base85' still owned by caller. Compress with binary_to_compressed_c.cpp with -base85 parameter.\n    IMGUI_API void              ClearInputData();           // Clear input data (all ImFontConfig structures including sizes, TTF data, glyph ranges, etc.) = all the data used to build the texture and fonts.\n    IMGUI_API void              ClearFonts();               // Clear input+output font data (same as ClearInputData() + glyphs storage, UV coordinates).\n    IMGUI_API void              ClearTexData();             // Clear output texture data (CPU side). Saves RAM once the texture has been copied to graphics memory.\n    IMGUI_API void              Clear();                    // Clear all input and output.\n\n    // Build atlas, retrieve pixel data.\n    // User is in charge of copying the pixels into graphics memory (e.g. create a texture with your engine). Then store your texture handle with SetTexID().\n    // The pitch is always = Width * BytesPerPixels (1 or 4)\n    // Building in RGBA32 format is provided for convenience and compatibility, but note that unless you manually manipulate or copy color data into\n    // the texture (e.g. when using the AddCustomRect*** api), then the RGB pixels emitted will always be white (~75% of memory/bandwidth waste.\n    IMGUI_API bool              Build();                    // Build pixels data. This is called automatically for you by the GetTexData*** functions.\n    IMGUI_API void              GetTexDataAsAlpha8(unsigned char** out_pixels, int* out_width, int* out_height, int* out_bytes_per_pixel = NULL);  // 1 byte per-pixel\n    IMGUI_API void              GetTexDataAsRGBA32(unsigned char** out_pixels, int* out_width, int* out_height, int* out_bytes_per_pixel = NULL);  // 4 bytes-per-pixel\n    bool                        IsBuilt() const             { return Fonts.Size > 0 && TexReady; } // Bit ambiguous: used to detect when user didn't build texture but effectively we should check TexID != 0 except that would be backend dependent...\n    void                        SetTexID(ImTextureID id)    { TexID = id; }\n\n    //-------------------------------------------\n    // Glyph Ranges\n    //-------------------------------------------\n\n    // Helpers to retrieve list of common Unicode ranges (2 value per range, values are inclusive, zero-terminated list)\n    // NB: Make sure that your string are UTF-8 and NOT in your local code page.\n    // Read https://github.com/ocornut/imgui/blob/master/docs/FONTS.md/#about-utf-8-encoding for details.\n    // NB: Consider using ImFontGlyphRangesBuilder to build glyph ranges from textual data.\n    IMGUI_API const ImWchar*    GetGlyphRangesDefault();                // Basic Latin, Extended Latin\n    IMGUI_API const ImWchar*    GetGlyphRangesGreek();                  // Default + Greek and Coptic\n    IMGUI_API const ImWchar*    GetGlyphRangesKorean();                 // Default + Korean characters\n    IMGUI_API const ImWchar*    GetGlyphRangesJapanese();               // Default + Hiragana, Katakana, Half-Width, Selection of 2999 Ideographs\n    IMGUI_API const ImWchar*    GetGlyphRangesChineseFull();            // Default + Half-Width + Japanese Hiragana/Katakana + full set of about 21000 CJK Unified Ideographs\n    IMGUI_API const ImWchar*    GetGlyphRangesChineseSimplifiedCommon();// Default + Half-Width + Japanese Hiragana/Katakana + set of 2500 CJK Unified Ideographs for common simplified Chinese\n    IMGUI_API const ImWchar*    GetGlyphRangesCyrillic();               // Default + about 400 Cyrillic characters\n    IMGUI_API const ImWchar*    GetGlyphRangesThai();                   // Default + Thai characters\n    IMGUI_API const ImWchar*    GetGlyphRangesVietnamese();             // Default + Vietnamese characters\n\n    //-------------------------------------------\n    // [ALPHA] Custom Rectangles/Glyphs API\n    //-------------------------------------------\n\n    // You can request arbitrary rectangles to be packed into the atlas, for your own purposes.\n    // - After calling Build(), you can query the rectangle position and render your pixels.\n    // - If you render colored output, set 'atlas->TexPixelsUseColors = true' as this may help some backends decide of preferred texture format.\n    // - You can also request your rectangles to be mapped as font glyph (given a font + Unicode point),\n    //   so you can render e.g. custom colorful icons and use them as regular glyphs.\n    // - Read docs/FONTS.md for more details about using colorful icons.\n    // - Note: this API may be redesigned later in order to support multi-monitor varying DPI settings.\n    IMGUI_API int               AddCustomRectRegular(int width, int height);\n    IMGUI_API int               AddCustomRectFontGlyph(ImFont* font, ImWchar id, int width, int height, float advance_x, const ImVec2& offset = ImVec2(0, 0));\n    ImFontAtlasCustomRect*      GetCustomRectByIndex(int index) { IM_ASSERT(index >= 0); return &CustomRects[index]; }\n\n    // [Internal]\n    IMGUI_API void              CalcCustomRectUV(const ImFontAtlasCustomRect* rect, ImVec2* out_uv_min, ImVec2* out_uv_max) const;\n\n    //-------------------------------------------\n    // Members\n    //-------------------------------------------\n\n    // Input\n    ImFontAtlasFlags            Flags;              // Build flags (see ImFontAtlasFlags_)\n    ImTextureID                 TexID;              // User data to refer to the texture once it has been uploaded to user's graphic systems. It is passed back to you during rendering via the ImDrawCmd structure.\n    int                         TexDesiredWidth;    // Texture width desired by user before Build(). Must be a power-of-two. If have many glyphs your graphics API have texture size restrictions you may want to increase texture width to decrease height.\n    int                         TexGlyphPadding;    // FIXME: Should be called \"TexPackPadding\". Padding between glyphs within texture in pixels. Defaults to 1. If your rendering method doesn't rely on bilinear filtering you may set this to 0 (will also need to set AntiAliasedLinesUseTex = false).\n    void*                       UserData;           // Store your own atlas related user-data (if e.g. you have multiple font atlas).\n\n    // [Internal]\n    // NB: Access texture data via GetTexData*() calls! Which will setup a default font for you.\n    bool                        Locked;             // Marked as Locked by ImGui::NewFrame() so attempt to modify the atlas will assert.\n    bool                        TexReady;           // Set when texture was built matching current font input\n    bool                        TexPixelsUseColors; // Tell whether our texture data is known to use colors (rather than just alpha channel), in order to help backend select a format.\n    unsigned char*              TexPixelsAlpha8;    // 1 component per pixel, each component is unsigned 8-bit. Total size = TexWidth * TexHeight\n    unsigned int*               TexPixelsRGBA32;    // 4 component per pixel, each component is unsigned 8-bit. Total size = TexWidth * TexHeight * 4\n    int                         TexWidth;           // Texture width calculated during Build().\n    int                         TexHeight;          // Texture height calculated during Build().\n    ImVec2                      TexUvScale;         // = (1.0f/TexWidth, 1.0f/TexHeight)\n    ImVec2                      TexUvWhitePixel;    // Texture coordinates to a white pixel\n    ImVector<ImFont*>           Fonts;              // Hold all the fonts returned by AddFont*. Fonts[0] is the default font upon calling ImGui::NewFrame(), use ImGui::PushFont()/PopFont() to change the current font.\n    ImVector<ImFontAtlasCustomRect> CustomRects;    // Rectangles for packing custom texture data into the atlas.\n    ImVector<ImFontConfig>      Sources;            // Source/configuration data\n    ImVec4                      TexUvLines[IM_DRAWLIST_TEX_LINES_WIDTH_MAX + 1];  // UVs for baked anti-aliased lines\n\n    // [Internal] Font builder\n    const ImFontBuilderIO*      FontBuilderIO;      // Opaque interface to a font builder (default to stb_truetype, can be changed to use FreeType by defining IMGUI_ENABLE_FREETYPE).\n    unsigned int                FontBuilderFlags;   // Shared flags (for all fonts) for custom font builder. THIS IS BUILD IMPLEMENTATION DEPENDENT. Per-font override is also available in ImFontConfig.\n\n    // [Internal] Packing data\n    int                         PackIdMouseCursors; // Custom texture rectangle ID for white pixel and mouse cursors\n    int                         PackIdLines;        // Custom texture rectangle ID for baked anti-aliased lines\n\n    // [Obsolete]\n    //typedef ImFontAtlasCustomRect    CustomRect;              // OBSOLETED in 1.72+\n    //typedef ImFontGlyphRangesBuilder GlyphRangesBuilder;      // OBSOLETED in 1.67+\n};\n\n// Font runtime data and rendering\n// ImFontAtlas automatically loads a default embedded font for you when you call GetTexDataAsAlpha8() or GetTexDataAsRGBA32().\nstruct ImFont\n{\n    // [Internal] Members: Hot ~20/24 bytes (for CalcTextSize)\n    ImVector<float>             IndexAdvanceX;      // 12-16 // out // Sparse. Glyphs->AdvanceX in a directly indexable way (cache-friendly for CalcTextSize functions which only this info, and are often bottleneck in large UI).\n    float                       FallbackAdvanceX;   // 4     // out // = FallbackGlyph->AdvanceX\n    float                       FontSize;           // 4     // in  // Height of characters/line, set during loading (don't change after loading)\n\n    // [Internal] Members: Hot ~28/40 bytes (for RenderText loop)\n    ImVector<ImU16>             IndexLookup;        // 12-16 // out // Sparse. Index glyphs by Unicode code-point.\n    ImVector<ImFontGlyph>       Glyphs;             // 12-16 // out // All glyphs.\n    ImFontGlyph*                FallbackGlyph;      // 4-8   // out // = FindGlyph(FontFallbackChar)\n\n    // [Internal] Members: Cold ~32/40 bytes\n    // Conceptually Sources[] is the list of font sources merged to create this font.\n    ImFontAtlas*                ContainerAtlas;     // 4-8   // out // What we has been loaded into\n    ImFontConfig*               Sources;            // 4-8   // in  // Pointer within ContainerAtlas->Sources[], to SourcesCount instances\n    short                       SourcesCount;       // 2     // in  // Number of ImFontConfig involved in creating this font. Usually 1, or >1 when merging multiple font sources into one ImFont.\n    short                       EllipsisCharCount;  // 1     // out // 1 or 3\n    ImWchar                     EllipsisChar;       // 2-4   // out // Character used for ellipsis rendering ('...').\n    ImWchar                     FallbackChar;       // 2-4   // out // Character used if a glyph isn't found (U+FFFD, '?')\n    float                       EllipsisWidth;      // 4     // out // Total ellipsis Width\n    float                       EllipsisCharStep;   // 4     // out // Step between characters when EllipsisCount > 0\n    float                       Scale;              // 4     // in  // Base font scale (1.0f), multiplied by the per-window font scale which you can adjust with SetWindowFontScale()\n    float                       Ascent, Descent;    // 4+4   // out // Ascent: distance from top to bottom of e.g. 'A' [0..FontSize] (unscaled)\n    int                         MetricsTotalSurface;// 4     // out // Total surface in pixels to get an idea of the font rasterization/texture cost (not exact, we approximate the cost of padding between glyphs)\n    bool                        DirtyLookupTables;  // 1     // out //\n    ImU8                        Used8kPagesMap[(IM_UNICODE_CODEPOINT_MAX+1)/8192/8]; // 1 bytes if ImWchar=ImWchar16, 16 bytes if ImWchar==ImWchar32. Store 1-bit for each block of 4K codepoints that has one active glyph. This is mainly used to facilitate iterations across all used codepoints.\n\n    // Methods\n    IMGUI_API ImFont();\n    IMGUI_API ~ImFont();\n    IMGUI_API ImFontGlyph*      FindGlyph(ImWchar c);\n    IMGUI_API ImFontGlyph*      FindGlyphNoFallback(ImWchar c);\n    float                       GetCharAdvance(ImWchar c)       { return ((int)c < IndexAdvanceX.Size) ? IndexAdvanceX[(int)c] : FallbackAdvanceX; }\n    bool                        IsLoaded() const                { return ContainerAtlas != NULL; }\n    const char*                 GetDebugName() const            { return Sources ? Sources->Name : \"<unknown>\"; }\n\n    // [Internal] Don't use!\n    // 'max_width' stops rendering after a certain width (could be turned into a 2d size). FLT_MAX to disable.\n    // 'wrap_width' enable automatic word-wrapping across multiple lines to fit into given width. 0.0f to disable.\n    IMGUI_API ImVec2            CalcTextSizeA(float size, float max_width, float wrap_width, const char* text_begin, const char* text_end = NULL, const char** remaining = NULL); // utf8\n    IMGUI_API const char*       CalcWordWrapPositionA(float scale, const char* text, const char* text_end, float wrap_width);\n    IMGUI_API void              RenderChar(ImDrawList* draw_list, float size, const ImVec2& pos, ImU32 col, ImWchar c);\n    IMGUI_API void              RenderText(ImDrawList* draw_list, float size, const ImVec2& pos, ImU32 col, const ImVec4& clip_rect, const char* text_begin, const char* text_end, float wrap_width = 0.0f, bool cpu_fine_clip = false);\n\n    // [Internal] Don't use!\n    IMGUI_API void              BuildLookupTable();\n    IMGUI_API void              ClearOutputData();\n    IMGUI_API void              GrowIndex(int new_size);\n    IMGUI_API void              AddGlyph(const ImFontConfig* src_cfg, ImWchar c, float x0, float y0, float x1, float y1, float u0, float v0, float u1, float v1, float advance_x);\n    IMGUI_API void              AddRemapChar(ImWchar dst, ImWchar src, bool overwrite_dst = true); // Makes 'dst' character/glyph points to 'src' character/glyph. Currently needs to be called AFTER fonts have been built.\n    IMGUI_API bool              IsGlyphRangeUnused(unsigned int c_begin, unsigned int c_last);\n};\n\n//-----------------------------------------------------------------------------\n// [SECTION] Viewports\n//-----------------------------------------------------------------------------\n\n// Flags stored in ImGuiViewport::Flags, giving indications to the platform backends.\nenum ImGuiViewportFlags_\n{\n    ImGuiViewportFlags_None                     = 0,\n    ImGuiViewportFlags_IsPlatformWindow         = 1 << 0,   // Represent a Platform Window\n    ImGuiViewportFlags_IsPlatformMonitor        = 1 << 1,   // Represent a Platform Monitor (unused yet)\n    ImGuiViewportFlags_OwnedByApp               = 1 << 2,   // Platform Window: Is created/managed by the application (rather than a dear imgui backend)\n};\n\n// - Currently represents the Platform Window created by the application which is hosting our Dear ImGui windows.\n// - In 'docking' branch with multi-viewport enabled, we extend this concept to have multiple active viewports.\n// - In the future we will extend this concept further to also represent Platform Monitor and support a \"no main platform window\" operation mode.\n// - About Main Area vs Work Area:\n//   - Main Area = entire viewport.\n//   - Work Area = entire viewport minus sections used by main menu bars (for platform windows), or by task bar (for platform monitor).\n//   - Windows are generally trying to stay within the Work Area of their host viewport.\nstruct ImGuiViewport\n{\n    ImGuiID             ID;                     // Unique identifier for the viewport\n    ImGuiViewportFlags  Flags;                  // See ImGuiViewportFlags_\n    ImVec2              Pos;                    // Main Area: Position of the viewport (Dear ImGui coordinates are the same as OS desktop/native coordinates)\n    ImVec2              Size;                   // Main Area: Size of the viewport.\n    ImVec2              WorkPos;                // Work Area: Position of the viewport minus task bars, menus bars, status bars (>= Pos)\n    ImVec2              WorkSize;               // Work Area: Size of the viewport minus task bars, menu bars, status bars (<= Size)\n\n    // Platform/Backend Dependent Data\n    void*               PlatformHandle;         // void* to hold higher-level, platform window handle (e.g. HWND, GLFWWindow*, SDL_Window*)\n    void*               PlatformHandleRaw;      // void* to hold lower-level, platform-native window handle (under Win32 this is expected to be a HWND, unused for other platforms)\n\n    ImGuiViewport()     { memset(this, 0, sizeof(*this)); }\n\n    // Helpers\n    ImVec2              GetCenter() const       { return ImVec2(Pos.x + Size.x * 0.5f, Pos.y + Size.y * 0.5f); }\n    ImVec2              GetWorkCenter() const   { return ImVec2(WorkPos.x + WorkSize.x * 0.5f, WorkPos.y + WorkSize.y * 0.5f); }\n};\n\n//-----------------------------------------------------------------------------\n// [SECTION] Platform Dependent Interfaces\n//-----------------------------------------------------------------------------\n\n// Access via ImGui::GetPlatformIO()\nstruct ImGuiPlatformIO\n{\n    IMGUI_API ImGuiPlatformIO();\n\n    //------------------------------------------------------------------\n    // Input - Interface with OS and Platform backend (most common stuff)\n    //------------------------------------------------------------------\n\n    // Optional: Access OS clipboard\n    // (default to use native Win32 clipboard on Windows, otherwise uses a private clipboard. Override to access OS clipboard on other architectures)\n    const char* (*Platform_GetClipboardTextFn)(ImGuiContext* ctx);\n    void        (*Platform_SetClipboardTextFn)(ImGuiContext* ctx, const char* text);\n    void*       Platform_ClipboardUserData;\n\n    // Optional: Open link/folder/file in OS Shell\n    // (default to use ShellExecuteW() on Windows, system() on Linux/Mac)\n    bool        (*Platform_OpenInShellFn)(ImGuiContext* ctx, const char* path);\n    void*       Platform_OpenInShellUserData;\n\n    // Optional: Notify OS Input Method Editor of the screen position of your cursor for text input position (e.g. when using Japanese/Chinese IME on Windows)\n    // (default to use native imm32 api on Windows)\n    void        (*Platform_SetImeDataFn)(ImGuiContext* ctx, ImGuiViewport* viewport, ImGuiPlatformImeData* data);\n    void*       Platform_ImeUserData;\n    //void      (*SetPlatformImeDataFn)(ImGuiViewport* viewport, ImGuiPlatformImeData* data); // [Renamed to platform_io.PlatformSetImeDataFn in 1.91.1]\n\n    // Optional: Platform locale\n    // [Experimental] Configure decimal point e.g. '.' or ',' useful for some languages (e.g. German), generally pulled from *localeconv()->decimal_point\n    ImWchar     Platform_LocaleDecimalPoint;     // '.'\n\n    //------------------------------------------------------------------\n    // Input - Interface with Renderer Backend\n    //------------------------------------------------------------------\n\n    // Written by some backends during ImGui_ImplXXXX_RenderDrawData() call to point backend_specific ImGui_ImplXXXX_RenderState* structure.\n    void*       Renderer_RenderState;\n};\n\n// (Optional) Support for IME (Input Method Editor) via the platform_io.Platform_SetImeDataFn() function.\nstruct ImGuiPlatformImeData\n{\n    bool    WantVisible;        // A widget wants the IME to be visible\n    ImVec2  InputPos;           // Position of the input cursor\n    float   InputLineHeight;    // Line height\n\n    ImGuiPlatformImeData() { memset(this, 0, sizeof(*this)); }\n};\n\n//-----------------------------------------------------------------------------\n// [SECTION] Obsolete functions and types\n// (Will be removed! Read 'API BREAKING CHANGES' section in imgui.cpp for details)\n// Please keep your copy of dear imgui up to date! Occasionally set '#define IMGUI_DISABLE_OBSOLETE_FUNCTIONS' in imconfig.h to stay ahead.\n//-----------------------------------------------------------------------------\n\n#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS\nnamespace ImGui\n{\n    // OBSOLETED in 1.91.9 (from February 2025)\n    IMGUI_API void      Image(ImTextureID user_texture_id, const ImVec2& image_size, const ImVec2& uv0, const ImVec2& uv1, const ImVec4& tint_col, const ImVec4& border_col); // <-- border_col was removed in favor of ImGuiCol_ImageBorder.\n    // OBSOLETED in 1.91.0 (from July 2024)\n    static inline void  PushButtonRepeat(bool repeat)                           { PushItemFlag(ImGuiItemFlags_ButtonRepeat, repeat); }\n    static inline void  PopButtonRepeat()                                       { PopItemFlag(); }\n    static inline void  PushTabStop(bool tab_stop)                              { PushItemFlag(ImGuiItemFlags_NoTabStop, !tab_stop); }\n    static inline void  PopTabStop()                                            { PopItemFlag(); }\n    IMGUI_API ImVec2    GetContentRegionMax();                                  // Content boundaries max (e.g. window boundaries including scrolling, or current column boundaries). You should never need this. Always use GetCursorScreenPos() and GetContentRegionAvail()!\n    IMGUI_API ImVec2    GetWindowContentRegionMin();                            // Content boundaries min for the window (roughly (0,0)-Scroll), in window-local coordinates. You should never need this. Always use GetCursorScreenPos() and GetContentRegionAvail()!\n    IMGUI_API ImVec2    GetWindowContentRegionMax();                            // Content boundaries max for the window (roughly (0,0)+Size-Scroll), in window-local coordinates. You should never need this. Always use GetCursorScreenPos() and GetContentRegionAvail()!\n    // OBSOLETED in 1.90.0 (from September 2023)\n    static inline bool  BeginChildFrame(ImGuiID id, const ImVec2& size, ImGuiWindowFlags window_flags = 0)  { return BeginChild(id, size, ImGuiChildFlags_FrameStyle, window_flags); }\n    static inline void  EndChildFrame()                                                                     { EndChild(); }\n    //static inline bool BeginChild(const char* str_id, const ImVec2& size_arg, bool borders, ImGuiWindowFlags window_flags){ return BeginChild(str_id, size_arg, borders ? ImGuiChildFlags_Borders : ImGuiChildFlags_None, window_flags); } // Unnecessary as true == ImGuiChildFlags_Borders\n    //static inline bool BeginChild(ImGuiID id, const ImVec2& size_arg, bool borders, ImGuiWindowFlags window_flags)        { return BeginChild(id, size_arg, borders ? ImGuiChildFlags_Borders : ImGuiChildFlags_None, window_flags);     } // Unnecessary as true == ImGuiChildFlags_Borders\n    static inline void  ShowStackToolWindow(bool* p_open = NULL)                { ShowIDStackToolWindow(p_open); }\n    IMGUI_API bool      Combo(const char* label, int* current_item, bool (*old_callback)(void* user_data, int idx, const char** out_text), void* user_data, int items_count, int popup_max_height_in_items = -1);\n    IMGUI_API bool      ListBox(const char* label, int* current_item, bool (*old_callback)(void* user_data, int idx, const char** out_text), void* user_data, int items_count, int height_in_items = -1);\n    // OBSOLETED in 1.89.7 (from June 2023)\n    IMGUI_API void      SetItemAllowOverlap();                                  // Use SetNextItemAllowOverlap() before item.\n    // OBSOLETED in 1.89.4 (from March 2023)\n    static inline void  PushAllowKeyboardFocus(bool tab_stop)                   { PushItemFlag(ImGuiItemFlags_NoTabStop, !tab_stop); }\n    static inline void  PopAllowKeyboardFocus()                                 { PopItemFlag(); }\n\n    // Some of the older obsolete names along with their replacement (commented out so they are not reported in IDE)\n    //-- OBSOLETED in 1.89 (from August 2022)\n    //IMGUI_API bool      ImageButton(ImTextureID user_texture_id, const ImVec2& size, const ImVec2& uv0 = ImVec2(0, 0), const ImVec2& uv1 = ImVec2(1, 1), int frame_padding = -1, const ImVec4& bg_col = ImVec4(0, 0, 0, 0), const ImVec4& tint_col = ImVec4(1, 1, 1, 1)); // --> Use new ImageButton() signature (explicit item id, regular FramePadding). Refer to code in 1.91 if you want to grab a copy of this version.\n    //-- OBSOLETED in 1.88 (from May 2022)\n    //static inline void  CaptureKeyboardFromApp(bool want_capture_keyboard = true)                   { SetNextFrameWantCaptureKeyboard(want_capture_keyboard); } // Renamed as name was misleading + removed default value.\n    //static inline void  CaptureMouseFromApp(bool want_capture_mouse = true)                         { SetNextFrameWantCaptureMouse(want_capture_mouse); }       // Renamed as name was misleading + removed default value.\n    //-- OBSOLETED in 1.87 (from February 2022, more formally obsoleted April 2024)\n    //IMGUI_API ImGuiKey  GetKeyIndex(ImGuiKey key);                                                  { IM_ASSERT(key >= ImGuiKey_NamedKey_BEGIN && key < ImGuiKey_NamedKey_END); const ImGuiKeyData* key_data = GetKeyData(key); return (ImGuiKey)(key_data - g.IO.KeysData); } // Map ImGuiKey_* values into legacy native key index. == io.KeyMap[key]. When using a 1.87+ backend using io.AddKeyEvent(), calling GetKeyIndex() with ANY ImGuiKey_XXXX values will return the same value!\n    //static inline ImGuiKey GetKeyIndex(ImGuiKey key)                                                { IM_ASSERT(key >= ImGuiKey_NamedKey_BEGIN && key < ImGuiKey_NamedKey_END); return key; }\n    //-- OBSOLETED in 1.86 (from November 2021)\n    //IMGUI_API void      CalcListClipping(int items_count, float items_height, int* out_items_display_start, int* out_items_display_end); // Code removed, see 1.90 for last version of the code. Calculate range of visible items for large list of evenly sized items. Prefer using ImGuiListClipper.\n    //-- OBSOLETED in 1.85 (from August 2021)\n    //static inline float GetWindowContentRegionWidth()                                               { return GetWindowContentRegionMax().x - GetWindowContentRegionMin().x; }\n    //-- OBSOLETED in 1.81 (from February 2021)\n    //static inline bool  ListBoxHeader(const char* label, const ImVec2& size = ImVec2(0, 0))         { return BeginListBox(label, size); }\n    //static inline bool  ListBoxHeader(const char* label, int items_count, int height_in_items = -1) { float height = GetTextLineHeightWithSpacing() * ((height_in_items < 0 ? ImMin(items_count, 7) : height_in_items) + 0.25f) + GetStyle().FramePadding.y * 2.0f; return BeginListBox(label, ImVec2(0.0f, height)); } // Helper to calculate size from items_count and height_in_items\n    //static inline void  ListBoxFooter()                                                             { EndListBox(); }\n    //-- OBSOLETED in 1.79 (from August 2020)\n    //static inline void  OpenPopupContextItem(const char* str_id = NULL, ImGuiMouseButton mb = 1)    { OpenPopupOnItemClick(str_id, mb); } // Bool return value removed. Use IsWindowAppearing() in BeginPopup() instead. Renamed in 1.77, renamed back in 1.79. Sorry!\n    //-- OBSOLETED in 1.78 (from June 2020): Old drag/sliders functions that took a 'float power > 1.0f' argument instead of ImGuiSliderFlags_Logarithmic. See github.com/ocornut/imgui/issues/3361 for details.\n    //IMGUI_API bool      DragScalar(const char* label, ImGuiDataType data_type, void* p_data, float v_speed, const void* p_min, const void* p_max, const char* format, float power = 1.0f)                                                            // OBSOLETED in 1.78 (from June 2020)\n    //IMGUI_API bool      DragScalarN(const char* label, ImGuiDataType data_type, void* p_data, int components, float v_speed, const void* p_min, const void* p_max, const char* format, float power = 1.0f);                                          // OBSOLETED in 1.78 (from June 2020)\n    //IMGUI_API bool      SliderScalar(const char* label, ImGuiDataType data_type, void* p_data, const void* p_min, const void* p_max, const char* format, float power = 1.0f);                                                                        // OBSOLETED in 1.78 (from June 2020)\n    //IMGUI_API bool      SliderScalarN(const char* label, ImGuiDataType data_type, void* p_data, int components, const void* p_min, const void* p_max, const char* format, float power = 1.0f);                                                       // OBSOLETED in 1.78 (from June 2020)\n    //static inline bool  DragFloat(const char* label, float* v, float v_speed, float v_min, float v_max, const char* format, float power = 1.0f)    { return DragScalar(label, ImGuiDataType_Float, v, v_speed, &v_min, &v_max, format, power); }     // OBSOLETED in 1.78 (from June 2020)\n    //static inline bool  DragFloat2(const char* label, float v[2], float v_speed, float v_min, float v_max, const char* format, float power = 1.0f) { return DragScalarN(label, ImGuiDataType_Float, v, 2, v_speed, &v_min, &v_max, format, power); } // OBSOLETED in 1.78 (from June 2020)\n    //static inline bool  DragFloat3(const char* label, float v[3], float v_speed, float v_min, float v_max, const char* format, float power = 1.0f) { return DragScalarN(label, ImGuiDataType_Float, v, 3, v_speed, &v_min, &v_max, format, power); } // OBSOLETED in 1.78 (from June 2020)\n    //static inline bool  DragFloat4(const char* label, float v[4], float v_speed, float v_min, float v_max, const char* format, float power = 1.0f) { return DragScalarN(label, ImGuiDataType_Float, v, 4, v_speed, &v_min, &v_max, format, power); } // OBSOLETED in 1.78 (from June 2020)\n    //static inline bool  SliderFloat(const char* label, float* v, float v_min, float v_max, const char* format, float power = 1.0f)                 { return SliderScalar(label, ImGuiDataType_Float, v, &v_min, &v_max, format, power); }            // OBSOLETED in 1.78 (from June 2020)\n    //static inline bool  SliderFloat2(const char* label, float v[2], float v_min, float v_max, const char* format, float power = 1.0f)              { return SliderScalarN(label, ImGuiDataType_Float, v, 2, &v_min, &v_max, format, power); }        // OBSOLETED in 1.78 (from June 2020)\n    //static inline bool  SliderFloat3(const char* label, float v[3], float v_min, float v_max, const char* format, float power = 1.0f)              { return SliderScalarN(label, ImGuiDataType_Float, v, 3, &v_min, &v_max, format, power); }        // OBSOLETED in 1.78 (from June 2020)\n    //static inline bool  SliderFloat4(const char* label, float v[4], float v_min, float v_max, const char* format, float power = 1.0f)              { return SliderScalarN(label, ImGuiDataType_Float, v, 4, &v_min, &v_max, format, power); }        // OBSOLETED in 1.78 (from June 2020)\n    //-- OBSOLETED in 1.77 and before\n    //static inline bool  BeginPopupContextWindow(const char* str_id, ImGuiMouseButton mb, bool over_items) { return BeginPopupContextWindow(str_id, mb | (over_items ? 0 : ImGuiPopupFlags_NoOpenOverItems)); } // OBSOLETED in 1.77 (from June 2020)\n    //static inline void  TreeAdvanceToLabelPos()               { SetCursorPosX(GetCursorPosX() + GetTreeNodeToLabelSpacing()); }   // OBSOLETED in 1.72 (from July 2019)\n    //static inline void  SetNextTreeNodeOpen(bool open, ImGuiCond cond = 0) { SetNextItemOpen(open, cond); }                       // OBSOLETED in 1.71 (from June 2019)\n    //static inline float GetContentRegionAvailWidth()          { return GetContentRegionAvail().x; }                               // OBSOLETED in 1.70 (from May 2019)\n    //static inline ImDrawList* GetOverlayDrawList()            { return GetForegroundDrawList(); }                                 // OBSOLETED in 1.69 (from Mar 2019)\n    //static inline void  SetScrollHere(float ratio = 0.5f)     { SetScrollHereY(ratio); }                                          // OBSOLETED in 1.66 (from Nov 2018)\n    //static inline bool  IsItemDeactivatedAfterChange()        { return IsItemDeactivatedAfterEdit(); }                            // OBSOLETED in 1.63 (from Aug 2018)\n    //-- OBSOLETED in 1.60 and before\n    //static inline bool  IsAnyWindowFocused()                  { return IsWindowFocused(ImGuiFocusedFlags_AnyWindow); }            // OBSOLETED in 1.60 (from Apr 2018)\n    //static inline bool  IsAnyWindowHovered()                  { return IsWindowHovered(ImGuiHoveredFlags_AnyWindow); }            // OBSOLETED in 1.60 (between Dec 2017 and Apr 2018)\n    //static inline void  ShowTestWindow()                      { return ShowDemoWindow(); }                                        // OBSOLETED in 1.53 (between Oct 2017 and Dec 2017)\n    //static inline bool  IsRootWindowFocused()                 { return IsWindowFocused(ImGuiFocusedFlags_RootWindow); }           // OBSOLETED in 1.53 (between Oct 2017 and Dec 2017)\n    //static inline bool  IsRootWindowOrAnyChildFocused()       { return IsWindowFocused(ImGuiFocusedFlags_RootAndChildWindows); }  // OBSOLETED in 1.53 (between Oct 2017 and Dec 2017)\n    //static inline void  SetNextWindowContentWidth(float w)    { SetNextWindowContentSize(ImVec2(w, 0.0f)); }                      // OBSOLETED in 1.53 (between Oct 2017 and Dec 2017)\n    //static inline float GetItemsLineHeightWithSpacing()       { return GetFrameHeightWithSpacing(); }                             // OBSOLETED in 1.53 (between Oct 2017 and Dec 2017)\n    //IMGUI_API bool      Begin(char* name, bool* p_open, ImVec2 size_first_use, float bg_alpha = -1.0f, ImGuiWindowFlags flags=0); // OBSOLETED in 1.52 (between Aug 2017 and Oct 2017): Equivalent of using SetNextWindowSize(size, ImGuiCond_FirstUseEver) and SetNextWindowBgAlpha().\n    //static inline bool  IsRootWindowOrAnyChildHovered()       { return IsWindowHovered(ImGuiHoveredFlags_RootAndChildWindows); }  // OBSOLETED in 1.52 (between Aug 2017 and Oct 2017)\n    //static inline void  AlignFirstTextHeightToWidgets()       { AlignTextToFramePadding(); }                                      // OBSOLETED in 1.52 (between Aug 2017 and Oct 2017)\n    //static inline void  SetNextWindowPosCenter(ImGuiCond c=0) { SetNextWindowPos(GetMainViewport()->GetCenter(), c, ImVec2(0.5f,0.5f)); } // OBSOLETED in 1.52 (between Aug 2017 and Oct 2017)\n    //static inline bool  IsItemHoveredRect()                   { return IsItemHovered(ImGuiHoveredFlags_RectOnly); }               // OBSOLETED in 1.51 (between Jun 2017 and Aug 2017)\n    //static inline bool  IsPosHoveringAnyWindow(const ImVec2&) { IM_ASSERT(0); return false; }                                     // OBSOLETED in 1.51 (between Jun 2017 and Aug 2017): This was misleading and partly broken. You probably want to use the io.WantCaptureMouse flag instead.\n    //static inline bool  IsMouseHoveringAnyWindow()            { return IsWindowHovered(ImGuiHoveredFlags_AnyWindow); }            // OBSOLETED in 1.51 (between Jun 2017 and Aug 2017)\n    //static inline bool  IsMouseHoveringWindow()               { return IsWindowHovered(ImGuiHoveredFlags_AllowWhenBlockedByPopup | ImGuiHoveredFlags_AllowWhenBlockedByActiveItem); }       // OBSOLETED in 1.51 (between Jun 2017 and Aug 2017)\n    //-- OBSOLETED in 1.50 and before\n    //static inline bool  CollapsingHeader(char* label, const char* str_id, bool framed = true, bool default_open = false) { return CollapsingHeader(label, (default_open ? (1 << 5) : 0)); } // OBSOLETED in 1.49\n    //static inline ImFont*GetWindowFont()                      { return GetFont(); }                                               // OBSOLETED in 1.48\n    //static inline float GetWindowFontSize()                   { return GetFontSize(); }                                           // OBSOLETED in 1.48\n    //static inline void  SetScrollPosHere()                    { SetScrollHere(); }                                                // OBSOLETED in 1.42\n}\n\n//-- OBSOLETED in 1.82 (from Mars 2021): flags for AddRect(), AddRectFilled(), AddImageRounded(), PathRect()\n//typedef ImDrawFlags ImDrawCornerFlags;\n//enum ImDrawCornerFlags_\n//{\n//    ImDrawCornerFlags_None      = ImDrawFlags_RoundCornersNone,         // Was == 0 prior to 1.82, this is now == ImDrawFlags_RoundCornersNone which is != 0 and not implicit\n//    ImDrawCornerFlags_TopLeft   = ImDrawFlags_RoundCornersTopLeft,      // Was == 0x01 (1 << 0) prior to 1.82. Order matches ImDrawFlags_NoRoundCorner* flag (we exploit this internally).\n//    ImDrawCornerFlags_TopRight  = ImDrawFlags_RoundCornersTopRight,     // Was == 0x02 (1 << 1) prior to 1.82.\n//    ImDrawCornerFlags_BotLeft   = ImDrawFlags_RoundCornersBottomLeft,   // Was == 0x04 (1 << 2) prior to 1.82.\n//    ImDrawCornerFlags_BotRight  = ImDrawFlags_RoundCornersBottomRight,  // Was == 0x08 (1 << 3) prior to 1.82.\n//    ImDrawCornerFlags_All       = ImDrawFlags_RoundCornersAll,          // Was == 0x0F prior to 1.82\n//    ImDrawCornerFlags_Top       = ImDrawCornerFlags_TopLeft | ImDrawCornerFlags_TopRight,\n//    ImDrawCornerFlags_Bot       = ImDrawCornerFlags_BotLeft | ImDrawCornerFlags_BotRight,\n//    ImDrawCornerFlags_Left      = ImDrawCornerFlags_TopLeft | ImDrawCornerFlags_BotLeft,\n//    ImDrawCornerFlags_Right     = ImDrawCornerFlags_TopRight | ImDrawCornerFlags_BotRight,\n//};\n\n// RENAMED and MERGED both ImGuiKey_ModXXX and ImGuiModFlags_XXX into ImGuiMod_XXX (from September 2022)\n// RENAMED ImGuiKeyModFlags -> ImGuiModFlags in 1.88 (from April 2022). Exceptionally commented out ahead of obscolescence schedule to reduce confusion and because they were not meant to be used in the first place.\n//typedef ImGuiKeyChord ImGuiModFlags;      // == int. We generally use ImGuiKeyChord to mean \"a ImGuiKey or-ed with any number of ImGuiMod_XXX value\", so you may store mods in there.\n//enum ImGuiModFlags_ { ImGuiModFlags_None = 0, ImGuiModFlags_Ctrl = ImGuiMod_Ctrl, ImGuiModFlags_Shift = ImGuiMod_Shift, ImGuiModFlags_Alt = ImGuiMod_Alt, ImGuiModFlags_Super = ImGuiMod_Super };\n//typedef ImGuiKeyChord ImGuiKeyModFlags; // == int\n//enum ImGuiKeyModFlags_ { ImGuiKeyModFlags_None = 0, ImGuiKeyModFlags_Ctrl = ImGuiMod_Ctrl, ImGuiKeyModFlags_Shift = ImGuiMod_Shift, ImGuiKeyModFlags_Alt = ImGuiMod_Alt, ImGuiKeyModFlags_Super = ImGuiMod_Super };\n\n#define IM_OFFSETOF(_TYPE,_MEMBER)  offsetof(_TYPE, _MEMBER)    // OBSOLETED IN 1.90 (now using C++11 standard version)\n\n#endif // #ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS\n\n// RENAMED IMGUI_DISABLE_METRICS_WINDOW > IMGUI_DISABLE_DEBUG_TOOLS in 1.88 (from June 2022)\n#ifdef IMGUI_DISABLE_METRICS_WINDOW\n#error IMGUI_DISABLE_METRICS_WINDOW was renamed to IMGUI_DISABLE_DEBUG_TOOLS, please use new name.\n#endif\n\n//-----------------------------------------------------------------------------\n\n#if defined(__clang__)\n#pragma clang diagnostic pop\n#elif defined(__GNUC__)\n#pragma GCC diagnostic pop\n#endif\n\n#ifdef _MSC_VER\n#pragma warning (pop)\n#endif\n\n// Include imgui_user.h at the end of imgui.h\n// May be convenient for some users to only explicitly include vanilla imgui.h and have extra stuff included.\n#ifdef IMGUI_INCLUDE_IMGUI_USER_H\n#ifdef IMGUI_USER_H_FILENAME\n#include IMGUI_USER_H_FILENAME\n#else\n#include \"imgui_user.h\"\n#endif\n#endif\n\n#endif // #ifndef IMGUI_DISABLE\n"
  },
  {
    "path": "src/DesktopPlusUI/imgui/imgui_demo.cpp",
    "content": "// dear imgui, v1.91.9b\n// (demo code)\n\n// Help:\n// - Read FAQ at http://dearimgui.com/faq\n// - Call and read ImGui::ShowDemoWindow() in imgui_demo.cpp. All applications in examples/ are doing that.\n// - Need help integrating Dear ImGui in your codebase?\n//   - Read Getting Started https://github.com/ocornut/imgui/wiki/Getting-Started\n//   - Read 'Programmer guide' in imgui.cpp for notes on how to setup Dear ImGui in your codebase.\n// Read top of imgui.cpp and imgui.h for many details, documentation, comments, links.\n// Get the latest version at https://github.com/ocornut/imgui\n\n// How to easily locate code?\n// - Use Tools->Item Picker to debug break in code by clicking any widgets: https://github.com/ocornut/imgui/wiki/Debug-Tools\n// - Browse an online version the demo with code linked to hovered widgets: https://pthom.github.io/imgui_manual_online/manual/imgui_manual.html\n// - Find a visible string and search for it in the code!\n\n//---------------------------------------------------\n// PLEASE DO NOT REMOVE THIS FILE FROM YOUR PROJECT!\n//---------------------------------------------------\n// Message to the person tempted to delete this file when integrating Dear ImGui into their codebase:\n// Think again! It is the most useful reference code that you and other coders will want to refer to and call.\n// Have the ImGui::ShowDemoWindow() function wired in an always-available debug menu of your game/app!\n// Also include Metrics! ItemPicker! DebugLog! and other debug features.\n// Removing this file from your project is hindering access to documentation for everyone in your team,\n// likely leading you to poorer usage of the library.\n// Everything in this file will be stripped out by the linker if you don't call ImGui::ShowDemoWindow().\n// If you want to link core Dear ImGui in your shipped builds but want a thorough guarantee that the demo will not be\n// linked, you can setup your imconfig.h with #define IMGUI_DISABLE_DEMO_WINDOWS and those functions will be empty.\n// In another situation, whenever you have Dear ImGui available you probably want this to be available for reference.\n// Thank you,\n// -Your beloved friend, imgui_demo.cpp (which you won't delete)\n\n//--------------------------------------------\n// ABOUT THE MEANING OF THE 'static' KEYWORD:\n//--------------------------------------------\n// In this demo code, we frequently use 'static' variables inside functions.\n// A static variable persists across calls. It is essentially a global variable but declared inside the scope of the function.\n// Think of \"static int n = 0;\" as \"global int n = 0;\" !\n// We do this IN THE DEMO because we want:\n// - to gather code and data in the same place.\n// - to make the demo source code faster to read, faster to change, smaller in size.\n// - it is also a convenient way of storing simple UI related information as long as your function\n//   doesn't need to be reentrant or used in multiple threads.\n// This might be a pattern you will want to use in your code, but most of the data you would be working\n// with in a complex codebase is likely going to be stored outside your functions.\n\n//-----------------------------------------\n// ABOUT THE CODING STYLE OF OUR DEMO CODE\n//-----------------------------------------\n// The Demo code in this file is designed to be easy to copy-and-paste into your application!\n// Because of this:\n// - We never omit the ImGui:: prefix when calling functions, even though most code here is in the same namespace.\n// - We try to declare static variables in the local scope, as close as possible to the code using them.\n// - We never use any of the helpers/facilities used internally by Dear ImGui, unless available in the public API.\n// - We never use maths operators on ImVec2/ImVec4. For our other sources files we use them, and they are provided\n//   by imgui.h using the IMGUI_DEFINE_MATH_OPERATORS define. For your own sources file they are optional\n//   and require you either enable those, either provide your own via IM_VEC2_CLASS_EXTRA in imconfig.h.\n//   Because we can't assume anything about your support of maths operators, we cannot use them in imgui_demo.cpp.\n\n// Navigating this file:\n// - In Visual Studio: CTRL+comma (\"Edit.GoToAll\") can follow symbols inside comments, whereas CTRL+F12 (\"Edit.GoToImplementation\") cannot.\n// - In Visual Studio w/ Visual Assist installed: ALT+G (\"VAssistX.GoToImplementation\") can also follow symbols inside comments.\n// - In VS Code, CLion, etc.: CTRL+click can follow symbols inside comments.\n// - You can search/grep for all sections listed in the index to find the section.\n\n/*\n\nIndex of this file:\n\n// [SECTION] Forward Declarations\n// [SECTION] Helpers\n// [SECTION] Demo Window / ShowDemoWindow()\n// [SECTION] DemoWindowMenuBar()\n// [SECTION] Helpers: ExampleTreeNode, ExampleMemberInfo (for use by Property Editor & Multi-Select demos)\n// [SECTION] DemoWindowWidgetsBasic()\n// [SECTION] DemoWindowWidgetsBullets()\n// [SECTION] DemoWindowWidgetsCollapsingHeaders()\n// [SECTION] DemoWindowWidgetsComboBoxes()\n// [SECTION] DemoWindowWidgetsColorAndPickers()\n// [SECTION] DemoWindowWidgetsDataTypes()\n// [SECTION] DemoWindowWidgetsDisableBlocks()\n// [SECTION] DemoWindowWidgetsDragAndDrop()\n// [SECTION] DemoWindowWidgetsDragsAndSliders()\n// [SECTION] DemoWindowWidgetsImages()\n// [SECTION] DemoWindowWidgetsListBoxes()\n// [SECTION] DemoWindowWidgetsMultiComponents()\n// [SECTION] DemoWindowWidgetsPlotting()\n// [SECTION] DemoWindowWidgetsProgressBars()\n// [SECTION] DemoWindowWidgetsQueryingStatuses()\n// [SECTION] DemoWindowWidgetsSelectables()\n// [SECTION] DemoWindowWidgetsSelectionAndMultiSelect()\n// [SECTION] DemoWindowWidgetsTabs()\n// [SECTION] DemoWindowWidgetsText()\n// [SECTION] DemoWindowWidgetsTextFilter()\n// [SECTION] DemoWindowWidgetsTextInput()\n// [SECTION] DemoWindowWidgetsTooltips()\n// [SECTION] DemoWindowWidgetsTreeNodes()\n// [SECTION] DemoWindowWidgetsVerticalSliders()\n// [SECTION] DemoWindowWidgets()\n// [SECTION] DemoWindowLayout()\n// [SECTION] DemoWindowPopups()\n// [SECTION] DemoWindowTables()\n// [SECTION] DemoWindowInputs()\n// [SECTION] About Window / ShowAboutWindow()\n// [SECTION] Style Editor / ShowStyleEditor()\n// [SECTION] User Guide / ShowUserGuide()\n// [SECTION] Example App: Main Menu Bar / ShowExampleAppMainMenuBar()\n// [SECTION] Example App: Debug Console / ShowExampleAppConsole()\n// [SECTION] Example App: Debug Log / ShowExampleAppLog()\n// [SECTION] Example App: Simple Layout / ShowExampleAppLayout()\n// [SECTION] Example App: Property Editor / ShowExampleAppPropertyEditor()\n// [SECTION] Example App: Long Text / ShowExampleAppLongText()\n// [SECTION] Example App: Auto Resize / ShowExampleAppAutoResize()\n// [SECTION] Example App: Constrained Resize / ShowExampleAppConstrainedResize()\n// [SECTION] Example App: Simple overlay / ShowExampleAppSimpleOverlay()\n// [SECTION] Example App: Fullscreen window / ShowExampleAppFullscreen()\n// [SECTION] Example App: Manipulating window titles / ShowExampleAppWindowTitles()\n// [SECTION] Example App: Custom Rendering using ImDrawList API / ShowExampleAppCustomRendering()\n// [SECTION] Example App: Documents Handling / ShowExampleAppDocuments()\n// [SECTION] Example App: Assets Browser / ShowExampleAppAssetsBrowser()\n\n*/\n\n#if defined(_MSC_VER) && !defined(_CRT_SECURE_NO_WARNINGS)\n#define _CRT_SECURE_NO_WARNINGS\n#endif\n\n#include \"imgui.h\"\n#ifndef IMGUI_DISABLE\n\n// System includes\n#include <ctype.h>          // toupper\n#include <limits.h>         // INT_MIN, INT_MAX\n#include <math.h>           // sqrtf, powf, cosf, sinf, floorf, ceilf\n#include <stdio.h>          // vsnprintf, sscanf, printf\n#include <stdlib.h>         // NULL, malloc, free, atoi\n#include <stdint.h>         // intptr_t\n#if !defined(_MSC_VER) || _MSC_VER >= 1800\n#include <inttypes.h>       // PRId64/PRIu64, not avail in some MinGW headers.\n#endif\n#ifdef __EMSCRIPTEN__\n#include <emscripten/version.h>     // __EMSCRIPTEN_major__ etc.\n#endif\n\n// Visual Studio warnings\n#ifdef _MSC_VER\n#pragma warning (disable: 4127)     // condition expression is constant\n#pragma warning (disable: 4996)     // 'This function or variable may be unsafe': strcpy, strdup, sprintf, vsnprintf, sscanf, fopen\n#pragma warning (disable: 26451)    // [Static Analyzer] Arithmetic overflow : Using operator 'xxx' on a 4 byte value and then casting the result to an 8 byte value. Cast the value to the wider type before calling operator 'xxx' to avoid overflow(io.2).\n#endif\n\n// Clang/GCC warnings with -Weverything\n#if defined(__clang__)\n#if __has_warning(\"-Wunknown-warning-option\")\n#pragma clang diagnostic ignored \"-Wunknown-warning-option\"         // warning: unknown warning group 'xxx'                     // not all warnings are known by all Clang versions and they tend to be rename-happy.. so ignoring warnings triggers new warnings on some configuration. Great!\n#endif\n#pragma clang diagnostic ignored \"-Wunknown-pragmas\"                // warning: unknown warning group 'xxx'\n#pragma clang diagnostic ignored \"-Wold-style-cast\"                 // warning: use of old-style cast                           // yes, they are more terse.\n#pragma clang diagnostic ignored \"-Wdeprecated-declarations\"        // warning: 'xx' is deprecated: The POSIX name for this..   // for strdup used in demo code (so user can copy & paste the code)\n#pragma clang diagnostic ignored \"-Wint-to-void-pointer-cast\"       // warning: cast to 'void *' from smaller integer type\n#pragma clang diagnostic ignored \"-Wformat\"                         // warning: format specifies type 'int' but the argument has type 'unsigned int'\n#pragma clang diagnostic ignored \"-Wformat-security\"                // warning: format string is not a string literal\n#pragma clang diagnostic ignored \"-Wexit-time-destructors\"          // warning: declaration requires an exit-time destructor    // exit-time destruction order is undefined. if MemFree() leads to users code that has been disabled before exit it might cause problems. ImGui coding style welcomes static/globals.\n#pragma clang diagnostic ignored \"-Wunused-macros\"                  // warning: macro is not used                               // we define snprintf/vsnprintf on Windows so they are available, but not always used.\n#pragma clang diagnostic ignored \"-Wzero-as-null-pointer-constant\"  // warning: zero as null pointer constant                   // some standard header variations use #define NULL 0\n#pragma clang diagnostic ignored \"-Wdouble-promotion\"               // warning: implicit conversion from 'float' to 'double' when passing argument to function  // using printf() is a misery with this as C++ va_arg ellipsis changes float to double.\n#pragma clang diagnostic ignored \"-Wreserved-id-macro\"              // warning: macro name is a reserved identifier\n#pragma clang diagnostic ignored \"-Wimplicit-int-float-conversion\"  // warning: implicit conversion from 'xxx' to 'float' may lose precision\n#pragma clang diagnostic ignored \"-Wunsafe-buffer-usage\"            // warning: 'xxx' is an unsafe pointer used for buffer access\n#pragma clang diagnostic ignored \"-Wswitch-default\"                 // warning: 'switch' missing 'default' label\n#elif defined(__GNUC__)\n#pragma GCC diagnostic ignored \"-Wpragmas\"                          // warning: unknown option after '#pragma GCC diagnostic' kind\n#pragma GCC diagnostic ignored \"-Wfloat-equal\"                      // warning: comparing floating-point with '==' or '!=' is unsafe\n#pragma GCC diagnostic ignored \"-Wint-to-pointer-cast\"              // warning: cast to pointer from integer of different size\n#pragma GCC diagnostic ignored \"-Wformat\"                           // warning: format '%p' expects argument of type 'int'/'void*', but argument X has type 'unsigned int'/'ImGuiWindow*'\n#pragma GCC diagnostic ignored \"-Wformat-security\"                  // warning: format string is not a string literal (potentially insecure)\n#pragma GCC diagnostic ignored \"-Wdouble-promotion\"                 // warning: implicit conversion from 'float' to 'double' when passing argument to function\n#pragma GCC diagnostic ignored \"-Wconversion\"                       // warning: conversion to 'xxxx' from 'xxxx' may alter its value\n#pragma GCC diagnostic ignored \"-Wmisleading-indentation\"           // [__GNUC__ >= 6] warning: this 'if' clause does not guard this statement      // GCC 6.0+ only. See #883 on GitHub.\n#pragma GCC diagnostic ignored \"-Wstrict-overflow\"                  // warning: assuming signed overflow does not occur when simplifying division / ..when changing X +- C1 cmp C2 to X cmp C2 -+ C1\n#pragma GCC diagnostic ignored \"-Wcast-qual\"                        // warning: cast from type 'const xxxx *' to type 'xxxx *' casts away qualifiers\n#endif\n\n// Play it nice with Windows users (Update: May 2018, Notepad now supports Unix-style carriage returns!)\n#ifdef _WIN32\n#define IM_NEWLINE  \"\\r\\n\"\n#else\n#define IM_NEWLINE  \"\\n\"\n#endif\n\n// Helpers\n#if defined(_MSC_VER) && !defined(snprintf)\n#define snprintf    _snprintf\n#endif\n#if defined(_MSC_VER) && !defined(vsnprintf)\n#define vsnprintf   _vsnprintf\n#endif\n\n// Format specifiers for 64-bit values (hasn't been decently standardized before VS2013)\n#if !defined(PRId64) && defined(_MSC_VER)\n#define PRId64 \"I64d\"\n#define PRIu64 \"I64u\"\n#elif !defined(PRId64)\n#define PRId64 \"lld\"\n#define PRIu64 \"llu\"\n#endif\n\n// Helpers macros\n// We normally try to not use many helpers in imgui_demo.cpp in order to make code easier to copy and paste,\n// but making an exception here as those are largely simplifying code...\n// In other imgui sources we can use nicer internal functions from imgui_internal.h (ImMin/ImMax) but not in the demo.\n#define IM_MIN(A, B)            (((A) < (B)) ? (A) : (B))\n#define IM_MAX(A, B)            (((A) >= (B)) ? (A) : (B))\n#define IM_CLAMP(V, MN, MX)     ((V) < (MN) ? (MN) : (V) > (MX) ? (MX) : (V))\n\n// Enforce cdecl calling convention for functions called by the standard library,\n// in case compilation settings changed the default to e.g. __vectorcall\n#ifndef IMGUI_CDECL\n#ifdef _MSC_VER\n#define IMGUI_CDECL __cdecl\n#else\n#define IMGUI_CDECL\n#endif\n#endif\n\n//-----------------------------------------------------------------------------\n// [SECTION] Forward Declarations\n//-----------------------------------------------------------------------------\n\n#if !defined(IMGUI_DISABLE_DEMO_WINDOWS)\n\n// Forward Declarations\nstruct ImGuiDemoWindowData;\nstatic void ShowExampleAppMainMenuBar();\nstatic void ShowExampleAppAssetsBrowser(bool* p_open);\nstatic void ShowExampleAppConsole(bool* p_open);\nstatic void ShowExampleAppCustomRendering(bool* p_open);\nstatic void ShowExampleAppDocuments(bool* p_open);\nstatic void ShowExampleAppLog(bool* p_open);\nstatic void ShowExampleAppLayout(bool* p_open);\nstatic void ShowExampleAppPropertyEditor(bool* p_open, ImGuiDemoWindowData* demo_data);\nstatic void ShowExampleAppSimpleOverlay(bool* p_open);\nstatic void ShowExampleAppAutoResize(bool* p_open);\nstatic void ShowExampleAppConstrainedResize(bool* p_open);\nstatic void ShowExampleAppFullscreen(bool* p_open);\nstatic void ShowExampleAppLongText(bool* p_open);\nstatic void ShowExampleAppWindowTitles(bool* p_open);\nstatic void ShowExampleMenuFile();\n\n// We split the contents of the big ShowDemoWindow() function into smaller functions\n// (because the link time of very large functions tends to grow non-linearly)\nstatic void DemoWindowMenuBar(ImGuiDemoWindowData* demo_data);\nstatic void DemoWindowWidgets(ImGuiDemoWindowData* demo_data);\nstatic void DemoWindowLayout();\nstatic void DemoWindowPopups();\nstatic void DemoWindowTables();\nstatic void DemoWindowColumns();\nstatic void DemoWindowInputs();\n\n// Helper tree functions used by Property Editor & Multi-Select demos\nstruct ExampleTreeNode;\nstatic ExampleTreeNode* ExampleTree_CreateNode(const char* name, int uid, ExampleTreeNode* parent);\nstatic void             ExampleTree_DestroyNode(ExampleTreeNode* node);\n\n//-----------------------------------------------------------------------------\n// [SECTION] Helpers\n//-----------------------------------------------------------------------------\n\n// Helper to display a little (?) mark which shows a tooltip when hovered.\n// In your own code you may want to display an actual icon if you are using a merged icon fonts (see docs/FONTS.md)\nstatic void HelpMarker(const char* desc)\n{\n    ImGui::TextDisabled(\"(?)\");\n    if (ImGui::BeginItemTooltip())\n    {\n        ImGui::PushTextWrapPos(ImGui::GetFontSize() * 35.0f);\n        ImGui::TextUnformatted(desc);\n        ImGui::PopTextWrapPos();\n        ImGui::EndTooltip();\n    }\n}\n\n// Helper to wire demo markers located in code to an interactive browser\ntypedef void (*ImGuiDemoMarkerCallback)(const char* file, int line, const char* section, void* user_data);\nextern ImGuiDemoMarkerCallback      GImGuiDemoMarkerCallback;\nextern void*                        GImGuiDemoMarkerCallbackUserData;\nImGuiDemoMarkerCallback             GImGuiDemoMarkerCallback = NULL;\nvoid*                               GImGuiDemoMarkerCallbackUserData = NULL;\n#define IMGUI_DEMO_MARKER(section)  do { if (GImGuiDemoMarkerCallback != NULL) GImGuiDemoMarkerCallback(__FILE__, __LINE__, section, GImGuiDemoMarkerCallbackUserData); } while (0)\n\n//-----------------------------------------------------------------------------\n// [SECTION] Demo Window / ShowDemoWindow()\n//-----------------------------------------------------------------------------\n\n// Data to be shared across different functions of the demo.\nstruct ImGuiDemoWindowData\n{\n    // Examples Apps (accessible from the \"Examples\" menu)\n    bool ShowMainMenuBar = false;\n    bool ShowAppAssetsBrowser = false;\n    bool ShowAppConsole = false;\n    bool ShowAppCustomRendering = false;\n    bool ShowAppDocuments = false;\n    bool ShowAppLog = false;\n    bool ShowAppLayout = false;\n    bool ShowAppPropertyEditor = false;\n    bool ShowAppSimpleOverlay = false;\n    bool ShowAppAutoResize = false;\n    bool ShowAppConstrainedResize = false;\n    bool ShowAppFullscreen = false;\n    bool ShowAppLongText = false;\n    bool ShowAppWindowTitles = false;\n\n    // Dear ImGui Tools (accessible from the \"Tools\" menu)\n    bool ShowMetrics = false;\n    bool ShowDebugLog = false;\n    bool ShowIDStackTool = false;\n    bool ShowStyleEditor = false;\n    bool ShowAbout = false;\n\n    // Other data\n    bool DisableSections = false;\n    ExampleTreeNode* DemoTree = NULL;\n\n    ~ImGuiDemoWindowData() { if (DemoTree) ExampleTree_DestroyNode(DemoTree); }\n};\n\n// Demonstrate most Dear ImGui features (this is big function!)\n// You may execute this function to experiment with the UI and understand what it does.\n// You may then search for keywords in the code when you are interested by a specific feature.\nvoid ImGui::ShowDemoWindow(bool* p_open)\n{\n    // Exceptionally add an extra assert here for people confused about initial Dear ImGui setup\n    // Most functions would normally just assert/crash if the context is missing.\n    IM_ASSERT(ImGui::GetCurrentContext() != NULL && \"Missing Dear ImGui context. Refer to examples app!\");\n\n    // Verify ABI compatibility between caller code and compiled version of Dear ImGui. This helps detects some build issues.\n    IMGUI_CHECKVERSION();\n\n    // Stored data\n    static ImGuiDemoWindowData demo_data;\n\n    // Examples Apps (accessible from the \"Examples\" menu)\n    if (demo_data.ShowMainMenuBar)          { ShowExampleAppMainMenuBar(); }\n    if (demo_data.ShowAppDocuments)         { ShowExampleAppDocuments(&demo_data.ShowAppDocuments); }\n    if (demo_data.ShowAppAssetsBrowser)     { ShowExampleAppAssetsBrowser(&demo_data.ShowAppAssetsBrowser); }\n    if (demo_data.ShowAppConsole)           { ShowExampleAppConsole(&demo_data.ShowAppConsole); }\n    if (demo_data.ShowAppCustomRendering)   { ShowExampleAppCustomRendering(&demo_data.ShowAppCustomRendering); }\n    if (demo_data.ShowAppLog)               { ShowExampleAppLog(&demo_data.ShowAppLog); }\n    if (demo_data.ShowAppLayout)            { ShowExampleAppLayout(&demo_data.ShowAppLayout); }\n    if (demo_data.ShowAppPropertyEditor)    { ShowExampleAppPropertyEditor(&demo_data.ShowAppPropertyEditor, &demo_data); }\n    if (demo_data.ShowAppSimpleOverlay)     { ShowExampleAppSimpleOverlay(&demo_data.ShowAppSimpleOverlay); }\n    if (demo_data.ShowAppAutoResize)        { ShowExampleAppAutoResize(&demo_data.ShowAppAutoResize); }\n    if (demo_data.ShowAppConstrainedResize) { ShowExampleAppConstrainedResize(&demo_data.ShowAppConstrainedResize); }\n    if (demo_data.ShowAppFullscreen)        { ShowExampleAppFullscreen(&demo_data.ShowAppFullscreen); }\n    if (demo_data.ShowAppLongText)          { ShowExampleAppLongText(&demo_data.ShowAppLongText); }\n    if (demo_data.ShowAppWindowTitles)      { ShowExampleAppWindowTitles(&demo_data.ShowAppWindowTitles); }\n\n    // Dear ImGui Tools (accessible from the \"Tools\" menu)\n    if (demo_data.ShowMetrics)              { ImGui::ShowMetricsWindow(&demo_data.ShowMetrics); }\n    if (demo_data.ShowDebugLog)             { ImGui::ShowDebugLogWindow(&demo_data.ShowDebugLog); }\n    if (demo_data.ShowIDStackTool)          { ImGui::ShowIDStackToolWindow(&demo_data.ShowIDStackTool); }\n    if (demo_data.ShowAbout)                { ImGui::ShowAboutWindow(&demo_data.ShowAbout); }\n    if (demo_data.ShowStyleEditor)\n    {\n        ImGui::Begin(\"Dear ImGui Style Editor\", &demo_data.ShowStyleEditor);\n        ImGui::ShowStyleEditor();\n        ImGui::End();\n    }\n\n    // Demonstrate the various window flags. Typically you would just use the default!\n    static bool no_titlebar = false;\n    static bool no_scrollbar = false;\n    static bool no_menu = false;\n    static bool no_move = false;\n    static bool no_resize = false;\n    static bool no_collapse = false;\n    static bool no_close = false;\n    static bool no_nav = false;\n    static bool no_background = false;\n    static bool no_bring_to_front = false;\n    static bool unsaved_document = false;\n\n    ImGuiWindowFlags window_flags = 0;\n    if (no_titlebar)        window_flags |= ImGuiWindowFlags_NoTitleBar;\n    if (no_scrollbar)       window_flags |= ImGuiWindowFlags_NoScrollbar;\n    if (!no_menu)           window_flags |= ImGuiWindowFlags_MenuBar;\n    if (no_move)            window_flags |= ImGuiWindowFlags_NoMove;\n    if (no_resize)          window_flags |= ImGuiWindowFlags_NoResize;\n    if (no_collapse)        window_flags |= ImGuiWindowFlags_NoCollapse;\n    if (no_nav)             window_flags |= ImGuiWindowFlags_NoNav;\n    if (no_background)      window_flags |= ImGuiWindowFlags_NoBackground;\n    if (no_bring_to_front)  window_flags |= ImGuiWindowFlags_NoBringToFrontOnFocus;\n    if (unsaved_document)   window_flags |= ImGuiWindowFlags_UnsavedDocument;\n    if (no_close)           p_open = NULL; // Don't pass our bool* to Begin\n\n    // We specify a default position/size in case there's no data in the .ini file.\n    // We only do it to make the demo applications a little more welcoming, but typically this isn't required.\n    const ImGuiViewport* main_viewport = ImGui::GetMainViewport();\n    ImGui::SetNextWindowPos(ImVec2(main_viewport->WorkPos.x + 650, main_viewport->WorkPos.y + 20), ImGuiCond_FirstUseEver);\n    ImGui::SetNextWindowSize(ImVec2(550, 680), ImGuiCond_FirstUseEver);\n\n    // Main body of the Demo window starts here.\n    if (!ImGui::Begin(\"Dear ImGui Demo\", p_open, window_flags))\n    {\n        // Early out if the window is collapsed, as an optimization.\n        ImGui::End();\n        return;\n    }\n\n    // Most \"big\" widgets share a common width settings by default. See 'Demo->Layout->Widgets Width' for details.\n    ImGui::PushItemWidth(ImGui::GetFontSize() * -12);           // e.g. Leave a fixed amount of width for labels (by passing a negative value), the rest goes to widgets.\n    //ImGui::PushItemWidth(-ImGui::GetWindowWidth() * 0.35f);   // e.g. Use 2/3 of the space for widgets and 1/3 for labels (right align)\n\n    // Menu Bar\n    DemoWindowMenuBar(&demo_data);\n\n    ImGui::Text(\"dear imgui says hello! (%s) (%d)\", IMGUI_VERSION, IMGUI_VERSION_NUM);\n    ImGui::Spacing();\n\n    IMGUI_DEMO_MARKER(\"Help\");\n    if (ImGui::CollapsingHeader(\"Help\"))\n    {\n        ImGui::SeparatorText(\"ABOUT THIS DEMO:\");\n        ImGui::BulletText(\"Sections below are demonstrating many aspects of the library.\");\n        ImGui::BulletText(\"The \\\"Examples\\\" menu above leads to more demo contents.\");\n        ImGui::BulletText(\"The \\\"Tools\\\" menu above gives access to: About Box, Style Editor,\\n\"\n                          \"and Metrics/Debugger (general purpose Dear ImGui debugging tool).\");\n\n        ImGui::SeparatorText(\"PROGRAMMER GUIDE:\");\n        ImGui::BulletText(\"See the ShowDemoWindow() code in imgui_demo.cpp. <- you are here!\");\n        ImGui::BulletText(\"See comments in imgui.cpp.\");\n        ImGui::BulletText(\"See example applications in the examples/ folder.\");\n        ImGui::BulletText(\"Read the FAQ at \");\n        ImGui::SameLine(0, 0);\n        ImGui::TextLinkOpenURL(\"https://www.dearimgui.com/faq/\");\n        ImGui::BulletText(\"Set 'io.ConfigFlags |= NavEnableKeyboard' for keyboard controls.\");\n        ImGui::BulletText(\"Set 'io.ConfigFlags |= NavEnableGamepad' for gamepad controls.\");\n\n        ImGui::SeparatorText(\"USER GUIDE:\");\n        ImGui::ShowUserGuide();\n    }\n\n    IMGUI_DEMO_MARKER(\"Configuration\");\n    if (ImGui::CollapsingHeader(\"Configuration\"))\n    {\n        ImGuiIO& io = ImGui::GetIO();\n\n        if (ImGui::TreeNode(\"Configuration##2\"))\n        {\n            ImGui::SeparatorText(\"General\");\n            ImGui::CheckboxFlags(\"io.ConfigFlags: NavEnableKeyboard\",    &io.ConfigFlags, ImGuiConfigFlags_NavEnableKeyboard);\n            ImGui::SameLine(); HelpMarker(\"Enable keyboard controls.\");\n            ImGui::CheckboxFlags(\"io.ConfigFlags: NavEnableGamepad\",     &io.ConfigFlags, ImGuiConfigFlags_NavEnableGamepad);\n            ImGui::SameLine(); HelpMarker(\"Enable gamepad controls. Require backend to set io.BackendFlags |= ImGuiBackendFlags_HasGamepad.\\n\\nRead instructions in imgui.cpp for details.\");\n            ImGui::CheckboxFlags(\"io.ConfigFlags: NoMouse\",              &io.ConfigFlags, ImGuiConfigFlags_NoMouse);\n            ImGui::SameLine(); HelpMarker(\"Instruct dear imgui to disable mouse inputs and interactions.\");\n\n            // The \"NoMouse\" option can get us stuck with a disabled mouse! Let's provide an alternative way to fix it:\n            if (io.ConfigFlags & ImGuiConfigFlags_NoMouse)\n            {\n                if (fmodf((float)ImGui::GetTime(), 0.40f) < 0.20f)\n                {\n                    ImGui::SameLine();\n                    ImGui::Text(\"<<PRESS SPACE TO DISABLE>>\");\n                }\n                // Prevent both being checked\n                if (ImGui::IsKeyPressed(ImGuiKey_Space) || (io.ConfigFlags & ImGuiConfigFlags_NoKeyboard))\n                    io.ConfigFlags &= ~ImGuiConfigFlags_NoMouse;\n            }\n\n            ImGui::CheckboxFlags(\"io.ConfigFlags: NoMouseCursorChange\",  &io.ConfigFlags, ImGuiConfigFlags_NoMouseCursorChange);\n            ImGui::SameLine(); HelpMarker(\"Instruct backend to not alter mouse cursor shape and visibility.\");\n            ImGui::CheckboxFlags(\"io.ConfigFlags: NoKeyboard\", &io.ConfigFlags, ImGuiConfigFlags_NoKeyboard);\n            ImGui::SameLine(); HelpMarker(\"Instruct dear imgui to disable keyboard inputs and interactions.\");\n\n            ImGui::Checkbox(\"io.ConfigInputTrickleEventQueue\", &io.ConfigInputTrickleEventQueue);\n            ImGui::SameLine(); HelpMarker(\"Enable input queue trickling: some types of events submitted during the same frame (e.g. button down + up) will be spread over multiple frames, improving interactions with low framerates.\");\n            ImGui::Checkbox(\"io.MouseDrawCursor\", &io.MouseDrawCursor);\n            ImGui::SameLine(); HelpMarker(\"Instruct Dear ImGui to render a mouse cursor itself. Note that a mouse cursor rendered via your application GPU rendering path will feel more laggy than hardware cursor, but will be more in sync with your other visuals.\\n\\nSome desktop applications may use both kinds of cursors (e.g. enable software cursor only when resizing/dragging something).\");\n\n            ImGui::SeparatorText(\"Keyboard/Gamepad Navigation\");\n            ImGui::Checkbox(\"io.ConfigNavSwapGamepadButtons\", &io.ConfigNavSwapGamepadButtons);\n            ImGui::Checkbox(\"io.ConfigNavMoveSetMousePos\", &io.ConfigNavMoveSetMousePos);\n            ImGui::SameLine(); HelpMarker(\"Directional/tabbing navigation teleports the mouse cursor. May be useful on TV/console systems where moving a virtual mouse is difficult\");\n            ImGui::Checkbox(\"io.ConfigNavCaptureKeyboard\", &io.ConfigNavCaptureKeyboard);\n            ImGui::Checkbox(\"io.ConfigNavEscapeClearFocusItem\", &io.ConfigNavEscapeClearFocusItem);\n            ImGui::SameLine(); HelpMarker(\"Pressing Escape clears focused item.\");\n            ImGui::Checkbox(\"io.ConfigNavEscapeClearFocusWindow\", &io.ConfigNavEscapeClearFocusWindow);\n            ImGui::SameLine(); HelpMarker(\"Pressing Escape clears focused window.\");\n            ImGui::Checkbox(\"io.ConfigNavCursorVisibleAuto\", &io.ConfigNavCursorVisibleAuto);\n            ImGui::SameLine(); HelpMarker(\"Using directional navigation key makes the cursor visible. Mouse click hides the cursor.\");\n            ImGui::Checkbox(\"io.ConfigNavCursorVisibleAlways\", &io.ConfigNavCursorVisibleAlways);\n            ImGui::SameLine(); HelpMarker(\"Navigation cursor is always visible.\");\n\n            ImGui::SeparatorText(\"Windows\");\n            ImGui::Checkbox(\"io.ConfigWindowsResizeFromEdges\", &io.ConfigWindowsResizeFromEdges);\n            ImGui::SameLine(); HelpMarker(\"Enable resizing of windows from their edges and from the lower-left corner.\\nThis requires ImGuiBackendFlags_HasMouseCursors for better mouse cursor feedback.\");\n            ImGui::Checkbox(\"io.ConfigWindowsMoveFromTitleBarOnly\", &io.ConfigWindowsMoveFromTitleBarOnly);\n            ImGui::Checkbox(\"io.ConfigWindowsCopyContentsWithCtrlC\", &io.ConfigWindowsCopyContentsWithCtrlC); // [EXPERIMENTAL]\n            ImGui::SameLine(); HelpMarker(\"*EXPERIMENTAL* CTRL+C copy the contents of focused window into the clipboard.\\n\\nExperimental because:\\n- (1) has known issues with nested Begin/End pairs.\\n- (2) text output quality varies.\\n- (3) text output is in submission order rather than spatial order.\");\n            ImGui::Checkbox(\"io.ConfigScrollbarScrollByPage\", &io.ConfigScrollbarScrollByPage);\n            ImGui::SameLine(); HelpMarker(\"Enable scrolling page by page when clicking outside the scrollbar grab.\\nWhen disabled, always scroll to clicked location.\\nWhen enabled, Shift+Click scrolls to clicked location.\");\n\n            ImGui::SeparatorText(\"Widgets\");\n            ImGui::Checkbox(\"io.ConfigInputTextCursorBlink\", &io.ConfigInputTextCursorBlink);\n            ImGui::SameLine(); HelpMarker(\"Enable blinking cursor (optional as some users consider it to be distracting).\");\n            ImGui::Checkbox(\"io.ConfigInputTextEnterKeepActive\", &io.ConfigInputTextEnterKeepActive);\n            ImGui::SameLine(); HelpMarker(\"Pressing Enter will keep item active and select contents (single-line only).\");\n            ImGui::Checkbox(\"io.ConfigDragClickToInputText\", &io.ConfigDragClickToInputText);\n            ImGui::SameLine(); HelpMarker(\"Enable turning DragXXX widgets into text input with a simple mouse click-release (without moving).\");\n            ImGui::Checkbox(\"io.ConfigMacOSXBehaviors\", &io.ConfigMacOSXBehaviors);\n            ImGui::SameLine(); HelpMarker(\"Swap Cmd<>Ctrl keys, enable various MacOS style behaviors.\");\n            ImGui::Text(\"Also see Style->Rendering for rendering options.\");\n\n            // Also read: https://github.com/ocornut/imgui/wiki/Error-Handling\n            ImGui::SeparatorText(\"Error Handling\");\n\n            ImGui::Checkbox(\"io.ConfigErrorRecovery\", &io.ConfigErrorRecovery);\n            ImGui::SameLine(); HelpMarker(\n                \"Options to configure how we handle recoverable errors.\\n\"\n                \"- Error recovery is not perfect nor guaranteed! It is a feature to ease development.\\n\"\n                \"- You not are not supposed to rely on it in the course of a normal application run.\\n\"\n                \"- Possible usage: facilitate recovery from errors triggered from a scripting language or after specific exceptions handlers.\\n\"\n                \"- Always ensure that on programmers seat you have at minimum Asserts or Tooltips enabled when making direct imgui API call! \"\n                \"Otherwise it would severely hinder your ability to catch and correct mistakes!\");\n            ImGui::Checkbox(\"io.ConfigErrorRecoveryEnableAssert\", &io.ConfigErrorRecoveryEnableAssert);\n            ImGui::Checkbox(\"io.ConfigErrorRecoveryEnableDebugLog\", &io.ConfigErrorRecoveryEnableDebugLog);\n            ImGui::Checkbox(\"io.ConfigErrorRecoveryEnableTooltip\", &io.ConfigErrorRecoveryEnableTooltip);\n            if (!io.ConfigErrorRecoveryEnableAssert && !io.ConfigErrorRecoveryEnableDebugLog && !io.ConfigErrorRecoveryEnableTooltip)\n                io.ConfigErrorRecoveryEnableAssert = io.ConfigErrorRecoveryEnableDebugLog = io.ConfigErrorRecoveryEnableTooltip = true;\n\n            // Also read: https://github.com/ocornut/imgui/wiki/Debug-Tools\n            ImGui::SeparatorText(\"Debug\");\n            ImGui::Checkbox(\"io.ConfigDebugIsDebuggerPresent\", &io.ConfigDebugIsDebuggerPresent);\n            ImGui::SameLine(); HelpMarker(\"Enable various tools calling IM_DEBUG_BREAK().\\n\\nRequires a debugger being attached, otherwise IM_DEBUG_BREAK() options will appear to crash your application.\");\n            ImGui::Checkbox(\"io.ConfigDebugHighlightIdConflicts\", &io.ConfigDebugHighlightIdConflicts);\n            ImGui::SameLine(); HelpMarker(\"Highlight and show an error message when multiple items have conflicting identifiers.\");\n            ImGui::BeginDisabled();\n            ImGui::Checkbox(\"io.ConfigDebugBeginReturnValueOnce\", &io.ConfigDebugBeginReturnValueOnce);\n            ImGui::EndDisabled();\n            ImGui::SameLine(); HelpMarker(\"First calls to Begin()/BeginChild() will return false.\\n\\nTHIS OPTION IS DISABLED because it needs to be set at application boot-time to make sense. Showing the disabled option is a way to make this feature easier to discover.\");\n            ImGui::Checkbox(\"io.ConfigDebugBeginReturnValueLoop\", &io.ConfigDebugBeginReturnValueLoop);\n            ImGui::SameLine(); HelpMarker(\"Some calls to Begin()/BeginChild() will return false.\\n\\nWill cycle through window depths then repeat. Windows should be flickering while running.\");\n            ImGui::Checkbox(\"io.ConfigDebugIgnoreFocusLoss\", &io.ConfigDebugIgnoreFocusLoss);\n            ImGui::SameLine(); HelpMarker(\"Option to deactivate io.AddFocusEvent(false) handling. May facilitate interactions with a debugger when focus loss leads to clearing inputs data.\");\n            ImGui::Checkbox(\"io.ConfigDebugIniSettings\", &io.ConfigDebugIniSettings);\n            ImGui::SameLine(); HelpMarker(\"Option to save .ini data with extra comments (particularly helpful for Docking, but makes saving slower).\");\n\n            ImGui::TreePop();\n            ImGui::Spacing();\n        }\n\n        IMGUI_DEMO_MARKER(\"Configuration/Backend Flags\");\n        if (ImGui::TreeNode(\"Backend Flags\"))\n        {\n            HelpMarker(\n                \"Those flags are set by the backends (imgui_impl_xxx files) to specify their capabilities.\\n\"\n                \"Here we expose them as read-only fields to avoid breaking interactions with your backend.\");\n\n            // FIXME: Maybe we need a BeginReadonly() equivalent to keep label bright?\n            ImGui::BeginDisabled();\n            ImGui::CheckboxFlags(\"io.BackendFlags: HasGamepad\",           &io.BackendFlags, ImGuiBackendFlags_HasGamepad);\n            ImGui::CheckboxFlags(\"io.BackendFlags: HasMouseCursors\",      &io.BackendFlags, ImGuiBackendFlags_HasMouseCursors);\n            ImGui::CheckboxFlags(\"io.BackendFlags: HasSetMousePos\",       &io.BackendFlags, ImGuiBackendFlags_HasSetMousePos);\n            ImGui::CheckboxFlags(\"io.BackendFlags: RendererHasVtxOffset\", &io.BackendFlags, ImGuiBackendFlags_RendererHasVtxOffset);\n            ImGui::EndDisabled();\n\n            ImGui::TreePop();\n            ImGui::Spacing();\n        }\n\n        IMGUI_DEMO_MARKER(\"Configuration/Style\");\n        if (ImGui::TreeNode(\"Style\"))\n        {\n            ImGui::Checkbox(\"Style Editor\", &demo_data.ShowStyleEditor);\n            ImGui::SameLine();\n            HelpMarker(\"The same contents can be accessed in 'Tools->Style Editor' or by calling the ShowStyleEditor() function.\");\n            ImGui::TreePop();\n            ImGui::Spacing();\n        }\n\n        IMGUI_DEMO_MARKER(\"Configuration/Capture, Logging\");\n        if (ImGui::TreeNode(\"Capture/Logging\"))\n        {\n            HelpMarker(\n                \"The logging API redirects all text output so you can easily capture the content of \"\n                \"a window or a block. Tree nodes can be automatically expanded.\\n\"\n                \"Try opening any of the contents below in this window and then click one of the \\\"Log To\\\" button.\");\n            ImGui::LogButtons();\n\n            HelpMarker(\"You can also call ImGui::LogText() to output directly to the log without a visual output.\");\n            if (ImGui::Button(\"Copy \\\"Hello, world!\\\" to clipboard\"))\n            {\n                ImGui::LogToClipboard();\n                ImGui::LogText(\"Hello, world!\");\n                ImGui::LogFinish();\n            }\n            ImGui::TreePop();\n        }\n    }\n\n    IMGUI_DEMO_MARKER(\"Window options\");\n    if (ImGui::CollapsingHeader(\"Window options\"))\n    {\n        if (ImGui::BeginTable(\"split\", 3))\n        {\n            ImGui::TableNextColumn(); ImGui::Checkbox(\"No titlebar\", &no_titlebar);\n            ImGui::TableNextColumn(); ImGui::Checkbox(\"No scrollbar\", &no_scrollbar);\n            ImGui::TableNextColumn(); ImGui::Checkbox(\"No menu\", &no_menu);\n            ImGui::TableNextColumn(); ImGui::Checkbox(\"No move\", &no_move);\n            ImGui::TableNextColumn(); ImGui::Checkbox(\"No resize\", &no_resize);\n            ImGui::TableNextColumn(); ImGui::Checkbox(\"No collapse\", &no_collapse);\n            ImGui::TableNextColumn(); ImGui::Checkbox(\"No close\", &no_close);\n            ImGui::TableNextColumn(); ImGui::Checkbox(\"No nav\", &no_nav);\n            ImGui::TableNextColumn(); ImGui::Checkbox(\"No background\", &no_background);\n            ImGui::TableNextColumn(); ImGui::Checkbox(\"No bring to front\", &no_bring_to_front);\n            ImGui::TableNextColumn(); ImGui::Checkbox(\"Unsaved document\", &unsaved_document);\n            ImGui::EndTable();\n        }\n    }\n\n    // All demo contents\n    DemoWindowWidgets(&demo_data);\n    DemoWindowLayout();\n    DemoWindowPopups();\n    DemoWindowTables();\n    DemoWindowInputs();\n\n    // End of ShowDemoWindow()\n    ImGui::PopItemWidth();\n    ImGui::End();\n}\n\n//-----------------------------------------------------------------------------\n// [SECTION] DemoWindowMenuBar()\n//-----------------------------------------------------------------------------\n\nstatic void DemoWindowMenuBar(ImGuiDemoWindowData* demo_data)\n{\n    IMGUI_DEMO_MARKER(\"Menu\");\n    if (ImGui::BeginMenuBar())\n    {\n        if (ImGui::BeginMenu(\"Menu\"))\n        {\n            IMGUI_DEMO_MARKER(\"Menu/File\");\n            ShowExampleMenuFile();\n            ImGui::EndMenu();\n        }\n        if (ImGui::BeginMenu(\"Examples\"))\n        {\n            IMGUI_DEMO_MARKER(\"Menu/Examples\");\n            ImGui::MenuItem(\"Main menu bar\", NULL, &demo_data->ShowMainMenuBar);\n\n            ImGui::SeparatorText(\"Mini apps\");\n            ImGui::MenuItem(\"Assets Browser\", NULL, &demo_data->ShowAppAssetsBrowser);\n            ImGui::MenuItem(\"Console\", NULL, &demo_data->ShowAppConsole);\n            ImGui::MenuItem(\"Custom rendering\", NULL, &demo_data->ShowAppCustomRendering);\n            ImGui::MenuItem(\"Documents\", NULL, &demo_data->ShowAppDocuments);\n            ImGui::MenuItem(\"Log\", NULL, &demo_data->ShowAppLog);\n            ImGui::MenuItem(\"Property editor\", NULL, &demo_data->ShowAppPropertyEditor);\n            ImGui::MenuItem(\"Simple layout\", NULL, &demo_data->ShowAppLayout);\n            ImGui::MenuItem(\"Simple overlay\", NULL, &demo_data->ShowAppSimpleOverlay);\n\n            ImGui::SeparatorText(\"Concepts\");\n            ImGui::MenuItem(\"Auto-resizing window\", NULL, &demo_data->ShowAppAutoResize);\n            ImGui::MenuItem(\"Constrained-resizing window\", NULL, &demo_data->ShowAppConstrainedResize);\n            ImGui::MenuItem(\"Fullscreen window\", NULL, &demo_data->ShowAppFullscreen);\n            ImGui::MenuItem(\"Long text display\", NULL, &demo_data->ShowAppLongText);\n            ImGui::MenuItem(\"Manipulating window titles\", NULL, &demo_data->ShowAppWindowTitles);\n\n            ImGui::EndMenu();\n        }\n        //if (ImGui::MenuItem(\"MenuItem\")) {} // You can also use MenuItem() inside a menu bar!\n        if (ImGui::BeginMenu(\"Tools\"))\n        {\n            IMGUI_DEMO_MARKER(\"Menu/Tools\");\n            ImGuiIO& io = ImGui::GetIO();\n#ifndef IMGUI_DISABLE_DEBUG_TOOLS\n            const bool has_debug_tools = true;\n#else\n            const bool has_debug_tools = false;\n#endif\n            ImGui::MenuItem(\"Metrics/Debugger\", NULL, &demo_data->ShowMetrics, has_debug_tools);\n            if (ImGui::BeginMenu(\"Debug Options\"))\n            {\n                ImGui::BeginDisabled(!has_debug_tools);\n                ImGui::Checkbox(\"Highlight ID Conflicts\", &io.ConfigDebugHighlightIdConflicts);\n                ImGui::EndDisabled();\n                ImGui::Checkbox(\"Assert on error recovery\", &io.ConfigErrorRecoveryEnableAssert);\n                ImGui::TextDisabled(\"(see Demo->Configuration for details & more)\");\n                ImGui::EndMenu();\n            }\n            ImGui::MenuItem(\"Debug Log\", NULL, &demo_data->ShowDebugLog, has_debug_tools);\n            ImGui::MenuItem(\"ID Stack Tool\", NULL, &demo_data->ShowIDStackTool, has_debug_tools);\n            bool is_debugger_present = io.ConfigDebugIsDebuggerPresent;\n            if (ImGui::MenuItem(\"Item Picker\", NULL, false, has_debug_tools))// && is_debugger_present))\n                ImGui::DebugStartItemPicker();\n            if (!is_debugger_present)\n                ImGui::SetItemTooltip(\"Requires io.ConfigDebugIsDebuggerPresent=true to be set.\\n\\nWe otherwise disable some extra features to avoid casual users crashing the application.\");\n            ImGui::MenuItem(\"Style Editor\", NULL, &demo_data->ShowStyleEditor);\n            ImGui::MenuItem(\"About Dear ImGui\", NULL, &demo_data->ShowAbout);\n\n            ImGui::EndMenu();\n        }\n        ImGui::EndMenuBar();\n    }\n}\n\n//-----------------------------------------------------------------------------\n// [SECTION] Helpers: ExampleTreeNode, ExampleMemberInfo (for use by Property Editor & Multi-Select demos)\n//-----------------------------------------------------------------------------\n\n// Simple representation for a tree\n// (this is designed to be simple to understand for our demos, not to be fancy or efficient etc.)\nstruct ExampleTreeNode\n{\n    // Tree structure\n    char                        Name[28] = \"\";\n    int                         UID = 0;\n    ExampleTreeNode* Parent = NULL;\n    ImVector<ExampleTreeNode*>  Childs;\n    unsigned short              IndexInParent = 0;  // Maintaining this allows us to implement linear traversal more easily\n\n    // Leaf Data\n    bool                        HasData = false;    // All leaves have data\n    bool                        DataMyBool = true;\n    int                         DataMyInt = 128;\n    ImVec2                      DataMyVec2 = ImVec2(0.0f, 3.141592f);\n};\n\n// Simple representation of struct metadata/serialization data.\n// (this is a minimal version of what a typical advanced application may provide)\nstruct ExampleMemberInfo\n{\n    const char* Name;       // Member name\n    ImGuiDataType   DataType;   // Member type\n    int             DataCount;  // Member count (1 when scalar)\n    int             Offset;     // Offset inside parent structure\n};\n\n// Metadata description of ExampleTreeNode struct.\nstatic const ExampleMemberInfo ExampleTreeNodeMemberInfos[]\n{\n    { \"MyName\",     ImGuiDataType_String,  1, offsetof(ExampleTreeNode, Name) },\n    { \"MyBool\",     ImGuiDataType_Bool,    1, offsetof(ExampleTreeNode, DataMyBool) },\n    { \"MyInt\",      ImGuiDataType_S32,     1, offsetof(ExampleTreeNode, DataMyInt) },\n    { \"MyVec2\",     ImGuiDataType_Float,   2, offsetof(ExampleTreeNode, DataMyVec2) },\n};\n\nstatic ExampleTreeNode* ExampleTree_CreateNode(const char* name, int uid, ExampleTreeNode* parent)\n{\n    ExampleTreeNode* node = IM_NEW(ExampleTreeNode);\n    snprintf(node->Name, IM_ARRAYSIZE(node->Name), \"%s\", name);\n    node->UID = uid;\n    node->Parent = parent;\n    node->IndexInParent = parent ? (unsigned short)parent->Childs.Size : 0;\n    if (parent)\n        parent->Childs.push_back(node);\n    return node;\n}\n\nstatic void ExampleTree_DestroyNode(ExampleTreeNode* node)\n{\n    for (ExampleTreeNode* child_node : node->Childs)\n        ExampleTree_DestroyNode(child_node);\n    IM_DELETE(node);\n}\n\n// Create example tree data\n// (this allocates _many_ more times than most other code in either Dear ImGui or others demo)\nstatic ExampleTreeNode* ExampleTree_CreateDemoTree()\n{\n    static const char* root_names[] = { \"Apple\", \"Banana\", \"Cherry\", \"Kiwi\", \"Mango\", \"Orange\", \"Pear\", \"Pineapple\", \"Strawberry\", \"Watermelon\" };\n    const size_t NAME_MAX_LEN = sizeof(ExampleTreeNode::Name);\n    char name_buf[NAME_MAX_LEN];\n    int uid = 0;\n    ExampleTreeNode* node_L0 = ExampleTree_CreateNode(\"<ROOT>\", ++uid, NULL);\n    const int root_items_multiplier = 2;\n    for (int idx_L0 = 0; idx_L0 < IM_ARRAYSIZE(root_names) * root_items_multiplier; idx_L0++)\n    {\n        snprintf(name_buf, IM_ARRAYSIZE(name_buf), \"%s %d\", root_names[idx_L0 / root_items_multiplier], idx_L0 % root_items_multiplier);\n        ExampleTreeNode* node_L1 = ExampleTree_CreateNode(name_buf, ++uid, node_L0);\n        const int number_of_childs = (int)strlen(node_L1->Name);\n        for (int idx_L1 = 0; idx_L1 < number_of_childs; idx_L1++)\n        {\n            snprintf(name_buf, IM_ARRAYSIZE(name_buf), \"Child %d\", idx_L1);\n            ExampleTreeNode* node_L2 = ExampleTree_CreateNode(name_buf, ++uid, node_L1);\n            node_L2->HasData = true;\n            if (idx_L1 == 0)\n            {\n                snprintf(name_buf, IM_ARRAYSIZE(name_buf), \"Sub-child %d\", 0);\n                ExampleTreeNode* node_L3 = ExampleTree_CreateNode(name_buf, ++uid, node_L2);\n                node_L3->HasData = true;\n            }\n        }\n    }\n    return node_L0;\n}\n\n//-----------------------------------------------------------------------------\n// [SECTION] DemoWindowWidgetsBasic()\n//-----------------------------------------------------------------------------\n\nstatic void DemoWindowWidgetsBasic()\n{\n    IMGUI_DEMO_MARKER(\"Widgets/Basic\");\n    if (ImGui::TreeNode(\"Basic\"))\n    {\n        ImGui::SeparatorText(\"General\");\n\n        IMGUI_DEMO_MARKER(\"Widgets/Basic/Button\");\n        static int clicked = 0;\n        if (ImGui::Button(\"Button\"))\n            clicked++;\n        if (clicked & 1)\n        {\n            ImGui::SameLine();\n            ImGui::Text(\"Thanks for clicking me!\");\n        }\n\n        IMGUI_DEMO_MARKER(\"Widgets/Basic/Checkbox\");\n        static bool check = true;\n        ImGui::Checkbox(\"checkbox\", &check);\n\n        IMGUI_DEMO_MARKER(\"Widgets/Basic/RadioButton\");\n        static int e = 0;\n        ImGui::RadioButton(\"radio a\", &e, 0); ImGui::SameLine();\n        ImGui::RadioButton(\"radio b\", &e, 1); ImGui::SameLine();\n        ImGui::RadioButton(\"radio c\", &e, 2);\n\n        // Color buttons, demonstrate using PushID() to add unique identifier in the ID stack, and changing style.\n        IMGUI_DEMO_MARKER(\"Widgets/Basic/Buttons (Colored)\");\n        for (int i = 0; i < 7; i++)\n        {\n            if (i > 0)\n                ImGui::SameLine();\n            ImGui::PushID(i);\n            ImGui::PushStyleColor(ImGuiCol_Button, (ImVec4)ImColor::HSV(i / 7.0f, 0.6f, 0.6f));\n            ImGui::PushStyleColor(ImGuiCol_ButtonHovered, (ImVec4)ImColor::HSV(i / 7.0f, 0.7f, 0.7f));\n            ImGui::PushStyleColor(ImGuiCol_ButtonActive, (ImVec4)ImColor::HSV(i / 7.0f, 0.8f, 0.8f));\n            ImGui::Button(\"Click\");\n            ImGui::PopStyleColor(3);\n            ImGui::PopID();\n        }\n\n        // Use AlignTextToFramePadding() to align text baseline to the baseline of framed widgets elements\n        // (otherwise a Text+SameLine+Button sequence will have the text a little too high by default!)\n        // See 'Demo->Layout->Text Baseline Alignment' for details.\n        ImGui::AlignTextToFramePadding();\n        ImGui::Text(\"Hold to repeat:\");\n        ImGui::SameLine();\n\n        // Arrow buttons with Repeater\n        IMGUI_DEMO_MARKER(\"Widgets/Basic/Buttons (Repeating)\");\n        static int counter = 0;\n        float spacing = ImGui::GetStyle().ItemInnerSpacing.x;\n        ImGui::PushItemFlag(ImGuiItemFlags_ButtonRepeat, true);\n        if (ImGui::ArrowButton(\"##left\", ImGuiDir_Left)) { counter--; }\n        ImGui::SameLine(0.0f, spacing);\n        if (ImGui::ArrowButton(\"##right\", ImGuiDir_Right)) { counter++; }\n        ImGui::PopItemFlag();\n        ImGui::SameLine();\n        ImGui::Text(\"%d\", counter);\n\n        ImGui::Button(\"Tooltip\");\n        ImGui::SetItemTooltip(\"I am a tooltip\");\n\n        ImGui::LabelText(\"label\", \"Value\");\n\n        ImGui::SeparatorText(\"Inputs\");\n\n        {\n            // To wire InputText() with std::string or any other custom string type,\n            // see the \"Text Input > Resize Callback\" section of this demo, and the misc/cpp/imgui_stdlib.h file.\n            IMGUI_DEMO_MARKER(\"Widgets/Basic/InputText\");\n            static char str0[128] = \"Hello, world!\";\n            ImGui::InputText(\"input text\", str0, IM_ARRAYSIZE(str0));\n            ImGui::SameLine(); HelpMarker(\n                \"USER:\\n\"\n                \"Hold SHIFT or use mouse to select text.\\n\"\n                \"CTRL+Left/Right to word jump.\\n\"\n                \"CTRL+A or Double-Click to select all.\\n\"\n                \"CTRL+X,CTRL+C,CTRL+V for clipboard.\\n\"\n                \"CTRL+Z to undo, CTRL+Y/CTRL+SHIFT+Z to redo.\\n\"\n                \"ESCAPE to revert.\\n\\n\"\n                \"PROGRAMMER:\\n\"\n                \"You can use the ImGuiInputTextFlags_CallbackResize facility if you need to wire InputText() \"\n                \"to a dynamic string type. See misc/cpp/imgui_stdlib.h for an example (this is not demonstrated \"\n                \"in imgui_demo.cpp).\");\n\n            static char str1[128] = \"\";\n            ImGui::InputTextWithHint(\"input text (w/ hint)\", \"enter text here\", str1, IM_ARRAYSIZE(str1));\n\n            IMGUI_DEMO_MARKER(\"Widgets/Basic/InputInt, InputFloat\");\n            static int i0 = 123;\n            ImGui::InputInt(\"input int\", &i0);\n\n            static float f0 = 0.001f;\n            ImGui::InputFloat(\"input float\", &f0, 0.01f, 1.0f, \"%.3f\");\n\n            static double d0 = 999999.00000001;\n            ImGui::InputDouble(\"input double\", &d0, 0.01f, 1.0f, \"%.8f\");\n\n            static float f1 = 1.e10f;\n            ImGui::InputFloat(\"input scientific\", &f1, 0.0f, 0.0f, \"%e\");\n            ImGui::SameLine(); HelpMarker(\n                \"You can input value using the scientific notation,\\n\"\n                \"  e.g. \\\"1e+8\\\" becomes \\\"100000000\\\".\");\n\n            static float vec4a[4] = { 0.10f, 0.20f, 0.30f, 0.44f };\n            ImGui::InputFloat3(\"input float3\", vec4a);\n        }\n\n        ImGui::SeparatorText(\"Drags\");\n\n        {\n            IMGUI_DEMO_MARKER(\"Widgets/Basic/DragInt, DragFloat\");\n            static int i1 = 50, i2 = 42, i3 = 128;\n            ImGui::DragInt(\"drag int\", &i1, 1);\n            ImGui::SameLine(); HelpMarker(\n                \"Click and drag to edit value.\\n\"\n                \"Hold SHIFT/ALT for faster/slower edit.\\n\"\n                \"Double-click or CTRL+click to input value.\");\n            ImGui::DragInt(\"drag int 0..100\", &i2, 1, 0, 100, \"%d%%\", ImGuiSliderFlags_AlwaysClamp);\n            ImGui::DragInt(\"drag int wrap 100..200\", &i3, 1, 100, 200, \"%d\", ImGuiSliderFlags_WrapAround);\n\n            static float f1 = 1.00f, f2 = 0.0067f;\n            ImGui::DragFloat(\"drag float\", &f1, 0.005f);\n            ImGui::DragFloat(\"drag small float\", &f2, 0.0001f, 0.0f, 0.0f, \"%.06f ns\");\n            //ImGui::DragFloat(\"drag wrap -1..1\", &f3, 0.005f, -1.0f, 1.0f, NULL, ImGuiSliderFlags_WrapAround);\n        }\n\n        ImGui::SeparatorText(\"Sliders\");\n\n        {\n            IMGUI_DEMO_MARKER(\"Widgets/Basic/SliderInt, SliderFloat\");\n            static int i1 = 0;\n            ImGui::SliderInt(\"slider int\", &i1, -1, 3);\n            ImGui::SameLine(); HelpMarker(\"CTRL+click to input value.\");\n\n            static float f1 = 0.123f, f2 = 0.0f;\n            ImGui::SliderFloat(\"slider float\", &f1, 0.0f, 1.0f, \"ratio = %.3f\");\n            ImGui::SliderFloat(\"slider float (log)\", &f2, -10.0f, 10.0f, \"%.4f\", ImGuiSliderFlags_Logarithmic);\n\n            IMGUI_DEMO_MARKER(\"Widgets/Basic/SliderAngle\");\n            static float angle = 0.0f;\n            ImGui::SliderAngle(\"slider angle\", &angle);\n\n            // Using the format string to display a name instead of an integer.\n            // Here we completely omit '%d' from the format string, so it'll only display a name.\n            // This technique can also be used with DragInt().\n            IMGUI_DEMO_MARKER(\"Widgets/Basic/Slider (enum)\");\n            enum Element { Element_Fire, Element_Earth, Element_Air, Element_Water, Element_COUNT };\n            static int elem = Element_Fire;\n            const char* elems_names[Element_COUNT] = { \"Fire\", \"Earth\", \"Air\", \"Water\" };\n            const char* elem_name = (elem >= 0 && elem < Element_COUNT) ? elems_names[elem] : \"Unknown\";\n            ImGui::SliderInt(\"slider enum\", &elem, 0, Element_COUNT - 1, elem_name); // Use ImGuiSliderFlags_NoInput flag to disable CTRL+Click here.\n            ImGui::SameLine(); HelpMarker(\"Using the format string parameter to display a name instead of the underlying integer.\");\n        }\n\n        ImGui::SeparatorText(\"Selectors/Pickers\");\n\n        {\n            IMGUI_DEMO_MARKER(\"Widgets/Basic/ColorEdit3, ColorEdit4\");\n            static float col1[3] = { 1.0f, 0.0f, 0.2f };\n            static float col2[4] = { 0.4f, 0.7f, 0.0f, 0.5f };\n            ImGui::ColorEdit3(\"color 1\", col1);\n            ImGui::SameLine(); HelpMarker(\n                \"Click on the color square to open a color picker.\\n\"\n                \"Click and hold to use drag and drop.\\n\"\n                \"Right-click on the color square to show options.\\n\"\n                \"CTRL+click on individual component to input value.\\n\");\n\n            ImGui::ColorEdit4(\"color 2\", col2);\n        }\n\n        {\n            // Using the _simplified_ one-liner Combo() api here\n            // See \"Combo\" section for examples of how to use the more flexible BeginCombo()/EndCombo() api.\n            IMGUI_DEMO_MARKER(\"Widgets/Basic/Combo\");\n            const char* items[] = { \"AAAA\", \"BBBB\", \"CCCC\", \"DDDD\", \"EEEE\", \"FFFF\", \"GGGG\", \"HHHH\", \"IIIIIII\", \"JJJJ\", \"KKKKKKK\" };\n            static int item_current = 0;\n            ImGui::Combo(\"combo\", &item_current, items, IM_ARRAYSIZE(items));\n            ImGui::SameLine(); HelpMarker(\n                \"Using the simplified one-liner Combo API here.\\n\"\n                \"Refer to the \\\"Combo\\\" section below for an explanation of how to use the more flexible and general BeginCombo/EndCombo API.\");\n        }\n\n        {\n            // Using the _simplified_ one-liner ListBox() api here\n            // See \"List boxes\" section for examples of how to use the more flexible BeginListBox()/EndListBox() api.\n            IMGUI_DEMO_MARKER(\"Widgets/Basic/ListBox\");\n            const char* items[] = { \"Apple\", \"Banana\", \"Cherry\", \"Kiwi\", \"Mango\", \"Orange\", \"Pineapple\", \"Strawberry\", \"Watermelon\" };\n            static int item_current = 1;\n            ImGui::ListBox(\"listbox\", &item_current, items, IM_ARRAYSIZE(items), 4);\n            ImGui::SameLine(); HelpMarker(\n                \"Using the simplified one-liner ListBox API here.\\n\"\n                \"Refer to the \\\"List boxes\\\" section below for an explanation of how to use the more flexible and general BeginListBox/EndListBox API.\");\n        }\n\n        // Testing ImGuiOnceUponAFrame helper.\n        //static ImGuiOnceUponAFrame once;\n        //for (int i = 0; i < 5; i++)\n        //    if (once)\n        //        ImGui::Text(\"This will be displayed only once.\");\n\n        ImGui::TreePop();\n    }\n}\n\n//-----------------------------------------------------------------------------\n// [SECTION] DemoWindowWidgetsBullets()\n//-----------------------------------------------------------------------------\n\nstatic void DemoWindowWidgetsBullets()\n{\n    IMGUI_DEMO_MARKER(\"Widgets/Bullets\");\n    if (ImGui::TreeNode(\"Bullets\"))\n    {\n        ImGui::BulletText(\"Bullet point 1\");\n        ImGui::BulletText(\"Bullet point 2\\nOn multiple lines\");\n        if (ImGui::TreeNode(\"Tree node\"))\n        {\n            ImGui::BulletText(\"Another bullet point\");\n            ImGui::TreePop();\n        }\n        ImGui::Bullet(); ImGui::Text(\"Bullet point 3 (two calls)\");\n        ImGui::Bullet(); ImGui::SmallButton(\"Button\");\n        ImGui::TreePop();\n    }\n}\n\n//-----------------------------------------------------------------------------\n// [SECTION] DemoWindowWidgetsCollapsingHeaders()\n//-----------------------------------------------------------------------------\n\nstatic void DemoWindowWidgetsCollapsingHeaders()\n{\n    IMGUI_DEMO_MARKER(\"Widgets/Collapsing Headers\");\n    if (ImGui::TreeNode(\"Collapsing Headers\"))\n    {\n        static bool closable_group = true;\n        ImGui::Checkbox(\"Show 2nd header\", &closable_group);\n        if (ImGui::CollapsingHeader(\"Header\", ImGuiTreeNodeFlags_None))\n        {\n            ImGui::Text(\"IsItemHovered: %d\", ImGui::IsItemHovered());\n            for (int i = 0; i < 5; i++)\n                ImGui::Text(\"Some content %d\", i);\n        }\n        if (ImGui::CollapsingHeader(\"Header with a close button\", &closable_group))\n        {\n            ImGui::Text(\"IsItemHovered: %d\", ImGui::IsItemHovered());\n            for (int i = 0; i < 5; i++)\n                ImGui::Text(\"More content %d\", i);\n        }\n        /*\n        if (ImGui::CollapsingHeader(\"Header with a bullet\", ImGuiTreeNodeFlags_Bullet))\n            ImGui::Text(\"IsItemHovered: %d\", ImGui::IsItemHovered());\n        */\n        ImGui::TreePop();\n    }\n}\n\n//-----------------------------------------------------------------------------\n// [SECTION] DemoWindowWidgetsColorAndPickers()\n//-----------------------------------------------------------------------------\n\nstatic void DemoWindowWidgetsColorAndPickers()\n{\n    IMGUI_DEMO_MARKER(\"Widgets/Color\");\n    if (ImGui::TreeNode(\"Color/Picker Widgets\"))\n    {\n        static ImVec4 color = ImVec4(114.0f / 255.0f, 144.0f / 255.0f, 154.0f / 255.0f, 200.0f / 255.0f);\n        static ImGuiColorEditFlags base_flags = ImGuiColorEditFlags_None;\n\n        ImGui::SeparatorText(\"Options\");\n        ImGui::CheckboxFlags(\"ImGuiColorEditFlags_NoAlpha\", &base_flags, ImGuiColorEditFlags_NoAlpha);\n        ImGui::CheckboxFlags(\"ImGuiColorEditFlags_AlphaOpaque\", &base_flags, ImGuiColorEditFlags_AlphaOpaque);\n        ImGui::CheckboxFlags(\"ImGuiColorEditFlags_AlphaNoBg\", &base_flags, ImGuiColorEditFlags_AlphaNoBg);\n        ImGui::CheckboxFlags(\"ImGuiColorEditFlags_AlphaPreviewHalf\", &base_flags, ImGuiColorEditFlags_AlphaPreviewHalf);\n        ImGui::CheckboxFlags(\"ImGuiColorEditFlags_NoDragDrop\", &base_flags, ImGuiColorEditFlags_NoDragDrop);\n        ImGui::CheckboxFlags(\"ImGuiColorEditFlags_NoOptions\", &base_flags, ImGuiColorEditFlags_NoOptions); ImGui::SameLine(); HelpMarker(\"Right-click on the individual color widget to show options.\");\n        ImGui::CheckboxFlags(\"ImGuiColorEditFlags_HDR\", &base_flags, ImGuiColorEditFlags_HDR); ImGui::SameLine(); HelpMarker(\"Currently all this does is to lift the 0..1 limits on dragging widgets.\");\n\n        IMGUI_DEMO_MARKER(\"Widgets/Color/ColorEdit\");\n        ImGui::SeparatorText(\"Inline color editor\");\n        ImGui::Text(\"Color widget:\");\n        ImGui::SameLine(); HelpMarker(\n            \"Click on the color square to open a color picker.\\n\"\n            \"CTRL+click on individual component to input value.\\n\");\n        ImGui::ColorEdit3(\"MyColor##1\", (float*)&color, base_flags);\n\n        IMGUI_DEMO_MARKER(\"Widgets/Color/ColorEdit (HSV, with Alpha)\");\n        ImGui::Text(\"Color widget HSV with Alpha:\");\n        ImGui::ColorEdit4(\"MyColor##2\", (float*)&color, ImGuiColorEditFlags_DisplayHSV | base_flags);\n\n        IMGUI_DEMO_MARKER(\"Widgets/Color/ColorEdit (float display)\");\n        ImGui::Text(\"Color widget with Float Display:\");\n        ImGui::ColorEdit4(\"MyColor##2f\", (float*)&color, ImGuiColorEditFlags_Float | base_flags);\n\n        IMGUI_DEMO_MARKER(\"Widgets/Color/ColorButton (with Picker)\");\n        ImGui::Text(\"Color button with Picker:\");\n        ImGui::SameLine(); HelpMarker(\n            \"With the ImGuiColorEditFlags_NoInputs flag you can hide all the slider/text inputs.\\n\"\n            \"With the ImGuiColorEditFlags_NoLabel flag you can pass a non-empty label which will only \"\n            \"be used for the tooltip and picker popup.\");\n        ImGui::ColorEdit4(\"MyColor##3\", (float*)&color, ImGuiColorEditFlags_NoInputs | ImGuiColorEditFlags_NoLabel | base_flags);\n\n        IMGUI_DEMO_MARKER(\"Widgets/Color/ColorButton (with custom Picker popup)\");\n        ImGui::Text(\"Color button with Custom Picker Popup:\");\n\n        // Generate a default palette. The palette will persist and can be edited.\n        static bool saved_palette_init = true;\n        static ImVec4 saved_palette[32] = {};\n        if (saved_palette_init)\n        {\n            for (int n = 0; n < IM_ARRAYSIZE(saved_palette); n++)\n            {\n                ImGui::ColorConvertHSVtoRGB(n / 31.0f, 0.8f, 0.8f,\n                    saved_palette[n].x, saved_palette[n].y, saved_palette[n].z);\n                saved_palette[n].w = 1.0f; // Alpha\n            }\n            saved_palette_init = false;\n        }\n\n        static ImVec4 backup_color;\n        bool open_popup = ImGui::ColorButton(\"MyColor##3b\", color, base_flags);\n        ImGui::SameLine(0, ImGui::GetStyle().ItemInnerSpacing.x);\n        open_popup |= ImGui::Button(\"Palette\");\n        if (open_popup)\n        {\n            ImGui::OpenPopup(\"mypicker\");\n            backup_color = color;\n        }\n        if (ImGui::BeginPopup(\"mypicker\"))\n        {\n            ImGui::Text(\"MY CUSTOM COLOR PICKER WITH AN AMAZING PALETTE!\");\n            ImGui::Separator();\n            ImGui::ColorPicker4(\"##picker\", (float*)&color, base_flags | ImGuiColorEditFlags_NoSidePreview | ImGuiColorEditFlags_NoSmallPreview);\n            ImGui::SameLine();\n\n            ImGui::BeginGroup(); // Lock X position\n            ImGui::Text(\"Current\");\n            ImGui::ColorButton(\"##current\", color, ImGuiColorEditFlags_NoPicker | ImGuiColorEditFlags_AlphaPreviewHalf, ImVec2(60, 40));\n            ImGui::Text(\"Previous\");\n            if (ImGui::ColorButton(\"##previous\", backup_color, ImGuiColorEditFlags_NoPicker | ImGuiColorEditFlags_AlphaPreviewHalf, ImVec2(60, 40)))\n                color = backup_color;\n            ImGui::Separator();\n            ImGui::Text(\"Palette\");\n            for (int n = 0; n < IM_ARRAYSIZE(saved_palette); n++)\n            {\n                ImGui::PushID(n);\n                if ((n % 8) != 0)\n                    ImGui::SameLine(0.0f, ImGui::GetStyle().ItemSpacing.y);\n\n                ImGuiColorEditFlags palette_button_flags = ImGuiColorEditFlags_NoAlpha | ImGuiColorEditFlags_NoPicker | ImGuiColorEditFlags_NoTooltip;\n                if (ImGui::ColorButton(\"##palette\", saved_palette[n], palette_button_flags, ImVec2(20, 20)))\n                    color = ImVec4(saved_palette[n].x, saved_palette[n].y, saved_palette[n].z, color.w); // Preserve alpha!\n\n                // Allow user to drop colors into each palette entry. Note that ColorButton() is already a\n                // drag source by default, unless specifying the ImGuiColorEditFlags_NoDragDrop flag.\n                if (ImGui::BeginDragDropTarget())\n                {\n                    if (const ImGuiPayload* payload = ImGui::AcceptDragDropPayload(IMGUI_PAYLOAD_TYPE_COLOR_3F))\n                        memcpy((float*)&saved_palette[n], payload->Data, sizeof(float) * 3);\n                    if (const ImGuiPayload* payload = ImGui::AcceptDragDropPayload(IMGUI_PAYLOAD_TYPE_COLOR_4F))\n                        memcpy((float*)&saved_palette[n], payload->Data, sizeof(float) * 4);\n                    ImGui::EndDragDropTarget();\n                }\n\n                ImGui::PopID();\n            }\n            ImGui::EndGroup();\n            ImGui::EndPopup();\n        }\n\n        IMGUI_DEMO_MARKER(\"Widgets/Color/ColorButton (simple)\");\n        ImGui::Text(\"Color button only:\");\n        static bool no_border = false;\n        ImGui::Checkbox(\"ImGuiColorEditFlags_NoBorder\", &no_border);\n        ImGui::ColorButton(\"MyColor##3c\", *(ImVec4*)&color, base_flags | (no_border ? ImGuiColorEditFlags_NoBorder : 0), ImVec2(80, 80));\n\n        IMGUI_DEMO_MARKER(\"Widgets/Color/ColorPicker\");\n        ImGui::SeparatorText(\"Color picker\");\n\n        static bool ref_color = false;\n        static ImVec4 ref_color_v(1.0f, 0.0f, 1.0f, 0.5f);\n        static int picker_mode = 0;\n        static int display_mode = 0;\n        static ImGuiColorEditFlags color_picker_flags = ImGuiColorEditFlags_AlphaBar;\n\n        ImGui::PushID(\"Color picker\");\n        ImGui::CheckboxFlags(\"ImGuiColorEditFlags_NoAlpha\", &color_picker_flags, ImGuiColorEditFlags_NoAlpha);\n        ImGui::CheckboxFlags(\"ImGuiColorEditFlags_AlphaBar\", &color_picker_flags, ImGuiColorEditFlags_AlphaBar);\n        ImGui::CheckboxFlags(\"ImGuiColorEditFlags_NoSidePreview\", &color_picker_flags, ImGuiColorEditFlags_NoSidePreview);\n        if (color_picker_flags & ImGuiColorEditFlags_NoSidePreview)\n        {\n            ImGui::SameLine();\n            ImGui::Checkbox(\"With Ref Color\", &ref_color);\n            if (ref_color)\n            {\n                ImGui::SameLine();\n                ImGui::ColorEdit4(\"##RefColor\", &ref_color_v.x, ImGuiColorEditFlags_NoInputs | base_flags);\n            }\n        }\n\n        ImGui::Combo(\"Picker Mode\", &picker_mode, \"Auto/Current\\0ImGuiColorEditFlags_PickerHueBar\\0ImGuiColorEditFlags_PickerHueWheel\\0\");\n        ImGui::SameLine(); HelpMarker(\"When not specified explicitly, user can right-click the picker to change mode.\");\n\n        ImGui::Combo(\"Display Mode\", &display_mode, \"Auto/Current\\0ImGuiColorEditFlags_NoInputs\\0ImGuiColorEditFlags_DisplayRGB\\0ImGuiColorEditFlags_DisplayHSV\\0ImGuiColorEditFlags_DisplayHex\\0\");\n        ImGui::SameLine(); HelpMarker(\n            \"ColorEdit defaults to displaying RGB inputs if you don't specify a display mode, \"\n            \"but the user can change it with a right-click on those inputs.\\n\\nColorPicker defaults to displaying RGB+HSV+Hex \"\n            \"if you don't specify a display mode.\\n\\nYou can change the defaults using SetColorEditOptions().\");\n\n        ImGuiColorEditFlags flags = base_flags | color_picker_flags;\n        if (picker_mode == 1)  flags |= ImGuiColorEditFlags_PickerHueBar;\n        if (picker_mode == 2)  flags |= ImGuiColorEditFlags_PickerHueWheel;\n        if (display_mode == 1) flags |= ImGuiColorEditFlags_NoInputs;       // Disable all RGB/HSV/Hex displays\n        if (display_mode == 2) flags |= ImGuiColorEditFlags_DisplayRGB;     // Override display mode\n        if (display_mode == 3) flags |= ImGuiColorEditFlags_DisplayHSV;\n        if (display_mode == 4) flags |= ImGuiColorEditFlags_DisplayHex;\n        ImGui::ColorPicker4(\"MyColor##4\", (float*)&color, flags, ref_color ? &ref_color_v.x : NULL);\n\n        ImGui::Text(\"Set defaults in code:\");\n        ImGui::SameLine(); HelpMarker(\n            \"SetColorEditOptions() is designed to allow you to set boot-time default.\\n\"\n            \"We don't have Push/Pop functions because you can force options on a per-widget basis if needed, \"\n            \"and the user can change non-forced ones with the options menu.\\nWe don't have a getter to avoid \"\n            \"encouraging you to persistently save values that aren't forward-compatible.\");\n        if (ImGui::Button(\"Default: Uint8 + HSV + Hue Bar\"))\n            ImGui::SetColorEditOptions(ImGuiColorEditFlags_Uint8 | ImGuiColorEditFlags_DisplayHSV | ImGuiColorEditFlags_PickerHueBar);\n        if (ImGui::Button(\"Default: Float + HDR + Hue Wheel\"))\n            ImGui::SetColorEditOptions(ImGuiColorEditFlags_Float | ImGuiColorEditFlags_HDR | ImGuiColorEditFlags_PickerHueWheel);\n\n        // Always display a small version of both types of pickers\n        // (that's in order to make it more visible in the demo to people who are skimming quickly through it)\n        ImGui::Text(\"Both types:\");\n        float w = (ImGui::GetContentRegionAvail().x - ImGui::GetStyle().ItemSpacing.y) * 0.40f;\n        ImGui::SetNextItemWidth(w);\n        ImGui::ColorPicker3(\"##MyColor##5\", (float*)&color, ImGuiColorEditFlags_PickerHueBar | ImGuiColorEditFlags_NoSidePreview | ImGuiColorEditFlags_NoInputs | ImGuiColorEditFlags_NoAlpha);\n        ImGui::SameLine();\n        ImGui::SetNextItemWidth(w);\n        ImGui::ColorPicker3(\"##MyColor##6\", (float*)&color, ImGuiColorEditFlags_PickerHueWheel | ImGuiColorEditFlags_NoSidePreview | ImGuiColorEditFlags_NoInputs | ImGuiColorEditFlags_NoAlpha);\n        ImGui::PopID();\n\n        // HSV encoded support (to avoid RGB<>HSV round trips and singularities when S==0 or V==0)\n        static ImVec4 color_hsv(0.23f, 1.0f, 1.0f, 1.0f); // Stored as HSV!\n        ImGui::Spacing();\n        ImGui::Text(\"HSV encoded colors\");\n        ImGui::SameLine(); HelpMarker(\n            \"By default, colors are given to ColorEdit and ColorPicker in RGB, but ImGuiColorEditFlags_InputHSV \"\n            \"allows you to store colors as HSV and pass them to ColorEdit and ColorPicker as HSV. This comes with the \"\n            \"added benefit that you can manipulate hue values with the picker even when saturation or value are zero.\");\n        ImGui::Text(\"Color widget with InputHSV:\");\n        ImGui::ColorEdit4(\"HSV shown as RGB##1\", (float*)&color_hsv, ImGuiColorEditFlags_DisplayRGB | ImGuiColorEditFlags_InputHSV | ImGuiColorEditFlags_Float);\n        ImGui::ColorEdit4(\"HSV shown as HSV##1\", (float*)&color_hsv, ImGuiColorEditFlags_DisplayHSV | ImGuiColorEditFlags_InputHSV | ImGuiColorEditFlags_Float);\n        ImGui::DragFloat4(\"Raw HSV values\", (float*)&color_hsv, 0.01f, 0.0f, 1.0f);\n\n        ImGui::TreePop();\n    }\n}\n\n//-----------------------------------------------------------------------------\n// [SECTION] DemoWindowWidgetsComboBoxes()\n//-----------------------------------------------------------------------------\n\nstatic void DemoWindowWidgetsComboBoxes()\n{\n    IMGUI_DEMO_MARKER(\"Widgets/Combo\");\n    if (ImGui::TreeNode(\"Combo\"))\n    {\n        // Combo Boxes are also called \"Dropdown\" in other systems\n        // Expose flags as checkbox for the demo\n        static ImGuiComboFlags flags = 0;\n        ImGui::CheckboxFlags(\"ImGuiComboFlags_PopupAlignLeft\", &flags, ImGuiComboFlags_PopupAlignLeft);\n        ImGui::SameLine(); HelpMarker(\"Only makes a difference if the popup is larger than the combo\");\n        if (ImGui::CheckboxFlags(\"ImGuiComboFlags_NoArrowButton\", &flags, ImGuiComboFlags_NoArrowButton))\n            flags &= ~ImGuiComboFlags_NoPreview;     // Clear incompatible flags\n        if (ImGui::CheckboxFlags(\"ImGuiComboFlags_NoPreview\", &flags, ImGuiComboFlags_NoPreview))\n            flags &= ~(ImGuiComboFlags_NoArrowButton | ImGuiComboFlags_WidthFitPreview); // Clear incompatible flags\n        if (ImGui::CheckboxFlags(\"ImGuiComboFlags_WidthFitPreview\", &flags, ImGuiComboFlags_WidthFitPreview))\n            flags &= ~ImGuiComboFlags_NoPreview;\n\n        // Override default popup height\n        if (ImGui::CheckboxFlags(\"ImGuiComboFlags_HeightSmall\", &flags, ImGuiComboFlags_HeightSmall))\n            flags &= ~(ImGuiComboFlags_HeightMask_ & ~ImGuiComboFlags_HeightSmall);\n        if (ImGui::CheckboxFlags(\"ImGuiComboFlags_HeightRegular\", &flags, ImGuiComboFlags_HeightRegular))\n            flags &= ~(ImGuiComboFlags_HeightMask_ & ~ImGuiComboFlags_HeightRegular);\n        if (ImGui::CheckboxFlags(\"ImGuiComboFlags_HeightLargest\", &flags, ImGuiComboFlags_HeightLargest))\n            flags &= ~(ImGuiComboFlags_HeightMask_ & ~ImGuiComboFlags_HeightLargest);\n\n        // Using the generic BeginCombo() API, you have full control over how to display the combo contents.\n        // (your selection data could be an index, a pointer to the object, an id for the object, a flag intrusively\n        // stored in the object itself, etc.)\n        const char* items[] = { \"AAAA\", \"BBBB\", \"CCCC\", \"DDDD\", \"EEEE\", \"FFFF\", \"GGGG\", \"HHHH\", \"IIII\", \"JJJJ\", \"KKKK\", \"LLLLLLL\", \"MMMM\", \"OOOOOOO\" };\n        static int item_selected_idx = 0; // Here we store our selection data as an index.\n\n        // Pass in the preview value visible before opening the combo (it could technically be different contents or not pulled from items[])\n        const char* combo_preview_value = items[item_selected_idx];\n        if (ImGui::BeginCombo(\"combo 1\", combo_preview_value, flags))\n        {\n            for (int n = 0; n < IM_ARRAYSIZE(items); n++)\n            {\n                const bool is_selected = (item_selected_idx == n);\n                if (ImGui::Selectable(items[n], is_selected))\n                    item_selected_idx = n;\n\n                // Set the initial focus when opening the combo (scrolling + keyboard navigation focus)\n                if (is_selected)\n                    ImGui::SetItemDefaultFocus();\n            }\n            ImGui::EndCombo();\n        }\n\n        // Show case embedding a filter using a simple trick: displaying the filter inside combo contents.\n        // See https://github.com/ocornut/imgui/issues/718 for advanced/esoteric alternatives.\n        if (ImGui::BeginCombo(\"combo 2 (w/ filter)\", combo_preview_value, flags))\n        {\n            static ImGuiTextFilter filter;\n            if (ImGui::IsWindowAppearing())\n            {\n                ImGui::SetKeyboardFocusHere();\n                filter.Clear();\n            }\n            ImGui::SetNextItemShortcut(ImGuiMod_Ctrl | ImGuiKey_F);\n            filter.Draw(\"##Filter\", -FLT_MIN);\n\n            for (int n = 0; n < IM_ARRAYSIZE(items); n++)\n            {\n                const bool is_selected = (item_selected_idx == n);\n                if (filter.PassFilter(items[n]))\n                    if (ImGui::Selectable(items[n], is_selected))\n                        item_selected_idx = n;\n            }\n            ImGui::EndCombo();\n        }\n\n        ImGui::Spacing();\n        ImGui::SeparatorText(\"One-liner variants\");\n        HelpMarker(\"The Combo() function is not greatly useful apart from cases were you want to embed all options in a single strings.\\nFlags above don't apply to this section.\");\n\n        // Simplified one-liner Combo() API, using values packed in a single constant string\n        // This is a convenience for when the selection set is small and known at compile-time.\n        static int item_current_2 = 0;\n        ImGui::Combo(\"combo 3 (one-liner)\", &item_current_2, \"aaaa\\0bbbb\\0cccc\\0dddd\\0eeee\\0\\0\");\n\n        // Simplified one-liner Combo() using an array of const char*\n        // This is not very useful (may obsolete): prefer using BeginCombo()/EndCombo() for full control.\n        static int item_current_3 = -1; // If the selection isn't within 0..count, Combo won't display a preview\n        ImGui::Combo(\"combo 4 (array)\", &item_current_3, items, IM_ARRAYSIZE(items));\n\n        // Simplified one-liner Combo() using an accessor function\n        static int item_current_4 = 0;\n        ImGui::Combo(\"combo 5 (function)\", &item_current_4, [](void* data, int n) { return ((const char**)data)[n]; }, items, IM_ARRAYSIZE(items));\n\n        ImGui::TreePop();\n    }\n}\n\n//-----------------------------------------------------------------------------\n// [SECTION] DemoWindowWidgetsDataTypes()\n//-----------------------------------------------------------------------------\n\nstatic void DemoWindowWidgetsDataTypes()\n{\n    IMGUI_DEMO_MARKER(\"Widgets/Data Types\");\n    if (ImGui::TreeNode(\"Data Types\"))\n    {\n        // DragScalar/InputScalar/SliderScalar functions allow various data types\n        // - signed/unsigned\n        // - 8/16/32/64-bits\n        // - integer/float/double\n        // To avoid polluting the public API with all possible combinations, we use the ImGuiDataType enum\n        // to pass the type, and passing all arguments by pointer.\n        // This is the reason the test code below creates local variables to hold \"zero\" \"one\" etc. for each type.\n        // In practice, if you frequently use a given type that is not covered by the normal API entry points,\n        // you can wrap it yourself inside a 1 line function which can take typed argument as value instead of void*,\n        // and then pass their address to the generic function. For example:\n        //   bool MySliderU64(const char *label, u64* value, u64 min = 0, u64 max = 0, const char* format = \"%lld\")\n        //   {\n        //      return SliderScalar(label, ImGuiDataType_U64, value, &min, &max, format);\n        //   }\n\n        // Setup limits (as helper variables so we can take their address, as explained above)\n        // Note: SliderScalar() functions have a maximum usable range of half the natural type maximum, hence the /2.\n        #ifndef LLONG_MIN\n        ImS64 LLONG_MIN = -9223372036854775807LL - 1;\n        ImS64 LLONG_MAX = 9223372036854775807LL;\n        ImU64 ULLONG_MAX = (2ULL * 9223372036854775807LL + 1);\n        #endif\n        const char    s8_zero  = 0,   s8_one  = 1,   s8_fifty  = 50, s8_min  = -128,        s8_max = 127;\n        const ImU8    u8_zero  = 0,   u8_one  = 1,   u8_fifty  = 50, u8_min  = 0,           u8_max = 255;\n        const short   s16_zero = 0,   s16_one = 1,   s16_fifty = 50, s16_min = -32768,      s16_max = 32767;\n        const ImU16   u16_zero = 0,   u16_one = 1,   u16_fifty = 50, u16_min = 0,           u16_max = 65535;\n        const ImS32   s32_zero = 0,   s32_one = 1,   s32_fifty = 50, s32_min = INT_MIN/2,   s32_max = INT_MAX/2,    s32_hi_a = INT_MAX/2 - 100,    s32_hi_b = INT_MAX/2;\n        const ImU32   u32_zero = 0,   u32_one = 1,   u32_fifty = 50, u32_min = 0,           u32_max = UINT_MAX/2,   u32_hi_a = UINT_MAX/2 - 100,   u32_hi_b = UINT_MAX/2;\n        const ImS64   s64_zero = 0,   s64_one = 1,   s64_fifty = 50, s64_min = LLONG_MIN/2, s64_max = LLONG_MAX/2,  s64_hi_a = LLONG_MAX/2 - 100,  s64_hi_b = LLONG_MAX/2;\n        const ImU64   u64_zero = 0,   u64_one = 1,   u64_fifty = 50, u64_min = 0,           u64_max = ULLONG_MAX/2, u64_hi_a = ULLONG_MAX/2 - 100, u64_hi_b = ULLONG_MAX/2;\n        const float   f32_zero = 0.f, f32_one = 1.f, f32_lo_a = -10000000000.0f, f32_hi_a = +10000000000.0f;\n        const double  f64_zero = 0.,  f64_one = 1.,  f64_lo_a = -1000000000000000.0, f64_hi_a = +1000000000000000.0;\n\n        // State\n        static char   s8_v  = 127;\n        static ImU8   u8_v  = 255;\n        static short  s16_v = 32767;\n        static ImU16  u16_v = 65535;\n        static ImS32  s32_v = -1;\n        static ImU32  u32_v = (ImU32)-1;\n        static ImS64  s64_v = -1;\n        static ImU64  u64_v = (ImU64)-1;\n        static float  f32_v = 0.123f;\n        static double f64_v = 90000.01234567890123456789;\n\n        const float drag_speed = 0.2f;\n        static bool drag_clamp = false;\n        IMGUI_DEMO_MARKER(\"Widgets/Data Types/Drags\");\n        ImGui::SeparatorText(\"Drags\");\n        ImGui::Checkbox(\"Clamp integers to 0..50\", &drag_clamp);\n        ImGui::SameLine(); HelpMarker(\n            \"As with every widget in dear imgui, we never modify values unless there is a user interaction.\\n\"\n            \"You can override the clamping limits by using CTRL+Click to input a value.\");\n        ImGui::DragScalar(\"drag s8\",        ImGuiDataType_S8,     &s8_v,  drag_speed, drag_clamp ? &s8_zero  : NULL, drag_clamp ? &s8_fifty  : NULL);\n        ImGui::DragScalar(\"drag u8\",        ImGuiDataType_U8,     &u8_v,  drag_speed, drag_clamp ? &u8_zero  : NULL, drag_clamp ? &u8_fifty  : NULL, \"%u ms\");\n        ImGui::DragScalar(\"drag s16\",       ImGuiDataType_S16,    &s16_v, drag_speed, drag_clamp ? &s16_zero : NULL, drag_clamp ? &s16_fifty : NULL);\n        ImGui::DragScalar(\"drag u16\",       ImGuiDataType_U16,    &u16_v, drag_speed, drag_clamp ? &u16_zero : NULL, drag_clamp ? &u16_fifty : NULL, \"%u ms\");\n        ImGui::DragScalar(\"drag s32\",       ImGuiDataType_S32,    &s32_v, drag_speed, drag_clamp ? &s32_zero : NULL, drag_clamp ? &s32_fifty : NULL);\n        ImGui::DragScalar(\"drag s32 hex\",   ImGuiDataType_S32,    &s32_v, drag_speed, drag_clamp ? &s32_zero : NULL, drag_clamp ? &s32_fifty : NULL, \"0x%08X\");\n        ImGui::DragScalar(\"drag u32\",       ImGuiDataType_U32,    &u32_v, drag_speed, drag_clamp ? &u32_zero : NULL, drag_clamp ? &u32_fifty : NULL, \"%u ms\");\n        ImGui::DragScalar(\"drag s64\",       ImGuiDataType_S64,    &s64_v, drag_speed, drag_clamp ? &s64_zero : NULL, drag_clamp ? &s64_fifty : NULL);\n        ImGui::DragScalar(\"drag u64\",       ImGuiDataType_U64,    &u64_v, drag_speed, drag_clamp ? &u64_zero : NULL, drag_clamp ? &u64_fifty : NULL);\n        ImGui::DragScalar(\"drag float\",     ImGuiDataType_Float,  &f32_v, 0.005f,  &f32_zero, &f32_one, \"%f\");\n        ImGui::DragScalar(\"drag float log\", ImGuiDataType_Float,  &f32_v, 0.005f,  &f32_zero, &f32_one, \"%f\", ImGuiSliderFlags_Logarithmic);\n        ImGui::DragScalar(\"drag double\",    ImGuiDataType_Double, &f64_v, 0.0005f, &f64_zero, NULL,     \"%.10f grams\");\n        ImGui::DragScalar(\"drag double log\",ImGuiDataType_Double, &f64_v, 0.0005f, &f64_zero, &f64_one, \"0 < %.10f < 1\", ImGuiSliderFlags_Logarithmic);\n\n        IMGUI_DEMO_MARKER(\"Widgets/Data Types/Sliders\");\n        ImGui::SeparatorText(\"Sliders\");\n        ImGui::SliderScalar(\"slider s8 full\",       ImGuiDataType_S8,     &s8_v,  &s8_min,   &s8_max,   \"%d\");\n        ImGui::SliderScalar(\"slider u8 full\",       ImGuiDataType_U8,     &u8_v,  &u8_min,   &u8_max,   \"%u\");\n        ImGui::SliderScalar(\"slider s16 full\",      ImGuiDataType_S16,    &s16_v, &s16_min,  &s16_max,  \"%d\");\n        ImGui::SliderScalar(\"slider u16 full\",      ImGuiDataType_U16,    &u16_v, &u16_min,  &u16_max,  \"%u\");\n        ImGui::SliderScalar(\"slider s32 low\",       ImGuiDataType_S32,    &s32_v, &s32_zero, &s32_fifty,\"%d\");\n        ImGui::SliderScalar(\"slider s32 high\",      ImGuiDataType_S32,    &s32_v, &s32_hi_a, &s32_hi_b, \"%d\");\n        ImGui::SliderScalar(\"slider s32 full\",      ImGuiDataType_S32,    &s32_v, &s32_min,  &s32_max,  \"%d\");\n        ImGui::SliderScalar(\"slider s32 hex\",       ImGuiDataType_S32,    &s32_v, &s32_zero, &s32_fifty, \"0x%04X\");\n        ImGui::SliderScalar(\"slider u32 low\",       ImGuiDataType_U32,    &u32_v, &u32_zero, &u32_fifty,\"%u\");\n        ImGui::SliderScalar(\"slider u32 high\",      ImGuiDataType_U32,    &u32_v, &u32_hi_a, &u32_hi_b, \"%u\");\n        ImGui::SliderScalar(\"slider u32 full\",      ImGuiDataType_U32,    &u32_v, &u32_min,  &u32_max,  \"%u\");\n        ImGui::SliderScalar(\"slider s64 low\",       ImGuiDataType_S64,    &s64_v, &s64_zero, &s64_fifty,\"%\" PRId64);\n        ImGui::SliderScalar(\"slider s64 high\",      ImGuiDataType_S64,    &s64_v, &s64_hi_a, &s64_hi_b, \"%\" PRId64);\n        ImGui::SliderScalar(\"slider s64 full\",      ImGuiDataType_S64,    &s64_v, &s64_min,  &s64_max,  \"%\" PRId64);\n        ImGui::SliderScalar(\"slider u64 low\",       ImGuiDataType_U64,    &u64_v, &u64_zero, &u64_fifty,\"%\" PRIu64 \" ms\");\n        ImGui::SliderScalar(\"slider u64 high\",      ImGuiDataType_U64,    &u64_v, &u64_hi_a, &u64_hi_b, \"%\" PRIu64 \" ms\");\n        ImGui::SliderScalar(\"slider u64 full\",      ImGuiDataType_U64,    &u64_v, &u64_min,  &u64_max,  \"%\" PRIu64 \" ms\");\n        ImGui::SliderScalar(\"slider float low\",     ImGuiDataType_Float,  &f32_v, &f32_zero, &f32_one);\n        ImGui::SliderScalar(\"slider float low log\", ImGuiDataType_Float,  &f32_v, &f32_zero, &f32_one,  \"%.10f\", ImGuiSliderFlags_Logarithmic);\n        ImGui::SliderScalar(\"slider float high\",    ImGuiDataType_Float,  &f32_v, &f32_lo_a, &f32_hi_a, \"%e\");\n        ImGui::SliderScalar(\"slider double low\",    ImGuiDataType_Double, &f64_v, &f64_zero, &f64_one,  \"%.10f grams\");\n        ImGui::SliderScalar(\"slider double low log\",ImGuiDataType_Double, &f64_v, &f64_zero, &f64_one,  \"%.10f\", ImGuiSliderFlags_Logarithmic);\n        ImGui::SliderScalar(\"slider double high\",   ImGuiDataType_Double, &f64_v, &f64_lo_a, &f64_hi_a, \"%e grams\");\n\n        ImGui::SeparatorText(\"Sliders (reverse)\");\n        ImGui::SliderScalar(\"slider s8 reverse\",    ImGuiDataType_S8,   &s8_v,  &s8_max,    &s8_min,   \"%d\");\n        ImGui::SliderScalar(\"slider u8 reverse\",    ImGuiDataType_U8,   &u8_v,  &u8_max,    &u8_min,   \"%u\");\n        ImGui::SliderScalar(\"slider s32 reverse\",   ImGuiDataType_S32,  &s32_v, &s32_fifty, &s32_zero, \"%d\");\n        ImGui::SliderScalar(\"slider u32 reverse\",   ImGuiDataType_U32,  &u32_v, &u32_fifty, &u32_zero, \"%u\");\n        ImGui::SliderScalar(\"slider s64 reverse\",   ImGuiDataType_S64,  &s64_v, &s64_fifty, &s64_zero, \"%\" PRId64);\n        ImGui::SliderScalar(\"slider u64 reverse\",   ImGuiDataType_U64,  &u64_v, &u64_fifty, &u64_zero, \"%\" PRIu64 \" ms\");\n\n        IMGUI_DEMO_MARKER(\"Widgets/Data Types/Inputs\");\n        static bool inputs_step = true;\n        static ImGuiInputTextFlags flags = ImGuiInputTextFlags_None;\n        ImGui::SeparatorText(\"Inputs\");\n        ImGui::Checkbox(\"Show step buttons\", &inputs_step);\n        ImGui::CheckboxFlags(\"ImGuiInputTextFlags_ReadOnly\", &flags, ImGuiInputTextFlags_ReadOnly);\n        ImGui::CheckboxFlags(\"ImGuiInputTextFlags_ParseEmptyRefVal\", &flags, ImGuiInputTextFlags_ParseEmptyRefVal);\n        ImGui::CheckboxFlags(\"ImGuiInputTextFlags_DisplayEmptyRefVal\", &flags, ImGuiInputTextFlags_DisplayEmptyRefVal);\n        ImGui::InputScalar(\"input s8\",      ImGuiDataType_S8,     &s8_v,  inputs_step ? &s8_one  : NULL, NULL, \"%d\", flags);\n        ImGui::InputScalar(\"input u8\",      ImGuiDataType_U8,     &u8_v,  inputs_step ? &u8_one  : NULL, NULL, \"%u\", flags);\n        ImGui::InputScalar(\"input s16\",     ImGuiDataType_S16,    &s16_v, inputs_step ? &s16_one : NULL, NULL, \"%d\", flags);\n        ImGui::InputScalar(\"input u16\",     ImGuiDataType_U16,    &u16_v, inputs_step ? &u16_one : NULL, NULL, \"%u\", flags);\n        ImGui::InputScalar(\"input s32\",     ImGuiDataType_S32,    &s32_v, inputs_step ? &s32_one : NULL, NULL, \"%d\", flags);\n        ImGui::InputScalar(\"input s32 hex\", ImGuiDataType_S32,    &s32_v, inputs_step ? &s32_one : NULL, NULL, \"%04X\", flags);\n        ImGui::InputScalar(\"input u32\",     ImGuiDataType_U32,    &u32_v, inputs_step ? &u32_one : NULL, NULL, \"%u\", flags);\n        ImGui::InputScalar(\"input u32 hex\", ImGuiDataType_U32,    &u32_v, inputs_step ? &u32_one : NULL, NULL, \"%08X\", flags);\n        ImGui::InputScalar(\"input s64\",     ImGuiDataType_S64,    &s64_v, inputs_step ? &s64_one : NULL, NULL, NULL, flags);\n        ImGui::InputScalar(\"input u64\",     ImGuiDataType_U64,    &u64_v, inputs_step ? &u64_one : NULL, NULL, NULL, flags);\n        ImGui::InputScalar(\"input float\",   ImGuiDataType_Float,  &f32_v, inputs_step ? &f32_one : NULL, NULL, NULL, flags);\n        ImGui::InputScalar(\"input double\",  ImGuiDataType_Double, &f64_v, inputs_step ? &f64_one : NULL, NULL, NULL, flags);\n\n        ImGui::TreePop();\n    }\n}\n\n//-----------------------------------------------------------------------------\n// [SECTION] DemoWindowWidgetsDisableBlocks()\n//-----------------------------------------------------------------------------\n\nstatic void DemoWindowWidgetsDisableBlocks(ImGuiDemoWindowData* demo_data)\n{\n    IMGUI_DEMO_MARKER(\"Widgets/Disable Blocks\");\n    if (ImGui::TreeNode(\"Disable Blocks\"))\n    {\n        ImGui::Checkbox(\"Disable entire section above\", &demo_data->DisableSections);\n        ImGui::SameLine(); HelpMarker(\"Demonstrate using BeginDisabled()/EndDisabled() across other sections.\");\n        ImGui::TreePop();\n    }\n}\n\n//-----------------------------------------------------------------------------\n// [SECTION] DemoWindowWidgetsDragAndDrop()\n//-----------------------------------------------------------------------------\n\nstatic void DemoWindowWidgetsDragAndDrop()\n{\n    IMGUI_DEMO_MARKER(\"Widgets/Drag and drop\");\n    if (ImGui::TreeNode(\"Drag and Drop\"))\n    {\n        IMGUI_DEMO_MARKER(\"Widgets/Drag and drop/Standard widgets\");\n        if (ImGui::TreeNode(\"Drag and drop in standard widgets\"))\n        {\n            // ColorEdit widgets automatically act as drag source and drag target.\n            // They are using standardized payload strings IMGUI_PAYLOAD_TYPE_COLOR_3F and IMGUI_PAYLOAD_TYPE_COLOR_4F\n            // to allow your own widgets to use colors in their drag and drop interaction.\n            // Also see 'Demo->Widgets->Color/Picker Widgets->Palette' demo.\n            HelpMarker(\"You can drag from the color squares.\");\n            static float col1[3] = { 1.0f, 0.0f, 0.2f };\n            static float col2[4] = { 0.4f, 0.7f, 0.0f, 0.5f };\n            ImGui::ColorEdit3(\"color 1\", col1);\n            ImGui::ColorEdit4(\"color 2\", col2);\n            ImGui::TreePop();\n        }\n\n        IMGUI_DEMO_MARKER(\"Widgets/Drag and drop/Copy-swap items\");\n        if (ImGui::TreeNode(\"Drag and drop to copy/swap items\"))\n        {\n            enum Mode\n            {\n                Mode_Copy,\n                Mode_Move,\n                Mode_Swap\n            };\n            static int mode = 0;\n            if (ImGui::RadioButton(\"Copy\", mode == Mode_Copy)) { mode = Mode_Copy; } ImGui::SameLine();\n            if (ImGui::RadioButton(\"Move\", mode == Mode_Move)) { mode = Mode_Move; } ImGui::SameLine();\n            if (ImGui::RadioButton(\"Swap\", mode == Mode_Swap)) { mode = Mode_Swap; }\n            static const char* names[9] =\n            {\n                \"Bobby\", \"Beatrice\", \"Betty\",\n                \"Brianna\", \"Barry\", \"Bernard\",\n                \"Bibi\", \"Blaine\", \"Bryn\"\n            };\n            for (int n = 0; n < IM_ARRAYSIZE(names); n++)\n            {\n                ImGui::PushID(n);\n                if ((n % 3) != 0)\n                    ImGui::SameLine();\n                ImGui::Button(names[n], ImVec2(60, 60));\n\n                // Our buttons are both drag sources and drag targets here!\n                if (ImGui::BeginDragDropSource(ImGuiDragDropFlags_None))\n                {\n                    // Set payload to carry the index of our item (could be anything)\n                    ImGui::SetDragDropPayload(\"DND_DEMO_CELL\", &n, sizeof(int));\n\n                    // Display preview (could be anything, e.g. when dragging an image we could decide to display\n                    // the filename and a small preview of the image, etc.)\n                    if (mode == Mode_Copy) { ImGui::Text(\"Copy %s\", names[n]); }\n                    if (mode == Mode_Move) { ImGui::Text(\"Move %s\", names[n]); }\n                    if (mode == Mode_Swap) { ImGui::Text(\"Swap %s\", names[n]); }\n                    ImGui::EndDragDropSource();\n                }\n                if (ImGui::BeginDragDropTarget())\n                {\n                    if (const ImGuiPayload* payload = ImGui::AcceptDragDropPayload(\"DND_DEMO_CELL\"))\n                    {\n                        IM_ASSERT(payload->DataSize == sizeof(int));\n                        int payload_n = *(const int*)payload->Data;\n                        if (mode == Mode_Copy)\n                        {\n                            names[n] = names[payload_n];\n                        }\n                        if (mode == Mode_Move)\n                        {\n                            names[n] = names[payload_n];\n                            names[payload_n] = \"\";\n                        }\n                        if (mode == Mode_Swap)\n                        {\n                            const char* tmp = names[n];\n                            names[n] = names[payload_n];\n                            names[payload_n] = tmp;\n                        }\n                    }\n                    ImGui::EndDragDropTarget();\n                }\n                ImGui::PopID();\n            }\n            ImGui::TreePop();\n        }\n\n        IMGUI_DEMO_MARKER(\"Widgets/Drag and Drop/Drag to reorder items (simple)\");\n        if (ImGui::TreeNode(\"Drag to reorder items (simple)\"))\n        {\n            // FIXME: there is temporary (usually single-frame) ID Conflict during reordering as a same item may be submitting twice.\n            // This code was always slightly faulty but in a way which was not easily noticeable.\n            // Until we fix this, enable ImGuiItemFlags_AllowDuplicateId to disable detecting the issue.\n            ImGui::PushItemFlag(ImGuiItemFlags_AllowDuplicateId, true);\n\n            // Simple reordering\n            HelpMarker(\n                \"We don't use the drag and drop api at all here! \"\n                \"Instead we query when the item is held but not hovered, and order items accordingly.\");\n            static const char* item_names[] = { \"Item One\", \"Item Two\", \"Item Three\", \"Item Four\", \"Item Five\" };\n            for (int n = 0; n < IM_ARRAYSIZE(item_names); n++)\n            {\n                const char* item = item_names[n];\n                ImGui::Selectable(item);\n\n                if (ImGui::IsItemActive() && !ImGui::IsItemHovered())\n                {\n                    int n_next = n + (ImGui::GetMouseDragDelta(0).y < 0.f ? -1 : 1);\n                    if (n_next >= 0 && n_next < IM_ARRAYSIZE(item_names))\n                    {\n                        item_names[n] = item_names[n_next];\n                        item_names[n_next] = item;\n                        ImGui::ResetMouseDragDelta();\n                    }\n                }\n            }\n\n            ImGui::PopItemFlag();\n            ImGui::TreePop();\n        }\n\n        IMGUI_DEMO_MARKER(\"Widgets/Drag and Drop/Tooltip at target location\");\n        if (ImGui::TreeNode(\"Tooltip at target location\"))\n        {\n            for (int n = 0; n < 2; n++)\n            {\n                // Drop targets\n                ImGui::Button(n ? \"drop here##1\" : \"drop here##0\");\n                if (ImGui::BeginDragDropTarget())\n                {\n                    ImGuiDragDropFlags drop_target_flags = ImGuiDragDropFlags_AcceptBeforeDelivery | ImGuiDragDropFlags_AcceptNoPreviewTooltip;\n                    if (const ImGuiPayload* payload = ImGui::AcceptDragDropPayload(IMGUI_PAYLOAD_TYPE_COLOR_4F, drop_target_flags))\n                    {\n                        IM_UNUSED(payload);\n                        ImGui::SetMouseCursor(ImGuiMouseCursor_NotAllowed);\n                        ImGui::SetTooltip(\"Cannot drop here!\");\n                    }\n                    ImGui::EndDragDropTarget();\n                }\n\n                // Drop source\n                static ImVec4 col4 = { 1.0f, 0.0f, 0.2f, 1.0f };\n                if (n == 0)\n                    ImGui::ColorButton(\"drag me\", col4);\n\n            }\n            ImGui::TreePop();\n        }\n\n        ImGui::TreePop();\n    }\n}\n\n//-----------------------------------------------------------------------------\n// [SECTION] DemoWindowWidgetsDragsAndSliders()\n//-----------------------------------------------------------------------------\n\nstatic void DemoWindowWidgetsDragsAndSliders()\n{\n    IMGUI_DEMO_MARKER(\"Widgets/Drag and Slider Flags\");\n    if (ImGui::TreeNode(\"Drag/Slider Flags\"))\n    {\n        // Demonstrate using advanced flags for DragXXX and SliderXXX functions. Note that the flags are the same!\n        static ImGuiSliderFlags flags = ImGuiSliderFlags_None;\n        ImGui::CheckboxFlags(\"ImGuiSliderFlags_AlwaysClamp\", &flags, ImGuiSliderFlags_AlwaysClamp);\n        ImGui::CheckboxFlags(\"ImGuiSliderFlags_ClampOnInput\", &flags, ImGuiSliderFlags_ClampOnInput);\n        ImGui::SameLine(); HelpMarker(\"Clamp value to min/max bounds when input manually with CTRL+Click. By default CTRL+Click allows going out of bounds.\");\n        ImGui::CheckboxFlags(\"ImGuiSliderFlags_ClampZeroRange\", &flags, ImGuiSliderFlags_ClampZeroRange);\n        ImGui::SameLine(); HelpMarker(\"Clamp even if min==max==0.0f. Otherwise DragXXX functions don't clamp.\");\n        ImGui::CheckboxFlags(\"ImGuiSliderFlags_Logarithmic\", &flags, ImGuiSliderFlags_Logarithmic);\n        ImGui::SameLine(); HelpMarker(\"Enable logarithmic editing (more precision for small values).\");\n        ImGui::CheckboxFlags(\"ImGuiSliderFlags_NoRoundToFormat\", &flags, ImGuiSliderFlags_NoRoundToFormat);\n        ImGui::SameLine(); HelpMarker(\"Disable rounding underlying value to match precision of the format string (e.g. %.3f values are rounded to those 3 digits).\");\n        ImGui::CheckboxFlags(\"ImGuiSliderFlags_NoInput\", &flags, ImGuiSliderFlags_NoInput);\n        ImGui::SameLine(); HelpMarker(\"Disable CTRL+Click or Enter key allowing to input text directly into the widget.\");\n        ImGui::CheckboxFlags(\"ImGuiSliderFlags_NoSpeedTweaks\", &flags, ImGuiSliderFlags_NoSpeedTweaks);\n        ImGui::SameLine(); HelpMarker(\"Disable keyboard modifiers altering tweak speed. Useful if you want to alter tweak speed yourself based on your own logic.\");\n        ImGui::CheckboxFlags(\"ImGuiSliderFlags_WrapAround\", &flags, ImGuiSliderFlags_WrapAround);\n        ImGui::SameLine(); HelpMarker(\"Enable wrapping around from max to min and from min to max (only supported by DragXXX() functions)\");\n\n        // Drags\n        static float drag_f = 0.5f;\n        static int drag_i = 50;\n        ImGui::Text(\"Underlying float value: %f\", drag_f);\n        ImGui::DragFloat(\"DragFloat (0 -> 1)\", &drag_f, 0.005f, 0.0f, 1.0f, \"%.3f\", flags);\n        ImGui::DragFloat(\"DragFloat (0 -> +inf)\", &drag_f, 0.005f, 0.0f, FLT_MAX, \"%.3f\", flags);\n        ImGui::DragFloat(\"DragFloat (-inf -> 1)\", &drag_f, 0.005f, -FLT_MAX, 1.0f, \"%.3f\", flags);\n        ImGui::DragFloat(\"DragFloat (-inf -> +inf)\", &drag_f, 0.005f, -FLT_MAX, +FLT_MAX, \"%.3f\", flags);\n        //ImGui::DragFloat(\"DragFloat (0 -> 0)\", &drag_f, 0.005f, 0.0f, 0.0f, \"%.3f\", flags);           // To test ClampZeroRange\n        //ImGui::DragFloat(\"DragFloat (100 -> 100)\", &drag_f, 0.005f, 100.0f, 100.0f, \"%.3f\", flags);\n        ImGui::DragInt(\"DragInt (0 -> 100)\", &drag_i, 0.5f, 0, 100, \"%d\", flags);\n\n        // Sliders\n        static float slider_f = 0.5f;\n        static int slider_i = 50;\n        const ImGuiSliderFlags flags_for_sliders = flags & ~ImGuiSliderFlags_WrapAround;\n        ImGui::Text(\"Underlying float value: %f\", slider_f);\n        ImGui::SliderFloat(\"SliderFloat (0 -> 1)\", &slider_f, 0.0f, 1.0f, \"%.3f\", flags_for_sliders);\n        ImGui::SliderInt(\"SliderInt (0 -> 100)\", &slider_i, 0, 100, \"%d\", flags_for_sliders);\n\n        ImGui::TreePop();\n    }\n}\n\n//-----------------------------------------------------------------------------\n// [SECTION] DemoWindowWidgetsImages()\n//-----------------------------------------------------------------------------\n\nstatic void DemoWindowWidgetsImages()\n{\n    IMGUI_DEMO_MARKER(\"Widgets/Images\");\n    if (ImGui::TreeNode(\"Images\"))\n    {\n        ImGuiIO& io = ImGui::GetIO();\n        ImGui::TextWrapped(\n            \"Below we are displaying the font texture (which is the only texture we have access to in this demo). \"\n            \"Use the 'ImTextureID' type as storage to pass pointers or identifier to your own texture data. \"\n            \"Hover the texture for a zoomed view!\");\n\n        // Below we are displaying the font texture because it is the only texture we have access to inside the demo!\n        // Remember that ImTextureID is just storage for whatever you want it to be. It is essentially a value that\n        // will be passed to the rendering backend via the ImDrawCmd structure.\n        // If you use one of the default imgui_impl_XXXX.cpp rendering backend, they all have comments at the top\n        // of their respective source file to specify what they expect to be stored in ImTextureID, for example:\n        // - The imgui_impl_dx11.cpp renderer expect a 'ID3D11ShaderResourceView*' pointer\n        // - The imgui_impl_opengl3.cpp renderer expect a GLuint OpenGL texture identifier, etc.\n        // More:\n        // - If you decided that ImTextureID = MyEngineTexture*, then you can pass your MyEngineTexture* pointers\n        //   to ImGui::Image(), and gather width/height through your own functions, etc.\n        // - You can use ShowMetricsWindow() to inspect the draw data that are being passed to your renderer,\n        //   it will help you debug issues if you are confused about it.\n        // - Consider using the lower-level ImDrawList::AddImage() API, via ImGui::GetWindowDrawList()->AddImage().\n        // - Read https://github.com/ocornut/imgui/blob/master/docs/FAQ.md\n        // - Read https://github.com/ocornut/imgui/wiki/Image-Loading-and-Displaying-Examples\n        ImTextureID my_tex_id = io.Fonts->TexID;\n        float my_tex_w = (float)io.Fonts->TexWidth;\n        float my_tex_h = (float)io.Fonts->TexHeight;\n        {\n            ImGui::Text(\"%.0fx%.0f\", my_tex_w, my_tex_h);\n            ImVec2 pos = ImGui::GetCursorScreenPos();\n            ImVec2 uv_min = ImVec2(0.0f, 0.0f);                 // Top-left\n            ImVec2 uv_max = ImVec2(1.0f, 1.0f);                 // Lower-right\n            ImGui::PushStyleVar(ImGuiStyleVar_ImageBorderSize, IM_MAX(1.0f, ImGui::GetStyle().ImageBorderSize));\n            ImGui::ImageWithBg(my_tex_id, ImVec2(my_tex_w, my_tex_h), uv_min, uv_max, ImVec4(0.0f, 0.0f, 0.0f, 1.0f));\n            if (ImGui::BeginItemTooltip())\n            {\n                float region_sz = 32.0f;\n                float region_x = io.MousePos.x - pos.x - region_sz * 0.5f;\n                float region_y = io.MousePos.y - pos.y - region_sz * 0.5f;\n                float zoom = 4.0f;\n                if (region_x < 0.0f) { region_x = 0.0f; }\n                else if (region_x > my_tex_w - region_sz) { region_x = my_tex_w - region_sz; }\n                if (region_y < 0.0f) { region_y = 0.0f; }\n                else if (region_y > my_tex_h - region_sz) { region_y = my_tex_h - region_sz; }\n                ImGui::Text(\"Min: (%.2f, %.2f)\", region_x, region_y);\n                ImGui::Text(\"Max: (%.2f, %.2f)\", region_x + region_sz, region_y + region_sz);\n                ImVec2 uv0 = ImVec2((region_x) / my_tex_w, (region_y) / my_tex_h);\n                ImVec2 uv1 = ImVec2((region_x + region_sz) / my_tex_w, (region_y + region_sz) / my_tex_h);\n                ImGui::ImageWithBg(my_tex_id, ImVec2(region_sz * zoom, region_sz * zoom), uv0, uv1, ImVec4(0.0f, 0.0f, 0.0f, 1.0f));\n                ImGui::EndTooltip();\n            }\n            ImGui::PopStyleVar();\n        }\n\n        IMGUI_DEMO_MARKER(\"Widgets/Images/Textured buttons\");\n        ImGui::TextWrapped(\"And now some textured buttons..\");\n        static int pressed_count = 0;\n        for (int i = 0; i < 8; i++)\n        {\n            // UV coordinates are often (0.0f, 0.0f) and (1.0f, 1.0f) to display an entire textures.\n            // Here are trying to display only a 32x32 pixels area of the texture, hence the UV computation.\n            // Read about UV coordinates here: https://github.com/ocornut/imgui/wiki/Image-Loading-and-Displaying-Examples\n            ImGui::PushID(i);\n            if (i > 0)\n                ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(i - 1.0f, i - 1.0f));\n            ImVec2 size = ImVec2(32.0f, 32.0f);                         // Size of the image we want to make visible\n            ImVec2 uv0 = ImVec2(0.0f, 0.0f);                            // UV coordinates for lower-left\n            ImVec2 uv1 = ImVec2(32.0f / my_tex_w, 32.0f / my_tex_h);    // UV coordinates for (32,32) in our texture\n            ImVec4 bg_col = ImVec4(0.0f, 0.0f, 0.0f, 1.0f);             // Black background\n            ImVec4 tint_col = ImVec4(1.0f, 1.0f, 1.0f, 1.0f);           // No tint\n            if (ImGui::ImageButton(\"\", my_tex_id, size, uv0, uv1, bg_col, tint_col))\n                pressed_count += 1;\n            if (i > 0)\n                ImGui::PopStyleVar();\n            ImGui::PopID();\n            ImGui::SameLine();\n        }\n        ImGui::NewLine();\n        ImGui::Text(\"Pressed %d times.\", pressed_count);\n        ImGui::TreePop();\n    }\n}\n\n//-----------------------------------------------------------------------------\n// [SECTION] DemoWindowWidgetsListBoxes()\n//-----------------------------------------------------------------------------\n\nstatic void DemoWindowWidgetsListBoxes()\n{\n    IMGUI_DEMO_MARKER(\"Widgets/List Boxes\");\n    if (ImGui::TreeNode(\"List Boxes\"))\n    {\n        // BeginListBox() is essentially a thin wrapper to using BeginChild()/EndChild()\n        // using the ImGuiChildFlags_FrameStyle flag for stylistic changes + displaying a label.\n        // You may be tempted to simply use BeginChild() directly. However note that BeginChild() requires EndChild()\n        // to always be called (inconsistent with BeginListBox()/EndListBox()).\n\n        // Using the generic BeginListBox() API, you have full control over how to display the combo contents.\n        // (your selection data could be an index, a pointer to the object, an id for the object, a flag intrusively\n        // stored in the object itself, etc.)\n        const char* items[] = { \"AAAA\", \"BBBB\", \"CCCC\", \"DDDD\", \"EEEE\", \"FFFF\", \"GGGG\", \"HHHH\", \"IIII\", \"JJJJ\", \"KKKK\", \"LLLLLLL\", \"MMMM\", \"OOOOOOO\" };\n        static int item_selected_idx = 0; // Here we store our selected data as an index.\n\n        static bool item_highlight = false;\n        int item_highlighted_idx = -1; // Here we store our highlighted data as an index.\n        ImGui::Checkbox(\"Highlight hovered item in second listbox\", &item_highlight);\n\n        if (ImGui::BeginListBox(\"listbox 1\"))\n        {\n            for (int n = 0; n < IM_ARRAYSIZE(items); n++)\n            {\n                const bool is_selected = (item_selected_idx == n);\n                if (ImGui::Selectable(items[n], is_selected))\n                    item_selected_idx = n;\n\n                if (item_highlight && ImGui::IsItemHovered())\n                    item_highlighted_idx = n;\n\n                // Set the initial focus when opening the combo (scrolling + keyboard navigation focus)\n                if (is_selected)\n                    ImGui::SetItemDefaultFocus();\n            }\n            ImGui::EndListBox();\n        }\n        ImGui::SameLine(); HelpMarker(\"Here we are sharing selection state between both boxes.\");\n\n        // Custom size: use all width, 5 items tall\n        ImGui::Text(\"Full-width:\");\n        if (ImGui::BeginListBox(\"##listbox 2\", ImVec2(-FLT_MIN, 5 * ImGui::GetTextLineHeightWithSpacing())))\n        {\n            for (int n = 0; n < IM_ARRAYSIZE(items); n++)\n            {\n                bool is_selected = (item_selected_idx == n);\n                ImGuiSelectableFlags flags = (item_highlighted_idx == n) ? ImGuiSelectableFlags_Highlight : 0;\n                if (ImGui::Selectable(items[n], is_selected, flags))\n                    item_selected_idx = n;\n\n                // Set the initial focus when opening the combo (scrolling + keyboard navigation focus)\n                if (is_selected)\n                    ImGui::SetItemDefaultFocus();\n            }\n            ImGui::EndListBox();\n        }\n\n        ImGui::TreePop();\n    }\n}\n\n//-----------------------------------------------------------------------------\n// [SECTION] DemoWindowWidgetsMultiComponents()\n//-----------------------------------------------------------------------------\n\nstatic void DemoWindowWidgetsMultiComponents()\n{\n    IMGUI_DEMO_MARKER(\"Widgets/Multi-component Widgets\");\n    if (ImGui::TreeNode(\"Multi-component Widgets\"))\n    {\n        static float vec4f[4] = { 0.10f, 0.20f, 0.30f, 0.44f };\n        static int vec4i[4] = { 1, 5, 100, 255 };\n\n        ImGui::SeparatorText(\"2-wide\");\n        ImGui::InputFloat2(\"input float2\", vec4f);\n        ImGui::DragFloat2(\"drag float2\", vec4f, 0.01f, 0.0f, 1.0f);\n        ImGui::SliderFloat2(\"slider float2\", vec4f, 0.0f, 1.0f);\n        ImGui::InputInt2(\"input int2\", vec4i);\n        ImGui::DragInt2(\"drag int2\", vec4i, 1, 0, 255);\n        ImGui::SliderInt2(\"slider int2\", vec4i, 0, 255);\n\n        ImGui::SeparatorText(\"3-wide\");\n        ImGui::InputFloat3(\"input float3\", vec4f);\n        ImGui::DragFloat3(\"drag float3\", vec4f, 0.01f, 0.0f, 1.0f);\n        ImGui::SliderFloat3(\"slider float3\", vec4f, 0.0f, 1.0f);\n        ImGui::InputInt3(\"input int3\", vec4i);\n        ImGui::DragInt3(\"drag int3\", vec4i, 1, 0, 255);\n        ImGui::SliderInt3(\"slider int3\", vec4i, 0, 255);\n\n        ImGui::SeparatorText(\"4-wide\");\n        ImGui::InputFloat4(\"input float4\", vec4f);\n        ImGui::DragFloat4(\"drag float4\", vec4f, 0.01f, 0.0f, 1.0f);\n        ImGui::SliderFloat4(\"slider float4\", vec4f, 0.0f, 1.0f);\n        ImGui::InputInt4(\"input int4\", vec4i);\n        ImGui::DragInt4(\"drag int4\", vec4i, 1, 0, 255);\n        ImGui::SliderInt4(\"slider int4\", vec4i, 0, 255);\n\n        ImGui::SeparatorText(\"Ranges\");\n        static float begin = 10, end = 90;\n        static int begin_i = 100, end_i = 1000;\n        ImGui::DragFloatRange2(\"range float\", &begin, &end, 0.25f, 0.0f, 100.0f, \"Min: %.1f %%\", \"Max: %.1f %%\", ImGuiSliderFlags_AlwaysClamp);\n        ImGui::DragIntRange2(\"range int\", &begin_i, &end_i, 5, 0, 1000, \"Min: %d units\", \"Max: %d units\");\n        ImGui::DragIntRange2(\"range int (no bounds)\", &begin_i, &end_i, 5, 0, 0, \"Min: %d units\", \"Max: %d units\");\n\n        ImGui::TreePop();\n    }\n}\n\n//-----------------------------------------------------------------------------\n// [SECTION] DemoWindowWidgetsPlotting()\n//-----------------------------------------------------------------------------\n\nstatic void DemoWindowWidgetsPlotting()\n{\n    // Plot/Graph widgets are not very good.\n// Consider using a third-party library such as ImPlot: https://github.com/epezent/implot\n// (see others https://github.com/ocornut/imgui/wiki/Useful-Extensions)\n    IMGUI_DEMO_MARKER(\"Widgets/Plotting\");\n    if (ImGui::TreeNode(\"Plotting\"))\n    {\n        ImGui::Text(\"Need better plotting and graphing? Consider using ImPlot:\");\n        ImGui::TextLinkOpenURL(\"https://github.com/epezent/implot\");\n        ImGui::Separator();\n\n        static bool animate = true;\n        ImGui::Checkbox(\"Animate\", &animate);\n\n        // Plot as lines and plot as histogram\n        static float arr[] = { 0.6f, 0.1f, 1.0f, 0.5f, 0.92f, 0.1f, 0.2f };\n        ImGui::PlotLines(\"Frame Times\", arr, IM_ARRAYSIZE(arr));\n        ImGui::PlotHistogram(\"Histogram\", arr, IM_ARRAYSIZE(arr), 0, NULL, 0.0f, 1.0f, ImVec2(0, 80.0f));\n        //ImGui::SameLine(); HelpMarker(\"Consider using ImPlot instead!\");\n\n        // Fill an array of contiguous float values to plot\n        // Tip: If your float aren't contiguous but part of a structure, you can pass a pointer to your first float\n        // and the sizeof() of your structure in the \"stride\" parameter.\n        static float values[90] = {};\n        static int values_offset = 0;\n        static double refresh_time = 0.0;\n        if (!animate || refresh_time == 0.0)\n            refresh_time = ImGui::GetTime();\n        while (refresh_time < ImGui::GetTime()) // Create data at fixed 60 Hz rate for the demo\n        {\n            static float phase = 0.0f;\n            values[values_offset] = cosf(phase);\n            values_offset = (values_offset + 1) % IM_ARRAYSIZE(values);\n            phase += 0.10f * values_offset;\n            refresh_time += 1.0f / 60.0f;\n        }\n\n        // Plots can display overlay texts\n        // (in this example, we will display an average value)\n        {\n            float average = 0.0f;\n            for (int n = 0; n < IM_ARRAYSIZE(values); n++)\n                average += values[n];\n            average /= (float)IM_ARRAYSIZE(values);\n            char overlay[32];\n            sprintf(overlay, \"avg %f\", average);\n            ImGui::PlotLines(\"Lines\", values, IM_ARRAYSIZE(values), values_offset, overlay, -1.0f, 1.0f, ImVec2(0, 80.0f));\n        }\n\n        // Use functions to generate output\n        // FIXME: This is actually VERY awkward because current plot API only pass in indices.\n        // We probably want an API passing floats and user provide sample rate/count.\n        struct Funcs\n        {\n            static float Sin(void*, int i) { return sinf(i * 0.1f); }\n            static float Saw(void*, int i) { return (i & 1) ? 1.0f : -1.0f; }\n        };\n        static int func_type = 0, display_count = 70;\n        ImGui::SeparatorText(\"Functions\");\n        ImGui::SetNextItemWidth(ImGui::GetFontSize() * 8);\n        ImGui::Combo(\"func\", &func_type, \"Sin\\0Saw\\0\");\n        ImGui::SameLine();\n        ImGui::SliderInt(\"Sample count\", &display_count, 1, 400);\n        float (*func)(void*, int) = (func_type == 0) ? Funcs::Sin : Funcs::Saw;\n        ImGui::PlotLines(\"Lines##2\", func, NULL, display_count, 0, NULL, -1.0f, 1.0f, ImVec2(0, 80));\n        ImGui::PlotHistogram(\"Histogram##2\", func, NULL, display_count, 0, NULL, -1.0f, 1.0f, ImVec2(0, 80));\n\n        ImGui::TreePop();\n    }\n}\n\n//-----------------------------------------------------------------------------\n// [SECTION] DemoWindowWidgetsProgressBars()\n//-----------------------------------------------------------------------------\n\nstatic void DemoWindowWidgetsProgressBars()\n{\n    IMGUI_DEMO_MARKER(\"Widgets/Progress Bars\");\n    if (ImGui::TreeNode(\"Progress Bars\"))\n    {\n        // Animate a simple progress bar\n        static float progress = 0.0f, progress_dir = 1.0f;\n        progress += progress_dir * 0.4f * ImGui::GetIO().DeltaTime;\n        if (progress >= +1.1f) { progress = +1.1f; progress_dir *= -1.0f; }\n        if (progress <= -0.1f) { progress = -0.1f; progress_dir *= -1.0f; }\n\n        // Typically we would use ImVec2(-1.0f,0.0f) or ImVec2(-FLT_MIN,0.0f) to use all available width,\n        // or ImVec2(width,0.0f) for a specified width. ImVec2(0.0f,0.0f) uses ItemWidth.\n        ImGui::ProgressBar(progress, ImVec2(0.0f, 0.0f));\n        ImGui::SameLine(0.0f, ImGui::GetStyle().ItemInnerSpacing.x);\n        ImGui::Text(\"Progress Bar\");\n\n        float progress_saturated = IM_CLAMP(progress, 0.0f, 1.0f);\n        char buf[32];\n        sprintf(buf, \"%d/%d\", (int)(progress_saturated * 1753), 1753);\n        ImGui::ProgressBar(progress, ImVec2(0.f, 0.f), buf);\n\n        // Pass an animated negative value, e.g. -1.0f * (float)ImGui::GetTime() is the recommended value.\n        // Adjust the factor if you want to adjust the animation speed.\n        ImGui::ProgressBar(-1.0f * (float)ImGui::GetTime(), ImVec2(0.0f, 0.0f), \"Searching..\");\n        ImGui::SameLine(0.0f, ImGui::GetStyle().ItemInnerSpacing.x);\n        ImGui::Text(\"Indeterminate\");\n\n        ImGui::TreePop();\n    }\n}\n\n//-----------------------------------------------------------------------------\n// [SECTION] DemoWindowWidgetsQueryingStatuses()\n//-----------------------------------------------------------------------------\n\nstatic void DemoWindowWidgetsQueryingStatuses()\n{\n    IMGUI_DEMO_MARKER(\"Widgets/Querying Item Status (Edited,Active,Hovered etc.)\");\n    if (ImGui::TreeNode(\"Querying Item Status (Edited/Active/Hovered etc.)\"))\n    {\n        // Select an item type\n        const char* item_names[] =\n        {\n            \"Text\", \"Button\", \"Button (w/ repeat)\", \"Checkbox\", \"SliderFloat\", \"InputText\", \"InputTextMultiline\", \"InputFloat\",\n            \"InputFloat3\", \"ColorEdit4\", \"Selectable\", \"MenuItem\", \"TreeNode\", \"TreeNode (w/ double-click)\", \"Combo\", \"ListBox\"\n        };\n        static int item_type = 4;\n        static bool item_disabled = false;\n        ImGui::Combo(\"Item Type\", &item_type, item_names, IM_ARRAYSIZE(item_names), IM_ARRAYSIZE(item_names));\n        ImGui::SameLine();\n        HelpMarker(\"Testing how various types of items are interacting with the IsItemXXX functions. Note that the bool return value of most ImGui function is generally equivalent to calling ImGui::IsItemHovered().\");\n        ImGui::Checkbox(\"Item Disabled\", &item_disabled);\n\n        // Submit selected items so we can query their status in the code following it.\n        bool ret = false;\n        static bool b = false;\n        static float col4f[4] = { 1.0f, 0.5, 0.0f, 1.0f };\n        static char str[16] = {};\n        if (item_disabled)\n            ImGui::BeginDisabled(true);\n        if (item_type == 0) { ImGui::Text(\"ITEM: Text\"); }                                              // Testing text items with no identifier/interaction\n        if (item_type == 1) { ret = ImGui::Button(\"ITEM: Button\"); }                                    // Testing button\n        if (item_type == 2) { ImGui::PushItemFlag(ImGuiItemFlags_ButtonRepeat, true); ret = ImGui::Button(\"ITEM: Button\"); ImGui::PopItemFlag(); } // Testing button (with repeater)\n        if (item_type == 3) { ret = ImGui::Checkbox(\"ITEM: Checkbox\", &b); }                            // Testing checkbox\n        if (item_type == 4) { ret = ImGui::SliderFloat(\"ITEM: SliderFloat\", &col4f[0], 0.0f, 1.0f); }   // Testing basic item\n        if (item_type == 5) { ret = ImGui::InputText(\"ITEM: InputText\", &str[0], IM_ARRAYSIZE(str)); }  // Testing input text (which handles tabbing)\n        if (item_type == 6) { ret = ImGui::InputTextMultiline(\"ITEM: InputTextMultiline\", &str[0], IM_ARRAYSIZE(str)); } // Testing input text (which uses a child window)\n        if (item_type == 7) { ret = ImGui::InputFloat(\"ITEM: InputFloat\", col4f, 1.0f); }               // Testing +/- buttons on scalar input\n        if (item_type == 8) { ret = ImGui::InputFloat3(\"ITEM: InputFloat3\", col4f); }                   // Testing multi-component items (IsItemXXX flags are reported merged)\n        if (item_type == 9) { ret = ImGui::ColorEdit4(\"ITEM: ColorEdit4\", col4f); }                     // Testing multi-component items (IsItemXXX flags are reported merged)\n        if (item_type == 10) { ret = ImGui::Selectable(\"ITEM: Selectable\"); }                            // Testing selectable item\n        if (item_type == 11) { ret = ImGui::MenuItem(\"ITEM: MenuItem\"); }                                // Testing menu item (they use ImGuiButtonFlags_PressedOnRelease button policy)\n        if (item_type == 12) { ret = ImGui::TreeNode(\"ITEM: TreeNode\"); if (ret) ImGui::TreePop(); }     // Testing tree node\n        if (item_type == 13) { ret = ImGui::TreeNodeEx(\"ITEM: TreeNode w/ ImGuiTreeNodeFlags_OpenOnDoubleClick\", ImGuiTreeNodeFlags_OpenOnDoubleClick | ImGuiTreeNodeFlags_NoTreePushOnOpen); } // Testing tree node with ImGuiButtonFlags_PressedOnDoubleClick button policy.\n        if (item_type == 14) { const char* items[] = { \"Apple\", \"Banana\", \"Cherry\", \"Kiwi\" }; static int current = 1; ret = ImGui::Combo(\"ITEM: Combo\", &current, items, IM_ARRAYSIZE(items)); }\n        if (item_type == 15) { const char* items[] = { \"Apple\", \"Banana\", \"Cherry\", \"Kiwi\" }; static int current = 1; ret = ImGui::ListBox(\"ITEM: ListBox\", &current, items, IM_ARRAYSIZE(items), IM_ARRAYSIZE(items)); }\n\n        bool hovered_delay_none = ImGui::IsItemHovered();\n        bool hovered_delay_stationary = ImGui::IsItemHovered(ImGuiHoveredFlags_Stationary);\n        bool hovered_delay_short = ImGui::IsItemHovered(ImGuiHoveredFlags_DelayShort);\n        bool hovered_delay_normal = ImGui::IsItemHovered(ImGuiHoveredFlags_DelayNormal);\n        bool hovered_delay_tooltip = ImGui::IsItemHovered(ImGuiHoveredFlags_ForTooltip); // = Normal + Stationary\n\n        // Display the values of IsItemHovered() and other common item state functions.\n        // Note that the ImGuiHoveredFlags_XXX flags can be combined.\n        // Because BulletText is an item itself and that would affect the output of IsItemXXX functions,\n        // we query every state in a single call to avoid storing them and to simplify the code.\n        ImGui::BulletText(\n            \"Return value = %d\\n\"\n            \"IsItemFocused() = %d\\n\"\n            \"IsItemHovered() = %d\\n\"\n            \"IsItemHovered(_AllowWhenBlockedByPopup) = %d\\n\"\n            \"IsItemHovered(_AllowWhenBlockedByActiveItem) = %d\\n\"\n            \"IsItemHovered(_AllowWhenOverlappedByItem) = %d\\n\"\n            \"IsItemHovered(_AllowWhenOverlappedByWindow) = %d\\n\"\n            \"IsItemHovered(_AllowWhenDisabled) = %d\\n\"\n            \"IsItemHovered(_RectOnly) = %d\\n\"\n            \"IsItemActive() = %d\\n\"\n            \"IsItemEdited() = %d\\n\"\n            \"IsItemActivated() = %d\\n\"\n            \"IsItemDeactivated() = %d\\n\"\n            \"IsItemDeactivatedAfterEdit() = %d\\n\"\n            \"IsItemVisible() = %d\\n\"\n            \"IsItemClicked() = %d\\n\"\n            \"IsItemToggledOpen() = %d\\n\"\n            \"GetItemRectMin() = (%.1f, %.1f)\\n\"\n            \"GetItemRectMax() = (%.1f, %.1f)\\n\"\n            \"GetItemRectSize() = (%.1f, %.1f)\",\n            ret,\n            ImGui::IsItemFocused(),\n            ImGui::IsItemHovered(),\n            ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenBlockedByPopup),\n            ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenBlockedByActiveItem),\n            ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenOverlappedByItem),\n            ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenOverlappedByWindow),\n            ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled),\n            ImGui::IsItemHovered(ImGuiHoveredFlags_RectOnly),\n            ImGui::IsItemActive(),\n            ImGui::IsItemEdited(),\n            ImGui::IsItemActivated(),\n            ImGui::IsItemDeactivated(),\n            ImGui::IsItemDeactivatedAfterEdit(),\n            ImGui::IsItemVisible(),\n            ImGui::IsItemClicked(),\n            ImGui::IsItemToggledOpen(),\n            ImGui::GetItemRectMin().x, ImGui::GetItemRectMin().y,\n            ImGui::GetItemRectMax().x, ImGui::GetItemRectMax().y,\n            ImGui::GetItemRectSize().x, ImGui::GetItemRectSize().y\n        );\n        ImGui::BulletText(\n            \"with Hovering Delay or Stationary test:\\n\"\n            \"IsItemHovered() = = %d\\n\"\n            \"IsItemHovered(_Stationary) = %d\\n\"\n            \"IsItemHovered(_DelayShort) = %d\\n\"\n            \"IsItemHovered(_DelayNormal) = %d\\n\"\n            \"IsItemHovered(_Tooltip) = %d\",\n            hovered_delay_none, hovered_delay_stationary, hovered_delay_short, hovered_delay_normal, hovered_delay_tooltip);\n\n        if (item_disabled)\n            ImGui::EndDisabled();\n\n        char buf[1] = \"\";\n        ImGui::InputText(\"unused\", buf, IM_ARRAYSIZE(buf), ImGuiInputTextFlags_ReadOnly);\n        ImGui::SameLine();\n        HelpMarker(\"This widget is only here to be able to tab-out of the widgets above and see e.g. Deactivated() status.\");\n\n        ImGui::TreePop();\n    }\n\n    IMGUI_DEMO_MARKER(\"Widgets/Querying Window Status (Focused,Hovered etc.)\");\n    if (ImGui::TreeNode(\"Querying Window Status (Focused/Hovered etc.)\"))\n    {\n        static bool embed_all_inside_a_child_window = false;\n        ImGui::Checkbox(\"Embed everything inside a child window for testing _RootWindow flag.\", &embed_all_inside_a_child_window);\n        if (embed_all_inside_a_child_window)\n            ImGui::BeginChild(\"outer_child\", ImVec2(0, ImGui::GetFontSize() * 20.0f), ImGuiChildFlags_Borders);\n\n        // Testing IsWindowFocused() function with its various flags.\n        ImGui::BulletText(\n            \"IsWindowFocused() = %d\\n\"\n            \"IsWindowFocused(_ChildWindows) = %d\\n\"\n            \"IsWindowFocused(_ChildWindows|_NoPopupHierarchy) = %d\\n\"\n            \"IsWindowFocused(_ChildWindows|_RootWindow) = %d\\n\"\n            \"IsWindowFocused(_ChildWindows|_RootWindow|_NoPopupHierarchy) = %d\\n\"\n            \"IsWindowFocused(_RootWindow) = %d\\n\"\n            \"IsWindowFocused(_RootWindow|_NoPopupHierarchy) = %d\\n\"\n            \"IsWindowFocused(_AnyWindow) = %d\\n\",\n            ImGui::IsWindowFocused(),\n            ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows),\n            ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows | ImGuiFocusedFlags_NoPopupHierarchy),\n            ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows | ImGuiFocusedFlags_RootWindow),\n            ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows | ImGuiFocusedFlags_RootWindow | ImGuiFocusedFlags_NoPopupHierarchy),\n            ImGui::IsWindowFocused(ImGuiFocusedFlags_RootWindow),\n            ImGui::IsWindowFocused(ImGuiFocusedFlags_RootWindow | ImGuiFocusedFlags_NoPopupHierarchy),\n            ImGui::IsWindowFocused(ImGuiFocusedFlags_AnyWindow));\n\n        // Testing IsWindowHovered() function with its various flags.\n        ImGui::BulletText(\n            \"IsWindowHovered() = %d\\n\"\n            \"IsWindowHovered(_AllowWhenBlockedByPopup) = %d\\n\"\n            \"IsWindowHovered(_AllowWhenBlockedByActiveItem) = %d\\n\"\n            \"IsWindowHovered(_ChildWindows) = %d\\n\"\n            \"IsWindowHovered(_ChildWindows|_NoPopupHierarchy) = %d\\n\"\n            \"IsWindowHovered(_ChildWindows|_RootWindow) = %d\\n\"\n            \"IsWindowHovered(_ChildWindows|_RootWindow|_NoPopupHierarchy) = %d\\n\"\n            \"IsWindowHovered(_RootWindow) = %d\\n\"\n            \"IsWindowHovered(_RootWindow|_NoPopupHierarchy) = %d\\n\"\n            \"IsWindowHovered(_ChildWindows|_AllowWhenBlockedByPopup) = %d\\n\"\n            \"IsWindowHovered(_AnyWindow) = %d\\n\"\n            \"IsWindowHovered(_Stationary) = %d\\n\",\n            ImGui::IsWindowHovered(),\n            ImGui::IsWindowHovered(ImGuiHoveredFlags_AllowWhenBlockedByPopup),\n            ImGui::IsWindowHovered(ImGuiHoveredFlags_AllowWhenBlockedByActiveItem),\n            ImGui::IsWindowHovered(ImGuiHoveredFlags_ChildWindows),\n            ImGui::IsWindowHovered(ImGuiHoveredFlags_ChildWindows | ImGuiHoveredFlags_NoPopupHierarchy),\n            ImGui::IsWindowHovered(ImGuiHoveredFlags_ChildWindows | ImGuiHoveredFlags_RootWindow),\n            ImGui::IsWindowHovered(ImGuiHoveredFlags_ChildWindows | ImGuiHoveredFlags_RootWindow | ImGuiHoveredFlags_NoPopupHierarchy),\n            ImGui::IsWindowHovered(ImGuiHoveredFlags_RootWindow),\n            ImGui::IsWindowHovered(ImGuiHoveredFlags_RootWindow | ImGuiHoveredFlags_NoPopupHierarchy),\n            ImGui::IsWindowHovered(ImGuiHoveredFlags_ChildWindows | ImGuiHoveredFlags_AllowWhenBlockedByPopup),\n            ImGui::IsWindowHovered(ImGuiHoveredFlags_AnyWindow),\n            ImGui::IsWindowHovered(ImGuiHoveredFlags_Stationary));\n\n        ImGui::BeginChild(\"child\", ImVec2(0, 50), ImGuiChildFlags_Borders);\n        ImGui::Text(\"This is another child window for testing the _ChildWindows flag.\");\n        ImGui::EndChild();\n        if (embed_all_inside_a_child_window)\n            ImGui::EndChild();\n\n        // Calling IsItemHovered() after begin returns the hovered status of the title bar.\n        // This is useful in particular if you want to create a context menu associated to the title bar of a window.\n        static bool test_window = false;\n        ImGui::Checkbox(\"Hovered/Active tests after Begin() for title bar testing\", &test_window);\n        if (test_window)\n        {\n            ImGui::Begin(\"Title bar Hovered/Active tests\", &test_window);\n            if (ImGui::BeginPopupContextItem()) // <-- This is using IsItemHovered()\n            {\n                if (ImGui::MenuItem(\"Close\")) { test_window = false; }\n                ImGui::EndPopup();\n            }\n            ImGui::Text(\n                \"IsItemHovered() after begin = %d (== is title bar hovered)\\n\"\n                \"IsItemActive() after begin = %d (== is window being clicked/moved)\\n\",\n                ImGui::IsItemHovered(), ImGui::IsItemActive());\n            ImGui::End();\n        }\n\n        ImGui::TreePop();\n    }\n}\n\n//-----------------------------------------------------------------------------\n// [SECTION] DemoWindowWidgetsSelectables()\n//-----------------------------------------------------------------------------\n\nstatic void DemoWindowWidgetsSelectables()\n{\n    IMGUI_DEMO_MARKER(\"Widgets/Selectables\");\n    //ImGui::SetNextItemOpen(true, ImGuiCond_Once);\n    if (ImGui::TreeNode(\"Selectables\"))\n    {\n        // Selectable() has 2 overloads:\n        // - The one taking \"bool selected\" as a read-only selection information.\n        //   When Selectable() has been clicked it returns true and you can alter selection state accordingly.\n        // - The one taking \"bool* p_selected\" as a read-write selection information (convenient in some cases)\n        // The earlier is more flexible, as in real application your selection may be stored in many different ways\n        // and not necessarily inside a bool value (e.g. in flags within objects, as an external list, etc).\n        IMGUI_DEMO_MARKER(\"Widgets/Selectables/Basic\");\n        if (ImGui::TreeNode(\"Basic\"))\n        {\n            static bool selection[5] = { false, true, false, false };\n            ImGui::Selectable(\"1. I am selectable\", &selection[0]);\n            ImGui::Selectable(\"2. I am selectable\", &selection[1]);\n            ImGui::Selectable(\"3. I am selectable\", &selection[2]);\n            if (ImGui::Selectable(\"4. I am double clickable\", selection[3], ImGuiSelectableFlags_AllowDoubleClick))\n                if (ImGui::IsMouseDoubleClicked(0))\n                    selection[3] = !selection[3];\n            ImGui::TreePop();\n        }\n\n        IMGUI_DEMO_MARKER(\"Widgets/Selectables/Rendering more items on the same line\");\n        if (ImGui::TreeNode(\"Rendering more items on the same line\"))\n        {\n            // (1) Using SetNextItemAllowOverlap()\n            // (2) Using the Selectable() override that takes \"bool* p_selected\" parameter, the bool value is toggled automatically.\n            static bool selected[3] = { false, false, false };\n            ImGui::SetNextItemAllowOverlap(); ImGui::Selectable(\"main.c\", &selected[0]); ImGui::SameLine(); ImGui::SmallButton(\"Link 1\");\n            ImGui::SetNextItemAllowOverlap(); ImGui::Selectable(\"Hello.cpp\", &selected[1]); ImGui::SameLine(); ImGui::SmallButton(\"Link 2\");\n            ImGui::SetNextItemAllowOverlap(); ImGui::Selectable(\"Hello.h\", &selected[2]); ImGui::SameLine(); ImGui::SmallButton(\"Link 3\");\n            ImGui::TreePop();\n        }\n\n        IMGUI_DEMO_MARKER(\"Widgets/Selectables/In Tables\");\n        if (ImGui::TreeNode(\"In Tables\"))\n        {\n            static bool selected[10] = {};\n\n            if (ImGui::BeginTable(\"split1\", 3, ImGuiTableFlags_Resizable | ImGuiTableFlags_NoSavedSettings | ImGuiTableFlags_Borders))\n            {\n                for (int i = 0; i < 10; i++)\n                {\n                    char label[32];\n                    sprintf(label, \"Item %d\", i);\n                    ImGui::TableNextColumn();\n                    ImGui::Selectable(label, &selected[i]); // FIXME-TABLE: Selection overlap\n                }\n                ImGui::EndTable();\n            }\n            ImGui::Spacing();\n            if (ImGui::BeginTable(\"split2\", 3, ImGuiTableFlags_Resizable | ImGuiTableFlags_NoSavedSettings | ImGuiTableFlags_Borders))\n            {\n                for (int i = 0; i < 10; i++)\n                {\n                    char label[32];\n                    sprintf(label, \"Item %d\", i);\n                    ImGui::TableNextRow();\n                    ImGui::TableNextColumn();\n                    ImGui::Selectable(label, &selected[i], ImGuiSelectableFlags_SpanAllColumns);\n                    ImGui::TableNextColumn();\n                    ImGui::Text(\"Some other contents\");\n                    ImGui::TableNextColumn();\n                    ImGui::Text(\"123456\");\n                }\n                ImGui::EndTable();\n            }\n            ImGui::TreePop();\n        }\n\n        IMGUI_DEMO_MARKER(\"Widgets/Selectables/Grid\");\n        if (ImGui::TreeNode(\"Grid\"))\n        {\n            static char selected[4][4] = { { 1, 0, 0, 0 }, { 0, 1, 0, 0 }, { 0, 0, 1, 0 }, { 0, 0, 0, 1 } };\n\n            // Add in a bit of silly fun...\n            const float time = (float)ImGui::GetTime();\n            const bool winning_state = memchr(selected, 0, sizeof(selected)) == NULL; // If all cells are selected...\n            if (winning_state)\n                ImGui::PushStyleVar(ImGuiStyleVar_SelectableTextAlign, ImVec2(0.5f + 0.5f * cosf(time * 2.0f), 0.5f + 0.5f * sinf(time * 3.0f)));\n\n            for (int y = 0; y < 4; y++)\n                for (int x = 0; x < 4; x++)\n                {\n                    if (x > 0)\n                        ImGui::SameLine();\n                    ImGui::PushID(y * 4 + x);\n                    if (ImGui::Selectable(\"Sailor\", selected[y][x] != 0, 0, ImVec2(50, 50)))\n                    {\n                        // Toggle clicked cell + toggle neighbors\n                        selected[y][x] ^= 1;\n                        if (x > 0) { selected[y][x - 1] ^= 1; }\n                        if (x < 3) { selected[y][x + 1] ^= 1; }\n                        if (y > 0) { selected[y - 1][x] ^= 1; }\n                        if (y < 3) { selected[y + 1][x] ^= 1; }\n                    }\n                    ImGui::PopID();\n                }\n\n            if (winning_state)\n                ImGui::PopStyleVar();\n            ImGui::TreePop();\n        }\n        IMGUI_DEMO_MARKER(\"Widgets/Selectables/Alignment\");\n        if (ImGui::TreeNode(\"Alignment\"))\n        {\n            HelpMarker(\n                \"By default, Selectables uses style.SelectableTextAlign but it can be overridden on a per-item \"\n                \"basis using PushStyleVar(). You'll probably want to always keep your default situation to \"\n                \"left-align otherwise it becomes difficult to layout multiple items on a same line\");\n            static bool selected[3 * 3] = { true, false, true, false, true, false, true, false, true };\n            for (int y = 0; y < 3; y++)\n            {\n                for (int x = 0; x < 3; x++)\n                {\n                    ImVec2 alignment = ImVec2((float)x / 2.0f, (float)y / 2.0f);\n                    char name[32];\n                    sprintf(name, \"(%.1f,%.1f)\", alignment.x, alignment.y);\n                    if (x > 0) ImGui::SameLine();\n                    ImGui::PushStyleVar(ImGuiStyleVar_SelectableTextAlign, alignment);\n                    ImGui::Selectable(name, &selected[3 * y + x], ImGuiSelectableFlags_None, ImVec2(80, 80));\n                    ImGui::PopStyleVar();\n                }\n            }\n            ImGui::TreePop();\n        }\n        ImGui::TreePop();\n    }\n}\n\n//-----------------------------------------------------------------------------\n// [SECTION] DemoWindowWidgetsSelectionAndMultiSelect()\n//-----------------------------------------------------------------------------\n// Multi-selection demos\n// Also read: https://github.com/ocornut/imgui/wiki/Multi-Select\n//-----------------------------------------------------------------------------\n\nstatic const char* ExampleNames[] =\n{\n    \"Artichoke\", \"Arugula\", \"Asparagus\", \"Avocado\", \"Bamboo Shoots\", \"Bean Sprouts\", \"Beans\", \"Beet\", \"Belgian Endive\", \"Bell Pepper\",\n    \"Bitter Gourd\", \"Bok Choy\", \"Broccoli\", \"Brussels Sprouts\", \"Burdock Root\", \"Cabbage\", \"Calabash\", \"Capers\", \"Carrot\", \"Cassava\",\n    \"Cauliflower\", \"Celery\", \"Celery Root\", \"Celcuce\", \"Chayote\", \"Chinese Broccoli\", \"Corn\", \"Cucumber\"\n};\n\n// Extra functions to add deletion support to ImGuiSelectionBasicStorage\nstruct ExampleSelectionWithDeletion : ImGuiSelectionBasicStorage\n{\n    // Find which item should be Focused after deletion.\n    // Call _before_ item submission. Retunr an index in the before-deletion item list, your item loop should call SetKeyboardFocusHere() on it.\n    // The subsequent ApplyDeletionPostLoop() code will use it to apply Selection.\n    // - We cannot provide this logic in core Dear ImGui because we don't have access to selection data.\n    // - We don't actually manipulate the ImVector<> here, only in ApplyDeletionPostLoop(), but using similar API for consistency and flexibility.\n    // - Important: Deletion only works if the underlying ImGuiID for your items are stable: aka not depend on their index, but on e.g. item id/ptr.\n    // FIXME-MULTISELECT: Doesn't take account of the possibility focus target will be moved during deletion. Need refocus or scroll offset.\n    int ApplyDeletionPreLoop(ImGuiMultiSelectIO* ms_io, int items_count)\n    {\n        if (Size == 0)\n            return -1;\n\n        // If focused item is not selected...\n        const int focused_idx = (int)ms_io->NavIdItem;  // Index of currently focused item\n        if (ms_io->NavIdSelected == false)  // This is merely a shortcut, == Contains(adapter->IndexToStorage(items, focused_idx))\n        {\n            ms_io->RangeSrcReset = true;    // Request to recover RangeSrc from NavId next frame. Would be ok to reset even when NavIdSelected==true, but it would take an extra frame to recover RangeSrc when deleting a selected item.\n            return focused_idx;             // Request to focus same item after deletion.\n        }\n\n        // If focused item is selected: land on first unselected item after focused item.\n        for (int idx = focused_idx + 1; idx < items_count; idx++)\n            if (!Contains(GetStorageIdFromIndex(idx)))\n                return idx;\n\n        // If focused item is selected: otherwise return last unselected item before focused item.\n        for (int idx = IM_MIN(focused_idx, items_count) - 1; idx >= 0; idx--)\n            if (!Contains(GetStorageIdFromIndex(idx)))\n                return idx;\n\n        return -1;\n    }\n\n    // Rewrite item list (delete items) + update selection.\n    // - Call after EndMultiSelect()\n    // - We cannot provide this logic in core Dear ImGui because we don't have access to your items, nor to selection data.\n    template<typename ITEM_TYPE>\n    void ApplyDeletionPostLoop(ImGuiMultiSelectIO* ms_io, ImVector<ITEM_TYPE>& items, int item_curr_idx_to_select)\n    {\n        // Rewrite item list (delete items) + convert old selection index (before deletion) to new selection index (after selection).\n        // If NavId was not part of selection, we will stay on same item.\n        ImVector<ITEM_TYPE> new_items;\n        new_items.reserve(items.Size - Size);\n        int item_next_idx_to_select = -1;\n        for (int idx = 0; idx < items.Size; idx++)\n        {\n            if (!Contains(GetStorageIdFromIndex(idx)))\n                new_items.push_back(items[idx]);\n            if (item_curr_idx_to_select == idx)\n                item_next_idx_to_select = new_items.Size - 1;\n        }\n        items.swap(new_items);\n\n        // Update selection\n        Clear();\n        if (item_next_idx_to_select != -1 && ms_io->NavIdSelected)\n            SetItemSelected(GetStorageIdFromIndex(item_next_idx_to_select), true);\n    }\n};\n\n// Example: Implement dual list box storage and interface\nstruct ExampleDualListBox\n{\n    ImVector<ImGuiID>           Items[2];               // ID is index into ExampleName[]\n    ImGuiSelectionBasicStorage  Selections[2];          // Store ExampleItemId into selection\n    bool                        OptKeepSorted = true;\n\n    void MoveAll(int src, int dst)\n    {\n        IM_ASSERT((src == 0 && dst == 1) || (src == 1 && dst == 0));\n        for (ImGuiID item_id : Items[src])\n            Items[dst].push_back(item_id);\n        Items[src].clear();\n        SortItems(dst);\n        Selections[src].Swap(Selections[dst]);\n        Selections[src].Clear();\n    }\n    void MoveSelected(int src, int dst)\n    {\n        for (int src_n = 0; src_n < Items[src].Size; src_n++)\n        {\n            ImGuiID item_id = Items[src][src_n];\n            if (!Selections[src].Contains(item_id))\n                continue;\n            Items[src].erase(&Items[src][src_n]); // FIXME-OPT: Could be implemented more optimally (rebuild src items and swap)\n            Items[dst].push_back(item_id);\n            src_n--;\n        }\n        if (OptKeepSorted)\n            SortItems(dst);\n        Selections[src].Swap(Selections[dst]);\n        Selections[src].Clear();\n    }\n    void ApplySelectionRequests(ImGuiMultiSelectIO* ms_io, int side)\n    {\n        // In this example we store item id in selection (instead of item index)\n        Selections[side].UserData = Items[side].Data;\n        Selections[side].AdapterIndexToStorageId = [](ImGuiSelectionBasicStorage* self, int idx) { ImGuiID* items = (ImGuiID*)self->UserData; return items[idx]; };\n        Selections[side].ApplyRequests(ms_io);\n    }\n    static int IMGUI_CDECL CompareItemsByValue(const void* lhs, const void* rhs)\n    {\n        const int* a = (const int*)lhs;\n        const int* b = (const int*)rhs;\n        return (*a - *b) > 0 ? +1 : -1;\n    }\n    void SortItems(int n)\n    {\n        qsort(Items[n].Data, (size_t)Items[n].Size, sizeof(Items[n][0]), CompareItemsByValue);\n    }\n    void Show()\n    {\n        //ImGui::Checkbox(\"Sorted\", &OptKeepSorted);\n        if (ImGui::BeginTable(\"split\", 3, ImGuiTableFlags_None))\n        {\n            ImGui::TableSetupColumn(\"\", ImGuiTableColumnFlags_WidthStretch);    // Left side\n            ImGui::TableSetupColumn(\"\", ImGuiTableColumnFlags_WidthFixed);      // Buttons\n            ImGui::TableSetupColumn(\"\", ImGuiTableColumnFlags_WidthStretch);    // Right side\n            ImGui::TableNextRow();\n\n            int request_move_selected = -1;\n            int request_move_all = -1;\n            float child_height_0 = 0.0f;\n            for (int side = 0; side < 2; side++)\n            {\n                // FIXME-MULTISELECT: Dual List Box: Add context menus\n                // FIXME-NAV: Using ImGuiWindowFlags_NavFlattened exhibit many issues.\n                ImVector<ImGuiID>& items = Items[side];\n                ImGuiSelectionBasicStorage& selection = Selections[side];\n\n                ImGui::TableSetColumnIndex((side == 0) ? 0 : 2);\n                ImGui::Text(\"%s (%d)\", (side == 0) ? \"Available\" : \"Basket\", items.Size);\n\n                // Submit scrolling range to avoid glitches on moving/deletion\n                const float items_height = ImGui::GetTextLineHeightWithSpacing();\n                ImGui::SetNextWindowContentSize(ImVec2(0.0f, items.Size * items_height));\n\n                bool child_visible;\n                if (side == 0)\n                {\n                    // Left child is resizable\n                    ImGui::SetNextWindowSizeConstraints(ImVec2(0.0f, ImGui::GetFrameHeightWithSpacing() * 4), ImVec2(FLT_MAX, FLT_MAX));\n                    child_visible = ImGui::BeginChild(\"0\", ImVec2(-FLT_MIN, ImGui::GetFontSize() * 20), ImGuiChildFlags_FrameStyle | ImGuiChildFlags_ResizeY);\n                    child_height_0 = ImGui::GetWindowSize().y;\n                }\n                else\n                {\n                    // Right child use same height as left one\n                    child_visible = ImGui::BeginChild(\"1\", ImVec2(-FLT_MIN, child_height_0), ImGuiChildFlags_FrameStyle);\n                }\n                if (child_visible)\n                {\n                    ImGuiMultiSelectFlags flags = ImGuiMultiSelectFlags_None;\n                    ImGuiMultiSelectIO* ms_io = ImGui::BeginMultiSelect(flags, selection.Size, items.Size);\n                    ApplySelectionRequests(ms_io, side);\n\n                    for (int item_n = 0; item_n < items.Size; item_n++)\n                    {\n                        ImGuiID item_id = items[item_n];\n                        bool item_is_selected = selection.Contains(item_id);\n                        ImGui::SetNextItemSelectionUserData(item_n);\n                        ImGui::Selectable(ExampleNames[item_id], item_is_selected, ImGuiSelectableFlags_AllowDoubleClick);\n                        if (ImGui::IsItemFocused())\n                        {\n                            // FIXME-MULTISELECT: Dual List Box: Transfer focus\n                            if (ImGui::IsKeyPressed(ImGuiKey_Enter) || ImGui::IsKeyPressed(ImGuiKey_KeypadEnter))\n                                request_move_selected = side;\n                            if (ImGui::IsMouseDoubleClicked(0)) // FIXME-MULTISELECT: Double-click on multi-selection?\n                                request_move_selected = side;\n                        }\n                    }\n\n                    ms_io = ImGui::EndMultiSelect();\n                    ApplySelectionRequests(ms_io, side);\n                }\n                ImGui::EndChild();\n            }\n\n            // Buttons columns\n            ImGui::TableSetColumnIndex(1);\n            ImGui::NewLine();\n            //ImVec2 button_sz = { ImGui::CalcTextSize(\">>\").x + ImGui::GetStyle().FramePadding.x * 2.0f, ImGui::GetFrameHeight() + padding.y * 2.0f };\n            ImVec2 button_sz = { ImGui::GetFrameHeight(), ImGui::GetFrameHeight() };\n\n            // (Using BeginDisabled()/EndDisabled() works but feels distracting given how it is currently visualized)\n            if (ImGui::Button(\">>\", button_sz))\n                request_move_all = 0;\n            if (ImGui::Button(\">\", button_sz))\n                request_move_selected = 0;\n            if (ImGui::Button(\"<\", button_sz))\n                request_move_selected = 1;\n            if (ImGui::Button(\"<<\", button_sz))\n                request_move_all = 1;\n\n            // Process requests\n            if (request_move_all != -1)\n                MoveAll(request_move_all, request_move_all ^ 1);\n            if (request_move_selected != -1)\n                MoveSelected(request_move_selected, request_move_selected ^ 1);\n\n            // FIXME-MULTISELECT: Support action from outside\n            /*\n            if (OptKeepSorted == false)\n            {\n                ImGui::NewLine();\n                if (ImGui::ArrowButton(\"MoveUp\", ImGuiDir_Up)) {}\n                if (ImGui::ArrowButton(\"MoveDown\", ImGuiDir_Down)) {}\n            }\n            */\n\n            ImGui::EndTable();\n        }\n    }\n};\n\nstatic void DemoWindowWidgetsSelectionAndMultiSelect(ImGuiDemoWindowData* demo_data)\n{\n    IMGUI_DEMO_MARKER(\"Widgets/Selection State & Multi-Select\");\n    if (ImGui::TreeNode(\"Selection State & Multi-Select\"))\n    {\n        HelpMarker(\"Selections can be built using Selectable(), TreeNode() or other widgets. Selection state is owned by application code/data.\");\n\n        // Without any fancy API: manage single-selection yourself.\n        IMGUI_DEMO_MARKER(\"Widgets/Selection State/Single-Select\");\n        if (ImGui::TreeNode(\"Single-Select\"))\n        {\n            static int selected = -1;\n            for (int n = 0; n < 5; n++)\n            {\n                char buf[32];\n                sprintf(buf, \"Object %d\", n);\n                if (ImGui::Selectable(buf, selected == n))\n                    selected = n;\n            }\n            ImGui::TreePop();\n        }\n\n        // Demonstrate implementation a most-basic form of multi-selection manually\n        // This doesn't support the SHIFT modifier which requires BeginMultiSelect()!\n        IMGUI_DEMO_MARKER(\"Widgets/Selection State/Multi-Select (manual/simplified, without BeginMultiSelect)\");\n        if (ImGui::TreeNode(\"Multi-Select (manual/simplified, without BeginMultiSelect)\"))\n        {\n            HelpMarker(\"Hold CTRL and click to select multiple items.\");\n            static bool selection[5] = { false, false, false, false, false };\n            for (int n = 0; n < 5; n++)\n            {\n                char buf[32];\n                sprintf(buf, \"Object %d\", n);\n                if (ImGui::Selectable(buf, selection[n]))\n                {\n                    if (!ImGui::GetIO().KeyCtrl) // Clear selection when CTRL is not held\n                        memset(selection, 0, sizeof(selection));\n                    selection[n] ^= 1; // Toggle current item\n                }\n            }\n            ImGui::TreePop();\n        }\n\n        // Demonstrate handling proper multi-selection using the BeginMultiSelect/EndMultiSelect API.\n        // SHIFT+Click w/ CTRL and other standard features are supported.\n        // We use the ImGuiSelectionBasicStorage helper which you may freely reimplement.\n        IMGUI_DEMO_MARKER(\"Widgets/Selection State/Multi-Select\");\n        if (ImGui::TreeNode(\"Multi-Select\"))\n        {\n            ImGui::Text(\"Supported features:\");\n            ImGui::BulletText(\"Keyboard navigation (arrows, page up/down, home/end, space).\");\n            ImGui::BulletText(\"Ctrl modifier to preserve and toggle selection.\");\n            ImGui::BulletText(\"Shift modifier for range selection.\");\n            ImGui::BulletText(\"CTRL+A to select all.\");\n            ImGui::BulletText(\"Escape to clear selection.\");\n            ImGui::BulletText(\"Click and drag to box-select.\");\n            ImGui::Text(\"Tip: Use 'Demo->Tools->Debug Log->Selection' to see selection requests as they happen.\");\n\n            // Use default selection.Adapter: Pass index to SetNextItemSelectionUserData(), store index in Selection\n            const int ITEMS_COUNT = 50;\n            static ImGuiSelectionBasicStorage selection;\n            ImGui::Text(\"Selection: %d/%d\", selection.Size, ITEMS_COUNT);\n\n            // The BeginChild() has no purpose for selection logic, other that offering a scrolling region.\n            if (ImGui::BeginChild(\"##Basket\", ImVec2(-FLT_MIN, ImGui::GetFontSize() * 20), ImGuiChildFlags_FrameStyle | ImGuiChildFlags_ResizeY))\n            {\n                ImGuiMultiSelectFlags flags = ImGuiMultiSelectFlags_ClearOnEscape | ImGuiMultiSelectFlags_BoxSelect1d;\n                ImGuiMultiSelectIO* ms_io = ImGui::BeginMultiSelect(flags, selection.Size, ITEMS_COUNT);\n                selection.ApplyRequests(ms_io);\n\n                for (int n = 0; n < ITEMS_COUNT; n++)\n                {\n                    char label[64];\n                    sprintf(label, \"Object %05d: %s\", n, ExampleNames[n % IM_ARRAYSIZE(ExampleNames)]);\n                    bool item_is_selected = selection.Contains((ImGuiID)n);\n                    ImGui::SetNextItemSelectionUserData(n);\n                    ImGui::Selectable(label, item_is_selected);\n                }\n\n                ms_io = ImGui::EndMultiSelect();\n                selection.ApplyRequests(ms_io);\n            }\n            ImGui::EndChild();\n            ImGui::TreePop();\n        }\n\n        // Demonstrate using the clipper with BeginMultiSelect()/EndMultiSelect()\n        IMGUI_DEMO_MARKER(\"Widgets/Selection State/Multi-Select (with clipper)\");\n        if (ImGui::TreeNode(\"Multi-Select (with clipper)\"))\n        {\n            // Use default selection.Adapter: Pass index to SetNextItemSelectionUserData(), store index in Selection\n            static ImGuiSelectionBasicStorage selection;\n\n            ImGui::Text(\"Added features:\");\n            ImGui::BulletText(\"Using ImGuiListClipper.\");\n\n            const int ITEMS_COUNT = 10000;\n            ImGui::Text(\"Selection: %d/%d\", selection.Size, ITEMS_COUNT);\n            if (ImGui::BeginChild(\"##Basket\", ImVec2(-FLT_MIN, ImGui::GetFontSize() * 20), ImGuiChildFlags_FrameStyle | ImGuiChildFlags_ResizeY))\n            {\n                ImGuiMultiSelectFlags flags = ImGuiMultiSelectFlags_ClearOnEscape | ImGuiMultiSelectFlags_BoxSelect1d;\n                ImGuiMultiSelectIO* ms_io = ImGui::BeginMultiSelect(flags, selection.Size, ITEMS_COUNT);\n                selection.ApplyRequests(ms_io);\n\n                ImGuiListClipper clipper;\n                clipper.Begin(ITEMS_COUNT);\n                if (ms_io->RangeSrcItem != -1)\n                    clipper.IncludeItemByIndex((int)ms_io->RangeSrcItem); // Ensure RangeSrc item is not clipped.\n                while (clipper.Step())\n                {\n                    for (int n = clipper.DisplayStart; n < clipper.DisplayEnd; n++)\n                    {\n                        char label[64];\n                        sprintf(label, \"Object %05d: %s\", n, ExampleNames[n % IM_ARRAYSIZE(ExampleNames)]);\n                        bool item_is_selected = selection.Contains((ImGuiID)n);\n                        ImGui::SetNextItemSelectionUserData(n);\n                        ImGui::Selectable(label, item_is_selected);\n                    }\n                }\n\n                ms_io = ImGui::EndMultiSelect();\n                selection.ApplyRequests(ms_io);\n            }\n            ImGui::EndChild();\n            ImGui::TreePop();\n        }\n\n        // Demonstrate dynamic item list + deletion support using the BeginMultiSelect/EndMultiSelect API.\n        // In order to support Deletion without any glitches you need to:\n        // - (1) If items are submitted in their own scrolling area, submit contents size SetNextWindowContentSize() ahead of time to prevent one-frame readjustment of scrolling.\n        // - (2) Items needs to have persistent ID Stack identifier = ID needs to not depends on their index. PushID(index) = KO. PushID(item_id) = OK. This is in order to focus items reliably after a selection.\n        // - (3) BeginXXXX process\n        // - (4) Focus process\n        // - (5) EndXXXX process\n        IMGUI_DEMO_MARKER(\"Widgets/Selection State/Multi-Select (with deletion)\");\n        if (ImGui::TreeNode(\"Multi-Select (with deletion)\"))\n        {\n            // Storing items data separately from selection data.\n            // (you may decide to store selection data inside your item (aka intrusive storage) if you don't need multiple views over same items)\n            // Use a custom selection.Adapter: store item identifier in Selection (instead of index)\n            static ImVector<ImGuiID> items;\n            static ExampleSelectionWithDeletion selection;\n            selection.UserData = (void*)&items;\n            selection.AdapterIndexToStorageId = [](ImGuiSelectionBasicStorage* self, int idx) { ImVector<ImGuiID>* p_items = (ImVector<ImGuiID>*)self->UserData; return (*p_items)[idx]; }; // Index -> ID\n\n            ImGui::Text(\"Added features:\");\n            ImGui::BulletText(\"Dynamic list with Delete key support.\");\n            ImGui::Text(\"Selection size: %d/%d\", selection.Size, items.Size);\n\n            // Initialize default list with 50 items + button to add/remove items.\n            static ImGuiID items_next_id = 0;\n            if (items_next_id == 0)\n                for (ImGuiID n = 0; n < 50; n++)\n                    items.push_back(items_next_id++);\n            if (ImGui::SmallButton(\"Add 20 items\"))     { for (int n = 0; n < 20; n++) { items.push_back(items_next_id++); } }\n            ImGui::SameLine();\n            if (ImGui::SmallButton(\"Remove 20 items\"))  { for (int n = IM_MIN(20, items.Size); n > 0; n--) { selection.SetItemSelected(items.back(), false); items.pop_back(); } }\n\n            // (1) Extra to support deletion: Submit scrolling range to avoid glitches on deletion\n            const float items_height = ImGui::GetTextLineHeightWithSpacing();\n            ImGui::SetNextWindowContentSize(ImVec2(0.0f, items.Size * items_height));\n\n            if (ImGui::BeginChild(\"##Basket\", ImVec2(-FLT_MIN, ImGui::GetFontSize() * 20), ImGuiChildFlags_FrameStyle | ImGuiChildFlags_ResizeY))\n            {\n                ImGuiMultiSelectFlags flags = ImGuiMultiSelectFlags_ClearOnEscape | ImGuiMultiSelectFlags_BoxSelect1d;\n                ImGuiMultiSelectIO* ms_io = ImGui::BeginMultiSelect(flags, selection.Size, items.Size);\n                selection.ApplyRequests(ms_io);\n\n                const bool want_delete = ImGui::Shortcut(ImGuiKey_Delete, ImGuiInputFlags_Repeat) && (selection.Size > 0);\n                const int item_curr_idx_to_focus = want_delete ? selection.ApplyDeletionPreLoop(ms_io, items.Size) : -1;\n\n                for (int n = 0; n < items.Size; n++)\n                {\n                    const ImGuiID item_id = items[n];\n                    char label[64];\n                    sprintf(label, \"Object %05u: %s\", item_id, ExampleNames[item_id % IM_ARRAYSIZE(ExampleNames)]);\n\n                    bool item_is_selected = selection.Contains(item_id);\n                    ImGui::SetNextItemSelectionUserData(n);\n                    ImGui::Selectable(label, item_is_selected);\n                    if (item_curr_idx_to_focus == n)\n                        ImGui::SetKeyboardFocusHere(-1);\n                }\n\n                // Apply multi-select requests\n                ms_io = ImGui::EndMultiSelect();\n                selection.ApplyRequests(ms_io);\n                if (want_delete)\n                    selection.ApplyDeletionPostLoop(ms_io, items, item_curr_idx_to_focus);\n            }\n            ImGui::EndChild();\n            ImGui::TreePop();\n        }\n\n        // Implement a Dual List Box (#6648)\n        IMGUI_DEMO_MARKER(\"Widgets/Selection State/Multi-Select (dual list box)\");\n        if (ImGui::TreeNode(\"Multi-Select (dual list box)\"))\n        {\n            // Init default state\n            static ExampleDualListBox dlb;\n            if (dlb.Items[0].Size == 0 && dlb.Items[1].Size == 0)\n                for (int item_id = 0; item_id < IM_ARRAYSIZE(ExampleNames); item_id++)\n                    dlb.Items[0].push_back((ImGuiID)item_id);\n\n            // Show\n            dlb.Show();\n\n            ImGui::TreePop();\n        }\n\n        // Demonstrate using the clipper with BeginMultiSelect()/EndMultiSelect()\n        IMGUI_DEMO_MARKER(\"Widgets/Selection State/Multi-Select (in a table)\");\n        if (ImGui::TreeNode(\"Multi-Select (in a table)\"))\n        {\n            static ImGuiSelectionBasicStorage selection;\n\n            const int ITEMS_COUNT = 10000;\n            ImGui::Text(\"Selection: %d/%d\", selection.Size, ITEMS_COUNT);\n            if (ImGui::BeginTable(\"##Basket\", 2, ImGuiTableFlags_ScrollY | ImGuiTableFlags_RowBg | ImGuiTableFlags_BordersOuter))\n            {\n                ImGui::TableSetupColumn(\"Object\");\n                ImGui::TableSetupColumn(\"Action\");\n                ImGui::TableSetupScrollFreeze(0, 1);\n                ImGui::TableHeadersRow();\n\n                ImGuiMultiSelectFlags flags = ImGuiMultiSelectFlags_ClearOnEscape | ImGuiMultiSelectFlags_BoxSelect1d;\n                ImGuiMultiSelectIO* ms_io = ImGui::BeginMultiSelect(flags, selection.Size, ITEMS_COUNT);\n                selection.ApplyRequests(ms_io);\n\n                ImGuiListClipper clipper;\n                clipper.Begin(ITEMS_COUNT);\n                if (ms_io->RangeSrcItem != -1)\n                    clipper.IncludeItemByIndex((int)ms_io->RangeSrcItem); // Ensure RangeSrc item is not clipped.\n                while (clipper.Step())\n                {\n                    for (int n = clipper.DisplayStart; n < clipper.DisplayEnd; n++)\n                    {\n                        ImGui::TableNextRow();\n                        ImGui::TableNextColumn();\n                        char label[64];\n                        sprintf(label, \"Object %05d: %s\", n, ExampleNames[n % IM_ARRAYSIZE(ExampleNames)]);\n                        bool item_is_selected = selection.Contains((ImGuiID)n);\n                        ImGui::SetNextItemSelectionUserData(n);\n                        ImGui::Selectable(label, item_is_selected, ImGuiSelectableFlags_SpanAllColumns | ImGuiSelectableFlags_AllowOverlap);\n                        ImGui::TableNextColumn();\n                        ImGui::SmallButton(\"hello\");\n                    }\n                }\n\n                ms_io = ImGui::EndMultiSelect();\n                selection.ApplyRequests(ms_io);\n                ImGui::EndTable();\n            }\n            ImGui::TreePop();\n        }\n\n        IMGUI_DEMO_MARKER(\"Widgets/Selection State/Multi-Select (checkboxes)\");\n        if (ImGui::TreeNode(\"Multi-Select (checkboxes)\"))\n        {\n            ImGui::Text(\"In a list of checkboxes (not selectable):\");\n            ImGui::BulletText(\"Using _NoAutoSelect + _NoAutoClear flags.\");\n            ImGui::BulletText(\"Shift+Click to check multiple boxes.\");\n            ImGui::BulletText(\"Shift+Keyboard to copy current value to other boxes.\");\n\n            // If you have an array of checkboxes, you may want to use NoAutoSelect + NoAutoClear and the ImGuiSelectionExternalStorage helper.\n            static bool items[20] = {};\n            static ImGuiMultiSelectFlags flags = ImGuiMultiSelectFlags_NoAutoSelect | ImGuiMultiSelectFlags_NoAutoClear | ImGuiMultiSelectFlags_ClearOnEscape;\n            ImGui::CheckboxFlags(\"ImGuiMultiSelectFlags_NoAutoSelect\", &flags, ImGuiMultiSelectFlags_NoAutoSelect);\n            ImGui::CheckboxFlags(\"ImGuiMultiSelectFlags_NoAutoClear\", &flags, ImGuiMultiSelectFlags_NoAutoClear);\n            ImGui::CheckboxFlags(\"ImGuiMultiSelectFlags_BoxSelect2d\", &flags, ImGuiMultiSelectFlags_BoxSelect2d); // Cannot use ImGuiMultiSelectFlags_BoxSelect1d as checkboxes are varying width.\n\n            if (ImGui::BeginChild(\"##Basket\", ImVec2(-FLT_MIN, ImGui::GetFontSize() * 20), ImGuiChildFlags_Borders | ImGuiChildFlags_ResizeY))\n            {\n                ImGuiMultiSelectIO* ms_io = ImGui::BeginMultiSelect(flags, -1, IM_ARRAYSIZE(items));\n                ImGuiSelectionExternalStorage storage_wrapper;\n                storage_wrapper.UserData = (void*)items;\n                storage_wrapper.AdapterSetItemSelected = [](ImGuiSelectionExternalStorage* self, int n, bool selected) { bool* array = (bool*)self->UserData; array[n] = selected; };\n                storage_wrapper.ApplyRequests(ms_io);\n                for (int n = 0; n < 20; n++)\n                {\n                    char label[32];\n                    sprintf(label, \"Item %d\", n);\n                    ImGui::SetNextItemSelectionUserData(n);\n                    ImGui::Checkbox(label, &items[n]);\n                }\n                ms_io = ImGui::EndMultiSelect();\n                storage_wrapper.ApplyRequests(ms_io);\n            }\n            ImGui::EndChild();\n\n            ImGui::TreePop();\n        }\n\n        // Demonstrate individual selection scopes in same window\n        IMGUI_DEMO_MARKER(\"Widgets/Selection State/Multi-Select (multiple scopes)\");\n        if (ImGui::TreeNode(\"Multi-Select (multiple scopes)\"))\n        {\n            // Use default select: Pass index to SetNextItemSelectionUserData(), store index in Selection\n            const int SCOPES_COUNT = 3;\n            const int ITEMS_COUNT = 8; // Per scope\n            static ImGuiSelectionBasicStorage selections_data[SCOPES_COUNT];\n\n            // Use ImGuiMultiSelectFlags_ScopeRect to not affect other selections in same window.\n            static ImGuiMultiSelectFlags flags = ImGuiMultiSelectFlags_ScopeRect | ImGuiMultiSelectFlags_ClearOnEscape;// | ImGuiMultiSelectFlags_ClearOnClickVoid;\n            if (ImGui::CheckboxFlags(\"ImGuiMultiSelectFlags_ScopeWindow\", &flags, ImGuiMultiSelectFlags_ScopeWindow) && (flags & ImGuiMultiSelectFlags_ScopeWindow))\n                flags &= ~ImGuiMultiSelectFlags_ScopeRect;\n            if (ImGui::CheckboxFlags(\"ImGuiMultiSelectFlags_ScopeRect\", &flags, ImGuiMultiSelectFlags_ScopeRect) && (flags & ImGuiMultiSelectFlags_ScopeRect))\n                flags &= ~ImGuiMultiSelectFlags_ScopeWindow;\n            ImGui::CheckboxFlags(\"ImGuiMultiSelectFlags_ClearOnClickVoid\", &flags, ImGuiMultiSelectFlags_ClearOnClickVoid);\n            ImGui::CheckboxFlags(\"ImGuiMultiSelectFlags_BoxSelect1d\", &flags, ImGuiMultiSelectFlags_BoxSelect1d);\n\n            for (int selection_scope_n = 0; selection_scope_n < SCOPES_COUNT; selection_scope_n++)\n            {\n                ImGui::PushID(selection_scope_n);\n                ImGuiSelectionBasicStorage* selection = &selections_data[selection_scope_n];\n                ImGuiMultiSelectIO* ms_io = ImGui::BeginMultiSelect(flags, selection->Size, ITEMS_COUNT);\n                selection->ApplyRequests(ms_io);\n\n                ImGui::SeparatorText(\"Selection scope\");\n                ImGui::Text(\"Selection size: %d/%d\", selection->Size, ITEMS_COUNT);\n\n                for (int n = 0; n < ITEMS_COUNT; n++)\n                {\n                    char label[64];\n                    sprintf(label, \"Object %05d: %s\", n, ExampleNames[n % IM_ARRAYSIZE(ExampleNames)]);\n                    bool item_is_selected = selection->Contains((ImGuiID)n);\n                    ImGui::SetNextItemSelectionUserData(n);\n                    ImGui::Selectable(label, item_is_selected);\n                }\n\n                // Apply multi-select requests\n                ms_io = ImGui::EndMultiSelect();\n                selection->ApplyRequests(ms_io);\n                ImGui::PopID();\n            }\n            ImGui::TreePop();\n        }\n\n        // See ShowExampleAppAssetsBrowser()\n        if (ImGui::TreeNode(\"Multi-Select (tiled assets browser)\"))\n        {\n            ImGui::Checkbox(\"Assets Browser\", &demo_data->ShowAppAssetsBrowser);\n            ImGui::Text(\"(also access from 'Examples->Assets Browser' in menu)\");\n            ImGui::TreePop();\n        }\n\n        // Demonstrate supporting multiple-selection in a tree.\n        // - We don't use linear indices for selection user data, but our ExampleTreeNode* pointer directly!\n        //   This showcase how SetNextItemSelectionUserData() never assume indices!\n        // - The difficulty here is to \"interpolate\" from RangeSrcItem to RangeDstItem in the SetAll/SetRange request.\n        //   We want this interpolation to match what the user sees: in visible order, skipping closed nodes.\n        //   This is implemented by our TreeGetNextNodeInVisibleOrder() user-space helper.\n        // - Important: In a real codebase aiming to implement full-featured selectable tree with custom filtering, you\n        //   are more likely to build an array mapping sequential indices to visible tree nodes, since your\n        //   filtering/search + clipping process will benefit from it. Having this will make this interpolation much easier.\n        // - Consider this a prototype: we are working toward simplifying some of it.\n        IMGUI_DEMO_MARKER(\"Widgets/Selection State/Multi-Select (trees)\");\n        if (ImGui::TreeNode(\"Multi-Select (trees)\"))\n        {\n            HelpMarker(\n                \"This is rather advanced and experimental. If you are getting started with multi-select, \"\n                \"please don't start by looking at how to use it for a tree!\\n\\n\"\n                \"Future versions will try to simplify and formalize some of this.\");\n\n            struct ExampleTreeFuncs\n            {\n                static void DrawNode(ExampleTreeNode* node, ImGuiSelectionBasicStorage* selection)\n                {\n                    ImGuiTreeNodeFlags tree_node_flags = ImGuiTreeNodeFlags_SpanAvailWidth | ImGuiTreeNodeFlags_OpenOnArrow | ImGuiTreeNodeFlags_OpenOnDoubleClick;\n                    tree_node_flags |= ImGuiTreeNodeFlags_NavLeftJumpsBackHere; // Enable pressing left to jump to parent\n                    if (node->Childs.Size == 0)\n                        tree_node_flags |= ImGuiTreeNodeFlags_Bullet | ImGuiTreeNodeFlags_Leaf;\n                    if (selection->Contains((ImGuiID)node->UID))\n                        tree_node_flags |= ImGuiTreeNodeFlags_Selected;\n\n                    // Using SetNextItemStorageID() to specify storage id, so we can easily peek into\n                    // the storage holding open/close stage, using our TreeNodeGetOpen/TreeNodeSetOpen() functions.\n                    ImGui::SetNextItemSelectionUserData((ImGuiSelectionUserData)(intptr_t)node);\n                    ImGui::SetNextItemStorageID((ImGuiID)node->UID);\n                    if (ImGui::TreeNodeEx(node->Name, tree_node_flags))\n                    {\n                        for (ExampleTreeNode* child : node->Childs)\n                            DrawNode(child, selection);\n                        ImGui::TreePop();\n                    }\n                    else if (ImGui::IsItemToggledOpen())\n                    {\n                        TreeCloseAndUnselectChildNodes(node, selection);\n                    }\n                }\n\n                static bool TreeNodeGetOpen(ExampleTreeNode* node)\n                {\n                    return ImGui::GetStateStorage()->GetBool((ImGuiID)node->UID);\n                }\n\n                static void TreeNodeSetOpen(ExampleTreeNode* node, bool open)\n                {\n                    ImGui::GetStateStorage()->SetBool((ImGuiID)node->UID, open);\n                }\n\n                // When closing a node: 1) close and unselect all child nodes, 2) select parent if any child was selected.\n                // FIXME: This is currently handled by user logic but I'm hoping to eventually provide tree node\n                // features to do this automatically, e.g. a ImGuiTreeNodeFlags_AutoCloseChildNodes etc.\n                static int TreeCloseAndUnselectChildNodes(ExampleTreeNode* node, ImGuiSelectionBasicStorage* selection, int depth = 0)\n                {\n                    // Recursive close (the test for depth == 0 is because we call this on a node that was just closed!)\n                    int unselected_count = selection->Contains((ImGuiID)node->UID) ? 1 : 0;\n                    if (depth == 0 || TreeNodeGetOpen(node))\n                    {\n                        for (ExampleTreeNode* child : node->Childs)\n                            unselected_count += TreeCloseAndUnselectChildNodes(child, selection, depth + 1);\n                        TreeNodeSetOpen(node, false);\n                    }\n\n                    // Select root node if any of its child was selected, otherwise unselect\n                    selection->SetItemSelected((ImGuiID)node->UID, (depth == 0 && unselected_count > 0));\n                    return unselected_count;\n                }\n\n                // Apply multi-selection requests\n                static void ApplySelectionRequests(ImGuiMultiSelectIO* ms_io, ExampleTreeNode* tree, ImGuiSelectionBasicStorage* selection)\n                {\n                    for (ImGuiSelectionRequest& req : ms_io->Requests)\n                    {\n                        if (req.Type == ImGuiSelectionRequestType_SetAll)\n                        {\n                            if (req.Selected)\n                                TreeSetAllInOpenNodes(tree, selection, req.Selected);\n                            else\n                                selection->Clear();\n                        }\n                        else if (req.Type == ImGuiSelectionRequestType_SetRange)\n                        {\n                            ExampleTreeNode* first_node = (ExampleTreeNode*)(intptr_t)req.RangeFirstItem;\n                            ExampleTreeNode* last_node = (ExampleTreeNode*)(intptr_t)req.RangeLastItem;\n                            for (ExampleTreeNode* node = first_node; node != NULL; node = TreeGetNextNodeInVisibleOrder(node, last_node))\n                                selection->SetItemSelected((ImGuiID)node->UID, req.Selected);\n                        }\n                    }\n                }\n\n                static void TreeSetAllInOpenNodes(ExampleTreeNode* node, ImGuiSelectionBasicStorage* selection, bool selected)\n                {\n                    if (node->Parent != NULL) // Root node isn't visible nor selectable in our scheme\n                        selection->SetItemSelected((ImGuiID)node->UID, selected);\n                    if (node->Parent == NULL || TreeNodeGetOpen(node))\n                        for (ExampleTreeNode* child : node->Childs)\n                            TreeSetAllInOpenNodes(child, selection, selected);\n                }\n\n                // Interpolate in *user-visible order* AND only *over opened nodes*.\n                // If you have a sequential mapping tables (e.g. generated after a filter/search pass) this would be simpler.\n                // Here the tricks are that:\n                // - we store/maintain ExampleTreeNode::IndexInParent which allows implementing a linear iterator easily, without searches, without recursion.\n                //   this could be replaced by a search in parent, aka 'int index_in_parent = curr_node->Parent->Childs.find_index(curr_node)'\n                //   which would only be called when crossing from child to a parent, aka not too much.\n                // - we call SetNextItemStorageID() before our TreeNode() calls with an ID which doesn't relate to UI stack,\n                //   making it easier to call TreeNodeGetOpen()/TreeNodeSetOpen() from any location.\n                static ExampleTreeNode* TreeGetNextNodeInVisibleOrder(ExampleTreeNode* curr_node, ExampleTreeNode* last_node)\n                {\n                    // Reached last node\n                    if (curr_node == last_node)\n                        return NULL;\n\n                    // Recurse into childs. Query storage to tell if the node is open.\n                    if (curr_node->Childs.Size > 0 && TreeNodeGetOpen(curr_node))\n                        return curr_node->Childs[0];\n\n                    // Next sibling, then into our own parent\n                    while (curr_node->Parent != NULL)\n                    {\n                        if (curr_node->IndexInParent + 1 < curr_node->Parent->Childs.Size)\n                            return curr_node->Parent->Childs[curr_node->IndexInParent + 1];\n                        curr_node = curr_node->Parent;\n                    }\n                    return NULL;\n                }\n\n            }; // ExampleTreeFuncs\n\n            static ImGuiSelectionBasicStorage selection;\n            if (demo_data->DemoTree == NULL)\n                demo_data->DemoTree = ExampleTree_CreateDemoTree(); // Create tree once\n            ImGui::Text(\"Selection size: %d\", selection.Size);\n\n            if (ImGui::BeginChild(\"##Tree\", ImVec2(-FLT_MIN, ImGui::GetFontSize() * 20), ImGuiChildFlags_FrameStyle | ImGuiChildFlags_ResizeY))\n            {\n                ExampleTreeNode* tree = demo_data->DemoTree;\n                ImGuiMultiSelectFlags ms_flags = ImGuiMultiSelectFlags_ClearOnEscape | ImGuiMultiSelectFlags_BoxSelect2d;\n                ImGuiMultiSelectIO* ms_io = ImGui::BeginMultiSelect(ms_flags, selection.Size, -1);\n                ExampleTreeFuncs::ApplySelectionRequests(ms_io, tree, &selection);\n                for (ExampleTreeNode* node : tree->Childs)\n                    ExampleTreeFuncs::DrawNode(node, &selection);\n                ms_io = ImGui::EndMultiSelect();\n                ExampleTreeFuncs::ApplySelectionRequests(ms_io, tree, &selection);\n            }\n            ImGui::EndChild();\n\n            ImGui::TreePop();\n        }\n\n        // Advanced demonstration of BeginMultiSelect()\n        // - Showcase clipping.\n        // - Showcase deletion.\n        // - Showcase basic drag and drop.\n        // - Showcase TreeNode variant (note that tree node don't expand in the demo: supporting expanding tree nodes + clipping a separate thing).\n        // - Showcase using inside a table.\n        IMGUI_DEMO_MARKER(\"Widgets/Selection State/Multi-Select (advanced)\");\n        //ImGui::SetNextItemOpen(true, ImGuiCond_Once);\n        if (ImGui::TreeNode(\"Multi-Select (advanced)\"))\n        {\n            // Options\n            enum WidgetType { WidgetType_Selectable, WidgetType_TreeNode };\n            static bool use_clipper = true;\n            static bool use_deletion = true;\n            static bool use_drag_drop = true;\n            static bool show_in_table = false;\n            static bool show_color_button = true;\n            static ImGuiMultiSelectFlags flags = ImGuiMultiSelectFlags_ClearOnEscape | ImGuiMultiSelectFlags_BoxSelect1d;\n            static WidgetType widget_type = WidgetType_Selectable;\n\n            if (ImGui::TreeNode(\"Options\"))\n            {\n                if (ImGui::RadioButton(\"Selectables\", widget_type == WidgetType_Selectable)) { widget_type = WidgetType_Selectable; }\n                ImGui::SameLine();\n                if (ImGui::RadioButton(\"Tree nodes\", widget_type == WidgetType_TreeNode)) { widget_type = WidgetType_TreeNode; }\n                ImGui::SameLine();\n                HelpMarker(\"TreeNode() is technically supported but... using this correctly is more complicated (you need some sort of linear/random access to your tree, which is suited to advanced trees setups already implementing filters and clipper. We will work toward simplifying and demoing this.\\n\\nFor now the tree demo is actually a little bit meaningless because it is an empty tree with only root nodes.\");\n                ImGui::Checkbox(\"Enable clipper\", &use_clipper);\n                ImGui::Checkbox(\"Enable deletion\", &use_deletion);\n                ImGui::Checkbox(\"Enable drag & drop\", &use_drag_drop);\n                ImGui::Checkbox(\"Show in a table\", &show_in_table);\n                ImGui::Checkbox(\"Show color button\", &show_color_button);\n                ImGui::CheckboxFlags(\"ImGuiMultiSelectFlags_SingleSelect\", &flags, ImGuiMultiSelectFlags_SingleSelect);\n                ImGui::CheckboxFlags(\"ImGuiMultiSelectFlags_NoSelectAll\", &flags, ImGuiMultiSelectFlags_NoSelectAll);\n                ImGui::CheckboxFlags(\"ImGuiMultiSelectFlags_NoRangeSelect\", &flags, ImGuiMultiSelectFlags_NoRangeSelect);\n                ImGui::CheckboxFlags(\"ImGuiMultiSelectFlags_NoAutoSelect\", &flags, ImGuiMultiSelectFlags_NoAutoSelect);\n                ImGui::CheckboxFlags(\"ImGuiMultiSelectFlags_NoAutoClear\", &flags, ImGuiMultiSelectFlags_NoAutoClear);\n                ImGui::CheckboxFlags(\"ImGuiMultiSelectFlags_NoAutoClearOnReselect\", &flags, ImGuiMultiSelectFlags_NoAutoClearOnReselect);\n                ImGui::CheckboxFlags(\"ImGuiMultiSelectFlags_BoxSelect1d\", &flags, ImGuiMultiSelectFlags_BoxSelect1d);\n                ImGui::CheckboxFlags(\"ImGuiMultiSelectFlags_BoxSelect2d\", &flags, ImGuiMultiSelectFlags_BoxSelect2d);\n                ImGui::CheckboxFlags(\"ImGuiMultiSelectFlags_BoxSelectNoScroll\", &flags, ImGuiMultiSelectFlags_BoxSelectNoScroll);\n                ImGui::CheckboxFlags(\"ImGuiMultiSelectFlags_ClearOnEscape\", &flags, ImGuiMultiSelectFlags_ClearOnEscape);\n                ImGui::CheckboxFlags(\"ImGuiMultiSelectFlags_ClearOnClickVoid\", &flags, ImGuiMultiSelectFlags_ClearOnClickVoid);\n                if (ImGui::CheckboxFlags(\"ImGuiMultiSelectFlags_ScopeWindow\", &flags, ImGuiMultiSelectFlags_ScopeWindow) && (flags & ImGuiMultiSelectFlags_ScopeWindow))\n                    flags &= ~ImGuiMultiSelectFlags_ScopeRect;\n                if (ImGui::CheckboxFlags(\"ImGuiMultiSelectFlags_ScopeRect\", &flags, ImGuiMultiSelectFlags_ScopeRect) && (flags & ImGuiMultiSelectFlags_ScopeRect))\n                    flags &= ~ImGuiMultiSelectFlags_ScopeWindow;\n                if (ImGui::CheckboxFlags(\"ImGuiMultiSelectFlags_SelectOnClick\", &flags, ImGuiMultiSelectFlags_SelectOnClick) && (flags & ImGuiMultiSelectFlags_SelectOnClick))\n                    flags &= ~ImGuiMultiSelectFlags_SelectOnClickRelease;\n                if (ImGui::CheckboxFlags(\"ImGuiMultiSelectFlags_SelectOnClickRelease\", &flags, ImGuiMultiSelectFlags_SelectOnClickRelease) && (flags & ImGuiMultiSelectFlags_SelectOnClickRelease))\n                    flags &= ~ImGuiMultiSelectFlags_SelectOnClick;\n                ImGui::SameLine(); HelpMarker(\"Allow dragging an unselected item without altering selection.\");\n                ImGui::TreePop();\n            }\n\n            // Initialize default list with 1000 items.\n            // Use default selection.Adapter: Pass index to SetNextItemSelectionUserData(), store index in Selection\n            static ImVector<int> items;\n            static int items_next_id = 0;\n            if (items_next_id == 0) { for (int n = 0; n < 1000; n++) { items.push_back(items_next_id++); } }\n            static ExampleSelectionWithDeletion selection;\n            static bool request_deletion_from_menu = false; // Queue deletion triggered from context menu\n\n            ImGui::Text(\"Selection size: %d/%d\", selection.Size, items.Size);\n\n            const float items_height = (widget_type == WidgetType_TreeNode) ? ImGui::GetTextLineHeight() : ImGui::GetTextLineHeightWithSpacing();\n            ImGui::SetNextWindowContentSize(ImVec2(0.0f, items.Size * items_height));\n            if (ImGui::BeginChild(\"##Basket\", ImVec2(-FLT_MIN, ImGui::GetFontSize() * 20), ImGuiChildFlags_FrameStyle | ImGuiChildFlags_ResizeY))\n            {\n                ImVec2 color_button_sz(ImGui::GetFontSize(), ImGui::GetFontSize());\n                if (widget_type == WidgetType_TreeNode)\n                    ImGui::PushStyleVarY(ImGuiStyleVar_ItemSpacing, 0.0f);\n\n                ImGuiMultiSelectIO* ms_io = ImGui::BeginMultiSelect(flags, selection.Size, items.Size);\n                selection.ApplyRequests(ms_io);\n\n                const bool want_delete = (ImGui::Shortcut(ImGuiKey_Delete, ImGuiInputFlags_Repeat) && (selection.Size > 0)) || request_deletion_from_menu;\n                const int item_curr_idx_to_focus = want_delete ? selection.ApplyDeletionPreLoop(ms_io, items.Size) : -1;\n                request_deletion_from_menu = false;\n\n                if (show_in_table)\n                {\n                    if (widget_type == WidgetType_TreeNode)\n                        ImGui::PushStyleVar(ImGuiStyleVar_CellPadding, ImVec2(0.0f, 0.0f));\n                    ImGui::BeginTable(\"##Split\", 2, ImGuiTableFlags_Resizable | ImGuiTableFlags_NoSavedSettings | ImGuiTableFlags_NoPadOuterX);\n                    ImGui::TableSetupColumn(\"\", ImGuiTableColumnFlags_WidthStretch, 0.70f);\n                    ImGui::TableSetupColumn(\"\", ImGuiTableColumnFlags_WidthStretch, 0.30f);\n                    //ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacingY, 0.0f);\n                }\n\n                ImGuiListClipper clipper;\n                if (use_clipper)\n                {\n                    clipper.Begin(items.Size);\n                    if (item_curr_idx_to_focus != -1)\n                        clipper.IncludeItemByIndex(item_curr_idx_to_focus); // Ensure focused item is not clipped.\n                    if (ms_io->RangeSrcItem != -1)\n                        clipper.IncludeItemByIndex((int)ms_io->RangeSrcItem); // Ensure RangeSrc item is not clipped.\n                }\n\n                while (!use_clipper || clipper.Step())\n                {\n                    const int item_begin = use_clipper ? clipper.DisplayStart : 0;\n                    const int item_end = use_clipper ? clipper.DisplayEnd : items.Size;\n                    for (int n = item_begin; n < item_end; n++)\n                    {\n                        if (show_in_table)\n                            ImGui::TableNextColumn();\n\n                        const int item_id = items[n];\n                        const char* item_category = ExampleNames[item_id % IM_ARRAYSIZE(ExampleNames)];\n                        char label[64];\n                        sprintf(label, \"Object %05d: %s\", item_id, item_category);\n\n                        // IMPORTANT: for deletion refocus to work we need object ID to be stable,\n                        // aka not depend on their index in the list. Here we use our persistent item_id\n                        // instead of index to build a unique ID that will persist.\n                        // (If we used PushID(index) instead, focus wouldn't be restored correctly after deletion).\n                        ImGui::PushID(item_id);\n\n                        // Emit a color button, to test that Shift+LeftArrow landing on an item that is not part\n                        // of the selection scope doesn't erroneously alter our selection.\n                        if (show_color_button)\n                        {\n                            ImU32 dummy_col = (ImU32)((unsigned int)n * 0xC250B74B) | IM_COL32_A_MASK;\n                            ImGui::ColorButton(\"##\", ImColor(dummy_col), ImGuiColorEditFlags_NoTooltip, color_button_sz);\n                            ImGui::SameLine();\n                        }\n\n                        // Submit item\n                        bool item_is_selected = selection.Contains((ImGuiID)n);\n                        bool item_is_open = false;\n                        ImGui::SetNextItemSelectionUserData(n);\n                        if (widget_type == WidgetType_Selectable)\n                        {\n                            ImGui::Selectable(label, item_is_selected, ImGuiSelectableFlags_None);\n                        }\n                        else if (widget_type == WidgetType_TreeNode)\n                        {\n                            ImGuiTreeNodeFlags tree_node_flags = ImGuiTreeNodeFlags_SpanAvailWidth | ImGuiTreeNodeFlags_OpenOnArrow | ImGuiTreeNodeFlags_OpenOnDoubleClick;\n                            if (item_is_selected)\n                                tree_node_flags |= ImGuiTreeNodeFlags_Selected;\n                            item_is_open = ImGui::TreeNodeEx(label, tree_node_flags);\n                        }\n\n                        // Focus (for after deletion)\n                        if (item_curr_idx_to_focus == n)\n                            ImGui::SetKeyboardFocusHere(-1);\n\n                        // Drag and Drop\n                        if (use_drag_drop && ImGui::BeginDragDropSource())\n                        {\n                            // Create payload with full selection OR single unselected item.\n                            // (the later is only possible when using ImGuiMultiSelectFlags_SelectOnClickRelease)\n                            if (ImGui::GetDragDropPayload() == NULL)\n                            {\n                                ImVector<int> payload_items;\n                                void* it = NULL;\n                                ImGuiID id = 0;\n                                if (!item_is_selected)\n                                    payload_items.push_back(item_id);\n                                else\n                                    while (selection.GetNextSelectedItem(&it, &id))\n                                        payload_items.push_back((int)id);\n                                ImGui::SetDragDropPayload(\"MULTISELECT_DEMO_ITEMS\", payload_items.Data, (size_t)payload_items.size_in_bytes());\n                            }\n\n                            // Display payload content in tooltip\n                            const ImGuiPayload* payload = ImGui::GetDragDropPayload();\n                            const int* payload_items = (int*)payload->Data;\n                            const int payload_count = (int)payload->DataSize / (int)sizeof(int);\n                            if (payload_count == 1)\n                                ImGui::Text(\"Object %05d: %s\", payload_items[0], ExampleNames[payload_items[0] % IM_ARRAYSIZE(ExampleNames)]);\n                            else\n                                ImGui::Text(\"Dragging %d objects\", payload_count);\n\n                            ImGui::EndDragDropSource();\n                        }\n\n                        if (widget_type == WidgetType_TreeNode && item_is_open)\n                            ImGui::TreePop();\n\n                        // Right-click: context menu\n                        if (ImGui::BeginPopupContextItem())\n                        {\n                            ImGui::BeginDisabled(!use_deletion || selection.Size == 0);\n                            sprintf(label, \"Delete %d item(s)###DeleteSelected\", selection.Size);\n                            if (ImGui::Selectable(label))\n                                request_deletion_from_menu = true;\n                            ImGui::EndDisabled();\n                            ImGui::Selectable(\"Close\");\n                            ImGui::EndPopup();\n                        }\n\n                        // Demo content within a table\n                        if (show_in_table)\n                        {\n                            ImGui::TableNextColumn();\n                            ImGui::SetNextItemWidth(-FLT_MIN);\n                            ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(0, 0));\n                            ImGui::InputText(\"###NoLabel\", (char*)(void*)item_category, strlen(item_category), ImGuiInputTextFlags_ReadOnly);\n                            ImGui::PopStyleVar();\n                        }\n\n                        ImGui::PopID();\n                    }\n                    if (!use_clipper)\n                        break;\n                }\n\n                if (show_in_table)\n                {\n                    ImGui::EndTable();\n                    if (widget_type == WidgetType_TreeNode)\n                        ImGui::PopStyleVar();\n                }\n\n                // Apply multi-select requests\n                ms_io = ImGui::EndMultiSelect();\n                selection.ApplyRequests(ms_io);\n                if (want_delete)\n                    selection.ApplyDeletionPostLoop(ms_io, items, item_curr_idx_to_focus);\n\n                if (widget_type == WidgetType_TreeNode)\n                    ImGui::PopStyleVar();\n            }\n            ImGui::EndChild();\n            ImGui::TreePop();\n        }\n        ImGui::TreePop();\n    }\n}\n\n//-----------------------------------------------------------------------------\n// [SECTION] DemoWindowWidgetsTabs()\n//-----------------------------------------------------------------------------\n\nstatic void DemoWindowWidgetsTabs()\n{\n    IMGUI_DEMO_MARKER(\"Widgets/Tabs\");\n    if (ImGui::TreeNode(\"Tabs\"))\n    {\n        IMGUI_DEMO_MARKER(\"Widgets/Tabs/Basic\");\n        if (ImGui::TreeNode(\"Basic\"))\n        {\n            ImGuiTabBarFlags tab_bar_flags = ImGuiTabBarFlags_None;\n            if (ImGui::BeginTabBar(\"MyTabBar\", tab_bar_flags))\n            {\n                if (ImGui::BeginTabItem(\"Avocado\"))\n                {\n                    ImGui::Text(\"This is the Avocado tab!\\nblah blah blah blah blah\");\n                    ImGui::EndTabItem();\n                }\n                if (ImGui::BeginTabItem(\"Broccoli\"))\n                {\n                    ImGui::Text(\"This is the Broccoli tab!\\nblah blah blah blah blah\");\n                    ImGui::EndTabItem();\n                }\n                if (ImGui::BeginTabItem(\"Cucumber\"))\n                {\n                    ImGui::Text(\"This is the Cucumber tab!\\nblah blah blah blah blah\");\n                    ImGui::EndTabItem();\n                }\n                ImGui::EndTabBar();\n            }\n            ImGui::Separator();\n            ImGui::TreePop();\n        }\n\n        IMGUI_DEMO_MARKER(\"Widgets/Tabs/Advanced & Close Button\");\n        if (ImGui::TreeNode(\"Advanced & Close Button\"))\n        {\n            // Expose a couple of the available flags. In most cases you may just call BeginTabBar() with no flags (0).\n            static ImGuiTabBarFlags tab_bar_flags = ImGuiTabBarFlags_Reorderable;\n            ImGui::CheckboxFlags(\"ImGuiTabBarFlags_Reorderable\", &tab_bar_flags, ImGuiTabBarFlags_Reorderable);\n            ImGui::CheckboxFlags(\"ImGuiTabBarFlags_AutoSelectNewTabs\", &tab_bar_flags, ImGuiTabBarFlags_AutoSelectNewTabs);\n            ImGui::CheckboxFlags(\"ImGuiTabBarFlags_TabListPopupButton\", &tab_bar_flags, ImGuiTabBarFlags_TabListPopupButton);\n            ImGui::CheckboxFlags(\"ImGuiTabBarFlags_NoCloseWithMiddleMouseButton\", &tab_bar_flags, ImGuiTabBarFlags_NoCloseWithMiddleMouseButton);\n            ImGui::CheckboxFlags(\"ImGuiTabBarFlags_DrawSelectedOverline\", &tab_bar_flags, ImGuiTabBarFlags_DrawSelectedOverline);\n            if ((tab_bar_flags & ImGuiTabBarFlags_FittingPolicyMask_) == 0)\n                tab_bar_flags |= ImGuiTabBarFlags_FittingPolicyDefault_;\n            if (ImGui::CheckboxFlags(\"ImGuiTabBarFlags_FittingPolicyResizeDown\", &tab_bar_flags, ImGuiTabBarFlags_FittingPolicyResizeDown))\n                tab_bar_flags &= ~(ImGuiTabBarFlags_FittingPolicyMask_ ^ ImGuiTabBarFlags_FittingPolicyResizeDown);\n            if (ImGui::CheckboxFlags(\"ImGuiTabBarFlags_FittingPolicyScroll\", &tab_bar_flags, ImGuiTabBarFlags_FittingPolicyScroll))\n                tab_bar_flags &= ~(ImGuiTabBarFlags_FittingPolicyMask_ ^ ImGuiTabBarFlags_FittingPolicyScroll);\n\n            // Tab Bar\n            ImGui::AlignTextToFramePadding();\n            ImGui::Text(\"Opened:\");\n            const char* names[4] = { \"Artichoke\", \"Beetroot\", \"Celery\", \"Daikon\" };\n            static bool opened[4] = { true, true, true, true }; // Persistent user state\n            for (int n = 0; n < IM_ARRAYSIZE(opened); n++)\n            {\n                ImGui::SameLine();\n                ImGui::Checkbox(names[n], &opened[n]);\n            }\n\n            // Passing a bool* to BeginTabItem() is similar to passing one to Begin():\n            // the underlying bool will be set to false when the tab is closed.\n            if (ImGui::BeginTabBar(\"MyTabBar\", tab_bar_flags))\n            {\n                for (int n = 0; n < IM_ARRAYSIZE(opened); n++)\n                    if (opened[n] && ImGui::BeginTabItem(names[n], &opened[n], ImGuiTabItemFlags_None))\n                    {\n                        ImGui::Text(\"This is the %s tab!\", names[n]);\n                        if (n & 1)\n                            ImGui::Text(\"I am an odd tab.\");\n                        ImGui::EndTabItem();\n                    }\n                ImGui::EndTabBar();\n            }\n            ImGui::Separator();\n            ImGui::TreePop();\n        }\n\n        IMGUI_DEMO_MARKER(\"Widgets/Tabs/TabItemButton & Leading-Trailing flags\");\n        if (ImGui::TreeNode(\"TabItemButton & Leading/Trailing flags\"))\n        {\n            static ImVector<int> active_tabs;\n            static int next_tab_id = 0;\n            if (next_tab_id == 0) // Initialize with some default tabs\n                for (int i = 0; i < 3; i++)\n                    active_tabs.push_back(next_tab_id++);\n\n            // TabItemButton() and Leading/Trailing flags are distinct features which we will demo together.\n            // (It is possible to submit regular tabs with Leading/Trailing flags, or TabItemButton tabs without Leading/Trailing flags...\n            // but they tend to make more sense together)\n            static bool show_leading_button = true;\n            static bool show_trailing_button = true;\n            ImGui::Checkbox(\"Show Leading TabItemButton()\", &show_leading_button);\n            ImGui::Checkbox(\"Show Trailing TabItemButton()\", &show_trailing_button);\n\n            // Expose some other flags which are useful to showcase how they interact with Leading/Trailing tabs\n            static ImGuiTabBarFlags tab_bar_flags = ImGuiTabBarFlags_AutoSelectNewTabs | ImGuiTabBarFlags_Reorderable | ImGuiTabBarFlags_FittingPolicyResizeDown;\n            ImGui::CheckboxFlags(\"ImGuiTabBarFlags_TabListPopupButton\", &tab_bar_flags, ImGuiTabBarFlags_TabListPopupButton);\n            if (ImGui::CheckboxFlags(\"ImGuiTabBarFlags_FittingPolicyResizeDown\", &tab_bar_flags, ImGuiTabBarFlags_FittingPolicyResizeDown))\n                tab_bar_flags &= ~(ImGuiTabBarFlags_FittingPolicyMask_ ^ ImGuiTabBarFlags_FittingPolicyResizeDown);\n            if (ImGui::CheckboxFlags(\"ImGuiTabBarFlags_FittingPolicyScroll\", &tab_bar_flags, ImGuiTabBarFlags_FittingPolicyScroll))\n                tab_bar_flags &= ~(ImGuiTabBarFlags_FittingPolicyMask_ ^ ImGuiTabBarFlags_FittingPolicyScroll);\n\n            if (ImGui::BeginTabBar(\"MyTabBar\", tab_bar_flags))\n            {\n                // Demo a Leading TabItemButton(): click the \"?\" button to open a menu\n                if (show_leading_button)\n                    if (ImGui::TabItemButton(\"?\", ImGuiTabItemFlags_Leading | ImGuiTabItemFlags_NoTooltip))\n                        ImGui::OpenPopup(\"MyHelpMenu\");\n                if (ImGui::BeginPopup(\"MyHelpMenu\"))\n                {\n                    ImGui::Selectable(\"Hello!\");\n                    ImGui::EndPopup();\n                }\n\n                // Demo Trailing Tabs: click the \"+\" button to add a new tab.\n                // (In your app you may want to use a font icon instead of the \"+\")\n                // We submit it before the regular tabs, but thanks to the ImGuiTabItemFlags_Trailing flag it will always appear at the end.\n                if (show_trailing_button)\n                    if (ImGui::TabItemButton(\"+\", ImGuiTabItemFlags_Trailing | ImGuiTabItemFlags_NoTooltip))\n                        active_tabs.push_back(next_tab_id++); // Add new tab\n\n                // Submit our regular tabs\n                for (int n = 0; n < active_tabs.Size; )\n                {\n                    bool open = true;\n                    char name[16];\n                    snprintf(name, IM_ARRAYSIZE(name), \"%04d\", active_tabs[n]);\n                    if (ImGui::BeginTabItem(name, &open, ImGuiTabItemFlags_None))\n                    {\n                        ImGui::Text(\"This is the %s tab!\", name);\n                        ImGui::EndTabItem();\n                    }\n\n                    if (!open)\n                        active_tabs.erase(active_tabs.Data + n);\n                    else\n                        n++;\n                }\n\n                ImGui::EndTabBar();\n            }\n            ImGui::Separator();\n            ImGui::TreePop();\n        }\n        ImGui::TreePop();\n    }\n}\n\n//-----------------------------------------------------------------------------\n// [SECTION] DemoWindowWidgetsText()\n//-----------------------------------------------------------------------------\n\nstatic void DemoWindowWidgetsText()\n{\n    IMGUI_DEMO_MARKER(\"Widgets/Text\");\n    if (ImGui::TreeNode(\"Text\"))\n    {\n        IMGUI_DEMO_MARKER(\"Widgets/Text/Colored Text\");\n        if (ImGui::TreeNode(\"Colorful Text\"))\n        {\n            // Using shortcut. You can use PushStyleColor()/PopStyleColor() for more flexibility.\n            ImGui::TextColored(ImVec4(1.0f, 0.0f, 1.0f, 1.0f), \"Pink\");\n            ImGui::TextColored(ImVec4(1.0f, 1.0f, 0.0f, 1.0f), \"Yellow\");\n            ImGui::TextDisabled(\"Disabled\");\n            ImGui::SameLine(); HelpMarker(\"The TextDisabled color is stored in ImGuiStyle.\");\n            ImGui::TreePop();\n        }\n\n        IMGUI_DEMO_MARKER(\"Widgets/Text/Word Wrapping\");\n        if (ImGui::TreeNode(\"Word Wrapping\"))\n        {\n            // Using shortcut. You can use PushTextWrapPos()/PopTextWrapPos() for more flexibility.\n            ImGui::TextWrapped(\n                \"This text should automatically wrap on the edge of the window. The current implementation \"\n                \"for text wrapping follows simple rules suitable for English and possibly other languages.\");\n            ImGui::Spacing();\n\n            static float wrap_width = 200.0f;\n            ImGui::SliderFloat(\"Wrap width\", &wrap_width, -20, 600, \"%.0f\");\n\n            ImDrawList* draw_list = ImGui::GetWindowDrawList();\n            for (int n = 0; n < 2; n++)\n            {\n                ImGui::Text(\"Test paragraph %d:\", n);\n                ImVec2 pos = ImGui::GetCursorScreenPos();\n                ImVec2 marker_min = ImVec2(pos.x + wrap_width, pos.y);\n                ImVec2 marker_max = ImVec2(pos.x + wrap_width + 10, pos.y + ImGui::GetTextLineHeight());\n                ImGui::PushTextWrapPos(ImGui::GetCursorPos().x + wrap_width);\n                if (n == 0)\n                    ImGui::Text(\"The lazy dog is a good dog. This paragraph should fit within %.0f pixels. Testing a 1 character word. The quick brown fox jumps over the lazy dog.\", wrap_width);\n                else\n                    ImGui::Text(\"aaaaaaaa bbbbbbbb, c cccccccc,dddddddd. d eeeeeeee   ffffffff. gggggggg!hhhhhhhh\");\n\n                // Draw actual text bounding box, following by marker of our expected limit (should not overlap!)\n                draw_list->AddRect(ImGui::GetItemRectMin(), ImGui::GetItemRectMax(), IM_COL32(255, 255, 0, 255));\n                draw_list->AddRectFilled(marker_min, marker_max, IM_COL32(255, 0, 255, 255));\n                ImGui::PopTextWrapPos();\n            }\n\n            ImGui::TreePop();\n        }\n\n        IMGUI_DEMO_MARKER(\"Widgets/Text/UTF-8 Text\");\n        if (ImGui::TreeNode(\"UTF-8 Text\"))\n        {\n            // UTF-8 test with Japanese characters\n            // (Needs a suitable font? Try \"Google Noto\" or \"Arial Unicode\". See docs/FONTS.md for details.)\n            // - From C++11 you can use the u8\"my text\" syntax to encode literal strings as UTF-8\n            // - For earlier compiler, you may be able to encode your sources as UTF-8 (e.g. in Visual Studio, you\n            //   can save your source files as 'UTF-8 without signature').\n            // - FOR THIS DEMO FILE ONLY, BECAUSE WE WANT TO SUPPORT OLD COMPILERS, WE ARE *NOT* INCLUDING RAW UTF-8\n            //   CHARACTERS IN THIS SOURCE FILE. Instead we are encoding a few strings with hexadecimal constants.\n            //   Don't do this in your application! Please use u8\"text in any language\" in your application!\n            // Note that characters values are preserved even by InputText() if the font cannot be displayed,\n            // so you can safely copy & paste garbled characters into another application.\n            ImGui::TextWrapped(\n                \"CJK text will only appear if the font was loaded with the appropriate CJK character ranges. \"\n                \"Call io.Fonts->AddFontFromFileTTF() manually to load extra character ranges. \"\n                \"Read docs/FONTS.md for details.\");\n            ImGui::Text(\"Hiragana: \\xe3\\x81\\x8b\\xe3\\x81\\x8d\\xe3\\x81\\x8f\\xe3\\x81\\x91\\xe3\\x81\\x93 (kakikukeko)\");\n            ImGui::Text(\"Kanjis: \\xe6\\x97\\xa5\\xe6\\x9c\\xac\\xe8\\xaa\\x9e (nihongo)\");\n            static char buf[32] = \"\\xe6\\x97\\xa5\\xe6\\x9c\\xac\\xe8\\xaa\\x9e\";\n            //static char buf[32] = u8\"NIHONGO\"; // <- this is how you would write it with C++11, using real kanjis\n            ImGui::InputText(\"UTF-8 input\", buf, IM_ARRAYSIZE(buf));\n            ImGui::TreePop();\n        }\n        ImGui::TreePop();\n    }\n}\n\n//-----------------------------------------------------------------------------\n// [SECTION] DemoWindowWidgetsTextFilter()\n//-----------------------------------------------------------------------------\n\nstatic void DemoWindowWidgetsTextFilter()\n{\n    IMGUI_DEMO_MARKER(\"Widgets/Text Filter\");\n    if (ImGui::TreeNode(\"Text Filter\"))\n    {\n        // Helper class to easy setup a text filter.\n        // You may want to implement a more feature-full filtering scheme in your own application.\n        HelpMarker(\"Not a widget per-se, but ImGuiTextFilter is a helper to perform simple filtering on text strings.\");\n        static ImGuiTextFilter filter;\n        ImGui::Text(\"Filter usage:\\n\"\n            \"  \\\"\\\"         display all lines\\n\"\n            \"  \\\"xxx\\\"      display lines containing \\\"xxx\\\"\\n\"\n            \"  \\\"xxx,yyy\\\"  display lines containing \\\"xxx\\\" or \\\"yyy\\\"\\n\"\n            \"  \\\"-xxx\\\"     hide lines containing \\\"xxx\\\"\");\n        filter.Draw();\n        const char* lines[] = { \"aaa1.c\", \"bbb1.c\", \"ccc1.c\", \"aaa2.cpp\", \"bbb2.cpp\", \"ccc2.cpp\", \"abc.h\", \"hello, world\" };\n        for (int i = 0; i < IM_ARRAYSIZE(lines); i++)\n            if (filter.PassFilter(lines[i]))\n                ImGui::BulletText(\"%s\", lines[i]);\n        ImGui::TreePop();\n    }\n}\n\n//-----------------------------------------------------------------------------\n// [SECTION] DemoWindowWidgetsTextInput()\n//-----------------------------------------------------------------------------\n\nstatic void DemoWindowWidgetsTextInput()\n{\n    // To wire InputText() with std::string or any other custom string type,\n    // see the \"Text Input > Resize Callback\" section of this demo, and the misc/cpp/imgui_stdlib.h file.\n    IMGUI_DEMO_MARKER(\"Widgets/Text Input\");\n    if (ImGui::TreeNode(\"Text Input\"))\n    {\n        IMGUI_DEMO_MARKER(\"Widgets/Text Input/Multi-line Text Input\");\n        if (ImGui::TreeNode(\"Multi-line Text Input\"))\n        {\n            // Note: we are using a fixed-sized buffer for simplicity here. See ImGuiInputTextFlags_CallbackResize\n            // and the code in misc/cpp/imgui_stdlib.h for how to setup InputText() for dynamically resizing strings.\n            static char text[1024 * 16] =\n                \"/*\\n\"\n                \" The Pentium F00F bug, shorthand for F0 0F C7 C8,\\n\"\n                \" the hexadecimal encoding of one offending instruction,\\n\"\n                \" more formally, the invalid operand with locked CMPXCHG8B\\n\"\n                \" instruction bug, is a design flaw in the majority of\\n\"\n                \" Intel Pentium, Pentium MMX, and Pentium OverDrive\\n\"\n                \" processors (all in the P5 microarchitecture).\\n\"\n                \"*/\\n\\n\"\n                \"label:\\n\"\n                \"\\tlock cmpxchg8b eax\\n\";\n\n            static ImGuiInputTextFlags flags = ImGuiInputTextFlags_AllowTabInput;\n            HelpMarker(\"You can use the ImGuiInputTextFlags_CallbackResize facility if you need to wire InputTextMultiline() to a dynamic string type. See misc/cpp/imgui_stdlib.h for an example. (This is not demonstrated in imgui_demo.cpp because we don't want to include <string> in here)\");\n            ImGui::CheckboxFlags(\"ImGuiInputTextFlags_ReadOnly\", &flags, ImGuiInputTextFlags_ReadOnly);\n            ImGui::CheckboxFlags(\"ImGuiInputTextFlags_AllowTabInput\", &flags, ImGuiInputTextFlags_AllowTabInput);\n            ImGui::SameLine(); HelpMarker(\"When _AllowTabInput is set, passing through the widget with Tabbing doesn't automatically activate it, in order to also cycling through subsequent widgets.\");\n            ImGui::CheckboxFlags(\"ImGuiInputTextFlags_CtrlEnterForNewLine\", &flags, ImGuiInputTextFlags_CtrlEnterForNewLine);\n            ImGui::InputTextMultiline(\"##source\", text, IM_ARRAYSIZE(text), ImVec2(-FLT_MIN, ImGui::GetTextLineHeight() * 16), flags);\n            ImGui::TreePop();\n        }\n\n        IMGUI_DEMO_MARKER(\"Widgets/Text Input/Filtered Text Input\");\n        if (ImGui::TreeNode(\"Filtered Text Input\"))\n        {\n            struct TextFilters\n            {\n                // Modify character input by altering 'data->Eventchar' (ImGuiInputTextFlags_CallbackCharFilter callback)\n                static int FilterCasingSwap(ImGuiInputTextCallbackData* data)\n                {\n                    if (data->EventChar >= 'a' && data->EventChar <= 'z') { data->EventChar -= 'a' - 'A'; } // Lowercase becomes uppercase\n                    else if (data->EventChar >= 'A' && data->EventChar <= 'Z') { data->EventChar += 'a' - 'A'; } // Uppercase becomes lowercase\n                    return 0;\n                }\n\n                // Return 0 (pass) if the character is 'i' or 'm' or 'g' or 'u' or 'i', otherwise return 1 (filter out)\n                static int FilterImGuiLetters(ImGuiInputTextCallbackData* data)\n                {\n                    if (data->EventChar < 256 && strchr(\"imgui\", (char)data->EventChar))\n                        return 0;\n                    return 1;\n                }\n            };\n\n            static char buf1[32] = \"\"; ImGui::InputText(\"default\", buf1, 32);\n            static char buf2[32] = \"\"; ImGui::InputText(\"decimal\", buf2, 32, ImGuiInputTextFlags_CharsDecimal);\n            static char buf3[32] = \"\"; ImGui::InputText(\"hexadecimal\", buf3, 32, ImGuiInputTextFlags_CharsHexadecimal | ImGuiInputTextFlags_CharsUppercase);\n            static char buf4[32] = \"\"; ImGui::InputText(\"uppercase\", buf4, 32, ImGuiInputTextFlags_CharsUppercase);\n            static char buf5[32] = \"\"; ImGui::InputText(\"no blank\", buf5, 32, ImGuiInputTextFlags_CharsNoBlank);\n            static char buf6[32] = \"\"; ImGui::InputText(\"casing swap\", buf6, 32, ImGuiInputTextFlags_CallbackCharFilter, TextFilters::FilterCasingSwap); // Use CharFilter callback to replace characters.\n            static char buf7[32] = \"\"; ImGui::InputText(\"\\\"imgui\\\"\", buf7, 32, ImGuiInputTextFlags_CallbackCharFilter, TextFilters::FilterImGuiLetters); // Use CharFilter callback to disable some characters.\n            ImGui::TreePop();\n        }\n\n        IMGUI_DEMO_MARKER(\"Widgets/Text Input/Password input\");\n        if (ImGui::TreeNode(\"Password Input\"))\n        {\n            static char password[64] = \"password123\";\n            ImGui::InputText(\"password\", password, IM_ARRAYSIZE(password), ImGuiInputTextFlags_Password);\n            ImGui::SameLine(); HelpMarker(\"Display all characters as '*'.\\nDisable clipboard cut and copy.\\nDisable logging.\\n\");\n            ImGui::InputTextWithHint(\"password (w/ hint)\", \"<password>\", password, IM_ARRAYSIZE(password), ImGuiInputTextFlags_Password);\n            ImGui::InputText(\"password (clear)\", password, IM_ARRAYSIZE(password));\n            ImGui::TreePop();\n        }\n\n        IMGUI_DEMO_MARKER(\"Widgets/Text Input/Completion, History, Edit Callbacks\");\n        if (ImGui::TreeNode(\"Completion, History, Edit Callbacks\"))\n        {\n            struct Funcs\n            {\n                static int MyCallback(ImGuiInputTextCallbackData* data)\n                {\n                    if (data->EventFlag == ImGuiInputTextFlags_CallbackCompletion)\n                    {\n                        data->InsertChars(data->CursorPos, \"..\");\n                    }\n                    else if (data->EventFlag == ImGuiInputTextFlags_CallbackHistory)\n                    {\n                        if (data->EventKey == ImGuiKey_UpArrow)\n                        {\n                            data->DeleteChars(0, data->BufTextLen);\n                            data->InsertChars(0, \"Pressed Up!\");\n                            data->SelectAll();\n                        }\n                        else if (data->EventKey == ImGuiKey_DownArrow)\n                        {\n                            data->DeleteChars(0, data->BufTextLen);\n                            data->InsertChars(0, \"Pressed Down!\");\n                            data->SelectAll();\n                        }\n                    }\n                    else if (data->EventFlag == ImGuiInputTextFlags_CallbackEdit)\n                    {\n                        // Toggle casing of first character\n                        char c = data->Buf[0];\n                        if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')) data->Buf[0] ^= 32;\n                        data->BufDirty = true;\n\n                        // Increment a counter\n                        int* p_int = (int*)data->UserData;\n                        *p_int = *p_int + 1;\n                    }\n                    return 0;\n                }\n            };\n            static char buf1[64];\n            ImGui::InputText(\"Completion\", buf1, 64, ImGuiInputTextFlags_CallbackCompletion, Funcs::MyCallback);\n            ImGui::SameLine(); HelpMarker(\n                \"Here we append \\\"..\\\" each time Tab is pressed. \"\n                \"See 'Examples>Console' for a more meaningful demonstration of using this callback.\");\n\n            static char buf2[64];\n            ImGui::InputText(\"History\", buf2, 64, ImGuiInputTextFlags_CallbackHistory, Funcs::MyCallback);\n            ImGui::SameLine(); HelpMarker(\n                \"Here we replace and select text each time Up/Down are pressed. \"\n                \"See 'Examples>Console' for a more meaningful demonstration of using this callback.\");\n\n            static char buf3[64];\n            static int edit_count = 0;\n            ImGui::InputText(\"Edit\", buf3, 64, ImGuiInputTextFlags_CallbackEdit, Funcs::MyCallback, (void*)&edit_count);\n            ImGui::SameLine(); HelpMarker(\n                \"Here we toggle the casing of the first character on every edit + count edits.\");\n            ImGui::SameLine(); ImGui::Text(\"(%d)\", edit_count);\n\n            ImGui::TreePop();\n        }\n\n        IMGUI_DEMO_MARKER(\"Widgets/Text Input/Resize Callback\");\n        if (ImGui::TreeNode(\"Resize Callback\"))\n        {\n            // To wire InputText() with std::string or any other custom string type,\n            // you can use the ImGuiInputTextFlags_CallbackResize flag + create a custom ImGui::InputText() wrapper\n            // using your preferred type. See misc/cpp/imgui_stdlib.h for an implementation of this using std::string.\n            HelpMarker(\n                \"Using ImGuiInputTextFlags_CallbackResize to wire your custom string type to InputText().\\n\\n\"\n                \"See misc/cpp/imgui_stdlib.h for an implementation of this for std::string.\");\n            struct Funcs\n            {\n                static int MyResizeCallback(ImGuiInputTextCallbackData* data)\n                {\n                    if (data->EventFlag == ImGuiInputTextFlags_CallbackResize)\n                    {\n                        ImVector<char>* my_str = (ImVector<char>*)data->UserData;\n                        IM_ASSERT(my_str->begin() == data->Buf);\n                        my_str->resize(data->BufSize); // NB: On resizing calls, generally data->BufSize == data->BufTextLen + 1\n                        data->Buf = my_str->begin();\n                    }\n                    return 0;\n                }\n\n                // Note: Because ImGui:: is a namespace you would typically add your own function into the namespace.\n                // For example, you code may declare a function 'ImGui::InputText(const char* label, MyString* my_str)'\n                static bool MyInputTextMultiline(const char* label, ImVector<char>* my_str, const ImVec2& size = ImVec2(0, 0), ImGuiInputTextFlags flags = 0)\n                {\n                    IM_ASSERT((flags & ImGuiInputTextFlags_CallbackResize) == 0);\n                    return ImGui::InputTextMultiline(label, my_str->begin(), (size_t)my_str->size(), size, flags | ImGuiInputTextFlags_CallbackResize, Funcs::MyResizeCallback, (void*)my_str);\n                }\n            };\n\n            // For this demo we are using ImVector as a string container.\n            // Note that because we need to store a terminating zero character, our size/capacity are 1 more\n            // than usually reported by a typical string class.\n            static ImVector<char> my_str;\n            if (my_str.empty())\n                my_str.push_back(0);\n            Funcs::MyInputTextMultiline(\"##MyStr\", &my_str, ImVec2(-FLT_MIN, ImGui::GetTextLineHeight() * 16));\n            ImGui::Text(\"Data: %p\\nSize: %d\\nCapacity: %d\", (void*)my_str.begin(), my_str.size(), my_str.capacity());\n            ImGui::TreePop();\n        }\n\n        IMGUI_DEMO_MARKER(\"Widgets/Text Input/Eliding, Alignment\");\n        if (ImGui::TreeNode(\"Eliding, Alignment\"))\n        {\n            static char buf1[128] = \"/path/to/some/folder/with/long/filename.cpp\";\n            static ImGuiInputTextFlags flags = ImGuiInputTextFlags_ElideLeft;\n            ImGui::CheckboxFlags(\"ImGuiInputTextFlags_ElideLeft\", &flags, ImGuiInputTextFlags_ElideLeft);\n            ImGui::InputText(\"Path\", buf1, IM_ARRAYSIZE(buf1), flags);\n            ImGui::TreePop();\n        }\n\n        IMGUI_DEMO_MARKER(\"Widgets/Text Input/Miscellaneous\");\n        if (ImGui::TreeNode(\"Miscellaneous\"))\n        {\n            static char buf1[16];\n            static ImGuiInputTextFlags flags = ImGuiInputTextFlags_EscapeClearsAll;\n            ImGui::CheckboxFlags(\"ImGuiInputTextFlags_EscapeClearsAll\", &flags, ImGuiInputTextFlags_EscapeClearsAll);\n            ImGui::CheckboxFlags(\"ImGuiInputTextFlags_ReadOnly\", &flags, ImGuiInputTextFlags_ReadOnly);\n            ImGui::CheckboxFlags(\"ImGuiInputTextFlags_NoUndoRedo\", &flags, ImGuiInputTextFlags_NoUndoRedo);\n            ImGui::InputText(\"Hello\", buf1, IM_ARRAYSIZE(buf1), flags);\n            ImGui::TreePop();\n        }\n\n        ImGui::TreePop();\n    }\n\n}\n\n//-----------------------------------------------------------------------------\n// [SECTION] DemoWindowWidgetsTooltips()\n//-----------------------------------------------------------------------------\n\nstatic void DemoWindowWidgetsTooltips()\n{\n    IMGUI_DEMO_MARKER(\"Widgets/Tooltips\");\n    if (ImGui::TreeNode(\"Tooltips\"))\n    {\n        // Tooltips are windows following the mouse. They do not take focus away.\n        ImGui::SeparatorText(\"General\");\n\n        // Typical use cases:\n        // - Short-form (text only):      SetItemTooltip(\"Hello\");\n        // - Short-form (any contents):   if (BeginItemTooltip()) { Text(\"Hello\"); EndTooltip(); }\n\n        // - Full-form (text only):       if (IsItemHovered(...)) { SetTooltip(\"Hello\"); }\n        // - Full-form (any contents):    if (IsItemHovered(...) && BeginTooltip()) { Text(\"Hello\"); EndTooltip(); }\n\n        HelpMarker(\n            \"Tooltip are typically created by using a IsItemHovered() + SetTooltip() sequence.\\n\\n\"\n            \"We provide a helper SetItemTooltip() function to perform the two with standards flags.\");\n\n        ImVec2 sz = ImVec2(-FLT_MIN, 0.0f);\n\n        ImGui::Button(\"Basic\", sz);\n        ImGui::SetItemTooltip(\"I am a tooltip\");\n\n        ImGui::Button(\"Fancy\", sz);\n        if (ImGui::BeginItemTooltip())\n        {\n            ImGui::Text(\"I am a fancy tooltip\");\n            static float arr[] = { 0.6f, 0.1f, 1.0f, 0.5f, 0.92f, 0.1f, 0.2f };\n            ImGui::PlotLines(\"Curve\", arr, IM_ARRAYSIZE(arr));\n            ImGui::Text(\"Sin(time) = %f\", sinf((float)ImGui::GetTime()));\n            ImGui::EndTooltip();\n        }\n\n        ImGui::SeparatorText(\"Always On\");\n\n        // Showcase NOT relying on a IsItemHovered() to emit a tooltip.\n        // Here the tooltip is always emitted when 'always_on == true'.\n        static int always_on = 0;\n        ImGui::RadioButton(\"Off\", &always_on, 0);\n        ImGui::SameLine();\n        ImGui::RadioButton(\"Always On (Simple)\", &always_on, 1);\n        ImGui::SameLine();\n        ImGui::RadioButton(\"Always On (Advanced)\", &always_on, 2);\n        if (always_on == 1)\n            ImGui::SetTooltip(\"I am following you around.\");\n        else if (always_on == 2 && ImGui::BeginTooltip())\n        {\n            ImGui::ProgressBar(sinf((float)ImGui::GetTime()) * 0.5f + 0.5f, ImVec2(ImGui::GetFontSize() * 25, 0.0f));\n            ImGui::EndTooltip();\n        }\n\n        ImGui::SeparatorText(\"Custom\");\n\n        HelpMarker(\n            \"Passing ImGuiHoveredFlags_ForTooltip to IsItemHovered() is the preferred way to standardize \"\n            \"tooltip activation details across your application. You may however decide to use custom \"\n            \"flags for a specific tooltip instance.\");\n\n        // The following examples are passed for documentation purpose but may not be useful to most users.\n        // Passing ImGuiHoveredFlags_ForTooltip to IsItemHovered() will pull ImGuiHoveredFlags flags values from\n        // 'style.HoverFlagsForTooltipMouse' or 'style.HoverFlagsForTooltipNav' depending on whether mouse or keyboard/gamepad is being used.\n        // With default settings, ImGuiHoveredFlags_ForTooltip is equivalent to ImGuiHoveredFlags_DelayShort + ImGuiHoveredFlags_Stationary.\n        ImGui::Button(\"Manual\", sz);\n        if (ImGui::IsItemHovered(ImGuiHoveredFlags_ForTooltip))\n            ImGui::SetTooltip(\"I am a manually emitted tooltip.\");\n\n        ImGui::Button(\"DelayNone\", sz);\n        if (ImGui::IsItemHovered(ImGuiHoveredFlags_DelayNone))\n            ImGui::SetTooltip(\"I am a tooltip with no delay.\");\n\n        ImGui::Button(\"DelayShort\", sz);\n        if (ImGui::IsItemHovered(ImGuiHoveredFlags_DelayShort | ImGuiHoveredFlags_NoSharedDelay))\n            ImGui::SetTooltip(\"I am a tooltip with a short delay (%0.2f sec).\", ImGui::GetStyle().HoverDelayShort);\n\n        ImGui::Button(\"DelayLong\", sz);\n        if (ImGui::IsItemHovered(ImGuiHoveredFlags_DelayNormal | ImGuiHoveredFlags_NoSharedDelay))\n            ImGui::SetTooltip(\"I am a tooltip with a long delay (%0.2f sec).\", ImGui::GetStyle().HoverDelayNormal);\n\n        ImGui::Button(\"Stationary\", sz);\n        if (ImGui::IsItemHovered(ImGuiHoveredFlags_Stationary))\n            ImGui::SetTooltip(\"I am a tooltip requiring mouse to be stationary before activating.\");\n\n        // Using ImGuiHoveredFlags_ForTooltip will pull flags from 'style.HoverFlagsForTooltipMouse' or 'style.HoverFlagsForTooltipNav',\n        // which default value include the ImGuiHoveredFlags_AllowWhenDisabled flag.\n        ImGui::BeginDisabled();\n        ImGui::Button(\"Disabled item\", sz);\n        if (ImGui::IsItemHovered(ImGuiHoveredFlags_ForTooltip))\n            ImGui::SetTooltip(\"I am a a tooltip for a disabled item.\");\n        ImGui::EndDisabled();\n\n        ImGui::TreePop();\n    }\n}\n\n//-----------------------------------------------------------------------------\n// [SECTION] DemoWindowWidgetsTreeNodes()\n//-----------------------------------------------------------------------------\n\nstatic void DemoWindowWidgetsTreeNodes()\n{\n    IMGUI_DEMO_MARKER(\"Widgets/Tree Nodes\");\n    if (ImGui::TreeNode(\"Tree Nodes\"))\n    {\n        IMGUI_DEMO_MARKER(\"Widgets/Tree Nodes/Basic trees\");\n        if (ImGui::TreeNode(\"Basic trees\"))\n        {\n            for (int i = 0; i < 5; i++)\n            {\n                // Use SetNextItemOpen() so set the default state of a node to be open. We could\n                // also use TreeNodeEx() with the ImGuiTreeNodeFlags_DefaultOpen flag to achieve the same thing!\n                if (i == 0)\n                    ImGui::SetNextItemOpen(true, ImGuiCond_Once);\n\n                // Here we use PushID() to generate a unique base ID, and then the \"\" used as TreeNode id won't conflict.\n                // An alternative to using 'PushID() + TreeNode(\"\", ...)' to generate a unique ID is to use 'TreeNode((void*)(intptr_t)i, ...)',\n                // aka generate a dummy pointer-sized value to be hashed. The demo below uses that technique. Both are fine.\n                ImGui::PushID(i);\n                if (ImGui::TreeNode(\"\", \"Child %d\", i))\n                {\n                    ImGui::Text(\"blah blah\");\n                    ImGui::SameLine();\n                    if (ImGui::SmallButton(\"button\")) {}\n                    ImGui::TreePop();\n                }\n                ImGui::PopID();\n            }\n            ImGui::TreePop();\n        }\n\n        IMGUI_DEMO_MARKER(\"Widgets/Tree Nodes/Advanced, with Selectable nodes\");\n        if (ImGui::TreeNode(\"Advanced, with Selectable nodes\"))\n        {\n            HelpMarker(\n                \"This is a more typical looking tree with selectable nodes.\\n\"\n                \"Click to select, CTRL+Click to toggle, click on arrows or double-click to open.\");\n            static ImGuiTreeNodeFlags base_flags = ImGuiTreeNodeFlags_OpenOnArrow | ImGuiTreeNodeFlags_OpenOnDoubleClick | ImGuiTreeNodeFlags_SpanAvailWidth;\n            static bool align_label_with_current_x_position = false;\n            static bool test_drag_and_drop = false;\n            ImGui::CheckboxFlags(\"ImGuiTreeNodeFlags_OpenOnArrow\", &base_flags, ImGuiTreeNodeFlags_OpenOnArrow);\n            ImGui::CheckboxFlags(\"ImGuiTreeNodeFlags_OpenOnDoubleClick\", &base_flags, ImGuiTreeNodeFlags_OpenOnDoubleClick);\n            ImGui::CheckboxFlags(\"ImGuiTreeNodeFlags_SpanAvailWidth\", &base_flags, ImGuiTreeNodeFlags_SpanAvailWidth); ImGui::SameLine(); HelpMarker(\"Extend hit area to all available width instead of allowing more items to be laid out after the node.\");\n            ImGui::CheckboxFlags(\"ImGuiTreeNodeFlags_SpanFullWidth\", &base_flags, ImGuiTreeNodeFlags_SpanFullWidth);\n            ImGui::CheckboxFlags(\"ImGuiTreeNodeFlags_SpanLabelWidth\", &base_flags, ImGuiTreeNodeFlags_SpanLabelWidth); ImGui::SameLine(); HelpMarker(\"Reduce hit area to the text label and a bit of margin.\");\n            ImGui::CheckboxFlags(\"ImGuiTreeNodeFlags_SpanAllColumns\", &base_flags, ImGuiTreeNodeFlags_SpanAllColumns); ImGui::SameLine(); HelpMarker(\"For use in Tables only.\");\n            ImGui::CheckboxFlags(\"ImGuiTreeNodeFlags_AllowOverlap\", &base_flags, ImGuiTreeNodeFlags_AllowOverlap);\n            ImGui::CheckboxFlags(\"ImGuiTreeNodeFlags_Framed\", &base_flags, ImGuiTreeNodeFlags_Framed); ImGui::SameLine(); HelpMarker(\"Draw frame with background (e.g. for CollapsingHeader)\");\n            ImGui::CheckboxFlags(\"ImGuiTreeNodeFlags_NavLeftJumpsBackHere\", &base_flags, ImGuiTreeNodeFlags_NavLeftJumpsBackHere);\n            ImGui::Checkbox(\"Align label with current X position\", &align_label_with_current_x_position);\n            ImGui::Checkbox(\"Test tree node as drag source\", &test_drag_and_drop);\n            ImGui::Text(\"Hello!\");\n            if (align_label_with_current_x_position)\n                ImGui::Unindent(ImGui::GetTreeNodeToLabelSpacing());\n\n            // 'selection_mask' is dumb representation of what may be user-side selection state.\n            //  You may retain selection state inside or outside your objects in whatever format you see fit.\n            // 'node_clicked' is temporary storage of what node we have clicked to process selection at the end\n            /// of the loop. May be a pointer to your own node type, etc.\n            static int selection_mask = (1 << 2);\n            int node_clicked = -1;\n            for (int i = 0; i < 6; i++)\n            {\n                // Disable the default \"open on single-click behavior\" + set Selected flag according to our selection.\n                // To alter selection we use IsItemClicked() && !IsItemToggledOpen(), so clicking on an arrow doesn't alter selection.\n                ImGuiTreeNodeFlags node_flags = base_flags;\n                const bool is_selected = (selection_mask & (1 << i)) != 0;\n                if (is_selected)\n                    node_flags |= ImGuiTreeNodeFlags_Selected;\n                if (i < 3)\n                {\n                    // Items 0..2 are Tree Node\n                    bool node_open = ImGui::TreeNodeEx((void*)(intptr_t)i, node_flags, \"Selectable Node %d\", i);\n                    if (ImGui::IsItemClicked() && !ImGui::IsItemToggledOpen())\n                        node_clicked = i;\n                    if (test_drag_and_drop && ImGui::BeginDragDropSource())\n                    {\n                        ImGui::SetDragDropPayload(\"_TREENODE\", NULL, 0);\n                        ImGui::Text(\"This is a drag and drop source\");\n                        ImGui::EndDragDropSource();\n                    }\n                    if (i == 2 && (base_flags & ImGuiTreeNodeFlags_SpanLabelWidth))\n                    {\n                        // Item 2 has an additional inline button to help demonstrate SpanLabelWidth.\n                        ImGui::SameLine();\n                        if (ImGui::SmallButton(\"button\")) {}\n                    }\n                    if (node_open)\n                    {\n                        ImGui::BulletText(\"Blah blah\\nBlah Blah\");\n                        ImGui::SameLine();\n                        ImGui::SmallButton(\"Button\");\n                        ImGui::TreePop();\n                    }\n                }\n                else\n                {\n                    // Items 3..5 are Tree Leaves\n                    // The only reason we use TreeNode at all is to allow selection of the leaf. Otherwise we can\n                    // use BulletText() or advance the cursor by GetTreeNodeToLabelSpacing() and call Text().\n                    node_flags |= ImGuiTreeNodeFlags_Leaf | ImGuiTreeNodeFlags_NoTreePushOnOpen; // ImGuiTreeNodeFlags_Bullet\n                    ImGui::TreeNodeEx((void*)(intptr_t)i, node_flags, \"Selectable Leaf %d\", i);\n                    if (ImGui::IsItemClicked() && !ImGui::IsItemToggledOpen())\n                        node_clicked = i;\n                    if (test_drag_and_drop && ImGui::BeginDragDropSource())\n                    {\n                        ImGui::SetDragDropPayload(\"_TREENODE\", NULL, 0);\n                        ImGui::Text(\"This is a drag and drop source\");\n                        ImGui::EndDragDropSource();\n                    }\n                }\n            }\n            if (node_clicked != -1)\n            {\n                // Update selection state\n                // (process outside of tree loop to avoid visual inconsistencies during the clicking frame)\n                if (ImGui::GetIO().KeyCtrl)\n                    selection_mask ^= (1 << node_clicked);          // CTRL+click to toggle\n                else //if (!(selection_mask & (1 << node_clicked))) // Depending on selection behavior you want, may want to preserve selection when clicking on item that is part of the selection\n                    selection_mask = (1 << node_clicked);           // Click to single-select\n            }\n            if (align_label_with_current_x_position)\n                ImGui::Indent(ImGui::GetTreeNodeToLabelSpacing());\n            ImGui::TreePop();\n        }\n        ImGui::TreePop();\n    }\n}\n\n//-----------------------------------------------------------------------------\n// [SECTION] DemoWindowWidgetsVerticalSliders()\n//-----------------------------------------------------------------------------\n\nstatic void DemoWindowWidgetsVerticalSliders()\n{\n    IMGUI_DEMO_MARKER(\"Widgets/Vertical Sliders\");\n    if (ImGui::TreeNode(\"Vertical Sliders\"))\n    {\n        const float spacing = 4;\n        ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(spacing, spacing));\n\n        static int int_value = 0;\n        ImGui::VSliderInt(\"##int\", ImVec2(18, 160), &int_value, 0, 5);\n        ImGui::SameLine();\n\n        static float values[7] = { 0.0f, 0.60f, 0.35f, 0.9f, 0.70f, 0.20f, 0.0f };\n        ImGui::PushID(\"set1\");\n        for (int i = 0; i < 7; i++)\n        {\n            if (i > 0) ImGui::SameLine();\n            ImGui::PushID(i);\n            ImGui::PushStyleColor(ImGuiCol_FrameBg, (ImVec4)ImColor::HSV(i / 7.0f, 0.5f, 0.5f));\n            ImGui::PushStyleColor(ImGuiCol_FrameBgHovered, (ImVec4)ImColor::HSV(i / 7.0f, 0.6f, 0.5f));\n            ImGui::PushStyleColor(ImGuiCol_FrameBgActive, (ImVec4)ImColor::HSV(i / 7.0f, 0.7f, 0.5f));\n            ImGui::PushStyleColor(ImGuiCol_SliderGrab, (ImVec4)ImColor::HSV(i / 7.0f, 0.9f, 0.9f));\n            ImGui::VSliderFloat(\"##v\", ImVec2(18, 160), &values[i], 0.0f, 1.0f, \"\");\n            if (ImGui::IsItemActive() || ImGui::IsItemHovered())\n                ImGui::SetTooltip(\"%.3f\", values[i]);\n            ImGui::PopStyleColor(4);\n            ImGui::PopID();\n        }\n        ImGui::PopID();\n\n        ImGui::SameLine();\n        ImGui::PushID(\"set2\");\n        static float values2[4] = { 0.20f, 0.80f, 0.40f, 0.25f };\n        const int rows = 3;\n        const ImVec2 small_slider_size(18, (float)(int)((160.0f - (rows - 1) * spacing) / rows));\n        for (int nx = 0; nx < 4; nx++)\n        {\n            if (nx > 0) ImGui::SameLine();\n            ImGui::BeginGroup();\n            for (int ny = 0; ny < rows; ny++)\n            {\n                ImGui::PushID(nx * rows + ny);\n                ImGui::VSliderFloat(\"##v\", small_slider_size, &values2[nx], 0.0f, 1.0f, \"\");\n                if (ImGui::IsItemActive() || ImGui::IsItemHovered())\n                    ImGui::SetTooltip(\"%.3f\", values2[nx]);\n                ImGui::PopID();\n            }\n            ImGui::EndGroup();\n        }\n        ImGui::PopID();\n\n        ImGui::SameLine();\n        ImGui::PushID(\"set3\");\n        for (int i = 0; i < 4; i++)\n        {\n            if (i > 0) ImGui::SameLine();\n            ImGui::PushID(i);\n            ImGui::PushStyleVar(ImGuiStyleVar_GrabMinSize, 40);\n            ImGui::VSliderFloat(\"##v\", ImVec2(40, 160), &values[i], 0.0f, 1.0f, \"%.2f\\nsec\");\n            ImGui::PopStyleVar();\n            ImGui::PopID();\n        }\n        ImGui::PopID();\n        ImGui::PopStyleVar();\n        ImGui::TreePop();\n    }\n}\n\n//-----------------------------------------------------------------------------\n// [SECTION] DemoWindowWidgets()\n//-----------------------------------------------------------------------------\n\nstatic void DemoWindowWidgets(ImGuiDemoWindowData* demo_data)\n{\n    IMGUI_DEMO_MARKER(\"Widgets\");\n    //ImGui::SetNextItemOpen(true, ImGuiCond_Once);\n    if (!ImGui::CollapsingHeader(\"Widgets\"))\n        return;\n\n    const bool disable_all = demo_data->DisableSections; // The Checkbox for that is inside the \"Disabled\" section at the bottom\n    if (disable_all)\n        ImGui::BeginDisabled();\n\n    DemoWindowWidgetsBasic();\n    DemoWindowWidgetsBullets();\n    DemoWindowWidgetsCollapsingHeaders();\n    DemoWindowWidgetsComboBoxes();\n    DemoWindowWidgetsColorAndPickers();\n    DemoWindowWidgetsDataTypes();\n\n    if (disable_all)\n        ImGui::EndDisabled();\n    DemoWindowWidgetsDisableBlocks(demo_data);\n    if (disable_all)\n        ImGui::BeginDisabled();\n\n    DemoWindowWidgetsDragAndDrop();\n    DemoWindowWidgetsDragsAndSliders();\n    DemoWindowWidgetsImages();\n    DemoWindowWidgetsListBoxes();\n    DemoWindowWidgetsMultiComponents();\n    DemoWindowWidgetsPlotting();\n    DemoWindowWidgetsProgressBars();\n    DemoWindowWidgetsQueryingStatuses();\n    DemoWindowWidgetsSelectables();\n    DemoWindowWidgetsSelectionAndMultiSelect(demo_data);\n    DemoWindowWidgetsTabs();\n    DemoWindowWidgetsText();\n    DemoWindowWidgetsTextFilter();\n    DemoWindowWidgetsTextInput();\n    DemoWindowWidgetsTooltips();\n    DemoWindowWidgetsTreeNodes();\n    DemoWindowWidgetsVerticalSliders();\n\n    if (disable_all)\n        ImGui::EndDisabled();\n}\n\n//-----------------------------------------------------------------------------\n// [SECTION] DemoWindowLayout()\n//-----------------------------------------------------------------------------\n\nstatic void DemoWindowLayout()\n{\n    IMGUI_DEMO_MARKER(\"Layout\");\n    if (!ImGui::CollapsingHeader(\"Layout & Scrolling\"))\n        return;\n\n    IMGUI_DEMO_MARKER(\"Layout/Child windows\");\n    if (ImGui::TreeNode(\"Child windows\"))\n    {\n        ImGui::SeparatorText(\"Child windows\");\n\n        HelpMarker(\"Use child windows to begin into a self-contained independent scrolling/clipping regions within a host window.\");\n        static bool disable_mouse_wheel = false;\n        static bool disable_menu = false;\n        ImGui::Checkbox(\"Disable Mouse Wheel\", &disable_mouse_wheel);\n        ImGui::Checkbox(\"Disable Menu\", &disable_menu);\n\n        // Child 1: no border, enable horizontal scrollbar\n        {\n            ImGuiWindowFlags window_flags = ImGuiWindowFlags_HorizontalScrollbar;\n            if (disable_mouse_wheel)\n                window_flags |= ImGuiWindowFlags_NoScrollWithMouse;\n            ImGui::BeginChild(\"ChildL\", ImVec2(ImGui::GetContentRegionAvail().x * 0.5f, 260), ImGuiChildFlags_None, window_flags);\n            for (int i = 0; i < 100; i++)\n                ImGui::Text(\"%04d: scrollable region\", i);\n            ImGui::EndChild();\n        }\n\n        ImGui::SameLine();\n\n        // Child 2: rounded border\n        {\n            ImGuiWindowFlags window_flags = ImGuiWindowFlags_None;\n            if (disable_mouse_wheel)\n                window_flags |= ImGuiWindowFlags_NoScrollWithMouse;\n            if (!disable_menu)\n                window_flags |= ImGuiWindowFlags_MenuBar;\n            ImGui::PushStyleVar(ImGuiStyleVar_ChildRounding, 5.0f);\n            ImGui::BeginChild(\"ChildR\", ImVec2(0, 260), ImGuiChildFlags_Borders, window_flags);\n            if (!disable_menu && ImGui::BeginMenuBar())\n            {\n                if (ImGui::BeginMenu(\"Menu\"))\n                {\n                    ShowExampleMenuFile();\n                    ImGui::EndMenu();\n                }\n                ImGui::EndMenuBar();\n            }\n            if (ImGui::BeginTable(\"split\", 2, ImGuiTableFlags_Resizable | ImGuiTableFlags_NoSavedSettings))\n            {\n                for (int i = 0; i < 100; i++)\n                {\n                    char buf[32];\n                    sprintf(buf, \"%03d\", i);\n                    ImGui::TableNextColumn();\n                    ImGui::Button(buf, ImVec2(-FLT_MIN, 0.0f));\n                }\n                ImGui::EndTable();\n            }\n            ImGui::EndChild();\n            ImGui::PopStyleVar();\n        }\n\n        // Child 3: manual-resize\n        ImGui::SeparatorText(\"Manual-resize\");\n        {\n            HelpMarker(\"Drag bottom border to resize. Double-click bottom border to auto-fit to vertical contents.\");\n            //if (ImGui::Button(\"Set Height to 200\"))\n            //    ImGui::SetNextWindowSize(ImVec2(-FLT_MIN, 200.0f));\n\n            ImGui::PushStyleColor(ImGuiCol_ChildBg, ImGui::GetStyleColorVec4(ImGuiCol_FrameBg));\n            if (ImGui::BeginChild(\"ResizableChild\", ImVec2(-FLT_MIN, ImGui::GetTextLineHeightWithSpacing() * 8), ImGuiChildFlags_Borders | ImGuiChildFlags_ResizeY))\n                for (int n = 0; n < 10; n++)\n                    ImGui::Text(\"Line %04d\", n);\n            ImGui::PopStyleColor();\n            ImGui::EndChild();\n        }\n\n        // Child 4: auto-resizing height with a limit\n        ImGui::SeparatorText(\"Auto-resize with constraints\");\n        {\n            static int draw_lines = 3;\n            static int max_height_in_lines = 10;\n            ImGui::SetNextItemWidth(ImGui::GetFontSize() * 8);\n            ImGui::DragInt(\"Lines Count\", &draw_lines, 0.2f);\n            ImGui::SetNextItemWidth(ImGui::GetFontSize() * 8);\n            ImGui::DragInt(\"Max Height (in Lines)\", &max_height_in_lines, 0.2f);\n\n            ImGui::SetNextWindowSizeConstraints(ImVec2(0.0f, ImGui::GetTextLineHeightWithSpacing() * 1), ImVec2(FLT_MAX, ImGui::GetTextLineHeightWithSpacing() * max_height_in_lines));\n            if (ImGui::BeginChild(\"ConstrainedChild\", ImVec2(-FLT_MIN, 0.0f), ImGuiChildFlags_Borders | ImGuiChildFlags_AutoResizeY))\n                for (int n = 0; n < draw_lines; n++)\n                    ImGui::Text(\"Line %04d\", n);\n            ImGui::EndChild();\n        }\n\n        ImGui::SeparatorText(\"Misc/Advanced\");\n\n        // Demonstrate a few extra things\n        // - Changing ImGuiCol_ChildBg (which is transparent black in default styles)\n        // - Using SetCursorPos() to position child window (the child window is an item from the POV of parent window)\n        //   You can also call SetNextWindowPos() to position the child window. The parent window will effectively\n        //   layout from this position.\n        // - Using ImGui::GetItemRectMin/Max() to query the \"item\" state (because the child window is an item from\n        //   the POV of the parent window). See 'Demo->Querying Status (Edited/Active/Hovered etc.)' for details.\n        {\n            static int offset_x = 0;\n            static bool override_bg_color = true;\n            static ImGuiChildFlags child_flags = ImGuiChildFlags_Borders | ImGuiChildFlags_ResizeX | ImGuiChildFlags_ResizeY;\n            ImGui::SetNextItemWidth(ImGui::GetFontSize() * 8);\n            ImGui::DragInt(\"Offset X\", &offset_x, 1.0f, -1000, 1000);\n            ImGui::Checkbox(\"Override ChildBg color\", &override_bg_color);\n            ImGui::CheckboxFlags(\"ImGuiChildFlags_Borders\", &child_flags, ImGuiChildFlags_Borders);\n            ImGui::CheckboxFlags(\"ImGuiChildFlags_AlwaysUseWindowPadding\", &child_flags, ImGuiChildFlags_AlwaysUseWindowPadding);\n            ImGui::CheckboxFlags(\"ImGuiChildFlags_ResizeX\", &child_flags, ImGuiChildFlags_ResizeX);\n            ImGui::CheckboxFlags(\"ImGuiChildFlags_ResizeY\", &child_flags, ImGuiChildFlags_ResizeY);\n            ImGui::CheckboxFlags(\"ImGuiChildFlags_FrameStyle\", &child_flags, ImGuiChildFlags_FrameStyle);\n            ImGui::SameLine(); HelpMarker(\"Style the child window like a framed item: use FrameBg, FrameRounding, FrameBorderSize, FramePadding instead of ChildBg, ChildRounding, ChildBorderSize, WindowPadding.\");\n            if (child_flags & ImGuiChildFlags_FrameStyle)\n                override_bg_color = false;\n\n            ImGui::SetCursorPosX(ImGui::GetCursorPosX() + (float)offset_x);\n            if (override_bg_color)\n                ImGui::PushStyleColor(ImGuiCol_ChildBg, IM_COL32(255, 0, 0, 100));\n            ImGui::BeginChild(\"Red\", ImVec2(200, 100), child_flags, ImGuiWindowFlags_None);\n            if (override_bg_color)\n                ImGui::PopStyleColor();\n\n            for (int n = 0; n < 50; n++)\n                ImGui::Text(\"Some test %d\", n);\n            ImGui::EndChild();\n            bool child_is_hovered = ImGui::IsItemHovered();\n            ImVec2 child_rect_min = ImGui::GetItemRectMin();\n            ImVec2 child_rect_max = ImGui::GetItemRectMax();\n            ImGui::Text(\"Hovered: %d\", child_is_hovered);\n            ImGui::Text(\"Rect of child window is: (%.0f,%.0f) (%.0f,%.0f)\", child_rect_min.x, child_rect_min.y, child_rect_max.x, child_rect_max.y);\n        }\n\n        ImGui::TreePop();\n    }\n\n    IMGUI_DEMO_MARKER(\"Layout/Widgets Width\");\n    if (ImGui::TreeNode(\"Widgets Width\"))\n    {\n        static float f = 0.0f;\n        static bool show_indented_items = true;\n        ImGui::Checkbox(\"Show indented items\", &show_indented_items);\n\n        // Use SetNextItemWidth() to set the width of a single upcoming item.\n        // Use PushItemWidth()/PopItemWidth() to set the width of a group of items.\n        // In real code use you'll probably want to choose width values that are proportional to your font size\n        // e.g. Using '20.0f * GetFontSize()' as width instead of '200.0f', etc.\n\n        ImGui::Text(\"SetNextItemWidth/PushItemWidth(100)\");\n        ImGui::SameLine(); HelpMarker(\"Fixed width.\");\n        ImGui::PushItemWidth(100);\n        ImGui::DragFloat(\"float##1b\", &f);\n        if (show_indented_items)\n        {\n            ImGui::Indent();\n            ImGui::DragFloat(\"float (indented)##1b\", &f);\n            ImGui::Unindent();\n        }\n        ImGui::PopItemWidth();\n\n        ImGui::Text(\"SetNextItemWidth/PushItemWidth(-100)\");\n        ImGui::SameLine(); HelpMarker(\"Align to right edge minus 100\");\n        ImGui::PushItemWidth(-100);\n        ImGui::DragFloat(\"float##2a\", &f);\n        if (show_indented_items)\n        {\n            ImGui::Indent();\n            ImGui::DragFloat(\"float (indented)##2b\", &f);\n            ImGui::Unindent();\n        }\n        ImGui::PopItemWidth();\n\n        ImGui::Text(\"SetNextItemWidth/PushItemWidth(GetContentRegionAvail().x * 0.5f)\");\n        ImGui::SameLine(); HelpMarker(\"Half of available width.\\n(~ right-cursor_pos)\\n(works within a column set)\");\n        ImGui::PushItemWidth(ImGui::GetContentRegionAvail().x * 0.5f);\n        ImGui::DragFloat(\"float##3a\", &f);\n        if (show_indented_items)\n        {\n            ImGui::Indent();\n            ImGui::DragFloat(\"float (indented)##3b\", &f);\n            ImGui::Unindent();\n        }\n        ImGui::PopItemWidth();\n\n        ImGui::Text(\"SetNextItemWidth/PushItemWidth(-GetContentRegionAvail().x * 0.5f)\");\n        ImGui::SameLine(); HelpMarker(\"Align to right edge minus half\");\n        ImGui::PushItemWidth(-ImGui::GetContentRegionAvail().x * 0.5f);\n        ImGui::DragFloat(\"float##4a\", &f);\n        if (show_indented_items)\n        {\n            ImGui::Indent();\n            ImGui::DragFloat(\"float (indented)##4b\", &f);\n            ImGui::Unindent();\n        }\n        ImGui::PopItemWidth();\n\n        // Demonstrate using PushItemWidth to surround three items.\n        // Calling SetNextItemWidth() before each of them would have the same effect.\n        ImGui::Text(\"SetNextItemWidth/PushItemWidth(-FLT_MIN)\");\n        ImGui::SameLine(); HelpMarker(\"Align to right edge\");\n        ImGui::PushItemWidth(-FLT_MIN);\n        ImGui::DragFloat(\"##float5a\", &f);\n        if (show_indented_items)\n        {\n            ImGui::Indent();\n            ImGui::DragFloat(\"float (indented)##5b\", &f);\n            ImGui::Unindent();\n        }\n        ImGui::PopItemWidth();\n\n        ImGui::TreePop();\n    }\n\n    IMGUI_DEMO_MARKER(\"Layout/Basic Horizontal Layout\");\n    if (ImGui::TreeNode(\"Basic Horizontal Layout\"))\n    {\n        ImGui::TextWrapped(\"(Use ImGui::SameLine() to keep adding items to the right of the preceding item)\");\n\n        // Text\n        IMGUI_DEMO_MARKER(\"Layout/Basic Horizontal Layout/SameLine\");\n        ImGui::Text(\"Two items: Hello\"); ImGui::SameLine();\n        ImGui::TextColored(ImVec4(1, 1, 0, 1), \"Sailor\");\n\n        // Adjust spacing\n        ImGui::Text(\"More spacing: Hello\"); ImGui::SameLine(0, 20);\n        ImGui::TextColored(ImVec4(1, 1, 0, 1), \"Sailor\");\n\n        // Button\n        ImGui::AlignTextToFramePadding();\n        ImGui::Text(\"Normal buttons\"); ImGui::SameLine();\n        ImGui::Button(\"Banana\"); ImGui::SameLine();\n        ImGui::Button(\"Apple\"); ImGui::SameLine();\n        ImGui::Button(\"Corniflower\");\n\n        // Button\n        ImGui::Text(\"Small buttons\"); ImGui::SameLine();\n        ImGui::SmallButton(\"Like this one\"); ImGui::SameLine();\n        ImGui::Text(\"can fit within a text block.\");\n\n        // Aligned to arbitrary position. Easy/cheap column.\n        IMGUI_DEMO_MARKER(\"Layout/Basic Horizontal Layout/SameLine (with offset)\");\n        ImGui::Text(\"Aligned\");\n        ImGui::SameLine(150); ImGui::Text(\"x=150\");\n        ImGui::SameLine(300); ImGui::Text(\"x=300\");\n        ImGui::Text(\"Aligned\");\n        ImGui::SameLine(150); ImGui::SmallButton(\"x=150\");\n        ImGui::SameLine(300); ImGui::SmallButton(\"x=300\");\n\n        // Checkbox\n        IMGUI_DEMO_MARKER(\"Layout/Basic Horizontal Layout/SameLine (more)\");\n        static bool c1 = false, c2 = false, c3 = false, c4 = false;\n        ImGui::Checkbox(\"My\", &c1); ImGui::SameLine();\n        ImGui::Checkbox(\"Tailor\", &c2); ImGui::SameLine();\n        ImGui::Checkbox(\"Is\", &c3); ImGui::SameLine();\n        ImGui::Checkbox(\"Rich\", &c4);\n\n        // Various\n        static float f0 = 1.0f, f1 = 2.0f, f2 = 3.0f;\n        ImGui::PushItemWidth(80);\n        const char* items[] = { \"AAAA\", \"BBBB\", \"CCCC\", \"DDDD\" };\n        static int item = -1;\n        ImGui::Combo(\"Combo\", &item, items, IM_ARRAYSIZE(items)); ImGui::SameLine();\n        ImGui::SliderFloat(\"X\", &f0, 0.0f, 5.0f); ImGui::SameLine();\n        ImGui::SliderFloat(\"Y\", &f1, 0.0f, 5.0f); ImGui::SameLine();\n        ImGui::SliderFloat(\"Z\", &f2, 0.0f, 5.0f);\n        ImGui::PopItemWidth();\n\n        ImGui::PushItemWidth(80);\n        ImGui::Text(\"Lists:\");\n        static int selection[4] = { 0, 1, 2, 3 };\n        for (int i = 0; i < 4; i++)\n        {\n            if (i > 0) ImGui::SameLine();\n            ImGui::PushID(i);\n            ImGui::ListBox(\"\", &selection[i], items, IM_ARRAYSIZE(items));\n            ImGui::PopID();\n            //ImGui::SetItemTooltip(\"ListBox %d hovered\", i);\n        }\n        ImGui::PopItemWidth();\n\n        // Dummy\n        IMGUI_DEMO_MARKER(\"Layout/Basic Horizontal Layout/Dummy\");\n        ImVec2 button_sz(40, 40);\n        ImGui::Button(\"A\", button_sz); ImGui::SameLine();\n        ImGui::Dummy(button_sz); ImGui::SameLine();\n        ImGui::Button(\"B\", button_sz);\n\n        // Manually wrapping\n        // (we should eventually provide this as an automatic layout feature, but for now you can do it manually)\n        IMGUI_DEMO_MARKER(\"Layout/Basic Horizontal Layout/Manual wrapping\");\n        ImGui::Text(\"Manual wrapping:\");\n        ImGuiStyle& style = ImGui::GetStyle();\n        int buttons_count = 20;\n        float window_visible_x2 = ImGui::GetCursorScreenPos().x + ImGui::GetContentRegionAvail().x;\n        for (int n = 0; n < buttons_count; n++)\n        {\n            ImGui::PushID(n);\n            ImGui::Button(\"Box\", button_sz);\n            float last_button_x2 = ImGui::GetItemRectMax().x;\n            float next_button_x2 = last_button_x2 + style.ItemSpacing.x + button_sz.x; // Expected position if next button was on same line\n            if (n + 1 < buttons_count && next_button_x2 < window_visible_x2)\n                ImGui::SameLine();\n            ImGui::PopID();\n        }\n\n        ImGui::TreePop();\n    }\n\n    IMGUI_DEMO_MARKER(\"Layout/Groups\");\n    if (ImGui::TreeNode(\"Groups\"))\n    {\n        HelpMarker(\n            \"BeginGroup() basically locks the horizontal position for new line. \"\n            \"EndGroup() bundles the whole group so that you can use \\\"item\\\" functions such as \"\n            \"IsItemHovered()/IsItemActive() or SameLine() etc. on the whole group.\");\n        ImGui::BeginGroup();\n        {\n            ImGui::BeginGroup();\n            ImGui::Button(\"AAA\");\n            ImGui::SameLine();\n            ImGui::Button(\"BBB\");\n            ImGui::SameLine();\n            ImGui::BeginGroup();\n            ImGui::Button(\"CCC\");\n            ImGui::Button(\"DDD\");\n            ImGui::EndGroup();\n            ImGui::SameLine();\n            ImGui::Button(\"EEE\");\n            ImGui::EndGroup();\n            ImGui::SetItemTooltip(\"First group hovered\");\n        }\n        // Capture the group size and create widgets using the same size\n        ImVec2 size = ImGui::GetItemRectSize();\n        const float values[5] = { 0.5f, 0.20f, 0.80f, 0.60f, 0.25f };\n        ImGui::PlotHistogram(\"##values\", values, IM_ARRAYSIZE(values), 0, NULL, 0.0f, 1.0f, size);\n\n        ImGui::Button(\"ACTION\", ImVec2((size.x - ImGui::GetStyle().ItemSpacing.x) * 0.5f, size.y));\n        ImGui::SameLine();\n        ImGui::Button(\"REACTION\", ImVec2((size.x - ImGui::GetStyle().ItemSpacing.x) * 0.5f, size.y));\n        ImGui::EndGroup();\n        ImGui::SameLine();\n\n        ImGui::Button(\"LEVERAGE\\nBUZZWORD\", size);\n        ImGui::SameLine();\n\n        if (ImGui::BeginListBox(\"List\", size))\n        {\n            ImGui::Selectable(\"Selected\", true);\n            ImGui::Selectable(\"Not Selected\", false);\n            ImGui::EndListBox();\n        }\n\n        ImGui::TreePop();\n    }\n\n    IMGUI_DEMO_MARKER(\"Layout/Text Baseline Alignment\");\n    if (ImGui::TreeNode(\"Text Baseline Alignment\"))\n    {\n        {\n            ImGui::BulletText(\"Text baseline:\");\n            ImGui::SameLine(); HelpMarker(\n                \"This is testing the vertical alignment that gets applied on text to keep it aligned with widgets. \"\n                \"Lines only composed of text or \\\"small\\\" widgets use less vertical space than lines with framed widgets.\");\n            ImGui::Indent();\n\n            ImGui::Text(\"KO Blahblah\"); ImGui::SameLine();\n            ImGui::Button(\"Some framed item\"); ImGui::SameLine();\n            HelpMarker(\"Baseline of button will look misaligned with text..\");\n\n            // If your line starts with text, call AlignTextToFramePadding() to align text to upcoming widgets.\n            // (because we don't know what's coming after the Text() statement, we need to move the text baseline\n            // down by FramePadding.y ahead of time)\n            ImGui::AlignTextToFramePadding();\n            ImGui::Text(\"OK Blahblah\"); ImGui::SameLine();\n            ImGui::Button(\"Some framed item##2\"); ImGui::SameLine();\n            HelpMarker(\"We call AlignTextToFramePadding() to vertically align the text baseline by +FramePadding.y\");\n\n            // SmallButton() uses the same vertical padding as Text\n            ImGui::Button(\"TEST##1\"); ImGui::SameLine();\n            ImGui::Text(\"TEST\"); ImGui::SameLine();\n            ImGui::SmallButton(\"TEST##2\");\n\n            // If your line starts with text, call AlignTextToFramePadding() to align text to upcoming widgets.\n            ImGui::AlignTextToFramePadding();\n            ImGui::Text(\"Text aligned to framed item\"); ImGui::SameLine();\n            ImGui::Button(\"Item##1\"); ImGui::SameLine();\n            ImGui::Text(\"Item\"); ImGui::SameLine();\n            ImGui::SmallButton(\"Item##2\"); ImGui::SameLine();\n            ImGui::Button(\"Item##3\");\n\n            ImGui::Unindent();\n        }\n\n        ImGui::Spacing();\n\n        {\n            ImGui::BulletText(\"Multi-line text:\");\n            ImGui::Indent();\n            ImGui::Text(\"One\\nTwo\\nThree\"); ImGui::SameLine();\n            ImGui::Text(\"Hello\\nWorld\"); ImGui::SameLine();\n            ImGui::Text(\"Banana\");\n\n            ImGui::Text(\"Banana\"); ImGui::SameLine();\n            ImGui::Text(\"Hello\\nWorld\"); ImGui::SameLine();\n            ImGui::Text(\"One\\nTwo\\nThree\");\n\n            ImGui::Button(\"HOP##1\"); ImGui::SameLine();\n            ImGui::Text(\"Banana\"); ImGui::SameLine();\n            ImGui::Text(\"Hello\\nWorld\"); ImGui::SameLine();\n            ImGui::Text(\"Banana\");\n\n            ImGui::Button(\"HOP##2\"); ImGui::SameLine();\n            ImGui::Text(\"Hello\\nWorld\"); ImGui::SameLine();\n            ImGui::Text(\"Banana\");\n            ImGui::Unindent();\n        }\n\n        ImGui::Spacing();\n\n        {\n            ImGui::BulletText(\"Misc items:\");\n            ImGui::Indent();\n\n            // SmallButton() sets FramePadding to zero. Text baseline is aligned to match baseline of previous Button.\n            ImGui::Button(\"80x80\", ImVec2(80, 80));\n            ImGui::SameLine();\n            ImGui::Button(\"50x50\", ImVec2(50, 50));\n            ImGui::SameLine();\n            ImGui::Button(\"Button()\");\n            ImGui::SameLine();\n            ImGui::SmallButton(\"SmallButton()\");\n\n            // Tree\n            const float spacing = ImGui::GetStyle().ItemInnerSpacing.x;\n            ImGui::Button(\"Button##1\");\n            ImGui::SameLine(0.0f, spacing);\n            if (ImGui::TreeNode(\"Node##1\"))\n            {\n                // Placeholder tree data\n                for (int i = 0; i < 6; i++)\n                    ImGui::BulletText(\"Item %d..\", i);\n                ImGui::TreePop();\n            }\n\n            // Vertically align text node a bit lower so it'll be vertically centered with upcoming widget.\n            // Otherwise you can use SmallButton() (smaller fit).\n            ImGui::AlignTextToFramePadding();\n\n            // Common mistake to avoid: if we want to SameLine after TreeNode we need to do it before we add\n            // other contents below the node.\n            bool node_open = ImGui::TreeNode(\"Node##2\");\n            ImGui::SameLine(0.0f, spacing); ImGui::Button(\"Button##2\");\n            if (node_open)\n            {\n                // Placeholder tree data\n                for (int i = 0; i < 6; i++)\n                    ImGui::BulletText(\"Item %d..\", i);\n                ImGui::TreePop();\n            }\n\n            // Bullet\n            ImGui::Button(\"Button##3\");\n            ImGui::SameLine(0.0f, spacing);\n            ImGui::BulletText(\"Bullet text\");\n\n            ImGui::AlignTextToFramePadding();\n            ImGui::BulletText(\"Node\");\n            ImGui::SameLine(0.0f, spacing); ImGui::Button(\"Button##4\");\n            ImGui::Unindent();\n        }\n\n        ImGui::TreePop();\n    }\n\n    IMGUI_DEMO_MARKER(\"Layout/Scrolling\");\n    if (ImGui::TreeNode(\"Scrolling\"))\n    {\n        // Vertical scroll functions\n        IMGUI_DEMO_MARKER(\"Layout/Scrolling/Vertical\");\n        HelpMarker(\"Use SetScrollHereY() or SetScrollFromPosY() to scroll to a given vertical position.\");\n\n        static int track_item = 50;\n        static bool enable_track = true;\n        static bool enable_extra_decorations = false;\n        static float scroll_to_off_px = 0.0f;\n        static float scroll_to_pos_px = 200.0f;\n\n        ImGui::Checkbox(\"Decoration\", &enable_extra_decorations);\n\n        ImGui::Checkbox(\"Track\", &enable_track);\n        ImGui::PushItemWidth(100);\n        ImGui::SameLine(140); enable_track |= ImGui::DragInt(\"##item\", &track_item, 0.25f, 0, 99, \"Item = %d\");\n\n        bool scroll_to_off = ImGui::Button(\"Scroll Offset\");\n        ImGui::SameLine(140); scroll_to_off |= ImGui::DragFloat(\"##off\", &scroll_to_off_px, 1.00f, 0, FLT_MAX, \"+%.0f px\");\n\n        bool scroll_to_pos = ImGui::Button(\"Scroll To Pos\");\n        ImGui::SameLine(140); scroll_to_pos |= ImGui::DragFloat(\"##pos\", &scroll_to_pos_px, 1.00f, -10, FLT_MAX, \"X/Y = %.0f px\");\n        ImGui::PopItemWidth();\n\n        if (scroll_to_off || scroll_to_pos)\n            enable_track = false;\n\n        ImGuiStyle& style = ImGui::GetStyle();\n        float child_w = (ImGui::GetContentRegionAvail().x - 4 * style.ItemSpacing.x) / 5;\n        if (child_w < 1.0f)\n            child_w = 1.0f;\n        ImGui::PushID(\"##VerticalScrolling\");\n        for (int i = 0; i < 5; i++)\n        {\n            if (i > 0) ImGui::SameLine();\n            ImGui::BeginGroup();\n            const char* names[] = { \"Top\", \"25%\", \"Center\", \"75%\", \"Bottom\" };\n            ImGui::TextUnformatted(names[i]);\n\n            const ImGuiWindowFlags child_flags = enable_extra_decorations ? ImGuiWindowFlags_MenuBar : 0;\n            const ImGuiID child_id = ImGui::GetID((void*)(intptr_t)i);\n            const bool child_is_visible = ImGui::BeginChild(child_id, ImVec2(child_w, 200.0f), ImGuiChildFlags_Borders, child_flags);\n            if (ImGui::BeginMenuBar())\n            {\n                ImGui::TextUnformatted(\"abc\");\n                ImGui::EndMenuBar();\n            }\n            if (scroll_to_off)\n                ImGui::SetScrollY(scroll_to_off_px);\n            if (scroll_to_pos)\n                ImGui::SetScrollFromPosY(ImGui::GetCursorStartPos().y + scroll_to_pos_px, i * 0.25f);\n            if (child_is_visible) // Avoid calling SetScrollHereY when running with culled items\n            {\n                for (int item = 0; item < 100; item++)\n                {\n                    if (enable_track && item == track_item)\n                    {\n                        ImGui::TextColored(ImVec4(1, 1, 0, 1), \"Item %d\", item);\n                        ImGui::SetScrollHereY(i * 0.25f); // 0.0f:top, 0.5f:center, 1.0f:bottom\n                    }\n                    else\n                    {\n                        ImGui::Text(\"Item %d\", item);\n                    }\n                }\n            }\n            float scroll_y = ImGui::GetScrollY();\n            float scroll_max_y = ImGui::GetScrollMaxY();\n            ImGui::EndChild();\n            ImGui::Text(\"%.0f/%.0f\", scroll_y, scroll_max_y);\n            ImGui::EndGroup();\n        }\n        ImGui::PopID();\n\n        // Horizontal scroll functions\n        IMGUI_DEMO_MARKER(\"Layout/Scrolling/Horizontal\");\n        ImGui::Spacing();\n        HelpMarker(\n            \"Use SetScrollHereX() or SetScrollFromPosX() to scroll to a given horizontal position.\\n\\n\"\n            \"Because the clipping rectangle of most window hides half worth of WindowPadding on the \"\n            \"left/right, using SetScrollFromPosX(+1) will usually result in clipped text whereas the \"\n            \"equivalent SetScrollFromPosY(+1) wouldn't.\");\n        ImGui::PushID(\"##HorizontalScrolling\");\n        for (int i = 0; i < 5; i++)\n        {\n            float child_height = ImGui::GetTextLineHeight() + style.ScrollbarSize + style.WindowPadding.y * 2.0f;\n            ImGuiWindowFlags child_flags = ImGuiWindowFlags_HorizontalScrollbar | (enable_extra_decorations ? ImGuiWindowFlags_AlwaysVerticalScrollbar : 0);\n            ImGuiID child_id = ImGui::GetID((void*)(intptr_t)i);\n            bool child_is_visible = ImGui::BeginChild(child_id, ImVec2(-100, child_height), ImGuiChildFlags_Borders, child_flags);\n            if (scroll_to_off)\n                ImGui::SetScrollX(scroll_to_off_px);\n            if (scroll_to_pos)\n                ImGui::SetScrollFromPosX(ImGui::GetCursorStartPos().x + scroll_to_pos_px, i * 0.25f);\n            if (child_is_visible) // Avoid calling SetScrollHereY when running with culled items\n            {\n                for (int item = 0; item < 100; item++)\n                {\n                    if (item > 0)\n                        ImGui::SameLine();\n                    if (enable_track && item == track_item)\n                    {\n                        ImGui::TextColored(ImVec4(1, 1, 0, 1), \"Item %d\", item);\n                        ImGui::SetScrollHereX(i * 0.25f); // 0.0f:left, 0.5f:center, 1.0f:right\n                    }\n                    else\n                    {\n                        ImGui::Text(\"Item %d\", item);\n                    }\n                }\n            }\n            float scroll_x = ImGui::GetScrollX();\n            float scroll_max_x = ImGui::GetScrollMaxX();\n            ImGui::EndChild();\n            ImGui::SameLine();\n            const char* names[] = { \"Left\", \"25%\", \"Center\", \"75%\", \"Right\" };\n            ImGui::Text(\"%s\\n%.0f/%.0f\", names[i], scroll_x, scroll_max_x);\n            ImGui::Spacing();\n        }\n        ImGui::PopID();\n\n        // Miscellaneous Horizontal Scrolling Demo\n        IMGUI_DEMO_MARKER(\"Layout/Scrolling/Horizontal (more)\");\n        HelpMarker(\n            \"Horizontal scrolling for a window is enabled via the ImGuiWindowFlags_HorizontalScrollbar flag.\\n\\n\"\n            \"You may want to also explicitly specify content width by using SetNextWindowContentWidth() before Begin().\");\n        static int lines = 7;\n        ImGui::SliderInt(\"Lines\", &lines, 1, 15);\n        ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 3.0f);\n        ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(2.0f, 1.0f));\n        ImVec2 scrolling_child_size = ImVec2(0, ImGui::GetFrameHeightWithSpacing() * 7 + 30);\n        ImGui::BeginChild(\"scrolling\", scrolling_child_size, ImGuiChildFlags_Borders, ImGuiWindowFlags_HorizontalScrollbar);\n        for (int line = 0; line < lines; line++)\n        {\n            // Display random stuff. For the sake of this trivial demo we are using basic Button() + SameLine()\n            // If you want to create your own time line for a real application you may be better off manipulating\n            // the cursor position yourself, aka using SetCursorPos/SetCursorScreenPos to position the widgets\n            // yourself. You may also want to use the lower-level ImDrawList API.\n            int num_buttons = 10 + ((line & 1) ? line * 9 : line * 3);\n            for (int n = 0; n < num_buttons; n++)\n            {\n                if (n > 0) ImGui::SameLine();\n                ImGui::PushID(n + line * 1000);\n                char num_buf[16];\n                sprintf(num_buf, \"%d\", n);\n                const char* label = (!(n % 15)) ? \"FizzBuzz\" : (!(n % 3)) ? \"Fizz\" : (!(n % 5)) ? \"Buzz\" : num_buf;\n                float hue = n * 0.05f;\n                ImGui::PushStyleColor(ImGuiCol_Button, (ImVec4)ImColor::HSV(hue, 0.6f, 0.6f));\n                ImGui::PushStyleColor(ImGuiCol_ButtonHovered, (ImVec4)ImColor::HSV(hue, 0.7f, 0.7f));\n                ImGui::PushStyleColor(ImGuiCol_ButtonActive, (ImVec4)ImColor::HSV(hue, 0.8f, 0.8f));\n                ImGui::Button(label, ImVec2(40.0f + sinf((float)(line + n)) * 20.0f, 0.0f));\n                ImGui::PopStyleColor(3);\n                ImGui::PopID();\n            }\n        }\n        float scroll_x = ImGui::GetScrollX();\n        float scroll_max_x = ImGui::GetScrollMaxX();\n        ImGui::EndChild();\n        ImGui::PopStyleVar(2);\n        float scroll_x_delta = 0.0f;\n        ImGui::SmallButton(\"<<\");\n        if (ImGui::IsItemActive())\n            scroll_x_delta = -ImGui::GetIO().DeltaTime * 1000.0f;\n        ImGui::SameLine();\n        ImGui::Text(\"Scroll from code\"); ImGui::SameLine();\n        ImGui::SmallButton(\">>\");\n        if (ImGui::IsItemActive())\n            scroll_x_delta = +ImGui::GetIO().DeltaTime * 1000.0f;\n        ImGui::SameLine();\n        ImGui::Text(\"%.0f/%.0f\", scroll_x, scroll_max_x);\n        if (scroll_x_delta != 0.0f)\n        {\n            // Demonstrate a trick: you can use Begin to set yourself in the context of another window\n            // (here we are already out of your child window)\n            ImGui::BeginChild(\"scrolling\");\n            ImGui::SetScrollX(ImGui::GetScrollX() + scroll_x_delta);\n            ImGui::EndChild();\n        }\n        ImGui::Spacing();\n\n        static bool show_horizontal_contents_size_demo_window = false;\n        ImGui::Checkbox(\"Show Horizontal contents size demo window\", &show_horizontal_contents_size_demo_window);\n\n        if (show_horizontal_contents_size_demo_window)\n        {\n            static bool show_h_scrollbar = true;\n            static bool show_button = true;\n            static bool show_tree_nodes = true;\n            static bool show_text_wrapped = false;\n            static bool show_columns = true;\n            static bool show_tab_bar = true;\n            static bool show_child = false;\n            static bool explicit_content_size = false;\n            static float contents_size_x = 300.0f;\n            if (explicit_content_size)\n                ImGui::SetNextWindowContentSize(ImVec2(contents_size_x, 0.0f));\n            ImGui::Begin(\"Horizontal contents size demo window\", &show_horizontal_contents_size_demo_window, show_h_scrollbar ? ImGuiWindowFlags_HorizontalScrollbar : 0);\n            IMGUI_DEMO_MARKER(\"Layout/Scrolling/Horizontal contents size demo window\");\n            ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(2, 0));\n            ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(2, 0));\n            HelpMarker(\n                \"Test how different widgets react and impact the work rectangle growing when horizontal scrolling is enabled.\\n\\n\"\n                \"Use 'Metrics->Tools->Show windows rectangles' to visualize rectangles.\");\n            ImGui::Checkbox(\"H-scrollbar\", &show_h_scrollbar);\n            ImGui::Checkbox(\"Button\", &show_button);            // Will grow contents size (unless explicitly overwritten)\n            ImGui::Checkbox(\"Tree nodes\", &show_tree_nodes);    // Will grow contents size and display highlight over full width\n            ImGui::Checkbox(\"Text wrapped\", &show_text_wrapped);// Will grow and use contents size\n            ImGui::Checkbox(\"Columns\", &show_columns);          // Will use contents size\n            ImGui::Checkbox(\"Tab bar\", &show_tab_bar);          // Will use contents size\n            ImGui::Checkbox(\"Child\", &show_child);              // Will grow and use contents size\n            ImGui::Checkbox(\"Explicit content size\", &explicit_content_size);\n            ImGui::Text(\"Scroll %.1f/%.1f %.1f/%.1f\", ImGui::GetScrollX(), ImGui::GetScrollMaxX(), ImGui::GetScrollY(), ImGui::GetScrollMaxY());\n            if (explicit_content_size)\n            {\n                ImGui::SameLine();\n                ImGui::SetNextItemWidth(100);\n                ImGui::DragFloat(\"##csx\", &contents_size_x);\n                ImVec2 p = ImGui::GetCursorScreenPos();\n                ImGui::GetWindowDrawList()->AddRectFilled(p, ImVec2(p.x + 10, p.y + 10), IM_COL32_WHITE);\n                ImGui::GetWindowDrawList()->AddRectFilled(ImVec2(p.x + contents_size_x - 10, p.y), ImVec2(p.x + contents_size_x, p.y + 10), IM_COL32_WHITE);\n                ImGui::Dummy(ImVec2(0, 10));\n            }\n            ImGui::PopStyleVar(2);\n            ImGui::Separator();\n            if (show_button)\n            {\n                ImGui::Button(\"this is a 300-wide button\", ImVec2(300, 0));\n            }\n            if (show_tree_nodes)\n            {\n                bool open = true;\n                if (ImGui::TreeNode(\"this is a tree node\"))\n                {\n                    if (ImGui::TreeNode(\"another one of those tree node...\"))\n                    {\n                        ImGui::Text(\"Some tree contents\");\n                        ImGui::TreePop();\n                    }\n                    ImGui::TreePop();\n                }\n                ImGui::CollapsingHeader(\"CollapsingHeader\", &open);\n            }\n            if (show_text_wrapped)\n            {\n                ImGui::TextWrapped(\"This text should automatically wrap on the edge of the work rectangle.\");\n            }\n            if (show_columns)\n            {\n                ImGui::Text(\"Tables:\");\n                if (ImGui::BeginTable(\"table\", 4, ImGuiTableFlags_Borders))\n                {\n                    for (int n = 0; n < 4; n++)\n                    {\n                        ImGui::TableNextColumn();\n                        ImGui::Text(\"Width %.2f\", ImGui::GetContentRegionAvail().x);\n                    }\n                    ImGui::EndTable();\n                }\n                ImGui::Text(\"Columns:\");\n                ImGui::Columns(4);\n                for (int n = 0; n < 4; n++)\n                {\n                    ImGui::Text(\"Width %.2f\", ImGui::GetColumnWidth());\n                    ImGui::NextColumn();\n                }\n                ImGui::Columns(1);\n            }\n            if (show_tab_bar && ImGui::BeginTabBar(\"Hello\"))\n            {\n                if (ImGui::BeginTabItem(\"OneOneOne\")) { ImGui::EndTabItem(); }\n                if (ImGui::BeginTabItem(\"TwoTwoTwo\")) { ImGui::EndTabItem(); }\n                if (ImGui::BeginTabItem(\"ThreeThreeThree\")) { ImGui::EndTabItem(); }\n                if (ImGui::BeginTabItem(\"FourFourFour\")) { ImGui::EndTabItem(); }\n                ImGui::EndTabBar();\n            }\n            if (show_child)\n            {\n                ImGui::BeginChild(\"child\", ImVec2(0, 0), ImGuiChildFlags_Borders);\n                ImGui::EndChild();\n            }\n            ImGui::End();\n        }\n\n        ImGui::TreePop();\n    }\n\n    IMGUI_DEMO_MARKER(\"Layout/Text Clipping\");\n    if (ImGui::TreeNode(\"Text Clipping\"))\n    {\n        static ImVec2 size(100.0f, 100.0f);\n        static ImVec2 offset(30.0f, 30.0f);\n        ImGui::DragFloat2(\"size\", (float*)&size, 0.5f, 1.0f, 200.0f, \"%.0f\");\n        ImGui::TextWrapped(\"(Click and drag to scroll)\");\n\n        HelpMarker(\n            \"(Left) Using ImGui::PushClipRect():\\n\"\n            \"Will alter ImGui hit-testing logic + ImDrawList rendering.\\n\"\n            \"(use this if you want your clipping rectangle to affect interactions)\\n\\n\"\n            \"(Center) Using ImDrawList::PushClipRect():\\n\"\n            \"Will alter ImDrawList rendering only.\\n\"\n            \"(use this as a shortcut if you are only using ImDrawList calls)\\n\\n\"\n            \"(Right) Using ImDrawList::AddText() with a fine ClipRect:\\n\"\n            \"Will alter only this specific ImDrawList::AddText() rendering.\\n\"\n            \"This is often used internally to avoid altering the clipping rectangle and minimize draw calls.\");\n\n        for (int n = 0; n < 3; n++)\n        {\n            if (n > 0)\n                ImGui::SameLine();\n\n            ImGui::PushID(n);\n            ImGui::InvisibleButton(\"##canvas\", size);\n            if (ImGui::IsItemActive() && ImGui::IsMouseDragging(ImGuiMouseButton_Left))\n            {\n                offset.x += ImGui::GetIO().MouseDelta.x;\n                offset.y += ImGui::GetIO().MouseDelta.y;\n            }\n            ImGui::PopID();\n            if (!ImGui::IsItemVisible()) // Skip rendering as ImDrawList elements are not clipped.\n                continue;\n\n            const ImVec2 p0 = ImGui::GetItemRectMin();\n            const ImVec2 p1 = ImGui::GetItemRectMax();\n            const char* text_str = \"Line 1 hello\\nLine 2 clip me!\";\n            const ImVec2 text_pos = ImVec2(p0.x + offset.x, p0.y + offset.y);\n            ImDrawList* draw_list = ImGui::GetWindowDrawList();\n            switch (n)\n            {\n            case 0:\n                ImGui::PushClipRect(p0, p1, true);\n                draw_list->AddRectFilled(p0, p1, IM_COL32(90, 90, 120, 255));\n                draw_list->AddText(text_pos, IM_COL32_WHITE, text_str);\n                ImGui::PopClipRect();\n                break;\n            case 1:\n                draw_list->PushClipRect(p0, p1, true);\n                draw_list->AddRectFilled(p0, p1, IM_COL32(90, 90, 120, 255));\n                draw_list->AddText(text_pos, IM_COL32_WHITE, text_str);\n                draw_list->PopClipRect();\n                break;\n            case 2:\n                ImVec4 clip_rect(p0.x, p0.y, p1.x, p1.y); // AddText() takes a ImVec4* here so let's convert.\n                draw_list->AddRectFilled(p0, p1, IM_COL32(90, 90, 120, 255));\n                draw_list->AddText(ImGui::GetFont(), ImGui::GetFontSize(), text_pos, IM_COL32_WHITE, text_str, NULL, 0.0f, &clip_rect);\n                break;\n            }\n        }\n\n        ImGui::TreePop();\n    }\n\n    IMGUI_DEMO_MARKER(\"Layout/Overlap Mode\");\n    if (ImGui::TreeNode(\"Overlap Mode\"))\n    {\n        static bool enable_allow_overlap = true;\n\n        HelpMarker(\n            \"Hit-testing is by default performed in item submission order, which generally is perceived as 'back-to-front'.\\n\\n\"\n            \"By using SetNextItemAllowOverlap() you can notify that an item may be overlapped by another. \"\n            \"Doing so alters the hovering logic: items using AllowOverlap mode requires an extra frame to accept hovered state.\");\n        ImGui::Checkbox(\"Enable AllowOverlap\", &enable_allow_overlap);\n\n        ImVec2 button1_pos = ImGui::GetCursorScreenPos();\n        ImVec2 button2_pos = ImVec2(button1_pos.x + 50.0f, button1_pos.y + 50.0f);\n        if (enable_allow_overlap)\n            ImGui::SetNextItemAllowOverlap();\n        ImGui::Button(\"Button 1\", ImVec2(80, 80));\n        ImGui::SetCursorScreenPos(button2_pos);\n        ImGui::Button(\"Button 2\", ImVec2(80, 80));\n\n        // This is typically used with width-spanning items.\n        // (note that Selectable() has a dedicated flag ImGuiSelectableFlags_AllowOverlap, which is a shortcut\n        // for using SetNextItemAllowOverlap(). For demo purpose we use SetNextItemAllowOverlap() here.)\n        if (enable_allow_overlap)\n            ImGui::SetNextItemAllowOverlap();\n        ImGui::Selectable(\"Some Selectable\", false);\n        ImGui::SameLine();\n        ImGui::SmallButton(\"++\");\n\n        ImGui::TreePop();\n    }\n}\n\n//-----------------------------------------------------------------------------\n// [SECTION] DemoWindowPopups()\n//-----------------------------------------------------------------------------\n\nstatic void DemoWindowPopups()\n{\n    IMGUI_DEMO_MARKER(\"Popups\");\n    if (!ImGui::CollapsingHeader(\"Popups & Modal windows\"))\n        return;\n\n    // The properties of popups windows are:\n    // - They block normal mouse hovering detection outside them. (*)\n    // - Unless modal, they can be closed by clicking anywhere outside them, or by pressing ESCAPE.\n    // - Their visibility state (~bool) is held internally by Dear ImGui instead of being held by the programmer as\n    //   we are used to with regular Begin() calls. User can manipulate the visibility state by calling OpenPopup().\n    // (*) One can use IsItemHovered(ImGuiHoveredFlags_AllowWhenBlockedByPopup) to bypass it and detect hovering even\n    //     when normally blocked by a popup.\n    // Those three properties are connected. The library needs to hold their visibility state BECAUSE it can close\n    // popups at any time.\n\n    // Typical use for regular windows:\n    //   bool my_tool_is_active = false; if (ImGui::Button(\"Open\")) my_tool_is_active = true; [...] if (my_tool_is_active) Begin(\"My Tool\", &my_tool_is_active) { [...] } End();\n    // Typical use for popups:\n    //   if (ImGui::Button(\"Open\")) ImGui::OpenPopup(\"MyPopup\"); if (ImGui::BeginPopup(\"MyPopup\") { [...] EndPopup(); }\n\n    // With popups we have to go through a library call (here OpenPopup) to manipulate the visibility state.\n    // This may be a bit confusing at first but it should quickly make sense. Follow on the examples below.\n\n    IMGUI_DEMO_MARKER(\"Popups/Popups\");\n    if (ImGui::TreeNode(\"Popups\"))\n    {\n        ImGui::TextWrapped(\n            \"When a popup is active, it inhibits interacting with windows that are behind the popup. \"\n            \"Clicking outside the popup closes it.\");\n\n        static int selected_fish = -1;\n        const char* names[] = { \"Bream\", \"Haddock\", \"Mackerel\", \"Pollock\", \"Tilefish\" };\n        static bool toggles[] = { true, false, false, false, false };\n\n        // Simple selection popup (if you want to show the current selection inside the Button itself,\n        // you may want to build a string using the \"###\" operator to preserve a constant ID with a variable label)\n        if (ImGui::Button(\"Select..\"))\n            ImGui::OpenPopup(\"my_select_popup\");\n        ImGui::SameLine();\n        ImGui::TextUnformatted(selected_fish == -1 ? \"<None>\" : names[selected_fish]);\n        if (ImGui::BeginPopup(\"my_select_popup\"))\n        {\n            ImGui::SeparatorText(\"Aquarium\");\n            for (int i = 0; i < IM_ARRAYSIZE(names); i++)\n                if (ImGui::Selectable(names[i]))\n                    selected_fish = i;\n            ImGui::EndPopup();\n        }\n\n        // Showing a menu with toggles\n        if (ImGui::Button(\"Toggle..\"))\n            ImGui::OpenPopup(\"my_toggle_popup\");\n        if (ImGui::BeginPopup(\"my_toggle_popup\"))\n        {\n            for (int i = 0; i < IM_ARRAYSIZE(names); i++)\n                ImGui::MenuItem(names[i], \"\", &toggles[i]);\n            if (ImGui::BeginMenu(\"Sub-menu\"))\n            {\n                ImGui::MenuItem(\"Click me\");\n                ImGui::EndMenu();\n            }\n\n            ImGui::Separator();\n            ImGui::Text(\"Tooltip here\");\n            ImGui::SetItemTooltip(\"I am a tooltip over a popup\");\n\n            if (ImGui::Button(\"Stacked Popup\"))\n                ImGui::OpenPopup(\"another popup\");\n            if (ImGui::BeginPopup(\"another popup\"))\n            {\n                for (int i = 0; i < IM_ARRAYSIZE(names); i++)\n                    ImGui::MenuItem(names[i], \"\", &toggles[i]);\n                if (ImGui::BeginMenu(\"Sub-menu\"))\n                {\n                    ImGui::MenuItem(\"Click me\");\n                    if (ImGui::Button(\"Stacked Popup\"))\n                        ImGui::OpenPopup(\"another popup\");\n                    if (ImGui::BeginPopup(\"another popup\"))\n                    {\n                        ImGui::Text(\"I am the last one here.\");\n                        ImGui::EndPopup();\n                    }\n                    ImGui::EndMenu();\n                }\n                ImGui::EndPopup();\n            }\n            ImGui::EndPopup();\n        }\n\n        // Call the more complete ShowExampleMenuFile which we use in various places of this demo\n        if (ImGui::Button(\"With a menu..\"))\n            ImGui::OpenPopup(\"my_file_popup\");\n        if (ImGui::BeginPopup(\"my_file_popup\", ImGuiWindowFlags_MenuBar))\n        {\n            if (ImGui::BeginMenuBar())\n            {\n                if (ImGui::BeginMenu(\"File\"))\n                {\n                    ShowExampleMenuFile();\n                    ImGui::EndMenu();\n                }\n                if (ImGui::BeginMenu(\"Edit\"))\n                {\n                    ImGui::MenuItem(\"Dummy\");\n                    ImGui::EndMenu();\n                }\n                ImGui::EndMenuBar();\n            }\n            ImGui::Text(\"Hello from popup!\");\n            ImGui::Button(\"This is a dummy button..\");\n            ImGui::EndPopup();\n        }\n\n        ImGui::TreePop();\n    }\n\n    IMGUI_DEMO_MARKER(\"Popups/Context menus\");\n    if (ImGui::TreeNode(\"Context menus\"))\n    {\n        HelpMarker(\"\\\"Context\\\" functions are simple helpers to associate a Popup to a given Item or Window identifier.\");\n\n        // BeginPopupContextItem() is a helper to provide common/simple popup behavior of essentially doing:\n        //     if (id == 0)\n        //         id = GetItemID(); // Use last item id\n        //     if (IsItemHovered() && IsMouseReleased(ImGuiMouseButton_Right))\n        //         OpenPopup(id);\n        //     return BeginPopup(id);\n        // For advanced uses you may want to replicate and customize this code.\n        // See more details in BeginPopupContextItem().\n\n        // Example 1\n        // When used after an item that has an ID (e.g. Button), we can skip providing an ID to BeginPopupContextItem(),\n        // and BeginPopupContextItem() will use the last item ID as the popup ID.\n        {\n            const char* names[5] = { \"Label1\", \"Label2\", \"Label3\", \"Label4\", \"Label5\" };\n            static int selected = -1;\n            for (int n = 0; n < 5; n++)\n            {\n                if (ImGui::Selectable(names[n], selected == n))\n                    selected = n;\n                if (ImGui::BeginPopupContextItem()) // <-- use last item id as popup id\n                {\n                    selected = n;\n                    ImGui::Text(\"This a popup for \\\"%s\\\"!\", names[n]);\n                    if (ImGui::Button(\"Close\"))\n                        ImGui::CloseCurrentPopup();\n                    ImGui::EndPopup();\n                }\n                ImGui::SetItemTooltip(\"Right-click to open popup\");\n            }\n        }\n\n        // Example 2\n        // Popup on a Text() element which doesn't have an identifier: we need to provide an identifier to BeginPopupContextItem().\n        // Using an explicit identifier is also convenient if you want to activate the popups from different locations.\n        {\n            HelpMarker(\"Text() elements don't have stable identifiers so we need to provide one.\");\n            static float value = 0.5f;\n            ImGui::Text(\"Value = %.3f <-- (1) right-click this text\", value);\n            if (ImGui::BeginPopupContextItem(\"my popup\"))\n            {\n                if (ImGui::Selectable(\"Set to zero\")) value = 0.0f;\n                if (ImGui::Selectable(\"Set to PI\")) value = 3.1415f;\n                ImGui::SetNextItemWidth(-FLT_MIN);\n                ImGui::DragFloat(\"##Value\", &value, 0.1f, 0.0f, 0.0f);\n                ImGui::EndPopup();\n            }\n\n            // We can also use OpenPopupOnItemClick() to toggle the visibility of a given popup.\n            // Here we make it that right-clicking this other text element opens the same popup as above.\n            // The popup itself will be submitted by the code above.\n            ImGui::Text(\"(2) Or right-click this text\");\n            ImGui::OpenPopupOnItemClick(\"my popup\", ImGuiPopupFlags_MouseButtonRight);\n\n            // Back to square one: manually open the same popup.\n            if (ImGui::Button(\"(3) Or click this button\"))\n                ImGui::OpenPopup(\"my popup\");\n        }\n\n        // Example 3\n        // When using BeginPopupContextItem() with an implicit identifier (NULL == use last item ID),\n        // we need to make sure your item identifier is stable.\n        // In this example we showcase altering the item label while preserving its identifier, using the ### operator (see FAQ).\n        {\n            HelpMarker(\"Showcase using a popup ID linked to item ID, with the item having a changing label + stable ID using the ### operator.\");\n            static char name[32] = \"Label1\";\n            char buf[64];\n            sprintf(buf, \"Button: %s###Button\", name); // ### operator override ID ignoring the preceding label\n            ImGui::Button(buf);\n            if (ImGui::BeginPopupContextItem())\n            {\n                ImGui::Text(\"Edit name:\");\n                ImGui::InputText(\"##edit\", name, IM_ARRAYSIZE(name));\n                if (ImGui::Button(\"Close\"))\n                    ImGui::CloseCurrentPopup();\n                ImGui::EndPopup();\n            }\n            ImGui::SameLine(); ImGui::Text(\"(<-- right-click here)\");\n        }\n\n        ImGui::TreePop();\n    }\n\n    IMGUI_DEMO_MARKER(\"Popups/Modals\");\n    if (ImGui::TreeNode(\"Modals\"))\n    {\n        ImGui::TextWrapped(\"Modal windows are like popups but the user cannot close them by clicking outside.\");\n\n        if (ImGui::Button(\"Delete..\"))\n            ImGui::OpenPopup(\"Delete?\");\n\n        // Always center this window when appearing\n        ImVec2 center = ImGui::GetMainViewport()->GetCenter();\n        ImGui::SetNextWindowPos(center, ImGuiCond_Appearing, ImVec2(0.5f, 0.5f));\n\n        if (ImGui::BeginPopupModal(\"Delete?\", NULL, ImGuiWindowFlags_AlwaysAutoResize))\n        {\n            ImGui::Text(\"All those beautiful files will be deleted.\\nThis operation cannot be undone!\");\n            ImGui::Separator();\n\n            //static int unused_i = 0;\n            //ImGui::Combo(\"Combo\", &unused_i, \"Delete\\0Delete harder\\0\");\n\n            static bool dont_ask_me_next_time = false;\n            ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(0, 0));\n            ImGui::Checkbox(\"Don't ask me next time\", &dont_ask_me_next_time);\n            ImGui::PopStyleVar();\n\n            if (ImGui::Button(\"OK\", ImVec2(120, 0))) { ImGui::CloseCurrentPopup(); }\n            ImGui::SetItemDefaultFocus();\n            ImGui::SameLine();\n            if (ImGui::Button(\"Cancel\", ImVec2(120, 0))) { ImGui::CloseCurrentPopup(); }\n            ImGui::EndPopup();\n        }\n\n        if (ImGui::Button(\"Stacked modals..\"))\n            ImGui::OpenPopup(\"Stacked 1\");\n        if (ImGui::BeginPopupModal(\"Stacked 1\", NULL, ImGuiWindowFlags_MenuBar))\n        {\n            if (ImGui::BeginMenuBar())\n            {\n                if (ImGui::BeginMenu(\"File\"))\n                {\n                    if (ImGui::MenuItem(\"Some menu item\")) {}\n                    ImGui::EndMenu();\n                }\n                ImGui::EndMenuBar();\n            }\n            ImGui::Text(\"Hello from Stacked The First\\nUsing style.Colors[ImGuiCol_ModalWindowDimBg] behind it.\");\n\n            // Testing behavior of widgets stacking their own regular popups over the modal.\n            static int item = 1;\n            static float color[4] = { 0.4f, 0.7f, 0.0f, 0.5f };\n            ImGui::Combo(\"Combo\", &item, \"aaaa\\0bbbb\\0cccc\\0dddd\\0eeee\\0\\0\");\n            ImGui::ColorEdit4(\"Color\", color);\n\n            if (ImGui::Button(\"Add another modal..\"))\n                ImGui::OpenPopup(\"Stacked 2\");\n\n            // Also demonstrate passing a bool* to BeginPopupModal(), this will create a regular close button which\n            // will close the popup. Note that the visibility state of popups is owned by imgui, so the input value\n            // of the bool actually doesn't matter here.\n            bool unused_open = true;\n            if (ImGui::BeginPopupModal(\"Stacked 2\", &unused_open))\n            {\n                ImGui::Text(\"Hello from Stacked The Second!\");\n                ImGui::ColorEdit4(\"Color\", color); // Allow opening another nested popup\n                if (ImGui::Button(\"Close\"))\n                    ImGui::CloseCurrentPopup();\n                ImGui::EndPopup();\n            }\n\n            if (ImGui::Button(\"Close\"))\n                ImGui::CloseCurrentPopup();\n            ImGui::EndPopup();\n        }\n\n        ImGui::TreePop();\n    }\n\n    IMGUI_DEMO_MARKER(\"Popups/Menus inside a regular window\");\n    if (ImGui::TreeNode(\"Menus inside a regular window\"))\n    {\n        ImGui::TextWrapped(\"Below we are testing adding menu items to a regular window. It's rather unusual but should work!\");\n        ImGui::Separator();\n\n        ImGui::MenuItem(\"Menu item\", \"CTRL+M\");\n        if (ImGui::BeginMenu(\"Menu inside a regular window\"))\n        {\n            ShowExampleMenuFile();\n            ImGui::EndMenu();\n        }\n        ImGui::Separator();\n        ImGui::TreePop();\n    }\n}\n\n// Dummy data structure that we use for the Table demo.\n// (pre-C++11 doesn't allow us to instantiate ImVector<MyItem> template if this structure is defined inside the demo function)\nnamespace\n{\n// We are passing our own identifier to TableSetupColumn() to facilitate identifying columns in the sorting code.\n// This identifier will be passed down into ImGuiTableSortSpec::ColumnUserID.\n// But it is possible to omit the user id parameter of TableSetupColumn() and just use the column index instead! (ImGuiTableSortSpec::ColumnIndex)\n// If you don't use sorting, you will generally never care about giving column an ID!\nenum MyItemColumnID\n{\n    MyItemColumnID_ID,\n    MyItemColumnID_Name,\n    MyItemColumnID_Action,\n    MyItemColumnID_Quantity,\n    MyItemColumnID_Description\n};\n\nstruct MyItem\n{\n    int         ID;\n    const char* Name;\n    int         Quantity;\n\n    // We have a problem which is affecting _only this demo_ and should not affect your code:\n    // As we don't rely on std:: or other third-party library to compile dear imgui, we only have reliable access to qsort(),\n    // however qsort doesn't allow passing user data to comparing function.\n    // As a workaround, we are storing the sort specs in a static/global for the comparing function to access.\n    // In your own use case you would probably pass the sort specs to your sorting/comparing functions directly and not use a global.\n    // We could technically call ImGui::TableGetSortSpecs() in CompareWithSortSpecs(), but considering that this function is called\n    // very often by the sorting algorithm it would be a little wasteful.\n    static const ImGuiTableSortSpecs* s_current_sort_specs;\n\n    static void SortWithSortSpecs(ImGuiTableSortSpecs* sort_specs, MyItem* items, int items_count)\n    {\n        s_current_sort_specs = sort_specs; // Store in variable accessible by the sort function.\n        if (items_count > 1)\n            qsort(items, (size_t)items_count, sizeof(items[0]), MyItem::CompareWithSortSpecs);\n        s_current_sort_specs = NULL;\n    }\n\n    // Compare function to be used by qsort()\n    static int IMGUI_CDECL CompareWithSortSpecs(const void* lhs, const void* rhs)\n    {\n        const MyItem* a = (const MyItem*)lhs;\n        const MyItem* b = (const MyItem*)rhs;\n        for (int n = 0; n < s_current_sort_specs->SpecsCount; n++)\n        {\n            // Here we identify columns using the ColumnUserID value that we ourselves passed to TableSetupColumn()\n            // We could also choose to identify columns based on their index (sort_spec->ColumnIndex), which is simpler!\n            const ImGuiTableColumnSortSpecs* sort_spec = &s_current_sort_specs->Specs[n];\n            int delta = 0;\n            switch (sort_spec->ColumnUserID)\n            {\n            case MyItemColumnID_ID:             delta = (a->ID - b->ID);                break;\n            case MyItemColumnID_Name:           delta = (strcmp(a->Name, b->Name));     break;\n            case MyItemColumnID_Quantity:       delta = (a->Quantity - b->Quantity);    break;\n            case MyItemColumnID_Description:    delta = (strcmp(a->Name, b->Name));     break;\n            default: IM_ASSERT(0); break;\n            }\n            if (delta > 0)\n                return (sort_spec->SortDirection == ImGuiSortDirection_Ascending) ? +1 : -1;\n            if (delta < 0)\n                return (sort_spec->SortDirection == ImGuiSortDirection_Ascending) ? -1 : +1;\n        }\n\n        // qsort() is instable so always return a way to differenciate items.\n        // Your own compare function may want to avoid fallback on implicit sort specs.\n        // e.g. a Name compare if it wasn't already part of the sort specs.\n        return (a->ID - b->ID);\n    }\n};\nconst ImGuiTableSortSpecs* MyItem::s_current_sort_specs = NULL;\n}\n\n// Make the UI compact because there are so many fields\nstatic void PushStyleCompact()\n{\n    ImGuiStyle& style = ImGui::GetStyle();\n    ImGui::PushStyleVarY(ImGuiStyleVar_FramePadding, (float)(int)(style.FramePadding.y * 0.60f));\n    ImGui::PushStyleVarY(ImGuiStyleVar_ItemSpacing, (float)(int)(style.ItemSpacing.y * 0.60f));\n}\n\nstatic void PopStyleCompact()\n{\n    ImGui::PopStyleVar(2);\n}\n\n// Show a combo box with a choice of sizing policies\nstatic void EditTableSizingFlags(ImGuiTableFlags* p_flags)\n{\n    struct EnumDesc { ImGuiTableFlags Value; const char* Name; const char* Tooltip; };\n    static const EnumDesc policies[] =\n    {\n        { ImGuiTableFlags_None,               \"Default\",                            \"Use default sizing policy:\\n- ImGuiTableFlags_SizingFixedFit if ScrollX is on or if host window has ImGuiWindowFlags_AlwaysAutoResize.\\n- ImGuiTableFlags_SizingStretchSame otherwise.\" },\n        { ImGuiTableFlags_SizingFixedFit,     \"ImGuiTableFlags_SizingFixedFit\",     \"Columns default to _WidthFixed (if resizable) or _WidthAuto (if not resizable), matching contents width.\" },\n        { ImGuiTableFlags_SizingFixedSame,    \"ImGuiTableFlags_SizingFixedSame\",    \"Columns are all the same width, matching the maximum contents width.\\nImplicitly disable ImGuiTableFlags_Resizable and enable ImGuiTableFlags_NoKeepColumnsVisible.\" },\n        { ImGuiTableFlags_SizingStretchProp,  \"ImGuiTableFlags_SizingStretchProp\",  \"Columns default to _WidthStretch with weights proportional to their widths.\" },\n        { ImGuiTableFlags_SizingStretchSame,  \"ImGuiTableFlags_SizingStretchSame\",  \"Columns default to _WidthStretch with same weights.\" }\n    };\n    int idx;\n    for (idx = 0; idx < IM_ARRAYSIZE(policies); idx++)\n        if (policies[idx].Value == (*p_flags & ImGuiTableFlags_SizingMask_))\n            break;\n    const char* preview_text = (idx < IM_ARRAYSIZE(policies)) ? policies[idx].Name + (idx > 0 ? strlen(\"ImGuiTableFlags\") : 0) : \"\";\n    if (ImGui::BeginCombo(\"Sizing Policy\", preview_text))\n    {\n        for (int n = 0; n < IM_ARRAYSIZE(policies); n++)\n            if (ImGui::Selectable(policies[n].Name, idx == n))\n                *p_flags = (*p_flags & ~ImGuiTableFlags_SizingMask_) | policies[n].Value;\n        ImGui::EndCombo();\n    }\n    ImGui::SameLine();\n    ImGui::TextDisabled(\"(?)\");\n    if (ImGui::BeginItemTooltip())\n    {\n        ImGui::PushTextWrapPos(ImGui::GetFontSize() * 50.0f);\n        for (int m = 0; m < IM_ARRAYSIZE(policies); m++)\n        {\n            ImGui::Separator();\n            ImGui::Text(\"%s:\", policies[m].Name);\n            ImGui::Separator();\n            ImGui::SetCursorPosX(ImGui::GetCursorPosX() + ImGui::GetStyle().IndentSpacing * 0.5f);\n            ImGui::TextUnformatted(policies[m].Tooltip);\n        }\n        ImGui::PopTextWrapPos();\n        ImGui::EndTooltip();\n    }\n}\n\nstatic void EditTableColumnsFlags(ImGuiTableColumnFlags* p_flags)\n{\n    ImGui::CheckboxFlags(\"_Disabled\", p_flags, ImGuiTableColumnFlags_Disabled); ImGui::SameLine(); HelpMarker(\"Master disable flag (also hide from context menu)\");\n    ImGui::CheckboxFlags(\"_DefaultHide\", p_flags, ImGuiTableColumnFlags_DefaultHide);\n    ImGui::CheckboxFlags(\"_DefaultSort\", p_flags, ImGuiTableColumnFlags_DefaultSort);\n    if (ImGui::CheckboxFlags(\"_WidthStretch\", p_flags, ImGuiTableColumnFlags_WidthStretch))\n        *p_flags &= ~(ImGuiTableColumnFlags_WidthMask_ ^ ImGuiTableColumnFlags_WidthStretch);\n    if (ImGui::CheckboxFlags(\"_WidthFixed\", p_flags, ImGuiTableColumnFlags_WidthFixed))\n        *p_flags &= ~(ImGuiTableColumnFlags_WidthMask_ ^ ImGuiTableColumnFlags_WidthFixed);\n    ImGui::CheckboxFlags(\"_NoResize\", p_flags, ImGuiTableColumnFlags_NoResize);\n    ImGui::CheckboxFlags(\"_NoReorder\", p_flags, ImGuiTableColumnFlags_NoReorder);\n    ImGui::CheckboxFlags(\"_NoHide\", p_flags, ImGuiTableColumnFlags_NoHide);\n    ImGui::CheckboxFlags(\"_NoClip\", p_flags, ImGuiTableColumnFlags_NoClip);\n    ImGui::CheckboxFlags(\"_NoSort\", p_flags, ImGuiTableColumnFlags_NoSort);\n    ImGui::CheckboxFlags(\"_NoSortAscending\", p_flags, ImGuiTableColumnFlags_NoSortAscending);\n    ImGui::CheckboxFlags(\"_NoSortDescending\", p_flags, ImGuiTableColumnFlags_NoSortDescending);\n    ImGui::CheckboxFlags(\"_NoHeaderLabel\", p_flags, ImGuiTableColumnFlags_NoHeaderLabel);\n    ImGui::CheckboxFlags(\"_NoHeaderWidth\", p_flags, ImGuiTableColumnFlags_NoHeaderWidth);\n    ImGui::CheckboxFlags(\"_PreferSortAscending\", p_flags, ImGuiTableColumnFlags_PreferSortAscending);\n    ImGui::CheckboxFlags(\"_PreferSortDescending\", p_flags, ImGuiTableColumnFlags_PreferSortDescending);\n    ImGui::CheckboxFlags(\"_IndentEnable\", p_flags, ImGuiTableColumnFlags_IndentEnable); ImGui::SameLine(); HelpMarker(\"Default for column 0\");\n    ImGui::CheckboxFlags(\"_IndentDisable\", p_flags, ImGuiTableColumnFlags_IndentDisable); ImGui::SameLine(); HelpMarker(\"Default for column >0\");\n    ImGui::CheckboxFlags(\"_AngledHeader\", p_flags, ImGuiTableColumnFlags_AngledHeader);\n}\n\nstatic void ShowTableColumnsStatusFlags(ImGuiTableColumnFlags flags)\n{\n    ImGui::CheckboxFlags(\"_IsEnabled\", &flags, ImGuiTableColumnFlags_IsEnabled);\n    ImGui::CheckboxFlags(\"_IsVisible\", &flags, ImGuiTableColumnFlags_IsVisible);\n    ImGui::CheckboxFlags(\"_IsSorted\", &flags, ImGuiTableColumnFlags_IsSorted);\n    ImGui::CheckboxFlags(\"_IsHovered\", &flags, ImGuiTableColumnFlags_IsHovered);\n}\n\n//-----------------------------------------------------------------------------\n// [SECTION] DemoWindowTables()\n//-----------------------------------------------------------------------------\n\nstatic void DemoWindowTables()\n{\n    //ImGui::SetNextItemOpen(true, ImGuiCond_Once);\n    IMGUI_DEMO_MARKER(\"Tables\");\n    if (!ImGui::CollapsingHeader(\"Tables & Columns\"))\n        return;\n\n    // Using those as a base value to create width/height that are factor of the size of our font\n    const float TEXT_BASE_WIDTH = ImGui::CalcTextSize(\"A\").x;\n    const float TEXT_BASE_HEIGHT = ImGui::GetTextLineHeightWithSpacing();\n\n    ImGui::PushID(\"Tables\");\n\n    int open_action = -1;\n    if (ImGui::Button(\"Expand all\"))\n        open_action = 1;\n    ImGui::SameLine();\n    if (ImGui::Button(\"Collapse all\"))\n        open_action = 0;\n    ImGui::SameLine();\n\n    // Options\n    static bool disable_indent = false;\n    ImGui::Checkbox(\"Disable tree indentation\", &disable_indent);\n    ImGui::SameLine();\n    HelpMarker(\"Disable the indenting of tree nodes so demo tables can use the full window width.\");\n    ImGui::Separator();\n    if (disable_indent)\n        ImGui::PushStyleVar(ImGuiStyleVar_IndentSpacing, 0.0f);\n\n    // About Styling of tables\n    // Most settings are configured on a per-table basis via the flags passed to BeginTable() and TableSetupColumns APIs.\n    // There are however a few settings that a shared and part of the ImGuiStyle structure:\n    //   style.CellPadding                          // Padding within each cell\n    //   style.Colors[ImGuiCol_TableHeaderBg]       // Table header background\n    //   style.Colors[ImGuiCol_TableBorderStrong]   // Table outer and header borders\n    //   style.Colors[ImGuiCol_TableBorderLight]    // Table inner borders\n    //   style.Colors[ImGuiCol_TableRowBg]          // Table row background when ImGuiTableFlags_RowBg is enabled (even rows)\n    //   style.Colors[ImGuiCol_TableRowBgAlt]       // Table row background when ImGuiTableFlags_RowBg is enabled (odds rows)\n\n    // Demos\n    if (open_action != -1)\n        ImGui::SetNextItemOpen(open_action != 0);\n    IMGUI_DEMO_MARKER(\"Tables/Basic\");\n    if (ImGui::TreeNode(\"Basic\"))\n    {\n        // Here we will showcase three different ways to output a table.\n        // They are very simple variations of a same thing!\n\n        // [Method 1] Using TableNextRow() to create a new row, and TableSetColumnIndex() to select the column.\n        // In many situations, this is the most flexible and easy to use pattern.\n        HelpMarker(\"Using TableNextRow() + calling TableSetColumnIndex() _before_ each cell, in a loop.\");\n        if (ImGui::BeginTable(\"table1\", 3))\n        {\n            for (int row = 0; row < 4; row++)\n            {\n                ImGui::TableNextRow();\n                for (int column = 0; column < 3; column++)\n                {\n                    ImGui::TableSetColumnIndex(column);\n                    ImGui::Text(\"Row %d Column %d\", row, column);\n                }\n            }\n            ImGui::EndTable();\n        }\n\n        // [Method 2] Using TableNextColumn() called multiple times, instead of using a for loop + TableSetColumnIndex().\n        // This is generally more convenient when you have code manually submitting the contents of each column.\n        HelpMarker(\"Using TableNextRow() + calling TableNextColumn() _before_ each cell, manually.\");\n        if (ImGui::BeginTable(\"table2\", 3))\n        {\n            for (int row = 0; row < 4; row++)\n            {\n                ImGui::TableNextRow();\n                ImGui::TableNextColumn();\n                ImGui::Text(\"Row %d\", row);\n                ImGui::TableNextColumn();\n                ImGui::Text(\"Some contents\");\n                ImGui::TableNextColumn();\n                ImGui::Text(\"123.456\");\n            }\n            ImGui::EndTable();\n        }\n\n        // [Method 3] We call TableNextColumn() _before_ each cell. We never call TableNextRow(),\n        // as TableNextColumn() will automatically wrap around and create new rows as needed.\n        // This is generally more convenient when your cells all contains the same type of data.\n        HelpMarker(\n            \"Only using TableNextColumn(), which tends to be convenient for tables where every cell contains \"\n            \"the same type of contents.\\n This is also more similar to the old NextColumn() function of the \"\n            \"Columns API, and provided to facilitate the Columns->Tables API transition.\");\n        if (ImGui::BeginTable(\"table3\", 3))\n        {\n            for (int item = 0; item < 14; item++)\n            {\n                ImGui::TableNextColumn();\n                ImGui::Text(\"Item %d\", item);\n            }\n            ImGui::EndTable();\n        }\n\n        ImGui::TreePop();\n    }\n\n    if (open_action != -1)\n        ImGui::SetNextItemOpen(open_action != 0);\n    IMGUI_DEMO_MARKER(\"Tables/Borders, background\");\n    if (ImGui::TreeNode(\"Borders, background\"))\n    {\n        // Expose a few Borders related flags interactively\n        enum ContentsType { CT_Text, CT_FillButton };\n        static ImGuiTableFlags flags = ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg;\n        static bool display_headers = false;\n        static int contents_type = CT_Text;\n\n        PushStyleCompact();\n        ImGui::CheckboxFlags(\"ImGuiTableFlags_RowBg\", &flags, ImGuiTableFlags_RowBg);\n        ImGui::CheckboxFlags(\"ImGuiTableFlags_Borders\", &flags, ImGuiTableFlags_Borders);\n        ImGui::SameLine(); HelpMarker(\"ImGuiTableFlags_Borders\\n = ImGuiTableFlags_BordersInnerV\\n | ImGuiTableFlags_BordersOuterV\\n | ImGuiTableFlags_BordersInnerH\\n | ImGuiTableFlags_BordersOuterH\");\n        ImGui::Indent();\n\n        ImGui::CheckboxFlags(\"ImGuiTableFlags_BordersH\", &flags, ImGuiTableFlags_BordersH);\n        ImGui::Indent();\n        ImGui::CheckboxFlags(\"ImGuiTableFlags_BordersOuterH\", &flags, ImGuiTableFlags_BordersOuterH);\n        ImGui::CheckboxFlags(\"ImGuiTableFlags_BordersInnerH\", &flags, ImGuiTableFlags_BordersInnerH);\n        ImGui::Unindent();\n\n        ImGui::CheckboxFlags(\"ImGuiTableFlags_BordersV\", &flags, ImGuiTableFlags_BordersV);\n        ImGui::Indent();\n        ImGui::CheckboxFlags(\"ImGuiTableFlags_BordersOuterV\", &flags, ImGuiTableFlags_BordersOuterV);\n        ImGui::CheckboxFlags(\"ImGuiTableFlags_BordersInnerV\", &flags, ImGuiTableFlags_BordersInnerV);\n        ImGui::Unindent();\n\n        ImGui::CheckboxFlags(\"ImGuiTableFlags_BordersOuter\", &flags, ImGuiTableFlags_BordersOuter);\n        ImGui::CheckboxFlags(\"ImGuiTableFlags_BordersInner\", &flags, ImGuiTableFlags_BordersInner);\n        ImGui::Unindent();\n\n        ImGui::AlignTextToFramePadding(); ImGui::Text(\"Cell contents:\");\n        ImGui::SameLine(); ImGui::RadioButton(\"Text\", &contents_type, CT_Text);\n        ImGui::SameLine(); ImGui::RadioButton(\"FillButton\", &contents_type, CT_FillButton);\n        ImGui::Checkbox(\"Display headers\", &display_headers);\n        ImGui::CheckboxFlags(\"ImGuiTableFlags_NoBordersInBody\", &flags, ImGuiTableFlags_NoBordersInBody); ImGui::SameLine(); HelpMarker(\"Disable vertical borders in columns Body (borders will always appear in Headers\");\n        PopStyleCompact();\n\n        if (ImGui::BeginTable(\"table1\", 3, flags))\n        {\n            // Display headers so we can inspect their interaction with borders\n            // (Headers are not the main purpose of this section of the demo, so we are not elaborating on them now. See other sections for details)\n            if (display_headers)\n            {\n                ImGui::TableSetupColumn(\"One\");\n                ImGui::TableSetupColumn(\"Two\");\n                ImGui::TableSetupColumn(\"Three\");\n                ImGui::TableHeadersRow();\n            }\n\n            for (int row = 0; row < 5; row++)\n            {\n                ImGui::TableNextRow();\n                for (int column = 0; column < 3; column++)\n                {\n                    ImGui::TableSetColumnIndex(column);\n                    char buf[32];\n                    sprintf(buf, \"Hello %d,%d\", column, row);\n                    if (contents_type == CT_Text)\n                        ImGui::TextUnformatted(buf);\n                    else if (contents_type == CT_FillButton)\n                        ImGui::Button(buf, ImVec2(-FLT_MIN, 0.0f));\n                }\n            }\n            ImGui::EndTable();\n        }\n        ImGui::TreePop();\n    }\n\n    if (open_action != -1)\n        ImGui::SetNextItemOpen(open_action != 0);\n    IMGUI_DEMO_MARKER(\"Tables/Resizable, stretch\");\n    if (ImGui::TreeNode(\"Resizable, stretch\"))\n    {\n        // By default, if we don't enable ScrollX the sizing policy for each column is \"Stretch\"\n        // All columns maintain a sizing weight, and they will occupy all available width.\n        static ImGuiTableFlags flags = ImGuiTableFlags_SizingStretchSame | ImGuiTableFlags_Resizable | ImGuiTableFlags_BordersOuter | ImGuiTableFlags_BordersV | ImGuiTableFlags_ContextMenuInBody;\n        PushStyleCompact();\n        ImGui::CheckboxFlags(\"ImGuiTableFlags_Resizable\", &flags, ImGuiTableFlags_Resizable);\n        ImGui::CheckboxFlags(\"ImGuiTableFlags_BordersV\", &flags, ImGuiTableFlags_BordersV);\n        ImGui::SameLine(); HelpMarker(\n            \"Using the _Resizable flag automatically enables the _BordersInnerV flag as well, \"\n            \"this is why the resize borders are still showing when unchecking this.\");\n        PopStyleCompact();\n\n        if (ImGui::BeginTable(\"table1\", 3, flags))\n        {\n            for (int row = 0; row < 5; row++)\n            {\n                ImGui::TableNextRow();\n                for (int column = 0; column < 3; column++)\n                {\n                    ImGui::TableSetColumnIndex(column);\n                    ImGui::Text(\"Hello %d,%d\", column, row);\n                }\n            }\n            ImGui::EndTable();\n        }\n        ImGui::TreePop();\n    }\n\n    if (open_action != -1)\n        ImGui::SetNextItemOpen(open_action != 0);\n    IMGUI_DEMO_MARKER(\"Tables/Resizable, fixed\");\n    if (ImGui::TreeNode(\"Resizable, fixed\"))\n    {\n        // Here we use ImGuiTableFlags_SizingFixedFit (even though _ScrollX is not set)\n        // So columns will adopt the \"Fixed\" policy and will maintain a fixed width regardless of the whole available width (unless table is small)\n        // If there is not enough available width to fit all columns, they will however be resized down.\n        // FIXME-TABLE: Providing a stretch-on-init would make sense especially for tables which don't have saved settings\n        HelpMarker(\n            \"Using _Resizable + _SizingFixedFit flags.\\n\"\n            \"Fixed-width columns generally makes more sense if you want to use horizontal scrolling.\\n\\n\"\n            \"Double-click a column border to auto-fit the column to its contents.\");\n        PushStyleCompact();\n        static ImGuiTableFlags flags = ImGuiTableFlags_SizingFixedFit | ImGuiTableFlags_Resizable | ImGuiTableFlags_BordersOuter | ImGuiTableFlags_BordersV | ImGuiTableFlags_ContextMenuInBody;\n        ImGui::CheckboxFlags(\"ImGuiTableFlags_NoHostExtendX\", &flags, ImGuiTableFlags_NoHostExtendX);\n        PopStyleCompact();\n\n        if (ImGui::BeginTable(\"table1\", 3, flags))\n        {\n            for (int row = 0; row < 5; row++)\n            {\n                ImGui::TableNextRow();\n                for (int column = 0; column < 3; column++)\n                {\n                    ImGui::TableSetColumnIndex(column);\n                    ImGui::Text(\"Hello %d,%d\", column, row);\n                }\n            }\n            ImGui::EndTable();\n        }\n        ImGui::TreePop();\n    }\n\n    if (open_action != -1)\n        ImGui::SetNextItemOpen(open_action != 0);\n    IMGUI_DEMO_MARKER(\"Tables/Resizable, mixed\");\n    if (ImGui::TreeNode(\"Resizable, mixed\"))\n    {\n        HelpMarker(\n            \"Using TableSetupColumn() to alter resizing policy on a per-column basis.\\n\\n\"\n            \"When combining Fixed and Stretch columns, generally you only want one, maybe two trailing columns to use _WidthStretch.\");\n        static ImGuiTableFlags flags = ImGuiTableFlags_SizingFixedFit | ImGuiTableFlags_RowBg | ImGuiTableFlags_Borders | ImGuiTableFlags_Resizable | ImGuiTableFlags_Reorderable | ImGuiTableFlags_Hideable;\n\n        if (ImGui::BeginTable(\"table1\", 3, flags))\n        {\n            ImGui::TableSetupColumn(\"AAA\", ImGuiTableColumnFlags_WidthFixed);\n            ImGui::TableSetupColumn(\"BBB\", ImGuiTableColumnFlags_WidthFixed);\n            ImGui::TableSetupColumn(\"CCC\", ImGuiTableColumnFlags_WidthStretch);\n            ImGui::TableHeadersRow();\n            for (int row = 0; row < 5; row++)\n            {\n                ImGui::TableNextRow();\n                for (int column = 0; column < 3; column++)\n                {\n                    ImGui::TableSetColumnIndex(column);\n                    ImGui::Text(\"%s %d,%d\", (column == 2) ? \"Stretch\" : \"Fixed\", column, row);\n                }\n            }\n            ImGui::EndTable();\n        }\n        if (ImGui::BeginTable(\"table2\", 6, flags))\n        {\n            ImGui::TableSetupColumn(\"AAA\", ImGuiTableColumnFlags_WidthFixed);\n            ImGui::TableSetupColumn(\"BBB\", ImGuiTableColumnFlags_WidthFixed);\n            ImGui::TableSetupColumn(\"CCC\", ImGuiTableColumnFlags_WidthFixed | ImGuiTableColumnFlags_DefaultHide);\n            ImGui::TableSetupColumn(\"DDD\", ImGuiTableColumnFlags_WidthStretch);\n            ImGui::TableSetupColumn(\"EEE\", ImGuiTableColumnFlags_WidthStretch);\n            ImGui::TableSetupColumn(\"FFF\", ImGuiTableColumnFlags_WidthStretch | ImGuiTableColumnFlags_DefaultHide);\n            ImGui::TableHeadersRow();\n            for (int row = 0; row < 5; row++)\n            {\n                ImGui::TableNextRow();\n                for (int column = 0; column < 6; column++)\n                {\n                    ImGui::TableSetColumnIndex(column);\n                    ImGui::Text(\"%s %d,%d\", (column >= 3) ? \"Stretch\" : \"Fixed\", column, row);\n                }\n            }\n            ImGui::EndTable();\n        }\n        ImGui::TreePop();\n    }\n\n    if (open_action != -1)\n        ImGui::SetNextItemOpen(open_action != 0);\n    IMGUI_DEMO_MARKER(\"Tables/Reorderable, hideable, with headers\");\n    if (ImGui::TreeNode(\"Reorderable, hideable, with headers\"))\n    {\n        HelpMarker(\n            \"Click and drag column headers to reorder columns.\\n\\n\"\n            \"Right-click on a header to open a context menu.\");\n        static ImGuiTableFlags flags = ImGuiTableFlags_Resizable | ImGuiTableFlags_Reorderable | ImGuiTableFlags_Hideable | ImGuiTableFlags_BordersOuter | ImGuiTableFlags_BordersV;\n        PushStyleCompact();\n        ImGui::CheckboxFlags(\"ImGuiTableFlags_Resizable\", &flags, ImGuiTableFlags_Resizable);\n        ImGui::CheckboxFlags(\"ImGuiTableFlags_Reorderable\", &flags, ImGuiTableFlags_Reorderable);\n        ImGui::CheckboxFlags(\"ImGuiTableFlags_Hideable\", &flags, ImGuiTableFlags_Hideable);\n        ImGui::CheckboxFlags(\"ImGuiTableFlags_NoBordersInBody\", &flags, ImGuiTableFlags_NoBordersInBody);\n        ImGui::CheckboxFlags(\"ImGuiTableFlags_NoBordersInBodyUntilResize\", &flags, ImGuiTableFlags_NoBordersInBodyUntilResize); ImGui::SameLine(); HelpMarker(\"Disable vertical borders in columns Body until hovered for resize (borders will always appear in Headers)\");\n        ImGui::CheckboxFlags(\"ImGuiTableFlags_HighlightHoveredColumn\", &flags, ImGuiTableFlags_HighlightHoveredColumn);\n        PopStyleCompact();\n\n        if (ImGui::BeginTable(\"table1\", 3, flags))\n        {\n            // Submit columns name with TableSetupColumn() and call TableHeadersRow() to create a row with a header in each column.\n            // (Later we will show how TableSetupColumn() has other uses, optional flags, sizing weight etc.)\n            ImGui::TableSetupColumn(\"One\");\n            ImGui::TableSetupColumn(\"Two\");\n            ImGui::TableSetupColumn(\"Three\");\n            ImGui::TableHeadersRow();\n            for (int row = 0; row < 6; row++)\n            {\n                ImGui::TableNextRow();\n                for (int column = 0; column < 3; column++)\n                {\n                    ImGui::TableSetColumnIndex(column);\n                    ImGui::Text(\"Hello %d,%d\", column, row);\n                }\n            }\n            ImGui::EndTable();\n        }\n\n        // Use outer_size.x == 0.0f instead of default to make the table as tight as possible\n        // (only valid when no scrolling and no stretch column)\n        if (ImGui::BeginTable(\"table2\", 3, flags | ImGuiTableFlags_SizingFixedFit, ImVec2(0.0f, 0.0f)))\n        {\n            ImGui::TableSetupColumn(\"One\");\n            ImGui::TableSetupColumn(\"Two\");\n            ImGui::TableSetupColumn(\"Three\");\n            ImGui::TableHeadersRow();\n            for (int row = 0; row < 6; row++)\n            {\n                ImGui::TableNextRow();\n                for (int column = 0; column < 3; column++)\n                {\n                    ImGui::TableSetColumnIndex(column);\n                    ImGui::Text(\"Fixed %d,%d\", column, row);\n                }\n            }\n            ImGui::EndTable();\n        }\n        ImGui::TreePop();\n    }\n\n    if (open_action != -1)\n        ImGui::SetNextItemOpen(open_action != 0);\n    IMGUI_DEMO_MARKER(\"Tables/Padding\");\n    if (ImGui::TreeNode(\"Padding\"))\n    {\n        // First example: showcase use of padding flags and effect of BorderOuterV/BorderInnerV on X padding.\n        // We don't expose BorderOuterH/BorderInnerH here because they have no effect on X padding.\n        HelpMarker(\n            \"We often want outer padding activated when any using features which makes the edges of a column visible:\\n\"\n            \"e.g.:\\n\"\n            \"- BorderOuterV\\n\"\n            \"- any form of row selection\\n\"\n            \"Because of this, activating BorderOuterV sets the default to PadOuterX. \"\n            \"Using PadOuterX or NoPadOuterX you can override the default.\\n\\n\"\n            \"Actual padding values are using style.CellPadding.\\n\\n\"\n            \"In this demo we don't show horizontal borders to emphasize how they don't affect default horizontal padding.\");\n\n        static ImGuiTableFlags flags1 = ImGuiTableFlags_BordersV;\n        PushStyleCompact();\n        ImGui::CheckboxFlags(\"ImGuiTableFlags_PadOuterX\", &flags1, ImGuiTableFlags_PadOuterX);\n        ImGui::SameLine(); HelpMarker(\"Enable outer-most padding (default if ImGuiTableFlags_BordersOuterV is set)\");\n        ImGui::CheckboxFlags(\"ImGuiTableFlags_NoPadOuterX\", &flags1, ImGuiTableFlags_NoPadOuterX);\n        ImGui::SameLine(); HelpMarker(\"Disable outer-most padding (default if ImGuiTableFlags_BordersOuterV is not set)\");\n        ImGui::CheckboxFlags(\"ImGuiTableFlags_NoPadInnerX\", &flags1, ImGuiTableFlags_NoPadInnerX);\n        ImGui::SameLine(); HelpMarker(\"Disable inner padding between columns (double inner padding if BordersOuterV is on, single inner padding if BordersOuterV is off)\");\n        ImGui::CheckboxFlags(\"ImGuiTableFlags_BordersOuterV\", &flags1, ImGuiTableFlags_BordersOuterV);\n        ImGui::CheckboxFlags(\"ImGuiTableFlags_BordersInnerV\", &flags1, ImGuiTableFlags_BordersInnerV);\n        static bool show_headers = false;\n        ImGui::Checkbox(\"show_headers\", &show_headers);\n        PopStyleCompact();\n\n        if (ImGui::BeginTable(\"table_padding\", 3, flags1))\n        {\n            if (show_headers)\n            {\n                ImGui::TableSetupColumn(\"One\");\n                ImGui::TableSetupColumn(\"Two\");\n                ImGui::TableSetupColumn(\"Three\");\n                ImGui::TableHeadersRow();\n            }\n\n            for (int row = 0; row < 5; row++)\n            {\n                ImGui::TableNextRow();\n                for (int column = 0; column < 3; column++)\n                {\n                    ImGui::TableSetColumnIndex(column);\n                    if (row == 0)\n                    {\n                        ImGui::Text(\"Avail %.2f\", ImGui::GetContentRegionAvail().x);\n                    }\n                    else\n                    {\n                        char buf[32];\n                        sprintf(buf, \"Hello %d,%d\", column, row);\n                        ImGui::Button(buf, ImVec2(-FLT_MIN, 0.0f));\n                    }\n                    //if (ImGui::TableGetColumnFlags() & ImGuiTableColumnFlags_IsHovered)\n                    //    ImGui::TableSetBgColor(ImGuiTableBgTarget_CellBg, IM_COL32(0, 100, 0, 255));\n                }\n            }\n            ImGui::EndTable();\n        }\n\n        // Second example: set style.CellPadding to (0.0) or a custom value.\n        // FIXME-TABLE: Vertical border effectively not displayed the same way as horizontal one...\n        HelpMarker(\"Setting style.CellPadding to (0,0) or a custom value.\");\n        static ImGuiTableFlags flags2 = ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg;\n        static ImVec2 cell_padding(0.0f, 0.0f);\n        static bool show_widget_frame_bg = true;\n\n        PushStyleCompact();\n        ImGui::CheckboxFlags(\"ImGuiTableFlags_Borders\", &flags2, ImGuiTableFlags_Borders);\n        ImGui::CheckboxFlags(\"ImGuiTableFlags_BordersH\", &flags2, ImGuiTableFlags_BordersH);\n        ImGui::CheckboxFlags(\"ImGuiTableFlags_BordersV\", &flags2, ImGuiTableFlags_BordersV);\n        ImGui::CheckboxFlags(\"ImGuiTableFlags_BordersInner\", &flags2, ImGuiTableFlags_BordersInner);\n        ImGui::CheckboxFlags(\"ImGuiTableFlags_BordersOuter\", &flags2, ImGuiTableFlags_BordersOuter);\n        ImGui::CheckboxFlags(\"ImGuiTableFlags_RowBg\", &flags2, ImGuiTableFlags_RowBg);\n        ImGui::CheckboxFlags(\"ImGuiTableFlags_Resizable\", &flags2, ImGuiTableFlags_Resizable);\n        ImGui::Checkbox(\"show_widget_frame_bg\", &show_widget_frame_bg);\n        ImGui::SliderFloat2(\"CellPadding\", &cell_padding.x, 0.0f, 10.0f, \"%.0f\");\n        PopStyleCompact();\n\n        ImGui::PushStyleVar(ImGuiStyleVar_CellPadding, cell_padding);\n        if (ImGui::BeginTable(\"table_padding_2\", 3, flags2))\n        {\n            static char text_bufs[3 * 5][16]; // Mini text storage for 3x5 cells\n            static bool init = true;\n            if (!show_widget_frame_bg)\n                ImGui::PushStyleColor(ImGuiCol_FrameBg, 0);\n            for (int cell = 0; cell < 3 * 5; cell++)\n            {\n                ImGui::TableNextColumn();\n                if (init)\n                    strcpy(text_bufs[cell], \"edit me\");\n                ImGui::SetNextItemWidth(-FLT_MIN);\n                ImGui::PushID(cell);\n                ImGui::InputText(\"##cell\", text_bufs[cell], IM_ARRAYSIZE(text_bufs[cell]));\n                ImGui::PopID();\n            }\n            if (!show_widget_frame_bg)\n                ImGui::PopStyleColor();\n            init = false;\n            ImGui::EndTable();\n        }\n        ImGui::PopStyleVar();\n\n        ImGui::TreePop();\n    }\n\n    if (open_action != -1)\n        ImGui::SetNextItemOpen(open_action != 0);\n    IMGUI_DEMO_MARKER(\"Tables/Explicit widths\");\n    if (ImGui::TreeNode(\"Sizing policies\"))\n    {\n        static ImGuiTableFlags flags1 = ImGuiTableFlags_BordersV | ImGuiTableFlags_BordersOuterH | ImGuiTableFlags_RowBg | ImGuiTableFlags_ContextMenuInBody;\n        PushStyleCompact();\n        ImGui::CheckboxFlags(\"ImGuiTableFlags_Resizable\", &flags1, ImGuiTableFlags_Resizable);\n        ImGui::CheckboxFlags(\"ImGuiTableFlags_NoHostExtendX\", &flags1, ImGuiTableFlags_NoHostExtendX);\n        PopStyleCompact();\n\n        static ImGuiTableFlags sizing_policy_flags[4] = { ImGuiTableFlags_SizingFixedFit, ImGuiTableFlags_SizingFixedSame, ImGuiTableFlags_SizingStretchProp, ImGuiTableFlags_SizingStretchSame };\n        for (int table_n = 0; table_n < 4; table_n++)\n        {\n            ImGui::PushID(table_n);\n            ImGui::SetNextItemWidth(TEXT_BASE_WIDTH * 30);\n            EditTableSizingFlags(&sizing_policy_flags[table_n]);\n\n            // To make it easier to understand the different sizing policy,\n            // For each policy: we display one table where the columns have equal contents width,\n            // and one where the columns have different contents width.\n            if (ImGui::BeginTable(\"table1\", 3, sizing_policy_flags[table_n] | flags1))\n            {\n                for (int row = 0; row < 3; row++)\n                {\n                    ImGui::TableNextRow();\n                    ImGui::TableNextColumn(); ImGui::Text(\"Oh dear\");\n                    ImGui::TableNextColumn(); ImGui::Text(\"Oh dear\");\n                    ImGui::TableNextColumn(); ImGui::Text(\"Oh dear\");\n                }\n                ImGui::EndTable();\n            }\n            if (ImGui::BeginTable(\"table2\", 3, sizing_policy_flags[table_n] | flags1))\n            {\n                for (int row = 0; row < 3; row++)\n                {\n                    ImGui::TableNextRow();\n                    ImGui::TableNextColumn(); ImGui::Text(\"AAAA\");\n                    ImGui::TableNextColumn(); ImGui::Text(\"BBBBBBBB\");\n                    ImGui::TableNextColumn(); ImGui::Text(\"CCCCCCCCCCCC\");\n                }\n                ImGui::EndTable();\n            }\n            ImGui::PopID();\n        }\n\n        ImGui::Spacing();\n        ImGui::TextUnformatted(\"Advanced\");\n        ImGui::SameLine();\n        HelpMarker(\n            \"This section allows you to interact and see the effect of various sizing policies \"\n            \"depending on whether Scroll is enabled and the contents of your columns.\");\n\n        enum ContentsType { CT_ShowWidth, CT_ShortText, CT_LongText, CT_Button, CT_FillButton, CT_InputText };\n        static ImGuiTableFlags flags = ImGuiTableFlags_ScrollY | ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg | ImGuiTableFlags_Resizable;\n        static int contents_type = CT_ShowWidth;\n        static int column_count = 3;\n\n        PushStyleCompact();\n        ImGui::PushID(\"Advanced\");\n        ImGui::PushItemWidth(TEXT_BASE_WIDTH * 30);\n        EditTableSizingFlags(&flags);\n        ImGui::Combo(\"Contents\", &contents_type, \"Show width\\0Short Text\\0Long Text\\0Button\\0Fill Button\\0InputText\\0\");\n        if (contents_type == CT_FillButton)\n        {\n            ImGui::SameLine();\n            HelpMarker(\n                \"Be mindful that using right-alignment (e.g. size.x = -FLT_MIN) creates a feedback loop \"\n                \"where contents width can feed into auto-column width can feed into contents width.\");\n        }\n        ImGui::DragInt(\"Columns\", &column_count, 0.1f, 1, 64, \"%d\", ImGuiSliderFlags_AlwaysClamp);\n        ImGui::CheckboxFlags(\"ImGuiTableFlags_Resizable\", &flags, ImGuiTableFlags_Resizable);\n        ImGui::CheckboxFlags(\"ImGuiTableFlags_PreciseWidths\", &flags, ImGuiTableFlags_PreciseWidths);\n        ImGui::SameLine(); HelpMarker(\"Disable distributing remainder width to stretched columns (width allocation on a 100-wide table with 3 columns: Without this flag: 33,33,34. With this flag: 33,33,33). With larger number of columns, resizing will appear to be less smooth.\");\n        ImGui::CheckboxFlags(\"ImGuiTableFlags_ScrollX\", &flags, ImGuiTableFlags_ScrollX);\n        ImGui::CheckboxFlags(\"ImGuiTableFlags_ScrollY\", &flags, ImGuiTableFlags_ScrollY);\n        ImGui::CheckboxFlags(\"ImGuiTableFlags_NoClip\", &flags, ImGuiTableFlags_NoClip);\n        ImGui::PopItemWidth();\n        ImGui::PopID();\n        PopStyleCompact();\n\n        if (ImGui::BeginTable(\"table2\", column_count, flags, ImVec2(0.0f, TEXT_BASE_HEIGHT * 7)))\n        {\n            for (int cell = 0; cell < 10 * column_count; cell++)\n            {\n                ImGui::TableNextColumn();\n                int column = ImGui::TableGetColumnIndex();\n                int row = ImGui::TableGetRowIndex();\n\n                ImGui::PushID(cell);\n                char label[32];\n                static char text_buf[32] = \"\";\n                sprintf(label, \"Hello %d,%d\", column, row);\n                switch (contents_type)\n                {\n                case CT_ShortText:  ImGui::TextUnformatted(label); break;\n                case CT_LongText:   ImGui::Text(\"Some %s text %d,%d\\nOver two lines..\", column == 0 ? \"long\" : \"longeeer\", column, row); break;\n                case CT_ShowWidth:  ImGui::Text(\"W: %.1f\", ImGui::GetContentRegionAvail().x); break;\n                case CT_Button:     ImGui::Button(label); break;\n                case CT_FillButton: ImGui::Button(label, ImVec2(-FLT_MIN, 0.0f)); break;\n                case CT_InputText:  ImGui::SetNextItemWidth(-FLT_MIN); ImGui::InputText(\"##\", text_buf, IM_ARRAYSIZE(text_buf)); break;\n                }\n                ImGui::PopID();\n            }\n            ImGui::EndTable();\n        }\n        ImGui::TreePop();\n    }\n\n    if (open_action != -1)\n        ImGui::SetNextItemOpen(open_action != 0);\n    IMGUI_DEMO_MARKER(\"Tables/Vertical scrolling, with clipping\");\n    if (ImGui::TreeNode(\"Vertical scrolling, with clipping\"))\n    {\n        HelpMarker(\n            \"Here we activate ScrollY, which will create a child window container to allow hosting scrollable contents.\\n\\n\"\n            \"We also demonstrate using ImGuiListClipper to virtualize the submission of many items.\");\n        static ImGuiTableFlags flags = ImGuiTableFlags_ScrollY | ImGuiTableFlags_RowBg | ImGuiTableFlags_BordersOuter | ImGuiTableFlags_BordersV | ImGuiTableFlags_Resizable | ImGuiTableFlags_Reorderable | ImGuiTableFlags_Hideable;\n\n        PushStyleCompact();\n        ImGui::CheckboxFlags(\"ImGuiTableFlags_ScrollY\", &flags, ImGuiTableFlags_ScrollY);\n        PopStyleCompact();\n\n        // When using ScrollX or ScrollY we need to specify a size for our table container!\n        // Otherwise by default the table will fit all available space, like a BeginChild() call.\n        ImVec2 outer_size = ImVec2(0.0f, TEXT_BASE_HEIGHT * 8);\n        if (ImGui::BeginTable(\"table_scrolly\", 3, flags, outer_size))\n        {\n            ImGui::TableSetupScrollFreeze(0, 1); // Make top row always visible\n            ImGui::TableSetupColumn(\"One\", ImGuiTableColumnFlags_None);\n            ImGui::TableSetupColumn(\"Two\", ImGuiTableColumnFlags_None);\n            ImGui::TableSetupColumn(\"Three\", ImGuiTableColumnFlags_None);\n            ImGui::TableHeadersRow();\n\n            // Demonstrate using clipper for large vertical lists\n            ImGuiListClipper clipper;\n            clipper.Begin(1000);\n            while (clipper.Step())\n            {\n                for (int row = clipper.DisplayStart; row < clipper.DisplayEnd; row++)\n                {\n                    ImGui::TableNextRow();\n                    for (int column = 0; column < 3; column++)\n                    {\n                        ImGui::TableSetColumnIndex(column);\n                        ImGui::Text(\"Hello %d,%d\", column, row);\n                    }\n                }\n            }\n            ImGui::EndTable();\n        }\n        ImGui::TreePop();\n    }\n\n    if (open_action != -1)\n        ImGui::SetNextItemOpen(open_action != 0);\n    IMGUI_DEMO_MARKER(\"Tables/Horizontal scrolling\");\n    if (ImGui::TreeNode(\"Horizontal scrolling\"))\n    {\n        HelpMarker(\n            \"When ScrollX is enabled, the default sizing policy becomes ImGuiTableFlags_SizingFixedFit, \"\n            \"as automatically stretching columns doesn't make much sense with horizontal scrolling.\\n\\n\"\n            \"Also note that as of the current version, you will almost always want to enable ScrollY along with ScrollX, \"\n            \"because the container window won't automatically extend vertically to fix contents \"\n            \"(this may be improved in future versions).\");\n        static ImGuiTableFlags flags = ImGuiTableFlags_ScrollX | ImGuiTableFlags_ScrollY | ImGuiTableFlags_RowBg | ImGuiTableFlags_BordersOuter | ImGuiTableFlags_BordersV | ImGuiTableFlags_Resizable | ImGuiTableFlags_Reorderable | ImGuiTableFlags_Hideable;\n        static int freeze_cols = 1;\n        static int freeze_rows = 1;\n\n        PushStyleCompact();\n        ImGui::CheckboxFlags(\"ImGuiTableFlags_Resizable\", &flags, ImGuiTableFlags_Resizable);\n        ImGui::CheckboxFlags(\"ImGuiTableFlags_ScrollX\", &flags, ImGuiTableFlags_ScrollX);\n        ImGui::CheckboxFlags(\"ImGuiTableFlags_ScrollY\", &flags, ImGuiTableFlags_ScrollY);\n        ImGui::SetNextItemWidth(ImGui::GetFrameHeight());\n        ImGui::DragInt(\"freeze_cols\", &freeze_cols, 0.2f, 0, 9, NULL, ImGuiSliderFlags_NoInput);\n        ImGui::SetNextItemWidth(ImGui::GetFrameHeight());\n        ImGui::DragInt(\"freeze_rows\", &freeze_rows, 0.2f, 0, 9, NULL, ImGuiSliderFlags_NoInput);\n        PopStyleCompact();\n\n        // When using ScrollX or ScrollY we need to specify a size for our table container!\n        // Otherwise by default the table will fit all available space, like a BeginChild() call.\n        ImVec2 outer_size = ImVec2(0.0f, TEXT_BASE_HEIGHT * 8);\n        if (ImGui::BeginTable(\"table_scrollx\", 7, flags, outer_size))\n        {\n            ImGui::TableSetupScrollFreeze(freeze_cols, freeze_rows);\n            ImGui::TableSetupColumn(\"Line #\", ImGuiTableColumnFlags_NoHide); // Make the first column not hideable to match our use of TableSetupScrollFreeze()\n            ImGui::TableSetupColumn(\"One\");\n            ImGui::TableSetupColumn(\"Two\");\n            ImGui::TableSetupColumn(\"Three\");\n            ImGui::TableSetupColumn(\"Four\");\n            ImGui::TableSetupColumn(\"Five\");\n            ImGui::TableSetupColumn(\"Six\");\n            ImGui::TableHeadersRow();\n            for (int row = 0; row < 20; row++)\n            {\n                ImGui::TableNextRow();\n                for (int column = 0; column < 7; column++)\n                {\n                    // Both TableNextColumn() and TableSetColumnIndex() return true when a column is visible or performing width measurement.\n                    // Because here we know that:\n                    // - A) all our columns are contributing the same to row height\n                    // - B) column 0 is always visible,\n                    // We only always submit this one column and can skip others.\n                    // More advanced per-column clipping behaviors may benefit from polling the status flags via TableGetColumnFlags().\n                    if (!ImGui::TableSetColumnIndex(column) && column > 0)\n                        continue;\n                    if (column == 0)\n                        ImGui::Text(\"Line %d\", row);\n                    else\n                        ImGui::Text(\"Hello world %d,%d\", column, row);\n                }\n            }\n            ImGui::EndTable();\n        }\n\n        ImGui::Spacing();\n        ImGui::TextUnformatted(\"Stretch + ScrollX\");\n        ImGui::SameLine();\n        HelpMarker(\n            \"Showcase using Stretch columns + ScrollX together: \"\n            \"this is rather unusual and only makes sense when specifying an 'inner_width' for the table!\\n\"\n            \"Without an explicit value, inner_width is == outer_size.x and therefore using Stretch columns \"\n            \"along with ScrollX doesn't make sense.\");\n        static ImGuiTableFlags flags2 = ImGuiTableFlags_SizingStretchSame | ImGuiTableFlags_ScrollX | ImGuiTableFlags_ScrollY | ImGuiTableFlags_BordersOuter | ImGuiTableFlags_RowBg | ImGuiTableFlags_ContextMenuInBody;\n        static float inner_width = 1000.0f;\n        PushStyleCompact();\n        ImGui::PushID(\"flags3\");\n        ImGui::PushItemWidth(TEXT_BASE_WIDTH * 30);\n        ImGui::CheckboxFlags(\"ImGuiTableFlags_ScrollX\", &flags2, ImGuiTableFlags_ScrollX);\n        ImGui::DragFloat(\"inner_width\", &inner_width, 1.0f, 0.0f, FLT_MAX, \"%.1f\");\n        ImGui::PopItemWidth();\n        ImGui::PopID();\n        PopStyleCompact();\n        if (ImGui::BeginTable(\"table2\", 7, flags2, outer_size, inner_width))\n        {\n            for (int cell = 0; cell < 20 * 7; cell++)\n            {\n                ImGui::TableNextColumn();\n                ImGui::Text(\"Hello world %d,%d\", ImGui::TableGetColumnIndex(), ImGui::TableGetRowIndex());\n            }\n            ImGui::EndTable();\n        }\n        ImGui::TreePop();\n    }\n\n    if (open_action != -1)\n        ImGui::SetNextItemOpen(open_action != 0);\n    IMGUI_DEMO_MARKER(\"Tables/Columns flags\");\n    if (ImGui::TreeNode(\"Columns flags\"))\n    {\n        // Create a first table just to show all the options/flags we want to make visible in our example!\n        const int column_count = 3;\n        const char* column_names[column_count] = { \"One\", \"Two\", \"Three\" };\n        static ImGuiTableColumnFlags column_flags[column_count] = { ImGuiTableColumnFlags_DefaultSort, ImGuiTableColumnFlags_None, ImGuiTableColumnFlags_DefaultHide };\n        static ImGuiTableColumnFlags column_flags_out[column_count] = { 0, 0, 0 }; // Output from TableGetColumnFlags()\n\n        if (ImGui::BeginTable(\"table_columns_flags_checkboxes\", column_count, ImGuiTableFlags_None))\n        {\n            PushStyleCompact();\n            for (int column = 0; column < column_count; column++)\n            {\n                ImGui::TableNextColumn();\n                ImGui::PushID(column);\n                ImGui::AlignTextToFramePadding(); // FIXME-TABLE: Workaround for wrong text baseline propagation across columns\n                ImGui::Text(\"'%s'\", column_names[column]);\n                ImGui::Spacing();\n                ImGui::Text(\"Input flags:\");\n                EditTableColumnsFlags(&column_flags[column]);\n                ImGui::Spacing();\n                ImGui::Text(\"Output flags:\");\n                ImGui::BeginDisabled();\n                ShowTableColumnsStatusFlags(column_flags_out[column]);\n                ImGui::EndDisabled();\n                ImGui::PopID();\n            }\n            PopStyleCompact();\n            ImGui::EndTable();\n        }\n\n        // Create the real table we care about for the example!\n        // We use a scrolling table to be able to showcase the difference between the _IsEnabled and _IsVisible flags above,\n        // otherwise in a non-scrolling table columns are always visible (unless using ImGuiTableFlags_NoKeepColumnsVisible\n        // + resizing the parent window down).\n        const ImGuiTableFlags flags\n            = ImGuiTableFlags_SizingFixedFit | ImGuiTableFlags_ScrollX | ImGuiTableFlags_ScrollY\n            | ImGuiTableFlags_RowBg | ImGuiTableFlags_BordersOuter | ImGuiTableFlags_BordersV\n            | ImGuiTableFlags_Resizable | ImGuiTableFlags_Reorderable | ImGuiTableFlags_Hideable | ImGuiTableFlags_Sortable;\n        ImVec2 outer_size = ImVec2(0.0f, TEXT_BASE_HEIGHT * 9);\n        if (ImGui::BeginTable(\"table_columns_flags\", column_count, flags, outer_size))\n        {\n            bool has_angled_header = false;\n            for (int column = 0; column < column_count; column++)\n            {\n                has_angled_header |= (column_flags[column] & ImGuiTableColumnFlags_AngledHeader) != 0;\n                ImGui::TableSetupColumn(column_names[column], column_flags[column]);\n            }\n            if (has_angled_header)\n                ImGui::TableAngledHeadersRow();\n            ImGui::TableHeadersRow();\n            for (int column = 0; column < column_count; column++)\n                column_flags_out[column] = ImGui::TableGetColumnFlags(column);\n            float indent_step = (float)((int)TEXT_BASE_WIDTH / 2);\n            for (int row = 0; row < 8; row++)\n            {\n                // Add some indentation to demonstrate usage of per-column IndentEnable/IndentDisable flags.\n                ImGui::Indent(indent_step);\n                ImGui::TableNextRow();\n                for (int column = 0; column < column_count; column++)\n                {\n                    ImGui::TableSetColumnIndex(column);\n                    ImGui::Text(\"%s %s\", (column == 0) ? \"Indented\" : \"Hello\", ImGui::TableGetColumnName(column));\n                }\n            }\n            ImGui::Unindent(indent_step * 8.0f);\n\n            ImGui::EndTable();\n        }\n        ImGui::TreePop();\n    }\n\n    if (open_action != -1)\n        ImGui::SetNextItemOpen(open_action != 0);\n    IMGUI_DEMO_MARKER(\"Tables/Columns widths\");\n    if (ImGui::TreeNode(\"Columns widths\"))\n    {\n        HelpMarker(\"Using TableSetupColumn() to setup default width.\");\n\n        static ImGuiTableFlags flags1 = ImGuiTableFlags_Borders | ImGuiTableFlags_NoBordersInBodyUntilResize;\n        PushStyleCompact();\n        ImGui::CheckboxFlags(\"ImGuiTableFlags_Resizable\", &flags1, ImGuiTableFlags_Resizable);\n        ImGui::CheckboxFlags(\"ImGuiTableFlags_NoBordersInBodyUntilResize\", &flags1, ImGuiTableFlags_NoBordersInBodyUntilResize);\n        PopStyleCompact();\n        if (ImGui::BeginTable(\"table1\", 3, flags1))\n        {\n            // We could also set ImGuiTableFlags_SizingFixedFit on the table and all columns will default to ImGuiTableColumnFlags_WidthFixed.\n            ImGui::TableSetupColumn(\"one\", ImGuiTableColumnFlags_WidthFixed, 100.0f); // Default to 100.0f\n            ImGui::TableSetupColumn(\"two\", ImGuiTableColumnFlags_WidthFixed, 200.0f); // Default to 200.0f\n            ImGui::TableSetupColumn(\"three\", ImGuiTableColumnFlags_WidthFixed);       // Default to auto\n            ImGui::TableHeadersRow();\n            for (int row = 0; row < 4; row++)\n            {\n                ImGui::TableNextRow();\n                for (int column = 0; column < 3; column++)\n                {\n                    ImGui::TableSetColumnIndex(column);\n                    if (row == 0)\n                        ImGui::Text(\"(w: %5.1f)\", ImGui::GetContentRegionAvail().x);\n                    else\n                        ImGui::Text(\"Hello %d,%d\", column, row);\n                }\n            }\n            ImGui::EndTable();\n        }\n\n        HelpMarker(\n            \"Using TableSetupColumn() to setup explicit width.\\n\\nUnless _NoKeepColumnsVisible is set, \"\n            \"fixed columns with set width may still be shrunk down if there's not enough space in the host.\");\n\n        static ImGuiTableFlags flags2 = ImGuiTableFlags_None;\n        PushStyleCompact();\n        ImGui::CheckboxFlags(\"ImGuiTableFlags_NoKeepColumnsVisible\", &flags2, ImGuiTableFlags_NoKeepColumnsVisible);\n        ImGui::CheckboxFlags(\"ImGuiTableFlags_BordersInnerV\", &flags2, ImGuiTableFlags_BordersInnerV);\n        ImGui::CheckboxFlags(\"ImGuiTableFlags_BordersOuterV\", &flags2, ImGuiTableFlags_BordersOuterV);\n        PopStyleCompact();\n        if (ImGui::BeginTable(\"table2\", 4, flags2))\n        {\n            // We could also set ImGuiTableFlags_SizingFixedFit on the table and then all columns\n            // will default to ImGuiTableColumnFlags_WidthFixed.\n            ImGui::TableSetupColumn(\"\", ImGuiTableColumnFlags_WidthFixed, 100.0f);\n            ImGui::TableSetupColumn(\"\", ImGuiTableColumnFlags_WidthFixed, TEXT_BASE_WIDTH * 15.0f);\n            ImGui::TableSetupColumn(\"\", ImGuiTableColumnFlags_WidthFixed, TEXT_BASE_WIDTH * 30.0f);\n            ImGui::TableSetupColumn(\"\", ImGuiTableColumnFlags_WidthFixed, TEXT_BASE_WIDTH * 15.0f);\n            for (int row = 0; row < 5; row++)\n            {\n                ImGui::TableNextRow();\n                for (int column = 0; column < 4; column++)\n                {\n                    ImGui::TableSetColumnIndex(column);\n                    if (row == 0)\n                        ImGui::Text(\"(w: %5.1f)\", ImGui::GetContentRegionAvail().x);\n                    else\n                        ImGui::Text(\"Hello %d,%d\", column, row);\n                }\n            }\n            ImGui::EndTable();\n        }\n        ImGui::TreePop();\n    }\n\n    if (open_action != -1)\n        ImGui::SetNextItemOpen(open_action != 0);\n    IMGUI_DEMO_MARKER(\"Tables/Nested tables\");\n    if (ImGui::TreeNode(\"Nested tables\"))\n    {\n        HelpMarker(\"This demonstrates embedding a table into another table cell.\");\n\n        if (ImGui::BeginTable(\"table_nested1\", 2, ImGuiTableFlags_Borders | ImGuiTableFlags_Resizable | ImGuiTableFlags_Reorderable | ImGuiTableFlags_Hideable))\n        {\n            ImGui::TableSetupColumn(\"A0\");\n            ImGui::TableSetupColumn(\"A1\");\n            ImGui::TableHeadersRow();\n\n            ImGui::TableNextColumn();\n            ImGui::Text(\"A0 Row 0\");\n            {\n                float rows_height = TEXT_BASE_HEIGHT * 2;\n                if (ImGui::BeginTable(\"table_nested2\", 2, ImGuiTableFlags_Borders | ImGuiTableFlags_Resizable | ImGuiTableFlags_Reorderable | ImGuiTableFlags_Hideable))\n                {\n                    ImGui::TableSetupColumn(\"B0\");\n                    ImGui::TableSetupColumn(\"B1\");\n                    ImGui::TableHeadersRow();\n\n                    ImGui::TableNextRow(ImGuiTableRowFlags_None, rows_height);\n                    ImGui::TableNextColumn();\n                    ImGui::Text(\"B0 Row 0\");\n                    ImGui::TableNextColumn();\n                    ImGui::Text(\"B1 Row 0\");\n                    ImGui::TableNextRow(ImGuiTableRowFlags_None, rows_height);\n                    ImGui::TableNextColumn();\n                    ImGui::Text(\"B0 Row 1\");\n                    ImGui::TableNextColumn();\n                    ImGui::Text(\"B1 Row 1\");\n\n                    ImGui::EndTable();\n                }\n            }\n            ImGui::TableNextColumn(); ImGui::Text(\"A1 Row 0\");\n            ImGui::TableNextColumn(); ImGui::Text(\"A0 Row 1\");\n            ImGui::TableNextColumn(); ImGui::Text(\"A1 Row 1\");\n            ImGui::EndTable();\n        }\n        ImGui::TreePop();\n    }\n\n    if (open_action != -1)\n        ImGui::SetNextItemOpen(open_action != 0);\n    IMGUI_DEMO_MARKER(\"Tables/Row height\");\n    if (ImGui::TreeNode(\"Row height\"))\n    {\n        HelpMarker(\n            \"You can pass a 'min_row_height' to TableNextRow().\\n\\nRows are padded with 'style.CellPadding.y' on top and bottom, \"\n            \"so effectively the minimum row height will always be >= 'style.CellPadding.y * 2.0f'.\\n\\n\"\n            \"We cannot honor a _maximum_ row height as that would require a unique clipping rectangle per row.\");\n        if (ImGui::BeginTable(\"table_row_height\", 1, ImGuiTableFlags_Borders))\n        {\n            for (int row = 0; row < 8; row++)\n            {\n                float min_row_height = (float)(int)(TEXT_BASE_HEIGHT * 0.30f * row);\n                ImGui::TableNextRow(ImGuiTableRowFlags_None, min_row_height);\n                ImGui::TableNextColumn();\n                ImGui::Text(\"min_row_height = %.2f\", min_row_height);\n            }\n            ImGui::EndTable();\n        }\n\n        HelpMarker(\n            \"Showcase using SameLine(0,0) to share Current Line Height between cells.\\n\\n\"\n            \"Please note that Tables Row Height is not the same thing as Current Line Height, \"\n            \"as a table cell may contains multiple lines.\");\n        if (ImGui::BeginTable(\"table_share_lineheight\", 2, ImGuiTableFlags_Borders))\n        {\n            ImGui::TableNextRow();\n            ImGui::TableNextColumn();\n            ImGui::ColorButton(\"##1\", ImVec4(0.13f, 0.26f, 0.40f, 1.0f), ImGuiColorEditFlags_None, ImVec2(40, 40));\n            ImGui::TableNextColumn();\n            ImGui::Text(\"Line 1\");\n            ImGui::Text(\"Line 2\");\n\n            ImGui::TableNextRow();\n            ImGui::TableNextColumn();\n            ImGui::ColorButton(\"##2\", ImVec4(0.13f, 0.26f, 0.40f, 1.0f), ImGuiColorEditFlags_None, ImVec2(40, 40));\n            ImGui::TableNextColumn();\n            ImGui::SameLine(0.0f, 0.0f); // Reuse line height from previous column\n            ImGui::Text(\"Line 1, with SameLine(0,0)\");\n            ImGui::Text(\"Line 2\");\n\n            ImGui::EndTable();\n        }\n\n        HelpMarker(\"Showcase altering CellPadding.y between rows. Note that CellPadding.x is locked for the entire table.\");\n        if (ImGui::BeginTable(\"table_changing_cellpadding_y\", 1, ImGuiTableFlags_Borders))\n        {\n            ImGuiStyle& style = ImGui::GetStyle();\n            for (int row = 0; row < 8; row++)\n            {\n                if ((row % 3) == 2)\n                    ImGui::PushStyleVarY(ImGuiStyleVar_CellPadding, 20.0f);\n                ImGui::TableNextRow(ImGuiTableRowFlags_None);\n                ImGui::TableNextColumn();\n                ImGui::Text(\"CellPadding.y = %.2f\", style.CellPadding.y);\n                if ((row % 3) == 2)\n                    ImGui::PopStyleVar();\n            }\n            ImGui::EndTable();\n        }\n\n        ImGui::TreePop();\n    }\n\n    if (open_action != -1)\n        ImGui::SetNextItemOpen(open_action != 0);\n    IMGUI_DEMO_MARKER(\"Tables/Outer size\");\n    if (ImGui::TreeNode(\"Outer size\"))\n    {\n        // Showcasing use of ImGuiTableFlags_NoHostExtendX and ImGuiTableFlags_NoHostExtendY\n        // Important to that note how the two flags have slightly different behaviors!\n        ImGui::Text(\"Using NoHostExtendX and NoHostExtendY:\");\n        PushStyleCompact();\n        static ImGuiTableFlags flags = ImGuiTableFlags_Borders | ImGuiTableFlags_Resizable | ImGuiTableFlags_ContextMenuInBody | ImGuiTableFlags_RowBg | ImGuiTableFlags_SizingFixedFit | ImGuiTableFlags_NoHostExtendX;\n        ImGui::CheckboxFlags(\"ImGuiTableFlags_NoHostExtendX\", &flags, ImGuiTableFlags_NoHostExtendX);\n        ImGui::SameLine(); HelpMarker(\"Make outer width auto-fit to columns, overriding outer_size.x value.\\n\\nOnly available when ScrollX/ScrollY are disabled and Stretch columns are not used.\");\n        ImGui::CheckboxFlags(\"ImGuiTableFlags_NoHostExtendY\", &flags, ImGuiTableFlags_NoHostExtendY);\n        ImGui::SameLine(); HelpMarker(\"Make outer height stop exactly at outer_size.y (prevent auto-extending table past the limit).\\n\\nOnly available when ScrollX/ScrollY are disabled. Data below the limit will be clipped and not visible.\");\n        PopStyleCompact();\n\n        ImVec2 outer_size = ImVec2(0.0f, TEXT_BASE_HEIGHT * 5.5f);\n        if (ImGui::BeginTable(\"table1\", 3, flags, outer_size))\n        {\n            for (int row = 0; row < 10; row++)\n            {\n                ImGui::TableNextRow();\n                for (int column = 0; column < 3; column++)\n                {\n                    ImGui::TableNextColumn();\n                    ImGui::Text(\"Cell %d,%d\", column, row);\n                }\n            }\n            ImGui::EndTable();\n        }\n        ImGui::SameLine();\n        ImGui::Text(\"Hello!\");\n\n        ImGui::Spacing();\n\n        ImGui::Text(\"Using explicit size:\");\n        if (ImGui::BeginTable(\"table2\", 3, ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg, ImVec2(TEXT_BASE_WIDTH * 30, 0.0f)))\n        {\n            for (int row = 0; row < 5; row++)\n            {\n                ImGui::TableNextRow();\n                for (int column = 0; column < 3; column++)\n                {\n                    ImGui::TableNextColumn();\n                    ImGui::Text(\"Cell %d,%d\", column, row);\n                }\n            }\n            ImGui::EndTable();\n        }\n        ImGui::SameLine();\n        if (ImGui::BeginTable(\"table3\", 3, ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg, ImVec2(TEXT_BASE_WIDTH * 30, 0.0f)))\n        {\n            for (int row = 0; row < 3; row++)\n            {\n                ImGui::TableNextRow(0, TEXT_BASE_HEIGHT * 1.5f);\n                for (int column = 0; column < 3; column++)\n                {\n                    ImGui::TableNextColumn();\n                    ImGui::Text(\"Cell %d,%d\", column, row);\n                }\n            }\n            ImGui::EndTable();\n        }\n\n        ImGui::TreePop();\n    }\n\n    if (open_action != -1)\n        ImGui::SetNextItemOpen(open_action != 0);\n    IMGUI_DEMO_MARKER(\"Tables/Background color\");\n    if (ImGui::TreeNode(\"Background color\"))\n    {\n        static ImGuiTableFlags flags = ImGuiTableFlags_RowBg;\n        static int row_bg_type = 1;\n        static int row_bg_target = 1;\n        static int cell_bg_type = 1;\n\n        PushStyleCompact();\n        ImGui::CheckboxFlags(\"ImGuiTableFlags_Borders\", &flags, ImGuiTableFlags_Borders);\n        ImGui::CheckboxFlags(\"ImGuiTableFlags_RowBg\", &flags, ImGuiTableFlags_RowBg);\n        ImGui::SameLine(); HelpMarker(\"ImGuiTableFlags_RowBg automatically sets RowBg0 to alternative colors pulled from the Style.\");\n        ImGui::Combo(\"row bg type\", (int*)&row_bg_type, \"None\\0Red\\0Gradient\\0\");\n        ImGui::Combo(\"row bg target\", (int*)&row_bg_target, \"RowBg0\\0RowBg1\\0\"); ImGui::SameLine(); HelpMarker(\"Target RowBg0 to override the alternating odd/even colors,\\nTarget RowBg1 to blend with them.\");\n        ImGui::Combo(\"cell bg type\", (int*)&cell_bg_type, \"None\\0Blue\\0\"); ImGui::SameLine(); HelpMarker(\"We are colorizing cells to B1->C2 here.\");\n        IM_ASSERT(row_bg_type >= 0 && row_bg_type <= 2);\n        IM_ASSERT(row_bg_target >= 0 && row_bg_target <= 1);\n        IM_ASSERT(cell_bg_type >= 0 && cell_bg_type <= 1);\n        PopStyleCompact();\n\n        if (ImGui::BeginTable(\"table1\", 5, flags))\n        {\n            for (int row = 0; row < 6; row++)\n            {\n                ImGui::TableNextRow();\n\n                // Demonstrate setting a row background color with 'ImGui::TableSetBgColor(ImGuiTableBgTarget_RowBgX, ...)'\n                // We use a transparent color so we can see the one behind in case our target is RowBg1 and RowBg0 was already targeted by the ImGuiTableFlags_RowBg flag.\n                if (row_bg_type != 0)\n                {\n                    ImU32 row_bg_color = ImGui::GetColorU32(row_bg_type == 1 ? ImVec4(0.7f, 0.3f, 0.3f, 0.65f) : ImVec4(0.2f + row * 0.1f, 0.2f, 0.2f, 0.65f)); // Flat or Gradient?\n                    ImGui::TableSetBgColor(ImGuiTableBgTarget_RowBg0 + row_bg_target, row_bg_color);\n                }\n\n                // Fill cells\n                for (int column = 0; column < 5; column++)\n                {\n                    ImGui::TableSetColumnIndex(column);\n                    ImGui::Text(\"%c%c\", 'A' + row, '0' + column);\n\n                    // Change background of Cells B1->C2\n                    // Demonstrate setting a cell background color with 'ImGui::TableSetBgColor(ImGuiTableBgTarget_CellBg, ...)'\n                    // (the CellBg color will be blended over the RowBg and ColumnBg colors)\n                    // We can also pass a column number as a third parameter to TableSetBgColor() and do this outside the column loop.\n                    if (row >= 1 && row <= 2 && column >= 1 && column <= 2 && cell_bg_type == 1)\n                    {\n                        ImU32 cell_bg_color = ImGui::GetColorU32(ImVec4(0.3f, 0.3f, 0.7f, 0.65f));\n                        ImGui::TableSetBgColor(ImGuiTableBgTarget_CellBg, cell_bg_color);\n                    }\n                }\n            }\n            ImGui::EndTable();\n        }\n        ImGui::TreePop();\n    }\n\n    if (open_action != -1)\n        ImGui::SetNextItemOpen(open_action != 0);\n    IMGUI_DEMO_MARKER(\"Tables/Tree view\");\n    if (ImGui::TreeNode(\"Tree view\"))\n    {\n        static ImGuiTableFlags table_flags = ImGuiTableFlags_BordersV | ImGuiTableFlags_BordersOuterH | ImGuiTableFlags_Resizable | ImGuiTableFlags_RowBg | ImGuiTableFlags_NoBordersInBody;\n\n        static ImGuiTreeNodeFlags tree_node_flags_base = ImGuiTreeNodeFlags_SpanAllColumns;\n        ImGui::CheckboxFlags(\"ImGuiTreeNodeFlags_SpanFullWidth\",  &tree_node_flags_base, ImGuiTreeNodeFlags_SpanFullWidth);\n        ImGui::CheckboxFlags(\"ImGuiTreeNodeFlags_SpanLabelWidth\",  &tree_node_flags_base, ImGuiTreeNodeFlags_SpanLabelWidth);\n        ImGui::CheckboxFlags(\"ImGuiTreeNodeFlags_SpanAllColumns\", &tree_node_flags_base, ImGuiTreeNodeFlags_SpanAllColumns);\n        ImGui::CheckboxFlags(\"ImGuiTreeNodeFlags_LabelSpanAllColumns\", &tree_node_flags_base, ImGuiTreeNodeFlags_LabelSpanAllColumns);\n        ImGui::SameLine(); HelpMarker(\"Useful if you know that you aren't displaying contents in other columns\");\n\n        HelpMarker(\"See \\\"Columns flags\\\" section to configure how indentation is applied to individual columns.\");\n        if (ImGui::BeginTable(\"3ways\", 3, table_flags))\n        {\n            // The first column will use the default _WidthStretch when ScrollX is Off and _WidthFixed when ScrollX is On\n            ImGui::TableSetupColumn(\"Name\", ImGuiTableColumnFlags_NoHide);\n            ImGui::TableSetupColumn(\"Size\", ImGuiTableColumnFlags_WidthFixed, TEXT_BASE_WIDTH * 12.0f);\n            ImGui::TableSetupColumn(\"Type\", ImGuiTableColumnFlags_WidthFixed, TEXT_BASE_WIDTH * 18.0f);\n            ImGui::TableHeadersRow();\n\n            // Simple storage to output a dummy file-system.\n            struct MyTreeNode\n            {\n                const char*     Name;\n                const char*     Type;\n                int             Size;\n                int             ChildIdx;\n                int             ChildCount;\n                static void DisplayNode(const MyTreeNode* node, const MyTreeNode* all_nodes)\n                {\n                    ImGui::TableNextRow();\n                    ImGui::TableNextColumn();\n                    const bool is_folder = (node->ChildCount > 0);\n\n                    ImGuiTreeNodeFlags node_flags = tree_node_flags_base;\n                    if (node != &all_nodes[0])\n                        node_flags &= ~ImGuiTreeNodeFlags_LabelSpanAllColumns; // Only demonstrate this on the root node.\n\n                    if (is_folder)\n                    {\n                        bool open = ImGui::TreeNodeEx(node->Name, node_flags);\n                        if ((node_flags & ImGuiTreeNodeFlags_LabelSpanAllColumns) == 0)\n                        {\n                            ImGui::TableNextColumn();\n                            ImGui::TextDisabled(\"--\");\n                            ImGui::TableNextColumn();\n                            ImGui::TextUnformatted(node->Type);\n                        }\n                        if (open)\n                        {\n                            for (int child_n = 0; child_n < node->ChildCount; child_n++)\n                                DisplayNode(&all_nodes[node->ChildIdx + child_n], all_nodes);\n                            ImGui::TreePop();\n                        }\n                    }\n                    else\n                    {\n                        ImGui::TreeNodeEx(node->Name, node_flags | ImGuiTreeNodeFlags_Leaf | ImGuiTreeNodeFlags_Bullet | ImGuiTreeNodeFlags_NoTreePushOnOpen);\n                        ImGui::TableNextColumn();\n                        ImGui::Text(\"%d\", node->Size);\n                        ImGui::TableNextColumn();\n                        ImGui::TextUnformatted(node->Type);\n                    }\n                }\n            };\n            static const MyTreeNode nodes[] =\n            {\n                { \"Root with Long Name\",          \"Folder\",       -1,       1, 3    }, // 0\n                { \"Music\",                        \"Folder\",       -1,       4, 2    }, // 1\n                { \"Textures\",                     \"Folder\",       -1,       6, 3    }, // 2\n                { \"desktop.ini\",                  \"System file\",  1024,    -1,-1    }, // 3\n                { \"File1_a.wav\",                  \"Audio file\",   123000,  -1,-1    }, // 4\n                { \"File1_b.wav\",                  \"Audio file\",   456000,  -1,-1    }, // 5\n                { \"Image001.png\",                 \"Image file\",   203128,  -1,-1    }, // 6\n                { \"Copy of Image001.png\",         \"Image file\",   203256,  -1,-1    }, // 7\n                { \"Copy of Image001 (Final2).png\",\"Image file\",   203512,  -1,-1    }, // 8\n            };\n\n            MyTreeNode::DisplayNode(&nodes[0], nodes);\n\n            ImGui::EndTable();\n        }\n        ImGui::TreePop();\n    }\n\n    if (open_action != -1)\n        ImGui::SetNextItemOpen(open_action != 0);\n    IMGUI_DEMO_MARKER(\"Tables/Item width\");\n    if (ImGui::TreeNode(\"Item width\"))\n    {\n        HelpMarker(\n            \"Showcase using PushItemWidth() and how it is preserved on a per-column basis.\\n\\n\"\n            \"Note that on auto-resizing non-resizable fixed columns, querying the content width for \"\n            \"e.g. right-alignment doesn't make sense.\");\n        if (ImGui::BeginTable(\"table_item_width\", 3, ImGuiTableFlags_Borders))\n        {\n            ImGui::TableSetupColumn(\"small\");\n            ImGui::TableSetupColumn(\"half\");\n            ImGui::TableSetupColumn(\"right-align\");\n            ImGui::TableHeadersRow();\n\n            for (int row = 0; row < 3; row++)\n            {\n                ImGui::TableNextRow();\n                if (row == 0)\n                {\n                    // Setup ItemWidth once (instead of setting up every time, which is also possible but less efficient)\n                    ImGui::TableSetColumnIndex(0);\n                    ImGui::PushItemWidth(TEXT_BASE_WIDTH * 3.0f); // Small\n                    ImGui::TableSetColumnIndex(1);\n                    ImGui::PushItemWidth(-ImGui::GetContentRegionAvail().x * 0.5f);\n                    ImGui::TableSetColumnIndex(2);\n                    ImGui::PushItemWidth(-FLT_MIN); // Right-aligned\n                }\n\n                // Draw our contents\n                static float dummy_f = 0.0f;\n                ImGui::PushID(row);\n                ImGui::TableSetColumnIndex(0);\n                ImGui::SliderFloat(\"float0\", &dummy_f, 0.0f, 1.0f);\n                ImGui::TableSetColumnIndex(1);\n                ImGui::SliderFloat(\"float1\", &dummy_f, 0.0f, 1.0f);\n                ImGui::TableSetColumnIndex(2);\n                ImGui::SliderFloat(\"##float2\", &dummy_f, 0.0f, 1.0f); // No visible label since right-aligned\n                ImGui::PopID();\n            }\n            ImGui::EndTable();\n        }\n        ImGui::TreePop();\n    }\n\n    // Demonstrate using TableHeader() calls instead of TableHeadersRow()\n    if (open_action != -1)\n        ImGui::SetNextItemOpen(open_action != 0);\n    IMGUI_DEMO_MARKER(\"Tables/Custom headers\");\n    if (ImGui::TreeNode(\"Custom headers\"))\n    {\n        const int COLUMNS_COUNT = 3;\n        if (ImGui::BeginTable(\"table_custom_headers\", COLUMNS_COUNT, ImGuiTableFlags_Borders | ImGuiTableFlags_Reorderable | ImGuiTableFlags_Hideable))\n        {\n            ImGui::TableSetupColumn(\"Apricot\");\n            ImGui::TableSetupColumn(\"Banana\");\n            ImGui::TableSetupColumn(\"Cherry\");\n\n            // Dummy entire-column selection storage\n            // FIXME: It would be nice to actually demonstrate full-featured selection using those checkbox.\n            static bool column_selected[3] = {};\n\n            // Instead of calling TableHeadersRow() we'll submit custom headers ourselves.\n            // (A different approach is also possible:\n            //    - Specify ImGuiTableColumnFlags_NoHeaderLabel in some TableSetupColumn() call.\n            //    - Call TableHeadersRow() normally. This will submit TableHeader() with no name.\n            //    - Then call TableSetColumnIndex() to position yourself in the column and submit your stuff e.g. Checkbox().)\n            ImGui::TableNextRow(ImGuiTableRowFlags_Headers);\n            for (int column = 0; column < COLUMNS_COUNT; column++)\n            {\n                ImGui::TableSetColumnIndex(column);\n                const char* column_name = ImGui::TableGetColumnName(column); // Retrieve name passed to TableSetupColumn()\n                ImGui::PushID(column);\n                ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(0, 0));\n                ImGui::Checkbox(\"##checkall\", &column_selected[column]);\n                ImGui::PopStyleVar();\n                ImGui::SameLine(0.0f, ImGui::GetStyle().ItemInnerSpacing.x);\n                ImGui::TableHeader(column_name);\n                ImGui::PopID();\n            }\n\n            // Submit table contents\n            for (int row = 0; row < 5; row++)\n            {\n                ImGui::TableNextRow();\n                for (int column = 0; column < 3; column++)\n                {\n                    char buf[32];\n                    sprintf(buf, \"Cell %d,%d\", column, row);\n                    ImGui::TableSetColumnIndex(column);\n                    ImGui::Selectable(buf, column_selected[column]);\n                }\n            }\n            ImGui::EndTable();\n        }\n        ImGui::TreePop();\n    }\n\n    // Demonstrate using ImGuiTableColumnFlags_AngledHeader flag to create angled headers\n    if (open_action != -1)\n        ImGui::SetNextItemOpen(open_action != 0);\n    IMGUI_DEMO_MARKER(\"Tables/Angled headers\");\n    if (ImGui::TreeNode(\"Angled headers\"))\n    {\n        const char* column_names[] = { \"Track\", \"cabasa\", \"ride\", \"smash\", \"tom-hi\", \"tom-mid\", \"tom-low\", \"hihat-o\", \"hihat-c\", \"snare-s\", \"snare-c\", \"clap\", \"rim\", \"kick\" };\n        const int columns_count = IM_ARRAYSIZE(column_names);\n        const int rows_count = 12;\n\n        static ImGuiTableFlags table_flags = ImGuiTableFlags_SizingFixedFit | ImGuiTableFlags_ScrollX | ImGuiTableFlags_ScrollY | ImGuiTableFlags_BordersOuter | ImGuiTableFlags_BordersInnerH | ImGuiTableFlags_Hideable | ImGuiTableFlags_Resizable | ImGuiTableFlags_Reorderable | ImGuiTableFlags_HighlightHoveredColumn;\n        static ImGuiTableColumnFlags column_flags = ImGuiTableColumnFlags_AngledHeader | ImGuiTableColumnFlags_WidthFixed;\n        static bool bools[columns_count * rows_count] = {}; // Dummy storage selection storage\n        static int frozen_cols = 1;\n        static int frozen_rows = 2;\n        ImGui::CheckboxFlags(\"_ScrollX\", &table_flags, ImGuiTableFlags_ScrollX);\n        ImGui::CheckboxFlags(\"_ScrollY\", &table_flags, ImGuiTableFlags_ScrollY);\n        ImGui::CheckboxFlags(\"_Resizable\", &table_flags, ImGuiTableFlags_Resizable);\n        ImGui::CheckboxFlags(\"_Sortable\", &table_flags, ImGuiTableFlags_Sortable);\n        ImGui::CheckboxFlags(\"_NoBordersInBody\", &table_flags, ImGuiTableFlags_NoBordersInBody);\n        ImGui::CheckboxFlags(\"_HighlightHoveredColumn\", &table_flags, ImGuiTableFlags_HighlightHoveredColumn);\n        ImGui::SetNextItemWidth(ImGui::GetFontSize() * 8);\n        ImGui::SliderInt(\"Frozen columns\", &frozen_cols, 0, 2);\n        ImGui::SetNextItemWidth(ImGui::GetFontSize() * 8);\n        ImGui::SliderInt(\"Frozen rows\", &frozen_rows, 0, 2);\n        ImGui::CheckboxFlags(\"Disable header contributing to column width\", &column_flags, ImGuiTableColumnFlags_NoHeaderWidth);\n\n        if (ImGui::TreeNode(\"Style settings\"))\n        {\n            ImGui::SameLine();\n            HelpMarker(\"Giving access to some ImGuiStyle value in this demo for convenience.\");\n            ImGui::SetNextItemWidth(ImGui::GetFontSize() * 8);\n            ImGui::SliderAngle(\"style.TableAngledHeadersAngle\", &ImGui::GetStyle().TableAngledHeadersAngle, -50.0f, +50.0f);\n            ImGui::SetNextItemWidth(ImGui::GetFontSize() * 8);\n            ImGui::SliderFloat2(\"style.TableAngledHeadersTextAlign\", (float*)&ImGui::GetStyle().TableAngledHeadersTextAlign, 0.0f, 1.0f, \"%.2f\");\n            ImGui::TreePop();\n        }\n\n        if (ImGui::BeginTable(\"table_angled_headers\", columns_count, table_flags, ImVec2(0.0f, TEXT_BASE_HEIGHT * 12)))\n        {\n            ImGui::TableSetupColumn(column_names[0], ImGuiTableColumnFlags_NoHide | ImGuiTableColumnFlags_NoReorder);\n            for (int n = 1; n < columns_count; n++)\n                ImGui::TableSetupColumn(column_names[n], column_flags);\n            ImGui::TableSetupScrollFreeze(frozen_cols, frozen_rows);\n\n            ImGui::TableAngledHeadersRow(); // Draw angled headers for all columns with the ImGuiTableColumnFlags_AngledHeader flag.\n            ImGui::TableHeadersRow();       // Draw remaining headers and allow access to context-menu and other functions.\n            for (int row = 0; row < rows_count; row++)\n            {\n                ImGui::PushID(row);\n                ImGui::TableNextRow();\n                ImGui::TableSetColumnIndex(0);\n                ImGui::AlignTextToFramePadding();\n                ImGui::Text(\"Track %d\", row);\n                for (int column = 1; column < columns_count; column++)\n                    if (ImGui::TableSetColumnIndex(column))\n                    {\n                        ImGui::PushID(column);\n                        ImGui::Checkbox(\"\", &bools[row * columns_count + column]);\n                        ImGui::PopID();\n                    }\n                ImGui::PopID();\n            }\n            ImGui::EndTable();\n        }\n        ImGui::TreePop();\n    }\n\n    // Demonstrate creating custom context menus inside columns,\n    // while playing it nice with context menus provided by TableHeadersRow()/TableHeader()\n    if (open_action != -1)\n        ImGui::SetNextItemOpen(open_action != 0);\n    IMGUI_DEMO_MARKER(\"Tables/Context menus\");\n    if (ImGui::TreeNode(\"Context menus\"))\n    {\n        HelpMarker(\n            \"By default, right-clicking over a TableHeadersRow()/TableHeader() line will open the default context-menu.\\n\"\n            \"Using ImGuiTableFlags_ContextMenuInBody we also allow right-clicking over columns body.\");\n        static ImGuiTableFlags flags1 = ImGuiTableFlags_Resizable | ImGuiTableFlags_Reorderable | ImGuiTableFlags_Hideable | ImGuiTableFlags_Borders | ImGuiTableFlags_ContextMenuInBody;\n\n        PushStyleCompact();\n        ImGui::CheckboxFlags(\"ImGuiTableFlags_ContextMenuInBody\", &flags1, ImGuiTableFlags_ContextMenuInBody);\n        PopStyleCompact();\n\n        // Context Menus: first example\n        // [1.1] Right-click on the TableHeadersRow() line to open the default table context menu.\n        // [1.2] Right-click in columns also open the default table context menu (if ImGuiTableFlags_ContextMenuInBody is set)\n        const int COLUMNS_COUNT = 3;\n        if (ImGui::BeginTable(\"table_context_menu\", COLUMNS_COUNT, flags1))\n        {\n            ImGui::TableSetupColumn(\"One\");\n            ImGui::TableSetupColumn(\"Two\");\n            ImGui::TableSetupColumn(\"Three\");\n\n            // [1.1]] Right-click on the TableHeadersRow() line to open the default table context menu.\n            ImGui::TableHeadersRow();\n\n            // Submit dummy contents\n            for (int row = 0; row < 4; row++)\n            {\n                ImGui::TableNextRow();\n                for (int column = 0; column < COLUMNS_COUNT; column++)\n                {\n                    ImGui::TableSetColumnIndex(column);\n                    ImGui::Text(\"Cell %d,%d\", column, row);\n                }\n            }\n            ImGui::EndTable();\n        }\n\n        // Context Menus: second example\n        // [2.1] Right-click on the TableHeadersRow() line to open the default table context menu.\n        // [2.2] Right-click on the \"..\" to open a custom popup\n        // [2.3] Right-click in columns to open another custom popup\n        HelpMarker(\n            \"Demonstrate mixing table context menu (over header), item context button (over button) \"\n            \"and custom per-colunm context menu (over column body).\");\n        ImGuiTableFlags flags2 = ImGuiTableFlags_Resizable | ImGuiTableFlags_SizingFixedFit | ImGuiTableFlags_Reorderable | ImGuiTableFlags_Hideable | ImGuiTableFlags_Borders;\n        if (ImGui::BeginTable(\"table_context_menu_2\", COLUMNS_COUNT, flags2))\n        {\n            ImGui::TableSetupColumn(\"One\");\n            ImGui::TableSetupColumn(\"Two\");\n            ImGui::TableSetupColumn(\"Three\");\n\n            // [2.1] Right-click on the TableHeadersRow() line to open the default table context menu.\n            ImGui::TableHeadersRow();\n            for (int row = 0; row < 4; row++)\n            {\n                ImGui::TableNextRow();\n                for (int column = 0; column < COLUMNS_COUNT; column++)\n                {\n                    // Submit dummy contents\n                    ImGui::TableSetColumnIndex(column);\n                    ImGui::Text(\"Cell %d,%d\", column, row);\n                    ImGui::SameLine();\n\n                    // [2.2] Right-click on the \"..\" to open a custom popup\n                    ImGui::PushID(row * COLUMNS_COUNT + column);\n                    ImGui::SmallButton(\"..\");\n                    if (ImGui::BeginPopupContextItem())\n                    {\n                        ImGui::Text(\"This is the popup for Button(\\\"..\\\") in Cell %d,%d\", column, row);\n                        if (ImGui::Button(\"Close\"))\n                            ImGui::CloseCurrentPopup();\n                        ImGui::EndPopup();\n                    }\n                    ImGui::PopID();\n                }\n            }\n\n            // [2.3] Right-click anywhere in columns to open another custom popup\n            // (instead of testing for !IsAnyItemHovered() we could also call OpenPopup() with ImGuiPopupFlags_NoOpenOverExistingPopup\n            // to manage popup priority as the popups triggers, here \"are we hovering a column\" are overlapping)\n            int hovered_column = -1;\n            for (int column = 0; column < COLUMNS_COUNT + 1; column++)\n            {\n                ImGui::PushID(column);\n                if (ImGui::TableGetColumnFlags(column) & ImGuiTableColumnFlags_IsHovered)\n                    hovered_column = column;\n                if (hovered_column == column && !ImGui::IsAnyItemHovered() && ImGui::IsMouseReleased(1))\n                    ImGui::OpenPopup(\"MyPopup\");\n                if (ImGui::BeginPopup(\"MyPopup\"))\n                {\n                    if (column == COLUMNS_COUNT)\n                        ImGui::Text(\"This is a custom popup for unused space after the last column.\");\n                    else\n                        ImGui::Text(\"This is a custom popup for Column %d\", column);\n                    if (ImGui::Button(\"Close\"))\n                        ImGui::CloseCurrentPopup();\n                    ImGui::EndPopup();\n                }\n                ImGui::PopID();\n            }\n\n            ImGui::EndTable();\n            ImGui::Text(\"Hovered column: %d\", hovered_column);\n        }\n        ImGui::TreePop();\n    }\n\n    // Demonstrate creating multiple tables with the same ID\n    if (open_action != -1)\n        ImGui::SetNextItemOpen(open_action != 0);\n    IMGUI_DEMO_MARKER(\"Tables/Synced instances\");\n    if (ImGui::TreeNode(\"Synced instances\"))\n    {\n        HelpMarker(\"Multiple tables with the same identifier will share their settings, width, visibility, order etc.\");\n\n        static ImGuiTableFlags flags = ImGuiTableFlags_Resizable | ImGuiTableFlags_Reorderable | ImGuiTableFlags_Hideable | ImGuiTableFlags_Borders | ImGuiTableFlags_SizingFixedFit | ImGuiTableFlags_NoSavedSettings;\n        ImGui::CheckboxFlags(\"ImGuiTableFlags_Resizable\", &flags, ImGuiTableFlags_Resizable);\n        ImGui::CheckboxFlags(\"ImGuiTableFlags_ScrollY\", &flags, ImGuiTableFlags_ScrollY);\n        ImGui::CheckboxFlags(\"ImGuiTableFlags_SizingFixedFit\", &flags, ImGuiTableFlags_SizingFixedFit);\n        ImGui::CheckboxFlags(\"ImGuiTableFlags_HighlightHoveredColumn\", &flags, ImGuiTableFlags_HighlightHoveredColumn);\n        for (int n = 0; n < 3; n++)\n        {\n            char buf[32];\n            sprintf(buf, \"Synced Table %d\", n);\n            bool open = ImGui::CollapsingHeader(buf, ImGuiTreeNodeFlags_DefaultOpen);\n            if (open && ImGui::BeginTable(\"Table\", 3, flags, ImVec2(0.0f, ImGui::GetTextLineHeightWithSpacing() * 5)))\n            {\n                ImGui::TableSetupColumn(\"One\");\n                ImGui::TableSetupColumn(\"Two\");\n                ImGui::TableSetupColumn(\"Three\");\n                ImGui::TableHeadersRow();\n                const int cell_count = (n == 1) ? 27 : 9; // Make second table have a scrollbar to verify that additional decoration is not affecting column positions.\n                for (int cell = 0; cell < cell_count; cell++)\n                {\n                    ImGui::TableNextColumn();\n                    ImGui::Text(\"this cell %d\", cell);\n                }\n                ImGui::EndTable();\n            }\n        }\n        ImGui::TreePop();\n    }\n\n    // Demonstrate using Sorting facilities\n    // This is a simplified version of the \"Advanced\" example, where we mostly focus on the code necessary to handle sorting.\n    // Note that the \"Advanced\" example also showcase manually triggering a sort (e.g. if item quantities have been modified)\n    static const char* template_items_names[] =\n    {\n        \"Banana\", \"Apple\", \"Cherry\", \"Watermelon\", \"Grapefruit\", \"Strawberry\", \"Mango\",\n        \"Kiwi\", \"Orange\", \"Pineapple\", \"Blueberry\", \"Plum\", \"Coconut\", \"Pear\", \"Apricot\"\n    };\n    if (open_action != -1)\n        ImGui::SetNextItemOpen(open_action != 0);\n    IMGUI_DEMO_MARKER(\"Tables/Sorting\");\n    if (ImGui::TreeNode(\"Sorting\"))\n    {\n        // Create item list\n        static ImVector<MyItem> items;\n        if (items.Size == 0)\n        {\n            items.resize(50, MyItem());\n            for (int n = 0; n < items.Size; n++)\n            {\n                const int template_n = n % IM_ARRAYSIZE(template_items_names);\n                MyItem& item = items[n];\n                item.ID = n;\n                item.Name = template_items_names[template_n];\n                item.Quantity = (n * n - n) % 20; // Assign default quantities\n            }\n        }\n\n        // Options\n        static ImGuiTableFlags flags =\n            ImGuiTableFlags_Resizable | ImGuiTableFlags_Reorderable | ImGuiTableFlags_Hideable | ImGuiTableFlags_Sortable | ImGuiTableFlags_SortMulti\n            | ImGuiTableFlags_RowBg | ImGuiTableFlags_BordersOuter | ImGuiTableFlags_BordersV | ImGuiTableFlags_NoBordersInBody\n            | ImGuiTableFlags_ScrollY;\n        PushStyleCompact();\n        ImGui::CheckboxFlags(\"ImGuiTableFlags_SortMulti\", &flags, ImGuiTableFlags_SortMulti);\n        ImGui::SameLine(); HelpMarker(\"When sorting is enabled: hold shift when clicking headers to sort on multiple column. TableGetSortSpecs() may return specs where (SpecsCount > 1).\");\n        ImGui::CheckboxFlags(\"ImGuiTableFlags_SortTristate\", &flags, ImGuiTableFlags_SortTristate);\n        ImGui::SameLine(); HelpMarker(\"When sorting is enabled: allow no sorting, disable default sorting. TableGetSortSpecs() may return specs where (SpecsCount == 0).\");\n        PopStyleCompact();\n\n        if (ImGui::BeginTable(\"table_sorting\", 4, flags, ImVec2(0.0f, TEXT_BASE_HEIGHT * 15), 0.0f))\n        {\n            // Declare columns\n            // We use the \"user_id\" parameter of TableSetupColumn() to specify a user id that will be stored in the sort specifications.\n            // This is so our sort function can identify a column given our own identifier. We could also identify them based on their index!\n            // Demonstrate using a mixture of flags among available sort-related flags:\n            // - ImGuiTableColumnFlags_DefaultSort\n            // - ImGuiTableColumnFlags_NoSort / ImGuiTableColumnFlags_NoSortAscending / ImGuiTableColumnFlags_NoSortDescending\n            // - ImGuiTableColumnFlags_PreferSortAscending / ImGuiTableColumnFlags_PreferSortDescending\n            ImGui::TableSetupColumn(\"ID\",       ImGuiTableColumnFlags_DefaultSort          | ImGuiTableColumnFlags_WidthFixed,   0.0f, MyItemColumnID_ID);\n            ImGui::TableSetupColumn(\"Name\",                                                  ImGuiTableColumnFlags_WidthFixed,   0.0f, MyItemColumnID_Name);\n            ImGui::TableSetupColumn(\"Action\",   ImGuiTableColumnFlags_NoSort               | ImGuiTableColumnFlags_WidthFixed,   0.0f, MyItemColumnID_Action);\n            ImGui::TableSetupColumn(\"Quantity\", ImGuiTableColumnFlags_PreferSortDescending | ImGuiTableColumnFlags_WidthStretch, 0.0f, MyItemColumnID_Quantity);\n            ImGui::TableSetupScrollFreeze(0, 1); // Make row always visible\n            ImGui::TableHeadersRow();\n\n            // Sort our data if sort specs have been changed!\n            if (ImGuiTableSortSpecs* sort_specs = ImGui::TableGetSortSpecs())\n                if (sort_specs->SpecsDirty)\n                {\n                    MyItem::SortWithSortSpecs(sort_specs, items.Data, items.Size);\n                    sort_specs->SpecsDirty = false;\n                }\n\n            // Demonstrate using clipper for large vertical lists\n            ImGuiListClipper clipper;\n            clipper.Begin(items.Size);\n            while (clipper.Step())\n                for (int row_n = clipper.DisplayStart; row_n < clipper.DisplayEnd; row_n++)\n                {\n                    // Display a data item\n                    MyItem* item = &items[row_n];\n                    ImGui::PushID(item->ID);\n                    ImGui::TableNextRow();\n                    ImGui::TableNextColumn();\n                    ImGui::Text(\"%04d\", item->ID);\n                    ImGui::TableNextColumn();\n                    ImGui::TextUnformatted(item->Name);\n                    ImGui::TableNextColumn();\n                    ImGui::SmallButton(\"None\");\n                    ImGui::TableNextColumn();\n                    ImGui::Text(\"%d\", item->Quantity);\n                    ImGui::PopID();\n                }\n            ImGui::EndTable();\n        }\n        ImGui::TreePop();\n    }\n\n    // In this example we'll expose most table flags and settings.\n    // For specific flags and settings refer to the corresponding section for more detailed explanation.\n    // This section is mostly useful to experiment with combining certain flags or settings with each others.\n    //ImGui::SetNextItemOpen(true, ImGuiCond_Once); // [DEBUG]\n    if (open_action != -1)\n        ImGui::SetNextItemOpen(open_action != 0);\n    IMGUI_DEMO_MARKER(\"Tables/Advanced\");\n    if (ImGui::TreeNode(\"Advanced\"))\n    {\n        static ImGuiTableFlags flags =\n            ImGuiTableFlags_Resizable | ImGuiTableFlags_Reorderable | ImGuiTableFlags_Hideable\n            | ImGuiTableFlags_Sortable | ImGuiTableFlags_SortMulti\n            | ImGuiTableFlags_RowBg | ImGuiTableFlags_Borders | ImGuiTableFlags_NoBordersInBody\n            | ImGuiTableFlags_ScrollX | ImGuiTableFlags_ScrollY\n            | ImGuiTableFlags_SizingFixedFit;\n        static ImGuiTableColumnFlags columns_base_flags = ImGuiTableColumnFlags_None;\n\n        enum ContentsType { CT_Text, CT_Button, CT_SmallButton, CT_FillButton, CT_Selectable, CT_SelectableSpanRow };\n        static int contents_type = CT_SelectableSpanRow;\n        const char* contents_type_names[] = { \"Text\", \"Button\", \"SmallButton\", \"FillButton\", \"Selectable\", \"Selectable (span row)\" };\n        static int freeze_cols = 1;\n        static int freeze_rows = 1;\n        static int items_count = IM_ARRAYSIZE(template_items_names) * 2;\n        static ImVec2 outer_size_value = ImVec2(0.0f, TEXT_BASE_HEIGHT * 12);\n        static float row_min_height = 0.0f; // Auto\n        static float inner_width_with_scroll = 0.0f; // Auto-extend\n        static bool outer_size_enabled = true;\n        static bool show_headers = true;\n        static bool show_wrapped_text = false;\n        //static ImGuiTextFilter filter;\n        //ImGui::SetNextItemOpen(true, ImGuiCond_Once); // FIXME-TABLE: Enabling this results in initial clipped first pass on table which tend to affect column sizing\n        if (ImGui::TreeNode(\"Options\"))\n        {\n            // Make the UI compact because there are so many fields\n            PushStyleCompact();\n            ImGui::PushItemWidth(TEXT_BASE_WIDTH * 28.0f);\n\n            if (ImGui::TreeNodeEx(\"Features:\", ImGuiTreeNodeFlags_DefaultOpen))\n            {\n                ImGui::CheckboxFlags(\"ImGuiTableFlags_Resizable\", &flags, ImGuiTableFlags_Resizable);\n                ImGui::CheckboxFlags(\"ImGuiTableFlags_Reorderable\", &flags, ImGuiTableFlags_Reorderable);\n                ImGui::CheckboxFlags(\"ImGuiTableFlags_Hideable\", &flags, ImGuiTableFlags_Hideable);\n                ImGui::CheckboxFlags(\"ImGuiTableFlags_Sortable\", &flags, ImGuiTableFlags_Sortable);\n                ImGui::CheckboxFlags(\"ImGuiTableFlags_NoSavedSettings\", &flags, ImGuiTableFlags_NoSavedSettings);\n                ImGui::CheckboxFlags(\"ImGuiTableFlags_ContextMenuInBody\", &flags, ImGuiTableFlags_ContextMenuInBody);\n                ImGui::TreePop();\n            }\n\n            if (ImGui::TreeNodeEx(\"Decorations:\", ImGuiTreeNodeFlags_DefaultOpen))\n            {\n                ImGui::CheckboxFlags(\"ImGuiTableFlags_RowBg\", &flags, ImGuiTableFlags_RowBg);\n                ImGui::CheckboxFlags(\"ImGuiTableFlags_BordersV\", &flags, ImGuiTableFlags_BordersV);\n                ImGui::CheckboxFlags(\"ImGuiTableFlags_BordersOuterV\", &flags, ImGuiTableFlags_BordersOuterV);\n                ImGui::CheckboxFlags(\"ImGuiTableFlags_BordersInnerV\", &flags, ImGuiTableFlags_BordersInnerV);\n                ImGui::CheckboxFlags(\"ImGuiTableFlags_BordersH\", &flags, ImGuiTableFlags_BordersH);\n                ImGui::CheckboxFlags(\"ImGuiTableFlags_BordersOuterH\", &flags, ImGuiTableFlags_BordersOuterH);\n                ImGui::CheckboxFlags(\"ImGuiTableFlags_BordersInnerH\", &flags, ImGuiTableFlags_BordersInnerH);\n                ImGui::CheckboxFlags(\"ImGuiTableFlags_NoBordersInBody\", &flags, ImGuiTableFlags_NoBordersInBody); ImGui::SameLine(); HelpMarker(\"Disable vertical borders in columns Body (borders will always appear in Headers\");\n                ImGui::CheckboxFlags(\"ImGuiTableFlags_NoBordersInBodyUntilResize\", &flags, ImGuiTableFlags_NoBordersInBodyUntilResize); ImGui::SameLine(); HelpMarker(\"Disable vertical borders in columns Body until hovered for resize (borders will always appear in Headers)\");\n                ImGui::TreePop();\n            }\n\n            if (ImGui::TreeNodeEx(\"Sizing:\", ImGuiTreeNodeFlags_DefaultOpen))\n            {\n                EditTableSizingFlags(&flags);\n                ImGui::SameLine(); HelpMarker(\"In the Advanced demo we override the policy of each column so those table-wide settings have less effect that typical.\");\n                ImGui::CheckboxFlags(\"ImGuiTableFlags_NoHostExtendX\", &flags, ImGuiTableFlags_NoHostExtendX);\n                ImGui::SameLine(); HelpMarker(\"Make outer width auto-fit to columns, overriding outer_size.x value.\\n\\nOnly available when ScrollX/ScrollY are disabled and Stretch columns are not used.\");\n                ImGui::CheckboxFlags(\"ImGuiTableFlags_NoHostExtendY\", &flags, ImGuiTableFlags_NoHostExtendY);\n                ImGui::SameLine(); HelpMarker(\"Make outer height stop exactly at outer_size.y (prevent auto-extending table past the limit).\\n\\nOnly available when ScrollX/ScrollY are disabled. Data below the limit will be clipped and not visible.\");\n                ImGui::CheckboxFlags(\"ImGuiTableFlags_NoKeepColumnsVisible\", &flags, ImGuiTableFlags_NoKeepColumnsVisible);\n                ImGui::SameLine(); HelpMarker(\"Only available if ScrollX is disabled.\");\n                ImGui::CheckboxFlags(\"ImGuiTableFlags_PreciseWidths\", &flags, ImGuiTableFlags_PreciseWidths);\n                ImGui::SameLine(); HelpMarker(\"Disable distributing remainder width to stretched columns (width allocation on a 100-wide table with 3 columns: Without this flag: 33,33,34. With this flag: 33,33,33). With larger number of columns, resizing will appear to be less smooth.\");\n                ImGui::CheckboxFlags(\"ImGuiTableFlags_NoClip\", &flags, ImGuiTableFlags_NoClip);\n                ImGui::SameLine(); HelpMarker(\"Disable clipping rectangle for every individual columns (reduce draw command count, items will be able to overflow into other columns). Generally incompatible with ScrollFreeze options.\");\n                ImGui::TreePop();\n            }\n\n            if (ImGui::TreeNodeEx(\"Padding:\", ImGuiTreeNodeFlags_DefaultOpen))\n            {\n                ImGui::CheckboxFlags(\"ImGuiTableFlags_PadOuterX\", &flags, ImGuiTableFlags_PadOuterX);\n                ImGui::CheckboxFlags(\"ImGuiTableFlags_NoPadOuterX\", &flags, ImGuiTableFlags_NoPadOuterX);\n                ImGui::CheckboxFlags(\"ImGuiTableFlags_NoPadInnerX\", &flags, ImGuiTableFlags_NoPadInnerX);\n                ImGui::TreePop();\n            }\n\n            if (ImGui::TreeNodeEx(\"Scrolling:\", ImGuiTreeNodeFlags_DefaultOpen))\n            {\n                ImGui::CheckboxFlags(\"ImGuiTableFlags_ScrollX\", &flags, ImGuiTableFlags_ScrollX);\n                ImGui::SameLine();\n                ImGui::SetNextItemWidth(ImGui::GetFrameHeight());\n                ImGui::DragInt(\"freeze_cols\", &freeze_cols, 0.2f, 0, 9, NULL, ImGuiSliderFlags_NoInput);\n                ImGui::CheckboxFlags(\"ImGuiTableFlags_ScrollY\", &flags, ImGuiTableFlags_ScrollY);\n                ImGui::SameLine();\n                ImGui::SetNextItemWidth(ImGui::GetFrameHeight());\n                ImGui::DragInt(\"freeze_rows\", &freeze_rows, 0.2f, 0, 9, NULL, ImGuiSliderFlags_NoInput);\n                ImGui::TreePop();\n            }\n\n            if (ImGui::TreeNodeEx(\"Sorting:\", ImGuiTreeNodeFlags_DefaultOpen))\n            {\n                ImGui::CheckboxFlags(\"ImGuiTableFlags_SortMulti\", &flags, ImGuiTableFlags_SortMulti);\n                ImGui::SameLine(); HelpMarker(\"When sorting is enabled: hold shift when clicking headers to sort on multiple column. TableGetSortSpecs() may return specs where (SpecsCount > 1).\");\n                ImGui::CheckboxFlags(\"ImGuiTableFlags_SortTristate\", &flags, ImGuiTableFlags_SortTristate);\n                ImGui::SameLine(); HelpMarker(\"When sorting is enabled: allow no sorting, disable default sorting. TableGetSortSpecs() may return specs where (SpecsCount == 0).\");\n                ImGui::TreePop();\n            }\n\n            if (ImGui::TreeNodeEx(\"Headers:\", ImGuiTreeNodeFlags_DefaultOpen))\n            {\n                ImGui::Checkbox(\"show_headers\", &show_headers);\n                ImGui::CheckboxFlags(\"ImGuiTableFlags_HighlightHoveredColumn\", &flags, ImGuiTableFlags_HighlightHoveredColumn);\n                ImGui::CheckboxFlags(\"ImGuiTableColumnFlags_AngledHeader\", &columns_base_flags, ImGuiTableColumnFlags_AngledHeader);\n                ImGui::SameLine(); HelpMarker(\"Enable AngledHeader on all columns. Best enabled on selected narrow columns (see \\\"Angled headers\\\" section of the demo).\");\n                ImGui::TreePop();\n            }\n\n            if (ImGui::TreeNodeEx(\"Other:\", ImGuiTreeNodeFlags_DefaultOpen))\n            {\n                ImGui::Checkbox(\"show_wrapped_text\", &show_wrapped_text);\n\n                ImGui::DragFloat2(\"##OuterSize\", &outer_size_value.x);\n                ImGui::SameLine(0.0f, ImGui::GetStyle().ItemInnerSpacing.x);\n                ImGui::Checkbox(\"outer_size\", &outer_size_enabled);\n                ImGui::SameLine();\n                HelpMarker(\"If scrolling is disabled (ScrollX and ScrollY not set):\\n\"\n                    \"- The table is output directly in the parent window.\\n\"\n                    \"- OuterSize.x < 0.0f will right-align the table.\\n\"\n                    \"- OuterSize.x = 0.0f will narrow fit the table unless there are any Stretch columns.\\n\"\n                    \"- OuterSize.y then becomes the minimum size for the table, which will extend vertically if there are more rows (unless NoHostExtendY is set).\");\n\n                // From a user point of view we will tend to use 'inner_width' differently depending on whether our table is embedding scrolling.\n                // To facilitate toying with this demo we will actually pass 0.0f to the BeginTable() when ScrollX is disabled.\n                ImGui::DragFloat(\"inner_width (when ScrollX active)\", &inner_width_with_scroll, 1.0f, 0.0f, FLT_MAX);\n\n                ImGui::DragFloat(\"row_min_height\", &row_min_height, 1.0f, 0.0f, FLT_MAX);\n                ImGui::SameLine(); HelpMarker(\"Specify height of the Selectable item.\");\n\n                ImGui::DragInt(\"items_count\", &items_count, 0.1f, 0, 9999);\n                ImGui::Combo(\"items_type (first column)\", &contents_type, contents_type_names, IM_ARRAYSIZE(contents_type_names));\n                //filter.Draw(\"filter\");\n                ImGui::TreePop();\n            }\n\n            ImGui::PopItemWidth();\n            PopStyleCompact();\n            ImGui::Spacing();\n            ImGui::TreePop();\n        }\n\n        // Update item list if we changed the number of items\n        static ImVector<MyItem> items;\n        static ImVector<int> selection;\n        static bool items_need_sort = false;\n        if (items.Size != items_count)\n        {\n            items.resize(items_count, MyItem());\n            for (int n = 0; n < items_count; n++)\n            {\n                const int template_n = n % IM_ARRAYSIZE(template_items_names);\n                MyItem& item = items[n];\n                item.ID = n;\n                item.Name = template_items_names[template_n];\n                item.Quantity = (template_n == 3) ? 10 : (template_n == 4) ? 20 : 0; // Assign default quantities\n            }\n        }\n\n        const ImDrawList* parent_draw_list = ImGui::GetWindowDrawList();\n        const int parent_draw_list_draw_cmd_count = parent_draw_list->CmdBuffer.Size;\n        ImVec2 table_scroll_cur, table_scroll_max; // For debug display\n        const ImDrawList* table_draw_list = NULL;  // \"\n\n        // Submit table\n        const float inner_width_to_use = (flags & ImGuiTableFlags_ScrollX) ? inner_width_with_scroll : 0.0f;\n        if (ImGui::BeginTable(\"table_advanced\", 6, flags, outer_size_enabled ? outer_size_value : ImVec2(0, 0), inner_width_to_use))\n        {\n            // Declare columns\n            // We use the \"user_id\" parameter of TableSetupColumn() to specify a user id that will be stored in the sort specifications.\n            // This is so our sort function can identify a column given our own identifier. We could also identify them based on their index!\n            ImGui::TableSetupColumn(\"ID\",           columns_base_flags | ImGuiTableColumnFlags_DefaultSort | ImGuiTableColumnFlags_WidthFixed | ImGuiTableColumnFlags_NoHide, 0.0f, MyItemColumnID_ID);\n            ImGui::TableSetupColumn(\"Name\",         columns_base_flags | ImGuiTableColumnFlags_WidthFixed, 0.0f, MyItemColumnID_Name);\n            ImGui::TableSetupColumn(\"Action\",       columns_base_flags | ImGuiTableColumnFlags_NoSort | ImGuiTableColumnFlags_WidthFixed, 0.0f, MyItemColumnID_Action);\n            ImGui::TableSetupColumn(\"Quantity\",     columns_base_flags | ImGuiTableColumnFlags_PreferSortDescending, 0.0f, MyItemColumnID_Quantity);\n            ImGui::TableSetupColumn(\"Description\",  columns_base_flags | ((flags & ImGuiTableFlags_NoHostExtendX) ? 0 : ImGuiTableColumnFlags_WidthStretch), 0.0f, MyItemColumnID_Description);\n            ImGui::TableSetupColumn(\"Hidden\",       columns_base_flags |  ImGuiTableColumnFlags_DefaultHide | ImGuiTableColumnFlags_NoSort);\n            ImGui::TableSetupScrollFreeze(freeze_cols, freeze_rows);\n\n            // Sort our data if sort specs have been changed!\n            ImGuiTableSortSpecs* sort_specs = ImGui::TableGetSortSpecs();\n            if (sort_specs && sort_specs->SpecsDirty)\n                items_need_sort = true;\n            if (sort_specs && items_need_sort && items.Size > 1)\n            {\n                MyItem::SortWithSortSpecs(sort_specs, items.Data, items.Size);\n                sort_specs->SpecsDirty = false;\n            }\n            items_need_sort = false;\n\n            // Take note of whether we are currently sorting based on the Quantity field,\n            // we will use this to trigger sorting when we know the data of this column has been modified.\n            const bool sorts_specs_using_quantity = (ImGui::TableGetColumnFlags(3) & ImGuiTableColumnFlags_IsSorted) != 0;\n\n            // Show headers\n            if (show_headers && (columns_base_flags & ImGuiTableColumnFlags_AngledHeader) != 0)\n                ImGui::TableAngledHeadersRow();\n            if (show_headers)\n                ImGui::TableHeadersRow();\n\n            // Show data\n            // FIXME-TABLE FIXME-NAV: How we can get decent up/down even though we have the buttons here?\n#if 1\n            // Demonstrate using clipper for large vertical lists\n            ImGuiListClipper clipper;\n            clipper.Begin(items.Size);\n            while (clipper.Step())\n            {\n                for (int row_n = clipper.DisplayStart; row_n < clipper.DisplayEnd; row_n++)\n#else\n            // Without clipper\n            {\n                for (int row_n = 0; row_n < items.Size; row_n++)\n#endif\n                {\n                    MyItem* item = &items[row_n];\n                    //if (!filter.PassFilter(item->Name))\n                    //    continue;\n\n                    const bool item_is_selected = selection.contains(item->ID);\n                    ImGui::PushID(item->ID);\n                    ImGui::TableNextRow(ImGuiTableRowFlags_None, row_min_height);\n\n                    // For the demo purpose we can select among different type of items submitted in the first column\n                    ImGui::TableSetColumnIndex(0);\n                    char label[32];\n                    sprintf(label, \"%04d\", item->ID);\n                    if (contents_type == CT_Text)\n                        ImGui::TextUnformatted(label);\n                    else if (contents_type == CT_Button)\n                        ImGui::Button(label);\n                    else if (contents_type == CT_SmallButton)\n                        ImGui::SmallButton(label);\n                    else if (contents_type == CT_FillButton)\n                        ImGui::Button(label, ImVec2(-FLT_MIN, 0.0f));\n                    else if (contents_type == CT_Selectable || contents_type == CT_SelectableSpanRow)\n                    {\n                        ImGuiSelectableFlags selectable_flags = (contents_type == CT_SelectableSpanRow) ? ImGuiSelectableFlags_SpanAllColumns | ImGuiSelectableFlags_AllowOverlap : ImGuiSelectableFlags_None;\n                        if (ImGui::Selectable(label, item_is_selected, selectable_flags, ImVec2(0, row_min_height)))\n                        {\n                            if (ImGui::GetIO().KeyCtrl)\n                            {\n                                if (item_is_selected)\n                                    selection.find_erase_unsorted(item->ID);\n                                else\n                                    selection.push_back(item->ID);\n                            }\n                            else\n                            {\n                                selection.clear();\n                                selection.push_back(item->ID);\n                            }\n                        }\n                    }\n\n                    if (ImGui::TableSetColumnIndex(1))\n                        ImGui::TextUnformatted(item->Name);\n\n                    // Here we demonstrate marking our data set as needing to be sorted again if we modified a quantity,\n                    // and we are currently sorting on the column showing the Quantity.\n                    // To avoid triggering a sort while holding the button, we only trigger it when the button has been released.\n                    // You will probably need some extra logic if you want to automatically sort when a specific entry changes.\n                    if (ImGui::TableSetColumnIndex(2))\n                    {\n                        if (ImGui::SmallButton(\"Chop\")) { item->Quantity += 1; }\n                        if (sorts_specs_using_quantity && ImGui::IsItemDeactivated()) { items_need_sort = true; }\n                        ImGui::SameLine();\n                        if (ImGui::SmallButton(\"Eat\")) { item->Quantity -= 1; }\n                        if (sorts_specs_using_quantity && ImGui::IsItemDeactivated()) { items_need_sort = true; }\n                    }\n\n                    if (ImGui::TableSetColumnIndex(3))\n                        ImGui::Text(\"%d\", item->Quantity);\n\n                    ImGui::TableSetColumnIndex(4);\n                    if (show_wrapped_text)\n                        ImGui::TextWrapped(\"Lorem ipsum dolor sit amet\");\n                    else\n                        ImGui::Text(\"Lorem ipsum dolor sit amet\");\n\n                    if (ImGui::TableSetColumnIndex(5))\n                        ImGui::Text(\"1234\");\n\n                    ImGui::PopID();\n                }\n            }\n\n            // Store some info to display debug details below\n            table_scroll_cur = ImVec2(ImGui::GetScrollX(), ImGui::GetScrollY());\n            table_scroll_max = ImVec2(ImGui::GetScrollMaxX(), ImGui::GetScrollMaxY());\n            table_draw_list = ImGui::GetWindowDrawList();\n            ImGui::EndTable();\n        }\n        static bool show_debug_details = false;\n        ImGui::Checkbox(\"Debug details\", &show_debug_details);\n        if (show_debug_details && table_draw_list)\n        {\n            ImGui::SameLine(0.0f, 0.0f);\n            const int table_draw_list_draw_cmd_count = table_draw_list->CmdBuffer.Size;\n            if (table_draw_list == parent_draw_list)\n                ImGui::Text(\": DrawCmd: +%d (in same window)\",\n                    table_draw_list_draw_cmd_count - parent_draw_list_draw_cmd_count);\n            else\n                ImGui::Text(\": DrawCmd: +%d (in child window), Scroll: (%.f/%.f) (%.f/%.f)\",\n                    table_draw_list_draw_cmd_count - 1, table_scroll_cur.x, table_scroll_max.x, table_scroll_cur.y, table_scroll_max.y);\n        }\n        ImGui::TreePop();\n    }\n\n    ImGui::PopID();\n\n    DemoWindowColumns();\n\n    if (disable_indent)\n        ImGui::PopStyleVar();\n}\n\n// Demonstrate old/legacy Columns API!\n// [2020: Columns are under-featured and not maintained. Prefer using the more flexible and powerful BeginTable() API!]\nstatic void DemoWindowColumns()\n{\n    IMGUI_DEMO_MARKER(\"Columns (legacy API)\");\n    bool open = ImGui::TreeNode(\"Legacy Columns API\");\n    ImGui::SameLine();\n    HelpMarker(\"Columns() is an old API! Prefer using the more flexible and powerful BeginTable() API!\");\n    if (!open)\n        return;\n\n    // Basic columns\n    IMGUI_DEMO_MARKER(\"Columns (legacy API)/Basic\");\n    if (ImGui::TreeNode(\"Basic\"))\n    {\n        ImGui::Text(\"Without border:\");\n        ImGui::Columns(3, \"mycolumns3\", false);  // 3-ways, no border\n        ImGui::Separator();\n        for (int n = 0; n < 14; n++)\n        {\n            char label[32];\n            sprintf(label, \"Item %d\", n);\n            if (ImGui::Selectable(label)) {}\n            //if (ImGui::Button(label, ImVec2(-FLT_MIN,0.0f))) {}\n            ImGui::NextColumn();\n        }\n        ImGui::Columns(1);\n        ImGui::Separator();\n\n        ImGui::Text(\"With border:\");\n        ImGui::Columns(4, \"mycolumns\"); // 4-ways, with border\n        ImGui::Separator();\n        ImGui::Text(\"ID\"); ImGui::NextColumn();\n        ImGui::Text(\"Name\"); ImGui::NextColumn();\n        ImGui::Text(\"Path\"); ImGui::NextColumn();\n        ImGui::Text(\"Hovered\"); ImGui::NextColumn();\n        ImGui::Separator();\n        const char* names[3] = { \"One\", \"Two\", \"Three\" };\n        const char* paths[3] = { \"/path/one\", \"/path/two\", \"/path/three\" };\n        static int selected = -1;\n        for (int i = 0; i < 3; i++)\n        {\n            char label[32];\n            sprintf(label, \"%04d\", i);\n            if (ImGui::Selectable(label, selected == i, ImGuiSelectableFlags_SpanAllColumns))\n                selected = i;\n            bool hovered = ImGui::IsItemHovered();\n            ImGui::NextColumn();\n            ImGui::Text(names[i]); ImGui::NextColumn();\n            ImGui::Text(paths[i]); ImGui::NextColumn();\n            ImGui::Text(\"%d\", hovered); ImGui::NextColumn();\n        }\n        ImGui::Columns(1);\n        ImGui::Separator();\n        ImGui::TreePop();\n    }\n\n    IMGUI_DEMO_MARKER(\"Columns (legacy API)/Borders\");\n    if (ImGui::TreeNode(\"Borders\"))\n    {\n        // NB: Future columns API should allow automatic horizontal borders.\n        static bool h_borders = true;\n        static bool v_borders = true;\n        static int columns_count = 4;\n        const int lines_count = 3;\n        ImGui::SetNextItemWidth(ImGui::GetFontSize() * 8);\n        ImGui::DragInt(\"##columns_count\", &columns_count, 0.1f, 2, 10, \"%d columns\");\n        if (columns_count < 2)\n            columns_count = 2;\n        ImGui::SameLine();\n        ImGui::Checkbox(\"horizontal\", &h_borders);\n        ImGui::SameLine();\n        ImGui::Checkbox(\"vertical\", &v_borders);\n        ImGui::Columns(columns_count, NULL, v_borders);\n        for (int i = 0; i < columns_count * lines_count; i++)\n        {\n            if (h_borders && ImGui::GetColumnIndex() == 0)\n                ImGui::Separator();\n            ImGui::PushID(i);\n            ImGui::Text(\"%c%c%c\", 'a' + i, 'a' + i, 'a' + i);\n            ImGui::Text(\"Width %.2f\", ImGui::GetColumnWidth());\n            ImGui::Text(\"Avail %.2f\", ImGui::GetContentRegionAvail().x);\n            ImGui::Text(\"Offset %.2f\", ImGui::GetColumnOffset());\n            ImGui::Text(\"Long text that is likely to clip\");\n            ImGui::Button(\"Button\", ImVec2(-FLT_MIN, 0.0f));\n            ImGui::PopID();\n            ImGui::NextColumn();\n        }\n        ImGui::Columns(1);\n        if (h_borders)\n            ImGui::Separator();\n        ImGui::TreePop();\n    }\n\n    // Create multiple items in a same cell before switching to next column\n    IMGUI_DEMO_MARKER(\"Columns (legacy API)/Mixed items\");\n    if (ImGui::TreeNode(\"Mixed items\"))\n    {\n        ImGui::Columns(3, \"mixed\");\n        ImGui::Separator();\n\n        ImGui::Text(\"Hello\");\n        ImGui::Button(\"Banana\");\n        ImGui::NextColumn();\n\n        ImGui::Text(\"ImGui\");\n        ImGui::Button(\"Apple\");\n        static float foo = 1.0f;\n        ImGui::InputFloat(\"red\", &foo, 0.05f, 0, \"%.3f\");\n        ImGui::Text(\"An extra line here.\");\n        ImGui::NextColumn();\n\n        ImGui::Text(\"Sailor\");\n        ImGui::Button(\"Corniflower\");\n        static float bar = 1.0f;\n        ImGui::InputFloat(\"blue\", &bar, 0.05f, 0, \"%.3f\");\n        ImGui::NextColumn();\n\n        if (ImGui::CollapsingHeader(\"Category A\")) { ImGui::Text(\"Blah blah blah\"); } ImGui::NextColumn();\n        if (ImGui::CollapsingHeader(\"Category B\")) { ImGui::Text(\"Blah blah blah\"); } ImGui::NextColumn();\n        if (ImGui::CollapsingHeader(\"Category C\")) { ImGui::Text(\"Blah blah blah\"); } ImGui::NextColumn();\n        ImGui::Columns(1);\n        ImGui::Separator();\n        ImGui::TreePop();\n    }\n\n    // Word wrapping\n    IMGUI_DEMO_MARKER(\"Columns (legacy API)/Word-wrapping\");\n    if (ImGui::TreeNode(\"Word-wrapping\"))\n    {\n        ImGui::Columns(2, \"word-wrapping\");\n        ImGui::Separator();\n        ImGui::TextWrapped(\"The quick brown fox jumps over the lazy dog.\");\n        ImGui::TextWrapped(\"Hello Left\");\n        ImGui::NextColumn();\n        ImGui::TextWrapped(\"The quick brown fox jumps over the lazy dog.\");\n        ImGui::TextWrapped(\"Hello Right\");\n        ImGui::Columns(1);\n        ImGui::Separator();\n        ImGui::TreePop();\n    }\n\n    IMGUI_DEMO_MARKER(\"Columns (legacy API)/Horizontal Scrolling\");\n    if (ImGui::TreeNode(\"Horizontal Scrolling\"))\n    {\n        ImGui::SetNextWindowContentSize(ImVec2(1500.0f, 0.0f));\n        ImVec2 child_size = ImVec2(0, ImGui::GetFontSize() * 20.0f);\n        ImGui::BeginChild(\"##ScrollingRegion\", child_size, ImGuiChildFlags_None, ImGuiWindowFlags_HorizontalScrollbar);\n        ImGui::Columns(10);\n\n        // Also demonstrate using clipper for large vertical lists\n        int ITEMS_COUNT = 2000;\n        ImGuiListClipper clipper;\n        clipper.Begin(ITEMS_COUNT);\n        while (clipper.Step())\n        {\n            for (int i = clipper.DisplayStart; i < clipper.DisplayEnd; i++)\n                for (int j = 0; j < 10; j++)\n                {\n                    ImGui::Text(\"Line %d Column %d...\", i, j);\n                    ImGui::NextColumn();\n                }\n        }\n        ImGui::Columns(1);\n        ImGui::EndChild();\n        ImGui::TreePop();\n    }\n\n    IMGUI_DEMO_MARKER(\"Columns (legacy API)/Tree\");\n    if (ImGui::TreeNode(\"Tree\"))\n    {\n        ImGui::Columns(2, \"tree\", true);\n        for (int x = 0; x < 3; x++)\n        {\n            bool open1 = ImGui::TreeNode((void*)(intptr_t)x, \"Node%d\", x);\n            ImGui::NextColumn();\n            ImGui::Text(\"Node contents\");\n            ImGui::NextColumn();\n            if (open1)\n            {\n                for (int y = 0; y < 3; y++)\n                {\n                    bool open2 = ImGui::TreeNode((void*)(intptr_t)y, \"Node%d.%d\", x, y);\n                    ImGui::NextColumn();\n                    ImGui::Text(\"Node contents\");\n                    if (open2)\n                    {\n                        ImGui::Text(\"Even more contents\");\n                        if (ImGui::TreeNode(\"Tree in column\"))\n                        {\n                            ImGui::Text(\"The quick brown fox jumps over the lazy dog\");\n                            ImGui::TreePop();\n                        }\n                    }\n                    ImGui::NextColumn();\n                    if (open2)\n                        ImGui::TreePop();\n                }\n                ImGui::TreePop();\n            }\n        }\n        ImGui::Columns(1);\n        ImGui::TreePop();\n    }\n\n    ImGui::TreePop();\n}\n\n//-----------------------------------------------------------------------------\n// [SECTION] DemoWindowInputs()\n//-----------------------------------------------------------------------------\n\nstatic void DemoWindowInputs()\n{\n    IMGUI_DEMO_MARKER(\"Inputs & Focus\");\n    if (ImGui::CollapsingHeader(\"Inputs & Focus\"))\n    {\n        ImGuiIO& io = ImGui::GetIO();\n\n        // Display inputs submitted to ImGuiIO\n        IMGUI_DEMO_MARKER(\"Inputs & Focus/Inputs\");\n        ImGui::SetNextItemOpen(true, ImGuiCond_Once);\n        bool inputs_opened = ImGui::TreeNode(\"Inputs\");\n        ImGui::SameLine();\n        HelpMarker(\n            \"This is a simplified view. See more detailed input state:\\n\"\n            \"- in 'Tools->Metrics/Debugger->Inputs'.\\n\"\n            \"- in 'Tools->Debug Log->IO'.\");\n        if (inputs_opened)\n        {\n            if (ImGui::IsMousePosValid())\n                ImGui::Text(\"Mouse pos: (%g, %g)\", io.MousePos.x, io.MousePos.y);\n            else\n                ImGui::Text(\"Mouse pos: <INVALID>\");\n            ImGui::Text(\"Mouse delta: (%g, %g)\", io.MouseDelta.x, io.MouseDelta.y);\n            ImGui::Text(\"Mouse down:\");\n            for (int i = 0; i < IM_ARRAYSIZE(io.MouseDown); i++) if (ImGui::IsMouseDown(i)) { ImGui::SameLine(); ImGui::Text(\"b%d (%.02f secs)\", i, io.MouseDownDuration[i]); }\n            ImGui::Text(\"Mouse wheel: %.1f\", io.MouseWheel);\n            ImGui::Text(\"Mouse clicked count:\");\n            for (int i = 0; i < IM_ARRAYSIZE(io.MouseDown); i++) if (io.MouseClickedCount[i] > 0) { ImGui::SameLine(); ImGui::Text(\"b%d: %d\", i, io.MouseClickedCount[i]); }\n\n            // We iterate both legacy native range and named ImGuiKey ranges. This is a little unusual/odd but this allows\n            // displaying the data for old/new backends.\n            // User code should never have to go through such hoops!\n            // You can generally iterate between ImGuiKey_NamedKey_BEGIN and ImGuiKey_NamedKey_END.\n            struct funcs { static bool IsLegacyNativeDupe(ImGuiKey) { return false; } };\n            ImGuiKey start_key = ImGuiKey_NamedKey_BEGIN;\n            ImGui::Text(\"Keys down:\");         for (ImGuiKey key = start_key; key < ImGuiKey_NamedKey_END; key = (ImGuiKey)(key + 1)) { if (funcs::IsLegacyNativeDupe(key) || !ImGui::IsKeyDown(key)) continue; ImGui::SameLine(); ImGui::Text((key < ImGuiKey_NamedKey_BEGIN) ? \"\\\"%s\\\"\" : \"\\\"%s\\\" %d\", ImGui::GetKeyName(key), key); }\n            ImGui::Text(\"Keys mods: %s%s%s%s\", io.KeyCtrl ? \"CTRL \" : \"\", io.KeyShift ? \"SHIFT \" : \"\", io.KeyAlt ? \"ALT \" : \"\", io.KeySuper ? \"SUPER \" : \"\");\n            ImGui::Text(\"Chars queue:\");       for (int i = 0; i < io.InputQueueCharacters.Size; i++) { ImWchar c = io.InputQueueCharacters[i]; ImGui::SameLine();  ImGui::Text(\"\\'%c\\' (0x%04X)\", (c > ' ' && c <= 255) ? (char)c : '?', c); } // FIXME: We should convert 'c' to UTF-8 here but the functions are not public.\n\n            ImGui::TreePop();\n        }\n\n        // Display ImGuiIO output flags\n        IMGUI_DEMO_MARKER(\"Inputs & Focus/Outputs\");\n        ImGui::SetNextItemOpen(true, ImGuiCond_Once);\n        bool outputs_opened = ImGui::TreeNode(\"Outputs\");\n        ImGui::SameLine();\n        HelpMarker(\n            \"The value of io.WantCaptureMouse and io.WantCaptureKeyboard are normally set by Dear ImGui \"\n            \"to instruct your application of how to route inputs. Typically, when a value is true, it means \"\n            \"Dear ImGui wants the corresponding inputs and we expect the underlying application to ignore them.\\n\\n\"\n            \"The most typical case is: when hovering a window, Dear ImGui set io.WantCaptureMouse to true, \"\n            \"and underlying application should ignore mouse inputs (in practice there are many and more subtle \"\n            \"rules leading to how those flags are set).\");\n        if (outputs_opened)\n        {\n            ImGui::Text(\"io.WantCaptureMouse: %d\", io.WantCaptureMouse);\n            ImGui::Text(\"io.WantCaptureMouseUnlessPopupClose: %d\", io.WantCaptureMouseUnlessPopupClose);\n            ImGui::Text(\"io.WantCaptureKeyboard: %d\", io.WantCaptureKeyboard);\n            ImGui::Text(\"io.WantTextInput: %d\", io.WantTextInput);\n            ImGui::Text(\"io.WantSetMousePos: %d\", io.WantSetMousePos);\n            ImGui::Text(\"io.NavActive: %d, io.NavVisible: %d\", io.NavActive, io.NavVisible);\n\n            IMGUI_DEMO_MARKER(\"Inputs & Focus/Outputs/WantCapture override\");\n            if (ImGui::TreeNode(\"WantCapture override\"))\n            {\n                HelpMarker(\n                    \"Hovering the colored canvas will override io.WantCaptureXXX fields.\\n\"\n                    \"Notice how normally (when set to none), the value of io.WantCaptureKeyboard would be false when hovering \"\n                    \"and true when clicking.\");\n                static int capture_override_mouse = -1;\n                static int capture_override_keyboard = -1;\n                const char* capture_override_desc[] = { \"None\", \"Set to false\", \"Set to true\" };\n                ImGui::SetNextItemWidth(ImGui::GetFontSize() * 15);\n                ImGui::SliderInt(\"SetNextFrameWantCaptureMouse() on hover\", &capture_override_mouse, -1, +1, capture_override_desc[capture_override_mouse + 1], ImGuiSliderFlags_AlwaysClamp);\n                ImGui::SetNextItemWidth(ImGui::GetFontSize() * 15);\n                ImGui::SliderInt(\"SetNextFrameWantCaptureKeyboard() on hover\", &capture_override_keyboard, -1, +1, capture_override_desc[capture_override_keyboard + 1], ImGuiSliderFlags_AlwaysClamp);\n\n                ImGui::ColorButton(\"##panel\", ImVec4(0.7f, 0.1f, 0.7f, 1.0f), ImGuiColorEditFlags_NoTooltip | ImGuiColorEditFlags_NoDragDrop, ImVec2(128.0f, 96.0f)); // Dummy item\n                if (ImGui::IsItemHovered() && capture_override_mouse != -1)\n                    ImGui::SetNextFrameWantCaptureMouse(capture_override_mouse == 1);\n                if (ImGui::IsItemHovered() && capture_override_keyboard != -1)\n                    ImGui::SetNextFrameWantCaptureKeyboard(capture_override_keyboard == 1);\n\n                ImGui::TreePop();\n            }\n            ImGui::TreePop();\n        }\n\n        // Demonstrate using Shortcut() and Routing Policies.\n        // The general flow is:\n        // - Code interested in a chord (e.g. \"Ctrl+A\") declares their intent.\n        // - Multiple locations may be interested in same chord! Routing helps find a winner.\n        // - Every frame, we resolve all claims and assign one owner if the modifiers are matching.\n        // - The lower-level function is 'bool SetShortcutRouting()', returns true when caller got the route.\n        // - Most of the times, SetShortcutRouting() is not called directly. User mostly calls Shortcut() with routing flags.\n        // - If you call Shortcut() WITHOUT any routing option, it uses ImGuiInputFlags_RouteFocused.\n        // TL;DR: Most uses will simply be:\n        // - Shortcut(ImGuiMod_Ctrl | ImGuiKey_A); // Use ImGuiInputFlags_RouteFocused policy.\n        IMGUI_DEMO_MARKER(\"Inputs & Focus/Shortcuts\");\n        if (ImGui::TreeNode(\"Shortcuts\"))\n        {\n            static ImGuiInputFlags route_options = ImGuiInputFlags_Repeat;\n            static ImGuiInputFlags route_type = ImGuiInputFlags_RouteFocused;\n            ImGui::CheckboxFlags(\"ImGuiInputFlags_Repeat\", &route_options, ImGuiInputFlags_Repeat);\n            ImGui::RadioButton(\"ImGuiInputFlags_RouteActive\", &route_type, ImGuiInputFlags_RouteActive);\n            ImGui::RadioButton(\"ImGuiInputFlags_RouteFocused (default)\", &route_type, ImGuiInputFlags_RouteFocused);\n            ImGui::RadioButton(\"ImGuiInputFlags_RouteGlobal\", &route_type, ImGuiInputFlags_RouteGlobal);\n            ImGui::Indent();\n            ImGui::BeginDisabled(route_type != ImGuiInputFlags_RouteGlobal);\n            ImGui::CheckboxFlags(\"ImGuiInputFlags_RouteOverFocused\", &route_options, ImGuiInputFlags_RouteOverFocused);\n            ImGui::CheckboxFlags(\"ImGuiInputFlags_RouteOverActive\", &route_options, ImGuiInputFlags_RouteOverActive);\n            ImGui::CheckboxFlags(\"ImGuiInputFlags_RouteUnlessBgFocused\", &route_options, ImGuiInputFlags_RouteUnlessBgFocused);\n            ImGui::EndDisabled();\n            ImGui::Unindent();\n            ImGui::RadioButton(\"ImGuiInputFlags_RouteAlways\", &route_type, ImGuiInputFlags_RouteAlways);\n            ImGuiInputFlags flags = route_type | route_options; // Merged flags\n            if (route_type != ImGuiInputFlags_RouteGlobal)\n                flags &= ~(ImGuiInputFlags_RouteOverFocused | ImGuiInputFlags_RouteOverActive | ImGuiInputFlags_RouteUnlessBgFocused);\n\n            ImGui::SeparatorText(\"Using SetNextItemShortcut()\");\n            ImGui::Text(\"Ctrl+S\");\n            ImGui::SetNextItemShortcut(ImGuiMod_Ctrl | ImGuiKey_S, flags | ImGuiInputFlags_Tooltip);\n            ImGui::Button(\"Save\");\n            ImGui::Text(\"Alt+F\");\n            ImGui::SetNextItemShortcut(ImGuiMod_Alt | ImGuiKey_F, flags | ImGuiInputFlags_Tooltip);\n            static float f = 0.5f;\n            ImGui::SliderFloat(\"Factor\", &f, 0.0f, 1.0f);\n\n            ImGui::SeparatorText(\"Using Shortcut()\");\n            const float line_height = ImGui::GetTextLineHeightWithSpacing();\n            const ImGuiKeyChord key_chord = ImGuiMod_Ctrl | ImGuiKey_A;\n\n            ImGui::Text(\"Ctrl+A\");\n            ImGui::Text(\"IsWindowFocused: %d, Shortcut: %s\", ImGui::IsWindowFocused(), ImGui::Shortcut(key_chord, flags) ? \"PRESSED\" : \"...\");\n\n            ImGui::PushStyleColor(ImGuiCol_ChildBg, ImVec4(1.0f, 0.0f, 1.0f, 0.1f));\n\n            ImGui::BeginChild(\"WindowA\", ImVec2(-FLT_MIN, line_height * 14), true);\n            ImGui::Text(\"Press CTRL+A and see who receives it!\");\n            ImGui::Separator();\n\n            // 1: Window polling for CTRL+A\n            ImGui::Text(\"(in WindowA)\");\n            ImGui::Text(\"IsWindowFocused: %d, Shortcut: %s\", ImGui::IsWindowFocused(), ImGui::Shortcut(key_chord, flags) ? \"PRESSED\" : \"...\");\n\n            // 2: InputText also polling for CTRL+A: it always uses _RouteFocused internally (gets priority when active)\n            // (Commmented because the owner-aware version of Shortcut() is still in imgui_internal.h)\n            //char str[16] = \"Press CTRL+A\";\n            //ImGui::Spacing();\n            //ImGui::InputText(\"InputTextB\", str, IM_ARRAYSIZE(str), ImGuiInputTextFlags_ReadOnly);\n            //ImGuiID item_id = ImGui::GetItemID();\n            //ImGui::SameLine(); HelpMarker(\"Internal widgets always use _RouteFocused\");\n            //ImGui::Text(\"IsWindowFocused: %d, Shortcut: %s\", ImGui::IsWindowFocused(), ImGui::Shortcut(key_chord, flags, item_id) ? \"PRESSED\" : \"...\");\n\n            // 3: Dummy child is not claiming the route: focusing them shouldn't steal route away from WindowA\n            ImGui::BeginChild(\"ChildD\", ImVec2(-FLT_MIN, line_height * 4), true);\n            ImGui::Text(\"(in ChildD: not using same Shortcut)\");\n            ImGui::Text(\"IsWindowFocused: %d\", ImGui::IsWindowFocused());\n            ImGui::EndChild();\n\n            // 4: Child window polling for CTRL+A. It is deeper than WindowA and gets priority when focused.\n            ImGui::BeginChild(\"ChildE\", ImVec2(-FLT_MIN, line_height * 4), true);\n            ImGui::Text(\"(in ChildE: using same Shortcut)\");\n            ImGui::Text(\"IsWindowFocused: %d, Shortcut: %s\", ImGui::IsWindowFocused(), ImGui::Shortcut(key_chord, flags) ? \"PRESSED\" : \"...\");\n            ImGui::EndChild();\n\n            // 5: In a popup\n            if (ImGui::Button(\"Open Popup\"))\n                ImGui::OpenPopup(\"PopupF\");\n            if (ImGui::BeginPopup(\"PopupF\"))\n            {\n                ImGui::Text(\"(in PopupF)\");\n                ImGui::Text(\"IsWindowFocused: %d, Shortcut: %s\", ImGui::IsWindowFocused(), ImGui::Shortcut(key_chord, flags) ? \"PRESSED\" : \"...\");\n                // (Commmented because the owner-aware version of Shortcut() is still in imgui_internal.h)\n                //ImGui::InputText(\"InputTextG\", str, IM_ARRAYSIZE(str), ImGuiInputTextFlags_ReadOnly);\n                //ImGui::Text(\"IsWindowFocused: %d, Shortcut: %s\", ImGui::IsWindowFocused(), ImGui::Shortcut(key_chord, flags, ImGui::GetItemID()) ? \"PRESSED\" : \"...\");\n                ImGui::EndPopup();\n            }\n            ImGui::EndChild();\n            ImGui::PopStyleColor();\n\n            ImGui::TreePop();\n        }\n\n        // Display mouse cursors\n        IMGUI_DEMO_MARKER(\"Inputs & Focus/Mouse Cursors\");\n        if (ImGui::TreeNode(\"Mouse Cursors\"))\n        {\n            const char* mouse_cursors_names[] = { \"Arrow\", \"TextInput\", \"ResizeAll\", \"ResizeNS\", \"ResizeEW\", \"ResizeNESW\", \"ResizeNWSE\", \"Hand\", \"Wait\", \"Progress\", \"NotAllowed\" };\n            IM_ASSERT(IM_ARRAYSIZE(mouse_cursors_names) == ImGuiMouseCursor_COUNT);\n\n            ImGuiMouseCursor current = ImGui::GetMouseCursor();\n            const char* cursor_name = (current >= ImGuiMouseCursor_Arrow) && (current < ImGuiMouseCursor_COUNT) ? mouse_cursors_names[current] : \"N/A\";\n            ImGui::Text(\"Current mouse cursor = %d: %s\", current, cursor_name);\n            ImGui::BeginDisabled(true);\n            ImGui::CheckboxFlags(\"io.BackendFlags: HasMouseCursors\", &io.BackendFlags, ImGuiBackendFlags_HasMouseCursors);\n            ImGui::EndDisabled();\n\n            ImGui::Text(\"Hover to see mouse cursors:\");\n            ImGui::SameLine(); HelpMarker(\n                \"Your application can render a different mouse cursor based on what ImGui::GetMouseCursor() returns. \"\n                \"If software cursor rendering (io.MouseDrawCursor) is set ImGui will draw the right cursor for you, \"\n                \"otherwise your backend needs to handle it.\");\n            for (int i = 0; i < ImGuiMouseCursor_COUNT; i++)\n            {\n                char label[32];\n                sprintf(label, \"Mouse cursor %d: %s\", i, mouse_cursors_names[i]);\n                ImGui::Bullet(); ImGui::Selectable(label, false);\n                if (ImGui::IsItemHovered())\n                    ImGui::SetMouseCursor(i);\n            }\n            ImGui::TreePop();\n        }\n\n        IMGUI_DEMO_MARKER(\"Inputs & Focus/Tabbing\");\n        if (ImGui::TreeNode(\"Tabbing\"))\n        {\n            ImGui::Text(\"Use TAB/SHIFT+TAB to cycle through keyboard editable fields.\");\n            static char buf[32] = \"hello\";\n            ImGui::InputText(\"1\", buf, IM_ARRAYSIZE(buf));\n            ImGui::InputText(\"2\", buf, IM_ARRAYSIZE(buf));\n            ImGui::InputText(\"3\", buf, IM_ARRAYSIZE(buf));\n            ImGui::PushItemFlag(ImGuiItemFlags_NoTabStop, true);\n            ImGui::InputText(\"4 (tab skip)\", buf, IM_ARRAYSIZE(buf));\n            ImGui::SameLine(); HelpMarker(\"Item won't be cycled through when using TAB or Shift+Tab.\");\n            ImGui::PopItemFlag();\n            ImGui::InputText(\"5\", buf, IM_ARRAYSIZE(buf));\n            ImGui::TreePop();\n        }\n\n        IMGUI_DEMO_MARKER(\"Inputs & Focus/Focus from code\");\n        if (ImGui::TreeNode(\"Focus from code\"))\n        {\n            bool focus_1 = ImGui::Button(\"Focus on 1\"); ImGui::SameLine();\n            bool focus_2 = ImGui::Button(\"Focus on 2\"); ImGui::SameLine();\n            bool focus_3 = ImGui::Button(\"Focus on 3\");\n            int has_focus = 0;\n            static char buf[128] = \"click on a button to set focus\";\n\n            if (focus_1) ImGui::SetKeyboardFocusHere();\n            ImGui::InputText(\"1\", buf, IM_ARRAYSIZE(buf));\n            if (ImGui::IsItemActive()) has_focus = 1;\n\n            if (focus_2) ImGui::SetKeyboardFocusHere();\n            ImGui::InputText(\"2\", buf, IM_ARRAYSIZE(buf));\n            if (ImGui::IsItemActive()) has_focus = 2;\n\n            ImGui::PushItemFlag(ImGuiItemFlags_NoTabStop, true);\n            if (focus_3) ImGui::SetKeyboardFocusHere();\n            ImGui::InputText(\"3 (tab skip)\", buf, IM_ARRAYSIZE(buf));\n            if (ImGui::IsItemActive()) has_focus = 3;\n            ImGui::SameLine(); HelpMarker(\"Item won't be cycled through when using TAB or Shift+Tab.\");\n            ImGui::PopItemFlag();\n\n            if (has_focus)\n                ImGui::Text(\"Item with focus: %d\", has_focus);\n            else\n                ImGui::Text(\"Item with focus: <none>\");\n\n            // Use >= 0 parameter to SetKeyboardFocusHere() to focus an upcoming item\n            static float f3[3] = { 0.0f, 0.0f, 0.0f };\n            int focus_ahead = -1;\n            if (ImGui::Button(\"Focus on X\")) { focus_ahead = 0; } ImGui::SameLine();\n            if (ImGui::Button(\"Focus on Y\")) { focus_ahead = 1; } ImGui::SameLine();\n            if (ImGui::Button(\"Focus on Z\")) { focus_ahead = 2; }\n            if (focus_ahead != -1) ImGui::SetKeyboardFocusHere(focus_ahead);\n            ImGui::SliderFloat3(\"Float3\", &f3[0], 0.0f, 1.0f);\n\n            ImGui::TextWrapped(\"NB: Cursor & selection are preserved when refocusing last used item in code.\");\n            ImGui::TreePop();\n        }\n\n        IMGUI_DEMO_MARKER(\"Inputs & Focus/Dragging\");\n        if (ImGui::TreeNode(\"Dragging\"))\n        {\n            ImGui::TextWrapped(\"You can use ImGui::GetMouseDragDelta(0) to query for the dragged amount on any widget.\");\n            for (int button = 0; button < 3; button++)\n            {\n                ImGui::Text(\"IsMouseDragging(%d):\", button);\n                ImGui::Text(\"  w/ default threshold: %d,\", ImGui::IsMouseDragging(button));\n                ImGui::Text(\"  w/ zero threshold: %d,\", ImGui::IsMouseDragging(button, 0.0f));\n                ImGui::Text(\"  w/ large threshold: %d,\", ImGui::IsMouseDragging(button, 20.0f));\n            }\n\n            ImGui::Button(\"Drag Me\");\n            if (ImGui::IsItemActive())\n                ImGui::GetForegroundDrawList()->AddLine(io.MouseClickedPos[0], io.MousePos, ImGui::GetColorU32(ImGuiCol_Button), 4.0f); // Draw a line between the button and the mouse cursor\n\n            // Drag operations gets \"unlocked\" when the mouse has moved past a certain threshold\n            // (the default threshold is stored in io.MouseDragThreshold). You can request a lower or higher\n            // threshold using the second parameter of IsMouseDragging() and GetMouseDragDelta().\n            ImVec2 value_raw = ImGui::GetMouseDragDelta(0, 0.0f);\n            ImVec2 value_with_lock_threshold = ImGui::GetMouseDragDelta(0);\n            ImVec2 mouse_delta = io.MouseDelta;\n            ImGui::Text(\"GetMouseDragDelta(0):\");\n            ImGui::Text(\"  w/ default threshold: (%.1f, %.1f)\", value_with_lock_threshold.x, value_with_lock_threshold.y);\n            ImGui::Text(\"  w/ zero threshold: (%.1f, %.1f)\", value_raw.x, value_raw.y);\n            ImGui::Text(\"io.MouseDelta: (%.1f, %.1f)\", mouse_delta.x, mouse_delta.y);\n            ImGui::TreePop();\n        }\n    }\n}\n\n//-----------------------------------------------------------------------------\n// [SECTION] About Window / ShowAboutWindow()\n// Access from Dear ImGui Demo -> Tools -> About\n//-----------------------------------------------------------------------------\n\nvoid ImGui::ShowAboutWindow(bool* p_open)\n{\n    if (!ImGui::Begin(\"About Dear ImGui\", p_open, ImGuiWindowFlags_AlwaysAutoResize))\n    {\n        ImGui::End();\n        return;\n    }\n    IMGUI_DEMO_MARKER(\"Tools/About Dear ImGui\");\n    ImGui::Text(\"Dear ImGui %s (%d)\", IMGUI_VERSION, IMGUI_VERSION_NUM);\n\n    ImGui::TextLinkOpenURL(\"Homepage\", \"https://github.com/ocornut/imgui\");\n    ImGui::SameLine();\n    ImGui::TextLinkOpenURL(\"FAQ\", \"https://github.com/ocornut/imgui/blob/master/docs/FAQ.md\");\n    ImGui::SameLine();\n    ImGui::TextLinkOpenURL(\"Wiki\", \"https://github.com/ocornut/imgui/wiki\");\n    ImGui::SameLine();\n    ImGui::TextLinkOpenURL(\"Releases\", \"https://github.com/ocornut/imgui/releases\");\n    ImGui::SameLine();\n    ImGui::TextLinkOpenURL(\"Funding\", \"https://github.com/ocornut/imgui/wiki/Funding\");\n\n    ImGui::Separator();\n    ImGui::Text(\"(c) 2014-2025 Omar Cornut\");\n    ImGui::Text(\"Developed by Omar Cornut and all Dear ImGui contributors.\");\n    ImGui::Text(\"Dear ImGui is licensed under the MIT License, see LICENSE for more information.\");\n    ImGui::Text(\"If your company uses this, please consider funding the project.\");\n\n    static bool show_config_info = false;\n    ImGui::Checkbox(\"Config/Build Information\", &show_config_info);\n    if (show_config_info)\n    {\n        ImGuiIO& io = ImGui::GetIO();\n        ImGuiStyle& style = ImGui::GetStyle();\n\n        bool copy_to_clipboard = ImGui::Button(\"Copy to clipboard\");\n        ImVec2 child_size = ImVec2(0, ImGui::GetTextLineHeightWithSpacing() * 18);\n        ImGui::BeginChild(ImGui::GetID(\"cfg_infos\"), child_size, ImGuiChildFlags_FrameStyle);\n        if (copy_to_clipboard)\n        {\n            ImGui::LogToClipboard();\n            ImGui::LogText(\"```\\n\"); // Back quotes will make text appears without formatting when pasting on GitHub\n        }\n\n        ImGui::Text(\"Dear ImGui %s (%d)\", IMGUI_VERSION, IMGUI_VERSION_NUM);\n        ImGui::Separator();\n        ImGui::Text(\"sizeof(size_t): %d, sizeof(ImDrawIdx): %d, sizeof(ImDrawVert): %d\", (int)sizeof(size_t), (int)sizeof(ImDrawIdx), (int)sizeof(ImDrawVert));\n        ImGui::Text(\"define: __cplusplus=%d\", (int)__cplusplus);\n#ifdef IMGUI_DISABLE_OBSOLETE_FUNCTIONS\n        ImGui::Text(\"define: IMGUI_DISABLE_OBSOLETE_FUNCTIONS\");\n#endif\n#ifdef IMGUI_DISABLE_WIN32_DEFAULT_CLIPBOARD_FUNCTIONS\n        ImGui::Text(\"define: IMGUI_DISABLE_WIN32_DEFAULT_CLIPBOARD_FUNCTIONS\");\n#endif\n#ifdef IMGUI_DISABLE_WIN32_DEFAULT_IME_FUNCTIONS\n        ImGui::Text(\"define: IMGUI_DISABLE_WIN32_DEFAULT_IME_FUNCTIONS\");\n#endif\n#ifdef IMGUI_DISABLE_WIN32_FUNCTIONS\n        ImGui::Text(\"define: IMGUI_DISABLE_WIN32_FUNCTIONS\");\n#endif\n#ifdef IMGUI_DISABLE_DEFAULT_SHELL_FUNCTIONS\n        ImGui::Text(\"define: IMGUI_DISABLE_DEFAULT_SHELL_FUNCTIONS\");\n#endif\n#ifdef IMGUI_DISABLE_DEFAULT_FORMAT_FUNCTIONS\n        ImGui::Text(\"define: IMGUI_DISABLE_DEFAULT_FORMAT_FUNCTIONS\");\n#endif\n#ifdef IMGUI_DISABLE_DEFAULT_MATH_FUNCTIONS\n        ImGui::Text(\"define: IMGUI_DISABLE_DEFAULT_MATH_FUNCTIONS\");\n#endif\n#ifdef IMGUI_DISABLE_DEFAULT_FILE_FUNCTIONS\n        ImGui::Text(\"define: IMGUI_DISABLE_DEFAULT_FILE_FUNCTIONS\");\n#endif\n#ifdef IMGUI_DISABLE_FILE_FUNCTIONS\n        ImGui::Text(\"define: IMGUI_DISABLE_FILE_FUNCTIONS\");\n#endif\n#ifdef IMGUI_DISABLE_DEFAULT_ALLOCATORS\n        ImGui::Text(\"define: IMGUI_DISABLE_DEFAULT_ALLOCATORS\");\n#endif\n#ifdef IMGUI_USE_BGRA_PACKED_COLOR\n        ImGui::Text(\"define: IMGUI_USE_BGRA_PACKED_COLOR\");\n#endif\n#ifdef _WIN32\n        ImGui::Text(\"define: _WIN32\");\n#endif\n#ifdef _WIN64\n        ImGui::Text(\"define: _WIN64\");\n#endif\n#ifdef __linux__\n        ImGui::Text(\"define: __linux__\");\n#endif\n#ifdef __APPLE__\n        ImGui::Text(\"define: __APPLE__\");\n#endif\n#ifdef _MSC_VER\n        ImGui::Text(\"define: _MSC_VER=%d\", _MSC_VER);\n#endif\n#ifdef _MSVC_LANG\n        ImGui::Text(\"define: _MSVC_LANG=%d\", (int)_MSVC_LANG);\n#endif\n#ifdef __MINGW32__\n        ImGui::Text(\"define: __MINGW32__\");\n#endif\n#ifdef __MINGW64__\n        ImGui::Text(\"define: __MINGW64__\");\n#endif\n#ifdef __GNUC__\n        ImGui::Text(\"define: __GNUC__=%d\", (int)__GNUC__);\n#endif\n#ifdef __clang_version__\n        ImGui::Text(\"define: __clang_version__=%s\", __clang_version__);\n#endif\n#ifdef __EMSCRIPTEN__\n        ImGui::Text(\"define: __EMSCRIPTEN__\");\n        ImGui::Text(\"Emscripten: %d.%d.%d\", __EMSCRIPTEN_major__, __EMSCRIPTEN_minor__, __EMSCRIPTEN_tiny__);\n#endif\n        ImGui::Separator();\n        ImGui::Text(\"io.BackendPlatformName: %s\", io.BackendPlatformName ? io.BackendPlatformName : \"NULL\");\n        ImGui::Text(\"io.BackendRendererName: %s\", io.BackendRendererName ? io.BackendRendererName : \"NULL\");\n        ImGui::Text(\"io.ConfigFlags: 0x%08X\", io.ConfigFlags);\n        if (io.ConfigFlags & ImGuiConfigFlags_NavEnableKeyboard)        ImGui::Text(\" NavEnableKeyboard\");\n        if (io.ConfigFlags & ImGuiConfigFlags_NavEnableGamepad)         ImGui::Text(\" NavEnableGamepad\");\n        if (io.ConfigFlags & ImGuiConfigFlags_NoMouse)                  ImGui::Text(\" NoMouse\");\n        if (io.ConfigFlags & ImGuiConfigFlags_NoMouseCursorChange)      ImGui::Text(\" NoMouseCursorChange\");\n        if (io.ConfigFlags & ImGuiConfigFlags_NoKeyboard)               ImGui::Text(\" NoKeyboard\");\n        if (io.MouseDrawCursor)                                         ImGui::Text(\"io.MouseDrawCursor\");\n        if (io.ConfigMacOSXBehaviors)                                   ImGui::Text(\"io.ConfigMacOSXBehaviors\");\n        if (io.ConfigNavMoveSetMousePos)                                ImGui::Text(\"io.ConfigNavMoveSetMousePos\");\n        if (io.ConfigNavCaptureKeyboard)                                ImGui::Text(\"io.ConfigNavCaptureKeyboard\");\n        if (io.ConfigInputTextCursorBlink)                              ImGui::Text(\"io.ConfigInputTextCursorBlink\");\n        if (io.ConfigWindowsResizeFromEdges)                            ImGui::Text(\"io.ConfigWindowsResizeFromEdges\");\n        if (io.ConfigWindowsMoveFromTitleBarOnly)                       ImGui::Text(\"io.ConfigWindowsMoveFromTitleBarOnly\");\n        if (io.ConfigMemoryCompactTimer >= 0.0f)                        ImGui::Text(\"io.ConfigMemoryCompactTimer = %.1f\", io.ConfigMemoryCompactTimer);\n        ImGui::Text(\"io.BackendFlags: 0x%08X\", io.BackendFlags);\n        if (io.BackendFlags & ImGuiBackendFlags_HasGamepad)             ImGui::Text(\" HasGamepad\");\n        if (io.BackendFlags & ImGuiBackendFlags_HasMouseCursors)        ImGui::Text(\" HasMouseCursors\");\n        if (io.BackendFlags & ImGuiBackendFlags_HasSetMousePos)         ImGui::Text(\" HasSetMousePos\");\n        if (io.BackendFlags & ImGuiBackendFlags_RendererHasVtxOffset)   ImGui::Text(\" RendererHasVtxOffset\");\n        ImGui::Separator();\n        ImGui::Text(\"io.Fonts: %d fonts, Flags: 0x%08X, TexSize: %d,%d\", io.Fonts->Fonts.Size, io.Fonts->Flags, io.Fonts->TexWidth, io.Fonts->TexHeight);\n        ImGui::Text(\"io.DisplaySize: %.2f,%.2f\", io.DisplaySize.x, io.DisplaySize.y);\n        ImGui::Text(\"io.DisplayFramebufferScale: %.2f,%.2f\", io.DisplayFramebufferScale.x, io.DisplayFramebufferScale.y);\n        ImGui::Separator();\n        ImGui::Text(\"style.WindowPadding: %.2f,%.2f\", style.WindowPadding.x, style.WindowPadding.y);\n        ImGui::Text(\"style.WindowBorderSize: %.2f\", style.WindowBorderSize);\n        ImGui::Text(\"style.FramePadding: %.2f,%.2f\", style.FramePadding.x, style.FramePadding.y);\n        ImGui::Text(\"style.FrameRounding: %.2f\", style.FrameRounding);\n        ImGui::Text(\"style.FrameBorderSize: %.2f\", style.FrameBorderSize);\n        ImGui::Text(\"style.ItemSpacing: %.2f,%.2f\", style.ItemSpacing.x, style.ItemSpacing.y);\n        ImGui::Text(\"style.ItemInnerSpacing: %.2f,%.2f\", style.ItemInnerSpacing.x, style.ItemInnerSpacing.y);\n\n        if (copy_to_clipboard)\n        {\n            ImGui::LogText(\"\\n```\\n\");\n            ImGui::LogFinish();\n        }\n        ImGui::EndChild();\n    }\n    ImGui::End();\n}\n\n//-----------------------------------------------------------------------------\n// [SECTION] Style Editor / ShowStyleEditor()\n//-----------------------------------------------------------------------------\n// - ShowFontSelector()\n// - ShowStyleSelector()\n// - ShowStyleEditor()\n//-----------------------------------------------------------------------------\n\n// Forward declare ShowFontAtlas() which isn't worth putting in public API yet\nnamespace ImGui { IMGUI_API void ShowFontAtlas(ImFontAtlas* atlas); }\n\n// Demo helper function to select among loaded fonts.\n// Here we use the regular BeginCombo()/EndCombo() api which is the more flexible one.\nvoid ImGui::ShowFontSelector(const char* label)\n{\n    ImGuiIO& io = ImGui::GetIO();\n    ImFont* font_current = ImGui::GetFont();\n    if (ImGui::BeginCombo(label, font_current->GetDebugName()))\n    {\n        for (ImFont* font : io.Fonts->Fonts)\n        {\n            ImGui::PushID((void*)font);\n            if (ImGui::Selectable(font->GetDebugName(), font == font_current))\n                io.FontDefault = font;\n            if (font == font_current)\n                ImGui::SetItemDefaultFocus();\n            ImGui::PopID();\n        }\n        ImGui::EndCombo();\n    }\n    ImGui::SameLine();\n    HelpMarker(\n        \"- Load additional fonts with io.Fonts->AddFontFromFileTTF().\\n\"\n        \"- The font atlas is built when calling io.Fonts->GetTexDataAsXXXX() or io.Fonts->Build().\\n\"\n        \"- Read FAQ and docs/FONTS.md for more details.\\n\"\n        \"- If you need to add/remove fonts at runtime (e.g. for DPI change), do it before calling NewFrame().\");\n}\n\n// Demo helper function to select among default colors. See ShowStyleEditor() for more advanced options.\n// Here we use the simplified Combo() api that packs items into a single literal string.\n// Useful for quick combo boxes where the choices are known locally.\nbool ImGui::ShowStyleSelector(const char* label)\n{\n    static int style_idx = -1;\n    if (ImGui::Combo(label, &style_idx, \"Dark\\0Light\\0Classic\\0\"))\n    {\n        switch (style_idx)\n        {\n        case 0: ImGui::StyleColorsDark(); break;\n        case 1: ImGui::StyleColorsLight(); break;\n        case 2: ImGui::StyleColorsClassic(); break;\n        }\n        return true;\n    }\n    return false;\n}\n\nvoid ImGui::ShowStyleEditor(ImGuiStyle* ref)\n{\n    IMGUI_DEMO_MARKER(\"Tools/Style Editor\");\n    // You can pass in a reference ImGuiStyle structure to compare to, revert to and save to\n    // (without a reference style pointer, we will use one compared locally as a reference)\n    ImGuiStyle& style = ImGui::GetStyle();\n    static ImGuiStyle ref_saved_style;\n\n    // Default to using internal storage as reference\n    static bool init = true;\n    if (init && ref == NULL)\n        ref_saved_style = style;\n    init = false;\n    if (ref == NULL)\n        ref = &ref_saved_style;\n\n    ImGui::PushItemWidth(ImGui::GetWindowWidth() * 0.50f);\n\n    if (ImGui::ShowStyleSelector(\"Colors##Selector\"))\n        ref_saved_style = style;\n    ImGui::ShowFontSelector(\"Fonts##Selector\");\n\n    // Simplified Settings (expose floating-pointer border sizes as boolean representing 0.0f or 1.0f)\n    if (ImGui::SliderFloat(\"FrameRounding\", &style.FrameRounding, 0.0f, 12.0f, \"%.0f\"))\n        style.GrabRounding = style.FrameRounding; // Make GrabRounding always the same value as FrameRounding\n    { bool border = (style.WindowBorderSize > 0.0f); if (ImGui::Checkbox(\"WindowBorder\", &border)) { style.WindowBorderSize = border ? 1.0f : 0.0f; } }\n    ImGui::SameLine();\n    { bool border = (style.FrameBorderSize > 0.0f);  if (ImGui::Checkbox(\"FrameBorder\",  &border)) { style.FrameBorderSize  = border ? 1.0f : 0.0f; } }\n    ImGui::SameLine();\n    { bool border = (style.PopupBorderSize > 0.0f);  if (ImGui::Checkbox(\"PopupBorder\",  &border)) { style.PopupBorderSize  = border ? 1.0f : 0.0f; } }\n\n    // Save/Revert button\n    if (ImGui::Button(\"Save Ref\"))\n        *ref = ref_saved_style = style;\n    ImGui::SameLine();\n    if (ImGui::Button(\"Revert Ref\"))\n        style = *ref;\n    ImGui::SameLine();\n    HelpMarker(\n        \"Save/Revert in local non-persistent storage. Default Colors definition are not affected. \"\n        \"Use \\\"Export\\\" below to save them somewhere.\");\n\n    ImGui::Separator();\n\n    if (ImGui::BeginTabBar(\"##tabs\", ImGuiTabBarFlags_None))\n    {\n        if (ImGui::BeginTabItem(\"Sizes\"))\n        {\n            ImGui::SeparatorText(\"Main\");\n            ImGui::SliderFloat2(\"WindowPadding\", (float*)&style.WindowPadding, 0.0f, 20.0f, \"%.0f\");\n            ImGui::SliderFloat2(\"FramePadding\", (float*)&style.FramePadding, 0.0f, 20.0f, \"%.0f\");\n            ImGui::SliderFloat2(\"ItemSpacing\", (float*)&style.ItemSpacing, 0.0f, 20.0f, \"%.0f\");\n            ImGui::SliderFloat2(\"ItemInnerSpacing\", (float*)&style.ItemInnerSpacing, 0.0f, 20.0f, \"%.0f\");\n            ImGui::SliderFloat2(\"TouchExtraPadding\", (float*)&style.TouchExtraPadding, 0.0f, 10.0f, \"%.0f\");\n            ImGui::SliderFloat(\"IndentSpacing\", &style.IndentSpacing, 0.0f, 30.0f, \"%.0f\");\n            ImGui::SliderFloat(\"ScrollbarSize\", &style.ScrollbarSize, 1.0f, 20.0f, \"%.0f\");\n            ImGui::SliderFloat(\"GrabMinSize\", &style.GrabMinSize, 1.0f, 20.0f, \"%.0f\");\n\n            ImGui::SeparatorText(\"Borders\");\n            ImGui::SliderFloat(\"WindowBorderSize\", &style.WindowBorderSize, 0.0f, 1.0f, \"%.0f\");\n            ImGui::SliderFloat(\"ChildBorderSize\", &style.ChildBorderSize, 0.0f, 1.0f, \"%.0f\");\n            ImGui::SliderFloat(\"PopupBorderSize\", &style.PopupBorderSize, 0.0f, 1.0f, \"%.0f\");\n            ImGui::SliderFloat(\"FrameBorderSize\", &style.FrameBorderSize, 0.0f, 1.0f, \"%.0f\");\n\n            ImGui::SeparatorText(\"Rounding\");\n            ImGui::SliderFloat(\"WindowRounding\", &style.WindowRounding, 0.0f, 12.0f, \"%.0f\");\n            ImGui::SliderFloat(\"ChildRounding\", &style.ChildRounding, 0.0f, 12.0f, \"%.0f\");\n            ImGui::SliderFloat(\"FrameRounding\", &style.FrameRounding, 0.0f, 12.0f, \"%.0f\");\n            ImGui::SliderFloat(\"PopupRounding\", &style.PopupRounding, 0.0f, 12.0f, \"%.0f\");\n            ImGui::SliderFloat(\"ScrollbarRounding\", &style.ScrollbarRounding, 0.0f, 12.0f, \"%.0f\");\n            ImGui::SliderFloat(\"GrabRounding\", &style.GrabRounding, 0.0f, 12.0f, \"%.0f\");\n\n            ImGui::SeparatorText(\"Tabs\");\n            ImGui::SliderFloat(\"TabBorderSize\", &style.TabBorderSize, 0.0f, 1.0f, \"%.0f\");\n            ImGui::SliderFloat(\"TabBarBorderSize\", &style.TabBarBorderSize, 0.0f, 2.0f, \"%.0f\");\n            ImGui::SliderFloat(\"TabBarOverlineSize\", &style.TabBarOverlineSize, 0.0f, 3.0f, \"%.0f\");\n            ImGui::SameLine(); HelpMarker(\"Overline is only drawn over the selected tab when ImGuiTabBarFlags_DrawSelectedOverline is set.\");\n            ImGui::DragFloat(\"TabCloseButtonMinWidthSelected\", &style.TabCloseButtonMinWidthSelected, 0.1f, -1.0f, 100.0f, (style.TabCloseButtonMinWidthSelected < 0.0f) ? \"%.0f (Always)\" : \"%.0f\");\n            ImGui::DragFloat(\"TabCloseButtonMinWidthUnselected\", &style.TabCloseButtonMinWidthUnselected, 0.1f, -1.0f, 100.0f, (style.TabCloseButtonMinWidthUnselected < 0.0f) ? \"%.0f (Always)\" : \"%.0f\");\n            ImGui::SliderFloat(\"TabRounding\", &style.TabRounding, 0.0f, 12.0f, \"%.0f\");\n\n            ImGui::SeparatorText(\"Tables\");\n            ImGui::SliderFloat2(\"CellPadding\", (float*)&style.CellPadding, 0.0f, 20.0f, \"%.0f\");\n            ImGui::SliderAngle(\"TableAngledHeadersAngle\", &style.TableAngledHeadersAngle, -50.0f, +50.0f);\n            ImGui::SliderFloat2(\"TableAngledHeadersTextAlign\", (float*)&style.TableAngledHeadersTextAlign, 0.0f, 1.0f, \"%.2f\");\n\n            ImGui::SeparatorText(\"Windows\");\n            ImGui::SliderFloat2(\"WindowTitleAlign\", (float*)&style.WindowTitleAlign, 0.0f, 1.0f, \"%.2f\");\n            ImGui::SliderFloat(\"WindowBorderHoverPadding\", &style.WindowBorderHoverPadding, 1.0f, 20.0f, \"%.0f\");\n            int window_menu_button_position = style.WindowMenuButtonPosition + 1;\n            if (ImGui::Combo(\"WindowMenuButtonPosition\", (int*)&window_menu_button_position, \"None\\0Left\\0Right\\0\"))\n                style.WindowMenuButtonPosition = (ImGuiDir)(window_menu_button_position - 1);\n\n            ImGui::SeparatorText(\"Widgets\");\n            ImGui::Combo(\"ColorButtonPosition\", (int*)&style.ColorButtonPosition, \"Left\\0Right\\0\");\n            ImGui::SliderFloat2(\"ButtonTextAlign\", (float*)&style.ButtonTextAlign, 0.0f, 1.0f, \"%.2f\");\n            ImGui::SameLine(); HelpMarker(\"Alignment applies when a button is larger than its text content.\");\n            ImGui::SliderFloat2(\"SelectableTextAlign\", (float*)&style.SelectableTextAlign, 0.0f, 1.0f, \"%.2f\");\n            ImGui::SameLine(); HelpMarker(\"Alignment applies when a selectable is larger than its text content.\");\n            ImGui::SliderFloat(\"SeparatorTextBorderSize\", &style.SeparatorTextBorderSize, 0.0f, 10.0f, \"%.0f\");\n            ImGui::SliderFloat2(\"SeparatorTextAlign\", (float*)&style.SeparatorTextAlign, 0.0f, 1.0f, \"%.2f\");\n            ImGui::SliderFloat2(\"SeparatorTextPadding\", (float*)&style.SeparatorTextPadding, 0.0f, 40.0f, \"%.0f\");\n            ImGui::SliderFloat(\"LogSliderDeadzone\", &style.LogSliderDeadzone, 0.0f, 12.0f, \"%.0f\");\n            ImGui::SliderFloat(\"ImageBorderSize\", &style.ImageBorderSize, 0.0f, 1.0f, \"%.0f\");\n\n            ImGui::SeparatorText(\"Tooltips\");\n            for (int n = 0; n < 2; n++)\n                if (ImGui::TreeNodeEx(n == 0 ? \"HoverFlagsForTooltipMouse\" : \"HoverFlagsForTooltipNav\"))\n                {\n                    ImGuiHoveredFlags* p = (n == 0) ? &style.HoverFlagsForTooltipMouse : &style.HoverFlagsForTooltipNav;\n                    ImGui::CheckboxFlags(\"ImGuiHoveredFlags_DelayNone\", p, ImGuiHoveredFlags_DelayNone);\n                    ImGui::CheckboxFlags(\"ImGuiHoveredFlags_DelayShort\", p, ImGuiHoveredFlags_DelayShort);\n                    ImGui::CheckboxFlags(\"ImGuiHoveredFlags_DelayNormal\", p, ImGuiHoveredFlags_DelayNormal);\n                    ImGui::CheckboxFlags(\"ImGuiHoveredFlags_Stationary\", p, ImGuiHoveredFlags_Stationary);\n                    ImGui::CheckboxFlags(\"ImGuiHoveredFlags_NoSharedDelay\", p, ImGuiHoveredFlags_NoSharedDelay);\n                    ImGui::TreePop();\n                }\n\n            ImGui::SeparatorText(\"Misc\");\n            ImGui::SliderFloat2(\"DisplayWindowPadding\", (float*)&style.DisplayWindowPadding, 0.0f, 30.0f, \"%.0f\"); ImGui::SameLine(); HelpMarker(\"Apply to regular windows: amount which we enforce to keep visible when moving near edges of your screen.\");\n            ImGui::SliderFloat2(\"DisplaySafeAreaPadding\", (float*)&style.DisplaySafeAreaPadding, 0.0f, 30.0f, \"%.0f\"); ImGui::SameLine(); HelpMarker(\"Apply to every windows, menus, popups, tooltips: amount where we avoid displaying contents. Adjust if you cannot see the edges of your screen (e.g. on a TV where scaling has not been configured).\");\n\n            ImGui::EndTabItem();\n        }\n\n        if (ImGui::BeginTabItem(\"Colors\"))\n        {\n            static int output_dest = 0;\n            static bool output_only_modified = true;\n            if (ImGui::Button(\"Export\"))\n            {\n                if (output_dest == 0)\n                    ImGui::LogToClipboard();\n                else\n                    ImGui::LogToTTY();\n                ImGui::LogText(\"ImVec4* colors = ImGui::GetStyle().Colors;\" IM_NEWLINE);\n                for (int i = 0; i < ImGuiCol_COUNT; i++)\n                {\n                    const ImVec4& col = style.Colors[i];\n                    const char* name = ImGui::GetStyleColorName(i);\n                    if (!output_only_modified || memcmp(&col, &ref->Colors[i], sizeof(ImVec4)) != 0)\n                        ImGui::LogText(\"colors[ImGuiCol_%s]%*s= ImVec4(%.2ff, %.2ff, %.2ff, %.2ff);\" IM_NEWLINE,\n                            name, 23 - (int)strlen(name), \"\", col.x, col.y, col.z, col.w);\n                }\n                ImGui::LogFinish();\n            }\n            ImGui::SameLine(); ImGui::SetNextItemWidth(120); ImGui::Combo(\"##output_type\", &output_dest, \"To Clipboard\\0To TTY\\0\");\n            ImGui::SameLine(); ImGui::Checkbox(\"Only Modified Colors\", &output_only_modified);\n\n            static ImGuiTextFilter filter;\n            filter.Draw(\"Filter colors\", ImGui::GetFontSize() * 16);\n\n            static ImGuiColorEditFlags alpha_flags = 0;\n            if (ImGui::RadioButton(\"Opaque\", alpha_flags == ImGuiColorEditFlags_AlphaOpaque))       { alpha_flags = ImGuiColorEditFlags_AlphaOpaque; } ImGui::SameLine();\n            if (ImGui::RadioButton(\"Alpha\",  alpha_flags == ImGuiColorEditFlags_None))              { alpha_flags = ImGuiColorEditFlags_None; } ImGui::SameLine();\n            if (ImGui::RadioButton(\"Both\",   alpha_flags == ImGuiColorEditFlags_AlphaPreviewHalf))  { alpha_flags = ImGuiColorEditFlags_AlphaPreviewHalf; } ImGui::SameLine();\n            HelpMarker(\n                \"In the color list:\\n\"\n                \"Left-click on color square to open color picker,\\n\"\n                \"Right-click to open edit options menu.\");\n\n            ImGui::SetNextWindowSizeConstraints(ImVec2(0.0f, ImGui::GetTextLineHeightWithSpacing() * 10), ImVec2(FLT_MAX, FLT_MAX));\n            ImGui::BeginChild(\"##colors\", ImVec2(0, 0), ImGuiChildFlags_Borders | ImGuiChildFlags_NavFlattened, ImGuiWindowFlags_AlwaysVerticalScrollbar | ImGuiWindowFlags_AlwaysHorizontalScrollbar);\n            ImGui::PushItemWidth(ImGui::GetFontSize() * -12);\n            for (int i = 0; i < ImGuiCol_COUNT; i++)\n            {\n                const char* name = ImGui::GetStyleColorName(i);\n                if (!filter.PassFilter(name))\n                    continue;\n                ImGui::PushID(i);\n#ifndef IMGUI_DISABLE_DEBUG_TOOLS\n                if (ImGui::Button(\"?\"))\n                    ImGui::DebugFlashStyleColor((ImGuiCol)i);\n                ImGui::SetItemTooltip(\"Flash given color to identify places where it is used.\");\n                ImGui::SameLine();\n#endif\n                ImGui::ColorEdit4(\"##color\", (float*)&style.Colors[i], ImGuiColorEditFlags_AlphaBar | alpha_flags);\n                if (memcmp(&style.Colors[i], &ref->Colors[i], sizeof(ImVec4)) != 0)\n                {\n                    // Tips: in a real user application, you may want to merge and use an icon font into the main font,\n                    // so instead of \"Save\"/\"Revert\" you'd use icons!\n                    // Read the FAQ and docs/FONTS.md about using icon fonts. It's really easy and super convenient!\n                    ImGui::SameLine(0.0f, style.ItemInnerSpacing.x); if (ImGui::Button(\"Save\")) { ref->Colors[i] = style.Colors[i]; }\n                    ImGui::SameLine(0.0f, style.ItemInnerSpacing.x); if (ImGui::Button(\"Revert\")) { style.Colors[i] = ref->Colors[i]; }\n                }\n                ImGui::SameLine(0.0f, style.ItemInnerSpacing.x);\n                ImGui::TextUnformatted(name);\n                ImGui::PopID();\n            }\n            ImGui::PopItemWidth();\n            ImGui::EndChild();\n\n            ImGui::EndTabItem();\n        }\n\n        if (ImGui::BeginTabItem(\"Fonts\"))\n        {\n            ImGuiIO& io = ImGui::GetIO();\n            ImFontAtlas* atlas = io.Fonts;\n            HelpMarker(\"Read FAQ and docs/FONTS.md for details on font loading.\");\n            ImGui::ShowFontAtlas(atlas);\n\n            // Post-baking font scaling. Note that this is NOT the nice way of scaling fonts, read below.\n            // (we enforce hard clamping manually as by default DragFloat/SliderFloat allows CTRL+Click text to get out of bounds).\n            const float MIN_SCALE = 0.3f;\n            const float MAX_SCALE = 2.0f;\n            HelpMarker(\n                \"Those are old settings provided for convenience.\\n\"\n                \"However, the _correct_ way of scaling your UI is currently to reload your font at the designed size, \"\n                \"rebuild the font atlas, and call style.ScaleAllSizes() on a reference ImGuiStyle structure.\\n\"\n                \"Using those settings here will give you poor quality results.\");\n            static float window_scale = 1.0f;\n            ImGui::PushItemWidth(ImGui::GetFontSize() * 8);\n            if (ImGui::DragFloat(\"window scale\", &window_scale, 0.005f, MIN_SCALE, MAX_SCALE, \"%.2f\", ImGuiSliderFlags_AlwaysClamp)) // Scale only this window\n                ImGui::SetWindowFontScale(window_scale);\n            ImGui::DragFloat(\"global scale\", &io.FontGlobalScale, 0.005f, MIN_SCALE, MAX_SCALE, \"%.2f\", ImGuiSliderFlags_AlwaysClamp); // Scale everything\n            ImGui::PopItemWidth();\n\n            ImGui::EndTabItem();\n        }\n\n        if (ImGui::BeginTabItem(\"Rendering\"))\n        {\n            ImGui::Checkbox(\"Anti-aliased lines\", &style.AntiAliasedLines);\n            ImGui::SameLine();\n            HelpMarker(\"When disabling anti-aliasing lines, you'll probably want to disable borders in your style as well.\");\n\n            ImGui::Checkbox(\"Anti-aliased lines use texture\", &style.AntiAliasedLinesUseTex);\n            ImGui::SameLine();\n            HelpMarker(\"Faster lines using texture data. Require backend to render with bilinear filtering (not point/nearest filtering).\");\n\n            ImGui::Checkbox(\"Anti-aliased fill\", &style.AntiAliasedFill);\n            ImGui::PushItemWidth(ImGui::GetFontSize() * 8);\n            ImGui::DragFloat(\"Curve Tessellation Tolerance\", &style.CurveTessellationTol, 0.02f, 0.10f, 10.0f, \"%.2f\");\n            if (style.CurveTessellationTol < 0.10f) style.CurveTessellationTol = 0.10f;\n\n            // When editing the \"Circle Segment Max Error\" value, draw a preview of its effect on auto-tessellated circles.\n            ImGui::DragFloat(\"Circle Tessellation Max Error\", &style.CircleTessellationMaxError , 0.005f, 0.10f, 5.0f, \"%.2f\", ImGuiSliderFlags_AlwaysClamp);\n            const bool show_samples = ImGui::IsItemActive();\n            if (show_samples)\n                ImGui::SetNextWindowPos(ImGui::GetCursorScreenPos());\n            if (show_samples && ImGui::BeginTooltip())\n            {\n                ImGui::TextUnformatted(\"(R = radius, N = approx number of segments)\");\n                ImGui::Spacing();\n                ImDrawList* draw_list = ImGui::GetWindowDrawList();\n                const float min_widget_width = ImGui::CalcTextSize(\"R: MMM\\nN: MMM\").x;\n                for (int n = 0; n < 8; n++)\n                {\n                    const float RAD_MIN = 5.0f;\n                    const float RAD_MAX = 70.0f;\n                    const float rad = RAD_MIN + (RAD_MAX - RAD_MIN) * (float)n / (8.0f - 1.0f);\n\n                    ImGui::BeginGroup();\n\n                    // N is not always exact here due to how PathArcTo() function work internally\n                    ImGui::Text(\"R: %.f\\nN: %d\", rad, draw_list->_CalcCircleAutoSegmentCount(rad));\n\n                    const float canvas_width = IM_MAX(min_widget_width, rad * 2.0f);\n                    const float offset_x     = floorf(canvas_width * 0.5f);\n                    const float offset_y     = floorf(RAD_MAX);\n\n                    const ImVec2 p1 = ImGui::GetCursorScreenPos();\n                    draw_list->AddCircle(ImVec2(p1.x + offset_x, p1.y + offset_y), rad, ImGui::GetColorU32(ImGuiCol_Text));\n                    ImGui::Dummy(ImVec2(canvas_width, RAD_MAX * 2));\n\n                    /*\n                    const ImVec2 p2 = ImGui::GetCursorScreenPos();\n                    draw_list->AddCircleFilled(ImVec2(p2.x + offset_x, p2.y + offset_y), rad, ImGui::GetColorU32(ImGuiCol_Text));\n                    ImGui::Dummy(ImVec2(canvas_width, RAD_MAX * 2));\n                    */\n\n                    ImGui::EndGroup();\n                    ImGui::SameLine();\n                }\n                ImGui::EndTooltip();\n            }\n            ImGui::SameLine();\n            HelpMarker(\"When drawing circle primitives with \\\"num_segments == 0\\\" tessellation will be calculated automatically.\");\n\n            ImGui::DragFloat(\"Global Alpha\", &style.Alpha, 0.005f, 0.20f, 1.0f, \"%.2f\"); // Not exposing zero here so user doesn't \"lose\" the UI (zero alpha clips all widgets). But application code could have a toggle to switch between zero and non-zero.\n            ImGui::DragFloat(\"Disabled Alpha\", &style.DisabledAlpha, 0.005f, 0.0f, 1.0f, \"%.2f\"); ImGui::SameLine(); HelpMarker(\"Additional alpha multiplier for disabled items (multiply over current value of Alpha).\");\n            ImGui::PopItemWidth();\n\n            ImGui::EndTabItem();\n        }\n\n        ImGui::EndTabBar();\n    }\n\n    ImGui::PopItemWidth();\n}\n\n//-----------------------------------------------------------------------------\n// [SECTION] User Guide / ShowUserGuide()\n//-----------------------------------------------------------------------------\n\nvoid ImGui::ShowUserGuide()\n{\n    ImGuiIO& io = ImGui::GetIO();\n    ImGui::BulletText(\"Double-click on title bar to collapse window.\");\n    ImGui::BulletText(\n        \"Click and drag on lower corner to resize window\\n\"\n        \"(double-click to auto fit window to its contents).\");\n    ImGui::BulletText(\"CTRL+Click on a slider or drag box to input value as text.\");\n    ImGui::BulletText(\"TAB/SHIFT+TAB to cycle through keyboard editable fields.\");\n    ImGui::BulletText(\"CTRL+Tab to select a window.\");\n    if (io.FontAllowUserScaling)\n        ImGui::BulletText(\"CTRL+Mouse Wheel to zoom window contents.\");\n    ImGui::BulletText(\"While inputting text:\\n\");\n    ImGui::Indent();\n    ImGui::BulletText(\"CTRL+Left/Right to word jump.\");\n    ImGui::BulletText(\"CTRL+A or double-click to select all.\");\n    ImGui::BulletText(\"CTRL+X/C/V to use clipboard cut/copy/paste.\");\n    ImGui::BulletText(\"CTRL+Z to undo, CTRL+Y/CTRL+SHIFT+Z to redo.\");\n    ImGui::BulletText(\"ESCAPE to revert.\");\n    ImGui::Unindent();\n    ImGui::BulletText(\"With keyboard navigation enabled:\");\n    ImGui::Indent();\n    ImGui::BulletText(\"Arrow keys to navigate.\");\n    ImGui::BulletText(\"Space to activate a widget.\");\n    ImGui::BulletText(\"Return to input text into a widget.\");\n    ImGui::BulletText(\"Escape to deactivate a widget, close popup, exit child window.\");\n    ImGui::BulletText(\"Alt to jump to the menu layer of a window.\");\n    ImGui::Unindent();\n}\n\n//-----------------------------------------------------------------------------\n// [SECTION] Example App: Main Menu Bar / ShowExampleAppMainMenuBar()\n//-----------------------------------------------------------------------------\n// - ShowExampleAppMainMenuBar()\n// - ShowExampleMenuFile()\n//-----------------------------------------------------------------------------\n\n// Demonstrate creating a \"main\" fullscreen menu bar and populating it.\n// Note the difference between BeginMainMenuBar() and BeginMenuBar():\n// - BeginMenuBar() = menu-bar inside current window (which needs the ImGuiWindowFlags_MenuBar flag!)\n// - BeginMainMenuBar() = helper to create menu-bar-sized window at the top of the main viewport + call BeginMenuBar() into it.\nstatic void ShowExampleAppMainMenuBar()\n{\n    if (ImGui::BeginMainMenuBar())\n    {\n        if (ImGui::BeginMenu(\"File\"))\n        {\n            ShowExampleMenuFile();\n            ImGui::EndMenu();\n        }\n        if (ImGui::BeginMenu(\"Edit\"))\n        {\n            if (ImGui::MenuItem(\"Undo\", \"CTRL+Z\")) {}\n            if (ImGui::MenuItem(\"Redo\", \"CTRL+Y\", false, false)) {} // Disabled item\n            ImGui::Separator();\n            if (ImGui::MenuItem(\"Cut\", \"CTRL+X\")) {}\n            if (ImGui::MenuItem(\"Copy\", \"CTRL+C\")) {}\n            if (ImGui::MenuItem(\"Paste\", \"CTRL+V\")) {}\n            ImGui::EndMenu();\n        }\n        ImGui::EndMainMenuBar();\n    }\n}\n\n// Note that shortcuts are currently provided for display only\n// (future version will add explicit flags to BeginMenu() to request processing shortcuts)\nstatic void ShowExampleMenuFile()\n{\n    IMGUI_DEMO_MARKER(\"Examples/Menu\");\n    ImGui::MenuItem(\"(demo menu)\", NULL, false, false);\n    if (ImGui::MenuItem(\"New\")) {}\n    if (ImGui::MenuItem(\"Open\", \"Ctrl+O\")) {}\n    if (ImGui::BeginMenu(\"Open Recent\"))\n    {\n        ImGui::MenuItem(\"fish_hat.c\");\n        ImGui::MenuItem(\"fish_hat.inl\");\n        ImGui::MenuItem(\"fish_hat.h\");\n        if (ImGui::BeginMenu(\"More..\"))\n        {\n            ImGui::MenuItem(\"Hello\");\n            ImGui::MenuItem(\"Sailor\");\n            if (ImGui::BeginMenu(\"Recurse..\"))\n            {\n                ShowExampleMenuFile();\n                ImGui::EndMenu();\n            }\n            ImGui::EndMenu();\n        }\n        ImGui::EndMenu();\n    }\n    if (ImGui::MenuItem(\"Save\", \"Ctrl+S\")) {}\n    if (ImGui::MenuItem(\"Save As..\")) {}\n\n    ImGui::Separator();\n    IMGUI_DEMO_MARKER(\"Examples/Menu/Options\");\n    if (ImGui::BeginMenu(\"Options\"))\n    {\n        static bool enabled = true;\n        ImGui::MenuItem(\"Enabled\", \"\", &enabled);\n        ImGui::BeginChild(\"child\", ImVec2(0, 60), ImGuiChildFlags_Borders);\n        for (int i = 0; i < 10; i++)\n            ImGui::Text(\"Scrolling Text %d\", i);\n        ImGui::EndChild();\n        static float f = 0.5f;\n        static int n = 0;\n        ImGui::SliderFloat(\"Value\", &f, 0.0f, 1.0f);\n        ImGui::InputFloat(\"Input\", &f, 0.1f);\n        ImGui::Combo(\"Combo\", &n, \"Yes\\0No\\0Maybe\\0\\0\");\n        ImGui::EndMenu();\n    }\n\n    IMGUI_DEMO_MARKER(\"Examples/Menu/Colors\");\n    if (ImGui::BeginMenu(\"Colors\"))\n    {\n        float sz = ImGui::GetTextLineHeight();\n        for (int i = 0; i < ImGuiCol_COUNT; i++)\n        {\n            const char* name = ImGui::GetStyleColorName((ImGuiCol)i);\n            ImVec2 p = ImGui::GetCursorScreenPos();\n            ImGui::GetWindowDrawList()->AddRectFilled(p, ImVec2(p.x + sz, p.y + sz), ImGui::GetColorU32((ImGuiCol)i));\n            ImGui::Dummy(ImVec2(sz, sz));\n            ImGui::SameLine();\n            ImGui::MenuItem(name);\n        }\n        ImGui::EndMenu();\n    }\n\n    // Here we demonstrate appending again to the \"Options\" menu (which we already created above)\n    // Of course in this demo it is a little bit silly that this function calls BeginMenu(\"Options\") twice.\n    // In a real code-base using it would make senses to use this feature from very different code locations.\n    if (ImGui::BeginMenu(\"Options\")) // <-- Append!\n    {\n        IMGUI_DEMO_MARKER(\"Examples/Menu/Append to an existing menu\");\n        static bool b = true;\n        ImGui::Checkbox(\"SomeOption\", &b);\n        ImGui::EndMenu();\n    }\n\n    if (ImGui::BeginMenu(\"Disabled\", false)) // Disabled\n    {\n        IM_ASSERT(0);\n    }\n    if (ImGui::MenuItem(\"Checked\", NULL, true)) {}\n    ImGui::Separator();\n    if (ImGui::MenuItem(\"Quit\", \"Alt+F4\")) {}\n}\n\n//-----------------------------------------------------------------------------\n// [SECTION] Example App: Debug Console / ShowExampleAppConsole()\n//-----------------------------------------------------------------------------\n\n// Demonstrate creating a simple console window, with scrolling, filtering, completion and history.\n// For the console example, we are using a more C++ like approach of declaring a class to hold both data and functions.\nstruct ExampleAppConsole\n{\n    char                  InputBuf[256];\n    ImVector<char*>       Items;\n    ImVector<const char*> Commands;\n    ImVector<char*>       History;\n    int                   HistoryPos;    // -1: new line, 0..History.Size-1 browsing history.\n    ImGuiTextFilter       Filter;\n    bool                  AutoScroll;\n    bool                  ScrollToBottom;\n\n    ExampleAppConsole()\n    {\n        IMGUI_DEMO_MARKER(\"Examples/Console\");\n        ClearLog();\n        memset(InputBuf, 0, sizeof(InputBuf));\n        HistoryPos = -1;\n\n        // \"CLASSIFY\" is here to provide the test case where \"C\"+[tab] completes to \"CL\" and display multiple matches.\n        Commands.push_back(\"HELP\");\n        Commands.push_back(\"HISTORY\");\n        Commands.push_back(\"CLEAR\");\n        Commands.push_back(\"CLASSIFY\");\n        AutoScroll = true;\n        ScrollToBottom = false;\n        AddLog(\"Welcome to Dear ImGui!\");\n    }\n    ~ExampleAppConsole()\n    {\n        ClearLog();\n        for (int i = 0; i < History.Size; i++)\n            ImGui::MemFree(History[i]);\n    }\n\n    // Portable helpers\n    static int   Stricmp(const char* s1, const char* s2)         { int d; while ((d = toupper(*s2) - toupper(*s1)) == 0 && *s1) { s1++; s2++; } return d; }\n    static int   Strnicmp(const char* s1, const char* s2, int n) { int d = 0; while (n > 0 && (d = toupper(*s2) - toupper(*s1)) == 0 && *s1) { s1++; s2++; n--; } return d; }\n    static char* Strdup(const char* s)                           { IM_ASSERT(s); size_t len = strlen(s) + 1; void* buf = ImGui::MemAlloc(len); IM_ASSERT(buf); return (char*)memcpy(buf, (const void*)s, len); }\n    static void  Strtrim(char* s)                                { char* str_end = s + strlen(s); while (str_end > s && str_end[-1] == ' ') str_end--; *str_end = 0; }\n\n    void    ClearLog()\n    {\n        for (int i = 0; i < Items.Size; i++)\n            ImGui::MemFree(Items[i]);\n        Items.clear();\n    }\n\n    void    AddLog(const char* fmt, ...) IM_FMTARGS(2)\n    {\n        // FIXME-OPT\n        char buf[1024];\n        va_list args;\n        va_start(args, fmt);\n        vsnprintf(buf, IM_ARRAYSIZE(buf), fmt, args);\n        buf[IM_ARRAYSIZE(buf)-1] = 0;\n        va_end(args);\n        Items.push_back(Strdup(buf));\n    }\n\n    void    Draw(const char* title, bool* p_open)\n    {\n        ImGui::SetNextWindowSize(ImVec2(520, 600), ImGuiCond_FirstUseEver);\n        if (!ImGui::Begin(title, p_open))\n        {\n            ImGui::End();\n            return;\n        }\n\n        // As a specific feature guaranteed by the library, after calling Begin() the last Item represent the title bar.\n        // So e.g. IsItemHovered() will return true when hovering the title bar.\n        // Here we create a context menu only available from the title bar.\n        if (ImGui::BeginPopupContextItem())\n        {\n            if (ImGui::MenuItem(\"Close Console\"))\n                *p_open = false;\n            ImGui::EndPopup();\n        }\n\n        ImGui::TextWrapped(\n            \"This example implements a console with basic coloring, completion (TAB key) and history (Up/Down keys). A more elaborate \"\n            \"implementation may want to store entries along with extra data such as timestamp, emitter, etc.\");\n        ImGui::TextWrapped(\"Enter 'HELP' for help.\");\n\n        // TODO: display items starting from the bottom\n\n        if (ImGui::SmallButton(\"Add Debug Text\"))  { AddLog(\"%d some text\", Items.Size); AddLog(\"some more text\"); AddLog(\"display very important message here!\"); }\n        ImGui::SameLine();\n        if (ImGui::SmallButton(\"Add Debug Error\")) { AddLog(\"[error] something went wrong\"); }\n        ImGui::SameLine();\n        if (ImGui::SmallButton(\"Clear\"))           { ClearLog(); }\n        ImGui::SameLine();\n        bool copy_to_clipboard = ImGui::SmallButton(\"Copy\");\n        //static float t = 0.0f; if (ImGui::GetTime() - t > 0.02f) { t = ImGui::GetTime(); AddLog(\"Spam %f\", t); }\n\n        ImGui::Separator();\n\n        // Options menu\n        if (ImGui::BeginPopup(\"Options\"))\n        {\n            ImGui::Checkbox(\"Auto-scroll\", &AutoScroll);\n            ImGui::EndPopup();\n        }\n\n        // Options, Filter\n        ImGui::SetNextItemShortcut(ImGuiMod_Ctrl | ImGuiKey_O, ImGuiInputFlags_Tooltip);\n        if (ImGui::Button(\"Options\"))\n            ImGui::OpenPopup(\"Options\");\n        ImGui::SameLine();\n        Filter.Draw(\"Filter (\\\"incl,-excl\\\") (\\\"error\\\")\", 180);\n        ImGui::Separator();\n\n        // Reserve enough left-over height for 1 separator + 1 input text\n        const float footer_height_to_reserve = ImGui::GetStyle().ItemSpacing.y + ImGui::GetFrameHeightWithSpacing();\n        if (ImGui::BeginChild(\"ScrollingRegion\", ImVec2(0, -footer_height_to_reserve), ImGuiChildFlags_NavFlattened, ImGuiWindowFlags_HorizontalScrollbar))\n        {\n            if (ImGui::BeginPopupContextWindow())\n            {\n                if (ImGui::Selectable(\"Clear\")) ClearLog();\n                ImGui::EndPopup();\n            }\n\n            // Display every line as a separate entry so we can change their color or add custom widgets.\n            // If you only want raw text you can use ImGui::TextUnformatted(log.begin(), log.end());\n            // NB- if you have thousands of entries this approach may be too inefficient and may require user-side clipping\n            // to only process visible items. The clipper will automatically measure the height of your first item and then\n            // \"seek\" to display only items in the visible area.\n            // To use the clipper we can replace your standard loop:\n            //      for (int i = 0; i < Items.Size; i++)\n            //   With:\n            //      ImGuiListClipper clipper;\n            //      clipper.Begin(Items.Size);\n            //      while (clipper.Step())\n            //         for (int i = clipper.DisplayStart; i < clipper.DisplayEnd; i++)\n            // - That your items are evenly spaced (same height)\n            // - That you have cheap random access to your elements (you can access them given their index,\n            //   without processing all the ones before)\n            // You cannot this code as-is if a filter is active because it breaks the 'cheap random-access' property.\n            // We would need random-access on the post-filtered list.\n            // A typical application wanting coarse clipping and filtering may want to pre-compute an array of indices\n            // or offsets of items that passed the filtering test, recomputing this array when user changes the filter,\n            // and appending newly elements as they are inserted. This is left as a task to the user until we can manage\n            // to improve this example code!\n            // If your items are of variable height:\n            // - Split them into same height items would be simpler and facilitate random-seeking into your list.\n            // - Consider using manual call to IsRectVisible() and skipping extraneous decoration from your items.\n            ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(4, 1)); // Tighten spacing\n            if (copy_to_clipboard)\n                ImGui::LogToClipboard();\n            for (const char* item : Items)\n            {\n                if (!Filter.PassFilter(item))\n                    continue;\n\n                // Normally you would store more information in your item than just a string.\n                // (e.g. make Items[] an array of structure, store color/type etc.)\n                ImVec4 color;\n                bool has_color = false;\n                if (strstr(item, \"[error]\")) { color = ImVec4(1.0f, 0.4f, 0.4f, 1.0f); has_color = true; }\n                else if (strncmp(item, \"# \", 2) == 0) { color = ImVec4(1.0f, 0.8f, 0.6f, 1.0f); has_color = true; }\n                if (has_color)\n                    ImGui::PushStyleColor(ImGuiCol_Text, color);\n                ImGui::TextUnformatted(item);\n                if (has_color)\n                    ImGui::PopStyleColor();\n            }\n            if (copy_to_clipboard)\n                ImGui::LogFinish();\n\n            // Keep up at the bottom of the scroll region if we were already at the bottom at the beginning of the frame.\n            // Using a scrollbar or mouse-wheel will take away from the bottom edge.\n            if (ScrollToBottom || (AutoScroll && ImGui::GetScrollY() >= ImGui::GetScrollMaxY()))\n                ImGui::SetScrollHereY(1.0f);\n            ScrollToBottom = false;\n\n            ImGui::PopStyleVar();\n        }\n        ImGui::EndChild();\n        ImGui::Separator();\n\n        // Command-line\n        bool reclaim_focus = false;\n        ImGuiInputTextFlags input_text_flags = ImGuiInputTextFlags_EnterReturnsTrue | ImGuiInputTextFlags_EscapeClearsAll | ImGuiInputTextFlags_CallbackCompletion | ImGuiInputTextFlags_CallbackHistory;\n        if (ImGui::InputText(\"Input\", InputBuf, IM_ARRAYSIZE(InputBuf), input_text_flags, &TextEditCallbackStub, (void*)this))\n        {\n            char* s = InputBuf;\n            Strtrim(s);\n            if (s[0])\n                ExecCommand(s);\n            strcpy(s, \"\");\n            reclaim_focus = true;\n        }\n\n        // Auto-focus on window apparition\n        ImGui::SetItemDefaultFocus();\n        if (reclaim_focus)\n            ImGui::SetKeyboardFocusHere(-1); // Auto focus previous widget\n\n        ImGui::End();\n    }\n\n    void    ExecCommand(const char* command_line)\n    {\n        AddLog(\"# %s\\n\", command_line);\n\n        // Insert into history. First find match and delete it so it can be pushed to the back.\n        // This isn't trying to be smart or optimal.\n        HistoryPos = -1;\n        for (int i = History.Size - 1; i >= 0; i--)\n            if (Stricmp(History[i], command_line) == 0)\n            {\n                ImGui::MemFree(History[i]);\n                History.erase(History.begin() + i);\n                break;\n            }\n        History.push_back(Strdup(command_line));\n\n        // Process command\n        if (Stricmp(command_line, \"CLEAR\") == 0)\n        {\n            ClearLog();\n        }\n        else if (Stricmp(command_line, \"HELP\") == 0)\n        {\n            AddLog(\"Commands:\");\n            for (int i = 0; i < Commands.Size; i++)\n                AddLog(\"- %s\", Commands[i]);\n        }\n        else if (Stricmp(command_line, \"HISTORY\") == 0)\n        {\n            int first = History.Size - 10;\n            for (int i = first > 0 ? first : 0; i < History.Size; i++)\n                AddLog(\"%3d: %s\\n\", i, History[i]);\n        }\n        else\n        {\n            AddLog(\"Unknown command: '%s'\\n\", command_line);\n        }\n\n        // On command input, we scroll to bottom even if AutoScroll==false\n        ScrollToBottom = true;\n    }\n\n    // In C++11 you'd be better off using lambdas for this sort of forwarding callbacks\n    static int TextEditCallbackStub(ImGuiInputTextCallbackData* data)\n    {\n        ExampleAppConsole* console = (ExampleAppConsole*)data->UserData;\n        return console->TextEditCallback(data);\n    }\n\n    int     TextEditCallback(ImGuiInputTextCallbackData* data)\n    {\n        //AddLog(\"cursor: %d, selection: %d-%d\", data->CursorPos, data->SelectionStart, data->SelectionEnd);\n        switch (data->EventFlag)\n        {\n        case ImGuiInputTextFlags_CallbackCompletion:\n            {\n                // Example of TEXT COMPLETION\n\n                // Locate beginning of current word\n                const char* word_end = data->Buf + data->CursorPos;\n                const char* word_start = word_end;\n                while (word_start > data->Buf)\n                {\n                    const char c = word_start[-1];\n                    if (c == ' ' || c == '\\t' || c == ',' || c == ';')\n                        break;\n                    word_start--;\n                }\n\n                // Build a list of candidates\n                ImVector<const char*> candidates;\n                for (int i = 0; i < Commands.Size; i++)\n                    if (Strnicmp(Commands[i], word_start, (int)(word_end - word_start)) == 0)\n                        candidates.push_back(Commands[i]);\n\n                if (candidates.Size == 0)\n                {\n                    // No match\n                    AddLog(\"No match for \\\"%.*s\\\"!\\n\", (int)(word_end - word_start), word_start);\n                }\n                else if (candidates.Size == 1)\n                {\n                    // Single match. Delete the beginning of the word and replace it entirely so we've got nice casing.\n                    data->DeleteChars((int)(word_start - data->Buf), (int)(word_end - word_start));\n                    data->InsertChars(data->CursorPos, candidates[0]);\n                    data->InsertChars(data->CursorPos, \" \");\n                }\n                else\n                {\n                    // Multiple matches. Complete as much as we can..\n                    // So inputting \"C\"+Tab will complete to \"CL\" then display \"CLEAR\" and \"CLASSIFY\" as matches.\n                    int match_len = (int)(word_end - word_start);\n                    for (;;)\n                    {\n                        int c = 0;\n                        bool all_candidates_matches = true;\n                        for (int i = 0; i < candidates.Size && all_candidates_matches; i++)\n                            if (i == 0)\n                                c = toupper(candidates[i][match_len]);\n                            else if (c == 0 || c != toupper(candidates[i][match_len]))\n                                all_candidates_matches = false;\n                        if (!all_candidates_matches)\n                            break;\n                        match_len++;\n                    }\n\n                    if (match_len > 0)\n                    {\n                        data->DeleteChars((int)(word_start - data->Buf), (int)(word_end - word_start));\n                        data->InsertChars(data->CursorPos, candidates[0], candidates[0] + match_len);\n                    }\n\n                    // List matches\n                    AddLog(\"Possible matches:\\n\");\n                    for (int i = 0; i < candidates.Size; i++)\n                        AddLog(\"- %s\\n\", candidates[i]);\n                }\n\n                break;\n            }\n        case ImGuiInputTextFlags_CallbackHistory:\n            {\n                // Example of HISTORY\n                const int prev_history_pos = HistoryPos;\n                if (data->EventKey == ImGuiKey_UpArrow)\n                {\n                    if (HistoryPos == -1)\n                        HistoryPos = History.Size - 1;\n                    else if (HistoryPos > 0)\n                        HistoryPos--;\n                }\n                else if (data->EventKey == ImGuiKey_DownArrow)\n                {\n                    if (HistoryPos != -1)\n                        if (++HistoryPos >= History.Size)\n                            HistoryPos = -1;\n                }\n\n                // A better implementation would preserve the data on the current input line along with cursor position.\n                if (prev_history_pos != HistoryPos)\n                {\n                    const char* history_str = (HistoryPos >= 0) ? History[HistoryPos] : \"\";\n                    data->DeleteChars(0, data->BufTextLen);\n                    data->InsertChars(0, history_str);\n                }\n            }\n        }\n        return 0;\n    }\n};\n\nstatic void ShowExampleAppConsole(bool* p_open)\n{\n    static ExampleAppConsole console;\n    console.Draw(\"Example: Console\", p_open);\n}\n\n//-----------------------------------------------------------------------------\n// [SECTION] Example App: Debug Log / ShowExampleAppLog()\n//-----------------------------------------------------------------------------\n\n// Usage:\n//  static ExampleAppLog my_log;\n//  my_log.AddLog(\"Hello %d world\\n\", 123);\n//  my_log.Draw(\"title\");\nstruct ExampleAppLog\n{\n    ImGuiTextBuffer     Buf;\n    ImGuiTextFilter     Filter;\n    ImVector<int>       LineOffsets; // Index to lines offset. We maintain this with AddLog() calls.\n    bool                AutoScroll;  // Keep scrolling if already at the bottom.\n\n    ExampleAppLog()\n    {\n        AutoScroll = true;\n        Clear();\n    }\n\n    void    Clear()\n    {\n        Buf.clear();\n        LineOffsets.clear();\n        LineOffsets.push_back(0);\n    }\n\n    void    AddLog(const char* fmt, ...) IM_FMTARGS(2)\n    {\n        int old_size = Buf.size();\n        va_list args;\n        va_start(args, fmt);\n        Buf.appendfv(fmt, args);\n        va_end(args);\n        for (int new_size = Buf.size(); old_size < new_size; old_size++)\n            if (Buf[old_size] == '\\n')\n                LineOffsets.push_back(old_size + 1);\n    }\n\n    void    Draw(const char* title, bool* p_open = NULL)\n    {\n        if (!ImGui::Begin(title, p_open))\n        {\n            ImGui::End();\n            return;\n        }\n\n        // Options menu\n        if (ImGui::BeginPopup(\"Options\"))\n        {\n            ImGui::Checkbox(\"Auto-scroll\", &AutoScroll);\n            ImGui::EndPopup();\n        }\n\n        // Main window\n        if (ImGui::Button(\"Options\"))\n            ImGui::OpenPopup(\"Options\");\n        ImGui::SameLine();\n        bool clear = ImGui::Button(\"Clear\");\n        ImGui::SameLine();\n        bool copy = ImGui::Button(\"Copy\");\n        ImGui::SameLine();\n        Filter.Draw(\"Filter\", -100.0f);\n\n        ImGui::Separator();\n\n        if (ImGui::BeginChild(\"scrolling\", ImVec2(0, 0), ImGuiChildFlags_None, ImGuiWindowFlags_HorizontalScrollbar))\n        {\n            if (clear)\n                Clear();\n            if (copy)\n                ImGui::LogToClipboard();\n\n            ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0, 0));\n            const char* buf = Buf.begin();\n            const char* buf_end = Buf.end();\n            if (Filter.IsActive())\n            {\n                // In this example we don't use the clipper when Filter is enabled.\n                // This is because we don't have random access to the result of our filter.\n                // A real application processing logs with ten of thousands of entries may want to store the result of\n                // search/filter.. especially if the filtering function is not trivial (e.g. reg-exp).\n                for (int line_no = 0; line_no < LineOffsets.Size; line_no++)\n                {\n                    const char* line_start = buf + LineOffsets[line_no];\n                    const char* line_end = (line_no + 1 < LineOffsets.Size) ? (buf + LineOffsets[line_no + 1] - 1) : buf_end;\n                    if (Filter.PassFilter(line_start, line_end))\n                        ImGui::TextUnformatted(line_start, line_end);\n                }\n            }\n            else\n            {\n                // The simplest and easy way to display the entire buffer:\n                //   ImGui::TextUnformatted(buf_begin, buf_end);\n                // And it'll just work. TextUnformatted() has specialization for large blob of text and will fast-forward\n                // to skip non-visible lines. Here we instead demonstrate using the clipper to only process lines that are\n                // within the visible area.\n                // If you have tens of thousands of items and their processing cost is non-negligible, coarse clipping them\n                // on your side is recommended. Using ImGuiListClipper requires\n                // - A) random access into your data\n                // - B) items all being the  same height,\n                // both of which we can handle since we have an array pointing to the beginning of each line of text.\n                // When using the filter (in the block of code above) we don't have random access into the data to display\n                // anymore, which is why we don't use the clipper. Storing or skimming through the search result would make\n                // it possible (and would be recommended if you want to search through tens of thousands of entries).\n                ImGuiListClipper clipper;\n                clipper.Begin(LineOffsets.Size);\n                while (clipper.Step())\n                {\n                    for (int line_no = clipper.DisplayStart; line_no < clipper.DisplayEnd; line_no++)\n                    {\n                        const char* line_start = buf + LineOffsets[line_no];\n                        const char* line_end = (line_no + 1 < LineOffsets.Size) ? (buf + LineOffsets[line_no + 1] - 1) : buf_end;\n                        ImGui::TextUnformatted(line_start, line_end);\n                    }\n                }\n                clipper.End();\n            }\n            ImGui::PopStyleVar();\n\n            // Keep up at the bottom of the scroll region if we were already at the bottom at the beginning of the frame.\n            // Using a scrollbar or mouse-wheel will take away from the bottom edge.\n            if (AutoScroll && ImGui::GetScrollY() >= ImGui::GetScrollMaxY())\n                ImGui::SetScrollHereY(1.0f);\n        }\n        ImGui::EndChild();\n        ImGui::End();\n    }\n};\n\n// Demonstrate creating a simple log window with basic filtering.\nstatic void ShowExampleAppLog(bool* p_open)\n{\n    static ExampleAppLog log;\n\n    // For the demo: add a debug button _BEFORE_ the normal log window contents\n    // We take advantage of a rarely used feature: multiple calls to Begin()/End() are appending to the _same_ window.\n    // Most of the contents of the window will be added by the log.Draw() call.\n    ImGui::SetNextWindowSize(ImVec2(500, 400), ImGuiCond_FirstUseEver);\n    ImGui::Begin(\"Example: Log\", p_open);\n    IMGUI_DEMO_MARKER(\"Examples/Log\");\n    if (ImGui::SmallButton(\"[Debug] Add 5 entries\"))\n    {\n        static int counter = 0;\n        const char* categories[3] = { \"info\", \"warn\", \"error\" };\n        const char* words[] = { \"Bumfuzzled\", \"Cattywampus\", \"Snickersnee\", \"Abibliophobia\", \"Absquatulate\", \"Nincompoop\", \"Pauciloquent\" };\n        for (int n = 0; n < 5; n++)\n        {\n            const char* category = categories[counter % IM_ARRAYSIZE(categories)];\n            const char* word = words[counter % IM_ARRAYSIZE(words)];\n            log.AddLog(\"[%05d] [%s] Hello, current time is %.1f, here's a word: '%s'\\n\",\n                ImGui::GetFrameCount(), category, ImGui::GetTime(), word);\n            counter++;\n        }\n    }\n    ImGui::End();\n\n    // Actually call in the regular Log helper (which will Begin() into the same window as we just did)\n    log.Draw(\"Example: Log\", p_open);\n}\n\n//-----------------------------------------------------------------------------\n// [SECTION] Example App: Simple Layout / ShowExampleAppLayout()\n//-----------------------------------------------------------------------------\n\n// Demonstrate create a window with multiple child windows.\nstatic void ShowExampleAppLayout(bool* p_open)\n{\n    ImGui::SetNextWindowSize(ImVec2(500, 440), ImGuiCond_FirstUseEver);\n    if (ImGui::Begin(\"Example: Simple layout\", p_open, ImGuiWindowFlags_MenuBar))\n    {\n        IMGUI_DEMO_MARKER(\"Examples/Simple layout\");\n        if (ImGui::BeginMenuBar())\n        {\n            if (ImGui::BeginMenu(\"File\"))\n            {\n                if (ImGui::MenuItem(\"Close\", \"Ctrl+W\")) { *p_open = false; }\n                ImGui::EndMenu();\n            }\n            ImGui::EndMenuBar();\n        }\n\n        // Left\n        static int selected = 0;\n        {\n            ImGui::BeginChild(\"left pane\", ImVec2(150, 0), ImGuiChildFlags_Borders | ImGuiChildFlags_ResizeX);\n            for (int i = 0; i < 100; i++)\n            {\n                // FIXME: Good candidate to use ImGuiSelectableFlags_SelectOnNav\n                char label[128];\n                sprintf(label, \"MyObject %d\", i);\n                if (ImGui::Selectable(label, selected == i))\n                    selected = i;\n            }\n            ImGui::EndChild();\n        }\n        ImGui::SameLine();\n\n        // Right\n        {\n            ImGui::BeginGroup();\n            ImGui::BeginChild(\"item view\", ImVec2(0, -ImGui::GetFrameHeightWithSpacing())); // Leave room for 1 line below us\n            ImGui::Text(\"MyObject: %d\", selected);\n            ImGui::Separator();\n            if (ImGui::BeginTabBar(\"##Tabs\", ImGuiTabBarFlags_None))\n            {\n                if (ImGui::BeginTabItem(\"Description\"))\n                {\n                    ImGui::TextWrapped(\"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. \");\n                    ImGui::EndTabItem();\n                }\n                if (ImGui::BeginTabItem(\"Details\"))\n                {\n                    ImGui::Text(\"ID: 0123456789\");\n                    ImGui::EndTabItem();\n                }\n                ImGui::EndTabBar();\n            }\n            ImGui::EndChild();\n            if (ImGui::Button(\"Revert\")) {}\n            ImGui::SameLine();\n            if (ImGui::Button(\"Save\")) {}\n            ImGui::EndGroup();\n        }\n    }\n    ImGui::End();\n}\n\n//-----------------------------------------------------------------------------\n// [SECTION] Example App: Property Editor / ShowExampleAppPropertyEditor()\n//-----------------------------------------------------------------------------\n// Some of the interactions are a bit lack-luster:\n// - We would want pressing validating or leaving the filter to somehow restore focus.\n// - We may want more advanced filtering (child nodes) and clipper support: both will need extra work.\n// - We would want to customize some keyboard interactions to easily keyboard navigate between the tree and the properties.\n//-----------------------------------------------------------------------------\n\nstruct ExampleAppPropertyEditor\n{\n    ImGuiTextFilter     Filter;\n    ExampleTreeNode*    VisibleNode = NULL;\n\n    void Draw(ExampleTreeNode* root_node)\n    {\n        // Left side: draw tree\n        // - Currently using a table to benefit from RowBg feature\n        if (ImGui::BeginChild(\"##tree\", ImVec2(300, 0), ImGuiChildFlags_ResizeX | ImGuiChildFlags_Borders | ImGuiChildFlags_NavFlattened))\n        {\n            ImGui::SetNextItemWidth(-FLT_MIN);\n            ImGui::SetNextItemShortcut(ImGuiMod_Ctrl | ImGuiKey_F, ImGuiInputFlags_Tooltip);\n            ImGui::PushItemFlag(ImGuiItemFlags_NoNavDefaultFocus, true);\n            if (ImGui::InputTextWithHint(\"##Filter\", \"incl,-excl\", Filter.InputBuf, IM_ARRAYSIZE(Filter.InputBuf), ImGuiInputTextFlags_EscapeClearsAll))\n                Filter.Build();\n            ImGui::PopItemFlag();\n\n            if (ImGui::BeginTable(\"##bg\", 1, ImGuiTableFlags_RowBg))\n            {\n                for (ExampleTreeNode* node : root_node->Childs)\n                    if (Filter.PassFilter(node->Name)) // Filter root node\n                        DrawTreeNode(node);\n                ImGui::EndTable();\n            }\n        }\n        ImGui::EndChild();\n\n        // Right side: draw properties\n        ImGui::SameLine();\n\n        ImGui::BeginGroup(); // Lock X position\n        if (ExampleTreeNode* node = VisibleNode)\n        {\n            ImGui::Text(\"%s\", node->Name);\n            ImGui::TextDisabled(\"UID: 0x%08X\", node->UID);\n            ImGui::Separator();\n            if (ImGui::BeginTable(\"##properties\", 2, ImGuiTableFlags_Resizable | ImGuiTableFlags_ScrollY))\n            {\n                // Push object ID after we entered the table, so table is shared for all objects\n                ImGui::PushID((int)node->UID);\n                ImGui::TableSetupColumn(\"\", ImGuiTableColumnFlags_WidthFixed);\n                ImGui::TableSetupColumn(\"\", ImGuiTableColumnFlags_WidthStretch, 2.0f); // Default twice larger\n                if (node->HasData)\n                {\n                    // In a typical application, the structure description would be derived from a data-driven system.\n                    // - We try to mimic this with our ExampleMemberInfo structure and the ExampleTreeNodeMemberInfos[] array.\n                    // - Limits and some details are hard-coded to simplify the demo.\n                    for (const ExampleMemberInfo& field_desc : ExampleTreeNodeMemberInfos)\n                    {\n                        ImGui::TableNextRow();\n                        ImGui::PushID(field_desc.Name);\n                        ImGui::TableNextColumn();\n                        ImGui::AlignTextToFramePadding();\n                        ImGui::TextUnformatted(field_desc.Name);\n                        ImGui::TableNextColumn();\n                        void* field_ptr = (void*)(((unsigned char*)node) + field_desc.Offset);\n                        switch (field_desc.DataType)\n                        {\n                        case ImGuiDataType_Bool:\n                        {\n                            IM_ASSERT(field_desc.DataCount == 1);\n                            ImGui::Checkbox(\"##Editor\", (bool*)field_ptr);\n                            break;\n                        }\n                        case ImGuiDataType_S32:\n                        {\n                            int v_min = INT_MIN, v_max = INT_MAX;\n                            ImGui::SetNextItemWidth(-FLT_MIN);\n                            ImGui::DragScalarN(\"##Editor\", field_desc.DataType, field_ptr, field_desc.DataCount, 1.0f, &v_min, &v_max);\n                            break;\n                        }\n                        case ImGuiDataType_Float:\n                        {\n                            float v_min = 0.0f, v_max = 1.0f;\n                            ImGui::SetNextItemWidth(-FLT_MIN);\n                            ImGui::SliderScalarN(\"##Editor\", field_desc.DataType, field_ptr, field_desc.DataCount, &v_min, &v_max);\n                            break;\n                        }\n                        case ImGuiDataType_String:\n                        {\n                            ImGui::InputText(\"##Editor\", reinterpret_cast<char*>(field_ptr), 28);\n                            break;\n                        }\n                        }\n                        ImGui::PopID();\n                    }\n                }\n                ImGui::PopID();\n                ImGui::EndTable();\n            }\n        }\n        ImGui::EndGroup();\n    }\n\n    void DrawTreeNode(ExampleTreeNode* node)\n    {\n        ImGui::TableNextRow();\n        ImGui::TableNextColumn();\n        ImGui::PushID(node->UID);\n        ImGuiTreeNodeFlags tree_flags = ImGuiTreeNodeFlags_None;\n        tree_flags |= ImGuiTreeNodeFlags_OpenOnArrow | ImGuiTreeNodeFlags_OpenOnDoubleClick;    // Standard opening mode as we are likely to want to add selection afterwards\n        tree_flags |= ImGuiTreeNodeFlags_NavLeftJumpsBackHere;                                  // Left arrow support\n        if (node == VisibleNode)\n            tree_flags |= ImGuiTreeNodeFlags_Selected;\n        if (node->Childs.Size == 0)\n            tree_flags |= ImGuiTreeNodeFlags_Leaf | ImGuiTreeNodeFlags_Bullet;\n        if (node->DataMyBool == false)\n            ImGui::PushStyleColor(ImGuiCol_Text, ImGui::GetStyle().Colors[ImGuiCol_TextDisabled]);\n        bool node_open = ImGui::TreeNodeEx(\"\", tree_flags, \"%s\", node->Name);\n        if (node->DataMyBool == false)\n            ImGui::PopStyleColor();\n        if (ImGui::IsItemFocused())\n            VisibleNode = node;\n        if (node_open)\n        {\n            for (ExampleTreeNode* child : node->Childs)\n                DrawTreeNode(child);\n            ImGui::TreePop();\n        }\n        ImGui::PopID();\n    }\n};\n\n// Demonstrate creating a simple property editor.\nstatic void ShowExampleAppPropertyEditor(bool* p_open, ImGuiDemoWindowData* demo_data)\n{\n    ImGui::SetNextWindowSize(ImVec2(430, 450), ImGuiCond_FirstUseEver);\n    if (!ImGui::Begin(\"Example: Property editor\", p_open))\n    {\n        ImGui::End();\n        return;\n    }\n\n    IMGUI_DEMO_MARKER(\"Examples/Property Editor\");\n    static ExampleAppPropertyEditor property_editor;\n    if (demo_data->DemoTree == NULL)\n        demo_data->DemoTree = ExampleTree_CreateDemoTree();\n    property_editor.Draw(demo_data->DemoTree);\n\n    ImGui::End();\n}\n\n//-----------------------------------------------------------------------------\n// [SECTION] Example App: Long Text / ShowExampleAppLongText()\n//-----------------------------------------------------------------------------\n\n// Demonstrate/test rendering huge amount of text, and the incidence of clipping.\nstatic void ShowExampleAppLongText(bool* p_open)\n{\n    ImGui::SetNextWindowSize(ImVec2(520, 600), ImGuiCond_FirstUseEver);\n    if (!ImGui::Begin(\"Example: Long text display\", p_open))\n    {\n        ImGui::End();\n        return;\n    }\n    IMGUI_DEMO_MARKER(\"Examples/Long text display\");\n\n    static int test_type = 0;\n    static ImGuiTextBuffer log;\n    static int lines = 0;\n    ImGui::Text(\"Printing unusually long amount of text.\");\n    ImGui::Combo(\"Test type\", &test_type,\n        \"Single call to TextUnformatted()\\0\"\n        \"Multiple calls to Text(), clipped\\0\"\n        \"Multiple calls to Text(), not clipped (slow)\\0\");\n    ImGui::Text(\"Buffer contents: %d lines, %d bytes\", lines, log.size());\n    if (ImGui::Button(\"Clear\")) { log.clear(); lines = 0; }\n    ImGui::SameLine();\n    if (ImGui::Button(\"Add 1000 lines\"))\n    {\n        for (int i = 0; i < 1000; i++)\n            log.appendf(\"%i The quick brown fox jumps over the lazy dog\\n\", lines + i);\n        lines += 1000;\n    }\n    ImGui::BeginChild(\"Log\");\n    switch (test_type)\n    {\n    case 0:\n        // Single call to TextUnformatted() with a big buffer\n        ImGui::TextUnformatted(log.begin(), log.end());\n        break;\n    case 1:\n        {\n            // Multiple calls to Text(), manually coarsely clipped - demonstrate how to use the ImGuiListClipper helper.\n            ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0, 0));\n            ImGuiListClipper clipper;\n            clipper.Begin(lines);\n            while (clipper.Step())\n                for (int i = clipper.DisplayStart; i < clipper.DisplayEnd; i++)\n                    ImGui::Text(\"%i The quick brown fox jumps over the lazy dog\", i);\n            ImGui::PopStyleVar();\n            break;\n        }\n    case 2:\n        // Multiple calls to Text(), not clipped (slow)\n        ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0, 0));\n        for (int i = 0; i < lines; i++)\n            ImGui::Text(\"%i The quick brown fox jumps over the lazy dog\", i);\n        ImGui::PopStyleVar();\n        break;\n    }\n    ImGui::EndChild();\n    ImGui::End();\n}\n\n//-----------------------------------------------------------------------------\n// [SECTION] Example App: Auto Resize / ShowExampleAppAutoResize()\n//-----------------------------------------------------------------------------\n\n// Demonstrate creating a window which gets auto-resized according to its content.\nstatic void ShowExampleAppAutoResize(bool* p_open)\n{\n    if (!ImGui::Begin(\"Example: Auto-resizing window\", p_open, ImGuiWindowFlags_AlwaysAutoResize))\n    {\n        ImGui::End();\n        return;\n    }\n    IMGUI_DEMO_MARKER(\"Examples/Auto-resizing window\");\n\n    static int lines = 10;\n    ImGui::TextUnformatted(\n        \"Window will resize every-frame to the size of its content.\\n\"\n        \"Note that you probably don't want to query the window size to\\n\"\n        \"output your content because that would create a feedback loop.\");\n    ImGui::SliderInt(\"Number of lines\", &lines, 1, 20);\n    for (int i = 0; i < lines; i++)\n        ImGui::Text(\"%*sThis is line %d\", i * 4, \"\", i); // Pad with space to extend size horizontally\n    ImGui::End();\n}\n\n//-----------------------------------------------------------------------------\n// [SECTION] Example App: Constrained Resize / ShowExampleAppConstrainedResize()\n//-----------------------------------------------------------------------------\n\n// Demonstrate creating a window with custom resize constraints.\n// Note that size constraints currently don't work on a docked window (when in 'docking' branch)\nstatic void ShowExampleAppConstrainedResize(bool* p_open)\n{\n    struct CustomConstraints\n    {\n        // Helper functions to demonstrate programmatic constraints\n        // FIXME: This doesn't take account of decoration size (e.g. title bar), library should make this easier.\n        // FIXME: None of the three demos works consistently when resizing from borders.\n        static void AspectRatio(ImGuiSizeCallbackData* data)\n        {\n            float aspect_ratio = *(float*)data->UserData;\n            data->DesiredSize.y = (float)(int)(data->DesiredSize.x / aspect_ratio);\n        }\n        static void Square(ImGuiSizeCallbackData* data)\n        {\n            data->DesiredSize.x = data->DesiredSize.y = IM_MAX(data->DesiredSize.x, data->DesiredSize.y);\n        }\n        static void Step(ImGuiSizeCallbackData* data)\n        {\n            float step = *(float*)data->UserData;\n            data->DesiredSize = ImVec2((int)(data->DesiredSize.x / step + 0.5f) * step, (int)(data->DesiredSize.y / step + 0.5f) * step);\n        }\n    };\n\n    const char* test_desc[] =\n    {\n        \"Between 100x100 and 500x500\",\n        \"At least 100x100\",\n        \"Resize vertical + lock current width\",\n        \"Resize horizontal + lock current height\",\n        \"Width Between 400 and 500\",\n        \"Height at least 400\",\n        \"Custom: Aspect Ratio 16:9\",\n        \"Custom: Always Square\",\n        \"Custom: Fixed Steps (100)\",\n    };\n\n    // Options\n    static bool auto_resize = false;\n    static bool window_padding = true;\n    static int type = 6; // Aspect Ratio\n    static int display_lines = 10;\n\n    // Submit constraint\n    float aspect_ratio = 16.0f / 9.0f;\n    float fixed_step = 100.0f;\n    if (type == 0) ImGui::SetNextWindowSizeConstraints(ImVec2(100, 100), ImVec2(500, 500));         // Between 100x100 and 500x500\n    if (type == 1) ImGui::SetNextWindowSizeConstraints(ImVec2(100, 100), ImVec2(FLT_MAX, FLT_MAX)); // Width > 100, Height > 100\n    if (type == 2) ImGui::SetNextWindowSizeConstraints(ImVec2(-1, 0),    ImVec2(-1, FLT_MAX));      // Resize vertical + lock current width\n    if (type == 3) ImGui::SetNextWindowSizeConstraints(ImVec2(0, -1),    ImVec2(FLT_MAX, -1));      // Resize horizontal + lock current height\n    if (type == 4) ImGui::SetNextWindowSizeConstraints(ImVec2(400, -1),  ImVec2(500, -1));          // Width Between and 400 and 500\n    if (type == 5) ImGui::SetNextWindowSizeConstraints(ImVec2(-1, 400),  ImVec2(-1, FLT_MAX));      // Height at least 400\n    if (type == 6) ImGui::SetNextWindowSizeConstraints(ImVec2(0, 0),     ImVec2(FLT_MAX, FLT_MAX), CustomConstraints::AspectRatio, (void*)&aspect_ratio);   // Aspect ratio\n    if (type == 7) ImGui::SetNextWindowSizeConstraints(ImVec2(0, 0),     ImVec2(FLT_MAX, FLT_MAX), CustomConstraints::Square);                              // Always Square\n    if (type == 8) ImGui::SetNextWindowSizeConstraints(ImVec2(0, 0),     ImVec2(FLT_MAX, FLT_MAX), CustomConstraints::Step, (void*)&fixed_step);            // Fixed Step\n\n    // Submit window\n    if (!window_padding)\n        ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0.0f, 0.0f));\n    const ImGuiWindowFlags window_flags = auto_resize ? ImGuiWindowFlags_AlwaysAutoResize : 0;\n    const bool window_open = ImGui::Begin(\"Example: Constrained Resize\", p_open, window_flags);\n    if (!window_padding)\n        ImGui::PopStyleVar();\n    if (window_open)\n    {\n        IMGUI_DEMO_MARKER(\"Examples/Constrained Resizing window\");\n        if (ImGui::GetIO().KeyShift)\n        {\n            // Display a dummy viewport (in your real app you would likely use ImageButton() to display a texture.\n            ImVec2 avail_size = ImGui::GetContentRegionAvail();\n            ImVec2 pos = ImGui::GetCursorScreenPos();\n            ImGui::ColorButton(\"viewport\", ImVec4(0.5f, 0.2f, 0.5f, 1.0f), ImGuiColorEditFlags_NoTooltip | ImGuiColorEditFlags_NoDragDrop, avail_size);\n            ImGui::SetCursorScreenPos(ImVec2(pos.x + 10, pos.y + 10));\n            ImGui::Text(\"%.2f x %.2f\", avail_size.x, avail_size.y);\n        }\n        else\n        {\n            ImGui::Text(\"(Hold SHIFT to display a dummy viewport)\");\n            if (ImGui::Button(\"Set 200x200\")) { ImGui::SetWindowSize(ImVec2(200, 200)); } ImGui::SameLine();\n            if (ImGui::Button(\"Set 500x500\")) { ImGui::SetWindowSize(ImVec2(500, 500)); } ImGui::SameLine();\n            if (ImGui::Button(\"Set 800x200\")) { ImGui::SetWindowSize(ImVec2(800, 200)); }\n            ImGui::SetNextItemWidth(ImGui::GetFontSize() * 20);\n            ImGui::Combo(\"Constraint\", &type, test_desc, IM_ARRAYSIZE(test_desc));\n            ImGui::SetNextItemWidth(ImGui::GetFontSize() * 20);\n            ImGui::DragInt(\"Lines\", &display_lines, 0.2f, 1, 100);\n            ImGui::Checkbox(\"Auto-resize\", &auto_resize);\n            ImGui::Checkbox(\"Window padding\", &window_padding);\n            for (int i = 0; i < display_lines; i++)\n                ImGui::Text(\"%*sHello, sailor! Making this line long enough for the example.\", i * 4, \"\");\n        }\n    }\n    ImGui::End();\n}\n\n//-----------------------------------------------------------------------------\n// [SECTION] Example App: Simple overlay / ShowExampleAppSimpleOverlay()\n//-----------------------------------------------------------------------------\n\n// Demonstrate creating a simple static window with no decoration\n// + a context-menu to choose which corner of the screen to use.\nstatic void ShowExampleAppSimpleOverlay(bool* p_open)\n{\n    static int location = 0;\n    ImGuiIO& io = ImGui::GetIO();\n    ImGuiWindowFlags window_flags = ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_NoFocusOnAppearing | ImGuiWindowFlags_NoNav;\n    if (location >= 0)\n    {\n        const float PAD = 10.0f;\n        const ImGuiViewport* viewport = ImGui::GetMainViewport();\n        ImVec2 work_pos = viewport->WorkPos; // Use work area to avoid menu-bar/task-bar, if any!\n        ImVec2 work_size = viewport->WorkSize;\n        ImVec2 window_pos, window_pos_pivot;\n        window_pos.x = (location & 1) ? (work_pos.x + work_size.x - PAD) : (work_pos.x + PAD);\n        window_pos.y = (location & 2) ? (work_pos.y + work_size.y - PAD) : (work_pos.y + PAD);\n        window_pos_pivot.x = (location & 1) ? 1.0f : 0.0f;\n        window_pos_pivot.y = (location & 2) ? 1.0f : 0.0f;\n        ImGui::SetNextWindowPos(window_pos, ImGuiCond_Always, window_pos_pivot);\n        window_flags |= ImGuiWindowFlags_NoMove;\n    }\n    else if (location == -2)\n    {\n        // Center window\n        ImGui::SetNextWindowPos(ImGui::GetMainViewport()->GetCenter(), ImGuiCond_Always, ImVec2(0.5f, 0.5f));\n        window_flags |= ImGuiWindowFlags_NoMove;\n    }\n    ImGui::SetNextWindowBgAlpha(0.35f); // Transparent background\n    if (ImGui::Begin(\"Example: Simple overlay\", p_open, window_flags))\n    {\n        IMGUI_DEMO_MARKER(\"Examples/Simple Overlay\");\n        ImGui::Text(\"Simple overlay\\n\" \"(right-click to change position)\");\n        ImGui::Separator();\n        if (ImGui::IsMousePosValid())\n            ImGui::Text(\"Mouse Position: (%.1f,%.1f)\", io.MousePos.x, io.MousePos.y);\n        else\n            ImGui::Text(\"Mouse Position: <invalid>\");\n        if (ImGui::BeginPopupContextWindow())\n        {\n            if (ImGui::MenuItem(\"Custom\",       NULL, location == -1)) location = -1;\n            if (ImGui::MenuItem(\"Center\",       NULL, location == -2)) location = -2;\n            if (ImGui::MenuItem(\"Top-left\",     NULL, location == 0)) location = 0;\n            if (ImGui::MenuItem(\"Top-right\",    NULL, location == 1)) location = 1;\n            if (ImGui::MenuItem(\"Bottom-left\",  NULL, location == 2)) location = 2;\n            if (ImGui::MenuItem(\"Bottom-right\", NULL, location == 3)) location = 3;\n            if (p_open && ImGui::MenuItem(\"Close\")) *p_open = false;\n            ImGui::EndPopup();\n        }\n    }\n    ImGui::End();\n}\n\n//-----------------------------------------------------------------------------\n// [SECTION] Example App: Fullscreen window / ShowExampleAppFullscreen()\n//-----------------------------------------------------------------------------\n\n// Demonstrate creating a window covering the entire screen/viewport\nstatic void ShowExampleAppFullscreen(bool* p_open)\n{\n    static bool use_work_area = true;\n    static ImGuiWindowFlags flags = ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoSavedSettings;\n\n    // We demonstrate using the full viewport area or the work area (without menu-bars, task-bars etc.)\n    // Based on your use case you may want one or the other.\n    const ImGuiViewport* viewport = ImGui::GetMainViewport();\n    ImGui::SetNextWindowPos(use_work_area ? viewport->WorkPos : viewport->Pos);\n    ImGui::SetNextWindowSize(use_work_area ? viewport->WorkSize : viewport->Size);\n\n    if (ImGui::Begin(\"Example: Fullscreen window\", p_open, flags))\n    {\n        ImGui::Checkbox(\"Use work area instead of main area\", &use_work_area);\n        ImGui::SameLine();\n        HelpMarker(\"Main Area = entire viewport,\\nWork Area = entire viewport minus sections used by the main menu bars, task bars etc.\\n\\nEnable the main-menu bar in Examples menu to see the difference.\");\n\n        ImGui::CheckboxFlags(\"ImGuiWindowFlags_NoBackground\", &flags, ImGuiWindowFlags_NoBackground);\n        ImGui::CheckboxFlags(\"ImGuiWindowFlags_NoDecoration\", &flags, ImGuiWindowFlags_NoDecoration);\n        ImGui::Indent();\n        ImGui::CheckboxFlags(\"ImGuiWindowFlags_NoTitleBar\", &flags, ImGuiWindowFlags_NoTitleBar);\n        ImGui::CheckboxFlags(\"ImGuiWindowFlags_NoCollapse\", &flags, ImGuiWindowFlags_NoCollapse);\n        ImGui::CheckboxFlags(\"ImGuiWindowFlags_NoScrollbar\", &flags, ImGuiWindowFlags_NoScrollbar);\n        ImGui::Unindent();\n\n        if (p_open && ImGui::Button(\"Close this window\"))\n            *p_open = false;\n    }\n    ImGui::End();\n}\n\n//-----------------------------------------------------------------------------\n// [SECTION] Example App: Manipulating Window Titles / ShowExampleAppWindowTitles()\n//-----------------------------------------------------------------------------\n\n// Demonstrate the use of \"##\" and \"###\" in identifiers to manipulate ID generation.\n// This applies to all regular items as well.\n// Read FAQ section \"How can I have multiple widgets with the same label?\" for details.\nstatic void ShowExampleAppWindowTitles(bool*)\n{\n    const ImGuiViewport* viewport = ImGui::GetMainViewport();\n    const ImVec2 base_pos = viewport->Pos;\n\n    // By default, Windows are uniquely identified by their title.\n    // You can use the \"##\" and \"###\" markers to manipulate the display/ID.\n\n    // Using \"##\" to display same title but have unique identifier.\n    ImGui::SetNextWindowPos(ImVec2(base_pos.x + 100, base_pos.y + 100), ImGuiCond_FirstUseEver);\n    ImGui::Begin(\"Same title as another window##1\");\n    IMGUI_DEMO_MARKER(\"Examples/Manipulating window titles\");\n    ImGui::Text(\"This is window 1.\\nMy title is the same as window 2, but my identifier is unique.\");\n    ImGui::End();\n\n    ImGui::SetNextWindowPos(ImVec2(base_pos.x + 100, base_pos.y + 200), ImGuiCond_FirstUseEver);\n    ImGui::Begin(\"Same title as another window##2\");\n    ImGui::Text(\"This is window 2.\\nMy title is the same as window 1, but my identifier is unique.\");\n    ImGui::End();\n\n    // Using \"###\" to display a changing title but keep a static identifier \"AnimatedTitle\"\n    char buf[128];\n    sprintf(buf, \"Animated title %c %d###AnimatedTitle\", \"|/-\\\\\"[(int)(ImGui::GetTime() / 0.25f) & 3], ImGui::GetFrameCount());\n    ImGui::SetNextWindowPos(ImVec2(base_pos.x + 100, base_pos.y + 300), ImGuiCond_FirstUseEver);\n    ImGui::Begin(buf);\n    ImGui::Text(\"This window has a changing title.\");\n    ImGui::End();\n}\n\n//-----------------------------------------------------------------------------\n// [SECTION] Example App: Custom Rendering using ImDrawList API / ShowExampleAppCustomRendering()\n//-----------------------------------------------------------------------------\n\n// Add a |_| looking shape\nstatic void PathConcaveShape(ImDrawList* draw_list, float x, float y, float sz)\n{\n    const ImVec2 pos_norms[] = { { 0.0f, 0.0f }, { 0.3f, 0.0f }, { 0.3f, 0.7f }, { 0.7f, 0.7f }, { 0.7f, 0.0f }, { 1.0f, 0.0f }, { 1.0f, 1.0f }, { 0.0f, 1.0f } };\n    for (const ImVec2& p : pos_norms)\n        draw_list->PathLineTo(ImVec2(x + 0.5f + (int)(sz * p.x), y + 0.5f + (int)(sz * p.y)));\n}\n\n// Demonstrate using the low-level ImDrawList to draw custom shapes.\nstatic void ShowExampleAppCustomRendering(bool* p_open)\n{\n    if (!ImGui::Begin(\"Example: Custom rendering\", p_open))\n    {\n        ImGui::End();\n        return;\n    }\n    IMGUI_DEMO_MARKER(\"Examples/Custom Rendering\");\n\n    // Tip: If you do a lot of custom rendering, you probably want to use your own geometrical types and benefit of\n    // overloaded operators, etc. Define IM_VEC2_CLASS_EXTRA in imconfig.h to create implicit conversions between your\n    // types and ImVec2/ImVec4. Dear ImGui defines overloaded operators but they are internal to imgui.cpp and not\n    // exposed outside (to avoid messing with your types) In this example we are not using the maths operators!\n\n    if (ImGui::BeginTabBar(\"##TabBar\"))\n    {\n        if (ImGui::BeginTabItem(\"Primitives\"))\n        {\n            ImGui::PushItemWidth(-ImGui::GetFontSize() * 15);\n            ImDrawList* draw_list = ImGui::GetWindowDrawList();\n\n            // Draw gradients\n            // (note that those are currently exacerbating our sRGB/Linear issues)\n            // Calling ImGui::GetColorU32() multiplies the given colors by the current Style Alpha, but you may pass the IM_COL32() directly as well..\n            ImGui::Text(\"Gradients\");\n            ImVec2 gradient_size = ImVec2(ImGui::CalcItemWidth(), ImGui::GetFrameHeight());\n            {\n                ImVec2 p0 = ImGui::GetCursorScreenPos();\n                ImVec2 p1 = ImVec2(p0.x + gradient_size.x, p0.y + gradient_size.y);\n                ImU32 col_a = ImGui::GetColorU32(IM_COL32(0, 0, 0, 255));\n                ImU32 col_b = ImGui::GetColorU32(IM_COL32(255, 255, 255, 255));\n                draw_list->AddRectFilledMultiColor(p0, p1, col_a, col_b, col_b, col_a);\n                ImGui::InvisibleButton(\"##gradient1\", gradient_size);\n            }\n            {\n                ImVec2 p0 = ImGui::GetCursorScreenPos();\n                ImVec2 p1 = ImVec2(p0.x + gradient_size.x, p0.y + gradient_size.y);\n                ImU32 col_a = ImGui::GetColorU32(IM_COL32(0, 255, 0, 255));\n                ImU32 col_b = ImGui::GetColorU32(IM_COL32(255, 0, 0, 255));\n                draw_list->AddRectFilledMultiColor(p0, p1, col_a, col_b, col_b, col_a);\n                ImGui::InvisibleButton(\"##gradient2\", gradient_size);\n            }\n\n            // Draw a bunch of primitives\n            ImGui::Text(\"All primitives\");\n            static float sz = 36.0f;\n            static float thickness = 3.0f;\n            static int ngon_sides = 6;\n            static bool circle_segments_override = false;\n            static int circle_segments_override_v = 12;\n            static bool curve_segments_override = false;\n            static int curve_segments_override_v = 8;\n            static ImVec4 colf = ImVec4(1.0f, 1.0f, 0.4f, 1.0f);\n            ImGui::DragFloat(\"Size\", &sz, 0.2f, 2.0f, 100.0f, \"%.0f\");\n            ImGui::DragFloat(\"Thickness\", &thickness, 0.05f, 1.0f, 8.0f, \"%.02f\");\n            ImGui::SliderInt(\"N-gon sides\", &ngon_sides, 3, 12);\n            ImGui::Checkbox(\"##circlesegmentoverride\", &circle_segments_override);\n            ImGui::SameLine(0.0f, ImGui::GetStyle().ItemInnerSpacing.x);\n            circle_segments_override |= ImGui::SliderInt(\"Circle segments override\", &circle_segments_override_v, 3, 40);\n            ImGui::Checkbox(\"##curvessegmentoverride\", &curve_segments_override);\n            ImGui::SameLine(0.0f, ImGui::GetStyle().ItemInnerSpacing.x);\n            curve_segments_override |= ImGui::SliderInt(\"Curves segments override\", &curve_segments_override_v, 3, 40);\n            ImGui::ColorEdit4(\"Color\", &colf.x);\n\n            const ImVec2 p = ImGui::GetCursorScreenPos();\n            const ImU32 col = ImColor(colf);\n            const float spacing = 10.0f;\n            const ImDrawFlags corners_tl_br = ImDrawFlags_RoundCornersTopLeft | ImDrawFlags_RoundCornersBottomRight;\n            const float rounding = sz / 5.0f;\n            const int circle_segments = circle_segments_override ? circle_segments_override_v : 0;\n            const int curve_segments = curve_segments_override ? curve_segments_override_v : 0;\n            const ImVec2 cp3[3] = { ImVec2(0.0f, sz * 0.6f), ImVec2(sz * 0.5f, -sz * 0.4f), ImVec2(sz, sz) }; // Control points for curves\n            const ImVec2 cp4[4] = { ImVec2(0.0f, 0.0f), ImVec2(sz * 1.3f, sz * 0.3f), ImVec2(sz - sz * 1.3f, sz - sz * 0.3f), ImVec2(sz, sz) };\n\n            float x = p.x + 4.0f;\n            float y = p.y + 4.0f;\n            for (int n = 0; n < 2; n++)\n            {\n                // First line uses a thickness of 1.0f, second line uses the configurable thickness\n                float th = (n == 0) ? 1.0f : thickness;\n                draw_list->AddNgon(ImVec2(x + sz*0.5f, y + sz*0.5f), sz*0.5f, col, ngon_sides, th);                 x += sz + spacing;  // N-gon\n                draw_list->AddCircle(ImVec2(x + sz*0.5f, y + sz*0.5f), sz*0.5f, col, circle_segments, th);          x += sz + spacing;  // Circle\n                draw_list->AddEllipse(ImVec2(x + sz*0.5f, y + sz*0.5f), ImVec2(sz*0.5f, sz*0.3f), col, -0.3f, circle_segments, th); x += sz + spacing;  // Ellipse\n                draw_list->AddRect(ImVec2(x, y), ImVec2(x + sz, y + sz), col, 0.0f, ImDrawFlags_None, th);          x += sz + spacing;  // Square\n                draw_list->AddRect(ImVec2(x, y), ImVec2(x + sz, y + sz), col, rounding, ImDrawFlags_None, th);      x += sz + spacing;  // Square with all rounded corners\n                draw_list->AddRect(ImVec2(x, y), ImVec2(x + sz, y + sz), col, rounding, corners_tl_br, th);         x += sz + spacing;  // Square with two rounded corners\n                draw_list->AddTriangle(ImVec2(x+sz*0.5f,y), ImVec2(x+sz, y+sz-0.5f), ImVec2(x, y+sz-0.5f), col, th);x += sz + spacing;  // Triangle\n                //draw_list->AddTriangle(ImVec2(x+sz*0.2f,y), ImVec2(x, y+sz-0.5f), ImVec2(x+sz*0.4f, y+sz-0.5f), col, th);x+= sz*0.4f + spacing; // Thin triangle\n                PathConcaveShape(draw_list, x, y, sz); draw_list->PathStroke(col, ImDrawFlags_Closed, th);          x += sz + spacing;  // Concave Shape\n                //draw_list->AddPolyline(concave_shape, IM_ARRAYSIZE(concave_shape), col, ImDrawFlags_Closed, th);\n                draw_list->AddLine(ImVec2(x, y), ImVec2(x + sz, y), col, th);                                       x += sz + spacing;  // Horizontal line (note: drawing a filled rectangle will be faster!)\n                draw_list->AddLine(ImVec2(x, y), ImVec2(x, y + sz), col, th);                                       x += spacing;       // Vertical line (note: drawing a filled rectangle will be faster!)\n                draw_list->AddLine(ImVec2(x, y), ImVec2(x + sz, y + sz), col, th);                                  x += sz + spacing;  // Diagonal line\n\n                // Path\n                draw_list->PathArcTo(ImVec2(x + sz*0.5f, y + sz*0.5f), sz*0.5f, 3.141592f, 3.141592f * -0.5f);\n                draw_list->PathStroke(col, ImDrawFlags_None, th);\n                x += sz + spacing;\n\n                // Quadratic Bezier Curve (3 control points)\n                draw_list->AddBezierQuadratic(ImVec2(x + cp3[0].x, y + cp3[0].y), ImVec2(x + cp3[1].x, y + cp3[1].y), ImVec2(x + cp3[2].x, y + cp3[2].y), col, th, curve_segments);\n                x += sz + spacing;\n\n                // Cubic Bezier Curve (4 control points)\n                draw_list->AddBezierCubic(ImVec2(x + cp4[0].x, y + cp4[0].y), ImVec2(x + cp4[1].x, y + cp4[1].y), ImVec2(x + cp4[2].x, y + cp4[2].y), ImVec2(x + cp4[3].x, y + cp4[3].y), col, th, curve_segments);\n\n                x = p.x + 4;\n                y += sz + spacing;\n            }\n\n            // Filled shapes\n            draw_list->AddNgonFilled(ImVec2(x + sz * 0.5f, y + sz * 0.5f), sz * 0.5f, col, ngon_sides);             x += sz + spacing;  // N-gon\n            draw_list->AddCircleFilled(ImVec2(x + sz * 0.5f, y + sz * 0.5f), sz * 0.5f, col, circle_segments);      x += sz + spacing;  // Circle\n            draw_list->AddEllipseFilled(ImVec2(x + sz * 0.5f, y + sz * 0.5f), ImVec2(sz * 0.5f, sz * 0.3f), col, -0.3f, circle_segments); x += sz + spacing;// Ellipse\n            draw_list->AddRectFilled(ImVec2(x, y), ImVec2(x + sz, y + sz), col);                                    x += sz + spacing;  // Square\n            draw_list->AddRectFilled(ImVec2(x, y), ImVec2(x + sz, y + sz), col, 10.0f);                             x += sz + spacing;  // Square with all rounded corners\n            draw_list->AddRectFilled(ImVec2(x, y), ImVec2(x + sz, y + sz), col, 10.0f, corners_tl_br);              x += sz + spacing;  // Square with two rounded corners\n            draw_list->AddTriangleFilled(ImVec2(x+sz*0.5f,y), ImVec2(x+sz, y+sz-0.5f), ImVec2(x, y+sz-0.5f), col);  x += sz + spacing;  // Triangle\n            //draw_list->AddTriangleFilled(ImVec2(x+sz*0.2f,y), ImVec2(x, y+sz-0.5f), ImVec2(x+sz*0.4f, y+sz-0.5f), col); x += sz*0.4f + spacing; // Thin triangle\n            PathConcaveShape(draw_list, x, y, sz); draw_list->PathFillConcave(col);                                 x += sz + spacing;  // Concave shape\n            draw_list->AddRectFilled(ImVec2(x, y), ImVec2(x + sz, y + thickness), col);                             x += sz + spacing;  // Horizontal line (faster than AddLine, but only handle integer thickness)\n            draw_list->AddRectFilled(ImVec2(x, y), ImVec2(x + thickness, y + sz), col);                             x += spacing * 2.0f;// Vertical line (faster than AddLine, but only handle integer thickness)\n            draw_list->AddRectFilled(ImVec2(x, y), ImVec2(x + 1, y + 1), col);                                      x += sz;            // Pixel (faster than AddLine)\n\n            // Path\n            draw_list->PathArcTo(ImVec2(x + sz * 0.5f, y + sz * 0.5f), sz * 0.5f, 3.141592f * -0.5f, 3.141592f);\n            draw_list->PathFillConvex(col);\n            x += sz + spacing;\n\n            // Quadratic Bezier Curve (3 control points)\n            draw_list->PathLineTo(ImVec2(x + cp3[0].x, y + cp3[0].y));\n            draw_list->PathBezierQuadraticCurveTo(ImVec2(x + cp3[1].x, y + cp3[1].y), ImVec2(x + cp3[2].x, y + cp3[2].y), curve_segments);\n            draw_list->PathFillConvex(col);\n            x += sz + spacing;\n\n            draw_list->AddRectFilledMultiColor(ImVec2(x, y), ImVec2(x + sz, y + sz), IM_COL32(0, 0, 0, 255), IM_COL32(255, 0, 0, 255), IM_COL32(255, 255, 0, 255), IM_COL32(0, 255, 0, 255));\n            x += sz + spacing;\n\n            ImGui::Dummy(ImVec2((sz + spacing) * 13.2f, (sz + spacing) * 3.0f));\n            ImGui::PopItemWidth();\n            ImGui::EndTabItem();\n        }\n\n        if (ImGui::BeginTabItem(\"Canvas\"))\n        {\n            static ImVector<ImVec2> points;\n            static ImVec2 scrolling(0.0f, 0.0f);\n            static bool opt_enable_grid = true;\n            static bool opt_enable_context_menu = true;\n            static bool adding_line = false;\n\n            ImGui::Checkbox(\"Enable grid\", &opt_enable_grid);\n            ImGui::Checkbox(\"Enable context menu\", &opt_enable_context_menu);\n            ImGui::Text(\"Mouse Left: drag to add lines,\\nMouse Right: drag to scroll, click for context menu.\");\n\n            // Typically you would use a BeginChild()/EndChild() pair to benefit from a clipping region + own scrolling.\n            // Here we demonstrate that this can be replaced by simple offsetting + custom drawing + PushClipRect/PopClipRect() calls.\n            // To use a child window instead we could use, e.g:\n            //      ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0, 0));      // Disable padding\n            //      ImGui::PushStyleColor(ImGuiCol_ChildBg, IM_COL32(50, 50, 50, 255));  // Set a background color\n            //      ImGui::BeginChild(\"canvas\", ImVec2(0.0f, 0.0f), ImGuiChildFlags_Borders, ImGuiWindowFlags_NoMove);\n            //      ImGui::PopStyleColor();\n            //      ImGui::PopStyleVar();\n            //      [...]\n            //      ImGui::EndChild();\n\n            // Using InvisibleButton() as a convenience 1) it will advance the layout cursor and 2) allows us to use IsItemHovered()/IsItemActive()\n            ImVec2 canvas_p0 = ImGui::GetCursorScreenPos();      // ImDrawList API uses screen coordinates!\n            ImVec2 canvas_sz = ImGui::GetContentRegionAvail();   // Resize canvas to what's available\n            if (canvas_sz.x < 50.0f) canvas_sz.x = 50.0f;\n            if (canvas_sz.y < 50.0f) canvas_sz.y = 50.0f;\n            ImVec2 canvas_p1 = ImVec2(canvas_p0.x + canvas_sz.x, canvas_p0.y + canvas_sz.y);\n\n            // Draw border and background color\n            ImGuiIO& io = ImGui::GetIO();\n            ImDrawList* draw_list = ImGui::GetWindowDrawList();\n            draw_list->AddRectFilled(canvas_p0, canvas_p1, IM_COL32(50, 50, 50, 255));\n            draw_list->AddRect(canvas_p0, canvas_p1, IM_COL32(255, 255, 255, 255));\n\n            // This will catch our interactions\n            ImGui::InvisibleButton(\"canvas\", canvas_sz, ImGuiButtonFlags_MouseButtonLeft | ImGuiButtonFlags_MouseButtonRight);\n            const bool is_hovered = ImGui::IsItemHovered(); // Hovered\n            const bool is_active = ImGui::IsItemActive();   // Held\n            const ImVec2 origin(canvas_p0.x + scrolling.x, canvas_p0.y + scrolling.y); // Lock scrolled origin\n            const ImVec2 mouse_pos_in_canvas(io.MousePos.x - origin.x, io.MousePos.y - origin.y);\n\n            // Add first and second point\n            if (is_hovered && !adding_line && ImGui::IsMouseClicked(ImGuiMouseButton_Left))\n            {\n                points.push_back(mouse_pos_in_canvas);\n                points.push_back(mouse_pos_in_canvas);\n                adding_line = true;\n            }\n            if (adding_line)\n            {\n                points.back() = mouse_pos_in_canvas;\n                if (!ImGui::IsMouseDown(ImGuiMouseButton_Left))\n                    adding_line = false;\n            }\n\n            // Pan (we use a zero mouse threshold when there's no context menu)\n            // You may decide to make that threshold dynamic based on whether the mouse is hovering something etc.\n            const float mouse_threshold_for_pan = opt_enable_context_menu ? -1.0f : 0.0f;\n            if (is_active && ImGui::IsMouseDragging(ImGuiMouseButton_Right, mouse_threshold_for_pan))\n            {\n                scrolling.x += io.MouseDelta.x;\n                scrolling.y += io.MouseDelta.y;\n            }\n\n            // Context menu (under default mouse threshold)\n            ImVec2 drag_delta = ImGui::GetMouseDragDelta(ImGuiMouseButton_Right);\n            if (opt_enable_context_menu && drag_delta.x == 0.0f && drag_delta.y == 0.0f)\n                ImGui::OpenPopupOnItemClick(\"context\", ImGuiPopupFlags_MouseButtonRight);\n            if (ImGui::BeginPopup(\"context\"))\n            {\n                if (adding_line)\n                    points.resize(points.size() - 2);\n                adding_line = false;\n                if (ImGui::MenuItem(\"Remove one\", NULL, false, points.Size > 0)) { points.resize(points.size() - 2); }\n                if (ImGui::MenuItem(\"Remove all\", NULL, false, points.Size > 0)) { points.clear(); }\n                ImGui::EndPopup();\n            }\n\n            // Draw grid + all lines in the canvas\n            draw_list->PushClipRect(canvas_p0, canvas_p1, true);\n            if (opt_enable_grid)\n            {\n                const float GRID_STEP = 64.0f;\n                for (float x = fmodf(scrolling.x, GRID_STEP); x < canvas_sz.x; x += GRID_STEP)\n                    draw_list->AddLine(ImVec2(canvas_p0.x + x, canvas_p0.y), ImVec2(canvas_p0.x + x, canvas_p1.y), IM_COL32(200, 200, 200, 40));\n                for (float y = fmodf(scrolling.y, GRID_STEP); y < canvas_sz.y; y += GRID_STEP)\n                    draw_list->AddLine(ImVec2(canvas_p0.x, canvas_p0.y + y), ImVec2(canvas_p1.x, canvas_p0.y + y), IM_COL32(200, 200, 200, 40));\n            }\n            for (int n = 0; n < points.Size; n += 2)\n                draw_list->AddLine(ImVec2(origin.x + points[n].x, origin.y + points[n].y), ImVec2(origin.x + points[n + 1].x, origin.y + points[n + 1].y), IM_COL32(255, 255, 0, 255), 2.0f);\n            draw_list->PopClipRect();\n\n            ImGui::EndTabItem();\n        }\n\n        if (ImGui::BeginTabItem(\"BG/FG draw lists\"))\n        {\n            static bool draw_bg = true;\n            static bool draw_fg = true;\n            ImGui::Checkbox(\"Draw in Background draw list\", &draw_bg);\n            ImGui::SameLine(); HelpMarker(\"The Background draw list will be rendered below every Dear ImGui windows.\");\n            ImGui::Checkbox(\"Draw in Foreground draw list\", &draw_fg);\n            ImGui::SameLine(); HelpMarker(\"The Foreground draw list will be rendered over every Dear ImGui windows.\");\n            ImVec2 window_pos = ImGui::GetWindowPos();\n            ImVec2 window_size = ImGui::GetWindowSize();\n            ImVec2 window_center = ImVec2(window_pos.x + window_size.x * 0.5f, window_pos.y + window_size.y * 0.5f);\n            if (draw_bg)\n                ImGui::GetBackgroundDrawList()->AddCircle(window_center, window_size.x * 0.6f, IM_COL32(255, 0, 0, 200), 0, 10 + 4);\n            if (draw_fg)\n                ImGui::GetForegroundDrawList()->AddCircle(window_center, window_size.y * 0.6f, IM_COL32(0, 255, 0, 200), 0, 10);\n            ImGui::EndTabItem();\n        }\n\n        // Demonstrate out-of-order rendering via channels splitting\n        // We use functions in ImDrawList as each draw list contains a convenience splitter,\n        // but you can also instantiate your own ImDrawListSplitter if you need to nest them.\n        if (ImGui::BeginTabItem(\"Draw Channels\"))\n        {\n            ImDrawList* draw_list = ImGui::GetWindowDrawList();\n            {\n                ImGui::Text(\"Blue shape is drawn first: appears in back\");\n                ImGui::Text(\"Red shape is drawn after: appears in front\");\n                ImVec2 p0 = ImGui::GetCursorScreenPos();\n                draw_list->AddRectFilled(ImVec2(p0.x, p0.y), ImVec2(p0.x + 50, p0.y + 50), IM_COL32(0, 0, 255, 255)); // Blue\n                draw_list->AddRectFilled(ImVec2(p0.x + 25, p0.y + 25), ImVec2(p0.x + 75, p0.y + 75), IM_COL32(255, 0, 0, 255)); // Red\n                ImGui::Dummy(ImVec2(75, 75));\n            }\n            ImGui::Separator();\n            {\n                ImGui::Text(\"Blue shape is drawn first, into channel 1: appears in front\");\n                ImGui::Text(\"Red shape is drawn after, into channel 0: appears in back\");\n                ImVec2 p1 = ImGui::GetCursorScreenPos();\n\n                // Create 2 channels and draw a Blue shape THEN a Red shape.\n                // You can create any number of channels. Tables API use 1 channel per column in order to better batch draw calls.\n                draw_list->ChannelsSplit(2);\n                draw_list->ChannelsSetCurrent(1);\n                draw_list->AddRectFilled(ImVec2(p1.x, p1.y), ImVec2(p1.x + 50, p1.y + 50), IM_COL32(0, 0, 255, 255)); // Blue\n                draw_list->ChannelsSetCurrent(0);\n                draw_list->AddRectFilled(ImVec2(p1.x + 25, p1.y + 25), ImVec2(p1.x + 75, p1.y + 75), IM_COL32(255, 0, 0, 255)); // Red\n\n                // Flatten/reorder channels. Red shape is in channel 0 and it appears below the Blue shape in channel 1.\n                // This works by copying draw indices only (vertices are not copied).\n                draw_list->ChannelsMerge();\n                ImGui::Dummy(ImVec2(75, 75));\n                ImGui::Text(\"After reordering, contents of channel 0 appears below channel 1.\");\n            }\n            ImGui::EndTabItem();\n        }\n\n        ImGui::EndTabBar();\n    }\n\n    ImGui::End();\n}\n\n//-----------------------------------------------------------------------------\n// [SECTION] Example App: Documents Handling / ShowExampleAppDocuments()\n//-----------------------------------------------------------------------------\n\n// Simplified structure to mimic a Document model\nstruct MyDocument\n{\n    char        Name[32];   // Document title\n    int         UID;        // Unique ID (necessary as we can change title)\n    bool        Open;       // Set when open (we keep an array of all available documents to simplify demo code!)\n    bool        OpenPrev;   // Copy of Open from last update.\n    bool        Dirty;      // Set when the document has been modified\n    ImVec4      Color;      // An arbitrary variable associated to the document\n\n    MyDocument(int uid, const char* name, bool open = true, const ImVec4& color = ImVec4(1.0f, 1.0f, 1.0f, 1.0f))\n    {\n        UID = uid;\n        snprintf(Name, sizeof(Name), \"%s\", name);\n        Open = OpenPrev = open;\n        Dirty = false;\n        Color = color;\n    }\n    void DoOpen()       { Open = true; }\n    void DoForceClose() { Open = false; Dirty = false; }\n    void DoSave()       { Dirty = false; }\n};\n\nstruct ExampleAppDocuments\n{\n    ImVector<MyDocument>    Documents;\n    ImVector<MyDocument*>   CloseQueue;\n    MyDocument*             RenamingDoc = NULL;\n    bool                    RenamingStarted = false;\n\n    ExampleAppDocuments()\n    {\n        Documents.push_back(MyDocument(0, \"Lettuce\",             true,  ImVec4(0.4f, 0.8f, 0.4f, 1.0f)));\n        Documents.push_back(MyDocument(1, \"Eggplant\",            true,  ImVec4(0.8f, 0.5f, 1.0f, 1.0f)));\n        Documents.push_back(MyDocument(2, \"Carrot\",              true,  ImVec4(1.0f, 0.8f, 0.5f, 1.0f)));\n        Documents.push_back(MyDocument(3, \"Tomato\",              false, ImVec4(1.0f, 0.3f, 0.4f, 1.0f)));\n        Documents.push_back(MyDocument(4, \"A Rather Long Title\", false, ImVec4(0.4f, 0.8f, 0.8f, 1.0f)));\n        Documents.push_back(MyDocument(5, \"Some Document\",       false, ImVec4(0.8f, 0.8f, 1.0f, 1.0f)));\n    }\n\n    // As we allow to change document name, we append a never-changing document ID so tabs are stable\n    void GetTabName(MyDocument* doc, char* out_buf, size_t out_buf_size)\n    {\n        snprintf(out_buf, out_buf_size, \"%s###doc%d\", doc->Name, doc->UID);\n    }\n\n    // Display placeholder contents for the Document\n    void DisplayDocContents(MyDocument* doc)\n    {\n        ImGui::PushID(doc);\n        ImGui::Text(\"Document \\\"%s\\\"\", doc->Name);\n        ImGui::PushStyleColor(ImGuiCol_Text, doc->Color);\n        ImGui::TextWrapped(\"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.\");\n        ImGui::PopStyleColor();\n\n        ImGui::SetNextItemShortcut(ImGuiMod_Ctrl | ImGuiKey_R, ImGuiInputFlags_Tooltip);\n        if (ImGui::Button(\"Rename..\"))\n        {\n            RenamingDoc = doc;\n            RenamingStarted = true;\n        }\n        ImGui::SameLine();\n\n        ImGui::SetNextItemShortcut(ImGuiMod_Ctrl | ImGuiKey_M, ImGuiInputFlags_Tooltip);\n        if (ImGui::Button(\"Modify\"))\n            doc->Dirty = true;\n\n        ImGui::SameLine();\n        ImGui::SetNextItemShortcut(ImGuiMod_Ctrl | ImGuiKey_S, ImGuiInputFlags_Tooltip);\n        if (ImGui::Button(\"Save\"))\n            doc->DoSave();\n\n        ImGui::SameLine();\n        ImGui::SetNextItemShortcut(ImGuiMod_Ctrl | ImGuiKey_W, ImGuiInputFlags_Tooltip);\n        if (ImGui::Button(\"Close\"))\n            CloseQueue.push_back(doc);\n        ImGui::ColorEdit3(\"color\", &doc->Color.x);  // Useful to test drag and drop and hold-dragged-to-open-tab behavior.\n        ImGui::PopID();\n    }\n\n    // Display context menu for the Document\n    void DisplayDocContextMenu(MyDocument* doc)\n    {\n        if (!ImGui::BeginPopupContextItem())\n            return;\n\n        char buf[256];\n        sprintf(buf, \"Save %s\", doc->Name);\n        if (ImGui::MenuItem(buf, \"Ctrl+S\", false, doc->Open))\n            doc->DoSave();\n        if (ImGui::MenuItem(\"Rename...\", \"Ctrl+R\", false, doc->Open))\n            RenamingDoc = doc;\n        if (ImGui::MenuItem(\"Close\", \"Ctrl+W\", false, doc->Open))\n            CloseQueue.push_back(doc);\n        ImGui::EndPopup();\n    }\n\n    // [Optional] Notify the system of Tabs/Windows closure that happened outside the regular tab interface.\n    // If a tab has been closed programmatically (aka closed from another source such as the Checkbox() in the demo,\n    // as opposed to clicking on the regular tab closing button) and stops being submitted, it will take a frame for\n    // the tab bar to notice its absence. During this frame there will be a gap in the tab bar, and if the tab that has\n    // disappeared was the selected one, the tab bar will report no selected tab during the frame. This will effectively\n    // give the impression of a flicker for one frame.\n    // We call SetTabItemClosed() to manually notify the Tab Bar or Docking system of removed tabs to avoid this glitch.\n    // Note that this completely optional, and only affect tab bars with the ImGuiTabBarFlags_Reorderable flag.\n    void NotifyOfDocumentsClosedElsewhere()\n    {\n        for (MyDocument& doc : Documents)\n        {\n            if (!doc.Open && doc.OpenPrev)\n                ImGui::SetTabItemClosed(doc.Name);\n            doc.OpenPrev = doc.Open;\n        }\n    }\n};\n\nvoid ShowExampleAppDocuments(bool* p_open)\n{\n    static ExampleAppDocuments app;\n\n    // Options\n    static bool opt_reorderable = true;\n    static ImGuiTabBarFlags opt_fitting_flags = ImGuiTabBarFlags_FittingPolicyDefault_;\n\n    bool window_contents_visible = ImGui::Begin(\"Example: Documents\", p_open, ImGuiWindowFlags_MenuBar);\n    if (!window_contents_visible)\n    {\n        ImGui::End();\n        return;\n    }\n\n    // Menu\n    if (ImGui::BeginMenuBar())\n    {\n        if (ImGui::BeginMenu(\"File\"))\n        {\n            int open_count = 0;\n            for (MyDocument& doc : app.Documents)\n                open_count += doc.Open ? 1 : 0;\n\n            if (ImGui::BeginMenu(\"Open\", open_count < app.Documents.Size))\n            {\n                for (MyDocument& doc : app.Documents)\n                    if (!doc.Open && ImGui::MenuItem(doc.Name))\n                        doc.DoOpen();\n                ImGui::EndMenu();\n            }\n            if (ImGui::MenuItem(\"Close All Documents\", NULL, false, open_count > 0))\n                for (MyDocument& doc : app.Documents)\n                    app.CloseQueue.push_back(&doc);\n            if (ImGui::MenuItem(\"Exit\") && p_open)\n                *p_open = false;\n            ImGui::EndMenu();\n        }\n        ImGui::EndMenuBar();\n    }\n\n    // [Debug] List documents with one checkbox for each\n    for (int doc_n = 0; doc_n < app.Documents.Size; doc_n++)\n    {\n        MyDocument& doc = app.Documents[doc_n];\n        if (doc_n > 0)\n            ImGui::SameLine();\n        ImGui::PushID(&doc);\n        if (ImGui::Checkbox(doc.Name, &doc.Open))\n            if (!doc.Open)\n                doc.DoForceClose();\n        ImGui::PopID();\n    }\n\n    ImGui::Separator();\n\n    // About the ImGuiWindowFlags_UnsavedDocument / ImGuiTabItemFlags_UnsavedDocument flags.\n    // They have multiple effects:\n    // - Display a dot next to the title.\n    // - Tab is selected when clicking the X close button.\n    // - Closure is not assumed (will wait for user to stop submitting the tab).\n    //   Otherwise closure is assumed when pressing the X, so if you keep submitting the tab may reappear at end of tab bar.\n    //   We need to assume closure by default otherwise waiting for \"lack of submission\" on the next frame would leave an empty\n    //   hole for one-frame, both in the tab-bar and in tab-contents when closing a tab/window.\n    //   The rarely used SetTabItemClosed() function is a way to notify of programmatic closure to avoid the one-frame hole.\n\n    // Submit Tab Bar and Tabs\n    {\n        ImGuiTabBarFlags tab_bar_flags = (opt_fitting_flags) | (opt_reorderable ? ImGuiTabBarFlags_Reorderable : 0);\n        tab_bar_flags |= ImGuiTabBarFlags_DrawSelectedOverline;\n        if (ImGui::BeginTabBar(\"##tabs\", tab_bar_flags))\n        {\n            if (opt_reorderable)\n                app.NotifyOfDocumentsClosedElsewhere();\n\n            // [DEBUG] Stress tests\n            //if ((ImGui::GetFrameCount() % 30) == 0) docs[1].Open ^= 1;            // [DEBUG] Automatically show/hide a tab. Test various interactions e.g. dragging with this on.\n            //if (ImGui::GetIO().KeyCtrl) ImGui::SetTabItemSelected(docs[1].Name);  // [DEBUG] Test SetTabItemSelected(), probably not very useful as-is anyway..\n\n            // Submit Tabs\n            for (MyDocument& doc : app.Documents)\n            {\n                if (!doc.Open)\n                    continue;\n\n                // As we allow to change document name, we append a never-changing document id so tabs are stable\n                char doc_name_buf[64];\n                app.GetTabName(&doc, doc_name_buf, sizeof(doc_name_buf));\n                ImGuiTabItemFlags tab_flags = (doc.Dirty ? ImGuiTabItemFlags_UnsavedDocument : 0);\n                bool visible = ImGui::BeginTabItem(doc_name_buf, &doc.Open, tab_flags);\n\n                // Cancel attempt to close when unsaved add to save queue so we can display a popup.\n                if (!doc.Open && doc.Dirty)\n                {\n                    doc.Open = true;\n                    app.CloseQueue.push_back(&doc);\n                }\n\n                app.DisplayDocContextMenu(&doc);\n                if (visible)\n                {\n                    app.DisplayDocContents(&doc);\n                    ImGui::EndTabItem();\n                }\n            }\n\n            ImGui::EndTabBar();\n        }\n    }\n\n    // Display renaming UI\n    if (app.RenamingDoc != NULL)\n    {\n        if (app.RenamingStarted)\n            ImGui::OpenPopup(\"Rename\");\n        if (ImGui::BeginPopup(\"Rename\"))\n        {\n            ImGui::SetNextItemWidth(ImGui::GetFontSize() * 30);\n            if (ImGui::InputText(\"###Name\", app.RenamingDoc->Name, IM_ARRAYSIZE(app.RenamingDoc->Name), ImGuiInputTextFlags_EnterReturnsTrue))\n            {\n                ImGui::CloseCurrentPopup();\n                app.RenamingDoc = NULL;\n            }\n            if (app.RenamingStarted)\n                ImGui::SetKeyboardFocusHere(-1);\n            ImGui::EndPopup();\n        }\n        else\n        {\n            app.RenamingDoc = NULL;\n        }\n        app.RenamingStarted = false;\n    }\n\n    // Display closing confirmation UI\n    if (!app.CloseQueue.empty())\n    {\n        int close_queue_unsaved_documents = 0;\n        for (int n = 0; n < app.CloseQueue.Size; n++)\n            if (app.CloseQueue[n]->Dirty)\n                close_queue_unsaved_documents++;\n\n        if (close_queue_unsaved_documents == 0)\n        {\n            // Close documents when all are unsaved\n            for (int n = 0; n < app.CloseQueue.Size; n++)\n                app.CloseQueue[n]->DoForceClose();\n            app.CloseQueue.clear();\n        }\n        else\n        {\n            if (!ImGui::IsPopupOpen(\"Save?\"))\n                ImGui::OpenPopup(\"Save?\");\n            if (ImGui::BeginPopupModal(\"Save?\", NULL, ImGuiWindowFlags_AlwaysAutoResize))\n            {\n                ImGui::Text(\"Save change to the following items?\");\n                float item_height = ImGui::GetTextLineHeightWithSpacing();\n                if (ImGui::BeginChild(ImGui::GetID(\"frame\"), ImVec2(-FLT_MIN, 6.25f * item_height), ImGuiChildFlags_FrameStyle))\n                    for (MyDocument* doc : app.CloseQueue)\n                        if (doc->Dirty)\n                            ImGui::Text(\"%s\", doc->Name);\n                ImGui::EndChild();\n\n                ImVec2 button_size(ImGui::GetFontSize() * 7.0f, 0.0f);\n                if (ImGui::Button(\"Yes\", button_size))\n                {\n                    for (MyDocument* doc : app.CloseQueue)\n                    {\n                        if (doc->Dirty)\n                            doc->DoSave();\n                        doc->DoForceClose();\n                    }\n                    app.CloseQueue.clear();\n                    ImGui::CloseCurrentPopup();\n                }\n                ImGui::SameLine();\n                if (ImGui::Button(\"No\", button_size))\n                {\n                    for (MyDocument* doc : app.CloseQueue)\n                        doc->DoForceClose();\n                    app.CloseQueue.clear();\n                    ImGui::CloseCurrentPopup();\n                }\n                ImGui::SameLine();\n                if (ImGui::Button(\"Cancel\", button_size))\n                {\n                    app.CloseQueue.clear();\n                    ImGui::CloseCurrentPopup();\n                }\n                ImGui::EndPopup();\n            }\n        }\n    }\n\n    ImGui::End();\n}\n\n//-----------------------------------------------------------------------------\n// [SECTION] Example App: Assets Browser / ShowExampleAppAssetsBrowser()\n//-----------------------------------------------------------------------------\n\n//#include \"imgui_internal.h\" // NavMoveRequestTryWrapping()\n\nstruct ExampleAsset\n{\n    ImGuiID ID;\n    int     Type;\n\n    ExampleAsset(ImGuiID id, int type) { ID = id; Type = type; }\n\n    static const ImGuiTableSortSpecs* s_current_sort_specs;\n\n    static void SortWithSortSpecs(ImGuiTableSortSpecs* sort_specs, ExampleAsset* items, int items_count)\n    {\n        s_current_sort_specs = sort_specs; // Store in variable accessible by the sort function.\n        if (items_count > 1)\n            qsort(items, (size_t)items_count, sizeof(items[0]), ExampleAsset::CompareWithSortSpecs);\n        s_current_sort_specs = NULL;\n    }\n\n    // Compare function to be used by qsort()\n    static int IMGUI_CDECL CompareWithSortSpecs(const void* lhs, const void* rhs)\n    {\n        const ExampleAsset* a = (const ExampleAsset*)lhs;\n        const ExampleAsset* b = (const ExampleAsset*)rhs;\n        for (int n = 0; n < s_current_sort_specs->SpecsCount; n++)\n        {\n            const ImGuiTableColumnSortSpecs* sort_spec = &s_current_sort_specs->Specs[n];\n            int delta = 0;\n            if (sort_spec->ColumnIndex == 0)\n                delta = ((int)a->ID - (int)b->ID);\n            else if (sort_spec->ColumnIndex == 1)\n                delta = (a->Type - b->Type);\n            if (delta > 0)\n                return (sort_spec->SortDirection == ImGuiSortDirection_Ascending) ? +1 : -1;\n            if (delta < 0)\n                return (sort_spec->SortDirection == ImGuiSortDirection_Ascending) ? -1 : +1;\n        }\n        return ((int)a->ID - (int)b->ID);\n    }\n};\nconst ImGuiTableSortSpecs* ExampleAsset::s_current_sort_specs = NULL;\n\nstruct ExampleAssetsBrowser\n{\n    // Options\n    bool            ShowTypeOverlay = true;\n    bool            AllowSorting = true;\n    bool            AllowDragUnselected = false;\n    bool            AllowBoxSelect = true;\n    float           IconSize = 32.0f;\n    int             IconSpacing = 10;\n    int             IconHitSpacing = 4;         // Increase hit-spacing if you want to make it possible to clear or box-select from gaps. Some spacing is required to able to amend with Shift+box-select. Value is small in Explorer.\n    bool            StretchSpacing = true;\n\n    // State\n    ImVector<ExampleAsset> Items;               // Our items\n    ExampleSelectionWithDeletion Selection;     // Our selection (ImGuiSelectionBasicStorage + helper funcs to handle deletion)\n    ImGuiID         NextItemId = 0;             // Unique identifier when creating new items\n    bool            RequestDelete = false;      // Deferred deletion request\n    bool            RequestSort = false;        // Deferred sort request\n    float           ZoomWheelAccum = 0.0f;      // Mouse wheel accumulator to handle smooth wheels better\n\n    // Calculated sizes for layout, output of UpdateLayoutSizes(). Could be locals but our code is simpler this way.\n    ImVec2          LayoutItemSize;\n    ImVec2          LayoutItemStep;             // == LayoutItemSize + LayoutItemSpacing\n    float           LayoutItemSpacing = 0.0f;\n    float           LayoutSelectableSpacing = 0.0f;\n    float           LayoutOuterPadding = 0.0f;\n    int             LayoutColumnCount = 0;\n    int             LayoutLineCount = 0;\n\n    // Functions\n    ExampleAssetsBrowser()\n    {\n        AddItems(10000);\n    }\n    void AddItems(int count)\n    {\n        if (Items.Size == 0)\n            NextItemId = 0;\n        Items.reserve(Items.Size + count);\n        for (int n = 0; n < count; n++, NextItemId++)\n            Items.push_back(ExampleAsset(NextItemId, (NextItemId % 20) < 15 ? 0 : (NextItemId % 20) < 18 ? 1 : 2));\n        RequestSort = true;\n    }\n    void ClearItems()\n    {\n        Items.clear();\n        Selection.Clear();\n    }\n\n    // Logic would be written in the main code BeginChild() and outputing to local variables.\n    // We extracted it into a function so we can call it easily from multiple places.\n    void UpdateLayoutSizes(float avail_width)\n    {\n        // Layout: when not stretching: allow extending into right-most spacing.\n        LayoutItemSpacing = (float)IconSpacing;\n        if (StretchSpacing == false)\n            avail_width += floorf(LayoutItemSpacing * 0.5f);\n\n        // Layout: calculate number of icon per line and number of lines\n        LayoutItemSize = ImVec2(floorf(IconSize), floorf(IconSize));\n        LayoutColumnCount = IM_MAX((int)(avail_width / (LayoutItemSize.x + LayoutItemSpacing)), 1);\n        LayoutLineCount = (Items.Size + LayoutColumnCount - 1) / LayoutColumnCount;\n\n        // Layout: when stretching: allocate remaining space to more spacing. Round before division, so item_spacing may be non-integer.\n        if (StretchSpacing && LayoutColumnCount > 1)\n            LayoutItemSpacing = floorf(avail_width - LayoutItemSize.x * LayoutColumnCount) / LayoutColumnCount;\n\n        LayoutItemStep = ImVec2(LayoutItemSize.x + LayoutItemSpacing, LayoutItemSize.y + LayoutItemSpacing);\n        LayoutSelectableSpacing = IM_MAX(floorf(LayoutItemSpacing) - IconHitSpacing, 0.0f);\n        LayoutOuterPadding = floorf(LayoutItemSpacing * 0.5f);\n    }\n\n    void Draw(const char* title, bool* p_open)\n    {\n        ImGui::SetNextWindowSize(ImVec2(IconSize * 25, IconSize * 15), ImGuiCond_FirstUseEver);\n        if (!ImGui::Begin(title, p_open, ImGuiWindowFlags_MenuBar))\n        {\n            ImGui::End();\n            return;\n        }\n\n        // Menu bar\n        if (ImGui::BeginMenuBar())\n        {\n            if (ImGui::BeginMenu(\"File\"))\n            {\n                if (ImGui::MenuItem(\"Add 10000 items\"))\n                    AddItems(10000);\n                if (ImGui::MenuItem(\"Clear items\"))\n                    ClearItems();\n                ImGui::Separator();\n                if (ImGui::MenuItem(\"Close\", NULL, false, p_open != NULL))\n                    *p_open = false;\n                ImGui::EndMenu();\n            }\n            if (ImGui::BeginMenu(\"Edit\"))\n            {\n                if (ImGui::MenuItem(\"Delete\", \"Del\", false, Selection.Size > 0))\n                    RequestDelete = true;\n                ImGui::EndMenu();\n            }\n            if (ImGui::BeginMenu(\"Options\"))\n            {\n                ImGui::PushItemWidth(ImGui::GetFontSize() * 10);\n\n                ImGui::SeparatorText(\"Contents\");\n                ImGui::Checkbox(\"Show Type Overlay\", &ShowTypeOverlay);\n                ImGui::Checkbox(\"Allow Sorting\", &AllowSorting);\n\n                ImGui::SeparatorText(\"Selection Behavior\");\n                ImGui::Checkbox(\"Allow dragging unselected item\", &AllowDragUnselected);\n                ImGui::Checkbox(\"Allow box-selection\", &AllowBoxSelect);\n\n                ImGui::SeparatorText(\"Layout\");\n                ImGui::SliderFloat(\"Icon Size\", &IconSize, 16.0f, 128.0f, \"%.0f\");\n                ImGui::SameLine(); HelpMarker(\"Use CTRL+Wheel to zoom\");\n                ImGui::SliderInt(\"Icon Spacing\", &IconSpacing, 0, 32);\n                ImGui::SliderInt(\"Icon Hit Spacing\", &IconHitSpacing, 0, 32);\n                ImGui::Checkbox(\"Stretch Spacing\", &StretchSpacing);\n                ImGui::PopItemWidth();\n                ImGui::EndMenu();\n            }\n            ImGui::EndMenuBar();\n        }\n\n        // Show a table with ONLY one header row to showcase the idea/possibility of using this to provide a sorting UI\n        if (AllowSorting)\n        {\n            ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0, 0));\n            ImGuiTableFlags table_flags_for_sort_specs = ImGuiTableFlags_Sortable | ImGuiTableFlags_SortMulti | ImGuiTableFlags_SizingFixedFit | ImGuiTableFlags_Borders;\n            if (ImGui::BeginTable(\"for_sort_specs_only\", 2, table_flags_for_sort_specs, ImVec2(0.0f, ImGui::GetFrameHeight())))\n            {\n                ImGui::TableSetupColumn(\"Index\");\n                ImGui::TableSetupColumn(\"Type\");\n                ImGui::TableHeadersRow();\n                if (ImGuiTableSortSpecs* sort_specs = ImGui::TableGetSortSpecs())\n                    if (sort_specs->SpecsDirty || RequestSort)\n                    {\n                        ExampleAsset::SortWithSortSpecs(sort_specs, Items.Data, Items.Size);\n                        sort_specs->SpecsDirty = RequestSort = false;\n                    }\n                ImGui::EndTable();\n            }\n            ImGui::PopStyleVar();\n        }\n\n        ImGuiIO& io = ImGui::GetIO();\n        ImGui::SetNextWindowContentSize(ImVec2(0.0f, LayoutOuterPadding + LayoutLineCount * (LayoutItemSize.y + LayoutItemSpacing)));\n        if (ImGui::BeginChild(\"Assets\", ImVec2(0.0f, -ImGui::GetTextLineHeightWithSpacing()), ImGuiChildFlags_Borders, ImGuiWindowFlags_NoMove))\n        {\n            ImDrawList* draw_list = ImGui::GetWindowDrawList();\n\n            const float avail_width = ImGui::GetContentRegionAvail().x;\n            UpdateLayoutSizes(avail_width);\n\n            // Calculate and store start position.\n            ImVec2 start_pos = ImGui::GetCursorScreenPos();\n            start_pos = ImVec2(start_pos.x + LayoutOuterPadding, start_pos.y + LayoutOuterPadding);\n            ImGui::SetCursorScreenPos(start_pos);\n\n            // Multi-select\n            ImGuiMultiSelectFlags ms_flags = ImGuiMultiSelectFlags_ClearOnEscape | ImGuiMultiSelectFlags_ClearOnClickVoid;\n\n            // - Enable box-select (in 2D mode, so that changing box-select rectangle X1/X2 boundaries will affect clipped items)\n            if (AllowBoxSelect)\n                ms_flags |= ImGuiMultiSelectFlags_BoxSelect2d;\n\n            // - This feature allows dragging an unselected item without selecting it (rarely used)\n            if (AllowDragUnselected)\n                ms_flags |= ImGuiMultiSelectFlags_SelectOnClickRelease;\n\n            // - Enable keyboard wrapping on X axis\n            // (FIXME-MULTISELECT: We haven't designed/exposed a general nav wrapping api yet, so this flag is provided as a courtesy to avoid doing:\n            //    ImGui::NavMoveRequestTryWrapping(ImGui::GetCurrentWindow(), ImGuiNavMoveFlags_WrapX);\n            // When we finish implementing a more general API for this, we will obsolete this flag in favor of the new system)\n            ms_flags |= ImGuiMultiSelectFlags_NavWrapX;\n\n            ImGuiMultiSelectIO* ms_io = ImGui::BeginMultiSelect(ms_flags, Selection.Size, Items.Size);\n\n            // Use custom selection adapter: store ID in selection (recommended)\n            Selection.UserData = this;\n            Selection.AdapterIndexToStorageId = [](ImGuiSelectionBasicStorage* self_, int idx) { ExampleAssetsBrowser* self = (ExampleAssetsBrowser*)self_->UserData; return self->Items[idx].ID; };\n            Selection.ApplyRequests(ms_io);\n\n            const bool want_delete = (ImGui::Shortcut(ImGuiKey_Delete, ImGuiInputFlags_Repeat) && (Selection.Size > 0)) || RequestDelete;\n            const int item_curr_idx_to_focus = want_delete ? Selection.ApplyDeletionPreLoop(ms_io, Items.Size) : -1;\n            RequestDelete = false;\n\n            // Push LayoutSelectableSpacing (which is LayoutItemSpacing minus hit-spacing, if we decide to have hit gaps between items)\n            // Altering style ItemSpacing may seem unnecessary as we position every items using SetCursorScreenPos()...\n            // But it is necessary for two reasons:\n            // - Selectables uses it by default to visually fill the space between two items.\n            // - The vertical spacing would be measured by Clipper to calculate line height if we didn't provide it explicitly (here we do).\n            ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(LayoutSelectableSpacing, LayoutSelectableSpacing));\n\n            // Rendering parameters\n            const ImU32 icon_type_overlay_colors[3] = { 0, IM_COL32(200, 70, 70, 255), IM_COL32(70, 170, 70, 255) };\n            const ImU32 icon_bg_color = ImGui::GetColorU32(IM_COL32(35, 35, 35, 220));\n            const ImVec2 icon_type_overlay_size = ImVec2(4.0f, 4.0f);\n            const bool display_label = (LayoutItemSize.x >= ImGui::CalcTextSize(\"999\").x);\n\n            const int column_count = LayoutColumnCount;\n            ImGuiListClipper clipper;\n            clipper.Begin(LayoutLineCount, LayoutItemStep.y);\n            if (item_curr_idx_to_focus != -1)\n                clipper.IncludeItemByIndex(item_curr_idx_to_focus / column_count); // Ensure focused item line is not clipped.\n            if (ms_io->RangeSrcItem != -1)\n                clipper.IncludeItemByIndex((int)ms_io->RangeSrcItem / column_count); // Ensure RangeSrc item line is not clipped.\n            while (clipper.Step())\n            {\n                for (int line_idx = clipper.DisplayStart; line_idx < clipper.DisplayEnd; line_idx++)\n                {\n                    const int item_min_idx_for_current_line = line_idx * column_count;\n                    const int item_max_idx_for_current_line = IM_MIN((line_idx + 1) * column_count, Items.Size);\n                    for (int item_idx = item_min_idx_for_current_line; item_idx < item_max_idx_for_current_line; ++item_idx)\n                    {\n                        ExampleAsset* item_data = &Items[item_idx];\n                        ImGui::PushID((int)item_data->ID);\n\n                        // Position item\n                        ImVec2 pos = ImVec2(start_pos.x + (item_idx % column_count) * LayoutItemStep.x, start_pos.y + line_idx * LayoutItemStep.y);\n                        ImGui::SetCursorScreenPos(pos);\n\n                        ImGui::SetNextItemSelectionUserData(item_idx);\n                        bool item_is_selected = Selection.Contains((ImGuiID)item_data->ID);\n                        bool item_is_visible = ImGui::IsRectVisible(LayoutItemSize);\n                        ImGui::Selectable(\"\", item_is_selected, ImGuiSelectableFlags_None, LayoutItemSize);\n\n                        // Update our selection state immediately (without waiting for EndMultiSelect() requests)\n                        // because we use this to alter the color of our text/icon.\n                        if (ImGui::IsItemToggledSelection())\n                            item_is_selected = !item_is_selected;\n\n                        // Focus (for after deletion)\n                        if (item_curr_idx_to_focus == item_idx)\n                            ImGui::SetKeyboardFocusHere(-1);\n\n                        // Drag and drop\n                        if (ImGui::BeginDragDropSource())\n                        {\n                            // Create payload with full selection OR single unselected item.\n                            // (the later is only possible when using ImGuiMultiSelectFlags_SelectOnClickRelease)\n                            if (ImGui::GetDragDropPayload() == NULL)\n                            {\n                                ImVector<ImGuiID> payload_items;\n                                void* it = NULL;\n                                ImGuiID id = 0;\n                                if (!item_is_selected)\n                                    payload_items.push_back(item_data->ID);\n                                else\n                                    while (Selection.GetNextSelectedItem(&it, &id))\n                                        payload_items.push_back(id);\n                                ImGui::SetDragDropPayload(\"ASSETS_BROWSER_ITEMS\", payload_items.Data, (size_t)payload_items.size_in_bytes());\n                            }\n\n                            // Display payload content in tooltip, by extracting it from the payload data\n                            // (we could read from selection, but it is more correct and reusable to read from payload)\n                            const ImGuiPayload* payload = ImGui::GetDragDropPayload();\n                            const int payload_count = (int)payload->DataSize / (int)sizeof(ImGuiID);\n                            ImGui::Text(\"%d assets\", payload_count);\n\n                            ImGui::EndDragDropSource();\n                        }\n\n                        // Render icon (a real app would likely display an image/thumbnail here)\n                        // Because we use ImGuiMultiSelectFlags_BoxSelect2d, clipping vertical may occasionally be larger, so we coarse-clip our rendering as well.\n                        if (item_is_visible)\n                        {\n                            ImVec2 box_min(pos.x - 1, pos.y - 1);\n                            ImVec2 box_max(box_min.x + LayoutItemSize.x + 2, box_min.y + LayoutItemSize.y + 2); // Dubious\n                            draw_list->AddRectFilled(box_min, box_max, icon_bg_color); // Background color\n                            if (ShowTypeOverlay && item_data->Type != 0)\n                            {\n                                ImU32 type_col = icon_type_overlay_colors[item_data->Type % IM_ARRAYSIZE(icon_type_overlay_colors)];\n                                draw_list->AddRectFilled(ImVec2(box_max.x - 2 - icon_type_overlay_size.x, box_min.y + 2), ImVec2(box_max.x - 2, box_min.y + 2 + icon_type_overlay_size.y), type_col);\n                            }\n                            if (display_label)\n                            {\n                                ImU32 label_col = ImGui::GetColorU32(item_is_selected ? ImGuiCol_Text : ImGuiCol_TextDisabled);\n                                char label[32];\n                                sprintf(label, \"%d\", item_data->ID);\n                                draw_list->AddText(ImVec2(box_min.x, box_max.y - ImGui::GetFontSize()), label_col, label);\n                            }\n                        }\n\n                        ImGui::PopID();\n                    }\n                }\n            }\n            clipper.End();\n            ImGui::PopStyleVar(); // ImGuiStyleVar_ItemSpacing\n\n            // Context menu\n            if (ImGui::BeginPopupContextWindow())\n            {\n                ImGui::Text(\"Selection: %d items\", Selection.Size);\n                ImGui::Separator();\n                if (ImGui::MenuItem(\"Delete\", \"Del\", false, Selection.Size > 0))\n                    RequestDelete = true;\n                ImGui::EndPopup();\n            }\n\n            ms_io = ImGui::EndMultiSelect();\n            Selection.ApplyRequests(ms_io);\n            if (want_delete)\n                Selection.ApplyDeletionPostLoop(ms_io, Items, item_curr_idx_to_focus);\n\n            // Zooming with CTRL+Wheel\n            if (ImGui::IsWindowAppearing())\n                ZoomWheelAccum = 0.0f;\n            if (ImGui::IsWindowHovered() && io.MouseWheel != 0.0f && ImGui::IsKeyDown(ImGuiMod_Ctrl) && ImGui::IsAnyItemActive() == false)\n            {\n                ZoomWheelAccum += io.MouseWheel;\n                if (fabsf(ZoomWheelAccum) >= 1.0f)\n                {\n                    // Calculate hovered item index from mouse location\n                    // FIXME: Locking aiming on 'hovered_item_idx' (with a cool-down timer) would ensure zoom keeps on it.\n                    const float hovered_item_nx = (io.MousePos.x - start_pos.x + LayoutItemSpacing * 0.5f) / LayoutItemStep.x;\n                    const float hovered_item_ny = (io.MousePos.y - start_pos.y + LayoutItemSpacing * 0.5f) / LayoutItemStep.y;\n                    const int hovered_item_idx = ((int)hovered_item_ny * LayoutColumnCount) + (int)hovered_item_nx;\n                    //ImGui::SetTooltip(\"%f,%f -> item %d\", hovered_item_nx, hovered_item_ny, hovered_item_idx); // Move those 4 lines in block above for easy debugging\n\n                    // Zoom\n                    IconSize *= powf(1.1f, (float)(int)ZoomWheelAccum);\n                    IconSize = IM_CLAMP(IconSize, 16.0f, 128.0f);\n                    ZoomWheelAccum -= (int)ZoomWheelAccum;\n                    UpdateLayoutSizes(avail_width);\n\n                    // Manipulate scroll to that we will land at the same Y location of currently hovered item.\n                    // - Calculate next frame position of item under mouse\n                    // - Set new scroll position to be used in next ImGui::BeginChild() call.\n                    float hovered_item_rel_pos_y = ((float)(hovered_item_idx / LayoutColumnCount) + fmodf(hovered_item_ny, 1.0f)) * LayoutItemStep.y;\n                    hovered_item_rel_pos_y += ImGui::GetStyle().WindowPadding.y;\n                    float mouse_local_y = io.MousePos.y - ImGui::GetWindowPos().y;\n                    ImGui::SetScrollY(hovered_item_rel_pos_y - mouse_local_y);\n                }\n            }\n        }\n        ImGui::EndChild();\n\n        ImGui::Text(\"Selected: %d/%d items\", Selection.Size, Items.Size);\n        ImGui::End();\n    }\n};\n\nvoid ShowExampleAppAssetsBrowser(bool* p_open)\n{\n    IMGUI_DEMO_MARKER(\"Examples/Assets Browser\");\n    static ExampleAssetsBrowser assets_browser;\n    assets_browser.Draw(\"Example: Assets Browser\", p_open);\n}\n\n// End of Demo code\n#else\n\nvoid ImGui::ShowAboutWindow(bool*) {}\nvoid ImGui::ShowDemoWindow(bool*) {}\nvoid ImGui::ShowUserGuide() {}\nvoid ImGui::ShowStyleEditor(ImGuiStyle*) {}\nbool ImGui::ShowStyleSelector(const char* label) { return false; }\nvoid ImGui::ShowFontSelector(const char* label) {}\n\n#endif\n\n#endif // #ifndef IMGUI_DISABLE\n"
  },
  {
    "path": "src/DesktopPlusUI/imgui/imgui_draw.cpp",
    "content": "// dear imgui, v1.91.9b\n// (drawing and font code)\n\n/*\n\nIndex of this file:\n\n// [SECTION] STB libraries implementation\n// [SECTION] Style functions\n// [SECTION] ImDrawList\n// [SECTION] ImTriangulator, ImDrawList concave polygon fill\n// [SECTION] ImDrawListSplitter\n// [SECTION] ImDrawData\n// [SECTION] Helpers ShadeVertsXXX functions\n// [SECTION] ImFontConfig\n// [SECTION] ImFontAtlas\n// [SECTION] ImFontAtlas: glyph ranges helpers\n// [SECTION] ImFontGlyphRangesBuilder\n// [SECTION] ImFont\n// [SECTION] ImGui Internal Render Helpers\n// [SECTION] Decompression code\n// [SECTION] Default font data (ProggyClean.ttf)\n\n*/\n\n#if defined(_MSC_VER) && !defined(_CRT_SECURE_NO_WARNINGS)\n#define _CRT_SECURE_NO_WARNINGS\n#endif\n\n#ifndef IMGUI_DEFINE_MATH_OPERATORS\n#define IMGUI_DEFINE_MATH_OPERATORS\n#endif\n\n#include \"imgui.h\"\n#ifndef IMGUI_DISABLE\n#include \"imgui_internal.h\"\n#ifdef IMGUI_ENABLE_FREETYPE\n#include \"misc/freetype/imgui_freetype.h\"\n#endif\n\n#include <stdio.h>      // vsnprintf, sscanf, printf\n\n// Visual Studio warnings\n#ifdef _MSC_VER\n#pragma warning (disable: 4127)     // condition expression is constant\n#pragma warning (disable: 4505)     // unreferenced local function has been removed (stb stuff)\n#pragma warning (disable: 4996)     // 'This function or variable may be unsafe': strcpy, strdup, sprintf, vsnprintf, sscanf, fopen\n#pragma warning (disable: 26451)    // [Static Analyzer] Arithmetic overflow : Using operator 'xxx' on a 4 byte value and then casting the result to a 8 byte value. Cast the value to the wider type before calling operator 'xxx' to avoid overflow(io.2).\n#pragma warning (disable: 26812)    // [Static Analyzer] The enum type 'xxx' is unscoped. Prefer 'enum class' over 'enum' (Enum.3). [MSVC Static Analyzer)\n#endif\n\n// Clang/GCC warnings with -Weverything\n#if defined(__clang__)\n#if __has_warning(\"-Wunknown-warning-option\")\n#pragma clang diagnostic ignored \"-Wunknown-warning-option\"         // warning: unknown warning group 'xxx'                      // not all warnings are known by all Clang versions and they tend to be rename-happy.. so ignoring warnings triggers new warnings on some configuration. Great!\n#endif\n#pragma clang diagnostic ignored \"-Wunknown-pragmas\"                // warning: unknown warning group 'xxx'\n#pragma clang diagnostic ignored \"-Wold-style-cast\"                 // warning: use of old-style cast                            // yes, they are more terse.\n#pragma clang diagnostic ignored \"-Wfloat-equal\"                    // warning: comparing floating point with == or != is unsafe // storing and comparing against same constants ok.\n#pragma clang diagnostic ignored \"-Wglobal-constructors\"            // warning: declaration requires a global destructor         // similar to above, not sure what the exact difference is.\n#pragma clang diagnostic ignored \"-Wsign-conversion\"                // warning: implicit conversion changes signedness\n#pragma clang diagnostic ignored \"-Wzero-as-null-pointer-constant\"  // warning: zero as null pointer constant                    // some standard header variations use #define NULL 0\n#pragma clang diagnostic ignored \"-Wcomma\"                          // warning: possible misuse of comma operator here\n#pragma clang diagnostic ignored \"-Wreserved-id-macro\"              // warning: macro name is a reserved identifier\n#pragma clang diagnostic ignored \"-Wdouble-promotion\"               // warning: implicit conversion from 'float' to 'double' when passing argument to function  // using printf() is a misery with this as C++ va_arg ellipsis changes float to double.\n#pragma clang diagnostic ignored \"-Wimplicit-int-float-conversion\"  // warning: implicit conversion from 'xxx' to 'float' may lose precision\n#pragma clang diagnostic ignored \"-Wreserved-identifier\"            // warning: identifier '_Xxx' is reserved because it starts with '_' followed by a capital letter\n#pragma clang diagnostic ignored \"-Wunsafe-buffer-usage\"            // warning: 'xxx' is an unsafe pointer used for buffer access\n#pragma clang diagnostic ignored \"-Wnontrivial-memaccess\"           // warning: first argument in call to 'memset' is a pointer to non-trivially copyable type\n#pragma clang diagnostic ignored \"-Wcast-qual\"                      // warning: cast from 'const xxxx *' to 'xxx *' drops const qualifier\n#pragma clang diagnostic ignored \"-Wswitch-default\"                 // warning: 'switch' missing 'default' label\n#elif defined(__GNUC__)\n#pragma GCC diagnostic ignored \"-Wpragmas\"                          // warning: unknown option after '#pragma GCC diagnostic' kind\n#pragma GCC diagnostic ignored \"-Wunused-function\"                  // warning: 'xxxx' defined but not used\n#pragma GCC diagnostic ignored \"-Wfloat-equal\"                      // warning: comparing floating-point with '==' or '!=' is unsafe\n#pragma GCC diagnostic ignored \"-Wdouble-promotion\"                 // warning: implicit conversion from 'float' to 'double' when passing argument to function\n#pragma GCC diagnostic ignored \"-Wconversion\"                       // warning: conversion to 'xxxx' from 'xxxx' may alter its value\n#pragma GCC diagnostic ignored \"-Wstack-protector\"                  // warning: stack protector not protecting local variables: variable length buffer\n#pragma GCC diagnostic ignored \"-Wstrict-overflow\"                  // warning: assuming signed overflow does not occur when simplifying division / ..when changing X +- C1 cmp C2 to X cmp C2 -+ C1\n#pragma GCC diagnostic ignored \"-Wclass-memaccess\"                  // [__GNUC__ >= 8] warning: 'memset/memcpy' clearing/writing an object of type 'xxxx' with no trivial copy-assignment; use assignment or value-initialization instead\n#pragma GCC diagnostic ignored \"-Wcast-qual\"                        // warning: cast from type 'const xxxx *' to type 'xxxx *' casts away qualifiers\n#endif\n\n//-------------------------------------------------------------------------\n// [SECTION] STB libraries implementation (for stb_truetype and stb_rect_pack)\n//-------------------------------------------------------------------------\n\n// Compile time options:\n//#define IMGUI_STB_NAMESPACE           ImStb\n//#define IMGUI_STB_TRUETYPE_FILENAME   \"my_folder/stb_truetype.h\"\n//#define IMGUI_STB_RECT_PACK_FILENAME  \"my_folder/stb_rect_pack.h\"\n//#define IMGUI_DISABLE_STB_TRUETYPE_IMPLEMENTATION\n//#define IMGUI_DISABLE_STB_RECT_PACK_IMPLEMENTATION\n\n#ifdef IMGUI_STB_NAMESPACE\nnamespace IMGUI_STB_NAMESPACE\n{\n#endif\n\n#ifdef _MSC_VER\n#pragma warning (push)\n#pragma warning (disable: 4456)                             // declaration of 'xx' hides previous local declaration\n#pragma warning (disable: 6011)                             // (stb_rectpack) Dereferencing NULL pointer 'cur->next'.\n#pragma warning (disable: 6385)                             // (stb_truetype) Reading invalid data from 'buffer':  the readable size is '_Old_3`kernel_width' bytes, but '3' bytes may be read.\n#pragma warning (disable: 28182)                            // (stb_rectpack) Dereferencing NULL pointer. 'cur' contains the same NULL value as 'cur->next' did.\n#endif\n\n#if defined(__clang__)\n#pragma clang diagnostic push\n#pragma clang diagnostic ignored \"-Wunused-function\"        // warning: 'xxxx' defined but not used\n#pragma clang diagnostic ignored \"-Wmissing-prototypes\"\n#pragma clang diagnostic ignored \"-Wimplicit-fallthrough\"\n#endif\n\n#if defined(__GNUC__)\n#pragma GCC diagnostic push\n#pragma GCC diagnostic ignored \"-Wtype-limits\"              // warning: comparison is always true due to limited range of data type [-Wtype-limits]\n#pragma GCC diagnostic ignored \"-Wimplicit-fallthrough\"     // warning: this statement may fall through\n#endif\n\n#ifndef STB_RECT_PACK_IMPLEMENTATION                        // in case the user already have an implementation in the _same_ compilation unit (e.g. unity builds)\n#ifndef IMGUI_DISABLE_STB_RECT_PACK_IMPLEMENTATION          // in case the user already have an implementation in another compilation unit\n#define STBRP_STATIC\n#define STBRP_ASSERT(x)     do { IM_ASSERT(x); } while (0)\n#define STBRP_SORT          ImQsort\n#define STB_RECT_PACK_IMPLEMENTATION\n#endif\n#ifdef IMGUI_STB_RECT_PACK_FILENAME\n#include IMGUI_STB_RECT_PACK_FILENAME\n#else\n#include \"imstb_rectpack.h\"\n#endif\n#endif\n\n#ifdef  IMGUI_ENABLE_STB_TRUETYPE\n#ifndef STB_TRUETYPE_IMPLEMENTATION                         // in case the user already have an implementation in the _same_ compilation unit (e.g. unity builds)\n#ifndef IMGUI_DISABLE_STB_TRUETYPE_IMPLEMENTATION           // in case the user already have an implementation in another compilation unit\n#define STBTT_malloc(x,u)   ((void)(u), IM_ALLOC(x))\n#define STBTT_free(x,u)     ((void)(u), IM_FREE(x))\n#define STBTT_assert(x)     do { IM_ASSERT(x); } while(0)\n#define STBTT_fmod(x,y)     ImFmod(x,y)\n#define STBTT_sqrt(x)       ImSqrt(x)\n#define STBTT_pow(x,y)      ImPow(x,y)\n#define STBTT_fabs(x)       ImFabs(x)\n#define STBTT_ifloor(x)     ((int)ImFloor(x))\n#define STBTT_iceil(x)      ((int)ImCeil(x))\n#define STBTT_strlen(x)     ImStrlen(x)\n#define STBTT_STATIC\n#define STB_TRUETYPE_IMPLEMENTATION\n#else\n#define STBTT_DEF extern\n#endif\n#ifdef IMGUI_STB_TRUETYPE_FILENAME\n#include IMGUI_STB_TRUETYPE_FILENAME\n#else\n#include \"imstb_truetype.h\"\n#endif\n#endif\n#endif // IMGUI_ENABLE_STB_TRUETYPE\n\n#if defined(__GNUC__)\n#pragma GCC diagnostic pop\n#endif\n\n#if defined(__clang__)\n#pragma clang diagnostic pop\n#endif\n\n#if defined(_MSC_VER)\n#pragma warning (pop)\n#endif\n\n#ifdef IMGUI_STB_NAMESPACE\n} // namespace ImStb\nusing namespace IMGUI_STB_NAMESPACE;\n#endif\n\n//-----------------------------------------------------------------------------\n// [SECTION] Style functions\n//-----------------------------------------------------------------------------\n\nvoid ImGui::StyleColorsDark(ImGuiStyle* dst)\n{\n    ImGuiStyle* style = dst ? dst : &ImGui::GetStyle();\n    ImVec4* colors = style->Colors;\n\n    colors[ImGuiCol_Text]                   = ImVec4(1.00f, 1.00f, 1.00f, 1.00f);\n    colors[ImGuiCol_TextDisabled]           = ImVec4(0.50f, 0.50f, 0.50f, 1.00f);\n    colors[ImGuiCol_WindowBg]               = ImVec4(0.06f, 0.06f, 0.06f, 0.94f);\n    colors[ImGuiCol_ChildBg]                = ImVec4(0.00f, 0.00f, 0.00f, 0.00f);\n    colors[ImGuiCol_PopupBg]                = ImVec4(0.08f, 0.08f, 0.08f, 0.94f);\n    colors[ImGuiCol_Border]                 = ImVec4(0.43f, 0.43f, 0.50f, 0.50f);\n    colors[ImGuiCol_BorderShadow]           = ImVec4(0.00f, 0.00f, 0.00f, 0.00f);\n    colors[ImGuiCol_FrameBg]                = ImVec4(0.16f, 0.29f, 0.48f, 0.54f);\n    colors[ImGuiCol_FrameBgHovered]         = ImVec4(0.26f, 0.59f, 0.98f, 0.40f);\n    colors[ImGuiCol_FrameBgActive]          = ImVec4(0.26f, 0.59f, 0.98f, 0.67f);\n    colors[ImGuiCol_TitleBg]                = ImVec4(0.04f, 0.04f, 0.04f, 1.00f);\n    colors[ImGuiCol_TitleBgActive]          = ImVec4(0.16f, 0.29f, 0.48f, 1.00f);\n    colors[ImGuiCol_TitleBgCollapsed]       = ImVec4(0.00f, 0.00f, 0.00f, 0.51f);\n    colors[ImGuiCol_MenuBarBg]              = ImVec4(0.14f, 0.14f, 0.14f, 1.00f);\n    colors[ImGuiCol_ScrollbarBg]            = ImVec4(0.02f, 0.02f, 0.02f, 0.53f);\n    colors[ImGuiCol_ScrollbarGrab]          = ImVec4(0.31f, 0.31f, 0.31f, 1.00f);\n    colors[ImGuiCol_ScrollbarGrabHovered]   = ImVec4(0.41f, 0.41f, 0.41f, 1.00f);\n    colors[ImGuiCol_ScrollbarGrabActive]    = ImVec4(0.51f, 0.51f, 0.51f, 1.00f);\n    colors[ImGuiCol_CheckMark]              = ImVec4(0.26f, 0.59f, 0.98f, 1.00f);\n    colors[ImGuiCol_SliderGrab]             = ImVec4(0.24f, 0.52f, 0.88f, 1.00f);\n    colors[ImGuiCol_SliderGrabActive]       = ImVec4(0.26f, 0.59f, 0.98f, 1.00f);\n    colors[ImGuiCol_Button]                 = ImVec4(0.26f, 0.59f, 0.98f, 0.40f);\n    colors[ImGuiCol_ButtonHovered]          = ImVec4(0.26f, 0.59f, 0.98f, 1.00f);\n    colors[ImGuiCol_ButtonActive]           = ImVec4(0.06f, 0.53f, 0.98f, 1.00f);\n    colors[ImGuiCol_Header]                 = ImVec4(0.26f, 0.59f, 0.98f, 0.31f);\n    colors[ImGuiCol_HeaderHovered]          = ImVec4(0.26f, 0.59f, 0.98f, 0.80f);\n    colors[ImGuiCol_HeaderActive]           = ImVec4(0.26f, 0.59f, 0.98f, 1.00f);\n    colors[ImGuiCol_Separator]              = colors[ImGuiCol_Border];\n    colors[ImGuiCol_SeparatorHovered]       = ImVec4(0.10f, 0.40f, 0.75f, 0.78f);\n    colors[ImGuiCol_SeparatorActive]        = ImVec4(0.10f, 0.40f, 0.75f, 1.00f);\n    colors[ImGuiCol_ResizeGrip]             = ImVec4(0.26f, 0.59f, 0.98f, 0.20f);\n    colors[ImGuiCol_ResizeGripHovered]      = ImVec4(0.26f, 0.59f, 0.98f, 0.67f);\n    colors[ImGuiCol_ResizeGripActive]       = ImVec4(0.26f, 0.59f, 0.98f, 0.95f);\n    colors[ImGuiCol_TabHovered]             = colors[ImGuiCol_HeaderHovered];\n    colors[ImGuiCol_Tab]                    = ImLerp(colors[ImGuiCol_Header],       colors[ImGuiCol_TitleBgActive], 0.80f);\n    colors[ImGuiCol_TabSelected]            = ImLerp(colors[ImGuiCol_HeaderActive], colors[ImGuiCol_TitleBgActive], 0.60f);\n    colors[ImGuiCol_TabSelectedOverline]    = colors[ImGuiCol_HeaderActive];\n    colors[ImGuiCol_TabDimmed]              = ImLerp(colors[ImGuiCol_Tab],          colors[ImGuiCol_TitleBg], 0.80f);\n    colors[ImGuiCol_TabDimmedSelected]      = ImLerp(colors[ImGuiCol_TabSelected],  colors[ImGuiCol_TitleBg], 0.40f);\n    colors[ImGuiCol_TabDimmedSelectedOverline] = ImVec4(0.50f, 0.50f, 0.50f, 0.00f);\n    colors[ImGuiCol_PlotLines]              = ImVec4(0.61f, 0.61f, 0.61f, 1.00f);\n    colors[ImGuiCol_PlotLinesHovered]       = ImVec4(1.00f, 0.43f, 0.35f, 1.00f);\n    colors[ImGuiCol_PlotHistogram]          = ImVec4(0.90f, 0.70f, 0.00f, 1.00f);\n    colors[ImGuiCol_PlotHistogramHovered]   = ImVec4(1.00f, 0.60f, 0.00f, 1.00f);\n    colors[ImGuiCol_TableHeaderBg]          = ImVec4(0.19f, 0.19f, 0.20f, 1.00f);\n    colors[ImGuiCol_TableBorderStrong]      = ImVec4(0.31f, 0.31f, 0.35f, 1.00f);   // Prefer using Alpha=1.0 here\n    colors[ImGuiCol_TableBorderLight]       = ImVec4(0.23f, 0.23f, 0.25f, 1.00f);   // Prefer using Alpha=1.0 here\n    colors[ImGuiCol_TableRowBg]             = ImVec4(0.00f, 0.00f, 0.00f, 0.00f);\n    colors[ImGuiCol_TableRowBgAlt]          = ImVec4(1.00f, 1.00f, 1.00f, 0.06f);\n    colors[ImGuiCol_TextLink]               = colors[ImGuiCol_HeaderActive];\n    colors[ImGuiCol_TextSelectedBg]         = ImVec4(0.26f, 0.59f, 0.98f, 0.35f);\n    colors[ImGuiCol_DragDropTarget]         = ImVec4(1.00f, 1.00f, 0.00f, 0.90f);\n    colors[ImGuiCol_NavCursor]              = ImVec4(0.26f, 0.59f, 0.98f, 1.00f);\n    colors[ImGuiCol_NavWindowingHighlight]  = ImVec4(1.00f, 1.00f, 1.00f, 0.70f);\n    colors[ImGuiCol_NavWindowingDimBg]      = ImVec4(0.80f, 0.80f, 0.80f, 0.20f);\n    colors[ImGuiCol_ModalWindowDimBg]       = ImVec4(0.80f, 0.80f, 0.80f, 0.35f);\n}\n\nvoid ImGui::StyleColorsClassic(ImGuiStyle* dst)\n{\n    ImGuiStyle* style = dst ? dst : &ImGui::GetStyle();\n    ImVec4* colors = style->Colors;\n\n    colors[ImGuiCol_Text]                   = ImVec4(0.90f, 0.90f, 0.90f, 1.00f);\n    colors[ImGuiCol_TextDisabled]           = ImVec4(0.60f, 0.60f, 0.60f, 1.00f);\n    colors[ImGuiCol_WindowBg]               = ImVec4(0.00f, 0.00f, 0.00f, 0.85f);\n    colors[ImGuiCol_ChildBg]                = ImVec4(0.00f, 0.00f, 0.00f, 0.00f);\n    colors[ImGuiCol_PopupBg]                = ImVec4(0.11f, 0.11f, 0.14f, 0.92f);\n    colors[ImGuiCol_Border]                 = ImVec4(0.50f, 0.50f, 0.50f, 0.50f);\n    colors[ImGuiCol_BorderShadow]           = ImVec4(0.00f, 0.00f, 0.00f, 0.00f);\n    colors[ImGuiCol_FrameBg]                = ImVec4(0.43f, 0.43f, 0.43f, 0.39f);\n    colors[ImGuiCol_FrameBgHovered]         = ImVec4(0.47f, 0.47f, 0.69f, 0.40f);\n    colors[ImGuiCol_FrameBgActive]          = ImVec4(0.42f, 0.41f, 0.64f, 0.69f);\n    colors[ImGuiCol_TitleBg]                = ImVec4(0.27f, 0.27f, 0.54f, 0.83f);\n    colors[ImGuiCol_TitleBgActive]          = ImVec4(0.32f, 0.32f, 0.63f, 0.87f);\n    colors[ImGuiCol_TitleBgCollapsed]       = ImVec4(0.40f, 0.40f, 0.80f, 0.20f);\n    colors[ImGuiCol_MenuBarBg]              = ImVec4(0.40f, 0.40f, 0.55f, 0.80f);\n    colors[ImGuiCol_ScrollbarBg]            = ImVec4(0.20f, 0.25f, 0.30f, 0.60f);\n    colors[ImGuiCol_ScrollbarGrab]          = ImVec4(0.40f, 0.40f, 0.80f, 0.30f);\n    colors[ImGuiCol_ScrollbarGrabHovered]   = ImVec4(0.40f, 0.40f, 0.80f, 0.40f);\n    colors[ImGuiCol_ScrollbarGrabActive]    = ImVec4(0.41f, 0.39f, 0.80f, 0.60f);\n    colors[ImGuiCol_CheckMark]              = ImVec4(0.90f, 0.90f, 0.90f, 0.50f);\n    colors[ImGuiCol_SliderGrab]             = ImVec4(1.00f, 1.00f, 1.00f, 0.30f);\n    colors[ImGuiCol_SliderGrabActive]       = ImVec4(0.41f, 0.39f, 0.80f, 0.60f);\n    colors[ImGuiCol_Button]                 = ImVec4(0.35f, 0.40f, 0.61f, 0.62f);\n    colors[ImGuiCol_ButtonHovered]          = ImVec4(0.40f, 0.48f, 0.71f, 0.79f);\n    colors[ImGuiCol_ButtonActive]           = ImVec4(0.46f, 0.54f, 0.80f, 1.00f);\n    colors[ImGuiCol_Header]                 = ImVec4(0.40f, 0.40f, 0.90f, 0.45f);\n    colors[ImGuiCol_HeaderHovered]          = ImVec4(0.45f, 0.45f, 0.90f, 0.80f);\n    colors[ImGuiCol_HeaderActive]           = ImVec4(0.53f, 0.53f, 0.87f, 0.80f);\n    colors[ImGuiCol_Separator]              = ImVec4(0.50f, 0.50f, 0.50f, 0.60f);\n    colors[ImGuiCol_SeparatorHovered]       = ImVec4(0.60f, 0.60f, 0.70f, 1.00f);\n    colors[ImGuiCol_SeparatorActive]        = ImVec4(0.70f, 0.70f, 0.90f, 1.00f);\n    colors[ImGuiCol_ResizeGrip]             = ImVec4(1.00f, 1.00f, 1.00f, 0.10f);\n    colors[ImGuiCol_ResizeGripHovered]      = ImVec4(0.78f, 0.82f, 1.00f, 0.60f);\n    colors[ImGuiCol_ResizeGripActive]       = ImVec4(0.78f, 0.82f, 1.00f, 0.90f);\n    colors[ImGuiCol_TabHovered]             = colors[ImGuiCol_HeaderHovered];\n    colors[ImGuiCol_Tab]                    = ImLerp(colors[ImGuiCol_Header],       colors[ImGuiCol_TitleBgActive], 0.80f);\n    colors[ImGuiCol_TabSelected]            = ImLerp(colors[ImGuiCol_HeaderActive], colors[ImGuiCol_TitleBgActive], 0.60f);\n    colors[ImGuiCol_TabSelectedOverline]    = colors[ImGuiCol_HeaderActive];\n    colors[ImGuiCol_TabDimmed]              = ImLerp(colors[ImGuiCol_Tab],          colors[ImGuiCol_TitleBg], 0.80f);\n    colors[ImGuiCol_TabDimmedSelected]      = ImLerp(colors[ImGuiCol_TabSelected],  colors[ImGuiCol_TitleBg], 0.40f);\n    colors[ImGuiCol_TabDimmedSelectedOverline] = ImVec4(0.53f, 0.53f, 0.87f, 0.00f);\n    colors[ImGuiCol_PlotLines]              = ImVec4(1.00f, 1.00f, 1.00f, 1.00f);\n    colors[ImGuiCol_PlotLinesHovered]       = ImVec4(0.90f, 0.70f, 0.00f, 1.00f);\n    colors[ImGuiCol_PlotHistogram]          = ImVec4(0.90f, 0.70f, 0.00f, 1.00f);\n    colors[ImGuiCol_PlotHistogramHovered]   = ImVec4(1.00f, 0.60f, 0.00f, 1.00f);\n    colors[ImGuiCol_TableHeaderBg]          = ImVec4(0.27f, 0.27f, 0.38f, 1.00f);\n    colors[ImGuiCol_TableBorderStrong]      = ImVec4(0.31f, 0.31f, 0.45f, 1.00f);   // Prefer using Alpha=1.0 here\n    colors[ImGuiCol_TableBorderLight]       = ImVec4(0.26f, 0.26f, 0.28f, 1.00f);   // Prefer using Alpha=1.0 here\n    colors[ImGuiCol_TableRowBg]             = ImVec4(0.00f, 0.00f, 0.00f, 0.00f);\n    colors[ImGuiCol_TableRowBgAlt]          = ImVec4(1.00f, 1.00f, 1.00f, 0.07f);\n    colors[ImGuiCol_TextLink]               = colors[ImGuiCol_HeaderActive];\n    colors[ImGuiCol_TextSelectedBg]         = ImVec4(0.00f, 0.00f, 1.00f, 0.35f);\n    colors[ImGuiCol_DragDropTarget]         = ImVec4(1.00f, 1.00f, 0.00f, 0.90f);\n    colors[ImGuiCol_NavCursor]              = colors[ImGuiCol_HeaderHovered];\n    colors[ImGuiCol_NavWindowingHighlight]  = ImVec4(1.00f, 1.00f, 1.00f, 0.70f);\n    colors[ImGuiCol_NavWindowingDimBg]      = ImVec4(0.80f, 0.80f, 0.80f, 0.20f);\n    colors[ImGuiCol_ModalWindowDimBg]       = ImVec4(0.20f, 0.20f, 0.20f, 0.35f);\n}\n\n// Those light colors are better suited with a thicker font than the default one + FrameBorder\nvoid ImGui::StyleColorsLight(ImGuiStyle* dst)\n{\n    ImGuiStyle* style = dst ? dst : &ImGui::GetStyle();\n    ImVec4* colors = style->Colors;\n\n    colors[ImGuiCol_Text]                   = ImVec4(0.00f, 0.00f, 0.00f, 1.00f);\n    colors[ImGuiCol_TextDisabled]           = ImVec4(0.60f, 0.60f, 0.60f, 1.00f);\n    colors[ImGuiCol_WindowBg]               = ImVec4(0.94f, 0.94f, 0.94f, 1.00f);\n    colors[ImGuiCol_ChildBg]                = ImVec4(0.00f, 0.00f, 0.00f, 0.00f);\n    colors[ImGuiCol_PopupBg]                = ImVec4(1.00f, 1.00f, 1.00f, 0.98f);\n    colors[ImGuiCol_Border]                 = ImVec4(0.00f, 0.00f, 0.00f, 0.30f);\n    colors[ImGuiCol_BorderShadow]           = ImVec4(0.00f, 0.00f, 0.00f, 0.00f);\n    colors[ImGuiCol_FrameBg]                = ImVec4(1.00f, 1.00f, 1.00f, 1.00f);\n    colors[ImGuiCol_FrameBgHovered]         = ImVec4(0.26f, 0.59f, 0.98f, 0.40f);\n    colors[ImGuiCol_FrameBgActive]          = ImVec4(0.26f, 0.59f, 0.98f, 0.67f);\n    colors[ImGuiCol_TitleBg]                = ImVec4(0.96f, 0.96f, 0.96f, 1.00f);\n    colors[ImGuiCol_TitleBgActive]          = ImVec4(0.82f, 0.82f, 0.82f, 1.00f);\n    colors[ImGuiCol_TitleBgCollapsed]       = ImVec4(1.00f, 1.00f, 1.00f, 0.51f);\n    colors[ImGuiCol_MenuBarBg]              = ImVec4(0.86f, 0.86f, 0.86f, 1.00f);\n    colors[ImGuiCol_ScrollbarBg]            = ImVec4(0.98f, 0.98f, 0.98f, 0.53f);\n    colors[ImGuiCol_ScrollbarGrab]          = ImVec4(0.69f, 0.69f, 0.69f, 0.80f);\n    colors[ImGuiCol_ScrollbarGrabHovered]   = ImVec4(0.49f, 0.49f, 0.49f, 0.80f);\n    colors[ImGuiCol_ScrollbarGrabActive]    = ImVec4(0.49f, 0.49f, 0.49f, 1.00f);\n    colors[ImGuiCol_CheckMark]              = ImVec4(0.26f, 0.59f, 0.98f, 1.00f);\n    colors[ImGuiCol_SliderGrab]             = ImVec4(0.26f, 0.59f, 0.98f, 0.78f);\n    colors[ImGuiCol_SliderGrabActive]       = ImVec4(0.46f, 0.54f, 0.80f, 0.60f);\n    colors[ImGuiCol_Button]                 = ImVec4(0.26f, 0.59f, 0.98f, 0.40f);\n    colors[ImGuiCol_ButtonHovered]          = ImVec4(0.26f, 0.59f, 0.98f, 1.00f);\n    colors[ImGuiCol_ButtonActive]           = ImVec4(0.06f, 0.53f, 0.98f, 1.00f);\n    colors[ImGuiCol_Header]                 = ImVec4(0.26f, 0.59f, 0.98f, 0.31f);\n    colors[ImGuiCol_HeaderHovered]          = ImVec4(0.26f, 0.59f, 0.98f, 0.80f);\n    colors[ImGuiCol_HeaderActive]           = ImVec4(0.26f, 0.59f, 0.98f, 1.00f);\n    colors[ImGuiCol_Separator]              = ImVec4(0.39f, 0.39f, 0.39f, 0.62f);\n    colors[ImGuiCol_SeparatorHovered]       = ImVec4(0.14f, 0.44f, 0.80f, 0.78f);\n    colors[ImGuiCol_SeparatorActive]        = ImVec4(0.14f, 0.44f, 0.80f, 1.00f);\n    colors[ImGuiCol_ResizeGrip]             = ImVec4(0.35f, 0.35f, 0.35f, 0.17f);\n    colors[ImGuiCol_ResizeGripHovered]      = ImVec4(0.26f, 0.59f, 0.98f, 0.67f);\n    colors[ImGuiCol_ResizeGripActive]       = ImVec4(0.26f, 0.59f, 0.98f, 0.95f);\n    colors[ImGuiCol_TabHovered]             = colors[ImGuiCol_HeaderHovered];\n    colors[ImGuiCol_Tab]                    = ImLerp(colors[ImGuiCol_Header],       colors[ImGuiCol_TitleBgActive], 0.90f);\n    colors[ImGuiCol_TabSelected]            = ImLerp(colors[ImGuiCol_HeaderActive], colors[ImGuiCol_TitleBgActive], 0.60f);\n    colors[ImGuiCol_TabSelectedOverline]    = colors[ImGuiCol_HeaderActive];\n    colors[ImGuiCol_TabDimmed]              = ImLerp(colors[ImGuiCol_Tab],          colors[ImGuiCol_TitleBg], 0.80f);\n    colors[ImGuiCol_TabDimmedSelected]      = ImLerp(colors[ImGuiCol_TabSelected],  colors[ImGuiCol_TitleBg], 0.40f);\n    colors[ImGuiCol_TabDimmedSelectedOverline] = ImVec4(0.26f, 0.59f, 1.00f, 0.00f);\n    colors[ImGuiCol_PlotLines]              = ImVec4(0.39f, 0.39f, 0.39f, 1.00f);\n    colors[ImGuiCol_PlotLinesHovered]       = ImVec4(1.00f, 0.43f, 0.35f, 1.00f);\n    colors[ImGuiCol_PlotHistogram]          = ImVec4(0.90f, 0.70f, 0.00f, 1.00f);\n    colors[ImGuiCol_PlotHistogramHovered]   = ImVec4(1.00f, 0.45f, 0.00f, 1.00f);\n    colors[ImGuiCol_TableHeaderBg]          = ImVec4(0.78f, 0.87f, 0.98f, 1.00f);\n    colors[ImGuiCol_TableBorderStrong]      = ImVec4(0.57f, 0.57f, 0.64f, 1.00f);   // Prefer using Alpha=1.0 here\n    colors[ImGuiCol_TableBorderLight]       = ImVec4(0.68f, 0.68f, 0.74f, 1.00f);   // Prefer using Alpha=1.0 here\n    colors[ImGuiCol_TableRowBg]             = ImVec4(0.00f, 0.00f, 0.00f, 0.00f);\n    colors[ImGuiCol_TableRowBgAlt]          = ImVec4(0.30f, 0.30f, 0.30f, 0.09f);\n    colors[ImGuiCol_TextLink]               = colors[ImGuiCol_HeaderActive];\n    colors[ImGuiCol_TextSelectedBg]         = ImVec4(0.26f, 0.59f, 0.98f, 0.35f);\n    colors[ImGuiCol_DragDropTarget]         = ImVec4(0.26f, 0.59f, 0.98f, 0.95f);\n    colors[ImGuiCol_NavCursor]              = colors[ImGuiCol_HeaderHovered];\n    colors[ImGuiCol_NavWindowingHighlight]  = ImVec4(0.70f, 0.70f, 0.70f, 0.70f);\n    colors[ImGuiCol_NavWindowingDimBg]      = ImVec4(0.20f, 0.20f, 0.20f, 0.20f);\n    colors[ImGuiCol_ModalWindowDimBg]       = ImVec4(0.20f, 0.20f, 0.20f, 0.35f);\n}\n\n//-----------------------------------------------------------------------------\n// [SECTION] ImDrawList\n//-----------------------------------------------------------------------------\n\nImDrawListSharedData::ImDrawListSharedData()\n{\n    memset(this, 0, sizeof(*this));\n    InitialFringeScale = 1.0f;\n    for (int i = 0; i < IM_ARRAYSIZE(ArcFastVtx); i++)\n    {\n        const float a = ((float)i * 2 * IM_PI) / (float)IM_ARRAYSIZE(ArcFastVtx);\n        ArcFastVtx[i] = ImVec2(ImCos(a), ImSin(a));\n    }\n    ArcFastRadiusCutoff = IM_DRAWLIST_CIRCLE_AUTO_SEGMENT_CALC_R(IM_DRAWLIST_ARCFAST_SAMPLE_MAX, CircleSegmentMaxError);\n}\n\nvoid ImDrawListSharedData::SetCircleTessellationMaxError(float max_error)\n{\n    if (CircleSegmentMaxError == max_error)\n        return;\n\n    IM_ASSERT(max_error > 0.0f);\n    CircleSegmentMaxError = max_error;\n    for (int i = 0; i < IM_ARRAYSIZE(CircleSegmentCounts); i++)\n    {\n        const float radius = (float)i;\n        CircleSegmentCounts[i] = (ImU8)((i > 0) ? IM_DRAWLIST_CIRCLE_AUTO_SEGMENT_CALC(radius, CircleSegmentMaxError) : IM_DRAWLIST_ARCFAST_SAMPLE_MAX);\n    }\n    ArcFastRadiusCutoff = IM_DRAWLIST_CIRCLE_AUTO_SEGMENT_CALC_R(IM_DRAWLIST_ARCFAST_SAMPLE_MAX, CircleSegmentMaxError);\n}\n\nImDrawList::ImDrawList(ImDrawListSharedData* shared_data)\n{\n    memset(this, 0, sizeof(*this));\n    _Data = shared_data;\n}\n\nImDrawList::~ImDrawList()\n{\n    _ClearFreeMemory();\n}\n\n// Initialize before use in a new frame. We always have a command ready in the buffer.\n// In the majority of cases, you would want to call PushClipRect() and PushTextureID() after this.\nvoid ImDrawList::_ResetForNewFrame()\n{\n    // Verify that the ImDrawCmd fields we want to memcmp() are contiguous in memory.\n    IM_STATIC_ASSERT(offsetof(ImDrawCmd, ClipRect) == 0);\n    IM_STATIC_ASSERT(offsetof(ImDrawCmd, TextureId) == sizeof(ImVec4));\n    IM_STATIC_ASSERT(offsetof(ImDrawCmd, VtxOffset) == sizeof(ImVec4) + sizeof(ImTextureID));\n    if (_Splitter._Count > 1)\n        _Splitter.Merge(this);\n\n    CmdBuffer.resize(0);\n    IdxBuffer.resize(0);\n    VtxBuffer.resize(0);\n    Flags = _Data->InitialFlags;\n    memset(&_CmdHeader, 0, sizeof(_CmdHeader));\n    _VtxCurrentIdx = 0;\n    _VtxWritePtr = NULL;\n    _IdxWritePtr = NULL;\n    _ClipRectStack.resize(0);\n    _TextureIdStack.resize(0);\n    _CallbacksDataBuf.resize(0);\n    _Path.resize(0);\n    _Splitter.Clear();\n    CmdBuffer.push_back(ImDrawCmd());\n    _FringeScale = _Data->InitialFringeScale;\n}\n\nvoid ImDrawList::_ClearFreeMemory()\n{\n    CmdBuffer.clear();\n    IdxBuffer.clear();\n    VtxBuffer.clear();\n    Flags = ImDrawListFlags_None;\n    _VtxCurrentIdx = 0;\n    _VtxWritePtr = NULL;\n    _IdxWritePtr = NULL;\n    _ClipRectStack.clear();\n    _TextureIdStack.clear();\n    _CallbacksDataBuf.clear();\n    _Path.clear();\n    _Splitter.ClearFreeMemory();\n}\n\nImDrawList* ImDrawList::CloneOutput() const\n{\n    ImDrawList* dst = IM_NEW(ImDrawList(_Data));\n    dst->CmdBuffer = CmdBuffer;\n    dst->IdxBuffer = IdxBuffer;\n    dst->VtxBuffer = VtxBuffer;\n    dst->Flags = Flags;\n    return dst;\n}\n\nvoid ImDrawList::AddDrawCmd()\n{\n    ImDrawCmd draw_cmd;\n    draw_cmd.ClipRect = _CmdHeader.ClipRect;    // Same as calling ImDrawCmd_HeaderCopy()\n    draw_cmd.TextureId = _CmdHeader.TextureId;\n    draw_cmd.VtxOffset = _CmdHeader.VtxOffset;\n    draw_cmd.IdxOffset = IdxBuffer.Size;\n\n    IM_ASSERT(draw_cmd.ClipRect.x <= draw_cmd.ClipRect.z && draw_cmd.ClipRect.y <= draw_cmd.ClipRect.w);\n    CmdBuffer.push_back(draw_cmd);\n}\n\n// Pop trailing draw command (used before merging or presenting to user)\n// Note that this leaves the ImDrawList in a state unfit for further commands, as most code assume that CmdBuffer.Size > 0 && CmdBuffer.back().UserCallback == NULL\nvoid ImDrawList::_PopUnusedDrawCmd()\n{\n    while (CmdBuffer.Size > 0)\n    {\n        ImDrawCmd* curr_cmd = &CmdBuffer.Data[CmdBuffer.Size - 1];\n        if (curr_cmd->ElemCount != 0 || curr_cmd->UserCallback != NULL)\n            return;// break;\n        CmdBuffer.pop_back();\n    }\n}\n\nvoid ImDrawList::AddCallback(ImDrawCallback callback, void* userdata, size_t userdata_size)\n{\n    IM_ASSERT_PARANOID(CmdBuffer.Size > 0);\n    ImDrawCmd* curr_cmd = &CmdBuffer.Data[CmdBuffer.Size - 1];\n    IM_ASSERT(curr_cmd->UserCallback == NULL);\n    if (curr_cmd->ElemCount != 0)\n    {\n        AddDrawCmd();\n        curr_cmd = &CmdBuffer.Data[CmdBuffer.Size - 1];\n    }\n\n    curr_cmd->UserCallback = callback;\n    if (userdata_size == 0)\n    {\n        // Store user data directly in command (no indirection)\n        curr_cmd->UserCallbackData = userdata;\n        curr_cmd->UserCallbackDataSize = 0;\n        curr_cmd->UserCallbackDataOffset = -1;\n    }\n    else\n    {\n        // Copy and store user data in a buffer\n        IM_ASSERT(userdata != NULL);\n        IM_ASSERT(userdata_size < (1u << 31));\n        curr_cmd->UserCallbackData = NULL; // Will be resolved during Render()\n        curr_cmd->UserCallbackDataSize = (int)userdata_size;\n        curr_cmd->UserCallbackDataOffset = _CallbacksDataBuf.Size;\n        _CallbacksDataBuf.resize(_CallbacksDataBuf.Size + (int)userdata_size);\n        memcpy(_CallbacksDataBuf.Data + (size_t)curr_cmd->UserCallbackDataOffset, userdata, userdata_size);\n    }\n\n    AddDrawCmd(); // Force a new command after us (see comment below)\n}\n\n// Compare ClipRect, TextureId and VtxOffset with a single memcmp()\n#define ImDrawCmd_HeaderSize                            (offsetof(ImDrawCmd, VtxOffset) + sizeof(unsigned int))\n#define ImDrawCmd_HeaderCompare(CMD_LHS, CMD_RHS)       (memcmp(CMD_LHS, CMD_RHS, ImDrawCmd_HeaderSize))    // Compare ClipRect, TextureId, VtxOffset\n#define ImDrawCmd_HeaderCopy(CMD_DST, CMD_SRC)          (memcpy(CMD_DST, CMD_SRC, ImDrawCmd_HeaderSize))    // Copy ClipRect, TextureId, VtxOffset\n#define ImDrawCmd_AreSequentialIdxOffset(CMD_0, CMD_1)  (CMD_0->IdxOffset + CMD_0->ElemCount == CMD_1->IdxOffset)\n\n// Try to merge two last draw commands\nvoid ImDrawList::_TryMergeDrawCmds()\n{\n    IM_ASSERT_PARANOID(CmdBuffer.Size > 0);\n    ImDrawCmd* curr_cmd = &CmdBuffer.Data[CmdBuffer.Size - 1];\n    ImDrawCmd* prev_cmd = curr_cmd - 1;\n    if (ImDrawCmd_HeaderCompare(curr_cmd, prev_cmd) == 0 && ImDrawCmd_AreSequentialIdxOffset(prev_cmd, curr_cmd) && curr_cmd->UserCallback == NULL && prev_cmd->UserCallback == NULL)\n    {\n        prev_cmd->ElemCount += curr_cmd->ElemCount;\n        CmdBuffer.pop_back();\n    }\n}\n\n// Our scheme may appears a bit unusual, basically we want the most-common calls AddLine AddRect etc. to not have to perform any check so we always have a command ready in the stack.\n// The cost of figuring out if a new command has to be added or if we can merge is paid in those Update** functions only.\nvoid ImDrawList::_OnChangedClipRect()\n{\n    // If current command is used with different settings we need to add a new command\n    IM_ASSERT_PARANOID(CmdBuffer.Size > 0);\n    ImDrawCmd* curr_cmd = &CmdBuffer.Data[CmdBuffer.Size - 1];\n    if (curr_cmd->ElemCount != 0 && memcmp(&curr_cmd->ClipRect, &_CmdHeader.ClipRect, sizeof(ImVec4)) != 0)\n    {\n        AddDrawCmd();\n        return;\n    }\n    IM_ASSERT(curr_cmd->UserCallback == NULL);\n\n    // Try to merge with previous command if it matches, else use current command\n    ImDrawCmd* prev_cmd = curr_cmd - 1;\n    if (curr_cmd->ElemCount == 0 && CmdBuffer.Size > 1 && ImDrawCmd_HeaderCompare(&_CmdHeader, prev_cmd) == 0 && ImDrawCmd_AreSequentialIdxOffset(prev_cmd, curr_cmd) && prev_cmd->UserCallback == NULL)\n    {\n        CmdBuffer.pop_back();\n        return;\n    }\n    curr_cmd->ClipRect = _CmdHeader.ClipRect;\n}\n\nvoid ImDrawList::_OnChangedTextureID()\n{\n    // If current command is used with different settings we need to add a new command\n    IM_ASSERT_PARANOID(CmdBuffer.Size > 0);\n    ImDrawCmd* curr_cmd = &CmdBuffer.Data[CmdBuffer.Size - 1];\n    if (curr_cmd->ElemCount != 0 && curr_cmd->TextureId != _CmdHeader.TextureId)\n    {\n        AddDrawCmd();\n        return;\n    }\n    IM_ASSERT(curr_cmd->UserCallback == NULL);\n\n    // Try to merge with previous command if it matches, else use current command\n    ImDrawCmd* prev_cmd = curr_cmd - 1;\n    if (curr_cmd->ElemCount == 0 && CmdBuffer.Size > 1 && ImDrawCmd_HeaderCompare(&_CmdHeader, prev_cmd) == 0 && ImDrawCmd_AreSequentialIdxOffset(prev_cmd, curr_cmd) && prev_cmd->UserCallback == NULL)\n    {\n        CmdBuffer.pop_back();\n        return;\n    }\n    curr_cmd->TextureId = _CmdHeader.TextureId;\n}\n\nvoid ImDrawList::_OnChangedVtxOffset()\n{\n    // We don't need to compare curr_cmd->VtxOffset != _CmdHeader.VtxOffset because we know it'll be different at the time we call this.\n    _VtxCurrentIdx = 0;\n    IM_ASSERT_PARANOID(CmdBuffer.Size > 0);\n    ImDrawCmd* curr_cmd = &CmdBuffer.Data[CmdBuffer.Size - 1];\n    //IM_ASSERT(curr_cmd->VtxOffset != _CmdHeader.VtxOffset); // See #3349\n    if (curr_cmd->ElemCount != 0)\n    {\n        AddDrawCmd();\n        return;\n    }\n    IM_ASSERT(curr_cmd->UserCallback == NULL);\n    curr_cmd->VtxOffset = _CmdHeader.VtxOffset;\n}\n\nint ImDrawList::_CalcCircleAutoSegmentCount(float radius) const\n{\n    // Automatic segment count\n    const int radius_idx = (int)(radius + 0.999999f); // ceil to never reduce accuracy\n    if (radius_idx >= 0 && radius_idx < IM_ARRAYSIZE(_Data->CircleSegmentCounts))\n        return _Data->CircleSegmentCounts[radius_idx]; // Use cached value\n    else\n        return IM_DRAWLIST_CIRCLE_AUTO_SEGMENT_CALC(radius, _Data->CircleSegmentMaxError);\n}\n\n// Render-level scissoring. This is passed down to your render function but not used for CPU-side coarse clipping. Prefer using higher-level ImGui::PushClipRect() to affect logic (hit-testing and widget culling)\nvoid ImDrawList::PushClipRect(const ImVec2& cr_min, const ImVec2& cr_max, bool intersect_with_current_clip_rect)\n{\n    ImVec4 cr(cr_min.x, cr_min.y, cr_max.x, cr_max.y);\n    if (intersect_with_current_clip_rect)\n    {\n        ImVec4 current = _CmdHeader.ClipRect;\n        if (cr.x < current.x) cr.x = current.x;\n        if (cr.y < current.y) cr.y = current.y;\n        if (cr.z > current.z) cr.z = current.z;\n        if (cr.w > current.w) cr.w = current.w;\n    }\n    cr.z = ImMax(cr.x, cr.z);\n    cr.w = ImMax(cr.y, cr.w);\n\n    _ClipRectStack.push_back(cr);\n    _CmdHeader.ClipRect = cr;\n    _OnChangedClipRect();\n}\n\nvoid ImDrawList::PushClipRectFullScreen()\n{\n    PushClipRect(ImVec2(_Data->ClipRectFullscreen.x, _Data->ClipRectFullscreen.y), ImVec2(_Data->ClipRectFullscreen.z, _Data->ClipRectFullscreen.w));\n}\n\nvoid ImDrawList::PopClipRect()\n{\n    _ClipRectStack.pop_back();\n    _CmdHeader.ClipRect = (_ClipRectStack.Size == 0) ? _Data->ClipRectFullscreen : _ClipRectStack.Data[_ClipRectStack.Size - 1];\n    _OnChangedClipRect();\n}\n\nvoid ImDrawList::PushTextureID(ImTextureID texture_id)\n{\n    _TextureIdStack.push_back(texture_id);\n    _CmdHeader.TextureId = texture_id;\n    _OnChangedTextureID();\n}\n\nvoid ImDrawList::PopTextureID()\n{\n    _TextureIdStack.pop_back();\n    _CmdHeader.TextureId = (_TextureIdStack.Size == 0) ? (ImTextureID)NULL : _TextureIdStack.Data[_TextureIdStack.Size - 1];\n    _OnChangedTextureID();\n}\n\n// This is used by ImGui::PushFont()/PopFont(). It works because we never use _TextureIdStack[] elsewhere than in PushTextureID()/PopTextureID().\nvoid ImDrawList::_SetTextureID(ImTextureID texture_id)\n{\n    if (_CmdHeader.TextureId == texture_id)\n        return;\n    _CmdHeader.TextureId = texture_id;\n    _OnChangedTextureID();\n}\n\n// Reserve space for a number of vertices and indices.\n// You must finish filling your reserved data before calling PrimReserve() again, as it may reallocate or\n// submit the intermediate results. PrimUnreserve() can be used to release unused allocations.\nvoid ImDrawList::PrimReserve(int idx_count, int vtx_count)\n{\n    // Large mesh support (when enabled)\n    IM_ASSERT_PARANOID(idx_count >= 0 && vtx_count >= 0);\n    if (sizeof(ImDrawIdx) == 2 && (_VtxCurrentIdx + vtx_count >= (1 << 16)) && (Flags & ImDrawListFlags_AllowVtxOffset))\n    {\n        // FIXME: In theory we should be testing that vtx_count <64k here.\n        // In practice, RenderText() relies on reserving ahead for a worst case scenario so it is currently useful for us\n        // to not make that check until we rework the text functions to handle clipping and large horizontal lines better.\n        _CmdHeader.VtxOffset = VtxBuffer.Size;\n        _OnChangedVtxOffset();\n    }\n\n    ImDrawCmd* draw_cmd = &CmdBuffer.Data[CmdBuffer.Size - 1];\n    draw_cmd->ElemCount += idx_count;\n\n    int vtx_buffer_old_size = VtxBuffer.Size;\n    VtxBuffer.resize(vtx_buffer_old_size + vtx_count);\n    _VtxWritePtr = VtxBuffer.Data + vtx_buffer_old_size;\n\n    int idx_buffer_old_size = IdxBuffer.Size;\n    IdxBuffer.resize(idx_buffer_old_size + idx_count);\n    _IdxWritePtr = IdxBuffer.Data + idx_buffer_old_size;\n}\n\n// Release the number of reserved vertices/indices from the end of the last reservation made with PrimReserve().\nvoid ImDrawList::PrimUnreserve(int idx_count, int vtx_count)\n{\n    IM_ASSERT_PARANOID(idx_count >= 0 && vtx_count >= 0);\n\n    ImDrawCmd* draw_cmd = &CmdBuffer.Data[CmdBuffer.Size - 1];\n    draw_cmd->ElemCount -= idx_count;\n    VtxBuffer.shrink(VtxBuffer.Size - vtx_count);\n    IdxBuffer.shrink(IdxBuffer.Size - idx_count);\n}\n\n// Fully unrolled with inline call to keep our debug builds decently fast.\nvoid ImDrawList::PrimRect(const ImVec2& a, const ImVec2& c, ImU32 col)\n{\n    ImVec2 b(c.x, a.y), d(a.x, c.y), uv(_Data->TexUvWhitePixel);\n    ImDrawIdx idx = (ImDrawIdx)_VtxCurrentIdx;\n    _IdxWritePtr[0] = idx; _IdxWritePtr[1] = (ImDrawIdx)(idx+1); _IdxWritePtr[2] = (ImDrawIdx)(idx+2);\n    _IdxWritePtr[3] = idx; _IdxWritePtr[4] = (ImDrawIdx)(idx+2); _IdxWritePtr[5] = (ImDrawIdx)(idx+3);\n    _VtxWritePtr[0].pos = a; _VtxWritePtr[0].uv = uv; _VtxWritePtr[0].col = col;\n    _VtxWritePtr[1].pos = b; _VtxWritePtr[1].uv = uv; _VtxWritePtr[1].col = col;\n    _VtxWritePtr[2].pos = c; _VtxWritePtr[2].uv = uv; _VtxWritePtr[2].col = col;\n    _VtxWritePtr[3].pos = d; _VtxWritePtr[3].uv = uv; _VtxWritePtr[3].col = col;\n    _VtxWritePtr += 4;\n    _VtxCurrentIdx += 4;\n    _IdxWritePtr += 6;\n}\n\nvoid ImDrawList::PrimRectUV(const ImVec2& a, const ImVec2& c, const ImVec2& uv_a, const ImVec2& uv_c, ImU32 col)\n{\n    ImVec2 b(c.x, a.y), d(a.x, c.y), uv_b(uv_c.x, uv_a.y), uv_d(uv_a.x, uv_c.y);\n    ImDrawIdx idx = (ImDrawIdx)_VtxCurrentIdx;\n    _IdxWritePtr[0] = idx; _IdxWritePtr[1] = (ImDrawIdx)(idx+1); _IdxWritePtr[2] = (ImDrawIdx)(idx+2);\n    _IdxWritePtr[3] = idx; _IdxWritePtr[4] = (ImDrawIdx)(idx+2); _IdxWritePtr[5] = (ImDrawIdx)(idx+3);\n    _VtxWritePtr[0].pos = a; _VtxWritePtr[0].uv = uv_a; _VtxWritePtr[0].col = col;\n    _VtxWritePtr[1].pos = b; _VtxWritePtr[1].uv = uv_b; _VtxWritePtr[1].col = col;\n    _VtxWritePtr[2].pos = c; _VtxWritePtr[2].uv = uv_c; _VtxWritePtr[2].col = col;\n    _VtxWritePtr[3].pos = d; _VtxWritePtr[3].uv = uv_d; _VtxWritePtr[3].col = col;\n    _VtxWritePtr += 4;\n    _VtxCurrentIdx += 4;\n    _IdxWritePtr += 6;\n}\n\nvoid ImDrawList::PrimQuadUV(const ImVec2& a, const ImVec2& b, const ImVec2& c, const ImVec2& d, const ImVec2& uv_a, const ImVec2& uv_b, const ImVec2& uv_c, const ImVec2& uv_d, ImU32 col)\n{\n    ImDrawIdx idx = (ImDrawIdx)_VtxCurrentIdx;\n    _IdxWritePtr[0] = idx; _IdxWritePtr[1] = (ImDrawIdx)(idx+1); _IdxWritePtr[2] = (ImDrawIdx)(idx+2);\n    _IdxWritePtr[3] = idx; _IdxWritePtr[4] = (ImDrawIdx)(idx+2); _IdxWritePtr[5] = (ImDrawIdx)(idx+3);\n    _VtxWritePtr[0].pos = a; _VtxWritePtr[0].uv = uv_a; _VtxWritePtr[0].col = col;\n    _VtxWritePtr[1].pos = b; _VtxWritePtr[1].uv = uv_b; _VtxWritePtr[1].col = col;\n    _VtxWritePtr[2].pos = c; _VtxWritePtr[2].uv = uv_c; _VtxWritePtr[2].col = col;\n    _VtxWritePtr[3].pos = d; _VtxWritePtr[3].uv = uv_d; _VtxWritePtr[3].col = col;\n    _VtxWritePtr += 4;\n    _VtxCurrentIdx += 4;\n    _IdxWritePtr += 6;\n}\n\n// On AddPolyline() and AddConvexPolyFilled() we intentionally avoid using ImVec2 and superfluous function calls to optimize debug/non-inlined builds.\n// - Those macros expects l-values and need to be used as their own statement.\n// - Those macros are intentionally not surrounded by the 'do {} while (0)' idiom because even that translates to runtime with debug compilers.\n#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\n#define IM_FIXNORMAL2F_MAX_INVLEN2          100.0f // 500.0f (see #4053, #3366)\n#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\n\n// TODO: Thickness anti-aliased lines cap are missing their AA fringe.\n// We avoid using the ImVec2 math operators here to reduce cost to a minimum for debug/non-inlined builds.\nvoid ImDrawList::AddPolyline(const ImVec2* points, const int points_count, ImU32 col, ImDrawFlags flags, float thickness)\n{\n    if (points_count < 2 || (col & IM_COL32_A_MASK) == 0)\n        return;\n\n    const bool closed = (flags & ImDrawFlags_Closed) != 0;\n    const ImVec2 opaque_uv = _Data->TexUvWhitePixel;\n    const int count = closed ? points_count : points_count - 1; // The number of line segments we need to draw\n    const bool thick_line = (thickness > _FringeScale);\n\n    if (Flags & ImDrawListFlags_AntiAliasedLines)\n    {\n        // Anti-aliased stroke\n        const float AA_SIZE = _FringeScale;\n        const ImU32 col_trans = col & ~IM_COL32_A_MASK;\n\n        // Thicknesses <1.0 should behave like thickness 1.0\n        thickness = ImMax(thickness, 1.0f);\n        const int integer_thickness = (int)thickness;\n        const float fractional_thickness = thickness - integer_thickness;\n\n        // Do we want to draw this line using a texture?\n        // - For now, only draw integer-width lines using textures to avoid issues with the way scaling occurs, could be improved.\n        // - If AA_SIZE is not 1.0f we cannot use the texture path.\n        const bool use_texture = (Flags & ImDrawListFlags_AntiAliasedLinesUseTex) && (integer_thickness < IM_DRAWLIST_TEX_LINES_WIDTH_MAX) && (fractional_thickness <= 0.00001f) && (AA_SIZE == 1.0f);\n\n        // We should never hit this, because NewFrame() doesn't set ImDrawListFlags_AntiAliasedLinesUseTex unless ImFontAtlasFlags_NoBakedLines is off\n        IM_ASSERT_PARANOID(!use_texture || !(_Data->Font->ContainerAtlas->Flags & ImFontAtlasFlags_NoBakedLines));\n\n        const int idx_count = use_texture ? (count * 6) : (thick_line ? count * 18 : count * 12);\n        const int vtx_count = use_texture ? (points_count * 2) : (thick_line ? points_count * 4 : points_count * 3);\n        PrimReserve(idx_count, vtx_count);\n\n        // Temporary buffer\n        // The first <points_count> items are normals at each line point, then after that there are either 2 or 4 temp points for each line point\n        _Data->TempBuffer.reserve_discard(points_count * ((use_texture || !thick_line) ? 3 : 5));\n        ImVec2* temp_normals = _Data->TempBuffer.Data;\n        ImVec2* temp_points = temp_normals + points_count;\n\n        // Calculate normals (tangents) for each line segment\n        for (int i1 = 0; i1 < count; i1++)\n        {\n            const int i2 = (i1 + 1) == points_count ? 0 : i1 + 1;\n            float dx = points[i2].x - points[i1].x;\n            float dy = points[i2].y - points[i1].y;\n            IM_NORMALIZE2F_OVER_ZERO(dx, dy);\n            temp_normals[i1].x = dy;\n            temp_normals[i1].y = -dx;\n        }\n        if (!closed)\n            temp_normals[points_count - 1] = temp_normals[points_count - 2];\n\n        // If we are drawing a one-pixel-wide line without a texture, or a textured line of any width, we only need 2 or 3 vertices per point\n        if (use_texture || !thick_line)\n        {\n            // [PATH 1] Texture-based lines (thick or non-thick)\n            // [PATH 2] Non texture-based lines (non-thick)\n\n            // The width of the geometry we need to draw - this is essentially <thickness> pixels for the line itself, plus \"one pixel\" for AA.\n            // - In the texture-based path, we don't use AA_SIZE here because the +1 is tied to the generated texture\n            //   (see ImFontAtlasBuildRenderLinesTexData() function), and so alternate values won't work without changes to that code.\n            // - In the non texture-based paths, we would allow AA_SIZE to potentially be != 1.0f with a patch (e.g. fringe_scale patch to\n            //   allow scaling geometry while preserving one-screen-pixel AA fringe).\n            const float half_draw_size = use_texture ? ((thickness * 0.5f) + 1) : AA_SIZE;\n\n            // If line is not closed, the first and last points need to be generated differently as there are no normals to blend\n            if (!closed)\n            {\n                temp_points[0] = points[0] + temp_normals[0] * half_draw_size;\n                temp_points[1] = points[0] - temp_normals[0] * half_draw_size;\n                temp_points[(points_count-1)*2+0] = points[points_count-1] + temp_normals[points_count-1] * half_draw_size;\n                temp_points[(points_count-1)*2+1] = points[points_count-1] - temp_normals[points_count-1] * half_draw_size;\n            }\n\n            // Generate the indices to form a number of triangles for each line segment, and the vertices for the line edges\n            // This takes points n and n+1 and writes into n+1, with the first point in a closed line being generated from the final one (as n+1 wraps)\n            // FIXME-OPT: Merge the different loops, possibly remove the temporary buffer.\n            unsigned int idx1 = _VtxCurrentIdx; // Vertex index for start of line segment\n            for (int i1 = 0; i1 < count; i1++) // i1 is the first point of the line segment\n            {\n                const int i2 = (i1 + 1) == points_count ? 0 : i1 + 1; // i2 is the second point of the line segment\n                const unsigned int idx2 = ((i1 + 1) == points_count) ? _VtxCurrentIdx : (idx1 + (use_texture ? 2 : 3)); // Vertex index for end of segment\n\n                // Average normals\n                float dm_x = (temp_normals[i1].x + temp_normals[i2].x) * 0.5f;\n                float dm_y = (temp_normals[i1].y + temp_normals[i2].y) * 0.5f;\n                IM_FIXNORMAL2F(dm_x, dm_y);\n                dm_x *= half_draw_size; // dm_x, dm_y are offset to the outer edge of the AA area\n                dm_y *= half_draw_size;\n\n                // Add temporary vertexes for the outer edges\n                ImVec2* out_vtx = &temp_points[i2 * 2];\n                out_vtx[0].x = points[i2].x + dm_x;\n                out_vtx[0].y = points[i2].y + dm_y;\n                out_vtx[1].x = points[i2].x - dm_x;\n                out_vtx[1].y = points[i2].y - dm_y;\n\n                if (use_texture)\n                {\n                    // Add indices for two triangles\n                    _IdxWritePtr[0] = (ImDrawIdx)(idx2 + 0); _IdxWritePtr[1] = (ImDrawIdx)(idx1 + 0); _IdxWritePtr[2] = (ImDrawIdx)(idx1 + 1); // Right tri\n                    _IdxWritePtr[3] = (ImDrawIdx)(idx2 + 1); _IdxWritePtr[4] = (ImDrawIdx)(idx1 + 1); _IdxWritePtr[5] = (ImDrawIdx)(idx2 + 0); // Left tri\n                    _IdxWritePtr += 6;\n                }\n                else\n                {\n                    // Add indexes for four triangles\n                    _IdxWritePtr[0] = (ImDrawIdx)(idx2 + 0); _IdxWritePtr[1] = (ImDrawIdx)(idx1 + 0); _IdxWritePtr[2] = (ImDrawIdx)(idx1 + 2); // Right tri 1\n                    _IdxWritePtr[3] = (ImDrawIdx)(idx1 + 2); _IdxWritePtr[4] = (ImDrawIdx)(idx2 + 2); _IdxWritePtr[5] = (ImDrawIdx)(idx2 + 0); // Right tri 2\n                    _IdxWritePtr[6] = (ImDrawIdx)(idx2 + 1); _IdxWritePtr[7] = (ImDrawIdx)(idx1 + 1); _IdxWritePtr[8] = (ImDrawIdx)(idx1 + 0); // Left tri 1\n                    _IdxWritePtr[9] = (ImDrawIdx)(idx1 + 0); _IdxWritePtr[10] = (ImDrawIdx)(idx2 + 0); _IdxWritePtr[11] = (ImDrawIdx)(idx2 + 1); // Left tri 2\n                    _IdxWritePtr += 12;\n                }\n\n                idx1 = idx2;\n            }\n\n            // Add vertexes for each point on the line\n            if (use_texture)\n            {\n                // If we're using textures we only need to emit the left/right edge vertices\n                ImVec4 tex_uvs = _Data->TexUvLines[integer_thickness];\n                /*if (fractional_thickness != 0.0f) // Currently always zero when use_texture==false!\n                {\n                    const ImVec4 tex_uvs_1 = _Data->TexUvLines[integer_thickness + 1];\n                    tex_uvs.x = tex_uvs.x + (tex_uvs_1.x - tex_uvs.x) * fractional_thickness; // inlined ImLerp()\n                    tex_uvs.y = tex_uvs.y + (tex_uvs_1.y - tex_uvs.y) * fractional_thickness;\n                    tex_uvs.z = tex_uvs.z + (tex_uvs_1.z - tex_uvs.z) * fractional_thickness;\n                    tex_uvs.w = tex_uvs.w + (tex_uvs_1.w - tex_uvs.w) * fractional_thickness;\n                }*/\n                ImVec2 tex_uv0(tex_uvs.x, tex_uvs.y);\n                ImVec2 tex_uv1(tex_uvs.z, tex_uvs.w);\n                for (int i = 0; i < points_count; i++)\n                {\n                    _VtxWritePtr[0].pos = temp_points[i * 2 + 0]; _VtxWritePtr[0].uv = tex_uv0; _VtxWritePtr[0].col = col; // Left-side outer edge\n                    _VtxWritePtr[1].pos = temp_points[i * 2 + 1]; _VtxWritePtr[1].uv = tex_uv1; _VtxWritePtr[1].col = col; // Right-side outer edge\n                    _VtxWritePtr += 2;\n                }\n            }\n            else\n            {\n                // If we're not using a texture, we need the center vertex as well\n                for (int i = 0; i < points_count; i++)\n                {\n                    _VtxWritePtr[0].pos = points[i];              _VtxWritePtr[0].uv = opaque_uv; _VtxWritePtr[0].col = col;       // Center of line\n                    _VtxWritePtr[1].pos = temp_points[i * 2 + 0]; _VtxWritePtr[1].uv = opaque_uv; _VtxWritePtr[1].col = col_trans; // Left-side outer edge\n                    _VtxWritePtr[2].pos = temp_points[i * 2 + 1]; _VtxWritePtr[2].uv = opaque_uv; _VtxWritePtr[2].col = col_trans; // Right-side outer edge\n                    _VtxWritePtr += 3;\n                }\n            }\n        }\n        else\n        {\n            // [PATH 2] Non texture-based lines (thick): we need to draw the solid line core and thus require four vertices per point\n            const float half_inner_thickness = (thickness - AA_SIZE) * 0.5f;\n\n            // If line is not closed, the first and last points need to be generated differently as there are no normals to blend\n            if (!closed)\n            {\n                const int points_last = points_count - 1;\n                temp_points[0] = points[0] + temp_normals[0] * (half_inner_thickness + AA_SIZE);\n                temp_points[1] = points[0] + temp_normals[0] * (half_inner_thickness);\n                temp_points[2] = points[0] - temp_normals[0] * (half_inner_thickness);\n                temp_points[3] = points[0] - temp_normals[0] * (half_inner_thickness + AA_SIZE);\n                temp_points[points_last * 4 + 0] = points[points_last] + temp_normals[points_last] * (half_inner_thickness + AA_SIZE);\n                temp_points[points_last * 4 + 1] = points[points_last] + temp_normals[points_last] * (half_inner_thickness);\n                temp_points[points_last * 4 + 2] = points[points_last] - temp_normals[points_last] * (half_inner_thickness);\n                temp_points[points_last * 4 + 3] = points[points_last] - temp_normals[points_last] * (half_inner_thickness + AA_SIZE);\n            }\n\n            // Generate the indices to form a number of triangles for each line segment, and the vertices for the line edges\n            // This takes points n and n+1 and writes into n+1, with the first point in a closed line being generated from the final one (as n+1 wraps)\n            // FIXME-OPT: Merge the different loops, possibly remove the temporary buffer.\n            unsigned int idx1 = _VtxCurrentIdx; // Vertex index for start of line segment\n            for (int i1 = 0; i1 < count; i1++) // i1 is the first point of the line segment\n            {\n                const int i2 = (i1 + 1) == points_count ? 0 : (i1 + 1); // i2 is the second point of the line segment\n                const unsigned int idx2 = (i1 + 1) == points_count ? _VtxCurrentIdx : (idx1 + 4); // Vertex index for end of segment\n\n                // Average normals\n                float dm_x = (temp_normals[i1].x + temp_normals[i2].x) * 0.5f;\n                float dm_y = (temp_normals[i1].y + temp_normals[i2].y) * 0.5f;\n                IM_FIXNORMAL2F(dm_x, dm_y);\n                float dm_out_x = dm_x * (half_inner_thickness + AA_SIZE);\n                float dm_out_y = dm_y * (half_inner_thickness + AA_SIZE);\n                float dm_in_x = dm_x * half_inner_thickness;\n                float dm_in_y = dm_y * half_inner_thickness;\n\n                // Add temporary vertices\n                ImVec2* out_vtx = &temp_points[i2 * 4];\n                out_vtx[0].x = points[i2].x + dm_out_x;\n                out_vtx[0].y = points[i2].y + dm_out_y;\n                out_vtx[1].x = points[i2].x + dm_in_x;\n                out_vtx[1].y = points[i2].y + dm_in_y;\n                out_vtx[2].x = points[i2].x - dm_in_x;\n                out_vtx[2].y = points[i2].y - dm_in_y;\n                out_vtx[3].x = points[i2].x - dm_out_x;\n                out_vtx[3].y = points[i2].y - dm_out_y;\n\n                // Add indexes\n                _IdxWritePtr[0]  = (ImDrawIdx)(idx2 + 1); _IdxWritePtr[1]  = (ImDrawIdx)(idx1 + 1); _IdxWritePtr[2]  = (ImDrawIdx)(idx1 + 2);\n                _IdxWritePtr[3]  = (ImDrawIdx)(idx1 + 2); _IdxWritePtr[4]  = (ImDrawIdx)(idx2 + 2); _IdxWritePtr[5]  = (ImDrawIdx)(idx2 + 1);\n                _IdxWritePtr[6]  = (ImDrawIdx)(idx2 + 1); _IdxWritePtr[7]  = (ImDrawIdx)(idx1 + 1); _IdxWritePtr[8]  = (ImDrawIdx)(idx1 + 0);\n                _IdxWritePtr[9]  = (ImDrawIdx)(idx1 + 0); _IdxWritePtr[10] = (ImDrawIdx)(idx2 + 0); _IdxWritePtr[11] = (ImDrawIdx)(idx2 + 1);\n                _IdxWritePtr[12] = (ImDrawIdx)(idx2 + 2); _IdxWritePtr[13] = (ImDrawIdx)(idx1 + 2); _IdxWritePtr[14] = (ImDrawIdx)(idx1 + 3);\n                _IdxWritePtr[15] = (ImDrawIdx)(idx1 + 3); _IdxWritePtr[16] = (ImDrawIdx)(idx2 + 3); _IdxWritePtr[17] = (ImDrawIdx)(idx2 + 2);\n                _IdxWritePtr += 18;\n\n                idx1 = idx2;\n            }\n\n            // Add vertices\n            for (int i = 0; i < points_count; i++)\n            {\n                _VtxWritePtr[0].pos = temp_points[i * 4 + 0]; _VtxWritePtr[0].uv = opaque_uv; _VtxWritePtr[0].col = col_trans;\n                _VtxWritePtr[1].pos = temp_points[i * 4 + 1]; _VtxWritePtr[1].uv = opaque_uv; _VtxWritePtr[1].col = col;\n                _VtxWritePtr[2].pos = temp_points[i * 4 + 2]; _VtxWritePtr[2].uv = opaque_uv; _VtxWritePtr[2].col = col;\n                _VtxWritePtr[3].pos = temp_points[i * 4 + 3]; _VtxWritePtr[3].uv = opaque_uv; _VtxWritePtr[3].col = col_trans;\n                _VtxWritePtr += 4;\n            }\n        }\n        _VtxCurrentIdx += (ImDrawIdx)vtx_count;\n    }\n    else\n    {\n        // [PATH 4] Non texture-based, Non anti-aliased lines\n        const int idx_count = count * 6;\n        const int vtx_count = count * 4;    // FIXME-OPT: Not sharing edges\n        PrimReserve(idx_count, vtx_count);\n\n        for (int i1 = 0; i1 < count; i1++)\n        {\n            const int i2 = (i1 + 1) == points_count ? 0 : i1 + 1;\n            const ImVec2& p1 = points[i1];\n            const ImVec2& p2 = points[i2];\n\n            float dx = p2.x - p1.x;\n            float dy = p2.y - p1.y;\n            IM_NORMALIZE2F_OVER_ZERO(dx, dy);\n            dx *= (thickness * 0.5f);\n            dy *= (thickness * 0.5f);\n\n            _VtxWritePtr[0].pos.x = p1.x + dy; _VtxWritePtr[0].pos.y = p1.y - dx; _VtxWritePtr[0].uv = opaque_uv; _VtxWritePtr[0].col = col;\n            _VtxWritePtr[1].pos.x = p2.x + dy; _VtxWritePtr[1].pos.y = p2.y - dx; _VtxWritePtr[1].uv = opaque_uv; _VtxWritePtr[1].col = col;\n            _VtxWritePtr[2].pos.x = p2.x - dy; _VtxWritePtr[2].pos.y = p2.y + dx; _VtxWritePtr[2].uv = opaque_uv; _VtxWritePtr[2].col = col;\n            _VtxWritePtr[3].pos.x = p1.x - dy; _VtxWritePtr[3].pos.y = p1.y + dx; _VtxWritePtr[3].uv = opaque_uv; _VtxWritePtr[3].col = col;\n            _VtxWritePtr += 4;\n\n            _IdxWritePtr[0] = (ImDrawIdx)(_VtxCurrentIdx); _IdxWritePtr[1] = (ImDrawIdx)(_VtxCurrentIdx + 1); _IdxWritePtr[2] = (ImDrawIdx)(_VtxCurrentIdx + 2);\n            _IdxWritePtr[3] = (ImDrawIdx)(_VtxCurrentIdx); _IdxWritePtr[4] = (ImDrawIdx)(_VtxCurrentIdx + 2); _IdxWritePtr[5] = (ImDrawIdx)(_VtxCurrentIdx + 3);\n            _IdxWritePtr += 6;\n            _VtxCurrentIdx += 4;\n        }\n    }\n}\n\n// - We intentionally avoid using ImVec2 and its math operators here to reduce cost to a minimum for debug/non-inlined builds.\n// - Filled shapes must always use clockwise winding order. The anti-aliasing fringe depends on it. Counter-clockwise shapes will have \"inward\" anti-aliasing.\nvoid ImDrawList::AddConvexPolyFilled(const ImVec2* points, const int points_count, ImU32 col)\n{\n    if (points_count < 3 || (col & IM_COL32_A_MASK) == 0)\n        return;\n\n    const ImVec2 uv = _Data->TexUvWhitePixel;\n\n    if (Flags & ImDrawListFlags_AntiAliasedFill)\n    {\n        // Anti-aliased Fill\n        const float AA_SIZE = _FringeScale;\n        const ImU32 col_trans = col & ~IM_COL32_A_MASK;\n        const int idx_count = (points_count - 2)*3 + points_count * 6;\n        const int vtx_count = (points_count * 2);\n        PrimReserve(idx_count, vtx_count);\n\n        // Add indexes for fill\n        unsigned int vtx_inner_idx = _VtxCurrentIdx;\n        unsigned int vtx_outer_idx = _VtxCurrentIdx + 1;\n        for (int i = 2; i < points_count; i++)\n        {\n            _IdxWritePtr[0] = (ImDrawIdx)(vtx_inner_idx); _IdxWritePtr[1] = (ImDrawIdx)(vtx_inner_idx + ((i - 1) << 1)); _IdxWritePtr[2] = (ImDrawIdx)(vtx_inner_idx + (i << 1));\n            _IdxWritePtr += 3;\n        }\n\n        // Compute normals\n        _Data->TempBuffer.reserve_discard(points_count);\n        ImVec2* temp_normals = _Data->TempBuffer.Data;\n        for (int i0 = points_count - 1, i1 = 0; i1 < points_count; i0 = i1++)\n        {\n            const ImVec2& p0 = points[i0];\n            const ImVec2& p1 = points[i1];\n            float dx = p1.x - p0.x;\n            float dy = p1.y - p0.y;\n            IM_NORMALIZE2F_OVER_ZERO(dx, dy);\n            temp_normals[i0].x = dy;\n            temp_normals[i0].y = -dx;\n        }\n\n        for (int i0 = points_count - 1, i1 = 0; i1 < points_count; i0 = i1++)\n        {\n            // Average normals\n            const ImVec2& n0 = temp_normals[i0];\n            const ImVec2& n1 = temp_normals[i1];\n            float dm_x = (n0.x + n1.x) * 0.5f;\n            float dm_y = (n0.y + n1.y) * 0.5f;\n            IM_FIXNORMAL2F(dm_x, dm_y);\n            dm_x *= AA_SIZE * 0.5f;\n            dm_y *= AA_SIZE * 0.5f;\n\n            // Add vertices\n            _VtxWritePtr[0].pos.x = (points[i1].x - dm_x); _VtxWritePtr[0].pos.y = (points[i1].y - dm_y); _VtxWritePtr[0].uv = uv; _VtxWritePtr[0].col = col;        // Inner\n            _VtxWritePtr[1].pos.x = (points[i1].x + dm_x); _VtxWritePtr[1].pos.y = (points[i1].y + dm_y); _VtxWritePtr[1].uv = uv; _VtxWritePtr[1].col = col_trans;  // Outer\n            _VtxWritePtr += 2;\n\n            // Add indexes for fringes\n            _IdxWritePtr[0] = (ImDrawIdx)(vtx_inner_idx + (i1 << 1)); _IdxWritePtr[1] = (ImDrawIdx)(vtx_inner_idx + (i0 << 1)); _IdxWritePtr[2] = (ImDrawIdx)(vtx_outer_idx + (i0 << 1));\n            _IdxWritePtr[3] = (ImDrawIdx)(vtx_outer_idx + (i0 << 1)); _IdxWritePtr[4] = (ImDrawIdx)(vtx_outer_idx + (i1 << 1)); _IdxWritePtr[5] = (ImDrawIdx)(vtx_inner_idx + (i1 << 1));\n            _IdxWritePtr += 6;\n        }\n        _VtxCurrentIdx += (ImDrawIdx)vtx_count;\n    }\n    else\n    {\n        // Non Anti-aliased Fill\n        const int idx_count = (points_count - 2)*3;\n        const int vtx_count = points_count;\n        PrimReserve(idx_count, vtx_count);\n        for (int i = 0; i < vtx_count; i++)\n        {\n            _VtxWritePtr[0].pos = points[i]; _VtxWritePtr[0].uv = uv; _VtxWritePtr[0].col = col;\n            _VtxWritePtr++;\n        }\n        for (int i = 2; i < points_count; i++)\n        {\n            _IdxWritePtr[0] = (ImDrawIdx)(_VtxCurrentIdx); _IdxWritePtr[1] = (ImDrawIdx)(_VtxCurrentIdx + i - 1); _IdxWritePtr[2] = (ImDrawIdx)(_VtxCurrentIdx + i);\n            _IdxWritePtr += 3;\n        }\n        _VtxCurrentIdx += (ImDrawIdx)vtx_count;\n    }\n}\n\nvoid ImDrawList::_PathArcToFastEx(const ImVec2& center, float radius, int a_min_sample, int a_max_sample, int a_step)\n{\n    if (radius < 0.5f)\n    {\n        _Path.push_back(center);\n        return;\n    }\n\n    // Calculate arc auto segment step size\n    if (a_step <= 0)\n        a_step = IM_DRAWLIST_ARCFAST_SAMPLE_MAX / _CalcCircleAutoSegmentCount(radius);\n\n    // Make sure we never do steps larger than one quarter of the circle\n    a_step = ImClamp(a_step, 1, IM_DRAWLIST_ARCFAST_TABLE_SIZE / 4);\n\n    const int sample_range = ImAbs(a_max_sample - a_min_sample);\n    const int a_next_step = a_step;\n\n    int samples = sample_range + 1;\n    bool extra_max_sample = false;\n    if (a_step > 1)\n    {\n        samples            = sample_range / a_step + 1;\n        const int overstep = sample_range % a_step;\n\n        if (overstep > 0)\n        {\n            extra_max_sample = true;\n            samples++;\n\n            // When we have overstep to avoid awkwardly looking one long line and one tiny one at the end,\n            // distribute first step range evenly between them by reducing first step size.\n            if (sample_range > 0)\n                a_step -= (a_step - overstep) / 2;\n        }\n    }\n\n    _Path.resize(_Path.Size + samples);\n    ImVec2* out_ptr = _Path.Data + (_Path.Size - samples);\n\n    int sample_index = a_min_sample;\n    if (sample_index < 0 || sample_index >= IM_DRAWLIST_ARCFAST_SAMPLE_MAX)\n    {\n        sample_index = sample_index % IM_DRAWLIST_ARCFAST_SAMPLE_MAX;\n        if (sample_index < 0)\n            sample_index += IM_DRAWLIST_ARCFAST_SAMPLE_MAX;\n    }\n\n    if (a_max_sample >= a_min_sample)\n    {\n        for (int a = a_min_sample; a <= a_max_sample; a += a_step, sample_index += a_step, a_step = a_next_step)\n        {\n            // a_step is clamped to IM_DRAWLIST_ARCFAST_SAMPLE_MAX, so we have guaranteed that it will not wrap over range twice or more\n            if (sample_index >= IM_DRAWLIST_ARCFAST_SAMPLE_MAX)\n                sample_index -= IM_DRAWLIST_ARCFAST_SAMPLE_MAX;\n\n            const ImVec2 s = _Data->ArcFastVtx[sample_index];\n            out_ptr->x = center.x + s.x * radius;\n            out_ptr->y = center.y + s.y * radius;\n            out_ptr++;\n        }\n    }\n    else\n    {\n        for (int a = a_min_sample; a >= a_max_sample; a -= a_step, sample_index -= a_step, a_step = a_next_step)\n        {\n            // a_step is clamped to IM_DRAWLIST_ARCFAST_SAMPLE_MAX, so we have guaranteed that it will not wrap over range twice or more\n            if (sample_index < 0)\n                sample_index += IM_DRAWLIST_ARCFAST_SAMPLE_MAX;\n\n            const ImVec2 s = _Data->ArcFastVtx[sample_index];\n            out_ptr->x = center.x + s.x * radius;\n            out_ptr->y = center.y + s.y * radius;\n            out_ptr++;\n        }\n    }\n\n    if (extra_max_sample)\n    {\n        int normalized_max_sample = a_max_sample % IM_DRAWLIST_ARCFAST_SAMPLE_MAX;\n        if (normalized_max_sample < 0)\n            normalized_max_sample += IM_DRAWLIST_ARCFAST_SAMPLE_MAX;\n\n        const ImVec2 s = _Data->ArcFastVtx[normalized_max_sample];\n        out_ptr->x = center.x + s.x * radius;\n        out_ptr->y = center.y + s.y * radius;\n        out_ptr++;\n    }\n\n    IM_ASSERT_PARANOID(_Path.Data + _Path.Size == out_ptr);\n}\n\nvoid ImDrawList::_PathArcToN(const ImVec2& center, float radius, float a_min, float a_max, int num_segments)\n{\n    if (radius < 0.5f)\n    {\n        _Path.push_back(center);\n        return;\n    }\n\n    // Note that we are adding a point at both a_min and a_max.\n    // If you are trying to draw a full closed circle you don't want the overlapping points!\n    _Path.reserve(_Path.Size + (num_segments + 1));\n    for (int i = 0; i <= num_segments; i++)\n    {\n        const float a = a_min + ((float)i / (float)num_segments) * (a_max - a_min);\n        _Path.push_back(ImVec2(center.x + ImCos(a) * radius, center.y + ImSin(a) * radius));\n    }\n}\n\n// 0: East, 3: South, 6: West, 9: North, 12: East\nvoid ImDrawList::PathArcToFast(const ImVec2& center, float radius, int a_min_of_12, int a_max_of_12)\n{\n    if (radius < 0.5f)\n    {\n        _Path.push_back(center);\n        return;\n    }\n    _PathArcToFastEx(center, radius, a_min_of_12 * IM_DRAWLIST_ARCFAST_SAMPLE_MAX / 12, a_max_of_12 * IM_DRAWLIST_ARCFAST_SAMPLE_MAX / 12, 0);\n}\n\nvoid ImDrawList::PathArcTo(const ImVec2& center, float radius, float a_min, float a_max, int num_segments)\n{\n    if (radius < 0.5f)\n    {\n        _Path.push_back(center);\n        return;\n    }\n\n    if (num_segments > 0)\n    {\n        _PathArcToN(center, radius, a_min, a_max, num_segments);\n        return;\n    }\n\n    // Automatic segment count\n    if (radius <= _Data->ArcFastRadiusCutoff)\n    {\n        const bool a_is_reverse = a_max < a_min;\n\n        // We are going to use precomputed values for mid samples.\n        // Determine first and last sample in lookup table that belong to the arc.\n        const float a_min_sample_f = IM_DRAWLIST_ARCFAST_SAMPLE_MAX * a_min / (IM_PI * 2.0f);\n        const float a_max_sample_f = IM_DRAWLIST_ARCFAST_SAMPLE_MAX * a_max / (IM_PI * 2.0f);\n\n        const int a_min_sample = a_is_reverse ? (int)ImFloor(a_min_sample_f) : (int)ImCeil(a_min_sample_f);\n        const int a_max_sample = a_is_reverse ? (int)ImCeil(a_max_sample_f) : (int)ImFloor(a_max_sample_f);\n        const int a_mid_samples = a_is_reverse ? ImMax(a_min_sample - a_max_sample, 0) : ImMax(a_max_sample - a_min_sample, 0);\n\n        const float a_min_segment_angle = a_min_sample * IM_PI * 2.0f / IM_DRAWLIST_ARCFAST_SAMPLE_MAX;\n        const float a_max_segment_angle = a_max_sample * IM_PI * 2.0f / IM_DRAWLIST_ARCFAST_SAMPLE_MAX;\n        const bool a_emit_start = ImAbs(a_min_segment_angle - a_min) >= 1e-5f;\n        const bool a_emit_end = ImAbs(a_max - a_max_segment_angle) >= 1e-5f;\n\n        _Path.reserve(_Path.Size + (a_mid_samples + 1 + (a_emit_start ? 1 : 0) + (a_emit_end ? 1 : 0)));\n        if (a_emit_start)\n            _Path.push_back(ImVec2(center.x + ImCos(a_min) * radius, center.y + ImSin(a_min) * radius));\n        if (a_mid_samples > 0)\n            _PathArcToFastEx(center, radius, a_min_sample, a_max_sample, 0);\n        if (a_emit_end)\n            _Path.push_back(ImVec2(center.x + ImCos(a_max) * radius, center.y + ImSin(a_max) * radius));\n    }\n    else\n    {\n        const float arc_length = ImAbs(a_max - a_min);\n        const int circle_segment_count = _CalcCircleAutoSegmentCount(radius);\n        const int arc_segment_count = ImMax((int)ImCeil(circle_segment_count * arc_length / (IM_PI * 2.0f)), (int)(2.0f * IM_PI / arc_length));\n        _PathArcToN(center, radius, a_min, a_max, arc_segment_count);\n    }\n}\n\nvoid ImDrawList::PathEllipticalArcTo(const ImVec2& center, const ImVec2& radius, float rot, float a_min, float a_max, int num_segments)\n{\n    if (num_segments <= 0)\n        num_segments = _CalcCircleAutoSegmentCount(ImMax(radius.x, radius.y)); // A bit pessimistic, maybe there's a better computation to do here.\n\n    _Path.reserve(_Path.Size + (num_segments + 1));\n\n    const float cos_rot = ImCos(rot);\n    const float sin_rot = ImSin(rot);\n    for (int i = 0; i <= num_segments; i++)\n    {\n        const float a = a_min + ((float)i / (float)num_segments) * (a_max - a_min);\n        ImVec2 point(ImCos(a) * radius.x, ImSin(a) * radius.y);\n        const ImVec2 rel((point.x * cos_rot) - (point.y * sin_rot), (point.x * sin_rot) + (point.y * cos_rot));\n        point.x = rel.x + center.x;\n        point.y = rel.y + center.y;\n        _Path.push_back(point);\n    }\n}\n\nImVec2 ImBezierCubicCalc(const ImVec2& p1, const ImVec2& p2, const ImVec2& p3, const ImVec2& p4, float t)\n{\n    float u = 1.0f - t;\n    float w1 = u * u * u;\n    float w2 = 3 * u * u * t;\n    float w3 = 3 * u * t * t;\n    float w4 = t * t * t;\n    return ImVec2(w1 * p1.x + w2 * p2.x + w3 * p3.x + w4 * p4.x, w1 * p1.y + w2 * p2.y + w3 * p3.y + w4 * p4.y);\n}\n\nImVec2 ImBezierQuadraticCalc(const ImVec2& p1, const ImVec2& p2, const ImVec2& p3, float t)\n{\n    float u = 1.0f - t;\n    float w1 = u * u;\n    float w2 = 2 * u * t;\n    float w3 = t * t;\n    return ImVec2(w1 * p1.x + w2 * p2.x + w3 * p3.x, w1 * p1.y + w2 * p2.y + w3 * p3.y);\n}\n\n// Closely mimics ImBezierCubicClosestPointCasteljau() in imgui.cpp\nstatic void PathBezierCubicCurveToCasteljau(ImVector<ImVec2>* path, float x1, float y1, float x2, float y2, float x3, float y3, float x4, float y4, float tess_tol, int level)\n{\n    float dx = x4 - x1;\n    float dy = y4 - y1;\n    float d2 = (x2 - x4) * dy - (y2 - y4) * dx;\n    float d3 = (x3 - x4) * dy - (y3 - y4) * dx;\n    d2 = (d2 >= 0) ? d2 : -d2;\n    d3 = (d3 >= 0) ? d3 : -d3;\n    if ((d2 + d3) * (d2 + d3) < tess_tol * (dx * dx + dy * dy))\n    {\n        path->push_back(ImVec2(x4, y4));\n    }\n    else if (level < 10)\n    {\n        float x12 = (x1 + x2) * 0.5f, y12 = (y1 + y2) * 0.5f;\n        float x23 = (x2 + x3) * 0.5f, y23 = (y2 + y3) * 0.5f;\n        float x34 = (x3 + x4) * 0.5f, y34 = (y3 + y4) * 0.5f;\n        float x123 = (x12 + x23) * 0.5f, y123 = (y12 + y23) * 0.5f;\n        float x234 = (x23 + x34) * 0.5f, y234 = (y23 + y34) * 0.5f;\n        float x1234 = (x123 + x234) * 0.5f, y1234 = (y123 + y234) * 0.5f;\n        PathBezierCubicCurveToCasteljau(path, x1, y1, x12, y12, x123, y123, x1234, y1234, tess_tol, level + 1);\n        PathBezierCubicCurveToCasteljau(path, x1234, y1234, x234, y234, x34, y34, x4, y4, tess_tol, level + 1);\n    }\n}\n\nstatic void PathBezierQuadraticCurveToCasteljau(ImVector<ImVec2>* path, float x1, float y1, float x2, float y2, float x3, float y3, float tess_tol, int level)\n{\n    float dx = x3 - x1, dy = y3 - y1;\n    float det = (x2 - x3) * dy - (y2 - y3) * dx;\n    if (det * det * 4.0f < tess_tol * (dx * dx + dy * dy))\n    {\n        path->push_back(ImVec2(x3, y3));\n    }\n    else if (level < 10)\n    {\n        float x12 = (x1 + x2) * 0.5f, y12 = (y1 + y2) * 0.5f;\n        float x23 = (x2 + x3) * 0.5f, y23 = (y2 + y3) * 0.5f;\n        float x123 = (x12 + x23) * 0.5f, y123 = (y12 + y23) * 0.5f;\n        PathBezierQuadraticCurveToCasteljau(path, x1, y1, x12, y12, x123, y123, tess_tol, level + 1);\n        PathBezierQuadraticCurveToCasteljau(path, x123, y123, x23, y23, x3, y3, tess_tol, level + 1);\n    }\n}\n\nvoid ImDrawList::PathBezierCubicCurveTo(const ImVec2& p2, const ImVec2& p3, const ImVec2& p4, int num_segments)\n{\n    ImVec2 p1 = _Path.back();\n    if (num_segments == 0)\n    {\n        IM_ASSERT(_Data->CurveTessellationTol > 0.0f);\n        PathBezierCubicCurveToCasteljau(&_Path, p1.x, p1.y, p2.x, p2.y, p3.x, p3.y, p4.x, p4.y, _Data->CurveTessellationTol, 0); // Auto-tessellated\n    }\n    else\n    {\n        float t_step = 1.0f / (float)num_segments;\n        for (int i_step = 1; i_step <= num_segments; i_step++)\n            _Path.push_back(ImBezierCubicCalc(p1, p2, p3, p4, t_step * i_step));\n    }\n}\n\nvoid ImDrawList::PathBezierQuadraticCurveTo(const ImVec2& p2, const ImVec2& p3, int num_segments)\n{\n    ImVec2 p1 = _Path.back();\n    if (num_segments == 0)\n    {\n        IM_ASSERT(_Data->CurveTessellationTol > 0.0f);\n        PathBezierQuadraticCurveToCasteljau(&_Path, p1.x, p1.y, p2.x, p2.y, p3.x, p3.y, _Data->CurveTessellationTol, 0);// Auto-tessellated\n    }\n    else\n    {\n        float t_step = 1.0f / (float)num_segments;\n        for (int i_step = 1; i_step <= num_segments; i_step++)\n            _Path.push_back(ImBezierQuadraticCalc(p1, p2, p3, t_step * i_step));\n    }\n}\n\nstatic inline ImDrawFlags FixRectCornerFlags(ImDrawFlags flags)\n{\n    /*\n    IM_STATIC_ASSERT(ImDrawFlags_RoundCornersTopLeft == (1 << 4));\n#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS\n    // Obsoleted in 1.82 (from February 2021). This code was stripped/simplified and mostly commented in 1.90 (from September 2023)\n    // - Legacy Support for hard coded ~0 (used to be a suggested equivalent to ImDrawCornerFlags_All)\n    if (flags == ~0)                    { return ImDrawFlags_RoundCornersAll; }\n    // - Legacy Support for hard coded 0x01 to 0x0F (matching 15 out of 16 old flags combinations). Read details in older version of this code.\n    if (flags >= 0x01 && flags <= 0x0F) { return (flags << 4); }\n    // We cannot support hard coded 0x00 with 'float rounding > 0.0f' --> replace with ImDrawFlags_RoundCornersNone or use 'float rounding = 0.0f'\n#endif\n    */\n    // If this assert triggers, please update your code replacing hardcoded values with new ImDrawFlags_RoundCorners* values.\n    // Note that ImDrawFlags_Closed (== 0x01) is an invalid flag for AddRect(), AddRectFilled(), PathRect() etc. anyway.\n    // See details in 1.82 Changelog as well as 2021/03/12 and 2023/09/08 entries in \"API BREAKING CHANGES\" section.\n    IM_ASSERT((flags & 0x0F) == 0 && \"Misuse of legacy hardcoded ImDrawCornerFlags values!\");\n\n    if ((flags & ImDrawFlags_RoundCornersMask_) == 0)\n        flags |= ImDrawFlags_RoundCornersAll;\n\n    return flags;\n}\n\nvoid ImDrawList::PathRect(const ImVec2& a, const ImVec2& b, float rounding, ImDrawFlags flags)\n{\n    if (rounding >= 0.5f)\n    {\n        flags = FixRectCornerFlags(flags);\n        rounding = ImMin(rounding, ImFabs(b.x - a.x) * (((flags & ImDrawFlags_RoundCornersTop) == ImDrawFlags_RoundCornersTop) || ((flags & ImDrawFlags_RoundCornersBottom) == ImDrawFlags_RoundCornersBottom) ? 0.5f : 1.0f) - 1.0f);\n        rounding = ImMin(rounding, ImFabs(b.y - a.y) * (((flags & ImDrawFlags_RoundCornersLeft) == ImDrawFlags_RoundCornersLeft) || ((flags & ImDrawFlags_RoundCornersRight) == ImDrawFlags_RoundCornersRight) ? 0.5f : 1.0f) - 1.0f);\n    }\n    if (rounding < 0.5f || (flags & ImDrawFlags_RoundCornersMask_) == ImDrawFlags_RoundCornersNone)\n    {\n        PathLineTo(a);\n        PathLineTo(ImVec2(b.x, a.y));\n        PathLineTo(b);\n        PathLineTo(ImVec2(a.x, b.y));\n    }\n    else\n    {\n        const float rounding_tl = (flags & ImDrawFlags_RoundCornersTopLeft)     ? rounding : 0.0f;\n        const float rounding_tr = (flags & ImDrawFlags_RoundCornersTopRight)    ? rounding : 0.0f;\n        const float rounding_br = (flags & ImDrawFlags_RoundCornersBottomRight) ? rounding : 0.0f;\n        const float rounding_bl = (flags & ImDrawFlags_RoundCornersBottomLeft)  ? rounding : 0.0f;\n        PathArcToFast(ImVec2(a.x + rounding_tl, a.y + rounding_tl), rounding_tl, 6, 9);\n        PathArcToFast(ImVec2(b.x - rounding_tr, a.y + rounding_tr), rounding_tr, 9, 12);\n        PathArcToFast(ImVec2(b.x - rounding_br, b.y - rounding_br), rounding_br, 0, 3);\n        PathArcToFast(ImVec2(a.x + rounding_bl, b.y - rounding_bl), rounding_bl, 3, 6);\n    }\n}\n\nvoid ImDrawList::AddLine(const ImVec2& p1, const ImVec2& p2, ImU32 col, float thickness)\n{\n    if ((col & IM_COL32_A_MASK) == 0)\n        return;\n    PathLineTo(p1 + ImVec2(0.5f, 0.5f));\n    PathLineTo(p2 + ImVec2(0.5f, 0.5f));\n    PathStroke(col, 0, thickness);\n}\n\n// p_min = upper-left, p_max = lower-right\n// Note we don't render 1 pixels sized rectangles properly.\nvoid ImDrawList::AddRect(const ImVec2& p_min, const ImVec2& p_max, ImU32 col, float rounding, ImDrawFlags flags, float thickness)\n{\n    if ((col & IM_COL32_A_MASK) == 0)\n        return;\n    if (Flags & ImDrawListFlags_AntiAliasedLines)\n        PathRect(p_min + ImVec2(0.50f, 0.50f), p_max - ImVec2(0.50f, 0.50f), rounding, flags);\n    else\n        PathRect(p_min + ImVec2(0.50f, 0.50f), p_max - ImVec2(0.49f, 0.49f), rounding, flags); // Better looking lower-right corner and rounded non-AA shapes.\n    PathStroke(col, ImDrawFlags_Closed, thickness);\n}\n\nvoid ImDrawList::AddRectFilled(const ImVec2& p_min, const ImVec2& p_max, ImU32 col, float rounding, ImDrawFlags flags)\n{\n    if ((col & IM_COL32_A_MASK) == 0)\n        return;\n    if (rounding < 0.5f || (flags & ImDrawFlags_RoundCornersMask_) == ImDrawFlags_RoundCornersNone)\n    {\n        PrimReserve(6, 4);\n        PrimRect(p_min, p_max, col);\n    }\n    else\n    {\n        PathRect(p_min, p_max, rounding, flags);\n        PathFillConvex(col);\n    }\n}\n\n// p_min = upper-left, p_max = lower-right\nvoid ImDrawList::AddRectFilledMultiColor(const ImVec2& p_min, const ImVec2& p_max, ImU32 col_upr_left, ImU32 col_upr_right, ImU32 col_bot_right, ImU32 col_bot_left)\n{\n    if (((col_upr_left | col_upr_right | col_bot_right | col_bot_left) & IM_COL32_A_MASK) == 0)\n        return;\n\n    const ImVec2 uv = _Data->TexUvWhitePixel;\n    PrimReserve(6, 4);\n    PrimWriteIdx((ImDrawIdx)(_VtxCurrentIdx)); PrimWriteIdx((ImDrawIdx)(_VtxCurrentIdx + 1)); PrimWriteIdx((ImDrawIdx)(_VtxCurrentIdx + 2));\n    PrimWriteIdx((ImDrawIdx)(_VtxCurrentIdx)); PrimWriteIdx((ImDrawIdx)(_VtxCurrentIdx + 2)); PrimWriteIdx((ImDrawIdx)(_VtxCurrentIdx + 3));\n    PrimWriteVtx(p_min, uv, col_upr_left);\n    PrimWriteVtx(ImVec2(p_max.x, p_min.y), uv, col_upr_right);\n    PrimWriteVtx(p_max, uv, col_bot_right);\n    PrimWriteVtx(ImVec2(p_min.x, p_max.y), uv, col_bot_left);\n}\n\nvoid ImDrawList::AddQuad(const ImVec2& p1, const ImVec2& p2, const ImVec2& p3, const ImVec2& p4, ImU32 col, float thickness)\n{\n    if ((col & IM_COL32_A_MASK) == 0)\n        return;\n\n    PathLineTo(p1);\n    PathLineTo(p2);\n    PathLineTo(p3);\n    PathLineTo(p4);\n    PathStroke(col, ImDrawFlags_Closed, thickness);\n}\n\nvoid ImDrawList::AddQuadFilled(const ImVec2& p1, const ImVec2& p2, const ImVec2& p3, const ImVec2& p4, ImU32 col)\n{\n    if ((col & IM_COL32_A_MASK) == 0)\n        return;\n\n    PathLineTo(p1);\n    PathLineTo(p2);\n    PathLineTo(p3);\n    PathLineTo(p4);\n    PathFillConvex(col);\n}\n\nvoid ImDrawList::AddTriangle(const ImVec2& p1, const ImVec2& p2, const ImVec2& p3, ImU32 col, float thickness)\n{\n    if ((col & IM_COL32_A_MASK) == 0)\n        return;\n\n    PathLineTo(p1);\n    PathLineTo(p2);\n    PathLineTo(p3);\n    PathStroke(col, ImDrawFlags_Closed, thickness);\n}\n\nvoid ImDrawList::AddTriangleFilled(const ImVec2& p1, const ImVec2& p2, const ImVec2& p3, ImU32 col)\n{\n    if ((col & IM_COL32_A_MASK) == 0)\n        return;\n\n    PathLineTo(p1);\n    PathLineTo(p2);\n    PathLineTo(p3);\n    PathFillConvex(col);\n}\n\nvoid ImDrawList::AddCircle(const ImVec2& center, float radius, ImU32 col, int num_segments, float thickness)\n{\n    if ((col & IM_COL32_A_MASK) == 0 || radius < 0.5f)\n        return;\n\n    if (num_segments <= 0)\n    {\n        // Use arc with automatic segment count\n        _PathArcToFastEx(center, radius - 0.5f, 0, IM_DRAWLIST_ARCFAST_SAMPLE_MAX, 0);\n        _Path.Size--;\n    }\n    else\n    {\n        // Explicit segment count (still clamp to avoid drawing insanely tessellated shapes)\n        num_segments = ImClamp(num_segments, 3, IM_DRAWLIST_CIRCLE_AUTO_SEGMENT_MAX);\n\n        // Because we are filling a closed shape we remove 1 from the count of segments/points\n        const float a_max = (IM_PI * 2.0f) * ((float)num_segments - 1.0f) / (float)num_segments;\n        PathArcTo(center, radius - 0.5f, 0.0f, a_max, num_segments - 1);\n    }\n\n    PathStroke(col, ImDrawFlags_Closed, thickness);\n}\n\nvoid ImDrawList::AddCircleFilled(const ImVec2& center, float radius, ImU32 col, int num_segments)\n{\n    if ((col & IM_COL32_A_MASK) == 0 || radius < 0.5f)\n        return;\n\n    if (num_segments <= 0)\n    {\n        // Use arc with automatic segment count\n        _PathArcToFastEx(center, radius, 0, IM_DRAWLIST_ARCFAST_SAMPLE_MAX, 0);\n        _Path.Size--;\n    }\n    else\n    {\n        // Explicit segment count (still clamp to avoid drawing insanely tessellated shapes)\n        num_segments = ImClamp(num_segments, 3, IM_DRAWLIST_CIRCLE_AUTO_SEGMENT_MAX);\n\n        // Because we are filling a closed shape we remove 1 from the count of segments/points\n        const float a_max = (IM_PI * 2.0f) * ((float)num_segments - 1.0f) / (float)num_segments;\n        PathArcTo(center, radius, 0.0f, a_max, num_segments - 1);\n    }\n\n    PathFillConvex(col);\n}\n\n// Guaranteed to honor 'num_segments'\nvoid ImDrawList::AddNgon(const ImVec2& center, float radius, ImU32 col, int num_segments, float thickness)\n{\n    if ((col & IM_COL32_A_MASK) == 0 || num_segments <= 2)\n        return;\n\n    // Because we are filling a closed shape we remove 1 from the count of segments/points\n    const float a_max = (IM_PI * 2.0f) * ((float)num_segments - 1.0f) / (float)num_segments;\n    PathArcTo(center, radius - 0.5f, 0.0f, a_max, num_segments - 1);\n    PathStroke(col, ImDrawFlags_Closed, thickness);\n}\n\n// Guaranteed to honor 'num_segments'\nvoid ImDrawList::AddNgonFilled(const ImVec2& center, float radius, ImU32 col, int num_segments)\n{\n    if ((col & IM_COL32_A_MASK) == 0 || num_segments <= 2)\n        return;\n\n    // Because we are filling a closed shape we remove 1 from the count of segments/points\n    const float a_max = (IM_PI * 2.0f) * ((float)num_segments - 1.0f) / (float)num_segments;\n    PathArcTo(center, radius, 0.0f, a_max, num_segments - 1);\n    PathFillConvex(col);\n}\n\n// Ellipse\nvoid ImDrawList::AddEllipse(const ImVec2& center, const ImVec2& radius, ImU32 col, float rot, int num_segments, float thickness)\n{\n    if ((col & IM_COL32_A_MASK) == 0)\n        return;\n\n    if (num_segments <= 0)\n        num_segments = _CalcCircleAutoSegmentCount(ImMax(radius.x, radius.y)); // A bit pessimistic, maybe there's a better computation to do here.\n\n    // Because we are filling a closed shape we remove 1 from the count of segments/points\n    const float a_max = IM_PI * 2.0f * ((float)num_segments - 1.0f) / (float)num_segments;\n    PathEllipticalArcTo(center, radius, rot, 0.0f, a_max, num_segments - 1);\n    PathStroke(col, true, thickness);\n}\n\nvoid ImDrawList::AddEllipseFilled(const ImVec2& center, const ImVec2& radius, ImU32 col, float rot, int num_segments)\n{\n    if ((col & IM_COL32_A_MASK) == 0)\n        return;\n\n    if (num_segments <= 0)\n        num_segments = _CalcCircleAutoSegmentCount(ImMax(radius.x, radius.y)); // A bit pessimistic, maybe there's a better computation to do here.\n\n    // Because we are filling a closed shape we remove 1 from the count of segments/points\n    const float a_max = IM_PI * 2.0f * ((float)num_segments - 1.0f) / (float)num_segments;\n    PathEllipticalArcTo(center, radius, rot, 0.0f, a_max, num_segments - 1);\n    PathFillConvex(col);\n}\n\n// Cubic Bezier takes 4 controls points\nvoid ImDrawList::AddBezierCubic(const ImVec2& p1, const ImVec2& p2, const ImVec2& p3, const ImVec2& p4, ImU32 col, float thickness, int num_segments)\n{\n    if ((col & IM_COL32_A_MASK) == 0)\n        return;\n\n    PathLineTo(p1);\n    PathBezierCubicCurveTo(p2, p3, p4, num_segments);\n    PathStroke(col, 0, thickness);\n}\n\n// Quadratic Bezier takes 3 controls points\nvoid ImDrawList::AddBezierQuadratic(const ImVec2& p1, const ImVec2& p2, const ImVec2& p3, ImU32 col, float thickness, int num_segments)\n{\n    if ((col & IM_COL32_A_MASK) == 0)\n        return;\n\n    PathLineTo(p1);\n    PathBezierQuadraticCurveTo(p2, p3, num_segments);\n    PathStroke(col, 0, thickness);\n}\n\nvoid ImDrawList::AddText(ImFont* font, float font_size, const ImVec2& pos, ImU32 col, const char* text_begin, const char* text_end, float wrap_width, const ImVec4* cpu_fine_clip_rect)\n{\n    if ((col & IM_COL32_A_MASK) == 0)\n        return;\n\n    // Accept null ranges\n    if (text_begin == text_end || text_begin[0] == 0)\n        return;\n    // No need to strlen() here: font->RenderText() will do it and may early out.\n\n    // Pull default font/size from the shared ImDrawListSharedData instance\n    if (font == NULL)\n        font = _Data->Font;\n    if (font_size == 0.0f)\n        font_size = _Data->FontSize;\n\n    IM_ASSERT(font->ContainerAtlas->TexID == _CmdHeader.TextureId);  // Use high-level ImGui::PushFont() or low-level ImDrawList::PushTextureId() to change font.\n\n    ImVec4 clip_rect = _CmdHeader.ClipRect;\n    if (cpu_fine_clip_rect)\n    {\n        clip_rect.x = ImMax(clip_rect.x, cpu_fine_clip_rect->x);\n        clip_rect.y = ImMax(clip_rect.y, cpu_fine_clip_rect->y);\n        clip_rect.z = ImMin(clip_rect.z, cpu_fine_clip_rect->z);\n        clip_rect.w = ImMin(clip_rect.w, cpu_fine_clip_rect->w);\n    }\n    font->RenderText(this, font_size, pos, col, clip_rect, text_begin, text_end, wrap_width, cpu_fine_clip_rect != NULL);\n}\n\nvoid ImDrawList::AddText(const ImVec2& pos, ImU32 col, const char* text_begin, const char* text_end)\n{\n    AddText(_Data->Font, _Data->FontSize, pos, col, text_begin, text_end);\n}\n\nvoid ImDrawList::AddImage(ImTextureID user_texture_id, const ImVec2& p_min, const ImVec2& p_max, const ImVec2& uv_min, const ImVec2& uv_max, ImU32 col)\n{\n    if ((col & IM_COL32_A_MASK) == 0)\n        return;\n\n    const bool push_texture_id = user_texture_id != _CmdHeader.TextureId;\n    if (push_texture_id)\n        PushTextureID(user_texture_id);\n\n    PrimReserve(6, 4);\n    PrimRectUV(p_min, p_max, uv_min, uv_max, col);\n\n    if (push_texture_id)\n        PopTextureID();\n}\n\nvoid ImDrawList::AddImageQuad(ImTextureID user_texture_id, const ImVec2& p1, const ImVec2& p2, const ImVec2& p3, const ImVec2& p4, const ImVec2& uv1, const ImVec2& uv2, const ImVec2& uv3, const ImVec2& uv4, ImU32 col)\n{\n    if ((col & IM_COL32_A_MASK) == 0)\n        return;\n\n    const bool push_texture_id = user_texture_id != _CmdHeader.TextureId;\n    if (push_texture_id)\n        PushTextureID(user_texture_id);\n\n    PrimReserve(6, 4);\n    PrimQuadUV(p1, p2, p3, p4, uv1, uv2, uv3, uv4, col);\n\n    if (push_texture_id)\n        PopTextureID();\n}\n\nvoid ImDrawList::AddImageRounded(ImTextureID user_texture_id, const ImVec2& p_min, const ImVec2& p_max, const ImVec2& uv_min, const ImVec2& uv_max, ImU32 col, float rounding, ImDrawFlags flags)\n{\n    if ((col & IM_COL32_A_MASK) == 0)\n        return;\n\n    flags = FixRectCornerFlags(flags);\n    if (rounding < 0.5f || (flags & ImDrawFlags_RoundCornersMask_) == ImDrawFlags_RoundCornersNone)\n    {\n        AddImage(user_texture_id, p_min, p_max, uv_min, uv_max, col);\n        return;\n    }\n\n    const bool push_texture_id = user_texture_id != _CmdHeader.TextureId;\n    if (push_texture_id)\n        PushTextureID(user_texture_id);\n\n    int vert_start_idx = VtxBuffer.Size;\n    PathRect(p_min, p_max, rounding, flags);\n    PathFillConvex(col);\n    int vert_end_idx = VtxBuffer.Size;\n    ImGui::ShadeVertsLinearUV(this, vert_start_idx, vert_end_idx, p_min, p_max, uv_min, uv_max, true);\n\n    if (push_texture_id)\n        PopTextureID();\n}\n\n//-----------------------------------------------------------------------------\n// [SECTION] ImTriangulator, ImDrawList concave polygon fill\n//-----------------------------------------------------------------------------\n// Triangulate concave polygons. Based on \"Triangulation by Ear Clipping\" paper, O(N^2) complexity.\n// Reference: https://www.geometrictools.com/Documentation/TriangulationByEarClipping.pdf\n// Provided as a convenience for user but not used by main library.\n//-----------------------------------------------------------------------------\n// - ImTriangulator [Internal]\n// - AddConcavePolyFilled()\n//-----------------------------------------------------------------------------\n\nenum ImTriangulatorNodeType\n{\n    ImTriangulatorNodeType_Convex,\n    ImTriangulatorNodeType_Ear,\n    ImTriangulatorNodeType_Reflex\n};\n\nstruct ImTriangulatorNode\n{\n    ImTriangulatorNodeType  Type;\n    int                     Index;\n    ImVec2                  Pos;\n    ImTriangulatorNode*     Next;\n    ImTriangulatorNode*     Prev;\n\n    void    Unlink()        { Next->Prev = Prev; Prev->Next = Next; }\n};\n\nstruct ImTriangulatorNodeSpan\n{\n    ImTriangulatorNode**    Data = NULL;\n    int                     Size = 0;\n\n    void    push_back(ImTriangulatorNode* node) { Data[Size++] = node; }\n    void    find_erase_unsorted(int idx)        { for (int i = Size - 1; i >= 0; i--) if (Data[i]->Index == idx) { Data[i] = Data[Size - 1]; Size--; return; } }\n};\n\nstruct ImTriangulator\n{\n    static int EstimateTriangleCount(int points_count)      { return (points_count < 3) ? 0 : points_count - 2; }\n    static int EstimateScratchBufferSize(int points_count)  { return sizeof(ImTriangulatorNode) * points_count + sizeof(ImTriangulatorNode*) * points_count * 2; }\n\n    void    Init(const ImVec2* points, int points_count, void* scratch_buffer);\n    void    GetNextTriangle(unsigned int out_triangle[3]);     // Return relative indexes for next triangle\n\n    // Internal functions\n    void    BuildNodes(const ImVec2* points, int points_count);\n    void    BuildReflexes();\n    void    BuildEars();\n    void    FlipNodeList();\n    bool    IsEar(int i0, int i1, int i2, const ImVec2& v0, const ImVec2& v1, const ImVec2& v2) const;\n    void    ReclassifyNode(ImTriangulatorNode* node);\n\n    // Internal members\n    int                     _TrianglesLeft = 0;\n    ImTriangulatorNode*     _Nodes = NULL;\n    ImTriangulatorNodeSpan  _Ears;\n    ImTriangulatorNodeSpan  _Reflexes;\n};\n\n// Distribute storage for nodes, ears and reflexes.\n// FIXME-OPT: if everything is convex, we could report it to caller and let it switch to an convex renderer\n// (this would require first building reflexes to bail to convex if empty, without even building nodes)\nvoid ImTriangulator::Init(const ImVec2* points, int points_count, void* scratch_buffer)\n{\n    IM_ASSERT(scratch_buffer != NULL && points_count >= 3);\n    _TrianglesLeft = EstimateTriangleCount(points_count);\n    _Nodes         = (ImTriangulatorNode*)scratch_buffer;                          // points_count x Node\n    _Ears.Data     = (ImTriangulatorNode**)(_Nodes + points_count);                // points_count x Node*\n    _Reflexes.Data = (ImTriangulatorNode**)(_Nodes + points_count) + points_count; // points_count x Node*\n    BuildNodes(points, points_count);\n    BuildReflexes();\n    BuildEars();\n}\n\nvoid ImTriangulator::BuildNodes(const ImVec2* points, int points_count)\n{\n    for (int i = 0; i < points_count; i++)\n    {\n        _Nodes[i].Type = ImTriangulatorNodeType_Convex;\n        _Nodes[i].Index = i;\n        _Nodes[i].Pos = points[i];\n        _Nodes[i].Next = _Nodes + i + 1;\n        _Nodes[i].Prev = _Nodes + i - 1;\n    }\n    _Nodes[0].Prev = _Nodes + points_count - 1;\n    _Nodes[points_count - 1].Next = _Nodes;\n}\n\nvoid ImTriangulator::BuildReflexes()\n{\n    ImTriangulatorNode* n1 = _Nodes;\n    for (int i = _TrianglesLeft; i >= 0; i--, n1 = n1->Next)\n    {\n        if (ImTriangleIsClockwise(n1->Prev->Pos, n1->Pos, n1->Next->Pos))\n            continue;\n        n1->Type = ImTriangulatorNodeType_Reflex;\n        _Reflexes.push_back(n1);\n    }\n}\n\nvoid ImTriangulator::BuildEars()\n{\n    ImTriangulatorNode* n1 = _Nodes;\n    for (int i = _TrianglesLeft; i >= 0; i--, n1 = n1->Next)\n    {\n        if (n1->Type != ImTriangulatorNodeType_Convex)\n            continue;\n        if (!IsEar(n1->Prev->Index, n1->Index, n1->Next->Index, n1->Prev->Pos, n1->Pos, n1->Next->Pos))\n            continue;\n        n1->Type = ImTriangulatorNodeType_Ear;\n        _Ears.push_back(n1);\n    }\n}\n\nvoid ImTriangulator::GetNextTriangle(unsigned int out_triangle[3])\n{\n    if (_Ears.Size == 0)\n    {\n        FlipNodeList();\n\n        ImTriangulatorNode* node = _Nodes;\n        for (int i = _TrianglesLeft; i >= 0; i--, node = node->Next)\n            node->Type = ImTriangulatorNodeType_Convex;\n        _Reflexes.Size = 0;\n        BuildReflexes();\n        BuildEars();\n\n        // If we still don't have ears, it means geometry is degenerated.\n        if (_Ears.Size == 0)\n        {\n            // Return first triangle available, mimicking the behavior of convex fill.\n            IM_ASSERT(_TrianglesLeft > 0); // Geometry is degenerated\n            _Ears.Data[0] = _Nodes;\n            _Ears.Size    = 1;\n        }\n    }\n\n    ImTriangulatorNode* ear = _Ears.Data[--_Ears.Size];\n    out_triangle[0] = ear->Prev->Index;\n    out_triangle[1] = ear->Index;\n    out_triangle[2] = ear->Next->Index;\n\n    ear->Unlink();\n    if (ear == _Nodes)\n        _Nodes = ear->Next;\n\n    ReclassifyNode(ear->Prev);\n    ReclassifyNode(ear->Next);\n    _TrianglesLeft--;\n}\n\nvoid ImTriangulator::FlipNodeList()\n{\n    ImTriangulatorNode* prev = _Nodes;\n    ImTriangulatorNode* temp = _Nodes;\n    ImTriangulatorNode* current = _Nodes->Next;\n    prev->Next = prev;\n    prev->Prev = prev;\n    while (current != _Nodes)\n    {\n        temp = current->Next;\n\n        current->Next = prev;\n        prev->Prev = current;\n        _Nodes->Next = current;\n        current->Prev = _Nodes;\n\n        prev = current;\n        current = temp;\n    }\n    _Nodes = prev;\n}\n\n// A triangle is an ear is no other vertex is inside it. We can test reflexes vertices only (see reference algorithm)\nbool ImTriangulator::IsEar(int i0, int i1, int i2, const ImVec2& v0, const ImVec2& v1, const ImVec2& v2) const\n{\n    ImTriangulatorNode** p_end = _Reflexes.Data + _Reflexes.Size;\n    for (ImTriangulatorNode** p = _Reflexes.Data; p < p_end; p++)\n    {\n        ImTriangulatorNode* reflex = *p;\n        if (reflex->Index != i0 && reflex->Index != i1 && reflex->Index != i2)\n            if (ImTriangleContainsPoint(v0, v1, v2, reflex->Pos))\n                return false;\n    }\n    return true;\n}\n\nvoid ImTriangulator::ReclassifyNode(ImTriangulatorNode* n1)\n{\n    // Classify node\n    ImTriangulatorNodeType type;\n    const ImTriangulatorNode* n0 = n1->Prev;\n    const ImTriangulatorNode* n2 = n1->Next;\n    if (!ImTriangleIsClockwise(n0->Pos, n1->Pos, n2->Pos))\n        type = ImTriangulatorNodeType_Reflex;\n    else if (IsEar(n0->Index, n1->Index, n2->Index, n0->Pos, n1->Pos, n2->Pos))\n        type = ImTriangulatorNodeType_Ear;\n    else\n        type = ImTriangulatorNodeType_Convex;\n\n    // Update lists when a type changes\n    if (type == n1->Type)\n        return;\n    if (n1->Type == ImTriangulatorNodeType_Reflex)\n        _Reflexes.find_erase_unsorted(n1->Index);\n    else if (n1->Type == ImTriangulatorNodeType_Ear)\n        _Ears.find_erase_unsorted(n1->Index);\n    if (type == ImTriangulatorNodeType_Reflex)\n        _Reflexes.push_back(n1);\n    else if (type == ImTriangulatorNodeType_Ear)\n        _Ears.push_back(n1);\n    n1->Type = type;\n}\n\n// Use ear-clipping algorithm to triangulate a simple polygon (no self-interaction, no holes).\n// (Reminder: we don't perform any coarse clipping/culling in ImDrawList layer!\n// It is up to caller to ensure not making costly calls that will be outside of visible area.\n// As concave fill is noticeably more expensive than other primitives, be mindful of this...\n// Caller can build AABB of points, and avoid filling if 'draw_list->_CmdHeader.ClipRect.Overlays(points_bb) == false')\nvoid ImDrawList::AddConcavePolyFilled(const ImVec2* points, const int points_count, ImU32 col)\n{\n    if (points_count < 3 || (col & IM_COL32_A_MASK) == 0)\n        return;\n\n    const ImVec2 uv = _Data->TexUvWhitePixel;\n    ImTriangulator triangulator;\n    unsigned int triangle[3];\n    if (Flags & ImDrawListFlags_AntiAliasedFill)\n    {\n        // Anti-aliased Fill\n        const float AA_SIZE = _FringeScale;\n        const ImU32 col_trans = col & ~IM_COL32_A_MASK;\n        const int idx_count = (points_count - 2) * 3 + points_count * 6;\n        const int vtx_count = (points_count * 2);\n        PrimReserve(idx_count, vtx_count);\n\n        // Add indexes for fill\n        unsigned int vtx_inner_idx = _VtxCurrentIdx;\n        unsigned int vtx_outer_idx = _VtxCurrentIdx + 1;\n\n        _Data->TempBuffer.reserve_discard((ImTriangulator::EstimateScratchBufferSize(points_count) + sizeof(ImVec2)) / sizeof(ImVec2));\n        triangulator.Init(points, points_count, _Data->TempBuffer.Data);\n        while (triangulator._TrianglesLeft > 0)\n        {\n            triangulator.GetNextTriangle(triangle);\n            _IdxWritePtr[0] = (ImDrawIdx)(vtx_inner_idx + (triangle[0] << 1)); _IdxWritePtr[1] = (ImDrawIdx)(vtx_inner_idx + (triangle[1] << 1)); _IdxWritePtr[2] = (ImDrawIdx)(vtx_inner_idx + (triangle[2] << 1));\n            _IdxWritePtr += 3;\n        }\n\n        // Compute normals\n        _Data->TempBuffer.reserve_discard(points_count);\n        ImVec2* temp_normals = _Data->TempBuffer.Data;\n        for (int i0 = points_count - 1, i1 = 0; i1 < points_count; i0 = i1++)\n        {\n            const ImVec2& p0 = points[i0];\n            const ImVec2& p1 = points[i1];\n            float dx = p1.x - p0.x;\n            float dy = p1.y - p0.y;\n            IM_NORMALIZE2F_OVER_ZERO(dx, dy);\n            temp_normals[i0].x = dy;\n            temp_normals[i0].y = -dx;\n        }\n\n        for (int i0 = points_count - 1, i1 = 0; i1 < points_count; i0 = i1++)\n        {\n            // Average normals\n            const ImVec2& n0 = temp_normals[i0];\n            const ImVec2& n1 = temp_normals[i1];\n            float dm_x = (n0.x + n1.x) * 0.5f;\n            float dm_y = (n0.y + n1.y) * 0.5f;\n            IM_FIXNORMAL2F(dm_x, dm_y);\n            dm_x *= AA_SIZE * 0.5f;\n            dm_y *= AA_SIZE * 0.5f;\n\n            // Add vertices\n            _VtxWritePtr[0].pos.x = (points[i1].x - dm_x); _VtxWritePtr[0].pos.y = (points[i1].y - dm_y); _VtxWritePtr[0].uv = uv; _VtxWritePtr[0].col = col;        // Inner\n            _VtxWritePtr[1].pos.x = (points[i1].x + dm_x); _VtxWritePtr[1].pos.y = (points[i1].y + dm_y); _VtxWritePtr[1].uv = uv; _VtxWritePtr[1].col = col_trans;  // Outer\n            _VtxWritePtr += 2;\n\n            // Add indexes for fringes\n            _IdxWritePtr[0] = (ImDrawIdx)(vtx_inner_idx + (i1 << 1)); _IdxWritePtr[1] = (ImDrawIdx)(vtx_inner_idx + (i0 << 1)); _IdxWritePtr[2] = (ImDrawIdx)(vtx_outer_idx + (i0 << 1));\n            _IdxWritePtr[3] = (ImDrawIdx)(vtx_outer_idx + (i0 << 1)); _IdxWritePtr[4] = (ImDrawIdx)(vtx_outer_idx + (i1 << 1)); _IdxWritePtr[5] = (ImDrawIdx)(vtx_inner_idx + (i1 << 1));\n            _IdxWritePtr += 6;\n        }\n        _VtxCurrentIdx += (ImDrawIdx)vtx_count;\n    }\n    else\n    {\n        // Non Anti-aliased Fill\n        const int idx_count = (points_count - 2) * 3;\n        const int vtx_count = points_count;\n        PrimReserve(idx_count, vtx_count);\n        for (int i = 0; i < vtx_count; i++)\n        {\n            _VtxWritePtr[0].pos = points[i]; _VtxWritePtr[0].uv = uv; _VtxWritePtr[0].col = col;\n            _VtxWritePtr++;\n        }\n        _Data->TempBuffer.reserve_discard((ImTriangulator::EstimateScratchBufferSize(points_count) + sizeof(ImVec2)) / sizeof(ImVec2));\n        triangulator.Init(points, points_count, _Data->TempBuffer.Data);\n        while (triangulator._TrianglesLeft > 0)\n        {\n            triangulator.GetNextTriangle(triangle);\n            _IdxWritePtr[0] = (ImDrawIdx)(_VtxCurrentIdx + triangle[0]); _IdxWritePtr[1] = (ImDrawIdx)(_VtxCurrentIdx + triangle[1]); _IdxWritePtr[2] = (ImDrawIdx)(_VtxCurrentIdx + triangle[2]);\n            _IdxWritePtr += 3;\n        }\n        _VtxCurrentIdx += (ImDrawIdx)vtx_count;\n    }\n}\n\n//-----------------------------------------------------------------------------\n// [SECTION] ImDrawListSplitter\n//-----------------------------------------------------------------------------\n// FIXME: This may be a little confusing, trying to be a little too low-level/optimal instead of just doing vector swap..\n//-----------------------------------------------------------------------------\n\nvoid ImDrawListSplitter::ClearFreeMemory()\n{\n    for (int i = 0; i < _Channels.Size; i++)\n    {\n        if (i == _Current)\n            memset(&_Channels[i], 0, sizeof(_Channels[i]));  // Current channel is a copy of CmdBuffer/IdxBuffer, don't destruct again\n        _Channels[i]._CmdBuffer.clear();\n        _Channels[i]._IdxBuffer.clear();\n    }\n    _Current = 0;\n    _Count = 1;\n    _Channels.clear();\n}\n\nvoid ImDrawListSplitter::Split(ImDrawList* draw_list, int channels_count)\n{\n    IM_UNUSED(draw_list);\n    IM_ASSERT(_Current == 0 && _Count <= 1 && \"Nested channel splitting is not supported. Please use separate instances of ImDrawListSplitter.\");\n    int old_channels_count = _Channels.Size;\n    if (old_channels_count < channels_count)\n    {\n        _Channels.reserve(channels_count); // Avoid over reserving since this is likely to stay stable\n        _Channels.resize(channels_count);\n    }\n    _Count = channels_count;\n\n    // Channels[] (24/32 bytes each) hold storage that we'll swap with draw_list->_CmdBuffer/_IdxBuffer\n    // The content of Channels[0] at this point doesn't matter. We clear it to make state tidy in a debugger but we don't strictly need to.\n    // When we switch to the next channel, we'll copy draw_list->_CmdBuffer/_IdxBuffer into Channels[0] and then Channels[1] into draw_list->CmdBuffer/_IdxBuffer\n    memset(&_Channels[0], 0, sizeof(ImDrawChannel));\n    for (int i = 1; i < channels_count; i++)\n    {\n        if (i >= old_channels_count)\n        {\n            IM_PLACEMENT_NEW(&_Channels[i]) ImDrawChannel();\n        }\n        else\n        {\n            _Channels[i]._CmdBuffer.resize(0);\n            _Channels[i]._IdxBuffer.resize(0);\n        }\n    }\n}\n\nvoid ImDrawListSplitter::Merge(ImDrawList* draw_list)\n{\n    // Note that we never use or rely on _Channels.Size because it is merely a buffer that we never shrink back to 0 to keep all sub-buffers ready for use.\n    if (_Count <= 1)\n        return;\n\n    SetCurrentChannel(draw_list, 0);\n    draw_list->_PopUnusedDrawCmd();\n\n    // Calculate our final buffer sizes. Also fix the incorrect IdxOffset values in each command.\n    int new_cmd_buffer_count = 0;\n    int new_idx_buffer_count = 0;\n    ImDrawCmd* last_cmd = (_Count > 0 && draw_list->CmdBuffer.Size > 0) ? &draw_list->CmdBuffer.back() : NULL;\n    int idx_offset = last_cmd ? last_cmd->IdxOffset + last_cmd->ElemCount : 0;\n    for (int i = 1; i < _Count; i++)\n    {\n        ImDrawChannel& ch = _Channels[i];\n        if (ch._CmdBuffer.Size > 0 && ch._CmdBuffer.back().ElemCount == 0 && ch._CmdBuffer.back().UserCallback == NULL) // Equivalent of PopUnusedDrawCmd()\n            ch._CmdBuffer.pop_back();\n\n        if (ch._CmdBuffer.Size > 0 && last_cmd != NULL)\n        {\n            // Do not include ImDrawCmd_AreSequentialIdxOffset() in the compare as we rebuild IdxOffset values ourselves.\n            // Manipulating IdxOffset (e.g. by reordering draw commands like done by RenderDimmedBackgroundBehindWindow()) is not supported within a splitter.\n            ImDrawCmd* next_cmd = &ch._CmdBuffer[0];\n            if (ImDrawCmd_HeaderCompare(last_cmd, next_cmd) == 0 && last_cmd->UserCallback == NULL && next_cmd->UserCallback == NULL)\n            {\n                // Merge previous channel last draw command with current channel first draw command if matching.\n                last_cmd->ElemCount += next_cmd->ElemCount;\n                idx_offset += next_cmd->ElemCount;\n                ch._CmdBuffer.erase(ch._CmdBuffer.Data); // FIXME-OPT: Improve for multiple merges.\n            }\n        }\n        if (ch._CmdBuffer.Size > 0)\n            last_cmd = &ch._CmdBuffer.back();\n        new_cmd_buffer_count += ch._CmdBuffer.Size;\n        new_idx_buffer_count += ch._IdxBuffer.Size;\n        for (int cmd_n = 0; cmd_n < ch._CmdBuffer.Size; cmd_n++)\n        {\n            ch._CmdBuffer.Data[cmd_n].IdxOffset = idx_offset;\n            idx_offset += ch._CmdBuffer.Data[cmd_n].ElemCount;\n        }\n    }\n    draw_list->CmdBuffer.resize(draw_list->CmdBuffer.Size + new_cmd_buffer_count);\n    draw_list->IdxBuffer.resize(draw_list->IdxBuffer.Size + new_idx_buffer_count);\n\n    // Write commands and indices in order (they are fairly small structures, we don't copy vertices only indices)\n    ImDrawCmd* cmd_write = draw_list->CmdBuffer.Data + draw_list->CmdBuffer.Size - new_cmd_buffer_count;\n    ImDrawIdx* idx_write = draw_list->IdxBuffer.Data + draw_list->IdxBuffer.Size - new_idx_buffer_count;\n    for (int i = 1; i < _Count; i++)\n    {\n        ImDrawChannel& ch = _Channels[i];\n        if (int sz = ch._CmdBuffer.Size) { memcpy(cmd_write, ch._CmdBuffer.Data, sz * sizeof(ImDrawCmd)); cmd_write += sz; }\n        if (int sz = ch._IdxBuffer.Size) { memcpy(idx_write, ch._IdxBuffer.Data, sz * sizeof(ImDrawIdx)); idx_write += sz; }\n    }\n    draw_list->_IdxWritePtr = idx_write;\n\n    // Ensure there's always a non-callback draw command trailing the command-buffer\n    if (draw_list->CmdBuffer.Size == 0 || draw_list->CmdBuffer.back().UserCallback != NULL)\n        draw_list->AddDrawCmd();\n\n    // If current command is used with different settings we need to add a new command\n    ImDrawCmd* curr_cmd = &draw_list->CmdBuffer.Data[draw_list->CmdBuffer.Size - 1];\n    if (curr_cmd->ElemCount == 0)\n        ImDrawCmd_HeaderCopy(curr_cmd, &draw_list->_CmdHeader); // Copy ClipRect, TextureId, VtxOffset\n    else if (ImDrawCmd_HeaderCompare(curr_cmd, &draw_list->_CmdHeader) != 0)\n        draw_list->AddDrawCmd();\n\n    _Count = 1;\n}\n\nvoid ImDrawListSplitter::SetCurrentChannel(ImDrawList* draw_list, int idx)\n{\n    IM_ASSERT(idx >= 0 && idx < _Count);\n    if (_Current == idx)\n        return;\n\n    // Overwrite ImVector (12/16 bytes), four times. This is merely a silly optimization instead of doing .swap()\n    memcpy(&_Channels.Data[_Current]._CmdBuffer, &draw_list->CmdBuffer, sizeof(draw_list->CmdBuffer));\n    memcpy(&_Channels.Data[_Current]._IdxBuffer, &draw_list->IdxBuffer, sizeof(draw_list->IdxBuffer));\n    _Current = idx;\n    memcpy(&draw_list->CmdBuffer, &_Channels.Data[idx]._CmdBuffer, sizeof(draw_list->CmdBuffer));\n    memcpy(&draw_list->IdxBuffer, &_Channels.Data[idx]._IdxBuffer, sizeof(draw_list->IdxBuffer));\n    draw_list->_IdxWritePtr = draw_list->IdxBuffer.Data + draw_list->IdxBuffer.Size;\n\n    // If current command is used with different settings we need to add a new command\n    ImDrawCmd* curr_cmd = (draw_list->CmdBuffer.Size == 0) ? NULL : &draw_list->CmdBuffer.Data[draw_list->CmdBuffer.Size - 1];\n    if (curr_cmd == NULL)\n        draw_list->AddDrawCmd();\n    else if (curr_cmd->ElemCount == 0)\n        ImDrawCmd_HeaderCopy(curr_cmd, &draw_list->_CmdHeader); // Copy ClipRect, TextureId, VtxOffset\n    else if (ImDrawCmd_HeaderCompare(curr_cmd, &draw_list->_CmdHeader) != 0)\n        draw_list->AddDrawCmd();\n}\n\n//-----------------------------------------------------------------------------\n// [SECTION] ImDrawData\n//-----------------------------------------------------------------------------\n\nvoid ImDrawData::Clear()\n{\n    Valid = false;\n    CmdListsCount = TotalIdxCount = TotalVtxCount = 0;\n    CmdLists.resize(0); // The ImDrawList are NOT owned by ImDrawData but e.g. by ImGuiContext, so we don't clear them.\n    DisplayPos = DisplaySize = FramebufferScale = ImVec2(0.0f, 0.0f);\n    OwnerViewport = NULL;\n}\n\n// Important: 'out_list' is generally going to be draw_data->CmdLists, but may be another temporary list\n// as long at it is expected that the result will be later merged into draw_data->CmdLists[].\nvoid ImGui::AddDrawListToDrawDataEx(ImDrawData* draw_data, ImVector<ImDrawList*>* out_list, ImDrawList* draw_list)\n{\n    if (draw_list->CmdBuffer.Size == 0)\n        return;\n    if (draw_list->CmdBuffer.Size == 1 && draw_list->CmdBuffer[0].ElemCount == 0 && draw_list->CmdBuffer[0].UserCallback == NULL)\n        return;\n\n    // Draw list sanity check. Detect mismatch between PrimReserve() calls and incrementing _VtxCurrentIdx, _VtxWritePtr etc.\n    // May trigger for you if you are using PrimXXX functions incorrectly.\n    IM_ASSERT(draw_list->VtxBuffer.Size == 0 || draw_list->_VtxWritePtr == draw_list->VtxBuffer.Data + draw_list->VtxBuffer.Size);\n    IM_ASSERT(draw_list->IdxBuffer.Size == 0 || draw_list->_IdxWritePtr == draw_list->IdxBuffer.Data + draw_list->IdxBuffer.Size);\n    if (!(draw_list->Flags & ImDrawListFlags_AllowVtxOffset))\n        IM_ASSERT((int)draw_list->_VtxCurrentIdx == draw_list->VtxBuffer.Size);\n\n    // Check that draw_list doesn't use more vertices than indexable (default ImDrawIdx = unsigned short = 2 bytes = 64K vertices per ImDrawList = per window)\n    // If this assert triggers because you are drawing lots of stuff manually:\n    // - First, make sure you are coarse clipping yourself and not trying to draw many things outside visible bounds.\n    //   Be mindful that the lower-level ImDrawList API doesn't filter vertices. Use the Metrics/Debugger window to inspect draw list contents.\n    // - If you want large meshes with more than 64K vertices, you can either:\n    //   (A) Handle the ImDrawCmd::VtxOffset value in your renderer backend, and set 'io.BackendFlags |= ImGuiBackendFlags_RendererHasVtxOffset'.\n    //       Most example backends already support this from 1.71. Pre-1.71 backends won't.\n    //       Some graphics API such as GL ES 1/2 don't have a way to offset the starting vertex so it is not supported for them.\n    //   (B) Or handle 32-bit indices in your renderer backend, and uncomment '#define ImDrawIdx unsigned int' line in imconfig.h.\n    //       Most example backends already support this. For example, the OpenGL example code detect index size at compile-time:\n    //         glDrawElements(GL_TRIANGLES, (GLsizei)pcmd->ElemCount, sizeof(ImDrawIdx) == 2 ? GL_UNSIGNED_SHORT : GL_UNSIGNED_INT, idx_buffer_offset);\n    //       Your own engine or render API may use different parameters or function calls to specify index sizes.\n    //       2 and 4 bytes indices are generally supported by most graphics API.\n    // - If for some reason neither of those solutions works for you, a workaround is to call BeginChild()/EndChild() before reaching\n    //   the 64K limit to split your draw commands in multiple draw lists.\n    if (sizeof(ImDrawIdx) == 2)\n        IM_ASSERT(draw_list->_VtxCurrentIdx < (1 << 16) && \"Too many vertices in ImDrawList using 16-bit indices. Read comment above\");\n\n    // Resolve callback data pointers\n    if (draw_list->_CallbacksDataBuf.Size > 0)\n        for (ImDrawCmd& cmd : draw_list->CmdBuffer)\n            if (cmd.UserCallback != NULL && cmd.UserCallbackDataOffset != -1 && cmd.UserCallbackDataSize > 0)\n                cmd.UserCallbackData = draw_list->_CallbacksDataBuf.Data + cmd.UserCallbackDataOffset;\n\n    // Add to output list + records state in ImDrawData\n    out_list->push_back(draw_list);\n    draw_data->CmdListsCount++;\n    draw_data->TotalVtxCount += draw_list->VtxBuffer.Size;\n    draw_data->TotalIdxCount += draw_list->IdxBuffer.Size;\n}\n\nvoid ImDrawData::AddDrawList(ImDrawList* draw_list)\n{\n    IM_ASSERT(CmdLists.Size == CmdListsCount);\n    draw_list->_PopUnusedDrawCmd();\n    ImGui::AddDrawListToDrawDataEx(this, &CmdLists, draw_list);\n}\n\n// For backward compatibility: convert all buffers from indexed to de-indexed, in case you cannot render indexed. Note: this is slow and most likely a waste of resources. Always prefer indexed rendering!\nvoid ImDrawData::DeIndexAllBuffers()\n{\n    ImVector<ImDrawVert> new_vtx_buffer;\n    TotalVtxCount = TotalIdxCount = 0;\n    for (int i = 0; i < CmdListsCount; i++)\n    {\n        ImDrawList* cmd_list = CmdLists[i];\n        if (cmd_list->IdxBuffer.empty())\n            continue;\n        new_vtx_buffer.resize(cmd_list->IdxBuffer.Size);\n        for (int j = 0; j < cmd_list->IdxBuffer.Size; j++)\n            new_vtx_buffer[j] = cmd_list->VtxBuffer[cmd_list->IdxBuffer[j]];\n        cmd_list->VtxBuffer.swap(new_vtx_buffer);\n        cmd_list->IdxBuffer.resize(0);\n        TotalVtxCount += cmd_list->VtxBuffer.Size;\n    }\n}\n\n// Helper to scale the ClipRect field of each ImDrawCmd.\n// Use if your final output buffer is at a different scale than draw_data->DisplaySize,\n// or if there is a difference between your window resolution and framebuffer resolution.\nvoid ImDrawData::ScaleClipRects(const ImVec2& fb_scale)\n{\n    for (ImDrawList* draw_list : CmdLists)\n        for (ImDrawCmd& cmd : draw_list->CmdBuffer)\n            cmd.ClipRect = ImVec4(cmd.ClipRect.x * fb_scale.x, cmd.ClipRect.y * fb_scale.y, cmd.ClipRect.z * fb_scale.x, cmd.ClipRect.w * fb_scale.y);\n}\n\n//-----------------------------------------------------------------------------\n// [SECTION] Helpers ShadeVertsXXX functions\n//-----------------------------------------------------------------------------\n\n// Generic linear color gradient, write to RGB fields, leave A untouched.\nvoid ImGui::ShadeVertsLinearColorGradientKeepAlpha(ImDrawList* draw_list, int vert_start_idx, int vert_end_idx, ImVec2 gradient_p0, ImVec2 gradient_p1, ImU32 col0, ImU32 col1)\n{\n    ImVec2 gradient_extent = gradient_p1 - gradient_p0;\n    float gradient_inv_length2 = 1.0f / ImLengthSqr(gradient_extent);\n    ImDrawVert* vert_start = draw_list->VtxBuffer.Data + vert_start_idx;\n    ImDrawVert* vert_end = draw_list->VtxBuffer.Data + vert_end_idx;\n    const int col0_r = (int)(col0 >> IM_COL32_R_SHIFT) & 0xFF;\n    const int col0_g = (int)(col0 >> IM_COL32_G_SHIFT) & 0xFF;\n    const int col0_b = (int)(col0 >> IM_COL32_B_SHIFT) & 0xFF;\n    const int col_delta_r = ((int)(col1 >> IM_COL32_R_SHIFT) & 0xFF) - col0_r;\n    const int col_delta_g = ((int)(col1 >> IM_COL32_G_SHIFT) & 0xFF) - col0_g;\n    const int col_delta_b = ((int)(col1 >> IM_COL32_B_SHIFT) & 0xFF) - col0_b;\n    for (ImDrawVert* vert = vert_start; vert < vert_end; vert++)\n    {\n        float d = ImDot(vert->pos - gradient_p0, gradient_extent);\n        float t = ImClamp(d * gradient_inv_length2, 0.0f, 1.0f);\n        int r = (int)(col0_r + col_delta_r * t);\n        int g = (int)(col0_g + col_delta_g * t);\n        int b = (int)(col0_b + col_delta_b * t);\n        vert->col = (r << IM_COL32_R_SHIFT) | (g << IM_COL32_G_SHIFT) | (b << IM_COL32_B_SHIFT) | (vert->col & IM_COL32_A_MASK);\n    }\n}\n\n// Distribute UV over (a, b) rectangle\nvoid ImGui::ShadeVertsLinearUV(ImDrawList* draw_list, int vert_start_idx, int vert_end_idx, const ImVec2& a, const ImVec2& b, const ImVec2& uv_a, const ImVec2& uv_b, bool clamp)\n{\n    const ImVec2 size = b - a;\n    const ImVec2 uv_size = uv_b - uv_a;\n    const ImVec2 scale = ImVec2(\n        size.x != 0.0f ? (uv_size.x / size.x) : 0.0f,\n        size.y != 0.0f ? (uv_size.y / size.y) : 0.0f);\n\n    ImDrawVert* vert_start = draw_list->VtxBuffer.Data + vert_start_idx;\n    ImDrawVert* vert_end = draw_list->VtxBuffer.Data + vert_end_idx;\n    if (clamp)\n    {\n        const ImVec2 min = ImMin(uv_a, uv_b);\n        const ImVec2 max = ImMax(uv_a, uv_b);\n        for (ImDrawVert* vertex = vert_start; vertex < vert_end; ++vertex)\n            vertex->uv = ImClamp(uv_a + ImMul(ImVec2(vertex->pos.x, vertex->pos.y) - a, scale), min, max);\n    }\n    else\n    {\n        for (ImDrawVert* vertex = vert_start; vertex < vert_end; ++vertex)\n            vertex->uv = uv_a + ImMul(ImVec2(vertex->pos.x, vertex->pos.y) - a, scale);\n    }\n}\n\nvoid ImGui::ShadeVertsTransformPos(ImDrawList* draw_list, int vert_start_idx, int vert_end_idx, const ImVec2& pivot_in, float cos_a, float sin_a, const ImVec2& pivot_out)\n{\n    ImDrawVert* vert_start = draw_list->VtxBuffer.Data + vert_start_idx;\n    ImDrawVert* vert_end = draw_list->VtxBuffer.Data + vert_end_idx;\n    for (ImDrawVert* vertex = vert_start; vertex < vert_end; ++vertex)\n        vertex->pos = ImRotate(vertex->pos- pivot_in, cos_a, sin_a) + pivot_out;\n}\n\n//-----------------------------------------------------------------------------\n// [SECTION] ImFontConfig\n//-----------------------------------------------------------------------------\n\nImFontConfig::ImFontConfig()\n{\n    memset(this, 0, sizeof(*this));\n    FontDataOwnedByAtlas = true;\n    OversampleH = 0; // Auto == 1 or 2 depending on size\n    OversampleV = 0; // Auto == 1\n    GlyphMaxAdvanceX = FLT_MAX;\n    RasterizerMultiply = 1.0f;\n    RasterizerDensity = 1.0f;\n    EllipsisChar = 0;\n}\n\n//-----------------------------------------------------------------------------\n// [SECTION] ImFontAtlas\n//-----------------------------------------------------------------------------\n// - Default texture data encoded in ASCII\n// - ImFontAtlas::ClearInputData()\n// - ImFontAtlas::ClearTexData()\n// - ImFontAtlas::ClearFonts()\n// - ImFontAtlas::Clear()\n// - ImFontAtlas::GetTexDataAsAlpha8()\n// - ImFontAtlas::GetTexDataAsRGBA32()\n// - ImFontAtlas::AddFont()\n// - ImFontAtlas::AddFontDefault()\n// - ImFontAtlas::AddFontFromFileTTF()\n// - ImFontAtlas::AddFontFromMemoryTTF()\n// - ImFontAtlas::AddFontFromMemoryCompressedTTF()\n// - ImFontAtlas::AddFontFromMemoryCompressedBase85TTF()\n// - ImFontAtlas::AddCustomRectRegular()\n// - ImFontAtlas::AddCustomRectFontGlyph()\n// - ImFontAtlas::CalcCustomRectUV()\n// - ImFontAtlasGetMouseCursorTexData()\n// - ImFontAtlas::Build()\n// - ImFontAtlasBuildMultiplyCalcLookupTable()\n// - ImFontAtlasBuildMultiplyRectAlpha8()\n// - ImFontAtlasBuildWithStbTruetype()\n// - ImFontAtlasGetBuilderForStbTruetype()\n// - ImFontAtlasUpdateSourcesPointers()\n// - ImFontAtlasBuildSetupFont()\n// - ImFontAtlasBuildPackCustomRects()\n// - ImFontAtlasBuildRender8bppRectFromString()\n// - ImFontAtlasBuildRender32bppRectFromString()\n// - ImFontAtlasBuildRenderDefaultTexData()\n// - ImFontAtlasBuildRenderLinesTexData()\n// - ImFontAtlasBuildInit()\n// - ImFontAtlasBuildFinish()\n//-----------------------------------------------------------------------------\n\n// A work of art lies ahead! (. = white layer, X = black layer, others are blank)\n// The 2x2 white texels on the top left are the ones we'll use everywhere in Dear ImGui to render filled shapes.\n// (This is used when io.MouseDrawCursor = true)\nconst int FONT_ATLAS_DEFAULT_TEX_DATA_W = 122; // Actual texture will be 2 times that + 1 spacing.\nconst int FONT_ATLAS_DEFAULT_TEX_DATA_H = 27;\nstatic const char FONT_ATLAS_DEFAULT_TEX_DATA_PIXELS[FONT_ATLAS_DEFAULT_TEX_DATA_W * FONT_ATLAS_DEFAULT_TEX_DATA_H + 1] =\n{\n    \"..-         -XXXXXXX-    X    -           X           -XXXXXXX          -          XXXXXXX-     XX          - XX       XX \"\n    \"..-         -X.....X-   X.X   -          X.X          -X.....X          -          X.....X-    X..X         -X..X     X..X\"\n    \"---         -XXX.XXX-  X...X  -         X...X         -X....X           -           X....X-    X..X         -X...X   X...X\"\n    \"X           -  X.X  - X.....X -        X.....X        -X...X            -            X...X-    X..X         - X...X X...X \"\n    \"XX          -  X.X  -X.......X-       X.......X       -X..X.X           -           X.X..X-    X..X         -  X...X...X  \"\n    \"X.X         -  X.X  -XXXX.XXXX-       XXXX.XXXX       -X.X X.X          -          X.X X.X-    X..XXX       -   X.....X   \"\n    \"X..X        -  X.X  -   X.X   -          X.X          -XX   X.X         -         X.X   XX-    X..X..XXX    -    X...X    \"\n    \"X...X       -  X.X  -   X.X   -    XX    X.X    XX    -      X.X        -        X.X      -    X..X..X..XX  -     X.X     \"\n    \"X....X      -  X.X  -   X.X   -   X.X    X.X    X.X   -       X.X       -       X.X       -    X..X..X..X.X -    X...X    \"\n    \"X.....X     -  X.X  -   X.X   -  X..X    X.X    X..X  -        X.X      -      X.X        -XXX X..X..X..X..X-   X.....X   \"\n    \"X......X    -  X.X  -   X.X   - X...XXXXXX.XXXXXX...X -         X.X   XX-XX   X.X         -X..XX........X..X-  X...X...X  \"\n    \"X.......X   -  X.X  -   X.X   -X.....................X-          X.X X.X-X.X X.X          -X...X...........X- X...X X...X \"\n    \"X........X  -  X.X  -   X.X   - X...XXXXXX.XXXXXX...X -           X.X..X-X..X.X           - X..............X-X...X   X...X\"\n    \"X.........X -XXX.XXX-   X.X   -  X..X    X.X    X..X  -            X...X-X...X            -  X.............X-X..X     X..X\"\n    \"X..........X-X.....X-   X.X   -   X.X    X.X    X.X   -           X....X-X....X           -  X.............X- XX       XX \"\n    \"X......XXXXX-XXXXXXX-   X.X   -    XX    X.X    XX    -          X.....X-X.....X          -   X............X--------------\"\n    \"X...X..X    ---------   X.X   -          X.X          -          XXXXXXX-XXXXXXX          -   X...........X -             \"\n    \"X..X X..X   -       -XXXX.XXXX-       XXXX.XXXX       -------------------------------------    X..........X -             \"\n    \"X.X  X..X   -       -X.......X-       X.......X       -    XX           XX    -           -    X..........X -             \"\n    \"XX    X..X  -       - X.....X -        X.....X        -   X.X           X.X   -           -     X........X  -             \"\n    \"      X..X  -       -  X...X  -         X...X         -  X..X           X..X  -           -     X........X  -             \"\n    \"       XX   -       -   X.X   -          X.X          - X...XXXXXXXXXXXXX...X -           -     XXXXXXXXXX  -             \"\n    \"-------------       -    X    -           X           -X.....................X-           -------------------             \"\n    \"                    ----------------------------------- X...XXXXXXXXXXXXX...X -                                           \"\n    \"                                                      -  X..X           X..X  -                                           \"\n    \"                                                      -   X.X           X.X   -                                           \"\n    \"                                                      -    XX           XX    -                                           \"\n};\n\nstatic const ImVec2 FONT_ATLAS_DEFAULT_TEX_CURSOR_DATA[ImGuiMouseCursor_COUNT][3] =\n{\n    // Pos ........ Size ......... Offset ......\n    { ImVec2( 0,3), ImVec2(12,19), ImVec2( 0, 0) }, // ImGuiMouseCursor_Arrow\n    { ImVec2(13,0), ImVec2( 7,16), ImVec2( 1, 8) }, // ImGuiMouseCursor_TextInput\n    { ImVec2(31,0), ImVec2(23,23), ImVec2(11,11) }, // ImGuiMouseCursor_ResizeAll\n    { ImVec2(21,0), ImVec2( 9,23), ImVec2( 4,11) }, // ImGuiMouseCursor_ResizeNS\n    { ImVec2(55,18),ImVec2(23, 9), ImVec2(11, 4) }, // ImGuiMouseCursor_ResizeEW\n    { ImVec2(73,0), ImVec2(17,17), ImVec2( 8, 8) }, // ImGuiMouseCursor_ResizeNESW\n    { ImVec2(55,0), ImVec2(17,17), ImVec2( 8, 8) }, // ImGuiMouseCursor_ResizeNWSE\n    { ImVec2(91,0), ImVec2(17,22), ImVec2( 5, 0) }, // ImGuiMouseCursor_Hand\n    { ImVec2(0,3),  ImVec2(12,19), ImVec2(0, 0) },  // ImGuiMouseCursor_Wait       // Arrow + custom code in ImGui::RenderMouseCursor()\n    { ImVec2(0,3),  ImVec2(12,19), ImVec2(0, 0) },  // ImGuiMouseCursor_Progress   // Arrow + custom code in ImGui::RenderMouseCursor()\n    { ImVec2(109,0),ImVec2(13,15), ImVec2( 6, 7) }, // ImGuiMouseCursor_NotAllowed\n};\n\nImFontAtlas::ImFontAtlas()\n{\n    memset(this, 0, sizeof(*this));\n    TexGlyphPadding = 1;\n    PackIdMouseCursors = PackIdLines = -1;\n}\n\nImFontAtlas::~ImFontAtlas()\n{\n    IM_ASSERT(!Locked && \"Cannot modify a locked ImFontAtlas between NewFrame() and EndFrame/Render()!\");\n    Clear();\n}\n\nvoid    ImFontAtlas::ClearInputData()\n{\n    IM_ASSERT(!Locked && \"Cannot modify a locked ImFontAtlas between NewFrame() and EndFrame/Render()!\");\n    for (ImFontConfig& font_cfg : Sources)\n        if (font_cfg.FontData && font_cfg.FontDataOwnedByAtlas)\n        {\n            IM_FREE(font_cfg.FontData);\n            font_cfg.FontData = NULL;\n        }\n\n    // When clearing this we lose access to the font name and other information used to build the font.\n    for (ImFont* font : Fonts)\n        if (font->Sources >= Sources.Data && font->Sources < Sources.Data + Sources.Size)\n        {\n            font->Sources = NULL;\n            font->SourcesCount = 0;\n        }\n    Sources.clear();\n    CustomRects.clear();\n    PackIdMouseCursors = PackIdLines = -1;\n    // Important: we leave TexReady untouched\n}\n\nvoid    ImFontAtlas::ClearTexData()\n{\n    IM_ASSERT(!Locked && \"Cannot modify a locked ImFontAtlas between NewFrame() and EndFrame/Render()!\");\n    if (TexPixelsAlpha8)\n        IM_FREE(TexPixelsAlpha8);\n    if (TexPixelsRGBA32)\n        IM_FREE(TexPixelsRGBA32);\n    TexPixelsAlpha8 = NULL;\n    TexPixelsRGBA32 = NULL;\n    TexPixelsUseColors = false;\n    // Important: we leave TexReady untouched\n}\n\nvoid    ImFontAtlas::ClearFonts()\n{\n    IM_ASSERT(!Locked && \"Cannot modify a locked ImFontAtlas between NewFrame() and EndFrame/Render()!\");\n    ClearInputData();\n    Fonts.clear_delete();\n    TexReady = false;\n}\n\nvoid    ImFontAtlas::Clear()\n{\n    ClearInputData();\n    ClearTexData();\n    ClearFonts();\n}\n\nvoid    ImFontAtlas::GetTexDataAsAlpha8(unsigned char** out_pixels, int* out_width, int* out_height, int* out_bytes_per_pixel)\n{\n    // Build atlas on demand\n    if (TexPixelsAlpha8 == NULL)\n        Build();\n\n    *out_pixels = TexPixelsAlpha8;\n    if (out_width) *out_width = TexWidth;\n    if (out_height) *out_height = TexHeight;\n    if (out_bytes_per_pixel) *out_bytes_per_pixel = 1;\n}\n\nvoid    ImFontAtlas::GetTexDataAsRGBA32(unsigned char** out_pixels, int* out_width, int* out_height, int* out_bytes_per_pixel)\n{\n    // Convert to RGBA32 format on demand\n    // Although it is likely to be the most commonly used format, our font rendering is 1 channel / 8 bpp\n    if (!TexPixelsRGBA32)\n    {\n        unsigned char* pixels = NULL;\n        GetTexDataAsAlpha8(&pixels, NULL, NULL);\n        if (pixels)\n        {\n            TexPixelsRGBA32 = (unsigned int*)IM_ALLOC((size_t)TexWidth * (size_t)TexHeight * 4);\n            const unsigned char* src = pixels;\n            unsigned int* dst = TexPixelsRGBA32;\n            for (int n = TexWidth * TexHeight; n > 0; n--)\n                *dst++ = IM_COL32(255, 255, 255, (unsigned int)(*src++));\n        }\n    }\n\n    *out_pixels = (unsigned char*)TexPixelsRGBA32;\n    if (out_width) *out_width = TexWidth;\n    if (out_height) *out_height = TexHeight;\n    if (out_bytes_per_pixel) *out_bytes_per_pixel = 4;\n}\n\nImFont* ImFontAtlas::AddFont(const ImFontConfig* font_cfg)\n{\n    IM_ASSERT(!Locked && \"Cannot modify a locked ImFontAtlas between NewFrame() and EndFrame/Render()!\");\n    IM_ASSERT(font_cfg->FontData != NULL && font_cfg->FontDataSize > 0);\n    IM_ASSERT(font_cfg->SizePixels > 0.0f && \"Is ImFontConfig struct correctly initialized?\");\n    IM_ASSERT(font_cfg->RasterizerDensity > 0.0f && \"Is ImFontConfig struct correctly initialized?\");\n\n    // Create new font\n    if (!font_cfg->MergeMode)\n        Fonts.push_back(IM_NEW(ImFont));\n    else\n        IM_ASSERT(Fonts.Size > 0 && \"Cannot use MergeMode for the first font\"); // When using MergeMode make sure that a font has already been added before. You can use ImGui::GetIO().Fonts->AddFontDefault() to add the default imgui font.\n\n    Sources.push_back(*font_cfg);\n    ImFontConfig& new_font_cfg = Sources.back();\n    if (new_font_cfg.DstFont == NULL)\n        new_font_cfg.DstFont = Fonts.back();\n    if (!new_font_cfg.FontDataOwnedByAtlas)\n    {\n        new_font_cfg.FontData = IM_ALLOC(new_font_cfg.FontDataSize);\n        new_font_cfg.FontDataOwnedByAtlas = true;\n        memcpy(new_font_cfg.FontData, font_cfg->FontData, (size_t)new_font_cfg.FontDataSize);\n    }\n\n    // Round font size\n    // - We started rounding in 1.90 WIP (18991) as our layout system currently doesn't support non-rounded font size well yet.\n    // - Note that using io.FontGlobalScale or SetWindowFontScale(), with are legacy-ish, partially supported features, can still lead to unrounded sizes.\n    // - We may support it better later and remove this rounding.\n    new_font_cfg.SizePixels = ImTrunc(new_font_cfg.SizePixels);\n\n    // Pointers to Sources data are otherwise dangling\n    ImFontAtlasUpdateSourcesPointers(this);\n\n    // Invalidate texture\n    TexReady = false;\n    ClearTexData();\n    return new_font_cfg.DstFont;\n}\n\n// Default font TTF is compressed with stb_compress then base85 encoded (see misc/fonts/binary_to_compressed_c.cpp for encoder)\nstatic unsigned int stb_decompress_length(const unsigned char* input);\nstatic unsigned int stb_decompress(unsigned char* output, const unsigned char* input, unsigned int length);\nstatic unsigned int Decode85Byte(char c)                                    { return c >= '\\\\' ? c-36 : c-35; }\nstatic void         Decode85(const unsigned char* src, unsigned char* dst)\n{\n    while (*src)\n    {\n        unsigned int tmp = Decode85Byte(src[0]) + 85 * (Decode85Byte(src[1]) + 85 * (Decode85Byte(src[2]) + 85 * (Decode85Byte(src[3]) + 85 * Decode85Byte(src[4]))));\n        dst[0] = ((tmp >> 0) & 0xFF); dst[1] = ((tmp >> 8) & 0xFF); dst[2] = ((tmp >> 16) & 0xFF); dst[3] = ((tmp >> 24) & 0xFF);   // We can't assume little-endianness.\n        src += 5;\n        dst += 4;\n    }\n}\n#ifndef IMGUI_DISABLE_DEFAULT_FONT\nstatic const char* GetDefaultCompressedFontDataTTF(int* out_size);\n#endif\n\n// Load embedded ProggyClean.ttf at size 13, disable oversampling\nImFont* ImFontAtlas::AddFontDefault(const ImFontConfig* font_cfg_template)\n{\n#ifndef IMGUI_DISABLE_DEFAULT_FONT\n    ImFontConfig font_cfg = font_cfg_template ? *font_cfg_template : ImFontConfig();\n    if (!font_cfg_template)\n    {\n        font_cfg.OversampleH = font_cfg.OversampleV = 1;\n        font_cfg.PixelSnapH = true;\n    }\n    if (font_cfg.SizePixels <= 0.0f)\n        font_cfg.SizePixels = 13.0f * 1.0f;\n    if (font_cfg.Name[0] == '\\0')\n        ImFormatString(font_cfg.Name, IM_ARRAYSIZE(font_cfg.Name), \"ProggyClean.ttf, %dpx\", (int)font_cfg.SizePixels);\n    font_cfg.EllipsisChar = (ImWchar)0x0085;\n    font_cfg.GlyphOffset.y = 1.0f * IM_TRUNC(font_cfg.SizePixels / 13.0f);  // Add +1 offset per 13 units\n\n    int ttf_compressed_size = 0;\n    const char* ttf_compressed = GetDefaultCompressedFontDataTTF(&ttf_compressed_size);\n    const ImWchar* glyph_ranges = font_cfg.GlyphRanges != NULL ? font_cfg.GlyphRanges : GetGlyphRangesDefault();\n    ImFont* font = AddFontFromMemoryCompressedTTF(ttf_compressed, ttf_compressed_size, font_cfg.SizePixels, &font_cfg, glyph_ranges);\n    return font;\n#else\n    IM_ASSERT(0 && \"AddFontDefault() disabled in this build.\");\n    IM_UNUSED(font_cfg_template);\n    return NULL;\n#endif // #ifndef IMGUI_DISABLE_DEFAULT_FONT\n}\n\nImFont* ImFontAtlas::AddFontFromFileTTF(const char* filename, float size_pixels, const ImFontConfig* font_cfg_template, const ImWchar* glyph_ranges)\n{\n    IM_ASSERT(!Locked && \"Cannot modify a locked ImFontAtlas between NewFrame() and EndFrame/Render()!\");\n    size_t data_size = 0;\n    void* data = ImFileLoadToMemory(filename, \"rb\", &data_size, 0);\n    if (!data)\n    {\n        IM_ASSERT_USER_ERROR(0, \"Could not load font file!\");\n        return NULL;\n    }\n    ImFontConfig font_cfg = font_cfg_template ? *font_cfg_template : ImFontConfig();\n    if (font_cfg.Name[0] == '\\0')\n    {\n        // Store a short copy of filename into into the font name for convenience\n        const char* p;\n        for (p = filename + ImStrlen(filename); p > filename && p[-1] != '/' && p[-1] != '\\\\'; p--) {}\n        ImFormatString(font_cfg.Name, IM_ARRAYSIZE(font_cfg.Name), \"%s, %.0fpx\", p, size_pixels);\n    }\n    return AddFontFromMemoryTTF(data, (int)data_size, size_pixels, &font_cfg, glyph_ranges);\n}\n\n// NB: Transfer ownership of 'ttf_data' to ImFontAtlas, unless font_cfg_template->FontDataOwnedByAtlas == false. Owned TTF buffer will be deleted after Build().\nImFont* ImFontAtlas::AddFontFromMemoryTTF(void* font_data, int font_data_size, float size_pixels, const ImFontConfig* font_cfg_template, const ImWchar* glyph_ranges)\n{\n    IM_ASSERT(!Locked && \"Cannot modify a locked ImFontAtlas between NewFrame() and EndFrame/Render()!\");\n    ImFontConfig font_cfg = font_cfg_template ? *font_cfg_template : ImFontConfig();\n    IM_ASSERT(font_cfg.FontData == NULL);\n    IM_ASSERT(font_data_size > 100 && \"Incorrect value for font_data_size!\"); // Heuristic to prevent accidentally passing a wrong value to font_data_size.\n    font_cfg.FontData = font_data;\n    font_cfg.FontDataSize = font_data_size;\n    font_cfg.SizePixels = size_pixels > 0.0f ? size_pixels : font_cfg.SizePixels;\n    if (glyph_ranges)\n        font_cfg.GlyphRanges = glyph_ranges;\n    return AddFont(&font_cfg);\n}\n\nImFont* ImFontAtlas::AddFontFromMemoryCompressedTTF(const void* compressed_ttf_data, int compressed_ttf_size, float size_pixels, const ImFontConfig* font_cfg_template, const ImWchar* glyph_ranges)\n{\n    const unsigned int buf_decompressed_size = stb_decompress_length((const unsigned char*)compressed_ttf_data);\n    unsigned char* buf_decompressed_data = (unsigned char*)IM_ALLOC(buf_decompressed_size);\n    stb_decompress(buf_decompressed_data, (const unsigned char*)compressed_ttf_data, (unsigned int)compressed_ttf_size);\n\n    ImFontConfig font_cfg = font_cfg_template ? *font_cfg_template : ImFontConfig();\n    IM_ASSERT(font_cfg.FontData == NULL);\n    font_cfg.FontDataOwnedByAtlas = true;\n    return AddFontFromMemoryTTF(buf_decompressed_data, (int)buf_decompressed_size, size_pixels, &font_cfg, glyph_ranges);\n}\n\nImFont* ImFontAtlas::AddFontFromMemoryCompressedBase85TTF(const char* compressed_ttf_data_base85, float size_pixels, const ImFontConfig* font_cfg, const ImWchar* glyph_ranges)\n{\n    int compressed_ttf_size = (((int)ImStrlen(compressed_ttf_data_base85) + 4) / 5) * 4;\n    void* compressed_ttf = IM_ALLOC((size_t)compressed_ttf_size);\n    Decode85((const unsigned char*)compressed_ttf_data_base85, (unsigned char*)compressed_ttf);\n    ImFont* font = AddFontFromMemoryCompressedTTF(compressed_ttf, compressed_ttf_size, size_pixels, font_cfg, glyph_ranges);\n    IM_FREE(compressed_ttf);\n    return font;\n}\n\nint ImFontAtlas::AddCustomRectRegular(int width, int height)\n{\n    IM_ASSERT(width > 0 && width <= 0xFFFF);\n    IM_ASSERT(height > 0 && height <= 0xFFFF);\n    ImFontAtlasCustomRect r;\n    r.Width = (unsigned short)width;\n    r.Height = (unsigned short)height;\n    CustomRects.push_back(r);\n    return CustomRects.Size - 1; // Return index\n}\n\nint ImFontAtlas::AddCustomRectFontGlyph(ImFont* font, ImWchar id, int width, int height, float advance_x, const ImVec2& offset)\n{\n#ifdef IMGUI_USE_WCHAR32\n    IM_ASSERT(id <= IM_UNICODE_CODEPOINT_MAX);\n#endif\n    IM_ASSERT(font != NULL);\n    IM_ASSERT(width > 0 && width <= 0xFFFF);\n    IM_ASSERT(height > 0 && height <= 0xFFFF);\n    ImFontAtlasCustomRect r;\n    r.Width = (unsigned short)width;\n    r.Height = (unsigned short)height;\n    r.GlyphID = id;\n    r.GlyphColored = 0; // Set to 1 manually to mark glyph as colored // FIXME: No official API for that (#8133)\n    r.GlyphAdvanceX = advance_x;\n    r.GlyphOffset = offset;\n    r.Font = font;\n    CustomRects.push_back(r);\n    return CustomRects.Size - 1; // Return index\n}\n\nvoid ImFontAtlas::CalcCustomRectUV(const ImFontAtlasCustomRect* rect, ImVec2* out_uv_min, ImVec2* out_uv_max) const\n{\n    IM_ASSERT(TexWidth > 0 && TexHeight > 0);   // Font atlas needs to be built before we can calculate UV coordinates\n    IM_ASSERT(rect->IsPacked());                // Make sure the rectangle has been packed\n    *out_uv_min = ImVec2((float)rect->X * TexUvScale.x, (float)rect->Y * TexUvScale.y);\n    *out_uv_max = ImVec2((float)(rect->X + rect->Width) * TexUvScale.x, (float)(rect->Y + rect->Height) * TexUvScale.y);\n}\n\nbool ImFontAtlasGetMouseCursorTexData(ImFontAtlas* atlas, ImGuiMouseCursor cursor_type, ImVec2* out_offset, ImVec2* out_size, ImVec2 out_uv_border[2], ImVec2 out_uv_fill[2])\n{\n    if (cursor_type <= ImGuiMouseCursor_None || cursor_type >= ImGuiMouseCursor_COUNT)\n        return false;\n    if (atlas->Flags & ImFontAtlasFlags_NoMouseCursors)\n        return false;\n\n    IM_ASSERT(atlas->PackIdMouseCursors != -1);\n    ImFontAtlasCustomRect* r = atlas->GetCustomRectByIndex(atlas->PackIdMouseCursors);\n    ImVec2 pos = FONT_ATLAS_DEFAULT_TEX_CURSOR_DATA[cursor_type][0] + ImVec2((float)r->X, (float)r->Y);\n    ImVec2 size = FONT_ATLAS_DEFAULT_TEX_CURSOR_DATA[cursor_type][1];\n    *out_size = size;\n    *out_offset = FONT_ATLAS_DEFAULT_TEX_CURSOR_DATA[cursor_type][2];\n    out_uv_border[0] = (pos) * atlas->TexUvScale;\n    out_uv_border[1] = (pos + size) * atlas->TexUvScale;\n    pos.x += FONT_ATLAS_DEFAULT_TEX_DATA_W + 1;\n    out_uv_fill[0] = (pos) * atlas->TexUvScale;\n    out_uv_fill[1] = (pos + size) * atlas->TexUvScale;\n    return true;\n}\n\nbool    ImFontAtlas::Build()\n{\n    IM_ASSERT(!Locked && \"Cannot modify a locked ImFontAtlas between NewFrame() and EndFrame/Render()!\");\n\n    // Default font is none are specified\n    if (Sources.Size == 0)\n        AddFontDefault();\n\n    // Select builder\n    // - Note that we do not reassign to atlas->FontBuilderIO, since it is likely to point to static data which\n    //   may mess with some hot-reloading schemes. If you need to assign to this (for dynamic selection) AND are\n    //   using a hot-reloading scheme that messes up static data, store your own instance of ImFontBuilderIO somewhere\n    //   and point to it instead of pointing directly to return value of the GetBuilderXXX functions.\n    const ImFontBuilderIO* builder_io = FontBuilderIO;\n    if (builder_io == NULL)\n    {\n#ifdef IMGUI_ENABLE_FREETYPE\n        builder_io = ImGuiFreeType::GetBuilderForFreeType();\n#elif defined(IMGUI_ENABLE_STB_TRUETYPE)\n        builder_io = ImFontAtlasGetBuilderForStbTruetype();\n#else\n        IM_ASSERT(0); // Invalid Build function\n#endif\n    }\n\n    // Build\n    return builder_io->FontBuilder_Build(this);\n}\n\nvoid    ImFontAtlasBuildMultiplyCalcLookupTable(unsigned char out_table[256], float in_brighten_factor)\n{\n    for (unsigned int i = 0; i < 256; i++)\n    {\n        unsigned int value = (unsigned int)(i * in_brighten_factor);\n        out_table[i] = value > 255 ? 255 : (value & 0xFF);\n    }\n}\n\nvoid    ImFontAtlasBuildMultiplyRectAlpha8(const unsigned char table[256], unsigned char* pixels, int x, int y, int w, int h, int stride)\n{\n    IM_ASSERT_PARANOID(w <= stride);\n    unsigned char* data = pixels + x + y * stride;\n    for (int j = h; j > 0; j--, data += stride - w)\n        for (int i = w; i > 0; i--, data++)\n            *data = table[*data];\n}\n\nvoid ImFontAtlasBuildGetOversampleFactors(const ImFontConfig* src, int* out_oversample_h, int* out_oversample_v)\n{\n    // Automatically disable horizontal oversampling over size 36\n    *out_oversample_h = (src->OversampleH != 0) ? src->OversampleH : (src->SizePixels * src->RasterizerDensity > 36.0f || src->PixelSnapH) ? 1 : 2;\n    *out_oversample_v = (src->OversampleV != 0) ? src->OversampleV : 1;\n}\n\n#ifdef IMGUI_ENABLE_STB_TRUETYPE\n// Temporary data for one source font (multiple source fonts can be merged into one destination ImFont)\n// (C++03 doesn't allow instancing ImVector<> with function-local types so we declare the type here.)\nstruct ImFontBuildSrcData\n{\n    stbtt_fontinfo      FontInfo;\n    stbtt_pack_range    PackRange;          // Hold the list of codepoints to pack (essentially points to Codepoints.Data)\n    stbrp_rect*         Rects;              // Rectangle to pack. We first fill in their size and the packer will give us their position.\n    stbtt_packedchar*   PackedChars;        // Output glyphs\n    const ImWchar*      SrcRanges;          // Ranges as requested by user (user is allowed to request too much, e.g. 0x0020..0xFFFF)\n    int                 DstIndex;           // Index into atlas->Fonts[] and dst_tmp_array[]\n    int                 GlyphsHighest;      // Highest requested codepoint\n    int                 GlyphsCount;        // Glyph count (excluding missing glyphs and glyphs already set by an earlier source font)\n    ImBitVector         GlyphsSet;          // Glyph bit map (random access, 1-bit per codepoint. This will be a maximum of 8KB)\n    ImVector<int>       GlyphsList;         // Glyph codepoints list (flattened version of GlyphsSet)\n};\n\n// Temporary data for one destination ImFont* (multiple source fonts can be merged into one destination ImFont)\nstruct ImFontBuildDstData\n{\n    int                 SrcCount;           // Number of source fonts targeting this destination font.\n    int                 GlyphsHighest;\n    int                 GlyphsCount;\n    ImBitVector         GlyphsSet;          // This is used to resolve collision when multiple sources are merged into a same destination font.\n};\n\nstatic void UnpackBitVectorToFlatIndexList(const ImBitVector* in, ImVector<int>* out)\n{\n    IM_ASSERT(sizeof(in->Storage.Data[0]) == sizeof(int));\n    const ImU32* it_begin = in->Storage.begin();\n    const ImU32* it_end = in->Storage.end();\n    for (const ImU32* it = it_begin; it < it_end; it++)\n        if (ImU32 entries_32 = *it)\n            for (ImU32 bit_n = 0; bit_n < 32; bit_n++)\n                if (entries_32 & ((ImU32)1 << bit_n))\n                    out->push_back((int)(((it - it_begin) << 5) + bit_n));\n}\n\nstatic bool ImFontAtlasBuildWithStbTruetype(ImFontAtlas* atlas)\n{\n    IM_ASSERT(atlas->Sources.Size > 0);\n\n    ImFontAtlasBuildInit(atlas);\n\n    // Clear atlas\n    atlas->TexID = (ImTextureID)NULL;\n    atlas->TexWidth = atlas->TexHeight = 0;\n    atlas->TexUvScale = ImVec2(0.0f, 0.0f);\n    atlas->TexUvWhitePixel = ImVec2(0.0f, 0.0f);\n    atlas->ClearTexData();\n\n    // Temporary storage for building\n    ImVector<ImFontBuildSrcData> src_tmp_array;\n    ImVector<ImFontBuildDstData> dst_tmp_array;\n    src_tmp_array.resize(atlas->Sources.Size);\n    dst_tmp_array.resize(atlas->Fonts.Size);\n    memset(src_tmp_array.Data, 0, (size_t)src_tmp_array.size_in_bytes());\n    memset(dst_tmp_array.Data, 0, (size_t)dst_tmp_array.size_in_bytes());\n\n    // 1. Initialize font loading structure, check font data validity\n    for (int src_i = 0; src_i < atlas->Sources.Size; src_i++)\n    {\n        ImFontBuildSrcData& src_tmp = src_tmp_array[src_i];\n        ImFontConfig& src = atlas->Sources[src_i];\n        IM_ASSERT(src.DstFont && (!src.DstFont->IsLoaded() || src.DstFont->ContainerAtlas == atlas));\n\n        // Find index from src.DstFont (we allow the user to set cfg.DstFont. Also it makes casual debugging nicer than when storing indices)\n        src_tmp.DstIndex = -1;\n        for (int output_i = 0; output_i < atlas->Fonts.Size && src_tmp.DstIndex == -1; output_i++)\n            if (src.DstFont == atlas->Fonts[output_i])\n                src_tmp.DstIndex = output_i;\n        if (src_tmp.DstIndex == -1)\n        {\n            IM_ASSERT(src_tmp.DstIndex != -1); // src.DstFont not pointing within atlas->Fonts[] array?\n            return false;\n        }\n        // Initialize helper structure for font loading and verify that the TTF/OTF data is correct\n        const int font_offset = stbtt_GetFontOffsetForIndex((unsigned char*)src.FontData, src.FontNo);\n        IM_ASSERT(font_offset >= 0 && \"FontData is incorrect, or FontNo cannot be found.\");\n        if (!stbtt_InitFont(&src_tmp.FontInfo, (unsigned char*)src.FontData, font_offset))\n        {\n            IM_ASSERT(0 && \"stbtt_InitFont(): failed to parse FontData. It is correct and complete? Check FontDataSize.\");\n            return false;\n        }\n\n        // Measure highest codepoints\n        ImFontBuildDstData& dst_tmp = dst_tmp_array[src_tmp.DstIndex];\n        src_tmp.SrcRanges = src.GlyphRanges ? src.GlyphRanges : atlas->GetGlyphRangesDefault();\n        for (const ImWchar* src_range = src_tmp.SrcRanges; src_range[0] && src_range[1]; src_range += 2)\n        {\n            // Check for valid range. This may also help detect *some* dangling pointers, because a common\n            // user error is to setup ImFontConfig::GlyphRanges with a pointer to data that isn't persistent,\n            // or to forget to zero-terminate the glyph range array.\n            IM_ASSERT(src_range[0] <= src_range[1] && \"Invalid range: is your glyph range array persistent? it is zero-terminated?\");\n            src_tmp.GlyphsHighest = ImMax(src_tmp.GlyphsHighest, (int)src_range[1]);\n        }\n        dst_tmp.SrcCount++;\n        dst_tmp.GlyphsHighest = ImMax(dst_tmp.GlyphsHighest, src_tmp.GlyphsHighest);\n    }\n\n    // 2. For every requested codepoint, check for their presence in the font data, and handle redundancy or overlaps between source fonts to avoid unused glyphs.\n    int total_glyphs_count = 0;\n    for (int src_i = 0; src_i < src_tmp_array.Size; src_i++)\n    {\n        ImFontBuildSrcData& src_tmp = src_tmp_array[src_i];\n        ImFontBuildDstData& dst_tmp = dst_tmp_array[src_tmp.DstIndex];\n        src_tmp.GlyphsSet.Create(src_tmp.GlyphsHighest + 1);\n        if (dst_tmp.GlyphsSet.Storage.empty())\n            dst_tmp.GlyphsSet.Create(dst_tmp.GlyphsHighest + 1);\n\n        for (const ImWchar* src_range = src_tmp.SrcRanges; src_range[0] && src_range[1]; src_range += 2)\n            for (unsigned int codepoint = src_range[0]; codepoint <= src_range[1]; codepoint++)\n            {\n                if (dst_tmp.GlyphsSet.TestBit(codepoint))    // Don't overwrite existing glyphs. We could make this an option for MergeMode (e.g. MergeOverwrite==true)\n                    continue;\n                if (!stbtt_FindGlyphIndex(&src_tmp.FontInfo, codepoint))    // It is actually in the font?\n                    continue;\n\n                // Add to avail set/counters\n                src_tmp.GlyphsCount++;\n                dst_tmp.GlyphsCount++;\n                src_tmp.GlyphsSet.SetBit(codepoint);\n                dst_tmp.GlyphsSet.SetBit(codepoint);\n                total_glyphs_count++;\n            }\n    }\n\n    // 3. Unpack our bit map into a flat list (we now have all the Unicode points that we know are requested _and_ available _and_ not overlapping another)\n    for (int src_i = 0; src_i < src_tmp_array.Size; src_i++)\n    {\n        ImFontBuildSrcData& src_tmp = src_tmp_array[src_i];\n        src_tmp.GlyphsList.reserve(src_tmp.GlyphsCount);\n        UnpackBitVectorToFlatIndexList(&src_tmp.GlyphsSet, &src_tmp.GlyphsList);\n        src_tmp.GlyphsSet.Clear();\n        IM_ASSERT(src_tmp.GlyphsList.Size == src_tmp.GlyphsCount);\n    }\n    for (int dst_i = 0; dst_i < dst_tmp_array.Size; dst_i++)\n        dst_tmp_array[dst_i].GlyphsSet.Clear();\n    dst_tmp_array.clear();\n\n    // Allocate packing character data and flag packed characters buffer as non-packed (x0=y0=x1=y1=0)\n    // (We technically don't need to zero-clear buf_rects, but let's do it for the sake of sanity)\n    ImVector<stbrp_rect> buf_rects;\n    ImVector<stbtt_packedchar> buf_packedchars;\n    buf_rects.resize(total_glyphs_count);\n    buf_packedchars.resize(total_glyphs_count);\n    memset(buf_rects.Data, 0, (size_t)buf_rects.size_in_bytes());\n    memset(buf_packedchars.Data, 0, (size_t)buf_packedchars.size_in_bytes());\n\n    // 4. Gather glyphs sizes so we can pack them in our virtual canvas.\n    int total_surface = 0;\n    int buf_rects_out_n = 0;\n    int buf_packedchars_out_n = 0;\n    const int pack_padding = atlas->TexGlyphPadding;\n    for (int src_i = 0; src_i < src_tmp_array.Size; src_i++)\n    {\n        ImFontBuildSrcData& src_tmp = src_tmp_array[src_i];\n        if (src_tmp.GlyphsCount == 0)\n            continue;\n\n        src_tmp.Rects = &buf_rects[buf_rects_out_n];\n        src_tmp.PackedChars = &buf_packedchars[buf_packedchars_out_n];\n        buf_rects_out_n += src_tmp.GlyphsCount;\n        buf_packedchars_out_n += src_tmp.GlyphsCount;\n\n        // Automatic selection of oversampling parameters\n        ImFontConfig& src = atlas->Sources[src_i];\n        int oversample_h, oversample_v;\n        ImFontAtlasBuildGetOversampleFactors(&src, &oversample_h, &oversample_v);\n\n        // Convert our ranges in the format stb_truetype wants\n        src_tmp.PackRange.font_size = src.SizePixels * src.RasterizerDensity;\n        src_tmp.PackRange.first_unicode_codepoint_in_range = 0;\n        src_tmp.PackRange.array_of_unicode_codepoints = src_tmp.GlyphsList.Data;\n        src_tmp.PackRange.num_chars = src_tmp.GlyphsList.Size;\n        src_tmp.PackRange.chardata_for_range = src_tmp.PackedChars;\n        src_tmp.PackRange.h_oversample = (unsigned char)oversample_h;\n        src_tmp.PackRange.v_oversample = (unsigned char)oversample_v;\n\n        // Gather the sizes of all rectangles we will need to pack (this loop is based on stbtt_PackFontRangesGatherRects)\n        const float scale = (src.SizePixels > 0.0f) ? stbtt_ScaleForPixelHeight(&src_tmp.FontInfo, src.SizePixels * src.RasterizerDensity) : stbtt_ScaleForMappingEmToPixels(&src_tmp.FontInfo, -src.SizePixels * src.RasterizerDensity);\n        for (int glyph_i = 0; glyph_i < src_tmp.GlyphsList.Size; glyph_i++)\n        {\n            int x0, y0, x1, y1;\n            const int glyph_index_in_font = stbtt_FindGlyphIndex(&src_tmp.FontInfo, src_tmp.GlyphsList[glyph_i]);\n            IM_ASSERT(glyph_index_in_font != 0);\n            stbtt_GetGlyphBitmapBoxSubpixel(&src_tmp.FontInfo, glyph_index_in_font, scale * oversample_h, scale * oversample_v, 0, 0, &x0, &y0, &x1, &y1);\n            src_tmp.Rects[glyph_i].w = (stbrp_coord)(x1 - x0 + pack_padding + oversample_h - 1);\n            src_tmp.Rects[glyph_i].h = (stbrp_coord)(y1 - y0 + pack_padding + oversample_v - 1);\n            total_surface += src_tmp.Rects[glyph_i].w * src_tmp.Rects[glyph_i].h;\n        }\n    }\n    for (int i = 0; i < atlas->CustomRects.Size; i++)\n        total_surface += (atlas->CustomRects[i].Width + pack_padding) * (atlas->CustomRects[i].Height + pack_padding);\n\n    // We need a width for the skyline algorithm, any width!\n    // The exact width doesn't really matter much, but some API/GPU have texture size limitations and increasing width can decrease height.\n    // User can override TexDesiredWidth and TexGlyphPadding if they wish, otherwise we use a simple heuristic to select the width based on expected surface.\n    const int surface_sqrt = (int)ImSqrt((float)total_surface) + 1;\n    atlas->TexHeight = 0;\n    if (atlas->TexDesiredWidth > 0)\n        atlas->TexWidth = atlas->TexDesiredWidth;\n    else\n        atlas->TexWidth = (surface_sqrt >= 4096 * 0.7f) ? 4096 : (surface_sqrt >= 2048 * 0.7f) ? 2048 : (surface_sqrt >= 1024 * 0.7f) ? 1024 : 512;\n\n    // 5. Start packing\n    // Pack our extra data rectangles first, so it will be on the upper-left corner of our texture (UV will have small values).\n    const int TEX_HEIGHT_MAX = 1024 * 32;\n    stbtt_pack_context spc = {};\n    stbtt_PackBegin(&spc, NULL, atlas->TexWidth, TEX_HEIGHT_MAX, 0, 0, NULL);\n    spc.padding = atlas->TexGlyphPadding; // Because we mixup stbtt_PackXXX and stbrp_PackXXX there's a bit of a hack here, not passing the value to stbtt_PackBegin() allows us to still pack a TexWidth-1 wide item. (#8107)\n    ImFontAtlasBuildPackCustomRects(atlas, spc.pack_info);\n\n    // 6. Pack each source font. No rendering yet, we are working with rectangles in an infinitely tall texture at this point.\n    for (int src_i = 0; src_i < src_tmp_array.Size; src_i++)\n    {\n        ImFontBuildSrcData& src_tmp = src_tmp_array[src_i];\n        if (src_tmp.GlyphsCount == 0)\n            continue;\n\n        stbrp_pack_rects((stbrp_context*)spc.pack_info, src_tmp.Rects, src_tmp.GlyphsCount);\n\n        // Extend texture height and mark missing glyphs as non-packed so we won't render them.\n        // FIXME: We are not handling packing failure here (would happen if we got off TEX_HEIGHT_MAX or if a single if larger than TexWidth?)\n        for (int glyph_i = 0; glyph_i < src_tmp.GlyphsCount; glyph_i++)\n            if (src_tmp.Rects[glyph_i].was_packed)\n                atlas->TexHeight = ImMax(atlas->TexHeight, src_tmp.Rects[glyph_i].y + src_tmp.Rects[glyph_i].h);\n    }\n\n    // 7. Allocate texture\n    atlas->TexHeight = (atlas->Flags & ImFontAtlasFlags_NoPowerOfTwoHeight) ? (atlas->TexHeight + 1) : ImUpperPowerOfTwo(atlas->TexHeight);\n    atlas->TexUvScale = ImVec2(1.0f / atlas->TexWidth, 1.0f / atlas->TexHeight);\n    atlas->TexPixelsAlpha8 = (unsigned char*)IM_ALLOC(atlas->TexWidth * atlas->TexHeight);\n    memset(atlas->TexPixelsAlpha8, 0, atlas->TexWidth * atlas->TexHeight);\n    spc.pixels = atlas->TexPixelsAlpha8;\n    spc.height = atlas->TexHeight;\n\n    // 8. Render/rasterize font characters into the texture\n    for (int src_i = 0; src_i < src_tmp_array.Size; src_i++)\n    {\n        ImFontConfig& src = atlas->Sources[src_i];\n        ImFontBuildSrcData& src_tmp = src_tmp_array[src_i];\n        if (src_tmp.GlyphsCount == 0)\n            continue;\n\n        stbtt_PackFontRangesRenderIntoRects(&spc, &src_tmp.FontInfo, &src_tmp.PackRange, 1, src_tmp.Rects);\n\n        // Apply multiply operator\n        if (src.RasterizerMultiply != 1.0f)\n        {\n            unsigned char multiply_table[256];\n            ImFontAtlasBuildMultiplyCalcLookupTable(multiply_table, src.RasterizerMultiply);\n            stbrp_rect* r = &src_tmp.Rects[0];\n            for (int glyph_i = 0; glyph_i < src_tmp.GlyphsCount; glyph_i++, r++)\n                if (r->was_packed)\n                    ImFontAtlasBuildMultiplyRectAlpha8(multiply_table, atlas->TexPixelsAlpha8, r->x, r->y, r->w, r->h, atlas->TexWidth * 1);\n        }\n        src_tmp.Rects = NULL;\n    }\n\n    // End packing\n    stbtt_PackEnd(&spc);\n    buf_rects.clear();\n\n    // 9. Setup ImFont and glyphs for runtime\n    for (int src_i = 0; src_i < src_tmp_array.Size; src_i++)\n    {\n        // When merging fonts with MergeMode=true:\n        // - We can have multiple input fonts writing into a same destination font.\n        // - dst_font->Sources is != from src which is our source configuration.\n        ImFontBuildSrcData& src_tmp = src_tmp_array[src_i];\n        ImFontConfig& src = atlas->Sources[src_i];\n        ImFont* dst_font = src.DstFont;\n\n        const float font_scale = stbtt_ScaleForPixelHeight(&src_tmp.FontInfo, src.SizePixels);\n        int unscaled_ascent, unscaled_descent, unscaled_line_gap;\n        stbtt_GetFontVMetrics(&src_tmp.FontInfo, &unscaled_ascent, &unscaled_descent, &unscaled_line_gap);\n\n        const float ascent = ImCeil(unscaled_ascent * font_scale);\n        const float descent = ImFloor(unscaled_descent * font_scale);\n        ImFontAtlasBuildSetupFont(atlas, dst_font, &src, ascent, descent);\n        const float font_off_x = src.GlyphOffset.x;\n        const float font_off_y = src.GlyphOffset.y + IM_ROUND(dst_font->Ascent);\n\n        const float inv_rasterization_scale = 1.0f / src.RasterizerDensity;\n\n        for (int glyph_i = 0; glyph_i < src_tmp.GlyphsCount; glyph_i++)\n        {\n            // Register glyph\n            const int codepoint = src_tmp.GlyphsList[glyph_i];\n            const stbtt_packedchar& pc = src_tmp.PackedChars[glyph_i];\n            stbtt_aligned_quad q;\n            float unused_x = 0.0f, unused_y = 0.0f;\n            stbtt_GetPackedQuad(src_tmp.PackedChars, atlas->TexWidth, atlas->TexHeight, glyph_i, &unused_x, &unused_y, &q, 0);\n            float x0 = q.x0 * inv_rasterization_scale + font_off_x;\n            float y0 = q.y0 * inv_rasterization_scale + font_off_y;\n            float x1 = q.x1 * inv_rasterization_scale + font_off_x;\n            float y1 = q.y1 * inv_rasterization_scale + font_off_y;\n            dst_font->AddGlyph(&src, (ImWchar)codepoint, x0, y0, x1, y1, q.s0, q.t0, q.s1, q.t1, pc.xadvance * inv_rasterization_scale);\n        }\n    }\n\n    // Cleanup\n    src_tmp_array.clear_destruct();\n\n    ImFontAtlasBuildFinish(atlas);\n    return true;\n}\n\nconst ImFontBuilderIO* ImFontAtlasGetBuilderForStbTruetype()\n{\n    static ImFontBuilderIO io;\n    io.FontBuilder_Build = ImFontAtlasBuildWithStbTruetype;\n    return &io;\n}\n\n#endif // IMGUI_ENABLE_STB_TRUETYPE\n\nvoid ImFontAtlasUpdateSourcesPointers(ImFontAtlas* atlas)\n{\n    for (ImFontConfig& src : atlas->Sources)\n    {\n        ImFont* font = src.DstFont;\n        if (!src.MergeMode)\n        {\n            font->Sources = &src;\n            font->SourcesCount = 0;\n        }\n        font->SourcesCount++;\n    }\n}\n\nvoid ImFontAtlasBuildSetupFont(ImFontAtlas* atlas, ImFont* font, ImFontConfig* font_config, float ascent, float descent)\n{\n    if (!font_config->MergeMode)\n    {\n        font->ClearOutputData();\n        font->FontSize = font_config->SizePixels;\n        IM_ASSERT(font->Sources == font_config);\n        font->ContainerAtlas = atlas;\n        font->Ascent = ascent;\n        font->Descent = descent;\n    }\n}\n\nvoid ImFontAtlasBuildPackCustomRects(ImFontAtlas* atlas, void* stbrp_context_opaque)\n{\n    stbrp_context* pack_context = (stbrp_context*)stbrp_context_opaque;\n    IM_ASSERT(pack_context != NULL);\n\n    ImVector<ImFontAtlasCustomRect>& user_rects = atlas->CustomRects;\n    IM_ASSERT(user_rects.Size >= 1); // We expect at least the default custom rects to be registered, else something went wrong.\n#ifdef __GNUC__\n    if (user_rects.Size < 1) { __builtin_unreachable(); } // Workaround for GCC bug if IM_ASSERT() is defined to conditionally throw (see #5343)\n#endif\n\n    const int pack_padding = atlas->TexGlyphPadding;\n    ImVector<stbrp_rect> pack_rects;\n    pack_rects.resize(user_rects.Size);\n    memset(pack_rects.Data, 0, (size_t)pack_rects.size_in_bytes());\n    for (int i = 0; i < user_rects.Size; i++)\n    {\n        pack_rects[i].w = user_rects[i].Width + pack_padding;\n        pack_rects[i].h = user_rects[i].Height + pack_padding;\n    }\n    stbrp_pack_rects(pack_context, &pack_rects[0], pack_rects.Size);\n    for (int i = 0; i < pack_rects.Size; i++)\n        if (pack_rects[i].was_packed)\n        {\n            user_rects[i].X = (unsigned short)pack_rects[i].x;\n            user_rects[i].Y = (unsigned short)pack_rects[i].y;\n            IM_ASSERT(pack_rects[i].w == user_rects[i].Width + pack_padding && pack_rects[i].h == user_rects[i].Height + pack_padding);\n            atlas->TexHeight = ImMax(atlas->TexHeight, pack_rects[i].y + pack_rects[i].h);\n        }\n}\n\nvoid ImFontAtlasBuildRender8bppRectFromString(ImFontAtlas* atlas, int x, int y, int w, int h, const char* in_str, char in_marker_char, unsigned char in_marker_pixel_value)\n{\n    IM_ASSERT(x >= 0 && x + w <= atlas->TexWidth);\n    IM_ASSERT(y >= 0 && y + h <= atlas->TexHeight);\n    unsigned char* out_pixel = atlas->TexPixelsAlpha8 + x + (y * atlas->TexWidth);\n    for (int off_y = 0; off_y < h; off_y++, out_pixel += atlas->TexWidth, in_str += w)\n        for (int off_x = 0; off_x < w; off_x++)\n            out_pixel[off_x] = (in_str[off_x] == in_marker_char) ? in_marker_pixel_value : 0x00;\n}\n\nvoid ImFontAtlasBuildRender32bppRectFromString(ImFontAtlas* atlas, int x, int y, int w, int h, const char* in_str, char in_marker_char, unsigned int in_marker_pixel_value)\n{\n    IM_ASSERT(x >= 0 && x + w <= atlas->TexWidth);\n    IM_ASSERT(y >= 0 && y + h <= atlas->TexHeight);\n    unsigned int* out_pixel = atlas->TexPixelsRGBA32 + x + (y * atlas->TexWidth);\n    for (int off_y = 0; off_y < h; off_y++, out_pixel += atlas->TexWidth, in_str += w)\n        for (int off_x = 0; off_x < w; off_x++)\n            out_pixel[off_x] = (in_str[off_x] == in_marker_char) ? in_marker_pixel_value : IM_COL32_BLACK_TRANS;\n}\n\nstatic void ImFontAtlasBuildRenderDefaultTexData(ImFontAtlas* atlas)\n{\n    ImFontAtlasCustomRect* r = atlas->GetCustomRectByIndex(atlas->PackIdMouseCursors);\n    IM_ASSERT(r->IsPacked());\n\n    const int w = atlas->TexWidth;\n    if (atlas->Flags & ImFontAtlasFlags_NoMouseCursors)\n    {\n        // White pixels only\n        IM_ASSERT(r->Width == 2 && r->Height == 2);\n        const int offset = (int)r->X + (int)r->Y * w;\n        if (atlas->TexPixelsAlpha8 != NULL)\n        {\n            atlas->TexPixelsAlpha8[offset] = atlas->TexPixelsAlpha8[offset + 1] = atlas->TexPixelsAlpha8[offset + w] = atlas->TexPixelsAlpha8[offset + w + 1] = 0xFF;\n        }\n        else\n        {\n            atlas->TexPixelsRGBA32[offset] = atlas->TexPixelsRGBA32[offset + 1] = atlas->TexPixelsRGBA32[offset + w] = atlas->TexPixelsRGBA32[offset + w + 1] = IM_COL32_WHITE;\n        }\n    }\n    else\n    {\n        // White pixels and mouse cursor\n        IM_ASSERT(r->Width == FONT_ATLAS_DEFAULT_TEX_DATA_W * 2 + 1 && r->Height == FONT_ATLAS_DEFAULT_TEX_DATA_H);\n        const int x_for_white = r->X;\n        const int x_for_black = r->X + FONT_ATLAS_DEFAULT_TEX_DATA_W + 1;\n        if (atlas->TexPixelsAlpha8 != NULL)\n        {\n            ImFontAtlasBuildRender8bppRectFromString(atlas, x_for_white, r->Y, FONT_ATLAS_DEFAULT_TEX_DATA_W, FONT_ATLAS_DEFAULT_TEX_DATA_H, FONT_ATLAS_DEFAULT_TEX_DATA_PIXELS, '.', 0xFF);\n            ImFontAtlasBuildRender8bppRectFromString(atlas, x_for_black, r->Y, FONT_ATLAS_DEFAULT_TEX_DATA_W, FONT_ATLAS_DEFAULT_TEX_DATA_H, FONT_ATLAS_DEFAULT_TEX_DATA_PIXELS, 'X', 0xFF);\n        }\n        else\n        {\n            ImFontAtlasBuildRender32bppRectFromString(atlas, x_for_white, r->Y, FONT_ATLAS_DEFAULT_TEX_DATA_W, FONT_ATLAS_DEFAULT_TEX_DATA_H, FONT_ATLAS_DEFAULT_TEX_DATA_PIXELS, '.', IM_COL32_WHITE);\n            ImFontAtlasBuildRender32bppRectFromString(atlas, x_for_black, r->Y, FONT_ATLAS_DEFAULT_TEX_DATA_W, FONT_ATLAS_DEFAULT_TEX_DATA_H, FONT_ATLAS_DEFAULT_TEX_DATA_PIXELS, 'X', IM_COL32_WHITE);\n        }\n    }\n    atlas->TexUvWhitePixel = ImVec2((r->X + 0.5f) * atlas->TexUvScale.x, (r->Y + 0.5f) * atlas->TexUvScale.y);\n}\n\nstatic void ImFontAtlasBuildRenderLinesTexData(ImFontAtlas* atlas)\n{\n    if (atlas->Flags & ImFontAtlasFlags_NoBakedLines)\n        return;\n\n    // This generates a triangular shape in the texture, with the various line widths stacked on top of each other to allow interpolation between them\n    ImFontAtlasCustomRect* r = atlas->GetCustomRectByIndex(atlas->PackIdLines);\n    IM_ASSERT(r->IsPacked());\n    for (int n = 0; n < IM_DRAWLIST_TEX_LINES_WIDTH_MAX + 1; n++) // +1 because of the zero-width row\n    {\n        // Each line consists of at least two empty pixels at the ends, with a line of solid pixels in the middle\n        int y = n;\n        int line_width = n;\n        int pad_left = (r->Width - line_width) / 2;\n        int pad_right = r->Width - (pad_left + line_width);\n\n        // Write each slice\n        IM_ASSERT(pad_left + line_width + pad_right == r->Width && y < r->Height); // Make sure we're inside the texture bounds before we start writing pixels\n        if (atlas->TexPixelsAlpha8 != NULL)\n        {\n            unsigned char* write_ptr = &atlas->TexPixelsAlpha8[r->X + ((r->Y + y) * atlas->TexWidth)];\n            for (int i = 0; i < pad_left; i++)\n                *(write_ptr + i) = 0x00;\n\n            for (int i = 0; i < line_width; i++)\n                *(write_ptr + pad_left + i) = 0xFF;\n\n            for (int i = 0; i < pad_right; i++)\n                *(write_ptr + pad_left + line_width + i) = 0x00;\n        }\n        else\n        {\n            unsigned int* write_ptr = &atlas->TexPixelsRGBA32[r->X + ((r->Y + y) * atlas->TexWidth)];\n            for (int i = 0; i < pad_left; i++)\n                *(write_ptr + i) = IM_COL32(255, 255, 255, 0);\n\n            for (int i = 0; i < line_width; i++)\n                *(write_ptr + pad_left + i) = IM_COL32_WHITE;\n\n            for (int i = 0; i < pad_right; i++)\n                *(write_ptr + pad_left + line_width + i) = IM_COL32(255, 255, 255, 0);\n        }\n\n        // Calculate UVs for this line\n        ImVec2 uv0 = ImVec2((float)(r->X + pad_left - 1), (float)(r->Y + y)) * atlas->TexUvScale;\n        ImVec2 uv1 = ImVec2((float)(r->X + pad_left + line_width + 1), (float)(r->Y + y + 1)) * atlas->TexUvScale;\n        float half_v = (uv0.y + uv1.y) * 0.5f; // Calculate a constant V in the middle of the row to avoid sampling artifacts\n        atlas->TexUvLines[n] = ImVec4(uv0.x, half_v, uv1.x, half_v);\n    }\n}\n\n// Note: this is called / shared by both the stb_truetype and the FreeType builder\nvoid ImFontAtlasBuildInit(ImFontAtlas* atlas)\n{\n    // Register texture region for mouse cursors or standard white pixels\n    if (atlas->PackIdMouseCursors < 0)\n    {\n        if (!(atlas->Flags & ImFontAtlasFlags_NoMouseCursors))\n            atlas->PackIdMouseCursors = atlas->AddCustomRectRegular(FONT_ATLAS_DEFAULT_TEX_DATA_W * 2 + 1, FONT_ATLAS_DEFAULT_TEX_DATA_H);\n        else\n            atlas->PackIdMouseCursors = atlas->AddCustomRectRegular(2, 2);\n    }\n\n    // Register texture region for thick lines\n    // The +2 here is to give space for the end caps, whilst height +1 is to accommodate the fact we have a zero-width row\n    if (atlas->PackIdLines < 0)\n    {\n        if (!(atlas->Flags & ImFontAtlasFlags_NoBakedLines))\n            atlas->PackIdLines = atlas->AddCustomRectRegular(IM_DRAWLIST_TEX_LINES_WIDTH_MAX + 2, IM_DRAWLIST_TEX_LINES_WIDTH_MAX + 1);\n    }\n}\n\n// This is called/shared by both the stb_truetype and the FreeType builder.\nvoid ImFontAtlasBuildFinish(ImFontAtlas* atlas)\n{\n    // Render into our custom data blocks\n    IM_ASSERT(atlas->TexPixelsAlpha8 != NULL || atlas->TexPixelsRGBA32 != NULL);\n    ImFontAtlasBuildRenderDefaultTexData(atlas);\n    ImFontAtlasBuildRenderLinesTexData(atlas);\n\n    // Register custom rectangle glyphs\n    for (int i = 0; i < atlas->CustomRects.Size; i++)\n    {\n        const ImFontAtlasCustomRect* r = &atlas->CustomRects[i];\n        if (r->Font == NULL || r->GlyphID == 0)\n            continue;\n\n        // Will ignore ImFontConfig settings: GlyphMinAdvanceX, GlyphMinAdvanceY, PixelSnapH\n        IM_ASSERT(r->Font->ContainerAtlas == atlas);\n        ImVec2 uv0, uv1;\n        atlas->CalcCustomRectUV(r, &uv0, &uv1);\n        r->Font->AddGlyph(NULL, (ImWchar)r->GlyphID, r->GlyphOffset.x, r->GlyphOffset.y, r->GlyphOffset.x + r->Width, r->GlyphOffset.y + r->Height, uv0.x, uv0.y, uv1.x, uv1.y, r->GlyphAdvanceX);\n        if (r->GlyphColored)\n            r->Font->Glyphs.back().Colored = 1;\n    }\n\n    // Build all fonts lookup tables\n    for (ImFont* font : atlas->Fonts)\n        if (font->DirtyLookupTables)\n            font->BuildLookupTable();\n\n    atlas->TexReady = true;\n}\n\n//-------------------------------------------------------------------------\n// [SECTION] ImFontAtlas: glyph ranges helpers\n//-------------------------------------------------------------------------\n// - GetGlyphRangesDefault()\n// - GetGlyphRangesGreek()\n// - GetGlyphRangesKorean()\n// - GetGlyphRangesChineseFull()\n// - GetGlyphRangesChineseSimplifiedCommon()\n// - GetGlyphRangesJapanese()\n// - GetGlyphRangesCyrillic()\n// - GetGlyphRangesThai()\n// - GetGlyphRangesVietnamese()\n//-----------------------------------------------------------------------------\n\n// Retrieve list of range (2 int per range, values are inclusive)\nconst ImWchar*   ImFontAtlas::GetGlyphRangesDefault()\n{\n    static const ImWchar ranges[] =\n    {\n        0x0020, 0x00FF, // Basic Latin + Latin Supplement\n        0,\n    };\n    return &ranges[0];\n}\n\nconst ImWchar*   ImFontAtlas::GetGlyphRangesGreek()\n{\n    static const ImWchar ranges[] =\n    {\n        0x0020, 0x00FF, // Basic Latin + Latin Supplement\n        0x0370, 0x03FF, // Greek and Coptic\n        0,\n    };\n    return &ranges[0];\n}\n\nconst ImWchar*  ImFontAtlas::GetGlyphRangesKorean()\n{\n    static const ImWchar ranges[] =\n    {\n        0x0020, 0x00FF, // Basic Latin + Latin Supplement\n        0x3131, 0x3163, // Korean alphabets\n        0xAC00, 0xD7A3, // Korean characters\n        0xFFFD, 0xFFFD, // Invalid\n        0,\n    };\n    return &ranges[0];\n}\n\nconst ImWchar*  ImFontAtlas::GetGlyphRangesChineseFull()\n{\n    static const ImWchar ranges[] =\n    {\n        0x0020, 0x00FF, // Basic Latin + Latin Supplement\n        0x2000, 0x206F, // General Punctuation\n        0x3000, 0x30FF, // CJK Symbols and Punctuations, Hiragana, Katakana\n        0x31F0, 0x31FF, // Katakana Phonetic Extensions\n        0xFF00, 0xFFEF, // Half-width characters\n        0xFFFD, 0xFFFD, // Invalid\n        0x4e00, 0x9FAF, // CJK Ideograms\n        0,\n    };\n    return &ranges[0];\n}\n\nstatic void UnpackAccumulativeOffsetsIntoRanges(int base_codepoint, const short* accumulative_offsets, int accumulative_offsets_count, ImWchar* out_ranges)\n{\n    for (int n = 0; n < accumulative_offsets_count; n++, out_ranges += 2)\n    {\n        out_ranges[0] = out_ranges[1] = (ImWchar)(base_codepoint + accumulative_offsets[n]);\n        base_codepoint += accumulative_offsets[n];\n    }\n    out_ranges[0] = 0;\n}\n\nconst ImWchar*  ImFontAtlas::GetGlyphRangesChineseSimplifiedCommon()\n{\n    // Store 2500 regularly used characters for Simplified Chinese.\n    // Sourced from https://zh.wiktionary.org/wiki/%E9%99%84%E5%BD%95:%E7%8E%B0%E4%BB%A3%E6%B1%89%E8%AF%AD%E5%B8%B8%E7%94%A8%E5%AD%97%E8%A1%A8\n    // This table covers 97.97% of all characters used during the month in July, 1987.\n    // You can use ImFontGlyphRangesBuilder to create your own ranges derived from this, by merging existing ranges or adding new characters.\n    // (Stored as accumulative offsets from the initial unicode codepoint 0x4E00. This encoding is designed to helps us compact the source code size.)\n    static const short accumulative_offsets_from_0x4E00[] =\n    {\n        0,1,2,4,1,1,1,1,2,1,3,2,1,2,2,1,1,1,1,1,5,2,1,2,3,3,3,2,2,4,1,1,1,2,1,5,2,3,1,2,1,2,1,1,2,1,1,2,2,1,4,1,1,1,1,5,10,1,2,19,2,1,2,1,2,1,2,1,2,\n        1,5,1,6,3,2,1,2,2,1,1,1,4,8,5,1,1,4,1,1,3,1,2,1,5,1,2,1,1,1,10,1,1,5,2,4,6,1,4,2,2,2,12,2,1,1,6,1,1,1,4,1,1,4,6,5,1,4,2,2,4,10,7,1,1,4,2,4,\n        2,1,4,3,6,10,12,5,7,2,14,2,9,1,1,6,7,10,4,7,13,1,5,4,8,4,1,1,2,28,5,6,1,1,5,2,5,20,2,2,9,8,11,2,9,17,1,8,6,8,27,4,6,9,20,11,27,6,68,2,2,1,1,\n        1,2,1,2,2,7,6,11,3,3,1,1,3,1,2,1,1,1,1,1,3,1,1,8,3,4,1,5,7,2,1,4,4,8,4,2,1,2,1,1,4,5,6,3,6,2,12,3,1,3,9,2,4,3,4,1,5,3,3,1,3,7,1,5,1,1,1,1,2,\n        3,4,5,2,3,2,6,1,1,2,1,7,1,7,3,4,5,15,2,2,1,5,3,22,19,2,1,1,1,1,2,5,1,1,1,6,1,1,12,8,2,9,18,22,4,1,1,5,1,16,1,2,7,10,15,1,1,6,2,4,1,2,4,1,6,\n        1,1,3,2,4,1,6,4,5,1,2,1,1,2,1,10,3,1,3,2,1,9,3,2,5,7,2,19,4,3,6,1,1,1,1,1,4,3,2,1,1,1,2,5,3,1,1,1,2,2,1,1,2,1,1,2,1,3,1,1,1,3,7,1,4,1,1,2,1,\n        1,2,1,2,4,4,3,8,1,1,1,2,1,3,5,1,3,1,3,4,6,2,2,14,4,6,6,11,9,1,15,3,1,28,5,2,5,5,3,1,3,4,5,4,6,14,3,2,3,5,21,2,7,20,10,1,2,19,2,4,28,28,2,3,\n        2,1,14,4,1,26,28,42,12,40,3,52,79,5,14,17,3,2,2,11,3,4,6,3,1,8,2,23,4,5,8,10,4,2,7,3,5,1,1,6,3,1,2,2,2,5,28,1,1,7,7,20,5,3,29,3,17,26,1,8,4,\n        27,3,6,11,23,5,3,4,6,13,24,16,6,5,10,25,35,7,3,2,3,3,14,3,6,2,6,1,4,2,3,8,2,1,1,3,3,3,4,1,1,13,2,2,4,5,2,1,14,14,1,2,2,1,4,5,2,3,1,14,3,12,\n        3,17,2,16,5,1,2,1,8,9,3,19,4,2,2,4,17,25,21,20,28,75,1,10,29,103,4,1,2,1,1,4,2,4,1,2,3,24,2,2,2,1,1,2,1,3,8,1,1,1,2,1,1,3,1,1,1,6,1,5,3,1,1,\n        1,3,4,1,1,5,2,1,5,6,13,9,16,1,1,1,1,3,2,3,2,4,5,2,5,2,2,3,7,13,7,2,2,1,1,1,1,2,3,3,2,1,6,4,9,2,1,14,2,14,2,1,18,3,4,14,4,11,41,15,23,15,23,\n        176,1,3,4,1,1,1,1,5,3,1,2,3,7,3,1,1,2,1,2,4,4,6,2,4,1,9,7,1,10,5,8,16,29,1,1,2,2,3,1,3,5,2,4,5,4,1,1,2,2,3,3,7,1,6,10,1,17,1,44,4,6,2,1,1,6,\n        5,4,2,10,1,6,9,2,8,1,24,1,2,13,7,8,8,2,1,4,1,3,1,3,3,5,2,5,10,9,4,9,12,2,1,6,1,10,1,1,7,7,4,10,8,3,1,13,4,3,1,6,1,3,5,2,1,2,17,16,5,2,16,6,\n        1,4,2,1,3,3,6,8,5,11,11,1,3,3,2,4,6,10,9,5,7,4,7,4,7,1,1,4,2,1,3,6,8,7,1,6,11,5,5,3,24,9,4,2,7,13,5,1,8,82,16,61,1,1,1,4,2,2,16,10,3,8,1,1,\n        6,4,2,1,3,1,1,1,4,3,8,4,2,2,1,1,1,1,1,6,3,5,1,1,4,6,9,2,1,1,1,2,1,7,2,1,6,1,5,4,4,3,1,8,1,3,3,1,3,2,2,2,2,3,1,6,1,2,1,2,1,3,7,1,8,2,1,2,1,5,\n        2,5,3,5,10,1,2,1,1,3,2,5,11,3,9,3,5,1,1,5,9,1,2,1,5,7,9,9,8,1,3,3,3,6,8,2,3,2,1,1,32,6,1,2,15,9,3,7,13,1,3,10,13,2,14,1,13,10,2,1,3,10,4,15,\n        2,15,15,10,1,3,9,6,9,32,25,26,47,7,3,2,3,1,6,3,4,3,2,8,5,4,1,9,4,2,2,19,10,6,2,3,8,1,2,2,4,2,1,9,4,4,4,6,4,8,9,2,3,1,1,1,1,3,5,5,1,3,8,4,6,\n        2,1,4,12,1,5,3,7,13,2,5,8,1,6,1,2,5,14,6,1,5,2,4,8,15,5,1,23,6,62,2,10,1,1,8,1,2,2,10,4,2,2,9,2,1,1,3,2,3,1,5,3,3,2,1,3,8,1,1,1,11,3,1,1,4,\n        3,7,1,14,1,2,3,12,5,2,5,1,6,7,5,7,14,11,1,3,1,8,9,12,2,1,11,8,4,4,2,6,10,9,13,1,1,3,1,5,1,3,2,4,4,1,18,2,3,14,11,4,29,4,2,7,1,3,13,9,2,2,5,\n        3,5,20,7,16,8,5,72,34,6,4,22,12,12,28,45,36,9,7,39,9,191,1,1,1,4,11,8,4,9,2,3,22,1,1,1,1,4,17,1,7,7,1,11,31,10,2,4,8,2,3,2,1,4,2,16,4,32,2,\n        3,19,13,4,9,1,5,2,14,8,1,1,3,6,19,6,5,1,16,6,2,10,8,5,1,2,3,1,5,5,1,11,6,6,1,3,3,2,6,3,8,1,1,4,10,7,5,7,7,5,8,9,2,1,3,4,1,1,3,1,3,3,2,6,16,\n        1,4,6,3,1,10,6,1,3,15,2,9,2,10,25,13,9,16,6,2,2,10,11,4,3,9,1,2,6,6,5,4,30,40,1,10,7,12,14,33,6,3,6,7,3,1,3,1,11,14,4,9,5,12,11,49,18,51,31,\n        140,31,2,2,1,5,1,8,1,10,1,4,4,3,24,1,10,1,3,6,6,16,3,4,5,2,1,4,2,57,10,6,22,2,22,3,7,22,6,10,11,36,18,16,33,36,2,5,5,1,1,1,4,10,1,4,13,2,7,\n        5,2,9,3,4,1,7,43,3,7,3,9,14,7,9,1,11,1,1,3,7,4,18,13,1,14,1,3,6,10,73,2,2,30,6,1,11,18,19,13,22,3,46,42,37,89,7,3,16,34,2,2,3,9,1,7,1,1,1,2,\n        2,4,10,7,3,10,3,9,5,28,9,2,6,13,7,3,1,3,10,2,7,2,11,3,6,21,54,85,2,1,4,2,2,1,39,3,21,2,2,5,1,1,1,4,1,1,3,4,15,1,3,2,4,4,2,3,8,2,20,1,8,7,13,\n        4,1,26,6,2,9,34,4,21,52,10,4,4,1,5,12,2,11,1,7,2,30,12,44,2,30,1,1,3,6,16,9,17,39,82,2,2,24,7,1,7,3,16,9,14,44,2,1,2,1,2,3,5,2,4,1,6,7,5,3,\n        2,6,1,11,5,11,2,1,18,19,8,1,3,24,29,2,1,3,5,2,2,1,13,6,5,1,46,11,3,5,1,1,5,8,2,10,6,12,6,3,7,11,2,4,16,13,2,5,1,1,2,2,5,2,28,5,2,23,10,8,4,\n        4,22,39,95,38,8,14,9,5,1,13,5,4,3,13,12,11,1,9,1,27,37,2,5,4,4,63,211,95,2,2,2,1,3,5,2,1,1,2,2,1,1,1,3,2,4,1,2,1,1,5,2,2,1,1,2,3,1,3,1,1,1,\n        3,1,4,2,1,3,6,1,1,3,7,15,5,3,2,5,3,9,11,4,2,22,1,6,3,8,7,1,4,28,4,16,3,3,25,4,4,27,27,1,4,1,2,2,7,1,3,5,2,28,8,2,14,1,8,6,16,25,3,3,3,14,3,\n        3,1,1,2,1,4,6,3,8,4,1,1,1,2,3,6,10,6,2,3,18,3,2,5,5,4,3,1,5,2,5,4,23,7,6,12,6,4,17,11,9,5,1,1,10,5,12,1,1,11,26,33,7,3,6,1,17,7,1,5,12,1,11,\n        2,4,1,8,14,17,23,1,2,1,7,8,16,11,9,6,5,2,6,4,16,2,8,14,1,11,8,9,1,1,1,9,25,4,11,19,7,2,15,2,12,8,52,7,5,19,2,16,4,36,8,1,16,8,24,26,4,6,2,9,\n        5,4,36,3,28,12,25,15,37,27,17,12,59,38,5,32,127,1,2,9,17,14,4,1,2,1,1,8,11,50,4,14,2,19,16,4,17,5,4,5,26,12,45,2,23,45,104,30,12,8,3,10,2,2,\n        3,3,1,4,20,7,2,9,6,15,2,20,1,3,16,4,11,15,6,134,2,5,59,1,2,2,2,1,9,17,3,26,137,10,211,59,1,2,4,1,4,1,1,1,2,6,2,3,1,1,2,3,2,3,1,3,4,4,2,3,3,\n        1,4,3,1,7,2,2,3,1,2,1,3,3,3,2,2,3,2,1,3,14,6,1,3,2,9,6,15,27,9,34,145,1,1,2,1,1,1,1,2,1,1,1,1,2,2,2,3,1,2,1,1,1,2,3,5,8,3,5,2,4,1,3,2,2,2,12,\n        4,1,1,1,10,4,5,1,20,4,16,1,15,9,5,12,2,9,2,5,4,2,26,19,7,1,26,4,30,12,15,42,1,6,8,172,1,1,4,2,1,1,11,2,2,4,2,1,2,1,10,8,1,2,1,4,5,1,2,5,1,8,\n        4,1,3,4,2,1,6,2,1,3,4,1,2,1,1,1,1,12,5,7,2,4,3,1,1,1,3,3,6,1,2,2,3,3,3,2,1,2,12,14,11,6,6,4,12,2,8,1,7,10,1,35,7,4,13,15,4,3,23,21,28,52,5,\n        26,5,6,1,7,10,2,7,53,3,2,1,1,1,2,163,532,1,10,11,1,3,3,4,8,2,8,6,2,2,23,22,4,2,2,4,2,1,3,1,3,3,5,9,8,2,1,2,8,1,10,2,12,21,20,15,105,2,3,1,1,\n        3,2,3,1,1,2,5,1,4,15,11,19,1,1,1,1,5,4,5,1,1,2,5,3,5,12,1,2,5,1,11,1,1,15,9,1,4,5,3,26,8,2,1,3,1,1,15,19,2,12,1,2,5,2,7,2,19,2,20,6,26,7,5,\n        2,2,7,34,21,13,70,2,128,1,1,2,1,1,2,1,1,3,2,2,2,15,1,4,1,3,4,42,10,6,1,49,85,8,1,2,1,1,4,4,2,3,6,1,5,7,4,3,211,4,1,2,1,2,5,1,2,4,2,2,6,5,6,\n        10,3,4,48,100,6,2,16,296,5,27,387,2,2,3,7,16,8,5,38,15,39,21,9,10,3,7,59,13,27,21,47,5,21,6\n    };\n    static ImWchar base_ranges[] = // not zero-terminated\n    {\n        0x0020, 0x00FF, // Basic Latin + Latin Supplement\n        0x2000, 0x206F, // General Punctuation\n        0x3000, 0x30FF, // CJK Symbols and Punctuations, Hiragana, Katakana\n        0x31F0, 0x31FF, // Katakana Phonetic Extensions\n        0xFF00, 0xFFEF, // Half-width characters\n        0xFFFD, 0xFFFD  // Invalid\n    };\n    static ImWchar full_ranges[IM_ARRAYSIZE(base_ranges) + IM_ARRAYSIZE(accumulative_offsets_from_0x4E00) * 2 + 1] = { 0 };\n    if (!full_ranges[0])\n    {\n        memcpy(full_ranges, base_ranges, sizeof(base_ranges));\n        UnpackAccumulativeOffsetsIntoRanges(0x4E00, accumulative_offsets_from_0x4E00, IM_ARRAYSIZE(accumulative_offsets_from_0x4E00), full_ranges + IM_ARRAYSIZE(base_ranges));\n    }\n    return &full_ranges[0];\n}\n\nconst ImWchar*  ImFontAtlas::GetGlyphRangesJapanese()\n{\n    // 2999 ideograms code points for Japanese\n    // - 2136 Joyo (meaning \"for regular use\" or \"for common use\") Kanji code points\n    // - 863 Jinmeiyo (meaning \"for personal name\") Kanji code points\n    // - Sourced from official information provided by the government agencies of Japan:\n    //   - List of Joyo Kanji by the Agency for Cultural Affairs\n    //     - https://www.bunka.go.jp/kokugo_nihongo/sisaku/joho/joho/kijun/naikaku/kanji/\n    //   - List of Jinmeiyo Kanji by the Ministry of Justice\n    //     - http://www.moj.go.jp/MINJI/minji86.html\n    //   - Available under the terms of the Creative Commons Attribution 4.0 International (CC BY 4.0).\n    //     - https://creativecommons.org/licenses/by/4.0/legalcode\n    // - You can generate this code by the script at:\n    //   - https://github.com/vaiorabbit/everyday_use_kanji\n    // - References:\n    //   - List of Joyo Kanji\n    //     - (Wikipedia) https://en.wikipedia.org/wiki/List_of_j%C5%8Dy%C5%8D_kanji\n    //   - List of Jinmeiyo Kanji\n    //     - (Wikipedia) https://en.wikipedia.org/wiki/Jinmeiy%C5%8D_kanji\n    // - Missing 1 Joyo Kanji: U+20B9F (Kun'yomi: Shikaru, On'yomi: Shitsu,shichi), see https://github.com/ocornut/imgui/pull/3627 for details.\n    // You can use ImFontGlyphRangesBuilder to create your own ranges derived from this, by merging existing ranges or adding new characters.\n    // (Stored as accumulative offsets from the initial unicode codepoint 0x4E00. This encoding is designed to helps us compact the source code size.)\n    static const short accumulative_offsets_from_0x4E00[] =\n    {\n        0,1,2,4,1,1,1,1,2,1,3,3,2,2,1,5,3,5,7,5,6,1,2,1,7,2,6,3,1,8,1,1,4,1,1,18,2,11,2,6,2,1,2,1,5,1,2,1,3,1,2,1,2,3,3,1,1,2,3,1,1,1,12,7,9,1,4,5,1,\n        1,2,1,10,1,1,9,2,2,4,5,6,9,3,1,1,1,1,9,3,18,5,2,2,2,2,1,6,3,7,1,1,1,1,2,2,4,2,1,23,2,10,4,3,5,2,4,10,2,4,13,1,6,1,9,3,1,1,6,6,7,6,3,1,2,11,3,\n        2,2,3,2,15,2,2,5,4,3,6,4,1,2,5,2,12,16,6,13,9,13,2,1,1,7,16,4,7,1,19,1,5,1,2,2,7,7,8,2,6,5,4,9,18,7,4,5,9,13,11,8,15,2,1,1,1,2,1,2,2,1,2,2,8,\n        2,9,3,3,1,1,4,4,1,1,1,4,9,1,4,3,5,5,2,7,5,3,4,8,2,1,13,2,3,3,1,14,1,1,4,5,1,3,6,1,5,2,1,1,3,3,3,3,1,1,2,7,6,6,7,1,4,7,6,1,1,1,1,1,12,3,3,9,5,\n        2,6,1,5,6,1,2,3,18,2,4,14,4,1,3,6,1,1,6,3,5,5,3,2,2,2,2,12,3,1,4,2,3,2,3,11,1,7,4,1,2,1,3,17,1,9,1,24,1,1,4,2,2,4,1,2,7,1,1,1,3,1,2,2,4,15,1,\n        1,2,1,1,2,1,5,2,5,20,2,5,9,1,10,8,7,6,1,1,1,1,1,1,6,2,1,2,8,1,1,1,1,5,1,1,3,1,1,1,1,3,1,1,12,4,1,3,1,1,1,1,1,10,3,1,7,5,13,1,2,3,4,6,1,1,30,\n        2,9,9,1,15,38,11,3,1,8,24,7,1,9,8,10,2,1,9,31,2,13,6,2,9,4,49,5,2,15,2,1,10,2,1,1,1,2,2,6,15,30,35,3,14,18,8,1,16,10,28,12,19,45,38,1,3,2,3,\n        13,2,1,7,3,6,5,3,4,3,1,5,7,8,1,5,3,18,5,3,6,1,21,4,24,9,24,40,3,14,3,21,3,2,1,2,4,2,3,1,15,15,6,5,1,1,3,1,5,6,1,9,7,3,3,2,1,4,3,8,21,5,16,4,\n        5,2,10,11,11,3,6,3,2,9,3,6,13,1,2,1,1,1,1,11,12,6,6,1,4,2,6,5,2,1,1,3,3,6,13,3,1,1,5,1,2,3,3,14,2,1,2,2,2,5,1,9,5,1,1,6,12,3,12,3,4,13,2,14,\n        2,8,1,17,5,1,16,4,2,2,21,8,9,6,23,20,12,25,19,9,38,8,3,21,40,25,33,13,4,3,1,4,1,2,4,1,2,5,26,2,1,1,2,1,3,6,2,1,1,1,1,1,1,2,3,1,1,1,9,2,3,1,1,\n        1,3,6,3,2,1,1,6,6,1,8,2,2,2,1,4,1,2,3,2,7,3,2,4,1,2,1,2,2,1,1,1,1,1,3,1,2,5,4,10,9,4,9,1,1,1,1,1,1,5,3,2,1,6,4,9,6,1,10,2,31,17,8,3,7,5,40,1,\n        7,7,1,6,5,2,10,7,8,4,15,39,25,6,28,47,18,10,7,1,3,1,1,2,1,1,1,3,3,3,1,1,1,3,4,2,1,4,1,3,6,10,7,8,6,2,2,1,3,3,2,5,8,7,9,12,2,15,1,1,4,1,2,1,1,\n        1,3,2,1,3,3,5,6,2,3,2,10,1,4,2,8,1,1,1,11,6,1,21,4,16,3,1,3,1,4,2,3,6,5,1,3,1,1,3,3,4,6,1,1,10,4,2,7,10,4,7,4,2,9,4,3,1,1,1,4,1,8,3,4,1,3,1,\n        6,1,4,2,1,4,7,2,1,8,1,4,5,1,1,2,2,4,6,2,7,1,10,1,1,3,4,11,10,8,21,4,6,1,3,5,2,1,2,28,5,5,2,3,13,1,2,3,1,4,2,1,5,20,3,8,11,1,3,3,3,1,8,10,9,2,\n        10,9,2,3,1,1,2,4,1,8,3,6,1,7,8,6,11,1,4,29,8,4,3,1,2,7,13,1,4,1,6,2,6,12,12,2,20,3,2,3,6,4,8,9,2,7,34,5,1,18,6,1,1,4,4,5,7,9,1,2,2,4,3,4,1,7,\n        2,2,2,6,2,3,25,5,3,6,1,4,6,7,4,2,1,4,2,13,6,4,4,3,1,5,3,4,4,3,2,1,1,4,1,2,1,1,3,1,11,1,6,3,1,7,3,6,2,8,8,6,9,3,4,11,3,2,10,12,2,5,11,1,6,4,5,\n        3,1,8,5,4,6,6,3,5,1,1,3,2,1,2,2,6,17,12,1,10,1,6,12,1,6,6,19,9,6,16,1,13,4,4,15,7,17,6,11,9,15,12,6,7,2,1,2,2,15,9,3,21,4,6,49,18,7,3,2,3,1,\n        6,8,2,2,6,2,9,1,3,6,4,4,1,2,16,2,5,2,1,6,2,3,5,3,1,2,5,1,2,1,9,3,1,8,6,4,8,11,3,1,1,1,1,3,1,13,8,4,1,3,2,2,1,4,1,11,1,5,2,1,5,2,5,8,6,1,1,7,\n        4,3,8,3,2,7,2,1,5,1,5,2,4,7,6,2,8,5,1,11,4,5,3,6,18,1,2,13,3,3,1,21,1,1,4,1,4,1,1,1,8,1,2,2,7,1,2,4,2,2,9,2,1,1,1,4,3,6,3,12,5,1,1,1,5,6,3,2,\n        4,8,2,2,4,2,7,1,8,9,5,2,3,2,1,3,2,13,7,14,6,5,1,1,2,1,4,2,23,2,1,1,6,3,1,4,1,15,3,1,7,3,9,14,1,3,1,4,1,1,5,8,1,3,8,3,8,15,11,4,14,4,4,2,5,5,\n        1,7,1,6,14,7,7,8,5,15,4,8,6,5,6,2,1,13,1,20,15,11,9,2,5,6,2,11,2,6,2,5,1,5,8,4,13,19,25,4,1,1,11,1,34,2,5,9,14,6,2,2,6,1,1,14,1,3,14,13,1,6,\n        12,21,14,14,6,32,17,8,32,9,28,1,2,4,11,8,3,1,14,2,5,15,1,1,1,1,3,6,4,1,3,4,11,3,1,1,11,30,1,5,1,4,1,5,8,1,1,3,2,4,3,17,35,2,6,12,17,3,1,6,2,\n        1,1,12,2,7,3,3,2,1,16,2,8,3,6,5,4,7,3,3,8,1,9,8,5,1,2,1,3,2,8,1,2,9,12,1,1,2,3,8,3,24,12,4,3,7,5,8,3,3,3,3,3,3,1,23,10,3,1,2,2,6,3,1,16,1,16,\n        22,3,10,4,11,6,9,7,7,3,6,2,2,2,4,10,2,1,1,2,8,7,1,6,4,1,3,3,3,5,10,12,12,2,3,12,8,15,1,1,16,6,6,1,5,9,11,4,11,4,2,6,12,1,17,5,13,1,4,9,5,1,11,\n        2,1,8,1,5,7,28,8,3,5,10,2,17,3,38,22,1,2,18,12,10,4,38,18,1,4,44,19,4,1,8,4,1,12,1,4,31,12,1,14,7,75,7,5,10,6,6,13,3,2,11,11,3,2,5,28,15,6,18,\n        18,5,6,4,3,16,1,7,18,7,36,3,5,3,1,7,1,9,1,10,7,2,4,2,6,2,9,7,4,3,32,12,3,7,10,2,23,16,3,1,12,3,31,4,11,1,3,8,9,5,1,30,15,6,12,3,2,2,11,19,9,\n        14,2,6,2,3,19,13,17,5,3,3,25,3,14,1,1,1,36,1,3,2,19,3,13,36,9,13,31,6,4,16,34,2,5,4,2,3,3,5,1,1,1,4,3,1,17,3,2,3,5,3,1,3,2,3,5,6,3,12,11,1,3,\n        1,2,26,7,12,7,2,14,3,3,7,7,11,25,25,28,16,4,36,1,2,1,6,2,1,9,3,27,17,4,3,4,13,4,1,3,2,2,1,10,4,2,4,6,3,8,2,1,18,1,1,24,2,2,4,33,2,3,63,7,1,6,\n        40,7,3,4,4,2,4,15,18,1,16,1,1,11,2,41,14,1,3,18,13,3,2,4,16,2,17,7,15,24,7,18,13,44,2,2,3,6,1,1,7,5,1,7,1,4,3,3,5,10,8,2,3,1,8,1,1,27,4,2,1,\n        12,1,2,1,10,6,1,6,7,5,2,3,7,11,5,11,3,6,6,2,3,15,4,9,1,1,2,1,2,11,2,8,12,8,5,4,2,3,1,5,2,2,1,14,1,12,11,4,1,11,17,17,4,3,2,5,5,7,3,1,5,9,9,8,\n        2,5,6,6,13,13,2,1,2,6,1,2,2,49,4,9,1,2,10,16,7,8,4,3,2,23,4,58,3,29,1,14,19,19,11,11,2,7,5,1,3,4,6,2,18,5,12,12,17,17,3,3,2,4,1,6,2,3,4,3,1,\n        1,1,1,5,1,1,9,1,3,1,3,6,1,8,1,1,2,6,4,14,3,1,4,11,4,1,3,32,1,2,4,13,4,1,2,4,2,1,3,1,11,1,4,2,1,4,4,6,3,5,1,6,5,7,6,3,23,3,5,3,5,3,3,13,3,9,10,\n        1,12,10,2,3,18,13,7,160,52,4,2,2,3,2,14,5,4,12,4,6,4,1,20,4,11,6,2,12,27,1,4,1,2,2,7,4,5,2,28,3,7,25,8,3,19,3,6,10,2,2,1,10,2,5,4,1,3,4,1,5,\n        3,2,6,9,3,6,2,16,3,3,16,4,5,5,3,2,1,2,16,15,8,2,6,21,2,4,1,22,5,8,1,1,21,11,2,1,11,11,19,13,12,4,2,3,2,3,6,1,8,11,1,4,2,9,5,2,1,11,2,9,1,1,2,\n        14,31,9,3,4,21,14,4,8,1,7,2,2,2,5,1,4,20,3,3,4,10,1,11,9,8,2,1,4,5,14,12,14,2,17,9,6,31,4,14,1,20,13,26,5,2,7,3,6,13,2,4,2,19,6,2,2,18,9,3,5,\n        12,12,14,4,6,2,3,6,9,5,22,4,5,25,6,4,8,5,2,6,27,2,35,2,16,3,7,8,8,6,6,5,9,17,2,20,6,19,2,13,3,1,1,1,4,17,12,2,14,7,1,4,18,12,38,33,2,10,1,1,\n        2,13,14,17,11,50,6,33,20,26,74,16,23,45,50,13,38,33,6,6,7,4,4,2,1,3,2,5,8,7,8,9,3,11,21,9,13,1,3,10,6,7,1,2,2,18,5,5,1,9,9,2,68,9,19,13,2,5,\n        1,4,4,7,4,13,3,9,10,21,17,3,26,2,1,5,2,4,5,4,1,7,4,7,3,4,2,1,6,1,1,20,4,1,9,2,2,1,3,3,2,3,2,1,1,1,20,2,3,1,6,2,3,6,2,4,8,1,3,2,10,3,5,3,4,4,\n        3,4,16,1,6,1,10,2,4,2,1,1,2,10,11,2,2,3,1,24,31,4,10,10,2,5,12,16,164,15,4,16,7,9,15,19,17,1,2,1,1,5,1,1,1,1,1,3,1,4,3,1,3,1,3,1,2,1,1,3,3,7,\n        2,8,1,2,2,2,1,3,4,3,7,8,12,92,2,10,3,1,3,14,5,25,16,42,4,7,7,4,2,21,5,27,26,27,21,25,30,31,2,1,5,13,3,22,5,6,6,11,9,12,1,5,9,7,5,5,22,60,3,5,\n        13,1,1,8,1,1,3,3,2,1,9,3,3,18,4,1,2,3,7,6,3,1,2,3,9,1,3,1,3,2,1,3,1,1,1,2,1,11,3,1,6,9,1,3,2,3,1,2,1,5,1,1,4,3,4,1,2,2,4,4,1,7,2,1,2,2,3,5,13,\n        18,3,4,14,9,9,4,16,3,7,5,8,2,6,48,28,3,1,1,4,2,14,8,2,9,2,1,15,2,4,3,2,10,16,12,8,7,1,1,3,1,1,1,2,7,4,1,6,4,38,39,16,23,7,15,15,3,2,12,7,21,\n        37,27,6,5,4,8,2,10,8,8,6,5,1,2,1,3,24,1,16,17,9,23,10,17,6,1,51,55,44,13,294,9,3,6,2,4,2,2,15,1,1,1,13,21,17,68,14,8,9,4,1,4,9,3,11,7,1,1,1,\n        5,6,3,2,1,1,1,2,3,8,1,2,2,4,1,5,5,2,1,4,3,7,13,4,1,4,1,3,1,1,1,5,5,10,1,6,1,5,2,1,5,2,4,1,4,5,7,3,18,2,9,11,32,4,3,3,2,4,7,11,16,9,11,8,13,38,\n        32,8,4,2,1,1,2,1,2,4,4,1,1,1,4,1,21,3,11,1,16,1,1,6,1,3,2,4,9,8,57,7,44,1,3,3,13,3,10,1,1,7,5,2,7,21,47,63,3,15,4,7,1,16,1,1,2,8,2,3,42,15,4,\n        1,29,7,22,10,3,78,16,12,20,18,4,67,11,5,1,3,15,6,21,31,32,27,18,13,71,35,5,142,4,10,1,2,50,19,33,16,35,37,16,19,27,7,1,133,19,1,4,8,7,20,1,4,\n        4,1,10,3,1,6,1,2,51,5,40,15,24,43,22928,11,1,13,154,70,3,1,1,7,4,10,1,2,1,1,2,1,2,1,2,2,1,1,2,1,1,1,1,1,2,1,1,1,1,1,1,1,1,1,1,1,1,1,2,1,1,1,\n        3,2,1,1,1,1,2,1,1,\n    };\n    static ImWchar base_ranges[] = // not zero-terminated\n    {\n        0x0020, 0x00FF, // Basic Latin + Latin Supplement\n        0x3000, 0x30FF, // CJK Symbols and Punctuations, Hiragana, Katakana\n        0x31F0, 0x31FF, // Katakana Phonetic Extensions\n        0xFF00, 0xFFEF, // Half-width characters\n        0xFFFD, 0xFFFD  // Invalid\n    };\n    static ImWchar full_ranges[IM_ARRAYSIZE(base_ranges) + IM_ARRAYSIZE(accumulative_offsets_from_0x4E00)*2 + 1] = { 0 };\n    if (!full_ranges[0])\n    {\n        memcpy(full_ranges, base_ranges, sizeof(base_ranges));\n        UnpackAccumulativeOffsetsIntoRanges(0x4E00, accumulative_offsets_from_0x4E00, IM_ARRAYSIZE(accumulative_offsets_from_0x4E00), full_ranges + IM_ARRAYSIZE(base_ranges));\n    }\n    return &full_ranges[0];\n}\n\nconst ImWchar*  ImFontAtlas::GetGlyphRangesCyrillic()\n{\n    static const ImWchar ranges[] =\n    {\n        0x0020, 0x00FF, // Basic Latin + Latin Supplement\n        0x0400, 0x052F, // Cyrillic + Cyrillic Supplement\n        0x2DE0, 0x2DFF, // Cyrillic Extended-A\n        0xA640, 0xA69F, // Cyrillic Extended-B\n        0,\n    };\n    return &ranges[0];\n}\n\nconst ImWchar*  ImFontAtlas::GetGlyphRangesThai()\n{\n    static const ImWchar ranges[] =\n    {\n        0x0020, 0x00FF, // Basic Latin\n        0x2010, 0x205E, // Punctuations\n        0x0E00, 0x0E7F, // Thai\n        0,\n    };\n    return &ranges[0];\n}\n\nconst ImWchar*  ImFontAtlas::GetGlyphRangesVietnamese()\n{\n    static const ImWchar ranges[] =\n    {\n        0x0020, 0x00FF, // Basic Latin\n        0x0102, 0x0103,\n        0x0110, 0x0111,\n        0x0128, 0x0129,\n        0x0168, 0x0169,\n        0x01A0, 0x01A1,\n        0x01AF, 0x01B0,\n        0x1EA0, 0x1EF9,\n        0,\n    };\n    return &ranges[0];\n}\n\n//-----------------------------------------------------------------------------\n// [SECTION] ImFontGlyphRangesBuilder\n//-----------------------------------------------------------------------------\n\nvoid ImFontGlyphRangesBuilder::AddText(const char* text, const char* text_end)\n{\n    while (text_end ? (text < text_end) : *text)\n    {\n        unsigned int c = 0;\n        int c_len = ImTextCharFromUtf8(&c, text, text_end);\n        text += c_len;\n        if (c_len == 0)\n            break;\n        AddChar((ImWchar)c);\n    }\n}\n\nvoid ImFontGlyphRangesBuilder::AddRanges(const ImWchar* ranges)\n{\n    for (; ranges[0]; ranges += 2)\n        for (unsigned int c = ranges[0]; c <= ranges[1] && c <= IM_UNICODE_CODEPOINT_MAX; c++) //-V560\n            AddChar((ImWchar)c);\n}\n\nvoid ImFontGlyphRangesBuilder::BuildRanges(ImVector<ImWchar>* out_ranges)\n{\n    const int max_codepoint = IM_UNICODE_CODEPOINT_MAX;\n    for (int n = 0; n <= max_codepoint; n++)\n        if (GetBit(n))\n        {\n            out_ranges->push_back((ImWchar)n);\n            while (n < max_codepoint && GetBit(n + 1))\n                n++;\n            out_ranges->push_back((ImWchar)n);\n        }\n    out_ranges->push_back(0);\n}\n\n//-----------------------------------------------------------------------------\n// [SECTION] ImFont\n//-----------------------------------------------------------------------------\n\nImFont::ImFont()\n{\n    memset(this, 0, sizeof(*this));\n    Scale = 1.0f;\n}\n\nImFont::~ImFont()\n{\n    ClearOutputData();\n}\n\nvoid    ImFont::ClearOutputData()\n{\n    FontSize = 0.0f;\n    FallbackAdvanceX = 0.0f;\n    Glyphs.clear();\n    IndexAdvanceX.clear();\n    IndexLookup.clear();\n    FallbackGlyph = NULL;\n    ContainerAtlas = NULL;\n    DirtyLookupTables = true;\n    Ascent = Descent = 0.0f;\n    MetricsTotalSurface = 0;\n    memset(Used8kPagesMap, 0, sizeof(Used8kPagesMap));\n}\n\nstatic ImWchar FindFirstExistingGlyph(ImFont* font, const ImWchar* candidate_chars, int candidate_chars_count)\n{\n    for (int n = 0; n < candidate_chars_count; n++)\n        if (font->FindGlyphNoFallback(candidate_chars[n]) != NULL)\n            return candidate_chars[n];\n    return 0;\n}\n\nvoid ImFont::BuildLookupTable()\n{\n    int max_codepoint = 0;\n    for (int i = 0; i != Glyphs.Size; i++)\n        max_codepoint = ImMax(max_codepoint, (int)Glyphs[i].Codepoint);\n\n    // Build lookup table\n    IM_ASSERT(Glyphs.Size > 0 && \"Font has not loaded glyph!\");\n    IM_ASSERT(Glyphs.Size < 0xFFFF); // -1 is reserved\n    IndexAdvanceX.clear();\n    IndexLookup.clear();\n    DirtyLookupTables = false;\n    memset(Used8kPagesMap, 0, sizeof(Used8kPagesMap));\n    GrowIndex(max_codepoint + 1);\n    for (int i = 0; i < Glyphs.Size; i++)\n    {\n        int codepoint = (int)Glyphs[i].Codepoint;\n        IndexAdvanceX[codepoint] = Glyphs[i].AdvanceX;\n        IndexLookup[codepoint] = (ImU16)i;\n\n        // Mark 4K page as used\n        const int page_n = codepoint / 8192;\n        Used8kPagesMap[page_n >> 3] |= 1 << (page_n & 7);\n    }\n\n    // Create a glyph to handle TAB\n    // FIXME: Needs proper TAB handling but it needs to be contextualized (or we could arbitrary say that each string starts at \"column 0\" ?)\n    if (FindGlyph((ImWchar)' '))\n    {\n        if (Glyphs.back().Codepoint != '\\t')   // So we can call this function multiple times (FIXME: Flaky)\n            Glyphs.resize(Glyphs.Size + 1);\n        ImFontGlyph& tab_glyph = Glyphs.back();\n        tab_glyph = *FindGlyph((ImWchar)' ');\n        tab_glyph.Codepoint = '\\t';\n        tab_glyph.AdvanceX *= IM_TABSIZE;\n        IndexAdvanceX[(int)tab_glyph.Codepoint] = (float)tab_glyph.AdvanceX;\n        IndexLookup[(int)tab_glyph.Codepoint] = (ImU16)(Glyphs.Size - 1);\n    }\n\n    // Mark special glyphs as not visible (note that AddGlyph already mark as non-visible glyphs with zero-size polygons)\n    if (ImFontGlyph* glyph = (ImFontGlyph*)(void*)FindGlyph((ImWchar)' '))\n        glyph->Visible = false;\n    if (ImFontGlyph* glyph = (ImFontGlyph*)(void*)FindGlyph((ImWchar)'\\t'))\n        glyph->Visible = false;\n\n    // Setup Fallback character\n    const ImWchar fallback_chars[] = { (ImWchar)IM_UNICODE_CODEPOINT_INVALID, (ImWchar)'?', (ImWchar)' ' };\n    FallbackGlyph = FindGlyphNoFallback(FallbackChar);\n    if (FallbackGlyph == NULL)\n    {\n        FallbackChar = FindFirstExistingGlyph(this, fallback_chars, IM_ARRAYSIZE(fallback_chars));\n        FallbackGlyph = FindGlyphNoFallback(FallbackChar);\n        if (FallbackGlyph == NULL)\n        {\n            FallbackGlyph = &Glyphs.back();\n            FallbackChar = (ImWchar)FallbackGlyph->Codepoint;\n        }\n    }\n    FallbackAdvanceX = FallbackGlyph->AdvanceX;\n    for (int i = 0; i < max_codepoint + 1; i++)\n        if (IndexAdvanceX[i] < 0.0f)\n            IndexAdvanceX[i] = FallbackAdvanceX;\n\n    // Setup Ellipsis character. It is required for rendering elided text. We prefer using U+2026 (horizontal ellipsis).\n    // However some old fonts may contain ellipsis at U+0085. Here we auto-detect most suitable ellipsis character.\n    // FIXME: Note that 0x2026 is rarely included in our font ranges. Because of this we are more likely to use three individual dots.\n    const ImWchar ellipsis_chars[] = { Sources->EllipsisChar, (ImWchar)0x2026, (ImWchar)0x0085 };\n    const ImWchar dots_chars[] = { (ImWchar)'.', (ImWchar)0xFF0E };\n    if (EllipsisChar == 0)\n        EllipsisChar = FindFirstExistingGlyph(this, ellipsis_chars, IM_ARRAYSIZE(ellipsis_chars));\n    const ImWchar dot_char = FindFirstExistingGlyph(this, dots_chars, IM_ARRAYSIZE(dots_chars));\n    if (EllipsisChar != 0)\n    {\n        EllipsisCharCount = 1;\n        EllipsisWidth = EllipsisCharStep = FindGlyph(EllipsisChar)->X1;\n    }\n    else if (dot_char != 0)\n    {\n        const ImFontGlyph* dot_glyph = FindGlyph(dot_char);\n        EllipsisChar = dot_char;\n        EllipsisCharCount = 3;\n        EllipsisCharStep = (float)(int)(dot_glyph->X1 - dot_glyph->X0) + 1.0f;\n        EllipsisWidth = ImMax(dot_glyph->AdvanceX, dot_glyph->X0 + EllipsisCharStep * 3.0f - 1.0f); // FIXME: Slightly odd for normally mono-space fonts but since this is used for trailing contents.\n    }\n}\n\n// API is designed this way to avoid exposing the 8K page size\n// e.g. use with IsGlyphRangeUnused(0, 255)\nbool ImFont::IsGlyphRangeUnused(unsigned int c_begin, unsigned int c_last)\n{\n    unsigned int page_begin = (c_begin / 8192);\n    unsigned int page_last = (c_last / 8192);\n    for (unsigned int page_n = page_begin; page_n <= page_last; page_n++)\n        if ((page_n >> 3) < sizeof(Used8kPagesMap))\n            if (Used8kPagesMap[page_n >> 3] & (1 << (page_n & 7)))\n                return false;\n    return true;\n}\n\nvoid ImFont::GrowIndex(int new_size)\n{\n    IM_ASSERT(IndexAdvanceX.Size == IndexLookup.Size);\n    if (new_size <= IndexLookup.Size)\n        return;\n    IndexAdvanceX.resize(new_size, -1.0f);\n    IndexLookup.resize(new_size, (ImU16)-1);\n}\n\n// x0/y0/x1/y1 are offset from the character upper-left layout position, in pixels. Therefore x0/y0 are often fairly close to zero.\n// Not to be mistaken with texture coordinates, which are held by u0/v0/u1/v1 in normalized format (0.0..1.0 on each texture axis).\n// 'src' is not necessarily == 'this->Sources' because multiple source fonts+configs can be used to build one target font.\nvoid ImFont::AddGlyph(const ImFontConfig* src, ImWchar codepoint, float x0, float y0, float x1, float y1, float u0, float v0, float u1, float v1, float advance_x)\n{\n    if (src != NULL)\n    {\n        // Clamp & recenter if needed\n        const float advance_x_original = advance_x;\n        advance_x = ImClamp(advance_x, src->GlyphMinAdvanceX, src->GlyphMaxAdvanceX);\n        if (advance_x != advance_x_original)\n        {\n            float char_off_x = src->PixelSnapH ? ImTrunc((advance_x - advance_x_original) * 0.5f) : (advance_x - advance_x_original) * 0.5f;\n            x0 += char_off_x;\n            x1 += char_off_x;\n        }\n\n        // Snap to pixel\n        if (src->PixelSnapH)\n            advance_x = IM_ROUND(advance_x);\n\n        // Bake extra spacing\n        advance_x += src->GlyphExtraAdvanceX;\n    }\n\n    int glyph_idx = Glyphs.Size;\n    Glyphs.resize(Glyphs.Size + 1);\n    ImFontGlyph& glyph = Glyphs[glyph_idx];\n    glyph.Codepoint = (unsigned int)codepoint;\n    glyph.Visible = (x0 != x1) && (y0 != y1);\n    glyph.Colored = false;\n    glyph.X0 = x0;\n    glyph.Y0 = y0;\n    glyph.X1 = x1;\n    glyph.Y1 = y1;\n    glyph.U0 = u0;\n    glyph.V0 = v0;\n    glyph.U1 = u1;\n    glyph.V1 = v1;\n    glyph.AdvanceX = advance_x;\n    IM_ASSERT(Glyphs.Size < 0xFFFF); // IndexLookup[] hold 16-bit values and -1 is reserved.\n\n    // Compute rough surface usage metrics (+1 to account for average padding, +0.99 to round)\n    // We use (U1-U0)*TexWidth instead of X1-X0 to account for oversampling.\n    float pad = ContainerAtlas->TexGlyphPadding + 0.99f;\n    DirtyLookupTables = true;\n    MetricsTotalSurface += (int)((glyph.U1 - glyph.U0) * ContainerAtlas->TexWidth + pad) * (int)((glyph.V1 - glyph.V0) * ContainerAtlas->TexHeight + pad);\n}\n\nvoid ImFont::AddRemapChar(ImWchar dst, ImWchar src, bool overwrite_dst)\n{\n    IM_ASSERT(IndexLookup.Size > 0);    // Currently this can only be called AFTER the font has been built, aka after calling ImFontAtlas::GetTexDataAs*() function.\n    unsigned int index_size = (unsigned int)IndexLookup.Size;\n\n    if (dst < index_size && IndexLookup.Data[dst] == (ImU16)-1 && !overwrite_dst) // 'dst' already exists\n        return;\n    if (src >= index_size && dst >= index_size) // both 'dst' and 'src' don't exist -> no-op\n        return;\n\n    GrowIndex(dst + 1);\n    IndexLookup[dst] = (src < index_size) ? IndexLookup.Data[src] : (ImU16)-1;\n    IndexAdvanceX[dst] = (src < index_size) ? IndexAdvanceX.Data[src] : 1.0f;\n}\n\n// Find glyph, return fallback if missing\nImFontGlyph* ImFont::FindGlyph(ImWchar c)\n{\n    if (c >= (size_t)IndexLookup.Size)\n        return FallbackGlyph;\n    const ImU16 i = IndexLookup.Data[c];\n    if (i == (ImU16)-1)\n        return FallbackGlyph;\n    return &Glyphs.Data[i];\n}\n\nImFontGlyph* ImFont::FindGlyphNoFallback(ImWchar c)\n{\n    if (c >= (size_t)IndexLookup.Size)\n        return NULL;\n    const ImU16 i = IndexLookup.Data[c];\n    if (i == (ImU16)-1)\n        return NULL;\n    return &Glyphs.Data[i];\n}\n\n// Trim trailing space and find beginning of next line\nstatic inline const char* CalcWordWrapNextLineStartA(const char* text, const char* text_end)\n{\n    while (text < text_end && ImCharIsBlankA(*text))\n        text++;\n    if (*text == '\\n')\n        text++;\n    return text;\n}\n\n#define ImFontGetCharAdvanceX(_FONT, _CH)  ((int)(_CH) < (_FONT)->IndexAdvanceX.Size ? (_FONT)->IndexAdvanceX.Data[_CH] : (_FONT)->FallbackAdvanceX)\n\n// Simple word-wrapping for English, not full-featured. Please submit failing cases!\n// This will return the next location to wrap from. If no wrapping if necessary, this will fast-forward to e.g. text_end.\n// FIXME: Much possible improvements (don't cut things like \"word !\", \"word!!!\" but cut within \"word,,,,\", more sensible support for punctuations, support for Unicode punctuations, etc.)\nconst char* ImFont::CalcWordWrapPositionA(float scale, const char* text, const char* text_end, float wrap_width)\n{\n    // For references, possible wrap point marked with ^\n    //  \"aaa bbb, ccc,ddd. eee   fff. ggg!\"\n    //      ^    ^    ^   ^   ^__    ^    ^\n\n    // List of hardcoded separators: .,;!?'\"\n\n    // Skip extra blanks after a line returns (that includes not counting them in width computation)\n    // e.g. \"Hello    world\" --> \"Hello\" \"World\"\n\n    // Cut words that cannot possibly fit within one line.\n    // e.g.: \"The tropical fish\" with ~5 characters worth of width --> \"The tr\" \"opical\" \"fish\"\n    float line_width = 0.0f;\n    float word_width = 0.0f;\n    float blank_width = 0.0f;\n    wrap_width /= scale; // We work with unscaled widths to avoid scaling every characters\n\n    const char* word_end = text;\n    const char* prev_word_end = NULL;\n    bool inside_word = true;\n\n    const char* s = text;\n    IM_ASSERT(text_end != NULL);\n    while (s < text_end)\n    {\n        unsigned int c = (unsigned int)*s;\n        const char* next_s;\n        if (c < 0x80)\n            next_s = s + 1;\n        else\n            next_s = s + ImTextCharFromUtf8(&c, s, text_end);\n\n        if (c < 32)\n        {\n            if (c == '\\n')\n            {\n                line_width = word_width = blank_width = 0.0f;\n                inside_word = true;\n                s = next_s;\n                continue;\n            }\n            if (c == '\\r')\n            {\n                s = next_s;\n                continue;\n            }\n        }\n\n        const float char_width = ImFontGetCharAdvanceX(this, c);\n        if (ImCharIsBlankW(c))\n        {\n            if (inside_word)\n            {\n                line_width += blank_width;\n                blank_width = 0.0f;\n                word_end = s;\n            }\n            blank_width += char_width;\n            inside_word = false;\n        }\n        else\n        {\n            word_width += char_width;\n            if (inside_word)\n            {\n                word_end = next_s;\n            }\n            else\n            {\n                prev_word_end = word_end;\n                line_width += word_width + blank_width;\n                word_width = blank_width = 0.0f;\n            }\n\n            // Allow wrapping after punctuation.\n            inside_word = (c != '.' && c != ',' && c != ';' && c != '!' && c != '?' && c != '\\\"');\n        }\n\n        // We ignore blank width at the end of the line (they can be skipped)\n        if (line_width + word_width > wrap_width)\n        {\n            // Words that cannot possibly fit within an entire line will be cut anywhere.\n            if (word_width < wrap_width)\n                s = prev_word_end ? prev_word_end : word_end;\n            break;\n        }\n\n        s = next_s;\n    }\n\n    // Wrap_width is too small to fit anything. Force displaying 1 character to minimize the height discontinuity.\n    // +1 may not be a character start point in UTF-8 but it's ok because caller loops use (text >= word_wrap_eol).\n    if (s == text && text < text_end)\n        return s + 1;\n    return s;\n}\n\nImVec2 ImFont::CalcTextSizeA(float size, float max_width, float wrap_width, const char* text_begin, const char* text_end, const char** remaining)\n{\n    if (!text_end)\n        text_end = text_begin + ImStrlen(text_begin); // FIXME-OPT: Need to avoid this.\n\n    const float line_height = size;\n    const float scale = size / FontSize;\n\n    ImVec2 text_size = ImVec2(0, 0);\n    float line_width = 0.0f;\n\n    const bool word_wrap_enabled = (wrap_width > 0.0f);\n    const char* word_wrap_eol = NULL;\n\n    const char* s = text_begin;\n    while (s < text_end)\n    {\n        if (word_wrap_enabled)\n        {\n            // Calculate how far we can render. Requires two passes on the string data but keeps the code simple and not intrusive for what's essentially an uncommon feature.\n            if (!word_wrap_eol)\n                word_wrap_eol = CalcWordWrapPositionA(scale, s, text_end, wrap_width - line_width);\n\n            if (s >= word_wrap_eol)\n            {\n                if (text_size.x < line_width)\n                    text_size.x = line_width;\n                text_size.y += line_height;\n                line_width = 0.0f;\n                word_wrap_eol = NULL;\n                s = CalcWordWrapNextLineStartA(s, text_end); // Wrapping skips upcoming blanks\n                continue;\n            }\n        }\n\n        // Decode and advance source\n        const char* prev_s = s;\n        unsigned int c = (unsigned int)*s;\n        if (c < 0x80)\n            s += 1;\n        else\n            s += ImTextCharFromUtf8(&c, s, text_end);\n\n        if (c < 32)\n        {\n            if (c == '\\n')\n            {\n                text_size.x = ImMax(text_size.x, line_width);\n                text_size.y += line_height;\n                line_width = 0.0f;\n                continue;\n            }\n            if (c == '\\r')\n                continue;\n        }\n\n        const float char_width = ImFontGetCharAdvanceX(this, c) * scale;\n        if (line_width + char_width >= max_width)\n        {\n            s = prev_s;\n            break;\n        }\n\n        line_width += char_width;\n    }\n\n    if (text_size.x < line_width)\n        text_size.x = line_width;\n\n    if (line_width > 0 || text_size.y == 0.0f)\n        text_size.y += line_height;\n\n    if (remaining)\n        *remaining = s;\n\n    return text_size;\n}\n\n// Note: as with every ImDrawList drawing function, this expects that the font atlas texture is bound.\nvoid ImFont::RenderChar(ImDrawList* draw_list, float size, const ImVec2& pos, ImU32 col, ImWchar c)\n{\n    const ImFontGlyph* glyph = FindGlyph(c);\n    if (!glyph || !glyph->Visible)\n        return;\n    if (glyph->Colored)\n        col |= ~IM_COL32_A_MASK;\n    float scale = (size >= 0.0f) ? (size / FontSize) : 1.0f;\n    float x = IM_TRUNC(pos.x);\n    float y = IM_TRUNC(pos.y);\n    draw_list->PrimReserve(6, 4);\n    draw_list->PrimRectUV(ImVec2(x + glyph->X0 * scale, y + glyph->Y0 * scale), ImVec2(x + glyph->X1 * scale, y + glyph->Y1 * scale), ImVec2(glyph->U0, glyph->V0), ImVec2(glyph->U1, glyph->V1), col);\n}\n\n// Note: as with every ImDrawList drawing function, this expects that the font atlas texture is bound.\nvoid ImFont::RenderText(ImDrawList* draw_list, float size, const ImVec2& pos, ImU32 col, const ImVec4& clip_rect, const char* text_begin, const char* text_end, float wrap_width, bool cpu_fine_clip)\n{\n    // Align to be pixel perfect\n    float x = IM_TRUNC(pos.x);\n    float y = IM_TRUNC(pos.y);\n    if (y > clip_rect.w)\n        return;\n\n    if (!text_end)\n        text_end = text_begin + ImStrlen(text_begin); // ImGui:: functions generally already provides a valid text_end, so this is merely to handle direct calls.\n\n    const float scale = size / FontSize;\n    const float line_height = FontSize * scale;\n    const float origin_x = x;\n    const bool word_wrap_enabled = (wrap_width > 0.0f);\n\n    // Fast-forward to first visible line\n    const char* s = text_begin;\n    if (y + line_height < clip_rect.y)\n        while (y + line_height < clip_rect.y && s < text_end)\n        {\n            const char* line_end = (const char*)ImMemchr(s, '\\n', text_end - s);\n            if (word_wrap_enabled)\n            {\n                // FIXME-OPT: This is not optimal as do first do a search for \\n before calling CalcWordWrapPositionA().\n                // If the specs for CalcWordWrapPositionA() were reworked to optionally return on \\n we could combine both.\n                // However it is still better than nothing performing the fast-forward!\n                s = CalcWordWrapPositionA(scale, s, line_end ? line_end : text_end, wrap_width);\n                s = CalcWordWrapNextLineStartA(s, text_end);\n            }\n            else\n            {\n                s = line_end ? line_end + 1 : text_end;\n            }\n            y += line_height;\n        }\n\n    // For large text, scan for the last visible line in order to avoid over-reserving in the call to PrimReserve()\n    // Note that very large horizontal line will still be affected by the issue (e.g. a one megabyte string buffer without a newline will likely crash atm)\n    if (text_end - s > 10000 && !word_wrap_enabled)\n    {\n        const char* s_end = s;\n        float y_end = y;\n        while (y_end < clip_rect.w && s_end < text_end)\n        {\n            s_end = (const char*)ImMemchr(s_end, '\\n', text_end - s_end);\n            s_end = s_end ? s_end + 1 : text_end;\n            y_end += line_height;\n        }\n        text_end = s_end;\n    }\n    if (s == text_end)\n        return;\n\n    // Reserve vertices for remaining worse case (over-reserving is useful and easily amortized)\n    const int vtx_count_max = (int)(text_end - s) * 4;\n    const int idx_count_max = (int)(text_end - s) * 6;\n    const int idx_expected_size = draw_list->IdxBuffer.Size + idx_count_max;\n    draw_list->PrimReserve(idx_count_max, vtx_count_max);\n    ImDrawVert*  vtx_write = draw_list->_VtxWritePtr;\n    ImDrawIdx*   idx_write = draw_list->_IdxWritePtr;\n    unsigned int vtx_index = draw_list->_VtxCurrentIdx;\n\n    const ImU32 col_untinted = col | ~IM_COL32_A_MASK;\n    const char* word_wrap_eol = NULL;\n\n    while (s < text_end)\n    {\n        if (word_wrap_enabled)\n        {\n            // Calculate how far we can render. Requires two passes on the string data but keeps the code simple and not intrusive for what's essentially an uncommon feature.\n            if (!word_wrap_eol)\n                word_wrap_eol = CalcWordWrapPositionA(scale, s, text_end, wrap_width - (x - origin_x));\n\n            if (s >= word_wrap_eol)\n            {\n                x = origin_x;\n                y += line_height;\n                if (y > clip_rect.w)\n                    break; // break out of main loop\n                word_wrap_eol = NULL;\n                s = CalcWordWrapNextLineStartA(s, text_end); // Wrapping skips upcoming blanks\n                continue;\n            }\n        }\n\n        // Decode and advance source\n        unsigned int c = (unsigned int)*s;\n        if (c < 0x80)\n            s += 1;\n        else\n            s += ImTextCharFromUtf8(&c, s, text_end);\n\n        if (c < 32)\n        {\n            if (c == '\\n')\n            {\n                x = origin_x;\n                y += line_height;\n                if (y > clip_rect.w)\n                    break; // break out of main loop\n                continue;\n            }\n            if (c == '\\r')\n                continue;\n        }\n\n        const ImFontGlyph* glyph = FindGlyph((ImWchar)c);\n        if (glyph == NULL)\n            continue;\n\n        float char_width = glyph->AdvanceX * scale;\n        if (glyph->Visible)\n        {\n            // We don't do a second finer clipping test on the Y axis as we've already skipped anything before clip_rect.y and exit once we pass clip_rect.w\n            float x1 = x + glyph->X0 * scale;\n            float x2 = x + glyph->X1 * scale;\n            float y1 = y + glyph->Y0 * scale;\n            float y2 = y + glyph->Y1 * scale;\n            if (x1 <= clip_rect.z && x2 >= clip_rect.x)\n            {\n                // Render a character\n                float u1 = glyph->U0;\n                float v1 = glyph->V0;\n                float u2 = glyph->U1;\n                float v2 = glyph->V1;\n\n                // CPU side clipping used to fit text in their frame when the frame is too small. Only does clipping for axis aligned quads.\n                if (cpu_fine_clip)\n                {\n                    if (x1 < clip_rect.x)\n                    {\n                        u1 = u1 + (1.0f - (x2 - clip_rect.x) / (x2 - x1)) * (u2 - u1);\n                        x1 = clip_rect.x;\n                    }\n                    if (y1 < clip_rect.y)\n                    {\n                        v1 = v1 + (1.0f - (y2 - clip_rect.y) / (y2 - y1)) * (v2 - v1);\n                        y1 = clip_rect.y;\n                    }\n                    if (x2 > clip_rect.z)\n                    {\n                        u2 = u1 + ((clip_rect.z - x1) / (x2 - x1)) * (u2 - u1);\n                        x2 = clip_rect.z;\n                    }\n                    if (y2 > clip_rect.w)\n                    {\n                        v2 = v1 + ((clip_rect.w - y1) / (y2 - y1)) * (v2 - v1);\n                        y2 = clip_rect.w;\n                    }\n                    if (y1 >= y2)\n                    {\n                        x += char_width;\n                        continue;\n                    }\n                }\n\n                // Support for untinted glyphs\n                ImU32 glyph_col = glyph->Colored ? col_untinted : col;\n\n                // We are NOT calling PrimRectUV() here because non-inlined causes too much overhead in a debug builds. Inlined here:\n                {\n                    vtx_write[0].pos.x = x1; vtx_write[0].pos.y = y1; vtx_write[0].col = glyph_col; vtx_write[0].uv.x = u1; vtx_write[0].uv.y = v1;\n                    vtx_write[1].pos.x = x2; vtx_write[1].pos.y = y1; vtx_write[1].col = glyph_col; vtx_write[1].uv.x = u2; vtx_write[1].uv.y = v1;\n                    vtx_write[2].pos.x = x2; vtx_write[2].pos.y = y2; vtx_write[2].col = glyph_col; vtx_write[2].uv.x = u2; vtx_write[2].uv.y = v2;\n                    vtx_write[3].pos.x = x1; vtx_write[3].pos.y = y2; vtx_write[3].col = glyph_col; vtx_write[3].uv.x = u1; vtx_write[3].uv.y = v2;\n                    idx_write[0] = (ImDrawIdx)(vtx_index); idx_write[1] = (ImDrawIdx)(vtx_index + 1); idx_write[2] = (ImDrawIdx)(vtx_index + 2);\n                    idx_write[3] = (ImDrawIdx)(vtx_index); idx_write[4] = (ImDrawIdx)(vtx_index + 2); idx_write[5] = (ImDrawIdx)(vtx_index + 3);\n                    vtx_write += 4;\n                    vtx_index += 4;\n                    idx_write += 6;\n                }\n            }\n        }\n        x += char_width;\n    }\n\n    // Give back unused vertices (clipped ones, blanks) ~ this is essentially a PrimUnreserve() action.\n    draw_list->VtxBuffer.Size = (int)(vtx_write - draw_list->VtxBuffer.Data); // Same as calling shrink()\n    draw_list->IdxBuffer.Size = (int)(idx_write - draw_list->IdxBuffer.Data);\n    draw_list->CmdBuffer[draw_list->CmdBuffer.Size - 1].ElemCount -= (idx_expected_size - draw_list->IdxBuffer.Size);\n    draw_list->_VtxWritePtr = vtx_write;\n    draw_list->_IdxWritePtr = idx_write;\n    draw_list->_VtxCurrentIdx = vtx_index;\n}\n\n//-----------------------------------------------------------------------------\n// [SECTION] ImGui Internal Render Helpers\n//-----------------------------------------------------------------------------\n// Vaguely redesigned to stop accessing ImGui global state:\n// - RenderArrow()\n// - RenderBullet()\n// - RenderCheckMark()\n// - RenderArrowPointingAt()\n// - RenderRectFilledRangeH()\n// - RenderRectFilledWithHole()\n//-----------------------------------------------------------------------------\n// Function in need of a redesign (legacy mess)\n// - RenderColorRectWithAlphaCheckerboard()\n//-----------------------------------------------------------------------------\n\n// Render an arrow aimed to be aligned with text (p_min is a position in the same space text would be positioned). To e.g. denote expanded/collapsed state\nvoid ImGui::RenderArrow(ImDrawList* draw_list, ImVec2 pos, ImU32 col, ImGuiDir dir, float scale)\n{\n    const float h = draw_list->_Data->FontSize * 1.00f;\n    float r = h * 0.40f * scale;\n    ImVec2 center = pos + ImVec2(h * 0.50f, h * 0.50f * scale);\n\n    ImVec2 a, b, c;\n    switch (dir)\n    {\n    case ImGuiDir_Up:\n    case ImGuiDir_Down:\n        if (dir == ImGuiDir_Up) r = -r;\n        a = ImVec2(+0.000f, +0.750f) * r;\n        b = ImVec2(-0.866f, -0.750f) * r;\n        c = ImVec2(+0.866f, -0.750f) * r;\n        break;\n    case ImGuiDir_Left:\n    case ImGuiDir_Right:\n        if (dir == ImGuiDir_Left) r = -r;\n        a = ImVec2(+0.750f, +0.000f) * r;\n        b = ImVec2(-0.750f, +0.866f) * r;\n        c = ImVec2(-0.750f, -0.866f) * r;\n        break;\n    case ImGuiDir_None:\n    case ImGuiDir_COUNT:\n        IM_ASSERT(0);\n        break;\n    }\n    draw_list->AddTriangleFilled(center + a, center + b, center + c, col);\n}\n\nvoid ImGui::RenderBullet(ImDrawList* draw_list, ImVec2 pos, ImU32 col)\n{\n    // FIXME-OPT: This should be baked in font.\n    draw_list->AddCircleFilled(pos, draw_list->_Data->FontSize * 0.20f, col, 8);\n}\n\nvoid ImGui::RenderCheckMark(ImDrawList* draw_list, ImVec2 pos, ImU32 col, float sz)\n{\n    float thickness = ImMax(sz / 5.0f, 1.0f);\n    sz -= thickness * 0.5f;\n    pos += ImVec2(thickness * 0.25f, thickness * 0.25f);\n\n    float third = sz / 3.0f;\n    float bx = pos.x + third;\n    float by = pos.y + sz - third * 0.5f;\n    draw_list->PathLineTo(ImVec2(bx - third, by - third));\n    draw_list->PathLineTo(ImVec2(bx, by));\n    draw_list->PathLineTo(ImVec2(bx + third * 2.0f, by - third * 2.0f));\n    draw_list->PathStroke(col, 0, thickness);\n}\n\n// Render an arrow. 'pos' is position of the arrow tip. half_sz.x is length from base to tip. half_sz.y is length on each side.\nvoid ImGui::RenderArrowPointingAt(ImDrawList* draw_list, ImVec2 pos, ImVec2 half_sz, ImGuiDir direction, ImU32 col)\n{\n    switch (direction)\n    {\n    case ImGuiDir_Left:  draw_list->AddTriangleFilled(ImVec2(pos.x + half_sz.x, pos.y - half_sz.y), ImVec2(pos.x + half_sz.x, pos.y + half_sz.y), pos, col); return;\n    case ImGuiDir_Right: draw_list->AddTriangleFilled(ImVec2(pos.x - half_sz.x, pos.y + half_sz.y), ImVec2(pos.x - half_sz.x, pos.y - half_sz.y), pos, col); return;\n    case ImGuiDir_Up:    draw_list->AddTriangleFilled(ImVec2(pos.x + half_sz.x, pos.y + half_sz.y), ImVec2(pos.x - half_sz.x, pos.y + half_sz.y), pos, col); return;\n    case ImGuiDir_Down:  draw_list->AddTriangleFilled(ImVec2(pos.x - half_sz.x, pos.y - half_sz.y), ImVec2(pos.x + half_sz.x, pos.y - half_sz.y), pos, col); return;\n    case ImGuiDir_None: case ImGuiDir_COUNT: break; // Fix warnings\n    }\n}\n\nstatic inline float ImAcos01(float x)\n{\n    if (x <= 0.0f) return IM_PI * 0.5f;\n    if (x >= 1.0f) return 0.0f;\n    return ImAcos(x);\n    //return (-0.69813170079773212f * x * x - 0.87266462599716477f) * x + 1.5707963267948966f; // Cheap approximation, may be enough for what we do.\n}\n\n// FIXME: Cleanup and move code to ImDrawList.\nvoid ImGui::RenderRectFilledRangeH(ImDrawList* draw_list, const ImRect& rect, ImU32 col, float x_start_norm, float x_end_norm, float rounding)\n{\n    if (x_end_norm == x_start_norm)\n        return;\n    if (x_start_norm > x_end_norm)\n        ImSwap(x_start_norm, x_end_norm);\n\n    ImVec2 p0 = ImVec2(ImLerp(rect.Min.x, rect.Max.x, x_start_norm), rect.Min.y);\n    ImVec2 p1 = ImVec2(ImLerp(rect.Min.x, rect.Max.x, x_end_norm), rect.Max.y);\n    if (rounding == 0.0f)\n    {\n        draw_list->AddRectFilled(p0, p1, col, 0.0f);\n        return;\n    }\n\n    rounding = ImClamp(ImMin((rect.Max.x - rect.Min.x) * 0.5f, (rect.Max.y - rect.Min.y) * 0.5f) - 1.0f, 0.0f, rounding);\n    const float inv_rounding = 1.0f / rounding;\n    const float arc0_b = ImAcos01(1.0f - (p0.x - rect.Min.x) * inv_rounding);\n    const float arc0_e = ImAcos01(1.0f - (p1.x - rect.Min.x) * inv_rounding);\n    const float half_pi = IM_PI * 0.5f; // We will == compare to this because we know this is the exact value ImAcos01 can return.\n    const float x0 = ImMax(p0.x, rect.Min.x + rounding);\n    if (arc0_b == arc0_e)\n    {\n        draw_list->PathLineTo(ImVec2(x0, p1.y));\n        draw_list->PathLineTo(ImVec2(x0, p0.y));\n    }\n    else if (arc0_b == 0.0f && arc0_e == half_pi)\n    {\n        draw_list->PathArcToFast(ImVec2(x0, p1.y - rounding), rounding, 3, 6); // BL\n        draw_list->PathArcToFast(ImVec2(x0, p0.y + rounding), rounding, 6, 9); // TR\n    }\n    else\n    {\n        draw_list->PathArcTo(ImVec2(x0, p1.y - rounding), rounding, IM_PI - arc0_e, IM_PI - arc0_b); // BL\n        draw_list->PathArcTo(ImVec2(x0, p0.y + rounding), rounding, IM_PI + arc0_b, IM_PI + arc0_e); // TR\n    }\n    if (p1.x > rect.Min.x + rounding)\n    {\n        const float arc1_b = ImAcos01(1.0f - (rect.Max.x - p1.x) * inv_rounding);\n        const float arc1_e = ImAcos01(1.0f - (rect.Max.x - p0.x) * inv_rounding);\n        const float x1 = ImMin(p1.x, rect.Max.x - rounding);\n        if (arc1_b == arc1_e)\n        {\n            draw_list->PathLineTo(ImVec2(x1, p0.y));\n            draw_list->PathLineTo(ImVec2(x1, p1.y));\n        }\n        else if (arc1_b == 0.0f && arc1_e == half_pi)\n        {\n            draw_list->PathArcToFast(ImVec2(x1, p0.y + rounding), rounding, 9, 12); // TR\n            draw_list->PathArcToFast(ImVec2(x1, p1.y - rounding), rounding, 0, 3);  // BR\n        }\n        else\n        {\n            draw_list->PathArcTo(ImVec2(x1, p0.y + rounding), rounding, -arc1_e, -arc1_b); // TR\n            draw_list->PathArcTo(ImVec2(x1, p1.y - rounding), rounding, +arc1_b, +arc1_e); // BR\n        }\n    }\n    draw_list->PathFillConvex(col);\n}\n\nvoid ImGui::RenderRectFilledWithHole(ImDrawList* draw_list, const ImRect& outer, const ImRect& inner, ImU32 col, float rounding)\n{\n    const bool fill_L = (inner.Min.x > outer.Min.x);\n    const bool fill_R = (inner.Max.x < outer.Max.x);\n    const bool fill_U = (inner.Min.y > outer.Min.y);\n    const bool fill_D = (inner.Max.y < outer.Max.y);\n    if (fill_L) draw_list->AddRectFilled(ImVec2(outer.Min.x, inner.Min.y), ImVec2(inner.Min.x, inner.Max.y), col, rounding, ImDrawFlags_RoundCornersNone | (fill_U ? 0 : ImDrawFlags_RoundCornersTopLeft)    | (fill_D ? 0 : ImDrawFlags_RoundCornersBottomLeft));\n    if (fill_R) draw_list->AddRectFilled(ImVec2(inner.Max.x, inner.Min.y), ImVec2(outer.Max.x, inner.Max.y), col, rounding, ImDrawFlags_RoundCornersNone | (fill_U ? 0 : ImDrawFlags_RoundCornersTopRight)   | (fill_D ? 0 : ImDrawFlags_RoundCornersBottomRight));\n    if (fill_U) draw_list->AddRectFilled(ImVec2(inner.Min.x, outer.Min.y), ImVec2(inner.Max.x, inner.Min.y), col, rounding, ImDrawFlags_RoundCornersNone | (fill_L ? 0 : ImDrawFlags_RoundCornersTopLeft)    | (fill_R ? 0 : ImDrawFlags_RoundCornersTopRight));\n    if (fill_D) draw_list->AddRectFilled(ImVec2(inner.Min.x, inner.Max.y), ImVec2(inner.Max.x, outer.Max.y), col, rounding, ImDrawFlags_RoundCornersNone | (fill_L ? 0 : ImDrawFlags_RoundCornersBottomLeft) | (fill_R ? 0 : ImDrawFlags_RoundCornersBottomRight));\n    if (fill_L && fill_U) draw_list->AddRectFilled(ImVec2(outer.Min.x, outer.Min.y), ImVec2(inner.Min.x, inner.Min.y), col, rounding, ImDrawFlags_RoundCornersTopLeft);\n    if (fill_R && fill_U) draw_list->AddRectFilled(ImVec2(inner.Max.x, outer.Min.y), ImVec2(outer.Max.x, inner.Min.y), col, rounding, ImDrawFlags_RoundCornersTopRight);\n    if (fill_L && fill_D) draw_list->AddRectFilled(ImVec2(outer.Min.x, inner.Max.y), ImVec2(inner.Min.x, outer.Max.y), col, rounding, ImDrawFlags_RoundCornersBottomLeft);\n    if (fill_R && fill_D) draw_list->AddRectFilled(ImVec2(inner.Max.x, inner.Max.y), ImVec2(outer.Max.x, outer.Max.y), col, rounding, ImDrawFlags_RoundCornersBottomRight);\n}\n\n// Helper for ColorPicker4()\n// NB: This is rather brittle and will show artifact when rounding this enabled if rounded corners overlap multiple cells. Caller currently responsible for avoiding that.\n// Spent a non reasonable amount of time trying to getting this right for ColorButton with rounding+anti-aliasing+ImGuiColorEditFlags_HalfAlphaPreview flag + various grid sizes and offsets, and eventually gave up... probably more reasonable to disable rounding altogether.\n// FIXME: uses ImGui::GetColorU32\nvoid ImGui::RenderColorRectWithAlphaCheckerboard(ImDrawList* draw_list, ImVec2 p_min, ImVec2 p_max, ImU32 col, float grid_step, ImVec2 grid_off, float rounding, ImDrawFlags flags)\n{\n    if ((flags & ImDrawFlags_RoundCornersMask_) == 0)\n        flags = ImDrawFlags_RoundCornersDefault_;\n    if (((col & IM_COL32_A_MASK) >> IM_COL32_A_SHIFT) < 0xFF)\n    {\n        ImU32 col_bg1 = GetColorU32(ImAlphaBlendColors(IM_COL32(204, 204, 204, 255), col));\n        ImU32 col_bg2 = GetColorU32(ImAlphaBlendColors(IM_COL32(128, 128, 128, 255), col));\n        draw_list->AddRectFilled(p_min, p_max, col_bg1, rounding, flags);\n\n        int yi = 0;\n        for (float y = p_min.y + grid_off.y; y < p_max.y; y += grid_step, yi++)\n        {\n            float y1 = ImClamp(y, p_min.y, p_max.y), y2 = ImMin(y + grid_step, p_max.y);\n            if (y2 <= y1)\n                continue;\n            for (float x = p_min.x + grid_off.x + (yi & 1) * grid_step; x < p_max.x; x += grid_step * 2.0f)\n            {\n                float x1 = ImClamp(x, p_min.x, p_max.x), x2 = ImMin(x + grid_step, p_max.x);\n                if (x2 <= x1)\n                    continue;\n                ImDrawFlags cell_flags = ImDrawFlags_RoundCornersNone;\n                if (y1 <= p_min.y) { if (x1 <= p_min.x) cell_flags |= ImDrawFlags_RoundCornersTopLeft; if (x2 >= p_max.x) cell_flags |= ImDrawFlags_RoundCornersTopRight; }\n                if (y2 >= p_max.y) { if (x1 <= p_min.x) cell_flags |= ImDrawFlags_RoundCornersBottomLeft; if (x2 >= p_max.x) cell_flags |= ImDrawFlags_RoundCornersBottomRight; }\n\n                // Combine flags\n                cell_flags = (flags == ImDrawFlags_RoundCornersNone || cell_flags == ImDrawFlags_RoundCornersNone) ? ImDrawFlags_RoundCornersNone : (cell_flags & flags);\n                draw_list->AddRectFilled(ImVec2(x1, y1), ImVec2(x2, y2), col_bg2, rounding, cell_flags);\n            }\n        }\n    }\n    else\n    {\n        draw_list->AddRectFilled(p_min, p_max, col, rounding, flags);\n    }\n}\n\n//-----------------------------------------------------------------------------\n// [SECTION] Decompression code\n//-----------------------------------------------------------------------------\n// Compressed with stb_compress() then converted to a C array and encoded as base85.\n// Use the program in misc/fonts/binary_to_compressed_c.cpp to create the array from a TTF file.\n// The purpose of encoding as base85 instead of \"0x00,0x01,...\" style is only save on _source code_ size.\n// Decompression from stb.h (public domain) by Sean Barrett https://github.com/nothings/stb/blob/master/stb.h\n//-----------------------------------------------------------------------------\n\nstatic unsigned int stb_decompress_length(const unsigned char *input)\n{\n    return (input[8] << 24) + (input[9] << 16) + (input[10] << 8) + input[11];\n}\n\nstatic unsigned char *stb__barrier_out_e, *stb__barrier_out_b;\nstatic const unsigned char *stb__barrier_in_b;\nstatic unsigned char *stb__dout;\nstatic void stb__match(const unsigned char *data, unsigned int length)\n{\n    // INVERSE of memmove... write each byte before copying the next...\n    IM_ASSERT(stb__dout + length <= stb__barrier_out_e);\n    if (stb__dout + length > stb__barrier_out_e) { stb__dout += length; return; }\n    if (data < stb__barrier_out_b) { stb__dout = stb__barrier_out_e+1; return; }\n    while (length--) *stb__dout++ = *data++;\n}\n\nstatic void stb__lit(const unsigned char *data, unsigned int length)\n{\n    IM_ASSERT(stb__dout + length <= stb__barrier_out_e);\n    if (stb__dout + length > stb__barrier_out_e) { stb__dout += length; return; }\n    if (data < stb__barrier_in_b) { stb__dout = stb__barrier_out_e+1; return; }\n    memcpy(stb__dout, data, length);\n    stb__dout += length;\n}\n\n#define stb__in2(x)   ((i[x] << 8) + i[(x)+1])\n#define stb__in3(x)   ((i[x] << 16) + stb__in2((x)+1))\n#define stb__in4(x)   ((i[x] << 24) + stb__in3((x)+1))\n\nstatic const unsigned char *stb_decompress_token(const unsigned char *i)\n{\n    if (*i >= 0x20) { // use fewer if's for cases that expand small\n        if (*i >= 0x80)       stb__match(stb__dout-i[1]-1, i[0] - 0x80 + 1), i += 2;\n        else if (*i >= 0x40)  stb__match(stb__dout-(stb__in2(0) - 0x4000 + 1), i[2]+1), i += 3;\n        else /* *i >= 0x20 */ stb__lit(i+1, i[0] - 0x20 + 1), i += 1 + (i[0] - 0x20 + 1);\n    } else { // more ifs for cases that expand large, since overhead is amortized\n        if (*i >= 0x18)       stb__match(stb__dout-(stb__in3(0) - 0x180000 + 1), i[3]+1), i += 4;\n        else if (*i >= 0x10)  stb__match(stb__dout-(stb__in3(0) - 0x100000 + 1), stb__in2(3)+1), i += 5;\n        else if (*i >= 0x08)  stb__lit(i+2, stb__in2(0) - 0x0800 + 1), i += 2 + (stb__in2(0) - 0x0800 + 1);\n        else if (*i == 0x07)  stb__lit(i+3, stb__in2(1) + 1), i += 3 + (stb__in2(1) + 1);\n        else if (*i == 0x06)  stb__match(stb__dout-(stb__in3(1)+1), i[4]+1), i += 5;\n        else if (*i == 0x04)  stb__match(stb__dout-(stb__in3(1)+1), stb__in2(4)+1), i += 6;\n    }\n    return i;\n}\n\nstatic unsigned int stb_adler32(unsigned int adler32, unsigned char *buffer, unsigned int buflen)\n{\n    const unsigned long ADLER_MOD = 65521;\n    unsigned long s1 = adler32 & 0xffff, s2 = adler32 >> 16;\n    unsigned long blocklen = buflen % 5552;\n\n    unsigned long i;\n    while (buflen) {\n        for (i=0; i + 7 < blocklen; i += 8) {\n            s1 += buffer[0], s2 += s1;\n            s1 += buffer[1], s2 += s1;\n            s1 += buffer[2], s2 += s1;\n            s1 += buffer[3], s2 += s1;\n            s1 += buffer[4], s2 += s1;\n            s1 += buffer[5], s2 += s1;\n            s1 += buffer[6], s2 += s1;\n            s1 += buffer[7], s2 += s1;\n\n            buffer += 8;\n        }\n\n        for (; i < blocklen; ++i)\n            s1 += *buffer++, s2 += s1;\n\n        s1 %= ADLER_MOD, s2 %= ADLER_MOD;\n        buflen -= blocklen;\n        blocklen = 5552;\n    }\n    return (unsigned int)(s2 << 16) + (unsigned int)s1;\n}\n\nstatic unsigned int stb_decompress(unsigned char *output, const unsigned char *i, unsigned int /*length*/)\n{\n    if (stb__in4(0) != 0x57bC0000) return 0;\n    if (stb__in4(4) != 0)          return 0; // error! stream is > 4GB\n    const unsigned int olen = stb_decompress_length(i);\n    stb__barrier_in_b = i;\n    stb__barrier_out_e = output + olen;\n    stb__barrier_out_b = output;\n    i += 16;\n\n    stb__dout = output;\n    for (;;) {\n        const unsigned char *old_i = i;\n        i = stb_decompress_token(i);\n        if (i == old_i) {\n            if (*i == 0x05 && i[1] == 0xfa) {\n                IM_ASSERT(stb__dout == output + olen);\n                if (stb__dout != output + olen) return 0;\n                if (stb_adler32(1, output, olen) != (unsigned int) stb__in4(2))\n                    return 0;\n                return olen;\n            } else {\n                IM_ASSERT(0); /* NOTREACHED */\n                return 0;\n            }\n        }\n        IM_ASSERT(stb__dout <= output + olen);\n        if (stb__dout > output + olen)\n            return 0;\n    }\n}\n\n//-----------------------------------------------------------------------------\n// [SECTION] Default font data (ProggyClean.ttf)\n//-----------------------------------------------------------------------------\n// ProggyClean.ttf\n// Copyright (c) 2004, 2005 Tristan Grimmer\n// MIT license (see License.txt in http://www.proggyfonts.net/index.php?menu=download)\n// Download and more information at http://www.proggyfonts.net or http://upperboundsinteractive.com/fonts.php\n//-----------------------------------------------------------------------------\n\n#ifndef IMGUI_DISABLE_DEFAULT_FONT\n\n// File: 'ProggyClean.ttf' (41208 bytes)\n// Exported using binary_to_compressed_c.exe -u8 \"ProggyClean.ttf\" proggy_clean_ttf\nstatic const unsigned int proggy_clean_ttf_compressed_size = 9583;\nstatic const unsigned char proggy_clean_ttf_compressed_data[9583] =\n{\n    87,188,0,0,0,0,0,0,0,0,160,248,0,4,0,0,55,0,1,0,0,0,12,0,128,0,3,0,64,79,83,47,50,136,235,116,144,0,0,1,72,130,21,44,78,99,109,97,112,2,18,35,117,0,0,3,160,130,19,36,82,99,118,116,\n    32,130,23,130,2,33,4,252,130,4,56,2,103,108,121,102,18,175,137,86,0,0,7,4,0,0,146,128,104,101,97,100,215,145,102,211,130,27,32,204,130,3,33,54,104,130,16,39,8,66,1,195,0,0,1,4,130,\n    15,59,36,104,109,116,120,138,0,126,128,0,0,1,152,0,0,2,6,108,111,99,97,140,115,176,216,0,0,5,130,30,41,2,4,109,97,120,112,1,174,0,218,130,31,32,40,130,16,44,32,110,97,109,101,37,89,\n    187,150,0,0,153,132,130,19,44,158,112,111,115,116,166,172,131,239,0,0,155,36,130,51,44,210,112,114,101,112,105,2,1,18,0,0,4,244,130,47,32,8,132,203,46,1,0,0,60,85,233,213,95,15,60,\n    245,0,3,8,0,131,0,34,183,103,119,130,63,43,0,0,189,146,166,215,0,0,254,128,3,128,131,111,130,241,33,2,0,133,0,32,1,130,65,38,192,254,64,0,0,3,128,131,16,130,5,32,1,131,7,138,3,33,2,\n    0,130,17,36,1,1,0,144,0,130,121,130,23,38,2,0,8,0,64,0,10,130,9,32,118,130,9,130,6,32,0,130,59,33,1,144,131,200,35,2,188,2,138,130,16,32,143,133,7,37,1,197,0,50,2,0,131,0,33,4,9,131,\n    5,145,3,43,65,108,116,115,0,64,0,0,32,172,8,0,131,0,35,5,0,1,128,131,77,131,3,33,3,128,191,1,33,1,128,130,184,35,0,0,128,0,130,3,131,11,32,1,130,7,33,0,128,131,1,32,1,136,9,32,0,132,\n    15,135,5,32,1,131,13,135,27,144,35,32,1,149,25,131,21,32,0,130,0,32,128,132,103,130,35,132,39,32,0,136,45,136,97,133,17,130,5,33,0,0,136,19,34,0,128,1,133,13,133,5,32,128,130,15,132,\n    131,32,3,130,5,32,3,132,27,144,71,32,0,133,27,130,29,130,31,136,29,131,63,131,3,65,63,5,132,5,132,205,130,9,33,0,0,131,9,137,119,32,3,132,19,138,243,130,55,32,1,132,35,135,19,131,201,\n    136,11,132,143,137,13,130,41,32,0,131,3,144,35,33,128,0,135,1,131,223,131,3,141,17,134,13,136,63,134,15,136,53,143,15,130,96,33,0,3,131,4,130,3,34,28,0,1,130,5,34,0,0,76,130,17,131,\n    9,36,28,0,4,0,48,130,17,46,8,0,8,0,2,0,0,0,127,0,255,32,172,255,255,130,9,34,0,0,129,132,9,130,102,33,223,213,134,53,132,22,33,1,6,132,6,64,4,215,32,129,165,216,39,177,0,1,141,184,\n    1,255,133,134,45,33,198,0,193,1,8,190,244,1,28,1,158,2,20,2,136,2,252,3,20,3,88,3,156,3,222,4,20,4,50,4,80,4,98,4,162,5,22,5,102,5,188,6,18,6,116,6,214,7,56,7,126,7,236,8,78,8,108,\n    8,150,8,208,9,16,9,74,9,136,10,22,10,128,11,4,11,86,11,200,12,46,12,130,12,234,13,94,13,164,13,234,14,80,14,150,15,40,15,176,16,18,16,116,16,224,17,82,17,182,18,4,18,110,18,196,19,\n    76,19,172,19,246,20,88,20,174,20,234,21,64,21,128,21,166,21,184,22,18,22,126,22,198,23,52,23,142,23,224,24,86,24,186,24,238,25,54,25,150,25,212,26,72,26,156,26,240,27,92,27,200,28,\n    4,28,76,28,150,28,234,29,42,29,146,29,210,30,64,30,142,30,224,31,36,31,118,31,166,31,166,32,16,130,1,52,46,32,138,32,178,32,200,33,20,33,116,33,152,33,238,34,98,34,134,35,12,130,1,\n    33,128,35,131,1,60,152,35,176,35,216,36,0,36,74,36,104,36,144,36,174,37,6,37,96,37,130,37,248,37,248,38,88,38,170,130,1,8,190,216,39,64,39,154,40,10,40,104,40,168,41,14,41,32,41,184,\n    41,248,42,54,42,96,42,96,43,2,43,42,43,94,43,172,43,230,44,32,44,52,44,154,45,40,45,92,45,120,45,170,45,232,46,38,46,166,47,38,47,182,47,244,48,94,48,200,49,62,49,180,50,30,50,158,\n    51,30,51,130,51,238,52,92,52,206,53,58,53,134,53,212,54,38,54,114,54,230,55,118,55,216,56,58,56,166,57,18,57,116,57,174,58,46,58,154,59,6,59,124,59,232,60,58,60,150,61,34,61,134,61,\n    236,62,86,62,198,63,42,63,154,64,18,64,106,64,208,65,54,65,162,66,8,66,64,66,122,66,184,66,240,67,98,67,204,68,42,68,138,68,238,69,88,69,182,69,226,70,84,70,180,71,20,71,122,71,218,\n    72,84,72,198,73,64,0,36,70,21,8,8,77,3,0,7,0,11,0,15,0,19,0,23,0,27,0,31,0,35,0,39,0,43,0,47,0,51,0,55,0,59,0,63,0,67,0,71,0,75,0,79,0,83,0,87,0,91,0,95,0,99,0,103,0,107,0,111,0,115,\n    0,119,0,123,0,127,0,131,0,135,0,139,0,143,0,0,17,53,51,21,49,150,3,32,5,130,23,32,33,130,3,211,7,151,115,32,128,133,0,37,252,128,128,2,128,128,190,5,133,74,32,4,133,6,206,5,42,0,7,\n    1,128,0,0,2,0,4,0,0,65,139,13,37,0,1,53,51,21,7,146,3,32,3,130,19,32,1,141,133,32,3,141,14,131,13,38,255,0,128,128,0,6,1,130,84,35,2,128,4,128,140,91,132,89,32,51,65,143,6,139,7,33,\n    1,0,130,57,32,254,130,3,32,128,132,4,32,4,131,14,138,89,35,0,0,24,0,130,0,33,3,128,144,171,66,55,33,148,115,65,187,19,32,5,130,151,143,155,163,39,32,1,136,182,32,253,134,178,132,7,\n    132,200,145,17,32,3,65,48,17,165,17,39,0,0,21,0,128,255,128,3,65,175,17,65,3,27,132,253,131,217,139,201,155,233,155,27,131,67,131,31,130,241,33,255,0,131,181,137,232,132,15,132,4,138,\n    247,34,255,0,128,179,238,32,0,130,0,32,20,65,239,48,33,0,19,67,235,10,32,51,65,203,14,65,215,11,32,7,154,27,135,39,32,33,130,35,33,128,128,130,231,32,253,132,231,32,128,132,232,34,\n    128,128,254,133,13,136,8,32,253,65,186,5,130,36,130,42,176,234,133,231,34,128,0,0,66,215,44,33,0,1,68,235,6,68,211,19,32,49,68,239,14,139,207,139,47,66,13,7,32,51,130,47,33,1,0,130,\n    207,35,128,128,1,0,131,222,131,5,130,212,130,6,131,212,32,0,130,10,133,220,130,233,130,226,32,254,133,255,178,233,39,3,1,128,3,0,2,0,4,68,15,7,68,99,12,130,89,130,104,33,128,4,133,\n    93,130,10,38,0,0,11,1,0,255,0,68,63,16,70,39,9,66,215,8,32,7,68,77,6,68,175,14,32,29,68,195,6,132,7,35,2,0,128,255,131,91,132,4,65,178,5,141,111,67,129,23,165,135,140,107,142,135,33,\n    21,5,69,71,6,131,7,33,1,0,140,104,132,142,130,4,137,247,140,30,68,255,12,39,11,0,128,0,128,3,0,3,69,171,15,67,251,7,65,15,8,66,249,11,65,229,7,67,211,7,66,13,7,35,1,128,128,254,133,\n    93,32,254,131,145,132,4,132,18,32,2,151,128,130,23,34,0,0,9,154,131,65,207,8,68,107,15,68,51,7,32,7,70,59,7,135,121,130,82,32,128,151,111,41,0,0,4,0,128,255,0,1,128,1,137,239,33,0,\n    37,70,145,10,65,77,10,65,212,14,37,0,0,0,5,0,128,66,109,5,70,123,10,33,0,19,72,33,18,133,237,70,209,11,33,0,2,130,113,137,119,136,115,33,1,0,133,43,130,5,34,0,0,10,69,135,6,70,219,\n    13,66,155,7,65,9,12,66,157,11,66,9,11,32,7,130,141,132,252,66,151,9,137,9,66,15,30,36,0,20,0,128,0,130,218,71,11,42,68,51,8,65,141,7,73,19,15,69,47,23,143,39,66,81,7,32,1,66,55,6,34,\n    1,128,128,68,25,5,69,32,6,137,6,136,25,32,254,131,42,32,3,66,88,26,148,26,32,0,130,0,32,14,164,231,70,225,12,66,233,7,67,133,19,71,203,15,130,161,32,255,130,155,32,254,139,127,134,\n    12,164,174,33,0,15,164,159,33,59,0,65,125,20,66,25,7,32,5,68,191,6,66,29,7,144,165,65,105,9,35,128,128,255,0,137,2,133,182,164,169,33,128,128,197,171,130,155,68,235,7,32,21,70,77,19,\n    66,21,10,68,97,8,66,30,5,66,4,43,34,0,17,0,71,19,41,65,253,20,71,25,23,65,91,15,65,115,7,34,2,128,128,66,9,8,130,169,33,1,0,66,212,13,132,28,72,201,43,35,0,0,0,18,66,27,38,76,231,5,\n    68,157,20,135,157,32,7,68,185,13,65,129,28,66,20,5,32,253,66,210,11,65,128,49,133,61,32,0,65,135,6,74,111,37,72,149,12,66,203,19,65,147,19,68,93,7,68,85,8,76,4,5,33,255,0,133,129,34,\n    254,0,128,68,69,8,181,197,34,0,0,12,65,135,32,65,123,20,69,183,27,133,156,66,50,5,72,87,10,67,137,32,33,0,19,160,139,78,251,13,68,55,20,67,119,19,65,91,36,69,177,15,32,254,143,16,65,\n    98,53,32,128,130,0,32,0,66,43,54,70,141,23,66,23,15,131,39,69,47,11,131,15,70,129,19,74,161,9,36,128,255,0,128,254,130,153,65,148,32,67,41,9,34,0,0,4,79,15,5,73,99,10,71,203,8,32,3,\n    72,123,6,72,43,8,32,2,133,56,131,99,130,9,34,0,0,6,72,175,5,73,159,14,144,63,135,197,132,189,133,66,33,255,0,73,6,7,70,137,12,35,0,0,0,10,130,3,73,243,25,67,113,12,65,73,7,69,161,7,\n    138,7,37,21,2,0,128,128,254,134,3,73,116,27,33,128,128,130,111,39,12,0,128,1,0,3,128,2,72,219,21,35,43,0,47,0,67,47,20,130,111,33,21,1,68,167,13,81,147,8,133,230,32,128,77,73,6,32,\n    128,131,142,134,18,130,6,32,255,75,18,12,131,243,37,128,0,128,3,128,3,74,231,21,135,123,32,29,134,107,135,7,32,21,74,117,7,135,7,134,96,135,246,74,103,23,132,242,33,0,10,67,151,28,\n    67,133,20,66,141,11,131,11,32,3,77,71,6,32,128,130,113,32,1,81,4,6,134,218,66,130,24,131,31,34,0,26,0,130,0,77,255,44,83,15,11,148,155,68,13,7,32,49,78,231,18,79,7,11,73,243,11,32,\n    33,65,187,10,130,63,65,87,8,73,239,19,35,0,128,1,0,131,226,32,252,65,100,6,32,128,139,8,33,1,0,130,21,32,253,72,155,44,73,255,20,32,128,71,67,8,81,243,39,67,15,20,74,191,23,68,121,\n    27,32,1,66,150,6,32,254,79,19,11,131,214,32,128,130,215,37,2,0,128,253,0,128,136,5,65,220,24,147,212,130,210,33,0,24,72,219,42,84,255,13,67,119,16,69,245,19,72,225,19,65,3,15,69,93,\n    19,131,55,132,178,71,115,14,81,228,6,142,245,33,253,0,132,43,172,252,65,16,11,75,219,8,65,219,31,66,223,24,75,223,10,33,29,1,80,243,10,66,175,8,131,110,134,203,133,172,130,16,70,30,\n    7,164,183,130,163,32,20,65,171,48,65,163,36,65,143,23,65,151,19,65,147,13,65,134,17,133,17,130,216,67,114,5,164,217,65,137,12,72,147,48,79,71,19,74,169,22,80,251,8,65,173,7,66,157,\n    15,74,173,15,32,254,65,170,8,71,186,45,72,131,6,77,143,40,187,195,152,179,65,123,38,68,215,57,68,179,15,65,85,7,69,187,14,32,21,66,95,15,67,19,25,32,1,83,223,6,32,2,76,240,7,77,166,\n    43,65,8,5,130,206,32,0,67,39,54,143,167,66,255,19,82,193,11,151,47,85,171,5,67,27,17,132,160,69,172,11,69,184,56,66,95,6,33,12,1,130,237,32,2,68,179,27,68,175,16,80,135,15,72,55,7,\n    71,87,12,73,3,12,132,12,66,75,32,76,215,5,169,139,147,135,148,139,81,12,12,81,185,36,75,251,7,65,23,27,76,215,9,87,165,12,65,209,15,72,157,7,65,245,31,32,128,71,128,6,32,1,82,125,5,\n    34,0,128,254,131,169,32,254,131,187,71,180,9,132,27,32,2,88,129,44,32,0,78,47,40,65,79,23,79,171,14,32,21,71,87,8,72,15,14,65,224,33,130,139,74,27,62,93,23,7,68,31,7,75,27,7,139,15,\n    74,3,7,74,23,27,65,165,11,65,177,15,67,123,5,32,1,130,221,32,252,71,96,5,74,12,12,133,244,130,25,34,1,0,128,130,2,139,8,93,26,8,65,9,32,65,57,14,140,14,32,0,73,79,67,68,119,11,135,\n    11,32,51,90,75,14,139,247,65,43,7,131,19,139,11,69,159,11,65,247,6,36,1,128,128,253,0,90,71,9,33,1,0,132,14,32,128,89,93,14,69,133,6,130,44,131,30,131,6,65,20,56,33,0,16,72,179,40,\n    75,47,12,65,215,19,74,95,19,65,43,11,131,168,67,110,5,75,23,17,69,106,6,75,65,5,71,204,43,32,0,80,75,47,71,203,15,159,181,68,91,11,67,197,7,73,101,13,68,85,6,33,128,128,130,214,130,\n    25,32,254,74,236,48,130,194,37,0,18,0,128,255,128,77,215,40,65,139,64,32,51,80,159,10,65,147,39,130,219,84,212,43,130,46,75,19,97,74,33,11,65,201,23,65,173,31,33,1,0,79,133,6,66,150,\n    5,67,75,48,85,187,6,70,207,37,32,71,87,221,13,73,163,14,80,167,15,132,15,83,193,19,82,209,8,78,99,9,72,190,11,77,110,49,89,63,5,80,91,35,99,63,32,70,235,23,81,99,10,69,148,10,65,110,\n    36,32,0,65,99,47,95,219,11,68,171,51,66,87,7,72,57,7,74,45,17,143,17,65,114,50,33,14,0,65,111,40,159,195,98,135,15,35,7,53,51,21,100,78,9,95,146,16,32,254,82,114,6,32,128,67,208,37,\n    130,166,99,79,58,32,17,96,99,14,72,31,19,72,87,31,82,155,7,67,47,14,32,21,131,75,134,231,72,51,17,72,78,8,133,8,80,133,6,33,253,128,88,37,9,66,124,36,72,65,12,134,12,71,55,43,66,139,\n    27,85,135,10,91,33,12,65,35,11,66,131,11,71,32,8,90,127,6,130,244,71,76,11,168,207,33,0,12,66,123,32,32,0,65,183,15,68,135,11,66,111,7,67,235,11,66,111,15,32,254,97,66,12,160,154,67,\n    227,52,80,33,15,87,249,15,93,45,31,75,111,12,93,45,11,77,99,9,160,184,81,31,12,32,15,98,135,30,104,175,7,77,249,36,69,73,15,78,5,12,32,254,66,151,19,34,128,128,4,87,32,12,149,35,133,\n    21,96,151,31,32,19,72,35,5,98,173,15,143,15,32,21,143,99,158,129,33,0,0,65,35,52,65,11,15,147,15,98,75,11,33,1,0,143,151,132,15,32,254,99,200,37,132,43,130,4,39,0,10,0,128,1,128,3,\n    0,104,151,14,97,187,20,69,131,15,67,195,11,87,227,7,33,128,128,132,128,33,254,0,68,131,9,65,46,26,42,0,0,0,7,0,0,255,128,3,128,0,88,223,15,33,0,21,89,61,22,66,209,12,65,2,12,37,0,2,\n    1,0,3,128,101,83,8,36,0,1,53,51,29,130,3,34,21,1,0,66,53,8,32,0,68,215,6,100,55,25,107,111,9,66,193,11,72,167,8,73,143,31,139,31,33,1,0,131,158,32,254,132,5,33,253,128,65,16,9,133,\n    17,89,130,25,141,212,33,0,0,93,39,8,90,131,25,93,39,14,66,217,6,106,179,8,159,181,71,125,15,139,47,138,141,87,11,14,76,23,14,65,231,26,140,209,66,122,8,81,179,5,101,195,26,32,47,74,\n    75,13,69,159,11,83,235,11,67,21,16,136,167,131,106,130,165,130,15,32,128,101,90,24,134,142,32,0,65,103,51,108,23,11,101,231,15,75,173,23,74,237,23,66,15,6,66,46,17,66,58,17,65,105,\n    49,66,247,55,71,179,12,70,139,15,86,229,7,84,167,15,32,1,95,72,12,89,49,6,33,128,128,65,136,38,66,30,9,32,0,100,239,7,66,247,29,70,105,20,65,141,19,69,81,15,130,144,32,128,83,41,5,\n    32,255,131,177,68,185,5,133,126,65,97,37,32,0,130,0,33,21,0,130,55,66,195,28,67,155,13,34,79,0,83,66,213,13,73,241,19,66,59,19,65,125,11,135,201,66,249,16,32,128,66,44,11,66,56,17,\n    68,143,8,68,124,38,67,183,12,96,211,9,65,143,29,112,171,5,32,0,68,131,63,34,33,53,51,71,121,11,32,254,98,251,16,32,253,74,231,10,65,175,37,133,206,37,0,0,8,1,0,0,107,123,11,113,115,\n    9,33,0,1,130,117,131,3,73,103,7,66,51,18,66,44,5,133,75,70,88,5,32,254,65,39,12,68,80,9,34,12,0,128,107,179,28,68,223,6,155,111,86,147,15,32,2,131,82,141,110,33,254,0,130,15,32,4,103,\n    184,15,141,35,87,176,5,83,11,5,71,235,23,114,107,11,65,189,16,70,33,15,86,153,31,135,126,86,145,30,65,183,41,32,0,130,0,32,10,65,183,24,34,35,0,39,67,85,9,65,179,15,143,15,33,1,0,65,\n    28,17,157,136,130,123,32,20,130,3,32,0,97,135,24,115,167,19,80,71,12,32,51,110,163,14,78,35,19,131,19,155,23,77,229,8,78,9,17,151,17,67,231,46,94,135,8,73,31,31,93,215,56,82,171,25,\n    72,77,8,162,179,169,167,99,131,11,69,85,19,66,215,15,76,129,13,68,115,22,72,79,35,67,113,5,34,0,0,19,70,31,46,65,89,52,73,223,15,85,199,33,95,33,8,132,203,73,29,32,67,48,16,177,215,\n    101,13,15,65,141,43,69,141,15,75,89,5,70,0,11,70,235,21,178,215,36,10,0,128,0,0,71,207,24,33,0,19,100,67,6,80,215,11,66,67,7,80,43,12,71,106,7,80,192,5,65,63,5,66,217,26,33,0,13,156,\n    119,68,95,5,72,233,12,134,129,85,81,11,76,165,20,65,43,8,73,136,8,75,10,31,38,128,128,0,0,0,13,1,130,4,32,3,106,235,29,114,179,12,66,131,23,32,7,77,133,6,67,89,12,131,139,116,60,9,\n    89,15,37,32,0,74,15,7,103,11,22,65,35,5,33,55,0,93,81,28,67,239,23,78,85,5,107,93,14,66,84,17,65,193,26,74,183,10,66,67,34,143,135,79,91,15,32,7,117,111,8,75,56,9,84,212,9,154,134,\n    32,0,130,0,32,18,130,3,70,171,41,83,7,16,70,131,19,84,191,15,84,175,19,84,167,30,84,158,12,154,193,68,107,15,33,0,0,65,79,42,65,71,7,73,55,7,118,191,16,83,180,9,32,255,76,166,9,154,\n    141,32,0,130,0,69,195,52,65,225,15,151,15,75,215,31,80,56,10,68,240,17,100,32,9,70,147,39,65,93,12,71,71,41,92,85,15,84,135,23,78,35,15,110,27,10,84,125,8,107,115,29,136,160,38,0,0,\n    14,0,128,255,0,82,155,24,67,239,8,119,255,11,69,131,11,77,29,6,112,31,8,134,27,105,203,8,32,2,75,51,11,75,195,12,74,13,29,136,161,37,128,0,0,0,11,1,130,163,82,115,8,125,191,17,69,35,\n    12,74,137,15,143,15,32,1,65,157,12,136,12,161,142,65,43,40,65,199,6,65,19,24,102,185,11,76,123,11,99,6,12,135,12,32,254,130,8,161,155,101,23,9,39,8,0,0,1,128,3,128,2,78,63,17,72,245,\n    12,67,41,11,90,167,9,32,128,97,49,9,32,128,109,51,14,132,97,81,191,8,130,97,125,99,12,121,35,9,127,75,15,71,79,12,81,151,23,87,97,7,70,223,15,80,245,16,105,97,15,32,254,113,17,6,32,\n    128,130,8,105,105,8,76,122,18,65,243,21,74,63,7,38,4,1,0,255,0,2,0,119,247,28,133,65,32,255,141,91,35,0,0,0,16,67,63,36,34,59,0,63,77,59,9,119,147,11,143,241,66,173,15,66,31,11,67,\n    75,8,81,74,16,32,128,131,255,87,181,42,127,43,5,34,255,128,2,120,235,11,37,19,0,23,0,0,37,109,191,14,118,219,7,127,43,14,65,79,14,35,0,0,0,3,73,91,5,130,5,38,3,0,7,0,11,0,0,70,205,\n    11,88,221,12,32,0,73,135,7,87,15,22,73,135,10,79,153,15,97,71,19,65,49,11,32,1,131,104,121,235,11,80,65,11,142,179,144,14,81,123,46,32,1,88,217,5,112,5,8,65,201,15,83,29,15,122,147,\n    11,135,179,142,175,143,185,67,247,39,66,199,7,35,5,0,128,3,69,203,15,123,163,12,67,127,7,130,119,71,153,10,141,102,70,175,8,32,128,121,235,30,136,89,100,191,11,116,195,11,111,235,15,\n    72,39,7,32,2,97,43,5,132,5,94,67,8,131,8,125,253,10,32,3,65,158,16,146,16,130,170,40,0,21,0,128,0,0,3,128,5,88,219,15,24,64,159,32,135,141,65,167,15,68,163,10,97,73,49,32,255,82,58,\n    7,93,80,8,97,81,16,24,67,87,52,34,0,0,5,130,231,33,128,2,80,51,13,65,129,8,113,61,6,132,175,65,219,5,130,136,77,152,17,32,0,95,131,61,70,215,6,33,21,51,90,53,10,78,97,23,105,77,31,\n    65,117,7,139,75,24,68,195,9,24,64,22,9,33,0,128,130,11,33,128,128,66,25,5,121,38,5,134,5,134,45,66,40,36,66,59,18,34,128,0,0,66,59,81,135,245,123,103,19,120,159,19,77,175,12,33,255,\n    0,87,29,10,94,70,21,66,59,54,39,3,1,128,3,0,2,128,4,24,65,7,15,66,47,7,72,98,12,37,0,0,0,3,1,0,24,65,55,21,131,195,32,1,67,178,6,33,4,0,77,141,8,32,6,131,47,74,67,16,24,69,3,20,24,\n    65,251,7,133,234,130,229,94,108,17,35,0,0,6,0,141,175,86,59,5,162,79,85,166,8,70,112,13,32,13,24,64,67,26,24,71,255,7,123,211,12,80,121,11,69,215,15,66,217,11,69,71,10,131,113,132,\n    126,119,90,9,66,117,19,132,19,32,0,130,0,24,64,47,59,33,7,0,73,227,5,68,243,15,85,13,12,76,37,22,74,254,15,130,138,33,0,4,65,111,6,137,79,65,107,16,32,1,77,200,6,34,128,128,3,75,154,\n    12,37,0,16,0,0,2,0,104,115,36,140,157,68,67,19,68,51,15,106,243,15,134,120,70,37,10,68,27,10,140,152,65,121,24,32,128,94,155,7,67,11,8,24,74,11,25,65,3,12,83,89,18,82,21,37,67,200,\n    5,130,144,24,64,172,12,33,4,0,134,162,74,80,14,145,184,32,0,130,0,69,251,20,32,19,81,243,5,82,143,8,33,5,53,89,203,5,133,112,79,109,15,33,0,21,130,71,80,175,41,36,75,0,79,0,83,121,\n    117,9,87,89,27,66,103,11,70,13,15,75,191,11,135,67,87,97,20,109,203,5,69,246,8,108,171,5,78,195,38,65,51,13,107,203,11,77,3,17,24,75,239,17,65,229,28,79,129,39,130,175,32,128,123,253,\n    7,132,142,24,65,51,15,65,239,41,36,128,128,0,0,13,65,171,5,66,163,28,136,183,118,137,11,80,255,15,67,65,7,74,111,8,32,0,130,157,32,253,24,76,35,10,103,212,5,81,175,9,69,141,7,66,150,\n    29,131,158,24,75,199,28,124,185,7,76,205,15,68,124,14,32,3,123,139,16,130,16,33,128,128,108,199,6,33,0,3,65,191,35,107,11,6,73,197,11,24,70,121,15,83,247,15,24,70,173,23,69,205,14,\n    32,253,131,140,32,254,136,4,94,198,9,32,3,78,4,13,66,127,13,143,13,32,0,130,0,33,16,0,24,69,59,39,109,147,12,76,253,19,24,69,207,15,69,229,15,130,195,71,90,10,139,10,130,152,73,43,\n    40,91,139,10,65,131,37,35,75,0,79,0,84,227,12,143,151,68,25,15,80,9,23,95,169,11,34,128,2,128,112,186,5,130,6,83,161,19,76,50,6,130,37,65,145,44,110,83,5,32,16,67,99,6,71,67,15,76,\n    55,17,140,215,67,97,23,76,69,15,77,237,11,104,211,23,77,238,11,65,154,43,33,0,10,83,15,28,83,13,20,67,145,19,67,141,14,97,149,21,68,9,15,86,251,5,66,207,5,66,27,37,82,1,23,127,71,12,\n    94,235,10,110,175,24,98,243,15,132,154,132,4,24,66,69,10,32,4,67,156,43,130,198,35,2,1,0,4,75,27,9,69,85,9,95,240,7,32,128,130,35,32,28,66,43,40,24,82,63,23,83,123,12,72,231,15,127,\n    59,23,116,23,19,117,71,7,24,77,99,15,67,111,15,71,101,8,36,2,128,128,252,128,127,60,11,32,1,132,16,130,18,141,24,67,107,9,32,3,68,194,15,175,15,38,0,11,0,128,1,128,2,80,63,25,32,0,\n    24,65,73,11,69,185,15,83,243,16,32,0,24,81,165,8,130,86,77,35,6,155,163,88,203,5,24,66,195,30,70,19,19,24,80,133,15,32,1,75,211,8,32,254,108,133,8,79,87,20,65,32,9,41,0,0,7,0,128,0,\n    0,2,128,2,68,87,15,66,1,16,92,201,16,24,76,24,17,133,17,34,128,0,30,66,127,64,34,115,0,119,73,205,9,66,43,11,109,143,15,24,79,203,11,90,143,15,131,15,155,31,65,185,15,86,87,11,35,128,\n    128,253,0,69,7,6,130,213,33,1,0,119,178,15,142,17,66,141,74,83,28,6,36,7,0,0,4,128,82,39,18,76,149,12,67,69,21,32,128,79,118,15,32,0,130,0,32,8,131,206,32,2,79,83,9,100,223,14,102,\n    113,23,115,115,7,24,65,231,12,130,162,32,4,68,182,19,130,102,93,143,8,69,107,29,24,77,255,12,143,197,72,51,7,76,195,15,132,139,85,49,15,130,152,131,18,71,81,23,70,14,11,36,0,10,0,128,\n    2,69,59,9,89,151,15,66,241,11,76,165,12,71,43,15,75,49,13,65,12,23,132,37,32,0,179,115,130,231,95,181,16,132,77,32,254,67,224,8,65,126,20,79,171,8,32,2,89,81,5,75,143,6,80,41,8,34,\n    2,0,128,24,81,72,9,32,0,130,0,35,17,0,0,255,77,99,39,95,65,36,67,109,15,24,69,93,11,77,239,5,95,77,23,35,128,1,0,128,24,86,7,8,132,167,32,2,69,198,41,130,202,33,0,26,120,75,44,24,89,\n    51,15,71,243,12,70,239,11,24,84,3,11,66,7,11,71,255,10,32,21,69,155,35,88,151,12,32,128,74,38,10,65,210,8,74,251,5,65,226,5,75,201,13,32,3,65,9,41,146,41,40,0,0,0,9,1,0,1,0,2,91,99,\n    19,32,35,106,119,13,70,219,15,83,239,12,137,154,32,2,67,252,19,36,128,0,0,4,1,130,196,32,2,130,8,91,107,8,32,0,135,81,24,73,211,8,132,161,73,164,13,36,0,8,0,128,2,105,123,26,139,67,\n    76,99,15,34,1,0,128,135,76,83,156,20,92,104,8,67,251,30,24,86,47,27,123,207,12,24,86,7,15,71,227,8,32,4,65,20,20,131,127,32,0,130,123,32,0,71,223,26,32,19,90,195,22,71,223,15,84,200,\n    6,32,128,133,241,24,84,149,9,67,41,25,36,0,0,0,22,0,88,111,49,32,87,66,21,5,77,3,27,123,75,7,71,143,19,135,183,71,183,19,130,171,74,252,5,131,5,89,87,17,32,1,132,18,130,232,68,11,10,\n    33,1,128,70,208,16,66,230,18,147,18,130,254,223,255,75,27,23,65,59,15,135,39,155,255,34,128,128,254,104,92,8,33,0,128,65,32,11,65,1,58,33,26,0,130,0,72,71,18,78,55,17,76,11,19,86,101,\n    12,75,223,11,89,15,11,24,76,87,15,75,235,15,131,15,72,95,7,85,71,11,72,115,11,73,64,6,34,1,128,128,66,215,9,34,128,254,128,134,14,33,128,255,67,102,5,32,0,130,16,70,38,11,66,26,57,\n    88,11,8,24,76,215,34,78,139,7,95,245,7,32,7,24,73,75,23,32,128,131,167,130,170,101,158,9,82,49,22,118,139,6,32,18,67,155,44,116,187,9,108,55,14,80,155,23,66,131,15,93,77,10,131,168,\n    32,128,73,211,12,24,75,187,22,32,4,96,71,20,67,108,19,132,19,120,207,8,32,5,76,79,15,66,111,21,66,95,8,32,3,190,211,111,3,8,211,212,32,20,65,167,44,34,75,0,79,97,59,13,32,33,112,63,\n    10,65,147,19,69,39,19,143,39,24,66,71,9,130,224,65,185,43,94,176,12,65,183,24,71,38,8,24,72,167,7,65,191,38,136,235,24,96,167,12,65,203,62,115,131,13,65,208,42,175,235,67,127,6,32,\n    4,76,171,29,114,187,5,32,71,65,211,5,65,203,68,72,51,8,164,219,32,0,172,214,71,239,58,78,3,27,66,143,15,77,19,15,147,31,35,33,53,51,21,66,183,10,173,245,66,170,30,150,30,34,0,0,23,\n    80,123,54,76,1,16,73,125,15,82,245,11,167,253,24,76,85,12,70,184,5,32,254,131,185,37,254,0,128,1,0,128,133,16,117,158,18,92,27,38,65,3,17,130,251,35,17,0,128,254,24,69,83,39,140,243,\n    121,73,19,109,167,7,81,41,15,24,95,175,12,102,227,15,121,96,11,24,95,189,7,32,3,145,171,154,17,24,77,47,9,33,0,5,70,71,37,68,135,7,32,29,117,171,11,69,87,15,24,79,97,19,24,79,149,23,\n    131,59,32,1,75,235,5,72,115,11,72,143,7,132,188,71,27,46,131,51,32,0,69,95,6,175,215,32,21,131,167,81,15,19,151,191,151,23,131,215,71,43,5,32,254,24,79,164,24,74,109,8,77,166,13,65,\n    176,26,88,162,5,98,159,6,171,219,120,247,6,79,29,8,99,169,10,103,59,19,65,209,35,131,35,91,25,19,112,94,15,83,36,8,173,229,33,20,0,88,75,43,71,31,12,65,191,71,33,1,0,130,203,32,254,\n    131,4,68,66,7,67,130,6,104,61,13,173,215,38,13,1,0,0,0,2,128,67,111,28,74,129,16,104,35,19,79,161,16,87,14,7,138,143,132,10,67,62,36,114,115,5,162,151,67,33,16,108,181,15,143,151,67,\n    5,5,24,100,242,15,170,153,34,0,0,14,65,51,34,32,55,79,75,9,32,51,74,7,10,65,57,38,132,142,32,254,72,0,14,139,163,32,128,80,254,8,67,158,21,65,63,7,32,4,72,227,27,95,155,12,67,119,19,\n    124,91,24,149,154,72,177,34,97,223,8,155,151,24,108,227,15,88,147,16,72,117,19,68,35,11,92,253,15,70,199,15,24,87,209,17,32,2,87,233,7,32,1,24,88,195,10,119,24,8,32,3,81,227,24,65,\n    125,21,35,128,128,0,25,76,59,48,24,90,187,9,97,235,12,66,61,11,91,105,19,24,79,141,11,24,79,117,15,24,79,129,27,90,53,13,130,13,32,253,131,228,24,79,133,40,69,70,8,66,137,31,65,33,\n    19,96,107,8,68,119,29,66,7,5,68,125,16,65,253,19,65,241,27,24,90,179,13,24,79,143,18,33,128,128,130,246,32,254,130,168,68,154,36,77,51,9,97,47,5,167,195,32,21,131,183,78,239,27,155,\n    195,78,231,14,201,196,77,11,6,32,5,73,111,37,97,247,12,77,19,31,155,207,78,215,19,162,212,69,17,14,66,91,19,80,143,57,78,203,39,159,215,32,128,93,134,8,24,80,109,24,66,113,15,169,215,\n    66,115,6,32,4,69,63,33,32,0,101,113,7,86,227,35,143,211,36,49,53,51,21,1,77,185,14,65,159,28,69,251,34,67,56,8,33,9,0,24,107,175,25,90,111,12,110,251,11,119,189,24,119,187,34,87,15,\n    9,32,4,66,231,37,90,39,7,66,239,8,84,219,15,69,105,23,24,85,27,27,87,31,11,33,1,128,76,94,6,32,1,85,241,7,33,128,128,106,48,10,33,128,128,69,136,11,133,13,24,79,116,49,84,236,8,24,\n    91,87,9,32,5,165,255,69,115,12,66,27,15,159,15,24,72,247,12,74,178,5,24,80,64,15,33,0,128,143,17,77,89,51,130,214,24,81,43,7,170,215,74,49,8,159,199,143,31,139,215,69,143,5,32,254,\n    24,81,50,35,181,217,84,123,70,143,195,159,15,65,187,16,66,123,7,65,175,15,65,193,29,68,207,39,79,27,5,70,131,6,32,4,68,211,33,33,67,0,83,143,14,159,207,143,31,140,223,33,0,128,24,80,\n    82,14,24,93,16,23,32,253,65,195,5,68,227,40,133,214,107,31,7,32,5,67,115,27,87,9,8,107,31,43,66,125,6,32,0,103,177,23,131,127,72,203,36,32,0,110,103,8,155,163,73,135,6,32,19,24,112,\n    99,10,65,71,11,73,143,19,143,31,126,195,5,24,85,21,9,24,76,47,14,32,254,24,93,77,36,68,207,11,39,25,0,0,255,128,3,128,4,66,51,37,95,247,13,82,255,24,76,39,19,147,221,66,85,27,24,118,\n    7,8,24,74,249,12,76,74,8,91,234,8,67,80,17,131,222,33,253,0,121,30,44,73,0,16,69,15,6,32,0,65,23,38,69,231,12,65,179,6,98,131,16,86,31,27,24,108,157,14,80,160,8,24,65,46,17,33,4,0,\n    96,2,18,144,191,65,226,8,68,19,5,171,199,80,9,15,180,199,67,89,5,32,255,24,79,173,28,174,201,24,79,179,50,32,1,24,122,5,10,82,61,10,180,209,83,19,8,32,128,24,80,129,27,111,248,43,131,\n    71,24,115,103,8,67,127,41,78,213,24,100,247,19,66,115,39,75,107,5,32,254,165,219,78,170,40,24,112,163,49,32,1,97,203,6,65,173,64,32,0,83,54,7,133,217,88,37,12,32,254,131,28,33,128,\n    3,67,71,44,84,183,6,32,5,69,223,33,96,7,7,123,137,16,192,211,24,112,14,9,32,255,67,88,29,68,14,10,84,197,38,33,0,22,116,47,50,32,87,106,99,9,116,49,15,89,225,15,97,231,23,70,41,19,\n    82,85,8,93,167,6,32,253,132,236,108,190,7,89,251,5,116,49,58,33,128,128,131,234,32,15,24,74,67,38,70,227,24,24,83,45,23,89,219,12,70,187,12,89,216,19,32,2,69,185,24,141,24,70,143,66,\n    24,82,119,56,78,24,10,32,253,133,149,132,6,24,106,233,7,69,198,48,178,203,81,243,12,68,211,15,106,255,23,66,91,15,69,193,7,100,39,10,24,83,72,16,176,204,33,19,0,88,207,45,68,21,12,\n    68,17,10,65,157,53,68,17,6,32,254,92,67,10,65,161,25,69,182,43,24,118,91,47,69,183,18,181,209,111,253,12,89,159,8,66,112,12,69,184,45,35,0,0,0,9,24,80,227,26,73,185,16,118,195,15,131,\n    15,33,1,0,65,59,15,66,39,27,160,111,66,205,12,148,111,143,110,33,128,128,156,112,24,81,199,8,75,199,23,66,117,20,155,121,32,254,68,126,12,72,213,29,134,239,149,123,89,27,16,148,117,\n    65,245,8,24,71,159,14,141,134,134,28,73,51,55,109,77,15,105,131,11,68,67,11,76,169,27,107,209,12,102,174,8,32,128,72,100,18,116,163,56,79,203,11,75,183,44,85,119,19,71,119,23,151,227,\n    32,1,93,27,8,65,122,5,77,102,8,110,120,20,66,23,8,66,175,17,66,63,12,133,12,79,35,8,74,235,33,67,149,16,69,243,15,78,57,15,69,235,16,67,177,7,151,192,130,23,67,84,29,141,192,174,187,\n    77,67,15,69,11,12,159,187,77,59,10,199,189,24,70,235,50,96,83,19,66,53,23,105,65,19,77,47,12,163,199,66,67,37,78,207,50,67,23,23,174,205,67,228,6,71,107,13,67,22,14,66,85,11,83,187,\n    38,124,47,49,95,7,19,66,83,23,67,23,19,24,96,78,17,80,101,16,71,98,40,33,0,7,88,131,22,24,89,245,12,84,45,12,102,213,5,123,12,9,32,2,126,21,14,43,255,0,128,128,0,0,20,0,128,255,128,\n    3,126,19,39,32,75,106,51,7,113,129,15,24,110,135,19,126,47,15,115,117,11,69,47,11,32,2,109,76,9,102,109,9,32,128,75,2,10,130,21,32,254,69,47,6,32,3,94,217,47,32,0,65,247,10,69,15,46,\n    65,235,31,65,243,15,101,139,10,66,174,14,65,247,16,72,102,28,69,17,14,84,243,9,165,191,88,47,48,66,53,12,32,128,71,108,6,203,193,32,17,75,187,42,73,65,16,65,133,52,114,123,9,167,199,\n    69,21,37,86,127,44,75,171,11,180,197,78,213,12,148,200,81,97,46,24,95,243,9,32,4,66,75,33,113,103,9,87,243,36,143,225,24,84,27,31,90,145,8,148,216,67,49,5,24,84,34,14,75,155,27,67,\n    52,13,140,13,36,0,20,0,128,255,24,135,99,46,88,59,43,155,249,80,165,7,136,144,71,161,23,32,253,132,33,32,254,88,87,44,136,84,35,128,0,0,21,81,103,5,94,47,44,76,51,12,143,197,151,15,\n    65,215,31,24,64,77,13,65,220,20,65,214,14,71,4,40,65,213,13,32,0,130,0,35,21,1,2,0,135,0,34,36,0,72,134,10,36,1,0,26,0,130,134,11,36,2,0,14,0,108,134,11,32,3,138,23,32,4,138,11,34,\n    5,0,20,134,33,34,0,0,6,132,23,32,1,134,15,32,18,130,25,133,11,37,1,0,13,0,49,0,133,11,36,2,0,7,0,38,134,11,36,3,0,17,0,45,134,11,32,4,138,35,36,5,0,10,0,62,134,23,32,6,132,23,36,3,\n    0,1,4,9,130,87,131,167,133,11,133,167,133,11,133,167,133,11,37,3,0,34,0,122,0,133,11,133,167,133,11,133,167,133,11,133,167,34,50,0,48,130,1,34,52,0,47,134,5,8,49,49,0,53,98,121,32,\n    84,114,105,115,116,97,110,32,71,114,105,109,109,101,114,82,101,103,117,108,97,114,84,84,88,32,80,114,111,103,103,121,67,108,101,97,110,84,84,50,48,48,52,47,130,2,53,49,53,0,98,0,121,\n    0,32,0,84,0,114,0,105,0,115,0,116,0,97,0,110,130,15,32,71,132,15,36,109,0,109,0,101,130,9,32,82,130,5,36,103,0,117,0,108,130,29,32,114,130,43,34,84,0,88,130,35,32,80,130,25,34,111,\n    0,103,130,1,34,121,0,67,130,27,32,101,132,59,32,84,130,31,33,0,0,65,155,9,34,20,0,0,65,11,6,130,8,135,2,33,1,1,130,9,8,120,1,1,2,1,3,1,4,1,5,1,6,1,7,1,8,1,9,1,10,1,11,1,12,1,13,1,14,\n    1,15,1,16,1,17,1,18,1,19,1,20,1,21,1,22,1,23,1,24,1,25,1,26,1,27,1,28,1,29,1,30,1,31,1,32,0,3,0,4,0,5,0,6,0,7,0,8,0,9,0,10,0,11,0,12,0,13,0,14,0,15,0,16,0,17,0,18,0,19,0,20,0,21,0,\n    22,0,23,0,24,0,25,0,26,0,27,0,28,0,29,0,30,0,31,130,187,8,66,33,0,34,0,35,0,36,0,37,0,38,0,39,0,40,0,41,0,42,0,43,0,44,0,45,0,46,0,47,0,48,0,49,0,50,0,51,0,52,0,53,0,54,0,55,0,56,0,\n    57,0,58,0,59,0,60,0,61,0,62,0,63,0,64,0,65,0,66,130,243,9,75,68,0,69,0,70,0,71,0,72,0,73,0,74,0,75,0,76,0,77,0,78,0,79,0,80,0,81,0,82,0,83,0,84,0,85,0,86,0,87,0,88,0,89,0,90,0,91,0,\n    92,0,93,0,94,0,95,0,96,0,97,1,33,1,34,1,35,1,36,1,37,1,38,1,39,1,40,1,41,1,42,1,43,1,44,1,45,1,46,1,47,1,48,1,49,1,50,1,51,1,52,1,53,1,54,1,55,1,56,1,57,1,58,1,59,1,60,1,61,1,62,1,\n    63,1,64,1,65,0,172,0,163,0,132,0,133,0,189,0,150,0,232,0,134,0,142,0,139,0,157,0,169,0,164,0,239,0,138,0,218,0,131,0,147,0,242,0,243,0,141,0,151,0,136,0,195,0,222,0,241,0,158,0,170,\n    0,245,0,244,0,246,0,162,0,173,0,201,0,199,0,174,0,98,0,99,0,144,0,100,0,203,0,101,0,200,0,202,0,207,0,204,0,205,0,206,0,233,0,102,0,211,0,208,0,209,0,175,0,103,0,240,0,145,0,214,0,\n    212,0,213,0,104,0,235,0,237,0,137,0,106,0,105,0,107,0,109,0,108,0,110,0,160,0,111,0,113,0,112,0,114,0,115,0,117,0,116,0,118,0,119,0,234,0,120,0,122,0,121,0,123,0,125,0,124,0,184,0,\n    161,0,127,0,126,0,128,0,129,0,236,0,238,0,186,14,117,110,105,99,111,100,101,35,48,120,48,48,48,49,141,14,32,50,141,14,32,51,141,14,32,52,141,14,32,53,141,14,32,54,141,14,32,55,141,\n    14,32,56,141,14,32,57,141,14,32,97,141,14,32,98,141,14,32,99,141,14,32,100,141,14,32,101,141,14,32,102,140,14,33,49,48,141,14,141,239,32,49,141,239,32,49,141,239,32,49,141,239,32,49,\n    141,239,32,49,141,239,32,49,141,239,32,49,141,239,32,49,141,239,32,49,141,239,32,49,141,239,32,49,141,239,32,49,141,239,32,49,141,239,45,49,102,6,100,101,108,101,116,101,4,69,117,114,\n    111,140,236,32,56,141,236,32,56,141,236,32,56,141,236,32,56,141,236,32,56,141,236,32,56,141,236,32,56,141,236,32,56,141,236,32,56,141,236,32,56,141,236,32,56,141,236,32,56,141,236,\n    32,56,141,236,32,56,141,236,32,56,65,220,13,32,57,65,220,13,32,57,141,239,32,57,141,239,32,57,141,239,32,57,141,239,32,57,141,239,32,57,141,239,32,57,141,239,32,57,141,239,32,57,141,\n    239,32,57,141,239,32,57,141,239,32,57,141,239,32,57,141,239,32,57,141,239,35,57,102,0,0,5,250,72,249,98,247,\n};\n\nstatic const char* GetDefaultCompressedFontDataTTF(int* out_size)\n{\n    *out_size = proggy_clean_ttf_compressed_size;\n    return (const char*)proggy_clean_ttf_compressed_data;\n}\n#endif // #ifndef IMGUI_DISABLE_DEFAULT_FONT\n\n#endif // #ifndef IMGUI_DISABLE\n"
  },
  {
    "path": "src/DesktopPlusUI/imgui/imgui_internal.h",
    "content": "// dear imgui, v1.91.9b\n// (internal structures/api)\n\n// You may use this file to debug, understand or extend Dear ImGui features but we don't provide any guarantee of forward compatibility.\n\n/*\n\nIndex of this file:\n\n// [SECTION] Header mess\n// [SECTION] Forward declarations\n// [SECTION] Context pointer\n// [SECTION] STB libraries includes\n// [SECTION] Macros\n// [SECTION] Generic helpers\n// [SECTION] ImDrawList support\n// [SECTION] Style support\n// [SECTION] Data types support\n// [SECTION] Widgets support: flags, enums, data structures\n// [SECTION] Popup support\n// [SECTION] Inputs support\n// [SECTION] Clipper support\n// [SECTION] Navigation support\n// [SECTION] Typing-select support\n// [SECTION] Columns support\n// [SECTION] Box-select support\n// [SECTION] Multi-select support\n// [SECTION] Docking support\n// [SECTION] Viewport support\n// [SECTION] Settings support\n// [SECTION] Localization support\n// [SECTION] Error handling, State recovery support\n// [SECTION] Metrics, Debug tools\n// [SECTION] Generic context hooks\n// [SECTION] ImGuiContext (main imgui context)\n// [SECTION] ImGuiWindowTempData, ImGuiWindow\n// [SECTION] Tab bar, Tab item support\n// [SECTION] Table support\n// [SECTION] ImGui internal API\n// [SECTION] ImFontAtlas internal API\n// [SECTION] Test Engine specific hooks (imgui_test_engine)\n\n*/\n\n#pragma once\n#ifndef IMGUI_DISABLE\n\n//-----------------------------------------------------------------------------\n// [SECTION] Header mess\n//-----------------------------------------------------------------------------\n\n#ifndef IMGUI_VERSION\n#include \"imgui.h\"\n#endif\n\n#include <stdio.h>      // FILE*, sscanf\n#include <stdlib.h>     // NULL, malloc, free, qsort, atoi, atof\n#include <math.h>       // sqrtf, fabsf, fmodf, powf, floorf, ceilf, cosf, sinf\n#include <limits.h>     // INT_MIN, INT_MAX\n\n// Enable SSE intrinsics if available\n#if (defined __SSE__ || defined __x86_64__ || defined _M_X64 || (defined(_M_IX86_FP) && (_M_IX86_FP >= 1))) && !defined(IMGUI_DISABLE_SSE)\n#define IMGUI_ENABLE_SSE\n#include <immintrin.h>\n#if (defined __AVX__ || defined __SSE4_2__)\n#define IMGUI_ENABLE_SSE4_2\n#include <nmmintrin.h>\n#endif\n#endif\n// Emscripten has partial SSE 4.2 support where _mm_crc32_u32 is not available. See https://emscripten.org/docs/porting/simd.html#id11 and #8213\n#if defined(IMGUI_ENABLE_SSE4_2) && !defined(IMGUI_USE_LEGACY_CRC32_ADLER) && !defined(__EMSCRIPTEN__)\n#define IMGUI_ENABLE_SSE4_2_CRC\n#endif\n\n// Visual Studio warnings\n#ifdef _MSC_VER\n#pragma warning (push)\n#pragma warning (disable: 4251)     // class 'xxx' needs to have dll-interface to be used by clients of struct 'xxx' // when IMGUI_API is set to__declspec(dllexport)\n#pragma warning (disable: 26812)    // The enum type 'xxx' is unscoped. Prefer 'enum class' over 'enum' (Enum.3). [MSVC Static Analyzer)\n#pragma warning (disable: 26495)    // [Static Analyzer] Variable 'XXX' is uninitialized. Always initialize a member variable (type.6).\n#if defined(_MSC_VER) && _MSC_VER >= 1922 // MSVC 2019 16.2 or later\n#pragma warning (disable: 5054)     // operator '|': deprecated between enumerations of different types\n#endif\n#endif\n\n// Clang/GCC warnings with -Weverything\n#if defined(__clang__)\n#pragma clang diagnostic push\n#if __has_warning(\"-Wunknown-warning-option\")\n#pragma clang diagnostic ignored \"-Wunknown-warning-option\"         // warning: unknown warning group 'xxx'\n#endif\n#pragma clang diagnostic ignored \"-Wunknown-pragmas\"                // warning: unknown warning group 'xxx'\n#pragma clang diagnostic ignored \"-Wfloat-equal\"                    // warning: comparing floating point with == or != is unsafe // storing and comparing against same constants ok, for ImFloor()\n#pragma clang diagnostic ignored \"-Wold-style-cast\"                 // warning: use of old-style cast\n#pragma clang diagnostic ignored \"-Wzero-as-null-pointer-constant\"  // warning: zero as null pointer constant\n#pragma clang diagnostic ignored \"-Wdouble-promotion\"               // warning: implicit conversion from 'float' to 'double' when passing argument to function\n#pragma clang diagnostic ignored \"-Wimplicit-int-float-conversion\"  // warning: implicit conversion from 'xxx' to 'float' may lose precision\n#pragma clang diagnostic ignored \"-Wmissing-noreturn\"               // warning: function 'xxx' could be declared with attribute 'noreturn'\n#pragma clang diagnostic ignored \"-Wdeprecated-enum-enum-conversion\"// warning: bitwise operation between different enumeration types ('XXXFlags_' and 'XXXFlagsPrivate_') is deprecated\n#pragma clang diagnostic ignored \"-Wunsafe-buffer-usage\"            // warning: 'xxx' is an unsafe pointer used for buffer access\n#pragma clang diagnostic ignored \"-Wnontrivial-memaccess\"           // warning: first argument in call to 'memset' is a pointer to non-trivially copyable type\n#elif defined(__GNUC__)\n#pragma GCC diagnostic push\n#pragma GCC diagnostic ignored \"-Wpragmas\"                          // warning: unknown option after '#pragma GCC diagnostic' kind\n#pragma GCC diagnostic ignored \"-Wfloat-equal\"                      // warning: comparing floating-point with '==' or '!=' is unsafe\n#pragma GCC diagnostic ignored \"-Wclass-memaccess\"                  // [__GNUC__ >= 8] warning: 'memset/memcpy' clearing/writing an object of type 'xxxx' with no trivial copy-assignment; use assignment or value-initialization instead\n#pragma GCC diagnostic ignored \"-Wdeprecated-enum-enum-conversion\"  // warning: bitwise operation between different enumeration types ('XXXFlags_' and 'XXXFlagsPrivate_') is deprecated\n#endif\n\n// In 1.89.4, we moved the implementation of \"courtesy maths operators\" from imgui_internal.h in imgui.h\n// As they are frequently requested, we do not want to encourage to many people using imgui_internal.h\n#if defined(IMGUI_DEFINE_MATH_OPERATORS) && !defined(IMGUI_DEFINE_MATH_OPERATORS_IMPLEMENTED)\n#error Please '#define IMGUI_DEFINE_MATH_OPERATORS' _BEFORE_ including imgui.h!\n#endif\n\n// Legacy defines\n#ifdef IMGUI_DISABLE_FORMAT_STRING_FUNCTIONS            // Renamed in 1.74\n#error Use IMGUI_DISABLE_DEFAULT_FORMAT_FUNCTIONS\n#endif\n#ifdef IMGUI_DISABLE_MATH_FUNCTIONS                     // Renamed in 1.74\n#error Use IMGUI_DISABLE_DEFAULT_MATH_FUNCTIONS\n#endif\n\n// Enable stb_truetype by default unless FreeType is enabled.\n// You can compile with both by defining both IMGUI_ENABLE_FREETYPE and IMGUI_ENABLE_STB_TRUETYPE together.\n#ifndef IMGUI_ENABLE_FREETYPE\n#define IMGUI_ENABLE_STB_TRUETYPE\n#endif\n\n//-----------------------------------------------------------------------------\n// [SECTION] Forward declarations\n//-----------------------------------------------------------------------------\n\n// Utilities\n// (other types which are not forwarded declared are: ImBitArray<>, ImSpan<>, ImSpanAllocator<>, ImPool<>, ImChunkStream<>)\nstruct ImBitVector;                 // Store 1-bit per value\nstruct ImRect;                      // An axis-aligned rectangle (2 points)\nstruct ImGuiTextIndex;              // Maintain a line index for a text buffer.\n\n// ImDrawList/ImFontAtlas\nstruct ImDrawDataBuilder;           // Helper to build a ImDrawData instance\nstruct ImDrawListSharedData;        // Data shared between all ImDrawList instances\n\n// ImGui\nstruct ImGuiBoxSelectState;         // Box-selection state (currently used by multi-selection, could potentially be used by others)\nstruct ImGuiColorMod;               // Stacked color modifier, backup of modified data so we can restore it\nstruct ImGuiContext;                // Main Dear ImGui context\nstruct ImGuiContextHook;            // Hook for extensions like ImGuiTestEngine\nstruct ImGuiDataTypeInfo;           // Type information associated to a ImGuiDataType enum\nstruct ImGuiDeactivatedItemData;    // Data for IsItemDeactivated()/IsItemDeactivatedAfterEdit() function.\nstruct ImGuiErrorRecoveryState;     // Storage of stack sizes for error handling and recovery\nstruct ImGuiGroupData;              // Stacked storage data for BeginGroup()/EndGroup()\nstruct ImGuiInputTextState;         // Internal state of the currently focused/edited text input box\nstruct ImGuiInputTextDeactivateData;// Short term storage to backup text of a deactivating InputText() while another is stealing active id\nstruct ImGuiLastItemData;           // Status storage for last submitted items\nstruct ImGuiLocEntry;               // A localization entry.\nstruct ImGuiMenuColumns;            // Simple column measurement, currently used for MenuItem() only\nstruct ImGuiMultiSelectState;       // Multi-selection persistent state (for focused selection).\nstruct ImGuiMultiSelectTempData;    // Multi-selection temporary state (while traversing).\nstruct ImGuiNavItemData;            // Result of a keyboard/gamepad directional navigation move query result\nstruct ImGuiMetricsConfig;          // Storage for ShowMetricsWindow() and DebugNodeXXX() functions\nstruct ImGuiNextWindowData;         // Storage for SetNextWindow** functions\nstruct ImGuiNextItemData;           // Storage for SetNextItem** functions\nstruct ImGuiOldColumnData;          // Storage data for a single column for legacy Columns() api\nstruct ImGuiOldColumns;             // Storage data for a columns set for legacy Columns() api\nstruct ImGuiPopupData;              // Storage for current popup stack\nstruct ImGuiSettingsHandler;        // Storage for one type registered in the .ini file\nstruct ImGuiStyleMod;               // Stacked style modifier, backup of modified data so we can restore it\nstruct ImGuiStyleVarInfo;           // Style variable information (e.g. to access style variables from an enum)\nstruct ImGuiTabBar;                 // Storage for a tab bar\nstruct ImGuiTabItem;                // Storage for a tab item (within a tab bar)\nstruct ImGuiTable;                  // Storage for a table\nstruct ImGuiTableHeaderData;        // Storage for TableAngledHeadersRow()\nstruct ImGuiTableColumn;            // Storage for one column of a table\nstruct ImGuiTableInstanceData;      // Storage for one instance of a same table\nstruct ImGuiTableTempData;          // Temporary storage for one table (one per table in the stack), shared between tables.\nstruct ImGuiTableSettings;          // Storage for a table .ini settings\nstruct ImGuiTableColumnsSettings;   // Storage for a column .ini settings\nstruct ImGuiTreeNodeStackData;      // Temporary storage for TreeNode().\nstruct ImGuiTypingSelectState;      // Storage for GetTypingSelectRequest()\nstruct ImGuiTypingSelectRequest;    // Storage for GetTypingSelectRequest() (aimed to be public)\nstruct ImGuiWindow;                 // Storage for one window\nstruct ImGuiWindowTempData;         // Temporary storage for one window (that's the data which in theory we could ditch at the end of the frame, in practice we currently keep it for each window)\nstruct ImGuiWindowSettings;         // Storage for a window .ini settings (we keep one of those even if the actual window wasn't instanced during this session)\n\n// Enumerations\n// Use your programming IDE \"Go to definition\" facility on the names of the center columns to find the actual flags/enum lists.\nenum ImGuiLocKey : int;                 // -> enum ImGuiLocKey              // Enum: a localization entry for translation.\ntypedef int ImGuiLayoutType;            // -> enum ImGuiLayoutType_         // Enum: Horizontal or vertical\n\n// Flags\ntypedef int ImGuiActivateFlags;         // -> enum ImGuiActivateFlags_      // Flags: for navigation/focus function (will be for ActivateItem() later)\ntypedef int ImGuiDebugLogFlags;         // -> enum ImGuiDebugLogFlags_      // Flags: for ShowDebugLogWindow(), g.DebugLogFlags\ntypedef int ImGuiFocusRequestFlags;     // -> enum ImGuiFocusRequestFlags_  // Flags: for FocusWindow()\ntypedef int ImGuiItemStatusFlags;       // -> enum ImGuiItemStatusFlags_    // Flags: for g.LastItemData.StatusFlags\ntypedef int ImGuiOldColumnFlags;        // -> enum ImGuiOldColumnFlags_     // Flags: for BeginColumns()\ntypedef int ImGuiLogFlags;              // -> enum ImGuiLogFlags_           // Flags: for LogBegin() text capturing function\ntypedef int ImGuiNavRenderCursorFlags;  // -> enum ImGuiNavRenderCursorFlags_//Flags: for RenderNavCursor()\ntypedef int ImGuiNavMoveFlags;          // -> enum ImGuiNavMoveFlags_       // Flags: for navigation requests\ntypedef int ImGuiNextItemDataFlags;     // -> enum ImGuiNextItemDataFlags_  // Flags: for SetNextItemXXX() functions\ntypedef int ImGuiNextWindowDataFlags;   // -> enum ImGuiNextWindowDataFlags_// Flags: for SetNextWindowXXX() functions\ntypedef int ImGuiScrollFlags;           // -> enum ImGuiScrollFlags_        // Flags: for ScrollToItem() and navigation requests\ntypedef int ImGuiSeparatorFlags;        // -> enum ImGuiSeparatorFlags_     // Flags: for SeparatorEx()\ntypedef int ImGuiTextFlags;             // -> enum ImGuiTextFlags_          // Flags: for TextEx()\ntypedef int ImGuiTooltipFlags;          // -> enum ImGuiTooltipFlags_       // Flags: for BeginTooltipEx()\ntypedef int ImGuiTypingSelectFlags;     // -> enum ImGuiTypingSelectFlags_  // Flags: for GetTypingSelectRequest()\ntypedef int ImGuiWindowRefreshFlags;    // -> enum ImGuiWindowRefreshFlags_ // Flags: for SetNextWindowRefreshPolicy()\n\n//-----------------------------------------------------------------------------\n// [SECTION] Context pointer\n// See implementation of this variable in imgui.cpp for comments and details.\n//-----------------------------------------------------------------------------\n\n#ifndef GImGui\nextern IMGUI_API ImGuiContext* GImGui;  // Current implicit context pointer\n#endif\n\n//-----------------------------------------------------------------------------\n// [SECTION] Macros\n//-----------------------------------------------------------------------------\n\n// Debug Printing Into TTY\n// (since IMGUI_VERSION_NUM >= 18729: IMGUI_DEBUG_LOG was reworked into IMGUI_DEBUG_PRINTF (and removed framecount from it). If you were using a #define IMGUI_DEBUG_LOG please rename)\n#ifndef IMGUI_DEBUG_PRINTF\n#ifndef IMGUI_DISABLE_DEFAULT_FORMAT_FUNCTIONS\n#define IMGUI_DEBUG_PRINTF(_FMT,...)    printf(_FMT, __VA_ARGS__)\n#else\n#define IMGUI_DEBUG_PRINTF(_FMT,...)    ((void)0)\n#endif\n#endif\n\n// Debug Logging for ShowDebugLogWindow(). This is designed for relatively rare events so please don't spam.\n#define IMGUI_DEBUG_LOG_ERROR(...)      do { if (g.DebugLogFlags & ImGuiDebugLogFlags_EventError)       IMGUI_DEBUG_LOG(__VA_ARGS__); else g.DebugLogSkippedErrors++; } while (0)\n#define IMGUI_DEBUG_LOG_ACTIVEID(...)   do { if (g.DebugLogFlags & ImGuiDebugLogFlags_EventActiveId)    IMGUI_DEBUG_LOG(__VA_ARGS__); } while (0)\n#define IMGUI_DEBUG_LOG_FOCUS(...)      do { if (g.DebugLogFlags & ImGuiDebugLogFlags_EventFocus)       IMGUI_DEBUG_LOG(__VA_ARGS__); } while (0)\n#define IMGUI_DEBUG_LOG_POPUP(...)      do { if (g.DebugLogFlags & ImGuiDebugLogFlags_EventPopup)       IMGUI_DEBUG_LOG(__VA_ARGS__); } while (0)\n#define IMGUI_DEBUG_LOG_NAV(...)        do { if (g.DebugLogFlags & ImGuiDebugLogFlags_EventNav)         IMGUI_DEBUG_LOG(__VA_ARGS__); } while (0)\n#define IMGUI_DEBUG_LOG_SELECTION(...)  do { if (g.DebugLogFlags & ImGuiDebugLogFlags_EventSelection)   IMGUI_DEBUG_LOG(__VA_ARGS__); } while (0)\n#define IMGUI_DEBUG_LOG_CLIPPER(...)    do { if (g.DebugLogFlags & ImGuiDebugLogFlags_EventClipper)     IMGUI_DEBUG_LOG(__VA_ARGS__); } while (0)\n#define IMGUI_DEBUG_LOG_IO(...)         do { if (g.DebugLogFlags & ImGuiDebugLogFlags_EventIO)          IMGUI_DEBUG_LOG(__VA_ARGS__); } while (0)\n#define IMGUI_DEBUG_LOG_FONT(...)       do { if (g.DebugLogFlags & ImGuiDebugLogFlags_EventFont)        IMGUI_DEBUG_LOG(__VA_ARGS__); } while (0)\n#define IMGUI_DEBUG_LOG_INPUTROUTING(...) do{if (g.DebugLogFlags & ImGuiDebugLogFlags_EventInputRouting)IMGUI_DEBUG_LOG(__VA_ARGS__); } while (0)\n\n// Static Asserts\n#define IM_STATIC_ASSERT(_COND)         static_assert(_COND, \"\")\n\n// \"Paranoid\" Debug Asserts are meant to only be enabled during specific debugging/work, otherwise would slow down the code too much.\n// We currently don't have many of those so the effect is currently negligible, but onward intent to add more aggressive ones in the code.\n//#define IMGUI_DEBUG_PARANOID\n#ifdef IMGUI_DEBUG_PARANOID\n#define IM_ASSERT_PARANOID(_EXPR)       IM_ASSERT(_EXPR)\n#else\n#define IM_ASSERT_PARANOID(_EXPR)\n#endif\n\n// Misc Macros\n#define IM_PI                           3.14159265358979323846f\n#ifdef _WIN32\n#define IM_NEWLINE                      \"\\r\\n\"   // Play it nice with Windows users (Update: since 2018-05, Notepad finally appears to support Unix-style carriage returns!)\n#else\n#define IM_NEWLINE                      \"\\n\"\n#endif\n#ifndef IM_TABSIZE                      // Until we move this to runtime and/or add proper tab support, at least allow users to compile-time override\n#define IM_TABSIZE                      (4)\n#endif\n#define IM_MEMALIGN(_OFF,_ALIGN)        (((_OFF) + ((_ALIGN) - 1)) & ~((_ALIGN) - 1))           // Memory align e.g. IM_ALIGN(0,4)=0, IM_ALIGN(1,4)=4, IM_ALIGN(4,4)=4, IM_ALIGN(5,4)=8\n#define IM_F32_TO_INT8_UNBOUND(_VAL)    ((int)((_VAL) * 255.0f + ((_VAL)>=0 ? 0.5f : -0.5f)))   // Unsaturated, for display purpose\n#define IM_F32_TO_INT8_SAT(_VAL)        ((int)(ImSaturate(_VAL) * 255.0f + 0.5f))               // Saturated, always output 0..255\n#define IM_TRUNC(_VAL)                  ((float)(int)(_VAL))                                    // ImTrunc() is not inlined in MSVC debug builds\n#define IM_ROUND(_VAL)                  ((float)(int)((_VAL) + 0.5f))                           //\n#define IM_STRINGIFY_HELPER(_X)         #_X\n#define IM_STRINGIFY(_X)                IM_STRINGIFY_HELPER(_X)                                 // Preprocessor idiom to stringify e.g. an integer.\n#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS\n#define IM_FLOOR IM_TRUNC\n#endif\n\n// Hint for branch prediction\n#if (defined(__cplusplus) && (__cplusplus >= 202002L)) || (defined(_MSVC_LANG) && (_MSVC_LANG >= 202002L))\n#define IM_LIKELY   [[likely]]\n#define IM_UNLIKELY [[unlikely]]\n#else\n#define IM_LIKELY\n#define IM_UNLIKELY\n#endif\n\n// Enforce cdecl calling convention for functions called by the standard library, in case compilation settings changed the default to e.g. __vectorcall\n#ifdef _MSC_VER\n#define IMGUI_CDECL __cdecl\n#else\n#define IMGUI_CDECL\n#endif\n\n// Warnings\n#if defined(_MSC_VER) && !defined(__clang__)\n#define IM_MSVC_WARNING_SUPPRESS(XXXX)  __pragma(warning(suppress: XXXX))\n#else\n#define IM_MSVC_WARNING_SUPPRESS(XXXX)\n#endif\n\n// Debug Tools\n// Use 'Metrics/Debugger->Tools->Item Picker' to break into the call-stack of a specific item.\n// This will call IM_DEBUG_BREAK() which you may redefine yourself. See https://github.com/scottt/debugbreak for more reference.\n#ifndef IM_DEBUG_BREAK\n#if defined (_MSC_VER)\n#define IM_DEBUG_BREAK()    __debugbreak()\n#elif defined(__clang__)\n#define IM_DEBUG_BREAK()    __builtin_debugtrap()\n#elif defined(__GNUC__) && (defined(__i386__) || defined(__x86_64__))\n#define IM_DEBUG_BREAK()    __asm__ volatile(\"int3;nop\")\n#elif defined(__GNUC__) && defined(__thumb__)\n#define IM_DEBUG_BREAK()    __asm__ volatile(\".inst 0xde01\")\n#elif defined(__GNUC__) && defined(__arm__) && !defined(__thumb__)\n#define IM_DEBUG_BREAK()    __asm__ volatile(\".inst 0xe7f001f0\")\n#else\n#define IM_DEBUG_BREAK()    IM_ASSERT(0)    // It is expected that you define IM_DEBUG_BREAK() into something that will break nicely in a debugger!\n#endif\n#endif // #ifndef IM_DEBUG_BREAK\n\n// Format specifiers, printing 64-bit hasn't been decently standardized...\n// In a real application you should be using PRId64 and PRIu64 from <inttypes.h> (non-windows) and on Windows define them yourself.\n#if defined(_MSC_VER) && !defined(__clang__)\n#define IM_PRId64   \"I64d\"\n#define IM_PRIu64   \"I64u\"\n#define IM_PRIX64   \"I64X\"\n#else\n#define IM_PRId64   \"lld\"\n#define IM_PRIu64   \"llu\"\n#define IM_PRIX64   \"llX\"\n#endif\n\n//-----------------------------------------------------------------------------\n// [SECTION] Generic helpers\n// Note that the ImXXX helpers functions are lower-level than ImGui functions.\n// ImGui functions or the ImGui context are never called/used from other ImXXX functions.\n//-----------------------------------------------------------------------------\n// - Helpers: Hashing\n// - Helpers: Sorting\n// - Helpers: Bit manipulation\n// - Helpers: String\n// - Helpers: Formatting\n// - Helpers: UTF-8 <> wchar conversions\n// - Helpers: ImVec2/ImVec4 operators\n// - Helpers: Maths\n// - Helpers: Geometry\n// - Helper: ImVec1\n// - Helper: ImVec2ih\n// - Helper: ImRect\n// - Helper: ImBitArray\n// - Helper: ImBitVector\n// - Helper: ImSpan<>, ImSpanAllocator<>\n// - Helper: ImPool<>\n// - Helper: ImChunkStream<>\n// - Helper: ImGuiTextIndex\n// - Helper: ImGuiStorage\n//-----------------------------------------------------------------------------\n\n// Helpers: Hashing\nIMGUI_API ImGuiID       ImHashData(const void* data, size_t data_size, ImGuiID seed = 0);\nIMGUI_API ImGuiID       ImHashStr(const char* data, size_t data_size = 0, ImGuiID seed = 0);\n\n// Helpers: Sorting\n#ifndef ImQsort\nstatic inline void      ImQsort(void* base, size_t count, size_t size_of_element, int(IMGUI_CDECL *compare_func)(void const*, void const*)) { if (count > 1) qsort(base, count, size_of_element, compare_func); }\n#endif\n\n// Helpers: Color Blending\nIMGUI_API ImU32         ImAlphaBlendColors(ImU32 col_a, ImU32 col_b);\n\n// Helpers: Bit manipulation\nstatic inline bool      ImIsPowerOfTwo(int v)               { return v != 0 && (v & (v - 1)) == 0; }\nstatic inline bool      ImIsPowerOfTwo(ImU64 v)             { return v != 0 && (v & (v - 1)) == 0; }\nstatic inline int       ImUpperPowerOfTwo(int v)            { v--; v |= v >> 1; v |= v >> 2; v |= v >> 4; v |= v >> 8; v |= v >> 16; v++; return v; }\nstatic inline unsigned int ImCountSetBits(unsigned int v)   { unsigned int count = 0; while (v > 0) { v = v & (v - 1); count++; } return count; }\n\n// Helpers: String\n#define ImStrlen strlen\n#define ImMemchr memchr\nIMGUI_API int           ImStricmp(const char* str1, const char* str2);                      // Case insensitive compare.\nIMGUI_API int           ImStrnicmp(const char* str1, const char* str2, size_t count);       // Case insensitive compare to a certain count.\nIMGUI_API void          ImStrncpy(char* dst, const char* src, size_t count);                // Copy to a certain count and always zero terminate (strncpy doesn't).\nIMGUI_API char*         ImStrdup(const char* str);                                          // Duplicate a string.\nIMGUI_API char*         ImStrdupcpy(char* dst, size_t* p_dst_size, const char* str);        // Copy in provided buffer, recreate buffer if needed.\nIMGUI_API const char*   ImStrchrRange(const char* str_begin, const char* str_end, char c);  // Find first occurrence of 'c' in string range.\nIMGUI_API const char*   ImStreolRange(const char* str, const char* str_end);                // End end-of-line\nIMGUI_API const char*   ImStristr(const char* haystack, const char* haystack_end, const char* needle, const char* needle_end);  // Find a substring in a string range.\nIMGUI_API void          ImStrTrimBlanks(char* str);                                         // Remove leading and trailing blanks from a buffer.\nIMGUI_API const char*   ImStrSkipBlank(const char* str);                                    // Find first non-blank character.\nIMGUI_API int           ImStrlenW(const ImWchar* str);                                      // Computer string length (ImWchar string)\nIMGUI_API const char*   ImStrbol(const char* buf_mid_line, const char* buf_begin);          // Find beginning-of-line\nIM_MSVC_RUNTIME_CHECKS_OFF\nstatic inline char      ImToUpper(char c)               { return (c >= 'a' && c <= 'z') ? c &= ~32 : c; }\nstatic inline bool      ImCharIsBlankA(char c)          { return c == ' ' || c == '\\t'; }\nstatic inline bool      ImCharIsBlankW(unsigned int c)  { return c == ' ' || c == '\\t' || c == 0x3000; }\nstatic inline bool      ImCharIsXdigitA(char c)         { return (c >= '0' && c <= '9') || (c >= 'A' && c <= 'F') || (c >= 'a' && c <= 'f'); }\nIM_MSVC_RUNTIME_CHECKS_RESTORE\n\n// Helpers: Formatting\nIMGUI_API int           ImFormatString(char* buf, size_t buf_size, const char* fmt, ...) IM_FMTARGS(3);\nIMGUI_API int           ImFormatStringV(char* buf, size_t buf_size, const char* fmt, va_list args) IM_FMTLIST(3);\nIMGUI_API void          ImFormatStringToTempBuffer(const char** out_buf, const char** out_buf_end, const char* fmt, ...) IM_FMTARGS(3);\nIMGUI_API void          ImFormatStringToTempBufferV(const char** out_buf, const char** out_buf_end, const char* fmt, va_list args) IM_FMTLIST(3);\nIMGUI_API const char*   ImParseFormatFindStart(const char* format);\nIMGUI_API const char*   ImParseFormatFindEnd(const char* format);\nIMGUI_API const char*   ImParseFormatTrimDecorations(const char* format, char* buf, size_t buf_size);\nIMGUI_API void          ImParseFormatSanitizeForPrinting(const char* fmt_in, char* fmt_out, size_t fmt_out_size);\nIMGUI_API const char*   ImParseFormatSanitizeForScanning(const char* fmt_in, char* fmt_out, size_t fmt_out_size);\nIMGUI_API int           ImParseFormatPrecision(const char* format, int default_value);\n\n// Helpers: UTF-8 <> wchar conversions\nIMGUI_API const char*   ImTextCharToUtf8(char out_buf[5], unsigned int c);                                                      // return out_buf\nIMGUI_API int           ImTextStrToUtf8(char* out_buf, int out_buf_size, const ImWchar* in_text, const ImWchar* in_text_end);   // return output UTF-8 bytes count\nIMGUI_API int           ImTextCharFromUtf8(unsigned int* out_char, const char* in_text, const char* in_text_end);               // read one character. return input UTF-8 bytes count\nIMGUI_API int           ImTextStrFromUtf8(ImWchar* out_buf, int out_buf_size, const char* in_text, const char* in_text_end, const char** in_remaining = NULL);   // return input UTF-8 bytes count\nIMGUI_API int           ImTextCountCharsFromUtf8(const char* in_text, const char* in_text_end);                                 // return number of UTF-8 code-points (NOT bytes count)\nIMGUI_API int           ImTextCountUtf8BytesFromChar(const char* in_text, const char* in_text_end);                             // return number of bytes to express one char in UTF-8\nIMGUI_API int           ImTextCountUtf8BytesFromStr(const ImWchar* in_text, const ImWchar* in_text_end);                        // return number of bytes to express string in UTF-8\nIMGUI_API const char*   ImTextFindPreviousUtf8Codepoint(const char* in_text_start, const char* in_text_curr);                   // return previous UTF-8 code-point.\nIMGUI_API int           ImTextCountLines(const char* in_text, const char* in_text_end);                                         // return number of lines taken by text. trailing carriage return doesn't count as an extra line.\n\n// Helpers: File System\n#ifdef IMGUI_DISABLE_FILE_FUNCTIONS\n#define IMGUI_DISABLE_DEFAULT_FILE_FUNCTIONS\ntypedef void* ImFileHandle;\nstatic inline ImFileHandle  ImFileOpen(const char*, const char*)                    { return NULL; }\nstatic inline bool          ImFileClose(ImFileHandle)                               { return false; }\nstatic inline ImU64         ImFileGetSize(ImFileHandle)                             { return (ImU64)-1; }\nstatic inline ImU64         ImFileRead(void*, ImU64, ImU64, ImFileHandle)           { return 0; }\nstatic inline ImU64         ImFileWrite(const void*, ImU64, ImU64, ImFileHandle)    { return 0; }\n#endif\n#ifndef IMGUI_DISABLE_DEFAULT_FILE_FUNCTIONS\ntypedef FILE* ImFileHandle;\nIMGUI_API ImFileHandle      ImFileOpen(const char* filename, const char* mode);\nIMGUI_API bool              ImFileClose(ImFileHandle file);\nIMGUI_API ImU64             ImFileGetSize(ImFileHandle file);\nIMGUI_API ImU64             ImFileRead(void* data, ImU64 size, ImU64 count, ImFileHandle file);\nIMGUI_API ImU64             ImFileWrite(const void* data, ImU64 size, ImU64 count, ImFileHandle file);\n#else\n#define IMGUI_DISABLE_TTY_FUNCTIONS // Can't use stdout, fflush if we are not using default file functions\n#endif\nIMGUI_API void*             ImFileLoadToMemory(const char* filename, const char* mode, size_t* out_file_size = NULL, int padding_bytes = 0);\n\n// Helpers: Maths\nIM_MSVC_RUNTIME_CHECKS_OFF\n// - Wrapper for standard libs functions. (Note that imgui_demo.cpp does _not_ use them to keep the code easy to copy)\n#ifndef IMGUI_DISABLE_DEFAULT_MATH_FUNCTIONS\n#define ImFabs(X)           fabsf(X)\n#define ImSqrt(X)           sqrtf(X)\n#define ImFmod(X, Y)        fmodf((X), (Y))\n#define ImCos(X)            cosf(X)\n#define ImSin(X)            sinf(X)\n#define ImAcos(X)           acosf(X)\n#define ImAtan2(Y, X)       atan2f((Y), (X))\n#define ImAtof(STR)         atof(STR)\n#define ImCeil(X)           ceilf(X)\nstatic inline float  ImPow(float x, float y)    { return powf(x, y); }          // DragBehaviorT/SliderBehaviorT uses ImPow with either float/double and need the precision\nstatic inline double ImPow(double x, double y)  { return pow(x, y); }\nstatic inline float  ImLog(float x)             { return logf(x); }             // DragBehaviorT/SliderBehaviorT uses ImLog with either float/double and need the precision\nstatic inline double ImLog(double x)            { return log(x); }\nstatic inline int    ImAbs(int x)               { return x < 0 ? -x : x; }\nstatic inline float  ImAbs(float x)             { return fabsf(x); }\nstatic inline double ImAbs(double x)            { return fabs(x); }\nstatic inline float  ImSign(float x)            { return (x < 0.0f) ? -1.0f : (x > 0.0f) ? 1.0f : 0.0f; } // Sign operator - returns -1, 0 or 1 based on sign of argument\nstatic inline double ImSign(double x)           { return (x < 0.0) ? -1.0 : (x > 0.0) ? 1.0 : 0.0; }\n#ifdef IMGUI_ENABLE_SSE\nstatic inline float  ImRsqrt(float x)           { return _mm_cvtss_f32(_mm_rsqrt_ss(_mm_set_ss(x))); }\n#else\nstatic inline float  ImRsqrt(float x)           { return 1.0f / sqrtf(x); }\n#endif\nstatic inline double ImRsqrt(double x)          { return 1.0 / sqrt(x); }\n#endif\n// - ImMin/ImMax/ImClamp/ImLerp/ImSwap are used by widgets which support variety of types: signed/unsigned int/long long float/double\n// (Exceptionally using templates here but we could also redefine them for those types)\ntemplate<typename T> static inline T ImMin(T lhs, T rhs)                        { return lhs < rhs ? lhs : rhs; }\ntemplate<typename T> static inline T ImMax(T lhs, T rhs)                        { return lhs >= rhs ? lhs : rhs; }\ntemplate<typename T> static inline T ImClamp(T v, T mn, T mx)                   { return (v < mn) ? mn : (v > mx) ? mx : v; }\ntemplate<typename T> static inline T ImLerp(T a, T b, float t)                  { return (T)(a + (b - a) * t); }\ntemplate<typename T> static inline void ImSwap(T& a, T& b)                      { T tmp = a; a = b; b = tmp; }\ntemplate<typename T> static inline T ImAddClampOverflow(T a, T b, T mn, T mx)   { if (b < 0 && (a < mn - b)) return mn; if (b > 0 && (a > mx - b)) return mx; return a + b; }\ntemplate<typename T> static inline T ImSubClampOverflow(T a, T b, T mn, T mx)   { if (b > 0 && (a < mn + b)) return mn; if (b < 0 && (a > mx + b)) return mx; return a - b; }\n// - Misc maths helpers\nstatic inline ImVec2 ImMin(const ImVec2& lhs, const ImVec2& rhs)                { return ImVec2(lhs.x < rhs.x ? lhs.x : rhs.x, lhs.y < rhs.y ? lhs.y : rhs.y); }\nstatic inline ImVec2 ImMax(const ImVec2& lhs, const ImVec2& rhs)                { return ImVec2(lhs.x >= rhs.x ? lhs.x : rhs.x, lhs.y >= rhs.y ? lhs.y : rhs.y); }\nstatic inline ImVec2 ImClamp(const ImVec2& v, const ImVec2&mn, const ImVec2&mx) { return ImVec2((v.x < mn.x) ? mn.x : (v.x > mx.x) ? mx.x : v.x, (v.y < mn.y) ? mn.y : (v.y > mx.y) ? mx.y : v.y); }\nstatic inline ImVec2 ImLerp(const ImVec2& a, const ImVec2& b, float t)          { return ImVec2(a.x + (b.x - a.x) * t, a.y + (b.y - a.y) * t); }\nstatic inline ImVec2 ImLerp(const ImVec2& a, const ImVec2& b, const ImVec2& t)  { return ImVec2(a.x + (b.x - a.x) * t.x, a.y + (b.y - a.y) * t.y); }\nstatic inline ImVec4 ImLerp(const ImVec4& a, const ImVec4& b, float t)          { return ImVec4(a.x + (b.x - a.x) * t, a.y + (b.y - a.y) * t, a.z + (b.z - a.z) * t, a.w + (b.w - a.w) * t); }\nstatic inline float  ImSaturate(float f)                                        { return (f < 0.0f) ? 0.0f : (f > 1.0f) ? 1.0f : f; }\nstatic inline float  ImLengthSqr(const ImVec2& lhs)                             { return (lhs.x * lhs.x) + (lhs.y * lhs.y); }\nstatic inline float  ImLengthSqr(const ImVec4& lhs)                             { return (lhs.x * lhs.x) + (lhs.y * lhs.y) + (lhs.z * lhs.z) + (lhs.w * lhs.w); }\nstatic inline float  ImInvLength(const ImVec2& lhs, float fail_value)           { float d = (lhs.x * lhs.x) + (lhs.y * lhs.y); if (d > 0.0f) return ImRsqrt(d); return fail_value; }\nstatic inline float  ImTrunc(float f)                                           { return (float)(int)(f); }\nstatic inline ImVec2 ImTrunc(const ImVec2& v)                                   { return ImVec2((float)(int)(v.x), (float)(int)(v.y)); }\nstatic inline float  ImFloor(float f)                                           { return (float)((f >= 0 || (float)(int)f == f) ? (int)f : (int)f - 1); } // Decent replacement for floorf()\nstatic inline ImVec2 ImFloor(const ImVec2& v)                                   { return ImVec2(ImFloor(v.x), ImFloor(v.y)); }\nstatic inline int    ImModPositive(int a, int b)                                { return (a + b) % b; }\nstatic inline float  ImDot(const ImVec2& a, const ImVec2& b)                    { return a.x * b.x + a.y * b.y; }\nstatic inline ImVec2 ImRotate(const ImVec2& v, float cos_a, float sin_a)        { return ImVec2(v.x * cos_a - v.y * sin_a, v.x * sin_a + v.y * cos_a); }\nstatic inline float  ImLinearSweep(float current, float target, float speed)    { if (current < target) return ImMin(current + speed, target); if (current > target) return ImMax(current - speed, target); return current; }\nstatic inline float  ImLinearRemapClamp(float s0, float s1, float d0, float d1, float x) { return ImSaturate((x - s0) / (s1 - s0)) * (d1 - d0) + d0; }\nstatic inline ImVec2 ImMul(const ImVec2& lhs, const ImVec2& rhs)                { return ImVec2(lhs.x * rhs.x, lhs.y * rhs.y); }\nstatic inline bool   ImIsFloatAboveGuaranteedIntegerPrecision(float f)          { return f <= -16777216 || f >= 16777216; }\nstatic inline float  ImExponentialMovingAverage(float avg, float sample, int n) { avg -= avg / n; avg += sample / n; return avg; }\nIM_MSVC_RUNTIME_CHECKS_RESTORE\n\n// Helpers: Geometry\nIMGUI_API ImVec2     ImBezierCubicCalc(const ImVec2& p1, const ImVec2& p2, const ImVec2& p3, const ImVec2& p4, float t);\nIMGUI_API ImVec2     ImBezierCubicClosestPoint(const ImVec2& p1, const ImVec2& p2, const ImVec2& p3, const ImVec2& p4, const ImVec2& p, int num_segments);       // For curves with explicit number of segments\nIMGUI_API ImVec2     ImBezierCubicClosestPointCasteljau(const ImVec2& p1, const ImVec2& p2, const ImVec2& p3, const ImVec2& p4, const ImVec2& p, float tess_tol);// For auto-tessellated curves you can use tess_tol = style.CurveTessellationTol\nIMGUI_API ImVec2     ImBezierQuadraticCalc(const ImVec2& p1, const ImVec2& p2, const ImVec2& p3, float t);\nIMGUI_API ImVec2     ImLineClosestPoint(const ImVec2& a, const ImVec2& b, const ImVec2& p);\nIMGUI_API bool       ImTriangleContainsPoint(const ImVec2& a, const ImVec2& b, const ImVec2& c, const ImVec2& p);\nIMGUI_API ImVec2     ImTriangleClosestPoint(const ImVec2& a, const ImVec2& b, const ImVec2& c, const ImVec2& p);\nIMGUI_API void       ImTriangleBarycentricCoords(const ImVec2& a, const ImVec2& b, const ImVec2& c, const ImVec2& p, float& out_u, float& out_v, float& out_w);\ninline float         ImTriangleArea(const ImVec2& a, const ImVec2& b, const ImVec2& c)          { return ImFabs((a.x * (b.y - c.y)) + (b.x * (c.y - a.y)) + (c.x * (a.y - b.y))) * 0.5f; }\ninline bool          ImTriangleIsClockwise(const ImVec2& a, const ImVec2& b, const ImVec2& c)   { return ((b.x - a.x) * (c.y - b.y)) - ((c.x - b.x) * (b.y - a.y)) > 0.0f; }\n\n// Helper: ImVec1 (1D vector)\n// (this odd construct is used to facilitate the transition between 1D and 2D, and the maintenance of some branches/patches)\nIM_MSVC_RUNTIME_CHECKS_OFF\nstruct ImVec1\n{\n    float   x;\n    constexpr ImVec1()         : x(0.0f) { }\n    constexpr ImVec1(float _x) : x(_x) { }\n};\n\n// Helper: ImVec2ih (2D vector, half-size integer, for long-term packed storage)\nstruct ImVec2ih\n{\n    short   x, y;\n    constexpr ImVec2ih()                           : x(0), y(0) {}\n    constexpr ImVec2ih(short _x, short _y)         : x(_x), y(_y) {}\n    constexpr explicit ImVec2ih(const ImVec2& rhs) : x((short)rhs.x), y((short)rhs.y) {}\n};\n\n// Helper: ImRect (2D axis aligned bounding-box)\n// NB: we can't rely on ImVec2 math operators being available here!\nstruct IMGUI_API ImRect\n{\n    ImVec2      Min;    // Upper-left\n    ImVec2      Max;    // Lower-right\n\n    constexpr ImRect()                                        : Min(0.0f, 0.0f), Max(0.0f, 0.0f)  {}\n    constexpr ImRect(const ImVec2& min, const ImVec2& max)    : Min(min), Max(max)                {}\n    constexpr ImRect(const ImVec4& v)                         : Min(v.x, v.y), Max(v.z, v.w)      {}\n    constexpr ImRect(float x1, float y1, float x2, float y2)  : Min(x1, y1), Max(x2, y2)          {}\n\n    ImVec2      GetCenter() const                   { return ImVec2((Min.x + Max.x) * 0.5f, (Min.y + Max.y) * 0.5f); }\n    ImVec2      GetSize() const                     { return ImVec2(Max.x - Min.x, Max.y - Min.y); }\n    float       GetWidth() const                    { return Max.x - Min.x; }\n    float       GetHeight() const                   { return Max.y - Min.y; }\n    float       GetArea() const                     { return (Max.x - Min.x) * (Max.y - Min.y); }\n    ImVec2      GetTL() const                       { return Min; }                   // Top-left\n    ImVec2      GetTR() const                       { return ImVec2(Max.x, Min.y); }  // Top-right\n    ImVec2      GetBL() const                       { return ImVec2(Min.x, Max.y); }  // Bottom-left\n    ImVec2      GetBR() const                       { return Max; }                   // Bottom-right\n    bool        Contains(const ImVec2& p) const     { return p.x     >= Min.x && p.y     >= Min.y && p.x     <  Max.x && p.y     <  Max.y; }\n    bool        Contains(const ImRect& r) const     { return r.Min.x >= Min.x && r.Min.y >= Min.y && r.Max.x <= Max.x && r.Max.y <= Max.y; }\n    bool        ContainsWithPad(const ImVec2& p, const ImVec2& pad) const { return p.x >= Min.x - pad.x && p.y >= Min.y - pad.y && p.x < Max.x + pad.x && p.y < Max.y + pad.y; }\n    bool        Overlaps(const ImRect& r) const     { return r.Min.y <  Max.y && r.Max.y >  Min.y && r.Min.x <  Max.x && r.Max.x >  Min.x; }\n    void        Add(const ImVec2& p)                { if (Min.x > p.x)     Min.x = p.x;     if (Min.y > p.y)     Min.y = p.y;     if (Max.x < p.x)     Max.x = p.x;     if (Max.y < p.y)     Max.y = p.y; }\n    void        Add(const ImRect& r)                { if (Min.x > r.Min.x) Min.x = r.Min.x; if (Min.y > r.Min.y) Min.y = r.Min.y; if (Max.x < r.Max.x) Max.x = r.Max.x; if (Max.y < r.Max.y) Max.y = r.Max.y; }\n    void        Expand(const float amount)          { Min.x -= amount;   Min.y -= amount;   Max.x += amount;   Max.y += amount; }\n    void        Expand(const ImVec2& amount)        { Min.x -= amount.x; Min.y -= amount.y; Max.x += amount.x; Max.y += amount.y; }\n    void        Translate(const ImVec2& d)          { Min.x += d.x; Min.y += d.y; Max.x += d.x; Max.y += d.y; }\n    void        TranslateX(float dx)                { Min.x += dx; Max.x += dx; }\n    void        TranslateY(float dy)                { Min.y += dy; Max.y += dy; }\n    void        ClipWith(const ImRect& r)           { Min = ImMax(Min, r.Min); Max = ImMin(Max, r.Max); }                   // Simple version, may lead to an inverted rectangle, which is fine for Contains/Overlaps test but not for display.\n    void        ClipWithFull(const ImRect& r)       { Min = ImClamp(Min, r.Min, r.Max); Max = ImClamp(Max, r.Min, r.Max); } // Full version, ensure both points are fully clipped.\n    void        Floor()                             { Min.x = IM_TRUNC(Min.x); Min.y = IM_TRUNC(Min.y); Max.x = IM_TRUNC(Max.x); Max.y = IM_TRUNC(Max.y); }\n    bool        IsInverted() const                  { return Min.x > Max.x || Min.y > Max.y; }\n    ImVec4      ToVec4() const                      { return ImVec4(Min.x, Min.y, Max.x, Max.y); }\n};\n\n// Helper: ImBitArray\n#define         IM_BITARRAY_TESTBIT(_ARRAY, _N)                 ((_ARRAY[(_N) >> 5] & ((ImU32)1 << ((_N) & 31))) != 0) // Macro version of ImBitArrayTestBit(): ensure args have side-effect or are costly!\n#define         IM_BITARRAY_CLEARBIT(_ARRAY, _N)                ((_ARRAY[(_N) >> 5] &= ~((ImU32)1 << ((_N) & 31))))    // Macro version of ImBitArrayClearBit(): ensure args have side-effect or are costly!\ninline size_t   ImBitArrayGetStorageSizeInBytes(int bitcount)   { return (size_t)((bitcount + 31) >> 5) << 2; }\ninline void     ImBitArrayClearAllBits(ImU32* arr, int bitcount){ memset(arr, 0, ImBitArrayGetStorageSizeInBytes(bitcount)); }\ninline bool     ImBitArrayTestBit(const ImU32* arr, int n)      { ImU32 mask = (ImU32)1 << (n & 31); return (arr[n >> 5] & mask) != 0; }\ninline void     ImBitArrayClearBit(ImU32* arr, int n)           { ImU32 mask = (ImU32)1 << (n & 31); arr[n >> 5] &= ~mask; }\ninline void     ImBitArraySetBit(ImU32* arr, int n)             { ImU32 mask = (ImU32)1 << (n & 31); arr[n >> 5] |= mask; }\ninline void     ImBitArraySetBitRange(ImU32* arr, int n, int n2) // Works on range [n..n2)\n{\n    n2--;\n    while (n <= n2)\n    {\n        int a_mod = (n & 31);\n        int b_mod = (n2 > (n | 31) ? 31 : (n2 & 31)) + 1;\n        ImU32 mask = (ImU32)(((ImU64)1 << b_mod) - 1) & ~(ImU32)(((ImU64)1 << a_mod) - 1);\n        arr[n >> 5] |= mask;\n        n = (n + 32) & ~31;\n    }\n}\n\ntypedef ImU32* ImBitArrayPtr; // Name for use in structs\n\n// Helper: ImBitArray class (wrapper over ImBitArray functions)\n// Store 1-bit per value.\ntemplate<int BITCOUNT, int OFFSET = 0>\nstruct ImBitArray\n{\n    ImU32           Storage[(BITCOUNT + 31) >> 5];\n    ImBitArray()                                { ClearAllBits(); }\n    void            ClearAllBits()              { memset(Storage, 0, sizeof(Storage)); }\n    void            SetAllBits()                { memset(Storage, 255, sizeof(Storage)); }\n    bool            TestBit(int n) const        { n += OFFSET; IM_ASSERT(n >= 0 && n < BITCOUNT); return IM_BITARRAY_TESTBIT(Storage, n); }\n    void            SetBit(int n)               { n += OFFSET; IM_ASSERT(n >= 0 && n < BITCOUNT); ImBitArraySetBit(Storage, n); }\n    void            ClearBit(int n)             { n += OFFSET; IM_ASSERT(n >= 0 && n < BITCOUNT); ImBitArrayClearBit(Storage, n); }\n    void            SetBitRange(int n, int n2)  { n += OFFSET; n2 += OFFSET; IM_ASSERT(n >= 0 && n < BITCOUNT && n2 > n && n2 <= BITCOUNT); ImBitArraySetBitRange(Storage, n, n2); } // Works on range [n..n2)\n    bool            operator[](int n) const     { n += OFFSET; IM_ASSERT(n >= 0 && n < BITCOUNT); return IM_BITARRAY_TESTBIT(Storage, n); }\n};\n\n// Helper: ImBitVector\n// Store 1-bit per value.\nstruct IMGUI_API ImBitVector\n{\n    ImVector<ImU32> Storage;\n    void            Create(int sz)              { Storage.resize((sz + 31) >> 5); memset(Storage.Data, 0, (size_t)Storage.Size * sizeof(Storage.Data[0])); }\n    void            Clear()                     { Storage.clear(); }\n    bool            TestBit(int n) const        { IM_ASSERT(n < (Storage.Size << 5)); return IM_BITARRAY_TESTBIT(Storage.Data, n); }\n    void            SetBit(int n)               { IM_ASSERT(n < (Storage.Size << 5)); ImBitArraySetBit(Storage.Data, n); }\n    void            ClearBit(int n)             { IM_ASSERT(n < (Storage.Size << 5)); ImBitArrayClearBit(Storage.Data, n); }\n};\nIM_MSVC_RUNTIME_CHECKS_RESTORE\n\n// Helper: ImSpan<>\n// Pointing to a span of data we don't own.\ntemplate<typename T>\nstruct ImSpan\n{\n    T*                  Data;\n    T*                  DataEnd;\n\n    // Constructors, destructor\n    inline ImSpan()                                 { Data = DataEnd = NULL; }\n    inline ImSpan(T* data, int size)                { Data = data; DataEnd = data + size; }\n    inline ImSpan(T* data, T* data_end)             { Data = data; DataEnd = data_end; }\n\n    inline void         set(T* data, int size)      { Data = data; DataEnd = data + size; }\n    inline void         set(T* data, T* data_end)   { Data = data; DataEnd = data_end; }\n    inline int          size() const                { return (int)(ptrdiff_t)(DataEnd - Data); }\n    inline int          size_in_bytes() const       { return (int)(ptrdiff_t)(DataEnd - Data) * (int)sizeof(T); }\n    inline T&           operator[](int i)           { T* p = Data + i; IM_ASSERT(p >= Data && p < DataEnd); return *p; }\n    inline const T&     operator[](int i) const     { const T* p = Data + i; IM_ASSERT(p >= Data && p < DataEnd); return *p; }\n\n    inline T*           begin()                     { return Data; }\n    inline const T*     begin() const               { return Data; }\n    inline T*           end()                       { return DataEnd; }\n    inline const T*     end() const                 { return DataEnd; }\n\n    // Utilities\n    inline int  index_from_ptr(const T* it) const   { IM_ASSERT(it >= Data && it < DataEnd); const ptrdiff_t off = it - Data; return (int)off; }\n};\n\n// Helper: ImSpanAllocator<>\n// Facilitate storing multiple chunks into a single large block (the \"arena\")\n// - Usage: call Reserve() N times, allocate GetArenaSizeInBytes() worth, pass it to SetArenaBasePtr(), call GetSpan() N times to retrieve the aligned ranges.\ntemplate<int CHUNKS>\nstruct ImSpanAllocator\n{\n    char*   BasePtr;\n    int     CurrOff;\n    int     CurrIdx;\n    int     Offsets[CHUNKS];\n    int     Sizes[CHUNKS];\n\n    ImSpanAllocator()                               { memset(this, 0, sizeof(*this)); }\n    inline void  Reserve(int n, size_t sz, int a=4) { IM_ASSERT(n == CurrIdx && n < CHUNKS); CurrOff = IM_MEMALIGN(CurrOff, a); Offsets[n] = CurrOff; Sizes[n] = (int)sz; CurrIdx++; CurrOff += (int)sz; }\n    inline int   GetArenaSizeInBytes()              { return CurrOff; }\n    inline void  SetArenaBasePtr(void* base_ptr)    { BasePtr = (char*)base_ptr; }\n    inline void* GetSpanPtrBegin(int n)             { IM_ASSERT(n >= 0 && n < CHUNKS && CurrIdx == CHUNKS); return (void*)(BasePtr + Offsets[n]); }\n    inline void* GetSpanPtrEnd(int n)               { IM_ASSERT(n >= 0 && n < CHUNKS && CurrIdx == CHUNKS); return (void*)(BasePtr + Offsets[n] + Sizes[n]); }\n    template<typename T>\n    inline void  GetSpan(int n, ImSpan<T>* span)    { span->set((T*)GetSpanPtrBegin(n), (T*)GetSpanPtrEnd(n)); }\n};\n\n// Helper: ImPool<>\n// Basic keyed storage for contiguous instances, slow/amortized insertion, O(1) indexable, O(Log N) queries by ID over a dense/hot buffer,\n// Honor constructor/destructor. Add/remove invalidate all pointers. Indexes have the same lifetime as the associated object.\ntypedef int ImPoolIdx;\ntemplate<typename T>\nstruct ImPool\n{\n    ImVector<T>     Buf;        // Contiguous data\n    ImGuiStorage    Map;        // ID->Index\n    ImPoolIdx       FreeIdx;    // Next free idx to use\n    ImPoolIdx       AliveCount; // Number of active/alive items (for display purpose)\n\n    ImPool()    { FreeIdx = AliveCount = 0; }\n    ~ImPool()   { Clear(); }\n    T*          GetByKey(ImGuiID key)               { int idx = Map.GetInt(key, -1); return (idx != -1) ? &Buf[idx] : NULL; }\n    T*          GetByIndex(ImPoolIdx n)             { return &Buf[n]; }\n    ImPoolIdx   GetIndex(const T* p) const          { IM_ASSERT(p >= Buf.Data && p < Buf.Data + Buf.Size); return (ImPoolIdx)(p - Buf.Data); }\n    T*          GetOrAddByKey(ImGuiID key)          { int* p_idx = Map.GetIntRef(key, -1); if (*p_idx != -1) return &Buf[*p_idx]; *p_idx = FreeIdx; return Add(); }\n    bool        Contains(const T* p) const          { return (p >= Buf.Data && p < Buf.Data + Buf.Size); }\n    void        Clear()                             { for (int n = 0; n < Map.Data.Size; n++) { int idx = Map.Data[n].val_i; if (idx != -1) Buf[idx].~T(); } Map.Clear(); Buf.clear(); FreeIdx = AliveCount = 0; }\n    T*          Add()                               { int idx = FreeIdx; if (idx == Buf.Size) { Buf.resize(Buf.Size + 1); FreeIdx++; } else { FreeIdx = *(int*)&Buf[idx]; } IM_PLACEMENT_NEW(&Buf[idx]) T(); AliveCount++; return &Buf[idx]; }\n    void        Remove(ImGuiID key, const T* p)     { Remove(key, GetIndex(p)); }\n    void        Remove(ImGuiID key, ImPoolIdx idx)  { Buf[idx].~T(); *(int*)&Buf[idx] = FreeIdx; FreeIdx = idx; Map.SetInt(key, -1); AliveCount--; }\n    void        Reserve(int capacity)               { Buf.reserve(capacity); Map.Data.reserve(capacity); }\n\n    // To iterate a ImPool: for (int n = 0; n < pool.GetMapSize(); n++) if (T* t = pool.TryGetMapData(n)) { ... }\n    // Can be avoided if you know .Remove() has never been called on the pool, or AliveCount == GetMapSize()\n    int         GetAliveCount() const               { return AliveCount; }      // Number of active/alive items in the pool (for display purpose)\n    int         GetBufSize() const                  { return Buf.Size; }\n    int         GetMapSize() const                  { return Map.Data.Size; }   // It is the map we need iterate to find valid items, since we don't have \"alive\" storage anywhere\n    T*          TryGetMapData(ImPoolIdx n)          { int idx = Map.Data[n].val_i; if (idx == -1) return NULL; return GetByIndex(idx); }\n};\n\n// Helper: ImChunkStream<>\n// Build and iterate a contiguous stream of variable-sized structures.\n// This is used by Settings to store persistent data while reducing allocation count.\n// We store the chunk size first, and align the final size on 4 bytes boundaries.\n// The tedious/zealous amount of casting is to avoid -Wcast-align warnings.\ntemplate<typename T>\nstruct ImChunkStream\n{\n    ImVector<char>  Buf;\n\n    void    clear()                     { Buf.clear(); }\n    bool    empty() const               { return Buf.Size == 0; }\n    int     size() const                { return Buf.Size; }\n    T*      alloc_chunk(size_t sz)      { size_t HDR_SZ = 4; sz = IM_MEMALIGN(HDR_SZ + sz, 4u); int off = Buf.Size; Buf.resize(off + (int)sz); ((int*)(void*)(Buf.Data + off))[0] = (int)sz; return (T*)(void*)(Buf.Data + off + (int)HDR_SZ); }\n    T*      begin()                     { size_t HDR_SZ = 4; if (!Buf.Data) return NULL; return (T*)(void*)(Buf.Data + HDR_SZ); }\n    T*      next_chunk(T* p)            { size_t HDR_SZ = 4; IM_ASSERT(p >= begin() && p < end()); p = (T*)(void*)((char*)(void*)p + chunk_size(p)); if (p == (T*)(void*)((char*)end() + HDR_SZ)) return (T*)0; IM_ASSERT(p < end()); return p; }\n    int     chunk_size(const T* p)      { return ((const int*)p)[-1]; }\n    T*      end()                       { return (T*)(void*)(Buf.Data + Buf.Size); }\n    int     offset_from_ptr(const T* p) { IM_ASSERT(p >= begin() && p < end()); const ptrdiff_t off = (const char*)p - Buf.Data; return (int)off; }\n    T*      ptr_from_offset(int off)    { IM_ASSERT(off >= 4 && off < Buf.Size); return (T*)(void*)(Buf.Data + off); }\n    void    swap(ImChunkStream<T>& rhs) { rhs.Buf.swap(Buf); }\n};\n\n// Helper: ImGuiTextIndex\n// Maintain a line index for a text buffer. This is a strong candidate to be moved into the public API.\nstruct ImGuiTextIndex\n{\n    ImVector<int>   LineOffsets;\n    int             EndOffset = 0;                          // Because we don't own text buffer we need to maintain EndOffset (may bake in LineOffsets?)\n\n    void            clear()                                 { LineOffsets.clear(); EndOffset = 0; }\n    int             size()                                  { return LineOffsets.Size; }\n    const char*     get_line_begin(const char* base, int n) { return base + LineOffsets[n]; }\n    const char*     get_line_end(const char* base, int n)   { return base + (n + 1 < LineOffsets.Size ? (LineOffsets[n + 1] - 1) : EndOffset); }\n    void            append(const char* base, int old_size, int new_size);\n};\n\n// Helper: ImGuiStorage\nIMGUI_API ImGuiStoragePair* ImLowerBound(ImGuiStoragePair* in_begin, ImGuiStoragePair* in_end, ImGuiID key);\n\n//-----------------------------------------------------------------------------\n// [SECTION] ImDrawList support\n//-----------------------------------------------------------------------------\n\n// ImDrawList: Helper function to calculate a circle's segment count given its radius and a \"maximum error\" value.\n// Estimation of number of circle segment based on error is derived using method described in https://stackoverflow.com/a/2244088/15194693\n// Number of segments (N) is calculated using equation:\n//   N = ceil ( pi / acos(1 - error / r) )     where r > 0, error <= r\n// Our equation is significantly simpler that one in the post thanks for choosing segment that is\n// perpendicular to X axis. Follow steps in the article from this starting condition and you will\n// will get this result.\n//\n// Rendering circles with an odd number of segments, while mathematically correct will produce\n// asymmetrical results on the raster grid. Therefore we're rounding N to next even number (7->8, 8->8, 9->10 etc.)\n#define IM_ROUNDUP_TO_EVEN(_V)                                  ((((_V) + 1) / 2) * 2)\n#define IM_DRAWLIST_CIRCLE_AUTO_SEGMENT_MIN                     4\n#define IM_DRAWLIST_CIRCLE_AUTO_SEGMENT_MAX                     512\n#define IM_DRAWLIST_CIRCLE_AUTO_SEGMENT_CALC(_RAD,_MAXERROR)    ImClamp(IM_ROUNDUP_TO_EVEN((int)ImCeil(IM_PI / ImAcos(1 - ImMin((_MAXERROR), (_RAD)) / (_RAD)))), IM_DRAWLIST_CIRCLE_AUTO_SEGMENT_MIN, IM_DRAWLIST_CIRCLE_AUTO_SEGMENT_MAX)\n\n// Raw equation from IM_DRAWLIST_CIRCLE_AUTO_SEGMENT_CALC rewritten for 'r' and 'error'.\n#define IM_DRAWLIST_CIRCLE_AUTO_SEGMENT_CALC_R(_N,_MAXERROR)    ((_MAXERROR) / (1 - ImCos(IM_PI / ImMax((float)(_N), IM_PI))))\n#define IM_DRAWLIST_CIRCLE_AUTO_SEGMENT_CALC_ERROR(_N,_RAD)     ((1 - ImCos(IM_PI / ImMax((float)(_N), IM_PI))) / (_RAD))\n\n// ImDrawList: Lookup table size for adaptive arc drawing, cover full circle.\n#ifndef IM_DRAWLIST_ARCFAST_TABLE_SIZE\n#define IM_DRAWLIST_ARCFAST_TABLE_SIZE                          48 // Number of samples in lookup table.\n#endif\n#define IM_DRAWLIST_ARCFAST_SAMPLE_MAX                          IM_DRAWLIST_ARCFAST_TABLE_SIZE // Sample index _PathArcToFastEx() for 360 angle.\n\n// Data shared between all ImDrawList instances\n// Conceptually this could have been called e.g. ImDrawListSharedContext\n// Typically one ImGui context would create and maintain one of this.\n// You may want to create your own instance of you try to ImDrawList completely without ImGui. In that case, watch out for future changes to this structure.\nstruct IMGUI_API ImDrawListSharedData\n{\n    ImVec2          TexUvWhitePixel;            // UV of white pixel in the atlas\n    const ImVec4*   TexUvLines;                 // UV of anti-aliased lines in the atlas\n    ImFont*         Font;                       // Current/default font (optional, for simplified AddText overload)\n    float           FontSize;                   // Current/default font size (optional, for simplified AddText overload)\n    float           FontScale;                  // Current/default font scale (== FontSize / Font->FontSize)\n    float           CurveTessellationTol;       // Tessellation tolerance when using PathBezierCurveTo()\n    float           CircleSegmentMaxError;      // Number of circle segments to use per pixel of radius for AddCircle() etc\n    float           InitialFringeScale;         // Initial scale to apply to AA fringe\n    ImDrawListFlags InitialFlags;               // Initial flags at the beginning of the frame (it is possible to alter flags on a per-drawlist basis afterwards)\n    ImVec4          ClipRectFullscreen;         // Value for PushClipRectFullscreen()\n    ImVector<ImVec2> TempBuffer;                // Temporary write buffer\n\n    // Lookup tables\n    ImVec2          ArcFastVtx[IM_DRAWLIST_ARCFAST_TABLE_SIZE]; // Sample points on the quarter of the circle.\n    float           ArcFastRadiusCutoff;                        // Cutoff radius after which arc drawing will fallback to slower PathArcTo()\n    ImU8            CircleSegmentCounts[64];    // Precomputed segment count for given radius before we calculate it dynamically (to avoid calculation overhead)\n\n    ImDrawListSharedData();\n    void SetCircleTessellationMaxError(float max_error);\n};\n\nstruct ImDrawDataBuilder\n{\n    ImVector<ImDrawList*>*  Layers[2];      // Pointers to global layers for: regular, tooltip. LayersP[0] is owned by DrawData.\n    ImVector<ImDrawList*>   LayerData1;\n\n    ImDrawDataBuilder()                     { memset(this, 0, sizeof(*this)); }\n};\n\n//-----------------------------------------------------------------------------\n// [SECTION] Style support\n//-----------------------------------------------------------------------------\n\nstruct ImGuiStyleVarInfo\n{\n    ImU32           Count : 8;      // 1+\n    ImGuiDataType   DataType : 8;\n    ImU32           Offset : 16;    // Offset in parent structure\n    void* GetVarPtr(void* parent) const { return (void*)((unsigned char*)parent + Offset); }\n};\n\n// Stacked color modifier, backup of modified data so we can restore it\nstruct ImGuiColorMod\n{\n    ImGuiCol        Col;\n    ImVec4          BackupValue;\n};\n\n// Stacked style modifier, backup of modified data so we can restore it. Data type inferred from the variable.\nstruct ImGuiStyleMod\n{\n    ImGuiStyleVar   VarIdx;\n    union           { int BackupInt[2]; float BackupFloat[2]; };\n    ImGuiStyleMod(ImGuiStyleVar idx, int v)     { VarIdx = idx; BackupInt[0] = v; }\n    ImGuiStyleMod(ImGuiStyleVar idx, float v)   { VarIdx = idx; BackupFloat[0] = v; }\n    ImGuiStyleMod(ImGuiStyleVar idx, ImVec2 v)  { VarIdx = idx; BackupFloat[0] = v.x; BackupFloat[1] = v.y; }\n};\n\n//-----------------------------------------------------------------------------\n// [SECTION] Data types support\n//-----------------------------------------------------------------------------\n\nstruct ImGuiDataTypeStorage\n{\n    ImU8        Data[8];        // Opaque storage to fit any data up to ImGuiDataType_COUNT\n};\n\n// Type information associated to one ImGuiDataType. Retrieve with DataTypeGetInfo().\nstruct ImGuiDataTypeInfo\n{\n    size_t      Size;           // Size in bytes\n    const char* Name;           // Short descriptive name for the type, for debugging\n    const char* PrintFmt;       // Default printf format for the type\n    const char* ScanFmt;        // Default scanf format for the type\n};\n\n// Extend ImGuiDataType_\nenum ImGuiDataTypePrivate_\n{\n    ImGuiDataType_Pointer = ImGuiDataType_COUNT,\n    ImGuiDataType_ID,\n};\n\n//-----------------------------------------------------------------------------\n// [SECTION] Widgets support: flags, enums, data structures\n//-----------------------------------------------------------------------------\n\n// Extend ImGuiItemFlags\n// - input: PushItemFlag() manipulates g.CurrentItemFlags, g.NextItemData.ItemFlags, ItemAdd() calls may add extra flags too.\n// - output: stored in g.LastItemData.ItemFlags\nenum ImGuiItemFlagsPrivate_\n{\n    // Controlled by user\n    ImGuiItemFlags_Disabled                 = 1 << 10, // false     // Disable interactions (DOES NOT affect visuals. DO NOT mix direct use of this with BeginDisabled(). See BeginDisabled()/EndDisabled() for full disable feature, and github #211).\n    ImGuiItemFlags_ReadOnly                 = 1 << 11, // false     // [ALPHA] Allow hovering interactions but underlying value is not changed.\n    ImGuiItemFlags_MixedValue               = 1 << 12, // false     // [BETA] Represent a mixed/indeterminate value, generally multi-selection where values differ. Currently only supported by Checkbox() (later should support all sorts of widgets)\n    ImGuiItemFlags_NoWindowHoverableCheck   = 1 << 13, // false     // Disable hoverable check in ItemHoverable()\n    ImGuiItemFlags_AllowOverlap             = 1 << 14, // false     // Allow being overlapped by another widget. Not-hovered to Hovered transition deferred by a frame.\n    ImGuiItemFlags_NoNavDisableMouseHover   = 1 << 15, // false     // Nav keyboard/gamepad mode doesn't disable hover highlight (behave as if NavHighlightItemUnderNav==false).\n    ImGuiItemFlags_NoMarkEdited             = 1 << 16, // false     // Skip calling MarkItemEdited()\n\n    // Controlled by widget code\n    ImGuiItemFlags_Inputable                = 1 << 20, // false     // [WIP] Auto-activate input mode when tab focused. Currently only used and supported by a few items before it becomes a generic feature.\n    ImGuiItemFlags_HasSelectionUserData     = 1 << 21, // false     // Set by SetNextItemSelectionUserData()\n    ImGuiItemFlags_IsMultiSelect            = 1 << 22, // false     // Set by SetNextItemSelectionUserData()\n\n    ImGuiItemFlags_Default_                 = ImGuiItemFlags_AutoClosePopups,    // Please don't change, use PushItemFlag() instead.\n\n    // Obsolete\n    //ImGuiItemFlags_SelectableDontClosePopup = !ImGuiItemFlags_AutoClosePopups, // Can't have a redirect as we inverted the behavior\n};\n\n// Status flags for an already submitted item\n// - output: stored in g.LastItemData.StatusFlags\nenum ImGuiItemStatusFlags_\n{\n    ImGuiItemStatusFlags_None               = 0,\n    ImGuiItemStatusFlags_HoveredRect        = 1 << 0,   // Mouse position is within item rectangle (does NOT mean that the window is in correct z-order and can be hovered!, this is only one part of the most-common IsItemHovered test)\n    ImGuiItemStatusFlags_HasDisplayRect     = 1 << 1,   // g.LastItemData.DisplayRect is valid\n    ImGuiItemStatusFlags_Edited             = 1 << 2,   // Value exposed by item was edited in the current frame (should match the bool return value of most widgets)\n    ImGuiItemStatusFlags_ToggledSelection   = 1 << 3,   // Set when Selectable(), TreeNode() reports toggling a selection. We can't report \"Selected\", only state changes, in order to easily handle clipping with less issues.\n    ImGuiItemStatusFlags_ToggledOpen        = 1 << 4,   // Set when TreeNode() reports toggling their open state.\n    ImGuiItemStatusFlags_HasDeactivated     = 1 << 5,   // Set if the widget/group is able to provide data for the ImGuiItemStatusFlags_Deactivated flag.\n    ImGuiItemStatusFlags_Deactivated        = 1 << 6,   // Only valid if ImGuiItemStatusFlags_HasDeactivated is set.\n    ImGuiItemStatusFlags_HoveredWindow      = 1 << 7,   // Override the HoveredWindow test to allow cross-window hover testing.\n    ImGuiItemStatusFlags_Visible            = 1 << 8,   // [WIP] Set when item is overlapping the current clipping rectangle (Used internally. Please don't use yet: API/system will change as we refactor Itemadd()).\n    ImGuiItemStatusFlags_HasClipRect        = 1 << 9,   // g.LastItemData.ClipRect is valid.\n    ImGuiItemStatusFlags_HasShortcut        = 1 << 10,  // g.LastItemData.Shortcut valid. Set by SetNextItemShortcut() -> ItemAdd().\n\n    // Additional status + semantic for ImGuiTestEngine\n#ifdef IMGUI_ENABLE_TEST_ENGINE\n    ImGuiItemStatusFlags_Openable           = 1 << 20,  // Item is an openable (e.g. TreeNode)\n    ImGuiItemStatusFlags_Opened             = 1 << 21,  // Opened status\n    ImGuiItemStatusFlags_Checkable          = 1 << 22,  // Item is a checkable (e.g. CheckBox, MenuItem)\n    ImGuiItemStatusFlags_Checked            = 1 << 23,  // Checked status\n    ImGuiItemStatusFlags_Inputable          = 1 << 24,  // Item is a text-inputable (e.g. InputText, SliderXXX, DragXXX)\n#endif\n};\n\n// Extend ImGuiHoveredFlags_\nenum ImGuiHoveredFlagsPrivate_\n{\n    ImGuiHoveredFlags_DelayMask_                    = ImGuiHoveredFlags_DelayNone | ImGuiHoveredFlags_DelayShort | ImGuiHoveredFlags_DelayNormal | ImGuiHoveredFlags_NoSharedDelay,\n    ImGuiHoveredFlags_AllowedMaskForIsWindowHovered = ImGuiHoveredFlags_ChildWindows | ImGuiHoveredFlags_RootWindow | ImGuiHoveredFlags_AnyWindow | ImGuiHoveredFlags_NoPopupHierarchy | ImGuiHoveredFlags_AllowWhenBlockedByPopup | ImGuiHoveredFlags_AllowWhenBlockedByActiveItem | ImGuiHoveredFlags_ForTooltip | ImGuiHoveredFlags_Stationary,\n    ImGuiHoveredFlags_AllowedMaskForIsItemHovered   = ImGuiHoveredFlags_AllowWhenBlockedByPopup | ImGuiHoveredFlags_AllowWhenBlockedByActiveItem | ImGuiHoveredFlags_AllowWhenOverlapped | ImGuiHoveredFlags_AllowWhenDisabled | ImGuiHoveredFlags_NoNavOverride | ImGuiHoveredFlags_ForTooltip | ImGuiHoveredFlags_Stationary | ImGuiHoveredFlags_DelayMask_,\n};\n\n// Extend ImGuiInputTextFlags_\nenum ImGuiInputTextFlagsPrivate_\n{\n    // [Internal]\n    ImGuiInputTextFlags_Multiline           = 1 << 26,  // For internal use by InputTextMultiline()\n    ImGuiInputTextFlags_MergedItem          = 1 << 27,  // For internal use by TempInputText(), will skip calling ItemAdd(). Require bounding-box to strictly match.\n    ImGuiInputTextFlags_LocalizeDecimalPoint= 1 << 28,  // For internal use by InputScalar() and TempInputScalar()\n};\n\n// Extend ImGuiButtonFlags_\nenum ImGuiButtonFlagsPrivate_\n{\n    ImGuiButtonFlags_PressedOnClick         = 1 << 4,   // return true on click (mouse down event)\n    ImGuiButtonFlags_PressedOnClickRelease  = 1 << 5,   // [Default] return true on click + release on same item <-- this is what the majority of Button are using\n    ImGuiButtonFlags_PressedOnClickReleaseAnywhere = 1 << 6, // return true on click + release even if the release event is not done while hovering the item\n    ImGuiButtonFlags_PressedOnRelease       = 1 << 7,   // return true on release (default requires click+release)\n    ImGuiButtonFlags_PressedOnDoubleClick   = 1 << 8,   // return true on double-click (default requires click+release)\n    ImGuiButtonFlags_PressedOnDragDropHold  = 1 << 9,   // return true when held into while we are drag and dropping another item (used by e.g. tree nodes, collapsing headers)\n    //ImGuiButtonFlags_Repeat               = 1 << 10,  // hold to repeat -> use ImGuiItemFlags_ButtonRepeat instead.\n    ImGuiButtonFlags_FlattenChildren        = 1 << 11,  // allow interactions even if a child window is overlapping\n    ImGuiButtonFlags_AllowOverlap           = 1 << 12,  // require previous frame HoveredId to either match id or be null before being usable.\n    //ImGuiButtonFlags_DontClosePopups      = 1 << 13,  // disable automatically closing parent popup on press\n    //ImGuiButtonFlags_Disabled             = 1 << 14,  // disable interactions -> use BeginDisabled() or ImGuiItemFlags_Disabled\n    ImGuiButtonFlags_AlignTextBaseLine      = 1 << 15,  // vertically align button to match text baseline - ButtonEx() only // FIXME: Should be removed and handled by SmallButton(), not possible currently because of DC.CursorPosPrevLine\n    ImGuiButtonFlags_NoKeyModsAllowed       = 1 << 16,  // disable mouse interaction if a key modifier is held\n    ImGuiButtonFlags_NoHoldingActiveId      = 1 << 17,  // don't set ActiveId while holding the mouse (ImGuiButtonFlags_PressedOnClick only)\n    ImGuiButtonFlags_NoNavFocus             = 1 << 18,  // don't override navigation focus when activated (FIXME: this is essentially used every time an item uses ImGuiItemFlags_NoNav, but because legacy specs don't requires LastItemData to be set ButtonBehavior(), we can't poll g.LastItemData.ItemFlags)\n    ImGuiButtonFlags_NoHoveredOnFocus       = 1 << 19,  // don't report as hovered when nav focus is on this item\n    ImGuiButtonFlags_NoSetKeyOwner          = 1 << 20,  // don't set key/input owner on the initial click (note: mouse buttons are keys! often, the key in question will be ImGuiKey_MouseLeft!)\n    ImGuiButtonFlags_NoTestKeyOwner         = 1 << 21,  // don't test key/input owner when polling the key (note: mouse buttons are keys! often, the key in question will be ImGuiKey_MouseLeft!)\n    ImGuiButtonFlags_PressedOnMask_         = ImGuiButtonFlags_PressedOnClick | ImGuiButtonFlags_PressedOnClickRelease | ImGuiButtonFlags_PressedOnClickReleaseAnywhere | ImGuiButtonFlags_PressedOnRelease | ImGuiButtonFlags_PressedOnDoubleClick | ImGuiButtonFlags_PressedOnDragDropHold,\n    ImGuiButtonFlags_PressedOnDefault_      = ImGuiButtonFlags_PressedOnClickRelease,\n};\n\n// Extend ImGuiComboFlags_\nenum ImGuiComboFlagsPrivate_\n{\n    ImGuiComboFlags_CustomPreview           = 1 << 20,  // enable BeginComboPreview()\n};\n\n// Extend ImGuiSliderFlags_\nenum ImGuiSliderFlagsPrivate_\n{\n    ImGuiSliderFlags_Vertical               = 1 << 20,  // Should this slider be orientated vertically?\n    ImGuiSliderFlags_ReadOnly               = 1 << 21,  // Consider using g.NextItemData.ItemFlags |= ImGuiItemFlags_ReadOnly instead.\n};\n\n// Extend ImGuiSelectableFlags_\nenum ImGuiSelectableFlagsPrivate_\n{\n    // NB: need to be in sync with last value of ImGuiSelectableFlags_\n    ImGuiSelectableFlags_NoHoldingActiveID      = 1 << 20,\n    ImGuiSelectableFlags_SelectOnNav            = 1 << 21,  // (WIP) Auto-select when moved into. This is not exposed in public API as to handle multi-select and modifiers we will need user to explicitly control focus scope. May be replaced with a BeginSelection() API.\n    ImGuiSelectableFlags_SelectOnClick          = 1 << 22,  // Override button behavior to react on Click (default is Click+Release)\n    ImGuiSelectableFlags_SelectOnRelease        = 1 << 23,  // Override button behavior to react on Release (default is Click+Release)\n    ImGuiSelectableFlags_SpanAvailWidth         = 1 << 24,  // Span all avail width even if we declared less for layout purpose. FIXME: We may be able to remove this (added in 6251d379, 2bcafc86 for menus)\n    ImGuiSelectableFlags_SetNavIdOnHover        = 1 << 25,  // Set Nav/Focus ID on mouse hover (used by MenuItem)\n    ImGuiSelectableFlags_NoPadWithHalfSpacing   = 1 << 26,  // Disable padding each side with ItemSpacing * 0.5f\n    ImGuiSelectableFlags_NoSetKeyOwner          = 1 << 27,  // Don't set key/input owner on the initial click (note: mouse buttons are keys! often, the key in question will be ImGuiKey_MouseLeft!)\n};\n\n// Extend ImGuiTreeNodeFlags_\nenum ImGuiTreeNodeFlagsPrivate_\n{\n    ImGuiTreeNodeFlags_ClipLabelForTrailingButton = 1 << 28,// FIXME-WIP: Hard-coded for CollapsingHeader()\n    ImGuiTreeNodeFlags_UpsideDownArrow            = 1 << 29,// FIXME-WIP: Turn Down arrow into an Up arrow, for reversed trees (#6517)\n    ImGuiTreeNodeFlags_OpenOnMask_                = ImGuiTreeNodeFlags_OpenOnDoubleClick | ImGuiTreeNodeFlags_OpenOnArrow,\n};\n\nenum ImGuiSeparatorFlags_\n{\n    ImGuiSeparatorFlags_None                    = 0,\n    ImGuiSeparatorFlags_Horizontal              = 1 << 0,   // Axis default to current layout type, so generally Horizontal unless e.g. in a menu bar\n    ImGuiSeparatorFlags_Vertical                = 1 << 1,\n    ImGuiSeparatorFlags_SpanAllColumns          = 1 << 2,   // Make separator cover all columns of a legacy Columns() set.\n};\n\n// Flags for FocusWindow(). This is not called ImGuiFocusFlags to avoid confusion with public-facing ImGuiFocusedFlags.\n// FIXME: Once we finishing replacing more uses of GetTopMostPopupModal()+IsWindowWithinBeginStackOf()\n// and FindBlockingModal() with this, we may want to change the flag to be opt-out instead of opt-in.\nenum ImGuiFocusRequestFlags_\n{\n    ImGuiFocusRequestFlags_None                 = 0,\n    ImGuiFocusRequestFlags_RestoreFocusedChild  = 1 << 0,   // Find last focused child (if any) and focus it instead.\n    ImGuiFocusRequestFlags_UnlessBelowModal     = 1 << 1,   // Do not set focus if the window is below a modal.\n};\n\nenum ImGuiTextFlags_\n{\n    ImGuiTextFlags_None                         = 0,\n    ImGuiTextFlags_NoWidthForLargeClippedText   = 1 << 0,\n};\n\nenum ImGuiTooltipFlags_\n{\n    ImGuiTooltipFlags_None                      = 0,\n    ImGuiTooltipFlags_OverridePrevious          = 1 << 1,   // Clear/ignore previously submitted tooltip (defaults to append)\n};\n\n// FIXME: this is in development, not exposed/functional as a generic feature yet.\n// Horizontal/Vertical enums are fixed to 0/1 so they may be used to index ImVec2\nenum ImGuiLayoutType_\n{\n    ImGuiLayoutType_Horizontal = 0,\n    ImGuiLayoutType_Vertical = 1\n};\n\n// Flags for LogBegin() text capturing function\nenum ImGuiLogFlags_\n{\n    ImGuiLogFlags_None = 0,\n\n    ImGuiLogFlags_OutputTTY         = 1 << 0,\n    ImGuiLogFlags_OutputFile        = 1 << 1,\n    ImGuiLogFlags_OutputBuffer      = 1 << 2,\n    ImGuiLogFlags_OutputClipboard   = 1 << 3,\n    ImGuiLogFlags_OutputMask_       = ImGuiLogFlags_OutputTTY | ImGuiLogFlags_OutputFile | ImGuiLogFlags_OutputBuffer | ImGuiLogFlags_OutputClipboard,\n};\n\n// X/Y enums are fixed to 0/1 so they may be used to index ImVec2\nenum ImGuiAxis\n{\n    ImGuiAxis_None = -1,\n    ImGuiAxis_X = 0,\n    ImGuiAxis_Y = 1\n};\n\nenum ImGuiPlotType\n{\n    ImGuiPlotType_Lines,\n    ImGuiPlotType_Histogram,\n};\n\n// Storage data for BeginComboPreview()/EndComboPreview()\nstruct IMGUI_API ImGuiComboPreviewData\n{\n    ImRect          PreviewRect;\n    ImVec2          BackupCursorPos;\n    ImVec2          BackupCursorMaxPos;\n    ImVec2          BackupCursorPosPrevLine;\n    float           BackupPrevLineTextBaseOffset;\n    ImGuiLayoutType BackupLayout;\n\n    ImGuiComboPreviewData() { memset(this, 0, sizeof(*this)); }\n};\n\n// Stacked storage data for BeginGroup()/EndGroup()\nstruct IMGUI_API ImGuiGroupData\n{\n    ImGuiID     WindowID;\n    ImVec2      BackupCursorPos;\n    ImVec2      BackupCursorMaxPos;\n    ImVec2      BackupCursorPosPrevLine;\n    ImVec1      BackupIndent;\n    ImVec1      BackupGroupOffset;\n    ImVec2      BackupCurrLineSize;\n    float       BackupCurrLineTextBaseOffset;\n    ImGuiID     BackupActiveIdIsAlive;\n    bool        BackupDeactivatedIdIsAlive;\n    bool        BackupHoveredIdIsAlive;\n    bool        BackupIsSameLine;\n    bool        EmitItem;\n};\n\n// Simple column measurement, currently used for MenuItem() only.. This is very short-sighted/throw-away code and NOT a generic helper.\nstruct IMGUI_API ImGuiMenuColumns\n{\n    ImU32       TotalWidth;\n    ImU32       NextTotalWidth;\n    ImU16       Spacing;\n    ImU16       OffsetIcon;         // Always zero for now\n    ImU16       OffsetLabel;        // Offsets are locked in Update()\n    ImU16       OffsetShortcut;\n    ImU16       OffsetMark;\n    ImU16       Widths[4];          // Width of:   Icon, Label, Shortcut, Mark  (accumulators for current frame)\n\n    ImGuiMenuColumns() { memset(this, 0, sizeof(*this)); }\n    void        Update(float spacing, bool window_reappearing);\n    float       DeclColumns(float w_icon, float w_label, float w_shortcut, float w_mark);\n    void        CalcNextTotalWidth(bool update_offsets);\n};\n\n// Internal temporary state for deactivating InputText() instances.\nstruct IMGUI_API ImGuiInputTextDeactivatedState\n{\n    ImGuiID            ID;              // widget id owning the text state (which just got deactivated)\n    ImVector<char>     TextA;           // text buffer\n\n    ImGuiInputTextDeactivatedState()    { memset(this, 0, sizeof(*this)); }\n    void    ClearFreeMemory()           { ID = 0; TextA.clear(); }\n};\n\n// Forward declare imstb_textedit.h structure + make its main configuration define accessible\n#undef IMSTB_TEXTEDIT_STRING\n#undef IMSTB_TEXTEDIT_CHARTYPE\n#define IMSTB_TEXTEDIT_STRING             ImGuiInputTextState\n#define IMSTB_TEXTEDIT_CHARTYPE           char\n#define IMSTB_TEXTEDIT_GETWIDTH_NEWLINE   (-1.0f)\n#define IMSTB_TEXTEDIT_UNDOSTATECOUNT     99\n#define IMSTB_TEXTEDIT_UNDOCHARCOUNT      999\nnamespace ImStb { struct STB_TexteditState; }\ntypedef ImStb::STB_TexteditState ImStbTexteditState;\n\n// Internal state of the currently focused/edited text input box\n// For a given item ID, access with ImGui::GetInputTextState()\nstruct IMGUI_API ImGuiInputTextState\n{\n    ImGuiContext*           Ctx;                    // parent UI context (needs to be set explicitly by parent).\n    ImStbTexteditState*     Stb;                    // State for stb_textedit.h\n    ImGuiInputTextFlags     Flags;                  // copy of InputText() flags. may be used to check if e.g. ImGuiInputTextFlags_Password is set.\n    ImGuiID                 ID;                     // widget id owning the text state\n    int                     TextLen;                // UTF-8 length of the string in TextA (in bytes)\n    const char*             TextSrc;                // == TextA.Data unless read-only, in which case == buf passed to InputText(). Field only set and valid _inside_ the call InputText() call.\n    ImVector<char>          TextA;                  // main UTF8 buffer. TextA.Size is a buffer size! Should always be >= buf_size passed by user (and of course >= CurLenA + 1).\n    ImVector<char>          TextToRevertTo;         // value to revert to when pressing Escape = backup of end-user buffer at the time of focus (in UTF-8, unaltered)\n    ImVector<char>          CallbackTextBackup;     // temporary storage for callback to support automatic reconcile of undo-stack\n    int                     BufCapacity;            // end-user buffer capacity (include zero terminator)\n    ImVec2                  Scroll;                 // horizontal offset (managed manually) + vertical scrolling (pulled from child window's own Scroll.y)\n    float                   CursorAnim;             // timer for cursor blink, reset on every user action so the cursor reappears immediately\n    bool                    CursorFollow;           // set when we want scrolling to follow the current cursor position (not always!)\n    bool                    SelectedAllMouseLock;   // after a double-click to select all, we ignore further mouse drags to update selection\n    bool                    Edited;                 // edited this frame\n    bool                    WantReloadUserBuf;      // force a reload of user buf so it may be modified externally. may be automatic in future version.\n    int                     ReloadSelectionStart;\n    int                     ReloadSelectionEnd;\n\n    ImGuiInputTextState();\n    ~ImGuiInputTextState();\n    void        ClearText()                 { TextLen = 0; TextA[0] = 0; CursorClamp(); }\n    void        ClearFreeMemory()           { TextA.clear(); TextToRevertTo.clear(); }\n    void        OnKeyPressed(int key);      // Cannot be inline because we call in code in stb_textedit.h implementation\n    void        OnCharPressed(unsigned int c);\n\n    // Cursor & Selection\n    void        CursorAnimReset();\n    void        CursorClamp();\n    bool        HasSelection() const;\n    void        ClearSelection();\n    int         GetCursorPos() const;\n    int         GetSelectionStart() const;\n    int         GetSelectionEnd() const;\n    void        SelectAll();\n\n    // Reload user buf (WIP #2890)\n    // If you modify underlying user-passed const char* while active you need to call this (InputText V2 may lift this)\n    //   strcpy(my_buf, \"hello\");\n    //   if (ImGuiInputTextState* state = ImGui::GetInputTextState(id)) // id may be ImGui::GetItemID() is last item\n    //       state->ReloadUserBufAndSelectAll();\n    void        ReloadUserBufAndSelectAll();\n    void        ReloadUserBufAndKeepSelection();\n    void        ReloadUserBufAndMoveToEnd();\n};\n\nenum ImGuiWindowRefreshFlags_\n{\n    ImGuiWindowRefreshFlags_None                = 0,\n    ImGuiWindowRefreshFlags_TryToAvoidRefresh   = 1 << 0,   // [EXPERIMENTAL] Try to keep existing contents, USER MUST NOT HONOR BEGIN() RETURNING FALSE AND NOT APPEND.\n    ImGuiWindowRefreshFlags_RefreshOnHover      = 1 << 1,   // [EXPERIMENTAL] Always refresh on hover\n    ImGuiWindowRefreshFlags_RefreshOnFocus      = 1 << 2,   // [EXPERIMENTAL] Always refresh on focus\n    // Refresh policy/frequency, Load Balancing etc.\n};\n\nenum ImGuiNextWindowDataFlags_\n{\n    ImGuiNextWindowDataFlags_None               = 0,\n    ImGuiNextWindowDataFlags_HasPos             = 1 << 0,\n    ImGuiNextWindowDataFlags_HasSize            = 1 << 1,\n    ImGuiNextWindowDataFlags_HasContentSize     = 1 << 2,\n    ImGuiNextWindowDataFlags_HasCollapsed       = 1 << 3,\n    ImGuiNextWindowDataFlags_HasSizeConstraint  = 1 << 4,\n    ImGuiNextWindowDataFlags_HasFocus           = 1 << 5,\n    ImGuiNextWindowDataFlags_HasBgAlpha         = 1 << 6,\n    ImGuiNextWindowDataFlags_HasScroll          = 1 << 7,\n    ImGuiNextWindowDataFlags_HasWindowFlags     = 1 << 8,\n    ImGuiNextWindowDataFlags_HasChildFlags      = 1 << 9,\n    ImGuiNextWindowDataFlags_HasRefreshPolicy   = 1 << 10,\n};\n\n// Storage for SetNexWindow** functions\nstruct ImGuiNextWindowData\n{\n    ImGuiNextWindowDataFlags    HasFlags;\n\n    // Members below are NOT cleared. Always rely on HasFlags.\n    ImGuiCond                   PosCond;\n    ImGuiCond                   SizeCond;\n    ImGuiCond                   CollapsedCond;\n    ImVec2                      PosVal;\n    ImVec2                      PosPivotVal;\n    ImVec2                      SizeVal;\n    ImVec2                      ContentSizeVal;\n    ImVec2                      ScrollVal;\n    ImGuiWindowFlags            WindowFlags;            // Only honored by BeginTable()\n    ImGuiChildFlags             ChildFlags;\n    bool                        CollapsedVal;\n    ImRect                      SizeConstraintRect;\n    ImGuiSizeCallback           SizeCallback;\n    void*                       SizeCallbackUserData;\n    float                       BgAlphaVal;             // Override background alpha\n    ImVec2                      MenuBarOffsetMinVal;    // (Always on) This is not exposed publicly, so we don't clear it and it doesn't have a corresponding flag (could we? for consistency?)\n    ImGuiWindowRefreshFlags     RefreshFlagsVal;\n\n    ImGuiNextWindowData()       { memset(this, 0, sizeof(*this)); }\n    inline void ClearFlags()    { HasFlags = ImGuiNextWindowDataFlags_None; }\n};\n\nenum ImGuiNextItemDataFlags_\n{\n    ImGuiNextItemDataFlags_None         = 0,\n    ImGuiNextItemDataFlags_HasWidth     = 1 << 0,\n    ImGuiNextItemDataFlags_HasOpen      = 1 << 1,\n    ImGuiNextItemDataFlags_HasShortcut  = 1 << 2,\n    ImGuiNextItemDataFlags_HasRefVal    = 1 << 3,\n    ImGuiNextItemDataFlags_HasStorageID = 1 << 4,\n};\n\nstruct ImGuiNextItemData\n{\n    ImGuiNextItemDataFlags      HasFlags;           // Called HasFlags instead of Flags to avoid mistaking this\n    ImGuiItemFlags              ItemFlags;          // Currently only tested/used for ImGuiItemFlags_AllowOverlap and ImGuiItemFlags_HasSelectionUserData.\n\n    // Members below are NOT cleared by ItemAdd() meaning they are still valid during e.g. NavProcessItem(). Always rely on HasFlags.\n    ImGuiID                     FocusScopeId;       // Set by SetNextItemSelectionUserData()\n    ImGuiSelectionUserData      SelectionUserData;  // Set by SetNextItemSelectionUserData() (note that NULL/0 is a valid value, we use -1 == ImGuiSelectionUserData_Invalid to mark invalid values)\n    float                       Width;              // Set by SetNextItemWidth()\n    ImGuiKeyChord               Shortcut;           // Set by SetNextItemShortcut()\n    ImGuiInputFlags             ShortcutFlags;      // Set by SetNextItemShortcut()\n    bool                        OpenVal;            // Set by SetNextItemOpen()\n    ImU8                        OpenCond;           // Set by SetNextItemOpen()\n    ImGuiDataTypeStorage        RefVal;             // Not exposed yet, for ImGuiInputTextFlags_ParseEmptyAsRefVal\n    ImGuiID                     StorageId;          // Set by SetNextItemStorageID()\n\n    ImGuiNextItemData()         { memset(this, 0, sizeof(*this)); SelectionUserData = -1; }\n    inline void ClearFlags()    { HasFlags = ImGuiNextItemDataFlags_None; ItemFlags = ImGuiItemFlags_None; } // Also cleared manually by ItemAdd()!\n};\n\n// Status storage for the last submitted item\nstruct ImGuiLastItemData\n{\n    ImGuiID                 ID;\n    ImGuiItemFlags          ItemFlags;          // See ImGuiItemFlags_ (called 'InFlags' before v1.91.4).\n    ImGuiItemStatusFlags    StatusFlags;        // See ImGuiItemStatusFlags_\n    ImRect                  Rect;               // Full rectangle\n    ImRect                  NavRect;            // Navigation scoring rectangle (not displayed)\n    // Rarely used fields are not explicitly cleared, only valid when the corresponding ImGuiItemStatusFlags are set.\n    ImRect                  DisplayRect;        // Display rectangle. ONLY VALID IF (StatusFlags & ImGuiItemStatusFlags_HasDisplayRect) is set.\n    ImRect                  ClipRect;           // Clip rectangle at the time of submitting item. ONLY VALID IF (StatusFlags & ImGuiItemStatusFlags_HasClipRect) is set..\n    ImGuiKeyChord           Shortcut;           // Shortcut at the time of submitting item. ONLY VALID IF (StatusFlags & ImGuiItemStatusFlags_HasShortcut) is set..\n\n    ImGuiLastItemData()     { memset(this, 0, sizeof(*this)); }\n};\n\n// Store data emitted by TreeNode() for usage by TreePop()\n// - To implement ImGuiTreeNodeFlags_NavLeftJumpsBackHere: store the minimum amount of data\n//   which we can't infer in TreePop(), to perform the equivalent of NavApplyItemToResult().\n//   Only stored when the node is a potential candidate for landing on a Left arrow jump.\nstruct ImGuiTreeNodeStackData\n{\n    ImGuiID                 ID;\n    ImGuiTreeNodeFlags      TreeFlags;\n    ImGuiItemFlags          ItemFlags;  // Used for nav landing\n    ImRect                  NavRect;    // Used for nav landing\n};\n\n// sizeof() = 20\nstruct IMGUI_API ImGuiErrorRecoveryState\n{\n    short   SizeOfWindowStack;\n    short   SizeOfIDStack;\n    short   SizeOfTreeStack;\n    short   SizeOfColorStack;\n    short   SizeOfStyleVarStack;\n    short   SizeOfFontStack;\n    short   SizeOfFocusScopeStack;\n    short   SizeOfGroupStack;\n    short   SizeOfItemFlagsStack;\n    short   SizeOfBeginPopupStack;\n    short   SizeOfDisabledStack;\n\n    ImGuiErrorRecoveryState() { memset(this, 0, sizeof(*this)); }\n};\n\n// Data saved for each window pushed into the stack\nstruct ImGuiWindowStackData\n{\n    ImGuiWindow*            Window;\n    ImGuiLastItemData       ParentLastItemDataBackup;\n    ImGuiErrorRecoveryState StackSizesInBegin;          // Store size of various stacks for asserting\n    bool                    DisabledOverrideReenable;   // Non-child window override disabled flag\n    float                   DisabledOverrideReenableAlphaBackup;\n};\n\nstruct ImGuiShrinkWidthItem\n{\n    int         Index;\n    float       Width;\n    float       InitialWidth;\n};\n\nstruct ImGuiPtrOrIndex\n{\n    void*       Ptr;            // Either field can be set, not both. e.g. Dock node tab bars are loose while BeginTabBar() ones are in a pool.\n    int         Index;          // Usually index in a main pool.\n\n    ImGuiPtrOrIndex(void* ptr)  { Ptr = ptr; Index = -1; }\n    ImGuiPtrOrIndex(int index)  { Ptr = NULL; Index = index; }\n};\n\n// Data used by IsItemDeactivated()/IsItemDeactivatedAfterEdit() functions\nstruct ImGuiDeactivatedItemData\n{\n    ImGuiID     ID;\n    int         ElapseFrame;\n    bool        HasBeenEditedBefore;\n    bool        IsAlive;\n};\n\n//-----------------------------------------------------------------------------\n// [SECTION] Popup support\n//-----------------------------------------------------------------------------\n\nenum ImGuiPopupPositionPolicy\n{\n    ImGuiPopupPositionPolicy_Default,\n    ImGuiPopupPositionPolicy_ComboBox,\n    ImGuiPopupPositionPolicy_Tooltip,\n};\n\n// Storage for popup stacks (g.OpenPopupStack and g.BeginPopupStack)\nstruct ImGuiPopupData\n{\n    ImGuiID             PopupId;        // Set on OpenPopup()\n    ImGuiWindow*        Window;         // Resolved on BeginPopup() - may stay unresolved if user never calls OpenPopup()\n    ImGuiWindow*        RestoreNavWindow;// Set on OpenPopup(), a NavWindow that will be restored on popup close\n    int                 ParentNavLayer; // Resolved on BeginPopup(). Actually a ImGuiNavLayer type (declared down below), initialized to -1 which is not part of an enum, but serves well-enough as \"not any of layers\" value\n    int                 OpenFrameCount; // Set on OpenPopup()\n    ImGuiID             OpenParentId;   // Set on OpenPopup(), we need this to differentiate multiple menu sets from each others (e.g. inside menu bar vs loose menu items)\n    ImVec2              OpenPopupPos;   // Set on OpenPopup(), preferred popup position (typically == OpenMousePos when using mouse)\n    ImVec2              OpenMousePos;   // Set on OpenPopup(), copy of mouse position at the time of opening popup\n\n    ImGuiPopupData()    { memset(this, 0, sizeof(*this)); ParentNavLayer = OpenFrameCount = -1; }\n};\n\n//-----------------------------------------------------------------------------\n// [SECTION] Inputs support\n//-----------------------------------------------------------------------------\n\n// Bit array for named keys\ntypedef ImBitArray<ImGuiKey_NamedKey_COUNT, -ImGuiKey_NamedKey_BEGIN>    ImBitArrayForNamedKeys;\n\n// [Internal] Key ranges\n#define ImGuiKey_LegacyNativeKey_BEGIN  0\n#define ImGuiKey_LegacyNativeKey_END    512\n#define ImGuiKey_Keyboard_BEGIN         (ImGuiKey_NamedKey_BEGIN)\n#define ImGuiKey_Keyboard_END           (ImGuiKey_GamepadStart)\n#define ImGuiKey_Gamepad_BEGIN          (ImGuiKey_GamepadStart)\n#define ImGuiKey_Gamepad_END            (ImGuiKey_GamepadRStickDown + 1)\n#define ImGuiKey_Mouse_BEGIN            (ImGuiKey_MouseLeft)\n#define ImGuiKey_Mouse_END              (ImGuiKey_MouseWheelY + 1)\n#define ImGuiKey_Aliases_BEGIN          (ImGuiKey_Mouse_BEGIN)\n#define ImGuiKey_Aliases_END            (ImGuiKey_Mouse_END)\n\n// [Internal] Named shortcuts for Navigation\n#define ImGuiKey_NavKeyboardTweakSlow   ImGuiMod_Ctrl\n#define ImGuiKey_NavKeyboardTweakFast   ImGuiMod_Shift\n#define ImGuiKey_NavGamepadTweakSlow    ImGuiKey_GamepadL1\n#define ImGuiKey_NavGamepadTweakFast    ImGuiKey_GamepadR1\n#define ImGuiKey_NavGamepadActivate     (g.IO.ConfigNavSwapGamepadButtons ? ImGuiKey_GamepadFaceRight : ImGuiKey_GamepadFaceDown)\n#define ImGuiKey_NavGamepadCancel       (g.IO.ConfigNavSwapGamepadButtons ? ImGuiKey_GamepadFaceDown : ImGuiKey_GamepadFaceRight)\n#define ImGuiKey_NavGamepadMenu         ImGuiKey_GamepadFaceLeft\n#define ImGuiKey_NavGamepadInput        ImGuiKey_GamepadFaceUp\n\nenum ImGuiInputEventType\n{\n    ImGuiInputEventType_None = 0,\n    ImGuiInputEventType_MousePos,\n    ImGuiInputEventType_MouseWheel,\n    ImGuiInputEventType_MouseButton,\n    ImGuiInputEventType_Key,\n    ImGuiInputEventType_Text,\n    ImGuiInputEventType_Focus,\n    ImGuiInputEventType_COUNT\n};\n\nenum ImGuiInputSource\n{\n    ImGuiInputSource_None = 0,\n    ImGuiInputSource_Mouse,         // Note: may be Mouse or TouchScreen or Pen. See io.MouseSource to distinguish them.\n    ImGuiInputSource_Keyboard,\n    ImGuiInputSource_Gamepad,\n    ImGuiInputSource_COUNT\n};\n\n// FIXME: Structures in the union below need to be declared as anonymous unions appears to be an extension?\n// Using ImVec2() would fail on Clang 'union member 'MousePos' has a non-trivial default constructor'\nstruct ImGuiInputEventMousePos      { float PosX, PosY; ImGuiMouseSource MouseSource; };\nstruct ImGuiInputEventMouseWheel    { float WheelX, WheelY; ImGuiMouseSource MouseSource; };\nstruct ImGuiInputEventMouseButton   { int Button; bool Down; ImGuiMouseSource MouseSource; };\nstruct ImGuiInputEventKey           { ImGuiKey Key; bool Down; float AnalogValue; };\nstruct ImGuiInputEventText          { unsigned int Char; };\nstruct ImGuiInputEventAppFocused    { bool Focused; };\n\nstruct ImGuiInputEvent\n{\n    ImGuiInputEventType             Type;\n    ImGuiInputSource                Source;\n    ImU32                           EventId;        // Unique, sequential increasing integer to identify an event (if you need to correlate them to other data).\n    union\n    {\n        ImGuiInputEventMousePos     MousePos;       // if Type == ImGuiInputEventType_MousePos\n        ImGuiInputEventMouseWheel   MouseWheel;     // if Type == ImGuiInputEventType_MouseWheel\n        ImGuiInputEventMouseButton  MouseButton;    // if Type == ImGuiInputEventType_MouseButton\n        ImGuiInputEventKey          Key;            // if Type == ImGuiInputEventType_Key\n        ImGuiInputEventText         Text;           // if Type == ImGuiInputEventType_Text\n        ImGuiInputEventAppFocused   AppFocused;     // if Type == ImGuiInputEventType_Focus\n    };\n    bool                            AddedByTestEngine;\n\n    ImGuiInputEvent() { memset(this, 0, sizeof(*this)); }\n};\n\n// Input function taking an 'ImGuiID owner_id' argument defaults to (ImGuiKeyOwner_Any == 0) aka don't test ownership, which matches legacy behavior.\n#define ImGuiKeyOwner_Any           ((ImGuiID)0)    // Accept key that have an owner, UNLESS a call to SetKeyOwner() explicitly used ImGuiInputFlags_LockThisFrame or ImGuiInputFlags_LockUntilRelease.\n#define ImGuiKeyOwner_NoOwner       ((ImGuiID)-1)   // Require key to have no owner.\n//#define ImGuiKeyOwner_None ImGuiKeyOwner_NoOwner  // We previously called this 'ImGuiKeyOwner_None' but it was inconsistent with our pattern that _None values == 0 and quite dangerous. Also using _NoOwner makes the IsKeyPressed() calls more explicit.\n\ntypedef ImS16 ImGuiKeyRoutingIndex;\n\n// Routing table entry (sizeof() == 16 bytes)\nstruct ImGuiKeyRoutingData\n{\n    ImGuiKeyRoutingIndex            NextEntryIndex;\n    ImU16                           Mods;               // Technically we'd only need 4-bits but for simplify we store ImGuiMod_ values which need 16-bits.\n    ImU8                            RoutingCurrScore;   // [DEBUG] For debug display\n    ImU8                            RoutingNextScore;   // Lower is better (0: perfect score)\n    ImGuiID                         RoutingCurr;\n    ImGuiID                         RoutingNext;\n\n    ImGuiKeyRoutingData()           { NextEntryIndex = -1; Mods = 0; RoutingCurrScore = RoutingNextScore = 255; RoutingCurr = RoutingNext = ImGuiKeyOwner_NoOwner; }\n};\n\n// Routing table: maintain a desired owner for each possible key-chord (key + mods), and setup owner in NewFrame() when mods are matching.\n// Stored in main context (1 instance)\nstruct ImGuiKeyRoutingTable\n{\n    ImGuiKeyRoutingIndex            Index[ImGuiKey_NamedKey_COUNT]; // Index of first entry in Entries[]\n    ImVector<ImGuiKeyRoutingData>   Entries;\n    ImVector<ImGuiKeyRoutingData>   EntriesNext;                    // Double-buffer to avoid reallocation (could use a shared buffer)\n\n    ImGuiKeyRoutingTable()          { Clear(); }\n    void Clear()                    { for (int n = 0; n < IM_ARRAYSIZE(Index); n++) Index[n] = -1; Entries.clear(); EntriesNext.clear(); }\n};\n\n// This extends ImGuiKeyData but only for named keys (legacy keys don't support the new features)\n// Stored in main context (1 per named key). In the future it might be merged into ImGuiKeyData.\nstruct ImGuiKeyOwnerData\n{\n    ImGuiID     OwnerCurr;\n    ImGuiID     OwnerNext;\n    bool        LockThisFrame;      // Reading this key requires explicit owner id (until end of frame). Set by ImGuiInputFlags_LockThisFrame.\n    bool        LockUntilRelease;   // Reading this key requires explicit owner id (until key is released). Set by ImGuiInputFlags_LockUntilRelease. When this is true LockThisFrame is always true as well.\n\n    ImGuiKeyOwnerData()             { OwnerCurr = OwnerNext = ImGuiKeyOwner_NoOwner; LockThisFrame = LockUntilRelease = false; }\n};\n\n// Extend ImGuiInputFlags_\n// Flags for extended versions of IsKeyPressed(), IsMouseClicked(), Shortcut(), SetKeyOwner(), SetItemKeyOwner()\n// Don't mistake with ImGuiInputTextFlags! (which is for ImGui::InputText() function)\nenum ImGuiInputFlagsPrivate_\n{\n    // Flags for IsKeyPressed(), IsKeyChordPressed(), IsMouseClicked(), Shortcut()\n    // - Repeat mode: Repeat rate selection\n    ImGuiInputFlags_RepeatRateDefault           = 1 << 1,   // Repeat rate: Regular (default)\n    ImGuiInputFlags_RepeatRateNavMove           = 1 << 2,   // Repeat rate: Fast\n    ImGuiInputFlags_RepeatRateNavTweak          = 1 << 3,   // Repeat rate: Faster\n    // - Repeat mode: Specify when repeating key pressed can be interrupted.\n    // - In theory ImGuiInputFlags_RepeatUntilOtherKeyPress may be a desirable default, but it would break too many behavior so everything is opt-in.\n    ImGuiInputFlags_RepeatUntilRelease          = 1 << 4,   // Stop repeating when released (default for all functions except Shortcut). This only exists to allow overriding Shortcut() default behavior.\n    ImGuiInputFlags_RepeatUntilKeyModsChange    = 1 << 5,   // Stop repeating when released OR if keyboard mods are changed (default for Shortcut)\n    ImGuiInputFlags_RepeatUntilKeyModsChangeFromNone = 1 << 6,  // Stop repeating when released OR if keyboard mods are leaving the None state. Allows going from Mod+Key to Key by releasing Mod.\n    ImGuiInputFlags_RepeatUntilOtherKeyPress    = 1 << 7,   // Stop repeating when released OR if any other keyboard key is pressed during the repeat\n\n    // Flags for SetKeyOwner(), SetItemKeyOwner()\n    // - Locking key away from non-input aware code. Locking is useful to make input-owner-aware code steal keys from non-input-owner-aware code. If all code is input-owner-aware locking would never be necessary.\n    ImGuiInputFlags_LockThisFrame               = 1 << 20,  // Further accesses to key data will require EXPLICIT owner ID (ImGuiKeyOwner_Any/0 will NOT accepted for polling). Cleared at end of frame.\n    ImGuiInputFlags_LockUntilRelease            = 1 << 21,  // Further accesses to key data will require EXPLICIT owner ID (ImGuiKeyOwner_Any/0 will NOT accepted for polling). Cleared when the key is released or at end of each frame if key is released.\n\n    // - Condition for SetItemKeyOwner()\n    ImGuiInputFlags_CondHovered                 = 1 << 22,  // Only set if item is hovered (default to both)\n    ImGuiInputFlags_CondActive                  = 1 << 23,  // Only set if item is active (default to both)\n    ImGuiInputFlags_CondDefault_                = ImGuiInputFlags_CondHovered | ImGuiInputFlags_CondActive,\n\n    // [Internal] Mask of which function support which flags\n    ImGuiInputFlags_RepeatRateMask_             = ImGuiInputFlags_RepeatRateDefault | ImGuiInputFlags_RepeatRateNavMove | ImGuiInputFlags_RepeatRateNavTweak,\n    ImGuiInputFlags_RepeatUntilMask_            = ImGuiInputFlags_RepeatUntilRelease | ImGuiInputFlags_RepeatUntilKeyModsChange | ImGuiInputFlags_RepeatUntilKeyModsChangeFromNone | ImGuiInputFlags_RepeatUntilOtherKeyPress,\n    ImGuiInputFlags_RepeatMask_                 = ImGuiInputFlags_Repeat | ImGuiInputFlags_RepeatRateMask_ | ImGuiInputFlags_RepeatUntilMask_,\n    ImGuiInputFlags_CondMask_                   = ImGuiInputFlags_CondHovered | ImGuiInputFlags_CondActive,\n    ImGuiInputFlags_RouteTypeMask_              = ImGuiInputFlags_RouteActive | ImGuiInputFlags_RouteFocused | ImGuiInputFlags_RouteGlobal | ImGuiInputFlags_RouteAlways,\n    ImGuiInputFlags_RouteOptionsMask_           = ImGuiInputFlags_RouteOverFocused | ImGuiInputFlags_RouteOverActive | ImGuiInputFlags_RouteUnlessBgFocused | ImGuiInputFlags_RouteFromRootWindow,\n    ImGuiInputFlags_SupportedByIsKeyPressed     = ImGuiInputFlags_RepeatMask_,\n    ImGuiInputFlags_SupportedByIsMouseClicked   = ImGuiInputFlags_Repeat,\n    ImGuiInputFlags_SupportedByShortcut         = ImGuiInputFlags_RepeatMask_ | ImGuiInputFlags_RouteTypeMask_ | ImGuiInputFlags_RouteOptionsMask_,\n    ImGuiInputFlags_SupportedBySetNextItemShortcut = ImGuiInputFlags_RepeatMask_ | ImGuiInputFlags_RouteTypeMask_ | ImGuiInputFlags_RouteOptionsMask_ | ImGuiInputFlags_Tooltip,\n    ImGuiInputFlags_SupportedBySetKeyOwner      = ImGuiInputFlags_LockThisFrame | ImGuiInputFlags_LockUntilRelease,\n    ImGuiInputFlags_SupportedBySetItemKeyOwner  = ImGuiInputFlags_SupportedBySetKeyOwner | ImGuiInputFlags_CondMask_,\n};\n\n//-----------------------------------------------------------------------------\n// [SECTION] Clipper support\n//-----------------------------------------------------------------------------\n\n// Note that Max is exclusive, so perhaps should be using a Begin/End convention.\nstruct ImGuiListClipperRange\n{\n    int     Min;\n    int     Max;\n    bool    PosToIndexConvert;      // Begin/End are absolute position (will be converted to indices later)\n    ImS8    PosToIndexOffsetMin;    // Add to Min after converting to indices\n    ImS8    PosToIndexOffsetMax;    // Add to Min after converting to indices\n\n    static ImGuiListClipperRange    FromIndices(int min, int max)                               { ImGuiListClipperRange r = { min, max, false, 0, 0 }; return r; }\n    static ImGuiListClipperRange    FromPositions(float y1, float y2, int off_min, int off_max) { ImGuiListClipperRange r = { (int)y1, (int)y2, true, (ImS8)off_min, (ImS8)off_max }; return r; }\n};\n\n// Temporary clipper data, buffers shared/reused between instances\nstruct ImGuiListClipperData\n{\n    ImGuiListClipper*               ListClipper;\n    float                           LossynessOffset;\n    int                             StepNo;\n    int                             ItemsFrozen;\n    ImVector<ImGuiListClipperRange> Ranges;\n\n    ImGuiListClipperData()          { memset(this, 0, sizeof(*this)); }\n    void                            Reset(ImGuiListClipper* clipper) { ListClipper = clipper; StepNo = ItemsFrozen = 0; Ranges.resize(0); }\n};\n\n//-----------------------------------------------------------------------------\n// [SECTION] Navigation support\n//-----------------------------------------------------------------------------\n\nenum ImGuiActivateFlags_\n{\n    ImGuiActivateFlags_None                 = 0,\n    ImGuiActivateFlags_PreferInput          = 1 << 0,       // Favor activation that requires keyboard text input (e.g. for Slider/Drag). Default for Enter key.\n    ImGuiActivateFlags_PreferTweak          = 1 << 1,       // Favor activation for tweaking with arrows or gamepad (e.g. for Slider/Drag). Default for Space key and if keyboard is not used.\n    ImGuiActivateFlags_TryToPreserveState   = 1 << 2,       // Request widget to preserve state if it can (e.g. InputText will try to preserve cursor/selection)\n    ImGuiActivateFlags_FromTabbing          = 1 << 3,       // Activation requested by a tabbing request\n    ImGuiActivateFlags_FromShortcut         = 1 << 4,       // Activation requested by an item shortcut via SetNextItemShortcut() function.\n};\n\n// Early work-in-progress API for ScrollToItem()\nenum ImGuiScrollFlags_\n{\n    ImGuiScrollFlags_None                   = 0,\n    ImGuiScrollFlags_KeepVisibleEdgeX       = 1 << 0,       // If item is not visible: scroll as little as possible on X axis to bring item back into view [default for X axis]\n    ImGuiScrollFlags_KeepVisibleEdgeY       = 1 << 1,       // If item is not visible: scroll as little as possible on Y axis to bring item back into view [default for Y axis for windows that are already visible]\n    ImGuiScrollFlags_KeepVisibleCenterX     = 1 << 2,       // If item is not visible: scroll to make the item centered on X axis [rarely used]\n    ImGuiScrollFlags_KeepVisibleCenterY     = 1 << 3,       // If item is not visible: scroll to make the item centered on Y axis\n    ImGuiScrollFlags_AlwaysCenterX          = 1 << 4,       // Always center the result item on X axis [rarely used]\n    ImGuiScrollFlags_AlwaysCenterY          = 1 << 5,       // Always center the result item on Y axis [default for Y axis for appearing window)\n    ImGuiScrollFlags_NoScrollParent         = 1 << 6,       // Disable forwarding scrolling to parent window if required to keep item/rect visible (only scroll window the function was applied to).\n    ImGuiScrollFlags_MaskX_                 = ImGuiScrollFlags_KeepVisibleEdgeX | ImGuiScrollFlags_KeepVisibleCenterX | ImGuiScrollFlags_AlwaysCenterX,\n    ImGuiScrollFlags_MaskY_                 = ImGuiScrollFlags_KeepVisibleEdgeY | ImGuiScrollFlags_KeepVisibleCenterY | ImGuiScrollFlags_AlwaysCenterY,\n};\n\nenum ImGuiNavRenderCursorFlags_\n{\n    ImGuiNavRenderCursorFlags_None          = 0,\n    ImGuiNavRenderCursorFlags_Compact       = 1 << 1,       // Compact highlight, no padding/distance from focused item\n    ImGuiNavRenderCursorFlags_AlwaysDraw    = 1 << 2,       // Draw rectangular highlight if (g.NavId == id) even when g.NavCursorVisible == false, aka even when using the mouse.\n    ImGuiNavRenderCursorFlags_NoRounding    = 1 << 3,\n#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS\n    ImGuiNavHighlightFlags_None             = ImGuiNavRenderCursorFlags_None,       // Renamed in 1.91.4\n    ImGuiNavHighlightFlags_Compact          = ImGuiNavRenderCursorFlags_Compact,    // Renamed in 1.91.4\n    ImGuiNavHighlightFlags_AlwaysDraw       = ImGuiNavRenderCursorFlags_AlwaysDraw, // Renamed in 1.91.4\n    ImGuiNavHighlightFlags_NoRounding       = ImGuiNavRenderCursorFlags_NoRounding, // Renamed in 1.91.4\n#endif\n};\n\nenum ImGuiNavMoveFlags_\n{\n    ImGuiNavMoveFlags_None                  = 0,\n    ImGuiNavMoveFlags_LoopX                 = 1 << 0,   // On failed request, restart from opposite side\n    ImGuiNavMoveFlags_LoopY                 = 1 << 1,\n    ImGuiNavMoveFlags_WrapX                 = 1 << 2,   // On failed request, request from opposite side one line down (when NavDir==right) or one line up (when NavDir==left)\n    ImGuiNavMoveFlags_WrapY                 = 1 << 3,   // This is not super useful but provided for completeness\n    ImGuiNavMoveFlags_WrapMask_             = ImGuiNavMoveFlags_LoopX | ImGuiNavMoveFlags_LoopY | ImGuiNavMoveFlags_WrapX | ImGuiNavMoveFlags_WrapY,\n    ImGuiNavMoveFlags_AllowCurrentNavId     = 1 << 4,   // Allow scoring and considering the current NavId as a move target candidate. This is used when the move source is offset (e.g. pressing PageDown actually needs to send a Up move request, if we are pressing PageDown from the bottom-most item we need to stay in place)\n    ImGuiNavMoveFlags_AlsoScoreVisibleSet   = 1 << 5,   // Store alternate result in NavMoveResultLocalVisible that only comprise elements that are already fully visible (used by PageUp/PageDown)\n    ImGuiNavMoveFlags_ScrollToEdgeY         = 1 << 6,   // Force scrolling to min/max (used by Home/End) // FIXME-NAV: Aim to remove or reword, probably unnecessary\n    ImGuiNavMoveFlags_Forwarded             = 1 << 7,\n    ImGuiNavMoveFlags_DebugNoResult         = 1 << 8,   // Dummy scoring for debug purpose, don't apply result\n    ImGuiNavMoveFlags_FocusApi              = 1 << 9,   // Requests from focus API can land/focus/activate items even if they are marked with _NoTabStop (see NavProcessItemForTabbingRequest() for details)\n    ImGuiNavMoveFlags_IsTabbing             = 1 << 10,  // == Focus + Activate if item is Inputable + DontChangeNavHighlight\n    ImGuiNavMoveFlags_IsPageMove            = 1 << 11,  // Identify a PageDown/PageUp request.\n    ImGuiNavMoveFlags_Activate              = 1 << 12,  // Activate/select target item.\n    ImGuiNavMoveFlags_NoSelect              = 1 << 13,  // Don't trigger selection by not setting g.NavJustMovedTo\n    ImGuiNavMoveFlags_NoSetNavCursorVisible = 1 << 14,  // Do not alter the nav cursor visible state\n    ImGuiNavMoveFlags_NoClearActiveId       = 1 << 15,  // (Experimental) Do not clear active id when applying move result\n};\n\nenum ImGuiNavLayer\n{\n    ImGuiNavLayer_Main  = 0,    // Main scrolling layer\n    ImGuiNavLayer_Menu  = 1,    // Menu layer (access with Alt)\n    ImGuiNavLayer_COUNT\n};\n\n// Storage for navigation query/results\nstruct ImGuiNavItemData\n{\n    ImGuiWindow*        Window;         // Init,Move    // Best candidate window (result->ItemWindow->RootWindowForNav == request->Window)\n    ImGuiID             ID;             // Init,Move    // Best candidate item ID\n    ImGuiID             FocusScopeId;   // Init,Move    // Best candidate focus scope ID\n    ImRect              RectRel;        // Init,Move    // Best candidate bounding box in window relative space\n    ImGuiItemFlags      ItemFlags;      // ????,Move    // Best candidate item flags\n    float               DistBox;        //      Move    // Best candidate box distance to current NavId\n    float               DistCenter;     //      Move    // Best candidate center distance to current NavId\n    float               DistAxial;      //      Move    // Best candidate axial distance to current NavId\n    ImGuiSelectionUserData SelectionUserData;//I+Mov    // Best candidate SetNextItemSelectionUserData() value. Valid if (ItemFlags & ImGuiItemFlags_HasSelectionUserData)\n\n    ImGuiNavItemData()  { Clear(); }\n    void Clear()        { Window = NULL; ID = FocusScopeId = 0; ItemFlags = 0; SelectionUserData = -1; DistBox = DistCenter = DistAxial = FLT_MAX; }\n};\n\n// Storage for PushFocusScope(), g.FocusScopeStack[], g.NavFocusRoute[]\nstruct ImGuiFocusScopeData\n{\n    ImGuiID             ID;\n    ImGuiID             WindowID;\n};\n\n//-----------------------------------------------------------------------------\n// [SECTION] Typing-select support\n//-----------------------------------------------------------------------------\n\n// Flags for GetTypingSelectRequest()\nenum ImGuiTypingSelectFlags_\n{\n    ImGuiTypingSelectFlags_None                 = 0,\n    ImGuiTypingSelectFlags_AllowBackspace       = 1 << 0,   // Backspace to delete character inputs. If using: ensure GetTypingSelectRequest() is not called more than once per frame (filter by e.g. focus state)\n    ImGuiTypingSelectFlags_AllowSingleCharMode  = 1 << 1,   // Allow \"single char\" search mode which is activated when pressing the same character multiple times.\n};\n\n// Returned by GetTypingSelectRequest(), designed to eventually be public.\nstruct IMGUI_API ImGuiTypingSelectRequest\n{\n    ImGuiTypingSelectFlags  Flags;              // Flags passed to GetTypingSelectRequest()\n    int                     SearchBufferLen;\n    const char*             SearchBuffer;       // Search buffer contents (use full string. unless SingleCharMode is set, in which case use SingleCharSize).\n    bool                    SelectRequest;      // Set when buffer was modified this frame, requesting a selection.\n    bool                    SingleCharMode;     // Notify when buffer contains same character repeated, to implement special mode. In this situation it preferred to not display any on-screen search indication.\n    ImS8                    SingleCharSize;     // Length in bytes of first letter codepoint (1 for ascii, 2-4 for UTF-8). If (SearchBufferLen==RepeatCharSize) only 1 letter has been input.\n};\n\n// Storage for GetTypingSelectRequest()\nstruct IMGUI_API ImGuiTypingSelectState\n{\n    ImGuiTypingSelectRequest Request;           // User-facing data\n    char            SearchBuffer[64];           // Search buffer: no need to make dynamic as this search is very transient.\n    ImGuiID         FocusScope;\n    int             LastRequestFrame = 0;\n    float           LastRequestTime = 0.0f;\n    bool            SingleCharModeLock = false; // After a certain single char repeat count we lock into SingleCharMode. Two benefits: 1) buffer never fill, 2) we can provide an immediate SingleChar mode without timer elapsing.\n\n    ImGuiTypingSelectState() { memset(this, 0, sizeof(*this)); }\n    void            Clear()  { SearchBuffer[0] = 0; SingleCharModeLock = false; } // We preserve remaining data for easier debugging\n};\n\n//-----------------------------------------------------------------------------\n// [SECTION] Columns support\n//-----------------------------------------------------------------------------\n\n// Flags for internal's BeginColumns(). This is an obsolete API. Prefer using BeginTable() nowadays!\nenum ImGuiOldColumnFlags_\n{\n    ImGuiOldColumnFlags_None                    = 0,\n    ImGuiOldColumnFlags_NoBorder                = 1 << 0,   // Disable column dividers\n    ImGuiOldColumnFlags_NoResize                = 1 << 1,   // Disable resizing columns when clicking on the dividers\n    ImGuiOldColumnFlags_NoPreserveWidths        = 1 << 2,   // Disable column width preservation when adjusting columns\n    ImGuiOldColumnFlags_NoForceWithinWindow     = 1 << 3,   // Disable forcing columns to fit within window\n    ImGuiOldColumnFlags_GrowParentContentsSize  = 1 << 4,   // Restore pre-1.51 behavior of extending the parent window contents size but _without affecting the columns width at all_. Will eventually remove.\n\n    // Obsolete names (will be removed)\n#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS\n    //ImGuiColumnsFlags_None                    = ImGuiOldColumnFlags_None,\n    //ImGuiColumnsFlags_NoBorder                = ImGuiOldColumnFlags_NoBorder,\n    //ImGuiColumnsFlags_NoResize                = ImGuiOldColumnFlags_NoResize,\n    //ImGuiColumnsFlags_NoPreserveWidths        = ImGuiOldColumnFlags_NoPreserveWidths,\n    //ImGuiColumnsFlags_NoForceWithinWindow     = ImGuiOldColumnFlags_NoForceWithinWindow,\n    //ImGuiColumnsFlags_GrowParentContentsSize  = ImGuiOldColumnFlags_GrowParentContentsSize,\n#endif\n};\n\nstruct ImGuiOldColumnData\n{\n    float               OffsetNorm;             // Column start offset, normalized 0.0 (far left) -> 1.0 (far right)\n    float               OffsetNormBeforeResize;\n    ImGuiOldColumnFlags Flags;                  // Not exposed\n    ImRect              ClipRect;\n\n    ImGuiOldColumnData() { memset(this, 0, sizeof(*this)); }\n};\n\nstruct ImGuiOldColumns\n{\n    ImGuiID             ID;\n    ImGuiOldColumnFlags Flags;\n    bool                IsFirstFrame;\n    bool                IsBeingResized;\n    int                 Current;\n    int                 Count;\n    float               OffMinX, OffMaxX;       // Offsets from HostWorkRect.Min.x\n    float               LineMinY, LineMaxY;\n    float               HostCursorPosY;         // Backup of CursorPos at the time of BeginColumns()\n    float               HostCursorMaxPosX;      // Backup of CursorMaxPos at the time of BeginColumns()\n    ImRect              HostInitialClipRect;    // Backup of ClipRect at the time of BeginColumns()\n    ImRect              HostBackupClipRect;     // Backup of ClipRect during PushColumnsBackground()/PopColumnsBackground()\n    ImRect              HostBackupParentWorkRect;//Backup of WorkRect at the time of BeginColumns()\n    ImVector<ImGuiOldColumnData> Columns;\n    ImDrawListSplitter  Splitter;\n\n    ImGuiOldColumns()   { memset(this, 0, sizeof(*this)); }\n};\n\n//-----------------------------------------------------------------------------\n// [SECTION] Box-select support\n//-----------------------------------------------------------------------------\n\nstruct ImGuiBoxSelectState\n{\n    // Active box-selection data (persistent, 1 active at a time)\n    ImGuiID                 ID;\n    bool                    IsActive;\n    bool                    IsStarting;\n    bool                    IsStartedFromVoid;  // Starting click was not from an item.\n    bool                    IsStartedSetNavIdOnce;\n    bool                    RequestClear;\n    ImGuiKeyChord           KeyMods : 16;       // Latched key-mods for box-select logic.\n    ImVec2                  StartPosRel;        // Start position in window-contents relative space (to support scrolling)\n    ImVec2                  EndPosRel;          // End position in window-contents relative space\n    ImVec2                  ScrollAccum;        // Scrolling accumulator (to behave at high-frame spaces)\n    ImGuiWindow*            Window;\n\n    // Temporary/Transient data\n    bool                    UnclipMode;         // (Temp/Transient, here in hot area). Set/cleared by the BeginMultiSelect()/EndMultiSelect() owning active box-select.\n    ImRect                  UnclipRect;         // Rectangle where ItemAdd() clipping may be temporarily disabled. Need support by multi-select supporting widgets.\n    ImRect                  BoxSelectRectPrev;  // Selection rectangle in absolute coordinates (derived every frame from BoxSelectStartPosRel and MousePos)\n    ImRect                  BoxSelectRectCurr;\n\n    ImGuiBoxSelectState()   { memset(this, 0, sizeof(*this)); }\n};\n\n//-----------------------------------------------------------------------------\n// [SECTION] Multi-select support\n//-----------------------------------------------------------------------------\n\n// We always assume that -1 is an invalid value (which works for indices and pointers)\n#define ImGuiSelectionUserData_Invalid        ((ImGuiSelectionUserData)-1)\n\n// Temporary storage for multi-select\nstruct IMGUI_API ImGuiMultiSelectTempData\n{\n    ImGuiMultiSelectIO      IO;                 // MUST BE FIRST FIELD. Requests are set and returned by BeginMultiSelect()/EndMultiSelect() + written to by user during the loop.\n    ImGuiMultiSelectState*  Storage;\n    ImGuiID                 FocusScopeId;       // Copied from g.CurrentFocusScopeId (unless another selection scope was pushed manually)\n    ImGuiMultiSelectFlags   Flags;\n    ImVec2                  ScopeRectMin;\n    ImVec2                  BackupCursorMaxPos;\n    ImGuiSelectionUserData  LastSubmittedItem;  // Copy of last submitted item data, used to merge output ranges.\n    ImGuiID                 BoxSelectId;\n    ImGuiKeyChord           KeyMods;\n    ImS8                    LoopRequestSetAll;  // -1: no operation, 0: clear all, 1: select all.\n    bool                    IsEndIO;            // Set when switching IO from BeginMultiSelect() to EndMultiSelect() state.\n    bool                    IsFocused;          // Set if currently focusing the selection scope (any item of the selection). May be used if you have custom shortcut associated to selection.\n    bool                    IsKeyboardSetRange; // Set by BeginMultiSelect() when using Shift+Navigation. Because scrolling may be affected we can't afford a frame of lag with Shift+Navigation.\n    bool                    NavIdPassedBy;\n    bool                    RangeSrcPassedBy;   // Set by the item that matches RangeSrcItem.\n    bool                    RangeDstPassedBy;   // Set by the item that matches NavJustMovedToId when IsSetRange is set.\n\n    ImGuiMultiSelectTempData()  { Clear(); }\n    void Clear()            { size_t io_sz = sizeof(IO); ClearIO(); memset((void*)(&IO + 1), 0, sizeof(*this) - io_sz); } // Zero-clear except IO as we preserve IO.Requests[] buffer allocation.\n    void ClearIO()          { IO.Requests.resize(0); IO.RangeSrcItem = IO.NavIdItem = ImGuiSelectionUserData_Invalid; IO.NavIdSelected = IO.RangeSrcReset = false; }\n};\n\n// Persistent storage for multi-select (as long as selection is alive)\nstruct IMGUI_API ImGuiMultiSelectState\n{\n    ImGuiWindow*            Window;\n    ImGuiID                 ID;\n    int                     LastFrameActive;    // Last used frame-count, for GC.\n    int                     LastSelectionSize;  // Set by BeginMultiSelect() based on optional info provided by user. May be -1 if unknown.\n    ImS8                    RangeSelected;      // -1 (don't have) or true/false\n    ImS8                    NavIdSelected;      // -1 (don't have) or true/false\n    ImGuiSelectionUserData  RangeSrcItem;       //\n    ImGuiSelectionUserData  NavIdItem;          // SetNextItemSelectionUserData() value for NavId (if part of submitted items)\n\n    ImGuiMultiSelectState() { Window = NULL; ID = 0; LastFrameActive = LastSelectionSize = 0; RangeSelected = NavIdSelected = -1; RangeSrcItem = NavIdItem = ImGuiSelectionUserData_Invalid; }\n};\n\n//-----------------------------------------------------------------------------\n// [SECTION] Docking support\n//-----------------------------------------------------------------------------\n\n#ifdef IMGUI_HAS_DOCK\n// <this is filled in 'docking' branch>\n#endif // #ifdef IMGUI_HAS_DOCK\n\n//-----------------------------------------------------------------------------\n// [SECTION] Viewport support\n//-----------------------------------------------------------------------------\n\n// ImGuiViewport Private/Internals fields (cardinal sin: we are using inheritance!)\n// Every instance of ImGuiViewport is in fact a ImGuiViewportP.\nstruct ImGuiViewportP : public ImGuiViewport\n{\n    int                 BgFgDrawListsLastFrame[2]; // Last frame number the background (0) and foreground (1) draw lists were used\n    ImDrawList*         BgFgDrawLists[2];       // Convenience background (0) and foreground (1) draw lists. We use them to draw software mouser cursor when io.MouseDrawCursor is set and to draw most debug overlays.\n    ImDrawData          DrawDataP;\n    ImDrawDataBuilder   DrawDataBuilder;        // Temporary data while building final ImDrawData\n\n    // Per-viewport work area\n    // - Insets are >= 0.0f values, distance from viewport corners to work area.\n    // - BeginMainMenuBar() and DockspaceOverViewport() tend to use work area to avoid stepping over existing contents.\n    // - Generally 'safeAreaInsets' in iOS land, 'DisplayCutout' in Android land.\n    ImVec2              WorkInsetMin;           // Work Area inset locked for the frame. GetWorkRect() always fits within GetMainRect().\n    ImVec2              WorkInsetMax;           // \"\n    ImVec2              BuildWorkInsetMin;      // Work Area inset accumulator for current frame, to become next frame's WorkInset\n    ImVec2              BuildWorkInsetMax;      // \"\n\n    ImGuiViewportP()    { BgFgDrawListsLastFrame[0] = BgFgDrawListsLastFrame[1] = -1; BgFgDrawLists[0] = BgFgDrawLists[1] = NULL; }\n    ~ImGuiViewportP()   { if (BgFgDrawLists[0]) IM_DELETE(BgFgDrawLists[0]); if (BgFgDrawLists[1]) IM_DELETE(BgFgDrawLists[1]); }\n\n    // Calculate work rect pos/size given a set of offset (we have 1 pair of offset for rect locked from last frame data, and 1 pair for currently building rect)\n    ImVec2  CalcWorkRectPos(const ImVec2& inset_min) const                           { return ImVec2(Pos.x + inset_min.x, Pos.y + inset_min.y); }\n    ImVec2  CalcWorkRectSize(const ImVec2& inset_min, const ImVec2& inset_max) const { return ImVec2(ImMax(0.0f, Size.x - inset_min.x - inset_max.x), ImMax(0.0f, Size.y - inset_min.y - inset_max.y)); }\n    void    UpdateWorkRect()            { WorkPos = CalcWorkRectPos(WorkInsetMin); WorkSize = CalcWorkRectSize(WorkInsetMin, WorkInsetMax); } // Update public fields\n\n    // Helpers to retrieve ImRect (we don't need to store BuildWorkRect as every access tend to change it, hence the code asymmetry)\n    ImRect  GetMainRect() const         { return ImRect(Pos.x, Pos.y, Pos.x + Size.x, Pos.y + Size.y); }\n    ImRect  GetWorkRect() const         { return ImRect(WorkPos.x, WorkPos.y, WorkPos.x + WorkSize.x, WorkPos.y + WorkSize.y); }\n    ImRect  GetBuildWorkRect() const    { ImVec2 pos = CalcWorkRectPos(BuildWorkInsetMin); ImVec2 size = CalcWorkRectSize(BuildWorkInsetMin, BuildWorkInsetMax); return ImRect(pos.x, pos.y, pos.x + size.x, pos.y + size.y); }\n};\n\n//-----------------------------------------------------------------------------\n// [SECTION] Settings support\n//-----------------------------------------------------------------------------\n\n// Windows data saved in imgui.ini file\n// Because we never destroy or rename ImGuiWindowSettings, we can store the names in a separate buffer easily.\n// (this is designed to be stored in a ImChunkStream buffer, with the variable-length Name following our structure)\nstruct ImGuiWindowSettings\n{\n    ImGuiID     ID;\n    ImVec2ih    Pos;\n    ImVec2ih    Size;\n    bool        Collapsed;\n    bool        IsChild;\n    bool        WantApply;      // Set when loaded from .ini data (to enable merging/loading .ini data into an already running context)\n    bool        WantDelete;     // Set to invalidate/delete the settings entry\n\n    ImGuiWindowSettings()       { memset(this, 0, sizeof(*this)); }\n    char* GetName()             { return (char*)(this + 1); }\n};\n\nstruct ImGuiSettingsHandler\n{\n    const char* TypeName;       // Short description stored in .ini file. Disallowed characters: '[' ']'\n    ImGuiID     TypeHash;       // == ImHashStr(TypeName)\n    void        (*ClearAllFn)(ImGuiContext* ctx, ImGuiSettingsHandler* handler);                                // Clear all settings data\n    void        (*ReadInitFn)(ImGuiContext* ctx, ImGuiSettingsHandler* handler);                                // Read: Called before reading (in registration order)\n    void*       (*ReadOpenFn)(ImGuiContext* ctx, ImGuiSettingsHandler* handler, const char* name);              // Read: Called when entering into a new ini entry e.g. \"[Window][Name]\"\n    void        (*ReadLineFn)(ImGuiContext* ctx, ImGuiSettingsHandler* handler, void* entry, const char* line); // Read: Called for every line of text within an ini entry\n    void        (*ApplyAllFn)(ImGuiContext* ctx, ImGuiSettingsHandler* handler);                                // Read: Called after reading (in registration order)\n    void        (*WriteAllFn)(ImGuiContext* ctx, ImGuiSettingsHandler* handler, ImGuiTextBuffer* out_buf);      // Write: Output every entries into 'out_buf'\n    void*       UserData;\n\n    ImGuiSettingsHandler() { memset(this, 0, sizeof(*this)); }\n};\n\n//-----------------------------------------------------------------------------\n// [SECTION] Localization support\n//-----------------------------------------------------------------------------\n\n// This is experimental and not officially supported, it'll probably fall short of features, if/when it does we may backtrack.\nenum ImGuiLocKey : int\n{\n    ImGuiLocKey_VersionStr,\n    ImGuiLocKey_TableSizeOne,\n    ImGuiLocKey_TableSizeAllFit,\n    ImGuiLocKey_TableSizeAllDefault,\n    ImGuiLocKey_TableResetOrder,\n    ImGuiLocKey_WindowingMainMenuBar,\n    ImGuiLocKey_WindowingPopup,\n    ImGuiLocKey_WindowingUntitled,\n    ImGuiLocKey_OpenLink_s,\n    ImGuiLocKey_CopyLink,\n    ImGuiLocKey_COUNT\n};\n\nstruct ImGuiLocEntry\n{\n    ImGuiLocKey     Key;\n    const char*     Text;\n};\n\n//-----------------------------------------------------------------------------\n// [SECTION] Error handling, State recovery support\n//-----------------------------------------------------------------------------\n\n// Macros used by Recoverable Error handling\n// - Only dispatch error if _EXPR: evaluate as assert (similar to an assert macro).\n// - The message will always be a string literal, in order to increase likelihood of being display by an assert handler.\n// - See 'Demo->Configuration->Error Handling' and ImGuiIO definitions for details on error handling.\n// - Read https://github.com/ocornut/imgui/wiki/Error-Handling for details on error handling.\n#ifndef IM_ASSERT_USER_ERROR\n#define IM_ASSERT_USER_ERROR(_EXPR,_MSG)    do { if (!(_EXPR) && ImGui::ErrorLog(_MSG)) { IM_ASSERT((_EXPR) && _MSG); } } while (0)    // Recoverable User Error\n#endif\n\n// The error callback is currently not public, as it is expected that only advanced users will rely on it.\ntypedef void (*ImGuiErrorCallback)(ImGuiContext* ctx, void* user_data, const char* msg); // Function signature for g.ErrorCallback\n\n//-----------------------------------------------------------------------------\n// [SECTION] Metrics, Debug Tools\n//-----------------------------------------------------------------------------\n\n// See IMGUI_DEBUG_LOG() and IMGUI_DEBUG_LOG_XXX() macros.\nenum ImGuiDebugLogFlags_\n{\n    // Event types\n    ImGuiDebugLogFlags_None                 = 0,\n    ImGuiDebugLogFlags_EventError           = 1 << 0,   // Error submitted by IM_ASSERT_USER_ERROR()\n    ImGuiDebugLogFlags_EventActiveId        = 1 << 1,\n    ImGuiDebugLogFlags_EventFocus           = 1 << 2,\n    ImGuiDebugLogFlags_EventPopup           = 1 << 3,\n    ImGuiDebugLogFlags_EventNav             = 1 << 4,\n    ImGuiDebugLogFlags_EventClipper         = 1 << 5,\n    ImGuiDebugLogFlags_EventSelection       = 1 << 6,\n    ImGuiDebugLogFlags_EventIO              = 1 << 7,\n    ImGuiDebugLogFlags_EventFont            = 1 << 8,\n    ImGuiDebugLogFlags_EventInputRouting    = 1 << 9,\n    ImGuiDebugLogFlags_EventDocking         = 1 << 10,  // Unused in this branch\n    ImGuiDebugLogFlags_EventViewport        = 1 << 11,  // Unused in this branch\n\n    ImGuiDebugLogFlags_EventMask_           = ImGuiDebugLogFlags_EventError | ImGuiDebugLogFlags_EventActiveId | ImGuiDebugLogFlags_EventFocus | ImGuiDebugLogFlags_EventPopup | ImGuiDebugLogFlags_EventNav | ImGuiDebugLogFlags_EventClipper | ImGuiDebugLogFlags_EventSelection | ImGuiDebugLogFlags_EventIO | ImGuiDebugLogFlags_EventFont | ImGuiDebugLogFlags_EventInputRouting | ImGuiDebugLogFlags_EventDocking | ImGuiDebugLogFlags_EventViewport,\n    ImGuiDebugLogFlags_OutputToTTY          = 1 << 20,  // Also send output to TTY\n    ImGuiDebugLogFlags_OutputToTestEngine   = 1 << 21,  // Also send output to Test Engine\n};\n\nstruct ImGuiDebugAllocEntry\n{\n    int         FrameCount;\n    ImS16       AllocCount;\n    ImS16       FreeCount;\n};\n\nstruct ImGuiDebugAllocInfo\n{\n    int         TotalAllocCount;            // Number of call to MemAlloc().\n    int         TotalFreeCount;\n    ImS16       LastEntriesIdx;             // Current index in buffer\n    ImGuiDebugAllocEntry LastEntriesBuf[6]; // Track last 6 frames that had allocations\n\n    ImGuiDebugAllocInfo() { memset(this, 0, sizeof(*this)); }\n};\n\nstruct ImGuiMetricsConfig\n{\n    bool        ShowDebugLog = false;\n    bool        ShowIDStackTool = false;\n    bool        ShowWindowsRects = false;\n    bool        ShowWindowsBeginOrder = false;\n    bool        ShowTablesRects = false;\n    bool        ShowDrawCmdMesh = true;\n    bool        ShowDrawCmdBoundingBoxes = true;\n    bool        ShowTextEncodingViewer = false;\n    int         ShowWindowsRectsType = -1;\n    int         ShowTablesRectsType = -1;\n    int         HighlightMonitorIdx = -1;\n    ImGuiID     HighlightViewportID = 0;\n};\n\nstruct ImGuiStackLevelInfo\n{\n    ImGuiID                 ID;\n    ImS8                    QueryFrameCount;            // >= 1: Query in progress\n    bool                    QuerySuccess;               // Obtained result from DebugHookIdInfo()\n    ImGuiDataType           DataType : 8;\n    char                    Desc[57];                   // Arbitrarily sized buffer to hold a result (FIXME: could replace Results[] with a chunk stream?) FIXME: Now that we added CTRL+C this should be fixed.\n\n    ImGuiStackLevelInfo()   { memset(this, 0, sizeof(*this)); }\n};\n\n// State for ID Stack tool queries\nstruct ImGuiIDStackTool\n{\n    int                     LastActiveFrame;\n    int                     StackLevel;                 // -1: query stack and resize Results, >= 0: individual stack level\n    ImGuiID                 QueryId;                    // ID to query details for\n    ImVector<ImGuiStackLevelInfo> Results;\n    bool                    CopyToClipboardOnCtrlC;\n    float                   CopyToClipboardLastTime;\n    ImGuiTextBuffer         ResultPathBuf;\n\n    ImGuiIDStackTool()      { memset(this, 0, sizeof(*this)); CopyToClipboardLastTime = -FLT_MAX; }\n};\n\n//-----------------------------------------------------------------------------\n// [SECTION] Generic context hooks\n//-----------------------------------------------------------------------------\n\ntypedef void (*ImGuiContextHookCallback)(ImGuiContext* ctx, ImGuiContextHook* hook);\nenum ImGuiContextHookType { ImGuiContextHookType_NewFramePre, ImGuiContextHookType_NewFramePost, ImGuiContextHookType_EndFramePre, ImGuiContextHookType_EndFramePost, ImGuiContextHookType_RenderPre, ImGuiContextHookType_RenderPost, ImGuiContextHookType_Shutdown, ImGuiContextHookType_PendingRemoval_ };\n\nstruct ImGuiContextHook\n{\n    ImGuiID                     HookId;     // A unique ID assigned by AddContextHook()\n    ImGuiContextHookType        Type;\n    ImGuiID                     Owner;\n    ImGuiContextHookCallback    Callback;\n    void*                       UserData;\n\n    ImGuiContextHook()          { memset(this, 0, sizeof(*this)); }\n};\n\n//-----------------------------------------------------------------------------\n// [SECTION] ImGuiContext (main Dear ImGui context)\n//-----------------------------------------------------------------------------\n\nstruct ImGuiContext\n{\n    bool                    Initialized;\n    bool                    FontAtlasOwnedByContext;            // IO.Fonts-> is owned by the ImGuiContext and will be destructed along with it.\n    ImGuiIO                 IO;\n    ImGuiPlatformIO         PlatformIO;\n    ImGuiStyle              Style;\n    ImFont*                 Font;                               // (Shortcut) == FontStack.empty() ? IO.Font : FontStack.back()\n    float                   FontSize;                           // (Shortcut) == FontBaseSize * g.CurrentWindow->FontWindowScale == window->FontSize(). Text height for current window.\n    float                   FontBaseSize;                       // (Shortcut) == IO.FontGlobalScale * Font->Scale * Font->FontSize. Base text height.\n    float                   FontScale;                          // == FontSize / Font->FontSize\n    float                   CurrentDpiScale;                    // Current window/viewport DpiScale\n    ImDrawListSharedData    DrawListSharedData;\n    double                  Time;\n    int                     FrameCount;\n    int                     FrameCountEnded;\n    int                     FrameCountRendered;\n    ImGuiID                 WithinEndChildID;                   // Set within EndChild()\n    bool                    WithinFrameScope;                   // Set by NewFrame(), cleared by EndFrame()\n    bool                    WithinFrameScopeWithImplicitWindow; // Set by NewFrame(), cleared by EndFrame() when the implicit debug window has been pushed\n    bool                    GcCompactAll;                       // Request full GC\n    bool                    TestEngineHookItems;                // Will call test engine hooks: ImGuiTestEngineHook_ItemAdd(), ImGuiTestEngineHook_ItemInfo(), ImGuiTestEngineHook_Log()\n    void*                   TestEngine;                         // Test engine user data\n    char                    ContextName[16];                    // Storage for a context name (to facilitate debugging multi-context setups)\n\n    // Inputs\n    ImVector<ImGuiInputEvent> InputEventsQueue;                 // Input events which will be trickled/written into IO structure.\n    ImVector<ImGuiInputEvent> InputEventsTrail;                 // Past input events processed in NewFrame(). This is to allow domain-specific application to access e.g mouse/pen trail.\n    ImGuiMouseSource        InputEventsNextMouseSource;\n    ImU32                   InputEventsNextEventId;\n\n    // Windows state\n    ImVector<ImGuiWindow*>  Windows;                            // Windows, sorted in display order, back to front\n    ImVector<ImGuiWindow*>  WindowsFocusOrder;                  // Root windows, sorted in focus order, back to front.\n    ImVector<ImGuiWindow*>  WindowsTempSortBuffer;              // Temporary buffer used in EndFrame() to reorder windows so parents are kept before their child\n    ImVector<ImGuiWindowStackData> CurrentWindowStack;\n    ImGuiStorage            WindowsById;                        // Map window's ImGuiID to ImGuiWindow*\n    int                     WindowsActiveCount;                 // Number of unique windows submitted by frame\n    float                   WindowsBorderHoverPadding;          // Padding around resizable windows for which hovering on counts as hovering the window == ImMax(style.TouchExtraPadding, style.WindowBorderHoverPadding). This isn't so multi-dpi friendly.\n    ImGuiID                 DebugBreakInWindow;                 // Set to break in Begin() call.\n    ImGuiWindow*            CurrentWindow;                      // Window being drawn into\n    ImGuiWindow*            HoveredWindow;                      // Window the mouse is hovering. Will typically catch mouse inputs.\n    ImGuiWindow*            HoveredWindowUnderMovingWindow;     // Hovered window ignoring MovingWindow. Only set if MovingWindow is set.\n    ImGuiWindow*            HoveredWindowBeforeClear;           // Window the mouse is hovering. Filled even with _NoMouse. This is currently useful for multi-context compositors.\n    ImGuiWindow*            MovingWindow;                       // Track the window we clicked on (in order to preserve focus). The actual window that is moved is generally MovingWindow->RootWindow.\n    ImGuiWindow*            WheelingWindow;                     // Track the window we started mouse-wheeling on. Until a timer elapse or mouse has moved, generally keep scrolling the same window even if during the course of scrolling the mouse ends up hovering a child window.\n    ImVec2                  WheelingWindowRefMousePos;\n    int                     WheelingWindowStartFrame;           // This may be set one frame before WheelingWindow is != NULL\n    int                     WheelingWindowScrolledFrame;\n    float                   WheelingWindowReleaseTimer;\n    ImVec2                  WheelingWindowWheelRemainder;\n    ImVec2                  WheelingAxisAvg;\n\n    // Item/widgets state and tracking information\n    ImGuiID                 DebugDrawIdConflicts;               // Set when we detect multiple items with the same identifier\n    ImGuiID                 DebugHookIdInfo;                    // Will call core hooks: DebugHookIdInfo() from GetID functions, used by ID Stack Tool [next HoveredId/ActiveId to not pull in an extra cache-line]\n    ImGuiID                 HoveredId;                          // Hovered widget, filled during the frame\n    ImGuiID                 HoveredIdPreviousFrame;\n    int                     HoveredIdPreviousFrameItemCount;    // Count numbers of items using the same ID as last frame's hovered id\n    float                   HoveredIdTimer;                     // Measure contiguous hovering time\n    float                   HoveredIdNotActiveTimer;            // Measure contiguous hovering time where the item has not been active\n    bool                    HoveredIdAllowOverlap;\n    bool                    HoveredIdIsDisabled;                // At least one widget passed the rect test, but has been discarded by disabled flag or popup inhibit. May be true even if HoveredId == 0.\n    bool                    ItemUnclipByLog;                    // Disable ItemAdd() clipping, essentially a memory-locality friendly copy of LogEnabled\n    ImGuiID                 ActiveId;                           // Active widget\n    ImGuiID                 ActiveIdIsAlive;                    // Active widget has been seen this frame (we can't use a bool as the ActiveId may change within the frame)\n    float                   ActiveIdTimer;\n    bool                    ActiveIdIsJustActivated;            // Set at the time of activation for one frame\n    bool                    ActiveIdAllowOverlap;               // Active widget allows another widget to steal active id (generally for overlapping widgets, but not always)\n    bool                    ActiveIdNoClearOnFocusLoss;         // Disable losing active id if the active id window gets unfocused.\n    bool                    ActiveIdHasBeenPressedBefore;       // Track whether the active id led to a press (this is to allow changing between PressOnClick and PressOnRelease without pressing twice). Used by range_select branch.\n    bool                    ActiveIdHasBeenEditedBefore;        // Was the value associated to the widget Edited over the course of the Active state.\n    bool                    ActiveIdHasBeenEditedThisFrame;\n    bool                    ActiveIdFromShortcut;\n    int                     ActiveIdMouseButton : 8;\n    ImVec2                  ActiveIdClickOffset;                // Clicked offset from upper-left corner, if applicable (currently only set by ButtonBehavior)\n    ImGuiWindow*            ActiveIdWindow;\n    ImGuiInputSource        ActiveIdSource;                     // Activating source: ImGuiInputSource_Mouse OR ImGuiInputSource_Keyboard OR ImGuiInputSource_Gamepad\n    ImGuiID                 ActiveIdPreviousFrame;\n    ImGuiDeactivatedItemData DeactivatedItemData;\n    ImGuiDataTypeStorage    ActiveIdValueOnActivation;          // Backup of initial value at the time of activation. ONLY SET BY SPECIFIC WIDGETS: DragXXX and SliderXXX.\n    ImGuiID                 LastActiveId;                       // Store the last non-zero ActiveId, useful for animation.\n    float                   LastActiveIdTimer;                  // Store the last non-zero ActiveId timer since the beginning of activation, useful for animation.\n\n    // Key/Input Ownership + Shortcut Routing system\n    // - The idea is that instead of \"eating\" a given key, we can link to an owner.\n    // - Input query can then read input by specifying ImGuiKeyOwner_Any (== 0), ImGuiKeyOwner_NoOwner (== -1) or a custom ID.\n    // - Routing is requested ahead of time for a given chord (Key + Mods) and granted in NewFrame().\n    double                  LastKeyModsChangeTime;              // Record the last time key mods changed (affect repeat delay when using shortcut logic)\n    double                  LastKeyModsChangeFromNoneTime;      // Record the last time key mods changed away from being 0 (affect repeat delay when using shortcut logic)\n    double                  LastKeyboardKeyPressTime;           // Record the last time a keyboard key (ignore mouse/gamepad ones) was pressed.\n    ImBitArrayForNamedKeys  KeysMayBeCharInput;                 // Lookup to tell if a key can emit char input, see IsKeyChordPotentiallyCharInput(). sizeof() = 20 bytes\n    ImGuiKeyOwnerData       KeysOwnerData[ImGuiKey_NamedKey_COUNT];\n    ImGuiKeyRoutingTable    KeysRoutingTable;\n    ImU32                   ActiveIdUsingNavDirMask;            // Active widget will want to read those nav move requests (e.g. can activate a button and move away from it)\n    bool                    ActiveIdUsingAllKeyboardKeys;       // Active widget will want to read all keyboard keys inputs. (this is a shortcut for not taking ownership of 100+ keys, frequently used by drag operations)\n    ImGuiKeyChord           DebugBreakInShortcutRouting;        // Set to break in SetShortcutRouting()/Shortcut() calls.\n    //ImU32                 ActiveIdUsingNavInputMask;          // [OBSOLETE] Since (IMGUI_VERSION_NUM >= 18804) : 'g.ActiveIdUsingNavInputMask |= (1 << ImGuiNavInput_Cancel);' becomes --> 'SetKeyOwner(ImGuiKey_Escape, g.ActiveId) and/or SetKeyOwner(ImGuiKey_NavGamepadCancel, g.ActiveId);'\n\n    // Next window/item data\n    ImGuiID                 CurrentFocusScopeId;                // Value for currently appending items == g.FocusScopeStack.back(). Not to be mistaken with g.NavFocusScopeId.\n    ImGuiItemFlags          CurrentItemFlags;                   // Value for currently appending items == g.ItemFlagsStack.back()\n    ImGuiID                 DebugLocateId;                      // Storage for DebugLocateItemOnHover() feature: this is read by ItemAdd() so we keep it in a hot/cached location\n    ImGuiNextItemData       NextItemData;                       // Storage for SetNextItem** functions\n    ImGuiLastItemData       LastItemData;                       // Storage for last submitted item (setup by ItemAdd)\n    ImGuiNextWindowData     NextWindowData;                     // Storage for SetNextWindow** functions\n    bool                    DebugShowGroupRects;\n\n    // Shared stacks\n    ImGuiCol                        DebugFlashStyleColorIdx;    // (Keep close to ColorStack to share cache line)\n    ImVector<ImGuiColorMod>         ColorStack;                 // Stack for PushStyleColor()/PopStyleColor() - inherited by Begin()\n    ImVector<ImGuiStyleMod>         StyleVarStack;              // Stack for PushStyleVar()/PopStyleVar() - inherited by Begin()\n    ImVector<ImFont*>               FontStack;                  // Stack for PushFont()/PopFont() - inherited by Begin()\n    ImVector<ImGuiFocusScopeData>   FocusScopeStack;            // Stack for PushFocusScope()/PopFocusScope() - inherited by BeginChild(), pushed into by Begin()\n    ImVector<ImGuiItemFlags>        ItemFlagsStack;             // Stack for PushItemFlag()/PopItemFlag() - inherited by Begin()\n    ImVector<ImGuiGroupData>        GroupStack;                 // Stack for BeginGroup()/EndGroup() - not inherited by Begin()\n    ImVector<ImGuiPopupData>        OpenPopupStack;             // Which popups are open (persistent)\n    ImVector<ImGuiPopupData>        BeginPopupStack;            // Which level of BeginPopup() we are in (reset every frame)\n    ImVector<ImGuiTreeNodeStackData>TreeNodeStack;              // Stack for TreeNode()\n\n    // Viewports\n    ImVector<ImGuiViewportP*> Viewports;                        // Active viewports (Size==1 in 'master' branch). Each viewports hold their copy of ImDrawData.\n\n    // Keyboard/Gamepad Navigation\n    bool                    NavCursorVisible;                   // Nav focus cursor/rectangle is visible? We hide it after a mouse click. We show it after a nav move.\n    bool                    NavHighlightItemUnderNav;           // Disable mouse hovering highlight. Highlight navigation focused item instead of mouse hovered item.\n    //bool                  NavDisableHighlight;                // Old name for !g.NavCursorVisible before 1.91.4 (2024/10/18). OPPOSITE VALUE (g.NavDisableHighlight == !g.NavCursorVisible)\n    //bool                  NavDisableMouseHover;               // Old name for g.NavHighlightItemUnderNav before 1.91.1 (2024/10/18) this was called When user starts using keyboard/gamepad, we hide mouse hovering highlight until mouse is touched again.\n    bool                    NavMousePosDirty;                   // When set we will update mouse position if io.ConfigNavMoveSetMousePos is set (not enabled by default)\n    bool                    NavIdIsAlive;                       // Nav widget has been seen this frame ~~ NavRectRel is valid\n    ImGuiID                 NavId;                              // Focused item for navigation\n    ImGuiWindow*            NavWindow;                          // Focused window for navigation. Could be called 'FocusedWindow'\n    ImGuiID                 NavFocusScopeId;                    // Focused focus scope (e.g. selection code often wants to \"clear other items\" when landing on an item of the same scope)\n    ImGuiNavLayer           NavLayer;                           // Focused layer (main scrolling layer, or menu/title bar layer)\n    ImGuiID                 NavActivateId;                      // ~~ (g.ActiveId == 0) && (IsKeyPressed(ImGuiKey_Space) || IsKeyDown(ImGuiKey_Enter) || IsKeyPressed(ImGuiKey_NavGamepadActivate)) ? NavId : 0, also set when calling ActivateItem()\n    ImGuiID                 NavActivateDownId;                  // ~~ IsKeyDown(ImGuiKey_Space) || IsKeyDown(ImGuiKey_Enter) || IsKeyDown(ImGuiKey_NavGamepadActivate) ? NavId : 0\n    ImGuiID                 NavActivatePressedId;               // ~~ IsKeyPressed(ImGuiKey_Space) || IsKeyPressed(ImGuiKey_Enter) || IsKeyPressed(ImGuiKey_NavGamepadActivate) ? NavId : 0 (no repeat)\n    ImGuiActivateFlags      NavActivateFlags;\n    ImVector<ImGuiFocusScopeData> NavFocusRoute;                // Reversed copy focus scope stack for NavId (should contains NavFocusScopeId). This essentially follow the window->ParentWindowForFocusRoute chain.\n    ImGuiID                 NavHighlightActivatedId;\n    float                   NavHighlightActivatedTimer;\n    ImGuiID                 NavNextActivateId;                  // Set by ActivateItem(), queued until next frame.\n    ImGuiActivateFlags      NavNextActivateFlags;\n    ImGuiInputSource        NavInputSource;                     // Keyboard or Gamepad mode? THIS CAN ONLY BE ImGuiInputSource_Keyboard or ImGuiInputSource_Mouse\n    ImGuiSelectionUserData  NavLastValidSelectionUserData;      // Last valid data passed to SetNextItemSelectionUser(), or -1. For current window. Not reset when focusing an item that doesn't have selection data.\n    ImS8                    NavCursorHideFrames;\n\n    // Navigation: Init & Move Requests\n    bool                    NavAnyRequest;                      // ~~ NavMoveRequest || NavInitRequest this is to perform early out in ItemAdd()\n    bool                    NavInitRequest;                     // Init request for appearing window to select first item\n    bool                    NavInitRequestFromMove;\n    ImGuiNavItemData        NavInitResult;                      // Init request result (first item of the window, or one for which SetItemDefaultFocus() was called)\n    bool                    NavMoveSubmitted;                   // Move request submitted, will process result on next NewFrame()\n    bool                    NavMoveScoringItems;                // Move request submitted, still scoring incoming items\n    bool                    NavMoveForwardToNextFrame;\n    ImGuiNavMoveFlags       NavMoveFlags;\n    ImGuiScrollFlags        NavMoveScrollFlags;\n    ImGuiKeyChord           NavMoveKeyMods;\n    ImGuiDir                NavMoveDir;                         // Direction of the move request (left/right/up/down)\n    ImGuiDir                NavMoveDirForDebug;\n    ImGuiDir                NavMoveClipDir;                     // FIXME-NAV: Describe the purpose of this better. Might want to rename?\n    ImRect                  NavScoringRect;                     // Rectangle used for scoring, in screen space. Based of window->NavRectRel[], modified for directional navigation scoring.\n    ImRect                  NavScoringNoClipRect;               // Some nav operations (such as PageUp/PageDown) enforce a region which clipper will attempt to always keep submitted\n    int                     NavScoringDebugCount;               // Metrics for debugging\n    int                     NavTabbingDir;                      // Generally -1 or +1, 0 when tabbing without a nav id\n    int                     NavTabbingCounter;                  // >0 when counting items for tabbing\n    ImGuiNavItemData        NavMoveResultLocal;                 // Best move request candidate within NavWindow\n    ImGuiNavItemData        NavMoveResultLocalVisible;          // Best move request candidate within NavWindow that are mostly visible (when using ImGuiNavMoveFlags_AlsoScoreVisibleSet flag)\n    ImGuiNavItemData        NavMoveResultOther;                 // Best move request candidate within NavWindow's flattened hierarchy (when using ImGuiWindowFlags_NavFlattened flag)\n    ImGuiNavItemData        NavTabbingResultFirst;              // First tabbing request candidate within NavWindow and flattened hierarchy\n\n    // Navigation: record of last move request\n    ImGuiID                 NavJustMovedFromFocusScopeId;       // Just navigated from this focus scope id (result of a successfully MoveRequest).\n    ImGuiID                 NavJustMovedToId;                   // Just navigated to this id (result of a successfully MoveRequest).\n    ImGuiID                 NavJustMovedToFocusScopeId;         // Just navigated to this focus scope id (result of a successfully MoveRequest).\n    ImGuiKeyChord           NavJustMovedToKeyMods;\n    bool                    NavJustMovedToIsTabbing;            // Copy of ImGuiNavMoveFlags_IsTabbing. Maybe we should store whole flags.\n    bool                    NavJustMovedToHasSelectionData;     // Copy of move result's ItemFlags & ImGuiItemFlags_HasSelectionUserData). Maybe we should just store ImGuiNavItemData.\n\n    // Navigation: Windowing (CTRL+TAB for list, or Menu button + keys or directional pads to move/resize)\n    ImGuiKeyChord           ConfigNavWindowingKeyNext;          // = ImGuiMod_Ctrl | ImGuiKey_Tab (or ImGuiMod_Super | ImGuiKey_Tab on OS X). For reconfiguration (see #4828)\n    ImGuiKeyChord           ConfigNavWindowingKeyPrev;          // = ImGuiMod_Ctrl | ImGuiMod_Shift | ImGuiKey_Tab (or ImGuiMod_Super | ImGuiMod_Shift | ImGuiKey_Tab on OS X)\n    ImGuiWindow*            NavWindowingTarget;                 // Target window when doing CTRL+Tab (or Pad Menu + FocusPrev/Next), this window is temporarily displayed top-most!\n    ImGuiWindow*            NavWindowingTargetAnim;             // Record of last valid NavWindowingTarget until DimBgRatio and NavWindowingHighlightAlpha becomes 0.0f, so the fade-out can stay on it.\n    ImGuiWindow*            NavWindowingListWindow;             // Internal window actually listing the CTRL+Tab contents\n    float                   NavWindowingTimer;\n    float                   NavWindowingHighlightAlpha;\n    bool                    NavWindowingToggleLayer;\n    ImGuiKey                NavWindowingToggleKey;\n    ImVec2                  NavWindowingAccumDeltaPos;\n    ImVec2                  NavWindowingAccumDeltaSize;\n\n    // Render\n    float                   DimBgRatio;                         // 0.0..1.0 animation when fading in a dimming background (for modal window and CTRL+TAB list)\n\n    // Drag and Drop\n    bool                    DragDropActive;\n    bool                    DragDropWithinSource;               // Set when within a BeginDragDropXXX/EndDragDropXXX block for a drag source.\n    bool                    DragDropWithinTarget;               // Set when within a BeginDragDropXXX/EndDragDropXXX block for a drag target.\n    ImGuiDragDropFlags      DragDropSourceFlags;\n    int                     DragDropSourceFrameCount;\n    int                     DragDropMouseButton;\n    ImGuiPayload            DragDropPayload;\n    ImRect                  DragDropTargetRect;                 // Store rectangle of current target candidate (we favor small targets when overlapping)\n    ImRect                  DragDropTargetClipRect;             // Store ClipRect at the time of item's drawing\n    ImGuiID                 DragDropTargetId;\n    ImGuiDragDropFlags      DragDropAcceptFlags;\n    float                   DragDropAcceptIdCurrRectSurface;    // Target item surface (we resolve overlapping targets by prioritizing the smaller surface)\n    ImGuiID                 DragDropAcceptIdCurr;               // Target item id (set at the time of accepting the payload)\n    ImGuiID                 DragDropAcceptIdPrev;               // Target item id from previous frame (we need to store this to allow for overlapping drag and drop targets)\n    int                     DragDropAcceptFrameCount;           // Last time a target expressed a desire to accept the source\n    ImGuiID                 DragDropHoldJustPressedId;          // Set when holding a payload just made ButtonBehavior() return a press.\n    ImVector<unsigned char> DragDropPayloadBufHeap;             // We don't expose the ImVector<> directly, ImGuiPayload only holds pointer+size\n    unsigned char           DragDropPayloadBufLocal[16];        // Local buffer for small payloads\n\n    // Clipper\n    int                             ClipperTempDataStacked;\n    ImVector<ImGuiListClipperData>  ClipperTempData;\n\n    // Tables\n    ImGuiTable*                     CurrentTable;\n    ImGuiID                         DebugBreakInTable;          // Set to break in BeginTable() call.\n    int                             TablesTempDataStacked;      // Temporary table data size (because we leave previous instances undestructed, we generally don't use TablesTempData.Size)\n    ImVector<ImGuiTableTempData>    TablesTempData;             // Temporary table data (buffers reused/shared across instances, support nesting)\n    ImPool<ImGuiTable>              Tables;                     // Persistent table data\n    ImVector<float>                 TablesLastTimeActive;       // Last used timestamp of each tables (SOA, for efficient GC)\n    ImVector<ImDrawChannel>         DrawChannelsTempMergeBuffer;\n\n    // Tab bars\n    ImGuiTabBar*                    CurrentTabBar;\n    ImPool<ImGuiTabBar>             TabBars;\n    ImVector<ImGuiPtrOrIndex>       CurrentTabBarStack;\n    ImVector<ImGuiShrinkWidthItem>  ShrinkWidthBuffer;\n\n    // Multi-Select state\n    ImGuiBoxSelectState             BoxSelectState;\n    ImGuiMultiSelectTempData*       CurrentMultiSelect;\n    int                             MultiSelectTempDataStacked; // Temporary multi-select data size (because we leave previous instances undestructed, we generally don't use MultiSelectTempData.Size)\n    ImVector<ImGuiMultiSelectTempData> MultiSelectTempData;\n    ImPool<ImGuiMultiSelectState>   MultiSelectStorage;\n\n    // Hover Delay system\n    ImGuiID                 HoverItemDelayId;\n    ImGuiID                 HoverItemDelayIdPreviousFrame;\n    float                   HoverItemDelayTimer;                // Currently used by IsItemHovered()\n    float                   HoverItemDelayClearTimer;           // Currently used by IsItemHovered(): grace time before g.TooltipHoverTimer gets cleared.\n    ImGuiID                 HoverItemUnlockedStationaryId;      // Mouse has once been stationary on this item. Only reset after departing the item.\n    ImGuiID                 HoverWindowUnlockedStationaryId;    // Mouse has once been stationary on this window. Only reset after departing the window.\n\n    // Mouse state\n    ImGuiMouseCursor        MouseCursor;\n    float                   MouseStationaryTimer;               // Time the mouse has been stationary (with some loose heuristic)\n    ImVec2                  MouseLastValidPos;\n\n    // Widget state\n    ImGuiInputTextState     InputTextState;\n    ImGuiInputTextDeactivatedState InputTextDeactivatedState;\n    ImFont                  InputTextPasswordFont;\n    ImGuiID                 TempInputId;                        // Temporary text input when CTRL+clicking on a slider, etc.\n    ImGuiDataTypeStorage    DataTypeZeroValue;                  // 0 for all data types\n    int                     BeginMenuDepth;\n    int                     BeginComboDepth;\n    ImGuiColorEditFlags     ColorEditOptions;                   // Store user options for color edit widgets\n    ImGuiID                 ColorEditCurrentID;                 // Set temporarily while inside of the parent-most ColorEdit4/ColorPicker4 (because they call each others).\n    ImGuiID                 ColorEditSavedID;                   // ID we are saving/restoring HS for\n    float                   ColorEditSavedHue;                  // Backup of last Hue associated to LastColor, so we can restore Hue in lossy RGB<>HSV round trips\n    float                   ColorEditSavedSat;                  // Backup of last Saturation associated to LastColor, so we can restore Saturation in lossy RGB<>HSV round trips\n    ImU32                   ColorEditSavedColor;                // RGB value with alpha set to 0.\n    ImVec4                  ColorPickerRef;                     // Initial/reference color at the time of opening the color picker.\n    ImGuiComboPreviewData   ComboPreviewData;\n    ImRect                  WindowResizeBorderExpectedRect;     // Expected border rect, switch to relative edit if moving\n    bool                    WindowResizeRelativeMode;\n    short                   ScrollbarSeekMode;                  // 0: scroll to clicked location, -1/+1: prev/next page.\n    float                   ScrollbarClickDeltaToGrabCenter;    // When scrolling to mouse location: distance between mouse and center of grab box, normalized in parent space.\n    float                   SliderGrabClickOffset;\n    float                   SliderCurrentAccum;                 // Accumulated slider delta when using navigation controls.\n    bool                    SliderCurrentAccumDirty;            // Has the accumulated slider delta changed since last time we tried to apply it?\n    bool                    DragCurrentAccumDirty;\n    float                   DragCurrentAccum;                   // Accumulator for dragging modification. Always high-precision, not rounded by end-user precision settings\n    float                   DragSpeedDefaultRatio;              // If speed == 0.0f, uses (max-min) * DragSpeedDefaultRatio\n    float                   DisabledAlphaBackup;                // Backup for style.Alpha for BeginDisabled()\n    short                   DisabledStackSize;\n    short                   TooltipOverrideCount;\n    ImGuiWindow*            TooltipPreviousWindow;              // Window of last tooltip submitted during the frame\n    ImVector<char>          ClipboardHandlerData;               // If no custom clipboard handler is defined\n    ImVector<ImGuiID>       MenusIdSubmittedThisFrame;          // A list of menu IDs that were rendered at least once\n    ImGuiTypingSelectState  TypingSelectState;                  // State for GetTypingSelectRequest()\n\n    // Platform support\n    ImGuiPlatformImeData    PlatformImeData;                    // Data updated by current frame\n    ImGuiPlatformImeData    PlatformImeDataPrev;                // Previous frame data. When changed we call the platform_io.Platform_SetImeDataFn() handler.\n\n    // Settings\n    bool                    SettingsLoaded;\n    float                   SettingsDirtyTimer;                 // Save .ini Settings to memory when time reaches zero\n    ImGuiTextBuffer         SettingsIniData;                    // In memory .ini settings\n    ImVector<ImGuiSettingsHandler>      SettingsHandlers;       // List of .ini settings handlers\n    ImChunkStream<ImGuiWindowSettings>  SettingsWindows;        // ImGuiWindow .ini settings entries\n    ImChunkStream<ImGuiTableSettings>   SettingsTables;         // ImGuiTable .ini settings entries\n    ImVector<ImGuiContextHook>          Hooks;                  // Hooks for extensions (e.g. test engine)\n    ImGuiID                             HookIdNext;             // Next available HookId\n\n    // Localization\n    const char*             LocalizationTable[ImGuiLocKey_COUNT];\n\n    // Capture/Logging\n    bool                    LogEnabled;                         // Currently capturing\n    ImGuiLogFlags           LogFlags;                           // Capture flags/type\n    ImGuiWindow*            LogWindow;\n    ImFileHandle            LogFile;                            // If != NULL log to stdout/ file\n    ImGuiTextBuffer         LogBuffer;                          // Accumulation buffer when log to clipboard. This is pointer so our GImGui static constructor doesn't call heap allocators.\n    const char*             LogNextPrefix;\n    const char*             LogNextSuffix;\n    float                   LogLinePosY;\n    bool                    LogLineFirstItem;\n    int                     LogDepthRef;\n    int                     LogDepthToExpand;\n    int                     LogDepthToExpandDefault;            // Default/stored value for LogDepthMaxExpand if not specified in the LogXXX function call.\n\n    // Error Handling\n    ImGuiErrorCallback      ErrorCallback;                      // = NULL. May be exposed in public API eventually.\n    void*                   ErrorCallbackUserData;              // = NULL\n    ImVec2                  ErrorTooltipLockedPos;\n    bool                    ErrorFirst;\n    int                     ErrorCountCurrentFrame;             // [Internal] Number of errors submitted this frame.\n    ImGuiErrorRecoveryState StackSizesInNewFrame;               // [Internal]\n    ImGuiErrorRecoveryState*StackSizesInBeginForCurrentWindow;  // [Internal]\n\n    // Debug Tools\n    // (some of the highly frequently used data are interleaved in other structures above: DebugBreakXXX fields, DebugHookIdInfo, DebugLocateId etc.)\n    int                     DebugDrawIdConflictsCount;          // Locked count (preserved when holding CTRL)\n    ImGuiDebugLogFlags      DebugLogFlags;\n    ImGuiTextBuffer         DebugLogBuf;\n    ImGuiTextIndex          DebugLogIndex;\n    int                     DebugLogSkippedErrors;\n    ImGuiDebugLogFlags      DebugLogAutoDisableFlags;\n    ImU8                    DebugLogAutoDisableFrames;\n    ImU8                    DebugLocateFrames;                  // For DebugLocateItemOnHover(). This is used together with DebugLocateId which is in a hot/cached spot above.\n    bool                    DebugBreakInLocateId;               // Debug break in ItemAdd() call for g.DebugLocateId.\n    ImGuiKeyChord           DebugBreakKeyChord;                 // = ImGuiKey_Pause\n    ImS8                    DebugBeginReturnValueCullDepth;     // Cycle between 0..9 then wrap around.\n    bool                    DebugItemPickerActive;              // Item picker is active (started with DebugStartItemPicker())\n    ImU8                    DebugItemPickerMouseButton;\n    ImGuiID                 DebugItemPickerBreakId;             // Will call IM_DEBUG_BREAK() when encountering this ID\n    float                   DebugFlashStyleColorTime;\n    ImVec4                  DebugFlashStyleColorBackup;\n    ImGuiMetricsConfig      DebugMetricsConfig;\n    ImGuiIDStackTool        DebugIDStackTool;\n    ImGuiDebugAllocInfo     DebugAllocInfo;\n\n    // Misc\n    float                   FramerateSecPerFrame[60];           // Calculate estimate of framerate for user over the last 60 frames..\n    int                     FramerateSecPerFrameIdx;\n    int                     FramerateSecPerFrameCount;\n    float                   FramerateSecPerFrameAccum;\n    int                     WantCaptureMouseNextFrame;          // Explicit capture override via SetNextFrameWantCaptureMouse()/SetNextFrameWantCaptureKeyboard(). Default to -1.\n    int                     WantCaptureKeyboardNextFrame;       // \"\n    int                     WantTextInputNextFrame;\n    ImVector<char>          TempBuffer;                         // Temporary text buffer\n    char                    TempKeychordName[64];\n\n    ImGuiContext(ImFontAtlas* shared_font_atlas);\n};\n\n//-----------------------------------------------------------------------------\n// [SECTION] ImGuiWindowTempData, ImGuiWindow\n//-----------------------------------------------------------------------------\n\n// Transient per-window data, reset at the beginning of the frame. This used to be called ImGuiDrawContext, hence the DC variable name in ImGuiWindow.\n// (That's theory, in practice the delimitation between ImGuiWindow and ImGuiWindowTempData is quite tenuous and could be reconsidered..)\n// (This doesn't need a constructor because we zero-clear it as part of ImGuiWindow and all frame-temporary data are setup on Begin)\nstruct IMGUI_API ImGuiWindowTempData\n{\n    // Layout\n    ImVec2                  CursorPos;              // Current emitting position, in absolute coordinates.\n    ImVec2                  CursorPosPrevLine;\n    ImVec2                  CursorStartPos;         // Initial position after Begin(), generally ~ window position + WindowPadding.\n    ImVec2                  CursorMaxPos;           // Used to implicitly calculate ContentSize at the beginning of next frame, for scrolling range and auto-resize. Always growing during the frame.\n    ImVec2                  IdealMaxPos;            // Used to implicitly calculate ContentSizeIdeal at the beginning of next frame, for auto-resize only. Always growing during the frame.\n    ImVec2                  CurrLineSize;\n    ImVec2                  PrevLineSize;\n    float                   CurrLineTextBaseOffset; // Baseline offset (0.0f by default on a new line, generally == style.FramePadding.y when a framed item has been added).\n    float                   PrevLineTextBaseOffset;\n    bool                    IsSameLine;\n    bool                    IsSetPos;\n    ImVec1                  Indent;                 // Indentation / start position from left of window (increased by TreePush/TreePop, etc.)\n    ImVec1                  ColumnsOffset;          // Offset to the current column (if ColumnsCurrent > 0). FIXME: This and the above should be a stack to allow use cases like Tree->Column->Tree. Need revamp columns API.\n    ImVec1                  GroupOffset;\n    ImVec2                  CursorStartPosLossyness;// Record the loss of precision of CursorStartPos due to really large scrolling amount. This is used by clipper to compensate and fix the most common use case of large scroll area.\n\n    // Keyboard/Gamepad navigation\n    ImGuiNavLayer           NavLayerCurrent;        // Current layer, 0..31 (we currently only use 0..1)\n    short                   NavLayersActiveMask;    // Which layers have been written to (result from previous frame)\n    short                   NavLayersActiveMaskNext;// Which layers have been written to (accumulator for current frame)\n    bool                    NavIsScrollPushableX;   // Set when current work location may be scrolled horizontally when moving left / right. This is generally always true UNLESS within a column.\n    bool                    NavHideHighlightOneFrame;\n    bool                    NavWindowHasScrollY;    // Set per window when scrolling can be used (== ScrollMax.y > 0.0f)\n\n    // Miscellaneous\n    bool                    MenuBarAppending;       // FIXME: Remove this\n    ImVec2                  MenuBarOffset;          // MenuBarOffset.x is sort of equivalent of a per-layer CursorPos.x, saved/restored as we switch to the menu bar. The only situation when MenuBarOffset.y is > 0 if when (SafeAreaPadding.y > FramePadding.y), often used on TVs.\n    ImGuiMenuColumns        MenuColumns;            // Simplified columns storage for menu items measurement\n    int                     TreeDepth;              // Current tree depth.\n    ImU32                   TreeHasStackDataDepthMask; // Store whether given depth has ImGuiTreeNodeStackData data. Could be turned into a ImU64 if necessary.\n    ImVector<ImGuiWindow*>  ChildWindows;\n    ImGuiStorage*           StateStorage;           // Current persistent per-window storage (store e.g. tree node open/close state)\n    ImGuiOldColumns*        CurrentColumns;         // Current columns set\n    int                     CurrentTableIdx;        // Current table index (into g.Tables)\n    ImGuiLayoutType         LayoutType;\n    ImGuiLayoutType         ParentLayoutType;       // Layout type of parent window at the time of Begin()\n    ImU32                   ModalDimBgColor;\n    ImGuiItemStatusFlags    WindowItemStatusFlags;\n    ImGuiItemStatusFlags    ChildItemStatusFlags;\n\n    // Local parameters stacks\n    // We store the current settings outside of the vectors to increase memory locality (reduce cache misses). The vectors are rarely modified. Also it allows us to not heap allocate for short-lived windows which are not using those settings.\n    float                   ItemWidth;              // Current item width (>0.0: width in pixels, <0.0: align xx pixels to the right of window).\n    float                   TextWrapPos;            // Current text wrap pos.\n    ImVector<float>         ItemWidthStack;         // Store item widths to restore (attention: .back() is not == ItemWidth)\n    ImVector<float>         TextWrapPosStack;       // Store text wrap pos to restore (attention: .back() is not == TextWrapPos)\n};\n\n// Storage for one window\nstruct IMGUI_API ImGuiWindow\n{\n    ImGuiContext*           Ctx;                                // Parent UI context (needs to be set explicitly by parent).\n    char*                   Name;                               // Window name, owned by the window.\n    ImGuiID                 ID;                                 // == ImHashStr(Name)\n    ImGuiWindowFlags        Flags;                              // See enum ImGuiWindowFlags_\n    ImGuiChildFlags         ChildFlags;                         // Set when window is a child window. See enum ImGuiChildFlags_\n    ImGuiViewportP*         Viewport;                           // Always set in Begin(). Inactive windows may have a NULL value here if their viewport was discarded.\n    ImVec2                  Pos;                                // Position (always rounded-up to nearest pixel)\n    ImVec2                  Size;                               // Current size (==SizeFull or collapsed title bar size)\n    ImVec2                  SizeFull;                           // Size when non collapsed\n    ImVec2                  ContentSize;                        // Size of contents/scrollable client area (calculated from the extents reach of the cursor) from previous frame. Does not include window decoration or window padding.\n    ImVec2                  ContentSizeIdeal;\n    ImVec2                  ContentSizeExplicit;                // Size of contents/scrollable client area explicitly request by the user via SetNextWindowContentSize().\n    ImVec2                  WindowPadding;                      // Window padding at the time of Begin().\n    float                   WindowRounding;                     // Window rounding at the time of Begin(). May be clamped lower to avoid rendering artifacts with title bar, menu bar etc.\n    float                   WindowBorderSize;                   // Window border size at the time of Begin().\n    float                   TitleBarHeight, MenuBarHeight;      // Note that those used to be function before 2024/05/28. If you have old code calling TitleBarHeight() you can change it to TitleBarHeight.\n    float                   DecoOuterSizeX1, DecoOuterSizeY1;   // Left/Up offsets. Sum of non-scrolling outer decorations (X1 generally == 0.0f. Y1 generally = TitleBarHeight + MenuBarHeight). Locked during Begin().\n    float                   DecoOuterSizeX2, DecoOuterSizeY2;   // Right/Down offsets (X2 generally == ScrollbarSize.x, Y2 == ScrollbarSizes.y).\n    float                   DecoInnerSizeX1, DecoInnerSizeY1;   // Applied AFTER/OVER InnerRect. Specialized for Tables as they use specialized form of clipping and frozen rows/columns are inside InnerRect (and not part of regular decoration sizes).\n    int                     NameBufLen;                         // Size of buffer storing Name. May be larger than strlen(Name)!\n    ImGuiID                 MoveId;                             // == window->GetID(\"#MOVE\")\n    ImGuiID                 ChildId;                            // ID of corresponding item in parent window (for navigation to return from child window to parent window)\n    ImGuiID                 PopupId;                            // ID in the popup stack when this window is used as a popup/menu (because we use generic Name/ID for recycling)\n    ImVec2                  Scroll;\n    ImVec2                  ScrollMax;\n    ImVec2                  ScrollTarget;                       // target scroll position. stored as cursor position with scrolling canceled out, so the highest point is always 0.0f. (FLT_MAX for no change)\n    ImVec2                  ScrollTargetCenterRatio;            // 0.0f = scroll so that target position is at top, 0.5f = scroll so that target position is centered\n    ImVec2                  ScrollTargetEdgeSnapDist;           // 0.0f = no snapping, >0.0f snapping threshold\n    ImVec2                  ScrollbarSizes;                     // Size taken by each scrollbars on their smaller axis. Pay attention! ScrollbarSizes.x == width of the vertical scrollbar, ScrollbarSizes.y = height of the horizontal scrollbar.\n    bool                    ScrollbarX, ScrollbarY;             // Are scrollbars visible?\n    bool                    ScrollbarXStabilizeEnabled;         // Was ScrollbarX previously auto-stabilized?\n    ImU8                    ScrollbarXStabilizeToggledHistory;  // Used to stabilize scrollbar visibility in case of feedback loops\n    bool                    Active;                             // Set to true on Begin(), unless Collapsed\n    bool                    WasActive;\n    bool                    WriteAccessed;                      // Set to true when any widget access the current window\n    bool                    Collapsed;                          // Set when collapsing window to become only title-bar\n    bool                    WantCollapseToggle;\n    bool                    SkipItems;                          // Set when items can safely be all clipped (e.g. window not visible or collapsed)\n    bool                    SkipRefresh;                        // [EXPERIMENTAL] Reuse previous frame drawn contents, Begin() returns false.\n    bool                    Appearing;                          // Set during the frame where the window is appearing (or re-appearing)\n    bool                    Hidden;                             // Do not display (== HiddenFrames*** > 0)\n    bool                    IsFallbackWindow;                   // Set on the \"Debug##Default\" window.\n    bool                    IsExplicitChild;                    // Set when passed _ChildWindow, left to false by BeginDocked()\n    bool                    HasCloseButton;                     // Set when the window has a close button (p_open != NULL)\n    signed char             ResizeBorderHovered;                // Current border being hovered for resize (-1: none, otherwise 0-3)\n    signed char             ResizeBorderHeld;                   // Current border being held for resize (-1: none, otherwise 0-3)\n    short                   BeginCount;                         // Number of Begin() during the current frame (generally 0 or 1, 1+ if appending via multiple Begin/End pairs)\n    short                   BeginCountPreviousFrame;            // Number of Begin() during the previous frame\n    short                   BeginOrderWithinParent;             // Begin() order within immediate parent window, if we are a child window. Otherwise 0.\n    short                   BeginOrderWithinContext;            // Begin() order within entire imgui context. This is mostly used for debugging submission order related issues.\n    short                   FocusOrder;                         // Order within WindowsFocusOrder[], altered when windows are focused.\n    ImS8                    AutoFitFramesX, AutoFitFramesY;\n    bool                    AutoFitOnlyGrows;\n    ImGuiDir                AutoPosLastDirection;\n    ImS8                    HiddenFramesCanSkipItems;           // Hide the window for N frames\n    ImS8                    HiddenFramesCannotSkipItems;        // Hide the window for N frames while allowing items to be submitted so we can measure their size\n    ImS8                    HiddenFramesForRenderOnly;          // Hide the window until frame N at Render() time only\n    ImS8                    DisableInputsFrames;                // Disable window interactions for N frames\n    ImGuiCond               SetWindowPosAllowFlags : 8;         // store acceptable condition flags for SetNextWindowPos() use.\n    ImGuiCond               SetWindowSizeAllowFlags : 8;        // store acceptable condition flags for SetNextWindowSize() use.\n    ImGuiCond               SetWindowCollapsedAllowFlags : 8;   // store acceptable condition flags for SetNextWindowCollapsed() use.\n    ImVec2                  SetWindowPosVal;                    // store window position when using a non-zero Pivot (position set needs to be processed when we know the window size)\n    ImVec2                  SetWindowPosPivot;                  // store window pivot for positioning. ImVec2(0, 0) when positioning from top-left corner; ImVec2(0.5f, 0.5f) for centering; ImVec2(1, 1) for bottom right.\n\n    ImVector<ImGuiID>       IDStack;                            // ID stack. ID are hashes seeded with the value at the top of the stack. (In theory this should be in the TempData structure)\n    ImGuiWindowTempData     DC;                                 // Temporary per-window data, reset at the beginning of the frame. This used to be called ImGuiDrawContext, hence the \"DC\" variable name.\n\n    // The best way to understand what those rectangles are is to use the 'Metrics->Tools->Show Windows Rectangles' viewer.\n    // The main 'OuterRect', omitted as a field, is window->Rect().\n    ImRect                  OuterRectClipped;                   // == Window->Rect() just after setup in Begin(). == window->Rect() for root window.\n    ImRect                  InnerRect;                          // Inner rectangle (omit title bar, menu bar, scroll bar)\n    ImRect                  InnerClipRect;                      // == InnerRect shrunk by WindowPadding*0.5f on each side, clipped within viewport or parent clip rect.\n    ImRect                  WorkRect;                           // Initially covers the whole scrolling region. Reduced by containers e.g columns/tables when active. Shrunk by WindowPadding*1.0f on each side. This is meant to replace ContentRegionRect over time (from 1.71+ onward).\n    ImRect                  ParentWorkRect;                     // Backup of WorkRect before entering a container such as columns/tables. Used by e.g. SpanAllColumns functions to easily access. Stacked containers are responsible for maintaining this. // FIXME-WORKRECT: Could be a stack?\n    ImRect                  ClipRect;                           // Current clipping/scissoring rectangle, evolve as we are using PushClipRect(), etc. == DrawList->clip_rect_stack.back().\n    ImRect                  ContentRegionRect;                  // FIXME: This is currently confusing/misleading. It is essentially WorkRect but not handling of scrolling. We currently rely on it as right/bottom aligned sizing operation need some size to rely on.\n    ImVec2ih                HitTestHoleSize;                    // Define an optional rectangular hole where mouse will pass-through the window.\n    ImVec2ih                HitTestHoleOffset;\n\n    int                     LastFrameActive;                    // Last frame number the window was Active.\n    float                   LastTimeActive;                     // Last timestamp the window was Active (using float as we don't need high precision there)\n    float                   ItemWidthDefault;\n    ImGuiStorage            StateStorage;\n    ImVector<ImGuiOldColumns> ColumnsStorage;\n    float                   FontWindowScale;                    // User scale multiplier per-window, via SetWindowFontScale()\n    float                   FontWindowScaleParents;\n    float                   FontRefSize;                        // This is a copy of window->CalcFontSize() at the time of Begin(), trying to phase out CalcFontSize() especially as it may be called on non-current window.\n    int                     SettingsOffset;                     // Offset into SettingsWindows[] (offsets are always valid as we only grow the array from the back)\n\n    ImDrawList*             DrawList;                           // == &DrawListInst (for backward compatibility reason with code using imgui_internal.h we keep this a pointer)\n    ImDrawList              DrawListInst;\n    ImGuiWindow*            ParentWindow;                       // If we are a child _or_ popup _or_ docked window, this is pointing to our parent. Otherwise NULL.\n    ImGuiWindow*            ParentWindowInBeginStack;\n    ImGuiWindow*            RootWindow;                         // Point to ourself or first ancestor that is not a child window. Doesn't cross through popups/dock nodes.\n    ImGuiWindow*            RootWindowPopupTree;                // Point to ourself or first ancestor that is not a child window. Cross through popups parent<>child.\n    ImGuiWindow*            RootWindowForTitleBarHighlight;     // Point to ourself or first ancestor which will display TitleBgActive color when this window is active.\n    ImGuiWindow*            RootWindowForNav;                   // Point to ourself or first ancestor which doesn't have the NavFlattened flag.\n    ImGuiWindow*            ParentWindowForFocusRoute;          // Set to manual link a window to its logical parent so that Shortcut() chain are honoerd (e.g. Tool linked to Document)\n\n    ImGuiWindow*            NavLastChildNavWindow;              // When going to the menu bar, we remember the child window we came from. (This could probably be made implicit if we kept g.Windows sorted by last focused including child window.)\n    ImGuiID                 NavLastIds[ImGuiNavLayer_COUNT];    // Last known NavId for this window, per layer (0/1)\n    ImRect                  NavRectRel[ImGuiNavLayer_COUNT];    // Reference rectangle, in window relative space\n    ImVec2                  NavPreferredScoringPosRel[ImGuiNavLayer_COUNT]; // Preferred X/Y position updated when moving on a given axis, reset to FLT_MAX.\n    ImGuiID                 NavRootFocusScopeId;                // Focus Scope ID at the time of Begin()\n\n    int                     MemoryDrawListIdxCapacity;          // Backup of last idx/vtx count, so when waking up the window we can preallocate and avoid iterative alloc/copy\n    int                     MemoryDrawListVtxCapacity;\n    bool                    MemoryCompacted;                    // Set when window extraneous data have been garbage collected\n\npublic:\n    ImGuiWindow(ImGuiContext* context, const char* name);\n    ~ImGuiWindow();\n\n    ImGuiID     GetID(const char* str, const char* str_end = NULL);\n    ImGuiID     GetID(const void* ptr);\n    ImGuiID     GetID(int n);\n    ImGuiID     GetIDFromPos(const ImVec2& p_abs);\n    ImGuiID     GetIDFromRectangle(const ImRect& r_abs);\n\n    // We don't use g.FontSize because the window may be != g.CurrentWindow.\n    ImRect      Rect() const            { return ImRect(Pos.x, Pos.y, Pos.x + Size.x, Pos.y + Size.y); }\n    float       CalcFontSize() const    { ImGuiContext& g = *Ctx; return g.FontBaseSize * FontWindowScale * FontWindowScaleParents; }\n    ImRect      TitleBarRect() const    { return ImRect(Pos, ImVec2(Pos.x + SizeFull.x, Pos.y + TitleBarHeight)); }\n    ImRect      MenuBarRect() const     { float y1 = Pos.y + TitleBarHeight; return ImRect(Pos.x, y1, Pos.x + SizeFull.x, y1 + MenuBarHeight); }\n};\n\n//-----------------------------------------------------------------------------\n// [SECTION] Tab bar, Tab item support\n//-----------------------------------------------------------------------------\n\n// Extend ImGuiTabBarFlags_\nenum ImGuiTabBarFlagsPrivate_\n{\n    ImGuiTabBarFlags_DockNode                   = 1 << 20,  // Part of a dock node [we don't use this in the master branch but it facilitate branch syncing to keep this around]\n    ImGuiTabBarFlags_IsFocused                  = 1 << 21,\n    ImGuiTabBarFlags_SaveSettings               = 1 << 22,  // FIXME: Settings are handled by the docking system, this only request the tab bar to mark settings dirty when reordering tabs\n};\n\n// Extend ImGuiTabItemFlags_\nenum ImGuiTabItemFlagsPrivate_\n{\n    ImGuiTabItemFlags_SectionMask_              = ImGuiTabItemFlags_Leading | ImGuiTabItemFlags_Trailing,\n    ImGuiTabItemFlags_NoCloseButton             = 1 << 20,  // Track whether p_open was set or not (we'll need this info on the next frame to recompute ContentWidth during layout)\n    ImGuiTabItemFlags_Button                    = 1 << 21,  // Used by TabItemButton, change the tab item behavior to mimic a button\n    ImGuiTabItemFlags_Invisible                 = 1 << 22,  // To reserve space e.g. with ImGuiTabItemFlags_Leading\n    //ImGuiTabItemFlags_Unsorted                = 1 << 23,  // [Docking] Trailing tabs with the _Unsorted flag will be sorted based on the DockOrder of their Window.\n};\n\n// Storage for one active tab item (sizeof() 40 bytes)\nstruct ImGuiTabItem\n{\n    ImGuiID             ID;\n    ImGuiTabItemFlags   Flags;\n    int                 LastFrameVisible;\n    int                 LastFrameSelected;      // This allows us to infer an ordered list of the last activated tabs with little maintenance\n    float               Offset;                 // Position relative to beginning of tab\n    float               Width;                  // Width currently displayed\n    float               ContentWidth;           // Width of label, stored during BeginTabItem() call\n    float               RequestedWidth;         // Width optionally requested by caller, -1.0f is unused\n    ImS32               NameOffset;             // When Window==NULL, offset to name within parent ImGuiTabBar::TabsNames\n    ImS16               BeginOrder;             // BeginTabItem() order, used to re-order tabs after toggling ImGuiTabBarFlags_Reorderable\n    ImS16               IndexDuringLayout;      // Index only used during TabBarLayout(). Tabs gets reordered so 'Tabs[n].IndexDuringLayout == n' but may mismatch during additions.\n    bool                WantClose;              // Marked as closed by SetTabItemClosed()\n\n    ImGuiTabItem()      { memset(this, 0, sizeof(*this)); LastFrameVisible = LastFrameSelected = -1; RequestedWidth = -1.0f; NameOffset = -1; BeginOrder = IndexDuringLayout = -1; }\n};\n\n// Storage for a tab bar (sizeof() 160 bytes)\nstruct IMGUI_API ImGuiTabBar\n{\n    ImGuiWindow*        Window;\n    ImVector<ImGuiTabItem> Tabs;\n    ImGuiTabBarFlags    Flags;\n    ImGuiID             ID;                     // Zero for tab-bars used by docking\n    ImGuiID             SelectedTabId;          // Selected tab/window\n    ImGuiID             NextSelectedTabId;      // Next selected tab/window. Will also trigger a scrolling animation\n    ImGuiID             VisibleTabId;           // Can occasionally be != SelectedTabId (e.g. when previewing contents for CTRL+TAB preview)\n    int                 CurrFrameVisible;\n    int                 PrevFrameVisible;\n    ImRect              BarRect;\n    float               CurrTabsContentsHeight;\n    float               PrevTabsContentsHeight; // Record the height of contents submitted below the tab bar\n    float               WidthAllTabs;           // Actual width of all tabs (locked during layout)\n    float               WidthAllTabsIdeal;      // Ideal width if all tabs were visible and not clipped\n    float               ScrollingAnim;\n    float               ScrollingTarget;\n    float               ScrollingTargetDistToVisibility;\n    float               ScrollingSpeed;\n    float               ScrollingRectMinX;\n    float               ScrollingRectMaxX;\n    float               SeparatorMinX;\n    float               SeparatorMaxX;\n    ImGuiID             ReorderRequestTabId;\n    ImS16               ReorderRequestOffset;\n    ImS8                BeginCount;\n    bool                WantLayout;\n    bool                VisibleTabWasSubmitted;\n    bool                TabsAddedNew;           // Set to true when a new tab item or button has been added to the tab bar during last frame\n    ImS16               TabsActiveCount;        // Number of tabs submitted this frame.\n    ImS16               LastTabItemIdx;         // Index of last BeginTabItem() tab for use by EndTabItem()\n    float               ItemSpacingY;\n    ImVec2              FramePadding;           // style.FramePadding locked at the time of BeginTabBar()\n    ImVec2              BackupCursorPos;\n    ImGuiTextBuffer     TabsNames;              // For non-docking tab bar we re-append names in a contiguous buffer.\n\n    ImGuiTabBar();\n};\n\n//-----------------------------------------------------------------------------\n// [SECTION] Table support\n//-----------------------------------------------------------------------------\n\n#define IM_COL32_DISABLE                IM_COL32(0,0,0,1)   // Special sentinel code which cannot be used as a regular color.\n#define IMGUI_TABLE_MAX_COLUMNS         512                 // May be further lifted\n\n// Our current column maximum is 64 but we may raise that in the future.\ntypedef ImS16 ImGuiTableColumnIdx;\ntypedef ImU16 ImGuiTableDrawChannelIdx;\n\n// [Internal] sizeof() ~ 112\n// We use the terminology \"Enabled\" to refer to a column that is not Hidden by user/api.\n// We use the terminology \"Clipped\" to refer to a column that is out of sight because of scrolling/clipping.\n// This is in contrast with some user-facing api such as IsItemVisible() / IsRectVisible() which use \"Visible\" to mean \"not clipped\".\nstruct ImGuiTableColumn\n{\n    ImGuiTableColumnFlags   Flags;                          // Flags after some patching (not directly same as provided by user). See ImGuiTableColumnFlags_\n    float                   WidthGiven;                     // Final/actual width visible == (MaxX - MinX), locked in TableUpdateLayout(). May be > WidthRequest to honor minimum width, may be < WidthRequest to honor shrinking columns down in tight space.\n    float                   MinX;                           // Absolute positions\n    float                   MaxX;\n    float                   WidthRequest;                   // Master width absolute value when !(Flags & _WidthStretch). When Stretch this is derived every frame from StretchWeight in TableUpdateLayout()\n    float                   WidthAuto;                      // Automatic width\n    float                   WidthMax;                       // Maximum width (FIXME: overwritten by each instance)\n    float                   StretchWeight;                  // Master width weight when (Flags & _WidthStretch). Often around ~1.0f initially.\n    float                   InitStretchWeightOrWidth;       // Value passed to TableSetupColumn(). For Width it is a content width (_without padding_).\n    ImRect                  ClipRect;                       // Clipping rectangle for the column\n    ImGuiID                 UserID;                         // Optional, value passed to TableSetupColumn()\n    float                   WorkMinX;                       // Contents region min ~(MinX + CellPaddingX + CellSpacingX1) == cursor start position when entering column\n    float                   WorkMaxX;                       // Contents region max ~(MaxX - CellPaddingX - CellSpacingX2)\n    float                   ItemWidth;                      // Current item width for the column, preserved across rows\n    float                   ContentMaxXFrozen;              // Contents maximum position for frozen rows (apart from headers), from which we can infer content width.\n    float                   ContentMaxXUnfrozen;\n    float                   ContentMaxXHeadersUsed;         // Contents maximum position for headers rows (regardless of freezing). TableHeader() automatically softclip itself + report ideal desired size, to avoid creating extraneous draw calls\n    float                   ContentMaxXHeadersIdeal;\n    ImS16                   NameOffset;                     // Offset into parent ColumnsNames[]\n    ImGuiTableColumnIdx     DisplayOrder;                   // Index within Table's IndexToDisplayOrder[] (column may be reordered by users)\n    ImGuiTableColumnIdx     IndexWithinEnabledSet;          // Index within enabled/visible set (<= IndexToDisplayOrder)\n    ImGuiTableColumnIdx     PrevEnabledColumn;              // Index of prev enabled/visible column within Columns[], -1 if first enabled/visible column\n    ImGuiTableColumnIdx     NextEnabledColumn;              // Index of next enabled/visible column within Columns[], -1 if last enabled/visible column\n    ImGuiTableColumnIdx     SortOrder;                      // Index of this column within sort specs, -1 if not sorting on this column, 0 for single-sort, may be >0 on multi-sort\n    ImGuiTableDrawChannelIdx DrawChannelCurrent;            // Index within DrawSplitter.Channels[]\n    ImGuiTableDrawChannelIdx DrawChannelFrozen;             // Draw channels for frozen rows (often headers)\n    ImGuiTableDrawChannelIdx DrawChannelUnfrozen;           // Draw channels for unfrozen rows\n    bool                    IsEnabled;                      // IsUserEnabled && (Flags & ImGuiTableColumnFlags_Disabled) == 0\n    bool                    IsUserEnabled;                  // Is the column not marked Hidden by the user? (unrelated to being off view, e.g. clipped by scrolling).\n    bool                    IsUserEnabledNextFrame;\n    bool                    IsVisibleX;                     // Is actually in view (e.g. overlapping the host window clipping rectangle, not scrolled).\n    bool                    IsVisibleY;\n    bool                    IsRequestOutput;                // Return value for TableSetColumnIndex() / TableNextColumn(): whether we request user to output contents or not.\n    bool                    IsSkipItems;                    // Do we want item submissions to this column to be completely ignored (no layout will happen).\n    bool                    IsPreserveWidthAuto;\n    ImS8                    NavLayerCurrent;                // ImGuiNavLayer in 1 byte\n    ImU8                    AutoFitQueue;                   // Queue of 8 values for the next 8 frames to request auto-fit\n    ImU8                    CannotSkipItemsQueue;           // Queue of 8 values for the next 8 frames to disable Clipped/SkipItem\n    ImU8                    SortDirection : 2;              // ImGuiSortDirection_Ascending or ImGuiSortDirection_Descending\n    ImU8                    SortDirectionsAvailCount : 2;   // Number of available sort directions (0 to 3)\n    ImU8                    SortDirectionsAvailMask : 4;    // Mask of available sort directions (1-bit each)\n    ImU8                    SortDirectionsAvailList;        // Ordered list of available sort directions (2-bits each, total 8-bits)\n\n    ImGuiTableColumn()\n    {\n        memset(this, 0, sizeof(*this));\n        StretchWeight = WidthRequest = -1.0f;\n        NameOffset = -1;\n        DisplayOrder = IndexWithinEnabledSet = -1;\n        PrevEnabledColumn = NextEnabledColumn = -1;\n        SortOrder = -1;\n        SortDirection = ImGuiSortDirection_None;\n        DrawChannelCurrent = DrawChannelFrozen = DrawChannelUnfrozen = (ImU8)-1;\n    }\n};\n\n// Transient cell data stored per row.\n// sizeof() ~ 6 bytes\nstruct ImGuiTableCellData\n{\n    ImU32                       BgColor;    // Actual color\n    ImGuiTableColumnIdx         Column;     // Column number\n};\n\n// Parameters for TableAngledHeadersRowEx()\n// This may end up being refactored for more general purpose.\n// sizeof() ~ 12 bytes\nstruct ImGuiTableHeaderData\n{\n    ImGuiTableColumnIdx         Index;      // Column index\n    ImU32                       TextColor;\n    ImU32                       BgColor0;\n    ImU32                       BgColor1;\n};\n\n// Per-instance data that needs preserving across frames (seemingly most others do not need to be preserved aside from debug needs. Does that means they could be moved to ImGuiTableTempData?)\n// sizeof() ~ 24 bytes\nstruct ImGuiTableInstanceData\n{\n    ImGuiID                     TableInstanceID;\n    float                       LastOuterHeight;            // Outer height from last frame\n    float                       LastTopHeadersRowHeight;    // Height of first consecutive header rows from last frame (FIXME: this is used assuming consecutive headers are in same frozen set)\n    float                       LastFrozenHeight;           // Height of frozen section from last frame\n    int                         HoveredRowLast;             // Index of row which was hovered last frame.\n    int                         HoveredRowNext;             // Index of row hovered this frame, set after encountering it.\n\n    ImGuiTableInstanceData()    { TableInstanceID = 0; LastOuterHeight = LastTopHeadersRowHeight = LastFrozenHeight = 0.0f; HoveredRowLast = HoveredRowNext = -1; }\n};\n\n// sizeof() ~ 592 bytes + heap allocs described in TableBeginInitMemory()\nstruct IMGUI_API ImGuiTable\n{\n    ImGuiID                     ID;\n    ImGuiTableFlags             Flags;\n    void*                       RawData;                    // Single allocation to hold Columns[], DisplayOrderToIndex[], and RowCellData[]\n    ImGuiTableTempData*         TempData;                   // Transient data while table is active. Point within g.CurrentTableStack[]\n    ImSpan<ImGuiTableColumn>    Columns;                    // Point within RawData[]\n    ImSpan<ImGuiTableColumnIdx> DisplayOrderToIndex;        // Point within RawData[]. Store display order of columns (when not reordered, the values are 0...Count-1)\n    ImSpan<ImGuiTableCellData>  RowCellData;                // Point within RawData[]. Store cells background requests for current row.\n    ImBitArrayPtr               EnabledMaskByDisplayOrder;  // Column DisplayOrder -> IsEnabled map\n    ImBitArrayPtr               EnabledMaskByIndex;         // Column Index -> IsEnabled map (== not hidden by user/api) in a format adequate for iterating column without touching cold data\n    ImBitArrayPtr               VisibleMaskByIndex;         // Column Index -> IsVisibleX|IsVisibleY map (== not hidden by user/api && not hidden by scrolling/cliprect)\n    ImGuiTableFlags             SettingsLoadedFlags;        // Which data were loaded from the .ini file (e.g. when order is not altered we won't save order)\n    int                         SettingsOffset;             // Offset in g.SettingsTables\n    int                         LastFrameActive;\n    int                         ColumnsCount;               // Number of columns declared in BeginTable()\n    int                         CurrentRow;\n    int                         CurrentColumn;\n    ImS16                       InstanceCurrent;            // Count of BeginTable() calls with same ID in the same frame (generally 0). This is a little bit similar to BeginCount for a window, but multiple tables with the same ID are multiple tables, they are just synced.\n    ImS16                       InstanceInteracted;         // Mark which instance (generally 0) of the same ID is being interacted with\n    float                       RowPosY1;\n    float                       RowPosY2;\n    float                       RowMinHeight;               // Height submitted to TableNextRow()\n    float                       RowCellPaddingY;            // Top and bottom padding. Reloaded during row change.\n    float                       RowTextBaseline;\n    float                       RowIndentOffsetX;\n    ImGuiTableRowFlags          RowFlags : 16;              // Current row flags, see ImGuiTableRowFlags_\n    ImGuiTableRowFlags          LastRowFlags : 16;\n    int                         RowBgColorCounter;          // Counter for alternating background colors (can be fast-forwarded by e.g clipper), not same as CurrentRow because header rows typically don't increase this.\n    ImU32                       RowBgColor[2];              // Background color override for current row.\n    ImU32                       BorderColorStrong;\n    ImU32                       BorderColorLight;\n    float                       BorderX1;\n    float                       BorderX2;\n    float                       HostIndentX;\n    float                       MinColumnWidth;\n    float                       OuterPaddingX;\n    float                       CellPaddingX;               // Padding from each borders. Locked in BeginTable()/Layout.\n    float                       CellSpacingX1;              // Spacing between non-bordered cells. Locked in BeginTable()/Layout.\n    float                       CellSpacingX2;\n    float                       InnerWidth;                 // User value passed to BeginTable(), see comments at the top of BeginTable() for details.\n    float                       ColumnsGivenWidth;          // Sum of current column width\n    float                       ColumnsAutoFitWidth;        // Sum of ideal column width in order nothing to be clipped, used for auto-fitting and content width submission in outer window\n    float                       ColumnsStretchSumWeights;   // Sum of weight of all enabled stretching columns\n    float                       ResizedColumnNextWidth;\n    float                       ResizeLockMinContentsX2;    // Lock minimum contents width while resizing down in order to not create feedback loops. But we allow growing the table.\n    float                       RefScale;                   // Reference scale to be able to rescale columns on font/dpi changes.\n    float                       AngledHeadersHeight;        // Set by TableAngledHeadersRow(), used in TableUpdateLayout()\n    float                       AngledHeadersSlope;         // Set by TableAngledHeadersRow(), used in TableUpdateLayout()\n    ImRect                      OuterRect;                  // Note: for non-scrolling table, OuterRect.Max.y is often FLT_MAX until EndTable(), unless a height has been specified in BeginTable().\n    ImRect                      InnerRect;                  // InnerRect but without decoration. As with OuterRect, for non-scrolling tables, InnerRect.Max.y is \"\n    ImRect                      WorkRect;\n    ImRect                      InnerClipRect;\n    ImRect                      BgClipRect;                 // We use this to cpu-clip cell background color fill, evolve during the frame as we cross frozen rows boundaries\n    ImRect                      Bg0ClipRectForDrawCmd;      // Actual ImDrawCmd clip rect for BG0/1 channel. This tends to be == OuterWindow->ClipRect at BeginTable() because output in BG0/BG1 is cpu-clipped\n    ImRect                      Bg2ClipRectForDrawCmd;      // Actual ImDrawCmd clip rect for BG2 channel. This tends to be a correct, tight-fit, because output to BG2 are done by widgets relying on regular ClipRect.\n    ImRect                      HostClipRect;               // This is used to check if we can eventually merge our columns draw calls into the current draw call of the current window.\n    ImRect                      HostBackupInnerClipRect;    // Backup of InnerWindow->ClipRect during PushTableBackground()/PopTableBackground()\n    ImGuiWindow*                OuterWindow;                // Parent window for the table\n    ImGuiWindow*                InnerWindow;                // Window holding the table data (== OuterWindow or a child window)\n    ImGuiTextBuffer             ColumnsNames;               // Contiguous buffer holding columns names\n    ImDrawListSplitter*         DrawSplitter;               // Shortcut to TempData->DrawSplitter while in table. Isolate draw commands per columns to avoid switching clip rect constantly\n    ImGuiTableInstanceData      InstanceDataFirst;\n    ImVector<ImGuiTableInstanceData>    InstanceDataExtra;  // FIXME-OPT: Using a small-vector pattern would be good.\n    ImGuiTableColumnSortSpecs   SortSpecsSingle;\n    ImVector<ImGuiTableColumnSortSpecs> SortSpecsMulti;     // FIXME-OPT: Using a small-vector pattern would be good.\n    ImGuiTableSortSpecs         SortSpecs;                  // Public facing sorts specs, this is what we return in TableGetSortSpecs()\n    ImGuiTableColumnIdx         SortSpecsCount;\n    ImGuiTableColumnIdx         ColumnsEnabledCount;        // Number of enabled columns (<= ColumnsCount)\n    ImGuiTableColumnIdx         ColumnsEnabledFixedCount;   // Number of enabled columns using fixed width (<= ColumnsCount)\n    ImGuiTableColumnIdx         DeclColumnsCount;           // Count calls to TableSetupColumn()\n    ImGuiTableColumnIdx         AngledHeadersCount;         // Count columns with angled headers\n    ImGuiTableColumnIdx         HoveredColumnBody;          // Index of column whose visible region is being hovered. Important: == ColumnsCount when hovering empty region after the right-most column!\n    ImGuiTableColumnIdx         HoveredColumnBorder;        // Index of column whose right-border is being hovered (for resizing).\n    ImGuiTableColumnIdx         HighlightColumnHeader;      // Index of column which should be highlighted.\n    ImGuiTableColumnIdx         AutoFitSingleColumn;        // Index of single column requesting auto-fit.\n    ImGuiTableColumnIdx         ResizedColumn;              // Index of column being resized. Reset when InstanceCurrent==0.\n    ImGuiTableColumnIdx         LastResizedColumn;          // Index of column being resized from previous frame.\n    ImGuiTableColumnIdx         HeldHeaderColumn;           // Index of column header being held.\n    ImGuiTableColumnIdx         ReorderColumn;              // Index of column being reordered. (not cleared)\n    ImGuiTableColumnIdx         ReorderColumnDir;           // -1 or +1\n    ImGuiTableColumnIdx         LeftMostEnabledColumn;      // Index of left-most non-hidden column.\n    ImGuiTableColumnIdx         RightMostEnabledColumn;     // Index of right-most non-hidden column.\n    ImGuiTableColumnIdx         LeftMostStretchedColumn;    // Index of left-most stretched column.\n    ImGuiTableColumnIdx         RightMostStretchedColumn;   // Index of right-most stretched column.\n    ImGuiTableColumnIdx         ContextPopupColumn;         // Column right-clicked on, of -1 if opening context menu from a neutral/empty spot\n    ImGuiTableColumnIdx         FreezeRowsRequest;          // Requested frozen rows count\n    ImGuiTableColumnIdx         FreezeRowsCount;            // Actual frozen row count (== FreezeRowsRequest, or == 0 when no scrolling offset)\n    ImGuiTableColumnIdx         FreezeColumnsRequest;       // Requested frozen columns count\n    ImGuiTableColumnIdx         FreezeColumnsCount;         // Actual frozen columns count (== FreezeColumnsRequest, or == 0 when no scrolling offset)\n    ImGuiTableColumnIdx         RowCellDataCurrent;         // Index of current RowCellData[] entry in current row\n    ImGuiTableDrawChannelIdx    DummyDrawChannel;           // Redirect non-visible columns here.\n    ImGuiTableDrawChannelIdx    Bg2DrawChannelCurrent;      // For Selectable() and other widgets drawing across columns after the freezing line. Index within DrawSplitter.Channels[]\n    ImGuiTableDrawChannelIdx    Bg2DrawChannelUnfrozen;\n    ImS8                        NavLayer;                   // ImGuiNavLayer at the time of BeginTable().\n    bool                        IsLayoutLocked;             // Set by TableUpdateLayout() which is called when beginning the first row.\n    bool                        IsInsideRow;                // Set when inside TableBeginRow()/TableEndRow().\n    bool                        IsInitializing;\n    bool                        IsSortSpecsDirty;\n    bool                        IsUsingHeaders;             // Set when the first row had the ImGuiTableRowFlags_Headers flag.\n    bool                        IsContextPopupOpen;         // Set when default context menu is open (also see: ContextPopupColumn, InstanceInteracted).\n    bool                        DisableDefaultContextMenu;  // Disable default context menu contents. You may submit your own using TableBeginContextMenuPopup()/EndPopup()\n    bool                        IsSettingsRequestLoad;\n    bool                        IsSettingsDirty;            // Set when table settings have changed and needs to be reported into ImGuiTableSetttings data.\n    bool                        IsDefaultDisplayOrder;      // Set when display order is unchanged from default (DisplayOrder contains 0...Count-1)\n    bool                        IsResetAllRequest;\n    bool                        IsResetDisplayOrderRequest;\n    bool                        IsUnfrozenRows;             // Set when we got past the frozen row.\n    bool                        IsDefaultSizingPolicy;      // Set if user didn't explicitly set a sizing policy in BeginTable()\n    bool                        IsActiveIdAliveBeforeTable;\n    bool                        IsActiveIdInTable;\n    bool                        HasScrollbarYCurr;          // Whether ANY instance of this table had a vertical scrollbar during the current frame.\n    bool                        HasScrollbarYPrev;          // Whether ANY instance of this table had a vertical scrollbar during the previous.\n    bool                        MemoryCompacted;\n    bool                        HostSkipItems;              // Backup of InnerWindow->SkipItem at the end of BeginTable(), because we will overwrite InnerWindow->SkipItem on a per-column basis\n\n    ImGuiTable()                { memset(this, 0, sizeof(*this)); LastFrameActive = -1; }\n    ~ImGuiTable()               { IM_FREE(RawData); }\n};\n\n// Transient data that are only needed between BeginTable() and EndTable(), those buffers are shared (1 per level of stacked table).\n// - Accessing those requires chasing an extra pointer so for very frequently used data we leave them in the main table structure.\n// - We also leave out of this structure data that tend to be particularly useful for debugging/metrics.\n// FIXME-TABLE: more transient data could be stored in a stacked ImGuiTableTempData: e.g. SortSpecs.\n// sizeof() ~ 136 bytes.\nstruct IMGUI_API ImGuiTableTempData\n{\n    int                         TableIndex;                 // Index in g.Tables.Buf[] pool\n    float                       LastTimeActive;             // Last timestamp this structure was used\n    float                       AngledHeadersExtraWidth;    // Used in EndTable()\n    ImVector<ImGuiTableHeaderData> AngledHeadersRequests;   // Used in TableAngledHeadersRow()\n\n    ImVec2                      UserOuterSize;              // outer_size.x passed to BeginTable()\n    ImDrawListSplitter          DrawSplitter;\n\n    ImRect                      HostBackupWorkRect;         // Backup of InnerWindow->WorkRect at the end of BeginTable()\n    ImRect                      HostBackupParentWorkRect;   // Backup of InnerWindow->ParentWorkRect at the end of BeginTable()\n    ImVec2                      HostBackupPrevLineSize;     // Backup of InnerWindow->DC.PrevLineSize at the end of BeginTable()\n    ImVec2                      HostBackupCurrLineSize;     // Backup of InnerWindow->DC.CurrLineSize at the end of BeginTable()\n    ImVec2                      HostBackupCursorMaxPos;     // Backup of InnerWindow->DC.CursorMaxPos at the end of BeginTable()\n    ImVec1                      HostBackupColumnsOffset;    // Backup of OuterWindow->DC.ColumnsOffset at the end of BeginTable()\n    float                       HostBackupItemWidth;        // Backup of OuterWindow->DC.ItemWidth at the end of BeginTable()\n    int                         HostBackupItemWidthStackSize;//Backup of OuterWindow->DC.ItemWidthStack.Size at the end of BeginTable()\n\n    ImGuiTableTempData()        { memset(this, 0, sizeof(*this)); LastTimeActive = -1.0f; }\n};\n\n// sizeof() ~ 16\nstruct ImGuiTableColumnSettings\n{\n    float                   WidthOrWeight;\n    ImGuiID                 UserID;\n    ImGuiTableColumnIdx     Index;\n    ImGuiTableColumnIdx     DisplayOrder;\n    ImGuiTableColumnIdx     SortOrder;\n    ImU8                    SortDirection : 2;\n    ImS8                    IsEnabled : 2; // \"Visible\" in ini file\n    ImU8                    IsStretch : 1;\n\n    ImGuiTableColumnSettings()\n    {\n        WidthOrWeight = 0.0f;\n        UserID = 0;\n        Index = -1;\n        DisplayOrder = SortOrder = -1;\n        SortDirection = ImGuiSortDirection_None;\n        IsEnabled = -1;\n        IsStretch = 0;\n    }\n};\n\n// This is designed to be stored in a single ImChunkStream (1 header followed by N ImGuiTableColumnSettings, etc.)\nstruct ImGuiTableSettings\n{\n    ImGuiID                     ID;                     // Set to 0 to invalidate/delete the setting\n    ImGuiTableFlags             SaveFlags;              // Indicate data we want to save using the Resizable/Reorderable/Sortable/Hideable flags (could be using its own flags..)\n    float                       RefScale;               // Reference scale to be able to rescale columns on font/dpi changes.\n    ImGuiTableColumnIdx         ColumnsCount;\n    ImGuiTableColumnIdx         ColumnsCountMax;        // Maximum number of columns this settings instance can store, we can recycle a settings instance with lower number of columns but not higher\n    bool                        WantApply;              // Set when loaded from .ini data (to enable merging/loading .ini data into an already running context)\n\n    ImGuiTableSettings()        { memset(this, 0, sizeof(*this)); }\n    ImGuiTableColumnSettings*   GetColumnSettings()     { return (ImGuiTableColumnSettings*)(this + 1); }\n};\n\n//-----------------------------------------------------------------------------\n// [SECTION] ImGui internal API\n// No guarantee of forward compatibility here!\n//-----------------------------------------------------------------------------\n\nnamespace ImGui\n{\n    // Windows\n    // We should always have a CurrentWindow in the stack (there is an implicit \"Debug\" window)\n    // If this ever crashes because g.CurrentWindow is NULL, it means that either:\n    // - ImGui::NewFrame() has never been called, which is illegal.\n    // - You are calling ImGui functions after ImGui::EndFrame()/ImGui::Render() and before the next ImGui::NewFrame(), which is also illegal.\n    IMGUI_API ImGuiIO&         GetIO(ImGuiContext* ctx);\n    IMGUI_API ImGuiPlatformIO& GetPlatformIO(ImGuiContext* ctx);\n    inline    ImGuiWindow*  GetCurrentWindowRead()      { ImGuiContext& g = *GImGui; return g.CurrentWindow; }\n    inline    ImGuiWindow*  GetCurrentWindow()          { ImGuiContext& g = *GImGui; g.CurrentWindow->WriteAccessed = true; return g.CurrentWindow; }\n    IMGUI_API ImGuiWindow*  FindWindowByID(ImGuiID id);\n    IMGUI_API ImGuiWindow*  FindWindowByName(const char* name);\n    IMGUI_API void          UpdateWindowParentAndRootLinks(ImGuiWindow* window, ImGuiWindowFlags flags, ImGuiWindow* parent_window);\n    IMGUI_API void          UpdateWindowSkipRefresh(ImGuiWindow* window);\n    IMGUI_API ImVec2        CalcWindowNextAutoFitSize(ImGuiWindow* window);\n    IMGUI_API bool          IsWindowChildOf(ImGuiWindow* window, ImGuiWindow* potential_parent, bool popup_hierarchy);\n    IMGUI_API bool          IsWindowWithinBeginStackOf(ImGuiWindow* window, ImGuiWindow* potential_parent);\n    IMGUI_API bool          IsWindowAbove(ImGuiWindow* potential_above, ImGuiWindow* potential_below);\n    IMGUI_API bool          IsWindowNavFocusable(ImGuiWindow* window);\n    IMGUI_API void          SetWindowPos(ImGuiWindow* window, const ImVec2& pos, ImGuiCond cond = 0);\n    IMGUI_API void          SetWindowSize(ImGuiWindow* window, const ImVec2& size, ImGuiCond cond = 0);\n    IMGUI_API void          SetWindowCollapsed(ImGuiWindow* window, bool collapsed, ImGuiCond cond = 0);\n    IMGUI_API void          SetWindowHitTestHole(ImGuiWindow* window, const ImVec2& pos, const ImVec2& size);\n    IMGUI_API void          SetWindowHiddenAndSkipItemsForCurrentFrame(ImGuiWindow* window);\n    inline void             SetWindowParentWindowForFocusRoute(ImGuiWindow* window, ImGuiWindow* parent_window) { window->ParentWindowForFocusRoute = parent_window; }\n    inline ImRect           WindowRectAbsToRel(ImGuiWindow* window, const ImRect& r) { ImVec2 off = window->DC.CursorStartPos; return ImRect(r.Min.x - off.x, r.Min.y - off.y, r.Max.x - off.x, r.Max.y - off.y); }\n    inline ImRect           WindowRectRelToAbs(ImGuiWindow* window, const ImRect& r) { ImVec2 off = window->DC.CursorStartPos; return ImRect(r.Min.x + off.x, r.Min.y + off.y, r.Max.x + off.x, r.Max.y + off.y); }\n    inline ImVec2           WindowPosAbsToRel(ImGuiWindow* window, const ImVec2& p)  { ImVec2 off = window->DC.CursorStartPos; return ImVec2(p.x - off.x, p.y - off.y); }\n    inline ImVec2           WindowPosRelToAbs(ImGuiWindow* window, const ImVec2& p)  { ImVec2 off = window->DC.CursorStartPos; return ImVec2(p.x + off.x, p.y + off.y); }\n\n    // Windows: Display Order and Focus Order\n    IMGUI_API void          FocusWindow(ImGuiWindow* window, ImGuiFocusRequestFlags flags = 0);\n    IMGUI_API void          FocusTopMostWindowUnderOne(ImGuiWindow* under_this_window, ImGuiWindow* ignore_window, ImGuiViewport* filter_viewport, ImGuiFocusRequestFlags flags);\n    IMGUI_API void          BringWindowToFocusFront(ImGuiWindow* window);\n    IMGUI_API void          BringWindowToDisplayFront(ImGuiWindow* window);\n    IMGUI_API void          BringWindowToDisplayBack(ImGuiWindow* window);\n    IMGUI_API void          BringWindowToDisplayBehind(ImGuiWindow* window, ImGuiWindow* above_window);\n    IMGUI_API int           FindWindowDisplayIndex(ImGuiWindow* window);\n    IMGUI_API ImGuiWindow*  FindBottomMostVisibleWindowWithinBeginStack(ImGuiWindow* window);\n\n    // Windows: Idle, Refresh Policies [EXPERIMENTAL]\n    IMGUI_API void          SetNextWindowRefreshPolicy(ImGuiWindowRefreshFlags flags);\n\n    // Fonts, drawing\n    IMGUI_API void          SetCurrentFont(ImFont* font);\n    inline ImFont*          GetDefaultFont() { ImGuiContext& g = *GImGui; return g.IO.FontDefault ? g.IO.FontDefault : g.IO.Fonts->Fonts[0]; }\n    IMGUI_API void          PushPasswordFont();\n    inline ImDrawList*      GetForegroundDrawList(ImGuiWindow* window) { IM_UNUSED(window); return GetForegroundDrawList(); } // This seemingly unnecessary wrapper simplifies compatibility between the 'master' and 'docking' branches.\n    IMGUI_API ImDrawList*   GetBackgroundDrawList(ImGuiViewport* viewport);                     // get background draw list for the given viewport. this draw list will be the first rendering one. Useful to quickly draw shapes/text behind dear imgui contents.\n    IMGUI_API ImDrawList*   GetForegroundDrawList(ImGuiViewport* viewport);                     // get foreground draw list for the given viewport. this draw list will be the last rendered one. Useful to quickly draw shapes/text over dear imgui contents.\n    IMGUI_API void          AddDrawListToDrawDataEx(ImDrawData* draw_data, ImVector<ImDrawList*>* out_list, ImDrawList* draw_list);\n\n    // Init\n    IMGUI_API void          Initialize();\n    IMGUI_API void          Shutdown();    // Since 1.60 this is a _private_ function. You can call DestroyContext() to destroy the context created by CreateContext().\n\n    // NewFrame\n    IMGUI_API void          UpdateInputEvents(bool trickle_fast_inputs);\n    IMGUI_API void          UpdateHoveredWindowAndCaptureFlags();\n    IMGUI_API void          FindHoveredWindowEx(const ImVec2& pos, bool find_first_and_in_any_viewport, ImGuiWindow** out_hovered_window, ImGuiWindow** out_hovered_window_under_moving_window);\n    IMGUI_API void          StartMouseMovingWindow(ImGuiWindow* window);\n    IMGUI_API void          UpdateMouseMovingWindowNewFrame();\n    IMGUI_API void          UpdateMouseMovingWindowEndFrame();\n\n    // Generic context hooks\n    IMGUI_API ImGuiID       AddContextHook(ImGuiContext* context, const ImGuiContextHook* hook);\n    IMGUI_API void          RemoveContextHook(ImGuiContext* context, ImGuiID hook_to_remove);\n    IMGUI_API void          CallContextHooks(ImGuiContext* context, ImGuiContextHookType type);\n\n    // Viewports\n    IMGUI_API void          ScaleWindowsInViewport(ImGuiViewportP* viewport, float scale);\n    IMGUI_API void          SetWindowViewport(ImGuiWindow* window, ImGuiViewportP* viewport);\n\n    // Settings\n    IMGUI_API void                  MarkIniSettingsDirty();\n    IMGUI_API void                  MarkIniSettingsDirty(ImGuiWindow* window);\n    IMGUI_API void                  ClearIniSettings();\n    IMGUI_API void                  AddSettingsHandler(const ImGuiSettingsHandler* handler);\n    IMGUI_API void                  RemoveSettingsHandler(const char* type_name);\n    IMGUI_API ImGuiSettingsHandler* FindSettingsHandler(const char* type_name);\n\n    // Settings - Windows\n    IMGUI_API ImGuiWindowSettings*  CreateNewWindowSettings(const char* name);\n    IMGUI_API ImGuiWindowSettings*  FindWindowSettingsByID(ImGuiID id);\n    IMGUI_API ImGuiWindowSettings*  FindWindowSettingsByWindow(ImGuiWindow* window);\n    IMGUI_API void                  ClearWindowSettings(const char* name);\n\n    // Localization\n    IMGUI_API void          LocalizeRegisterEntries(const ImGuiLocEntry* entries, int count);\n    inline const char*      LocalizeGetMsg(ImGuiLocKey key) { ImGuiContext& g = *GImGui; const char* msg = g.LocalizationTable[key]; return msg ? msg : \"*Missing Text*\"; }\n\n    // Scrolling\n    IMGUI_API void          SetScrollX(ImGuiWindow* window, float scroll_x);\n    IMGUI_API void          SetScrollY(ImGuiWindow* window, float scroll_y);\n    IMGUI_API void          SetScrollFromPosX(ImGuiWindow* window, float local_x, float center_x_ratio);\n    IMGUI_API void          SetScrollFromPosY(ImGuiWindow* window, float local_y, float center_y_ratio);\n\n    // Early work-in-progress API (ScrollToItem() will become public)\n    IMGUI_API void          ScrollToItem(ImGuiScrollFlags flags = 0);\n    IMGUI_API void          ScrollToRect(ImGuiWindow* window, const ImRect& rect, ImGuiScrollFlags flags = 0);\n    IMGUI_API ImVec2        ScrollToRectEx(ImGuiWindow* window, const ImRect& rect, ImGuiScrollFlags flags = 0);\n//#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS\n    inline void             ScrollToBringRectIntoView(ImGuiWindow* window, const ImRect& rect) { ScrollToRect(window, rect, ImGuiScrollFlags_KeepVisibleEdgeY); }\n//#endif\n\n    // Basic Accessors\n    inline ImGuiItemStatusFlags GetItemStatusFlags() { ImGuiContext& g = *GImGui; return g.LastItemData.StatusFlags; }\n    inline ImGuiItemFlags   GetItemFlags()  { ImGuiContext& g = *GImGui; return g.LastItemData.ItemFlags; }\n    inline ImGuiID          GetActiveID()   { ImGuiContext& g = *GImGui; return g.ActiveId; }\n    inline ImGuiID          GetFocusID()    { ImGuiContext& g = *GImGui; return g.NavId; }\n    IMGUI_API void          SetActiveID(ImGuiID id, ImGuiWindow* window);\n    IMGUI_API void          SetFocusID(ImGuiID id, ImGuiWindow* window);\n    IMGUI_API void          ClearActiveID();\n    IMGUI_API ImGuiID       GetHoveredID();\n    IMGUI_API void          SetHoveredID(ImGuiID id);\n    IMGUI_API void          KeepAliveID(ImGuiID id);\n    IMGUI_API void          MarkItemEdited(ImGuiID id);     // Mark data associated to given item as \"edited\", used by IsItemDeactivatedAfterEdit() function.\n    IMGUI_API void          PushOverrideID(ImGuiID id);     // Push given value as-is at the top of the ID stack (whereas PushID combines old and new hashes)\n    IMGUI_API ImGuiID       GetIDWithSeed(const char* str_id_begin, const char* str_id_end, ImGuiID seed);\n    IMGUI_API ImGuiID       GetIDWithSeed(int n, ImGuiID seed);\n\n    // Basic Helpers for widget code\n    IMGUI_API void          ItemSize(const ImVec2& size, float text_baseline_y = -1.0f);\n    inline void             ItemSize(const ImRect& bb, float text_baseline_y = -1.0f) { ItemSize(bb.GetSize(), text_baseline_y); } // FIXME: This is a misleading API since we expect CursorPos to be bb.Min.\n    IMGUI_API bool          ItemAdd(const ImRect& bb, ImGuiID id, const ImRect* nav_bb = NULL, ImGuiItemFlags extra_flags = 0);\n    IMGUI_API bool          ItemHoverable(const ImRect& bb, ImGuiID id, ImGuiItemFlags item_flags);\n    IMGUI_API bool          IsWindowContentHoverable(ImGuiWindow* window, ImGuiHoveredFlags flags = 0);\n    IMGUI_API bool          IsClippedEx(const ImRect& bb, ImGuiID id);\n    IMGUI_API void          SetLastItemData(ImGuiID item_id, ImGuiItemFlags item_flags, ImGuiItemStatusFlags status_flags, const ImRect& item_rect);\n    IMGUI_API ImVec2        CalcItemSize(ImVec2 size, float default_w, float default_h);\n    IMGUI_API float         CalcWrapWidthForPos(const ImVec2& pos, float wrap_pos_x);\n    IMGUI_API void          PushMultiItemsWidths(int components, float width_full);\n    IMGUI_API void          ShrinkWidths(ImGuiShrinkWidthItem* items, int count, float width_excess);\n\n    // Parameter stacks (shared)\n    IMGUI_API const ImGuiStyleVarInfo* GetStyleVarInfo(ImGuiStyleVar idx);\n    IMGUI_API void          BeginDisabledOverrideReenable();\n    IMGUI_API void          EndDisabledOverrideReenable();\n\n    // Logging/Capture\n    IMGUI_API void          LogBegin(ImGuiLogFlags flags, int auto_open_depth);         // -> BeginCapture() when we design v2 api, for now stay under the radar by using the old name.\n    IMGUI_API void          LogToBuffer(int auto_open_depth = -1);                      // Start logging/capturing to internal buffer\n    IMGUI_API void          LogRenderedText(const ImVec2* ref_pos, const char* text, const char* text_end = NULL);\n    IMGUI_API void          LogSetNextTextDecoration(const char* prefix, const char* suffix);\n\n    // Childs\n    IMGUI_API bool          BeginChildEx(const char* name, ImGuiID id, const ImVec2& size_arg, ImGuiChildFlags child_flags, ImGuiWindowFlags window_flags);\n\n    // Popups, Modals\n    IMGUI_API bool          BeginPopupEx(ImGuiID id, ImGuiWindowFlags extra_window_flags);\n    IMGUI_API bool          BeginPopupMenuEx(ImGuiID id, const char* label, ImGuiWindowFlags extra_window_flags);\n    IMGUI_API void          OpenPopupEx(ImGuiID id, ImGuiPopupFlags popup_flags = ImGuiPopupFlags_None);\n    IMGUI_API void          ClosePopupToLevel(int remaining, bool restore_focus_to_window_under_popup);\n    IMGUI_API void          ClosePopupsOverWindow(ImGuiWindow* ref_window, bool restore_focus_to_window_under_popup);\n    IMGUI_API void          ClosePopupsExceptModals();\n    IMGUI_API bool          IsPopupOpen(ImGuiID id, ImGuiPopupFlags popup_flags);\n    IMGUI_API ImRect        GetPopupAllowedExtentRect(ImGuiWindow* window);\n    IMGUI_API ImGuiWindow*  GetTopMostPopupModal();\n    IMGUI_API ImGuiWindow*  GetTopMostAndVisiblePopupModal();\n    IMGUI_API ImGuiWindow*  FindBlockingModal(ImGuiWindow* window);\n    IMGUI_API ImVec2        FindBestWindowPosForPopup(ImGuiWindow* window);\n    IMGUI_API ImVec2        FindBestWindowPosForPopupEx(const ImVec2& ref_pos, const ImVec2& size, ImGuiDir* last_dir, const ImRect& r_outer, const ImRect& r_avoid, ImGuiPopupPositionPolicy policy);\n\n    // Tooltips\n    IMGUI_API bool          BeginTooltipEx(ImGuiTooltipFlags tooltip_flags, ImGuiWindowFlags extra_window_flags);\n    IMGUI_API bool          BeginTooltipHidden();\n\n    // Menus\n    IMGUI_API bool          BeginViewportSideBar(const char* name, ImGuiViewport* viewport, ImGuiDir dir, float size, ImGuiWindowFlags window_flags);\n    IMGUI_API bool          BeginMenuEx(const char* label, const char* icon, bool enabled = true);\n    IMGUI_API bool          MenuItemEx(const char* label, const char* icon, const char* shortcut = NULL, bool selected = false, bool enabled = true);\n\n    // Combos\n    IMGUI_API bool          BeginComboPopup(ImGuiID popup_id, const ImRect& bb, ImGuiComboFlags flags);\n    IMGUI_API bool          BeginComboPreview();\n    IMGUI_API void          EndComboPreview();\n\n    // Keyboard/Gamepad Navigation\n    IMGUI_API void          NavInitWindow(ImGuiWindow* window, bool force_reinit);\n    IMGUI_API void          NavInitRequestApplyResult();\n    IMGUI_API bool          NavMoveRequestButNoResultYet();\n    IMGUI_API void          NavMoveRequestSubmit(ImGuiDir move_dir, ImGuiDir clip_dir, ImGuiNavMoveFlags move_flags, ImGuiScrollFlags scroll_flags);\n    IMGUI_API void          NavMoveRequestForward(ImGuiDir move_dir, ImGuiDir clip_dir, ImGuiNavMoveFlags move_flags, ImGuiScrollFlags scroll_flags);\n    IMGUI_API void          NavMoveRequestResolveWithLastItem(ImGuiNavItemData* result);\n    IMGUI_API void          NavMoveRequestResolveWithPastTreeNode(ImGuiNavItemData* result, ImGuiTreeNodeStackData* tree_node_data);\n    IMGUI_API void          NavMoveRequestCancel();\n    IMGUI_API void          NavMoveRequestApplyResult();\n    IMGUI_API void          NavMoveRequestTryWrapping(ImGuiWindow* window, ImGuiNavMoveFlags move_flags);\n    IMGUI_API void          NavHighlightActivated(ImGuiID id);\n    IMGUI_API void          NavClearPreferredPosForAxis(ImGuiAxis axis);\n    IMGUI_API void          SetNavCursorVisibleAfterMove();\n    IMGUI_API void          NavUpdateCurrentWindowIsScrollPushableX();\n    IMGUI_API void          SetNavWindow(ImGuiWindow* window);\n    IMGUI_API void          SetNavID(ImGuiID id, ImGuiNavLayer nav_layer, ImGuiID focus_scope_id, const ImRect& rect_rel);\n    IMGUI_API void          SetNavFocusScope(ImGuiID focus_scope_id);\n\n    // Focus/Activation\n    // This should be part of a larger set of API: FocusItem(offset = -1), FocusItemByID(id), ActivateItem(offset = -1), ActivateItemByID(id) etc. which are\n    // much harder to design and implement than expected. I have a couple of private branches on this matter but it's not simple. For now implementing the easy ones.\n    IMGUI_API void          FocusItem();                    // Focus last item (no selection/activation).\n    IMGUI_API void          ActivateItemByID(ImGuiID id);   // Activate an item by ID (button, checkbox, tree node etc.). Activation is queued and processed on the next frame when the item is encountered again.\n\n    // Inputs\n    // FIXME: Eventually we should aim to move e.g. IsActiveIdUsingKey() into IsKeyXXX functions.\n    inline bool             IsNamedKey(ImGuiKey key)                    { return key >= ImGuiKey_NamedKey_BEGIN && key < ImGuiKey_NamedKey_END; }\n    inline bool             IsNamedKeyOrMod(ImGuiKey key)               { return (key >= ImGuiKey_NamedKey_BEGIN && key < ImGuiKey_NamedKey_END) || key == ImGuiMod_Ctrl || key == ImGuiMod_Shift || key == ImGuiMod_Alt || key == ImGuiMod_Super; }\n    inline bool             IsLegacyKey(ImGuiKey key)                   { return key >= ImGuiKey_LegacyNativeKey_BEGIN && key < ImGuiKey_LegacyNativeKey_END; }\n    inline bool             IsKeyboardKey(ImGuiKey key)                 { return key >= ImGuiKey_Keyboard_BEGIN && key < ImGuiKey_Keyboard_END; }\n    inline bool             IsGamepadKey(ImGuiKey key)                  { return key >= ImGuiKey_Gamepad_BEGIN && key < ImGuiKey_Gamepad_END; }\n    inline bool             IsMouseKey(ImGuiKey key)                    { return key >= ImGuiKey_Mouse_BEGIN && key < ImGuiKey_Mouse_END; }\n    inline bool             IsAliasKey(ImGuiKey key)                    { return key >= ImGuiKey_Aliases_BEGIN && key < ImGuiKey_Aliases_END; }\n    inline bool             IsLRModKey(ImGuiKey key)                    { return key >= ImGuiKey_LeftCtrl && key <= ImGuiKey_RightSuper; }\n    ImGuiKeyChord           FixupKeyChord(ImGuiKeyChord key_chord);\n    inline ImGuiKey         ConvertSingleModFlagToKey(ImGuiKey key)\n    {\n        if (key == ImGuiMod_Ctrl) return ImGuiKey_ReservedForModCtrl;\n        if (key == ImGuiMod_Shift) return ImGuiKey_ReservedForModShift;\n        if (key == ImGuiMod_Alt) return ImGuiKey_ReservedForModAlt;\n        if (key == ImGuiMod_Super) return ImGuiKey_ReservedForModSuper;\n        return key;\n    }\n\n    IMGUI_API ImGuiKeyData* GetKeyData(ImGuiContext* ctx, ImGuiKey key);\n    inline ImGuiKeyData*    GetKeyData(ImGuiKey key)                                    { ImGuiContext& g = *GImGui; return GetKeyData(&g, key); }\n    IMGUI_API const char*   GetKeyChordName(ImGuiKeyChord key_chord);\n    inline ImGuiKey         MouseButtonToKey(ImGuiMouseButton button)                   { IM_ASSERT(button >= 0 && button < ImGuiMouseButton_COUNT); return (ImGuiKey)(ImGuiKey_MouseLeft + button); }\n    IMGUI_API bool          IsMouseDragPastThreshold(ImGuiMouseButton button, float lock_threshold = -1.0f);\n    IMGUI_API ImVec2        GetKeyMagnitude2d(ImGuiKey key_left, ImGuiKey key_right, ImGuiKey key_up, ImGuiKey key_down);\n    IMGUI_API float         GetNavTweakPressedAmount(ImGuiAxis axis);\n    IMGUI_API int           CalcTypematicRepeatAmount(float t0, float t1, float repeat_delay, float repeat_rate);\n    IMGUI_API void          GetTypematicRepeatRate(ImGuiInputFlags flags, float* repeat_delay, float* repeat_rate);\n    IMGUI_API void          TeleportMousePos(const ImVec2& pos);\n    IMGUI_API void          SetActiveIdUsingAllKeyboardKeys();\n    inline bool             IsActiveIdUsingNavDir(ImGuiDir dir)                         { ImGuiContext& g = *GImGui; return (g.ActiveIdUsingNavDirMask & (1 << dir)) != 0; }\n\n    // [EXPERIMENTAL] Low-Level: Key/Input Ownership\n    // - The idea is that instead of \"eating\" a given input, we can link to an owner id.\n    // - Ownership is most often claimed as a result of reacting to a press/down event (but occasionally may be claimed ahead).\n    // - Input queries can then read input by specifying ImGuiKeyOwner_Any (== 0), ImGuiKeyOwner_NoOwner (== -1) or a custom ID.\n    // - Legacy input queries (without specifying an owner or _Any or _None) are equivalent to using ImGuiKeyOwner_Any (== 0).\n    // - Input ownership is automatically released on the frame after a key is released. Therefore:\n    //   - for ownership registration happening as a result of a down/press event, the SetKeyOwner() call may be done once (common case).\n    //   - for ownership registration happening ahead of a down/press event, the SetKeyOwner() call needs to be made every frame (happens if e.g. claiming ownership on hover).\n    // - SetItemKeyOwner() is a shortcut for common simple case. A custom widget will probably want to call SetKeyOwner() multiple times directly based on its interaction state.\n    // - This is marked experimental because not all widgets are fully honoring the Set/Test idioms. We will need to move forward step by step.\n    //   Please open a GitHub Issue to submit your usage scenario or if there's a use case you need solved.\n    IMGUI_API ImGuiID       GetKeyOwner(ImGuiKey key);\n    IMGUI_API void          SetKeyOwner(ImGuiKey key, ImGuiID owner_id, ImGuiInputFlags flags = 0);\n    IMGUI_API void          SetKeyOwnersForKeyChord(ImGuiKeyChord key, ImGuiID owner_id, ImGuiInputFlags flags = 0);\n    IMGUI_API void          SetItemKeyOwner(ImGuiKey key, ImGuiInputFlags flags);       // Set key owner to last item if it is hovered or active. Equivalent to 'if (IsItemHovered() || IsItemActive()) { SetKeyOwner(key, GetItemID());'.\n    IMGUI_API bool          TestKeyOwner(ImGuiKey key, ImGuiID owner_id);               // Test that key is either not owned, either owned by 'owner_id'\n    inline ImGuiKeyOwnerData* GetKeyOwnerData(ImGuiContext* ctx, ImGuiKey key)          { if (key & ImGuiMod_Mask_) key = ConvertSingleModFlagToKey(key); IM_ASSERT(IsNamedKey(key)); return &ctx->KeysOwnerData[key - ImGuiKey_NamedKey_BEGIN]; }\n\n    // [EXPERIMENTAL] High-Level: Input Access functions w/ support for Key/Input Ownership\n    // - Important: legacy IsKeyPressed(ImGuiKey, bool repeat=true) _DEFAULTS_ to repeat, new IsKeyPressed() requires _EXPLICIT_ ImGuiInputFlags_Repeat flag.\n    // - Expected to be later promoted to public API, the prototypes are designed to replace existing ones (since owner_id can default to Any == 0)\n    // - Specifying a value for 'ImGuiID owner' will test that EITHER the key is NOT owned (UNLESS locked), EITHER the key is owned by 'owner'.\n    //   Legacy functions use ImGuiKeyOwner_Any meaning that they typically ignore ownership, unless a call to SetKeyOwner() explicitly used ImGuiInputFlags_LockThisFrame or ImGuiInputFlags_LockUntilRelease.\n    // - Binding generators may want to ignore those for now, or suffix them with Ex() until we decide if this gets moved into public API.\n    IMGUI_API bool          IsKeyDown(ImGuiKey key, ImGuiID owner_id);\n    IMGUI_API bool          IsKeyPressed(ImGuiKey key, ImGuiInputFlags flags, ImGuiID owner_id = 0);    // Important: when transitioning from old to new IsKeyPressed(): old API has \"bool repeat = true\", so would default to repeat. New API requiress explicit ImGuiInputFlags_Repeat.\n    IMGUI_API bool          IsKeyReleased(ImGuiKey key, ImGuiID owner_id);\n    IMGUI_API bool          IsKeyChordPressed(ImGuiKeyChord key_chord, ImGuiInputFlags flags, ImGuiID owner_id = 0);\n    IMGUI_API bool          IsMouseDown(ImGuiMouseButton button, ImGuiID owner_id);\n    IMGUI_API bool          IsMouseClicked(ImGuiMouseButton button, ImGuiInputFlags flags, ImGuiID owner_id = 0);\n    IMGUI_API bool          IsMouseReleased(ImGuiMouseButton button, ImGuiID owner_id);\n    IMGUI_API bool          IsMouseDoubleClicked(ImGuiMouseButton button, ImGuiID owner_id);\n\n    // Shortcut Testing & Routing\n    // - Set Shortcut() and SetNextItemShortcut() in imgui.h\n    // - When a policy (except for ImGuiInputFlags_RouteAlways *) is set, Shortcut() will register itself with SetShortcutRouting(),\n    //   allowing the system to decide where to route the input among other route-aware calls.\n    //   (* using ImGuiInputFlags_RouteAlways is roughly equivalent to calling IsKeyChordPressed(key) and bypassing route registration and check)\n    // - When using one of the routing option:\n    //   - The default route is ImGuiInputFlags_RouteFocused (accept inputs if window is in focus stack. Deep-most focused window takes inputs. ActiveId takes inputs over deep-most focused window.)\n    //   - Routes are requested given a chord (key + modifiers) and a routing policy.\n    //   - Routes are resolved during NewFrame(): if keyboard modifiers are matching current ones: SetKeyOwner() is called + route is granted for the frame.\n    //   - Each route may be granted to a single owner. When multiple requests are made we have policies to select the winning route (e.g. deep most window).\n    //   - Multiple read sites may use the same owner id can all access the granted route.\n    //   - When owner_id is 0 we use the current Focus Scope ID as a owner ID in order to identify our location.\n    // - You can chain two unrelated windows in the focus stack using SetWindowParentWindowForFocusRoute()\n    //   e.g. if you have a tool window associated to a document, and you want document shortcuts to run when the tool is focused.\n    IMGUI_API bool          Shortcut(ImGuiKeyChord key_chord, ImGuiInputFlags flags, ImGuiID owner_id);\n    IMGUI_API bool          SetShortcutRouting(ImGuiKeyChord key_chord, ImGuiInputFlags flags, ImGuiID owner_id); // owner_id needs to be explicit and cannot be 0\n    IMGUI_API bool          TestShortcutRouting(ImGuiKeyChord key_chord, ImGuiID owner_id);\n    IMGUI_API ImGuiKeyRoutingData* GetShortcutRoutingData(ImGuiKeyChord key_chord);\n\n    // [EXPERIMENTAL] Focus Scope\n    // This is generally used to identify a unique input location (for e.g. a selection set)\n    // There is one per window (automatically set in Begin), but:\n    // - Selection patterns generally need to react (e.g. clear a selection) when landing on one item of the set.\n    //   So in order to identify a set multiple lists in same window may each need a focus scope.\n    //   If you imagine an hypothetical BeginSelectionGroup()/EndSelectionGroup() api, it would likely call PushFocusScope()/EndFocusScope()\n    // - Shortcut routing also use focus scope as a default location identifier if an owner is not provided.\n    // We don't use the ID Stack for this as it is common to want them separate.\n    IMGUI_API void          PushFocusScope(ImGuiID id);\n    IMGUI_API void          PopFocusScope();\n    inline ImGuiID          GetCurrentFocusScope() { ImGuiContext& g = *GImGui; return g.CurrentFocusScopeId; }   // Focus scope we are outputting into, set by PushFocusScope()\n\n    // Drag and Drop\n    IMGUI_API bool          IsDragDropActive();\n    IMGUI_API bool          BeginDragDropTargetCustom(const ImRect& bb, ImGuiID id);\n    IMGUI_API void          ClearDragDrop();\n    IMGUI_API bool          IsDragDropPayloadBeingAccepted();\n    IMGUI_API void          RenderDragDropTargetRect(const ImRect& bb, const ImRect& item_clip_rect);\n\n    // Typing-Select API\n    // (provide Windows Explorer style \"select items by typing partial name\" + \"cycle through items by typing same letter\" feature)\n    // (this is currently not documented nor used by main library, but should work. See \"widgets_typingselect\" in imgui_test_suite for usage code. Please let us know if you use this!)\n    IMGUI_API ImGuiTypingSelectRequest* GetTypingSelectRequest(ImGuiTypingSelectFlags flags = ImGuiTypingSelectFlags_None);\n    IMGUI_API int           TypingSelectFindMatch(ImGuiTypingSelectRequest* req, int items_count, const char* (*get_item_name_func)(void*, int), void* user_data, int nav_item_idx);\n    IMGUI_API int           TypingSelectFindNextSingleCharMatch(ImGuiTypingSelectRequest* req, int items_count, const char* (*get_item_name_func)(void*, int), void* user_data, int nav_item_idx);\n    IMGUI_API int           TypingSelectFindBestLeadingMatch(ImGuiTypingSelectRequest* req, int items_count, const char* (*get_item_name_func)(void*, int), void* user_data);\n\n    // Box-Select API\n    IMGUI_API bool          BeginBoxSelect(const ImRect& scope_rect, ImGuiWindow* window, ImGuiID box_select_id, ImGuiMultiSelectFlags ms_flags);\n    IMGUI_API void          EndBoxSelect(const ImRect& scope_rect, ImGuiMultiSelectFlags ms_flags);\n\n    // Multi-Select API\n    IMGUI_API void          MultiSelectItemHeader(ImGuiID id, bool* p_selected, ImGuiButtonFlags* p_button_flags);\n    IMGUI_API void          MultiSelectItemFooter(ImGuiID id, bool* p_selected, bool* p_pressed);\n    IMGUI_API void          MultiSelectAddSetAll(ImGuiMultiSelectTempData* ms, bool selected);\n    IMGUI_API void          MultiSelectAddSetRange(ImGuiMultiSelectTempData* ms, bool selected, int range_dir, ImGuiSelectionUserData first_item, ImGuiSelectionUserData last_item);\n    inline ImGuiBoxSelectState*     GetBoxSelectState(ImGuiID id)   { ImGuiContext& g = *GImGui; return (id != 0 && g.BoxSelectState.ID == id && g.BoxSelectState.IsActive) ? &g.BoxSelectState : NULL; }\n    inline ImGuiMultiSelectState*   GetMultiSelectState(ImGuiID id) { ImGuiContext& g = *GImGui; return g.MultiSelectStorage.GetByKey(id); }\n\n    // Internal Columns API (this is not exposed because we will encourage transitioning to the Tables API)\n    IMGUI_API void          SetWindowClipRectBeforeSetChannel(ImGuiWindow* window, const ImRect& clip_rect);\n    IMGUI_API void          BeginColumns(const char* str_id, int count, ImGuiOldColumnFlags flags = 0); // setup number of columns. use an identifier to distinguish multiple column sets. close with EndColumns().\n    IMGUI_API void          EndColumns();                                                               // close columns\n    IMGUI_API void          PushColumnClipRect(int column_index);\n    IMGUI_API void          PushColumnsBackground();\n    IMGUI_API void          PopColumnsBackground();\n    IMGUI_API ImGuiID       GetColumnsID(const char* str_id, int count);\n    IMGUI_API ImGuiOldColumns* FindOrCreateColumns(ImGuiWindow* window, ImGuiID id);\n    IMGUI_API float         GetColumnOffsetFromNorm(const ImGuiOldColumns* columns, float offset_norm);\n    IMGUI_API float         GetColumnNormFromOffset(const ImGuiOldColumns* columns, float offset);\n\n    // Tables: Candidates for public API\n    IMGUI_API void          TableOpenContextMenu(int column_n = -1);\n    IMGUI_API void          TableSetColumnWidth(int column_n, float width);\n    IMGUI_API void          TableSetColumnSortDirection(int column_n, ImGuiSortDirection sort_direction, bool append_to_sort_specs);\n    IMGUI_API int           TableGetHoveredRow();       // Retrieve *PREVIOUS FRAME* hovered row. This difference with TableGetHoveredColumn() is the reason why this is not public yet.\n    IMGUI_API float         TableGetHeaderRowHeight();\n    IMGUI_API float         TableGetHeaderAngledMaxLabelWidth();\n    IMGUI_API void          TablePushBackgroundChannel();\n    IMGUI_API void          TablePopBackgroundChannel();\n    IMGUI_API void          TableAngledHeadersRowEx(ImGuiID row_id, float angle, float max_label_width, const ImGuiTableHeaderData* data, int data_count);\n\n    // Tables: Internals\n    inline    ImGuiTable*   GetCurrentTable() { ImGuiContext& g = *GImGui; return g.CurrentTable; }\n    IMGUI_API ImGuiTable*   TableFindByID(ImGuiID id);\n    IMGUI_API bool          BeginTableEx(const char* name, ImGuiID id, int columns_count, ImGuiTableFlags flags = 0, const ImVec2& outer_size = ImVec2(0, 0), float inner_width = 0.0f);\n    IMGUI_API void          TableBeginInitMemory(ImGuiTable* table, int columns_count);\n    IMGUI_API void          TableBeginApplyRequests(ImGuiTable* table);\n    IMGUI_API void          TableSetupDrawChannels(ImGuiTable* table);\n    IMGUI_API void          TableUpdateLayout(ImGuiTable* table);\n    IMGUI_API void          TableUpdateBorders(ImGuiTable* table);\n    IMGUI_API void          TableUpdateColumnsWeightFromWidth(ImGuiTable* table);\n    IMGUI_API void          TableDrawBorders(ImGuiTable* table);\n    IMGUI_API void          TableDrawDefaultContextMenu(ImGuiTable* table, ImGuiTableFlags flags_for_section_to_display);\n    IMGUI_API bool          TableBeginContextMenuPopup(ImGuiTable* table);\n    IMGUI_API void          TableMergeDrawChannels(ImGuiTable* table);\n    inline ImGuiTableInstanceData*  TableGetInstanceData(ImGuiTable* table, int instance_no) { if (instance_no == 0) return &table->InstanceDataFirst; return &table->InstanceDataExtra[instance_no - 1]; }\n    inline ImGuiID                  TableGetInstanceID(ImGuiTable* table, int instance_no)   { return TableGetInstanceData(table, instance_no)->TableInstanceID; }\n    IMGUI_API void          TableSortSpecsSanitize(ImGuiTable* table);\n    IMGUI_API void          TableSortSpecsBuild(ImGuiTable* table);\n    IMGUI_API ImGuiSortDirection TableGetColumnNextSortDirection(ImGuiTableColumn* column);\n    IMGUI_API void          TableFixColumnSortDirection(ImGuiTable* table, ImGuiTableColumn* column);\n    IMGUI_API float         TableGetColumnWidthAuto(ImGuiTable* table, ImGuiTableColumn* column);\n    IMGUI_API void          TableBeginRow(ImGuiTable* table);\n    IMGUI_API void          TableEndRow(ImGuiTable* table);\n    IMGUI_API void          TableBeginCell(ImGuiTable* table, int column_n);\n    IMGUI_API void          TableEndCell(ImGuiTable* table);\n    IMGUI_API ImRect        TableGetCellBgRect(const ImGuiTable* table, int column_n);\n    IMGUI_API const char*   TableGetColumnName(const ImGuiTable* table, int column_n);\n    IMGUI_API ImGuiID       TableGetColumnResizeID(ImGuiTable* table, int column_n, int instance_no = 0);\n    IMGUI_API float         TableCalcMaxColumnWidth(const ImGuiTable* table, int column_n);\n    IMGUI_API void          TableSetColumnWidthAutoSingle(ImGuiTable* table, int column_n);\n    IMGUI_API void          TableSetColumnWidthAutoAll(ImGuiTable* table);\n    IMGUI_API void          TableRemove(ImGuiTable* table);\n    IMGUI_API void          TableGcCompactTransientBuffers(ImGuiTable* table);\n    IMGUI_API void          TableGcCompactTransientBuffers(ImGuiTableTempData* table);\n    IMGUI_API void          TableGcCompactSettings();\n\n    // Tables: Settings\n    IMGUI_API void                  TableLoadSettings(ImGuiTable* table);\n    IMGUI_API void                  TableSaveSettings(ImGuiTable* table);\n    IMGUI_API void                  TableResetSettings(ImGuiTable* table);\n    IMGUI_API ImGuiTableSettings*   TableGetBoundSettings(ImGuiTable* table);\n    IMGUI_API void                  TableSettingsAddSettingsHandler();\n    IMGUI_API ImGuiTableSettings*   TableSettingsCreate(ImGuiID id, int columns_count);\n    IMGUI_API ImGuiTableSettings*   TableSettingsFindByID(ImGuiID id);\n\n    // Tab Bars\n    inline    ImGuiTabBar*  GetCurrentTabBar() { ImGuiContext& g = *GImGui; return g.CurrentTabBar; }\n    IMGUI_API bool          BeginTabBarEx(ImGuiTabBar* tab_bar, const ImRect& bb, ImGuiTabBarFlags flags);\n    IMGUI_API ImGuiTabItem* TabBarFindTabByID(ImGuiTabBar* tab_bar, ImGuiID tab_id);\n    IMGUI_API ImGuiTabItem* TabBarFindTabByOrder(ImGuiTabBar* tab_bar, int order);\n    IMGUI_API ImGuiTabItem* TabBarGetCurrentTab(ImGuiTabBar* tab_bar);\n    inline int              TabBarGetTabOrder(ImGuiTabBar* tab_bar, ImGuiTabItem* tab) { return tab_bar->Tabs.index_from_ptr(tab); }\n    IMGUI_API const char*   TabBarGetTabName(ImGuiTabBar* tab_bar, ImGuiTabItem* tab);\n    IMGUI_API void          TabBarRemoveTab(ImGuiTabBar* tab_bar, ImGuiID tab_id);\n    IMGUI_API void          TabBarCloseTab(ImGuiTabBar* tab_bar, ImGuiTabItem* tab);\n    IMGUI_API void          TabBarQueueFocus(ImGuiTabBar* tab_bar, ImGuiTabItem* tab);\n    IMGUI_API void          TabBarQueueFocus(ImGuiTabBar* tab_bar, const char* tab_name);\n    IMGUI_API void          TabBarQueueReorder(ImGuiTabBar* tab_bar, ImGuiTabItem* tab, int offset);\n    IMGUI_API void          TabBarQueueReorderFromMousePos(ImGuiTabBar* tab_bar, ImGuiTabItem* tab, ImVec2 mouse_pos);\n    IMGUI_API bool          TabBarProcessReorder(ImGuiTabBar* tab_bar);\n    IMGUI_API bool          TabItemEx(ImGuiTabBar* tab_bar, const char* label, bool* p_open, ImGuiTabItemFlags flags, ImGuiWindow* docked_window);\n    IMGUI_API void          TabItemSpacing(const char* str_id, ImGuiTabItemFlags flags, float width);\n    IMGUI_API ImVec2        TabItemCalcSize(const char* label, bool has_close_button_or_unsaved_marker);\n    IMGUI_API ImVec2        TabItemCalcSize(ImGuiWindow* window);\n    IMGUI_API void          TabItemBackground(ImDrawList* draw_list, const ImRect& bb, ImGuiTabItemFlags flags, ImU32 col);\n    IMGUI_API void          TabItemLabelAndCloseButton(ImDrawList* draw_list, const ImRect& bb, ImGuiTabItemFlags flags, ImVec2 frame_padding, const char* label, ImGuiID tab_id, ImGuiID close_button_id, bool is_contents_visible, bool* out_just_closed, bool* out_text_clipped);\n\n    // Render helpers\n    // AVOID USING OUTSIDE OF IMGUI.CPP! NOT FOR PUBLIC CONSUMPTION. THOSE FUNCTIONS ARE A MESS. THEIR SIGNATURE AND BEHAVIOR WILL CHANGE, THEY NEED TO BE REFACTORED INTO SOMETHING DECENT.\n    // NB: All position are in absolute pixels coordinates (we are never using window coordinates internally)\n    IMGUI_API void          RenderText(ImVec2 pos, const char* text, const char* text_end = NULL, bool hide_text_after_hash = true);\n    IMGUI_API void          RenderTextWrapped(ImVec2 pos, const char* text, const char* text_end, float wrap_width);\n    IMGUI_API void          RenderTextClipped(const ImVec2& pos_min, const ImVec2& pos_max, const char* text, const char* text_end, const ImVec2* text_size_if_known, const ImVec2& align = ImVec2(0, 0), const ImRect* clip_rect = NULL);\n    IMGUI_API void          RenderTextClippedEx(ImDrawList* draw_list, const ImVec2& pos_min, const ImVec2& pos_max, const char* text, const char* text_end, const ImVec2* text_size_if_known, const ImVec2& align = ImVec2(0, 0), const ImRect* clip_rect = NULL);\n    IMGUI_API void          RenderTextEllipsis(ImDrawList* draw_list, const ImVec2& pos_min, const ImVec2& pos_max, float clip_max_x, float ellipsis_max_x, const char* text, const char* text_end, const ImVec2* text_size_if_known);\n    IMGUI_API void          RenderFrame(ImVec2 p_min, ImVec2 p_max, ImU32 fill_col, bool borders = true, float rounding = 0.0f);\n    IMGUI_API void          RenderFrameBorder(ImVec2 p_min, ImVec2 p_max, float rounding = 0.0f);\n    IMGUI_API void          RenderColorRectWithAlphaCheckerboard(ImDrawList* draw_list, ImVec2 p_min, ImVec2 p_max, ImU32 fill_col, float grid_step, ImVec2 grid_off, float rounding = 0.0f, ImDrawFlags flags = 0);\n    IMGUI_API void          RenderNavCursor(const ImRect& bb, ImGuiID id, ImGuiNavRenderCursorFlags flags = ImGuiNavRenderCursorFlags_None); // Navigation highlight\n#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS\n    inline    void          RenderNavHighlight(const ImRect& bb, ImGuiID id, ImGuiNavRenderCursorFlags flags = ImGuiNavRenderCursorFlags_None) { RenderNavCursor(bb, id, flags); } // Renamed in 1.91.4\n#endif\n    IMGUI_API const char*   FindRenderedTextEnd(const char* text, const char* text_end = NULL); // Find the optional ## from which we stop displaying text.\n    IMGUI_API void          RenderMouseCursor(ImVec2 pos, float scale, ImGuiMouseCursor mouse_cursor, ImU32 col_fill, ImU32 col_border, ImU32 col_shadow);\n\n    // Render helpers (those functions don't access any ImGui state!)\n    IMGUI_API void          RenderArrow(ImDrawList* draw_list, ImVec2 pos, ImU32 col, ImGuiDir dir, float scale = 1.0f);\n    IMGUI_API void          RenderBullet(ImDrawList* draw_list, ImVec2 pos, ImU32 col);\n    IMGUI_API void          RenderCheckMark(ImDrawList* draw_list, ImVec2 pos, ImU32 col, float sz);\n    IMGUI_API void          RenderArrowPointingAt(ImDrawList* draw_list, ImVec2 pos, ImVec2 half_sz, ImGuiDir direction, ImU32 col);\n    IMGUI_API void          RenderRectFilledRangeH(ImDrawList* draw_list, const ImRect& rect, ImU32 col, float x_start_norm, float x_end_norm, float rounding);\n    IMGUI_API void          RenderRectFilledWithHole(ImDrawList* draw_list, const ImRect& outer, const ImRect& inner, ImU32 col, float rounding);\n\n    // Widgets\n    IMGUI_API void          TextEx(const char* text, const char* text_end = NULL, ImGuiTextFlags flags = 0);\n    IMGUI_API bool          ButtonEx(const char* label, const ImVec2& size_arg = ImVec2(0, 0), ImGuiButtonFlags flags = 0);\n    IMGUI_API bool          ArrowButtonEx(const char* str_id, ImGuiDir dir, ImVec2 size_arg, ImGuiButtonFlags flags = 0);\n    IMGUI_API bool          ImageButtonEx(ImGuiID id, ImTextureID user_texture_id, const ImVec2& image_size, const ImVec2& uv0, const ImVec2& uv1, const ImVec4& bg_col, const ImVec4& tint_col, ImGuiButtonFlags flags = 0);\n    IMGUI_API void          SeparatorEx(ImGuiSeparatorFlags flags, float thickness = 1.0f);\n    IMGUI_API void          SeparatorTextEx(ImGuiID id, const char* label, const char* label_end, float extra_width);\n    IMGUI_API bool          CheckboxFlags(const char* label, ImS64* flags, ImS64 flags_value);\n    IMGUI_API bool          CheckboxFlags(const char* label, ImU64* flags, ImU64 flags_value);\n\n    // Widgets: Window Decorations\n    IMGUI_API bool          CloseButton(ImGuiID id, const ImVec2& pos);\n    IMGUI_API bool          CollapseButton(ImGuiID id, const ImVec2& pos);\n    IMGUI_API void          Scrollbar(ImGuiAxis axis);\n    IMGUI_API bool          ScrollbarEx(const ImRect& bb, ImGuiID id, ImGuiAxis axis, ImS64* p_scroll_v, ImS64 avail_v, ImS64 contents_v, ImDrawFlags draw_rounding_flags = 0);\n    IMGUI_API ImRect        GetWindowScrollbarRect(ImGuiWindow* window, ImGuiAxis axis);\n    IMGUI_API ImGuiID       GetWindowScrollbarID(ImGuiWindow* window, ImGuiAxis axis);\n    IMGUI_API ImGuiID       GetWindowResizeCornerID(ImGuiWindow* window, int n); // 0..3: corners\n    IMGUI_API ImGuiID       GetWindowResizeBorderID(ImGuiWindow* window, ImGuiDir dir);\n\n    // Widgets low-level behaviors\n    IMGUI_API bool          ButtonBehavior(const ImRect& bb, ImGuiID id, bool* out_hovered, bool* out_held, ImGuiButtonFlags flags = 0);\n    IMGUI_API bool          DragBehavior(ImGuiID id, ImGuiDataType data_type, void* p_v, float v_speed, const void* p_min, const void* p_max, const char* format, ImGuiSliderFlags flags);\n    IMGUI_API bool          SliderBehavior(const ImRect& bb, ImGuiID id, ImGuiDataType data_type, void* p_v, const void* p_min, const void* p_max, const char* format, ImGuiSliderFlags flags, ImRect* out_grab_bb);\n    IMGUI_API bool          SplitterBehavior(const ImRect& bb, ImGuiID id, ImGuiAxis axis, float* size1, float* size2, float min_size1, float min_size2, float hover_extend = 0.0f, float hover_visibility_delay = 0.0f, ImU32 bg_col = 0);\n\n    // Widgets: Tree Nodes\n    IMGUI_API bool          TreeNodeBehavior(ImGuiID id, ImGuiTreeNodeFlags flags, const char* label, const char* label_end = NULL);\n    IMGUI_API void          TreePushOverrideID(ImGuiID id);\n    IMGUI_API bool          TreeNodeGetOpen(ImGuiID storage_id);\n    IMGUI_API void          TreeNodeSetOpen(ImGuiID storage_id, bool open);\n    IMGUI_API bool          TreeNodeUpdateNextOpen(ImGuiID storage_id, ImGuiTreeNodeFlags flags);   // Return open state. Consume previous SetNextItemOpen() data, if any. May return true when logging.\n\n    // Template functions are instantiated in imgui_widgets.cpp for a finite number of types.\n    // To use them externally (for custom widget) you may need an \"extern template\" statement in your code in order to link to existing instances and silence Clang warnings (see #2036).\n    // e.g. \" extern template IMGUI_API float RoundScalarWithFormatT<float, float>(const char* format, ImGuiDataType data_type, float v); \"\n    template<typename T, typename SIGNED_T, typename FLOAT_T>   IMGUI_API float ScaleRatioFromValueT(ImGuiDataType data_type, T v, T v_min, T v_max, bool is_logarithmic, float logarithmic_zero_epsilon, float zero_deadzone_size);\n    template<typename T, typename SIGNED_T, typename FLOAT_T>   IMGUI_API T     ScaleValueFromRatioT(ImGuiDataType data_type, float t, T v_min, T v_max, bool is_logarithmic, float logarithmic_zero_epsilon, float zero_deadzone_size);\n    template<typename T, typename SIGNED_T, typename FLOAT_T>   IMGUI_API bool  DragBehaviorT(ImGuiDataType data_type, T* v, float v_speed, T v_min, T v_max, const char* format, ImGuiSliderFlags flags);\n    template<typename T, typename SIGNED_T, typename FLOAT_T>   IMGUI_API bool  SliderBehaviorT(const ImRect& bb, ImGuiID id, ImGuiDataType data_type, T* v, T v_min, T v_max, const char* format, ImGuiSliderFlags flags, ImRect* out_grab_bb);\n    template<typename T>                                        IMGUI_API T     RoundScalarWithFormatT(const char* format, ImGuiDataType data_type, T v);\n    template<typename T>                                        IMGUI_API bool  CheckboxFlagsT(const char* label, T* flags, T flags_value);\n\n    // Data type helpers\n    IMGUI_API const ImGuiDataTypeInfo*  DataTypeGetInfo(ImGuiDataType data_type);\n    IMGUI_API int           DataTypeFormatString(char* buf, int buf_size, ImGuiDataType data_type, const void* p_data, const char* format);\n    IMGUI_API void          DataTypeApplyOp(ImGuiDataType data_type, int op, void* output, const void* arg_1, const void* arg_2);\n    IMGUI_API bool          DataTypeApplyFromText(const char* buf, ImGuiDataType data_type, void* p_data, const char* format, void* p_data_when_empty = NULL);\n    IMGUI_API int           DataTypeCompare(ImGuiDataType data_type, const void* arg_1, const void* arg_2);\n    IMGUI_API bool          DataTypeClamp(ImGuiDataType data_type, void* p_data, const void* p_min, const void* p_max);\n    IMGUI_API bool          DataTypeIsZero(ImGuiDataType data_type, const void* p_data);\n\n    // InputText\n    IMGUI_API bool          InputTextEx(const char* label, const char* hint, char* buf, int buf_size, const ImVec2& size_arg, ImGuiInputTextFlags flags, ImGuiInputTextCallback callback = NULL, void* user_data = NULL);\n    IMGUI_API void          InputTextDeactivateHook(ImGuiID id);\n    IMGUI_API bool          TempInputText(const ImRect& bb, ImGuiID id, const char* label, char* buf, int buf_size, ImGuiInputTextFlags flags);\n    IMGUI_API bool          TempInputScalar(const ImRect& bb, ImGuiID id, const char* label, ImGuiDataType data_type, void* p_data, const char* format, const void* p_clamp_min = NULL, const void* p_clamp_max = NULL);\n    inline bool             TempInputIsActive(ImGuiID id)       { ImGuiContext& g = *GImGui; return (g.ActiveId == id && g.TempInputId == id); }\n    inline ImGuiInputTextState* GetInputTextState(ImGuiID id)   { ImGuiContext& g = *GImGui; return (id != 0 && g.InputTextState.ID == id) ? &g.InputTextState : NULL; } // Get input text state if active\n    IMGUI_API void          SetNextItemRefVal(ImGuiDataType data_type, void* p_data);\n    inline bool             IsItemActiveAsInputText() { ImGuiContext& g = *GImGui; return g.ActiveId != 0 && g.ActiveId == g.LastItemData.ID && g.InputTextState.ID == g.LastItemData.ID; } // This may be useful to apply workaround that a based on distinguish whenever an item is active as a text input field.\n\n    // Color\n    IMGUI_API void          ColorTooltip(const char* text, const float* col, ImGuiColorEditFlags flags);\n    IMGUI_API void          ColorEditOptionsPopup(const float* col, ImGuiColorEditFlags flags);\n    IMGUI_API void          ColorPickerOptionsPopup(const float* ref_col, ImGuiColorEditFlags flags);\n\n    // Plot\n    IMGUI_API int           PlotEx(ImGuiPlotType plot_type, const char* label, float (*values_getter)(void* data, int idx), void* data, int values_count, int values_offset, const char* overlay_text, float scale_min, float scale_max, const ImVec2& size_arg);\n\n    // Shade functions (write over already created vertices)\n    IMGUI_API void          ShadeVertsLinearColorGradientKeepAlpha(ImDrawList* draw_list, int vert_start_idx, int vert_end_idx, ImVec2 gradient_p0, ImVec2 gradient_p1, ImU32 col0, ImU32 col1);\n    IMGUI_API void          ShadeVertsLinearUV(ImDrawList* draw_list, int vert_start_idx, int vert_end_idx, const ImVec2& a, const ImVec2& b, const ImVec2& uv_a, const ImVec2& uv_b, bool clamp);\n    IMGUI_API void          ShadeVertsTransformPos(ImDrawList* draw_list, int vert_start_idx, int vert_end_idx, const ImVec2& pivot_in, float cos_a, float sin_a, const ImVec2& pivot_out);\n\n    // Garbage collection\n    IMGUI_API void          GcCompactTransientMiscBuffers();\n    IMGUI_API void          GcCompactTransientWindowBuffers(ImGuiWindow* window);\n    IMGUI_API void          GcAwakeTransientWindowBuffers(ImGuiWindow* window);\n\n    // Error handling, State Recovery\n    IMGUI_API bool          ErrorLog(const char* msg);\n    IMGUI_API void          ErrorRecoveryStoreState(ImGuiErrorRecoveryState* state_out);\n    IMGUI_API void          ErrorRecoveryTryToRecoverState(const ImGuiErrorRecoveryState* state_in);\n    IMGUI_API void          ErrorRecoveryTryToRecoverWindowState(const ImGuiErrorRecoveryState* state_in);\n    IMGUI_API void          ErrorCheckUsingSetCursorPosToExtendParentBoundaries();\n    IMGUI_API void          ErrorCheckEndFrameFinalizeErrorTooltip();\n    IMGUI_API bool          BeginErrorTooltip();\n    IMGUI_API void          EndErrorTooltip();\n\n    // Debug Tools\n    IMGUI_API void          DebugAllocHook(ImGuiDebugAllocInfo* info, int frame_count, void* ptr, size_t size); // size >= 0 : alloc, size = -1 : free\n    IMGUI_API void          DebugDrawCursorPos(ImU32 col = IM_COL32(255, 0, 0, 255));\n    IMGUI_API void          DebugDrawLineExtents(ImU32 col = IM_COL32(255, 0, 0, 255));\n    IMGUI_API void          DebugDrawItemRect(ImU32 col = IM_COL32(255, 0, 0, 255));\n    IMGUI_API void          DebugTextUnformattedWithLocateItem(const char* line_begin, const char* line_end);\n    IMGUI_API void          DebugLocateItem(ImGuiID target_id);                     // Call sparingly: only 1 at the same time!\n    IMGUI_API void          DebugLocateItemOnHover(ImGuiID target_id);              // Only call on reaction to a mouse Hover: because only 1 at the same time!\n    IMGUI_API void          DebugLocateItemResolveWithLastItem();\n    IMGUI_API void          DebugBreakClearData();\n    IMGUI_API bool          DebugBreakButton(const char* label, const char* description_of_location);\n    IMGUI_API void          DebugBreakButtonTooltip(bool keyboard_only, const char* description_of_location);\n    IMGUI_API void          ShowFontAtlas(ImFontAtlas* atlas);\n    IMGUI_API void          DebugHookIdInfo(ImGuiID id, ImGuiDataType data_type, const void* data_id, const void* data_id_end);\n    IMGUI_API void          DebugNodeColumns(ImGuiOldColumns* columns);\n    IMGUI_API void          DebugNodeDrawList(ImGuiWindow* window, ImGuiViewportP* viewport, const ImDrawList* draw_list, const char* label);\n    IMGUI_API void          DebugNodeDrawCmdShowMeshAndBoundingBox(ImDrawList* out_draw_list, const ImDrawList* draw_list, const ImDrawCmd* draw_cmd, bool show_mesh, bool show_aabb);\n    IMGUI_API void          DebugNodeFont(ImFont* font);\n    IMGUI_API void          DebugNodeFontGlyph(ImFont* font, const ImFontGlyph* glyph);\n    IMGUI_API void          DebugNodeStorage(ImGuiStorage* storage, const char* label);\n    IMGUI_API void          DebugNodeTabBar(ImGuiTabBar* tab_bar, const char* label);\n    IMGUI_API void          DebugNodeTable(ImGuiTable* table);\n    IMGUI_API void          DebugNodeTableSettings(ImGuiTableSettings* settings);\n    IMGUI_API void          DebugNodeInputTextState(ImGuiInputTextState* state);\n    IMGUI_API void          DebugNodeTypingSelectState(ImGuiTypingSelectState* state);\n    IMGUI_API void          DebugNodeMultiSelectState(ImGuiMultiSelectState* state);\n    IMGUI_API void          DebugNodeWindow(ImGuiWindow* window, const char* label);\n    IMGUI_API void          DebugNodeWindowSettings(ImGuiWindowSettings* settings);\n    IMGUI_API void          DebugNodeWindowsList(ImVector<ImGuiWindow*>* windows, const char* label);\n    IMGUI_API void          DebugNodeWindowsListByBeginStackParent(ImGuiWindow** windows, int windows_size, ImGuiWindow* parent_in_begin_stack);\n    IMGUI_API void          DebugNodeViewport(ImGuiViewportP* viewport);\n    IMGUI_API void          DebugRenderKeyboardPreview(ImDrawList* draw_list);\n    IMGUI_API void          DebugRenderViewportThumbnail(ImDrawList* draw_list, ImGuiViewportP* viewport, const ImRect& bb);\n\n    // Obsolete functions\n#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS\n    //inline void   SetItemUsingMouseWheel()                                            { SetItemKeyOwner(ImGuiKey_MouseWheelY); }      // Changed in 1.89\n    //inline bool   TreeNodeBehaviorIsOpen(ImGuiID id, ImGuiTreeNodeFlags flags = 0)    { return TreeNodeUpdateNextOpen(id, flags); }   // Renamed in 1.89\n    //inline bool   IsKeyPressedMap(ImGuiKey key, bool repeat = true)                   { IM_ASSERT(IsNamedKey(key)); return IsKeyPressed(key, repeat); } // Removed in 1.87: Mapping from named key is always identity!\n\n    // Refactored focus/nav/tabbing system in 1.82 and 1.84. If you have old/custom copy-and-pasted widgets which used FocusableItemRegister():\n    //  (Old) IMGUI_VERSION_NUM  < 18209: using 'ItemAdd(....)'                              and 'bool tab_focused = FocusableItemRegister(...)'\n    //  (Old) IMGUI_VERSION_NUM >= 18209: using 'ItemAdd(..., ImGuiItemAddFlags_Focusable)'  and 'bool tab_focused = (g.LastItemData.StatusFlags & ImGuiItemStatusFlags_Focused) != 0'\n    //  (New) IMGUI_VERSION_NUM >= 18413: using 'ItemAdd(..., ImGuiItemFlags_Inputable)'     and 'bool tab_focused = (g.NavActivateId == id && (g.NavActivateFlags & ImGuiActivateFlags_PreferInput))'\n    //inline bool   FocusableItemRegister(ImGuiWindow* window, ImGuiID id)              // -> pass ImGuiItemAddFlags_Inputable flag to ItemAdd()\n    //inline void   FocusableItemUnregister(ImGuiWindow* window)                        // -> unnecessary: TempInputText() uses ImGuiInputTextFlags_MergedItem\n#endif\n\n} // namespace ImGui\n\n\n//-----------------------------------------------------------------------------\n// [SECTION] ImFontAtlas internal API\n//-----------------------------------------------------------------------------\n\n// This structure is likely to evolve as we add support for incremental atlas updates.\n// Conceptually this could be in ImGuiPlatformIO, but we are far from ready to make this public.\nstruct ImFontBuilderIO\n{\n    bool    (*FontBuilder_Build)(ImFontAtlas* atlas);\n};\n\n// Helper for font builder\n#ifdef IMGUI_ENABLE_STB_TRUETYPE\nIMGUI_API const ImFontBuilderIO* ImFontAtlasGetBuilderForStbTruetype();\n#endif\nIMGUI_API void      ImFontAtlasUpdateSourcesPointers(ImFontAtlas* atlas);\nIMGUI_API void      ImFontAtlasBuildInit(ImFontAtlas* atlas);\nIMGUI_API void      ImFontAtlasBuildSetupFont(ImFontAtlas* atlas, ImFont* font, ImFontConfig* src, float ascent, float descent);\nIMGUI_API void      ImFontAtlasBuildPackCustomRects(ImFontAtlas* atlas, void* stbrp_context_opaque);\nIMGUI_API void      ImFontAtlasBuildFinish(ImFontAtlas* atlas);\nIMGUI_API void      ImFontAtlasBuildRender8bppRectFromString(ImFontAtlas* atlas, int x, int y, int w, int h, const char* in_str, char in_marker_char, unsigned char in_marker_pixel_value);\nIMGUI_API void      ImFontAtlasBuildRender32bppRectFromString(ImFontAtlas* atlas, int x, int y, int w, int h, const char* in_str, char in_marker_char, unsigned int in_marker_pixel_value);\nIMGUI_API void      ImFontAtlasBuildMultiplyCalcLookupTable(unsigned char out_table[256], float in_multiply_factor);\nIMGUI_API void      ImFontAtlasBuildMultiplyRectAlpha8(const unsigned char table[256], unsigned char* pixels, int x, int y, int w, int h, int stride);\nIMGUI_API void      ImFontAtlasBuildGetOversampleFactors(const ImFontConfig* src, int* out_oversample_h, int* out_oversample_v);\n\nIMGUI_API bool      ImFontAtlasGetMouseCursorTexData(ImFontAtlas* atlas, ImGuiMouseCursor cursor_type, ImVec2* out_offset, ImVec2* out_size, ImVec2 out_uv_border[2], ImVec2 out_uv_fill[2]);\n\n//-----------------------------------------------------------------------------\n// [SECTION] Test Engine specific hooks (imgui_test_engine)\n//-----------------------------------------------------------------------------\n\n#ifdef IMGUI_ENABLE_TEST_ENGINE\nextern void         ImGuiTestEngineHook_ItemAdd(ImGuiContext* ctx, ImGuiID id, const ImRect& bb, const ImGuiLastItemData* item_data);           // item_data may be NULL\nextern void         ImGuiTestEngineHook_ItemInfo(ImGuiContext* ctx, ImGuiID id, const char* label, ImGuiItemStatusFlags flags);\nextern void         ImGuiTestEngineHook_Log(ImGuiContext* ctx, const char* fmt, ...);\nextern const char*  ImGuiTestEngine_FindItemDebugLabel(ImGuiContext* ctx, ImGuiID id);\n\n// In IMGUI_VERSION_NUM >= 18934: changed IMGUI_TEST_ENGINE_ITEM_ADD(bb,id) to IMGUI_TEST_ENGINE_ITEM_ADD(id,bb,item_data);\n#define IMGUI_TEST_ENGINE_ITEM_ADD(_ID,_BB,_ITEM_DATA)      if (g.TestEngineHookItems) ImGuiTestEngineHook_ItemAdd(&g, _ID, _BB, _ITEM_DATA)    // Register item bounding box\n#define IMGUI_TEST_ENGINE_ITEM_INFO(_ID,_LABEL,_FLAGS)      if (g.TestEngineHookItems) ImGuiTestEngineHook_ItemInfo(&g, _ID, _LABEL, _FLAGS)    // Register item label and status flags (optional)\n#define IMGUI_TEST_ENGINE_LOG(_FMT,...)                     ImGuiTestEngineHook_Log(&g, _FMT, __VA_ARGS__)                                      // Custom log entry from user land into test log\n#else\n#define IMGUI_TEST_ENGINE_ITEM_ADD(_BB,_ID)                 ((void)0)\n#define IMGUI_TEST_ENGINE_ITEM_INFO(_ID,_LABEL,_FLAGS)      ((void)g)\n#endif\n\n//-----------------------------------------------------------------------------\n\n#if defined(__clang__)\n#pragma clang diagnostic pop\n#elif defined(__GNUC__)\n#pragma GCC diagnostic pop\n#endif\n\n#ifdef _MSC_VER\n#pragma warning (pop)\n#endif\n\n#endif // #ifndef IMGUI_DISABLE\n"
  },
  {
    "path": "src/DesktopPlusUI/imgui/imgui_tables.cpp",
    "content": "// dear imgui, v1.91b\n// (tables and columns code)\n\n/*\n\nIndex of this file:\n\n// [SECTION] Commentary\n// [SECTION] Header mess\n// [SECTION] Tables: Main code\n// [SECTION] Tables: Simple accessors\n// [SECTION] Tables: Row changes\n// [SECTION] Tables: Columns changes\n// [SECTION] Tables: Columns width management\n// [SECTION] Tables: Drawing\n// [SECTION] Tables: Sorting\n// [SECTION] Tables: Headers\n// [SECTION] Tables: Context Menu\n// [SECTION] Tables: Settings (.ini data)\n// [SECTION] Tables: Garbage Collection\n// [SECTION] Tables: Debugging\n// [SECTION] Columns, BeginColumns, EndColumns, etc.\n\n*/\n\n// Navigating this file:\n// - In Visual Studio: CTRL+comma (\"Edit.GoToAll\") can follow symbols inside comments, whereas CTRL+F12 (\"Edit.GoToImplementation\") cannot.\n// - In Visual Studio w/ Visual Assist installed: ALT+G (\"VAssistX.GoToImplementation\") can also follow symbols inside comments.\n// - In VS Code, CLion, etc.: CTRL+click can follow symbols inside comments.\n\n//-----------------------------------------------------------------------------\n// [SECTION] Commentary\n//-----------------------------------------------------------------------------\n\n//-----------------------------------------------------------------------------\n// Typical tables call flow: (root level is generally public API):\n//-----------------------------------------------------------------------------\n// - BeginTable()                               user begin into a table\n//    | BeginChild()                            - (if ScrollX/ScrollY is set)\n//    | TableBeginInitMemory()                  - first time table is used\n//    | TableResetSettings()                    - on settings reset\n//    | TableLoadSettings()                     - on settings load\n//    | TableBeginApplyRequests()               - apply queued resizing/reordering/hiding requests\n//    | - TableSetColumnWidth()                 - apply resizing width (for mouse resize, often requested by previous frame)\n//    |    - TableUpdateColumnsWeightFromWidth()- recompute columns weights (of stretch columns) from their respective width\n// - TableSetupColumn()                         user submit columns details (optional)\n// - TableSetupScrollFreeze()                   user submit scroll freeze information (optional)\n//-----------------------------------------------------------------------------\n// - TableUpdateLayout() [Internal]             followup to BeginTable(): setup everything: widths, columns positions, clipping rectangles. Automatically called by the FIRST call to TableNextRow() or TableHeadersRow().\n//    | TableSetupDrawChannels()                - setup ImDrawList channels\n//    | TableUpdateBorders()                    - detect hovering columns for resize, ahead of contents submission\n//    | TableBeginContextMenuPopup()\n//    | - TableDrawDefaultContextMenu()         - draw right-click context menu contents\n//-----------------------------------------------------------------------------\n// - TableHeadersRow() or TableHeader()         user submit a headers row (optional)\n//    | TableSortSpecsClickColumn()             - when left-clicked: alter sort order and sort direction\n//    | TableOpenContextMenu()                  - when right-clicked: trigger opening of the default context menu\n// - TableGetSortSpecs()                        user queries updated sort specs (optional, generally after submitting headers)\n// - TableNextRow()                             user begin into a new row (also automatically called by TableHeadersRow())\n//    | TableEndRow()                           - finish existing row\n//    | TableBeginRow()                         - add a new row\n// - TableSetColumnIndex() / TableNextColumn()  user begin into a cell\n//    | TableEndCell()                          - close existing column/cell\n//    | TableBeginCell()                        - enter into current column/cell\n// - [...]                                      user emit contents\n//-----------------------------------------------------------------------------\n// - EndTable()                                 user ends the table\n//    | TableDrawBorders()                      - draw outer borders, inner vertical borders\n//    | TableMergeDrawChannels()                - merge draw channels if clipping isn't required\n//    | EndChild()                              - (if ScrollX/ScrollY is set)\n//-----------------------------------------------------------------------------\n\n//-----------------------------------------------------------------------------\n// TABLE SIZING\n//-----------------------------------------------------------------------------\n// (Read carefully because this is subtle but it does make sense!)\n//-----------------------------------------------------------------------------\n// About 'outer_size':\n// Its meaning needs to differ slightly depending on if we are using ScrollX/ScrollY flags.\n// Default value is ImVec2(0.0f, 0.0f).\n//   X\n//   - outer_size.x <= 0.0f  ->  Right-align from window/work-rect right-most edge. With -FLT_MIN or 0.0f will align exactly on right-most edge.\n//   - outer_size.x  > 0.0f  ->  Set Fixed width.\n//   Y with ScrollX/ScrollY disabled: we output table directly in current window\n//   - outer_size.y  < 0.0f  ->  Bottom-align (but will auto extend, unless _NoHostExtendY is set). Not meaningful if parent window can vertically scroll.\n//   - outer_size.y  = 0.0f  ->  No minimum height (but will auto extend, unless _NoHostExtendY is set)\n//   - outer_size.y  > 0.0f  ->  Set Minimum height (but will auto extend, unless _NoHostExtendY is set)\n//   Y with ScrollX/ScrollY enabled: using a child window for scrolling\n//   - outer_size.y  < 0.0f  ->  Bottom-align. Not meaningful if parent window can vertically scroll.\n//   - outer_size.y  = 0.0f  ->  Bottom-align, consistent with BeginChild(). Not recommended unless table is last item in parent window.\n//   - outer_size.y  > 0.0f  ->  Set Exact height. Recommended when using Scrolling on any axis.\n//-----------------------------------------------------------------------------\n// Outer size is also affected by the NoHostExtendX/NoHostExtendY flags.\n// Important to note how the two flags have slightly different behaviors!\n//   - ImGuiTableFlags_NoHostExtendX -> Make outer width auto-fit to columns (overriding outer_size.x value). Only available when ScrollX/ScrollY are disabled and Stretch columns are not used.\n//   - ImGuiTableFlags_NoHostExtendY -> Make outer height stop exactly at outer_size.y (prevent auto-extending table past the limit). Only available when ScrollX/ScrollY is disabled. Data below the limit will be clipped and not visible.\n// In theory ImGuiTableFlags_NoHostExtendY could be the default and any non-scrolling tables with outer_size.y != 0.0f would use exact height.\n// This would be consistent but perhaps less useful and more confusing (as vertically clipped items are not useful and not easily noticeable).\n//-----------------------------------------------------------------------------\n// About 'inner_width':\n//   With ScrollX disabled:\n//   - inner_width          ->  *ignored*\n//   With ScrollX enabled:\n//   - inner_width  < 0.0f  ->  *illegal* fit in known width (right align from outer_size.x) <-- weird\n//   - inner_width  = 0.0f  ->  fit in outer_width: Fixed size columns will take space they need (if avail, otherwise shrink down), Stretch columns becomes Fixed columns.\n//   - inner_width  > 0.0f  ->  override scrolling width, generally to be larger than outer_size.x. Fixed column take space they need (if avail, otherwise shrink down), Stretch columns share remaining space!\n//-----------------------------------------------------------------------------\n// Details:\n// - If you want to use Stretch columns with ScrollX, you generally need to specify 'inner_width' otherwise the concept\n//   of \"available space\" doesn't make sense.\n// - Even if not really useful, we allow 'inner_width < outer_size.x' for consistency and to facilitate understanding\n//   of what the value does.\n//-----------------------------------------------------------------------------\n\n//-----------------------------------------------------------------------------\n// COLUMNS SIZING POLICIES\n// (Reference: ImGuiTableFlags_SizingXXX flags and ImGuiTableColumnFlags_WidthXXX flags)\n//-----------------------------------------------------------------------------\n// About overriding column sizing policy and width/weight with TableSetupColumn():\n// We use a default parameter of -1 for 'init_width'/'init_weight'.\n//   - with ImGuiTableColumnFlags_WidthFixed,    init_width  <= 0 (default)  --> width is automatic\n//   - with ImGuiTableColumnFlags_WidthFixed,    init_width  >  0 (explicit) --> width is custom\n//   - with ImGuiTableColumnFlags_WidthStretch,  init_weight <= 0 (default)  --> weight is 1.0f\n//   - with ImGuiTableColumnFlags_WidthStretch,  init_weight >  0 (explicit) --> weight is custom\n// Widths are specified _without_ CellPadding. If you specify a width of 100.0f, the column will be cover (100.0f + Padding * 2.0f)\n// and you can fit a 100.0f wide item in it without clipping and with padding honored.\n//-----------------------------------------------------------------------------\n// About default sizing policy (if you don't specify a ImGuiTableColumnFlags_WidthXXXX flag)\n//   - with Table policy ImGuiTableFlags_SizingFixedFit      --> default Column policy is ImGuiTableColumnFlags_WidthFixed, default Width is equal to contents width\n//   - with Table policy ImGuiTableFlags_SizingFixedSame     --> default Column policy is ImGuiTableColumnFlags_WidthFixed, default Width is max of all contents width\n//   - with Table policy ImGuiTableFlags_SizingStretchSame   --> default Column policy is ImGuiTableColumnFlags_WidthStretch, default Weight is 1.0f\n//   - with Table policy ImGuiTableFlags_SizingStretchWeight --> default Column policy is ImGuiTableColumnFlags_WidthStretch, default Weight is proportional to contents\n// Default Width and default Weight can be overridden when calling TableSetupColumn().\n//-----------------------------------------------------------------------------\n// About mixing Fixed/Auto and Stretch columns together:\n//   - the typical use of mixing sizing policies is: any number of LEADING Fixed columns, followed by one or two TRAILING Stretch columns.\n//   - using mixed policies with ScrollX does not make much sense, as using Stretch columns with ScrollX does not make much sense in the first place!\n//     that is, unless 'inner_width' is passed to BeginTable() to explicitly provide a total width to layout columns in.\n//   - when using ImGuiTableFlags_SizingFixedSame with mixed columns, only the Fixed/Auto columns will match their widths to the width of the maximum contents.\n//   - when using ImGuiTableFlags_SizingStretchSame with mixed columns, only the Stretch columns will match their weights/widths.\n//-----------------------------------------------------------------------------\n// About using column width:\n// If a column is manually resizable or has a width specified with TableSetupColumn():\n//   - you may use GetContentRegionAvail().x to query the width available in a given column.\n//   - right-side alignment features such as SetNextItemWidth(-x) or PushItemWidth(-x) will rely on this width.\n// If the column is not resizable and has no width specified with TableSetupColumn():\n//   - its width will be automatic and be set to the max of items submitted.\n//   - therefore you generally cannot have ALL items of the columns use e.g. SetNextItemWidth(-FLT_MIN).\n//   - but if the column has one or more items of known/fixed size, this will become the reference width used by SetNextItemWidth(-FLT_MIN).\n//-----------------------------------------------------------------------------\n\n\n//-----------------------------------------------------------------------------\n// TABLES CLIPPING/CULLING\n//-----------------------------------------------------------------------------\n// About clipping/culling of Rows in Tables:\n// - For large numbers of rows, it is recommended you use ImGuiListClipper to submit only visible rows.\n//   ImGuiListClipper is reliant on the fact that rows are of equal height.\n//   See 'Demo->Tables->Vertical Scrolling' or 'Demo->Tables->Advanced' for a demo of using the clipper.\n// - Note that auto-resizing columns don't play well with using the clipper.\n//   By default a table with _ScrollX but without _Resizable will have column auto-resize.\n//   So, if you want to use the clipper, make sure to either enable _Resizable, either setup columns width explicitly with _WidthFixed.\n//-----------------------------------------------------------------------------\n// About clipping/culling of Columns in Tables:\n// - Both TableSetColumnIndex() and TableNextColumn() return true when the column is visible or performing\n//   width measurements. Otherwise, you may skip submitting the contents of a cell/column, BUT ONLY if you know\n//   it is not going to contribute to row height.\n//   In many situations, you may skip submitting contents for every column but one (e.g. the first one).\n// - Case A: column is not hidden by user, and at least partially in sight (most common case).\n// - Case B: column is clipped / out of sight (because of scrolling or parent ClipRect): TableNextColumn() return false as a hint but we still allow layout output.\n// - Case C: column is hidden explicitly by the user (e.g. via the context menu, or _DefaultHide column flag, etc.).\n//\n//                        [A]         [B]          [C]\n//  TableNextColumn():    true        false        false       -> [userland] when TableNextColumn() / TableSetColumnIndex() returns false, user can skip submitting items but only if the column doesn't contribute to row height.\n//          SkipItems:    false       false        true        -> [internal] when SkipItems is true, most widgets will early out if submitted, resulting is no layout output.\n//           ClipRect:    normal      zero-width   zero-width  -> [internal] when ClipRect is zero, ItemAdd() will return false and most widgets will early out mid-way.\n//  ImDrawList output:    normal      dummy        dummy       -> [internal] when using the dummy channel, ImDrawList submissions (if any) will be wasted (because cliprect is zero-width anyway).\n//\n// - We need to distinguish those cases because non-hidden columns that are clipped outside of scrolling bounds should still contribute their height to the row.\n//   However, in the majority of cases, the contribution to row height is the same for all columns, or the tallest cells are known by the programmer.\n//-----------------------------------------------------------------------------\n// About clipping/culling of whole Tables:\n// - Scrolling tables with a known outer size can be clipped earlier as BeginTable() will return false.\n//-----------------------------------------------------------------------------\n\n//-----------------------------------------------------------------------------\n// [SECTION] Header mess\n//-----------------------------------------------------------------------------\n\n#if defined(_MSC_VER) && !defined(_CRT_SECURE_NO_WARNINGS)\n#define _CRT_SECURE_NO_WARNINGS\n#endif\n\n#ifndef IMGUI_DEFINE_MATH_OPERATORS\n#define IMGUI_DEFINE_MATH_OPERATORS\n#endif\n\n#include \"imgui.h\"\n#ifndef IMGUI_DISABLE\n#include \"imgui_internal.h\"\n\n// System includes\n#include <stdint.h>     // intptr_t\n\n// Visual Studio warnings\n#ifdef _MSC_VER\n#pragma warning (disable: 4127)     // condition expression is constant\n#pragma warning (disable: 4996)     // 'This function or variable may be unsafe': strcpy, strdup, sprintf, vsnprintf, sscanf, fopen\n#if defined(_MSC_VER) && _MSC_VER >= 1922 // MSVC 2019 16.2 or later\n#pragma warning (disable: 5054)     // operator '|': deprecated between enumerations of different types\n#endif\n#pragma warning (disable: 26451)    // [Static Analyzer] Arithmetic overflow : Using operator 'xxx' on a 4 byte value and then casting the result to a 8 byte value. Cast the value to the wider type before calling operator 'xxx' to avoid overflow(io.2).\n#pragma warning (disable: 26812)    // [Static Analyzer] The enum type 'xxx' is unscoped. Prefer 'enum class' over 'enum' (Enum.3).\n#endif\n\n// Clang/GCC warnings with -Weverything\n#if defined(__clang__)\n#if __has_warning(\"-Wunknown-warning-option\")\n#pragma clang diagnostic ignored \"-Wunknown-warning-option\"         // warning: unknown warning group 'xxx'                      // not all warnings are known by all Clang versions and they tend to be rename-happy.. so ignoring warnings triggers new warnings on some configuration. Great!\n#endif\n#pragma clang diagnostic ignored \"-Wunknown-pragmas\"                // warning: unknown warning group 'xxx'\n#pragma clang diagnostic ignored \"-Wold-style-cast\"                 // warning: use of old-style cast                            // yes, they are more terse.\n#pragma clang diagnostic ignored \"-Wfloat-equal\"                    // warning: comparing floating point with == or != is unsafe // storing and comparing against same constants (typically 0.0f) is ok.\n#pragma clang diagnostic ignored \"-Wformat\"                         // warning: format specifies type 'int' but the argument has type 'unsigned int'\n#pragma clang diagnostic ignored \"-Wformat-nonliteral\"              // warning: format string is not a string literal            // passing non-literal to vsnformat(). yes, user passing incorrect format strings can crash the code.\n#pragma clang diagnostic ignored \"-Wsign-conversion\"                // warning: implicit conversion changes signedness\n#pragma clang diagnostic ignored \"-Wzero-as-null-pointer-constant\"  // warning: zero as null pointer constant                    // some standard header variations use #define NULL 0\n#pragma clang diagnostic ignored \"-Wdouble-promotion\"               // warning: implicit conversion from 'float' to 'double' when passing argument to function  // using printf() is a misery with this as C++ va_arg ellipsis changes float to double.\n#pragma clang diagnostic ignored \"-Wenum-enum-conversion\"           // warning: bitwise operation between different enumeration types ('XXXFlags_' and 'XXXFlagsPrivate_')\n#pragma clang diagnostic ignored \"-Wdeprecated-enum-enum-conversion\"// warning: bitwise operation between different enumeration types ('XXXFlags_' and 'XXXFlagsPrivate_') is deprecated\n#pragma clang diagnostic ignored \"-Wimplicit-int-float-conversion\"  // warning: implicit conversion from 'xxx' to 'float' may lose precision\n#pragma clang diagnostic ignored \"-Wunsafe-buffer-usage\"            // warning: 'xxx' is an unsafe pointer used for buffer access\n#pragma clang diagnostic ignored \"-Wnontrivial-memaccess\"           // warning: first argument in call to 'memset' is a pointer to non-trivially copyable type\n#pragma clang diagnostic ignored \"-Wswitch-default\"                 // warning: 'switch' missing 'default' label\n#elif defined(__GNUC__)\n#pragma GCC diagnostic ignored \"-Wpragmas\"                          // warning: unknown option after '#pragma GCC diagnostic' kind\n#pragma GCC diagnostic ignored \"-Wfloat-equal\"                      // warning: comparing floating-point with '==' or '!=' is unsafe\n#pragma GCC diagnostic ignored \"-Wformat-nonliteral\"                // warning: format not a string literal, format string not checked\n#pragma GCC diagnostic ignored \"-Wdouble-promotion\"                 // warning: implicit conversion from 'float' to 'double' when passing argument to function\n#pragma GCC diagnostic ignored \"-Wformat\"                           // warning: format '%p' expects argument of type 'int'/'void*', but argument X has type 'unsigned int'/'ImGuiWindow*'\n#pragma GCC diagnostic ignored \"-Wstrict-overflow\"\n#pragma GCC diagnostic ignored \"-Wclass-memaccess\"                  // [__GNUC__ >= 8] warning: 'memset/memcpy' clearing/writing an object of type 'xxxx' with no trivial copy-assignment; use assignment or value-initialization instead\n#endif\n\n//-----------------------------------------------------------------------------\n// [SECTION] Tables: Main code\n//-----------------------------------------------------------------------------\n// - TableFixFlags() [Internal]\n// - TableFindByID() [Internal]\n// - BeginTable()\n// - BeginTableEx() [Internal]\n// - TableBeginInitMemory() [Internal]\n// - TableBeginApplyRequests() [Internal]\n// - TableSetupColumnFlags() [Internal]\n// - TableUpdateLayout() [Internal]\n// - TableUpdateBorders() [Internal]\n// - EndTable()\n// - TableSetupColumn()\n// - TableSetupScrollFreeze()\n//-----------------------------------------------------------------------------\n\n// Configuration\nstatic const int TABLE_DRAW_CHANNEL_BG0 = 0;\nstatic const int TABLE_DRAW_CHANNEL_BG2_FROZEN = 1;\nstatic const int TABLE_DRAW_CHANNEL_NOCLIP = 2;                     // When using ImGuiTableFlags_NoClip (this becomes the last visible channel)\nstatic const float TABLE_BORDER_SIZE                     = 1.0f;    // FIXME-TABLE: Currently hard-coded because of clipping assumptions with outer borders rendering.\nstatic const float TABLE_RESIZE_SEPARATOR_HALF_THICKNESS = 4.0f;    // Extend outside inner borders.\nstatic const float TABLE_RESIZE_SEPARATOR_FEEDBACK_TIMER = 0.06f;   // Delay/timer before making the hover feedback (color+cursor) visible because tables/columns tends to be more cramped.\n\n// Helper\ninline ImGuiTableFlags TableFixFlags(ImGuiTableFlags flags, ImGuiWindow* outer_window)\n{\n    // Adjust flags: set default sizing policy\n    if ((flags & ImGuiTableFlags_SizingMask_) == 0)\n        flags |= ((flags & ImGuiTableFlags_ScrollX) || (outer_window->Flags & ImGuiWindowFlags_AlwaysAutoResize)) ? ImGuiTableFlags_SizingFixedFit : ImGuiTableFlags_SizingStretchSame;\n\n    // Adjust flags: enable NoKeepColumnsVisible when using ImGuiTableFlags_SizingFixedSame\n    if ((flags & ImGuiTableFlags_SizingMask_) == ImGuiTableFlags_SizingFixedSame)\n        flags |= ImGuiTableFlags_NoKeepColumnsVisible;\n\n    // Adjust flags: enforce borders when resizable\n    if (flags & ImGuiTableFlags_Resizable)\n        flags |= ImGuiTableFlags_BordersInnerV;\n\n    // Adjust flags: disable NoHostExtendX/NoHostExtendY if we have any scrolling going on\n    if (flags & (ImGuiTableFlags_ScrollX | ImGuiTableFlags_ScrollY))\n        flags &= ~(ImGuiTableFlags_NoHostExtendX | ImGuiTableFlags_NoHostExtendY);\n\n    // Adjust flags: NoBordersInBodyUntilResize takes priority over NoBordersInBody\n    if (flags & ImGuiTableFlags_NoBordersInBodyUntilResize)\n        flags &= ~ImGuiTableFlags_NoBordersInBody;\n\n    // Adjust flags: disable saved settings if there's nothing to save\n    if ((flags & (ImGuiTableFlags_Resizable | ImGuiTableFlags_Hideable | ImGuiTableFlags_Reorderable | ImGuiTableFlags_Sortable)) == 0)\n        flags |= ImGuiTableFlags_NoSavedSettings;\n\n    // Inherit _NoSavedSettings from top-level window (child windows always have _NoSavedSettings set)\n    if (outer_window->RootWindow->Flags & ImGuiWindowFlags_NoSavedSettings)\n        flags |= ImGuiTableFlags_NoSavedSettings;\n\n    return flags;\n}\n\nImGuiTable* ImGui::TableFindByID(ImGuiID id)\n{\n    ImGuiContext& g = *GImGui;\n    return g.Tables.GetByKey(id);\n}\n\n// Read about \"TABLE SIZING\" at the top of this file.\nbool    ImGui::BeginTable(const char* str_id, int columns_count, ImGuiTableFlags flags, const ImVec2& outer_size, float inner_width)\n{\n    ImGuiID id = GetID(str_id);\n    return BeginTableEx(str_id, id, columns_count, flags, outer_size, inner_width);\n}\n\nbool    ImGui::BeginTableEx(const char* name, ImGuiID id, int columns_count, ImGuiTableFlags flags, const ImVec2& outer_size, float inner_width)\n{\n    ImGuiContext& g = *GImGui;\n    ImGuiWindow* outer_window = GetCurrentWindow();\n    if (outer_window->SkipItems) // Consistent with other tables + beneficial side effect that assert on miscalling EndTable() will be more visible.\n        return false;\n\n    // Sanity checks\n    IM_ASSERT(columns_count > 0 && columns_count < IMGUI_TABLE_MAX_COLUMNS);\n    if (flags & ImGuiTableFlags_ScrollX)\n        IM_ASSERT(inner_width >= 0.0f);\n\n    // If an outer size is specified ahead we will be able to early out when not visible. Exact clipping criteria may evolve.\n    // FIXME: coarse clipping because access to table data causes two issues:\n    // - instance numbers varying/unstable. may not be a direct problem for users, but could make outside access broken or confusing, e.g. TestEngine.\n    // - can't implement support for ImGuiChildFlags_ResizeY as we need to somehow pull the height data from somewhere. this also needs stable instance numbers.\n    // The side-effects of accessing table data on coarse clip would be:\n    // - always reserving the pooled ImGuiTable data ahead for a fully clipped table (minor IMHO). Also the 'outer_window_is_measuring_size' criteria may already be defeating this in some situations.\n    // - always performing the GetOrAddByKey() O(log N) query in g.Tables.Map[].\n    const bool use_child_window = (flags & (ImGuiTableFlags_ScrollX | ImGuiTableFlags_ScrollY)) != 0;\n    const ImVec2 avail_size = GetContentRegionAvail();\n    const ImVec2 actual_outer_size = ImTrunc(CalcItemSize(outer_size, ImMax(avail_size.x, 1.0f), use_child_window ? ImMax(avail_size.y, 1.0f) : 0.0f));\n    const ImRect outer_rect(outer_window->DC.CursorPos, outer_window->DC.CursorPos + actual_outer_size);\n    const bool outer_window_is_measuring_size = (outer_window->AutoFitFramesX > 0) || (outer_window->AutoFitFramesY > 0); // Doesn't apply to AlwaysAutoResize windows!\n    if (use_child_window && IsClippedEx(outer_rect, 0) && !outer_window_is_measuring_size)\n    {\n        ItemSize(outer_rect);\n        ItemAdd(outer_rect, id);\n        g.NextWindowData.ClearFlags();\n        return false;\n    }\n\n    // [DEBUG] Debug break requested by user\n    if (g.DebugBreakInTable == id)\n        IM_DEBUG_BREAK();\n\n    // Acquire storage for the table\n    ImGuiTable* table = g.Tables.GetOrAddByKey(id);\n\n    // Acquire temporary buffers\n    const int table_idx = g.Tables.GetIndex(table);\n    if (++g.TablesTempDataStacked > g.TablesTempData.Size)\n        g.TablesTempData.resize(g.TablesTempDataStacked, ImGuiTableTempData());\n    ImGuiTableTempData* temp_data = table->TempData = &g.TablesTempData[g.TablesTempDataStacked - 1];\n    temp_data->TableIndex = table_idx;\n    table->DrawSplitter = &table->TempData->DrawSplitter;\n    table->DrawSplitter->Clear();\n\n    // Fix flags\n    table->IsDefaultSizingPolicy = (flags & ImGuiTableFlags_SizingMask_) == 0;\n    flags = TableFixFlags(flags, outer_window);\n\n    // Initialize\n    const int previous_frame_active = table->LastFrameActive;\n    const int instance_no = (previous_frame_active != g.FrameCount) ? 0 : table->InstanceCurrent + 1;\n    const ImGuiTableFlags previous_flags = table->Flags;\n    table->ID = id;\n    table->Flags = flags;\n    table->LastFrameActive = g.FrameCount;\n    table->OuterWindow = table->InnerWindow = outer_window;\n    table->ColumnsCount = columns_count;\n    table->IsLayoutLocked = false;\n    table->InnerWidth = inner_width;\n    table->NavLayer = (ImS8)outer_window->DC.NavLayerCurrent;\n    temp_data->UserOuterSize = outer_size;\n\n    // Instance data (for instance 0, TableID == TableInstanceID)\n    ImGuiID instance_id;\n    table->InstanceCurrent = (ImS16)instance_no;\n    if (instance_no > 0)\n    {\n        IM_ASSERT(table->ColumnsCount == columns_count && \"BeginTable(): Cannot change columns count mid-frame while preserving same ID\");\n        if (table->InstanceDataExtra.Size < instance_no)\n            table->InstanceDataExtra.push_back(ImGuiTableInstanceData());\n        instance_id = GetIDWithSeed(instance_no, GetIDWithSeed(\"##Instances\", NULL, id)); // Push \"##Instances\" followed by (int)instance_no in ID stack.\n    }\n    else\n    {\n        instance_id = id;\n    }\n    ImGuiTableInstanceData* table_instance = TableGetInstanceData(table, table->InstanceCurrent);\n    table_instance->TableInstanceID = instance_id;\n\n    // When not using a child window, WorkRect.Max will grow as we append contents.\n    if (use_child_window)\n    {\n        // Ensure no vertical scrollbar appears if we only want horizontal one, to make flag consistent\n        // (we have no other way to disable vertical scrollbar of a window while keeping the horizontal one showing)\n        ImVec2 override_content_size(FLT_MAX, FLT_MAX);\n        if ((flags & ImGuiTableFlags_ScrollX) && !(flags & ImGuiTableFlags_ScrollY))\n            override_content_size.y = FLT_MIN;\n\n        // Ensure specified width (when not specified, Stretched columns will act as if the width == OuterWidth and\n        // never lead to any scrolling). We don't handle inner_width < 0.0f, we could potentially use it to right-align\n        // based on the right side of the child window work rect, which would require knowing ahead if we are going to\n        // have decoration taking horizontal spaces (typically a vertical scrollbar).\n        if ((flags & ImGuiTableFlags_ScrollX) && inner_width > 0.0f)\n            override_content_size.x = inner_width;\n\n        if (override_content_size.x != FLT_MAX || override_content_size.y != FLT_MAX)\n            SetNextWindowContentSize(ImVec2(override_content_size.x != FLT_MAX ? override_content_size.x : 0.0f, override_content_size.y != FLT_MAX ? override_content_size.y : 0.0f));\n\n        // Reset scroll if we are reactivating it\n        if ((previous_flags & (ImGuiTableFlags_ScrollX | ImGuiTableFlags_ScrollY)) == 0)\n            if ((g.NextWindowData.HasFlags & ImGuiNextWindowDataFlags_HasScroll) == 0)\n                SetNextWindowScroll(ImVec2(0.0f, 0.0f));\n\n        // Create scrolling region (without border and zero window padding)\n        ImGuiChildFlags child_child_flags = (g.NextWindowData.HasFlags & ImGuiNextWindowDataFlags_HasChildFlags) ? g.NextWindowData.ChildFlags : ImGuiChildFlags_None;\n        ImGuiWindowFlags child_window_flags = (g.NextWindowData.HasFlags & ImGuiNextWindowDataFlags_HasWindowFlags) ? g.NextWindowData.WindowFlags : ImGuiWindowFlags_None;\n        if (flags & ImGuiTableFlags_ScrollX)\n            child_window_flags |= ImGuiWindowFlags_HorizontalScrollbar;\n        BeginChildEx(name, instance_id, outer_rect.GetSize(), child_child_flags, child_window_flags);\n        table->InnerWindow = g.CurrentWindow;\n        table->WorkRect = table->InnerWindow->WorkRect;\n        table->OuterRect = table->InnerWindow->Rect();\n        table->InnerRect = table->InnerWindow->InnerRect;\n        IM_ASSERT(table->InnerWindow->WindowPadding.x == 0.0f && table->InnerWindow->WindowPadding.y == 0.0f && table->InnerWindow->WindowBorderSize == 0.0f);\n\n        // Allow submitting when host is measuring\n        if (table->InnerWindow->SkipItems && outer_window_is_measuring_size)\n            table->InnerWindow->SkipItems = false;\n\n        // When using multiple instances, ensure they have the same amount of horizontal decorations (aka vertical scrollbar) so stretched columns can be aligned)\n        if (instance_no == 0)\n        {\n            table->HasScrollbarYPrev = table->HasScrollbarYCurr;\n            table->HasScrollbarYCurr = false;\n        }\n        table->HasScrollbarYCurr |= table->InnerWindow->ScrollbarY;\n    }\n    else\n    {\n        // For non-scrolling tables, WorkRect == OuterRect == InnerRect.\n        // But at this point we do NOT have a correct value for .Max.y (unless a height has been explicitly passed in). It will only be updated in EndTable().\n        table->WorkRect = table->OuterRect = table->InnerRect = outer_rect;\n        table->HasScrollbarYPrev = table->HasScrollbarYCurr = false;\n    }\n\n    // Push a standardized ID for both child-using and not-child-using tables\n    PushOverrideID(id);\n    if (instance_no > 0)\n        PushOverrideID(instance_id); // FIXME: Somehow this is not resolved by stack-tool, even tho GetIDWithSeed() submitted the symbol.\n\n    // Backup a copy of host window members we will modify\n    ImGuiWindow* inner_window = table->InnerWindow;\n    table->HostIndentX = inner_window->DC.Indent.x;\n    table->HostClipRect = inner_window->ClipRect;\n    table->HostSkipItems = inner_window->SkipItems;\n    temp_data->HostBackupWorkRect = inner_window->WorkRect;\n    temp_data->HostBackupParentWorkRect = inner_window->ParentWorkRect;\n    temp_data->HostBackupColumnsOffset = outer_window->DC.ColumnsOffset;\n    temp_data->HostBackupPrevLineSize = inner_window->DC.PrevLineSize;\n    temp_data->HostBackupCurrLineSize = inner_window->DC.CurrLineSize;\n    temp_data->HostBackupCursorMaxPos = inner_window->DC.CursorMaxPos;\n    temp_data->HostBackupItemWidth = outer_window->DC.ItemWidth;\n    temp_data->HostBackupItemWidthStackSize = outer_window->DC.ItemWidthStack.Size;\n    inner_window->DC.PrevLineSize = inner_window->DC.CurrLineSize = ImVec2(0.0f, 0.0f);\n\n    // Make borders not overlap our contents by offsetting HostClipRect (#6765, #7428, #3752)\n    // (we normally shouldn't alter HostClipRect as we rely on TableMergeDrawChannels() expanding non-clipped column toward the\n    // limits of that rectangle, in order for ImDrawListSplitter::Merge() to merge the draw commands. However since the overlap\n    // problem only affect scrolling tables in this case we can get away with doing it without extra cost).\n    if (inner_window != outer_window)\n    {\n        // FIXME: Because inner_window's Scrollbar doesn't know about border size, since it's not encoded in window->WindowBorderSize,\n        // it already overlaps it and doesn't need an extra offset. Ideally we should be able to pass custom border size with\n        // different x/y values to BeginChild().\n        if (flags & ImGuiTableFlags_BordersOuterV)\n        {\n            table->HostClipRect.Min.x = ImMin(table->HostClipRect.Min.x + TABLE_BORDER_SIZE, table->HostClipRect.Max.x);\n            if (inner_window->DecoOuterSizeX2 == 0.0f)\n                table->HostClipRect.Max.x = ImMax(table->HostClipRect.Max.x - TABLE_BORDER_SIZE, table->HostClipRect.Min.x);\n        }\n        if (flags & ImGuiTableFlags_BordersOuterH)\n        {\n            table->HostClipRect.Min.y = ImMin(table->HostClipRect.Min.y + TABLE_BORDER_SIZE, table->HostClipRect.Max.y);\n            if (inner_window->DecoOuterSizeY2 == 0.0f)\n                table->HostClipRect.Max.y = ImMax(table->HostClipRect.Max.y - TABLE_BORDER_SIZE, table->HostClipRect.Min.y);\n        }\n    }\n\n    // Padding and Spacing\n    // - None               ........Content..... Pad .....Content........\n    // - PadOuter           | Pad ..Content..... Pad .....Content.. Pad |\n    // - PadInner           ........Content.. Pad | Pad ..Content........\n    // - PadOuter+PadInner  | Pad ..Content.. Pad | Pad ..Content.. Pad |\n    const bool pad_outer_x = (flags & ImGuiTableFlags_NoPadOuterX) ? false : (flags & ImGuiTableFlags_PadOuterX) ? true : (flags & ImGuiTableFlags_BordersOuterV) != 0;\n    const bool pad_inner_x = (flags & ImGuiTableFlags_NoPadInnerX) ? false : true;\n    const float inner_spacing_for_border = (flags & ImGuiTableFlags_BordersInnerV) ? TABLE_BORDER_SIZE : 0.0f;\n    const float inner_spacing_explicit = (pad_inner_x && (flags & ImGuiTableFlags_BordersInnerV) == 0) ? g.Style.CellPadding.x : 0.0f;\n    const float inner_padding_explicit = (pad_inner_x && (flags & ImGuiTableFlags_BordersInnerV) != 0) ? g.Style.CellPadding.x : 0.0f;\n    table->CellSpacingX1 = inner_spacing_explicit + inner_spacing_for_border;\n    table->CellSpacingX2 = inner_spacing_explicit;\n    table->CellPaddingX = inner_padding_explicit;\n\n    const float outer_padding_for_border = (flags & ImGuiTableFlags_BordersOuterV) ? TABLE_BORDER_SIZE : 0.0f;\n    const float outer_padding_explicit = pad_outer_x ? g.Style.CellPadding.x : 0.0f;\n    table->OuterPaddingX = (outer_padding_for_border + outer_padding_explicit) - table->CellPaddingX;\n\n    table->CurrentColumn = -1;\n    table->CurrentRow = -1;\n    table->RowBgColorCounter = 0;\n    table->LastRowFlags = ImGuiTableRowFlags_None;\n    table->InnerClipRect = (inner_window == outer_window) ? table->WorkRect : inner_window->ClipRect;\n    table->InnerClipRect.ClipWith(table->WorkRect);     // We need this to honor inner_width\n    table->InnerClipRect.ClipWithFull(table->HostClipRect);\n    table->InnerClipRect.Max.y = (flags & ImGuiTableFlags_NoHostExtendY) ? ImMin(table->InnerClipRect.Max.y, inner_window->WorkRect.Max.y) : table->HostClipRect.Max.y;\n\n    table->RowPosY1 = table->RowPosY2 = table->WorkRect.Min.y; // This is needed somehow\n    table->RowTextBaseline = 0.0f; // This will be cleared again by TableBeginRow()\n    table->RowCellPaddingY = 0.0f;\n    table->FreezeRowsRequest = table->FreezeRowsCount = 0; // This will be setup by TableSetupScrollFreeze(), if any\n    table->FreezeColumnsRequest = table->FreezeColumnsCount = 0;\n    table->IsUnfrozenRows = true;\n    table->DeclColumnsCount = table->AngledHeadersCount = 0;\n    if (previous_frame_active + 1 < g.FrameCount)\n        table->IsActiveIdInTable = false;\n    table->AngledHeadersHeight = 0.0f;\n    temp_data->AngledHeadersExtraWidth = 0.0f;\n\n    // Using opaque colors facilitate overlapping lines of the grid, otherwise we'd need to improve TableDrawBorders()\n    table->BorderColorStrong = GetColorU32(ImGuiCol_TableBorderStrong);\n    table->BorderColorLight = GetColorU32(ImGuiCol_TableBorderLight);\n\n    // Make table current\n    g.CurrentTable = table;\n    outer_window->DC.NavIsScrollPushableX = false; // Shortcut for NavUpdateCurrentWindowIsScrollPushableX();\n    outer_window->DC.CurrentTableIdx = table_idx;\n    if (inner_window != outer_window) // So EndChild() within the inner window can restore the table properly.\n        inner_window->DC.CurrentTableIdx = table_idx;\n\n    if ((previous_flags & ImGuiTableFlags_Reorderable) && (flags & ImGuiTableFlags_Reorderable) == 0)\n        table->IsResetDisplayOrderRequest = true;\n\n    // Mark as used to avoid GC\n    if (table_idx >= g.TablesLastTimeActive.Size)\n        g.TablesLastTimeActive.resize(table_idx + 1, -1.0f);\n    g.TablesLastTimeActive[table_idx] = (float)g.Time;\n    temp_data->LastTimeActive = (float)g.Time;\n    table->MemoryCompacted = false;\n\n    // Setup memory buffer (clear data if columns count changed)\n    ImGuiTableColumn* old_columns_to_preserve = NULL;\n    void* old_columns_raw_data = NULL;\n    const int old_columns_count = table->Columns.size();\n    if (old_columns_count != 0 && old_columns_count != columns_count)\n    {\n        // Attempt to preserve width on column count change (#4046)\n        old_columns_to_preserve = table->Columns.Data;\n        old_columns_raw_data = table->RawData;\n        table->RawData = NULL;\n    }\n    if (table->RawData == NULL)\n    {\n        TableBeginInitMemory(table, columns_count);\n        table->IsInitializing = table->IsSettingsRequestLoad = true;\n    }\n    if (table->IsResetAllRequest)\n        TableResetSettings(table);\n    if (table->IsInitializing)\n    {\n        // Initialize\n        table->SettingsOffset = -1;\n        table->IsSortSpecsDirty = true;\n        table->IsSettingsDirty = true; // Records itself into .ini file even when in default state (#7934)\n        table->InstanceInteracted = -1;\n        table->ContextPopupColumn = -1;\n        table->ReorderColumn = table->ResizedColumn = table->LastResizedColumn = -1;\n        table->AutoFitSingleColumn = -1;\n        table->HoveredColumnBody = table->HoveredColumnBorder = -1;\n        for (int n = 0; n < columns_count; n++)\n        {\n            ImGuiTableColumn* column = &table->Columns[n];\n            if (old_columns_to_preserve && n < old_columns_count)\n            {\n                // FIXME: We don't attempt to preserve column order in this path.\n                *column = old_columns_to_preserve[n];\n            }\n            else\n            {\n                float width_auto = column->WidthAuto;\n                *column = ImGuiTableColumn();\n                column->WidthAuto = width_auto;\n                column->IsPreserveWidthAuto = true; // Preserve WidthAuto when reinitializing a live table: not technically necessary but remove a visible flicker\n                column->IsEnabled = column->IsUserEnabled = column->IsUserEnabledNextFrame = true;\n            }\n            column->DisplayOrder = table->DisplayOrderToIndex[n] = (ImGuiTableColumnIdx)n;\n        }\n    }\n    if (old_columns_raw_data)\n        IM_FREE(old_columns_raw_data);\n\n    // Load settings\n    if (table->IsSettingsRequestLoad)\n        TableLoadSettings(table);\n\n    // Handle DPI/font resize\n    // This is designed to facilitate DPI changes with the assumption that e.g. style.CellPadding has been scaled as well.\n    // It will also react to changing fonts with mixed results. It doesn't need to be perfect but merely provide a decent transition.\n    // FIXME-DPI: Provide consistent standards for reference size. Perhaps using g.CurrentDpiScale would be more self explanatory.\n    // This is will lead us to non-rounded WidthRequest in columns, which should work but is a poorly tested path.\n    const float new_ref_scale_unit = g.FontSize; // g.Font->GetCharAdvance('A') ?\n    if (table->RefScale != 0.0f && table->RefScale != new_ref_scale_unit)\n    {\n        const float scale_factor = new_ref_scale_unit / table->RefScale;\n        //IMGUI_DEBUG_PRINT(\"[table] %08X RefScaleUnit %.3f -> %.3f, scaling width by %.3f\\n\", table->ID, table->RefScaleUnit, new_ref_scale_unit, scale_factor);\n        for (int n = 0; n < columns_count; n++)\n            table->Columns[n].WidthRequest = table->Columns[n].WidthRequest * scale_factor;\n    }\n    table->RefScale = new_ref_scale_unit;\n\n    // Disable output until user calls TableNextRow() or TableNextColumn() leading to the TableUpdateLayout() call..\n    // This is not strictly necessary but will reduce cases were \"out of table\" output will be misleading to the user.\n    // Because we cannot safely assert in EndTable() when no rows have been created, this seems like our best option.\n    inner_window->SkipItems = true;\n\n    // Clear names\n    // At this point the ->NameOffset field of each column will be invalid until TableUpdateLayout() or the first call to TableSetupColumn()\n    if (table->ColumnsNames.Buf.Size > 0)\n        table->ColumnsNames.Buf.resize(0);\n\n    // Apply queued resizing/reordering/hiding requests\n    TableBeginApplyRequests(table);\n\n    return true;\n}\n\n// For reference, the average total _allocation count_ for a table is:\n// + 0 (for ImGuiTable instance, we are pooling allocations in g.Tables[])\n// + 1 (for table->RawData allocated below)\n// + 1 (for table->ColumnsNames, if names are used)\n// Shared allocations for the maximum number of simultaneously nested tables (generally a very small number)\n// + 1 (for table->Splitter._Channels)\n// + 2 * active_channels_count (for ImDrawCmd and ImDrawIdx buffers inside channels)\n// Where active_channels_count is variable but often == columns_count or == columns_count + 1, see TableSetupDrawChannels() for details.\n// Unused channels don't perform their +2 allocations.\nvoid ImGui::TableBeginInitMemory(ImGuiTable* table, int columns_count)\n{\n    // Allocate single buffer for our arrays\n    const int columns_bit_array_size = (int)ImBitArrayGetStorageSizeInBytes(columns_count);\n    ImSpanAllocator<6> span_allocator;\n    span_allocator.Reserve(0, columns_count * sizeof(ImGuiTableColumn));\n    span_allocator.Reserve(1, columns_count * sizeof(ImGuiTableColumnIdx));\n    span_allocator.Reserve(2, columns_count * sizeof(ImGuiTableCellData), 4);\n    for (int n = 3; n < 6; n++)\n        span_allocator.Reserve(n, columns_bit_array_size);\n    table->RawData = IM_ALLOC(span_allocator.GetArenaSizeInBytes());\n    memset(table->RawData, 0, span_allocator.GetArenaSizeInBytes());\n    span_allocator.SetArenaBasePtr(table->RawData);\n    span_allocator.GetSpan(0, &table->Columns);\n    span_allocator.GetSpan(1, &table->DisplayOrderToIndex);\n    span_allocator.GetSpan(2, &table->RowCellData);\n    table->EnabledMaskByDisplayOrder = (ImU32*)span_allocator.GetSpanPtrBegin(3);\n    table->EnabledMaskByIndex = (ImU32*)span_allocator.GetSpanPtrBegin(4);\n    table->VisibleMaskByIndex = (ImU32*)span_allocator.GetSpanPtrBegin(5);\n}\n\n// Apply queued resizing/reordering/hiding requests\nvoid ImGui::TableBeginApplyRequests(ImGuiTable* table)\n{\n    // Handle resizing request\n    // (We process this in the TableBegin() of the first instance of each table)\n    // FIXME-TABLE: Contains columns if our work area doesn't allow for scrolling?\n    if (table->InstanceCurrent == 0)\n    {\n        if (table->ResizedColumn != -1 && table->ResizedColumnNextWidth != FLT_MAX)\n            TableSetColumnWidth(table->ResizedColumn, table->ResizedColumnNextWidth);\n        table->LastResizedColumn = table->ResizedColumn;\n        table->ResizedColumnNextWidth = FLT_MAX;\n        table->ResizedColumn = -1;\n\n        // Process auto-fit for single column, which is a special case for stretch columns and fixed columns with FixedSame policy.\n        // FIXME-TABLE: Would be nice to redistribute available stretch space accordingly to other weights, instead of giving it all to siblings.\n        if (table->AutoFitSingleColumn != -1)\n        {\n            TableSetColumnWidth(table->AutoFitSingleColumn, table->Columns[table->AutoFitSingleColumn].WidthAuto);\n            table->AutoFitSingleColumn = -1;\n        }\n    }\n\n    // Handle reordering request\n    // Note: we don't clear ReorderColumn after handling the request.\n    if (table->InstanceCurrent == 0)\n    {\n        if (table->HeldHeaderColumn == -1 && table->ReorderColumn != -1)\n            table->ReorderColumn = -1;\n        table->HeldHeaderColumn = -1;\n        if (table->ReorderColumn != -1 && table->ReorderColumnDir != 0)\n        {\n            // We need to handle reordering across hidden columns.\n            // In the configuration below, moving C to the right of E will lead to:\n            //    ... C [D] E  --->  ... [D] E  C   (Column name/index)\n            //    ... 2  3  4        ...  2  3  4   (Display order)\n            const int reorder_dir = table->ReorderColumnDir;\n            IM_ASSERT(reorder_dir == -1 || reorder_dir == +1);\n            IM_ASSERT(table->Flags & ImGuiTableFlags_Reorderable);\n            ImGuiTableColumn* src_column = &table->Columns[table->ReorderColumn];\n            ImGuiTableColumn* dst_column = &table->Columns[(reorder_dir == -1) ? src_column->PrevEnabledColumn : src_column->NextEnabledColumn];\n            IM_UNUSED(dst_column);\n            const int src_order = src_column->DisplayOrder;\n            const int dst_order = dst_column->DisplayOrder;\n            src_column->DisplayOrder = (ImGuiTableColumnIdx)dst_order;\n            for (int order_n = src_order + reorder_dir; order_n != dst_order + reorder_dir; order_n += reorder_dir)\n                table->Columns[table->DisplayOrderToIndex[order_n]].DisplayOrder -= (ImGuiTableColumnIdx)reorder_dir;\n            IM_ASSERT(dst_column->DisplayOrder == dst_order - reorder_dir);\n\n            // Display order is stored in both columns->IndexDisplayOrder and table->DisplayOrder[]. Rebuild later from the former.\n            for (int column_n = 0; column_n < table->ColumnsCount; column_n++)\n                table->DisplayOrderToIndex[table->Columns[column_n].DisplayOrder] = (ImGuiTableColumnIdx)column_n;\n            table->ReorderColumnDir = 0;\n            table->IsSettingsDirty = true;\n        }\n    }\n\n    // Handle display order reset request\n    if (table->IsResetDisplayOrderRequest)\n    {\n        for (int n = 0; n < table->ColumnsCount; n++)\n            table->DisplayOrderToIndex[n] = table->Columns[n].DisplayOrder = (ImGuiTableColumnIdx)n;\n        table->IsResetDisplayOrderRequest = false;\n        table->IsSettingsDirty = true;\n    }\n}\n\n// Adjust flags: default width mode + stretch columns are not allowed when auto extending\nstatic void TableSetupColumnFlags(ImGuiTable* table, ImGuiTableColumn* column, ImGuiTableColumnFlags flags_in)\n{\n    ImGuiTableColumnFlags flags = flags_in;\n\n    // Sizing Policy\n    if ((flags & ImGuiTableColumnFlags_WidthMask_) == 0)\n    {\n        const ImGuiTableFlags table_sizing_policy = (table->Flags & ImGuiTableFlags_SizingMask_);\n        if (table_sizing_policy == ImGuiTableFlags_SizingFixedFit || table_sizing_policy == ImGuiTableFlags_SizingFixedSame)\n            flags |= ImGuiTableColumnFlags_WidthFixed;\n        else\n            flags |= ImGuiTableColumnFlags_WidthStretch;\n    }\n    else\n    {\n        IM_ASSERT(ImIsPowerOfTwo(flags & ImGuiTableColumnFlags_WidthMask_)); // Check that only 1 of each set is used.\n    }\n\n    // Resize\n    if ((table->Flags & ImGuiTableFlags_Resizable) == 0)\n        flags |= ImGuiTableColumnFlags_NoResize;\n\n    // Sorting\n    if ((flags & ImGuiTableColumnFlags_NoSortAscending) && (flags & ImGuiTableColumnFlags_NoSortDescending))\n        flags |= ImGuiTableColumnFlags_NoSort;\n\n    // Indentation\n    if ((flags & ImGuiTableColumnFlags_IndentMask_) == 0)\n        flags |= (table->Columns.index_from_ptr(column) == 0) ? ImGuiTableColumnFlags_IndentEnable : ImGuiTableColumnFlags_IndentDisable;\n\n    // Alignment\n    //if ((flags & ImGuiTableColumnFlags_AlignMask_) == 0)\n    //    flags |= ImGuiTableColumnFlags_AlignCenter;\n    //IM_ASSERT(ImIsPowerOfTwo(flags & ImGuiTableColumnFlags_AlignMask_)); // Check that only 1 of each set is used.\n\n    // Preserve status flags\n    column->Flags = flags | (column->Flags & ImGuiTableColumnFlags_StatusMask_);\n\n    // Build an ordered list of available sort directions\n    column->SortDirectionsAvailCount = column->SortDirectionsAvailMask = column->SortDirectionsAvailList = 0;\n    if (table->Flags & ImGuiTableFlags_Sortable)\n    {\n        int count = 0, mask = 0, list = 0;\n        if ((flags & ImGuiTableColumnFlags_PreferSortAscending)  != 0 && (flags & ImGuiTableColumnFlags_NoSortAscending)  == 0) { mask |= 1 << ImGuiSortDirection_Ascending;  list |= ImGuiSortDirection_Ascending  << (count << 1); count++; }\n        if ((flags & ImGuiTableColumnFlags_PreferSortDescending) != 0 && (flags & ImGuiTableColumnFlags_NoSortDescending) == 0) { mask |= 1 << ImGuiSortDirection_Descending; list |= ImGuiSortDirection_Descending << (count << 1); count++; }\n        if ((flags & ImGuiTableColumnFlags_PreferSortAscending)  == 0 && (flags & ImGuiTableColumnFlags_NoSortAscending)  == 0) { mask |= 1 << ImGuiSortDirection_Ascending;  list |= ImGuiSortDirection_Ascending  << (count << 1); count++; }\n        if ((flags & ImGuiTableColumnFlags_PreferSortDescending) == 0 && (flags & ImGuiTableColumnFlags_NoSortDescending) == 0) { mask |= 1 << ImGuiSortDirection_Descending; list |= ImGuiSortDirection_Descending << (count << 1); count++; }\n        if ((table->Flags & ImGuiTableFlags_SortTristate) || count == 0) { mask |= 1 << ImGuiSortDirection_None; count++; }\n        column->SortDirectionsAvailList = (ImU8)list;\n        column->SortDirectionsAvailMask = (ImU8)mask;\n        column->SortDirectionsAvailCount = (ImU8)count;\n        ImGui::TableFixColumnSortDirection(table, column);\n    }\n}\n\n// Layout columns for the frame. This is in essence the followup to BeginTable() and this is our largest function.\n// Runs on the first call to TableNextRow(), to give a chance for TableSetupColumn() and other TableSetupXXXXX() functions to be called first.\n// FIXME-TABLE: Our width (and therefore our WorkRect) will be minimal in the first frame for _WidthAuto columns.\n// Increase feedback side-effect with widgets relying on WorkRect.Max.x... Maybe provide a default distribution for _WidthAuto columns?\nvoid ImGui::TableUpdateLayout(ImGuiTable* table)\n{\n    ImGuiContext& g = *GImGui;\n    IM_ASSERT(table->IsLayoutLocked == false);\n\n    const ImGuiTableFlags table_sizing_policy = (table->Flags & ImGuiTableFlags_SizingMask_);\n    table->IsDefaultDisplayOrder = true;\n    table->ColumnsEnabledCount = 0;\n    ImBitArrayClearAllBits(table->EnabledMaskByIndex, table->ColumnsCount);\n    ImBitArrayClearAllBits(table->EnabledMaskByDisplayOrder, table->ColumnsCount);\n    table->LeftMostEnabledColumn = -1;\n    table->MinColumnWidth = ImMax(1.0f, g.Style.FramePadding.x * 1.0f); // g.Style.ColumnsMinSpacing; // FIXME-TABLE\n\n    // [Part 1] Apply/lock Enabled and Order states. Calculate auto/ideal width for columns. Count fixed/stretch columns.\n    // Process columns in their visible orders as we are building the Prev/Next indices.\n    int count_fixed = 0;                // Number of columns that have fixed sizing policies\n    int count_stretch = 0;              // Number of columns that have stretch sizing policies\n    int prev_visible_column_idx = -1;\n    bool has_auto_fit_request = false;\n    bool has_resizable = false;\n    float stretch_sum_width_auto = 0.0f;\n    float fixed_max_width_auto = 0.0f;\n    for (int order_n = 0; order_n < table->ColumnsCount; order_n++)\n    {\n        const int column_n = table->DisplayOrderToIndex[order_n];\n        if (column_n != order_n)\n            table->IsDefaultDisplayOrder = false;\n        ImGuiTableColumn* column = &table->Columns[column_n];\n\n        // Clear column setup if not submitted by user. Currently we make it mandatory to call TableSetupColumn() every frame.\n        // It would easily work without but we're not ready to guarantee it since e.g. names need resubmission anyway.\n        // We take a slight shortcut but in theory we could be calling TableSetupColumn() here with dummy values, it should yield the same effect.\n        if (table->DeclColumnsCount <= column_n)\n        {\n            TableSetupColumnFlags(table, column, ImGuiTableColumnFlags_None);\n            column->NameOffset = -1;\n            column->UserID = 0;\n            column->InitStretchWeightOrWidth = -1.0f;\n        }\n\n        // Update Enabled state, mark settings and sort specs dirty\n        if (!(table->Flags & ImGuiTableFlags_Hideable) || (column->Flags & ImGuiTableColumnFlags_NoHide))\n            column->IsUserEnabledNextFrame = true;\n        if (column->IsUserEnabled != column->IsUserEnabledNextFrame)\n        {\n            column->IsUserEnabled = column->IsUserEnabledNextFrame;\n            table->IsSettingsDirty = true;\n        }\n        column->IsEnabled = column->IsUserEnabled && (column->Flags & ImGuiTableColumnFlags_Disabled) == 0;\n\n        if (column->SortOrder != -1 && !column->IsEnabled)\n            table->IsSortSpecsDirty = true;\n        if (column->SortOrder > 0 && !(table->Flags & ImGuiTableFlags_SortMulti))\n            table->IsSortSpecsDirty = true;\n\n        // Auto-fit unsized columns\n        const bool start_auto_fit = (column->Flags & ImGuiTableColumnFlags_WidthFixed) ? (column->WidthRequest < 0.0f) : (column->StretchWeight < 0.0f);\n        if (start_auto_fit)\n            column->AutoFitQueue = column->CannotSkipItemsQueue = (1 << 3) - 1; // Fit for three frames\n\n        if (!column->IsEnabled)\n        {\n            column->IndexWithinEnabledSet = -1;\n            continue;\n        }\n\n        // Mark as enabled and link to previous/next enabled column\n        column->PrevEnabledColumn = (ImGuiTableColumnIdx)prev_visible_column_idx;\n        column->NextEnabledColumn = -1;\n        if (prev_visible_column_idx != -1)\n            table->Columns[prev_visible_column_idx].NextEnabledColumn = (ImGuiTableColumnIdx)column_n;\n        else\n            table->LeftMostEnabledColumn = (ImGuiTableColumnIdx)column_n;\n        column->IndexWithinEnabledSet = table->ColumnsEnabledCount++;\n        ImBitArraySetBit(table->EnabledMaskByIndex, column_n);\n        ImBitArraySetBit(table->EnabledMaskByDisplayOrder, column->DisplayOrder);\n        prev_visible_column_idx = column_n;\n        IM_ASSERT(column->IndexWithinEnabledSet <= column->DisplayOrder);\n\n        // Calculate ideal/auto column width (that's the width required for all contents to be visible without clipping)\n        // Combine width from regular rows + width from headers unless requested not to.\n        if (!column->IsPreserveWidthAuto && table->InstanceCurrent == 0)\n            column->WidthAuto = TableGetColumnWidthAuto(table, column);\n\n        // Non-resizable columns keep their requested width (apply user value regardless of IsPreserveWidthAuto)\n        const bool column_is_resizable = (column->Flags & ImGuiTableColumnFlags_NoResize) == 0;\n        if (column_is_resizable)\n            has_resizable = true;\n        if ((column->Flags & ImGuiTableColumnFlags_WidthFixed) && column->InitStretchWeightOrWidth > 0.0f && !column_is_resizable)\n            column->WidthAuto = column->InitStretchWeightOrWidth;\n\n        if (column->AutoFitQueue != 0x00)\n            has_auto_fit_request = true;\n        if (column->Flags & ImGuiTableColumnFlags_WidthStretch)\n        {\n            stretch_sum_width_auto += column->WidthAuto;\n            count_stretch++;\n        }\n        else\n        {\n            fixed_max_width_auto = ImMax(fixed_max_width_auto, column->WidthAuto);\n            count_fixed++;\n        }\n    }\n    if ((table->Flags & ImGuiTableFlags_Sortable) && table->SortSpecsCount == 0 && !(table->Flags & ImGuiTableFlags_SortTristate))\n        table->IsSortSpecsDirty = true;\n    table->RightMostEnabledColumn = (ImGuiTableColumnIdx)prev_visible_column_idx;\n    IM_ASSERT(table->LeftMostEnabledColumn >= 0 && table->RightMostEnabledColumn >= 0);\n\n    // [Part 2] Disable child window clipping while fitting columns. This is not strictly necessary but makes it possible to avoid\n    // the column fitting having to wait until the first visible frame of the child container (may or not be a good thing). Also see #6510.\n    // FIXME-TABLE: for always auto-resizing columns may not want to do that all the time.\n    if (has_auto_fit_request && table->OuterWindow != table->InnerWindow)\n        table->InnerWindow->SkipItems = false;\n    if (has_auto_fit_request)\n        table->IsSettingsDirty = true;\n\n    // [Part 3] Fix column flags and record a few extra information.\n    float sum_width_requests = 0.0f;    // Sum of all width for fixed and auto-resize columns, excluding width contributed by Stretch columns but including spacing/padding.\n    float stretch_sum_weights = 0.0f;   // Sum of all weights for stretch columns.\n    table->LeftMostStretchedColumn = table->RightMostStretchedColumn = -1;\n    for (int column_n = 0; column_n < table->ColumnsCount; column_n++)\n    {\n        if (!IM_BITARRAY_TESTBIT(table->EnabledMaskByIndex, column_n))\n            continue;\n        ImGuiTableColumn* column = &table->Columns[column_n];\n\n        const bool column_is_resizable = (column->Flags & ImGuiTableColumnFlags_NoResize) == 0;\n        if (column->Flags & ImGuiTableColumnFlags_WidthFixed)\n        {\n            // Apply same widths policy\n            float width_auto = column->WidthAuto;\n            if (table_sizing_policy == ImGuiTableFlags_SizingFixedSame && (column->AutoFitQueue != 0x00 || !column_is_resizable))\n                width_auto = fixed_max_width_auto;\n\n            // Apply automatic width\n            // Latch initial size for fixed columns and update it constantly for auto-resizing column (unless clipped!)\n            if (column->AutoFitQueue != 0x00)\n                column->WidthRequest = width_auto;\n            else if ((column->Flags & ImGuiTableColumnFlags_WidthFixed) && !column_is_resizable && column->IsRequestOutput)\n                column->WidthRequest = width_auto;\n\n            // FIXME-TABLE: Increase minimum size during init frame to avoid biasing auto-fitting widgets\n            // (e.g. TextWrapped) too much. Otherwise what tends to happen is that TextWrapped would output a very\n            // large height (= first frame scrollbar display very off + clipper would skip lots of items).\n            // This is merely making the side-effect less extreme, but doesn't properly fixes it.\n            // FIXME: Move this to ->WidthGiven to avoid temporary lossyless?\n            // FIXME: This break IsPreserveWidthAuto from not flickering if the stored WidthAuto was smaller.\n            if (column->AutoFitQueue > 0x01 && table->IsInitializing && !column->IsPreserveWidthAuto)\n                column->WidthRequest = ImMax(column->WidthRequest, table->MinColumnWidth * 4.0f); // FIXME-TABLE: Another constant/scale?\n            sum_width_requests += column->WidthRequest;\n        }\n        else\n        {\n            // Initialize stretch weight\n            if (column->AutoFitQueue != 0x00 || column->StretchWeight < 0.0f || !column_is_resizable)\n            {\n                if (column->InitStretchWeightOrWidth > 0.0f)\n                    column->StretchWeight = column->InitStretchWeightOrWidth;\n                else if (table_sizing_policy == ImGuiTableFlags_SizingStretchProp)\n                    column->StretchWeight = (column->WidthAuto / stretch_sum_width_auto) * count_stretch;\n                else\n                    column->StretchWeight = 1.0f;\n            }\n\n            stretch_sum_weights += column->StretchWeight;\n            if (table->LeftMostStretchedColumn == -1 || table->Columns[table->LeftMostStretchedColumn].DisplayOrder > column->DisplayOrder)\n                table->LeftMostStretchedColumn = (ImGuiTableColumnIdx)column_n;\n            if (table->RightMostStretchedColumn == -1 || table->Columns[table->RightMostStretchedColumn].DisplayOrder < column->DisplayOrder)\n                table->RightMostStretchedColumn = (ImGuiTableColumnIdx)column_n;\n        }\n        column->IsPreserveWidthAuto = false;\n        sum_width_requests += table->CellPaddingX * 2.0f;\n    }\n    table->ColumnsEnabledFixedCount = (ImGuiTableColumnIdx)count_fixed;\n    table->ColumnsStretchSumWeights = stretch_sum_weights;\n\n    // [Part 4] Apply final widths based on requested widths\n    const ImRect work_rect = table->WorkRect;\n    const float width_spacings = (table->OuterPaddingX * 2.0f) + (table->CellSpacingX1 + table->CellSpacingX2) * (table->ColumnsEnabledCount - 1);\n    const float width_removed = (table->HasScrollbarYPrev && !table->InnerWindow->ScrollbarY) ? g.Style.ScrollbarSize : 0.0f; // To synchronize decoration width of synced tables with mismatching scrollbar state (#5920)\n    const float width_avail = ImMax(1.0f, (((table->Flags & ImGuiTableFlags_ScrollX) && table->InnerWidth == 0.0f) ? table->InnerClipRect.GetWidth() : work_rect.GetWidth()) - width_removed);\n    const float width_avail_for_stretched_columns = width_avail - width_spacings - sum_width_requests;\n    float width_remaining_for_stretched_columns = width_avail_for_stretched_columns;\n    table->ColumnsGivenWidth = width_spacings + (table->CellPaddingX * 2.0f) * table->ColumnsEnabledCount;\n    for (int column_n = 0; column_n < table->ColumnsCount; column_n++)\n    {\n        if (!IM_BITARRAY_TESTBIT(table->EnabledMaskByIndex, column_n))\n            continue;\n        ImGuiTableColumn* column = &table->Columns[column_n];\n\n        // Allocate width for stretched/weighted columns (StretchWeight gets converted into WidthRequest)\n        if (column->Flags & ImGuiTableColumnFlags_WidthStretch)\n        {\n            float weight_ratio = column->StretchWeight / stretch_sum_weights;\n            column->WidthRequest = IM_TRUNC(ImMax(width_avail_for_stretched_columns * weight_ratio, table->MinColumnWidth) + 0.01f);\n            width_remaining_for_stretched_columns -= column->WidthRequest;\n        }\n\n        // [Resize Rule 1] The right-most Visible column is not resizable if there is at least one Stretch column\n        // See additional comments in TableSetColumnWidth().\n        if (column->NextEnabledColumn == -1 && table->LeftMostStretchedColumn != -1)\n            column->Flags |= ImGuiTableColumnFlags_NoDirectResize_;\n\n        // Assign final width, record width in case we will need to shrink\n        column->WidthGiven = ImTrunc(ImMax(column->WidthRequest, table->MinColumnWidth));\n        table->ColumnsGivenWidth += column->WidthGiven;\n    }\n\n    // [Part 5] Redistribute stretch remainder width due to rounding (remainder width is < 1.0f * number of Stretch column).\n    // Using right-to-left distribution (more likely to match resizing cursor).\n    if (width_remaining_for_stretched_columns >= 1.0f && !(table->Flags & ImGuiTableFlags_PreciseWidths))\n        for (int order_n = table->ColumnsCount - 1; stretch_sum_weights > 0.0f && width_remaining_for_stretched_columns >= 1.0f && order_n >= 0; order_n--)\n        {\n            if (!IM_BITARRAY_TESTBIT(table->EnabledMaskByDisplayOrder, order_n))\n                continue;\n            ImGuiTableColumn* column = &table->Columns[table->DisplayOrderToIndex[order_n]];\n            if (!(column->Flags & ImGuiTableColumnFlags_WidthStretch))\n                continue;\n            column->WidthRequest += 1.0f;\n            column->WidthGiven += 1.0f;\n            width_remaining_for_stretched_columns -= 1.0f;\n        }\n\n    // Determine if table is hovered which will be used to flag columns as hovered.\n    // - In principle we'd like to use the equivalent of IsItemHovered(ImGuiHoveredFlags_AllowWhenBlockedByActiveItem),\n    //   but because our item is partially submitted at this point we use ItemHoverable() and a workaround (temporarily\n    //   clear ActiveId, which is equivalent to the change provided by _AllowWhenBLockedByActiveItem).\n    // - This allows columns to be marked as hovered when e.g. clicking a button inside the column, or using drag and drop.\n    ImGuiTableInstanceData* table_instance = TableGetInstanceData(table, table->InstanceCurrent);\n    table_instance->HoveredRowLast = table_instance->HoveredRowNext;\n    table_instance->HoveredRowNext = -1;\n    table->HoveredColumnBody = table->HoveredColumnBorder = -1;\n    const ImRect mouse_hit_rect(table->OuterRect.Min.x, table->OuterRect.Min.y, table->OuterRect.Max.x, ImMax(table->OuterRect.Max.y, table->OuterRect.Min.y + table_instance->LastOuterHeight));\n    const ImGuiID backup_active_id = g.ActiveId;\n    g.ActiveId = 0;\n    const bool is_hovering_table = ItemHoverable(mouse_hit_rect, 0, ImGuiItemFlags_None);\n    g.ActiveId = backup_active_id;\n\n    // Determine skewed MousePos.x to support angled headers.\n    float mouse_skewed_x = g.IO.MousePos.x;\n    if (table->AngledHeadersHeight > 0.0f)\n        if (g.IO.MousePos.y >= table->OuterRect.Min.y && g.IO.MousePos.y <= table->OuterRect.Min.y + table->AngledHeadersHeight)\n            mouse_skewed_x += ImTrunc((table->OuterRect.Min.y + table->AngledHeadersHeight - g.IO.MousePos.y) * table->AngledHeadersSlope);\n\n    // [Part 6] Setup final position, offset, skip/clip states and clipping rectangles, detect hovered column\n    // Process columns in their visible orders as we are comparing the visible order and adjusting host_clip_rect while looping.\n    int visible_n = 0;\n    bool has_at_least_one_column_requesting_output = false;\n    bool offset_x_frozen = (table->FreezeColumnsCount > 0);\n    float offset_x = ((table->FreezeColumnsCount > 0) ? table->OuterRect.Min.x : work_rect.Min.x) + table->OuterPaddingX - table->CellSpacingX1;\n    ImRect host_clip_rect = table->InnerClipRect;\n    //host_clip_rect.Max.x += table->CellPaddingX + table->CellSpacingX2;\n    ImBitArrayClearAllBits(table->VisibleMaskByIndex, table->ColumnsCount);\n    for (int order_n = 0; order_n < table->ColumnsCount; order_n++)\n    {\n        const int column_n = table->DisplayOrderToIndex[order_n];\n        ImGuiTableColumn* column = &table->Columns[column_n];\n\n        // Initial nav layer: using FreezeRowsCount, NOT FreezeRowsRequest, so Header line changes layer when frozen\n        column->NavLayerCurrent = (ImS8)(table->FreezeRowsCount > 0 ? ImGuiNavLayer_Menu : (ImGuiNavLayer)table->NavLayer);\n\n        if (offset_x_frozen && table->FreezeColumnsCount == visible_n)\n        {\n            offset_x += work_rect.Min.x - table->OuterRect.Min.x;\n            offset_x_frozen = false;\n        }\n\n        // Clear status flags\n        column->Flags &= ~ImGuiTableColumnFlags_StatusMask_;\n\n        if (!IM_BITARRAY_TESTBIT(table->EnabledMaskByDisplayOrder, order_n))\n        {\n            // Hidden column: clear a few fields and we are done with it for the remainder of the function.\n            // We set a zero-width clip rect but set Min.y/Max.y properly to not interfere with the clipper.\n            column->MinX = column->MaxX = column->WorkMinX = column->ClipRect.Min.x = column->ClipRect.Max.x = offset_x;\n            column->WidthGiven = 0.0f;\n            column->ClipRect.Min.y = work_rect.Min.y;\n            column->ClipRect.Max.y = FLT_MAX;\n            column->ClipRect.ClipWithFull(host_clip_rect);\n            column->IsVisibleX = column->IsVisibleY = column->IsRequestOutput = false;\n            column->IsSkipItems = true;\n            column->ItemWidth = 1.0f;\n            continue;\n        }\n\n        // Lock start position\n        column->MinX = offset_x;\n\n        // Lock width based on start position and minimum/maximum width for this position\n        column->WidthMax = TableCalcMaxColumnWidth(table, column_n);\n        column->WidthGiven = ImMin(column->WidthGiven, column->WidthMax);\n        column->WidthGiven = ImMax(column->WidthGiven, ImMin(column->WidthRequest, table->MinColumnWidth));\n        column->MaxX = offset_x + column->WidthGiven + table->CellSpacingX1 + table->CellSpacingX2 + table->CellPaddingX * 2.0f;\n\n        // Lock other positions\n        // - ClipRect.Min.x: Because merging draw commands doesn't compare min boundaries, we make ClipRect.Min.x match left bounds to be consistent regardless of merging.\n        // - ClipRect.Max.x: using WorkMaxX instead of MaxX (aka including padding) makes things more consistent when resizing down, tho slightly detrimental to visibility in very-small column.\n        // - ClipRect.Max.x: using MaxX makes it easier for header to receive hover highlight with no discontinuity and display sorting arrow.\n        // - FIXME-TABLE: We want equal width columns to have equal (ClipRect.Max.x - WorkMinX) width, which means ClipRect.max.x cannot stray off host_clip_rect.Max.x else right-most column may appear shorter.\n        const float previous_instance_work_min_x = column->WorkMinX;\n        column->WorkMinX = column->MinX + table->CellPaddingX + table->CellSpacingX1;\n        column->WorkMaxX = column->MaxX - table->CellPaddingX - table->CellSpacingX2; // Expected max\n        column->ItemWidth = ImTrunc(column->WidthGiven * 0.65f);\n        column->ClipRect.Min.x = column->MinX;\n        column->ClipRect.Min.y = work_rect.Min.y;\n        column->ClipRect.Max.x = column->MaxX; //column->WorkMaxX;\n        column->ClipRect.Max.y = FLT_MAX;\n        column->ClipRect.ClipWithFull(host_clip_rect);\n\n        // Mark column as Clipped (not in sight)\n        // Note that scrolling tables (where inner_window != outer_window) handle Y clipped earlier in BeginTable() so IsVisibleY really only applies to non-scrolling tables.\n        // FIXME-TABLE: Because InnerClipRect.Max.y is conservatively ==outer_window->ClipRect.Max.y, we never can mark columns _Above_ the scroll line as not IsVisibleY.\n        // Taking advantage of LastOuterHeight would yield good results there...\n        // FIXME-TABLE: Y clipping is disabled because it effectively means not submitting will reduce contents width which is fed to outer_window->DC.CursorMaxPos.x,\n        // and this may be used (e.g. typically by outer_window using AlwaysAutoResize or outer_window's horizontal scrollbar, but could be something else).\n        // Possible solution to preserve last known content width for clipped column. Test 'table_reported_size' fails when enabling Y clipping and window is resized small.\n        column->IsVisibleX = (column->ClipRect.Max.x > column->ClipRect.Min.x);\n        column->IsVisibleY = true; // (column->ClipRect.Max.y > column->ClipRect.Min.y);\n        const bool is_visible = column->IsVisibleX; //&& column->IsVisibleY;\n        if (is_visible)\n            ImBitArraySetBit(table->VisibleMaskByIndex, column_n);\n\n        // Mark column as requesting output from user. Note that fixed + non-resizable sets are auto-fitting at all times and therefore always request output.\n        column->IsRequestOutput = is_visible || column->AutoFitQueue != 0 || column->CannotSkipItemsQueue != 0;\n\n        // Mark column as SkipItems (ignoring all items/layout)\n        // (table->HostSkipItems is a copy of inner_window->SkipItems before we cleared it above in Part 2)\n        column->IsSkipItems = !column->IsEnabled || table->HostSkipItems;\n        if (column->IsSkipItems)\n            IM_ASSERT(!is_visible);\n        if (column->IsRequestOutput && !column->IsSkipItems)\n            has_at_least_one_column_requesting_output = true;\n\n        // Update status flags\n        column->Flags |= ImGuiTableColumnFlags_IsEnabled;\n        if (is_visible)\n            column->Flags |= ImGuiTableColumnFlags_IsVisible;\n        if (column->SortOrder != -1)\n            column->Flags |= ImGuiTableColumnFlags_IsSorted;\n\n        // Detect hovered column\n        if (is_hovering_table && mouse_skewed_x >= column->ClipRect.Min.x && mouse_skewed_x < column->ClipRect.Max.x)\n        {\n            column->Flags |= ImGuiTableColumnFlags_IsHovered;\n            table->HoveredColumnBody = (ImGuiTableColumnIdx)column_n;\n        }\n\n        // Alignment\n        // FIXME-TABLE: This align based on the whole column width, not per-cell, and therefore isn't useful in\n        // many cases (to be able to honor this we might be able to store a log of cells width, per row, for\n        // visible rows, but nav/programmatic scroll would have visible artifacts.)\n        //if (column->Flags & ImGuiTableColumnFlags_AlignRight)\n        //    column->WorkMinX = ImMax(column->WorkMinX, column->MaxX - column->ContentWidthRowsUnfrozen);\n        //else if (column->Flags & ImGuiTableColumnFlags_AlignCenter)\n        //    column->WorkMinX = ImLerp(column->WorkMinX, ImMax(column->StartX, column->MaxX - column->ContentWidthRowsUnfrozen), 0.5f);\n\n        // Reset content width variables\n        if (table->InstanceCurrent == 0)\n        {\n            column->ContentMaxXFrozen = column->WorkMinX;\n            column->ContentMaxXUnfrozen = column->WorkMinX;\n            column->ContentMaxXHeadersUsed = column->WorkMinX;\n            column->ContentMaxXHeadersIdeal = column->WorkMinX;\n        }\n        else\n        {\n            // As we store an absolute value to make per-cell updates faster, we need to offset values used for width computation.\n            const float offset_from_previous_instance = column->WorkMinX - previous_instance_work_min_x;\n            column->ContentMaxXFrozen += offset_from_previous_instance;\n            column->ContentMaxXUnfrozen += offset_from_previous_instance;\n            column->ContentMaxXHeadersUsed += offset_from_previous_instance;\n            column->ContentMaxXHeadersIdeal += offset_from_previous_instance;\n        }\n\n        // Don't decrement auto-fit counters until container window got a chance to submit its items\n        if (table->HostSkipItems == false && table->InstanceCurrent == 0)\n        {\n            column->AutoFitQueue >>= 1;\n            column->CannotSkipItemsQueue >>= 1;\n        }\n\n        if (visible_n < table->FreezeColumnsCount)\n            host_clip_rect.Min.x = ImClamp(column->MaxX + TABLE_BORDER_SIZE, host_clip_rect.Min.x, host_clip_rect.Max.x);\n\n        offset_x += column->WidthGiven + table->CellSpacingX1 + table->CellSpacingX2 + table->CellPaddingX * 2.0f;\n        visible_n++;\n    }\n\n    // In case the table is visible (e.g. decorations) but all columns clipped, we keep a column visible.\n    // Else if give no chance to a clipper-savy user to submit rows and therefore total contents height used by scrollbar.\n    if (has_at_least_one_column_requesting_output == false)\n    {\n        table->Columns[table->LeftMostEnabledColumn].IsRequestOutput = true;\n        table->Columns[table->LeftMostEnabledColumn].IsSkipItems = false;\n    }\n\n    // [Part 7] Detect/store when we are hovering the unused space after the right-most column (so e.g. context menus can react on it)\n    // Clear Resizable flag if none of our column are actually resizable (either via an explicit _NoResize flag, either\n    // because of using _WidthAuto/_WidthStretch). This will hide the resizing option from the context menu.\n    const float unused_x1 = ImMax(table->WorkRect.Min.x, table->Columns[table->RightMostEnabledColumn].ClipRect.Max.x);\n    if (is_hovering_table && table->HoveredColumnBody == -1)\n        if (mouse_skewed_x >= unused_x1)\n            table->HoveredColumnBody = (ImGuiTableColumnIdx)table->ColumnsCount;\n    if (has_resizable == false && (table->Flags & ImGuiTableFlags_Resizable))\n        table->Flags &= ~ImGuiTableFlags_Resizable;\n\n    table->IsActiveIdAliveBeforeTable = (g.ActiveIdIsAlive != 0);\n\n    // [Part 8] Lock actual OuterRect/WorkRect right-most position.\n    // This is done late to handle the case of fixed-columns tables not claiming more widths that they need.\n    // Because of this we are careful with uses of WorkRect and InnerClipRect before this point.\n    if (table->RightMostStretchedColumn != -1)\n        table->Flags &= ~ImGuiTableFlags_NoHostExtendX;\n    if (table->Flags & ImGuiTableFlags_NoHostExtendX)\n    {\n        table->OuterRect.Max.x = table->WorkRect.Max.x = unused_x1;\n        table->InnerClipRect.Max.x = ImMin(table->InnerClipRect.Max.x, unused_x1);\n    }\n    table->InnerWindow->ParentWorkRect = table->WorkRect;\n    table->BorderX1 = table->InnerClipRect.Min.x;\n    table->BorderX2 = table->InnerClipRect.Max.x;\n\n    // Setup window's WorkRect.Max.y for GetContentRegionAvail(). Other values will be updated in each TableBeginCell() call.\n    float window_content_max_y;\n    if (table->Flags & ImGuiTableFlags_NoHostExtendY)\n        window_content_max_y = table->OuterRect.Max.y;\n    else\n        window_content_max_y = ImMax(table->InnerWindow->ContentRegionRect.Max.y, (table->Flags & ImGuiTableFlags_ScrollY) ? 0.0f : table->OuterRect.Max.y);\n    table->InnerWindow->WorkRect.Max.y = ImClamp(window_content_max_y - g.Style.CellPadding.y, table->InnerWindow->WorkRect.Min.y, table->InnerWindow->WorkRect.Max.y);\n\n    // [Part 9] Allocate draw channels and setup background cliprect\n    TableSetupDrawChannels(table);\n\n    // [Part 10] Hit testing on borders\n    if (table->Flags & ImGuiTableFlags_Resizable)\n        TableUpdateBorders(table);\n    table_instance->LastTopHeadersRowHeight = 0.0f;\n    table->IsLayoutLocked = true;\n    table->IsUsingHeaders = false;\n\n    // Highlight header\n    table->HighlightColumnHeader = -1;\n    if (table->IsContextPopupOpen && table->ContextPopupColumn != -1 && table->InstanceInteracted == table->InstanceCurrent)\n        table->HighlightColumnHeader = table->ContextPopupColumn;\n    else if ((table->Flags & ImGuiTableFlags_HighlightHoveredColumn) && table->HoveredColumnBody != -1 && table->HoveredColumnBody != table->ColumnsCount && table->HoveredColumnBorder == -1)\n        if (g.ActiveId == 0 || (table->IsActiveIdInTable || g.DragDropActive))\n            table->HighlightColumnHeader = table->HoveredColumnBody;\n\n    // [Part 11] Default context menu\n    // - To append to this menu: you can call TableBeginContextMenuPopup()/.../EndPopup().\n    // - To modify or replace this: set table->IsContextPopupNoDefaultContents = true, then call TableBeginContextMenuPopup()/.../EndPopup().\n    // - You may call TableDrawDefaultContextMenu() with selected flags to display specific sections of the default menu,\n    //   e.g. TableDrawDefaultContextMenu(table, table->Flags & ~ImGuiTableFlags_Hideable) will display everything EXCEPT columns visibility options.\n    if (table->DisableDefaultContextMenu == false && TableBeginContextMenuPopup(table))\n    {\n        TableDrawDefaultContextMenu(table, table->Flags);\n        EndPopup();\n    }\n\n    // [Part 12] Sanitize and build sort specs before we have a chance to use them for display.\n    // This path will only be exercised when sort specs are modified before header rows (e.g. init or visibility change)\n    if (table->IsSortSpecsDirty && (table->Flags & ImGuiTableFlags_Sortable))\n        TableSortSpecsBuild(table);\n\n    // [Part 13] Setup inner window decoration size (for scrolling / nav tracking to properly take account of frozen rows/columns)\n    if (table->FreezeColumnsRequest > 0)\n        table->InnerWindow->DecoInnerSizeX1 = table->Columns[table->DisplayOrderToIndex[table->FreezeColumnsRequest - 1]].MaxX - table->OuterRect.Min.x;\n    if (table->FreezeRowsRequest > 0)\n        table->InnerWindow->DecoInnerSizeY1 = table_instance->LastFrozenHeight;\n    table_instance->LastFrozenHeight = 0.0f;\n\n    // Initial state\n    ImGuiWindow* inner_window = table->InnerWindow;\n    if (table->Flags & ImGuiTableFlags_NoClip)\n        table->DrawSplitter->SetCurrentChannel(inner_window->DrawList, TABLE_DRAW_CHANNEL_NOCLIP);\n    else\n        inner_window->DrawList->PushClipRect(inner_window->InnerClipRect.Min, inner_window->InnerClipRect.Max, false); // FIXME: use table->InnerClipRect?\n}\n\n// Process hit-testing on resizing borders. Actual size change will be applied in EndTable()\n// - Set table->HoveredColumnBorder with a short delay/timer to reduce visual feedback noise.\nvoid ImGui::TableUpdateBorders(ImGuiTable* table)\n{\n    ImGuiContext& g = *GImGui;\n    IM_ASSERT(table->Flags & ImGuiTableFlags_Resizable);\n\n    // At this point OuterRect height may be zero or under actual final height, so we rely on temporal coherency and\n    // use the final height from last frame. Because this is only affecting _interaction_ with columns, it is not\n    // really problematic (whereas the actual visual will be displayed in EndTable() and using the current frame height).\n    // Actual columns highlight/render will be performed in EndTable() and not be affected.\n    ImGuiTableInstanceData* table_instance = TableGetInstanceData(table, table->InstanceCurrent);\n    const float hit_half_width = ImTrunc(TABLE_RESIZE_SEPARATOR_HALF_THICKNESS * g.CurrentDpiScale);\n    const float hit_y1 = (table->FreezeRowsCount >= 1 ? table->OuterRect.Min.y : table->WorkRect.Min.y) + table->AngledHeadersHeight;\n    const float hit_y2_body = ImMax(table->OuterRect.Max.y, hit_y1 + table_instance->LastOuterHeight - table->AngledHeadersHeight);\n    const float hit_y2_head = hit_y1 + table_instance->LastTopHeadersRowHeight;\n\n    for (int order_n = 0; order_n < table->ColumnsCount; order_n++)\n    {\n        if (!IM_BITARRAY_TESTBIT(table->EnabledMaskByDisplayOrder, order_n))\n            continue;\n\n        const int column_n = table->DisplayOrderToIndex[order_n];\n        ImGuiTableColumn* column = &table->Columns[column_n];\n        if (column->Flags & (ImGuiTableColumnFlags_NoResize | ImGuiTableColumnFlags_NoDirectResize_))\n            continue;\n\n        // ImGuiTableFlags_NoBordersInBodyUntilResize will be honored in TableDrawBorders()\n        const float border_y2_hit = (table->Flags & ImGuiTableFlags_NoBordersInBody) ? hit_y2_head : hit_y2_body;\n        if ((table->Flags & ImGuiTableFlags_NoBordersInBody) && table->IsUsingHeaders == false)\n            continue;\n\n        if (!column->IsVisibleX && table->LastResizedColumn != column_n)\n            continue;\n\n        ImGuiID column_id = TableGetColumnResizeID(table, column_n, table->InstanceCurrent);\n        ImRect hit_rect(column->MaxX - hit_half_width, hit_y1, column->MaxX + hit_half_width, border_y2_hit);\n        ItemAdd(hit_rect, column_id, NULL, ImGuiItemFlags_NoNav);\n        //GetForegroundDrawList()->AddRect(hit_rect.Min, hit_rect.Max, IM_COL32(255, 0, 0, 100));\n\n        bool hovered = false, held = false;\n        bool pressed = ButtonBehavior(hit_rect, column_id, &hovered, &held, ImGuiButtonFlags_FlattenChildren | ImGuiButtonFlags_PressedOnClick | ImGuiButtonFlags_PressedOnDoubleClick | ImGuiButtonFlags_NoNavFocus);\n        if (pressed && IsMouseDoubleClicked(0))\n        {\n            TableSetColumnWidthAutoSingle(table, column_n);\n            ClearActiveID();\n            held = false;\n        }\n        if (held)\n        {\n            if (table->LastResizedColumn == -1)\n                table->ResizeLockMinContentsX2 = table->RightMostEnabledColumn != -1 ? table->Columns[table->RightMostEnabledColumn].MaxX : -FLT_MAX;\n            table->ResizedColumn = (ImGuiTableColumnIdx)column_n;\n            table->InstanceInteracted = table->InstanceCurrent;\n        }\n        if ((hovered && g.HoveredIdTimer > TABLE_RESIZE_SEPARATOR_FEEDBACK_TIMER) || held)\n        {\n            table->HoveredColumnBorder = (ImGuiTableColumnIdx)column_n;\n            SetMouseCursor(ImGuiMouseCursor_ResizeEW);\n        }\n    }\n}\n\nvoid    ImGui::EndTable()\n{\n    ImGuiContext& g = *GImGui;\n    ImGuiTable* table = g.CurrentTable;\n    if (table == NULL)\n    {\n        IM_ASSERT_USER_ERROR(table != NULL, \"EndTable() call should only be done while in BeginTable() scope!\");\n        return;\n    }\n\n    // This assert would be very useful to catch a common error... unfortunately it would probably trigger in some\n    // cases, and for consistency user may sometimes output empty tables (and still benefit from e.g. outer border)\n    //IM_ASSERT(table->IsLayoutLocked && \"Table unused: never called TableNextRow(), is that the intent?\");\n\n    // If the user never got to call TableNextRow() or TableNextColumn(), we call layout ourselves to ensure all our\n    // code paths are consistent (instead of just hoping that TableBegin/TableEnd will work), get borders drawn, etc.\n    if (!table->IsLayoutLocked)\n        TableUpdateLayout(table);\n\n    const ImGuiTableFlags flags = table->Flags;\n    ImGuiWindow* inner_window = table->InnerWindow;\n    ImGuiWindow* outer_window = table->OuterWindow;\n    ImGuiTableTempData* temp_data = table->TempData;\n    IM_ASSERT(inner_window == g.CurrentWindow);\n    IM_ASSERT(outer_window == inner_window || outer_window == inner_window->ParentWindow);\n\n    if (table->IsInsideRow)\n        TableEndRow(table);\n\n    // Context menu in columns body\n    if (flags & ImGuiTableFlags_ContextMenuInBody)\n        if (table->HoveredColumnBody != -1 && !IsAnyItemHovered() && IsMouseReleased(ImGuiMouseButton_Right))\n            TableOpenContextMenu((int)table->HoveredColumnBody);\n\n    // Finalize table height\n    ImGuiTableInstanceData* table_instance = TableGetInstanceData(table, table->InstanceCurrent);\n    inner_window->DC.PrevLineSize = temp_data->HostBackupPrevLineSize;\n    inner_window->DC.CurrLineSize = temp_data->HostBackupCurrLineSize;\n    inner_window->DC.CursorMaxPos = temp_data->HostBackupCursorMaxPos;\n    const float inner_content_max_y = table->RowPosY2;\n    IM_ASSERT(table->RowPosY2 == inner_window->DC.CursorPos.y);\n    if (inner_window != outer_window)\n        inner_window->DC.CursorMaxPos.y = inner_content_max_y;\n    else if (!(flags & ImGuiTableFlags_NoHostExtendY))\n        table->OuterRect.Max.y = table->InnerRect.Max.y = ImMax(table->OuterRect.Max.y, inner_content_max_y); // Patch OuterRect/InnerRect height\n    table->WorkRect.Max.y = ImMax(table->WorkRect.Max.y, table->OuterRect.Max.y);\n    table_instance->LastOuterHeight = table->OuterRect.GetHeight();\n\n    // Setup inner scrolling range\n    // FIXME: This ideally should be done earlier, in BeginTable() SetNextWindowContentSize call, just like writing to inner_window->DC.CursorMaxPos.y,\n    // but since the later is likely to be impossible to do we'd rather update both axes together.\n    if (table->Flags & ImGuiTableFlags_ScrollX)\n    {\n        const float outer_padding_for_border = (table->Flags & ImGuiTableFlags_BordersOuterV) ? TABLE_BORDER_SIZE : 0.0f;\n        float max_pos_x = table->InnerWindow->DC.CursorMaxPos.x;\n        if (table->RightMostEnabledColumn != -1)\n            max_pos_x = ImMax(max_pos_x, table->Columns[table->RightMostEnabledColumn].WorkMaxX + table->CellPaddingX + table->OuterPaddingX - outer_padding_for_border);\n        if (table->ResizedColumn != -1)\n            max_pos_x = ImMax(max_pos_x, table->ResizeLockMinContentsX2);\n        table->InnerWindow->DC.CursorMaxPos.x = max_pos_x + table->TempData->AngledHeadersExtraWidth;\n    }\n\n    // Pop clipping rect\n    if (!(flags & ImGuiTableFlags_NoClip))\n        inner_window->DrawList->PopClipRect();\n    inner_window->ClipRect = inner_window->DrawList->_ClipRectStack.back();\n\n    // Draw borders\n    if ((flags & ImGuiTableFlags_Borders) != 0)\n        TableDrawBorders(table);\n\n#if 0\n    // Strip out dummy channel draw calls\n    // We have no way to prevent user submitting direct ImDrawList calls into a hidden column (but ImGui:: calls will be clipped out)\n    // Pros: remove draw calls which will have no effect. since they'll have zero-size cliprect they may be early out anyway.\n    // Cons: making it harder for users watching metrics/debugger to spot the wasted vertices.\n    if (table->DummyDrawChannel != (ImGuiTableColumnIdx)-1)\n    {\n        ImDrawChannel* dummy_channel = &table->DrawSplitter._Channels[table->DummyDrawChannel];\n        dummy_channel->_CmdBuffer.resize(0);\n        dummy_channel->_IdxBuffer.resize(0);\n    }\n#endif\n\n    // Flatten channels and merge draw calls\n    ImDrawListSplitter* splitter = table->DrawSplitter;\n    splitter->SetCurrentChannel(inner_window->DrawList, 0);\n    if ((table->Flags & ImGuiTableFlags_NoClip) == 0)\n        TableMergeDrawChannels(table);\n    splitter->Merge(inner_window->DrawList);\n\n    // Update ColumnsAutoFitWidth to get us ahead for host using our size to auto-resize without waiting for next BeginTable()\n    float auto_fit_width_for_fixed = 0.0f;\n    float auto_fit_width_for_stretched = 0.0f;\n    float auto_fit_width_for_stretched_min = 0.0f;\n    for (int column_n = 0; column_n < table->ColumnsCount; column_n++)\n        if (IM_BITARRAY_TESTBIT(table->EnabledMaskByIndex, column_n))\n        {\n            ImGuiTableColumn* column = &table->Columns[column_n];\n            float column_width_request = ((column->Flags & ImGuiTableColumnFlags_WidthFixed) && !(column->Flags & ImGuiTableColumnFlags_NoResize)) ? column->WidthRequest : TableGetColumnWidthAuto(table, column);\n            if (column->Flags & ImGuiTableColumnFlags_WidthFixed)\n                auto_fit_width_for_fixed += column_width_request;\n            else\n                auto_fit_width_for_stretched += column_width_request;\n            if ((column->Flags & ImGuiTableColumnFlags_WidthStretch) && (column->Flags & ImGuiTableColumnFlags_NoResize) != 0)\n                auto_fit_width_for_stretched_min = ImMax(auto_fit_width_for_stretched_min, column_width_request / (column->StretchWeight / table->ColumnsStretchSumWeights));\n        }\n    const float width_spacings = (table->OuterPaddingX * 2.0f) + (table->CellSpacingX1 + table->CellSpacingX2) * (table->ColumnsEnabledCount - 1);\n    table->ColumnsAutoFitWidth = width_spacings + (table->CellPaddingX * 2.0f) * table->ColumnsEnabledCount + auto_fit_width_for_fixed + ImMax(auto_fit_width_for_stretched, auto_fit_width_for_stretched_min);\n\n    // Update scroll\n    if ((table->Flags & ImGuiTableFlags_ScrollX) == 0 && inner_window != outer_window)\n    {\n        inner_window->Scroll.x = 0.0f;\n    }\n    else if (table->LastResizedColumn != -1 && table->ResizedColumn == -1 && inner_window->ScrollbarX && table->InstanceInteracted == table->InstanceCurrent)\n    {\n        // When releasing a column being resized, scroll to keep the resulting column in sight\n        const float neighbor_width_to_keep_visible = table->MinColumnWidth + table->CellPaddingX * 2.0f;\n        ImGuiTableColumn* column = &table->Columns[table->LastResizedColumn];\n        if (column->MaxX < table->InnerClipRect.Min.x)\n            SetScrollFromPosX(inner_window, column->MaxX - inner_window->Pos.x - neighbor_width_to_keep_visible, 1.0f);\n        else if (column->MaxX > table->InnerClipRect.Max.x)\n            SetScrollFromPosX(inner_window, column->MaxX - inner_window->Pos.x + neighbor_width_to_keep_visible, 1.0f);\n    }\n\n    // Apply resizing/dragging at the end of the frame\n    if (table->ResizedColumn != -1 && table->InstanceCurrent == table->InstanceInteracted)\n    {\n        ImGuiTableColumn* column = &table->Columns[table->ResizedColumn];\n        const float new_x2 = (g.IO.MousePos.x - g.ActiveIdClickOffset.x + ImTrunc(TABLE_RESIZE_SEPARATOR_HALF_THICKNESS * g.CurrentDpiScale));\n        const float new_width = ImTrunc(new_x2 - column->MinX - table->CellSpacingX1 - table->CellPaddingX * 2.0f);\n        table->ResizedColumnNextWidth = new_width;\n    }\n\n    table->IsActiveIdInTable = (g.ActiveIdIsAlive != 0 && table->IsActiveIdAliveBeforeTable == false);\n\n    // Pop from id stack\n    IM_ASSERT_USER_ERROR(inner_window->IDStack.back() == table_instance->TableInstanceID, \"Mismatching PushID/PopID!\");\n    IM_ASSERT_USER_ERROR(outer_window->DC.ItemWidthStack.Size >= temp_data->HostBackupItemWidthStackSize, \"Too many PopItemWidth!\");\n    if (table->InstanceCurrent > 0)\n        PopID();\n    PopID();\n\n    // Restore window data that we modified\n    const ImVec2 backup_outer_max_pos = outer_window->DC.CursorMaxPos;\n    inner_window->WorkRect = temp_data->HostBackupWorkRect;\n    inner_window->ParentWorkRect = temp_data->HostBackupParentWorkRect;\n    inner_window->SkipItems = table->HostSkipItems;\n    outer_window->DC.CursorPos = table->OuterRect.Min;\n    outer_window->DC.ItemWidth = temp_data->HostBackupItemWidth;\n    outer_window->DC.ItemWidthStack.Size = temp_data->HostBackupItemWidthStackSize;\n    outer_window->DC.ColumnsOffset = temp_data->HostBackupColumnsOffset;\n\n    // Layout in outer window\n    // (FIXME: To allow auto-fit and allow desirable effect of SameLine() we dissociate 'used' vs 'ideal' size by overriding\n    // CursorPosPrevLine and CursorMaxPos manually. That should be a more general layout feature, see same problem e.g. #3414)\n    if (inner_window != outer_window)\n    {\n        short backup_nav_layers_active_mask = inner_window->DC.NavLayersActiveMask;\n        inner_window->DC.NavLayersActiveMask |= 1 << table->NavLayer; // So empty table don't appear to navigate differently.\n        g.CurrentTable = NULL; // To avoid error recovery recursing\n        EndChild();\n        g.CurrentTable = table;\n        inner_window->DC.NavLayersActiveMask = backup_nav_layers_active_mask;\n    }\n    else\n    {\n        ItemSize(table->OuterRect.GetSize());\n        ItemAdd(table->OuterRect, 0);\n    }\n\n    // Override declared contents width/height to enable auto-resize while not needlessly adding a scrollbar\n    if (table->Flags & ImGuiTableFlags_NoHostExtendX)\n    {\n        // FIXME-TABLE: Could we remove this section?\n        // ColumnsAutoFitWidth may be one frame ahead here since for Fixed+NoResize is calculated from latest contents\n        IM_ASSERT((table->Flags & ImGuiTableFlags_ScrollX) == 0);\n        outer_window->DC.CursorMaxPos.x = ImMax(backup_outer_max_pos.x, table->OuterRect.Min.x + table->ColumnsAutoFitWidth);\n    }\n    else if (temp_data->UserOuterSize.x <= 0.0f)\n    {\n        // Some references for this: #7651 + tests \"table_reported_size\", \"table_reported_size_outer\" equivalent Y block\n        // - Checking for ImGuiTableFlags_ScrollX/ScrollY flag makes us a frame ahead when disabling those flags.\n        // - FIXME-TABLE: Would make sense to pre-compute expected scrollbar visibility/sizes to generally save a frame of feedback.\n        const float inner_content_max_x = table->OuterRect.Min.x + table->ColumnsAutoFitWidth; // Slightly misleading name but used for code symmetry with inner_content_max_y\n        const float decoration_size = table->TempData->AngledHeadersExtraWidth + ((table->Flags & ImGuiTableFlags_ScrollY) ? inner_window->ScrollbarSizes.x : 0.0f);\n        outer_window->DC.IdealMaxPos.x = ImMax(outer_window->DC.IdealMaxPos.x, inner_content_max_x + decoration_size - temp_data->UserOuterSize.x);\n        outer_window->DC.CursorMaxPos.x = ImMax(backup_outer_max_pos.x, ImMin(table->OuterRect.Max.x, inner_content_max_x + decoration_size));\n    }\n    else\n    {\n        outer_window->DC.CursorMaxPos.x = ImMax(backup_outer_max_pos.x, table->OuterRect.Max.x);\n    }\n    if (temp_data->UserOuterSize.y <= 0.0f)\n    {\n        const float decoration_size = (table->Flags & ImGuiTableFlags_ScrollX) ? inner_window->ScrollbarSizes.y : 0.0f;\n        outer_window->DC.IdealMaxPos.y = ImMax(outer_window->DC.IdealMaxPos.y, inner_content_max_y + decoration_size - temp_data->UserOuterSize.y);\n        outer_window->DC.CursorMaxPos.y = ImMax(backup_outer_max_pos.y, ImMin(table->OuterRect.Max.y, inner_content_max_y + decoration_size));\n    }\n    else\n    {\n        // OuterRect.Max.y may already have been pushed downward from the initial value (unless ImGuiTableFlags_NoHostExtendY is set)\n        outer_window->DC.CursorMaxPos.y = ImMax(backup_outer_max_pos.y, table->OuterRect.Max.y);\n    }\n\n    // Save settings\n    if (table->IsSettingsDirty)\n        TableSaveSettings(table);\n    table->IsInitializing = false;\n\n    // Clear or restore current table, if any\n    IM_ASSERT(g.CurrentWindow == outer_window && g.CurrentTable == table);\n    IM_ASSERT(g.TablesTempDataStacked > 0);\n    temp_data = (--g.TablesTempDataStacked > 0) ? &g.TablesTempData[g.TablesTempDataStacked - 1] : NULL;\n    g.CurrentTable = temp_data ? g.Tables.GetByIndex(temp_data->TableIndex) : NULL;\n    if (g.CurrentTable)\n    {\n        g.CurrentTable->TempData = temp_data;\n        g.CurrentTable->DrawSplitter = &temp_data->DrawSplitter;\n    }\n    outer_window->DC.CurrentTableIdx = g.CurrentTable ? g.Tables.GetIndex(g.CurrentTable) : -1;\n    NavUpdateCurrentWindowIsScrollPushableX();\n}\n\n// Called in TableSetupColumn() when initializing and in TableLoadSettings() for defaults before applying stored settings.\n// 'init_mask' specify which fields to initialize.\nstatic void TableInitColumnDefaults(ImGuiTable* table, ImGuiTableColumn* column, ImGuiTableColumnFlags init_mask)\n{\n    ImGuiTableColumnFlags flags = column->Flags;\n    if (init_mask & ImGuiTableFlags_Resizable)\n    {\n        float init_width_or_weight = column->InitStretchWeightOrWidth;\n        column->WidthRequest = ((flags & ImGuiTableColumnFlags_WidthFixed) && init_width_or_weight > 0.0f) ? init_width_or_weight : -1.0f;\n        column->StretchWeight = (init_width_or_weight > 0.0f && (flags & ImGuiTableColumnFlags_WidthStretch)) ? init_width_or_weight : -1.0f;\n        if (init_width_or_weight > 0.0f) // Disable auto-fit if an explicit width/weight has been specified\n            column->AutoFitQueue = 0x00;\n    }\n    if (init_mask & ImGuiTableFlags_Reorderable)\n        column->DisplayOrder = (ImGuiTableColumnIdx)table->Columns.index_from_ptr(column);\n    if (init_mask & ImGuiTableFlags_Hideable)\n        column->IsUserEnabled = column->IsUserEnabledNextFrame = (flags & ImGuiTableColumnFlags_DefaultHide) ? 0 : 1;\n    if (init_mask & ImGuiTableFlags_Sortable)\n    {\n        // Multiple columns using _DefaultSort will be reassigned unique SortOrder values when building the sort specs.\n        column->SortOrder = (flags & ImGuiTableColumnFlags_DefaultSort) ? 0 : -1;\n        column->SortDirection = (flags & ImGuiTableColumnFlags_DefaultSort) ? ((flags & ImGuiTableColumnFlags_PreferSortDescending) ? (ImS8)ImGuiSortDirection_Descending : (ImU8)(ImGuiSortDirection_Ascending)) : (ImS8)ImGuiSortDirection_None;\n    }\n}\n\n// See \"COLUMNS SIZING POLICIES\" comments at the top of this file\n// If (init_width_or_weight <= 0.0f) it is ignored\nvoid ImGui::TableSetupColumn(const char* label, ImGuiTableColumnFlags flags, float init_width_or_weight, ImGuiID user_id)\n{\n    ImGuiContext& g = *GImGui;\n    ImGuiTable* table = g.CurrentTable;\n    if (table == NULL)\n    {\n        IM_ASSERT_USER_ERROR(table != NULL, \"Call should only be done while in BeginTable() scope!\");\n        return;\n    }\n    IM_ASSERT(table->IsLayoutLocked == false && \"Need to call TableSetupColumn() before first row!\");\n    IM_ASSERT((flags & ImGuiTableColumnFlags_StatusMask_) == 0 && \"Illegal to pass StatusMask values to TableSetupColumn()\");\n    if (table->DeclColumnsCount >= table->ColumnsCount)\n    {\n        IM_ASSERT_USER_ERROR(table->DeclColumnsCount < table->ColumnsCount, \"Called TableSetupColumn() too many times!\");\n        return;\n    }\n\n    ImGuiTableColumn* column = &table->Columns[table->DeclColumnsCount];\n    table->DeclColumnsCount++;\n\n    // Assert when passing a width or weight if policy is entirely left to default, to avoid storing width into weight and vice-versa.\n    // Give a grace to users of ImGuiTableFlags_ScrollX.\n    if (table->IsDefaultSizingPolicy && (flags & ImGuiTableColumnFlags_WidthMask_) == 0 && (flags & ImGuiTableFlags_ScrollX) == 0)\n        IM_ASSERT(init_width_or_weight <= 0.0f && \"Can only specify width/weight if sizing policy is set explicitly in either Table or Column.\");\n\n    // When passing a width automatically enforce WidthFixed policy\n    // (whereas TableSetupColumnFlags would default to WidthAuto if table is not resizable)\n    if ((flags & ImGuiTableColumnFlags_WidthMask_) == 0 && init_width_or_weight > 0.0f)\n        if ((table->Flags & ImGuiTableFlags_SizingMask_) == ImGuiTableFlags_SizingFixedFit || (table->Flags & ImGuiTableFlags_SizingMask_) == ImGuiTableFlags_SizingFixedSame)\n            flags |= ImGuiTableColumnFlags_WidthFixed;\n    if (flags & ImGuiTableColumnFlags_AngledHeader)\n    {\n        flags |= ImGuiTableColumnFlags_NoHeaderLabel;\n        table->AngledHeadersCount++;\n    }\n\n    TableSetupColumnFlags(table, column, flags);\n    column->UserID = user_id;\n    flags = column->Flags;\n\n    // Initialize defaults\n    column->InitStretchWeightOrWidth = init_width_or_weight;\n    if (table->IsInitializing)\n    {\n        ImGuiTableFlags init_flags = ~table->SettingsLoadedFlags;\n        if (column->WidthRequest < 0.0f && column->StretchWeight < 0.0f)\n            init_flags |= ImGuiTableFlags_Resizable;\n        TableInitColumnDefaults(table, column, init_flags);\n    }\n\n    // Store name (append with zero-terminator in contiguous buffer)\n    // FIXME: If we recorded the number of \\n in names we could compute header row height\n    column->NameOffset = -1;\n    if (label != NULL && label[0] != 0)\n    {\n        column->NameOffset = (ImS16)table->ColumnsNames.size();\n        table->ColumnsNames.append(label, label + ImStrlen(label) + 1);\n    }\n}\n\n// [Public]\nvoid ImGui::TableSetupScrollFreeze(int columns, int rows)\n{\n    ImGuiContext& g = *GImGui;\n    ImGuiTable* table = g.CurrentTable;\n    if (table == NULL)\n    {\n        IM_ASSERT_USER_ERROR(table != NULL, \"Call should only be done while in BeginTable() scope!\");\n        return;\n    }\n    IM_ASSERT(table->IsLayoutLocked == false && \"Need to call TableSetupColumn() before first row!\");\n    IM_ASSERT(columns >= 0 && columns < IMGUI_TABLE_MAX_COLUMNS);\n    IM_ASSERT(rows >= 0 && rows < 128); // Arbitrary limit\n\n    table->FreezeColumnsRequest = (table->Flags & ImGuiTableFlags_ScrollX) ? (ImGuiTableColumnIdx)ImMin(columns, table->ColumnsCount) : 0;\n    table->FreezeColumnsCount = (table->InnerWindow->Scroll.x != 0.0f) ? table->FreezeColumnsRequest : 0;\n    table->FreezeRowsRequest = (table->Flags & ImGuiTableFlags_ScrollY) ? (ImGuiTableColumnIdx)rows : 0;\n    table->FreezeRowsCount = (table->InnerWindow->Scroll.y != 0.0f) ? table->FreezeRowsRequest : 0;\n    table->IsUnfrozenRows = (table->FreezeRowsCount == 0); // Make sure this is set before TableUpdateLayout() so ImGuiListClipper can benefit from it.b\n\n    // Ensure frozen columns are ordered in their section. We still allow multiple frozen columns to be reordered.\n    // FIXME-TABLE: This work for preserving 2143 into 21|43. How about 4321 turning into 21|43? (preserve relative order in each section)\n    for (int column_n = 0; column_n < table->FreezeColumnsRequest; column_n++)\n    {\n        int order_n = table->DisplayOrderToIndex[column_n];\n        if (order_n != column_n && order_n >= table->FreezeColumnsRequest)\n        {\n            ImSwap(table->Columns[table->DisplayOrderToIndex[order_n]].DisplayOrder, table->Columns[table->DisplayOrderToIndex[column_n]].DisplayOrder);\n            ImSwap(table->DisplayOrderToIndex[order_n], table->DisplayOrderToIndex[column_n]);\n        }\n    }\n}\n\n//-----------------------------------------------------------------------------\n// [SECTION] Tables: Simple accessors\n//-----------------------------------------------------------------------------\n// - TableGetColumnCount()\n// - TableGetColumnName()\n// - TableGetColumnName() [Internal]\n// - TableSetColumnEnabled()\n// - TableGetColumnFlags()\n// - TableGetCellBgRect() [Internal]\n// - TableGetColumnResizeID() [Internal]\n// - TableGetHoveredColumn() [Internal]\n// - TableGetHoveredRow() [Internal]\n// - TableSetBgColor()\n//-----------------------------------------------------------------------------\n\nint ImGui::TableGetColumnCount()\n{\n    ImGuiContext& g = *GImGui;\n    ImGuiTable* table = g.CurrentTable;\n    return table ? table->ColumnsCount : 0;\n}\n\nconst char* ImGui::TableGetColumnName(int column_n)\n{\n    ImGuiContext& g = *GImGui;\n    ImGuiTable* table = g.CurrentTable;\n    if (!table)\n        return NULL;\n    if (column_n < 0)\n        column_n = table->CurrentColumn;\n    return TableGetColumnName(table, column_n);\n}\n\nconst char* ImGui::TableGetColumnName(const ImGuiTable* table, int column_n)\n{\n    if (table->IsLayoutLocked == false && column_n >= table->DeclColumnsCount)\n        return \"\"; // NameOffset is invalid at this point\n    const ImGuiTableColumn* column = &table->Columns[column_n];\n    if (column->NameOffset == -1)\n        return \"\";\n    return &table->ColumnsNames.Buf[column->NameOffset];\n}\n\n// Change user accessible enabled/disabled state of a column (often perceived as \"showing/hiding\" from users point of view)\n// Note that end-user can use the context menu to change this themselves (right-click in headers, or right-click in columns body with ImGuiTableFlags_ContextMenuInBody)\n// - Require table to have the ImGuiTableFlags_Hideable flag because we are manipulating user accessible state.\n// - Request will be applied during next layout, which happens on the first call to TableNextRow() after BeginTable().\n// - For the getter you can test (TableGetColumnFlags() & ImGuiTableColumnFlags_IsEnabled) != 0.\n// - Alternative: the ImGuiTableColumnFlags_Disabled is an overriding/master disable flag which will also hide the column from context menu.\nvoid ImGui::TableSetColumnEnabled(int column_n, bool enabled)\n{\n    ImGuiContext& g = *GImGui;\n    ImGuiTable* table = g.CurrentTable;\n    if (table == NULL)\n    {\n        IM_ASSERT_USER_ERROR(table != NULL, \"Call should only be done while in BeginTable() scope!\");\n        return;\n    }\n    IM_ASSERT(table->Flags & ImGuiTableFlags_Hideable); // See comments above\n    if (column_n < 0)\n        column_n = table->CurrentColumn;\n    IM_ASSERT(column_n >= 0 && column_n < table->ColumnsCount);\n    ImGuiTableColumn* column = &table->Columns[column_n];\n    column->IsUserEnabledNextFrame = enabled;\n}\n\n// We allow querying for an extra column in order to poll the IsHovered state of the right-most section\nImGuiTableColumnFlags ImGui::TableGetColumnFlags(int column_n)\n{\n    ImGuiContext& g = *GImGui;\n    ImGuiTable* table = g.CurrentTable;\n    if (!table)\n        return ImGuiTableColumnFlags_None;\n    if (column_n < 0)\n        column_n = table->CurrentColumn;\n    if (column_n == table->ColumnsCount)\n        return (table->HoveredColumnBody == column_n) ? ImGuiTableColumnFlags_IsHovered : ImGuiTableColumnFlags_None;\n    return table->Columns[column_n].Flags;\n}\n\n// Return the cell rectangle based on currently known height.\n// - Important: we generally don't know our row height until the end of the row, so Max.y will be incorrect in many situations.\n//   The only case where this is correct is if we provided a min_row_height to TableNextRow() and don't go below it, or in TableEndRow() when we locked that height.\n// - Important: if ImGuiTableFlags_PadOuterX is set but ImGuiTableFlags_PadInnerX is not set, the outer-most left and right\n//   columns report a small offset so their CellBgRect can extend up to the outer border.\n//   FIXME: But the rendering code in TableEndRow() nullifies that with clamping required for scrolling.\nImRect ImGui::TableGetCellBgRect(const ImGuiTable* table, int column_n)\n{\n    const ImGuiTableColumn* column = &table->Columns[column_n];\n    float x1 = column->MinX;\n    float x2 = column->MaxX;\n    //if (column->PrevEnabledColumn == -1)\n    //    x1 -= table->OuterPaddingX;\n    //if (column->NextEnabledColumn == -1)\n    //    x2 += table->OuterPaddingX;\n    x1 = ImMax(x1, table->WorkRect.Min.x);\n    x2 = ImMin(x2, table->WorkRect.Max.x);\n    return ImRect(x1, table->RowPosY1, x2, table->RowPosY2);\n}\n\n// Return the resizing ID for the right-side of the given column.\nImGuiID ImGui::TableGetColumnResizeID(ImGuiTable* table, int column_n, int instance_no)\n{\n    IM_ASSERT(column_n >= 0 && column_n < table->ColumnsCount);\n    ImGuiID instance_id = TableGetInstanceID(table, instance_no);\n    return instance_id + 1 + column_n; // FIXME: #6140: still not ideal\n}\n\n// Return -1 when table is not hovered. return columns_count if hovering the unused space at the right of the right-most visible column.\nint ImGui::TableGetHoveredColumn()\n{\n    ImGuiContext& g = *GImGui;\n    ImGuiTable* table = g.CurrentTable;\n    if (!table)\n        return -1;\n    return (int)table->HoveredColumnBody;\n}\n\n// Return -1 when table is not hovered. Return maxrow+1 if in table but below last submitted row.\n// *IMPORTANT* Unlike TableGetHoveredColumn(), this has a one frame latency in updating the value.\n// This difference with is the reason why this is not public yet.\nint ImGui::TableGetHoveredRow()\n{\n    ImGuiContext& g = *GImGui;\n    ImGuiTable* table = g.CurrentTable;\n    if (!table)\n        return -1;\n    ImGuiTableInstanceData* table_instance = TableGetInstanceData(table, table->InstanceCurrent);\n    return (int)table_instance->HoveredRowLast;\n}\n\nvoid ImGui::TableSetBgColor(ImGuiTableBgTarget target, ImU32 color, int column_n)\n{\n    ImGuiContext& g = *GImGui;\n    ImGuiTable* table = g.CurrentTable;\n    IM_ASSERT(target != ImGuiTableBgTarget_None);\n\n    if (color == IM_COL32_DISABLE)\n        color = 0;\n\n    // We cannot draw neither the cell or row background immediately as we don't know the row height at this point in time.\n    switch (target)\n    {\n    case ImGuiTableBgTarget_CellBg:\n    {\n        if (table->RowPosY1 > table->InnerClipRect.Max.y) // Discard\n            return;\n        if (column_n == -1)\n            column_n = table->CurrentColumn;\n        if (!IM_BITARRAY_TESTBIT(table->VisibleMaskByIndex, column_n))\n            return;\n        if (table->RowCellDataCurrent < 0 || table->RowCellData[table->RowCellDataCurrent].Column != column_n)\n            table->RowCellDataCurrent++;\n        ImGuiTableCellData* cell_data = &table->RowCellData[table->RowCellDataCurrent];\n        cell_data->BgColor = color;\n        cell_data->Column = (ImGuiTableColumnIdx)column_n;\n        break;\n    }\n    case ImGuiTableBgTarget_RowBg0:\n    case ImGuiTableBgTarget_RowBg1:\n    {\n        if (table->RowPosY1 > table->InnerClipRect.Max.y) // Discard\n            return;\n        IM_ASSERT(column_n == -1);\n        int bg_idx = (target == ImGuiTableBgTarget_RowBg1) ? 1 : 0;\n        table->RowBgColor[bg_idx] = color;\n        break;\n    }\n    default:\n        IM_ASSERT(0);\n    }\n}\n\n//-------------------------------------------------------------------------\n// [SECTION] Tables: Row changes\n//-------------------------------------------------------------------------\n// - TableGetRowIndex()\n// - TableNextRow()\n// - TableBeginRow() [Internal]\n// - TableEndRow() [Internal]\n//-------------------------------------------------------------------------\n\n// [Public] Note: for row coloring we use ->RowBgColorCounter which is the same value without counting header rows\nint ImGui::TableGetRowIndex()\n{\n    ImGuiContext& g = *GImGui;\n    ImGuiTable* table = g.CurrentTable;\n    if (!table)\n        return 0;\n    return table->CurrentRow;\n}\n\n// [Public] Starts into the first cell of a new row\nvoid ImGui::TableNextRow(ImGuiTableRowFlags row_flags, float row_min_height)\n{\n    ImGuiContext& g = *GImGui;\n    ImGuiTable* table = g.CurrentTable;\n\n    if (!table->IsLayoutLocked)\n        TableUpdateLayout(table);\n    if (table->IsInsideRow)\n        TableEndRow(table);\n\n    table->LastRowFlags = table->RowFlags;\n    table->RowFlags = row_flags;\n    table->RowCellPaddingY = g.Style.CellPadding.y;\n    table->RowMinHeight = row_min_height;\n    TableBeginRow(table);\n\n    // We honor min_row_height requested by user, but cannot guarantee per-row maximum height,\n    // because that would essentially require a unique clipping rectangle per-cell.\n    table->RowPosY2 += table->RowCellPaddingY * 2.0f;\n    table->RowPosY2 = ImMax(table->RowPosY2, table->RowPosY1 + row_min_height);\n\n    // Disable output until user calls TableNextColumn()\n    table->InnerWindow->SkipItems = true;\n}\n\n// [Internal] Only called by TableNextRow()\nvoid ImGui::TableBeginRow(ImGuiTable* table)\n{\n    ImGuiWindow* window = table->InnerWindow;\n    IM_ASSERT(!table->IsInsideRow);\n\n    // New row\n    table->CurrentRow++;\n    table->CurrentColumn = -1;\n    table->RowBgColor[0] = table->RowBgColor[1] = IM_COL32_DISABLE;\n    table->RowCellDataCurrent = -1;\n    table->IsInsideRow = true;\n\n    // Begin frozen rows\n    float next_y1 = table->RowPosY2;\n    if (table->CurrentRow == 0 && table->FreezeRowsCount > 0)\n        next_y1 = window->DC.CursorPos.y = table->OuterRect.Min.y;\n\n    table->RowPosY1 = table->RowPosY2 = next_y1;\n    table->RowTextBaseline = 0.0f;\n    table->RowIndentOffsetX = window->DC.Indent.x - table->HostIndentX; // Lock indent\n\n    window->DC.PrevLineTextBaseOffset = 0.0f;\n    window->DC.CursorPosPrevLine = ImVec2(window->DC.CursorPos.x, window->DC.CursorPos.y + table->RowCellPaddingY); // This allows users to call SameLine() to share LineSize between columns.\n    window->DC.PrevLineSize = window->DC.CurrLineSize = ImVec2(0.0f, 0.0f); // This allows users to call SameLine() to share LineSize between columns, and to call it from first column too.\n    window->DC.IsSameLine = window->DC.IsSetPos = false;\n    window->DC.CursorMaxPos.y = next_y1;\n\n    // Making the header BG color non-transparent will allow us to overlay it multiple times when handling smooth dragging.\n    if (table->RowFlags & ImGuiTableRowFlags_Headers)\n    {\n        TableSetBgColor(ImGuiTableBgTarget_RowBg0, GetColorU32(ImGuiCol_TableHeaderBg));\n        if (table->CurrentRow == 0)\n            table->IsUsingHeaders = true;\n    }\n}\n\n// [Internal] Called by TableNextRow()\nvoid ImGui::TableEndRow(ImGuiTable* table)\n{\n    ImGuiContext& g = *GImGui;\n    ImGuiWindow* window = g.CurrentWindow;\n    IM_ASSERT(window == table->InnerWindow);\n    IM_ASSERT(table->IsInsideRow);\n\n    if (table->CurrentColumn != -1)\n        TableEndCell(table);\n\n    // Logging\n    if (g.LogEnabled)\n        LogRenderedText(NULL, \"|\");\n\n    // Position cursor at the bottom of our row so it can be used for e.g. clipping calculation. However it is\n    // likely that the next call to TableBeginCell() will reposition the cursor to take account of vertical padding.\n    window->DC.CursorPos.y = table->RowPosY2;\n\n    // Row background fill\n    const float bg_y1 = table->RowPosY1;\n    const float bg_y2 = table->RowPosY2;\n    const bool unfreeze_rows_actual = (table->CurrentRow + 1 == table->FreezeRowsCount);\n    const bool unfreeze_rows_request = (table->CurrentRow + 1 == table->FreezeRowsRequest);\n    ImGuiTableInstanceData* table_instance = TableGetInstanceData(table, table->InstanceCurrent);\n    if ((table->RowFlags & ImGuiTableRowFlags_Headers) && (table->CurrentRow == 0 || (table->LastRowFlags & ImGuiTableRowFlags_Headers)))\n        table_instance->LastTopHeadersRowHeight += bg_y2 - bg_y1;\n\n    const bool is_visible = (bg_y2 >= table->InnerClipRect.Min.y && bg_y1 <= table->InnerClipRect.Max.y);\n    if (is_visible)\n    {\n        // Update data for TableGetHoveredRow()\n        if (table->HoveredColumnBody != -1 && g.IO.MousePos.y >= bg_y1 && g.IO.MousePos.y < bg_y2 && table_instance->HoveredRowNext < 0)\n            table_instance->HoveredRowNext = table->CurrentRow;\n\n        // Decide of background color for the row\n        ImU32 bg_col0 = 0;\n        ImU32 bg_col1 = 0;\n        if (table->RowBgColor[0] != IM_COL32_DISABLE)\n            bg_col0 = table->RowBgColor[0];\n        else if (table->Flags & ImGuiTableFlags_RowBg)\n            bg_col0 = GetColorU32((table->RowBgColorCounter & 1) ? ImGuiCol_TableRowBgAlt : ImGuiCol_TableRowBg);\n        if (table->RowBgColor[1] != IM_COL32_DISABLE)\n            bg_col1 = table->RowBgColor[1];\n\n        // Decide of top border color\n        ImU32 top_border_col = 0;\n        const float border_size = TABLE_BORDER_SIZE;\n        if (table->CurrentRow > 0 && (table->Flags & ImGuiTableFlags_BordersInnerH))\n            top_border_col = (table->LastRowFlags & ImGuiTableRowFlags_Headers) ? table->BorderColorStrong : table->BorderColorLight;\n\n        const bool draw_cell_bg_color = table->RowCellDataCurrent >= 0;\n        const bool draw_strong_bottom_border = unfreeze_rows_actual;\n        if ((bg_col0 | bg_col1 | top_border_col) != 0 || draw_strong_bottom_border || draw_cell_bg_color)\n        {\n            // In theory we could call SetWindowClipRectBeforeSetChannel() but since we know TableEndRow() is\n            // always followed by a change of clipping rectangle we perform the smallest overwrite possible here.\n            if ((table->Flags & ImGuiTableFlags_NoClip) == 0)\n                window->DrawList->_CmdHeader.ClipRect = table->Bg0ClipRectForDrawCmd.ToVec4();\n            table->DrawSplitter->SetCurrentChannel(window->DrawList, TABLE_DRAW_CHANNEL_BG0);\n        }\n\n        // Draw row background\n        // We soft/cpu clip this so all backgrounds and borders can share the same clipping rectangle\n        if (bg_col0 || bg_col1)\n        {\n            ImRect row_rect(table->WorkRect.Min.x, bg_y1, table->WorkRect.Max.x, bg_y2);\n            row_rect.ClipWith(table->BgClipRect);\n            if (bg_col0 != 0 && row_rect.Min.y < row_rect.Max.y)\n                window->DrawList->AddRectFilled(row_rect.Min, row_rect.Max, bg_col0);\n            if (bg_col1 != 0 && row_rect.Min.y < row_rect.Max.y)\n                window->DrawList->AddRectFilled(row_rect.Min, row_rect.Max, bg_col1);\n        }\n\n        // Draw cell background color\n        if (draw_cell_bg_color)\n        {\n            ImGuiTableCellData* cell_data_end = &table->RowCellData[table->RowCellDataCurrent];\n            for (ImGuiTableCellData* cell_data = &table->RowCellData[0]; cell_data <= cell_data_end; cell_data++)\n            {\n                // As we render the BG here we need to clip things (for layout we would not)\n                // FIXME: This cancels the OuterPadding addition done by TableGetCellBgRect(), need to keep it while rendering correctly while scrolling.\n                const ImGuiTableColumn* column = &table->Columns[cell_data->Column];\n                ImRect cell_bg_rect = TableGetCellBgRect(table, cell_data->Column);\n                cell_bg_rect.ClipWith(table->BgClipRect);\n                cell_bg_rect.Min.x = ImMax(cell_bg_rect.Min.x, column->ClipRect.Min.x);     // So that first column after frozen one gets clipped when scrolling\n                cell_bg_rect.Max.x = ImMin(cell_bg_rect.Max.x, column->MaxX);\n                if (cell_bg_rect.Min.y < cell_bg_rect.Max.y)\n                    window->DrawList->AddRectFilled(cell_bg_rect.Min, cell_bg_rect.Max, cell_data->BgColor);\n            }\n        }\n\n        // Draw top border\n        if (top_border_col && bg_y1 >= table->BgClipRect.Min.y && bg_y1 < table->BgClipRect.Max.y)\n            window->DrawList->AddLine(ImVec2(table->BorderX1, bg_y1), ImVec2(table->BorderX2, bg_y1), top_border_col, border_size);\n\n        // Draw bottom border at the row unfreezing mark (always strong)\n        if (draw_strong_bottom_border && bg_y2 >= table->BgClipRect.Min.y && bg_y2 < table->BgClipRect.Max.y)\n            window->DrawList->AddLine(ImVec2(table->BorderX1, bg_y2), ImVec2(table->BorderX2, bg_y2), table->BorderColorStrong, border_size);\n    }\n\n    // End frozen rows (when we are past the last frozen row line, teleport cursor and alter clipping rectangle)\n    // We need to do that in TableEndRow() instead of TableBeginRow() so the list clipper can mark end of row and\n    // get the new cursor position.\n    if (unfreeze_rows_request)\n    {\n        for (int column_n = 0; column_n < table->ColumnsCount; column_n++)\n            table->Columns[column_n].NavLayerCurrent = table->NavLayer;\n        const float y0 = ImMax(table->RowPosY2 + 1, table->InnerClipRect.Min.y);\n        table_instance->LastFrozenHeight = y0 - table->OuterRect.Min.y;\n\n        if (unfreeze_rows_actual)\n        {\n            IM_ASSERT(table->IsUnfrozenRows == false);\n            table->IsUnfrozenRows = true;\n\n            // BgClipRect starts as table->InnerClipRect, reduce it now and make BgClipRectForDrawCmd == BgClipRect\n            table->BgClipRect.Min.y = table->Bg2ClipRectForDrawCmd.Min.y = ImMin(y0, table->InnerClipRect.Max.y);\n            table->BgClipRect.Max.y = table->Bg2ClipRectForDrawCmd.Max.y = table->InnerClipRect.Max.y;\n            table->Bg2DrawChannelCurrent = table->Bg2DrawChannelUnfrozen;\n            IM_ASSERT(table->Bg2ClipRectForDrawCmd.Min.y <= table->Bg2ClipRectForDrawCmd.Max.y);\n\n            float row_height = table->RowPosY2 - table->RowPosY1;\n            table->RowPosY2 = window->DC.CursorPos.y = table->WorkRect.Min.y + table->RowPosY2 - table->OuterRect.Min.y;\n            table->RowPosY1 = table->RowPosY2 - row_height;\n            for (int column_n = 0; column_n < table->ColumnsCount; column_n++)\n            {\n                ImGuiTableColumn* column = &table->Columns[column_n];\n                column->DrawChannelCurrent = column->DrawChannelUnfrozen;\n                column->ClipRect.Min.y = table->Bg2ClipRectForDrawCmd.Min.y;\n            }\n\n            // Update cliprect ahead of TableBeginCell() so clipper can access to new ClipRect->Min.y\n            SetWindowClipRectBeforeSetChannel(window, table->Columns[0].ClipRect);\n            table->DrawSplitter->SetCurrentChannel(window->DrawList, table->Columns[0].DrawChannelCurrent);\n        }\n    }\n\n    if (!(table->RowFlags & ImGuiTableRowFlags_Headers))\n        table->RowBgColorCounter++;\n    table->IsInsideRow = false;\n}\n\n//-------------------------------------------------------------------------\n// [SECTION] Tables: Columns changes\n//-------------------------------------------------------------------------\n// - TableGetColumnIndex()\n// - TableSetColumnIndex()\n// - TableNextColumn()\n// - TableBeginCell() [Internal]\n// - TableEndCell() [Internal]\n//-------------------------------------------------------------------------\n\nint ImGui::TableGetColumnIndex()\n{\n    ImGuiContext& g = *GImGui;\n    ImGuiTable* table = g.CurrentTable;\n    if (!table)\n        return 0;\n    return table->CurrentColumn;\n}\n\n// [Public] Append into a specific column\nbool ImGui::TableSetColumnIndex(int column_n)\n{\n    ImGuiContext& g = *GImGui;\n    ImGuiTable* table = g.CurrentTable;\n    if (!table)\n        return false;\n\n    if (table->CurrentColumn != column_n)\n    {\n        if (table->CurrentColumn != -1)\n            TableEndCell(table);\n        if ((column_n >= 0 && column_n < table->ColumnsCount) == false)\n        {\n            IM_ASSERT_USER_ERROR(column_n >= 0 && column_n < table->ColumnsCount, \"TableSetColumnIndex() invalid column index!\");\n            return false;\n        }\n        TableBeginCell(table, column_n);\n    }\n\n    // Return whether the column is visible. User may choose to skip submitting items based on this return value,\n    // however they shouldn't skip submitting for columns that may have the tallest contribution to row height.\n    return table->Columns[column_n].IsRequestOutput;\n}\n\n// [Public] Append into the next column, wrap and create a new row when already on last column\nbool ImGui::TableNextColumn()\n{\n    ImGuiContext& g = *GImGui;\n    ImGuiTable* table = g.CurrentTable;\n    if (!table)\n        return false;\n\n    if (table->IsInsideRow && table->CurrentColumn + 1 < table->ColumnsCount)\n    {\n        if (table->CurrentColumn != -1)\n            TableEndCell(table);\n        TableBeginCell(table, table->CurrentColumn + 1);\n    }\n    else\n    {\n        TableNextRow();\n        TableBeginCell(table, 0);\n    }\n\n    // Return whether the column is visible. User may choose to skip submitting items based on this return value,\n    // however they shouldn't skip submitting for columns that may have the tallest contribution to row height.\n    return table->Columns[table->CurrentColumn].IsRequestOutput;\n}\n\n\n// [Internal] Called by TableSetColumnIndex()/TableNextColumn()\n// This is called very frequently, so we need to be mindful of unnecessary overhead.\n// FIXME-TABLE FIXME-OPT: Could probably shortcut some things for non-active or clipped columns.\nvoid ImGui::TableBeginCell(ImGuiTable* table, int column_n)\n{\n    ImGuiContext& g = *GImGui;\n    ImGuiTableColumn* column = &table->Columns[column_n];\n    ImGuiWindow* window = table->InnerWindow;\n    table->CurrentColumn = column_n;\n\n    // Start position is roughly ~~ CellRect.Min + CellPadding + Indent\n    float start_x = column->WorkMinX;\n    if (column->Flags & ImGuiTableColumnFlags_IndentEnable)\n        start_x += table->RowIndentOffsetX; // ~~ += window.DC.Indent.x - table->HostIndentX, except we locked it for the row.\n\n    window->DC.CursorPos.x = start_x;\n    window->DC.CursorPos.y = table->RowPosY1 + table->RowCellPaddingY;\n    window->DC.CursorMaxPos.x = window->DC.CursorPos.x;\n    window->DC.ColumnsOffset.x = start_x - window->Pos.x - window->DC.Indent.x; // FIXME-WORKRECT\n    window->DC.CursorPosPrevLine.x = window->DC.CursorPos.x; // PrevLine.y is preserved. This allows users to call SameLine() to share LineSize between columns.\n    window->DC.CurrLineTextBaseOffset = table->RowTextBaseline;\n    window->DC.NavLayerCurrent = (ImGuiNavLayer)column->NavLayerCurrent;\n\n    // Note how WorkRect.Max.y is only set once during layout\n    window->WorkRect.Min.y = window->DC.CursorPos.y;\n    window->WorkRect.Min.x = column->WorkMinX;\n    window->WorkRect.Max.x = column->WorkMaxX;\n    window->DC.ItemWidth = column->ItemWidth;\n\n    window->SkipItems = column->IsSkipItems;\n    if (column->IsSkipItems)\n    {\n        g.LastItemData.ID = 0;\n        g.LastItemData.StatusFlags = 0;\n    }\n\n    if (table->Flags & ImGuiTableFlags_NoClip)\n    {\n        // FIXME: if we end up drawing all borders/bg in EndTable, could remove this and just assert that channel hasn't changed.\n        table->DrawSplitter->SetCurrentChannel(window->DrawList, TABLE_DRAW_CHANNEL_NOCLIP);\n        //IM_ASSERT(table->DrawSplitter._Current == TABLE_DRAW_CHANNEL_NOCLIP);\n    }\n    else\n    {\n        // FIXME-TABLE: Could avoid this if draw channel is dummy channel?\n        SetWindowClipRectBeforeSetChannel(window, column->ClipRect);\n        table->DrawSplitter->SetCurrentChannel(window->DrawList, column->DrawChannelCurrent);\n    }\n\n    // Logging\n    if (g.LogEnabled && !column->IsSkipItems)\n    {\n        LogRenderedText(&window->DC.CursorPos, \"|\");\n        g.LogLinePosY = FLT_MAX;\n    }\n}\n\n// [Internal] Called by TableNextRow()/TableSetColumnIndex()/TableNextColumn()\nvoid ImGui::TableEndCell(ImGuiTable* table)\n{\n    ImGuiTableColumn* column = &table->Columns[table->CurrentColumn];\n    ImGuiWindow* window = table->InnerWindow;\n\n    if (window->DC.IsSetPos)\n        ErrorCheckUsingSetCursorPosToExtendParentBoundaries();\n\n    // Report maximum position so we can infer content size per column.\n    float* p_max_pos_x;\n    if (table->RowFlags & ImGuiTableRowFlags_Headers)\n        p_max_pos_x = &column->ContentMaxXHeadersUsed;  // Useful in case user submit contents in header row that is not a TableHeader() call\n    else\n        p_max_pos_x = table->IsUnfrozenRows ? &column->ContentMaxXUnfrozen : &column->ContentMaxXFrozen;\n    *p_max_pos_x = ImMax(*p_max_pos_x, window->DC.CursorMaxPos.x);\n    if (column->IsEnabled)\n        table->RowPosY2 = ImMax(table->RowPosY2, window->DC.CursorMaxPos.y + table->RowCellPaddingY);\n    column->ItemWidth = window->DC.ItemWidth;\n\n    // Propagate text baseline for the entire row\n    // FIXME-TABLE: Here we propagate text baseline from the last line of the cell.. instead of the first one.\n    table->RowTextBaseline = ImMax(table->RowTextBaseline, window->DC.PrevLineTextBaseOffset);\n}\n\n//-------------------------------------------------------------------------\n// [SECTION] Tables: Columns width management\n//-------------------------------------------------------------------------\n// - TableGetMaxColumnWidth() [Internal]\n// - TableGetColumnWidthAuto() [Internal]\n// - TableSetColumnWidth()\n// - TableSetColumnWidthAutoSingle() [Internal]\n// - TableSetColumnWidthAutoAll() [Internal]\n// - TableUpdateColumnsWeightFromWidth() [Internal]\n//-------------------------------------------------------------------------\n// Note that actual columns widths are computed in TableUpdateLayout().\n//-------------------------------------------------------------------------\n\n// Maximum column content width given current layout. Use column->MinX so this value differs on a per-column basis.\nfloat ImGui::TableCalcMaxColumnWidth(const ImGuiTable* table, int column_n)\n{\n    const ImGuiTableColumn* column = &table->Columns[column_n];\n    float max_width = FLT_MAX;\n    const float min_column_distance = table->MinColumnWidth + table->CellPaddingX * 2.0f + table->CellSpacingX1 + table->CellSpacingX2;\n    if (table->Flags & ImGuiTableFlags_ScrollX)\n    {\n        // Frozen columns can't reach beyond visible width else scrolling will naturally break.\n        // (we use DisplayOrder as within a set of multiple frozen column reordering is possible)\n        if (column->DisplayOrder < table->FreezeColumnsRequest)\n        {\n            max_width = (table->InnerClipRect.Max.x - (table->FreezeColumnsRequest - column->DisplayOrder) * min_column_distance) - column->MinX;\n            max_width = max_width - table->OuterPaddingX - table->CellPaddingX - table->CellSpacingX2;\n        }\n    }\n    else if ((table->Flags & ImGuiTableFlags_NoKeepColumnsVisible) == 0)\n    {\n        // If horizontal scrolling if disabled, we apply a final lossless shrinking of columns in order to make\n        // sure they are all visible. Because of this we also know that all of the columns will always fit in\n        // table->WorkRect and therefore in table->InnerRect (because ScrollX is off)\n        // FIXME-TABLE: This is solved incorrectly but also quite a difficult problem to fix as we also want ClipRect width to match.\n        // See \"table_width_distrib\" and \"table_width_keep_visible\" tests\n        max_width = table->WorkRect.Max.x - (table->ColumnsEnabledCount - column->IndexWithinEnabledSet - 1) * min_column_distance - column->MinX;\n        //max_width -= table->CellSpacingX1;\n        max_width -= table->CellSpacingX2;\n        max_width -= table->CellPaddingX * 2.0f;\n        max_width -= table->OuterPaddingX;\n    }\n    return max_width;\n}\n\n// Note this is meant to be stored in column->WidthAuto, please generally use the WidthAuto field\nfloat ImGui::TableGetColumnWidthAuto(ImGuiTable* table, ImGuiTableColumn* column)\n{\n    const float content_width_body = ImMax(column->ContentMaxXFrozen, column->ContentMaxXUnfrozen) - column->WorkMinX;\n    const float content_width_headers = column->ContentMaxXHeadersIdeal - column->WorkMinX;\n    float width_auto = content_width_body;\n    if (!(column->Flags & ImGuiTableColumnFlags_NoHeaderWidth))\n        width_auto = ImMax(width_auto, content_width_headers);\n\n    // Non-resizable fixed columns preserve their requested width\n    if ((column->Flags & ImGuiTableColumnFlags_WidthFixed) && column->InitStretchWeightOrWidth > 0.0f)\n        if (!(table->Flags & ImGuiTableFlags_Resizable) || (column->Flags & ImGuiTableColumnFlags_NoResize))\n            width_auto = column->InitStretchWeightOrWidth;\n\n    return ImMax(width_auto, table->MinColumnWidth);\n}\n\n// 'width' = inner column width, without padding\nvoid ImGui::TableSetColumnWidth(int column_n, float width)\n{\n    ImGuiContext& g = *GImGui;\n    ImGuiTable* table = g.CurrentTable;\n    IM_ASSERT(table != NULL && table->IsLayoutLocked == false);\n    IM_ASSERT(column_n >= 0 && column_n < table->ColumnsCount);\n    ImGuiTableColumn* column_0 = &table->Columns[column_n];\n    float column_0_width = width;\n\n    // Apply constraints early\n    // Compare both requested and actual given width to avoid overwriting requested width when column is stuck (minimum size, bounded)\n    IM_ASSERT(table->MinColumnWidth > 0.0f);\n    const float min_width = table->MinColumnWidth;\n    const float max_width = ImMax(min_width, column_0->WidthMax); // Don't use TableCalcMaxColumnWidth() here as it would rely on MinX from last instance (#7933)\n    column_0_width = ImClamp(column_0_width, min_width, max_width);\n    if (column_0->WidthGiven == column_0_width || column_0->WidthRequest == column_0_width)\n        return;\n\n    //IMGUI_DEBUG_PRINT(\"TableSetColumnWidth(%d, %.1f->%.1f)\\n\", column_0_idx, column_0->WidthGiven, column_0_width);\n    ImGuiTableColumn* column_1 = (column_0->NextEnabledColumn != -1) ? &table->Columns[column_0->NextEnabledColumn] : NULL;\n\n    // In this surprisingly not simple because of how we support mixing Fixed and multiple Stretch columns.\n    // - All fixed: easy.\n    // - All stretch: easy.\n    // - One or more fixed + one stretch: easy.\n    // - One or more fixed + more than one stretch: tricky.\n    // Qt when manual resize is enabled only supports a single _trailing_ stretch column, we support more cases here.\n\n    // When forwarding resize from Wn| to Fn+1| we need to be considerate of the _NoResize flag on Fn+1.\n    // FIXME-TABLE: Find a way to rewrite all of this so interactions feel more consistent for the user.\n    // Scenarios:\n    // - F1 F2 F3  resize from F1| or F2|   --> ok: alter ->WidthRequested of Fixed column. Subsequent columns will be offset.\n    // - F1 F2 F3  resize from F3|          --> ok: alter ->WidthRequested of Fixed column. If active, ScrollX extent can be altered.\n    // - F1 F2 W3  resize from F1| or F2|   --> ok: alter ->WidthRequested of Fixed column. If active, ScrollX extent can be altered, but it doesn't make much sense as the Stretch column will always be minimal size.\n    // - F1 F2 W3  resize from W3|          --> ok: no-op (disabled by Resize Rule 1)\n    // - W1 W2 W3  resize from W1| or W2|   --> ok\n    // - W1 W2 W3  resize from W3|          --> ok: no-op (disabled by Resize Rule 1)\n    // - W1 F2 F3  resize from F3|          --> ok: no-op (disabled by Resize Rule 1)\n    // - W1 F2     resize from F2|          --> ok: no-op (disabled by Resize Rule 1)\n    // - W1 W2 F3  resize from W1| or W2|   --> ok\n    // - W1 F2 W3  resize from W1| or F2|   --> ok\n    // - F1 W2 F3  resize from W2|          --> ok\n    // - F1 W3 F2  resize from W3|          --> ok\n    // - W1 F2 F3  resize from W1|          --> ok: equivalent to resizing |F2. F3 will not move.\n    // - W1 F2 F3  resize from F2|          --> ok\n    // All resizes from a Wx columns are locking other columns.\n\n    // Possible improvements:\n    // - W1 W2 W3  resize W1|               --> to not be stuck, both W2 and W3 would stretch down. Seems possible to fix. Would be most beneficial to simplify resize of all-weighted columns.\n    // - W3 F1 F2  resize W3|               --> to not be stuck past F1|, both F1 and F2 would need to stretch down, which would be lossy or ambiguous. Seems hard to fix.\n\n    // [Resize Rule 1] Can't resize from right of right-most visible column if there is any Stretch column. Implemented in TableUpdateLayout().\n\n    // If we have all Fixed columns OR resizing a Fixed column that doesn't come after a Stretch one, we can do an offsetting resize.\n    // This is the preferred resize path\n    if (column_0->Flags & ImGuiTableColumnFlags_WidthFixed)\n        if (!column_1 || table->LeftMostStretchedColumn == -1 || table->Columns[table->LeftMostStretchedColumn].DisplayOrder >= column_0->DisplayOrder)\n        {\n            column_0->WidthRequest = column_0_width;\n            table->IsSettingsDirty = true;\n            return;\n        }\n\n    // We can also use previous column if there's no next one (this is used when doing an auto-fit on the right-most stretch column)\n    if (column_1 == NULL)\n        column_1 = (column_0->PrevEnabledColumn != -1) ? &table->Columns[column_0->PrevEnabledColumn] : NULL;\n    if (column_1 == NULL)\n        return;\n\n    // Resizing from right-side of a Stretch column before a Fixed column forward sizing to left-side of fixed column.\n    // (old_a + old_b == new_a + new_b) --> (new_a == old_a + old_b - new_b)\n    float column_1_width = ImMax(column_1->WidthRequest - (column_0_width - column_0->WidthRequest), min_width);\n    column_0_width = column_0->WidthRequest + column_1->WidthRequest - column_1_width;\n    IM_ASSERT(column_0_width > 0.0f && column_1_width > 0.0f);\n    column_0->WidthRequest = column_0_width;\n    column_1->WidthRequest = column_1_width;\n    if ((column_0->Flags | column_1->Flags) & ImGuiTableColumnFlags_WidthStretch)\n        TableUpdateColumnsWeightFromWidth(table);\n    table->IsSettingsDirty = true;\n}\n\n// Disable clipping then auto-fit, will take 2 frames\n// (we don't take a shortcut for unclipped columns to reduce inconsistencies when e.g. resizing multiple columns)\nvoid ImGui::TableSetColumnWidthAutoSingle(ImGuiTable* table, int column_n)\n{\n    // Single auto width uses auto-fit\n    ImGuiTableColumn* column = &table->Columns[column_n];\n    if (!column->IsEnabled)\n        return;\n    column->CannotSkipItemsQueue = (1 << 0);\n    table->AutoFitSingleColumn = (ImGuiTableColumnIdx)column_n;\n}\n\nvoid ImGui::TableSetColumnWidthAutoAll(ImGuiTable* table)\n{\n    for (int column_n = 0; column_n < table->ColumnsCount; column_n++)\n    {\n        ImGuiTableColumn* column = &table->Columns[column_n];\n        if (!column->IsEnabled && !(column->Flags & ImGuiTableColumnFlags_WidthStretch)) // Cannot reset weight of hidden stretch column\n            continue;\n        column->CannotSkipItemsQueue = (1 << 0);\n        column->AutoFitQueue = (1 << 1);\n    }\n}\n\nvoid ImGui::TableUpdateColumnsWeightFromWidth(ImGuiTable* table)\n{\n    IM_ASSERT(table->LeftMostStretchedColumn != -1 && table->RightMostStretchedColumn != -1);\n\n    // Measure existing quantities\n    float visible_weight = 0.0f;\n    float visible_width = 0.0f;\n    for (int column_n = 0; column_n < table->ColumnsCount; column_n++)\n    {\n        ImGuiTableColumn* column = &table->Columns[column_n];\n        if (!column->IsEnabled || !(column->Flags & ImGuiTableColumnFlags_WidthStretch))\n            continue;\n        IM_ASSERT(column->StretchWeight > 0.0f);\n        visible_weight += column->StretchWeight;\n        visible_width += column->WidthRequest;\n    }\n    IM_ASSERT(visible_weight > 0.0f && visible_width > 0.0f);\n\n    // Apply new weights\n    for (int column_n = 0; column_n < table->ColumnsCount; column_n++)\n    {\n        ImGuiTableColumn* column = &table->Columns[column_n];\n        if (!column->IsEnabled || !(column->Flags & ImGuiTableColumnFlags_WidthStretch))\n            continue;\n        column->StretchWeight = (column->WidthRequest / visible_width) * visible_weight;\n        IM_ASSERT(column->StretchWeight > 0.0f);\n    }\n}\n\n//-------------------------------------------------------------------------\n// [SECTION] Tables: Drawing\n//-------------------------------------------------------------------------\n// - TablePushBackgroundChannel() [Internal]\n// - TablePopBackgroundChannel() [Internal]\n// - TableSetupDrawChannels() [Internal]\n// - TableMergeDrawChannels() [Internal]\n// - TableGetColumnBorderCol() [Internal]\n// - TableDrawBorders() [Internal]\n//-------------------------------------------------------------------------\n\n// Bg2 is used by Selectable (and possibly other widgets) to render to the background.\n// Unlike our Bg0/1 channel which we uses for RowBg/CellBg/Borders and where we guarantee all shapes to be CPU-clipped, the Bg2 channel being widgets-facing will rely on regular ClipRect.\nvoid ImGui::TablePushBackgroundChannel()\n{\n    ImGuiContext& g = *GImGui;\n    ImGuiWindow* window = g.CurrentWindow;\n    ImGuiTable* table = g.CurrentTable;\n\n    // Optimization: avoid SetCurrentChannel() + PushClipRect()\n    table->HostBackupInnerClipRect = window->ClipRect;\n    SetWindowClipRectBeforeSetChannel(window, table->Bg2ClipRectForDrawCmd);\n    table->DrawSplitter->SetCurrentChannel(window->DrawList, table->Bg2DrawChannelCurrent);\n}\n\nvoid ImGui::TablePopBackgroundChannel()\n{\n    ImGuiContext& g = *GImGui;\n    ImGuiWindow* window = g.CurrentWindow;\n    ImGuiTable* table = g.CurrentTable;\n    ImGuiTableColumn* column = &table->Columns[table->CurrentColumn];\n\n    // Optimization: avoid PopClipRect() + SetCurrentChannel()\n    SetWindowClipRectBeforeSetChannel(window, table->HostBackupInnerClipRect);\n    table->DrawSplitter->SetCurrentChannel(window->DrawList, column->DrawChannelCurrent);\n}\n\n// Allocate draw channels. Called by TableUpdateLayout()\n// - We allocate them following storage order instead of display order so reordering columns won't needlessly\n//   increase overall dormant memory cost.\n// - We isolate headers draw commands in their own channels instead of just altering clip rects.\n//   This is in order to facilitate merging of draw commands.\n// - After crossing FreezeRowsCount, all columns see their current draw channel changed to a second set of channels.\n// - We only use the dummy draw channel so we can push a null clipping rectangle into it without affecting other\n//   channels, while simplifying per-row/per-cell overhead. It will be empty and discarded when merged.\n// - We allocate 1 or 2 background draw channels. This is because we know TablePushBackgroundChannel() is only used for\n//   horizontal spanning. If we allowed vertical spanning we'd need one background draw channel per merge group (1-4).\n// Draw channel allocation (before merging):\n// - NoClip                       --> 2+D+1 channels: bg0/1 + bg2 + foreground (same clip rect == always 1 draw call)\n// - Clip                         --> 2+D+N channels\n// - FreezeRows                   --> 2+D+N*2 (unless scrolling value is zero)\n// - FreezeRows || FreezeColunns  --> 3+D+N*2 (unless scrolling value is zero)\n// Where D is 1 if any column is clipped or hidden (dummy channel) otherwise 0.\nvoid ImGui::TableSetupDrawChannels(ImGuiTable* table)\n{\n    const int freeze_row_multiplier = (table->FreezeRowsCount > 0) ? 2 : 1;\n    const int channels_for_row = (table->Flags & ImGuiTableFlags_NoClip) ? 1 : table->ColumnsEnabledCount;\n    const int channels_for_bg = 1 + 1 * freeze_row_multiplier;\n    const int channels_for_dummy = (table->ColumnsEnabledCount < table->ColumnsCount || (memcmp(table->VisibleMaskByIndex, table->EnabledMaskByIndex, ImBitArrayGetStorageSizeInBytes(table->ColumnsCount)) != 0)) ? +1 : 0;\n    const int channels_total = channels_for_bg + (channels_for_row * freeze_row_multiplier) + channels_for_dummy;\n    table->DrawSplitter->Split(table->InnerWindow->DrawList, channels_total);\n    table->DummyDrawChannel = (ImGuiTableDrawChannelIdx)((channels_for_dummy > 0) ? channels_total - 1 : -1);\n    table->Bg2DrawChannelCurrent = TABLE_DRAW_CHANNEL_BG2_FROZEN;\n    table->Bg2DrawChannelUnfrozen = (ImGuiTableDrawChannelIdx)((table->FreezeRowsCount > 0) ? 2 + channels_for_row : TABLE_DRAW_CHANNEL_BG2_FROZEN);\n\n    int draw_channel_current = 2;\n    for (int column_n = 0; column_n < table->ColumnsCount; column_n++)\n    {\n        ImGuiTableColumn* column = &table->Columns[column_n];\n        if (column->IsVisibleX && column->IsVisibleY)\n        {\n            column->DrawChannelFrozen = (ImGuiTableDrawChannelIdx)(draw_channel_current);\n            column->DrawChannelUnfrozen = (ImGuiTableDrawChannelIdx)(draw_channel_current + (table->FreezeRowsCount > 0 ? channels_for_row + 1 : 0));\n            if (!(table->Flags & ImGuiTableFlags_NoClip))\n                draw_channel_current++;\n        }\n        else\n        {\n            column->DrawChannelFrozen = column->DrawChannelUnfrozen = table->DummyDrawChannel;\n        }\n        column->DrawChannelCurrent = column->DrawChannelFrozen;\n    }\n\n    // Initial draw cmd starts with a BgClipRect that matches the one of its host, to facilitate merge draw commands by default.\n    // All our cell highlight are manually clipped with BgClipRect. When unfreezing it will be made smaller to fit scrolling rect.\n    // (This technically isn't part of setting up draw channels, but is reasonably related to be done here)\n    table->BgClipRect = table->InnerClipRect;\n    table->Bg0ClipRectForDrawCmd = table->OuterWindow->ClipRect;\n    table->Bg2ClipRectForDrawCmd = table->HostClipRect;\n    IM_ASSERT(table->BgClipRect.Min.y <= table->BgClipRect.Max.y);\n}\n\n// This function reorder draw channels based on matching clip rectangle, to facilitate merging them. Called by EndTable().\n// For simplicity we call it TableMergeDrawChannels() but in fact it only reorder channels + overwrite ClipRect,\n// actual merging is done by table->DrawSplitter.Merge() which is called right after TableMergeDrawChannels().\n//\n// Columns where the contents didn't stray off their local clip rectangle can be merged. To achieve\n// this we merge their clip rect and make them contiguous in the channel list, so they can be merged\n// by the call to DrawSplitter.Merge() following to the call to this function.\n// We reorder draw commands by arranging them into a maximum of 4 distinct groups:\n//\n//   1 group:               2 groups:              2 groups:              4 groups:\n//   [ 0. ] no freeze       [ 0. ] row freeze      [ 01 ] col freeze      [ 01 ] row+col freeze\n//   [ .. ]  or no scroll   [ 2. ]  and v-scroll   [ .. ]  and h-scroll   [ 23 ]  and v+h-scroll\n//\n// Each column itself can use 1 channel (row freeze disabled) or 2 channels (row freeze enabled).\n// When the contents of a column didn't stray off its limit, we move its channels into the corresponding group\n// based on its position (within frozen rows/columns groups or not).\n// At the end of the operation our 1-4 groups will each have a ImDrawCmd using the same ClipRect.\n// This function assume that each column are pointing to a distinct draw channel,\n// otherwise merge_group->ChannelsCount will not match set bit count of merge_group->ChannelsMask.\n//\n// Column channels will not be merged into one of the 1-4 groups in the following cases:\n// - The contents stray off its clipping rectangle (we only compare the MaxX value, not the MinX value).\n//   Direct ImDrawList calls won't be taken into account by default, if you use them make sure the ImGui:: bounds\n//   matches, by e.g. calling SetCursorScreenPos().\n// - The channel uses more than one draw command itself. We drop all our attempt at merging stuff here..\n//   we could do better but it's going to be rare and probably not worth the hassle.\n// Columns for which the draw channel(s) haven't been merged with other will use their own ImDrawCmd.\n//\n// This function is particularly tricky to understand.. take a breath.\nvoid ImGui::TableMergeDrawChannels(ImGuiTable* table)\n{\n    ImGuiContext& g = *GImGui;\n    ImDrawListSplitter* splitter = table->DrawSplitter;\n    const bool has_freeze_v = (table->FreezeRowsCount > 0);\n    const bool has_freeze_h = (table->FreezeColumnsCount > 0);\n    IM_ASSERT(splitter->_Current == 0);\n\n    // Track which groups we are going to attempt to merge, and which channels goes into each group.\n    struct MergeGroup\n    {\n        ImRect          ClipRect;\n        int             ChannelsCount = 0;\n        ImBitArrayPtr   ChannelsMask = NULL;\n    };\n    int merge_group_mask = 0x00;\n    MergeGroup merge_groups[4];\n\n    // Use a reusable temp buffer for the merge masks as they are dynamically sized.\n    const int max_draw_channels = (4 + table->ColumnsCount * 2);\n    const int size_for_masks_bitarrays_one = (int)ImBitArrayGetStorageSizeInBytes(max_draw_channels);\n    g.TempBuffer.reserve(size_for_masks_bitarrays_one * 5);\n    memset(g.TempBuffer.Data, 0, size_for_masks_bitarrays_one * 5);\n    for (int n = 0; n < IM_ARRAYSIZE(merge_groups); n++)\n        merge_groups[n].ChannelsMask = (ImBitArrayPtr)(void*)(g.TempBuffer.Data + (size_for_masks_bitarrays_one * n));\n    ImBitArrayPtr remaining_mask = (ImBitArrayPtr)(void*)(g.TempBuffer.Data + (size_for_masks_bitarrays_one * 4));\n\n    // 1. Scan channels and take note of those which can be merged\n    for (int column_n = 0; column_n < table->ColumnsCount; column_n++)\n    {\n        if (!IM_BITARRAY_TESTBIT(table->VisibleMaskByIndex, column_n))\n            continue;\n        ImGuiTableColumn* column = &table->Columns[column_n];\n\n        const int merge_group_sub_count = has_freeze_v ? 2 : 1;\n        for (int merge_group_sub_n = 0; merge_group_sub_n < merge_group_sub_count; merge_group_sub_n++)\n        {\n            const int channel_no = (merge_group_sub_n == 0) ? column->DrawChannelFrozen : column->DrawChannelUnfrozen;\n\n            // Don't attempt to merge if there are multiple draw calls within the column\n            ImDrawChannel* src_channel = &splitter->_Channels[channel_no];\n            if (src_channel->_CmdBuffer.Size > 0 && src_channel->_CmdBuffer.back().ElemCount == 0 && src_channel->_CmdBuffer.back().UserCallback == NULL) // Equivalent of PopUnusedDrawCmd()\n                src_channel->_CmdBuffer.pop_back();\n            if (src_channel->_CmdBuffer.Size != 1)\n                continue;\n\n            // Find out the width of this merge group and check if it will fit in our column\n            // (note that we assume that rendering didn't stray on the left direction. we should need a CursorMinPos to detect it)\n            if (!(column->Flags & ImGuiTableColumnFlags_NoClip))\n            {\n                float content_max_x;\n                if (!has_freeze_v)\n                    content_max_x = ImMax(column->ContentMaxXUnfrozen, column->ContentMaxXHeadersUsed); // No row freeze\n                else if (merge_group_sub_n == 0)\n                    content_max_x = ImMax(column->ContentMaxXFrozen, column->ContentMaxXHeadersUsed);   // Row freeze: use width before freeze\n                else\n                    content_max_x = column->ContentMaxXUnfrozen;                                        // Row freeze: use width after freeze\n                if (content_max_x > column->ClipRect.Max.x)\n                    continue;\n            }\n\n            const int merge_group_n = (has_freeze_h && column_n < table->FreezeColumnsCount ? 0 : 1) + (has_freeze_v && merge_group_sub_n == 0 ? 0 : 2);\n            IM_ASSERT(channel_no < max_draw_channels);\n            MergeGroup* merge_group = &merge_groups[merge_group_n];\n            if (merge_group->ChannelsCount == 0)\n                merge_group->ClipRect = ImRect(+FLT_MAX, +FLT_MAX, -FLT_MAX, -FLT_MAX);\n            ImBitArraySetBit(merge_group->ChannelsMask, channel_no);\n            merge_group->ChannelsCount++;\n            merge_group->ClipRect.Add(src_channel->_CmdBuffer[0].ClipRect);\n            merge_group_mask |= (1 << merge_group_n);\n        }\n\n        // Invalidate current draw channel\n        // (we don't clear DrawChannelFrozen/DrawChannelUnfrozen solely to facilitate debugging/later inspection of data)\n        column->DrawChannelCurrent = (ImGuiTableDrawChannelIdx)-1;\n    }\n\n    // [DEBUG] Display merge groups\n#if 0\n    if (g.IO.KeyShift)\n        for (int merge_group_n = 0; merge_group_n < IM_ARRAYSIZE(merge_groups); merge_group_n++)\n        {\n            MergeGroup* merge_group = &merge_groups[merge_group_n];\n            if (merge_group->ChannelsCount == 0)\n                continue;\n            char buf[32];\n            ImFormatString(buf, 32, \"MG%d:%d\", merge_group_n, merge_group->ChannelsCount);\n            ImVec2 text_pos = merge_group->ClipRect.Min + ImVec2(4, 4);\n            ImVec2 text_size = CalcTextSize(buf, NULL);\n            GetForegroundDrawList()->AddRectFilled(text_pos, text_pos + text_size, IM_COL32(0, 0, 0, 255));\n            GetForegroundDrawList()->AddText(text_pos, IM_COL32(255, 255, 0, 255), buf, NULL);\n            GetForegroundDrawList()->AddRect(merge_group->ClipRect.Min, merge_group->ClipRect.Max, IM_COL32(255, 255, 0, 255));\n        }\n#endif\n\n    // 2. Rewrite channel list in our preferred order\n    if (merge_group_mask != 0)\n    {\n        // We skip channel 0 (Bg0/Bg1) and 1 (Bg2 frozen) from the shuffling since they won't move - see channels allocation in TableSetupDrawChannels().\n        const int LEADING_DRAW_CHANNELS = 2;\n        g.DrawChannelsTempMergeBuffer.resize(splitter->_Count - LEADING_DRAW_CHANNELS); // Use shared temporary storage so the allocation gets amortized\n        ImDrawChannel* dst_tmp = g.DrawChannelsTempMergeBuffer.Data;\n        ImBitArraySetBitRange(remaining_mask, LEADING_DRAW_CHANNELS, splitter->_Count);\n        ImBitArrayClearBit(remaining_mask, table->Bg2DrawChannelUnfrozen);\n        IM_ASSERT(has_freeze_v == false || table->Bg2DrawChannelUnfrozen != TABLE_DRAW_CHANNEL_BG2_FROZEN);\n        int remaining_count = splitter->_Count - (has_freeze_v ? LEADING_DRAW_CHANNELS + 1 : LEADING_DRAW_CHANNELS);\n        //ImRect host_rect = (table->InnerWindow == table->OuterWindow) ? table->InnerClipRect : table->HostClipRect;\n        ImRect host_rect = table->HostClipRect;\n        for (int merge_group_n = 0; merge_group_n < IM_ARRAYSIZE(merge_groups); merge_group_n++)\n        {\n            if (int merge_channels_count = merge_groups[merge_group_n].ChannelsCount)\n            {\n                MergeGroup* merge_group = &merge_groups[merge_group_n];\n                ImRect merge_clip_rect = merge_group->ClipRect;\n\n                // Extend outer-most clip limits to match those of host, so draw calls can be merged even if\n                // outer-most columns have some outer padding offsetting them from their parent ClipRect.\n                // The principal cases this is dealing with are:\n                // - On a same-window table (not scrolling = single group), all fitting columns ClipRect -> will extend and match host ClipRect -> will merge\n                // - Columns can use padding and have left-most ClipRect.Min.x and right-most ClipRect.Max.x != from host ClipRect -> will extend and match host ClipRect -> will merge\n                // FIXME-TABLE FIXME-WORKRECT: We are wasting a merge opportunity on tables without scrolling if column doesn't fit\n                // within host clip rect, solely because of the half-padding difference between window->WorkRect and window->InnerClipRect.\n                if ((merge_group_n & 1) == 0 || !has_freeze_h)\n                    merge_clip_rect.Min.x = ImMin(merge_clip_rect.Min.x, host_rect.Min.x);\n                if ((merge_group_n & 2) == 0 || !has_freeze_v)\n                    merge_clip_rect.Min.y = ImMin(merge_clip_rect.Min.y, host_rect.Min.y);\n                if ((merge_group_n & 1) != 0)\n                    merge_clip_rect.Max.x = ImMax(merge_clip_rect.Max.x, host_rect.Max.x);\n                if ((merge_group_n & 2) != 0 && (table->Flags & ImGuiTableFlags_NoHostExtendY) == 0)\n                    merge_clip_rect.Max.y = ImMax(merge_clip_rect.Max.y, host_rect.Max.y);\n                //GetForegroundDrawList()->AddRect(merge_group->ClipRect.Min, merge_group->ClipRect.Max, IM_COL32(255, 0, 0, 200), 0.0f, 0, 1.0f); // [DEBUG]\n                //GetForegroundDrawList()->AddLine(merge_group->ClipRect.Min, merge_clip_rect.Min, IM_COL32(255, 100, 0, 200));\n                //GetForegroundDrawList()->AddLine(merge_group->ClipRect.Max, merge_clip_rect.Max, IM_COL32(255, 100, 0, 200));\n                remaining_count -= merge_group->ChannelsCount;\n                for (int n = 0; n < (size_for_masks_bitarrays_one >> 2); n++)\n                    remaining_mask[n] &= ~merge_group->ChannelsMask[n];\n                for (int n = 0; n < splitter->_Count && merge_channels_count != 0; n++)\n                {\n                    // Copy + overwrite new clip rect\n                    if (!IM_BITARRAY_TESTBIT(merge_group->ChannelsMask, n))\n                        continue;\n                    IM_BITARRAY_CLEARBIT(merge_group->ChannelsMask, n);\n                    merge_channels_count--;\n\n                    ImDrawChannel* channel = &splitter->_Channels[n];\n                    IM_ASSERT(channel->_CmdBuffer.Size == 1 && merge_clip_rect.Contains(ImRect(channel->_CmdBuffer[0].ClipRect)));\n                    channel->_CmdBuffer[0].ClipRect = merge_clip_rect.ToVec4();\n                    memcpy(dst_tmp++, channel, sizeof(ImDrawChannel));\n                }\n            }\n\n            // Make sure Bg2DrawChannelUnfrozen appears in the middle of our groups (whereas Bg0/Bg1 and Bg2 frozen are fixed to 0 and 1)\n            if (merge_group_n == 1 && has_freeze_v)\n                memcpy(dst_tmp++, &splitter->_Channels[table->Bg2DrawChannelUnfrozen], sizeof(ImDrawChannel));\n        }\n\n        // Append unmergeable channels that we didn't reorder at the end of the list\n        for (int n = 0; n < splitter->_Count && remaining_count != 0; n++)\n        {\n            if (!IM_BITARRAY_TESTBIT(remaining_mask, n))\n                continue;\n            ImDrawChannel* channel = &splitter->_Channels[n];\n            memcpy(dst_tmp++, channel, sizeof(ImDrawChannel));\n            remaining_count--;\n        }\n        IM_ASSERT(dst_tmp == g.DrawChannelsTempMergeBuffer.Data + g.DrawChannelsTempMergeBuffer.Size);\n        memcpy(splitter->_Channels.Data + LEADING_DRAW_CHANNELS, g.DrawChannelsTempMergeBuffer.Data, (splitter->_Count - LEADING_DRAW_CHANNELS) * sizeof(ImDrawChannel));\n    }\n}\n\nstatic ImU32 TableGetColumnBorderCol(ImGuiTable* table, int order_n, int column_n)\n{\n    const bool is_hovered = (table->HoveredColumnBorder == column_n);\n    const bool is_resized = (table->ResizedColumn == column_n) && (table->InstanceInteracted == table->InstanceCurrent);\n    const bool is_frozen_separator = (table->FreezeColumnsCount == order_n + 1);\n    if (is_resized || is_hovered)\n        return ImGui::GetColorU32(is_resized ? ImGuiCol_SeparatorActive : ImGuiCol_SeparatorHovered);\n    if (is_frozen_separator || (table->Flags & (ImGuiTableFlags_NoBordersInBody | ImGuiTableFlags_NoBordersInBodyUntilResize)))\n        return table->BorderColorStrong;\n    return table->BorderColorLight;\n}\n\n// FIXME-TABLE: This is a mess, need to redesign how we render borders (as some are also done in TableEndRow)\nvoid ImGui::TableDrawBorders(ImGuiTable* table)\n{\n    ImGuiWindow* inner_window = table->InnerWindow;\n    if (!table->OuterWindow->ClipRect.Overlaps(table->OuterRect))\n        return;\n\n    ImDrawList* inner_drawlist = inner_window->DrawList;\n    table->DrawSplitter->SetCurrentChannel(inner_drawlist, TABLE_DRAW_CHANNEL_BG0);\n    inner_drawlist->PushClipRect(table->Bg0ClipRectForDrawCmd.Min, table->Bg0ClipRectForDrawCmd.Max, false);\n\n    // Draw inner border and resizing feedback\n    ImGuiTableInstanceData* table_instance = TableGetInstanceData(table, table->InstanceCurrent);\n    const float border_size = TABLE_BORDER_SIZE;\n    const float draw_y1 = ImMax(table->InnerRect.Min.y, (table->FreezeRowsCount >= 1 ? table->InnerRect.Min.y : table->WorkRect.Min.y) + table->AngledHeadersHeight) + ((table->Flags & ImGuiTableFlags_BordersOuterH) ? 1.0f : 0.0f);\n    const float draw_y2_body = table->InnerRect.Max.y;\n    const float draw_y2_head = table->IsUsingHeaders ? ImMin(table->InnerRect.Max.y, (table->FreezeRowsCount >= 1 ? table->InnerRect.Min.y : table->WorkRect.Min.y) + table_instance->LastTopHeadersRowHeight) : draw_y1;\n    if (table->Flags & ImGuiTableFlags_BordersInnerV)\n    {\n        for (int order_n = 0; order_n < table->ColumnsCount; order_n++)\n        {\n            if (!IM_BITARRAY_TESTBIT(table->EnabledMaskByDisplayOrder, order_n))\n                continue;\n\n            const int column_n = table->DisplayOrderToIndex[order_n];\n            ImGuiTableColumn* column = &table->Columns[column_n];\n            const bool is_hovered = (table->HoveredColumnBorder == column_n);\n            const bool is_resized = (table->ResizedColumn == column_n) && (table->InstanceInteracted == table->InstanceCurrent);\n            const bool is_resizable = (column->Flags & (ImGuiTableColumnFlags_NoResize | ImGuiTableColumnFlags_NoDirectResize_)) == 0;\n            const bool is_frozen_separator = (table->FreezeColumnsCount == order_n + 1);\n            if (column->MaxX > table->InnerClipRect.Max.x && !is_resized)\n                continue;\n\n            // Decide whether right-most column is visible\n            if (column->NextEnabledColumn == -1 && !is_resizable)\n                if ((table->Flags & ImGuiTableFlags_SizingMask_) != ImGuiTableFlags_SizingFixedSame || (table->Flags & ImGuiTableFlags_NoHostExtendX))\n                    continue;\n            if (column->MaxX <= column->ClipRect.Min.x) // FIXME-TABLE FIXME-STYLE: Assume BorderSize==1, this is problematic if we want to increase the border size..\n                continue;\n\n            // Draw in outer window so right-most column won't be clipped\n            // Always draw full height border when being resized/hovered, or on the delimitation of frozen column scrolling.\n            float draw_y2 = (is_hovered || is_resized || is_frozen_separator || (table->Flags & (ImGuiTableFlags_NoBordersInBody | ImGuiTableFlags_NoBordersInBodyUntilResize)) == 0) ? draw_y2_body : draw_y2_head;\n            if (draw_y2 > draw_y1)\n                inner_drawlist->AddLine(ImVec2(column->MaxX, draw_y1), ImVec2(column->MaxX, draw_y2), TableGetColumnBorderCol(table, order_n, column_n), border_size);\n        }\n    }\n\n    // Draw outer border\n    // FIXME: could use AddRect or explicit VLine/HLine helper?\n    if (table->Flags & ImGuiTableFlags_BordersOuter)\n    {\n        // Display outer border offset by 1 which is a simple way to display it without adding an extra draw call\n        // (Without the offset, in outer_window it would be rendered behind cells, because child windows are above their\n        // parent. In inner_window, it won't reach out over scrollbars. Another weird solution would be to display part\n        // of it in inner window, and the part that's over scrollbars in the outer window..)\n        // Either solution currently won't allow us to use a larger border size: the border would clipped.\n        const ImRect outer_border = table->OuterRect;\n        const ImU32 outer_col = table->BorderColorStrong;\n        if ((table->Flags & ImGuiTableFlags_BordersOuter) == ImGuiTableFlags_BordersOuter)\n        {\n            inner_drawlist->AddRect(outer_border.Min, outer_border.Max, outer_col, 0.0f, 0, border_size);\n        }\n        else if (table->Flags & ImGuiTableFlags_BordersOuterV)\n        {\n            inner_drawlist->AddLine(outer_border.Min, ImVec2(outer_border.Min.x, outer_border.Max.y), outer_col, border_size);\n            inner_drawlist->AddLine(ImVec2(outer_border.Max.x, outer_border.Min.y), outer_border.Max, outer_col, border_size);\n        }\n        else if (table->Flags & ImGuiTableFlags_BordersOuterH)\n        {\n            inner_drawlist->AddLine(outer_border.Min, ImVec2(outer_border.Max.x, outer_border.Min.y), outer_col, border_size);\n            inner_drawlist->AddLine(ImVec2(outer_border.Min.x, outer_border.Max.y), outer_border.Max, outer_col, border_size);\n        }\n    }\n    if ((table->Flags & ImGuiTableFlags_BordersInnerH) && table->RowPosY2 < table->OuterRect.Max.y)\n    {\n        // Draw bottom-most row border between it is above outer border.\n        const float border_y = table->RowPosY2;\n        if (border_y >= table->BgClipRect.Min.y && border_y < table->BgClipRect.Max.y)\n            inner_drawlist->AddLine(ImVec2(table->BorderX1, border_y), ImVec2(table->BorderX2, border_y), table->BorderColorLight, border_size);\n    }\n\n    inner_drawlist->PopClipRect();\n}\n\n//-------------------------------------------------------------------------\n// [SECTION] Tables: Sorting\n//-------------------------------------------------------------------------\n// - TableGetSortSpecs()\n// - TableFixColumnSortDirection() [Internal]\n// - TableGetColumnNextSortDirection() [Internal]\n// - TableSetColumnSortDirection() [Internal]\n// - TableSortSpecsSanitize() [Internal]\n// - TableSortSpecsBuild() [Internal]\n//-------------------------------------------------------------------------\n\n// Return NULL if no sort specs (most often when ImGuiTableFlags_Sortable is not set)\n// When 'sort_specs->SpecsDirty == true' you should sort your data. It will be true when sorting specs have\n// changed since last call, or the first time. Make sure to set 'SpecsDirty = false' after sorting,\n// else you may wastefully sort your data every frame!\n// Lifetime: don't hold on this pointer over multiple frames or past any subsequent call to BeginTable()!\nImGuiTableSortSpecs* ImGui::TableGetSortSpecs()\n{\n    ImGuiContext& g = *GImGui;\n    ImGuiTable* table = g.CurrentTable;\n    IM_ASSERT(table != NULL);\n\n    if (!(table->Flags & ImGuiTableFlags_Sortable))\n        return NULL;\n\n    // Require layout (in case TableHeadersRow() hasn't been called) as it may alter IsSortSpecsDirty in some paths.\n    if (!table->IsLayoutLocked)\n        TableUpdateLayout(table);\n\n    TableSortSpecsBuild(table);\n    return &table->SortSpecs;\n}\n\nstatic inline ImGuiSortDirection TableGetColumnAvailSortDirection(ImGuiTableColumn* column, int n)\n{\n    IM_ASSERT(n < column->SortDirectionsAvailCount);\n    return (ImGuiSortDirection)((column->SortDirectionsAvailList >> (n << 1)) & 0x03);\n}\n\n// Fix sort direction if currently set on a value which is unavailable (e.g. activating NoSortAscending/NoSortDescending)\nvoid ImGui::TableFixColumnSortDirection(ImGuiTable* table, ImGuiTableColumn* column)\n{\n    if (column->SortOrder == -1 || (column->SortDirectionsAvailMask & (1 << column->SortDirection)) != 0)\n        return;\n    column->SortDirection = (ImU8)TableGetColumnAvailSortDirection(column, 0);\n    table->IsSortSpecsDirty = true;\n}\n\n// Calculate next sort direction that would be set after clicking the column\n// - If the PreferSortDescending flag is set, we will default to a Descending direction on the first click.\n// - Note that the PreferSortAscending flag is never checked, it is essentially the default and therefore a no-op.\nIM_STATIC_ASSERT(ImGuiSortDirection_None == 0 && ImGuiSortDirection_Ascending == 1 && ImGuiSortDirection_Descending == 2);\nImGuiSortDirection ImGui::TableGetColumnNextSortDirection(ImGuiTableColumn* column)\n{\n    IM_ASSERT(column->SortDirectionsAvailCount > 0);\n    if (column->SortOrder == -1)\n        return TableGetColumnAvailSortDirection(column, 0);\n    for (int n = 0; n < 3; n++)\n        if (column->SortDirection == TableGetColumnAvailSortDirection(column, n))\n            return TableGetColumnAvailSortDirection(column, (n + 1) % column->SortDirectionsAvailCount);\n    IM_ASSERT(0);\n    return ImGuiSortDirection_None;\n}\n\n// Note that the NoSortAscending/NoSortDescending flags are processed in TableSortSpecsSanitize(), and they may change/revert\n// the value of SortDirection. We could technically also do it here but it would be unnecessary and duplicate code.\nvoid ImGui::TableSetColumnSortDirection(int column_n, ImGuiSortDirection sort_direction, bool append_to_sort_specs)\n{\n    ImGuiContext& g = *GImGui;\n    ImGuiTable* table = g.CurrentTable;\n\n    if (!(table->Flags & ImGuiTableFlags_SortMulti))\n        append_to_sort_specs = false;\n    if (!(table->Flags & ImGuiTableFlags_SortTristate))\n        IM_ASSERT(sort_direction != ImGuiSortDirection_None);\n\n    ImGuiTableColumnIdx sort_order_max = 0;\n    if (append_to_sort_specs)\n        for (int other_column_n = 0; other_column_n < table->ColumnsCount; other_column_n++)\n            sort_order_max = ImMax(sort_order_max, table->Columns[other_column_n].SortOrder);\n\n    ImGuiTableColumn* column = &table->Columns[column_n];\n    column->SortDirection = (ImU8)sort_direction;\n    if (column->SortDirection == ImGuiSortDirection_None)\n        column->SortOrder = -1;\n    else if (column->SortOrder == -1 || !append_to_sort_specs)\n        column->SortOrder = append_to_sort_specs ? sort_order_max + 1 : 0;\n\n    for (int other_column_n = 0; other_column_n < table->ColumnsCount; other_column_n++)\n    {\n        ImGuiTableColumn* other_column = &table->Columns[other_column_n];\n        if (other_column != column && !append_to_sort_specs)\n            other_column->SortOrder = -1;\n        TableFixColumnSortDirection(table, other_column);\n    }\n    table->IsSettingsDirty = true;\n    table->IsSortSpecsDirty = true;\n}\n\nvoid ImGui::TableSortSpecsSanitize(ImGuiTable* table)\n{\n    IM_ASSERT(table->Flags & ImGuiTableFlags_Sortable);\n\n    // Clear SortOrder from hidden column and verify that there's no gap or duplicate.\n    int sort_order_count = 0;\n    ImU64 sort_order_mask = 0x00;\n    for (int column_n = 0; column_n < table->ColumnsCount; column_n++)\n    {\n        ImGuiTableColumn* column = &table->Columns[column_n];\n        if (column->SortOrder != -1 && !column->IsEnabled)\n            column->SortOrder = -1;\n        if (column->SortOrder == -1)\n            continue;\n        sort_order_count++;\n        sort_order_mask |= ((ImU64)1 << column->SortOrder);\n        IM_ASSERT(sort_order_count < (int)sizeof(sort_order_mask) * 8);\n    }\n\n    const bool need_fix_linearize = ((ImU64)1 << sort_order_count) != (sort_order_mask + 1);\n    const bool need_fix_single_sort_order = (sort_order_count > 1) && !(table->Flags & ImGuiTableFlags_SortMulti);\n    if (need_fix_linearize || need_fix_single_sort_order)\n    {\n        ImU64 fixed_mask = 0x00;\n        for (int sort_n = 0; sort_n < sort_order_count; sort_n++)\n        {\n            // Fix: Rewrite sort order fields if needed so they have no gap or duplicate.\n            // (e.g. SortOrder 0 disappeared, SortOrder 1..2 exists --> rewrite then as SortOrder 0..1)\n            int column_with_smallest_sort_order = -1;\n            for (int column_n = 0; column_n < table->ColumnsCount; column_n++)\n                if ((fixed_mask & ((ImU64)1 << (ImU64)column_n)) == 0 && table->Columns[column_n].SortOrder != -1)\n                    if (column_with_smallest_sort_order == -1 || table->Columns[column_n].SortOrder < table->Columns[column_with_smallest_sort_order].SortOrder)\n                        column_with_smallest_sort_order = column_n;\n            IM_ASSERT(column_with_smallest_sort_order != -1);\n            fixed_mask |= ((ImU64)1 << column_with_smallest_sort_order);\n            table->Columns[column_with_smallest_sort_order].SortOrder = (ImGuiTableColumnIdx)sort_n;\n\n            // Fix: Make sure only one column has a SortOrder if ImGuiTableFlags_MultiSortable is not set.\n            if (need_fix_single_sort_order)\n            {\n                sort_order_count = 1;\n                for (int column_n = 0; column_n < table->ColumnsCount; column_n++)\n                    if (column_n != column_with_smallest_sort_order)\n                        table->Columns[column_n].SortOrder = -1;\n                break;\n            }\n        }\n    }\n\n    // Fallback default sort order (if no column with the ImGuiTableColumnFlags_DefaultSort flag)\n    if (sort_order_count == 0 && !(table->Flags & ImGuiTableFlags_SortTristate))\n        for (int column_n = 0; column_n < table->ColumnsCount; column_n++)\n        {\n            ImGuiTableColumn* column = &table->Columns[column_n];\n            if (column->IsEnabled && !(column->Flags & ImGuiTableColumnFlags_NoSort))\n            {\n                sort_order_count = 1;\n                column->SortOrder = 0;\n                column->SortDirection = (ImU8)TableGetColumnAvailSortDirection(column, 0);\n                break;\n            }\n        }\n\n    table->SortSpecsCount = (ImGuiTableColumnIdx)sort_order_count;\n}\n\nvoid ImGui::TableSortSpecsBuild(ImGuiTable* table)\n{\n    bool dirty = table->IsSortSpecsDirty;\n    if (dirty)\n    {\n        TableSortSpecsSanitize(table);\n        table->SortSpecsMulti.resize(table->SortSpecsCount <= 1 ? 0 : table->SortSpecsCount);\n        table->SortSpecs.SpecsDirty = true; // Mark as dirty for user\n        table->IsSortSpecsDirty = false; // Mark as not dirty for us\n    }\n\n    // Write output\n    // May be able to move all SortSpecs data from table (48 bytes) to ImGuiTableTempData if we decide to write it back on every BeginTable()\n    ImGuiTableColumnSortSpecs* sort_specs = (table->SortSpecsCount == 0) ? NULL : (table->SortSpecsCount == 1) ? &table->SortSpecsSingle : table->SortSpecsMulti.Data;\n    if (dirty && sort_specs != NULL)\n        for (int column_n = 0; column_n < table->ColumnsCount; column_n++)\n        {\n            ImGuiTableColumn* column = &table->Columns[column_n];\n            if (column->SortOrder == -1)\n                continue;\n            IM_ASSERT(column->SortOrder < table->SortSpecsCount);\n            ImGuiTableColumnSortSpecs* sort_spec = &sort_specs[column->SortOrder];\n            sort_spec->ColumnUserID = column->UserID;\n            sort_spec->ColumnIndex = (ImGuiTableColumnIdx)column_n;\n            sort_spec->SortOrder = (ImGuiTableColumnIdx)column->SortOrder;\n            sort_spec->SortDirection = (ImGuiSortDirection)column->SortDirection;\n        }\n\n    table->SortSpecs.Specs = sort_specs;\n    table->SortSpecs.SpecsCount = table->SortSpecsCount;\n}\n\n//-------------------------------------------------------------------------\n// [SECTION] Tables: Headers\n//-------------------------------------------------------------------------\n// - TableGetHeaderRowHeight() [Internal]\n// - TableGetHeaderAngledMaxLabelWidth() [Internal]\n// - TableHeadersRow()\n// - TableHeader()\n// - TableAngledHeadersRow()\n// - TableAngledHeadersRowEx() [Internal]\n//-------------------------------------------------------------------------\n\nfloat ImGui::TableGetHeaderRowHeight()\n{\n    // Caring for a minor edge case:\n    // Calculate row height, for the unlikely case that some labels may be taller than others.\n    // If we didn't do that, uneven header height would highlight but smaller one before the tallest wouldn't catch input for all height.\n    // In your custom header row you may omit this all together and just call TableNextRow() without a height...\n    ImGuiContext& g = *GImGui;\n    ImGuiTable* table = g.CurrentTable;\n    float row_height = g.FontSize;\n    for (int column_n = 0; column_n < table->ColumnsCount; column_n++)\n        if (IM_BITARRAY_TESTBIT(table->EnabledMaskByIndex, column_n))\n            if ((table->Columns[column_n].Flags & ImGuiTableColumnFlags_NoHeaderLabel) == 0)\n                row_height = ImMax(row_height, CalcTextSize(TableGetColumnName(table, column_n)).y);\n    return row_height + g.Style.CellPadding.y * 2.0f;\n}\n\nfloat ImGui::TableGetHeaderAngledMaxLabelWidth()\n{\n    ImGuiContext& g = *GImGui;\n    ImGuiTable* table = g.CurrentTable;\n    float width = 0.0f;\n    for (int column_n = 0; column_n < table->ColumnsCount; column_n++)\n        if (IM_BITARRAY_TESTBIT(table->EnabledMaskByIndex, column_n))\n            if (table->Columns[column_n].Flags & ImGuiTableColumnFlags_AngledHeader)\n                width = ImMax(width, CalcTextSize(TableGetColumnName(table, column_n), NULL, true).x);\n    return width + g.Style.CellPadding.y * 2.0f; // Swap padding\n}\n\n// [Public] This is a helper to output TableHeader() calls based on the column names declared in TableSetupColumn().\n// The intent is that advanced users willing to create customized headers would not need to use this helper\n// and can create their own! For example: TableHeader() may be preceded by Checkbox() or other custom widgets.\n// See 'Demo->Tables->Custom headers' for a demonstration of implementing a custom version of this.\n// This code is intentionally written to not make much use of internal functions, to give you better direction\n// if you need to write your own.\n// FIXME-TABLE: TableOpenContextMenu() and TableGetHeaderRowHeight() are not public.\nvoid ImGui::TableHeadersRow()\n{\n    ImGuiContext& g = *GImGui;\n    ImGuiTable* table = g.CurrentTable;\n    if (table == NULL)\n    {\n        IM_ASSERT_USER_ERROR(table != NULL, \"Call should only be done while in BeginTable() scope!\");\n        return;\n    }\n\n    // Call layout if not already done. This is automatically done by TableNextRow: we do it here _only_ to make\n    // it easier to debug-step in TableUpdateLayout(). Your own version of this function doesn't need this.\n    if (!table->IsLayoutLocked)\n        TableUpdateLayout(table);\n\n    // Open row\n    const float row_height = TableGetHeaderRowHeight();\n    TableNextRow(ImGuiTableRowFlags_Headers, row_height);\n    const float row_y1 = GetCursorScreenPos().y;\n    if (table->HostSkipItems) // Merely an optimization, you may skip in your own code.\n        return;\n\n    const int columns_count = TableGetColumnCount();\n    for (int column_n = 0; column_n < columns_count; column_n++)\n    {\n        if (!TableSetColumnIndex(column_n))\n            continue;\n\n        // Push an id to allow empty/unnamed headers. This is also idiomatic as it ensure there is a consistent ID path to access columns (for e.g. automation)\n        const char* name = (TableGetColumnFlags(column_n) & ImGuiTableColumnFlags_NoHeaderLabel) ? \"\" : TableGetColumnName(column_n);\n        PushID(column_n);\n        TableHeader(name);\n        PopID();\n    }\n\n    // Allow opening popup from the right-most section after the last column.\n    ImVec2 mouse_pos = ImGui::GetMousePos();\n    if (IsMouseReleased(1) && TableGetHoveredColumn() == columns_count)\n        if (mouse_pos.y >= row_y1 && mouse_pos.y < row_y1 + row_height)\n            TableOpenContextMenu(columns_count); // Will open a non-column-specific popup.\n}\n\n// Emit a column header (text + optional sort order)\n// We cpu-clip text here so that all columns headers can be merged into a same draw call.\n// Note that because of how we cpu-clip and display sorting indicators, you _cannot_ use SameLine() after a TableHeader()\nvoid ImGui::TableHeader(const char* label)\n{\n    ImGuiContext& g = *GImGui;\n    ImGuiWindow* window = g.CurrentWindow;\n    if (window->SkipItems)\n        return;\n\n    ImGuiTable* table = g.CurrentTable;\n    if (table == NULL)\n    {\n        IM_ASSERT_USER_ERROR(table != NULL, \"Call should only be done while in BeginTable() scope!\");\n        return;\n    }\n\n    IM_ASSERT(table->CurrentColumn != -1);\n    const int column_n = table->CurrentColumn;\n    ImGuiTableColumn* column = &table->Columns[column_n];\n\n    // Label\n    if (label == NULL)\n        label = \"\";\n    const char* label_end = FindRenderedTextEnd(label);\n    ImVec2 label_size = CalcTextSize(label, label_end, true);\n    ImVec2 label_pos = window->DC.CursorPos;\n\n    // If we already got a row height, there's use that.\n    // FIXME-TABLE: Padding problem if the correct outer-padding CellBgRect strays off our ClipRect?\n    ImRect cell_r = TableGetCellBgRect(table, column_n);\n    float label_height = ImMax(label_size.y, table->RowMinHeight - table->RowCellPaddingY * 2.0f);\n\n    // Calculate ideal size for sort order arrow\n    float w_arrow = 0.0f;\n    float w_sort_text = 0.0f;\n    bool sort_arrow = false;\n    char sort_order_suf[4] = \"\";\n    const float ARROW_SCALE = 0.65f;\n    if ((table->Flags & ImGuiTableFlags_Sortable) && !(column->Flags & ImGuiTableColumnFlags_NoSort))\n    {\n        w_arrow = ImTrunc(g.FontSize * ARROW_SCALE + g.Style.FramePadding.x);\n        if (column->SortOrder != -1)\n            sort_arrow = true;\n        if (column->SortOrder > 0)\n        {\n            ImFormatString(sort_order_suf, IM_ARRAYSIZE(sort_order_suf), \"%d\", column->SortOrder + 1);\n            w_sort_text = g.Style.ItemInnerSpacing.x + CalcTextSize(sort_order_suf).x;\n        }\n    }\n\n    // We feed our unclipped width to the column without writing on CursorMaxPos, so that column is still considered for merging.\n    float max_pos_x = label_pos.x + label_size.x + w_sort_text + w_arrow;\n    column->ContentMaxXHeadersUsed = ImMax(column->ContentMaxXHeadersUsed, sort_arrow ? cell_r.Max.x : ImMin(max_pos_x, cell_r.Max.x));\n    column->ContentMaxXHeadersIdeal = ImMax(column->ContentMaxXHeadersIdeal, max_pos_x);\n\n    // Keep header highlighted when context menu is open.\n    ImGuiID id = window->GetID(label);\n    ImRect bb(cell_r.Min.x, cell_r.Min.y, cell_r.Max.x, ImMax(cell_r.Max.y, cell_r.Min.y + label_height + g.Style.CellPadding.y * 2.0f));\n    ItemSize(ImVec2(0.0f, label_height)); // Don't declare unclipped width, it'll be fed ContentMaxPosHeadersIdeal\n    if (!ItemAdd(bb, id))\n        return;\n\n    //GetForegroundDrawList()->AddRect(cell_r.Min, cell_r.Max, IM_COL32(255, 0, 0, 255)); // [DEBUG]\n    //GetForegroundDrawList()->AddRect(bb.Min, bb.Max, IM_COL32(255, 0, 0, 255)); // [DEBUG]\n\n    // Using AllowOverlap mode because we cover the whole cell, and we want user to be able to submit subsequent items.\n    const bool highlight = (table->HighlightColumnHeader == column_n);\n    bool hovered, held;\n    bool pressed = ButtonBehavior(bb, id, &hovered, &held, ImGuiButtonFlags_AllowOverlap);\n    if (held || hovered || highlight)\n    {\n        const ImU32 col = GetColorU32(held ? ImGuiCol_HeaderActive : hovered ? ImGuiCol_HeaderHovered : ImGuiCol_Header);\n        //RenderFrame(bb.Min, bb.Max, col, false, 0.0f);\n        TableSetBgColor(ImGuiTableBgTarget_CellBg, col, table->CurrentColumn);\n    }\n    else\n    {\n        // Submit single cell bg color in the case we didn't submit a full header row\n        if ((table->RowFlags & ImGuiTableRowFlags_Headers) == 0)\n            TableSetBgColor(ImGuiTableBgTarget_CellBg, GetColorU32(ImGuiCol_TableHeaderBg), table->CurrentColumn);\n    }\n    RenderNavCursor(bb, id, ImGuiNavRenderCursorFlags_Compact | ImGuiNavRenderCursorFlags_NoRounding);\n    if (held)\n        table->HeldHeaderColumn = (ImGuiTableColumnIdx)column_n;\n    window->DC.CursorPos.y -= g.Style.ItemSpacing.y * 0.5f;\n\n    // Drag and drop to re-order columns.\n    // FIXME-TABLE: Scroll request while reordering a column and it lands out of the scrolling zone.\n    if (held && (table->Flags & ImGuiTableFlags_Reorderable) && IsMouseDragging(0) && !g.DragDropActive)\n    {\n        // While moving a column it will jump on the other side of the mouse, so we also test for MouseDelta.x\n        table->ReorderColumn = (ImGuiTableColumnIdx)column_n;\n        table->InstanceInteracted = table->InstanceCurrent;\n\n        // We don't reorder: through the frozen<>unfrozen line, or through a column that is marked with ImGuiTableColumnFlags_NoReorder.\n        if (g.IO.MouseDelta.x < 0.0f && g.IO.MousePos.x < cell_r.Min.x)\n            if (ImGuiTableColumn* prev_column = (column->PrevEnabledColumn != -1) ? &table->Columns[column->PrevEnabledColumn] : NULL)\n                if (!((column->Flags | prev_column->Flags) & ImGuiTableColumnFlags_NoReorder))\n                    if ((column->IndexWithinEnabledSet < table->FreezeColumnsRequest) == (prev_column->IndexWithinEnabledSet < table->FreezeColumnsRequest))\n                        table->ReorderColumnDir = -1;\n        if (g.IO.MouseDelta.x > 0.0f && g.IO.MousePos.x > cell_r.Max.x)\n            if (ImGuiTableColumn* next_column = (column->NextEnabledColumn != -1) ? &table->Columns[column->NextEnabledColumn] : NULL)\n                if (!((column->Flags | next_column->Flags) & ImGuiTableColumnFlags_NoReorder))\n                    if ((column->IndexWithinEnabledSet < table->FreezeColumnsRequest) == (next_column->IndexWithinEnabledSet < table->FreezeColumnsRequest))\n                        table->ReorderColumnDir = +1;\n    }\n\n    // Sort order arrow\n    const float ellipsis_max = ImMax(cell_r.Max.x - w_arrow - w_sort_text, label_pos.x);\n    if ((table->Flags & ImGuiTableFlags_Sortable) && !(column->Flags & ImGuiTableColumnFlags_NoSort))\n    {\n        if (column->SortOrder != -1)\n        {\n            float x = ImMax(cell_r.Min.x, cell_r.Max.x - w_arrow - w_sort_text);\n            float y = label_pos.y;\n            if (column->SortOrder > 0)\n            {\n                PushStyleColor(ImGuiCol_Text, GetColorU32(ImGuiCol_Text, 0.70f));\n                RenderText(ImVec2(x + g.Style.ItemInnerSpacing.x, y), sort_order_suf);\n                PopStyleColor();\n                x += w_sort_text;\n            }\n            RenderArrow(window->DrawList, ImVec2(x, y), GetColorU32(ImGuiCol_Text), column->SortDirection == ImGuiSortDirection_Ascending ? ImGuiDir_Up : ImGuiDir_Down, ARROW_SCALE);\n        }\n\n        // Handle clicking on column header to adjust Sort Order\n        if (pressed && table->ReorderColumn != column_n)\n        {\n            ImGuiSortDirection sort_direction = TableGetColumnNextSortDirection(column);\n            TableSetColumnSortDirection(column_n, sort_direction, g.IO.KeyShift);\n        }\n    }\n\n    // Render clipped label. Clipping here ensure that in the majority of situations, all our header cells will\n    // be merged into a single draw call.\n    //window->DrawList->AddCircleFilled(ImVec2(ellipsis_max, label_pos.y), 40, IM_COL32_WHITE);\n    RenderTextEllipsis(window->DrawList, label_pos, ImVec2(ellipsis_max, label_pos.y + label_height + g.Style.FramePadding.y), ellipsis_max, ellipsis_max, label, label_end, &label_size);\n\n    const bool text_clipped = label_size.x > (ellipsis_max - label_pos.x);\n    if (text_clipped && hovered && g.ActiveId == 0)\n        SetItemTooltip(\"%.*s\", (int)(label_end - label), label);\n\n    // We don't use BeginPopupContextItem() because we want the popup to stay up even after the column is hidden\n    if (IsMouseReleased(1) && IsItemHovered())\n        TableOpenContextMenu(column_n);\n}\n\n// Unlike TableHeadersRow() it is not expected that you can reimplement or customize this with custom widgets.\n// FIXME: No hit-testing/button on the angled header.\nvoid ImGui::TableAngledHeadersRow()\n{\n    ImGuiContext& g = *GImGui;\n    ImGuiTable* table = g.CurrentTable;\n    ImGuiTableTempData* temp_data = table->TempData;\n    temp_data->AngledHeadersRequests.resize(0);\n    temp_data->AngledHeadersRequests.reserve(table->ColumnsEnabledCount);\n\n    // Which column needs highlight?\n    const ImGuiID row_id = GetID(\"##AngledHeaders\");\n    ImGuiTableInstanceData* table_instance = TableGetInstanceData(table, table->InstanceCurrent);\n    int highlight_column_n = table->HighlightColumnHeader;\n    if (highlight_column_n == -1 && table->HoveredColumnBody != -1)\n        if (table_instance->HoveredRowLast == 0 && table->HoveredColumnBorder == -1 && (g.ActiveId == 0 || g.ActiveId == row_id || (table->IsActiveIdInTable || g.DragDropActive)))\n            highlight_column_n = table->HoveredColumnBody;\n\n    // Build up request\n    ImU32 col_header_bg = GetColorU32(ImGuiCol_TableHeaderBg);\n    ImU32 col_text = GetColorU32(ImGuiCol_Text);\n    for (int order_n = 0; order_n < table->ColumnsCount; order_n++)\n        if (IM_BITARRAY_TESTBIT(table->EnabledMaskByDisplayOrder, order_n))\n        {\n            const int column_n = table->DisplayOrderToIndex[order_n];\n            ImGuiTableColumn* column = &table->Columns[column_n];\n            if ((column->Flags & ImGuiTableColumnFlags_AngledHeader) == 0) // Note: can't rely on ImGuiTableColumnFlags_IsVisible test here.\n                continue;\n            ImGuiTableHeaderData request = { (ImGuiTableColumnIdx)column_n, col_text, col_header_bg, (column_n == highlight_column_n) ? GetColorU32(ImGuiCol_Header) : 0 };\n            temp_data->AngledHeadersRequests.push_back(request);\n        }\n\n    // Render row\n    TableAngledHeadersRowEx(row_id, g.Style.TableAngledHeadersAngle, 0.0f, temp_data->AngledHeadersRequests.Data, temp_data->AngledHeadersRequests.Size);\n}\n\n// Important: data must be fed left to right\nvoid ImGui::TableAngledHeadersRowEx(ImGuiID row_id, float angle, float max_label_width, const ImGuiTableHeaderData* data, int data_count)\n{\n    ImGuiContext& g = *GImGui;\n    ImGuiTable* table = g.CurrentTable;\n    ImGuiWindow* window = g.CurrentWindow;\n    ImDrawList* draw_list = window->DrawList;\n    if (table == NULL)\n    {\n        IM_ASSERT_USER_ERROR(table != NULL, \"Call should only be done while in BeginTable() scope!\");\n        return;\n    }\n    IM_ASSERT(table->CurrentRow == -1 && \"Must be first row\");\n\n    if (max_label_width == 0.0f)\n        max_label_width = TableGetHeaderAngledMaxLabelWidth();\n\n    // Angle argument expressed in (-IM_PI/2 .. +IM_PI/2) as it is easier to think about for user.\n    const bool flip_label = (angle < 0.0f);\n    angle -= IM_PI * 0.5f;\n    const float cos_a = ImCos(angle);\n    const float sin_a = ImSin(angle);\n    const float label_cos_a = flip_label ? ImCos(angle + IM_PI) : cos_a;\n    const float label_sin_a = flip_label ? ImSin(angle + IM_PI) : sin_a;\n    const ImVec2 unit_right = ImVec2(cos_a, sin_a);\n\n    // Calculate our base metrics and set angled headers data _before_ the first call to TableNextRow()\n    // FIXME-STYLE: Would it be better for user to submit 'max_label_width' or 'row_height' ? One can be derived from the other.\n    const float header_height = g.FontSize + g.Style.CellPadding.x * 2.0f;\n    const float row_height = ImTrunc(ImFabs(ImRotate(ImVec2(max_label_width, flip_label ? +header_height : -header_height), cos_a, sin_a).y));\n    table->AngledHeadersHeight = row_height;\n    table->AngledHeadersSlope = (sin_a != 0.0f) ? (cos_a / sin_a) : 0.0f;\n    const ImVec2 header_angled_vector = unit_right * (row_height / -sin_a); // vector from bottom-left to top-left, and from bottom-right to top-right\n\n    // Declare row, override and draw our own background\n    TableNextRow(ImGuiTableRowFlags_Headers, row_height);\n    TableNextColumn();\n    const ImRect row_r(table->WorkRect.Min.x, table->BgClipRect.Min.y, table->WorkRect.Max.x, table->RowPosY2);\n    table->DrawSplitter->SetCurrentChannel(draw_list, TABLE_DRAW_CHANNEL_BG0);\n    float clip_rect_min_x = table->BgClipRect.Min.x;\n    if (table->FreezeColumnsCount > 0)\n        clip_rect_min_x = ImMax(clip_rect_min_x, table->Columns[table->FreezeColumnsCount - 1].MaxX);\n    TableSetBgColor(ImGuiTableBgTarget_RowBg0, 0); // Cancel\n    PushClipRect(table->BgClipRect.Min, table->BgClipRect.Max, false); // Span all columns\n    draw_list->AddRectFilled(ImVec2(table->BgClipRect.Min.x, row_r.Min.y), ImVec2(table->BgClipRect.Max.x, row_r.Max.y), GetColorU32(ImGuiCol_TableHeaderBg, 0.25f)); // FIXME-STYLE: Change row background with an arbitrary color.\n    PushClipRect(ImVec2(clip_rect_min_x, table->BgClipRect.Min.y), table->BgClipRect.Max, true); // Span all columns\n\n    ButtonBehavior(row_r, row_id, NULL, NULL);\n    KeepAliveID(row_id);\n\n    const float ascent_scaled = g.Font->Ascent * g.FontScale; // FIXME: Standardize those scaling factors better\n    const float line_off_for_ascent_x = (ImMax((g.FontSize - ascent_scaled) * 0.5f, 0.0f) / -sin_a) * (flip_label ? -1.0f : 1.0f);\n    const ImVec2 padding = g.Style.CellPadding; // We will always use swapped component\n    const ImVec2 align = g.Style.TableAngledHeadersTextAlign;\n\n    // Draw background and labels in first pass, then all borders.\n    float max_x = -FLT_MAX;\n    for (int pass = 0; pass < 2; pass++)\n        for (int order_n = 0; order_n < data_count; order_n++)\n        {\n            const ImGuiTableHeaderData* request = &data[order_n];\n            const int column_n = request->Index;\n            ImGuiTableColumn* column = &table->Columns[column_n];\n\n            ImVec2 bg_shape[4];\n            bg_shape[0] = ImVec2(column->MaxX, row_r.Max.y);\n            bg_shape[1] = ImVec2(column->MinX, row_r.Max.y);\n            bg_shape[2] = bg_shape[1] + header_angled_vector;\n            bg_shape[3] = bg_shape[0] + header_angled_vector;\n            if (pass == 0)\n            {\n                // Draw shape\n                draw_list->AddQuadFilled(bg_shape[0], bg_shape[1], bg_shape[2], bg_shape[3], request->BgColor0);\n                draw_list->AddQuadFilled(bg_shape[0], bg_shape[1], bg_shape[2], bg_shape[3], request->BgColor1); // Optional highlight\n                max_x = ImMax(max_x, bg_shape[3].x);\n\n                // Draw label\n                // - First draw at an offset where RenderTextXXX() function won't meddle with applying current ClipRect, then transform to final offset.\n                // - Handle multiple lines manually, as we want each lines to follow on the horizontal border, rather than see a whole block rotated.\n                const char* label_name = TableGetColumnName(table, column_n);\n                const char* label_name_end = FindRenderedTextEnd(label_name);\n                const float line_off_step_x = (g.FontSize / -sin_a);\n                const int label_lines = ImTextCountLines(label_name, label_name_end);\n\n                // Left<>Right alignment\n                float line_off_curr_x = flip_label ? (label_lines - 1) * line_off_step_x : 0.0f;\n                float line_off_for_align_x = ImMax((((column->MaxX - column->MinX) - padding.x * 2.0f) - (label_lines * line_off_step_x)), 0.0f) * align.x;\n                line_off_curr_x += line_off_for_align_x - line_off_for_ascent_x;\n\n                // Register header width\n                column->ContentMaxXHeadersUsed = column->ContentMaxXHeadersIdeal = column->WorkMinX + ImCeil(label_lines * line_off_step_x - line_off_for_align_x);\n\n                while (label_name < label_name_end)\n                {\n                    const char* label_name_eol = strchr(label_name, '\\n');\n                    if (label_name_eol == NULL)\n                        label_name_eol = label_name_end;\n\n                    // FIXME: Individual line clipping for right-most column is broken for negative angles.\n                    ImVec2 label_size = CalcTextSize(label_name, label_name_eol);\n                    float clip_width = max_label_width - padding.y; // Using padding.y*2.0f would be symmetrical but hide more text.\n                    float clip_height = ImMin(label_size.y, column->ClipRect.Max.x - column->WorkMinX - line_off_curr_x);\n                    ImRect clip_r(window->ClipRect.Min, window->ClipRect.Min + ImVec2(clip_width, clip_height));\n                    int vtx_idx_begin = draw_list->_VtxCurrentIdx;\n                    PushStyleColor(ImGuiCol_Text, request->TextColor);\n                    RenderTextEllipsis(draw_list, clip_r.Min, clip_r.Max, clip_r.Max.x, clip_r.Max.x, label_name, label_name_eol, &label_size);\n                    PopStyleColor();\n                    int vtx_idx_end = draw_list->_VtxCurrentIdx;\n\n                    // Up<>Down alignment\n                    const float available_space = ImMax(clip_width - label_size.x + ImAbs(padding.x * cos_a) * 2.0f - ImAbs(padding.y * sin_a) * 2.0f, 0.0f);\n                    const float vertical_offset = available_space * align.y * (flip_label ? -1.0f : 1.0f);\n\n                    // Rotate and offset label\n                    ImVec2 pivot_in = ImVec2(window->ClipRect.Min.x - vertical_offset, window->ClipRect.Min.y + label_size.y);\n                    ImVec2 pivot_out = ImVec2(column->WorkMinX, row_r.Max.y);\n                    line_off_curr_x += flip_label ? -line_off_step_x : line_off_step_x;\n                    pivot_out += unit_right * padding.y;\n                    if (flip_label)\n                        pivot_out += unit_right * (clip_width - ImMax(0.0f, clip_width - label_size.x));\n                    pivot_out.x += flip_label ? line_off_curr_x + line_off_step_x : line_off_curr_x;\n                    ShadeVertsTransformPos(draw_list, vtx_idx_begin, vtx_idx_end, pivot_in, label_cos_a, label_sin_a, pivot_out); // Rotate and offset\n                    //if (g.IO.KeyShift) { ImDrawList* fg_dl = GetForegroundDrawList(); vtx_idx_begin = fg_dl->_VtxCurrentIdx; fg_dl->AddRect(clip_r.Min, clip_r.Max, IM_COL32(0, 255, 0, 255), 0.0f, 0, 1.0f); ShadeVertsTransformPos(fg_dl, vtx_idx_begin, fg_dl->_VtxCurrentIdx, pivot_in, label_cos_a, label_sin_a, pivot_out); }\n\n                    label_name = label_name_eol + 1;\n                }\n            }\n            if (pass == 1)\n            {\n                // Draw border\n                draw_list->AddLine(bg_shape[0], bg_shape[3], TableGetColumnBorderCol(table, order_n, column_n));\n            }\n        }\n    PopClipRect();\n    PopClipRect();\n    table->TempData->AngledHeadersExtraWidth = ImMax(0.0f, max_x - table->Columns[table->RightMostEnabledColumn].MaxX);\n}\n\n//-------------------------------------------------------------------------\n// [SECTION] Tables: Context Menu\n//-------------------------------------------------------------------------\n// - TableOpenContextMenu() [Internal]\n// - TableBeginContextMenuPopup() [Internal]\n// - TableDrawDefaultContextMenu() [Internal]\n//-------------------------------------------------------------------------\n\n// Use -1 to open menu not specific to a given column.\nvoid ImGui::TableOpenContextMenu(int column_n)\n{\n    ImGuiContext& g = *GImGui;\n    ImGuiTable* table = g.CurrentTable;\n    if (column_n == -1 && table->CurrentColumn != -1)   // When called within a column automatically use this one (for consistency)\n        column_n = table->CurrentColumn;\n    if (column_n == table->ColumnsCount)                // To facilitate using with TableGetHoveredColumn()\n        column_n = -1;\n    IM_ASSERT(column_n >= -1 && column_n < table->ColumnsCount);\n    if (table->Flags & (ImGuiTableFlags_Resizable | ImGuiTableFlags_Reorderable | ImGuiTableFlags_Hideable))\n    {\n        table->IsContextPopupOpen = true;\n        table->ContextPopupColumn = (ImGuiTableColumnIdx)column_n;\n        table->InstanceInteracted = table->InstanceCurrent;\n        const ImGuiID context_menu_id = ImHashStr(\"##ContextMenu\", 0, table->ID);\n        OpenPopupEx(context_menu_id, ImGuiPopupFlags_None);\n    }\n}\n\nbool ImGui::TableBeginContextMenuPopup(ImGuiTable* table)\n{\n    if (!table->IsContextPopupOpen || table->InstanceCurrent != table->InstanceInteracted)\n        return false;\n    const ImGuiID context_menu_id = ImHashStr(\"##ContextMenu\", 0, table->ID);\n    if (BeginPopupEx(context_menu_id, ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoSavedSettings))\n        return true;\n    table->IsContextPopupOpen = false;\n    return false;\n}\n\n// Output context menu into current window (generally a popup)\n// FIXME-TABLE: Ideally this should be writable by the user. Full programmatic access to that data?\n// Sections to display are pulled from 'flags_for_section_to_display', which is typically == table->Flags.\n// - ImGuiTableFlags_Resizable   -> display Sizing menu items\n// - ImGuiTableFlags_Reorderable -> display \"Reset Order\"\n////- ImGuiTableFlags_Sortable   -> display sorting options (disabled)\n// - ImGuiTableFlags_Hideable    -> display columns visibility menu items\n// It means if you have a custom context menus you can call this section and omit some sections, and add your own.\nvoid ImGui::TableDrawDefaultContextMenu(ImGuiTable* table, ImGuiTableFlags flags_for_section_to_display)\n{\n    ImGuiContext& g = *GImGui;\n    ImGuiWindow* window = g.CurrentWindow;\n    if (window->SkipItems)\n        return;\n\n    bool want_separator = false;\n    const int column_n = (table->ContextPopupColumn >= 0 && table->ContextPopupColumn < table->ColumnsCount) ? table->ContextPopupColumn : -1;\n    ImGuiTableColumn* column = (column_n != -1) ? &table->Columns[column_n] : NULL;\n\n    // Sizing\n    if (flags_for_section_to_display & ImGuiTableFlags_Resizable)\n    {\n        if (column != NULL)\n        {\n            const bool can_resize = !(column->Flags & ImGuiTableColumnFlags_NoResize) && column->IsEnabled;\n            if (MenuItem(LocalizeGetMsg(ImGuiLocKey_TableSizeOne), NULL, false, can_resize)) // \"###SizeOne\"\n                TableSetColumnWidthAutoSingle(table, column_n);\n        }\n\n        const char* size_all_desc;\n        if (table->ColumnsEnabledFixedCount == table->ColumnsEnabledCount && (table->Flags & ImGuiTableFlags_SizingMask_) != ImGuiTableFlags_SizingFixedSame)\n            size_all_desc = LocalizeGetMsg(ImGuiLocKey_TableSizeAllFit);        // \"###SizeAll\" All fixed\n        else\n            size_all_desc = LocalizeGetMsg(ImGuiLocKey_TableSizeAllDefault);    // \"###SizeAll\" All stretch or mixed\n        if (MenuItem(size_all_desc, NULL))\n            TableSetColumnWidthAutoAll(table);\n        want_separator = true;\n    }\n\n    // Ordering\n    if (flags_for_section_to_display & ImGuiTableFlags_Reorderable)\n    {\n        if (MenuItem(LocalizeGetMsg(ImGuiLocKey_TableResetOrder), NULL, false, !table->IsDefaultDisplayOrder))\n            table->IsResetDisplayOrderRequest = true;\n        want_separator = true;\n    }\n\n    // Reset all (should work but seems unnecessary/noisy to expose?)\n    //if (MenuItem(\"Reset all\"))\n    //    table->IsResetAllRequest = true;\n\n    // Sorting\n    // (modify TableOpenContextMenu() to add _Sortable flag if enabling this)\n#if 0\n    if ((flags_for_section_to_display & ImGuiTableFlags_Sortable) && column != NULL && (column->Flags & ImGuiTableColumnFlags_NoSort) == 0)\n    {\n        if (want_separator)\n            Separator();\n        want_separator = true;\n\n        bool append_to_sort_specs = g.IO.KeyShift;\n        if (MenuItem(\"Sort in Ascending Order\", NULL, column->SortOrder != -1 && column->SortDirection == ImGuiSortDirection_Ascending, (column->Flags & ImGuiTableColumnFlags_NoSortAscending) == 0))\n            TableSetColumnSortDirection(table, column_n, ImGuiSortDirection_Ascending, append_to_sort_specs);\n        if (MenuItem(\"Sort in Descending Order\", NULL, column->SortOrder != -1 && column->SortDirection == ImGuiSortDirection_Descending, (column->Flags & ImGuiTableColumnFlags_NoSortDescending) == 0))\n            TableSetColumnSortDirection(table, column_n, ImGuiSortDirection_Descending, append_to_sort_specs);\n    }\n#endif\n\n    // Hiding / Visibility\n    if (flags_for_section_to_display & ImGuiTableFlags_Hideable)\n    {\n        if (want_separator)\n            Separator();\n        want_separator = true;\n\n        PushItemFlag(ImGuiItemFlags_AutoClosePopups, false);\n        for (int other_column_n = 0; other_column_n < table->ColumnsCount; other_column_n++)\n        {\n            ImGuiTableColumn* other_column = &table->Columns[other_column_n];\n            if (other_column->Flags & ImGuiTableColumnFlags_Disabled)\n                continue;\n\n            const char* name = TableGetColumnName(table, other_column_n);\n            if (name == NULL || name[0] == 0)\n                name = \"<Unknown>\";\n\n            // Make sure we can't hide the last active column\n            bool menu_item_active = (other_column->Flags & ImGuiTableColumnFlags_NoHide) ? false : true;\n            if (other_column->IsUserEnabled && table->ColumnsEnabledCount <= 1)\n                menu_item_active = false;\n            if (MenuItem(name, NULL, other_column->IsUserEnabled, menu_item_active))\n                other_column->IsUserEnabledNextFrame = !other_column->IsUserEnabled;\n        }\n        PopItemFlag();\n    }\n}\n\n//-------------------------------------------------------------------------\n// [SECTION] Tables: Settings (.ini data)\n//-------------------------------------------------------------------------\n// FIXME: The binding/finding/creating flow are too confusing.\n//-------------------------------------------------------------------------\n// - TableSettingsInit() [Internal]\n// - TableSettingsCalcChunkSize() [Internal]\n// - TableSettingsCreate() [Internal]\n// - TableSettingsFindByID() [Internal]\n// - TableGetBoundSettings() [Internal]\n// - TableResetSettings()\n// - TableSaveSettings() [Internal]\n// - TableLoadSettings() [Internal]\n// - TableSettingsHandler_ClearAll() [Internal]\n// - TableSettingsHandler_ApplyAll() [Internal]\n// - TableSettingsHandler_ReadOpen() [Internal]\n// - TableSettingsHandler_ReadLine() [Internal]\n// - TableSettingsHandler_WriteAll() [Internal]\n// - TableSettingsInstallHandler() [Internal]\n//-------------------------------------------------------------------------\n// [Init] 1: TableSettingsHandler_ReadXXXX()   Load and parse .ini file into TableSettings.\n// [Main] 2: TableLoadSettings()               When table is created, bind Table to TableSettings, serialize TableSettings data into Table.\n// [Main] 3: TableSaveSettings()               When table properties are modified, serialize Table data into bound or new TableSettings, mark .ini as dirty.\n// [Main] 4: TableSettingsHandler_WriteAll()   When .ini file is dirty (which can come from other source), save TableSettings into .ini file.\n//-------------------------------------------------------------------------\n\n// Clear and initialize empty settings instance\nstatic void TableSettingsInit(ImGuiTableSettings* settings, ImGuiID id, int columns_count, int columns_count_max)\n{\n    IM_PLACEMENT_NEW(settings) ImGuiTableSettings();\n    ImGuiTableColumnSettings* settings_column = settings->GetColumnSettings();\n    for (int n = 0; n < columns_count_max; n++, settings_column++)\n        IM_PLACEMENT_NEW(settings_column) ImGuiTableColumnSettings();\n    settings->ID = id;\n    settings->ColumnsCount = (ImGuiTableColumnIdx)columns_count;\n    settings->ColumnsCountMax = (ImGuiTableColumnIdx)columns_count_max;\n    settings->WantApply = true;\n}\n\nstatic size_t TableSettingsCalcChunkSize(int columns_count)\n{\n    return sizeof(ImGuiTableSettings) + (size_t)columns_count * sizeof(ImGuiTableColumnSettings);\n}\n\nImGuiTableSettings* ImGui::TableSettingsCreate(ImGuiID id, int columns_count)\n{\n    ImGuiContext& g = *GImGui;\n    ImGuiTableSettings* settings = g.SettingsTables.alloc_chunk(TableSettingsCalcChunkSize(columns_count));\n    TableSettingsInit(settings, id, columns_count, columns_count);\n    return settings;\n}\n\n// Find existing settings\nImGuiTableSettings* ImGui::TableSettingsFindByID(ImGuiID id)\n{\n    // FIXME-OPT: Might want to store a lookup map for this?\n    ImGuiContext& g = *GImGui;\n    for (ImGuiTableSettings* settings = g.SettingsTables.begin(); settings != NULL; settings = g.SettingsTables.next_chunk(settings))\n        if (settings->ID == id)\n            return settings;\n    return NULL;\n}\n\n// Get settings for a given table, NULL if none\nImGuiTableSettings* ImGui::TableGetBoundSettings(ImGuiTable* table)\n{\n    if (table->SettingsOffset != -1)\n    {\n        ImGuiContext& g = *GImGui;\n        ImGuiTableSettings* settings = g.SettingsTables.ptr_from_offset(table->SettingsOffset);\n        IM_ASSERT(settings->ID == table->ID);\n        if (settings->ColumnsCountMax >= table->ColumnsCount)\n            return settings; // OK\n        settings->ID = 0; // Invalidate storage, we won't fit because of a count change\n    }\n    return NULL;\n}\n\n// Restore initial state of table (with or without saved settings)\nvoid ImGui::TableResetSettings(ImGuiTable* table)\n{\n    table->IsInitializing = table->IsSettingsDirty = true;\n    table->IsResetAllRequest = false;\n    table->IsSettingsRequestLoad = false;                   // Don't reload from ini\n    table->SettingsLoadedFlags = ImGuiTableFlags_None;      // Mark as nothing loaded so our initialized data becomes authoritative\n}\n\nvoid ImGui::TableSaveSettings(ImGuiTable* table)\n{\n    table->IsSettingsDirty = false;\n    if (table->Flags & ImGuiTableFlags_NoSavedSettings)\n        return;\n\n    // Bind or create settings data\n    ImGuiContext& g = *GImGui;\n    ImGuiTableSettings* settings = TableGetBoundSettings(table);\n    if (settings == NULL)\n    {\n        settings = TableSettingsCreate(table->ID, table->ColumnsCount);\n        table->SettingsOffset = g.SettingsTables.offset_from_ptr(settings);\n    }\n    settings->ColumnsCount = (ImGuiTableColumnIdx)table->ColumnsCount;\n\n    // Serialize ImGuiTable/ImGuiTableColumn into ImGuiTableSettings/ImGuiTableColumnSettings\n    IM_ASSERT(settings->ID == table->ID);\n    IM_ASSERT(settings->ColumnsCount == table->ColumnsCount && settings->ColumnsCountMax >= settings->ColumnsCount);\n    ImGuiTableColumn* column = table->Columns.Data;\n    ImGuiTableColumnSettings* column_settings = settings->GetColumnSettings();\n\n    bool save_ref_scale = false;\n    settings->SaveFlags = ImGuiTableFlags_None;\n    for (int n = 0; n < table->ColumnsCount; n++, column++, column_settings++)\n    {\n        const float width_or_weight = (column->Flags & ImGuiTableColumnFlags_WidthStretch) ? column->StretchWeight : column->WidthRequest;\n        column_settings->WidthOrWeight = width_or_weight;\n        column_settings->Index = (ImGuiTableColumnIdx)n;\n        column_settings->DisplayOrder = column->DisplayOrder;\n        column_settings->SortOrder = column->SortOrder;\n        column_settings->SortDirection = column->SortDirection;\n        column_settings->IsEnabled = column->IsUserEnabled;\n        column_settings->IsStretch = (column->Flags & ImGuiTableColumnFlags_WidthStretch) ? 1 : 0;\n        if ((column->Flags & ImGuiTableColumnFlags_WidthStretch) == 0)\n            save_ref_scale = true;\n\n        // We skip saving some data in the .ini file when they are unnecessary to restore our state.\n        // Note that fixed width where initial width was derived from auto-fit will always be saved as InitStretchWeightOrWidth will be 0.0f.\n        // FIXME-TABLE: We don't have logic to easily compare SortOrder to DefaultSortOrder yet so it's always saved when present.\n        if (width_or_weight != column->InitStretchWeightOrWidth)\n            settings->SaveFlags |= ImGuiTableFlags_Resizable;\n        if (column->DisplayOrder != n)\n            settings->SaveFlags |= ImGuiTableFlags_Reorderable;\n        if (column->SortOrder != -1)\n            settings->SaveFlags |= ImGuiTableFlags_Sortable;\n        if (column->IsUserEnabled != ((column->Flags & ImGuiTableColumnFlags_DefaultHide) == 0))\n            settings->SaveFlags |= ImGuiTableFlags_Hideable;\n    }\n    settings->SaveFlags &= table->Flags;\n    settings->RefScale = save_ref_scale ? table->RefScale : 0.0f;\n\n    MarkIniSettingsDirty();\n}\n\nvoid ImGui::TableLoadSettings(ImGuiTable* table)\n{\n    ImGuiContext& g = *GImGui;\n    table->IsSettingsRequestLoad = false;\n    if (table->Flags & ImGuiTableFlags_NoSavedSettings)\n        return;\n\n    // Bind settings\n    ImGuiTableSettings* settings;\n    if (table->SettingsOffset == -1)\n    {\n        settings = TableSettingsFindByID(table->ID);\n        if (settings == NULL)\n            return;\n        if (settings->ColumnsCount != table->ColumnsCount) // Allow settings if columns count changed. We could otherwise decide to return...\n            table->IsSettingsDirty = true;\n        table->SettingsOffset = g.SettingsTables.offset_from_ptr(settings);\n    }\n    else\n    {\n        settings = TableGetBoundSettings(table);\n    }\n\n    table->SettingsLoadedFlags = settings->SaveFlags;\n    table->RefScale = settings->RefScale;\n\n    // Initialize default columns settings\n    for (int column_n = 0; column_n < table->ColumnsCount; column_n++)\n    {\n        ImGuiTableColumn* column = &table->Columns[column_n];\n        TableInitColumnDefaults(table, column, ~0);\n        column->AutoFitQueue = 0x00;\n    }\n\n    // Serialize ImGuiTableSettings/ImGuiTableColumnSettings into ImGuiTable/ImGuiTableColumn\n    ImGuiTableColumnSettings* column_settings = settings->GetColumnSettings();\n    ImU64 display_order_mask = 0;\n    for (int data_n = 0; data_n < settings->ColumnsCount; data_n++, column_settings++)\n    {\n        int column_n = column_settings->Index;\n        if (column_n < 0 || column_n >= table->ColumnsCount)\n            continue;\n\n        ImGuiTableColumn* column = &table->Columns[column_n];\n        if (settings->SaveFlags & ImGuiTableFlags_Resizable)\n        {\n            if (column_settings->IsStretch)\n                column->StretchWeight = column_settings->WidthOrWeight;\n            else\n                column->WidthRequest = column_settings->WidthOrWeight;\n        }\n        if (settings->SaveFlags & ImGuiTableFlags_Reorderable)\n            column->DisplayOrder = column_settings->DisplayOrder;\n        display_order_mask |= (ImU64)1 << column->DisplayOrder;\n        if ((settings->SaveFlags & ImGuiTableFlags_Hideable) && column_settings->IsEnabled != -1)\n            column->IsUserEnabled = column->IsUserEnabledNextFrame = column_settings->IsEnabled == 1;\n        column->SortOrder = column_settings->SortOrder;\n        column->SortDirection = column_settings->SortDirection;\n    }\n\n    // Validate and fix invalid display order data\n    const ImU64 expected_display_order_mask = (settings->ColumnsCount == 64) ? ~0 : ((ImU64)1 << settings->ColumnsCount) - 1;\n    if (display_order_mask != expected_display_order_mask)\n        for (int column_n = 0; column_n < table->ColumnsCount; column_n++)\n            table->Columns[column_n].DisplayOrder = (ImGuiTableColumnIdx)column_n;\n\n    // Rebuild index\n    for (int column_n = 0; column_n < table->ColumnsCount; column_n++)\n        table->DisplayOrderToIndex[table->Columns[column_n].DisplayOrder] = (ImGuiTableColumnIdx)column_n;\n}\n\nstatic void TableSettingsHandler_ClearAll(ImGuiContext* ctx, ImGuiSettingsHandler*)\n{\n    ImGuiContext& g = *ctx;\n    for (int i = 0; i != g.Tables.GetMapSize(); i++)\n        if (ImGuiTable* table = g.Tables.TryGetMapData(i))\n            table->SettingsOffset = -1;\n    g.SettingsTables.clear();\n}\n\n// Apply to existing windows (if any)\nstatic void TableSettingsHandler_ApplyAll(ImGuiContext* ctx, ImGuiSettingsHandler*)\n{\n    ImGuiContext& g = *ctx;\n    for (int i = 0; i != g.Tables.GetMapSize(); i++)\n        if (ImGuiTable* table = g.Tables.TryGetMapData(i))\n        {\n            table->IsSettingsRequestLoad = true;\n            table->SettingsOffset = -1;\n        }\n}\n\nstatic void* TableSettingsHandler_ReadOpen(ImGuiContext*, ImGuiSettingsHandler*, const char* name)\n{\n    ImGuiID id = 0;\n    int columns_count = 0;\n    if (sscanf(name, \"0x%08X,%d\", &id, &columns_count) < 2)\n        return NULL;\n\n    if (ImGuiTableSettings* settings = ImGui::TableSettingsFindByID(id))\n    {\n        if (settings->ColumnsCountMax >= columns_count)\n        {\n            TableSettingsInit(settings, id, columns_count, settings->ColumnsCountMax); // Recycle\n            return settings;\n        }\n        settings->ID = 0; // Invalidate storage, we won't fit because of a count change\n    }\n    return ImGui::TableSettingsCreate(id, columns_count);\n}\n\nstatic void TableSettingsHandler_ReadLine(ImGuiContext*, ImGuiSettingsHandler*, void* entry, const char* line)\n{\n    // \"Column 0  UserID=0x42AD2D21 Width=100 Visible=1 Order=0 Sort=0v\"\n    ImGuiTableSettings* settings = (ImGuiTableSettings*)entry;\n    float f = 0.0f;\n    int column_n = 0, r = 0, n = 0;\n\n    if (sscanf(line, \"RefScale=%f\", &f) == 1) { settings->RefScale = f; return; }\n\n    if (sscanf(line, \"Column %d%n\", &column_n, &r) == 1)\n    {\n        if (column_n < 0 || column_n >= settings->ColumnsCount)\n            return;\n        line = ImStrSkipBlank(line + r);\n        char c = 0;\n        ImGuiTableColumnSettings* column = settings->GetColumnSettings() + column_n;\n        column->Index = (ImGuiTableColumnIdx)column_n;\n        if (sscanf(line, \"UserID=0x%08X%n\", (ImU32*)&n, &r)==1) { line = ImStrSkipBlank(line + r); column->UserID = (ImGuiID)n; }\n        if (sscanf(line, \"Width=%d%n\", &n, &r) == 1)            { line = ImStrSkipBlank(line + r); column->WidthOrWeight = (float)n; column->IsStretch = 0; settings->SaveFlags |= ImGuiTableFlags_Resizable; }\n        if (sscanf(line, \"Weight=%f%n\", &f, &r) == 1)           { line = ImStrSkipBlank(line + r); column->WidthOrWeight = f; column->IsStretch = 1; settings->SaveFlags |= ImGuiTableFlags_Resizable; }\n        if (sscanf(line, \"Visible=%d%n\", &n, &r) == 1)          { line = ImStrSkipBlank(line + r); column->IsEnabled = (ImU8)n; settings->SaveFlags |= ImGuiTableFlags_Hideable; }\n        if (sscanf(line, \"Order=%d%n\", &n, &r) == 1)            { line = ImStrSkipBlank(line + r); column->DisplayOrder = (ImGuiTableColumnIdx)n; settings->SaveFlags |= ImGuiTableFlags_Reorderable; }\n        if (sscanf(line, \"Sort=%d%c%n\", &n, &c, &r) == 2)       { line = ImStrSkipBlank(line + r); column->SortOrder = (ImGuiTableColumnIdx)n; column->SortDirection = (c == '^') ? ImGuiSortDirection_Descending : ImGuiSortDirection_Ascending; settings->SaveFlags |= ImGuiTableFlags_Sortable; }\n    }\n}\n\nstatic void TableSettingsHandler_WriteAll(ImGuiContext* ctx, ImGuiSettingsHandler* handler, ImGuiTextBuffer* buf)\n{\n    ImGuiContext& g = *ctx;\n    for (ImGuiTableSettings* settings = g.SettingsTables.begin(); settings != NULL; settings = g.SettingsTables.next_chunk(settings))\n    {\n        if (settings->ID == 0) // Skip ditched settings\n            continue;\n\n        // TableSaveSettings() may clear some of those flags when we establish that the data can be stripped\n        // (e.g. Order was unchanged)\n        const bool save_size    = (settings->SaveFlags & ImGuiTableFlags_Resizable) != 0;\n        const bool save_visible = (settings->SaveFlags & ImGuiTableFlags_Hideable) != 0;\n        const bool save_order   = (settings->SaveFlags & ImGuiTableFlags_Reorderable) != 0;\n        const bool save_sort    = (settings->SaveFlags & ImGuiTableFlags_Sortable) != 0;\n        // We need to save the [Table] entry even if all the bools are false, since this records a table with \"default settings\".\n\n        buf->reserve(buf->size() + 30 + settings->ColumnsCount * 50); // ballpark reserve\n        buf->appendf(\"[%s][0x%08X,%d]\\n\", handler->TypeName, settings->ID, settings->ColumnsCount);\n        if (settings->RefScale != 0.0f)\n            buf->appendf(\"RefScale=%g\\n\", settings->RefScale);\n        ImGuiTableColumnSettings* column = settings->GetColumnSettings();\n        for (int column_n = 0; column_n < settings->ColumnsCount; column_n++, column++)\n        {\n            // \"Column 0  UserID=0x42AD2D21 Width=100 Visible=1 Order=0 Sort=0v\"\n            bool save_column = column->UserID != 0 || save_size || save_visible || save_order || (save_sort && column->SortOrder != -1);\n            if (!save_column)\n                continue;\n            buf->appendf(\"Column %-2d\", column_n);\n            if (column->UserID != 0)                    { buf->appendf(\" UserID=%08X\", column->UserID); }\n            if (save_size && column->IsStretch)         { buf->appendf(\" Weight=%.4f\", column->WidthOrWeight); }\n            if (save_size && !column->IsStretch)        { buf->appendf(\" Width=%d\", (int)column->WidthOrWeight); }\n            if (save_visible)                           { buf->appendf(\" Visible=%d\", column->IsEnabled); }\n            if (save_order)                             { buf->appendf(\" Order=%d\", column->DisplayOrder); }\n            if (save_sort && column->SortOrder != -1)   { buf->appendf(\" Sort=%d%c\", column->SortOrder, (column->SortDirection == ImGuiSortDirection_Ascending) ? 'v' : '^'); }\n            buf->append(\"\\n\");\n        }\n        buf->append(\"\\n\");\n    }\n}\n\nvoid ImGui::TableSettingsAddSettingsHandler()\n{\n    ImGuiSettingsHandler ini_handler;\n    ini_handler.TypeName = \"Table\";\n    ini_handler.TypeHash = ImHashStr(\"Table\");\n    ini_handler.ClearAllFn = TableSettingsHandler_ClearAll;\n    ini_handler.ReadOpenFn = TableSettingsHandler_ReadOpen;\n    ini_handler.ReadLineFn = TableSettingsHandler_ReadLine;\n    ini_handler.ApplyAllFn = TableSettingsHandler_ApplyAll;\n    ini_handler.WriteAllFn = TableSettingsHandler_WriteAll;\n    AddSettingsHandler(&ini_handler);\n}\n\n//-------------------------------------------------------------------------\n// [SECTION] Tables: Garbage Collection\n//-------------------------------------------------------------------------\n// - TableRemove() [Internal]\n// - TableGcCompactTransientBuffers() [Internal]\n// - TableGcCompactSettings() [Internal]\n//-------------------------------------------------------------------------\n\n// Remove Table (currently only used by TestEngine)\nvoid ImGui::TableRemove(ImGuiTable* table)\n{\n    //IMGUI_DEBUG_PRINT(\"TableRemove() id=0x%08X\\n\", table->ID);\n    ImGuiContext& g = *GImGui;\n    int table_idx = g.Tables.GetIndex(table);\n    //memset(table->RawData.Data, 0, table->RawData.size_in_bytes());\n    //memset(table, 0, sizeof(ImGuiTable));\n    g.Tables.Remove(table->ID, table);\n    g.TablesLastTimeActive[table_idx] = -1.0f;\n}\n\n// Free up/compact internal Table buffers for when it gets unused\nvoid ImGui::TableGcCompactTransientBuffers(ImGuiTable* table)\n{\n    //IMGUI_DEBUG_PRINT(\"TableGcCompactTransientBuffers() id=0x%08X\\n\", table->ID);\n    ImGuiContext& g = *GImGui;\n    IM_ASSERT(table->MemoryCompacted == false);\n    table->SortSpecs.Specs = NULL;\n    table->SortSpecsMulti.clear();\n    table->IsSortSpecsDirty = true; // FIXME: In theory shouldn't have to leak into user performing a sort on resume.\n    table->ColumnsNames.clear();\n    table->MemoryCompacted = true;\n    for (int n = 0; n < table->ColumnsCount; n++)\n        table->Columns[n].NameOffset = -1;\n    g.TablesLastTimeActive[g.Tables.GetIndex(table)] = -1.0f;\n}\n\nvoid ImGui::TableGcCompactTransientBuffers(ImGuiTableTempData* temp_data)\n{\n    temp_data->DrawSplitter.ClearFreeMemory();\n    temp_data->LastTimeActive = -1.0f;\n}\n\n// Compact and remove unused settings data (currently only used by TestEngine)\nvoid ImGui::TableGcCompactSettings()\n{\n    ImGuiContext& g = *GImGui;\n    int required_memory = 0;\n    for (ImGuiTableSettings* settings = g.SettingsTables.begin(); settings != NULL; settings = g.SettingsTables.next_chunk(settings))\n        if (settings->ID != 0)\n            required_memory += (int)TableSettingsCalcChunkSize(settings->ColumnsCount);\n    if (required_memory == g.SettingsTables.Buf.Size)\n        return;\n    ImChunkStream<ImGuiTableSettings> new_chunk_stream;\n    new_chunk_stream.Buf.reserve(required_memory);\n    for (ImGuiTableSettings* settings = g.SettingsTables.begin(); settings != NULL; settings = g.SettingsTables.next_chunk(settings))\n        if (settings->ID != 0)\n            memcpy(new_chunk_stream.alloc_chunk(TableSettingsCalcChunkSize(settings->ColumnsCount)), settings, TableSettingsCalcChunkSize(settings->ColumnsCount));\n    g.SettingsTables.swap(new_chunk_stream);\n}\n\n\n//-------------------------------------------------------------------------\n// [SECTION] Tables: Debugging\n//-------------------------------------------------------------------------\n// - DebugNodeTable() [Internal]\n//-------------------------------------------------------------------------\n\n#ifndef IMGUI_DISABLE_DEBUG_TOOLS\n\nstatic const char* DebugNodeTableGetSizingPolicyDesc(ImGuiTableFlags sizing_policy)\n{\n    sizing_policy &= ImGuiTableFlags_SizingMask_;\n    if (sizing_policy == ImGuiTableFlags_SizingFixedFit)    { return \"FixedFit\"; }\n    if (sizing_policy == ImGuiTableFlags_SizingFixedSame)   { return \"FixedSame\"; }\n    if (sizing_policy == ImGuiTableFlags_SizingStretchProp) { return \"StretchProp\"; }\n    if (sizing_policy == ImGuiTableFlags_SizingStretchSame) { return \"StretchSame\"; }\n    return \"N/A\";\n}\n\nvoid ImGui::DebugNodeTable(ImGuiTable* table)\n{\n    ImGuiContext& g = *GImGui;\n    const bool is_active = (table->LastFrameActive >= g.FrameCount - 2); // Note that fully clipped early out scrolling tables will appear as inactive here.\n    if (!is_active) { PushStyleColor(ImGuiCol_Text, GetStyleColorVec4(ImGuiCol_TextDisabled)); }\n    bool open = TreeNode(table, \"Table 0x%08X (%d columns, in '%s')%s\", table->ID, table->ColumnsCount, table->OuterWindow->Name, is_active ? \"\" : \" *Inactive*\");\n    if (!is_active) { PopStyleColor(); }\n    if (IsItemHovered())\n        GetForegroundDrawList()->AddRect(table->OuterRect.Min, table->OuterRect.Max, IM_COL32(255, 255, 0, 255));\n    if (IsItemVisible() && table->HoveredColumnBody != -1)\n        GetForegroundDrawList()->AddRect(GetItemRectMin(), GetItemRectMax(), IM_COL32(255, 255, 0, 255));\n    if (!open)\n        return;\n    if (table->InstanceCurrent > 0)\n        Text(\"** %d instances of same table! Some data below will refer to last instance.\", table->InstanceCurrent + 1);\n    if (g.IO.ConfigDebugIsDebuggerPresent)\n    {\n        if (DebugBreakButton(\"**DebugBreak**\", \"in BeginTable()\"))\n            g.DebugBreakInTable = table->ID;\n        SameLine();\n    }\n\n    bool clear_settings = SmallButton(\"Clear settings\");\n    BulletText(\"OuterRect: Pos: (%.1f,%.1f) Size: (%.1f,%.1f) Sizing: '%s'\", table->OuterRect.Min.x, table->OuterRect.Min.y, table->OuterRect.GetWidth(), table->OuterRect.GetHeight(), DebugNodeTableGetSizingPolicyDesc(table->Flags));\n    BulletText(\"ColumnsGivenWidth: %.1f, ColumnsAutoFitWidth: %.1f, InnerWidth: %.1f%s\", table->ColumnsGivenWidth, table->ColumnsAutoFitWidth, table->InnerWidth, table->InnerWidth == 0.0f ? \" (auto)\" : \"\");\n    BulletText(\"CellPaddingX: %.1f, CellSpacingX: %.1f/%.1f, OuterPaddingX: %.1f\", table->CellPaddingX, table->CellSpacingX1, table->CellSpacingX2, table->OuterPaddingX);\n    BulletText(\"HoveredColumnBody: %d, HoveredColumnBorder: %d\", table->HoveredColumnBody, table->HoveredColumnBorder);\n    BulletText(\"ResizedColumn: %d, ReorderColumn: %d, HeldHeaderColumn: %d\", table->ResizedColumn, table->ReorderColumn, table->HeldHeaderColumn);\n    for (int n = 0; n < table->InstanceCurrent + 1; n++)\n    {\n        ImGuiTableInstanceData* table_instance = TableGetInstanceData(table, n);\n        BulletText(\"Instance %d: HoveredRow: %d, LastOuterHeight: %.2f\", n, table_instance->HoveredRowLast, table_instance->LastOuterHeight);\n    }\n    //BulletText(\"BgDrawChannels: %d/%d\", 0, table->BgDrawChannelUnfrozen);\n    float sum_weights = 0.0f;\n    for (int n = 0; n < table->ColumnsCount; n++)\n        if (table->Columns[n].Flags & ImGuiTableColumnFlags_WidthStretch)\n            sum_weights += table->Columns[n].StretchWeight;\n    for (int n = 0; n < table->ColumnsCount; n++)\n    {\n        ImGuiTableColumn* column = &table->Columns[n];\n        const char* name = TableGetColumnName(table, n);\n        char buf[512];\n        ImFormatString(buf, IM_ARRAYSIZE(buf),\n            \"Column %d order %d '%s': offset %+.2f to %+.2f%s\\n\"\n            \"Enabled: %d, VisibleX/Y: %d/%d, RequestOutput: %d, SkipItems: %d, DrawChannels: %d,%d\\n\"\n            \"WidthGiven: %.1f, Request/Auto: %.1f/%.1f, StretchWeight: %.3f (%.1f%%)\\n\"\n            \"MinX: %.1f, MaxX: %.1f (%+.1f), ClipRect: %.1f to %.1f (+%.1f)\\n\"\n            \"ContentWidth: %.1f,%.1f, HeadersUsed/Ideal %.1f/%.1f\\n\"\n            \"Sort: %d%s, UserID: 0x%08X, Flags: 0x%04X: %s%s%s..\",\n            n, column->DisplayOrder, name, column->MinX - table->WorkRect.Min.x, column->MaxX - table->WorkRect.Min.x, (n < table->FreezeColumnsRequest) ? \" (Frozen)\" : \"\",\n            column->IsEnabled, column->IsVisibleX, column->IsVisibleY, column->IsRequestOutput, column->IsSkipItems, column->DrawChannelFrozen, column->DrawChannelUnfrozen,\n            column->WidthGiven, column->WidthRequest, column->WidthAuto, column->StretchWeight, column->StretchWeight > 0.0f ? (column->StretchWeight / sum_weights) * 100.0f : 0.0f,\n            column->MinX, column->MaxX, column->MaxX - column->MinX, column->ClipRect.Min.x, column->ClipRect.Max.x, column->ClipRect.Max.x - column->ClipRect.Min.x,\n            column->ContentMaxXFrozen - column->WorkMinX, column->ContentMaxXUnfrozen - column->WorkMinX, column->ContentMaxXHeadersUsed - column->WorkMinX, column->ContentMaxXHeadersIdeal - column->WorkMinX,\n            column->SortOrder, (column->SortDirection == ImGuiSortDirection_Ascending) ? \" (Asc)\" : (column->SortDirection == ImGuiSortDirection_Descending) ? \" (Des)\" : \"\", column->UserID, column->Flags,\n            (column->Flags & ImGuiTableColumnFlags_WidthStretch) ? \"WidthStretch \" : \"\",\n            (column->Flags & ImGuiTableColumnFlags_WidthFixed) ? \"WidthFixed \" : \"\",\n            (column->Flags & ImGuiTableColumnFlags_NoResize) ? \"NoResize \" : \"\");\n        Bullet();\n        Selectable(buf);\n        if (IsItemHovered())\n        {\n            ImRect r(column->MinX, table->OuterRect.Min.y, column->MaxX, table->OuterRect.Max.y);\n            GetForegroundDrawList()->AddRect(r.Min, r.Max, IM_COL32(255, 255, 0, 255));\n        }\n    }\n    if (ImGuiTableSettings* settings = TableGetBoundSettings(table))\n        DebugNodeTableSettings(settings);\n    if (clear_settings)\n        table->IsResetAllRequest = true;\n    TreePop();\n}\n\nvoid ImGui::DebugNodeTableSettings(ImGuiTableSettings* settings)\n{\n    if (!TreeNode((void*)(intptr_t)settings->ID, \"Settings 0x%08X (%d columns)\", settings->ID, settings->ColumnsCount))\n        return;\n    BulletText(\"SaveFlags: 0x%08X\", settings->SaveFlags);\n    BulletText(\"ColumnsCount: %d (max %d)\", settings->ColumnsCount, settings->ColumnsCountMax);\n    for (int n = 0; n < settings->ColumnsCount; n++)\n    {\n        ImGuiTableColumnSettings* column_settings = &settings->GetColumnSettings()[n];\n        ImGuiSortDirection sort_dir = (column_settings->SortOrder != -1) ? (ImGuiSortDirection)column_settings->SortDirection : ImGuiSortDirection_None;\n        BulletText(\"Column %d Order %d SortOrder %d %s Vis %d %s %7.3f UserID 0x%08X\",\n            n, column_settings->DisplayOrder, column_settings->SortOrder,\n            (sort_dir == ImGuiSortDirection_Ascending) ? \"Asc\" : (sort_dir == ImGuiSortDirection_Descending) ? \"Des\" : \"---\",\n            column_settings->IsEnabled, column_settings->IsStretch ? \"Weight\" : \"Width \", column_settings->WidthOrWeight, column_settings->UserID);\n    }\n    TreePop();\n}\n\n#else // #ifndef IMGUI_DISABLE_DEBUG_TOOLS\n\nvoid ImGui::DebugNodeTable(ImGuiTable*) {}\nvoid ImGui::DebugNodeTableSettings(ImGuiTableSettings*) {}\n\n#endif\n\n\n//-------------------------------------------------------------------------\n// [SECTION] Columns, BeginColumns, EndColumns, etc.\n// (This is a legacy API, prefer using BeginTable/EndTable!)\n//-------------------------------------------------------------------------\n// FIXME: sizing is lossy when columns width is very small (default width may turn negative etc.)\n//-------------------------------------------------------------------------\n// - SetWindowClipRectBeforeSetChannel() [Internal]\n// - GetColumnIndex()\n// - GetColumnsCount()\n// - GetColumnOffset()\n// - GetColumnWidth()\n// - SetColumnOffset()\n// - SetColumnWidth()\n// - PushColumnClipRect() [Internal]\n// - PushColumnsBackground() [Internal]\n// - PopColumnsBackground() [Internal]\n// - FindOrCreateColumns() [Internal]\n// - GetColumnsID() [Internal]\n// - BeginColumns()\n// - NextColumn()\n// - EndColumns()\n// - Columns()\n//-------------------------------------------------------------------------\n\n// [Internal] Small optimization to avoid calls to PopClipRect/SetCurrentChannel/PushClipRect in sequences,\n// they would meddle many times with the underlying ImDrawCmd.\n// Instead, we do a preemptive overwrite of clipping rectangle _without_ altering the command-buffer and let\n// the subsequent single call to SetCurrentChannel() does it things once.\nvoid ImGui::SetWindowClipRectBeforeSetChannel(ImGuiWindow* window, const ImRect& clip_rect)\n{\n    ImVec4 clip_rect_vec4 = clip_rect.ToVec4();\n    window->ClipRect = clip_rect;\n    window->DrawList->_CmdHeader.ClipRect = clip_rect_vec4;\n    window->DrawList->_ClipRectStack.Data[window->DrawList->_ClipRectStack.Size - 1] = clip_rect_vec4;\n}\n\nint ImGui::GetColumnIndex()\n{\n    ImGuiWindow* window = GetCurrentWindowRead();\n    return window->DC.CurrentColumns ? window->DC.CurrentColumns->Current : 0;\n}\n\nint ImGui::GetColumnsCount()\n{\n    ImGuiWindow* window = GetCurrentWindowRead();\n    return window->DC.CurrentColumns ? window->DC.CurrentColumns->Count : 1;\n}\n\nfloat ImGui::GetColumnOffsetFromNorm(const ImGuiOldColumns* columns, float offset_norm)\n{\n    return offset_norm * (columns->OffMaxX - columns->OffMinX);\n}\n\nfloat ImGui::GetColumnNormFromOffset(const ImGuiOldColumns* columns, float offset)\n{\n    return offset / (columns->OffMaxX - columns->OffMinX);\n}\n\nstatic const float COLUMNS_HIT_RECT_HALF_THICKNESS = 4.0f;\n\nstatic float GetDraggedColumnOffset(ImGuiOldColumns* columns, int column_index)\n{\n    // Active (dragged) column always follow mouse. The reason we need this is that dragging a column to the right edge of an auto-resizing\n    // window creates a feedback loop because we store normalized positions. So while dragging we enforce absolute positioning.\n    ImGuiContext& g = *GImGui;\n    ImGuiWindow* window = g.CurrentWindow;\n    IM_ASSERT(column_index > 0); // We are not supposed to drag column 0.\n    IM_ASSERT(g.ActiveId == columns->ID + ImGuiID(column_index));\n\n    float x = g.IO.MousePos.x - g.ActiveIdClickOffset.x + ImTrunc(COLUMNS_HIT_RECT_HALF_THICKNESS * g.CurrentDpiScale) - window->Pos.x;\n    x = ImMax(x, ImGui::GetColumnOffset(column_index - 1) + g.Style.ColumnsMinSpacing);\n    if ((columns->Flags & ImGuiOldColumnFlags_NoPreserveWidths))\n        x = ImMin(x, ImGui::GetColumnOffset(column_index + 1) - g.Style.ColumnsMinSpacing);\n\n    return x;\n}\n\nfloat ImGui::GetColumnOffset(int column_index)\n{\n    ImGuiWindow* window = GetCurrentWindowRead();\n    ImGuiOldColumns* columns = window->DC.CurrentColumns;\n    if (columns == NULL)\n        return 0.0f;\n\n    if (column_index < 0)\n        column_index = columns->Current;\n    IM_ASSERT(column_index < columns->Columns.Size);\n\n    const float t = columns->Columns[column_index].OffsetNorm;\n    const float x_offset = ImLerp(columns->OffMinX, columns->OffMaxX, t);\n    return x_offset;\n}\n\nstatic float GetColumnWidthEx(ImGuiOldColumns* columns, int column_index, bool before_resize = false)\n{\n    if (column_index < 0)\n        column_index = columns->Current;\n\n    float offset_norm;\n    if (before_resize)\n        offset_norm = columns->Columns[column_index + 1].OffsetNormBeforeResize - columns->Columns[column_index].OffsetNormBeforeResize;\n    else\n        offset_norm = columns->Columns[column_index + 1].OffsetNorm - columns->Columns[column_index].OffsetNorm;\n    return ImGui::GetColumnOffsetFromNorm(columns, offset_norm);\n}\n\nfloat ImGui::GetColumnWidth(int column_index)\n{\n    ImGuiContext& g = *GImGui;\n    ImGuiWindow* window = g.CurrentWindow;\n    ImGuiOldColumns* columns = window->DC.CurrentColumns;\n    if (columns == NULL)\n        return GetContentRegionAvail().x;\n\n    if (column_index < 0)\n        column_index = columns->Current;\n    return GetColumnOffsetFromNorm(columns, columns->Columns[column_index + 1].OffsetNorm - columns->Columns[column_index].OffsetNorm);\n}\n\nvoid ImGui::SetColumnOffset(int column_index, float offset)\n{\n    ImGuiContext& g = *GImGui;\n    ImGuiWindow* window = g.CurrentWindow;\n    ImGuiOldColumns* columns = window->DC.CurrentColumns;\n    IM_ASSERT(columns != NULL);\n\n    if (column_index < 0)\n        column_index = columns->Current;\n    IM_ASSERT(column_index < columns->Columns.Size);\n\n    const bool preserve_width = !(columns->Flags & ImGuiOldColumnFlags_NoPreserveWidths) && (column_index < columns->Count - 1);\n    const float width = preserve_width ? GetColumnWidthEx(columns, column_index, columns->IsBeingResized) : 0.0f;\n\n    if (!(columns->Flags & ImGuiOldColumnFlags_NoForceWithinWindow))\n        offset = ImMin(offset, columns->OffMaxX - g.Style.ColumnsMinSpacing * (columns->Count - column_index));\n    columns->Columns[column_index].OffsetNorm = GetColumnNormFromOffset(columns, offset - columns->OffMinX);\n\n    if (preserve_width)\n        SetColumnOffset(column_index + 1, offset + ImMax(g.Style.ColumnsMinSpacing, width));\n}\n\nvoid ImGui::SetColumnWidth(int column_index, float width)\n{\n    ImGuiWindow* window = GetCurrentWindowRead();\n    ImGuiOldColumns* columns = window->DC.CurrentColumns;\n    IM_ASSERT(columns != NULL);\n\n    if (column_index < 0)\n        column_index = columns->Current;\n    SetColumnOffset(column_index + 1, GetColumnOffset(column_index) + width);\n}\n\nvoid ImGui::PushColumnClipRect(int column_index)\n{\n    ImGuiWindow* window = GetCurrentWindowRead();\n    ImGuiOldColumns* columns = window->DC.CurrentColumns;\n    if (column_index < 0)\n        column_index = columns->Current;\n\n    ImGuiOldColumnData* column = &columns->Columns[column_index];\n    PushClipRect(column->ClipRect.Min, column->ClipRect.Max, false);\n}\n\n// Get into the columns background draw command (which is generally the same draw command as before we called BeginColumns)\nvoid ImGui::PushColumnsBackground()\n{\n    ImGuiWindow* window = GetCurrentWindowRead();\n    ImGuiOldColumns* columns = window->DC.CurrentColumns;\n    if (columns->Count == 1)\n        return;\n\n    // Optimization: avoid SetCurrentChannel() + PushClipRect()\n    columns->HostBackupClipRect = window->ClipRect;\n    SetWindowClipRectBeforeSetChannel(window, columns->HostInitialClipRect);\n    columns->Splitter.SetCurrentChannel(window->DrawList, 0);\n}\n\nvoid ImGui::PopColumnsBackground()\n{\n    ImGuiWindow* window = GetCurrentWindowRead();\n    ImGuiOldColumns* columns = window->DC.CurrentColumns;\n    if (columns->Count == 1)\n        return;\n\n    // Optimization: avoid PopClipRect() + SetCurrentChannel()\n    SetWindowClipRectBeforeSetChannel(window, columns->HostBackupClipRect);\n    columns->Splitter.SetCurrentChannel(window->DrawList, columns->Current + 1);\n}\n\nImGuiOldColumns* ImGui::FindOrCreateColumns(ImGuiWindow* window, ImGuiID id)\n{\n    // We have few columns per window so for now we don't need bother much with turning this into a faster lookup.\n    for (int n = 0; n < window->ColumnsStorage.Size; n++)\n        if (window->ColumnsStorage[n].ID == id)\n            return &window->ColumnsStorage[n];\n\n    window->ColumnsStorage.push_back(ImGuiOldColumns());\n    ImGuiOldColumns* columns = &window->ColumnsStorage.back();\n    columns->ID = id;\n    return columns;\n}\n\nImGuiID ImGui::GetColumnsID(const char* str_id, int columns_count)\n{\n    ImGuiWindow* window = GetCurrentWindow();\n\n    // Differentiate column ID with an arbitrary prefix for cases where users name their columns set the same as another widget.\n    // In addition, when an identifier isn't explicitly provided we include the number of columns in the hash to make it uniquer.\n    PushID(0x11223347 + (str_id ? 0 : columns_count));\n    ImGuiID id = window->GetID(str_id ? str_id : \"columns\");\n    PopID();\n\n    return id;\n}\n\nvoid ImGui::BeginColumns(const char* str_id, int columns_count, ImGuiOldColumnFlags flags)\n{\n    ImGuiContext& g = *GImGui;\n    ImGuiWindow* window = GetCurrentWindow();\n\n    IM_ASSERT(columns_count >= 1);\n    IM_ASSERT(window->DC.CurrentColumns == NULL);   // Nested columns are currently not supported\n\n    // Acquire storage for the columns set\n    ImGuiID id = GetColumnsID(str_id, columns_count);\n    ImGuiOldColumns* columns = FindOrCreateColumns(window, id);\n    IM_ASSERT(columns->ID == id);\n    columns->Current = 0;\n    columns->Count = columns_count;\n    columns->Flags = flags;\n    window->DC.CurrentColumns = columns;\n    window->DC.NavIsScrollPushableX = false; // Shortcut for NavUpdateCurrentWindowIsScrollPushableX();\n\n    columns->HostCursorPosY = window->DC.CursorPos.y;\n    columns->HostCursorMaxPosX = window->DC.CursorMaxPos.x;\n    columns->HostInitialClipRect = window->ClipRect;\n    columns->HostBackupParentWorkRect = window->ParentWorkRect;\n    window->ParentWorkRect = window->WorkRect;\n\n    // Set state for first column\n    // We aim so that the right-most column will have the same clipping width as other after being clipped by parent ClipRect\n    const float column_padding = g.Style.ItemSpacing.x;\n    const float half_clip_extend_x = ImTrunc(ImMax(window->WindowPadding.x * 0.5f, window->WindowBorderSize));\n    const float max_1 = window->WorkRect.Max.x + column_padding - ImMax(column_padding - window->WindowPadding.x, 0.0f);\n    const float max_2 = window->WorkRect.Max.x + half_clip_extend_x;\n    columns->OffMinX = window->DC.Indent.x - column_padding + ImMax(column_padding - window->WindowPadding.x, 0.0f);\n    columns->OffMaxX = ImMax(ImMin(max_1, max_2) - window->Pos.x, columns->OffMinX + 1.0f);\n    columns->LineMinY = columns->LineMaxY = window->DC.CursorPos.y;\n\n    // Clear data if columns count changed\n    if (columns->Columns.Size != 0 && columns->Columns.Size != columns_count + 1)\n        columns->Columns.resize(0);\n\n    // Initialize default widths\n    columns->IsFirstFrame = (columns->Columns.Size == 0);\n    if (columns->Columns.Size == 0)\n    {\n        columns->Columns.reserve(columns_count + 1);\n        for (int n = 0; n < columns_count + 1; n++)\n        {\n            ImGuiOldColumnData column;\n            column.OffsetNorm = n / (float)columns_count;\n            columns->Columns.push_back(column);\n        }\n    }\n\n    for (int n = 0; n < columns_count; n++)\n    {\n        // Compute clipping rectangle\n        ImGuiOldColumnData* column = &columns->Columns[n];\n        float clip_x1 = IM_ROUND(window->Pos.x + GetColumnOffset(n));\n        float clip_x2 = IM_ROUND(window->Pos.x + GetColumnOffset(n + 1) - 1.0f);\n        column->ClipRect = ImRect(clip_x1, -FLT_MAX, clip_x2, +FLT_MAX);\n        column->ClipRect.ClipWithFull(window->ClipRect);\n    }\n\n    if (columns->Count > 1)\n    {\n        columns->Splitter.Split(window->DrawList, 1 + columns->Count);\n        columns->Splitter.SetCurrentChannel(window->DrawList, 1);\n        PushColumnClipRect(0);\n    }\n\n    // We don't generally store Indent.x inside ColumnsOffset because it may be manipulated by the user.\n    float offset_0 = GetColumnOffset(columns->Current);\n    float offset_1 = GetColumnOffset(columns->Current + 1);\n    float width = offset_1 - offset_0;\n    PushItemWidth(width * 0.65f);\n    window->DC.ColumnsOffset.x = ImMax(column_padding - window->WindowPadding.x, 0.0f);\n    window->DC.CursorPos.x = IM_TRUNC(window->Pos.x + window->DC.Indent.x + window->DC.ColumnsOffset.x);\n    window->WorkRect.Max.x = window->Pos.x + offset_1 - column_padding;\n    window->WorkRect.Max.y = window->ContentRegionRect.Max.y;\n}\n\nvoid ImGui::NextColumn()\n{\n    ImGuiWindow* window = GetCurrentWindow();\n    if (window->SkipItems || window->DC.CurrentColumns == NULL)\n        return;\n\n    ImGuiContext& g = *GImGui;\n    ImGuiOldColumns* columns = window->DC.CurrentColumns;\n\n    if (columns->Count == 1)\n    {\n        window->DC.CursorPos.x = IM_TRUNC(window->Pos.x + window->DC.Indent.x + window->DC.ColumnsOffset.x);\n        IM_ASSERT(columns->Current == 0);\n        return;\n    }\n\n    // Next column\n    if (++columns->Current == columns->Count)\n        columns->Current = 0;\n\n    PopItemWidth();\n\n    // Optimization: avoid PopClipRect() + SetCurrentChannel() + PushClipRect()\n    // (which would needlessly attempt to update commands in the wrong channel, then pop or overwrite them),\n    ImGuiOldColumnData* column = &columns->Columns[columns->Current];\n    SetWindowClipRectBeforeSetChannel(window, column->ClipRect);\n    columns->Splitter.SetCurrentChannel(window->DrawList, columns->Current + 1);\n\n    const float column_padding = g.Style.ItemSpacing.x;\n    columns->LineMaxY = ImMax(columns->LineMaxY, window->DC.CursorPos.y);\n    if (columns->Current > 0)\n    {\n        // Columns 1+ ignore IndentX (by canceling it out)\n        // FIXME-COLUMNS: Unnecessary, could be locked?\n        window->DC.ColumnsOffset.x = GetColumnOffset(columns->Current) - window->DC.Indent.x + column_padding;\n    }\n    else\n    {\n        // New row/line: column 0 honor IndentX.\n        window->DC.ColumnsOffset.x = ImMax(column_padding - window->WindowPadding.x, 0.0f);\n        window->DC.IsSameLine = false;\n        columns->LineMinY = columns->LineMaxY;\n    }\n    window->DC.CursorPos.x = IM_TRUNC(window->Pos.x + window->DC.Indent.x + window->DC.ColumnsOffset.x);\n    window->DC.CursorPos.y = columns->LineMinY;\n    window->DC.CurrLineSize = ImVec2(0.0f, 0.0f);\n    window->DC.CurrLineTextBaseOffset = 0.0f;\n\n    // FIXME-COLUMNS: Share code with BeginColumns() - move code on columns setup.\n    float offset_0 = GetColumnOffset(columns->Current);\n    float offset_1 = GetColumnOffset(columns->Current + 1);\n    float width = offset_1 - offset_0;\n    PushItemWidth(width * 0.65f);\n    window->WorkRect.Max.x = window->Pos.x + offset_1 - column_padding;\n}\n\nvoid ImGui::EndColumns()\n{\n    ImGuiContext& g = *GImGui;\n    ImGuiWindow* window = GetCurrentWindow();\n    ImGuiOldColumns* columns = window->DC.CurrentColumns;\n    IM_ASSERT(columns != NULL);\n\n    PopItemWidth();\n    if (columns->Count > 1)\n    {\n        PopClipRect();\n        columns->Splitter.Merge(window->DrawList);\n    }\n\n    const ImGuiOldColumnFlags flags = columns->Flags;\n    columns->LineMaxY = ImMax(columns->LineMaxY, window->DC.CursorPos.y);\n    window->DC.CursorPos.y = columns->LineMaxY;\n    if (!(flags & ImGuiOldColumnFlags_GrowParentContentsSize))\n        window->DC.CursorMaxPos.x = columns->HostCursorMaxPosX;  // Restore cursor max pos, as columns don't grow parent\n\n    // Draw columns borders and handle resize\n    // The IsBeingResized flag ensure we preserve pre-resize columns width so back-and-forth are not lossy\n    bool is_being_resized = false;\n    if (!(flags & ImGuiOldColumnFlags_NoBorder) && !window->SkipItems)\n    {\n        // We clip Y boundaries CPU side because very long triangles are mishandled by some GPU drivers.\n        const float y1 = ImMax(columns->HostCursorPosY, window->ClipRect.Min.y);\n        const float y2 = ImMin(window->DC.CursorPos.y, window->ClipRect.Max.y);\n        int dragging_column = -1;\n        for (int n = 1; n < columns->Count; n++)\n        {\n            ImGuiOldColumnData* column = &columns->Columns[n];\n            float x = window->Pos.x + GetColumnOffset(n);\n            const ImGuiID column_id = columns->ID + ImGuiID(n);\n            const float column_hit_hw = ImTrunc(COLUMNS_HIT_RECT_HALF_THICKNESS * g.CurrentDpiScale);\n            const ImRect column_hit_rect(ImVec2(x - column_hit_hw, y1), ImVec2(x + column_hit_hw, y2));\n            if (!ItemAdd(column_hit_rect, column_id, NULL, ImGuiItemFlags_NoNav))\n                continue;\n\n            bool hovered = false, held = false;\n            if (!(flags & ImGuiOldColumnFlags_NoResize))\n            {\n                ButtonBehavior(column_hit_rect, column_id, &hovered, &held);\n                if (hovered || held)\n                    SetMouseCursor(ImGuiMouseCursor_ResizeEW);\n                if (held && !(column->Flags & ImGuiOldColumnFlags_NoResize))\n                    dragging_column = n;\n            }\n\n            // Draw column\n            const ImU32 col = GetColorU32(held ? ImGuiCol_SeparatorActive : hovered ? ImGuiCol_SeparatorHovered : ImGuiCol_Separator);\n            const float xi = IM_TRUNC(x);\n            window->DrawList->AddLine(ImVec2(xi, y1 + 1.0f), ImVec2(xi, y2), col);\n        }\n\n        // Apply dragging after drawing the column lines, so our rendered lines are in sync with how items were displayed during the frame.\n        if (dragging_column != -1)\n        {\n            if (!columns->IsBeingResized)\n                for (int n = 0; n < columns->Count + 1; n++)\n                    columns->Columns[n].OffsetNormBeforeResize = columns->Columns[n].OffsetNorm;\n            columns->IsBeingResized = is_being_resized = true;\n            float x = GetDraggedColumnOffset(columns, dragging_column);\n            SetColumnOffset(dragging_column, x);\n        }\n    }\n    columns->IsBeingResized = is_being_resized;\n\n    window->WorkRect = window->ParentWorkRect;\n    window->ParentWorkRect = columns->HostBackupParentWorkRect;\n    window->DC.CurrentColumns = NULL;\n    window->DC.ColumnsOffset.x = 0.0f;\n    window->DC.CursorPos.x = IM_TRUNC(window->Pos.x + window->DC.Indent.x + window->DC.ColumnsOffset.x);\n    NavUpdateCurrentWindowIsScrollPushableX();\n}\n\nvoid ImGui::Columns(int columns_count, const char* id, bool borders)\n{\n    ImGuiWindow* window = GetCurrentWindow();\n    IM_ASSERT(columns_count >= 1);\n\n    ImGuiOldColumnFlags flags = (borders ? 0 : ImGuiOldColumnFlags_NoBorder);\n    //flags |= ImGuiOldColumnFlags_NoPreserveWidths; // NB: Legacy behavior\n    ImGuiOldColumns* columns = window->DC.CurrentColumns;\n    if (columns != NULL && columns->Count == columns_count && columns->Flags == flags)\n        return;\n\n    if (columns != NULL)\n        EndColumns();\n\n    if (columns_count != 1)\n        BeginColumns(id, columns_count, flags);\n}\n\n//-------------------------------------------------------------------------\n\n#endif // #ifndef IMGUI_DISABLE\n"
  },
  {
    "path": "src/DesktopPlusUI/imgui/imgui_widgets.cpp",
    "content": "// dear imgui, v1.91b\n// (widgets code)\n\n/*\n\nIndex of this file:\n\n// [SECTION] Forward Declarations\n// [SECTION] Widgets: Text, etc.\n// [SECTION] Widgets: Main (Button, Image, Checkbox, RadioButton, ProgressBar, Bullet, etc.)\n// [SECTION] Widgets: Low-level Layout helpers (Spacing, Dummy, NewLine, Separator, etc.)\n// [SECTION] Widgets: ComboBox\n// [SECTION] Data Type and Data Formatting Helpers\n// [SECTION] Widgets: DragScalar, DragFloat, DragInt, etc.\n// [SECTION] Widgets: SliderScalar, SliderFloat, SliderInt, etc.\n// [SECTION] Widgets: InputScalar, InputFloat, InputInt, etc.\n// [SECTION] Widgets: InputText, InputTextMultiline\n// [SECTION] Widgets: ColorEdit, ColorPicker, ColorButton, etc.\n// [SECTION] Widgets: TreeNode, CollapsingHeader, etc.\n// [SECTION] Widgets: Selectable\n// [SECTION] Widgets: Typing-Select support\n// [SECTION] Widgets: Box-Select support\n// [SECTION] Widgets: Multi-Select support\n// [SECTION] Widgets: Multi-Select helpers\n// [SECTION] Widgets: ListBox\n// [SECTION] Widgets: PlotLines, PlotHistogram\n// [SECTION] Widgets: Value helpers\n// [SECTION] Widgets: MenuItem, BeginMenu, EndMenu, etc.\n// [SECTION] Widgets: BeginTabBar, EndTabBar, etc.\n// [SECTION] Widgets: BeginTabItem, EndTabItem, etc.\n// [SECTION] Widgets: Columns, BeginColumns, EndColumns, etc.\n\n*/\n\n#if defined(_MSC_VER) && !defined(_CRT_SECURE_NO_WARNINGS)\n#define _CRT_SECURE_NO_WARNINGS\n#endif\n\n#ifndef IMGUI_DEFINE_MATH_OPERATORS\n#define IMGUI_DEFINE_MATH_OPERATORS\n#endif\n\n#include \"imgui.h\"\n#ifndef IMGUI_DISABLE\n#include \"imgui_internal.h\"\n\n// System includes\n#include <stdint.h>     // intptr_t\n\n//-------------------------------------------------------------------------\n// Warnings\n//-------------------------------------------------------------------------\n\n// Visual Studio warnings\n#ifdef _MSC_VER\n#pragma warning (disable: 4127)     // condition expression is constant\n#pragma warning (disable: 4996)     // 'This function or variable may be unsafe': strcpy, strdup, sprintf, vsnprintf, sscanf, fopen\n#if defined(_MSC_VER) && _MSC_VER >= 1922 // MSVC 2019 16.2 or later\n#pragma warning (disable: 5054)     // operator '|': deprecated between enumerations of different types\n#endif\n#pragma warning (disable: 26451)    // [Static Analyzer] Arithmetic overflow : Using operator 'xxx' on a 4 byte value and then casting the result to a 8 byte value. Cast the value to the wider type before calling operator 'xxx' to avoid overflow(io.2).\n#pragma warning (disable: 26812)    // [Static Analyzer] The enum type 'xxx' is unscoped. Prefer 'enum class' over 'enum' (Enum.3).\n#endif\n\n// Clang/GCC warnings with -Weverything\n#if defined(__clang__)\n#if __has_warning(\"-Wunknown-warning-option\")\n#pragma clang diagnostic ignored \"-Wunknown-warning-option\"         // warning: unknown warning group 'xxx'                      // not all warnings are known by all Clang versions and they tend to be rename-happy.. so ignoring warnings triggers new warnings on some configuration. Great!\n#endif\n#pragma clang diagnostic ignored \"-Wunknown-pragmas\"                // warning: unknown warning group 'xxx'\n#pragma clang diagnostic ignored \"-Wold-style-cast\"                 // warning: use of old-style cast                            // yes, they are more terse.\n#pragma clang diagnostic ignored \"-Wfloat-equal\"                    // warning: comparing floating point with == or != is unsafe // storing and comparing against same constants (typically 0.0f) is ok.\n#pragma clang diagnostic ignored \"-Wformat\"                         // warning: format specifies type 'int' but the argument has type 'unsigned int'\n#pragma clang diagnostic ignored \"-Wformat-nonliteral\"              // warning: format string is not a string literal            // passing non-literal to vsnformat(). yes, user passing incorrect format strings can crash the code.\n#pragma clang diagnostic ignored \"-Wsign-conversion\"                // warning: implicit conversion changes signedness\n#pragma clang diagnostic ignored \"-Wunused-macros\"                  // warning: macro is not used                                // we define snprintf/vsnprintf on Windows so they are available, but not always used.\n#pragma clang diagnostic ignored \"-Wzero-as-null-pointer-constant\"  // warning: zero as null pointer constant                    // some standard header variations use #define NULL 0\n#pragma clang diagnostic ignored \"-Wdouble-promotion\"               // warning: implicit conversion from 'float' to 'double' when passing argument to function  // using printf() is a misery with this as C++ va_arg ellipsis changes float to double.\n#pragma clang diagnostic ignored \"-Wenum-enum-conversion\"           // warning: bitwise operation between different enumeration types ('XXXFlags_' and 'XXXFlagsPrivate_')\n#pragma clang diagnostic ignored \"-Wdeprecated-enum-enum-conversion\"// warning: bitwise operation between different enumeration types ('XXXFlags_' and 'XXXFlagsPrivate_') is deprecated\n#pragma clang diagnostic ignored \"-Wimplicit-int-float-conversion\"  // warning: implicit conversion from 'xxx' to 'float' may lose precision\n#pragma clang diagnostic ignored \"-Wunsafe-buffer-usage\"            // warning: 'xxx' is an unsafe pointer used for buffer access\n#pragma clang diagnostic ignored \"-Wnontrivial-memaccess\"           // warning: first argument in call to 'memset' is a pointer to non-trivially copyable type\n#pragma clang diagnostic ignored \"-Wswitch-default\"                 // warning: 'switch' missing 'default' label\n#elif defined(__GNUC__)\n#pragma GCC diagnostic ignored \"-Wpragmas\"                          // warning: unknown option after '#pragma GCC diagnostic' kind\n#pragma GCC diagnostic ignored \"-Wfloat-equal\"                      // warning: comparing floating-point with '==' or '!=' is unsafe\n#pragma GCC diagnostic ignored \"-Wformat\"                           // warning: format '%p' expects argument of type 'int'/'void*', but argument X has type 'unsigned int'/'ImGuiWindow*'\n#pragma GCC diagnostic ignored \"-Wformat-nonliteral\"                // warning: format not a string literal, format string not checked\n#pragma GCC diagnostic ignored \"-Wdeprecated-enum-enum-conversion\"  // warning: bitwise operation between different enumeration types ('XXXFlags_' and 'XXXFlagsPrivate_') is deprecated\n#pragma GCC diagnostic ignored \"-Wdouble-promotion\"                 // warning: implicit conversion from 'float' to 'double' when passing argument to function\n#pragma GCC diagnostic ignored \"-Wstrict-overflow\"                  // warning: assuming signed overflow does not occur when simplifying division / ..when changing X +- C1 cmp C2 to X cmp C2 -+ C1\n#pragma GCC diagnostic ignored \"-Wclass-memaccess\"                  // [__GNUC__ >= 8] warning: 'memset/memcpy' clearing/writing an object of type 'xxxx' with no trivial copy-assignment; use assignment or value-initialization instead\n#pragma GCC diagnostic ignored \"-Wcast-qual\"                        // warning: cast from type 'const xxxx *' to type 'xxxx *' casts away qualifiers\n#endif\n\n//-------------------------------------------------------------------------\n// Data\n//-------------------------------------------------------------------------\n\n// Widgets\nstatic const float          DRAGDROP_HOLD_TO_OPEN_TIMER = 0.70f;    // Time for drag-hold to activate items accepting the ImGuiButtonFlags_PressedOnDragDropHold button behavior.\nstatic const float          DRAG_MOUSE_THRESHOLD_FACTOR = 0.50f;    // Multiplier for the default value of io.MouseDragThreshold to make DragFloat/DragInt react faster to mouse drags.\n\n// Those MIN/MAX values are not define because we need to point to them\nstatic const signed char    IM_S8_MIN  = -128;\nstatic const signed char    IM_S8_MAX  = 127;\nstatic const unsigned char  IM_U8_MIN  = 0;\nstatic const unsigned char  IM_U8_MAX  = 0xFF;\nstatic const signed short   IM_S16_MIN = -32768;\nstatic const signed short   IM_S16_MAX = 32767;\nstatic const unsigned short IM_U16_MIN = 0;\nstatic const unsigned short IM_U16_MAX = 0xFFFF;\nstatic const ImS32          IM_S32_MIN = INT_MIN;    // (-2147483647 - 1), (0x80000000);\nstatic const ImS32          IM_S32_MAX = INT_MAX;    // (2147483647), (0x7FFFFFFF)\nstatic const ImU32          IM_U32_MIN = 0;\nstatic const ImU32          IM_U32_MAX = UINT_MAX;   // (0xFFFFFFFF)\n#ifdef LLONG_MIN\nstatic const ImS64          IM_S64_MIN = LLONG_MIN;  // (-9223372036854775807ll - 1ll);\nstatic const ImS64          IM_S64_MAX = LLONG_MAX;  // (9223372036854775807ll);\n#else\nstatic const ImS64          IM_S64_MIN = -9223372036854775807LL - 1;\nstatic const ImS64          IM_S64_MAX = 9223372036854775807LL;\n#endif\nstatic const ImU64          IM_U64_MIN = 0;\n#ifdef ULLONG_MAX\nstatic const ImU64          IM_U64_MAX = ULLONG_MAX; // (0xFFFFFFFFFFFFFFFFull);\n#else\nstatic const ImU64          IM_U64_MAX = (2ULL * 9223372036854775807LL + 1);\n#endif\n\n//-------------------------------------------------------------------------\n// [SECTION] Forward Declarations\n//-------------------------------------------------------------------------\n\n// For InputTextEx()\nstatic bool     InputTextFilterCharacter(ImGuiContext* ctx, unsigned int* p_char, ImGuiInputTextFlags flags, ImGuiInputTextCallback callback, void* user_data, bool input_source_is_clipboard = false);\nstatic int      InputTextCalcTextLenAndLineCount(const char* text_begin, const char** out_text_end);\nstatic ImVec2   InputTextCalcTextSize(ImGuiContext* ctx, const char* text_begin, const char* text_end, const char** remaining = NULL, ImVec2* out_offset = NULL, bool stop_on_new_line = false);\n\n//-------------------------------------------------------------------------\n// [SECTION] Widgets: Text, etc.\n//-------------------------------------------------------------------------\n// - TextEx() [Internal]\n// - TextUnformatted()\n// - Text()\n// - TextV()\n// - TextColored()\n// - TextColoredV()\n// - TextDisabled()\n// - TextDisabledV()\n// - TextWrapped()\n// - TextWrappedV()\n// - LabelText()\n// - LabelTextV()\n// - BulletText()\n// - BulletTextV()\n//-------------------------------------------------------------------------\n\nvoid ImGui::TextEx(const char* text, const char* text_end, ImGuiTextFlags flags)\n{\n    ImGuiWindow* window = GetCurrentWindow();\n    if (window->SkipItems)\n        return;\n    ImGuiContext& g = *GImGui;\n\n    // Accept null ranges\n    if (text == text_end)\n        text = text_end = \"\";\n\n    // Calculate length\n    const char* text_begin = text;\n    if (text_end == NULL)\n        text_end = text + ImStrlen(text); // FIXME-OPT\n\n    const ImVec2 text_pos(window->DC.CursorPos.x, window->DC.CursorPos.y + window->DC.CurrLineTextBaseOffset);\n    const float wrap_pos_x = window->DC.TextWrapPos;\n    const bool wrap_enabled = (wrap_pos_x >= 0.0f);\n    if (text_end - text <= 2000 || wrap_enabled)\n    {\n        // Common case\n        const float wrap_width = wrap_enabled ? CalcWrapWidthForPos(window->DC.CursorPos, wrap_pos_x) : 0.0f;\n        const ImVec2 text_size = CalcTextSize(text_begin, text_end, false, wrap_width);\n\n        ImRect bb(text_pos, text_pos + text_size);\n        ItemSize(text_size, 0.0f);\n        if (!ItemAdd(bb, 0))\n            return;\n\n        // Render (we don't hide text after ## in this end-user function)\n        RenderTextWrapped(bb.Min, text_begin, text_end, wrap_width);\n    }\n    else\n    {\n        // Long text!\n        // Perform manual coarse clipping to optimize for long multi-line text\n        // - From this point we will only compute the width of lines that are visible. Optimization only available when word-wrapping is disabled.\n        // - We also don't vertically center the text within the line full height, which is unlikely to matter because we are likely the biggest and only item on the line.\n        // - We use memchr(), pay attention that well optimized versions of those str/mem functions are much faster than a casually written loop.\n        const char* line = text;\n        const float line_height = GetTextLineHeight();\n        ImVec2 text_size(0, 0);\n\n        // Lines to skip (can't skip when logging text)\n        ImVec2 pos = text_pos;\n        if (!g.LogEnabled)\n        {\n            int lines_skippable = (int)((window->ClipRect.Min.y - text_pos.y) / line_height);\n            if (lines_skippable > 0)\n            {\n                int lines_skipped = 0;\n                while (line < text_end && lines_skipped < lines_skippable)\n                {\n                    const char* line_end = (const char*)ImMemchr(line, '\\n', text_end - line);\n                    if (!line_end)\n                        line_end = text_end;\n                    if ((flags & ImGuiTextFlags_NoWidthForLargeClippedText) == 0)\n                        text_size.x = ImMax(text_size.x, CalcTextSize(line, line_end).x);\n                    line = line_end + 1;\n                    lines_skipped++;\n                }\n                pos.y += lines_skipped * line_height;\n            }\n        }\n\n        // Lines to render\n        if (line < text_end)\n        {\n            ImRect line_rect(pos, pos + ImVec2(FLT_MAX, line_height));\n            while (line < text_end)\n            {\n                if (IsClippedEx(line_rect, 0))\n                    break;\n\n                const char* line_end = (const char*)ImMemchr(line, '\\n', text_end - line);\n                if (!line_end)\n                    line_end = text_end;\n                text_size.x = ImMax(text_size.x, CalcTextSize(line, line_end).x);\n                RenderText(pos, line, line_end, false);\n                line = line_end + 1;\n                line_rect.Min.y += line_height;\n                line_rect.Max.y += line_height;\n                pos.y += line_height;\n            }\n\n            // Count remaining lines\n            int lines_skipped = 0;\n            while (line < text_end)\n            {\n                const char* line_end = (const char*)ImMemchr(line, '\\n', text_end - line);\n                if (!line_end)\n                    line_end = text_end;\n                if ((flags & ImGuiTextFlags_NoWidthForLargeClippedText) == 0)\n                    text_size.x = ImMax(text_size.x, CalcTextSize(line, line_end).x);\n                line = line_end + 1;\n                lines_skipped++;\n            }\n            pos.y += lines_skipped * line_height;\n        }\n        text_size.y = (pos - text_pos).y;\n\n        ImRect bb(text_pos, text_pos + text_size);\n        ItemSize(text_size, 0.0f);\n        ItemAdd(bb, 0);\n    }\n}\n\nvoid ImGui::TextUnformatted(const char* text, const char* text_end)\n{\n    TextEx(text, text_end, ImGuiTextFlags_NoWidthForLargeClippedText);\n}\n\nvoid ImGui::Text(const char* fmt, ...)\n{\n    va_list args;\n    va_start(args, fmt);\n    TextV(fmt, args);\n    va_end(args);\n}\n\nvoid ImGui::TextV(const char* fmt, va_list args)\n{\n    ImGuiWindow* window = GetCurrentWindow();\n    if (window->SkipItems)\n        return;\n\n    const char* text, *text_end;\n    ImFormatStringToTempBufferV(&text, &text_end, fmt, args);\n    TextEx(text, text_end, ImGuiTextFlags_NoWidthForLargeClippedText);\n}\n\nvoid ImGui::TextColored(const ImVec4& col, const char* fmt, ...)\n{\n    va_list args;\n    va_start(args, fmt);\n    TextColoredV(col, fmt, args);\n    va_end(args);\n}\n\nvoid ImGui::TextColoredV(const ImVec4& col, const char* fmt, va_list args)\n{\n    PushStyleColor(ImGuiCol_Text, col);\n    TextV(fmt, args);\n    PopStyleColor();\n}\n\nvoid ImGui::TextDisabled(const char* fmt, ...)\n{\n    va_list args;\n    va_start(args, fmt);\n    TextDisabledV(fmt, args);\n    va_end(args);\n}\n\nvoid ImGui::TextDisabledV(const char* fmt, va_list args)\n{\n    ImGuiContext& g = *GImGui;\n    PushStyleColor(ImGuiCol_Text, g.Style.Colors[ImGuiCol_TextDisabled]);\n    TextV(fmt, args);\n    PopStyleColor();\n}\n\nvoid ImGui::TextWrapped(const char* fmt, ...)\n{\n    va_list args;\n    va_start(args, fmt);\n    TextWrappedV(fmt, args);\n    va_end(args);\n}\n\nvoid ImGui::TextWrappedV(const char* fmt, va_list args)\n{\n    ImGuiContext& g = *GImGui;\n    const bool need_backup = (g.CurrentWindow->DC.TextWrapPos < 0.0f);  // Keep existing wrap position if one is already set\n    if (need_backup)\n        PushTextWrapPos(0.0f);\n    TextV(fmt, args);\n    if (need_backup)\n        PopTextWrapPos();\n}\n\nvoid ImGui::LabelText(const char* label, const char* fmt, ...)\n{\n    va_list args;\n    va_start(args, fmt);\n    LabelTextV(label, fmt, args);\n    va_end(args);\n}\n\n// Add a label+text combo aligned to other label+value widgets\nvoid ImGui::LabelTextV(const char* label, const char* fmt, va_list args)\n{\n    ImGuiWindow* window = GetCurrentWindow();\n    if (window->SkipItems)\n        return;\n\n    ImGuiContext& g = *GImGui;\n    const ImGuiStyle& style = g.Style;\n    const float w = CalcItemWidth();\n\n    const char* value_text_begin, *value_text_end;\n    ImFormatStringToTempBufferV(&value_text_begin, &value_text_end, fmt, args);\n    const ImVec2 value_size = CalcTextSize(value_text_begin, value_text_end, false);\n    const ImVec2 label_size = CalcTextSize(label, NULL, true);\n\n    const ImVec2 pos = window->DC.CursorPos;\n    const ImRect value_bb(pos, pos + ImVec2(w, value_size.y + style.FramePadding.y * 2));\n    const ImRect total_bb(pos, pos + ImVec2(w + (label_size.x > 0.0f ? style.ItemInnerSpacing.x + label_size.x : 0.0f), ImMax(value_size.y, label_size.y) + style.FramePadding.y * 2));\n    ItemSize(total_bb, style.FramePadding.y);\n    if (!ItemAdd(total_bb, 0))\n        return;\n\n    // Render\n    RenderTextClipped(value_bb.Min + style.FramePadding, value_bb.Max, value_text_begin, value_text_end, &value_size, ImVec2(0.0f, 0.0f));\n    if (label_size.x > 0.0f)\n        RenderText(ImVec2(value_bb.Max.x + style.ItemInnerSpacing.x, value_bb.Min.y + style.FramePadding.y), label);\n}\n\nvoid ImGui::BulletText(const char* fmt, ...)\n{\n    va_list args;\n    va_start(args, fmt);\n    BulletTextV(fmt, args);\n    va_end(args);\n}\n\n// Text with a little bullet aligned to the typical tree node.\nvoid ImGui::BulletTextV(const char* fmt, va_list args)\n{\n    ImGuiWindow* window = GetCurrentWindow();\n    if (window->SkipItems)\n        return;\n\n    ImGuiContext& g = *GImGui;\n    const ImGuiStyle& style = g.Style;\n\n    const char* text_begin, *text_end;\n    ImFormatStringToTempBufferV(&text_begin, &text_end, fmt, args);\n    const ImVec2 label_size = CalcTextSize(text_begin, text_end, false);\n    const ImVec2 total_size = ImVec2(g.FontSize + (label_size.x > 0.0f ? (label_size.x + style.FramePadding.x * 2) : 0.0f), label_size.y);  // Empty text doesn't add padding\n    ImVec2 pos = window->DC.CursorPos;\n    pos.y += window->DC.CurrLineTextBaseOffset;\n    ItemSize(total_size, 0.0f);\n    const ImRect bb(pos, pos + total_size);\n    if (!ItemAdd(bb, 0))\n        return;\n\n    // Render\n    ImU32 text_col = GetColorU32(ImGuiCol_Text);\n    RenderBullet(window->DrawList, bb.Min + ImVec2(style.FramePadding.x + g.FontSize * 0.5f, g.FontSize * 0.5f), text_col);\n    RenderText(bb.Min + ImVec2(g.FontSize + style.FramePadding.x * 2, 0.0f), text_begin, text_end, false);\n}\n\n//-------------------------------------------------------------------------\n// [SECTION] Widgets: Main\n//-------------------------------------------------------------------------\n// - ButtonBehavior() [Internal]\n// - Button()\n// - SmallButton()\n// - InvisibleButton()\n// - ArrowButton()\n// - CloseButton() [Internal]\n// - CollapseButton() [Internal]\n// - GetWindowScrollbarID() [Internal]\n// - GetWindowScrollbarRect() [Internal]\n// - Scrollbar() [Internal]\n// - ScrollbarEx() [Internal]\n// - Image()\n// - ImageButton()\n// - Checkbox()\n// - CheckboxFlagsT() [Internal]\n// - CheckboxFlags()\n// - RadioButton()\n// - ProgressBar()\n// - Bullet()\n// - Hyperlink()\n//-------------------------------------------------------------------------\n\n// The ButtonBehavior() function is key to many interactions and used by many/most widgets.\n// Because we handle so many cases (keyboard/gamepad navigation, drag and drop) and many specific behavior (via ImGuiButtonFlags_),\n// this code is a little complex.\n// By far the most common path is interacting with the Mouse using the default ImGuiButtonFlags_PressedOnClickRelease button behavior.\n// See the series of events below and the corresponding state reported by dear imgui:\n//------------------------------------------------------------------------------------------------------------------------------------------------\n// with PressedOnClickRelease:             return-value  IsItemHovered()  IsItemActive()  IsItemActivated()  IsItemDeactivated()  IsItemClicked()\n//   Frame N+0 (mouse is outside bb)        -             -                -               -                  -                    -\n//   Frame N+1 (mouse moves inside bb)      -             true             -               -                  -                    -\n//   Frame N+2 (mouse button is down)       -             true             true            true               -                    true\n//   Frame N+3 (mouse button is down)       -             true             true            -                  -                    -\n//   Frame N+4 (mouse moves outside bb)     -             -                true            -                  -                    -\n//   Frame N+5 (mouse moves inside bb)      -             true             true            -                  -                    -\n//   Frame N+6 (mouse button is released)   true          true             -               -                  true                 -\n//   Frame N+7 (mouse button is released)   -             true             -               -                  -                    -\n//   Frame N+8 (mouse moves outside bb)     -             -                -               -                  -                    -\n//------------------------------------------------------------------------------------------------------------------------------------------------\n// with PressedOnClick:                    return-value  IsItemHovered()  IsItemActive()  IsItemActivated()  IsItemDeactivated()  IsItemClicked()\n//   Frame N+2 (mouse button is down)       true          true             true            true               -                    true\n//   Frame N+3 (mouse button is down)       -             true             true            -                  -                    -\n//   Frame N+6 (mouse button is released)   -             true             -               -                  true                 -\n//   Frame N+7 (mouse button is released)   -             true             -               -                  -                    -\n//------------------------------------------------------------------------------------------------------------------------------------------------\n// with PressedOnRelease:                  return-value  IsItemHovered()  IsItemActive()  IsItemActivated()  IsItemDeactivated()  IsItemClicked()\n//   Frame N+2 (mouse button is down)       -             true             -               -                  -                    true\n//   Frame N+3 (mouse button is down)       -             true             -               -                  -                    -\n//   Frame N+6 (mouse button is released)   true          true             -               -                  -                    -\n//   Frame N+7 (mouse button is released)   -             true             -               -                  -                    -\n//------------------------------------------------------------------------------------------------------------------------------------------------\n// with PressedOnDoubleClick:              return-value  IsItemHovered()  IsItemActive()  IsItemActivated()  IsItemDeactivated()  IsItemClicked()\n//   Frame N+0 (mouse button is down)       -             true             -               -                  -                    true\n//   Frame N+1 (mouse button is down)       -             true             -               -                  -                    -\n//   Frame N+2 (mouse button is released)   -             true             -               -                  -                    -\n//   Frame N+3 (mouse button is released)   -             true             -               -                  -                    -\n//   Frame N+4 (mouse button is down)       true          true             true            true               -                    true\n//   Frame N+5 (mouse button is down)       -             true             true            -                  -                    -\n//   Frame N+6 (mouse button is released)   -             true             -               -                  true                 -\n//   Frame N+7 (mouse button is released)   -             true             -               -                  -                    -\n//------------------------------------------------------------------------------------------------------------------------------------------------\n// Note that some combinations are supported,\n// - PressedOnDragDropHold can generally be associated with any flag.\n// - PressedOnDoubleClick can be associated by PressedOnClickRelease/PressedOnRelease, in which case the second release event won't be reported.\n//------------------------------------------------------------------------------------------------------------------------------------------------\n// The behavior of the return-value changes when ImGuiItemFlags_ButtonRepeat is set:\n//                                         Repeat+                  Repeat+           Repeat+             Repeat+\n//                                         PressedOnClickRelease    PressedOnClick    PressedOnRelease    PressedOnDoubleClick\n//-------------------------------------------------------------------------------------------------------------------------------------------------\n//   Frame N+0 (mouse button is down)       -                        true              -                   true\n//   ...                                    -                        -                 -                   -\n//   Frame N + RepeatDelay                  true                     true              -                   true\n//   ...                                    -                        -                 -                   -\n//   Frame N + RepeatDelay + RepeatRate*N   true                     true              -                   true\n//-------------------------------------------------------------------------------------------------------------------------------------------------\n\n// - FIXME: For refactor we could output flags, incl mouse hovered vs nav keyboard vs nav triggered etc.\n//   And better standardize how widgets use 'GetColor32((held && hovered) ? ... : hovered ? ...)' vs 'GetColor32(held ? ... : hovered ? ...);'\n//   For mouse feedback we typically prefer the 'held && hovered' test, but for nav feedback not always. Outputting hovered=true on Activation may be misleading.\n// - Since v1.91.2 (Sept 2024) we included io.ConfigDebugHighlightIdConflicts feature.\n//   One idiom which was previously valid which will now emit a warning is when using multiple overlayed ButtonBehavior()\n//   with same ID and different MouseButton (see #8030). You can fix it by:\n//       (1) switching to use a single ButtonBehavior() with multiple _MouseButton flags.\n//    or (2) surrounding those calls with PushItemFlag(ImGuiItemFlags_AllowDuplicateId, true); ... PopItemFlag()\nbool ImGui::ButtonBehavior(const ImRect& bb, ImGuiID id, bool* out_hovered, bool* out_held, ImGuiButtonFlags flags)\n{\n    ImGuiContext& g = *GImGui;\n    ImGuiWindow* window = GetCurrentWindow();\n\n    // Default behavior inherited from item flags\n    // Note that _both_ ButtonFlags and ItemFlags are valid sources, so copy one into the item_flags and only check that.\n    ImGuiItemFlags item_flags = (g.LastItemData.ID == id ? g.LastItemData.ItemFlags : g.CurrentItemFlags);\n    if (flags & ImGuiButtonFlags_AllowOverlap)\n        item_flags |= ImGuiItemFlags_AllowOverlap;\n\n    // Default only reacts to left mouse button\n    if ((flags & ImGuiButtonFlags_MouseButtonMask_) == 0)\n        flags |= ImGuiButtonFlags_MouseButtonLeft;\n\n    // Default behavior requires click + release inside bounding box\n    if ((flags & ImGuiButtonFlags_PressedOnMask_) == 0)\n        flags |= (item_flags & ImGuiItemFlags_ButtonRepeat) ? ImGuiButtonFlags_PressedOnClick : ImGuiButtonFlags_PressedOnDefault_;\n\n    ImGuiWindow* backup_hovered_window = g.HoveredWindow;\n    const bool flatten_hovered_children = (flags & ImGuiButtonFlags_FlattenChildren) && g.HoveredWindow && g.HoveredWindow->RootWindow == window;\n    if (flatten_hovered_children)\n        g.HoveredWindow = window;\n\n#ifdef IMGUI_ENABLE_TEST_ENGINE\n    // Alternate registration spot, for when caller didn't use ItemAdd()\n    if (g.LastItemData.ID != id)\n        IMGUI_TEST_ENGINE_ITEM_ADD(id, bb, NULL);\n#endif\n\n    bool pressed = false;\n    bool hovered = ItemHoverable(bb, id, item_flags);\n\n    // Special mode for Drag and Drop where holding button pressed for a long time while dragging another item triggers the button\n    if (g.DragDropActive && (flags & ImGuiButtonFlags_PressedOnDragDropHold) && !(g.DragDropSourceFlags & ImGuiDragDropFlags_SourceNoHoldToOpenOthers))\n        if (IsItemHovered(ImGuiHoveredFlags_AllowWhenBlockedByActiveItem))\n        {\n            hovered = true;\n            SetHoveredID(id);\n            if (g.HoveredIdTimer - g.IO.DeltaTime <= DRAGDROP_HOLD_TO_OPEN_TIMER && g.HoveredIdTimer >= DRAGDROP_HOLD_TO_OPEN_TIMER)\n            {\n                pressed = true;\n                g.DragDropHoldJustPressedId = id;\n                FocusWindow(window);\n            }\n        }\n\n    if (flatten_hovered_children)\n        g.HoveredWindow = backup_hovered_window;\n\n    // Mouse handling\n    const ImGuiID test_owner_id = (flags & ImGuiButtonFlags_NoTestKeyOwner) ? ImGuiKeyOwner_Any : id;\n    if (hovered)\n    {\n        IM_ASSERT(id != 0); // Lazily check inside rare path.\n\n        // Poll mouse buttons\n        // - 'mouse_button_clicked' is generally carried into ActiveIdMouseButton when setting ActiveId.\n        // - Technically we only need some values in one code path, but since this is gated by hovered test this is fine.\n        int mouse_button_clicked = -1;\n        int mouse_button_released = -1;\n        for (int button = 0; button < 3; button++)\n            if (flags & (ImGuiButtonFlags_MouseButtonLeft << button)) // Handle ImGuiButtonFlags_MouseButtonRight and ImGuiButtonFlags_MouseButtonMiddle here.\n            {\n                if (IsMouseClicked(button, ImGuiInputFlags_None, test_owner_id) && mouse_button_clicked == -1) { mouse_button_clicked = button; }\n                if (IsMouseReleased(button, test_owner_id) && mouse_button_released == -1) { mouse_button_released = button; }\n            }\n\n        // Process initial action\n        const bool mods_ok = !(flags & ImGuiButtonFlags_NoKeyModsAllowed) || (!g.IO.KeyCtrl && !g.IO.KeyShift && !g.IO.KeyAlt);\n        if (mods_ok)\n        {\n            if (mouse_button_clicked != -1 && g.ActiveId != id)\n            {\n                if (!(flags & ImGuiButtonFlags_NoSetKeyOwner))\n                    SetKeyOwner(MouseButtonToKey(mouse_button_clicked), id);\n                if (flags & (ImGuiButtonFlags_PressedOnClickRelease | ImGuiButtonFlags_PressedOnClickReleaseAnywhere))\n                {\n                    SetActiveID(id, window);\n                    g.ActiveIdMouseButton = mouse_button_clicked;\n                    if (!(flags & ImGuiButtonFlags_NoNavFocus))\n                    {\n                        SetFocusID(id, window);\n                        FocusWindow(window);\n                    }\n                    else\n                    {\n                        FocusWindow(window, ImGuiFocusRequestFlags_RestoreFocusedChild); // Still need to focus and bring to front, but try to avoid losing NavId when navigating a child\n                    }\n                }\n                if ((flags & ImGuiButtonFlags_PressedOnClick) || ((flags & ImGuiButtonFlags_PressedOnDoubleClick) && g.IO.MouseClickedCount[mouse_button_clicked] == 2))\n                {\n                    pressed = true;\n                    if (flags & ImGuiButtonFlags_NoHoldingActiveId)\n                        ClearActiveID();\n                    else\n                        SetActiveID(id, window); // Hold on ID\n                    g.ActiveIdMouseButton = mouse_button_clicked;\n                    if (!(flags & ImGuiButtonFlags_NoNavFocus))\n                    {\n                        SetFocusID(id, window);\n                        FocusWindow(window);\n                    }\n                    else\n                    {\n                        FocusWindow(window, ImGuiFocusRequestFlags_RestoreFocusedChild); // Still need to focus and bring to front, but try to avoid losing NavId when navigating a child\n                    }\n                }\n            }\n            if (flags & ImGuiButtonFlags_PressedOnRelease)\n            {\n                if (mouse_button_released != -1)\n                {\n                    const bool has_repeated_at_least_once = (item_flags & ImGuiItemFlags_ButtonRepeat) && g.IO.MouseDownDurationPrev[mouse_button_released] >= g.IO.KeyRepeatDelay; // Repeat mode trumps on release behavior\n                    if (!has_repeated_at_least_once)\n                        pressed = true;\n                    if (!(flags & ImGuiButtonFlags_NoNavFocus))\n                        SetFocusID(id, window); // FIXME: Lack of FocusWindow() call here is inconsistent with other paths. Research why.\n                    ClearActiveID();\n                }\n            }\n\n            // 'Repeat' mode acts when held regardless of _PressedOn flags (see table above).\n            // Relies on repeat logic of IsMouseClicked() but we may as well do it ourselves if we end up exposing finer RepeatDelay/RepeatRate settings.\n            if (g.ActiveId == id && (item_flags & ImGuiItemFlags_ButtonRepeat))\n                if (g.IO.MouseDownDuration[g.ActiveIdMouseButton] > 0.0f && IsMouseClicked(g.ActiveIdMouseButton, ImGuiInputFlags_Repeat, test_owner_id))\n                    pressed = true;\n        }\n\n        if (pressed && g.IO.ConfigNavCursorVisibleAuto)\n            g.NavCursorVisible = false;\n    }\n\n    // Keyboard/Gamepad navigation handling\n    // We report navigated and navigation-activated items as hovered but we don't set g.HoveredId to not interfere with mouse.\n    if (g.NavId == id && g.NavCursorVisible && g.NavHighlightItemUnderNav)\n        if (!(flags & ImGuiButtonFlags_NoHoveredOnFocus))\n            hovered = true;\n    if (g.NavActivateDownId == id)\n    {\n        bool nav_activated_by_code = (g.NavActivateId == id);\n        bool nav_activated_by_inputs = (g.NavActivatePressedId == id);\n        if (!nav_activated_by_inputs && (item_flags & ImGuiItemFlags_ButtonRepeat))\n        {\n            // Avoid pressing multiple keys from triggering excessive amount of repeat events\n            const ImGuiKeyData* key1 = GetKeyData(ImGuiKey_Space);\n            const ImGuiKeyData* key2 = GetKeyData(ImGuiKey_Enter);\n            const ImGuiKeyData* key3 = GetKeyData(ImGuiKey_NavGamepadActivate);\n            const float t1 = ImMax(ImMax(key1->DownDuration, key2->DownDuration), key3->DownDuration);\n            nav_activated_by_inputs = CalcTypematicRepeatAmount(t1 - g.IO.DeltaTime, t1, g.IO.KeyRepeatDelay, g.IO.KeyRepeatRate) > 0;\n        }\n        if (nav_activated_by_code || nav_activated_by_inputs)\n        {\n            // Set active id so it can be queried by user via IsItemActive(), equivalent of holding the mouse button.\n            pressed = true;\n            SetActiveID(id, window);\n            g.ActiveIdSource = g.NavInputSource;\n            if (!(flags & ImGuiButtonFlags_NoNavFocus) && !(g.NavActivateFlags & ImGuiActivateFlags_FromShortcut))\n                SetFocusID(id, window);\n            if (g.NavActivateFlags & ImGuiActivateFlags_FromShortcut)\n                g.ActiveIdFromShortcut = true;\n        }\n    }\n\n    // Process while held\n    bool held = false;\n    if (g.ActiveId == id)\n    {\n        if (g.ActiveIdSource == ImGuiInputSource_Mouse)\n        {\n            if (g.ActiveIdIsJustActivated)\n                g.ActiveIdClickOffset = g.IO.MousePos - bb.Min;\n\n            const int mouse_button = g.ActiveIdMouseButton;\n            if (mouse_button == -1)\n            {\n                // Fallback for the rare situation were g.ActiveId was set programmatically or from another widget (e.g. #6304).\n                ClearActiveID();\n            }\n            else if (IsMouseDown(mouse_button, test_owner_id))\n            {\n                held = true;\n            }\n            else\n            {\n                bool release_in = hovered && (flags & ImGuiButtonFlags_PressedOnClickRelease) != 0;\n                bool release_anywhere = (flags & ImGuiButtonFlags_PressedOnClickReleaseAnywhere) != 0;\n                if ((release_in || release_anywhere) && !g.DragDropActive)\n                {\n                    // Report as pressed when releasing the mouse (this is the most common path)\n                    bool is_double_click_release = (flags & ImGuiButtonFlags_PressedOnDoubleClick) && g.IO.MouseReleased[mouse_button] && g.IO.MouseClickedLastCount[mouse_button] == 2;\n                    bool is_repeating_already = (item_flags & ImGuiItemFlags_ButtonRepeat) && g.IO.MouseDownDurationPrev[mouse_button] >= g.IO.KeyRepeatDelay; // Repeat mode trumps <on release>\n                    bool is_button_avail_or_owned = TestKeyOwner(MouseButtonToKey(mouse_button), test_owner_id);\n                    if (!is_double_click_release && !is_repeating_already && is_button_avail_or_owned)\n                        pressed = true;\n                }\n                ClearActiveID();\n            }\n            if (!(flags & ImGuiButtonFlags_NoNavFocus) && g.IO.ConfigNavCursorVisibleAuto)\n                g.NavCursorVisible = false;\n        }\n        else if (g.ActiveIdSource == ImGuiInputSource_Keyboard || g.ActiveIdSource == ImGuiInputSource_Gamepad)\n        {\n            // When activated using Nav, we hold on the ActiveID until activation button is released\n            if (g.NavActivateDownId == id)\n                held = true; // hovered == true not true as we are already likely hovered on direct activation.\n            else\n                ClearActiveID();\n        }\n        if (pressed)\n            g.ActiveIdHasBeenPressedBefore = true;\n    }\n\n    // Activation highlight (this may be a remote activation)\n    if (g.NavHighlightActivatedId == id)\n        hovered = true;\n\n    if (out_hovered) *out_hovered = hovered;\n    if (out_held) *out_held = held;\n\n    return pressed;\n}\n\nbool ImGui::ButtonEx(const char* label, const ImVec2& size_arg, ImGuiButtonFlags flags)\n{\n    ImGuiWindow* window = GetCurrentWindow();\n    if (window->SkipItems)\n        return false;\n\n    ImGuiContext& g = *GImGui;\n    const ImGuiStyle& style = g.Style;\n    const ImGuiID id = window->GetID(label);\n    const ImVec2 label_size = CalcTextSize(label, NULL, true);\n\n    ImVec2 pos = window->DC.CursorPos;\n    if ((flags & ImGuiButtonFlags_AlignTextBaseLine) && style.FramePadding.y < window->DC.CurrLineTextBaseOffset) // Try to vertically align buttons that are smaller/have no padding so that text baseline matches (bit hacky, since it shouldn't be a flag)\n        pos.y += window->DC.CurrLineTextBaseOffset - style.FramePadding.y;\n    ImVec2 size = CalcItemSize(size_arg, label_size.x + style.FramePadding.x * 2.0f, label_size.y + style.FramePadding.y * 2.0f);\n\n    const ImRect bb(pos, pos + size);\n    ItemSize(size, style.FramePadding.y);\n    if (!ItemAdd(bb, id))\n        return false;\n\n    bool hovered, held;\n    bool pressed = ButtonBehavior(bb, id, &hovered, &held, flags);\n\n    // Render\n    const ImU32 col = GetColorU32((held && hovered) ? ImGuiCol_ButtonActive : hovered ? ImGuiCol_ButtonHovered : ImGuiCol_Button);\n    RenderNavCursor(bb, id);\n    RenderFrame(bb.Min, bb.Max, col, true, style.FrameRounding);\n\n    if (g.LogEnabled)\n        LogSetNextTextDecoration(\"[\", \"]\");\n    RenderTextClipped(bb.Min + style.FramePadding, bb.Max - style.FramePadding, label, NULL, &label_size, style.ButtonTextAlign, &bb);\n\n    // Automatically close popups\n    //if (pressed && !(flags & ImGuiButtonFlags_DontClosePopups) && (window->Flags & ImGuiWindowFlags_Popup))\n    //    CloseCurrentPopup();\n\n    IMGUI_TEST_ENGINE_ITEM_INFO(id, label, g.LastItemData.StatusFlags);\n    return pressed;\n}\n\nbool ImGui::Button(const char* label, const ImVec2& size_arg)\n{\n    return ButtonEx(label, size_arg, ImGuiButtonFlags_None);\n}\n\n// Small buttons fits within text without additional vertical spacing.\nbool ImGui::SmallButton(const char* label)\n{\n    ImGuiContext& g = *GImGui;\n    float backup_padding_y = g.Style.FramePadding.y;\n    g.Style.FramePadding.y = 0.0f;\n    bool pressed = ButtonEx(label, ImVec2(0, 0), ImGuiButtonFlags_AlignTextBaseLine);\n    g.Style.FramePadding.y = backup_padding_y;\n    return pressed;\n}\n\n// Tip: use ImGui::PushID()/PopID() to push indices or pointers in the ID stack.\n// Then you can keep 'str_id' empty or the same for all your buttons (instead of creating a string based on a non-string id)\nbool ImGui::InvisibleButton(const char* str_id, const ImVec2& size_arg, ImGuiButtonFlags flags)\n{\n    ImGuiContext& g = *GImGui;\n    ImGuiWindow* window = GetCurrentWindow();\n    if (window->SkipItems)\n        return false;\n\n    // Cannot use zero-size for InvisibleButton(). Unlike Button() there is not way to fallback using the label size.\n    IM_ASSERT(size_arg.x != 0.0f && size_arg.y != 0.0f);\n\n    const ImGuiID id = window->GetID(str_id);\n    ImVec2 size = CalcItemSize(size_arg, 0.0f, 0.0f);\n    const ImRect bb(window->DC.CursorPos, window->DC.CursorPos + size);\n    ItemSize(size);\n    if (!ItemAdd(bb, id, NULL, (flags & ImGuiButtonFlags_EnableNav) ? ImGuiItemFlags_None : ImGuiItemFlags_NoNav))\n        return false;\n\n    bool hovered, held;\n    bool pressed = ButtonBehavior(bb, id, &hovered, &held, flags);\n    RenderNavCursor(bb, id);\n\n    IMGUI_TEST_ENGINE_ITEM_INFO(id, str_id, g.LastItemData.StatusFlags);\n    return pressed;\n}\n\nbool ImGui::ArrowButtonEx(const char* str_id, ImGuiDir dir, ImVec2 size, ImGuiButtonFlags flags)\n{\n    ImGuiContext& g = *GImGui;\n    ImGuiWindow* window = GetCurrentWindow();\n    if (window->SkipItems)\n        return false;\n\n    const ImGuiID id = window->GetID(str_id);\n    const ImRect bb(window->DC.CursorPos, window->DC.CursorPos + size);\n    const float default_size = GetFrameHeight();\n    ItemSize(size, (size.y >= default_size) ? g.Style.FramePadding.y : -1.0f);\n    if (!ItemAdd(bb, id))\n        return false;\n\n    bool hovered, held;\n    bool pressed = ButtonBehavior(bb, id, &hovered, &held, flags);\n\n    // Render\n    const ImU32 bg_col = GetColorU32((held && hovered) ? ImGuiCol_ButtonActive : hovered ? ImGuiCol_ButtonHovered : ImGuiCol_Button);\n    const ImU32 text_col = GetColorU32(ImGuiCol_Text);\n    RenderNavCursor(bb, id);\n    RenderFrame(bb.Min, bb.Max, bg_col, true, g.Style.FrameRounding);\n    RenderArrow(window->DrawList, bb.Min + ImVec2(ImMax(0.0f, (size.x - g.FontSize) * 0.5f), ImMax(0.0f, (size.y - g.FontSize) * 0.5f)), text_col, dir);\n\n    IMGUI_TEST_ENGINE_ITEM_INFO(id, str_id, g.LastItemData.StatusFlags);\n    return pressed;\n}\n\nbool ImGui::ArrowButton(const char* str_id, ImGuiDir dir)\n{\n    float sz = GetFrameHeight();\n    return ArrowButtonEx(str_id, dir, ImVec2(sz, sz), ImGuiButtonFlags_None);\n}\n\n// Button to close a window\nbool ImGui::CloseButton(ImGuiID id, const ImVec2& pos)\n{\n    ImGuiContext& g = *GImGui;\n    ImGuiWindow* window = g.CurrentWindow;\n\n    // Tweak 1: Shrink hit-testing area if button covers an abnormally large proportion of the visible region. That's in order to facilitate moving the window away. (#3825)\n    // This may better be applied as a general hit-rect reduction mechanism for all widgets to ensure the area to move window is always accessible?\n    const ImRect bb(pos, pos + ImVec2(g.FontSize, g.FontSize));\n    ImRect bb_interact = bb;\n    const float area_to_visible_ratio = window->OuterRectClipped.GetArea() / bb.GetArea();\n    if (area_to_visible_ratio < 1.5f)\n        bb_interact.Expand(ImTrunc(bb_interact.GetSize() * -0.25f));\n\n    // Tweak 2: We intentionally allow interaction when clipped so that a mechanical Alt,Right,Activate sequence can always close a window.\n    // (this isn't the common behavior of buttons, but it doesn't affect the user because navigation tends to keep items visible in scrolling layer).\n    bool is_clipped = !ItemAdd(bb_interact, id);\n\n    bool hovered, held;\n    bool pressed = ButtonBehavior(bb_interact, id, &hovered, &held);\n    if (is_clipped)\n        return pressed;\n\n    // Render\n    ImU32 bg_col = GetColorU32(held ? ImGuiCol_ButtonActive : ImGuiCol_ButtonHovered);\n    if (hovered)\n        window->DrawList->AddRectFilled(bb.Min, bb.Max, bg_col);\n    RenderNavCursor(bb, id, ImGuiNavRenderCursorFlags_Compact);\n    ImU32 cross_col = GetColorU32(ImGuiCol_Text);\n    ImVec2 cross_center = bb.GetCenter() - ImVec2(0.5f, 0.5f);\n    float cross_extent = g.FontSize * 0.5f * 0.7071f - 1.0f;\n    window->DrawList->AddLine(cross_center + ImVec2(+cross_extent, +cross_extent), cross_center + ImVec2(-cross_extent, -cross_extent), cross_col, 1.0f);\n    window->DrawList->AddLine(cross_center + ImVec2(+cross_extent, -cross_extent), cross_center + ImVec2(-cross_extent, +cross_extent), cross_col, 1.0f);\n\n    return pressed;\n}\n\nbool ImGui::CollapseButton(ImGuiID id, const ImVec2& pos)\n{\n    ImGuiContext& g = *GImGui;\n    ImGuiWindow* window = g.CurrentWindow;\n\n    ImRect bb(pos, pos + ImVec2(g.FontSize, g.FontSize));\n    bool is_clipped = !ItemAdd(bb, id);\n    bool hovered, held;\n    bool pressed = ButtonBehavior(bb, id, &hovered, &held, ImGuiButtonFlags_None);\n    if (is_clipped)\n        return pressed;\n\n    // Render\n    ImU32 bg_col = GetColorU32((held && hovered) ? ImGuiCol_ButtonActive : hovered ? ImGuiCol_ButtonHovered : ImGuiCol_Button);\n    ImU32 text_col = GetColorU32(ImGuiCol_Text);\n    if (hovered || held)\n        window->DrawList->AddRectFilled(bb.Min, bb.Max, bg_col);\n    RenderNavCursor(bb, id, ImGuiNavRenderCursorFlags_Compact);\n    RenderArrow(window->DrawList, bb.Min, text_col, window->Collapsed ? ImGuiDir_Right : ImGuiDir_Down, 1.0f);\n\n    // Switch to moving the window after mouse is moved beyond the initial drag threshold\n    if (IsItemActive() && IsMouseDragging(0))\n        StartMouseMovingWindow(window);\n\n    return pressed;\n}\n\nImGuiID ImGui::GetWindowScrollbarID(ImGuiWindow* window, ImGuiAxis axis)\n{\n    return window->GetID(axis == ImGuiAxis_X ? \"#SCROLLX\" : \"#SCROLLY\");\n}\n\n// Return scrollbar rectangle, must only be called for corresponding axis if window->ScrollbarX/Y is set.\nImRect ImGui::GetWindowScrollbarRect(ImGuiWindow* window, ImGuiAxis axis)\n{\n    ImGuiContext& g = *GImGui;\n    const ImRect outer_rect = window->Rect();\n    const ImRect inner_rect = window->InnerRect;\n    const float scrollbar_size = window->ScrollbarSizes[axis ^ 1]; // (ScrollbarSizes.x = width of Y scrollbar; ScrollbarSizes.y = height of X scrollbar)\n    IM_ASSERT(scrollbar_size >= 0.0f);\n    const float border_size = IM_ROUND(window->WindowBorderSize * 0.5f);\n    const float border_top = (window->Flags & ImGuiWindowFlags_MenuBar) ? IM_ROUND(g.Style.FrameBorderSize * 0.5f) : 0.0f;\n    if (axis == ImGuiAxis_X)\n        return ImRect(inner_rect.Min.x + border_size, ImMax(outer_rect.Min.y + border_size, outer_rect.Max.y - border_size - scrollbar_size), inner_rect.Max.x - border_size, outer_rect.Max.y - border_size);\n    else\n        return ImRect(ImMax(outer_rect.Min.x, outer_rect.Max.x - border_size - scrollbar_size), inner_rect.Min.y + border_top, outer_rect.Max.x - border_size, inner_rect.Max.y - border_size);\n}\n\nvoid ImGui::Scrollbar(ImGuiAxis axis)\n{\n    ImGuiContext& g = *GImGui;\n    ImGuiWindow* window = g.CurrentWindow;\n    const ImGuiID id = GetWindowScrollbarID(window, axis);\n\n    // Calculate scrollbar bounding box\n    ImRect bb = GetWindowScrollbarRect(window, axis);\n    ImDrawFlags rounding_corners = ImDrawFlags_RoundCornersNone;\n    if (axis == ImGuiAxis_X)\n    {\n        rounding_corners |= ImDrawFlags_RoundCornersBottomLeft;\n        if (!window->ScrollbarY)\n            rounding_corners |= ImDrawFlags_RoundCornersBottomRight;\n    }\n    else\n    {\n        if ((window->Flags & ImGuiWindowFlags_NoTitleBar) && !(window->Flags & ImGuiWindowFlags_MenuBar))\n            rounding_corners |= ImDrawFlags_RoundCornersTopRight;\n        if (!window->ScrollbarX)\n            rounding_corners |= ImDrawFlags_RoundCornersBottomRight;\n    }\n    float size_visible = window->InnerRect.Max[axis] - window->InnerRect.Min[axis];\n    float size_contents = window->ContentSize[axis] + window->WindowPadding[axis] * 2.0f;\n    ImS64 scroll = (ImS64)window->Scroll[axis];\n    ScrollbarEx(bb, id, axis, &scroll, (ImS64)size_visible, (ImS64)size_contents, rounding_corners);\n    window->Scroll[axis] = (float)scroll;\n}\n\n// Vertical/Horizontal scrollbar\n// The entire piece of code below is rather confusing because:\n// - We handle absolute seeking (when first clicking outside the grab) and relative manipulation (afterward or when clicking inside the grab)\n// - We store values as normalized ratio and in a form that allows the window content to change while we are holding on a scrollbar\n// - We handle both horizontal and vertical scrollbars, which makes the terminology not ideal.\n// Still, the code should probably be made simpler..\nbool ImGui::ScrollbarEx(const ImRect& bb_frame, ImGuiID id, ImGuiAxis axis, ImS64* p_scroll_v, ImS64 size_visible_v, ImS64 size_contents_v, ImDrawFlags draw_rounding_flags)\n{\n    ImGuiContext& g = *GImGui;\n    ImGuiWindow* window = g.CurrentWindow;\n    if (window->SkipItems)\n        return false;\n\n    const float bb_frame_width = bb_frame.GetWidth();\n    const float bb_frame_height = bb_frame.GetHeight();\n    if (bb_frame_width <= 0.0f || bb_frame_height <= 0.0f)\n        return false;\n\n    // When we are too small, start hiding and disabling the grab (this reduce visual noise on very small window and facilitate using the window resize grab)\n    float alpha = 1.0f;\n    if ((axis == ImGuiAxis_Y) && bb_frame_height < bb_frame_width)\n        alpha = ImSaturate(bb_frame_height / ImMax(bb_frame_width * 2.0f, 1.0f));\n    if (alpha <= 0.0f)\n        return false;\n\n    const ImGuiStyle& style = g.Style;\n    const bool allow_interaction = (alpha >= 1.0f);\n\n    ImRect bb = bb_frame;\n    bb.Expand(ImVec2(-ImClamp(IM_TRUNC((bb_frame_width - 2.0f) * 0.5f), 0.0f, 3.0f), -ImClamp(IM_TRUNC((bb_frame_height - 2.0f) * 0.5f), 0.0f, 3.0f)));\n\n    // V denote the main, longer axis of the scrollbar (= height for a vertical scrollbar)\n    const float scrollbar_size_v = (axis == ImGuiAxis_X) ? bb.GetWidth() : bb.GetHeight();\n\n    // Calculate the height of our grabbable box. It generally represent the amount visible (vs the total scrollable amount)\n    // But we maintain a minimum size in pixel to allow for the user to still aim inside.\n    IM_ASSERT(ImMax(size_contents_v, size_visible_v) > 0.0f); // Adding this assert to check if the ImMax(XXX,1.0f) is still needed. PLEASE CONTACT ME if this triggers.\n    const ImS64 win_size_v = ImMax(ImMax(size_contents_v, size_visible_v), (ImS64)1);\n    const float grab_h_minsize = ImMin(bb.GetSize()[axis], style.GrabMinSize);\n    const float grab_h_pixels = ImClamp(scrollbar_size_v * ((float)size_visible_v / (float)win_size_v), grab_h_minsize, scrollbar_size_v);\n    const float grab_h_norm = grab_h_pixels / scrollbar_size_v;\n\n    // Handle input right away. None of the code of Begin() is relying on scrolling position before calling Scrollbar().\n    bool held = false;\n    bool hovered = false;\n    ItemAdd(bb_frame, id, NULL, ImGuiItemFlags_NoNav);\n    ButtonBehavior(bb, id, &hovered, &held, ImGuiButtonFlags_NoNavFocus);\n\n    const ImS64 scroll_max = ImMax((ImS64)1, size_contents_v - size_visible_v);\n    float scroll_ratio = ImSaturate((float)*p_scroll_v / (float)scroll_max);\n    float grab_v_norm = scroll_ratio * (scrollbar_size_v - grab_h_pixels) / scrollbar_size_v; // Grab position in normalized space\n    if (held && allow_interaction && grab_h_norm < 1.0f)\n    {\n        const float scrollbar_pos_v = bb.Min[axis];\n        const float mouse_pos_v = g.IO.MousePos[axis];\n\n        // Click position in scrollbar normalized space (0.0f->1.0f)\n        const float clicked_v_norm = ImSaturate((mouse_pos_v - scrollbar_pos_v) / scrollbar_size_v);\n\n        const int held_dir = (clicked_v_norm < grab_v_norm) ? -1 : (clicked_v_norm > grab_v_norm + grab_h_norm) ? +1 : 0;\n        if (g.ActiveIdIsJustActivated)\n        {\n            // On initial click when held_dir == 0 (clicked over grab): calculate the distance between mouse and the center of the grab\n            const bool scroll_to_clicked_location = (g.IO.ConfigScrollbarScrollByPage == false || g.IO.KeyShift || held_dir == 0);\n            g.ScrollbarSeekMode = scroll_to_clicked_location ? 0 : (short)held_dir;\n            g.ScrollbarClickDeltaToGrabCenter = (held_dir == 0 && !g.IO.KeyShift) ? clicked_v_norm - grab_v_norm - grab_h_norm * 0.5f : 0.0f;\n        }\n\n        // Apply scroll (p_scroll_v will generally point on one member of window->Scroll)\n        // It is ok to modify Scroll here because we are being called in Begin() after the calculation of ContentSize and before setting up our starting position\n        if (g.ScrollbarSeekMode == 0)\n        {\n            // Absolute seeking\n            const float scroll_v_norm = ImSaturate((clicked_v_norm - g.ScrollbarClickDeltaToGrabCenter - grab_h_norm * 0.5f) / (1.0f - grab_h_norm));\n            *p_scroll_v = (ImS64)(scroll_v_norm * scroll_max);\n        }\n        else\n        {\n            // Page by page\n            if (IsMouseClicked(ImGuiMouseButton_Left, ImGuiInputFlags_Repeat) && held_dir == g.ScrollbarSeekMode)\n            {\n                float page_dir = (g.ScrollbarSeekMode > 0.0f) ? +1.0f : -1.0f;\n                *p_scroll_v = ImClamp(*p_scroll_v + (ImS64)(page_dir * size_visible_v), (ImS64)0, scroll_max);\n            }\n        }\n\n        // Update values for rendering\n        scroll_ratio = ImSaturate((float)*p_scroll_v / (float)scroll_max);\n        grab_v_norm = scroll_ratio * (scrollbar_size_v - grab_h_pixels) / scrollbar_size_v;\n\n        // Update distance to grab now that we have seek'ed and saturated\n        //if (seek_absolute)\n        //    g.ScrollbarClickDeltaToGrabCenter = clicked_v_norm - grab_v_norm - grab_h_norm * 0.5f;\n    }\n\n    // Render\n    const ImU32 bg_col = GetColorU32(ImGuiCol_ScrollbarBg);\n    const ImU32 grab_col = GetColorU32(held ? ImGuiCol_ScrollbarGrabActive : hovered ? ImGuiCol_ScrollbarGrabHovered : ImGuiCol_ScrollbarGrab, alpha);\n    window->DrawList->AddRectFilled(bb_frame.Min, bb_frame.Max, bg_col, window->WindowRounding, draw_rounding_flags);\n    ImRect grab_rect;\n    if (axis == ImGuiAxis_X)\n        grab_rect = ImRect(ImLerp(bb.Min.x, bb.Max.x, grab_v_norm), bb.Min.y, ImLerp(bb.Min.x, bb.Max.x, grab_v_norm) + grab_h_pixels, bb.Max.y);\n    else\n        grab_rect = ImRect(bb.Min.x, ImLerp(bb.Min.y, bb.Max.y, grab_v_norm), bb.Max.x, ImLerp(bb.Min.y, bb.Max.y, grab_v_norm) + grab_h_pixels);\n    window->DrawList->AddRectFilled(grab_rect.Min, grab_rect.Max, grab_col, style.ScrollbarRounding);\n\n    return held;\n}\n\n// - Read about ImTextureID here: https://github.com/ocornut/imgui/wiki/Image-Loading-and-Displaying-Examples\n// - 'uv0' and 'uv1' are texture coordinates. Read about them from the same link above.\nvoid ImGui::ImageWithBg(ImTextureID user_texture_id, const ImVec2& image_size, const ImVec2& uv0, const ImVec2& uv1, const ImVec4& bg_col, const ImVec4& tint_col)\n{\n    ImGuiContext& g = *GImGui;\n    ImGuiWindow* window = GetCurrentWindow();\n    if (window->SkipItems)\n        return;\n\n    const ImVec2 padding(g.Style.ImageBorderSize, g.Style.ImageBorderSize);\n    const ImRect bb(window->DC.CursorPos, window->DC.CursorPos + image_size + padding * 2.0f);\n    ItemSize(bb);\n    if (!ItemAdd(bb, 0))\n        return;\n\n    // Render\n    if (g.Style.ImageBorderSize > 0.0f)\n        window->DrawList->AddRect(bb.Min, bb.Max, GetColorU32(ImGuiCol_Border), 0.0f, ImDrawFlags_None, g.Style.ImageBorderSize);\n    if (bg_col.w > 0.0f)\n        window->DrawList->AddRectFilled(bb.Min + padding, bb.Max - padding, GetColorU32(bg_col));\n    window->DrawList->AddImage(user_texture_id, bb.Min + padding, bb.Max - padding, uv0, uv1, GetColorU32(tint_col));\n}\n\nvoid ImGui::Image(ImTextureID user_texture_id, const ImVec2& image_size, const ImVec2& uv0, const ImVec2& uv1)\n{\n    ImageWithBg(user_texture_id, image_size, uv0, uv1);\n}\n\n// 1.91.9 (February 2025) removed 'tint_col' and 'border_col' parameters, made border size not depend on color value. (#8131, #8238)\n#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS\nvoid ImGui::Image(ImTextureID user_texture_id, const ImVec2& image_size, const ImVec2& uv0, const ImVec2& uv1, const ImVec4& tint_col, const ImVec4& border_col)\n{\n    ImGuiContext& g = *GImGui;\n    PushStyleVar(ImGuiStyleVar_ImageBorderSize, (border_col.w > 0.0f) ? ImMax(1.0f, g.Style.ImageBorderSize) : 0.0f); // Preserve legacy behavior where border is always visible when border_col's Alpha is >0.0f\n    PushStyleColor(ImGuiCol_Border, border_col);\n    ImageWithBg(user_texture_id, image_size, uv0, uv1, ImVec4(0, 0, 0, 0), tint_col);\n    PopStyleColor();\n    PopStyleVar();\n}\n#endif\n\nbool ImGui::ImageButtonEx(ImGuiID id, ImTextureID user_texture_id, const ImVec2& image_size, const ImVec2& uv0, const ImVec2& uv1, const ImVec4& bg_col, const ImVec4& tint_col, ImGuiButtonFlags flags)\n{\n    ImGuiContext& g = *GImGui;\n    ImGuiWindow* window = GetCurrentWindow();\n    if (window->SkipItems)\n        return false;\n\n    const ImVec2 padding = g.Style.FramePadding;\n    const ImRect bb(window->DC.CursorPos, window->DC.CursorPos + image_size + padding * 2.0f);\n    ItemSize(bb);\n    if (!ItemAdd(bb, id))\n        return false;\n\n    bool hovered, held;\n    bool pressed = ButtonBehavior(bb, id, &hovered, &held, flags);\n\n    // Render\n    const ImU32 col = GetColorU32((held && hovered) ? ImGuiCol_ButtonActive : hovered ? ImGuiCol_ButtonHovered : ImGuiCol_Button);\n    RenderNavCursor(bb, id);\n    RenderFrame(bb.Min, bb.Max, col, true, ImClamp((float)ImMin(padding.x, padding.y), 0.0f, g.Style.FrameRounding));\n    if (bg_col.w > 0.0f)\n        window->DrawList->AddRectFilled(bb.Min + padding, bb.Max - padding, GetColorU32(bg_col));\n    window->DrawList->AddImage(user_texture_id, bb.Min + padding, bb.Max - padding, uv0, uv1, GetColorU32(tint_col));\n\n    return pressed;\n}\n\n// - ImageButton() adds style.FramePadding*2.0f to provided size. This is in order to facilitate fitting an image in a button.\n// - ImageButton() draws a background based on regular Button() color + optionally an inner background if specified. (#8165) // FIXME: Maybe that's not the best design?\nbool ImGui::ImageButton(const char* str_id, ImTextureID user_texture_id, const ImVec2& image_size, const ImVec2& uv0, const ImVec2& uv1, const ImVec4& bg_col, const ImVec4& tint_col)\n{\n    ImGuiContext& g = *GImGui;\n    ImGuiWindow* window = g.CurrentWindow;\n    if (window->SkipItems)\n        return false;\n\n    return ImageButtonEx(window->GetID(str_id), user_texture_id, image_size, uv0, uv1, bg_col, tint_col);\n}\n\n#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS\n// Legacy API obsoleted in 1.89. Two differences with new ImageButton()\n// - old ImageButton() used ImTextureID as item id (created issue with multiple buttons with same image, transient texture id values, opaque computation of ID)\n// - new ImageButton() requires an explicit 'const char* str_id'\n// - old ImageButton() had frame_padding' override argument.\n// - new ImageButton() always use style.FramePadding.\n/*\nbool ImGui::ImageButton(ImTextureID user_texture_id, const ImVec2& size, const ImVec2& uv0, const ImVec2& uv1, int frame_padding, const ImVec4& bg_col, const ImVec4& tint_col)\n{\n    // Default to using texture ID as ID. User can still push string/integer prefixes.\n    PushID((ImTextureID)(intptr_t)user_texture_id);\n    if (frame_padding >= 0)\n        PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2((float)frame_padding, (float)frame_padding));\n    bool ret = ImageButton(\"\", user_texture_id, size, uv0, uv1, bg_col, tint_col);\n    if (frame_padding >= 0)\n        PopStyleVar();\n    PopID();\n    return ret;\n}\n*/\n#endif // #ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS\n\nbool ImGui::Checkbox(const char* label, bool* v)\n{\n    ImGuiWindow* window = GetCurrentWindow();\n    if (window->SkipItems)\n        return false;\n\n    ImGuiContext& g = *GImGui;\n    const ImGuiStyle& style = g.Style;\n    const ImGuiID id = window->GetID(label);\n    const ImVec2 label_size = CalcTextSize(label, NULL, true);\n\n    const float square_sz = GetFrameHeight();\n    const ImVec2 pos = window->DC.CursorPos;\n    const ImRect total_bb(pos, pos + ImVec2(square_sz + (label_size.x > 0.0f ? style.ItemInnerSpacing.x + label_size.x : 0.0f), label_size.y + style.FramePadding.y * 2.0f));\n    ItemSize(total_bb, style.FramePadding.y);\n    const bool is_visible = ItemAdd(total_bb, id);\n    const bool is_multi_select = (g.LastItemData.ItemFlags & ImGuiItemFlags_IsMultiSelect) != 0;\n    if (!is_visible)\n        if (!is_multi_select || !g.BoxSelectState.UnclipMode || !g.BoxSelectState.UnclipRect.Overlaps(total_bb)) // Extra layer of \"no logic clip\" for box-select support\n        {\n            IMGUI_TEST_ENGINE_ITEM_INFO(id, label, g.LastItemData.StatusFlags | ImGuiItemStatusFlags_Checkable | (*v ? ImGuiItemStatusFlags_Checked : 0));\n            return false;\n        }\n\n    // Range-Selection/Multi-selection support (header)\n    bool checked = *v;\n    if (is_multi_select)\n        MultiSelectItemHeader(id, &checked, NULL);\n\n    bool hovered, held;\n    bool pressed = ButtonBehavior(total_bb, id, &hovered, &held);\n\n    // Range-Selection/Multi-selection support (footer)\n    if (is_multi_select)\n        MultiSelectItemFooter(id, &checked, &pressed);\n    else if (pressed)\n        checked = !checked;\n\n    if (*v != checked)\n    {\n        *v = checked;\n        pressed = true; // return value\n        MarkItemEdited(id);\n    }\n\n    const ImRect check_bb(pos, pos + ImVec2(square_sz, square_sz));\n    const bool mixed_value = (g.LastItemData.ItemFlags & ImGuiItemFlags_MixedValue) != 0;\n    if (is_visible)\n    {\n        RenderNavCursor(total_bb, id);\n        RenderFrame(check_bb.Min, check_bb.Max, GetColorU32((held && hovered) ? ImGuiCol_FrameBgActive : hovered ? ImGuiCol_FrameBgHovered : ImGuiCol_FrameBg), true, style.FrameRounding);\n        ImU32 check_col = GetColorU32(ImGuiCol_CheckMark);\n        if (mixed_value)\n        {\n            // Undocumented tristate/mixed/indeterminate checkbox (#2644)\n            // This may seem awkwardly designed because the aim is to make ImGuiItemFlags_MixedValue supported by all widgets (not just checkbox)\n            ImVec2 pad(ImMax(1.0f, IM_TRUNC(square_sz / 3.6f)), ImMax(1.0f, IM_TRUNC(square_sz / 3.6f)));\n            window->DrawList->AddRectFilled(check_bb.Min + pad, check_bb.Max - pad, check_col, style.FrameRounding);\n        }\n        else if (*v)\n        {\n            const float pad = ImMax(1.0f, IM_TRUNC(square_sz / 6.0f));\n            RenderCheckMark(window->DrawList, check_bb.Min + ImVec2(pad, pad), check_col, square_sz - pad * 2.0f);\n        }\n    }\n    const ImVec2 label_pos = ImVec2(check_bb.Max.x + style.ItemInnerSpacing.x, check_bb.Min.y + style.FramePadding.y);\n    if (g.LogEnabled)\n        LogRenderedText(&label_pos, mixed_value ? \"[~]\" : *v ? \"[x]\" : \"[ ]\");\n    if (is_visible && label_size.x > 0.0f)\n        RenderText(label_pos, label);\n\n    IMGUI_TEST_ENGINE_ITEM_INFO(id, label, g.LastItemData.StatusFlags | ImGuiItemStatusFlags_Checkable | (*v ? ImGuiItemStatusFlags_Checked : 0));\n    return pressed;\n}\n\ntemplate<typename T>\nbool ImGui::CheckboxFlagsT(const char* label, T* flags, T flags_value)\n{\n    bool all_on = (*flags & flags_value) == flags_value;\n    bool any_on = (*flags & flags_value) != 0;\n    bool pressed;\n    if (!all_on && any_on)\n    {\n        ImGuiContext& g = *GImGui;\n        g.NextItemData.ItemFlags |= ImGuiItemFlags_MixedValue;\n        pressed = Checkbox(label, &all_on);\n    }\n    else\n    {\n        pressed = Checkbox(label, &all_on);\n\n    }\n    if (pressed)\n    {\n        if (all_on)\n            *flags |= flags_value;\n        else\n            *flags &= ~flags_value;\n    }\n    return pressed;\n}\n\nbool ImGui::CheckboxFlags(const char* label, int* flags, int flags_value)\n{\n    return CheckboxFlagsT(label, flags, flags_value);\n}\n\nbool ImGui::CheckboxFlags(const char* label, unsigned int* flags, unsigned int flags_value)\n{\n    return CheckboxFlagsT(label, flags, flags_value);\n}\n\nbool ImGui::CheckboxFlags(const char* label, ImS64* flags, ImS64 flags_value)\n{\n    return CheckboxFlagsT(label, flags, flags_value);\n}\n\nbool ImGui::CheckboxFlags(const char* label, ImU64* flags, ImU64 flags_value)\n{\n    return CheckboxFlagsT(label, flags, flags_value);\n}\n\nbool ImGui::RadioButton(const char* label, bool active)\n{\n    ImGuiWindow* window = GetCurrentWindow();\n    if (window->SkipItems)\n        return false;\n\n    ImGuiContext& g = *GImGui;\n    const ImGuiStyle& style = g.Style;\n    const ImGuiID id = window->GetID(label);\n    const ImVec2 label_size = CalcTextSize(label, NULL, true);\n\n    const float square_sz = GetFrameHeight();\n    const ImVec2 pos = window->DC.CursorPos;\n    const ImRect check_bb(pos, pos + ImVec2(square_sz, square_sz));\n    const ImRect total_bb(pos, pos + ImVec2(square_sz + (label_size.x > 0.0f ? style.ItemInnerSpacing.x + label_size.x : 0.0f), label_size.y + style.FramePadding.y * 2.0f));\n    ItemSize(total_bb, style.FramePadding.y);\n    if (!ItemAdd(total_bb, id))\n        return false;\n\n    ImVec2 center = check_bb.GetCenter();\n    center.x = IM_ROUND(center.x);\n    center.y = IM_ROUND(center.y);\n    const float radius = (square_sz - 1.0f) * 0.5f;\n\n    bool hovered, held;\n    bool pressed = ButtonBehavior(total_bb, id, &hovered, &held);\n    if (pressed)\n        MarkItemEdited(id);\n\n    RenderNavCursor(total_bb, id);\n    const int num_segment = window->DrawList->_CalcCircleAutoSegmentCount(radius);\n    window->DrawList->AddCircleFilled(center, radius, GetColorU32((held && hovered) ? ImGuiCol_FrameBgActive : hovered ? ImGuiCol_FrameBgHovered : ImGuiCol_FrameBg), num_segment);\n    if (active)\n    {\n        const float pad = ImMax(1.0f, IM_TRUNC(square_sz / 6.0f));\n        window->DrawList->AddCircleFilled(center, radius - pad, GetColorU32(ImGuiCol_CheckMark));\n    }\n\n    if (style.FrameBorderSize > 0.0f)\n    {\n        window->DrawList->AddCircle(center + ImVec2(1, 1), radius, GetColorU32(ImGuiCol_BorderShadow), num_segment, style.FrameBorderSize);\n        window->DrawList->AddCircle(center, radius, GetColorU32(ImGuiCol_Border), num_segment, style.FrameBorderSize);\n    }\n\n    ImVec2 label_pos = ImVec2(check_bb.Max.x + style.ItemInnerSpacing.x, check_bb.Min.y + style.FramePadding.y);\n    if (g.LogEnabled)\n        LogRenderedText(&label_pos, active ? \"(x)\" : \"( )\");\n    if (label_size.x > 0.0f)\n        RenderText(label_pos, label);\n\n    IMGUI_TEST_ENGINE_ITEM_INFO(id, label, g.LastItemData.StatusFlags);\n    return pressed;\n}\n\n// FIXME: This would work nicely if it was a public template, e.g. 'template<T> RadioButton(const char* label, T* v, T v_button)', but I'm not sure how we would expose it..\nbool ImGui::RadioButton(const char* label, int* v, int v_button)\n{\n    const bool pressed = RadioButton(label, *v == v_button);\n    if (pressed)\n        *v = v_button;\n    return pressed;\n}\n\n// size_arg (for each axis) < 0.0f: align to end, 0.0f: auto, > 0.0f: specified size\nvoid ImGui::ProgressBar(float fraction, const ImVec2& size_arg, const char* overlay)\n{\n    ImGuiWindow* window = GetCurrentWindow();\n    if (window->SkipItems)\n        return;\n\n    ImGuiContext& g = *GImGui;\n    const ImGuiStyle& style = g.Style;\n\n    ImVec2 pos = window->DC.CursorPos;\n    ImVec2 size = CalcItemSize(size_arg, CalcItemWidth(), g.FontSize + style.FramePadding.y * 2.0f);\n    ImRect bb(pos, pos + size);\n    ItemSize(size, style.FramePadding.y);\n    if (!ItemAdd(bb, 0))\n        return;\n\n    // Fraction < 0.0f will display an indeterminate progress bar animation\n    // The value must be animated along with time, so e.g. passing '-1.0f * ImGui::GetTime()' as fraction works.\n    const bool is_indeterminate = (fraction < 0.0f);\n    if (!is_indeterminate)\n        fraction = ImSaturate(fraction);\n\n    // Out of courtesy we accept a NaN fraction without crashing\n    float fill_n0 = 0.0f;\n    float fill_n1 = (fraction == fraction) ? fraction : 0.0f;\n\n    if (is_indeterminate)\n    {\n        const float fill_width_n = 0.2f;\n        fill_n0 = ImFmod(-fraction, 1.0f) * (1.0f + fill_width_n) - fill_width_n;\n        fill_n1 = ImSaturate(fill_n0 + fill_width_n);\n        fill_n0 = ImSaturate(fill_n0);\n    }\n\n    // Render\n    RenderFrame(bb.Min, bb.Max, GetColorU32(ImGuiCol_FrameBg), true, style.FrameRounding);\n    bb.Expand(ImVec2(-style.FrameBorderSize, -style.FrameBorderSize));\n    RenderRectFilledRangeH(window->DrawList, bb, GetColorU32(ImGuiCol_PlotHistogram), fill_n0, fill_n1, style.FrameRounding);\n\n    // Default displaying the fraction as percentage string, but user can override it\n    // Don't display text for indeterminate bars by default\n    char overlay_buf[32];\n    if (!is_indeterminate || overlay != NULL)\n    {\n        if (!overlay)\n        {\n            ImFormatString(overlay_buf, IM_ARRAYSIZE(overlay_buf), \"%.0f%%\", fraction * 100 + 0.01f);\n            overlay = overlay_buf;\n        }\n\n        ImVec2 overlay_size = CalcTextSize(overlay, NULL);\n        if (overlay_size.x > 0.0f)\n        {\n            float text_x = is_indeterminate ? (bb.Min.x + bb.Max.x - overlay_size.x) * 0.5f : ImLerp(bb.Min.x, bb.Max.x, fill_n1) + style.ItemSpacing.x;\n            RenderTextClipped(ImVec2(ImClamp(text_x, bb.Min.x, bb.Max.x - overlay_size.x - style.ItemInnerSpacing.x), bb.Min.y), bb.Max, overlay, NULL, &overlay_size, ImVec2(0.0f, 0.5f), &bb);\n        }\n    }\n}\n\nvoid ImGui::Bullet()\n{\n    ImGuiWindow* window = GetCurrentWindow();\n    if (window->SkipItems)\n        return;\n\n    ImGuiContext& g = *GImGui;\n    const ImGuiStyle& style = g.Style;\n    const float line_height = ImMax(ImMin(window->DC.CurrLineSize.y, g.FontSize + style.FramePadding.y * 2), g.FontSize);\n    const ImRect bb(window->DC.CursorPos, window->DC.CursorPos + ImVec2(g.FontSize, line_height));\n    ItemSize(bb);\n    if (!ItemAdd(bb, 0))\n    {\n        SameLine(0, style.FramePadding.x * 2);\n        return;\n    }\n\n    // Render and stay on same line\n    ImU32 text_col = GetColorU32(ImGuiCol_Text);\n    RenderBullet(window->DrawList, bb.Min + ImVec2(style.FramePadding.x + g.FontSize * 0.5f, line_height * 0.5f), text_col);\n    SameLine(0, style.FramePadding.x * 2.0f);\n}\n\n// This is provided as a convenience for being an often requested feature.\n// FIXME-STYLE: we delayed adding as there is a larger plan to revamp the styling system.\n// Because of this we currently don't provide many styling options for this widget\n// (e.g. hovered/active colors are automatically inferred from a single color).\nbool ImGui::TextLink(const char* label)\n{\n    ImGuiWindow* window = GetCurrentWindow();\n    if (window->SkipItems)\n        return false;\n\n    ImGuiContext& g = *GImGui;\n    const ImGuiID id = window->GetID(label);\n    const char* label_end = FindRenderedTextEnd(label);\n\n    ImVec2 pos(window->DC.CursorPos.x, window->DC.CursorPos.y + window->DC.CurrLineTextBaseOffset);\n    ImVec2 size = CalcTextSize(label, label_end, true);\n    ImRect bb(pos, pos + size);\n    ItemSize(size, 0.0f);\n    if (!ItemAdd(bb, id))\n        return false;\n\n    bool hovered, held;\n    bool pressed = ButtonBehavior(bb, id, &hovered, &held);\n    RenderNavCursor(bb, id);\n\n    if (hovered)\n        SetMouseCursor(ImGuiMouseCursor_Hand);\n\n    ImVec4 text_colf = g.Style.Colors[ImGuiCol_TextLink];\n    ImVec4 line_colf = text_colf;\n    {\n        // FIXME-STYLE: Read comments above. This widget is NOT written in the same style as some earlier widgets,\n        // as we are currently experimenting/planning a different styling system.\n        float h, s, v;\n        ColorConvertRGBtoHSV(text_colf.x, text_colf.y, text_colf.z, h, s, v);\n        if (held || hovered)\n        {\n            v = ImSaturate(v + (held ? 0.4f : 0.3f));\n            h = ImFmod(h + 0.02f, 1.0f);\n        }\n        ColorConvertHSVtoRGB(h, s, v, text_colf.x, text_colf.y, text_colf.z);\n        v = ImSaturate(v - 0.20f);\n        ColorConvertHSVtoRGB(h, s, v, line_colf.x, line_colf.y, line_colf.z);\n    }\n\n    float line_y = bb.Max.y + ImFloor(g.Font->Descent * g.FontScale * 0.20f);\n    window->DrawList->AddLine(ImVec2(bb.Min.x, line_y), ImVec2(bb.Max.x, line_y), GetColorU32(line_colf)); // FIXME-TEXT: Underline mode.\n\n    PushStyleColor(ImGuiCol_Text, GetColorU32(text_colf));\n    RenderText(bb.Min, label, label_end);\n    PopStyleColor();\n\n    IMGUI_TEST_ENGINE_ITEM_INFO(id, label, g.LastItemData.StatusFlags);\n    return pressed;\n}\n\nvoid ImGui::TextLinkOpenURL(const char* label, const char* url)\n{\n    ImGuiContext& g = *GImGui;\n    if (url == NULL)\n        url = label;\n    if (TextLink(label))\n        if (g.PlatformIO.Platform_OpenInShellFn != NULL)\n            g.PlatformIO.Platform_OpenInShellFn(&g, url);\n    SetItemTooltip(LocalizeGetMsg(ImGuiLocKey_OpenLink_s), url); // It is more reassuring for user to _always_ display URL when we same as label\n    if (BeginPopupContextItem())\n    {\n        if (MenuItem(LocalizeGetMsg(ImGuiLocKey_CopyLink)))\n            SetClipboardText(url);\n        EndPopup();\n    }\n}\n\n//-------------------------------------------------------------------------\n// [SECTION] Widgets: Low-level Layout helpers\n//-------------------------------------------------------------------------\n// - Spacing()\n// - Dummy()\n// - NewLine()\n// - AlignTextToFramePadding()\n// - SeparatorEx() [Internal]\n// - Separator()\n// - SplitterBehavior() [Internal]\n// - ShrinkWidths() [Internal]\n//-------------------------------------------------------------------------\n\nvoid ImGui::Spacing()\n{\n    ImGuiWindow* window = GetCurrentWindow();\n    if (window->SkipItems)\n        return;\n    ItemSize(ImVec2(0, 0));\n}\n\nvoid ImGui::Dummy(const ImVec2& size)\n{\n    ImGuiWindow* window = GetCurrentWindow();\n    if (window->SkipItems)\n        return;\n\n    const ImRect bb(window->DC.CursorPos, window->DC.CursorPos + size);\n    ItemSize(size);\n    ItemAdd(bb, 0);\n}\n\nvoid ImGui::NewLine()\n{\n    ImGuiWindow* window = GetCurrentWindow();\n    if (window->SkipItems)\n        return;\n\n    ImGuiContext& g = *GImGui;\n    const ImGuiLayoutType backup_layout_type = window->DC.LayoutType;\n    window->DC.LayoutType = ImGuiLayoutType_Vertical;\n    window->DC.IsSameLine = false;\n    if (window->DC.CurrLineSize.y > 0.0f)     // In the event that we are on a line with items that is smaller that FontSize high, we will preserve its height.\n        ItemSize(ImVec2(0, 0));\n    else\n        ItemSize(ImVec2(0.0f, g.FontSize));\n    window->DC.LayoutType = backup_layout_type;\n}\n\nvoid ImGui::AlignTextToFramePadding()\n{\n    ImGuiWindow* window = GetCurrentWindow();\n    if (window->SkipItems)\n        return;\n\n    ImGuiContext& g = *GImGui;\n    window->DC.CurrLineSize.y = ImMax(window->DC.CurrLineSize.y, g.FontSize + g.Style.FramePadding.y * 2);\n    window->DC.CurrLineTextBaseOffset = ImMax(window->DC.CurrLineTextBaseOffset, g.Style.FramePadding.y);\n}\n\n// Horizontal/vertical separating line\n// FIXME: Surprisingly, this seemingly trivial widget is a victim of many different legacy/tricky layout issues.\n// Note how thickness == 1.0f is handled specifically as not moving CursorPos by 'thickness', but other values are.\nvoid ImGui::SeparatorEx(ImGuiSeparatorFlags flags, float thickness)\n{\n    ImGuiWindow* window = GetCurrentWindow();\n    if (window->SkipItems)\n        return;\n\n    ImGuiContext& g = *GImGui;\n    IM_ASSERT(ImIsPowerOfTwo(flags & (ImGuiSeparatorFlags_Horizontal | ImGuiSeparatorFlags_Vertical)));   // Check that only 1 option is selected\n    IM_ASSERT(thickness > 0.0f);\n\n    if (flags & ImGuiSeparatorFlags_Vertical)\n    {\n        // Vertical separator, for menu bars (use current line height).\n        float y1 = window->DC.CursorPos.y;\n        float y2 = window->DC.CursorPos.y + window->DC.CurrLineSize.y;\n        const ImRect bb(ImVec2(window->DC.CursorPos.x, y1), ImVec2(window->DC.CursorPos.x + thickness, y2));\n        ItemSize(ImVec2(thickness, 0.0f));\n        if (!ItemAdd(bb, 0))\n            return;\n\n        // Draw\n        window->DrawList->AddRectFilled(bb.Min, bb.Max, GetColorU32(ImGuiCol_Separator));\n        if (g.LogEnabled)\n            LogText(\" |\");\n    }\n    else if (flags & ImGuiSeparatorFlags_Horizontal)\n    {\n        // Horizontal Separator\n        float x1 = window->DC.CursorPos.x;\n        float x2 = window->WorkRect.Max.x;\n\n        // Preserve legacy behavior inside Columns()\n        // Before Tables API happened, we relied on Separator() to span all columns of a Columns() set.\n        // We currently don't need to provide the same feature for tables because tables naturally have border features.\n        ImGuiOldColumns* columns = (flags & ImGuiSeparatorFlags_SpanAllColumns) ? window->DC.CurrentColumns : NULL;\n        if (columns)\n        {\n            x1 = window->Pos.x + window->DC.Indent.x; // Used to be Pos.x before 2023/10/03\n            x2 = window->Pos.x + window->Size.x;\n            PushColumnsBackground();\n        }\n\n        // We don't provide our width to the layout so that it doesn't get feed back into AutoFit\n        // FIXME: This prevents ->CursorMaxPos based bounding box evaluation from working (e.g. TableEndCell)\n        const float thickness_for_layout = (thickness == 1.0f) ? 0.0f : thickness; // FIXME: See 1.70/1.71 Separator() change: makes legacy 1-px separator not affect layout yet. Should change.\n        const ImRect bb(ImVec2(x1, window->DC.CursorPos.y), ImVec2(x2, window->DC.CursorPos.y + thickness));\n        ItemSize(ImVec2(0.0f, thickness_for_layout));\n\n        if (ItemAdd(bb, 0))\n        {\n            // Draw\n            window->DrawList->AddRectFilled(bb.Min, bb.Max, GetColorU32(ImGuiCol_Separator));\n            if (g.LogEnabled)\n                LogRenderedText(&bb.Min, \"--------------------------------\\n\");\n\n        }\n        if (columns)\n        {\n            PopColumnsBackground();\n            columns->LineMinY = window->DC.CursorPos.y;\n        }\n    }\n}\n\nvoid ImGui::Separator()\n{\n    ImGuiContext& g = *GImGui;\n    ImGuiWindow* window = g.CurrentWindow;\n    if (window->SkipItems)\n        return;\n\n    // Those flags should eventually be configurable by the user\n    // FIXME: We cannot g.Style.SeparatorTextBorderSize for thickness as it relates to SeparatorText() which is a decorated separator, not defaulting to 1.0f.\n    ImGuiSeparatorFlags flags = (window->DC.LayoutType == ImGuiLayoutType_Horizontal) ? ImGuiSeparatorFlags_Vertical : ImGuiSeparatorFlags_Horizontal;\n\n    // Only applies to legacy Columns() api as they relied on Separator() a lot.\n    if (window->DC.CurrentColumns)\n        flags |= ImGuiSeparatorFlags_SpanAllColumns;\n\n    SeparatorEx(flags, 1.0f);\n}\n\nvoid ImGui::SeparatorTextEx(ImGuiID id, const char* label, const char* label_end, float extra_w)\n{\n    ImGuiContext& g = *GImGui;\n    ImGuiWindow* window = g.CurrentWindow;\n    ImGuiStyle& style = g.Style;\n\n    const ImVec2 label_size = CalcTextSize(label, label_end, false);\n    const ImVec2 pos = window->DC.CursorPos;\n    const ImVec2 padding = style.SeparatorTextPadding;\n\n    const float separator_thickness = style.SeparatorTextBorderSize;\n    const ImVec2 min_size(label_size.x + extra_w + padding.x * 2.0f, ImMax(label_size.y + padding.y * 2.0f, separator_thickness));\n    const ImRect bb(pos, ImVec2(window->WorkRect.Max.x, pos.y + min_size.y));\n    const float text_baseline_y = ImTrunc((bb.GetHeight() - label_size.y) * style.SeparatorTextAlign.y + 0.99999f); //ImMax(padding.y, ImFloor((style.SeparatorTextSize - label_size.y) * 0.5f));\n    ItemSize(min_size, text_baseline_y);\n    if (!ItemAdd(bb, id))\n        return;\n\n    const float sep1_x1 = pos.x;\n    const float sep2_x2 = bb.Max.x;\n    const float seps_y = ImTrunc((bb.Min.y + bb.Max.y) * 0.5f + 0.99999f);\n\n    const float label_avail_w = ImMax(0.0f, sep2_x2 - sep1_x1 - padding.x * 2.0f);\n    const ImVec2 label_pos(pos.x + padding.x + ImMax(0.0f, (label_avail_w - label_size.x - extra_w) * style.SeparatorTextAlign.x), pos.y + text_baseline_y); // FIXME-ALIGN\n\n    // This allows using SameLine() to position something in the 'extra_w'\n    window->DC.CursorPosPrevLine.x = label_pos.x + label_size.x;\n\n    const ImU32 separator_col = GetColorU32(ImGuiCol_Separator);\n    if (label_size.x > 0.0f)\n    {\n        const float sep1_x2 = label_pos.x - style.ItemSpacing.x;\n        const float sep2_x1 = label_pos.x + label_size.x + extra_w + style.ItemSpacing.x;\n        if (sep1_x2 > sep1_x1 && separator_thickness > 0.0f)\n            window->DrawList->AddLine(ImVec2(sep1_x1, seps_y), ImVec2(sep1_x2, seps_y), separator_col, separator_thickness);\n        if (sep2_x2 > sep2_x1 && separator_thickness > 0.0f)\n            window->DrawList->AddLine(ImVec2(sep2_x1, seps_y), ImVec2(sep2_x2, seps_y), separator_col, separator_thickness);\n        if (g.LogEnabled)\n            LogSetNextTextDecoration(\"---\", NULL);\n        RenderTextEllipsis(window->DrawList, label_pos, ImVec2(bb.Max.x, bb.Max.y + style.ItemSpacing.y), bb.Max.x, bb.Max.x, label, label_end, &label_size);\n    }\n    else\n    {\n        if (g.LogEnabled)\n            LogText(\"---\");\n        if (separator_thickness > 0.0f)\n            window->DrawList->AddLine(ImVec2(sep1_x1, seps_y), ImVec2(sep2_x2, seps_y), separator_col, separator_thickness);\n    }\n}\n\nvoid ImGui::SeparatorText(const char* label)\n{\n    ImGuiWindow* window = GetCurrentWindow();\n    if (window->SkipItems)\n        return;\n\n    // The SeparatorText() vs SeparatorTextEx() distinction is designed to be considerate that we may want:\n    // - allow separator-text to be draggable items (would require a stable ID + a noticeable highlight)\n    // - this high-level entry point to allow formatting? (which in turns may require ID separate from formatted string)\n    // - because of this we probably can't turn 'const char* label' into 'const char* fmt, ...'\n    // Otherwise, we can decide that users wanting to drag this would layout a dedicated drag-item,\n    // and then we can turn this into a format function.\n    SeparatorTextEx(0, label, FindRenderedTextEnd(label), 0.0f);\n}\n\n// Using 'hover_visibility_delay' allows us to hide the highlight and mouse cursor for a short time, which can be convenient to reduce visual noise.\nbool ImGui::SplitterBehavior(const ImRect& bb, ImGuiID id, ImGuiAxis axis, float* size1, float* size2, float min_size1, float min_size2, float hover_extend, float hover_visibility_delay, ImU32 bg_col)\n{\n    ImGuiContext& g = *GImGui;\n    ImGuiWindow* window = g.CurrentWindow;\n\n    if (!ItemAdd(bb, id, NULL, ImGuiItemFlags_NoNav))\n        return false;\n\n    // FIXME: AFAIK the only leftover reason for passing ImGuiButtonFlags_AllowOverlap here is\n    // to allow caller of SplitterBehavior() to call SetItemAllowOverlap() after the item.\n    // Nowadays we would instead want to use SetNextItemAllowOverlap() before the item.\n    ImGuiButtonFlags button_flags = ImGuiButtonFlags_FlattenChildren;\n#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS\n    button_flags |= ImGuiButtonFlags_AllowOverlap;\n#endif\n\n    bool hovered, held;\n    ImRect bb_interact = bb;\n    bb_interact.Expand(axis == ImGuiAxis_Y ? ImVec2(0.0f, hover_extend) : ImVec2(hover_extend, 0.0f));\n    ButtonBehavior(bb_interact, id, &hovered, &held, button_flags);\n    if (hovered)\n        g.LastItemData.StatusFlags |= ImGuiItemStatusFlags_HoveredRect; // for IsItemHovered(), because bb_interact is larger than bb\n\n    if (held || (hovered && g.HoveredIdPreviousFrame == id && g.HoveredIdTimer >= hover_visibility_delay))\n        SetMouseCursor(axis == ImGuiAxis_Y ? ImGuiMouseCursor_ResizeNS : ImGuiMouseCursor_ResizeEW);\n\n    ImRect bb_render = bb;\n    if (held)\n    {\n        float mouse_delta = (g.IO.MousePos - g.ActiveIdClickOffset - bb_interact.Min)[axis];\n\n        // Minimum pane size\n        float size_1_maximum_delta = ImMax(0.0f, *size1 - min_size1);\n        float size_2_maximum_delta = ImMax(0.0f, *size2 - min_size2);\n        if (mouse_delta < -size_1_maximum_delta)\n            mouse_delta = -size_1_maximum_delta;\n        if (mouse_delta > size_2_maximum_delta)\n            mouse_delta = size_2_maximum_delta;\n\n        // Apply resize\n        if (mouse_delta != 0.0f)\n        {\n            *size1 = ImMax(*size1 + mouse_delta, min_size1);\n            *size2 = ImMax(*size2 - mouse_delta, min_size2);\n            bb_render.Translate((axis == ImGuiAxis_X) ? ImVec2(mouse_delta, 0.0f) : ImVec2(0.0f, mouse_delta));\n            MarkItemEdited(id);\n        }\n    }\n\n    // Render at new position\n    if (bg_col & IM_COL32_A_MASK)\n        window->DrawList->AddRectFilled(bb_render.Min, bb_render.Max, bg_col, 0.0f);\n    const ImU32 col = GetColorU32(held ? ImGuiCol_SeparatorActive : (hovered && g.HoveredIdTimer >= hover_visibility_delay) ? ImGuiCol_SeparatorHovered : ImGuiCol_Separator);\n    window->DrawList->AddRectFilled(bb_render.Min, bb_render.Max, col, 0.0f);\n\n    return held;\n}\n\nstatic int IMGUI_CDECL ShrinkWidthItemComparer(const void* lhs, const void* rhs)\n{\n    const ImGuiShrinkWidthItem* a = (const ImGuiShrinkWidthItem*)lhs;\n    const ImGuiShrinkWidthItem* b = (const ImGuiShrinkWidthItem*)rhs;\n    if (int d = (int)(b->Width - a->Width))\n        return d;\n    return (b->Index - a->Index);\n}\n\n// Shrink excess width from a set of item, by removing width from the larger items first.\n// Set items Width to -1.0f to disable shrinking this item.\nvoid ImGui::ShrinkWidths(ImGuiShrinkWidthItem* items, int count, float width_excess)\n{\n    if (count == 1)\n    {\n        if (items[0].Width >= 0.0f)\n            items[0].Width = ImMax(items[0].Width - width_excess, 1.0f);\n        return;\n    }\n    ImQsort(items, (size_t)count, sizeof(ImGuiShrinkWidthItem), ShrinkWidthItemComparer);\n    int count_same_width = 1;\n    while (width_excess > 0.0f && count_same_width < count)\n    {\n        while (count_same_width < count && items[0].Width <= items[count_same_width].Width)\n            count_same_width++;\n        float max_width_to_remove_per_item = (count_same_width < count && items[count_same_width].Width >= 0.0f) ? (items[0].Width - items[count_same_width].Width) : (items[0].Width - 1.0f);\n        if (max_width_to_remove_per_item <= 0.0f)\n            break;\n        float width_to_remove_per_item = ImMin(width_excess / count_same_width, max_width_to_remove_per_item);\n        for (int item_n = 0; item_n < count_same_width; item_n++)\n            items[item_n].Width -= width_to_remove_per_item;\n        width_excess -= width_to_remove_per_item * count_same_width;\n    }\n\n    // Round width and redistribute remainder\n    // Ensure that e.g. the right-most tab of a shrunk tab-bar always reaches exactly at the same distance from the right-most edge of the tab bar separator.\n    width_excess = 0.0f;\n    for (int n = 0; n < count; n++)\n    {\n        float width_rounded = ImTrunc(items[n].Width);\n        width_excess += items[n].Width - width_rounded;\n        items[n].Width = width_rounded;\n    }\n    while (width_excess > 0.0f)\n        for (int n = 0; n < count && width_excess > 0.0f; n++)\n        {\n            float width_to_add = ImMin(items[n].InitialWidth - items[n].Width, 1.0f);\n            items[n].Width += width_to_add;\n            width_excess -= width_to_add;\n        }\n}\n\n//-------------------------------------------------------------------------\n// [SECTION] Widgets: ComboBox\n//-------------------------------------------------------------------------\n// - CalcMaxPopupHeightFromItemCount() [Internal]\n// - BeginCombo()\n// - BeginComboPopup() [Internal]\n// - EndCombo()\n// - BeginComboPreview() [Internal]\n// - EndComboPreview() [Internal]\n// - Combo()\n//-------------------------------------------------------------------------\n\nstatic float CalcMaxPopupHeightFromItemCount(int items_count)\n{\n    ImGuiContext& g = *GImGui;\n    if (items_count <= 0)\n        return FLT_MAX;\n    return (g.FontSize + g.Style.ItemSpacing.y) * items_count - g.Style.ItemSpacing.y + (g.Style.WindowPadding.y * 2);\n}\n\nbool ImGui::BeginCombo(const char* label, const char* preview_value, ImGuiComboFlags flags)\n{\n    ImGuiContext& g = *GImGui;\n    ImGuiWindow* window = GetCurrentWindow();\n\n    ImGuiNextWindowDataFlags backup_next_window_data_flags = g.NextWindowData.HasFlags;\n    g.NextWindowData.ClearFlags(); // We behave like Begin() and need to consume those values\n    if (window->SkipItems)\n        return false;\n\n    const ImGuiStyle& style = g.Style;\n    const ImGuiID id = window->GetID(label);\n    IM_ASSERT((flags & (ImGuiComboFlags_NoArrowButton | ImGuiComboFlags_NoPreview)) != (ImGuiComboFlags_NoArrowButton | ImGuiComboFlags_NoPreview)); // Can't use both flags together\n    if (flags & ImGuiComboFlags_WidthFitPreview)\n        IM_ASSERT((flags & (ImGuiComboFlags_NoPreview | (ImGuiComboFlags)ImGuiComboFlags_CustomPreview)) == 0);\n\n    const float arrow_size = (flags & ImGuiComboFlags_NoArrowButton) ? 0.0f : GetFrameHeight();\n    const ImVec2 label_size = CalcTextSize(label, NULL, true);\n    const float preview_width = ((flags & ImGuiComboFlags_WidthFitPreview) && (preview_value != NULL)) ? CalcTextSize(preview_value, NULL, true).x : 0.0f;\n    const float w = (flags & ImGuiComboFlags_NoPreview) ? arrow_size : ((flags & ImGuiComboFlags_WidthFitPreview) ? (arrow_size + preview_width + style.FramePadding.x * 2.0f) : CalcItemWidth());\n    const ImRect bb(window->DC.CursorPos, window->DC.CursorPos + ImVec2(w, label_size.y + style.FramePadding.y * 2.0f));\n    const ImRect total_bb(bb.Min, bb.Max + ImVec2(label_size.x > 0.0f ? style.ItemInnerSpacing.x + label_size.x : 0.0f, 0.0f));\n    ItemSize(total_bb, style.FramePadding.y);\n    if (!ItemAdd(total_bb, id, &bb))\n        return false;\n\n    // Open on click\n    bool hovered, held;\n    bool pressed = ButtonBehavior(bb, id, &hovered, &held);\n    const ImGuiID popup_id = ImHashStr(\"##ComboPopup\", 0, id);\n    bool popup_open = IsPopupOpen(popup_id, ImGuiPopupFlags_None);\n    if (pressed && !popup_open)\n    {\n        OpenPopupEx(popup_id, ImGuiPopupFlags_None);\n        popup_open = true;\n    }\n\n    // Render shape\n    const ImU32 frame_col = GetColorU32(hovered ? ImGuiCol_FrameBgHovered : ImGuiCol_FrameBg);\n    const float value_x2 = ImMax(bb.Min.x, bb.Max.x - arrow_size);\n    RenderNavCursor(bb, id);\n    if (!(flags & ImGuiComboFlags_NoPreview))\n        window->DrawList->AddRectFilled(bb.Min, ImVec2(value_x2, bb.Max.y), frame_col, style.FrameRounding, (flags & ImGuiComboFlags_NoArrowButton) ? ImDrawFlags_RoundCornersAll : ImDrawFlags_RoundCornersLeft);\n    if (!(flags & ImGuiComboFlags_NoArrowButton))\n    {\n        ImU32 bg_col = GetColorU32((popup_open || hovered) ? ImGuiCol_ButtonHovered : ImGuiCol_Button);\n        ImU32 text_col = GetColorU32(ImGuiCol_Text);\n        window->DrawList->AddRectFilled(ImVec2(value_x2, bb.Min.y), bb.Max, bg_col, style.FrameRounding, (w <= arrow_size) ? ImDrawFlags_RoundCornersAll : ImDrawFlags_RoundCornersRight);\n        if (value_x2 + arrow_size - style.FramePadding.x <= bb.Max.x)\n            RenderArrow(window->DrawList, ImVec2(value_x2 + style.FramePadding.y, bb.Min.y + style.FramePadding.y), text_col, ImGuiDir_Down, 1.0f);\n    }\n    RenderFrameBorder(bb.Min, bb.Max, style.FrameRounding);\n\n    // Custom preview\n    if (flags & ImGuiComboFlags_CustomPreview)\n    {\n        g.ComboPreviewData.PreviewRect = ImRect(bb.Min.x, bb.Min.y, value_x2, bb.Max.y);\n        IM_ASSERT(preview_value == NULL || preview_value[0] == 0);\n        preview_value = NULL;\n    }\n\n    // Render preview and label\n    if (preview_value != NULL && !(flags & ImGuiComboFlags_NoPreview))\n    {\n        if (g.LogEnabled)\n            LogSetNextTextDecoration(\"{\", \"}\");\n        RenderTextClipped(bb.Min + style.FramePadding, ImVec2(value_x2, bb.Max.y), preview_value, NULL, NULL);\n    }\n    if (label_size.x > 0)\n        RenderText(ImVec2(bb.Max.x + style.ItemInnerSpacing.x, bb.Min.y + style.FramePadding.y), label);\n\n    if (!popup_open)\n        return false;\n\n    g.NextWindowData.HasFlags = backup_next_window_data_flags;\n    return BeginComboPopup(popup_id, bb, flags);\n}\n\nbool ImGui::BeginComboPopup(ImGuiID popup_id, const ImRect& bb, ImGuiComboFlags flags)\n{\n    ImGuiContext& g = *GImGui;\n    if (!IsPopupOpen(popup_id, ImGuiPopupFlags_None))\n    {\n        g.NextWindowData.ClearFlags();\n        return false;\n    }\n\n    // Set popup size\n    float w = bb.GetWidth();\n    if (g.NextWindowData.HasFlags & ImGuiNextWindowDataFlags_HasSizeConstraint)\n    {\n        g.NextWindowData.SizeConstraintRect.Min.x = ImMax(g.NextWindowData.SizeConstraintRect.Min.x, w);\n    }\n    else\n    {\n        if ((flags & ImGuiComboFlags_HeightMask_) == 0)\n            flags |= ImGuiComboFlags_HeightRegular;\n        IM_ASSERT(ImIsPowerOfTwo(flags & ImGuiComboFlags_HeightMask_)); // Only one\n        int popup_max_height_in_items = -1;\n        if (flags & ImGuiComboFlags_HeightRegular)     popup_max_height_in_items = 8;\n        else if (flags & ImGuiComboFlags_HeightSmall)  popup_max_height_in_items = 4;\n        else if (flags & ImGuiComboFlags_HeightLarge)  popup_max_height_in_items = 20;\n        ImVec2 constraint_min(0.0f, 0.0f), constraint_max(FLT_MAX, FLT_MAX);\n        if ((g.NextWindowData.HasFlags & ImGuiNextWindowDataFlags_HasSize) == 0 || g.NextWindowData.SizeVal.x <= 0.0f) // Don't apply constraints if user specified a size\n            constraint_min.x = w;\n        if ((g.NextWindowData.HasFlags & ImGuiNextWindowDataFlags_HasSize) == 0 || g.NextWindowData.SizeVal.y <= 0.0f)\n            constraint_max.y = CalcMaxPopupHeightFromItemCount(popup_max_height_in_items);\n        SetNextWindowSizeConstraints(constraint_min, constraint_max);\n    }\n\n    // This is essentially a specialized version of BeginPopupEx()\n    char name[16];\n    ImFormatString(name, IM_ARRAYSIZE(name), \"##Combo_%02d\", g.BeginComboDepth); // Recycle windows based on depth\n\n    // Set position given a custom constraint (peak into expected window size so we can position it)\n    // FIXME: This might be easier to express with an hypothetical SetNextWindowPosConstraints() function?\n    // FIXME: This might be moved to Begin() or at least around the same spot where Tooltips and other Popups are calling FindBestWindowPosForPopupEx()?\n    if (ImGuiWindow* popup_window = FindWindowByName(name))\n        if (popup_window->WasActive)\n        {\n            // Always override 'AutoPosLastDirection' to not leave a chance for a past value to affect us.\n            ImVec2 size_expected = CalcWindowNextAutoFitSize(popup_window);\n            popup_window->AutoPosLastDirection = (flags & ImGuiComboFlags_PopupAlignLeft) ? ImGuiDir_Left : ImGuiDir_Down; // Left = \"Below, Toward Left\", Down = \"Below, Toward Right (default)\"\n            ImRect r_outer = GetPopupAllowedExtentRect(popup_window);\n            ImVec2 pos = FindBestWindowPosForPopupEx(bb.GetBL(), size_expected, &popup_window->AutoPosLastDirection, r_outer, bb, ImGuiPopupPositionPolicy_ComboBox);\n            SetNextWindowPos(pos);\n        }\n\n    // We don't use BeginPopupEx() solely because we have a custom name string, which we could make an argument to BeginPopupEx()\n    ImGuiWindowFlags window_flags = ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_Popup | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_NoMove;\n    PushStyleVarX(ImGuiStyleVar_WindowPadding, g.Style.FramePadding.x); // Horizontally align ourselves with the framed text\n    bool ret = Begin(name, NULL, window_flags);\n    PopStyleVar();\n    if (!ret)\n    {\n        EndPopup();\n        IM_ASSERT(0);   // This should never happen as we tested for IsPopupOpen() above\n        return false;\n    }\n    g.BeginComboDepth++;\n    return true;\n}\n\nvoid ImGui::EndCombo()\n{\n    ImGuiContext& g = *GImGui;\n    EndPopup();\n    g.BeginComboDepth--;\n}\n\n// Call directly after the BeginCombo/EndCombo block. The preview is designed to only host non-interactive elements\n// (Experimental, see GitHub issues: #1658, #4168)\nbool ImGui::BeginComboPreview()\n{\n    ImGuiContext& g = *GImGui;\n    ImGuiWindow* window = g.CurrentWindow;\n    ImGuiComboPreviewData* preview_data = &g.ComboPreviewData;\n\n    if (window->SkipItems || !(g.LastItemData.StatusFlags & ImGuiItemStatusFlags_Visible))\n        return false;\n    IM_ASSERT(g.LastItemData.Rect.Min.x == preview_data->PreviewRect.Min.x && g.LastItemData.Rect.Min.y == preview_data->PreviewRect.Min.y); // Didn't call after BeginCombo/EndCombo block or forgot to pass ImGuiComboFlags_CustomPreview flag?\n    if (!window->ClipRect.Overlaps(preview_data->PreviewRect)) // Narrower test (optional)\n        return false;\n\n    // FIXME: This could be contained in a PushWorkRect() api\n    preview_data->BackupCursorPos = window->DC.CursorPos;\n    preview_data->BackupCursorMaxPos = window->DC.CursorMaxPos;\n    preview_data->BackupCursorPosPrevLine = window->DC.CursorPosPrevLine;\n    preview_data->BackupPrevLineTextBaseOffset = window->DC.PrevLineTextBaseOffset;\n    preview_data->BackupLayout = window->DC.LayoutType;\n    window->DC.CursorPos = preview_data->PreviewRect.Min + g.Style.FramePadding;\n    window->DC.CursorMaxPos = window->DC.CursorPos;\n    window->DC.LayoutType = ImGuiLayoutType_Horizontal;\n    window->DC.IsSameLine = false;\n    PushClipRect(preview_data->PreviewRect.Min, preview_data->PreviewRect.Max, true);\n\n    return true;\n}\n\nvoid ImGui::EndComboPreview()\n{\n    ImGuiContext& g = *GImGui;\n    ImGuiWindow* window = g.CurrentWindow;\n    ImGuiComboPreviewData* preview_data = &g.ComboPreviewData;\n\n    // FIXME: Using CursorMaxPos approximation instead of correct AABB which we will store in ImDrawCmd in the future\n    ImDrawList* draw_list = window->DrawList;\n    if (window->DC.CursorMaxPos.x < preview_data->PreviewRect.Max.x && window->DC.CursorMaxPos.y < preview_data->PreviewRect.Max.y)\n        if (draw_list->CmdBuffer.Size > 1) // Unlikely case that the PushClipRect() didn't create a command\n        {\n            draw_list->_CmdHeader.ClipRect = draw_list->CmdBuffer[draw_list->CmdBuffer.Size - 1].ClipRect = draw_list->CmdBuffer[draw_list->CmdBuffer.Size - 2].ClipRect;\n            draw_list->_TryMergeDrawCmds();\n        }\n    PopClipRect();\n    window->DC.CursorPos = preview_data->BackupCursorPos;\n    window->DC.CursorMaxPos = ImMax(window->DC.CursorMaxPos, preview_data->BackupCursorMaxPos);\n    window->DC.CursorPosPrevLine = preview_data->BackupCursorPosPrevLine;\n    window->DC.PrevLineTextBaseOffset = preview_data->BackupPrevLineTextBaseOffset;\n    window->DC.LayoutType = preview_data->BackupLayout;\n    window->DC.IsSameLine = false;\n    preview_data->PreviewRect = ImRect();\n}\n\n// Getter for the old Combo() API: const char*[]\nstatic const char* Items_ArrayGetter(void* data, int idx)\n{\n    const char* const* items = (const char* const*)data;\n    return items[idx];\n}\n\n// Getter for the old Combo() API: \"item1\\0item2\\0item3\\0\"\nstatic const char* Items_SingleStringGetter(void* data, int idx)\n{\n    const char* items_separated_by_zeros = (const char*)data;\n    int items_count = 0;\n    const char* p = items_separated_by_zeros;\n    while (*p)\n    {\n        if (idx == items_count)\n            break;\n        p += ImStrlen(p) + 1;\n        items_count++;\n    }\n    return *p ? p : NULL;\n}\n\n// Old API, prefer using BeginCombo() nowadays if you can.\nbool ImGui::Combo(const char* label, int* current_item, const char* (*getter)(void* user_data, int idx), void* user_data, int items_count, int popup_max_height_in_items)\n{\n    ImGuiContext& g = *GImGui;\n\n    // Call the getter to obtain the preview string which is a parameter to BeginCombo()\n    const char* preview_value = NULL;\n    if (*current_item >= 0 && *current_item < items_count)\n        preview_value = getter(user_data, *current_item);\n\n    // The old Combo() API exposed \"popup_max_height_in_items\". The new more general BeginCombo() API doesn't have/need it, but we emulate it here.\n    if (popup_max_height_in_items != -1 && !(g.NextWindowData.HasFlags & ImGuiNextWindowDataFlags_HasSizeConstraint))\n        SetNextWindowSizeConstraints(ImVec2(0, 0), ImVec2(FLT_MAX, CalcMaxPopupHeightFromItemCount(popup_max_height_in_items)));\n\n    if (!BeginCombo(label, preview_value, ImGuiComboFlags_None))\n        return false;\n\n    // Display items\n    bool value_changed = false;\n    ImGuiListClipper clipper;\n    clipper.Begin(items_count);\n    clipper.IncludeItemByIndex(*current_item);\n    while (clipper.Step())\n        for (int i = clipper.DisplayStart; i < clipper.DisplayEnd; i++)\n        {\n            const char* item_text = getter(user_data, i);\n            if (item_text == NULL)\n                item_text = \"*Unknown item*\";\n\n            PushID(i);\n            const bool item_selected = (i == *current_item);\n            if (Selectable(item_text, item_selected) && *current_item != i)\n            {\n                value_changed = true;\n                *current_item = i;\n            }\n            if (item_selected)\n                SetItemDefaultFocus();\n            PopID();\n        }\n\n    EndCombo();\n    if (value_changed)\n        MarkItemEdited(g.LastItemData.ID);\n\n    return value_changed;\n}\n\n// Combo box helper allowing to pass an array of strings.\nbool ImGui::Combo(const char* label, int* current_item, const char* const items[], int items_count, int height_in_items)\n{\n    const bool value_changed = Combo(label, current_item, Items_ArrayGetter, (void*)items, items_count, height_in_items);\n    return value_changed;\n}\n\n// Combo box helper allowing to pass all items in a single string literal holding multiple zero-terminated items \"item1\\0item2\\0\"\nbool ImGui::Combo(const char* label, int* current_item, const char* items_separated_by_zeros, int height_in_items)\n{\n    int items_count = 0;\n    const char* p = items_separated_by_zeros;       // FIXME-OPT: Avoid computing this, or at least only when combo is open\n    while (*p)\n    {\n        p += ImStrlen(p) + 1;\n        items_count++;\n    }\n    bool value_changed = Combo(label, current_item, Items_SingleStringGetter, (void*)items_separated_by_zeros, items_count, height_in_items);\n    return value_changed;\n}\n\n#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS\n\nstruct ImGuiGetNameFromIndexOldToNewCallbackData { void* UserData; bool (*OldCallback)(void*, int, const char**); };\nstatic const char* ImGuiGetNameFromIndexOldToNewCallback(void* user_data, int idx)\n{\n    ImGuiGetNameFromIndexOldToNewCallbackData* data = (ImGuiGetNameFromIndexOldToNewCallbackData*)user_data;\n    const char* s = NULL;\n    data->OldCallback(data->UserData, idx, &s);\n    return s;\n}\n\nbool ImGui::ListBox(const char* label, int* current_item, bool (*old_getter)(void*, int, const char**), void* user_data, int items_count, int height_in_items)\n{\n    ImGuiGetNameFromIndexOldToNewCallbackData old_to_new_data = { user_data, old_getter };\n    return ListBox(label, current_item, ImGuiGetNameFromIndexOldToNewCallback, &old_to_new_data, items_count, height_in_items);\n}\nbool ImGui::Combo(const char* label, int* current_item, bool (*old_getter)(void*, int, const char**), void* user_data, int items_count, int popup_max_height_in_items)\n{\n    ImGuiGetNameFromIndexOldToNewCallbackData old_to_new_data = { user_data, old_getter };\n    return Combo(label, current_item, ImGuiGetNameFromIndexOldToNewCallback, &old_to_new_data, items_count, popup_max_height_in_items);\n}\n\n#endif\n\n//-------------------------------------------------------------------------\n// [SECTION] Data Type and Data Formatting Helpers [Internal]\n//-------------------------------------------------------------------------\n// - DataTypeGetInfo()\n// - DataTypeFormatString()\n// - DataTypeApplyOp()\n// - DataTypeApplyFromText()\n// - DataTypeCompare()\n// - DataTypeClamp()\n// - GetMinimumStepAtDecimalPrecision\n// - RoundScalarWithFormat<>()\n//-------------------------------------------------------------------------\n\nstatic const ImGuiDataTypeInfo GDataTypeInfo[] =\n{\n    { sizeof(char),             \"S8\",   \"%d\",   \"%d\"    },  // ImGuiDataType_S8\n    { sizeof(unsigned char),    \"U8\",   \"%u\",   \"%u\"    },\n    { sizeof(short),            \"S16\",  \"%d\",   \"%d\"    },  // ImGuiDataType_S16\n    { sizeof(unsigned short),   \"U16\",  \"%u\",   \"%u\"    },\n    { sizeof(int),              \"S32\",  \"%d\",   \"%d\"    },  // ImGuiDataType_S32\n    { sizeof(unsigned int),     \"U32\",  \"%u\",   \"%u\"    },\n#ifdef _MSC_VER\n    { sizeof(ImS64),            \"S64\",  \"%I64d\",\"%I64d\" },  // ImGuiDataType_S64\n    { sizeof(ImU64),            \"U64\",  \"%I64u\",\"%I64u\" },\n#else\n    { sizeof(ImS64),            \"S64\",  \"%lld\", \"%lld\"  },  // ImGuiDataType_S64\n    { sizeof(ImU64),            \"U64\",  \"%llu\", \"%llu\"  },\n#endif\n    { sizeof(float),            \"float\", \"%.3f\",\"%f\"    },  // ImGuiDataType_Float (float are promoted to double in va_arg)\n    { sizeof(double),           \"double\",\"%f\",  \"%lf\"   },  // ImGuiDataType_Double\n    { sizeof(bool),             \"bool\", \"%d\",   \"%d\"    },  // ImGuiDataType_Bool\n    { 0,                        \"char*\",\"%s\",   \"%s\"    },  // ImGuiDataType_String\n};\nIM_STATIC_ASSERT(IM_ARRAYSIZE(GDataTypeInfo) == ImGuiDataType_COUNT);\n\nconst ImGuiDataTypeInfo* ImGui::DataTypeGetInfo(ImGuiDataType data_type)\n{\n    IM_ASSERT(data_type >= 0 && data_type < ImGuiDataType_COUNT);\n    return &GDataTypeInfo[data_type];\n}\n\nint ImGui::DataTypeFormatString(char* buf, int buf_size, ImGuiDataType data_type, const void* p_data, const char* format)\n{\n    // Signedness doesn't matter when pushing integer arguments\n    if (data_type == ImGuiDataType_S32 || data_type == ImGuiDataType_U32)\n        return ImFormatString(buf, buf_size, format, *(const ImU32*)p_data);\n    if (data_type == ImGuiDataType_S64 || data_type == ImGuiDataType_U64)\n        return ImFormatString(buf, buf_size, format, *(const ImU64*)p_data);\n    if (data_type == ImGuiDataType_Float)\n        return ImFormatString(buf, buf_size, format, *(const float*)p_data);\n    if (data_type == ImGuiDataType_Double)\n        return ImFormatString(buf, buf_size, format, *(const double*)p_data);\n    if (data_type == ImGuiDataType_S8)\n        return ImFormatString(buf, buf_size, format, *(const ImS8*)p_data);\n    if (data_type == ImGuiDataType_U8)\n        return ImFormatString(buf, buf_size, format, *(const ImU8*)p_data);\n    if (data_type == ImGuiDataType_S16)\n        return ImFormatString(buf, buf_size, format, *(const ImS16*)p_data);\n    if (data_type == ImGuiDataType_U16)\n        return ImFormatString(buf, buf_size, format, *(const ImU16*)p_data);\n    IM_ASSERT(0);\n    return 0;\n}\n\nvoid ImGui::DataTypeApplyOp(ImGuiDataType data_type, int op, void* output, const void* arg1, const void* arg2)\n{\n    IM_ASSERT(op == '+' || op == '-');\n    switch (data_type)\n    {\n        case ImGuiDataType_S8:\n            if (op == '+') { *(ImS8*)output  = ImAddClampOverflow(*(const ImS8*)arg1,  *(const ImS8*)arg2,  IM_S8_MIN,  IM_S8_MAX); }\n            if (op == '-') { *(ImS8*)output  = ImSubClampOverflow(*(const ImS8*)arg1,  *(const ImS8*)arg2,  IM_S8_MIN,  IM_S8_MAX); }\n            return;\n        case ImGuiDataType_U8:\n            if (op == '+') { *(ImU8*)output  = ImAddClampOverflow(*(const ImU8*)arg1,  *(const ImU8*)arg2,  IM_U8_MIN,  IM_U8_MAX); }\n            if (op == '-') { *(ImU8*)output  = ImSubClampOverflow(*(const ImU8*)arg1,  *(const ImU8*)arg2,  IM_U8_MIN,  IM_U8_MAX); }\n            return;\n        case ImGuiDataType_S16:\n            if (op == '+') { *(ImS16*)output = ImAddClampOverflow(*(const ImS16*)arg1, *(const ImS16*)arg2, IM_S16_MIN, IM_S16_MAX); }\n            if (op == '-') { *(ImS16*)output = ImSubClampOverflow(*(const ImS16*)arg1, *(const ImS16*)arg2, IM_S16_MIN, IM_S16_MAX); }\n            return;\n        case ImGuiDataType_U16:\n            if (op == '+') { *(ImU16*)output = ImAddClampOverflow(*(const ImU16*)arg1, *(const ImU16*)arg2, IM_U16_MIN, IM_U16_MAX); }\n            if (op == '-') { *(ImU16*)output = ImSubClampOverflow(*(const ImU16*)arg1, *(const ImU16*)arg2, IM_U16_MIN, IM_U16_MAX); }\n            return;\n        case ImGuiDataType_S32:\n            if (op == '+') { *(ImS32*)output = ImAddClampOverflow(*(const ImS32*)arg1, *(const ImS32*)arg2, IM_S32_MIN, IM_S32_MAX); }\n            if (op == '-') { *(ImS32*)output = ImSubClampOverflow(*(const ImS32*)arg1, *(const ImS32*)arg2, IM_S32_MIN, IM_S32_MAX); }\n            return;\n        case ImGuiDataType_U32:\n            if (op == '+') { *(ImU32*)output = ImAddClampOverflow(*(const ImU32*)arg1, *(const ImU32*)arg2, IM_U32_MIN, IM_U32_MAX); }\n            if (op == '-') { *(ImU32*)output = ImSubClampOverflow(*(const ImU32*)arg1, *(const ImU32*)arg2, IM_U32_MIN, IM_U32_MAX); }\n            return;\n        case ImGuiDataType_S64:\n            if (op == '+') { *(ImS64*)output = ImAddClampOverflow(*(const ImS64*)arg1, *(const ImS64*)arg2, IM_S64_MIN, IM_S64_MAX); }\n            if (op == '-') { *(ImS64*)output = ImSubClampOverflow(*(const ImS64*)arg1, *(const ImS64*)arg2, IM_S64_MIN, IM_S64_MAX); }\n            return;\n        case ImGuiDataType_U64:\n            if (op == '+') { *(ImU64*)output = ImAddClampOverflow(*(const ImU64*)arg1, *(const ImU64*)arg2, IM_U64_MIN, IM_U64_MAX); }\n            if (op == '-') { *(ImU64*)output = ImSubClampOverflow(*(const ImU64*)arg1, *(const ImU64*)arg2, IM_U64_MIN, IM_U64_MAX); }\n            return;\n        case ImGuiDataType_Float:\n            if (op == '+') { *(float*)output = *(const float*)arg1 + *(const float*)arg2; }\n            if (op == '-') { *(float*)output = *(const float*)arg1 - *(const float*)arg2; }\n            return;\n        case ImGuiDataType_Double:\n            if (op == '+') { *(double*)output = *(const double*)arg1 + *(const double*)arg2; }\n            if (op == '-') { *(double*)output = *(const double*)arg1 - *(const double*)arg2; }\n            return;\n        case ImGuiDataType_COUNT: break;\n    }\n    IM_ASSERT(0);\n}\n\n// User can input math operators (e.g. +100) to edit a numerical values.\n// NB: This is _not_ a full expression evaluator. We should probably add one and replace this dumb mess..\nbool ImGui::DataTypeApplyFromText(const char* buf, ImGuiDataType data_type, void* p_data, const char* format, void* p_data_when_empty)\n{\n    // Copy the value in an opaque buffer so we can compare at the end of the function if it changed at all.\n    const ImGuiDataTypeInfo* type_info = DataTypeGetInfo(data_type);\n    ImGuiDataTypeStorage data_backup;\n    memcpy(&data_backup, p_data, type_info->Size);\n\n    while (ImCharIsBlankA(*buf))\n        buf++;\n    if (!buf[0])\n    {\n        if (p_data_when_empty != NULL)\n        {\n            memcpy(p_data, p_data_when_empty, type_info->Size);\n            return memcmp(&data_backup, p_data, type_info->Size) != 0;\n        }\n        return false;\n    }\n\n    // Sanitize format\n    // - For float/double we have to ignore format with precision (e.g. \"%.2f\") because sscanf doesn't take them in, so force them into %f and %lf\n    // - In theory could treat empty format as using default, but this would only cover rare/bizarre case of using InputScalar() + integer + format string without %.\n    char format_sanitized[32];\n    if (data_type == ImGuiDataType_Float || data_type == ImGuiDataType_Double)\n        format = type_info->ScanFmt;\n    else\n        format = ImParseFormatSanitizeForScanning(format, format_sanitized, IM_ARRAYSIZE(format_sanitized));\n\n    // Small types need a 32-bit buffer to receive the result from scanf()\n    int v32 = 0;\n    if (sscanf(buf, format, type_info->Size >= 4 ? p_data : &v32) < 1)\n        return false;\n    if (type_info->Size < 4)\n    {\n        if (data_type == ImGuiDataType_S8)\n            *(ImS8*)p_data = (ImS8)ImClamp(v32, (int)IM_S8_MIN, (int)IM_S8_MAX);\n        else if (data_type == ImGuiDataType_U8)\n            *(ImU8*)p_data = (ImU8)ImClamp(v32, (int)IM_U8_MIN, (int)IM_U8_MAX);\n        else if (data_type == ImGuiDataType_S16)\n            *(ImS16*)p_data = (ImS16)ImClamp(v32, (int)IM_S16_MIN, (int)IM_S16_MAX);\n        else if (data_type == ImGuiDataType_U16)\n            *(ImU16*)p_data = (ImU16)ImClamp(v32, (int)IM_U16_MIN, (int)IM_U16_MAX);\n        else\n            IM_ASSERT(0);\n    }\n\n    return memcmp(&data_backup, p_data, type_info->Size) != 0;\n}\n\ntemplate<typename T>\nstatic int DataTypeCompareT(const T* lhs, const T* rhs)\n{\n    if (*lhs < *rhs) return -1;\n    if (*lhs > *rhs) return +1;\n    return 0;\n}\n\nint ImGui::DataTypeCompare(ImGuiDataType data_type, const void* arg_1, const void* arg_2)\n{\n    switch (data_type)\n    {\n    case ImGuiDataType_S8:     return DataTypeCompareT<ImS8  >((const ImS8*  )arg_1, (const ImS8*  )arg_2);\n    case ImGuiDataType_U8:     return DataTypeCompareT<ImU8  >((const ImU8*  )arg_1, (const ImU8*  )arg_2);\n    case ImGuiDataType_S16:    return DataTypeCompareT<ImS16 >((const ImS16* )arg_1, (const ImS16* )arg_2);\n    case ImGuiDataType_U16:    return DataTypeCompareT<ImU16 >((const ImU16* )arg_1, (const ImU16* )arg_2);\n    case ImGuiDataType_S32:    return DataTypeCompareT<ImS32 >((const ImS32* )arg_1, (const ImS32* )arg_2);\n    case ImGuiDataType_U32:    return DataTypeCompareT<ImU32 >((const ImU32* )arg_1, (const ImU32* )arg_2);\n    case ImGuiDataType_S64:    return DataTypeCompareT<ImS64 >((const ImS64* )arg_1, (const ImS64* )arg_2);\n    case ImGuiDataType_U64:    return DataTypeCompareT<ImU64 >((const ImU64* )arg_1, (const ImU64* )arg_2);\n    case ImGuiDataType_Float:  return DataTypeCompareT<float >((const float* )arg_1, (const float* )arg_2);\n    case ImGuiDataType_Double: return DataTypeCompareT<double>((const double*)arg_1, (const double*)arg_2);\n    case ImGuiDataType_COUNT:  break;\n    }\n    IM_ASSERT(0);\n    return 0;\n}\n\ntemplate<typename T>\nstatic bool DataTypeClampT(T* v, const T* v_min, const T* v_max)\n{\n    // Clamp, both sides are optional, return true if modified\n    if (v_min && *v < *v_min) { *v = *v_min; return true; }\n    if (v_max && *v > *v_max) { *v = *v_max; return true; }\n    return false;\n}\n\nbool ImGui::DataTypeClamp(ImGuiDataType data_type, void* p_data, const void* p_min, const void* p_max)\n{\n    switch (data_type)\n    {\n    case ImGuiDataType_S8:     return DataTypeClampT<ImS8  >((ImS8*  )p_data, (const ImS8*  )p_min, (const ImS8*  )p_max);\n    case ImGuiDataType_U8:     return DataTypeClampT<ImU8  >((ImU8*  )p_data, (const ImU8*  )p_min, (const ImU8*  )p_max);\n    case ImGuiDataType_S16:    return DataTypeClampT<ImS16 >((ImS16* )p_data, (const ImS16* )p_min, (const ImS16* )p_max);\n    case ImGuiDataType_U16:    return DataTypeClampT<ImU16 >((ImU16* )p_data, (const ImU16* )p_min, (const ImU16* )p_max);\n    case ImGuiDataType_S32:    return DataTypeClampT<ImS32 >((ImS32* )p_data, (const ImS32* )p_min, (const ImS32* )p_max);\n    case ImGuiDataType_U32:    return DataTypeClampT<ImU32 >((ImU32* )p_data, (const ImU32* )p_min, (const ImU32* )p_max);\n    case ImGuiDataType_S64:    return DataTypeClampT<ImS64 >((ImS64* )p_data, (const ImS64* )p_min, (const ImS64* )p_max);\n    case ImGuiDataType_U64:    return DataTypeClampT<ImU64 >((ImU64* )p_data, (const ImU64* )p_min, (const ImU64* )p_max);\n    case ImGuiDataType_Float:  return DataTypeClampT<float >((float* )p_data, (const float* )p_min, (const float* )p_max);\n    case ImGuiDataType_Double: return DataTypeClampT<double>((double*)p_data, (const double*)p_min, (const double*)p_max);\n    case ImGuiDataType_COUNT:  break;\n    }\n    IM_ASSERT(0);\n    return false;\n}\n\nbool ImGui::DataTypeIsZero(ImGuiDataType data_type, const void* p_data)\n{\n    ImGuiContext& g = *GImGui;\n    return DataTypeCompare(data_type, p_data, &g.DataTypeZeroValue) == 0;\n}\n\nstatic float GetMinimumStepAtDecimalPrecision(int decimal_precision)\n{\n    static const float min_steps[10] = { 1.0f, 0.1f, 0.01f, 0.001f, 0.0001f, 0.00001f, 0.000001f, 0.0000001f, 0.00000001f, 0.000000001f };\n    if (decimal_precision < 0)\n        return FLT_MIN;\n    return (decimal_precision < IM_ARRAYSIZE(min_steps)) ? min_steps[decimal_precision] : ImPow(10.0f, (float)-decimal_precision);\n}\n\ntemplate<typename TYPE>\nTYPE ImGui::RoundScalarWithFormatT(const char* format, ImGuiDataType data_type, TYPE v)\n{\n    IM_UNUSED(data_type);\n    IM_ASSERT(data_type == ImGuiDataType_Float || data_type == ImGuiDataType_Double);\n    const char* fmt_start = ImParseFormatFindStart(format);\n    if (fmt_start[0] != '%' || fmt_start[1] == '%') // Don't apply if the value is not visible in the format string\n        return v;\n\n    // Sanitize format\n    char fmt_sanitized[32];\n    ImParseFormatSanitizeForPrinting(fmt_start, fmt_sanitized, IM_ARRAYSIZE(fmt_sanitized));\n    fmt_start = fmt_sanitized;\n\n    // Format value with our rounding, and read back\n    char v_str[64];\n    ImFormatString(v_str, IM_ARRAYSIZE(v_str), fmt_start, v);\n    const char* p = v_str;\n    while (*p == ' ')\n        p++;\n    v = (TYPE)ImAtof(p);\n\n    return v;\n}\n\n//-------------------------------------------------------------------------\n// [SECTION] Widgets: DragScalar, DragFloat, DragInt, etc.\n//-------------------------------------------------------------------------\n// - DragBehaviorT<>() [Internal]\n// - DragBehavior() [Internal]\n// - DragScalar()\n// - DragScalarN()\n// - DragFloat()\n// - DragFloat2()\n// - DragFloat3()\n// - DragFloat4()\n// - DragFloatRange2()\n// - DragInt()\n// - DragInt2()\n// - DragInt3()\n// - DragInt4()\n// - DragIntRange2()\n//-------------------------------------------------------------------------\n\n// This is called by DragBehavior() when the widget is active (held by mouse or being manipulated with Nav controls)\ntemplate<typename TYPE, typename SIGNEDTYPE, typename FLOATTYPE>\nbool ImGui::DragBehaviorT(ImGuiDataType data_type, TYPE* v, float v_speed, const TYPE v_min, const TYPE v_max, const char* format, ImGuiSliderFlags flags)\n{\n    ImGuiContext& g = *GImGui;\n    const ImGuiAxis axis = (flags & ImGuiSliderFlags_Vertical) ? ImGuiAxis_Y : ImGuiAxis_X;\n    const bool is_bounded = (v_min < v_max) || ((v_min == v_max) && (v_min != 0.0f || (flags & ImGuiSliderFlags_ClampZeroRange)));\n    const bool is_wrapped = is_bounded && (flags & ImGuiSliderFlags_WrapAround);\n    const bool is_logarithmic = (flags & ImGuiSliderFlags_Logarithmic) != 0;\n    const bool is_floating_point = (data_type == ImGuiDataType_Float) || (data_type == ImGuiDataType_Double);\n\n    // Default tweak speed\n    if (v_speed == 0.0f && is_bounded && (v_max - v_min < FLT_MAX))\n        v_speed = (float)((v_max - v_min) * g.DragSpeedDefaultRatio);\n\n    // Inputs accumulates into g.DragCurrentAccum, which is flushed into the current value as soon as it makes a difference with our precision settings\n    float adjust_delta = 0.0f;\n    if (g.ActiveIdSource == ImGuiInputSource_Mouse && IsMousePosValid() && IsMouseDragPastThreshold(0, g.IO.MouseDragThreshold * DRAG_MOUSE_THRESHOLD_FACTOR))\n    {\n        adjust_delta = g.IO.MouseDelta[axis];\n        if (g.IO.KeyAlt && !(flags & ImGuiSliderFlags_NoSpeedTweaks))\n            adjust_delta *= 1.0f / 100.0f;\n        if (g.IO.KeyShift && !(flags & ImGuiSliderFlags_NoSpeedTweaks))\n            adjust_delta *= 10.0f;\n    }\n    else if (g.ActiveIdSource == ImGuiInputSource_Keyboard || g.ActiveIdSource == ImGuiInputSource_Gamepad)\n    {\n        const int decimal_precision = is_floating_point ? ImParseFormatPrecision(format, 3) : 0;\n        const bool tweak_slow = IsKeyDown((g.NavInputSource == ImGuiInputSource_Gamepad) ? ImGuiKey_NavGamepadTweakSlow : ImGuiKey_NavKeyboardTweakSlow);\n        const bool tweak_fast = IsKeyDown((g.NavInputSource == ImGuiInputSource_Gamepad) ? ImGuiKey_NavGamepadTweakFast : ImGuiKey_NavKeyboardTweakFast);\n        const float tweak_factor = (flags & ImGuiSliderFlags_NoSpeedTweaks) ? 1.0f : tweak_slow ? 1.0f / 10.0f : tweak_fast ? 10.0f : 1.0f;\n        adjust_delta = GetNavTweakPressedAmount(axis) * tweak_factor;\n        v_speed = ImMax(v_speed, GetMinimumStepAtDecimalPrecision(decimal_precision));\n    }\n    adjust_delta *= v_speed;\n\n    // For vertical drag we currently assume that Up=higher value (like we do with vertical sliders). This may become a parameter.\n    if (axis == ImGuiAxis_Y)\n        adjust_delta = -adjust_delta;\n\n    // For logarithmic use our range is effectively 0..1 so scale the delta into that range\n    if (is_logarithmic && (v_max - v_min < FLT_MAX) && ((v_max - v_min) > 0.000001f)) // Epsilon to avoid /0\n        adjust_delta /= (float)(v_max - v_min);\n\n    // Clear current value on activation\n    // Avoid altering values and clamping when we are _already_ past the limits and heading in the same direction, so e.g. if range is 0..255, current value is 300 and we are pushing to the right side, keep the 300.\n    const bool is_just_activated = g.ActiveIdIsJustActivated;\n    const bool is_already_past_limits_and_pushing_outward = is_bounded && !is_wrapped && ((*v >= v_max && adjust_delta > 0.0f) || (*v <= v_min && adjust_delta < 0.0f));\n    if (is_just_activated || is_already_past_limits_and_pushing_outward)\n    {\n        g.DragCurrentAccum = 0.0f;\n        g.DragCurrentAccumDirty = false;\n    }\n    else if (adjust_delta != 0.0f)\n    {\n        g.DragCurrentAccum += adjust_delta;\n        g.DragCurrentAccumDirty = true;\n    }\n\n    if (!g.DragCurrentAccumDirty)\n        return false;\n\n    TYPE v_cur = *v;\n    FLOATTYPE v_old_ref_for_accum_remainder = (FLOATTYPE)0.0f;\n\n    float logarithmic_zero_epsilon = 0.0f; // Only valid when is_logarithmic is true\n    const float zero_deadzone_halfsize = 0.0f; // Drag widgets have no deadzone (as it doesn't make sense)\n    if (is_logarithmic)\n    {\n        // When using logarithmic sliders, we need to clamp to avoid hitting zero, but our choice of clamp value greatly affects slider precision. We attempt to use the specified precision to estimate a good lower bound.\n        const int decimal_precision = is_floating_point ? ImParseFormatPrecision(format, 3) : 1;\n        logarithmic_zero_epsilon = ImPow(0.1f, (float)decimal_precision);\n\n        // Convert to parametric space, apply delta, convert back\n        float v_old_parametric = ScaleRatioFromValueT<TYPE, SIGNEDTYPE, FLOATTYPE>(data_type, v_cur, v_min, v_max, is_logarithmic, logarithmic_zero_epsilon, zero_deadzone_halfsize);\n        float v_new_parametric = v_old_parametric + g.DragCurrentAccum;\n        v_cur = ScaleValueFromRatioT<TYPE, SIGNEDTYPE, FLOATTYPE>(data_type, v_new_parametric, v_min, v_max, is_logarithmic, logarithmic_zero_epsilon, zero_deadzone_halfsize);\n        v_old_ref_for_accum_remainder = v_old_parametric;\n    }\n    else\n    {\n        v_cur += (SIGNEDTYPE)g.DragCurrentAccum;\n    }\n\n    // Round to user desired precision based on format string\n    if (is_floating_point && !(flags & ImGuiSliderFlags_NoRoundToFormat))\n        v_cur = RoundScalarWithFormatT<TYPE>(format, data_type, v_cur);\n\n    // Preserve remainder after rounding has been applied. This also allow slow tweaking of values.\n    g.DragCurrentAccumDirty = false;\n    if (is_logarithmic)\n    {\n        // Convert to parametric space, apply delta, convert back\n        float v_new_parametric = ScaleRatioFromValueT<TYPE, SIGNEDTYPE, FLOATTYPE>(data_type, v_cur, v_min, v_max, is_logarithmic, logarithmic_zero_epsilon, zero_deadzone_halfsize);\n        g.DragCurrentAccum -= (float)(v_new_parametric - v_old_ref_for_accum_remainder);\n    }\n    else\n    {\n        g.DragCurrentAccum -= (float)((SIGNEDTYPE)v_cur - (SIGNEDTYPE)*v);\n    }\n\n    // Lose zero sign for float/double\n    if (v_cur == (TYPE)-0)\n        v_cur = (TYPE)0;\n\n    if (*v != v_cur && is_bounded)\n    {\n        if (is_wrapped)\n        {\n            // Wrap values\n            if (v_cur < v_min)\n                v_cur += v_max - v_min + (is_floating_point ? 0 : 1);\n            if (v_cur > v_max)\n                v_cur -= v_max - v_min + (is_floating_point ? 0 : 1);\n        }\n        else\n        {\n            // Clamp values + handle overflow/wrap-around for integer types.\n            if (v_cur < v_min || (v_cur > *v && adjust_delta < 0.0f && !is_floating_point))\n                v_cur = v_min;\n            if (v_cur > v_max || (v_cur < *v && adjust_delta > 0.0f && !is_floating_point))\n                v_cur = v_max;\n        }\n    }\n\n    // Apply result\n    if (*v == v_cur)\n        return false;\n    *v = v_cur;\n    return true;\n}\n\nbool ImGui::DragBehavior(ImGuiID id, ImGuiDataType data_type, void* p_v, float v_speed, const void* p_min, const void* p_max, const char* format, ImGuiSliderFlags flags)\n{\n    // Read imgui.cpp \"API BREAKING CHANGES\" section for 1.78 if you hit this assert.\n    IM_ASSERT((flags == 1 || (flags & ImGuiSliderFlags_InvalidMask_) == 0) && \"Invalid ImGuiSliderFlags flags! Has the legacy 'float power' argument been mistakenly cast to flags? Call function with ImGuiSliderFlags_Logarithmic flags instead.\");\n\n    ImGuiContext& g = *GImGui;\n    if (g.ActiveId == id)\n    {\n        // Those are the things we can do easily outside the DragBehaviorT<> template, saves code generation.\n        if (g.ActiveIdSource == ImGuiInputSource_Mouse && !g.IO.MouseDown[0])\n            ClearActiveID();\n        else if ((g.ActiveIdSource == ImGuiInputSource_Keyboard || g.ActiveIdSource == ImGuiInputSource_Gamepad) && g.NavActivatePressedId == id && !g.ActiveIdIsJustActivated)\n            ClearActiveID();\n    }\n    if (g.ActiveId != id)\n        return false;\n    if ((g.LastItemData.ItemFlags & ImGuiItemFlags_ReadOnly) || (flags & ImGuiSliderFlags_ReadOnly))\n        return false;\n\n    switch (data_type)\n    {\n    case ImGuiDataType_S8:     { ImS32 v32 = (ImS32)*(ImS8*)p_v;  bool r = DragBehaviorT<ImS32, ImS32, float>(ImGuiDataType_S32, &v32, v_speed, p_min ? *(const ImS8*) p_min : IM_S8_MIN,  p_max ? *(const ImS8*)p_max  : IM_S8_MAX,  format, flags); if (r) *(ImS8*)p_v = (ImS8)v32; return r; }\n    case ImGuiDataType_U8:     { ImU32 v32 = (ImU32)*(ImU8*)p_v;  bool r = DragBehaviorT<ImU32, ImS32, float>(ImGuiDataType_U32, &v32, v_speed, p_min ? *(const ImU8*) p_min : IM_U8_MIN,  p_max ? *(const ImU8*)p_max  : IM_U8_MAX,  format, flags); if (r) *(ImU8*)p_v = (ImU8)v32; return r; }\n    case ImGuiDataType_S16:    { ImS32 v32 = (ImS32)*(ImS16*)p_v; bool r = DragBehaviorT<ImS32, ImS32, float>(ImGuiDataType_S32, &v32, v_speed, p_min ? *(const ImS16*)p_min : IM_S16_MIN, p_max ? *(const ImS16*)p_max : IM_S16_MAX, format, flags); if (r) *(ImS16*)p_v = (ImS16)v32; return r; }\n    case ImGuiDataType_U16:    { ImU32 v32 = (ImU32)*(ImU16*)p_v; bool r = DragBehaviorT<ImU32, ImS32, float>(ImGuiDataType_U32, &v32, v_speed, p_min ? *(const ImU16*)p_min : IM_U16_MIN, p_max ? *(const ImU16*)p_max : IM_U16_MAX, format, flags); if (r) *(ImU16*)p_v = (ImU16)v32; return r; }\n    case ImGuiDataType_S32:    return DragBehaviorT<ImS32, ImS32, float >(data_type, (ImS32*)p_v,  v_speed, p_min ? *(const ImS32* )p_min : IM_S32_MIN, p_max ? *(const ImS32* )p_max : IM_S32_MAX, format, flags);\n    case ImGuiDataType_U32:    return DragBehaviorT<ImU32, ImS32, float >(data_type, (ImU32*)p_v,  v_speed, p_min ? *(const ImU32* )p_min : IM_U32_MIN, p_max ? *(const ImU32* )p_max : IM_U32_MAX, format, flags);\n    case ImGuiDataType_S64:    return DragBehaviorT<ImS64, ImS64, double>(data_type, (ImS64*)p_v,  v_speed, p_min ? *(const ImS64* )p_min : IM_S64_MIN, p_max ? *(const ImS64* )p_max : IM_S64_MAX, format, flags);\n    case ImGuiDataType_U64:    return DragBehaviorT<ImU64, ImS64, double>(data_type, (ImU64*)p_v,  v_speed, p_min ? *(const ImU64* )p_min : IM_U64_MIN, p_max ? *(const ImU64* )p_max : IM_U64_MAX, format, flags);\n    case ImGuiDataType_Float:  return DragBehaviorT<float, float, float >(data_type, (float*)p_v,  v_speed, p_min ? *(const float* )p_min : -FLT_MAX,   p_max ? *(const float* )p_max : FLT_MAX,    format, flags);\n    case ImGuiDataType_Double: return DragBehaviorT<double,double,double>(data_type, (double*)p_v, v_speed, p_min ? *(const double*)p_min : -DBL_MAX,   p_max ? *(const double*)p_max : DBL_MAX,    format, flags);\n    case ImGuiDataType_COUNT:  break;\n    }\n    IM_ASSERT(0);\n    return false;\n}\n\n// Note: p_data, p_min and p_max are _pointers_ to a memory address holding the data. For a Drag widget, p_min and p_max are optional.\n// Read code of e.g. DragFloat(), DragInt() etc. or examples in 'Demo->Widgets->Data Types' to understand how to use this function directly.\nbool ImGui::DragScalar(const char* label, ImGuiDataType data_type, void* p_data, float v_speed, const void* p_min, const void* p_max, const char* format, ImGuiSliderFlags flags)\n{\n    ImGuiWindow* window = GetCurrentWindow();\n    if (window->SkipItems)\n        return false;\n\n    ImGuiContext& g = *GImGui;\n    const ImGuiStyle& style = g.Style;\n    const ImGuiID id = window->GetID(label);\n    const float w = CalcItemWidth();\n\n    const ImVec2 label_size = CalcTextSize(label, NULL, true);\n    const ImRect frame_bb(window->DC.CursorPos, window->DC.CursorPos + ImVec2(w, label_size.y + style.FramePadding.y * 2.0f));\n    const ImRect total_bb(frame_bb.Min, frame_bb.Max + ImVec2(label_size.x > 0.0f ? style.ItemInnerSpacing.x + label_size.x : 0.0f, 0.0f));\n\n    const bool temp_input_allowed = (flags & ImGuiSliderFlags_NoInput) == 0;\n    ItemSize(total_bb, style.FramePadding.y);\n    if (!ItemAdd(total_bb, id, &frame_bb, temp_input_allowed ? ImGuiItemFlags_Inputable : 0))\n        return false;\n\n    // Default format string when passing NULL\n    if (format == NULL)\n        format = DataTypeGetInfo(data_type)->PrintFmt;\n\n    const bool hovered = ItemHoverable(frame_bb, id, g.LastItemData.ItemFlags);\n    bool temp_input_is_active = temp_input_allowed && TempInputIsActive(id);\n    if (!temp_input_is_active)\n    {\n        // Tabbing or CTRL+click on Drag turns it into an InputText\n        const bool clicked = hovered && IsMouseClicked(0, ImGuiInputFlags_None, id);\n        const bool double_clicked = (hovered && g.IO.MouseClickedCount[0] == 2 && TestKeyOwner(ImGuiKey_MouseLeft, id));\n        const bool make_active = (clicked || double_clicked || g.NavActivateId == id);\n        if (make_active && (clicked || double_clicked))\n            SetKeyOwner(ImGuiKey_MouseLeft, id);\n        if (make_active && temp_input_allowed)\n            if ((clicked && g.IO.KeyCtrl) || double_clicked || (g.NavActivateId == id && (g.NavActivateFlags & ImGuiActivateFlags_PreferInput)))\n                temp_input_is_active = true;\n\n        // (Optional) simple click (without moving) turns Drag into an InputText\n        if (g.IO.ConfigDragClickToInputText && temp_input_allowed && !temp_input_is_active)\n            if (g.ActiveId == id && hovered && g.IO.MouseReleased[0] && !IsMouseDragPastThreshold(0, g.IO.MouseDragThreshold * DRAG_MOUSE_THRESHOLD_FACTOR))\n            {\n                g.NavActivateId = id;\n                g.NavActivateFlags = ImGuiActivateFlags_PreferInput;\n                temp_input_is_active = true;\n            }\n\n        // Store initial value (not used by main lib but available as a convenience but some mods e.g. to revert)\n        if (make_active)\n            memcpy(&g.ActiveIdValueOnActivation, p_data, DataTypeGetInfo(data_type)->Size);\n\n        if (make_active && !temp_input_is_active)\n        {\n            SetActiveID(id, window);\n            SetFocusID(id, window);\n            FocusWindow(window);\n            g.ActiveIdUsingNavDirMask = (1 << ImGuiDir_Left) | (1 << ImGuiDir_Right);\n        }\n    }\n\n    if (temp_input_is_active)\n    {\n        // Only clamp CTRL+Click input when ImGuiSliderFlags_ClampOnInput is set (generally via ImGuiSliderFlags_AlwaysClamp)\n        bool clamp_enabled = false;\n        if ((flags & ImGuiSliderFlags_ClampOnInput) && (p_min != NULL || p_max != NULL))\n        {\n            const int clamp_range_dir = (p_min != NULL && p_max != NULL) ? DataTypeCompare(data_type, p_min, p_max) : 0; // -1 when *p_min < *p_max, == 0 when *p_min == *p_max\n            if (p_min == NULL || p_max == NULL || clamp_range_dir < 0)\n                clamp_enabled = true;\n            else if (clamp_range_dir == 0)\n                clamp_enabled = DataTypeIsZero(data_type, p_min) ? ((flags & ImGuiSliderFlags_ClampZeroRange) != 0) : true;\n        }\n        return TempInputScalar(frame_bb, id, label, data_type, p_data, format, clamp_enabled ? p_min : NULL, clamp_enabled ? p_max : NULL);\n    }\n\n    // Draw frame\n    const ImU32 frame_col = GetColorU32(g.ActiveId == id ? ImGuiCol_FrameBgActive : hovered ? ImGuiCol_FrameBgHovered : ImGuiCol_FrameBg);\n    RenderNavCursor(frame_bb, id);\n    RenderFrame(frame_bb.Min, frame_bb.Max, frame_col, true, style.FrameRounding);\n\n    // Drag behavior\n    const bool value_changed = DragBehavior(id, data_type, p_data, v_speed, p_min, p_max, format, flags);\n    if (value_changed)\n        MarkItemEdited(id);\n\n    // Display value using user-provided display format so user can add prefix/suffix/decorations to the value.\n    char value_buf[64];\n    const char* value_buf_end = value_buf + DataTypeFormatString(value_buf, IM_ARRAYSIZE(value_buf), data_type, p_data, format);\n    if (g.LogEnabled)\n        LogSetNextTextDecoration(\"{\", \"}\");\n    RenderTextClipped(frame_bb.Min, frame_bb.Max, value_buf, value_buf_end, NULL, ImVec2(0.5f, 0.5f));\n\n    if (label_size.x > 0.0f)\n        RenderText(ImVec2(frame_bb.Max.x + style.ItemInnerSpacing.x, frame_bb.Min.y + style.FramePadding.y), label);\n\n    IMGUI_TEST_ENGINE_ITEM_INFO(id, label, g.LastItemData.StatusFlags | (temp_input_allowed ? ImGuiItemStatusFlags_Inputable : 0));\n    return value_changed;\n}\n\nbool ImGui::DragScalarN(const char* label, ImGuiDataType data_type, void* p_data, int components, float v_speed, const void* p_min, const void* p_max, const char* format, ImGuiSliderFlags flags)\n{\n    ImGuiWindow* window = GetCurrentWindow();\n    if (window->SkipItems)\n        return false;\n\n    ImGuiContext& g = *GImGui;\n    bool value_changed = false;\n    BeginGroup();\n    PushID(label);\n    PushMultiItemsWidths(components, CalcItemWidth());\n    size_t type_size = GDataTypeInfo[data_type].Size;\n    for (int i = 0; i < components; i++)\n    {\n        PushID(i);\n        if (i > 0)\n            SameLine(0, g.Style.ItemInnerSpacing.x);\n        value_changed |= DragScalar(\"\", data_type, p_data, v_speed, p_min, p_max, format, flags);\n        PopID();\n        PopItemWidth();\n        p_data = (void*)((char*)p_data + type_size);\n    }\n    PopID();\n\n    const char* label_end = FindRenderedTextEnd(label);\n    if (label != label_end)\n    {\n        SameLine(0, g.Style.ItemInnerSpacing.x);\n        TextEx(label, label_end);\n    }\n\n    EndGroup();\n    return value_changed;\n}\n\nbool ImGui::DragFloat(const char* label, float* v, float v_speed, float v_min, float v_max, const char* format, ImGuiSliderFlags flags)\n{\n    return DragScalar(label, ImGuiDataType_Float, v, v_speed, &v_min, &v_max, format, flags);\n}\n\nbool ImGui::DragFloat2(const char* label, float v[2], float v_speed, float v_min, float v_max, const char* format, ImGuiSliderFlags flags)\n{\n    return DragScalarN(label, ImGuiDataType_Float, v, 2, v_speed, &v_min, &v_max, format, flags);\n}\n\nbool ImGui::DragFloat3(const char* label, float v[3], float v_speed, float v_min, float v_max, const char* format, ImGuiSliderFlags flags)\n{\n    return DragScalarN(label, ImGuiDataType_Float, v, 3, v_speed, &v_min, &v_max, format, flags);\n}\n\nbool ImGui::DragFloat4(const char* label, float v[4], float v_speed, float v_min, float v_max, const char* format, ImGuiSliderFlags flags)\n{\n    return DragScalarN(label, ImGuiDataType_Float, v, 4, v_speed, &v_min, &v_max, format, flags);\n}\n\n// NB: You likely want to specify the ImGuiSliderFlags_AlwaysClamp when using this.\nbool ImGui::DragFloatRange2(const char* label, float* v_current_min, float* v_current_max, float v_speed, float v_min, float v_max, const char* format, const char* format_max, ImGuiSliderFlags flags)\n{\n    ImGuiWindow* window = GetCurrentWindow();\n    if (window->SkipItems)\n        return false;\n\n    ImGuiContext& g = *GImGui;\n    PushID(label);\n    BeginGroup();\n    PushMultiItemsWidths(2, CalcItemWidth());\n\n    float min_min = (v_min >= v_max) ? -FLT_MAX : v_min;\n    float min_max = (v_min >= v_max) ? *v_current_max : ImMin(v_max, *v_current_max);\n    ImGuiSliderFlags min_flags = flags | ((min_min == min_max) ? ImGuiSliderFlags_ReadOnly : 0);\n    bool value_changed = DragScalar(\"##min\", ImGuiDataType_Float, v_current_min, v_speed, &min_min, &min_max, format, min_flags);\n    PopItemWidth();\n    SameLine(0, g.Style.ItemInnerSpacing.x);\n\n    float max_min = (v_min >= v_max) ? *v_current_min : ImMax(v_min, *v_current_min);\n    float max_max = (v_min >= v_max) ? FLT_MAX : v_max;\n    ImGuiSliderFlags max_flags = flags | ((max_min == max_max) ? ImGuiSliderFlags_ReadOnly : 0);\n    value_changed |= DragScalar(\"##max\", ImGuiDataType_Float, v_current_max, v_speed, &max_min, &max_max, format_max ? format_max : format, max_flags);\n    PopItemWidth();\n    SameLine(0, g.Style.ItemInnerSpacing.x);\n\n    TextEx(label, FindRenderedTextEnd(label));\n    EndGroup();\n    PopID();\n\n    return value_changed;\n}\n\n// NB: v_speed is float to allow adjusting the drag speed with more precision\nbool ImGui::DragInt(const char* label, int* v, float v_speed, int v_min, int v_max, const char* format, ImGuiSliderFlags flags)\n{\n    return DragScalar(label, ImGuiDataType_S32, v, v_speed, &v_min, &v_max, format, flags);\n}\n\nbool ImGui::DragInt2(const char* label, int v[2], float v_speed, int v_min, int v_max, const char* format, ImGuiSliderFlags flags)\n{\n    return DragScalarN(label, ImGuiDataType_S32, v, 2, v_speed, &v_min, &v_max, format, flags);\n}\n\nbool ImGui::DragInt3(const char* label, int v[3], float v_speed, int v_min, int v_max, const char* format, ImGuiSliderFlags flags)\n{\n    return DragScalarN(label, ImGuiDataType_S32, v, 3, v_speed, &v_min, &v_max, format, flags);\n}\n\nbool ImGui::DragInt4(const char* label, int v[4], float v_speed, int v_min, int v_max, const char* format, ImGuiSliderFlags flags)\n{\n    return DragScalarN(label, ImGuiDataType_S32, v, 4, v_speed, &v_min, &v_max, format, flags);\n}\n\n// NB: You likely want to specify the ImGuiSliderFlags_AlwaysClamp when using this.\nbool ImGui::DragIntRange2(const char* label, int* v_current_min, int* v_current_max, float v_speed, int v_min, int v_max, const char* format, const char* format_max, ImGuiSliderFlags flags)\n{\n    ImGuiWindow* window = GetCurrentWindow();\n    if (window->SkipItems)\n        return false;\n\n    ImGuiContext& g = *GImGui;\n    PushID(label);\n    BeginGroup();\n    PushMultiItemsWidths(2, CalcItemWidth());\n\n    int min_min = (v_min >= v_max) ? INT_MIN : v_min;\n    int min_max = (v_min >= v_max) ? *v_current_max : ImMin(v_max, *v_current_max);\n    ImGuiSliderFlags min_flags = flags | ((min_min == min_max) ? ImGuiSliderFlags_ReadOnly : 0);\n    bool value_changed = DragInt(\"##min\", v_current_min, v_speed, min_min, min_max, format, min_flags);\n    PopItemWidth();\n    SameLine(0, g.Style.ItemInnerSpacing.x);\n\n    int max_min = (v_min >= v_max) ? *v_current_min : ImMax(v_min, *v_current_min);\n    int max_max = (v_min >= v_max) ? INT_MAX : v_max;\n    ImGuiSliderFlags max_flags = flags | ((max_min == max_max) ? ImGuiSliderFlags_ReadOnly : 0);\n    value_changed |= DragInt(\"##max\", v_current_max, v_speed, max_min, max_max, format_max ? format_max : format, max_flags);\n    PopItemWidth();\n    SameLine(0, g.Style.ItemInnerSpacing.x);\n\n    TextEx(label, FindRenderedTextEnd(label));\n    EndGroup();\n    PopID();\n\n    return value_changed;\n}\n\n//-------------------------------------------------------------------------\n// [SECTION] Widgets: SliderScalar, SliderFloat, SliderInt, etc.\n//-------------------------------------------------------------------------\n// - ScaleRatioFromValueT<> [Internal]\n// - ScaleValueFromRatioT<> [Internal]\n// - SliderBehaviorT<>() [Internal]\n// - SliderBehavior() [Internal]\n// - SliderScalar()\n// - SliderScalarN()\n// - SliderFloat()\n// - SliderFloat2()\n// - SliderFloat3()\n// - SliderFloat4()\n// - SliderAngle()\n// - SliderInt()\n// - SliderInt2()\n// - SliderInt3()\n// - SliderInt4()\n// - VSliderScalar()\n// - VSliderFloat()\n// - VSliderInt()\n//-------------------------------------------------------------------------\n\n// Convert a value v in the output space of a slider into a parametric position on the slider itself (the logical opposite of ScaleValueFromRatioT)\ntemplate<typename TYPE, typename SIGNEDTYPE, typename FLOATTYPE>\nfloat ImGui::ScaleRatioFromValueT(ImGuiDataType data_type, TYPE v, TYPE v_min, TYPE v_max, bool is_logarithmic, float logarithmic_zero_epsilon, float zero_deadzone_halfsize)\n{\n    if (v_min == v_max)\n        return 0.0f;\n    IM_UNUSED(data_type);\n\n    const TYPE v_clamped = (v_min < v_max) ? ImClamp(v, v_min, v_max) : ImClamp(v, v_max, v_min);\n    if (is_logarithmic)\n    {\n        bool flipped = v_max < v_min;\n\n        if (flipped) // Handle the case where the range is backwards\n            ImSwap(v_min, v_max);\n\n        // Fudge min/max to avoid getting close to log(0)\n        FLOATTYPE v_min_fudged = (ImAbs((FLOATTYPE)v_min) < logarithmic_zero_epsilon) ? ((v_min < 0.0f) ? -logarithmic_zero_epsilon : logarithmic_zero_epsilon) : (FLOATTYPE)v_min;\n        FLOATTYPE v_max_fudged = (ImAbs((FLOATTYPE)v_max) < logarithmic_zero_epsilon) ? ((v_max < 0.0f) ? -logarithmic_zero_epsilon : logarithmic_zero_epsilon) : (FLOATTYPE)v_max;\n\n        // Awkward special cases - we need ranges of the form (-100 .. 0) to convert to (-100 .. -epsilon), not (-100 .. epsilon)\n        if ((v_min == 0.0f) && (v_max < 0.0f))\n            v_min_fudged = -logarithmic_zero_epsilon;\n        else if ((v_max == 0.0f) && (v_min < 0.0f))\n            v_max_fudged = -logarithmic_zero_epsilon;\n\n        float result;\n        if (v_clamped <= v_min_fudged)\n            result = 0.0f; // Workaround for values that are in-range but below our fudge\n        else if (v_clamped >= v_max_fudged)\n            result = 1.0f; // Workaround for values that are in-range but above our fudge\n        else if ((v_min * v_max) < 0.0f) // Range crosses zero, so split into two portions\n        {\n            float zero_point_center = (-(float)v_min) / ((float)v_max - (float)v_min); // The zero point in parametric space.  There's an argument we should take the logarithmic nature into account when calculating this, but for now this should do (and the most common case of a symmetrical range works fine)\n            float zero_point_snap_L = zero_point_center - zero_deadzone_halfsize;\n            float zero_point_snap_R = zero_point_center + zero_deadzone_halfsize;\n            if (v == 0.0f)\n                result = zero_point_center; // Special case for exactly zero\n            else if (v < 0.0f)\n                result = (1.0f - (float)(ImLog(-(FLOATTYPE)v_clamped / logarithmic_zero_epsilon) / ImLog(-v_min_fudged / logarithmic_zero_epsilon))) * zero_point_snap_L;\n            else\n                result = zero_point_snap_R + ((float)(ImLog((FLOATTYPE)v_clamped / logarithmic_zero_epsilon) / ImLog(v_max_fudged / logarithmic_zero_epsilon)) * (1.0f - zero_point_snap_R));\n        }\n        else if ((v_min < 0.0f) || (v_max < 0.0f)) // Entirely negative slider\n            result = 1.0f - (float)(ImLog(-(FLOATTYPE)v_clamped / -v_max_fudged) / ImLog(-v_min_fudged / -v_max_fudged));\n        else\n            result = (float)(ImLog((FLOATTYPE)v_clamped / v_min_fudged) / ImLog(v_max_fudged / v_min_fudged));\n\n        return flipped ? (1.0f - result) : result;\n    }\n    else\n    {\n        // Linear slider\n        return (float)((FLOATTYPE)(SIGNEDTYPE)(v_clamped - v_min) / (FLOATTYPE)(SIGNEDTYPE)(v_max - v_min));\n    }\n}\n\n// Convert a parametric position on a slider into a value v in the output space (the logical opposite of ScaleRatioFromValueT)\ntemplate<typename TYPE, typename SIGNEDTYPE, typename FLOATTYPE>\nTYPE ImGui::ScaleValueFromRatioT(ImGuiDataType data_type, float t, TYPE v_min, TYPE v_max, bool is_logarithmic, float logarithmic_zero_epsilon, float zero_deadzone_halfsize)\n{\n    // We special-case the extents because otherwise our logarithmic fudging can lead to \"mathematically correct\"\n    // but non-intuitive behaviors like a fully-left slider not actually reaching the minimum value. Also generally simpler.\n    if (t <= 0.0f || v_min == v_max)\n        return v_min;\n    if (t >= 1.0f)\n        return v_max;\n\n    TYPE result = (TYPE)0;\n    if (is_logarithmic)\n    {\n        // Fudge min/max to avoid getting silly results close to zero\n        FLOATTYPE v_min_fudged = (ImAbs((FLOATTYPE)v_min) < logarithmic_zero_epsilon) ? ((v_min < 0.0f) ? -logarithmic_zero_epsilon : logarithmic_zero_epsilon) : (FLOATTYPE)v_min;\n        FLOATTYPE v_max_fudged = (ImAbs((FLOATTYPE)v_max) < logarithmic_zero_epsilon) ? ((v_max < 0.0f) ? -logarithmic_zero_epsilon : logarithmic_zero_epsilon) : (FLOATTYPE)v_max;\n\n        const bool flipped = v_max < v_min; // Check if range is \"backwards\"\n        if (flipped)\n            ImSwap(v_min_fudged, v_max_fudged);\n\n        // Awkward special case - we need ranges of the form (-100 .. 0) to convert to (-100 .. -epsilon), not (-100 .. epsilon)\n        if ((v_max == 0.0f) && (v_min < 0.0f))\n            v_max_fudged = -logarithmic_zero_epsilon;\n\n        float t_with_flip = flipped ? (1.0f - t) : t; // t, but flipped if necessary to account for us flipping the range\n\n        if ((v_min * v_max) < 0.0f) // Range crosses zero, so we have to do this in two parts\n        {\n            float zero_point_center = (-(float)ImMin(v_min, v_max)) / ImAbs((float)v_max - (float)v_min); // The zero point in parametric space\n            float zero_point_snap_L = zero_point_center - zero_deadzone_halfsize;\n            float zero_point_snap_R = zero_point_center + zero_deadzone_halfsize;\n            if (t_with_flip >= zero_point_snap_L && t_with_flip <= zero_point_snap_R)\n                result = (TYPE)0.0f; // Special case to make getting exactly zero possible (the epsilon prevents it otherwise)\n            else if (t_with_flip < zero_point_center)\n                result = (TYPE)-(logarithmic_zero_epsilon * ImPow(-v_min_fudged / logarithmic_zero_epsilon, (FLOATTYPE)(1.0f - (t_with_flip / zero_point_snap_L))));\n            else\n                result = (TYPE)(logarithmic_zero_epsilon * ImPow(v_max_fudged / logarithmic_zero_epsilon, (FLOATTYPE)((t_with_flip - zero_point_snap_R) / (1.0f - zero_point_snap_R))));\n        }\n        else if ((v_min < 0.0f) || (v_max < 0.0f)) // Entirely negative slider\n            result = (TYPE)-(-v_max_fudged * ImPow(-v_min_fudged / -v_max_fudged, (FLOATTYPE)(1.0f - t_with_flip)));\n        else\n            result = (TYPE)(v_min_fudged * ImPow(v_max_fudged / v_min_fudged, (FLOATTYPE)t_with_flip));\n    }\n    else\n    {\n        // Linear slider\n        const bool is_floating_point = (data_type == ImGuiDataType_Float) || (data_type == ImGuiDataType_Double);\n        if (is_floating_point)\n        {\n            result = ImLerp(v_min, v_max, t);\n        }\n        else if (t < 1.0)\n        {\n            // - For integer values we want the clicking position to match the grab box so we round above\n            //   This code is carefully tuned to work with large values (e.g. high ranges of U64) while preserving this property..\n            // - Not doing a *1.0 multiply at the end of a range as it tends to be lossy. While absolute aiming at a large s64/u64\n            //   range is going to be imprecise anyway, with this check we at least make the edge values matches expected limits.\n            FLOATTYPE v_new_off_f = (SIGNEDTYPE)(v_max - v_min) * t;\n            result = (TYPE)((SIGNEDTYPE)v_min + (SIGNEDTYPE)(v_new_off_f + (FLOATTYPE)(v_min > v_max ? -0.5 : 0.5)));\n        }\n    }\n\n    return result;\n}\n\n// FIXME: Try to move more of the code into shared SliderBehavior()\ntemplate<typename TYPE, typename SIGNEDTYPE, typename FLOATTYPE>\nbool ImGui::SliderBehaviorT(const ImRect& bb, ImGuiID id, ImGuiDataType data_type, TYPE* v, const TYPE v_min, const TYPE v_max, const char* format, ImGuiSliderFlags flags, ImRect* out_grab_bb)\n{\n    ImGuiContext& g = *GImGui;\n    const ImGuiStyle& style = g.Style;\n\n    const ImGuiAxis axis = (flags & ImGuiSliderFlags_Vertical) ? ImGuiAxis_Y : ImGuiAxis_X;\n    const bool is_logarithmic = (flags & ImGuiSliderFlags_Logarithmic) != 0;\n    const bool is_floating_point = (data_type == ImGuiDataType_Float) || (data_type == ImGuiDataType_Double);\n    const float v_range_f = (float)(v_min < v_max ? v_max - v_min : v_min - v_max); // We don't need high precision for what we do with it.\n\n    // Calculate bounds\n    const float grab_padding = 2.0f; // FIXME: Should be part of style.\n    const float slider_sz = (bb.Max[axis] - bb.Min[axis]) - grab_padding * 2.0f;\n    float grab_sz = style.GrabMinSize;\n    if (!is_floating_point && v_range_f >= 0.0f)                         // v_range_f < 0 may happen on integer overflows\n        grab_sz = ImMax(slider_sz / (v_range_f + 1), style.GrabMinSize); // For integer sliders: if possible have the grab size represent 1 unit\n    grab_sz = ImMin(grab_sz, slider_sz);\n    const float slider_usable_sz = slider_sz - grab_sz;\n    const float slider_usable_pos_min = bb.Min[axis] + grab_padding + grab_sz * 0.5f;\n    const float slider_usable_pos_max = bb.Max[axis] - grab_padding - grab_sz * 0.5f;\n\n    float logarithmic_zero_epsilon = 0.0f; // Only valid when is_logarithmic is true\n    float zero_deadzone_halfsize = 0.0f; // Only valid when is_logarithmic is true\n    if (is_logarithmic)\n    {\n        // When using logarithmic sliders, we need to clamp to avoid hitting zero, but our choice of clamp value greatly affects slider precision. We attempt to use the specified precision to estimate a good lower bound.\n        const int decimal_precision = is_floating_point ? ImParseFormatPrecision(format, 3) : 1;\n        logarithmic_zero_epsilon = ImPow(0.1f, (float)decimal_precision);\n        zero_deadzone_halfsize = (style.LogSliderDeadzone * 0.5f) / ImMax(slider_usable_sz, 1.0f);\n    }\n\n    // Process interacting with the slider\n    bool value_changed = false;\n    if (g.ActiveId == id)\n    {\n        bool set_new_value = false;\n        float clicked_t = 0.0f;\n        if (g.ActiveIdSource == ImGuiInputSource_Mouse)\n        {\n            if (!g.IO.MouseDown[0])\n            {\n                ClearActiveID();\n            }\n            else\n            {\n                const float mouse_abs_pos = g.IO.MousePos[axis];\n                if (g.ActiveIdIsJustActivated)\n                {\n                    float grab_t = ScaleRatioFromValueT<TYPE, SIGNEDTYPE, FLOATTYPE>(data_type, *v, v_min, v_max, is_logarithmic, logarithmic_zero_epsilon, zero_deadzone_halfsize);\n                    if (axis == ImGuiAxis_Y)\n                        grab_t = 1.0f - grab_t;\n                    const float grab_pos = ImLerp(slider_usable_pos_min, slider_usable_pos_max, grab_t);\n                    const bool clicked_around_grab = (mouse_abs_pos >= grab_pos - grab_sz * 0.5f - 1.0f) && (mouse_abs_pos <= grab_pos + grab_sz * 0.5f + 1.0f); // No harm being extra generous here.\n                    g.SliderGrabClickOffset = (clicked_around_grab && is_floating_point) ? mouse_abs_pos - grab_pos : 0.0f;\n                }\n                if (slider_usable_sz > 0.0f)\n                    clicked_t = ImSaturate((mouse_abs_pos - g.SliderGrabClickOffset - slider_usable_pos_min) / slider_usable_sz);\n                if (axis == ImGuiAxis_Y)\n                    clicked_t = 1.0f - clicked_t;\n                set_new_value = true;\n            }\n        }\n        else if (g.ActiveIdSource == ImGuiInputSource_Keyboard || g.ActiveIdSource == ImGuiInputSource_Gamepad)\n        {\n            if (g.ActiveIdIsJustActivated)\n            {\n                g.SliderCurrentAccum = 0.0f; // Reset any stored nav delta upon activation\n                g.SliderCurrentAccumDirty = false;\n            }\n\n            float input_delta = (axis == ImGuiAxis_X) ? GetNavTweakPressedAmount(axis) : -GetNavTweakPressedAmount(axis);\n            if (input_delta != 0.0f)\n            {\n                const bool tweak_slow = IsKeyDown((g.NavInputSource == ImGuiInputSource_Gamepad) ? ImGuiKey_NavGamepadTweakSlow : ImGuiKey_NavKeyboardTweakSlow);\n                const bool tweak_fast = IsKeyDown((g.NavInputSource == ImGuiInputSource_Gamepad) ? ImGuiKey_NavGamepadTweakFast : ImGuiKey_NavKeyboardTweakFast);\n                const int decimal_precision = is_floating_point ? ImParseFormatPrecision(format, 3) : 0;\n                if (decimal_precision > 0)\n                {\n                    input_delta /= 100.0f; // Keyboard/Gamepad tweak speeds in % of slider bounds\n                    if (tweak_slow)\n                        input_delta /= 10.0f;\n                }\n                else\n                {\n                    if ((v_range_f >= -100.0f && v_range_f <= 100.0f && v_range_f != 0.0f) || tweak_slow)\n                        input_delta = ((input_delta < 0.0f) ? -1.0f : +1.0f) / v_range_f; // Keyboard/Gamepad tweak speeds in integer steps\n                    else\n                        input_delta /= 100.0f;\n                }\n                if (tweak_fast)\n                    input_delta *= 10.0f;\n\n                g.SliderCurrentAccum += input_delta;\n                g.SliderCurrentAccumDirty = true;\n            }\n\n            float delta = g.SliderCurrentAccum;\n            if (g.NavActivatePressedId == id && !g.ActiveIdIsJustActivated)\n            {\n                ClearActiveID();\n            }\n            else if (g.SliderCurrentAccumDirty)\n            {\n                clicked_t = ScaleRatioFromValueT<TYPE, SIGNEDTYPE, FLOATTYPE>(data_type, *v, v_min, v_max, is_logarithmic, logarithmic_zero_epsilon, zero_deadzone_halfsize);\n\n                if ((clicked_t >= 1.0f && delta > 0.0f) || (clicked_t <= 0.0f && delta < 0.0f)) // This is to avoid applying the saturation when already past the limits\n                {\n                    set_new_value = false;\n                    g.SliderCurrentAccum = 0.0f; // If pushing up against the limits, don't continue to accumulate\n                }\n                else\n                {\n                    set_new_value = true;\n                    float old_clicked_t = clicked_t;\n                    clicked_t = ImSaturate(clicked_t + delta);\n\n                    // Calculate what our \"new\" clicked_t will be, and thus how far we actually moved the slider, and subtract this from the accumulator\n                    TYPE v_new = ScaleValueFromRatioT<TYPE, SIGNEDTYPE, FLOATTYPE>(data_type, clicked_t, v_min, v_max, is_logarithmic, logarithmic_zero_epsilon, zero_deadzone_halfsize);\n                    if (is_floating_point && !(flags & ImGuiSliderFlags_NoRoundToFormat))\n                        v_new = RoundScalarWithFormatT<TYPE>(format, data_type, v_new);\n                    float new_clicked_t = ScaleRatioFromValueT<TYPE, SIGNEDTYPE, FLOATTYPE>(data_type, v_new, v_min, v_max, is_logarithmic, logarithmic_zero_epsilon, zero_deadzone_halfsize);\n\n                    if (delta > 0)\n                        g.SliderCurrentAccum -= ImMin(new_clicked_t - old_clicked_t, delta);\n                    else\n                        g.SliderCurrentAccum -= ImMax(new_clicked_t - old_clicked_t, delta);\n                }\n\n                g.SliderCurrentAccumDirty = false;\n            }\n        }\n\n        if (set_new_value)\n            if ((g.LastItemData.ItemFlags & ImGuiItemFlags_ReadOnly) || (flags & ImGuiSliderFlags_ReadOnly))\n                set_new_value = false;\n\n        if (set_new_value)\n        {\n            TYPE v_new = ScaleValueFromRatioT<TYPE, SIGNEDTYPE, FLOATTYPE>(data_type, clicked_t, v_min, v_max, is_logarithmic, logarithmic_zero_epsilon, zero_deadzone_halfsize);\n\n            // Round to user desired precision based on format string\n            if (is_floating_point && !(flags & ImGuiSliderFlags_NoRoundToFormat))\n                v_new = RoundScalarWithFormatT<TYPE>(format, data_type, v_new);\n\n            // Apply result\n            if (*v != v_new)\n            {\n                *v = v_new;\n                value_changed = true;\n            }\n        }\n    }\n\n    if (slider_sz < 1.0f)\n    {\n        *out_grab_bb = ImRect(bb.Min, bb.Min);\n    }\n    else\n    {\n        // Output grab position so it can be displayed by the caller\n        float grab_t = ScaleRatioFromValueT<TYPE, SIGNEDTYPE, FLOATTYPE>(data_type, *v, v_min, v_max, is_logarithmic, logarithmic_zero_epsilon, zero_deadzone_halfsize);\n        if (axis == ImGuiAxis_Y)\n            grab_t = 1.0f - grab_t;\n        const float grab_pos = ImLerp(slider_usable_pos_min, slider_usable_pos_max, grab_t);\n        if (axis == ImGuiAxis_X)\n            *out_grab_bb = ImRect(grab_pos - grab_sz * 0.5f, bb.Min.y + grab_padding, grab_pos + grab_sz * 0.5f, bb.Max.y - grab_padding);\n        else\n            *out_grab_bb = ImRect(bb.Min.x + grab_padding, grab_pos - grab_sz * 0.5f, bb.Max.x - grab_padding, grab_pos + grab_sz * 0.5f);\n    }\n\n    return value_changed;\n}\n\n// For 32-bit and larger types, slider bounds are limited to half the natural type range.\n// So e.g. an integer Slider between INT_MAX-10 and INT_MAX will fail, but an integer Slider between INT_MAX/2-10 and INT_MAX/2 will be ok.\n// It would be possible to lift that limitation with some work but it doesn't seem to be worth it for sliders.\nbool ImGui::SliderBehavior(const ImRect& bb, ImGuiID id, ImGuiDataType data_type, void* p_v, const void* p_min, const void* p_max, const char* format, ImGuiSliderFlags flags, ImRect* out_grab_bb)\n{\n    // Read imgui.cpp \"API BREAKING CHANGES\" section for 1.78 if you hit this assert.\n    IM_ASSERT((flags == 1 || (flags & ImGuiSliderFlags_InvalidMask_) == 0) && \"Invalid ImGuiSliderFlags flags! Has the legacy 'float power' argument been mistakenly cast to flags? Call function with ImGuiSliderFlags_Logarithmic flags instead.\");\n    IM_ASSERT((flags & ImGuiSliderFlags_WrapAround) == 0); // Not supported by SliderXXX(), only by DragXXX()\n\n    switch (data_type)\n    {\n    case ImGuiDataType_S8:  { ImS32 v32 = (ImS32)*(ImS8*)p_v;  bool r = SliderBehaviorT<ImS32, ImS32, float>(bb, id, ImGuiDataType_S32, &v32, *(const ImS8*)p_min,  *(const ImS8*)p_max,  format, flags, out_grab_bb); if (r) *(ImS8*)p_v  = (ImS8)v32;  return r; }\n    case ImGuiDataType_U8:  { ImU32 v32 = (ImU32)*(ImU8*)p_v;  bool r = SliderBehaviorT<ImU32, ImS32, float>(bb, id, ImGuiDataType_U32, &v32, *(const ImU8*)p_min,  *(const ImU8*)p_max,  format, flags, out_grab_bb); if (r) *(ImU8*)p_v  = (ImU8)v32;  return r; }\n    case ImGuiDataType_S16: { ImS32 v32 = (ImS32)*(ImS16*)p_v; bool r = SliderBehaviorT<ImS32, ImS32, float>(bb, id, ImGuiDataType_S32, &v32, *(const ImS16*)p_min, *(const ImS16*)p_max, format, flags, out_grab_bb); if (r) *(ImS16*)p_v = (ImS16)v32; return r; }\n    case ImGuiDataType_U16: { ImU32 v32 = (ImU32)*(ImU16*)p_v; bool r = SliderBehaviorT<ImU32, ImS32, float>(bb, id, ImGuiDataType_U32, &v32, *(const ImU16*)p_min, *(const ImU16*)p_max, format, flags, out_grab_bb); if (r) *(ImU16*)p_v = (ImU16)v32; return r; }\n    case ImGuiDataType_S32:\n        IM_ASSERT(*(const ImS32*)p_min >= IM_S32_MIN / 2 && *(const ImS32*)p_max <= IM_S32_MAX / 2);\n        return SliderBehaviorT<ImS32, ImS32, float >(bb, id, data_type, (ImS32*)p_v,  *(const ImS32*)p_min,  *(const ImS32*)p_max,  format, flags, out_grab_bb);\n    case ImGuiDataType_U32:\n        IM_ASSERT(*(const ImU32*)p_max <= IM_U32_MAX / 2);\n        return SliderBehaviorT<ImU32, ImS32, float >(bb, id, data_type, (ImU32*)p_v,  *(const ImU32*)p_min,  *(const ImU32*)p_max,  format, flags, out_grab_bb);\n    case ImGuiDataType_S64:\n        IM_ASSERT(*(const ImS64*)p_min >= IM_S64_MIN / 2 && *(const ImS64*)p_max <= IM_S64_MAX / 2);\n        return SliderBehaviorT<ImS64, ImS64, double>(bb, id, data_type, (ImS64*)p_v,  *(const ImS64*)p_min,  *(const ImS64*)p_max,  format, flags, out_grab_bb);\n    case ImGuiDataType_U64:\n        IM_ASSERT(*(const ImU64*)p_max <= IM_U64_MAX / 2);\n        return SliderBehaviorT<ImU64, ImS64, double>(bb, id, data_type, (ImU64*)p_v,  *(const ImU64*)p_min,  *(const ImU64*)p_max,  format, flags, out_grab_bb);\n    case ImGuiDataType_Float:\n        IM_ASSERT(*(const float*)p_min >= -FLT_MAX / 2.0f && *(const float*)p_max <= FLT_MAX / 2.0f);\n        return SliderBehaviorT<float, float, float >(bb, id, data_type, (float*)p_v,  *(const float*)p_min,  *(const float*)p_max,  format, flags, out_grab_bb);\n    case ImGuiDataType_Double:\n        IM_ASSERT(*(const double*)p_min >= -DBL_MAX / 2.0f && *(const double*)p_max <= DBL_MAX / 2.0f);\n        return SliderBehaviorT<double, double, double>(bb, id, data_type, (double*)p_v, *(const double*)p_min, *(const double*)p_max, format, flags, out_grab_bb);\n    case ImGuiDataType_COUNT: break;\n    }\n    IM_ASSERT(0);\n    return false;\n}\n\n// Note: p_data, p_min and p_max are _pointers_ to a memory address holding the data. For a slider, they are all required.\n// Read code of e.g. SliderFloat(), SliderInt() etc. or examples in 'Demo->Widgets->Data Types' to understand how to use this function directly.\nbool ImGui::SliderScalar(const char* label, ImGuiDataType data_type, void* p_data, const void* p_min, const void* p_max, const char* format, ImGuiSliderFlags flags)\n{\n    ImGuiWindow* window = GetCurrentWindow();\n    if (window->SkipItems)\n        return false;\n\n    ImGuiContext& g = *GImGui;\n    const ImGuiStyle& style = g.Style;\n    const ImGuiID id = window->GetID(label);\n    const float w = CalcItemWidth();\n\n    const ImVec2 label_size = CalcTextSize(label, NULL, true);\n    const ImRect frame_bb(window->DC.CursorPos, window->DC.CursorPos + ImVec2(w, label_size.y + style.FramePadding.y * 2.0f));\n    const ImRect total_bb(frame_bb.Min, frame_bb.Max + ImVec2(label_size.x > 0.0f ? style.ItemInnerSpacing.x + label_size.x : 0.0f, 0.0f));\n\n    const bool temp_input_allowed = (flags & ImGuiSliderFlags_NoInput) == 0;\n    ItemSize(total_bb, style.FramePadding.y);\n    if (!ItemAdd(total_bb, id, &frame_bb, temp_input_allowed ? ImGuiItemFlags_Inputable : 0))\n        return false;\n\n    // Default format string when passing NULL\n    if (format == NULL)\n        format = DataTypeGetInfo(data_type)->PrintFmt;\n\n    const bool hovered = ItemHoverable(frame_bb, id, g.LastItemData.ItemFlags);\n    bool temp_input_is_active = temp_input_allowed && TempInputIsActive(id);\n    if (!temp_input_is_active)\n    {\n        // Tabbing or CTRL+click on Slider turns it into an input box\n        const bool clicked = hovered && IsMouseClicked(0, ImGuiInputFlags_None, id);\n        const bool make_active = (clicked || g.NavActivateId == id);\n        if (make_active && clicked)\n            SetKeyOwner(ImGuiKey_MouseLeft, id);\n        if (make_active && temp_input_allowed)\n            if ((clicked && g.IO.KeyCtrl) || (g.NavActivateId == id && (g.NavActivateFlags & ImGuiActivateFlags_PreferInput)))\n                temp_input_is_active = true;\n\n        // Store initial value (not used by main lib but available as a convenience but some mods e.g. to revert)\n        if (make_active)\n            memcpy(&g.ActiveIdValueOnActivation, p_data, DataTypeGetInfo(data_type)->Size);\n\n        if (make_active && !temp_input_is_active)\n        {\n            SetActiveID(id, window);\n            SetFocusID(id, window);\n            FocusWindow(window);\n            g.ActiveIdUsingNavDirMask |= (1 << ImGuiDir_Left) | (1 << ImGuiDir_Right);\n        }\n    }\n\n    if (temp_input_is_active)\n    {\n        // Only clamp CTRL+Click input when ImGuiSliderFlags_ClampOnInput is set (generally via ImGuiSliderFlags_AlwaysClamp)\n        const bool clamp_enabled = (flags & ImGuiSliderFlags_ClampOnInput) != 0;\n        return TempInputScalar(frame_bb, id, label, data_type, p_data, format, clamp_enabled ? p_min : NULL, clamp_enabled ? p_max : NULL);\n    }\n\n    // Draw frame\n    const ImU32 frame_col = GetColorU32(g.ActiveId == id ? ImGuiCol_FrameBgActive : hovered ? ImGuiCol_FrameBgHovered : ImGuiCol_FrameBg);\n    RenderNavCursor(frame_bb, id);\n    RenderFrame(frame_bb.Min, frame_bb.Max, frame_col, true, g.Style.FrameRounding);\n\n    // Slider behavior\n    ImRect grab_bb;\n    const bool value_changed = SliderBehavior(frame_bb, id, data_type, p_data, p_min, p_max, format, flags, &grab_bb);\n    if (value_changed)\n        MarkItemEdited(id);\n\n    // Render grab\n    if (grab_bb.Max.x > grab_bb.Min.x)\n        window->DrawList->AddRectFilled(grab_bb.Min, grab_bb.Max, GetColorU32(g.ActiveId == id ? ImGuiCol_SliderGrabActive : ImGuiCol_SliderGrab), style.GrabRounding);\n\n    // Display value using user-provided display format so user can add prefix/suffix/decorations to the value.\n    char value_buf[64];\n    const char* value_buf_end = value_buf + DataTypeFormatString(value_buf, IM_ARRAYSIZE(value_buf), data_type, p_data, format);\n    if (g.LogEnabled)\n        LogSetNextTextDecoration(\"{\", \"}\");\n    RenderTextClipped(frame_bb.Min, frame_bb.Max, value_buf, value_buf_end, NULL, ImVec2(0.5f, 0.5f));\n\n    if (label_size.x > 0.0f)\n        RenderText(ImVec2(frame_bb.Max.x + style.ItemInnerSpacing.x, frame_bb.Min.y + style.FramePadding.y), label);\n\n    IMGUI_TEST_ENGINE_ITEM_INFO(id, label, g.LastItemData.StatusFlags | (temp_input_allowed ? ImGuiItemStatusFlags_Inputable : 0));\n    return value_changed;\n}\n\n// Add multiple sliders on 1 line for compact edition of multiple components\nbool ImGui::SliderScalarN(const char* label, ImGuiDataType data_type, void* v, int components, const void* v_min, const void* v_max, const char* format, ImGuiSliderFlags flags)\n{\n    ImGuiWindow* window = GetCurrentWindow();\n    if (window->SkipItems)\n        return false;\n\n    ImGuiContext& g = *GImGui;\n    bool value_changed = false;\n    BeginGroup();\n    PushID(label);\n    PushMultiItemsWidths(components, CalcItemWidth());\n    size_t type_size = GDataTypeInfo[data_type].Size;\n    for (int i = 0; i < components; i++)\n    {\n        PushID(i);\n        if (i > 0)\n            SameLine(0, g.Style.ItemInnerSpacing.x);\n        value_changed |= SliderScalar(\"\", data_type, v, v_min, v_max, format, flags);\n        PopID();\n        PopItemWidth();\n        v = (void*)((char*)v + type_size);\n    }\n    PopID();\n\n    const char* label_end = FindRenderedTextEnd(label);\n    if (label != label_end)\n    {\n        SameLine(0, g.Style.ItemInnerSpacing.x);\n        TextEx(label, label_end);\n    }\n\n    EndGroup();\n    return value_changed;\n}\n\nbool ImGui::SliderFloat(const char* label, float* v, float v_min, float v_max, const char* format, ImGuiSliderFlags flags)\n{\n    return SliderScalar(label, ImGuiDataType_Float, v, &v_min, &v_max, format, flags);\n}\n\nbool ImGui::SliderFloat2(const char* label, float v[2], float v_min, float v_max, const char* format, ImGuiSliderFlags flags)\n{\n    return SliderScalarN(label, ImGuiDataType_Float, v, 2, &v_min, &v_max, format, flags);\n}\n\nbool ImGui::SliderFloat3(const char* label, float v[3], float v_min, float v_max, const char* format, ImGuiSliderFlags flags)\n{\n    return SliderScalarN(label, ImGuiDataType_Float, v, 3, &v_min, &v_max, format, flags);\n}\n\nbool ImGui::SliderFloat4(const char* label, float v[4], float v_min, float v_max, const char* format, ImGuiSliderFlags flags)\n{\n    return SliderScalarN(label, ImGuiDataType_Float, v, 4, &v_min, &v_max, format, flags);\n}\n\nbool ImGui::SliderAngle(const char* label, float* v_rad, float v_degrees_min, float v_degrees_max, const char* format, ImGuiSliderFlags flags)\n{\n    if (format == NULL)\n        format = \"%.0f deg\";\n    float v_deg = (*v_rad) * 360.0f / (2 * IM_PI);\n    bool value_changed = SliderFloat(label, &v_deg, v_degrees_min, v_degrees_max, format, flags);\n    if (value_changed)\n        *v_rad = v_deg * (2 * IM_PI) / 360.0f;\n    return value_changed;\n}\n\nbool ImGui::SliderInt(const char* label, int* v, int v_min, int v_max, const char* format, ImGuiSliderFlags flags)\n{\n    return SliderScalar(label, ImGuiDataType_S32, v, &v_min, &v_max, format, flags);\n}\n\nbool ImGui::SliderInt2(const char* label, int v[2], int v_min, int v_max, const char* format, ImGuiSliderFlags flags)\n{\n    return SliderScalarN(label, ImGuiDataType_S32, v, 2, &v_min, &v_max, format, flags);\n}\n\nbool ImGui::SliderInt3(const char* label, int v[3], int v_min, int v_max, const char* format, ImGuiSliderFlags flags)\n{\n    return SliderScalarN(label, ImGuiDataType_S32, v, 3, &v_min, &v_max, format, flags);\n}\n\nbool ImGui::SliderInt4(const char* label, int v[4], int v_min, int v_max, const char* format, ImGuiSliderFlags flags)\n{\n    return SliderScalarN(label, ImGuiDataType_S32, v, 4, &v_min, &v_max, format, flags);\n}\n\nbool ImGui::VSliderScalar(const char* label, const ImVec2& size, ImGuiDataType data_type, void* p_data, const void* p_min, const void* p_max, const char* format, ImGuiSliderFlags flags)\n{\n    ImGuiWindow* window = GetCurrentWindow();\n    if (window->SkipItems)\n        return false;\n\n    ImGuiContext& g = *GImGui;\n    const ImGuiStyle& style = g.Style;\n    const ImGuiID id = window->GetID(label);\n\n    const ImVec2 label_size = CalcTextSize(label, NULL, true);\n    const ImRect frame_bb(window->DC.CursorPos, window->DC.CursorPos + size);\n    const ImRect bb(frame_bb.Min, frame_bb.Max + ImVec2(label_size.x > 0.0f ? style.ItemInnerSpacing.x + label_size.x : 0.0f, 0.0f));\n\n    ItemSize(bb, style.FramePadding.y);\n    if (!ItemAdd(frame_bb, id))\n        return false;\n\n    // Default format string when passing NULL\n    if (format == NULL)\n        format = DataTypeGetInfo(data_type)->PrintFmt;\n\n    const bool hovered = ItemHoverable(frame_bb, id, g.LastItemData.ItemFlags);\n    const bool clicked = hovered && IsMouseClicked(0, ImGuiInputFlags_None, id);\n    if (clicked || g.NavActivateId == id)\n    {\n        if (clicked)\n            SetKeyOwner(ImGuiKey_MouseLeft, id);\n        SetActiveID(id, window);\n        SetFocusID(id, window);\n        FocusWindow(window);\n        g.ActiveIdUsingNavDirMask |= (1 << ImGuiDir_Up) | (1 << ImGuiDir_Down);\n    }\n\n    // Draw frame\n    const ImU32 frame_col = GetColorU32(g.ActiveId == id ? ImGuiCol_FrameBgActive : hovered ? ImGuiCol_FrameBgHovered : ImGuiCol_FrameBg);\n    RenderNavCursor(frame_bb, id);\n    RenderFrame(frame_bb.Min, frame_bb.Max, frame_col, true, g.Style.FrameRounding);\n\n    // Slider behavior\n    ImRect grab_bb;\n    const bool value_changed = SliderBehavior(frame_bb, id, data_type, p_data, p_min, p_max, format, flags | ImGuiSliderFlags_Vertical, &grab_bb);\n    if (value_changed)\n        MarkItemEdited(id);\n\n    // Render grab\n    if (grab_bb.Max.y > grab_bb.Min.y)\n        window->DrawList->AddRectFilled(grab_bb.Min, grab_bb.Max, GetColorU32(g.ActiveId == id ? ImGuiCol_SliderGrabActive : ImGuiCol_SliderGrab), style.GrabRounding);\n\n    // Display value using user-provided display format so user can add prefix/suffix/decorations to the value.\n    // For the vertical slider we allow centered text to overlap the frame padding\n    char value_buf[64];\n    const char* value_buf_end = value_buf + DataTypeFormatString(value_buf, IM_ARRAYSIZE(value_buf), data_type, p_data, format);\n    RenderTextClipped(ImVec2(frame_bb.Min.x, frame_bb.Min.y + style.FramePadding.y), frame_bb.Max, value_buf, value_buf_end, NULL, ImVec2(0.5f, 0.0f));\n    if (label_size.x > 0.0f)\n        RenderText(ImVec2(frame_bb.Max.x + style.ItemInnerSpacing.x, frame_bb.Min.y + style.FramePadding.y), label);\n\n    return value_changed;\n}\n\nbool ImGui::VSliderFloat(const char* label, const ImVec2& size, float* v, float v_min, float v_max, const char* format, ImGuiSliderFlags flags)\n{\n    return VSliderScalar(label, size, ImGuiDataType_Float, v, &v_min, &v_max, format, flags);\n}\n\nbool ImGui::VSliderInt(const char* label, const ImVec2& size, int* v, int v_min, int v_max, const char* format, ImGuiSliderFlags flags)\n{\n    return VSliderScalar(label, size, ImGuiDataType_S32, v, &v_min, &v_max, format, flags);\n}\n\n//-------------------------------------------------------------------------\n// [SECTION] Widgets: InputScalar, InputFloat, InputInt, etc.\n//-------------------------------------------------------------------------\n// - ImParseFormatFindStart() [Internal]\n// - ImParseFormatFindEnd() [Internal]\n// - ImParseFormatTrimDecorations() [Internal]\n// - ImParseFormatSanitizeForPrinting() [Internal]\n// - ImParseFormatSanitizeForScanning() [Internal]\n// - ImParseFormatPrecision() [Internal]\n// - TempInputTextScalar() [Internal]\n// - InputScalar()\n// - InputScalarN()\n// - InputFloat()\n// - InputFloat2()\n// - InputFloat3()\n// - InputFloat4()\n// - InputInt()\n// - InputInt2()\n// - InputInt3()\n// - InputInt4()\n// - InputDouble()\n//-------------------------------------------------------------------------\n\n// We don't use strchr() because our strings are usually very short and often start with '%'\nconst char* ImParseFormatFindStart(const char* fmt)\n{\n    while (char c = fmt[0])\n    {\n        if (c == '%' && fmt[1] != '%')\n            return fmt;\n        else if (c == '%')\n            fmt++;\n        fmt++;\n    }\n    return fmt;\n}\n\nconst char* ImParseFormatFindEnd(const char* fmt)\n{\n    // Printf/scanf types modifiers: I/L/h/j/l/t/w/z. Other uppercase letters qualify as types aka end of the format.\n    if (fmt[0] != '%')\n        return fmt;\n    const unsigned int ignored_uppercase_mask = (1 << ('I'-'A')) | (1 << ('L'-'A'));\n    const unsigned int ignored_lowercase_mask = (1 << ('h'-'a')) | (1 << ('j'-'a')) | (1 << ('l'-'a')) | (1 << ('t'-'a')) | (1 << ('w'-'a')) | (1 << ('z'-'a'));\n    for (char c; (c = *fmt) != 0; fmt++)\n    {\n        if (c >= 'A' && c <= 'Z' && ((1 << (c - 'A')) & ignored_uppercase_mask) == 0)\n            return fmt + 1;\n        if (c >= 'a' && c <= 'z' && ((1 << (c - 'a')) & ignored_lowercase_mask) == 0)\n            return fmt + 1;\n    }\n    return fmt;\n}\n\n// Extract the format out of a format string with leading or trailing decorations\n//  fmt = \"blah blah\"  -> return \"\"\n//  fmt = \"%.3f\"       -> return fmt\n//  fmt = \"hello %.3f\" -> return fmt + 6\n//  fmt = \"%.3f hello\" -> return buf written with \"%.3f\"\nconst char* ImParseFormatTrimDecorations(const char* fmt, char* buf, size_t buf_size)\n{\n    const char* fmt_start = ImParseFormatFindStart(fmt);\n    if (fmt_start[0] != '%')\n        return \"\";\n    const char* fmt_end = ImParseFormatFindEnd(fmt_start);\n    if (fmt_end[0] == 0) // If we only have leading decoration, we don't need to copy the data.\n        return fmt_start;\n    ImStrncpy(buf, fmt_start, ImMin((size_t)(fmt_end - fmt_start) + 1, buf_size));\n    return buf;\n}\n\n// Sanitize format\n// - Zero terminate so extra characters after format (e.g. \"%f123\") don't confuse atof/atoi\n// - stb_sprintf.h supports several new modifiers which format numbers in a way that also makes them incompatible atof/atoi.\nvoid ImParseFormatSanitizeForPrinting(const char* fmt_in, char* fmt_out, size_t fmt_out_size)\n{\n    const char* fmt_end = ImParseFormatFindEnd(fmt_in);\n    IM_UNUSED(fmt_out_size);\n    IM_ASSERT((size_t)(fmt_end - fmt_in + 1) < fmt_out_size); // Format is too long, let us know if this happens to you!\n    while (fmt_in < fmt_end)\n    {\n        char c = *fmt_in++;\n        if (c != '\\'' && c != '$' && c != '_') // Custom flags provided by stb_sprintf.h. POSIX 2008 also supports '.\n            *(fmt_out++) = c;\n    }\n    *fmt_out = 0; // Zero-terminate\n}\n\n// - For scanning we need to remove all width and precision fields and flags \"%+3.7f\" -> \"%f\". BUT don't strip types like \"%I64d\" which includes digits. ! \"%07I64d\" -> \"%I64d\"\nconst char* ImParseFormatSanitizeForScanning(const char* fmt_in, char* fmt_out, size_t fmt_out_size)\n{\n    const char* fmt_end = ImParseFormatFindEnd(fmt_in);\n    const char* fmt_out_begin = fmt_out;\n    IM_UNUSED(fmt_out_size);\n    IM_ASSERT((size_t)(fmt_end - fmt_in + 1) < fmt_out_size); // Format is too long, let us know if this happens to you!\n    bool has_type = false;\n    while (fmt_in < fmt_end)\n    {\n        char c = *fmt_in++;\n        if (!has_type && ((c >= '0' && c <= '9') || c == '.' || c == '+' || c == '#'))\n            continue;\n        has_type |= ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')); // Stop skipping digits\n        if (c != '\\'' && c != '$' && c != '_') // Custom flags provided by stb_sprintf.h. POSIX 2008 also supports '.\n            *(fmt_out++) = c;\n    }\n    *fmt_out = 0; // Zero-terminate\n    return fmt_out_begin;\n}\n\ntemplate<typename TYPE>\nstatic const char* ImAtoi(const char* src, TYPE* output)\n{\n    int negative = 0;\n    if (*src == '-') { negative = 1; src++; }\n    if (*src == '+') { src++; }\n    TYPE v = 0;\n    while (*src >= '0' && *src <= '9')\n        v = (v * 10) + (*src++ - '0');\n    *output = negative ? -v : v;\n    return src;\n}\n\n// Parse display precision back from the display format string\n// FIXME: This is still used by some navigation code path to infer a minimum tweak step, but we should aim to rework widgets so it isn't needed.\nint ImParseFormatPrecision(const char* fmt, int default_precision)\n{\n    fmt = ImParseFormatFindStart(fmt);\n    if (fmt[0] != '%')\n        return default_precision;\n    fmt++;\n    while (*fmt >= '0' && *fmt <= '9')\n        fmt++;\n    int precision = INT_MAX;\n    if (*fmt == '.')\n    {\n        fmt = ImAtoi<int>(fmt + 1, &precision);\n        if (precision < 0 || precision > 99)\n            precision = default_precision;\n    }\n    if (*fmt == 'e' || *fmt == 'E') // Maximum precision with scientific notation\n        precision = -1;\n    if ((*fmt == 'g' || *fmt == 'G') && precision == INT_MAX)\n        precision = -1;\n    return (precision == INT_MAX) ? default_precision : precision;\n}\n\n// Create text input in place of another active widget (e.g. used when doing a CTRL+Click on drag/slider widgets)\n// FIXME: Facilitate using this in variety of other situations.\n// FIXME: Among other things, setting ImGuiItemFlags_AllowDuplicateId in LastItemData is currently correct but\n// the expected relationship between TempInputXXX functions and LastItemData is a little fishy.\nbool ImGui::TempInputText(const ImRect& bb, ImGuiID id, const char* label, char* buf, int buf_size, ImGuiInputTextFlags flags)\n{\n    // On the first frame, g.TempInputTextId == 0, then on subsequent frames it becomes == id.\n    // We clear ActiveID on the first frame to allow the InputText() taking it back.\n    ImGuiContext& g = *GImGui;\n    const bool init = (g.TempInputId != id);\n    if (init)\n        ClearActiveID();\n\n    g.CurrentWindow->DC.CursorPos = bb.Min;\n    g.LastItemData.ItemFlags |= ImGuiItemFlags_AllowDuplicateId;\n    bool value_changed = InputTextEx(label, NULL, buf, buf_size, bb.GetSize(), flags | ImGuiInputTextFlags_MergedItem);\n    if (init)\n    {\n        // First frame we started displaying the InputText widget, we expect it to take the active id.\n        IM_ASSERT(g.ActiveId == id);\n        g.TempInputId = g.ActiveId;\n    }\n    return value_changed;\n}\n\n// Note that Drag/Slider functions are only forwarding the min/max values clamping values if the ImGuiSliderFlags_AlwaysClamp flag is set!\n// This is intended: this way we allow CTRL+Click manual input to set a value out of bounds, for maximum flexibility.\n// However this may not be ideal for all uses, as some user code may break on out of bound values.\nbool ImGui::TempInputScalar(const ImRect& bb, ImGuiID id, const char* label, ImGuiDataType data_type, void* p_data, const char* format, const void* p_clamp_min, const void* p_clamp_max)\n{\n    // FIXME: May need to clarify display behavior if format doesn't contain %.\n    // \"%d\" -> \"%d\" / \"There are %d items\" -> \"%d\" / \"items\" -> \"%d\" (fallback). Also see #6405\n    ImGuiContext& g = *GImGui;\n    const ImGuiDataTypeInfo* type_info = DataTypeGetInfo(data_type);\n    char fmt_buf[32];\n    char data_buf[32];\n    format = ImParseFormatTrimDecorations(format, fmt_buf, IM_ARRAYSIZE(fmt_buf));\n    if (format[0] == 0)\n        format = type_info->PrintFmt;\n    DataTypeFormatString(data_buf, IM_ARRAYSIZE(data_buf), data_type, p_data, format);\n    ImStrTrimBlanks(data_buf);\n\n    ImGuiInputTextFlags flags = ImGuiInputTextFlags_AutoSelectAll | (ImGuiInputTextFlags)ImGuiInputTextFlags_LocalizeDecimalPoint;\n    g.LastItemData.ItemFlags |= ImGuiItemFlags_NoMarkEdited; // Because TempInputText() uses ImGuiInputTextFlags_MergedItem it doesn't submit a new item, so we poke LastItemData.\n    bool value_changed = false;\n    if (TempInputText(bb, id, label, data_buf, IM_ARRAYSIZE(data_buf), flags))\n    {\n        // Backup old value\n        size_t data_type_size = type_info->Size;\n        ImGuiDataTypeStorage data_backup;\n        memcpy(&data_backup, p_data, data_type_size);\n\n        // Apply new value (or operations) then clamp\n        DataTypeApplyFromText(data_buf, data_type, p_data, format, NULL);\n        if (p_clamp_min || p_clamp_max)\n        {\n            if (p_clamp_min && p_clamp_max && DataTypeCompare(data_type, p_clamp_min, p_clamp_max) > 0)\n                ImSwap(p_clamp_min, p_clamp_max);\n            DataTypeClamp(data_type, p_data, p_clamp_min, p_clamp_max);\n        }\n\n        // Only mark as edited if new value is different\n        g.LastItemData.ItemFlags &= ~ImGuiItemFlags_NoMarkEdited;\n        value_changed = memcmp(&data_backup, p_data, data_type_size) != 0;\n        if (value_changed)\n            MarkItemEdited(id);\n    }\n    return value_changed;\n}\n\nvoid ImGui::SetNextItemRefVal(ImGuiDataType data_type, void* p_data)\n{\n    ImGuiContext& g = *GImGui;\n    g.NextItemData.HasFlags |= ImGuiNextItemDataFlags_HasRefVal;\n    memcpy(&g.NextItemData.RefVal, p_data, DataTypeGetInfo(data_type)->Size);\n}\n\n// Note: p_data, p_step, p_step_fast are _pointers_ to a memory address holding the data. For an Input widget, p_step and p_step_fast are optional.\n// Read code of e.g. InputFloat(), InputInt() etc. or examples in 'Demo->Widgets->Data Types' to understand how to use this function directly.\nbool ImGui::InputScalar(const char* label, ImGuiDataType data_type, void* p_data, const void* p_step, const void* p_step_fast, const char* format, ImGuiInputTextFlags flags)\n{\n    ImGuiWindow* window = GetCurrentWindow();\n    if (window->SkipItems)\n        return false;\n\n    ImGuiContext& g = *GImGui;\n    ImGuiStyle& style = g.Style;\n    IM_ASSERT((flags & ImGuiInputTextFlags_EnterReturnsTrue) == 0); // Not supported by InputScalar(). Please open an issue if you this would be useful to you. Otherwise use IsItemDeactivatedAfterEdit()!\n\n    if (format == NULL)\n        format = DataTypeGetInfo(data_type)->PrintFmt;\n\n    void* p_data_default = (g.NextItemData.HasFlags & ImGuiNextItemDataFlags_HasRefVal) ? &g.NextItemData.RefVal : &g.DataTypeZeroValue;\n\n    char buf[64];\n    if ((flags & ImGuiInputTextFlags_DisplayEmptyRefVal) && DataTypeCompare(data_type, p_data, p_data_default) == 0)\n        buf[0] = 0;\n    else\n        DataTypeFormatString(buf, IM_ARRAYSIZE(buf), data_type, p_data, format);\n\n    // Disable the MarkItemEdited() call in InputText but keep ImGuiItemStatusFlags_Edited.\n    // We call MarkItemEdited() ourselves by comparing the actual data rather than the string.\n    g.NextItemData.ItemFlags |= ImGuiItemFlags_NoMarkEdited;\n    flags |= ImGuiInputTextFlags_AutoSelectAll | (ImGuiInputTextFlags)ImGuiInputTextFlags_LocalizeDecimalPoint;\n\n    bool value_changed = false;\n    if (p_step == NULL)\n    {\n        if (InputText(label, buf, IM_ARRAYSIZE(buf), flags))\n            value_changed = DataTypeApplyFromText(buf, data_type, p_data, format, (flags & ImGuiInputTextFlags_ParseEmptyRefVal) ? p_data_default : NULL);\n    }\n    else\n    {\n        const float button_size = GetFrameHeight();\n\n        BeginGroup(); // The only purpose of the group here is to allow the caller to query item data e.g. IsItemActive()\n        PushID(label);\n        SetNextItemWidth(ImMax(1.0f, CalcItemWidth() - (button_size + style.ItemInnerSpacing.x) * 2));\n        if (InputText(\"\", buf, IM_ARRAYSIZE(buf), flags)) // PushId(label) + \"\" gives us the expected ID from outside point of view\n            value_changed = DataTypeApplyFromText(buf, data_type, p_data, format, (flags & ImGuiInputTextFlags_ParseEmptyRefVal) ? p_data_default : NULL);\n        IMGUI_TEST_ENGINE_ITEM_INFO(g.LastItemData.ID, label, g.LastItemData.StatusFlags | ImGuiItemStatusFlags_Inputable);\n\n        // Step buttons\n        const ImVec2 backup_frame_padding = style.FramePadding;\n        style.FramePadding.x = style.FramePadding.y;\n        if (flags & ImGuiInputTextFlags_ReadOnly)\n            BeginDisabled();\n        PushItemFlag(ImGuiItemFlags_ButtonRepeat, true);\n        SameLine(0, style.ItemInnerSpacing.x);\n        if (ButtonEx(\"-\", ImVec2(button_size, button_size)))\n        {\n            DataTypeApplyOp(data_type, '-', p_data, p_data, g.IO.KeyCtrl && p_step_fast ? p_step_fast : p_step);\n            value_changed = true;\n        }\n        SameLine(0, style.ItemInnerSpacing.x);\n        if (ButtonEx(\"+\", ImVec2(button_size, button_size)))\n        {\n            DataTypeApplyOp(data_type, '+', p_data, p_data, g.IO.KeyCtrl && p_step_fast ? p_step_fast : p_step);\n            value_changed = true;\n        }\n        PopItemFlag();\n        if (flags & ImGuiInputTextFlags_ReadOnly)\n            EndDisabled();\n\n        const char* label_end = FindRenderedTextEnd(label);\n        if (label != label_end)\n        {\n            SameLine(0, style.ItemInnerSpacing.x);\n            TextEx(label, label_end);\n        }\n        style.FramePadding = backup_frame_padding;\n\n        PopID();\n        EndGroup();\n    }\n\n    g.LastItemData.ItemFlags &= ~ImGuiItemFlags_NoMarkEdited;\n    if (value_changed)\n        MarkItemEdited(g.LastItemData.ID);\n\n    return value_changed;\n}\n\nbool ImGui::InputScalarN(const char* label, ImGuiDataType data_type, void* p_data, int components, const void* p_step, const void* p_step_fast, const char* format, ImGuiInputTextFlags flags)\n{\n    ImGuiWindow* window = GetCurrentWindow();\n    if (window->SkipItems)\n        return false;\n\n    ImGuiContext& g = *GImGui;\n    bool value_changed = false;\n    BeginGroup();\n    PushID(label);\n    PushMultiItemsWidths(components, CalcItemWidth());\n    size_t type_size = GDataTypeInfo[data_type].Size;\n    for (int i = 0; i < components; i++)\n    {\n        PushID(i);\n        if (i > 0)\n            SameLine(0, g.Style.ItemInnerSpacing.x);\n        value_changed |= InputScalar(\"\", data_type, p_data, p_step, p_step_fast, format, flags);\n        PopID();\n        PopItemWidth();\n        p_data = (void*)((char*)p_data + type_size);\n    }\n    PopID();\n\n    const char* label_end = FindRenderedTextEnd(label);\n    if (label != label_end)\n    {\n        SameLine(0.0f, g.Style.ItemInnerSpacing.x);\n        TextEx(label, label_end);\n    }\n\n    EndGroup();\n    return value_changed;\n}\n\nbool ImGui::InputFloat(const char* label, float* v, float step, float step_fast, const char* format, ImGuiInputTextFlags flags)\n{\n    return InputScalar(label, ImGuiDataType_Float, (void*)v, (void*)(step > 0.0f ? &step : NULL), (void*)(step_fast > 0.0f ? &step_fast : NULL), format, flags);\n}\n\nbool ImGui::InputFloat2(const char* label, float v[2], const char* format, ImGuiInputTextFlags flags)\n{\n    return InputScalarN(label, ImGuiDataType_Float, v, 2, NULL, NULL, format, flags);\n}\n\nbool ImGui::InputFloat3(const char* label, float v[3], const char* format, ImGuiInputTextFlags flags)\n{\n    return InputScalarN(label, ImGuiDataType_Float, v, 3, NULL, NULL, format, flags);\n}\n\nbool ImGui::InputFloat4(const char* label, float v[4], const char* format, ImGuiInputTextFlags flags)\n{\n    return InputScalarN(label, ImGuiDataType_Float, v, 4, NULL, NULL, format, flags);\n}\n\nbool ImGui::InputInt(const char* label, int* v, int step, int step_fast, ImGuiInputTextFlags flags)\n{\n    // Hexadecimal input provided as a convenience but the flag name is awkward. Typically you'd use InputText() to parse your own data, if you want to handle prefixes.\n    const char* format = (flags & ImGuiInputTextFlags_CharsHexadecimal) ? \"%08X\" : \"%d\";\n    return InputScalar(label, ImGuiDataType_S32, (void*)v, (void*)(step > 0 ? &step : NULL), (void*)(step_fast > 0 ? &step_fast : NULL), format, flags);\n}\n\nbool ImGui::InputInt2(const char* label, int v[2], ImGuiInputTextFlags flags)\n{\n    return InputScalarN(label, ImGuiDataType_S32, v, 2, NULL, NULL, \"%d\", flags);\n}\n\nbool ImGui::InputInt3(const char* label, int v[3], ImGuiInputTextFlags flags)\n{\n    return InputScalarN(label, ImGuiDataType_S32, v, 3, NULL, NULL, \"%d\", flags);\n}\n\nbool ImGui::InputInt4(const char* label, int v[4], ImGuiInputTextFlags flags)\n{\n    return InputScalarN(label, ImGuiDataType_S32, v, 4, NULL, NULL, \"%d\", flags);\n}\n\nbool ImGui::InputDouble(const char* label, double* v, double step, double step_fast, const char* format, ImGuiInputTextFlags flags)\n{\n    return InputScalar(label, ImGuiDataType_Double, (void*)v, (void*)(step > 0.0 ? &step : NULL), (void*)(step_fast > 0.0 ? &step_fast : NULL), format, flags);\n}\n\n//-------------------------------------------------------------------------\n// [SECTION] Widgets: InputText, InputTextMultiline, InputTextWithHint\n//-------------------------------------------------------------------------\n// - imstb_textedit.h include\n// - InputText()\n// - InputTextWithHint()\n// - InputTextMultiline()\n// - InputTextGetCharInfo() [Internal]\n// - InputTextReindexLines() [Internal]\n// - InputTextReindexLinesRange() [Internal]\n// - InputTextEx() [Internal]\n// - DebugNodeInputTextState() [Internal]\n//-------------------------------------------------------------------------\n\nnamespace ImStb\n{\n#include \"imstb_textedit.h\"\n}\n\nbool ImGui::InputText(const char* label, char* buf, size_t buf_size, ImGuiInputTextFlags flags, ImGuiInputTextCallback callback, void* user_data)\n{\n    IM_ASSERT(!(flags & ImGuiInputTextFlags_Multiline)); // call InputTextMultiline()\n    return InputTextEx(label, NULL, buf, (int)buf_size, ImVec2(0, 0), flags, callback, user_data);\n}\n\nbool ImGui::InputTextMultiline(const char* label, char* buf, size_t buf_size, const ImVec2& size, ImGuiInputTextFlags flags, ImGuiInputTextCallback callback, void* user_data)\n{\n    return InputTextEx(label, NULL, buf, (int)buf_size, size, flags | ImGuiInputTextFlags_Multiline, callback, user_data);\n}\n\nbool ImGui::InputTextWithHint(const char* label, const char* hint, char* buf, size_t buf_size, ImGuiInputTextFlags flags, ImGuiInputTextCallback callback, void* user_data)\n{\n    IM_ASSERT(!(flags & ImGuiInputTextFlags_Multiline)); // call InputTextMultiline() or  InputTextEx() manually if you need multi-line + hint.\n    return InputTextEx(label, hint, buf, (int)buf_size, ImVec2(0, 0), flags, callback, user_data);\n}\n\n// This is only used in the path where the multiline widget is inactivate.\nstatic int InputTextCalcTextLenAndLineCount(const char* text_begin, const char** out_text_end)\n{\n    int line_count = 0;\n    const char* s = text_begin;\n    while (true)\n    {\n        const char* s_eol = strchr(s, '\\n');\n        line_count++;\n        if (s_eol == NULL)\n        {\n            s = s + ImStrlen(s);\n            break;\n        }\n        s = s_eol + 1;\n    }\n    *out_text_end = s;\n    return line_count;\n}\n\n// FIXME: Ideally we'd share code with ImFont::CalcTextSizeA()\nstatic ImVec2 InputTextCalcTextSize(ImGuiContext* ctx, const char* text_begin, const char* text_end, const char** remaining, ImVec2* out_offset, bool stop_on_new_line)\n{\n    ImGuiContext& g = *ctx;\n    ImFont* font = g.Font;\n    const float line_height = g.FontSize;\n    const float scale = line_height / font->FontSize;\n\n    ImVec2 text_size = ImVec2(0, 0);\n    float line_width = 0.0f;\n\n    const char* s = text_begin;\n    while (s < text_end)\n    {\n        unsigned int c = (unsigned int)*s;\n        if (c < 0x80)\n            s += 1;\n        else\n            s += ImTextCharFromUtf8(&c, s, text_end);\n\n        if (c == '\\n')\n        {\n            text_size.x = ImMax(text_size.x, line_width);\n            text_size.y += line_height;\n            line_width = 0.0f;\n            if (stop_on_new_line)\n                break;\n            continue;\n        }\n        if (c == '\\r')\n            continue;\n\n        const float char_width = ((int)c < font->IndexAdvanceX.Size ? font->IndexAdvanceX.Data[c] : font->FallbackAdvanceX) * scale;\n        line_width += char_width;\n    }\n\n    if (text_size.x < line_width)\n        text_size.x = line_width;\n\n    if (out_offset)\n        *out_offset = ImVec2(line_width, text_size.y + line_height);  // offset allow for the possibility of sitting after a trailing \\n\n\n    if (line_width > 0 || text_size.y == 0.0f)                        // whereas size.y will ignore the trailing \\n\n        text_size.y += line_height;\n\n    if (remaining)\n        *remaining = s;\n\n    return text_size;\n}\n\n// Wrapper for stb_textedit.h to edit text (our wrapper is for: statically sized buffer, single-line, wchar characters. InputText converts between UTF-8 and wchar)\n// With our UTF-8 use of stb_textedit:\n// - STB_TEXTEDIT_GETCHAR is nothing more than a a \"GETBYTE\". It's only used to compare to ascii or to copy blocks of text so we are fine.\n// - One exception is the STB_TEXTEDIT_IS_SPACE feature which would expect a full char in order to handle full-width space such as 0x3000 (see ImCharIsBlankW).\n// - ...but we don't use that feature.\nnamespace ImStb\n{\nstatic int     STB_TEXTEDIT_STRINGLEN(const ImGuiInputTextState* obj)                             { return obj->TextLen; }\nstatic char    STB_TEXTEDIT_GETCHAR(const ImGuiInputTextState* obj, int idx)                      { IM_ASSERT(idx <= obj->TextLen); return obj->TextSrc[idx]; }\nstatic float   STB_TEXTEDIT_GETWIDTH(ImGuiInputTextState* obj, int line_start_idx, int char_idx)  { unsigned int c; ImTextCharFromUtf8(&c, obj->TextSrc + line_start_idx + char_idx, obj->TextSrc + obj->TextLen); if ((ImWchar)c == '\\n') return IMSTB_TEXTEDIT_GETWIDTH_NEWLINE; ImGuiContext& g = *obj->Ctx; return g.Font->GetCharAdvance((ImWchar)c) * g.FontScale; }\nstatic char    STB_TEXTEDIT_NEWLINE = '\\n';\nstatic void    STB_TEXTEDIT_LAYOUTROW(StbTexteditRow* r, ImGuiInputTextState* obj, int line_start_idx)\n{\n    const char* text = obj->TextSrc;\n    const char* text_remaining = NULL;\n    const ImVec2 size = InputTextCalcTextSize(obj->Ctx, text + line_start_idx, text + obj->TextLen, &text_remaining, NULL, true);\n    r->x0 = 0.0f;\n    r->x1 = size.x;\n    r->baseline_y_delta = size.y;\n    r->ymin = 0.0f;\n    r->ymax = size.y;\n    r->num_chars = (int)(text_remaining - (text + line_start_idx));\n}\n\n#define IMSTB_TEXTEDIT_GETNEXTCHARINDEX  IMSTB_TEXTEDIT_GETNEXTCHARINDEX_IMPL\n#define IMSTB_TEXTEDIT_GETPREVCHARINDEX  IMSTB_TEXTEDIT_GETPREVCHARINDEX_IMPL\n\nstatic int IMSTB_TEXTEDIT_GETNEXTCHARINDEX_IMPL(ImGuiInputTextState* obj, int idx)\n{\n    if (idx >= obj->TextLen)\n        return obj->TextLen + 1;\n    unsigned int c;\n    return idx + ImTextCharFromUtf8(&c, obj->TextSrc + idx, obj->TextSrc + obj->TextLen);\n}\n\nstatic int IMSTB_TEXTEDIT_GETPREVCHARINDEX_IMPL(ImGuiInputTextState* obj, int idx)\n{\n    if (idx <= 0)\n        return -1;\n    const char* p = ImTextFindPreviousUtf8Codepoint(obj->TextSrc, obj->TextSrc + idx);\n    return (int)(p - obj->TextSrc);\n}\n\nstatic bool ImCharIsSeparatorW(unsigned int c)\n{\n    static const unsigned int separator_list[] =\n    {\n        ',', 0x3001, '.', 0x3002, ';', 0xFF1B, '(', 0xFF08, ')', 0xFF09, '{', 0xFF5B, '}', 0xFF5D,\n        '[', 0x300C, ']', 0x300D, '|', 0xFF5C, '!', 0xFF01, '\\\\', 0xFFE5, '/', 0x30FB, 0xFF0F,\n        '\\n', '\\r',\n    };\n    for (unsigned int separator : separator_list)\n        if (c == separator)\n            return true;\n    return false;\n}\n\nstatic int is_word_boundary_from_right(ImGuiInputTextState* obj, int idx)\n{\n    // When ImGuiInputTextFlags_Password is set, we don't want actions such as CTRL+Arrow to leak the fact that underlying data are blanks or separators.\n    if ((obj->Flags & ImGuiInputTextFlags_Password) || idx <= 0)\n        return 0;\n\n    const char* curr_p = obj->TextSrc + idx;\n    const char* prev_p = ImTextFindPreviousUtf8Codepoint(obj->TextSrc, curr_p);\n    unsigned int curr_c; ImTextCharFromUtf8(&curr_c, curr_p, obj->TextSrc + obj->TextLen);\n    unsigned int prev_c; ImTextCharFromUtf8(&prev_c, prev_p, obj->TextSrc + obj->TextLen);\n\n    bool prev_white = ImCharIsBlankW(prev_c);\n    bool prev_separ = ImCharIsSeparatorW(prev_c);\n    bool curr_white = ImCharIsBlankW(curr_c);\n    bool curr_separ = ImCharIsSeparatorW(curr_c);\n    return ((prev_white || prev_separ) && !(curr_separ || curr_white)) || (curr_separ && !prev_separ);\n}\nstatic int is_word_boundary_from_left(ImGuiInputTextState* obj, int idx)\n{\n    if ((obj->Flags & ImGuiInputTextFlags_Password) || idx <= 0)\n        return 0;\n\n    const char* curr_p = obj->TextSrc + idx;\n    const char* prev_p = ImTextFindPreviousUtf8Codepoint(obj->TextSrc, curr_p);\n    unsigned int prev_c; ImTextCharFromUtf8(&prev_c, curr_p, obj->TextSrc + obj->TextLen);\n    unsigned int curr_c; ImTextCharFromUtf8(&curr_c, prev_p, obj->TextSrc + obj->TextLen);\n\n    bool prev_white = ImCharIsBlankW(prev_c);\n    bool prev_separ = ImCharIsSeparatorW(prev_c);\n    bool curr_white = ImCharIsBlankW(curr_c);\n    bool curr_separ = ImCharIsSeparatorW(curr_c);\n    return ((prev_white) && !(curr_separ || curr_white)) || (curr_separ && !prev_separ);\n}\nstatic int  STB_TEXTEDIT_MOVEWORDLEFT_IMPL(ImGuiInputTextState* obj, int idx)\n{\n    idx = IMSTB_TEXTEDIT_GETPREVCHARINDEX(obj, idx);\n    while (idx >= 0 && !is_word_boundary_from_right(obj, idx))\n        idx = IMSTB_TEXTEDIT_GETPREVCHARINDEX(obj, idx);\n    return idx < 0 ? 0 : idx;\n}\nstatic int  STB_TEXTEDIT_MOVEWORDRIGHT_MAC(ImGuiInputTextState* obj, int idx)\n{\n    int len = obj->TextLen;\n    idx = IMSTB_TEXTEDIT_GETNEXTCHARINDEX(obj, idx);\n    while (idx < len && !is_word_boundary_from_left(obj, idx))\n        idx = IMSTB_TEXTEDIT_GETNEXTCHARINDEX(obj, idx);\n    return idx > len ? len : idx;\n}\nstatic int  STB_TEXTEDIT_MOVEWORDRIGHT_WIN(ImGuiInputTextState* obj, int idx)\n{\n    idx = IMSTB_TEXTEDIT_GETNEXTCHARINDEX(obj, idx);\n    int len = obj->TextLen;\n    while (idx < len && !is_word_boundary_from_right(obj, idx))\n        idx = IMSTB_TEXTEDIT_GETNEXTCHARINDEX(obj, idx);\n    return idx > len ? len : idx;\n}\nstatic int  STB_TEXTEDIT_MOVEWORDRIGHT_IMPL(ImGuiInputTextState* obj, int idx)  { ImGuiContext& g = *obj->Ctx; if (g.IO.ConfigMacOSXBehaviors) return STB_TEXTEDIT_MOVEWORDRIGHT_MAC(obj, idx); else return STB_TEXTEDIT_MOVEWORDRIGHT_WIN(obj, idx); }\n#define STB_TEXTEDIT_MOVEWORDLEFT       STB_TEXTEDIT_MOVEWORDLEFT_IMPL  // They need to be #define for stb_textedit.h\n#define STB_TEXTEDIT_MOVEWORDRIGHT      STB_TEXTEDIT_MOVEWORDRIGHT_IMPL\n\nstatic void STB_TEXTEDIT_DELETECHARS(ImGuiInputTextState* obj, int pos, int n)\n{\n    // Offset remaining text (+ copy zero terminator)\n    IM_ASSERT(obj->TextSrc == obj->TextA.Data);\n    char* dst = obj->TextA.Data + pos;\n    char* src = obj->TextA.Data + pos + n;\n    memmove(dst, src, obj->TextLen - n - pos + 1);\n    obj->Edited = true;\n    obj->TextLen -= n;\n}\n\nstatic bool STB_TEXTEDIT_INSERTCHARS(ImGuiInputTextState* obj, int pos, const char* new_text, int new_text_len)\n{\n    const bool is_resizable = (obj->Flags & ImGuiInputTextFlags_CallbackResize) != 0;\n    const int text_len = obj->TextLen;\n    IM_ASSERT(pos <= text_len);\n\n    if (!is_resizable && (new_text_len + obj->TextLen + 1 > obj->BufCapacity))\n        return false;\n\n    // Grow internal buffer if needed\n    IM_ASSERT(obj->TextSrc == obj->TextA.Data);\n    if (new_text_len + text_len + 1 > obj->TextA.Size)\n    {\n        if (!is_resizable)\n            return false;\n        obj->TextA.resize(text_len + ImClamp(new_text_len, 32, ImMax(256, new_text_len)) + 1);\n        obj->TextSrc = obj->TextA.Data;\n    }\n\n    char* text = obj->TextA.Data;\n    if (pos != text_len)\n        memmove(text + pos + new_text_len, text + pos, (size_t)(text_len - pos));\n    memcpy(text + pos, new_text, (size_t)new_text_len);\n\n    obj->Edited = true;\n    obj->TextLen += new_text_len;\n    obj->TextA[obj->TextLen] = '\\0';\n\n    return true;\n}\n\n// We don't use an enum so we can build even with conflicting symbols (if another user of stb_textedit.h leak their STB_TEXTEDIT_K_* symbols)\n#define STB_TEXTEDIT_K_LEFT         0x200000 // keyboard input to move cursor left\n#define STB_TEXTEDIT_K_RIGHT        0x200001 // keyboard input to move cursor right\n#define STB_TEXTEDIT_K_UP           0x200002 // keyboard input to move cursor up\n#define STB_TEXTEDIT_K_DOWN         0x200003 // keyboard input to move cursor down\n#define STB_TEXTEDIT_K_LINESTART    0x200004 // keyboard input to move cursor to start of line\n#define STB_TEXTEDIT_K_LINEEND      0x200005 // keyboard input to move cursor to end of line\n#define STB_TEXTEDIT_K_TEXTSTART    0x200006 // keyboard input to move cursor to start of text\n#define STB_TEXTEDIT_K_TEXTEND      0x200007 // keyboard input to move cursor to end of text\n#define STB_TEXTEDIT_K_DELETE       0x200008 // keyboard input to delete selection or character under cursor\n#define STB_TEXTEDIT_K_BACKSPACE    0x200009 // keyboard input to delete selection or character left of cursor\n#define STB_TEXTEDIT_K_UNDO         0x20000A // keyboard input to perform undo\n#define STB_TEXTEDIT_K_REDO         0x20000B // keyboard input to perform redo\n#define STB_TEXTEDIT_K_WORDLEFT     0x20000C // keyboard input to move cursor left one word\n#define STB_TEXTEDIT_K_WORDRIGHT    0x20000D // keyboard input to move cursor right one word\n#define STB_TEXTEDIT_K_PGUP         0x20000E // keyboard input to move cursor up a page\n#define STB_TEXTEDIT_K_PGDOWN       0x20000F // keyboard input to move cursor down a page\n#define STB_TEXTEDIT_K_SHIFT        0x400000\n\n#define IMSTB_TEXTEDIT_IMPLEMENTATION\n#define IMSTB_TEXTEDIT_memmove memmove\n#include \"imstb_textedit.h\"\n\n// stb_textedit internally allows for a single undo record to do addition and deletion, but somehow, calling\n// the stb_textedit_paste() function creates two separate records, so we perform it manually. (FIXME: Report to nothings/stb?)\nstatic void stb_textedit_replace(ImGuiInputTextState* str, STB_TexteditState* state, const IMSTB_TEXTEDIT_CHARTYPE* text, int text_len)\n{\n    stb_text_makeundo_replace(str, state, 0, str->TextLen, text_len);\n    ImStb::STB_TEXTEDIT_DELETECHARS(str, 0, str->TextLen);\n    state->cursor = state->select_start = state->select_end = 0;\n    if (text_len <= 0)\n        return;\n    if (ImStb::STB_TEXTEDIT_INSERTCHARS(str, 0, text, text_len))\n    {\n        state->cursor = state->select_start = state->select_end = text_len;\n        state->has_preferred_x = 0;\n        return;\n    }\n    IM_ASSERT(0); // Failed to insert character, normally shouldn't happen because of how we currently use stb_textedit_replace()\n}\n\n} // namespace ImStb\n\n// We added an extra indirection where 'Stb' is heap-allocated, in order facilitate the work of bindings generators.\nImGuiInputTextState::ImGuiInputTextState()\n{\n    memset(this, 0, sizeof(*this));\n    Stb = IM_NEW(ImStbTexteditState);\n    memset(Stb, 0, sizeof(*Stb));\n}\n\nImGuiInputTextState::~ImGuiInputTextState()\n{\n    IM_DELETE(Stb);\n}\n\nvoid ImGuiInputTextState::OnKeyPressed(int key)\n{\n    stb_textedit_key(this, Stb, key);\n    CursorFollow = true;\n    CursorAnimReset();\n}\n\nvoid ImGuiInputTextState::OnCharPressed(unsigned int c)\n{\n    // Convert the key to a UTF8 byte sequence.\n    // The changes we had to make to stb_textedit_key made it very much UTF-8 specific which is not too great.\n    char utf8[5];\n    ImTextCharToUtf8(utf8, c);\n    stb_textedit_text(this, Stb, utf8, (int)ImStrlen(utf8));\n    CursorFollow = true;\n    CursorAnimReset();\n}\n\n// Those functions are not inlined in imgui_internal.h, allowing us to hide ImStbTexteditState from that header.\nvoid ImGuiInputTextState::CursorAnimReset()                 { CursorAnim = -0.30f; } // After a user-input the cursor stays on for a while without blinking\nvoid ImGuiInputTextState::CursorClamp()                     { Stb->cursor = ImMin(Stb->cursor, TextLen); Stb->select_start = ImMin(Stb->select_start, TextLen); Stb->select_end = ImMin(Stb->select_end, TextLen); }\nbool ImGuiInputTextState::HasSelection() const              { return Stb->select_start != Stb->select_end; }\nvoid ImGuiInputTextState::ClearSelection()                  { Stb->select_start = Stb->select_end = Stb->cursor; }\nint  ImGuiInputTextState::GetCursorPos() const              { return Stb->cursor; }\nint  ImGuiInputTextState::GetSelectionStart() const         { return Stb->select_start; }\nint  ImGuiInputTextState::GetSelectionEnd() const           { return Stb->select_end; }\nvoid ImGuiInputTextState::SelectAll()                       { Stb->select_start = 0; Stb->cursor = Stb->select_end = TextLen; Stb->has_preferred_x = 0; }\nvoid ImGuiInputTextState::ReloadUserBufAndSelectAll()       { WantReloadUserBuf = true; ReloadSelectionStart = 0; ReloadSelectionEnd = INT_MAX; }\nvoid ImGuiInputTextState::ReloadUserBufAndKeepSelection()   { WantReloadUserBuf = true; ReloadSelectionStart = Stb->select_start; ReloadSelectionEnd = Stb->select_end; }\nvoid ImGuiInputTextState::ReloadUserBufAndMoveToEnd()       { WantReloadUserBuf = true; ReloadSelectionStart = ReloadSelectionEnd = INT_MAX; }\n\nImGuiInputTextCallbackData::ImGuiInputTextCallbackData()\n{\n    memset(this, 0, sizeof(*this));\n}\n\n// Public API to manipulate UTF-8 text from within a callback.\n// FIXME: The existence of this rarely exercised code path is a bit of a nuisance.\n// Historically they existed because STB_TEXTEDIT_INSERTCHARS() etc. worked on our ImWchar\n// buffer, but nowadays they both work on UTF-8 data. Should aim to merge both.\nvoid ImGuiInputTextCallbackData::DeleteChars(int pos, int bytes_count)\n{\n    IM_ASSERT(pos + bytes_count <= BufTextLen);\n    char* dst = Buf + pos;\n    const char* src = Buf + pos + bytes_count;\n    memmove(dst, src, BufTextLen - bytes_count - pos + 1);\n\n    if (CursorPos >= pos + bytes_count)\n        CursorPos -= bytes_count;\n    else if (CursorPos >= pos)\n        CursorPos = pos;\n    SelectionStart = SelectionEnd = CursorPos;\n    BufDirty = true;\n    BufTextLen -= bytes_count;\n}\n\nvoid ImGuiInputTextCallbackData::InsertChars(int pos, const char* new_text, const char* new_text_end)\n{\n    // Accept null ranges\n    if (new_text == new_text_end)\n        return;\n\n    // Grow internal buffer if needed\n    const bool is_resizable = (Flags & ImGuiInputTextFlags_CallbackResize) != 0;\n    const int new_text_len = new_text_end ? (int)(new_text_end - new_text) : (int)ImStrlen(new_text);\n    if (new_text_len + BufTextLen >= BufSize)\n    {\n        if (!is_resizable)\n            return;\n\n        ImGuiContext& g = *Ctx;\n        ImGuiInputTextState* edit_state = &g.InputTextState;\n        IM_ASSERT(edit_state->ID != 0 && g.ActiveId == edit_state->ID);\n        IM_ASSERT(Buf == edit_state->TextA.Data);\n        int new_buf_size = BufTextLen + ImClamp(new_text_len * 4, 32, ImMax(256, new_text_len)) + 1;\n        edit_state->TextA.resize(new_buf_size + 1);\n        edit_state->TextSrc = edit_state->TextA.Data;\n        Buf = edit_state->TextA.Data;\n        BufSize = edit_state->BufCapacity = new_buf_size;\n    }\n\n    if (BufTextLen != pos)\n        memmove(Buf + pos + new_text_len, Buf + pos, (size_t)(BufTextLen - pos));\n    memcpy(Buf + pos, new_text, (size_t)new_text_len * sizeof(char));\n    Buf[BufTextLen + new_text_len] = '\\0';\n\n    if (CursorPos >= pos)\n        CursorPos += new_text_len;\n    SelectionStart = SelectionEnd = CursorPos;\n    BufDirty = true;\n    BufTextLen += new_text_len;\n}\n\nvoid ImGui::PushPasswordFont()\n{\n    ImGuiContext& g = *GImGui;\n    ImFont* in_font = g.Font;\n    ImFont* out_font = &g.InputTextPasswordFont;\n    ImFontGlyph* glyph = in_font->FindGlyph('*');\n    out_font->FontSize = in_font->FontSize;\n    out_font->Scale = in_font->Scale;\n    out_font->Ascent = in_font->Ascent;\n    out_font->Descent = in_font->Descent;\n    out_font->ContainerAtlas = in_font->ContainerAtlas;\n    out_font->FallbackGlyph = glyph;\n    out_font->FallbackAdvanceX = glyph->AdvanceX;\n    IM_ASSERT(out_font->Glyphs.Size == 0 && out_font->IndexAdvanceX.Size == 0 && out_font->IndexLookup.Size == 0);\n    PushFont(out_font);\n}\n\n// Return false to discard a character.\nstatic bool InputTextFilterCharacter(ImGuiContext* ctx, unsigned int* p_char, ImGuiInputTextFlags flags, ImGuiInputTextCallback callback, void* user_data, bool input_source_is_clipboard)\n{\n    unsigned int c = *p_char;\n\n    // Filter non-printable (NB: isprint is unreliable! see #2467)\n    bool apply_named_filters = true;\n    if (c < 0x20)\n    {\n        bool pass = false;\n        pass |= (c == '\\n') && (flags & ImGuiInputTextFlags_Multiline) != 0;    // Note that an Enter KEY will emit \\r and be ignored (we poll for KEY in InputText() code)\n        if (c == '\\n' && input_source_is_clipboard && (flags & ImGuiInputTextFlags_Multiline) == 0) // In single line mode, replace \\n with a space\n        {\n            c = *p_char = ' ';\n            pass = true;\n        }\n        pass |= (c == '\\n') && (flags & ImGuiInputTextFlags_Multiline) != 0;\n        pass |= (c == '\\t') && (flags & ImGuiInputTextFlags_AllowTabInput) != 0;\n        if (!pass)\n            return false;\n        apply_named_filters = false; // Override named filters below so newline and tabs can still be inserted.\n    }\n\n    if (input_source_is_clipboard == false)\n    {\n        // We ignore Ascii representation of delete (emitted from Backspace on OSX, see #2578, #2817)\n        if (c == 127)\n            return false;\n\n        // Filter private Unicode range. GLFW on OSX seems to send private characters for special keys like arrow keys (FIXME)\n        if (c >= 0xE000 && c <= 0xF8FF)\n            return false;\n    }\n\n    // Filter Unicode ranges we are not handling in this build\n    if (c > IM_UNICODE_CODEPOINT_MAX)\n        return false;\n\n    // Generic named filters\n    if (apply_named_filters && (flags & (ImGuiInputTextFlags_CharsDecimal | ImGuiInputTextFlags_CharsHexadecimal | ImGuiInputTextFlags_CharsUppercase | ImGuiInputTextFlags_CharsNoBlank | ImGuiInputTextFlags_CharsScientific | (ImGuiInputTextFlags)ImGuiInputTextFlags_LocalizeDecimalPoint)))\n    {\n        // The libc allows overriding locale, with e.g. 'setlocale(LC_NUMERIC, \"de_DE.UTF-8\");' which affect the output/input of printf/scanf to use e.g. ',' instead of '.'.\n        // The standard mandate that programs starts in the \"C\" locale where the decimal point is '.'.\n        // We don't really intend to provide widespread support for it, but out of empathy for people stuck with using odd API, we support the bare minimum aka overriding the decimal point.\n        // Change the default decimal_point with:\n        //   ImGui::GetPlatformIO()->Platform_LocaleDecimalPoint = *localeconv()->decimal_point;\n        // Users of non-default decimal point (in particular ',') may be affected by word-selection logic (is_word_boundary_from_right/is_word_boundary_from_left) functions.\n        ImGuiContext& g = *ctx;\n        const unsigned c_decimal_point = (unsigned int)g.PlatformIO.Platform_LocaleDecimalPoint;\n        if (flags & (ImGuiInputTextFlags_CharsDecimal | ImGuiInputTextFlags_CharsScientific | (ImGuiInputTextFlags)ImGuiInputTextFlags_LocalizeDecimalPoint))\n            if (c == '.' || c == ',')\n                c = c_decimal_point;\n\n        // Full-width -> half-width conversion for numeric fields (https://en.wikipedia.org/wiki/Halfwidth_and_Fullwidth_Forms_(Unicode_block)\n        // While this is mostly convenient, this has the side-effect for uninformed users accidentally inputting full-width characters that they may\n        // scratch their head as to why it works in numerical fields vs in generic text fields it would require support in the font.\n        if (flags & (ImGuiInputTextFlags_CharsDecimal | ImGuiInputTextFlags_CharsScientific | ImGuiInputTextFlags_CharsHexadecimal))\n            if (c >= 0xFF01 && c <= 0xFF5E)\n                c = c - 0xFF01 + 0x21;\n\n        // Allow 0-9 . - + * /\n        if (flags & ImGuiInputTextFlags_CharsDecimal)\n            if (!(c >= '0' && c <= '9') && (c != c_decimal_point) && (c != '-') && (c != '+') && (c != '*') && (c != '/'))\n                return false;\n\n        // Allow 0-9 . - + * / e E\n        if (flags & ImGuiInputTextFlags_CharsScientific)\n            if (!(c >= '0' && c <= '9') && (c != c_decimal_point) && (c != '-') && (c != '+') && (c != '*') && (c != '/') && (c != 'e') && (c != 'E'))\n                return false;\n\n        // Allow 0-9 a-F A-F\n        if (flags & ImGuiInputTextFlags_CharsHexadecimal)\n            if (!(c >= '0' && c <= '9') && !(c >= 'a' && c <= 'f') && !(c >= 'A' && c <= 'F'))\n                return false;\n\n        // Turn a-z into A-Z\n        if (flags & ImGuiInputTextFlags_CharsUppercase)\n            if (c >= 'a' && c <= 'z')\n                c += (unsigned int)('A' - 'a');\n\n        if (flags & ImGuiInputTextFlags_CharsNoBlank)\n            if (ImCharIsBlankW(c))\n                return false;\n\n        *p_char = c;\n    }\n\n    // Custom callback filter\n    if (flags & ImGuiInputTextFlags_CallbackCharFilter)\n    {\n        ImGuiContext& g = *GImGui;\n        ImGuiInputTextCallbackData callback_data;\n        callback_data.Ctx = &g;\n        callback_data.EventFlag = ImGuiInputTextFlags_CallbackCharFilter;\n        callback_data.EventChar = (ImWchar)c;\n        callback_data.Flags = flags;\n        callback_data.UserData = user_data;\n        if (callback(&callback_data) != 0)\n            return false;\n        *p_char = callback_data.EventChar;\n        if (!callback_data.EventChar)\n            return false;\n    }\n\n    return true;\n}\n\n// Find the shortest single replacement we can make to get from old_buf to new_buf\n// Note that this doesn't directly alter state->TextA, state->TextLen. They are expected to be made valid separately.\n// FIXME: Ideally we should transition toward (1) making InsertChars()/DeleteChars() update undo-stack (2) discourage (and keep reconcile) or obsolete (and remove reconcile) accessing buffer directly.\nstatic void InputTextReconcileUndoState(ImGuiInputTextState* state, const char* old_buf, int old_length, const char* new_buf, int new_length)\n{\n    const int shorter_length = ImMin(old_length, new_length);\n    int first_diff;\n    for (first_diff = 0; first_diff < shorter_length; first_diff++)\n        if (old_buf[first_diff] != new_buf[first_diff])\n            break;\n    if (first_diff == old_length && first_diff == new_length)\n        return;\n\n    int old_last_diff = old_length   - 1;\n    int new_last_diff = new_length - 1;\n    for (; old_last_diff >= first_diff && new_last_diff >= first_diff; old_last_diff--, new_last_diff--)\n        if (old_buf[old_last_diff] != new_buf[new_last_diff])\n            break;\n\n    const int insert_len = new_last_diff - first_diff + 1;\n    const int delete_len = old_last_diff - first_diff + 1;\n    if (insert_len > 0 || delete_len > 0)\n        if (IMSTB_TEXTEDIT_CHARTYPE* p = stb_text_createundo(&state->Stb->undostate, first_diff, delete_len, insert_len))\n            for (int i = 0; i < delete_len; i++)\n                p[i] = old_buf[first_diff + i];\n}\n\n// As InputText() retain textual data and we currently provide a path for user to not retain it (via local variables)\n// we need some form of hook to reapply data back to user buffer on deactivation frame. (#4714)\n// It would be more desirable that we discourage users from taking advantage of the \"user not retaining data\" trick,\n// but that more likely be attractive when we do have _NoLiveEdit flag available.\nvoid ImGui::InputTextDeactivateHook(ImGuiID id)\n{\n    ImGuiContext& g = *GImGui;\n    ImGuiInputTextState* state = &g.InputTextState;\n    if (id == 0 || state->ID != id)\n        return;\n    g.InputTextDeactivatedState.ID = state->ID;\n    if (state->Flags & ImGuiInputTextFlags_ReadOnly)\n    {\n        g.InputTextDeactivatedState.TextA.resize(0); // In theory this data won't be used, but clear to be neat.\n    }\n    else\n    {\n        IM_ASSERT(state->TextA.Data != 0);\n        IM_ASSERT(state->TextA[state->TextLen] == 0);\n        g.InputTextDeactivatedState.TextA.resize(state->TextLen + 1);\n        memcpy(g.InputTextDeactivatedState.TextA.Data, state->TextA.Data, state->TextLen + 1);\n    }\n}\n\n// Edit a string of text\n// - buf_size account for the zero-terminator, so a buf_size of 6 can hold \"Hello\" but not \"Hello!\".\n//   This is so we can easily call InputText() on static arrays using ARRAYSIZE() and to match\n//   Note that in std::string world, capacity() would omit 1 byte used by the zero-terminator.\n// - When active, hold on a privately held copy of the text (and apply back to 'buf'). So changing 'buf' while the InputText is active has no effect.\n// - If you want to use ImGui::InputText() with std::string, see misc/cpp/imgui_stdlib.h\n// (FIXME: Rather confusing and messy function, among the worse part of our codebase, expecting to rewrite a V2 at some point.. Partly because we are\n//  doing UTF8 > U16 > UTF8 conversions on the go to easily interface with stb_textedit. Ideally should stay in UTF-8 all the time. See https://github.com/nothings/stb/issues/188)\nbool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_size, const ImVec2& size_arg, ImGuiInputTextFlags flags, ImGuiInputTextCallback callback, void* callback_user_data)\n{\n    ImGuiWindow* window = GetCurrentWindow();\n    if (window->SkipItems)\n        return false;\n\n    IM_ASSERT(buf != NULL && buf_size >= 0);\n    IM_ASSERT(!((flags & ImGuiInputTextFlags_CallbackHistory) && (flags & ImGuiInputTextFlags_Multiline)));        // Can't use both together (they both use up/down keys)\n    IM_ASSERT(!((flags & ImGuiInputTextFlags_CallbackCompletion) && (flags & ImGuiInputTextFlags_AllowTabInput))); // Can't use both together (they both use tab key)\n    IM_ASSERT(!((flags & ImGuiInputTextFlags_ElideLeft) && (flags & ImGuiInputTextFlags_Multiline)));               // Multiline will not work with left-trimming\n\n    ImGuiContext& g = *GImGui;\n    ImGuiIO& io = g.IO;\n    const ImGuiStyle& style = g.Style;\n\n    const bool RENDER_SELECTION_WHEN_INACTIVE = false;\n    const bool is_multiline = (flags & ImGuiInputTextFlags_Multiline) != 0;\n\n    if (is_multiline) // Open group before calling GetID() because groups tracks id created within their scope (including the scrollbar)\n        BeginGroup();\n    const ImGuiID id = window->GetID(label);\n    const ImVec2 label_size = CalcTextSize(label, NULL, true);\n    const ImVec2 frame_size = CalcItemSize(size_arg, CalcItemWidth(), (is_multiline ? g.FontSize * 8.0f : label_size.y) + style.FramePadding.y * 2.0f); // Arbitrary default of 8 lines high for multi-line\n    const ImVec2 total_size = ImVec2(frame_size.x + (label_size.x > 0.0f ? style.ItemInnerSpacing.x + label_size.x : 0.0f), frame_size.y);\n\n    const ImRect frame_bb(window->DC.CursorPos, window->DC.CursorPos + frame_size);\n    const ImRect total_bb(frame_bb.Min, frame_bb.Min + total_size);\n\n    ImGuiWindow* draw_window = window;\n    ImVec2 inner_size = frame_size;\n    ImGuiLastItemData item_data_backup;\n    if (is_multiline)\n    {\n        ImVec2 backup_pos = window->DC.CursorPos;\n        ItemSize(total_bb, style.FramePadding.y);\n        if (!ItemAdd(total_bb, id, &frame_bb, ImGuiItemFlags_Inputable))\n        {\n            EndGroup();\n            return false;\n        }\n        item_data_backup = g.LastItemData;\n        window->DC.CursorPos = backup_pos;\n\n        // Prevent NavActivation from Tabbing when our widget accepts Tab inputs: this allows cycling through widgets without stopping.\n        if (g.NavActivateId == id && (g.NavActivateFlags & ImGuiActivateFlags_FromTabbing) && (flags & ImGuiInputTextFlags_AllowTabInput))\n            g.NavActivateId = 0;\n\n        // Prevent NavActivate reactivating in BeginChild() when we are already active.\n        const ImGuiID backup_activate_id = g.NavActivateId;\n        if (g.ActiveId == id) // Prevent reactivation\n            g.NavActivateId = 0;\n\n        // We reproduce the contents of BeginChildFrame() in order to provide 'label' so our window internal data are easier to read/debug.\n        PushStyleColor(ImGuiCol_ChildBg, style.Colors[ImGuiCol_FrameBg]);\n        PushStyleVar(ImGuiStyleVar_ChildRounding, style.FrameRounding);\n        PushStyleVar(ImGuiStyleVar_ChildBorderSize, style.FrameBorderSize);\n        PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0, 0)); // Ensure no clip rect so mouse hover can reach FramePadding edges\n        bool child_visible = BeginChildEx(label, id, frame_bb.GetSize(), ImGuiChildFlags_Borders, ImGuiWindowFlags_NoMove);\n        g.NavActivateId = backup_activate_id;\n        PopStyleVar(3);\n        PopStyleColor();\n        if (!child_visible)\n        {\n            EndChild();\n            EndGroup();\n            return false;\n        }\n        draw_window = g.CurrentWindow; // Child window\n        draw_window->DC.NavLayersActiveMaskNext |= (1 << draw_window->DC.NavLayerCurrent); // This is to ensure that EndChild() will display a navigation highlight so we can \"enter\" into it.\n        draw_window->DC.CursorPos += style.FramePadding;\n        inner_size.x -= draw_window->ScrollbarSizes.x;\n    }\n    else\n    {\n        // Support for internal ImGuiInputTextFlags_MergedItem flag, which could be redesigned as an ItemFlags if needed (with test performed in ItemAdd)\n        ItemSize(total_bb, style.FramePadding.y);\n        if (!(flags & ImGuiInputTextFlags_MergedItem))\n            if (!ItemAdd(total_bb, id, &frame_bb, ImGuiItemFlags_Inputable))\n                return false;\n    }\n\n    // Ensure mouse cursor is set even after switching to keyboard/gamepad mode. May generalize further? (#6417)\n    bool hovered = ItemHoverable(frame_bb, id, g.LastItemData.ItemFlags | ImGuiItemFlags_NoNavDisableMouseHover);\n    if (hovered)\n        SetMouseCursor(ImGuiMouseCursor_TextInput);\n    if (hovered && g.NavHighlightItemUnderNav)\n        hovered = false;\n\n    // We are only allowed to access the state if we are already the active widget.\n    ImGuiInputTextState* state = GetInputTextState(id);\n\n    if (g.LastItemData.ItemFlags & ImGuiItemFlags_ReadOnly)\n        flags |= ImGuiInputTextFlags_ReadOnly;\n    const bool is_readonly = (flags & ImGuiInputTextFlags_ReadOnly) != 0;\n    const bool is_password = (flags & ImGuiInputTextFlags_Password) != 0;\n    const bool is_undoable = (flags & ImGuiInputTextFlags_NoUndoRedo) == 0;\n    const bool is_resizable = (flags & ImGuiInputTextFlags_CallbackResize) != 0;\n    if (is_resizable)\n        IM_ASSERT(callback != NULL); // Must provide a callback if you set the ImGuiInputTextFlags_CallbackResize flag!\n\n    const bool input_requested_by_nav = (g.ActiveId != id) && ((g.NavActivateId == id) && ((g.NavActivateFlags & ImGuiActivateFlags_PreferInput) || (g.NavInputSource == ImGuiInputSource_Keyboard)));\n\n    const bool user_clicked = hovered && io.MouseClicked[0];\n    const bool user_scroll_finish = is_multiline && state != NULL && g.ActiveId == 0 && g.ActiveIdPreviousFrame == GetWindowScrollbarID(draw_window, ImGuiAxis_Y);\n    const bool user_scroll_active = is_multiline && state != NULL && g.ActiveId == GetWindowScrollbarID(draw_window, ImGuiAxis_Y);\n    bool clear_active_id = false;\n    bool select_all = false;\n\n    float scroll_y = is_multiline ? draw_window->Scroll.y : FLT_MAX;\n\n    const bool init_reload_from_user_buf = (state != NULL && state->WantReloadUserBuf);\n    const bool init_changed_specs = (state != NULL && state->Stb->single_line != !is_multiline); // state != NULL means its our state.\n    const bool init_make_active = (user_clicked || user_scroll_finish || input_requested_by_nav);\n    const bool init_state = (init_make_active || user_scroll_active);\n    if (init_reload_from_user_buf)\n    {\n        int new_len = (int)ImStrlen(buf);\n        IM_ASSERT(new_len + 1 <= buf_size && \"Is your input buffer properly zero-terminated?\");\n        state->WantReloadUserBuf = false;\n        InputTextReconcileUndoState(state, state->TextA.Data, state->TextLen, buf, new_len);\n        state->TextA.resize(buf_size + 1); // we use +1 to make sure that .Data is always pointing to at least an empty string.\n        state->TextLen = new_len;\n        memcpy(state->TextA.Data, buf, state->TextLen + 1);\n        state->Stb->select_start = state->ReloadSelectionStart;\n        state->Stb->cursor = state->Stb->select_end = state->ReloadSelectionEnd;\n        state->CursorClamp();\n    }\n    else if ((init_state && g.ActiveId != id) || init_changed_specs)\n    {\n        // Access state even if we don't own it yet.\n        state = &g.InputTextState;\n        state->CursorAnimReset();\n\n        // Backup state of deactivating item so they'll have a chance to do a write to output buffer on the same frame they report IsItemDeactivatedAfterEdit (#4714)\n        InputTextDeactivateHook(state->ID);\n\n        // Take a copy of the initial buffer value.\n        // From the moment we focused we are normally ignoring the content of 'buf' (unless we are in read-only mode)\n        const int buf_len = (int)ImStrlen(buf);\n        IM_ASSERT(buf_len + 1 <= buf_size && \"Is your input buffer properly zero-terminated?\");\n        state->TextToRevertTo.resize(buf_len + 1);    // UTF-8. we use +1 to make sure that .Data is always pointing to at least an empty string.\n        memcpy(state->TextToRevertTo.Data, buf, buf_len + 1);\n\n        // Preserve cursor position and undo/redo stack if we come back to same widget\n        // FIXME: Since we reworked this on 2022/06, may want to differentiate recycle_cursor vs recycle_undostate?\n        bool recycle_state = (state->ID == id && !init_changed_specs);\n        if (recycle_state && (state->TextLen != buf_len || (state->TextA.Data == NULL || strncmp(state->TextA.Data, buf, buf_len) != 0)))\n            recycle_state = false;\n\n        // Start edition\n        state->ID = id;\n        state->TextLen = buf_len;\n        if (!is_readonly)\n        {\n            state->TextA.resize(buf_size + 1); // we use +1 to make sure that .Data is always pointing to at least an empty string.\n            memcpy(state->TextA.Data, buf, state->TextLen + 1);\n        }\n\n        // Find initial scroll position for right alignment\n        state->Scroll = ImVec2(0.0f, 0.0f);\n        if (flags & ImGuiInputTextFlags_ElideLeft)\n            state->Scroll.x += ImMax(0.0f, CalcTextSize(buf).x - frame_size.x + style.FramePadding.x * 2.0f);\n\n        // Recycle existing cursor/selection/undo stack but clamp position\n        // Note a single mouse click will override the cursor/position immediately by calling stb_textedit_click handler.\n        if (recycle_state)\n            state->CursorClamp();\n        else\n            stb_textedit_initialize_state(state->Stb, !is_multiline);\n\n        if (!is_multiline)\n        {\n            if (flags & ImGuiInputTextFlags_AutoSelectAll)\n                select_all = true;\n            if (input_requested_by_nav && (!recycle_state || !(g.NavActivateFlags & ImGuiActivateFlags_TryToPreserveState)))\n                select_all = true;\n            if (user_clicked && io.KeyCtrl)\n                select_all = true;\n        }\n\n        if (flags & ImGuiInputTextFlags_AlwaysOverwrite)\n            state->Stb->insert_mode = 1; // stb field name is indeed incorrect (see #2863)\n    }\n\n    const bool is_osx = io.ConfigMacOSXBehaviors;\n    if (g.ActiveId != id && init_make_active)\n    {\n        IM_ASSERT(state && state->ID == id);\n        SetActiveID(id, window);\n        SetFocusID(id, window);\n        FocusWindow(window);\n    }\n    if (g.ActiveId == id)\n    {\n        // Declare some inputs, the other are registered and polled via Shortcut() routing system.\n        // FIXME: The reason we don't use Shortcut() is we would need a routing flag to specify multiple mods, or to all mods combinaison into individual shortcuts.\n        const ImGuiKey always_owned_keys[] = { ImGuiKey_LeftArrow, ImGuiKey_RightArrow, ImGuiKey_Enter, ImGuiKey_KeypadEnter, ImGuiKey_Delete, ImGuiKey_Backspace, ImGuiKey_Home, ImGuiKey_End };\n        for (ImGuiKey key : always_owned_keys)\n            SetKeyOwner(key, id);\n        if (user_clicked)\n            SetKeyOwner(ImGuiKey_MouseLeft, id);\n        g.ActiveIdUsingNavDirMask |= (1 << ImGuiDir_Left) | (1 << ImGuiDir_Right);\n        if (is_multiline || (flags & ImGuiInputTextFlags_CallbackHistory))\n        {\n            g.ActiveIdUsingNavDirMask |= (1 << ImGuiDir_Up) | (1 << ImGuiDir_Down);\n            SetKeyOwner(ImGuiKey_UpArrow, id);\n            SetKeyOwner(ImGuiKey_DownArrow, id);\n        }\n        if (is_multiline)\n        {\n            SetKeyOwner(ImGuiKey_PageUp, id);\n            SetKeyOwner(ImGuiKey_PageDown, id);\n        }\n        // FIXME: May be a problem to always steal Alt on OSX, would ideally still allow an uninterrupted Alt down-up to toggle menu\n        if (is_osx)\n            SetKeyOwner(ImGuiMod_Alt, id);\n\n        // Expose scroll in a manner that is agnostic to us using a child window\n        if (is_multiline && state != NULL)\n            state->Scroll.y = draw_window->Scroll.y;\n\n        // Read-only mode always ever read from source buffer. Refresh TextLen when active.\n        if (is_readonly && state != NULL)\n            state->TextLen = (int)ImStrlen(buf);\n        //if (is_readonly && state != NULL)\n        //    state->TextA.clear(); // Uncomment to facilitate debugging, but we otherwise prefer to keep/amortize th allocation.\n    }\n    if (state != NULL)\n        state->TextSrc = is_readonly ? buf : state->TextA.Data;\n\n    // We have an edge case if ActiveId was set through another widget (e.g. widget being swapped), clear id immediately (don't wait until the end of the function)\n    if (g.ActiveId == id && state == NULL)\n        ClearActiveID();\n\n    // Release focus when we click outside\n    if (g.ActiveId == id && io.MouseClicked[0] && !init_state && !init_make_active) //-V560\n        clear_active_id = true;\n\n    // Lock the decision of whether we are going to take the path displaying the cursor or selection\n    bool render_cursor = (g.ActiveId == id) || (state && user_scroll_active);\n    bool render_selection = state && (state->HasSelection() || select_all) && (RENDER_SELECTION_WHEN_INACTIVE || render_cursor);\n    bool value_changed = false;\n    bool validated = false;\n\n    // Select the buffer to render.\n    const bool buf_display_from_state = (render_cursor || render_selection || g.ActiveId == id) && !is_readonly && state;\n    bool is_displaying_hint = (hint != NULL && (buf_display_from_state ? state->TextA.Data : buf)[0] == 0);\n\n    // Password pushes a temporary font with only a fallback glyph\n    if (is_password && !is_displaying_hint)\n        PushPasswordFont();\n\n    // Process mouse inputs and character inputs\n    if (g.ActiveId == id)\n    {\n        IM_ASSERT(state != NULL);\n        state->Edited = false;\n        state->BufCapacity = buf_size;\n        state->Flags = flags;\n\n        // Although we are active we don't prevent mouse from hovering other elements unless we are interacting right now with the widget.\n        // Down the line we should have a cleaner library-wide concept of Selected vs Active.\n        g.ActiveIdAllowOverlap = !io.MouseDown[0];\n\n        // Edit in progress\n        const float mouse_x = (io.MousePos.x - frame_bb.Min.x - style.FramePadding.x) + state->Scroll.x;\n        const float mouse_y = (is_multiline ? (io.MousePos.y - draw_window->DC.CursorPos.y) : (g.FontSize * 0.5f));\n\n        if (select_all)\n        {\n            state->SelectAll();\n            state->SelectedAllMouseLock = true;\n        }\n        else if (hovered && io.MouseClickedCount[0] >= 2 && !io.KeyShift)\n        {\n            stb_textedit_click(state, state->Stb, mouse_x, mouse_y);\n            const int multiclick_count = (io.MouseClickedCount[0] - 2);\n            if ((multiclick_count % 2) == 0)\n            {\n                // Double-click: Select word\n                // We always use the \"Mac\" word advance for double-click select vs CTRL+Right which use the platform dependent variant:\n                // FIXME: There are likely many ways to improve this behavior, but there's no \"right\" behavior (depends on use-case, software, OS)\n                const bool is_bol = (state->Stb->cursor == 0) || ImStb::STB_TEXTEDIT_GETCHAR(state, state->Stb->cursor - 1) == '\\n';\n                if (STB_TEXT_HAS_SELECTION(state->Stb) || !is_bol)\n                    state->OnKeyPressed(STB_TEXTEDIT_K_WORDLEFT);\n                //state->OnKeyPressed(STB_TEXTEDIT_K_WORDRIGHT | STB_TEXTEDIT_K_SHIFT);\n                if (!STB_TEXT_HAS_SELECTION(state->Stb))\n                    ImStb::stb_textedit_prep_selection_at_cursor(state->Stb);\n                state->Stb->cursor = ImStb::STB_TEXTEDIT_MOVEWORDRIGHT_MAC(state, state->Stb->cursor);\n                state->Stb->select_end = state->Stb->cursor;\n                ImStb::stb_textedit_clamp(state, state->Stb);\n            }\n            else\n            {\n                // Triple-click: Select line\n                const bool is_eol = ImStb::STB_TEXTEDIT_GETCHAR(state, state->Stb->cursor) == '\\n';\n                state->OnKeyPressed(STB_TEXTEDIT_K_LINESTART);\n                state->OnKeyPressed(STB_TEXTEDIT_K_LINEEND | STB_TEXTEDIT_K_SHIFT);\n                state->OnKeyPressed(STB_TEXTEDIT_K_RIGHT | STB_TEXTEDIT_K_SHIFT);\n                if (!is_eol && is_multiline)\n                {\n                    ImSwap(state->Stb->select_start, state->Stb->select_end);\n                    state->Stb->cursor = state->Stb->select_end;\n                }\n                state->CursorFollow = false;\n            }\n            state->CursorAnimReset();\n        }\n        else if (io.MouseClicked[0] && !state->SelectedAllMouseLock)\n        {\n            if (hovered)\n            {\n                if (io.KeyShift)\n                    stb_textedit_drag(state, state->Stb, mouse_x, mouse_y);\n                else\n                    stb_textedit_click(state, state->Stb, mouse_x, mouse_y);\n                state->CursorAnimReset();\n            }\n        }\n        else if (io.MouseDown[0] && !state->SelectedAllMouseLock && (io.MouseDelta.x != 0.0f || io.MouseDelta.y != 0.0f))\n        {\n            stb_textedit_drag(state, state->Stb, mouse_x, mouse_y);\n            state->CursorAnimReset();\n            state->CursorFollow = true;\n        }\n        if (state->SelectedAllMouseLock && !io.MouseDown[0])\n            state->SelectedAllMouseLock = false;\n\n        // We expect backends to emit a Tab key but some also emit a Tab character which we ignore (#2467, #1336)\n        // (For Tab and Enter: Win32/SFML/Allegro are sending both keys and chars, GLFW and SDL are only sending keys. For Space they all send all threes)\n        if ((flags & ImGuiInputTextFlags_AllowTabInput) && !is_readonly)\n        {\n            if (Shortcut(ImGuiKey_Tab, ImGuiInputFlags_Repeat, id))\n            {\n                unsigned int c = '\\t'; // Insert TAB\n                if (InputTextFilterCharacter(&g, &c, flags, callback, callback_user_data))\n                    state->OnCharPressed(c);\n            }\n            // FIXME: Implement Shift+Tab\n            /*\n            if (Shortcut(ImGuiKey_Tab | ImGuiMod_Shift, ImGuiInputFlags_Repeat, id))\n            {\n            }\n            */\n        }\n\n        // Process regular text input (before we check for Return because using some IME will effectively send a Return?)\n        // We ignore CTRL inputs, but need to allow ALT+CTRL as some keyboards (e.g. German) use AltGR (which _is_ Alt+Ctrl) to input certain characters.\n        const bool ignore_char_inputs = (io.KeyCtrl && !io.KeyAlt) || (is_osx && io.KeyCtrl);\n        if (io.InputQueueCharacters.Size > 0)\n        {\n            if (!ignore_char_inputs && !is_readonly && !input_requested_by_nav)\n                for (int n = 0; n < io.InputQueueCharacters.Size; n++)\n                {\n                    // Insert character if they pass filtering\n                    unsigned int c = (unsigned int)io.InputQueueCharacters[n];\n                    if (c == '\\t') // Skip Tab, see above.\n                        continue;\n                    if (InputTextFilterCharacter(&g, &c, flags, callback, callback_user_data))\n                        state->OnCharPressed(c);\n                }\n\n            // Consume characters\n            io.InputQueueCharacters.resize(0);\n        }\n    }\n\n    // Process other shortcuts/key-presses\n    bool revert_edit = false;\n    if (g.ActiveId == id && !g.ActiveIdIsJustActivated && !clear_active_id)\n    {\n        IM_ASSERT(state != NULL);\n\n        const int row_count_per_page = ImMax((int)((inner_size.y - style.FramePadding.y) / g.FontSize), 1);\n        state->Stb->row_count_per_page = row_count_per_page;\n\n        const int k_mask = (io.KeyShift ? STB_TEXTEDIT_K_SHIFT : 0);\n        const bool is_wordmove_key_down = is_osx ? io.KeyAlt : io.KeyCtrl;                     // OS X style: Text editing cursor movement using Alt instead of Ctrl\n        const bool is_startend_key_down = is_osx && io.KeyCtrl && !io.KeySuper && !io.KeyAlt;  // OS X style: Line/Text Start and End using Cmd+Arrows instead of Home/End\n\n        // Using Shortcut() with ImGuiInputFlags_RouteFocused (default policy) to allow routing operations for other code (e.g. calling window trying to use CTRL+A and CTRL+B: former would be handled by InputText)\n        // Otherwise we could simply assume that we own the keys as we are active.\n        const ImGuiInputFlags f_repeat = ImGuiInputFlags_Repeat;\n        const bool is_cut   = (Shortcut(ImGuiMod_Ctrl | ImGuiKey_X, f_repeat, id) || Shortcut(ImGuiMod_Shift | ImGuiKey_Delete, f_repeat, id)) && !is_readonly && !is_password && (!is_multiline || state->HasSelection());\n        const bool is_copy  = (Shortcut(ImGuiMod_Ctrl | ImGuiKey_C, 0,        id) || Shortcut(ImGuiMod_Ctrl  | ImGuiKey_Insert, 0,        id)) && !is_password && (!is_multiline || state->HasSelection());\n        const bool is_paste = (Shortcut(ImGuiMod_Ctrl | ImGuiKey_V, f_repeat, id) || Shortcut(ImGuiMod_Shift | ImGuiKey_Insert, f_repeat, id)) && !is_readonly;\n        const bool is_undo  = (Shortcut(ImGuiMod_Ctrl | ImGuiKey_Z, f_repeat, id)) && !is_readonly && is_undoable;\n        const bool is_redo =  (Shortcut(ImGuiMod_Ctrl | ImGuiKey_Y, f_repeat, id) || Shortcut(ImGuiMod_Ctrl | ImGuiMod_Shift | ImGuiKey_Z, f_repeat, id)) && !is_readonly && is_undoable;\n        const bool is_select_all = Shortcut(ImGuiMod_Ctrl | ImGuiKey_A, 0, id);\n\n        // We allow validate/cancel with Nav source (gamepad) to makes it easier to undo an accidental NavInput press with no keyboard wired, but otherwise it isn't very useful.\n        const bool nav_gamepad_active = (io.ConfigFlags & ImGuiConfigFlags_NavEnableGamepad) != 0 && (io.BackendFlags & ImGuiBackendFlags_HasGamepad) != 0;\n        const bool is_enter_pressed = IsKeyPressed(ImGuiKey_Enter, true) || IsKeyPressed(ImGuiKey_KeypadEnter, true);\n        const bool is_gamepad_validate = nav_gamepad_active && (IsKeyPressed(ImGuiKey_NavGamepadActivate, false) || IsKeyPressed(ImGuiKey_NavGamepadInput, false));\n        const bool is_cancel = Shortcut(ImGuiKey_Escape, f_repeat, id) || (nav_gamepad_active && Shortcut(ImGuiKey_NavGamepadCancel, f_repeat, id));\n\n        // FIXME: Should use more Shortcut() and reduce IsKeyPressed()+SetKeyOwner(), but requires modifiers combination to be taken account of.\n        // FIXME-OSX: Missing support for Alt(option)+Right/Left = go to end of line, or next line if already in end of line.\n        if (IsKeyPressed(ImGuiKey_LeftArrow))                        { state->OnKeyPressed((is_startend_key_down ? STB_TEXTEDIT_K_LINESTART : is_wordmove_key_down ? STB_TEXTEDIT_K_WORDLEFT : STB_TEXTEDIT_K_LEFT) | k_mask); }\n        else if (IsKeyPressed(ImGuiKey_RightArrow))                  { state->OnKeyPressed((is_startend_key_down ? STB_TEXTEDIT_K_LINEEND : is_wordmove_key_down ? STB_TEXTEDIT_K_WORDRIGHT : STB_TEXTEDIT_K_RIGHT) | k_mask); }\n        else if (IsKeyPressed(ImGuiKey_UpArrow) && is_multiline)     { if (io.KeyCtrl) SetScrollY(draw_window, ImMax(draw_window->Scroll.y - g.FontSize, 0.0f)); else state->OnKeyPressed((is_startend_key_down ? STB_TEXTEDIT_K_TEXTSTART : STB_TEXTEDIT_K_UP) | k_mask); }\n        else if (IsKeyPressed(ImGuiKey_DownArrow) && is_multiline)   { if (io.KeyCtrl) SetScrollY(draw_window, ImMin(draw_window->Scroll.y + g.FontSize, GetScrollMaxY())); else state->OnKeyPressed((is_startend_key_down ? STB_TEXTEDIT_K_TEXTEND : STB_TEXTEDIT_K_DOWN) | k_mask); }\n        else if (IsKeyPressed(ImGuiKey_PageUp) && is_multiline)      { state->OnKeyPressed(STB_TEXTEDIT_K_PGUP | k_mask); scroll_y -= row_count_per_page * g.FontSize; }\n        else if (IsKeyPressed(ImGuiKey_PageDown) && is_multiline)    { state->OnKeyPressed(STB_TEXTEDIT_K_PGDOWN | k_mask); scroll_y += row_count_per_page * g.FontSize; }\n        else if (IsKeyPressed(ImGuiKey_Home))                        { state->OnKeyPressed(io.KeyCtrl ? STB_TEXTEDIT_K_TEXTSTART | k_mask : STB_TEXTEDIT_K_LINESTART | k_mask); }\n        else if (IsKeyPressed(ImGuiKey_End))                         { state->OnKeyPressed(io.KeyCtrl ? STB_TEXTEDIT_K_TEXTEND | k_mask : STB_TEXTEDIT_K_LINEEND | k_mask); }\n        else if (IsKeyPressed(ImGuiKey_Delete) && !is_readonly && !is_cut)\n        {\n            if (!state->HasSelection())\n            {\n                // OSX doesn't seem to have Super+Delete to delete until end-of-line, so we don't emulate that (as opposed to Super+Backspace)\n                if (is_wordmove_key_down)\n                    state->OnKeyPressed(STB_TEXTEDIT_K_WORDRIGHT | STB_TEXTEDIT_K_SHIFT);\n            }\n            state->OnKeyPressed(STB_TEXTEDIT_K_DELETE | k_mask);\n        }\n        else if (IsKeyPressed(ImGuiKey_Backspace) && !is_readonly)\n        {\n            if (!state->HasSelection())\n            {\n                if (is_wordmove_key_down)\n                    state->OnKeyPressed(STB_TEXTEDIT_K_WORDLEFT | STB_TEXTEDIT_K_SHIFT);\n                else if (is_osx && io.KeyCtrl && !io.KeyAlt && !io.KeySuper)\n                    state->OnKeyPressed(STB_TEXTEDIT_K_LINESTART | STB_TEXTEDIT_K_SHIFT);\n            }\n            state->OnKeyPressed(STB_TEXTEDIT_K_BACKSPACE | k_mask);\n        }\n        else if (is_enter_pressed || is_gamepad_validate)\n        {\n            // Determine if we turn Enter into a \\n character\n            bool ctrl_enter_for_new_line = (flags & ImGuiInputTextFlags_CtrlEnterForNewLine) != 0;\n            if (!is_multiline || is_gamepad_validate || (ctrl_enter_for_new_line && !io.KeyCtrl) || (!ctrl_enter_for_new_line && io.KeyCtrl))\n            {\n                validated = true;\n                if (io.ConfigInputTextEnterKeepActive && !is_multiline)\n                    state->SelectAll(); // No need to scroll\n                else\n                    clear_active_id = true;\n            }\n            else if (!is_readonly)\n            {\n                unsigned int c = '\\n'; // Insert new line\n                if (InputTextFilterCharacter(&g, &c, flags, callback, callback_user_data))\n                    state->OnCharPressed(c);\n            }\n        }\n        else if (is_cancel)\n        {\n            if (flags & ImGuiInputTextFlags_EscapeClearsAll)\n            {\n                if (buf[0] != 0)\n                {\n                    revert_edit = true;\n                }\n                else\n                {\n                    render_cursor = render_selection = false;\n                    clear_active_id = true;\n                }\n            }\n            else\n            {\n                clear_active_id = revert_edit = true;\n                render_cursor = render_selection = false;\n            }\n        }\n        else if (is_undo || is_redo)\n        {\n            state->OnKeyPressed(is_undo ? STB_TEXTEDIT_K_UNDO : STB_TEXTEDIT_K_REDO);\n            state->ClearSelection();\n        }\n        else if (is_select_all)\n        {\n            state->SelectAll();\n            state->CursorFollow = true;\n        }\n        else if (is_cut || is_copy)\n        {\n            // Cut, Copy\n            if (g.PlatformIO.Platform_SetClipboardTextFn != NULL)\n            {\n                // SetClipboardText() only takes null terminated strings + state->TextSrc may point to read-only user buffer, so we need to make a copy.\n                const int ib = state->HasSelection() ? ImMin(state->Stb->select_start, state->Stb->select_end) : 0;\n                const int ie = state->HasSelection() ? ImMax(state->Stb->select_start, state->Stb->select_end) : state->TextLen;\n                g.TempBuffer.reserve(ie - ib + 1);\n                memcpy(g.TempBuffer.Data, state->TextSrc + ib, ie - ib);\n                g.TempBuffer.Data[ie - ib] = 0;\n                SetClipboardText(g.TempBuffer.Data);\n            }\n            if (is_cut)\n            {\n                if (!state->HasSelection())\n                    state->SelectAll();\n                state->CursorFollow = true;\n                stb_textedit_cut(state, state->Stb);\n            }\n        }\n        else if (is_paste)\n        {\n            if (const char* clipboard = GetClipboardText())\n            {\n                // Filter pasted buffer\n                const int clipboard_len = (int)ImStrlen(clipboard);\n                ImVector<char> clipboard_filtered;\n                clipboard_filtered.reserve(clipboard_len + 1);\n                for (const char* s = clipboard; *s != 0; )\n                {\n                    unsigned int c;\n                    int in_len = ImTextCharFromUtf8(&c, s, NULL);\n                    s += in_len;\n                    if (!InputTextFilterCharacter(&g, &c, flags, callback, callback_user_data, true))\n                        continue;\n                    char c_utf8[5];\n                    ImTextCharToUtf8(c_utf8, c);\n                    int out_len = (int)ImStrlen(c_utf8);\n                    clipboard_filtered.resize(clipboard_filtered.Size + out_len);\n                    memcpy(clipboard_filtered.Data + clipboard_filtered.Size - out_len, c_utf8, out_len);\n                }\n                if (clipboard_filtered.Size > 0) // If everything was filtered, ignore the pasting operation\n                {\n                    clipboard_filtered.push_back(0);\n                    stb_textedit_paste(state, state->Stb, clipboard_filtered.Data, clipboard_filtered.Size - 1);\n                    state->CursorFollow = true;\n                }\n            }\n        }\n\n        // Update render selection flag after events have been handled, so selection highlight can be displayed during the same frame.\n        render_selection |= state->HasSelection() && (RENDER_SELECTION_WHEN_INACTIVE || render_cursor);\n    }\n\n    // Process callbacks and apply result back to user's buffer.\n    const char* apply_new_text = NULL;\n    int apply_new_text_length = 0;\n    if (g.ActiveId == id)\n    {\n        IM_ASSERT(state != NULL);\n        if (revert_edit && !is_readonly)\n        {\n            if (flags & ImGuiInputTextFlags_EscapeClearsAll)\n            {\n                // Clear input\n                IM_ASSERT(buf[0] != 0);\n                apply_new_text = \"\";\n                apply_new_text_length = 0;\n                value_changed = true;\n                IMSTB_TEXTEDIT_CHARTYPE empty_string;\n                stb_textedit_replace(state, state->Stb, &empty_string, 0);\n            }\n            else if (strcmp(buf, state->TextToRevertTo.Data) != 0)\n            {\n                apply_new_text = state->TextToRevertTo.Data;\n                apply_new_text_length = state->TextToRevertTo.Size - 1;\n\n                // Restore initial value. Only return true if restoring to the initial value changes the current buffer contents.\n                // Push records into the undo stack so we can CTRL+Z the revert operation itself\n                value_changed = true;\n                stb_textedit_replace(state, state->Stb, state->TextToRevertTo.Data, state->TextToRevertTo.Size - 1);\n            }\n        }\n\n        // FIXME-OPT: We always reapply the live buffer back to the input buffer before clearing ActiveId,\n        // even though strictly speaking it wasn't modified on this frame. Should mark dirty state from the stb_textedit callbacks.\n        // If we do that, need to ensure that as special case, 'validated == true' also writes back.\n        // This also allows the user to use InputText() without maintaining any user-side storage.\n        // (please note that if you use this property along ImGuiInputTextFlags_CallbackResize you can end up with your temporary string object\n        // unnecessarily allocating once a frame, either store your string data, either if you don't then don't use ImGuiInputTextFlags_CallbackResize).\n        const bool apply_edit_back_to_user_buffer = true;// !revert_edit || (validated && (flags & ImGuiInputTextFlags_EnterReturnsTrue) != 0);\n        if (apply_edit_back_to_user_buffer)\n        {\n            // Apply current edited text immediately.\n            // Note that as soon as the input box is active, the in-widget value gets priority over any underlying modification of the input buffer\n\n            // User callback\n            if ((flags & (ImGuiInputTextFlags_CallbackCompletion | ImGuiInputTextFlags_CallbackHistory | ImGuiInputTextFlags_CallbackEdit | ImGuiInputTextFlags_CallbackAlways)) != 0)\n            {\n                IM_ASSERT(callback != NULL);\n\n                // The reason we specify the usage semantic (Completion/History) is that Completion needs to disable keyboard TABBING at the moment.\n                ImGuiInputTextFlags event_flag = 0;\n                ImGuiKey event_key = ImGuiKey_None;\n                if ((flags & ImGuiInputTextFlags_CallbackCompletion) != 0 && Shortcut(ImGuiKey_Tab, 0, id))\n                {\n                    event_flag = ImGuiInputTextFlags_CallbackCompletion;\n                    event_key = ImGuiKey_Tab;\n                }\n                else if ((flags & ImGuiInputTextFlags_CallbackHistory) != 0 && IsKeyPressed(ImGuiKey_UpArrow))\n                {\n                    event_flag = ImGuiInputTextFlags_CallbackHistory;\n                    event_key = ImGuiKey_UpArrow;\n                }\n                else if ((flags & ImGuiInputTextFlags_CallbackHistory) != 0 && IsKeyPressed(ImGuiKey_DownArrow))\n                {\n                    event_flag = ImGuiInputTextFlags_CallbackHistory;\n                    event_key = ImGuiKey_DownArrow;\n                }\n                else if ((flags & ImGuiInputTextFlags_CallbackEdit) && state->Edited)\n                {\n                    event_flag = ImGuiInputTextFlags_CallbackEdit;\n                }\n                else if (flags & ImGuiInputTextFlags_CallbackAlways)\n                {\n                    event_flag = ImGuiInputTextFlags_CallbackAlways;\n                }\n\n                if (event_flag)\n                {\n                    ImGuiInputTextCallbackData callback_data;\n                    callback_data.Ctx = &g;\n                    callback_data.EventFlag = event_flag;\n                    callback_data.Flags = flags;\n                    callback_data.UserData = callback_user_data;\n\n                    // FIXME-OPT: Undo stack reconcile needs a backup of the data until we rework API, see #7925\n                    char* callback_buf = is_readonly ? buf : state->TextA.Data;\n                    IM_ASSERT(callback_buf == state->TextSrc);\n                    state->CallbackTextBackup.resize(state->TextLen + 1);\n                    memcpy(state->CallbackTextBackup.Data, callback_buf, state->TextLen + 1);\n\n                    callback_data.EventKey = event_key;\n                    callback_data.Buf = callback_buf;\n                    callback_data.BufTextLen = state->TextLen;\n                    callback_data.BufSize = state->BufCapacity;\n                    callback_data.BufDirty = false;\n\n                    const int utf8_cursor_pos = callback_data.CursorPos = state->Stb->cursor;\n                    const int utf8_selection_start = callback_data.SelectionStart = state->Stb->select_start;\n                    const int utf8_selection_end = callback_data.SelectionEnd = state->Stb->select_end;\n\n                    // Call user code\n                    callback(&callback_data);\n\n                    // Read back what user may have modified\n                    callback_buf = is_readonly ? buf : state->TextA.Data; // Pointer may have been invalidated by a resize callback\n                    IM_ASSERT(callback_data.Buf == callback_buf);         // Invalid to modify those fields\n                    IM_ASSERT(callback_data.BufSize == state->BufCapacity);\n                    IM_ASSERT(callback_data.Flags == flags);\n                    const bool buf_dirty = callback_data.BufDirty;\n                    if (callback_data.CursorPos != utf8_cursor_pos || buf_dirty)            { state->Stb->cursor = callback_data.CursorPos; state->CursorFollow = true; }\n                    if (callback_data.SelectionStart != utf8_selection_start || buf_dirty)  { state->Stb->select_start = (callback_data.SelectionStart == callback_data.CursorPos) ? state->Stb->cursor : callback_data.SelectionStart; }\n                    if (callback_data.SelectionEnd != utf8_selection_end || buf_dirty)      { state->Stb->select_end = (callback_data.SelectionEnd == callback_data.SelectionStart) ? state->Stb->select_start : callback_data.SelectionEnd; }\n                    if (buf_dirty)\n                    {\n                        // Callback may update buffer and thus set buf_dirty even in read-only mode.\n                        IM_ASSERT(callback_data.BufTextLen == (int)ImStrlen(callback_data.Buf)); // You need to maintain BufTextLen if you change the text!\n                        InputTextReconcileUndoState(state, state->CallbackTextBackup.Data, state->CallbackTextBackup.Size - 1, callback_data.Buf, callback_data.BufTextLen);\n                        state->TextLen = callback_data.BufTextLen;  // Assume correct length and valid UTF-8 from user, saves us an extra strlen()\n                        state->CursorAnimReset();\n                    }\n                }\n            }\n\n            // Will copy result string if modified\n            if (!is_readonly && strcmp(state->TextSrc, buf) != 0)\n            {\n                apply_new_text = state->TextSrc;\n                apply_new_text_length = state->TextLen;\n                value_changed = true;\n            }\n        }\n    }\n\n    // Handle reapplying final data on deactivation (see InputTextDeactivateHook() for details)\n    if (g.InputTextDeactivatedState.ID == id)\n    {\n        if (g.ActiveId != id && IsItemDeactivatedAfterEdit() && !is_readonly && strcmp(g.InputTextDeactivatedState.TextA.Data, buf) != 0)\n        {\n            apply_new_text = g.InputTextDeactivatedState.TextA.Data;\n            apply_new_text_length = g.InputTextDeactivatedState.TextA.Size - 1;\n            value_changed = true;\n            //IMGUI_DEBUG_LOG(\"InputText(): apply Deactivated data for 0x%08X: \\\"%.*s\\\".\\n\", id, apply_new_text_length, apply_new_text);\n        }\n        g.InputTextDeactivatedState.ID = 0;\n    }\n\n    // Copy result to user buffer. This can currently only happen when (g.ActiveId == id)\n    if (apply_new_text != NULL)\n    {\n        //// We cannot test for 'backup_current_text_length != apply_new_text_length' here because we have no guarantee that the size\n        //// of our owned buffer matches the size of the string object held by the user, and by design we allow InputText() to be used\n        //// without any storage on user's side.\n        IM_ASSERT(apply_new_text_length >= 0);\n        if (is_resizable)\n        {\n            ImGuiInputTextCallbackData callback_data;\n            callback_data.Ctx = &g;\n            callback_data.EventFlag = ImGuiInputTextFlags_CallbackResize;\n            callback_data.Flags = flags;\n            callback_data.Buf = buf;\n            callback_data.BufTextLen = apply_new_text_length;\n            callback_data.BufSize = ImMax(buf_size, apply_new_text_length + 1);\n            callback_data.UserData = callback_user_data;\n            callback(&callback_data);\n            buf = callback_data.Buf;\n            buf_size = callback_data.BufSize;\n            apply_new_text_length = ImMin(callback_data.BufTextLen, buf_size - 1);\n            IM_ASSERT(apply_new_text_length <= buf_size);\n        }\n        //IMGUI_DEBUG_PRINT(\"InputText(\\\"%s\\\"): apply_new_text length %d\\n\", label, apply_new_text_length);\n\n        // If the underlying buffer resize was denied or not carried to the next frame, apply_new_text_length+1 may be >= buf_size.\n        ImStrncpy(buf, apply_new_text, ImMin(apply_new_text_length + 1, buf_size));\n    }\n\n    // Release active ID at the end of the function (so e.g. pressing Return still does a final application of the value)\n    // Otherwise request text input ahead for next frame.\n    if (g.ActiveId == id && clear_active_id)\n        ClearActiveID();\n    else if (g.ActiveId == id)\n        g.WantTextInputNextFrame = 1;\n\n    // Render frame\n    if (!is_multiline)\n    {\n        RenderNavCursor(frame_bb, id);\n        RenderFrame(frame_bb.Min, frame_bb.Max, GetColorU32(ImGuiCol_FrameBg), true, style.FrameRounding);\n    }\n\n    const ImVec4 clip_rect(frame_bb.Min.x, frame_bb.Min.y, frame_bb.Min.x + inner_size.x, frame_bb.Min.y + inner_size.y); // Not using frame_bb.Max because we have adjusted size\n    ImVec2 draw_pos = is_multiline ? draw_window->DC.CursorPos : frame_bb.Min + style.FramePadding;\n    ImVec2 text_size(0.0f, 0.0f);\n\n    // Set upper limit of single-line InputTextEx() at 2 million characters strings. The current pathological worst case is a long line\n    // without any carriage return, which would makes ImFont::RenderText() reserve too many vertices and probably crash. Avoid it altogether.\n    // Note that we only use this limit on single-line InputText(), so a pathologically large line on a InputTextMultiline() would still crash.\n    const int buf_display_max_length = 2 * 1024 * 1024;\n    const char* buf_display = buf_display_from_state ? state->TextA.Data : buf; //-V595\n    const char* buf_display_end = NULL; // We have specialized paths below for setting the length\n\n    // Display hint when contents is empty\n    // At this point we need to handle the possibility that a callback could have modified the underlying buffer (#8368)\n    const bool new_is_displaying_hint = (hint != NULL && (buf_display_from_state ? state->TextA.Data : buf)[0] == 0);\n    if (new_is_displaying_hint != is_displaying_hint)\n    {\n        if (is_password && !is_displaying_hint)\n            PopFont();\n        is_displaying_hint = new_is_displaying_hint;\n        if (is_password && !is_displaying_hint)\n            PushPasswordFont();\n    }\n    if (is_displaying_hint)\n    {\n        buf_display = hint;\n        buf_display_end = hint + ImStrlen(hint);\n    }\n\n    // Render text. We currently only render selection when the widget is active or while scrolling.\n    // FIXME: We could remove the '&& render_cursor' to keep rendering selection when inactive.\n    if (render_cursor || render_selection)\n    {\n        IM_ASSERT(state != NULL);\n        if (!is_displaying_hint)\n            buf_display_end = buf_display + state->TextLen;\n\n        // Render text (with cursor and selection)\n        // This is going to be messy. We need to:\n        // - Display the text (this alone can be more easily clipped)\n        // - Handle scrolling, highlight selection, display cursor (those all requires some form of 1d->2d cursor position calculation)\n        // - Measure text height (for scrollbar)\n        // We are attempting to do most of that in **one main pass** to minimize the computation cost (non-negligible for large amount of text) + 2nd pass for selection rendering (we could merge them by an extra refactoring effort)\n        // FIXME: This should occur on buf_display but we'd need to maintain cursor/select_start/select_end for UTF-8.\n        const char* text_begin = buf_display;\n        const char* text_end = text_begin + state->TextLen;\n        ImVec2 cursor_offset, select_start_offset;\n\n        {\n            // Find lines numbers straddling cursor and selection min position\n            int cursor_line_no = render_cursor ? -1 : -1000;\n            int selmin_line_no = render_selection ? -1 : -1000;\n            const char* cursor_ptr = render_cursor ? text_begin + state->Stb->cursor : NULL;\n            const char* selmin_ptr = render_selection ? text_begin + ImMin(state->Stb->select_start, state->Stb->select_end) : NULL;\n\n            // Count lines and find line number for cursor and selection ends\n            int line_count = 1;\n            if (is_multiline)\n            {\n                for (const char* s = text_begin; (s = (const char*)ImMemchr(s, '\\n', (size_t)(text_end - s))) != NULL; s++)\n                {\n                    if (cursor_line_no == -1 && s >= cursor_ptr) { cursor_line_no = line_count; }\n                    if (selmin_line_no == -1 && s >= selmin_ptr) { selmin_line_no = line_count; }\n                    line_count++;\n                }\n            }\n            if (cursor_line_no == -1)\n                cursor_line_no = line_count;\n            if (selmin_line_no == -1)\n                selmin_line_no = line_count;\n\n            // Calculate 2d position by finding the beginning of the line and measuring distance\n            cursor_offset.x = InputTextCalcTextSize(&g, ImStrbol(cursor_ptr, text_begin), cursor_ptr).x;\n            cursor_offset.y = cursor_line_no * g.FontSize;\n            if (selmin_line_no >= 0)\n            {\n                select_start_offset.x = InputTextCalcTextSize(&g, ImStrbol(selmin_ptr, text_begin), selmin_ptr).x;\n                select_start_offset.y = selmin_line_no * g.FontSize;\n            }\n\n            // Store text height (note that we haven't calculated text width at all, see GitHub issues #383, #1224)\n            if (is_multiline)\n                text_size = ImVec2(inner_size.x, line_count * g.FontSize);\n        }\n\n        // Scroll\n        if (render_cursor && state->CursorFollow)\n        {\n            // Horizontal scroll in chunks of quarter width\n            if (!(flags & ImGuiInputTextFlags_NoHorizontalScroll))\n            {\n                const float scroll_increment_x = inner_size.x * 0.25f;\n                const float visible_width = inner_size.x - style.FramePadding.x;\n                if (cursor_offset.x < state->Scroll.x)\n                    state->Scroll.x = IM_TRUNC(ImMax(0.0f, cursor_offset.x - scroll_increment_x));\n                else if (cursor_offset.x - visible_width >= state->Scroll.x)\n                    state->Scroll.x = IM_TRUNC(cursor_offset.x - visible_width + scroll_increment_x);\n            }\n            else\n            {\n                state->Scroll.y = 0.0f;\n            }\n\n            // Vertical scroll\n            if (is_multiline)\n            {\n                // Test if cursor is vertically visible\n                if (cursor_offset.y - g.FontSize < scroll_y)\n                    scroll_y = ImMax(0.0f, cursor_offset.y - g.FontSize);\n                else if (cursor_offset.y - (inner_size.y - style.FramePadding.y * 2.0f) >= scroll_y)\n                    scroll_y = cursor_offset.y - inner_size.y + style.FramePadding.y * 2.0f;\n                const float scroll_max_y = ImMax((text_size.y + style.FramePadding.y * 2.0f) - inner_size.y, 0.0f);\n                scroll_y = ImClamp(scroll_y, 0.0f, scroll_max_y);\n                draw_pos.y += (draw_window->Scroll.y - scroll_y);   // Manipulate cursor pos immediately avoid a frame of lag\n                draw_window->Scroll.y = scroll_y;\n            }\n\n            state->CursorFollow = false;\n        }\n\n        // Draw selection\n        const ImVec2 draw_scroll = ImVec2(state->Scroll.x, 0.0f);\n        if (render_selection)\n        {\n            const char* text_selected_begin = text_begin + ImMin(state->Stb->select_start, state->Stb->select_end);\n            const char* text_selected_end = text_begin + ImMax(state->Stb->select_start, state->Stb->select_end);\n\n            ImU32 bg_color = GetColorU32(ImGuiCol_TextSelectedBg, render_cursor ? 1.0f : 0.6f); // FIXME: current code flow mandate that render_cursor is always true here, we are leaving the transparent one for tests.\n            float bg_offy_up = is_multiline ? 0.0f : -1.0f;    // FIXME: those offsets should be part of the style? they don't play so well with multi-line selection.\n            float bg_offy_dn = is_multiline ? 0.0f : 2.0f;\n            ImVec2 rect_pos = draw_pos + select_start_offset - draw_scroll;\n            for (const char* p = text_selected_begin; p < text_selected_end; )\n            {\n                if (rect_pos.y > clip_rect.w + g.FontSize)\n                    break;\n                if (rect_pos.y < clip_rect.y)\n                {\n                    p = (const char*)ImMemchr((void*)p, '\\n', text_selected_end - p);\n                    p = p ? p + 1 : text_selected_end;\n                }\n                else\n                {\n                    ImVec2 rect_size = InputTextCalcTextSize(&g, p, text_selected_end, &p, NULL, true);\n                    if (rect_size.x <= 0.0f) rect_size.x = IM_TRUNC(g.Font->GetCharAdvance((ImWchar)' ') * 0.50f); // So we can see selected empty lines\n                    ImRect rect(rect_pos + ImVec2(0.0f, bg_offy_up - g.FontSize), rect_pos + ImVec2(rect_size.x, bg_offy_dn));\n                    rect.ClipWith(clip_rect);\n                    if (rect.Overlaps(clip_rect))\n                        draw_window->DrawList->AddRectFilled(rect.Min, rect.Max, bg_color);\n                    rect_pos.x = draw_pos.x - draw_scroll.x;\n                }\n                rect_pos.y += g.FontSize;\n            }\n        }\n\n        // We test for 'buf_display_max_length' as a way to avoid some pathological cases (e.g. single-line 1 MB string) which would make ImDrawList crash.\n        // FIXME-OPT: Multiline could submit a smaller amount of contents to AddText() since we already iterated through it.\n        if (is_multiline || (buf_display_end - buf_display) < buf_display_max_length)\n        {\n            ImU32 col = GetColorU32(is_displaying_hint ? ImGuiCol_TextDisabled : ImGuiCol_Text);\n            draw_window->DrawList->AddText(g.Font, g.FontSize, draw_pos - draw_scroll, col, buf_display, buf_display_end, 0.0f, is_multiline ? NULL : &clip_rect);\n        }\n\n        // Draw blinking cursor\n        if (render_cursor)\n        {\n            state->CursorAnim += io.DeltaTime;\n            bool cursor_is_visible = (!g.IO.ConfigInputTextCursorBlink) || (state->CursorAnim <= 0.0f) || ImFmod(state->CursorAnim, 1.20f) <= 0.80f;\n            ImVec2 cursor_screen_pos = ImTrunc(draw_pos + cursor_offset - draw_scroll);\n            ImRect cursor_screen_rect(cursor_screen_pos.x, cursor_screen_pos.y - g.FontSize + 0.5f, cursor_screen_pos.x + 1.0f, cursor_screen_pos.y - 1.5f);\n            if (cursor_is_visible && cursor_screen_rect.Overlaps(clip_rect))\n                draw_window->DrawList->AddLine(cursor_screen_rect.Min, cursor_screen_rect.GetBL(), GetColorU32(ImGuiCol_Text));\n\n            // Notify OS of text input position for advanced IME (-1 x offset so that Windows IME can cover our cursor. Bit of an extra nicety.)\n            if (!is_readonly)\n            {\n                g.PlatformImeData.WantVisible = true;\n                g.PlatformImeData.InputPos = ImVec2(cursor_screen_pos.x - 1.0f, cursor_screen_pos.y - g.FontSize);\n                g.PlatformImeData.InputLineHeight = g.FontSize;\n            }\n        }\n    }\n    else\n    {\n        // Render text only (no selection, no cursor)\n        if (is_multiline)\n            text_size = ImVec2(inner_size.x, InputTextCalcTextLenAndLineCount(buf_display, &buf_display_end) * g.FontSize); // We don't need width\n        else if (!is_displaying_hint && g.ActiveId == id)\n            buf_display_end = buf_display + state->TextLen;\n        else if (!is_displaying_hint)\n            buf_display_end = buf_display + ImStrlen(buf_display);\n\n        if (is_multiline || (buf_display_end - buf_display) < buf_display_max_length)\n        {\n            // Find render position for right alignment\n            if (flags & ImGuiInputTextFlags_ElideLeft)\n                draw_pos.x = ImMin(draw_pos.x, frame_bb.Max.x - CalcTextSize(buf_display, NULL).x - style.FramePadding.x);\n\n            const ImVec2 draw_scroll = /*state ? ImVec2(state->Scroll.x, 0.0f) :*/ ImVec2(0.0f, 0.0f); // Preserve scroll when inactive?\n            ImU32 col = GetColorU32(is_displaying_hint ? ImGuiCol_TextDisabled : ImGuiCol_Text);\n            draw_window->DrawList->AddText(g.Font, g.FontSize, draw_pos - draw_scroll, col, buf_display, buf_display_end, 0.0f, is_multiline ? NULL : &clip_rect);\n        }\n    }\n\n    if (is_password && !is_displaying_hint)\n        PopFont();\n\n    if (is_multiline)\n    {\n        // For focus requests to work on our multiline we need to ensure our child ItemAdd() call specifies the ImGuiItemFlags_Inputable (see #4761, #7870)...\n        Dummy(ImVec2(text_size.x, text_size.y + style.FramePadding.y));\n        g.NextItemData.ItemFlags |= (ImGuiItemFlags)ImGuiItemFlags_Inputable | ImGuiItemFlags_NoTabStop;\n        EndChild();\n        item_data_backup.StatusFlags |= (g.LastItemData.StatusFlags & ImGuiItemStatusFlags_HoveredWindow);\n\n        // ...and then we need to undo the group overriding last item data, which gets a bit messy as EndGroup() tries to forward scrollbar being active...\n        // FIXME: This quite messy/tricky, should attempt to get rid of the child window.\n        EndGroup();\n        if (g.LastItemData.ID == 0 || g.LastItemData.ID != GetWindowScrollbarID(draw_window, ImGuiAxis_Y))\n        {\n            g.LastItemData.ID = id;\n            g.LastItemData.ItemFlags = item_data_backup.ItemFlags;\n            g.LastItemData.StatusFlags = item_data_backup.StatusFlags;\n        }\n    }\n    if (state)\n        state->TextSrc = NULL;\n\n    // Log as text\n    if (g.LogEnabled && (!is_password || is_displaying_hint))\n    {\n        LogSetNextTextDecoration(\"{\", \"}\");\n        LogRenderedText(&draw_pos, buf_display, buf_display_end);\n    }\n\n    if (label_size.x > 0)\n        RenderText(ImVec2(frame_bb.Max.x + style.ItemInnerSpacing.x, frame_bb.Min.y + style.FramePadding.y), label);\n\n    if (value_changed)\n        MarkItemEdited(id);\n\n    IMGUI_TEST_ENGINE_ITEM_INFO(id, label, g.LastItemData.StatusFlags | ImGuiItemStatusFlags_Inputable);\n    if ((flags & ImGuiInputTextFlags_EnterReturnsTrue) != 0)\n        return validated;\n    else\n        return value_changed;\n}\n\nvoid ImGui::DebugNodeInputTextState(ImGuiInputTextState* state)\n{\n#ifndef IMGUI_DISABLE_DEBUG_TOOLS\n    ImGuiContext& g = *GImGui;\n    ImStb::STB_TexteditState* stb_state = state->Stb;\n    ImStb::StbUndoState* undo_state = &stb_state->undostate;\n    Text(\"ID: 0x%08X, ActiveID: 0x%08X\", state->ID, g.ActiveId);\n    DebugLocateItemOnHover(state->ID);\n    Text(\"CurLenA: %d, Cursor: %d, Selection: %d..%d\", state->TextLen, stb_state->cursor, stb_state->select_start, stb_state->select_end);\n    Text(\"BufCapacityA: %d\", state->BufCapacity);\n    Text(\"(Internal Buffer: TextA Size: %d, Capacity: %d)\", state->TextA.Size, state->TextA.Capacity);\n    Text(\"has_preferred_x: %d (%.2f)\", stb_state->has_preferred_x, stb_state->preferred_x);\n    Text(\"undo_point: %d, redo_point: %d, undo_char_point: %d, redo_char_point: %d\", undo_state->undo_point, undo_state->redo_point, undo_state->undo_char_point, undo_state->redo_char_point);\n    if (BeginChild(\"undopoints\", ImVec2(0.0f, GetTextLineHeight() * 10), ImGuiChildFlags_Borders | ImGuiChildFlags_ResizeY)) // Visualize undo state\n    {\n        PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0, 0));\n        for (int n = 0; n < IMSTB_TEXTEDIT_UNDOSTATECOUNT; n++)\n        {\n            ImStb::StbUndoRecord* undo_rec = &undo_state->undo_rec[n];\n            const char undo_rec_type = (n < undo_state->undo_point) ? 'u' : (n >= undo_state->redo_point) ? 'r' : ' ';\n            if (undo_rec_type == ' ')\n                BeginDisabled();\n            const int buf_preview_len = (undo_rec_type != ' ' && undo_rec->char_storage != -1) ? undo_rec->insert_length : 0;\n            const char* buf_preview_str = undo_state->undo_char + undo_rec->char_storage;\n            Text(\"%c [%02d] where %03d, insert %03d, delete %03d, char_storage %03d \\\"%.*s\\\"\",\n                undo_rec_type, n, undo_rec->where, undo_rec->insert_length, undo_rec->delete_length, undo_rec->char_storage, buf_preview_len, buf_preview_str);\n            if (undo_rec_type == ' ')\n                EndDisabled();\n        }\n        PopStyleVar();\n    }\n    EndChild();\n#else\n    IM_UNUSED(state);\n#endif\n}\n\n//-------------------------------------------------------------------------\n// [SECTION] Widgets: ColorEdit, ColorPicker, ColorButton, etc.\n//-------------------------------------------------------------------------\n// - ColorEdit3()\n// - ColorEdit4()\n// - ColorPicker3()\n// - RenderColorRectWithAlphaCheckerboard() [Internal]\n// - ColorPicker4()\n// - ColorButton()\n// - SetColorEditOptions()\n// - ColorTooltip() [Internal]\n// - ColorEditOptionsPopup() [Internal]\n// - ColorPickerOptionsPopup() [Internal]\n//-------------------------------------------------------------------------\n\nbool ImGui::ColorEdit3(const char* label, float col[3], ImGuiColorEditFlags flags)\n{\n    return ColorEdit4(label, col, flags | ImGuiColorEditFlags_NoAlpha);\n}\n\nstatic void ColorEditRestoreH(const float* col, float* H)\n{\n    ImGuiContext& g = *GImGui;\n    IM_ASSERT(g.ColorEditCurrentID != 0);\n    if (g.ColorEditSavedID != g.ColorEditCurrentID || g.ColorEditSavedColor != ImGui::ColorConvertFloat4ToU32(ImVec4(col[0], col[1], col[2], 0)))\n        return;\n    *H = g.ColorEditSavedHue;\n}\n\n// ColorEdit supports RGB and HSV inputs. In case of RGB input resulting color may have undefined hue and/or saturation.\n// Since widget displays both RGB and HSV values we must preserve hue and saturation to prevent these values resetting.\nstatic void ColorEditRestoreHS(const float* col, float* H, float* S, float* V)\n{\n    ImGuiContext& g = *GImGui;\n    IM_ASSERT(g.ColorEditCurrentID != 0);\n    if (g.ColorEditSavedID != g.ColorEditCurrentID || g.ColorEditSavedColor != ImGui::ColorConvertFloat4ToU32(ImVec4(col[0], col[1], col[2], 0)))\n        return;\n\n    // When S == 0, H is undefined.\n    // When H == 1 it wraps around to 0.\n    if (*S == 0.0f || (*H == 0.0f && g.ColorEditSavedHue == 1))\n        *H = g.ColorEditSavedHue;\n\n    // When V == 0, S is undefined.\n    if (*V == 0.0f)\n        *S = g.ColorEditSavedSat;\n}\n\n// Edit colors components (each component in 0.0f..1.0f range).\n// See enum ImGuiColorEditFlags_ for available options. e.g. Only access 3 floats if ImGuiColorEditFlags_NoAlpha flag is set.\n// With typical options: Left-click on color square to open color picker. Right-click to open option menu. CTRL+Click over input fields to edit them and TAB to go to next item.\nbool ImGui::ColorEdit4(const char* label, float col[4], ImGuiColorEditFlags flags)\n{\n    ImGuiWindow* window = GetCurrentWindow();\n    if (window->SkipItems)\n        return false;\n\n    ImGuiContext& g = *GImGui;\n    const ImGuiStyle& style = g.Style;\n    const float square_sz = GetFrameHeight();\n    const char* label_display_end = FindRenderedTextEnd(label);\n    float w_full = CalcItemWidth();\n    g.NextItemData.ClearFlags();\n\n    BeginGroup();\n    PushID(label);\n    const bool set_current_color_edit_id = (g.ColorEditCurrentID == 0);\n    if (set_current_color_edit_id)\n        g.ColorEditCurrentID = window->IDStack.back();\n\n    // If we're not showing any slider there's no point in doing any HSV conversions\n    const ImGuiColorEditFlags flags_untouched = flags;\n    if (flags & ImGuiColorEditFlags_NoInputs)\n        flags = (flags & (~ImGuiColorEditFlags_DisplayMask_)) | ImGuiColorEditFlags_DisplayRGB | ImGuiColorEditFlags_NoOptions;\n\n    // Context menu: display and modify options (before defaults are applied)\n    if (!(flags & ImGuiColorEditFlags_NoOptions))\n        ColorEditOptionsPopup(col, flags);\n\n    // Read stored options\n    if (!(flags & ImGuiColorEditFlags_DisplayMask_))\n        flags |= (g.ColorEditOptions & ImGuiColorEditFlags_DisplayMask_);\n    if (!(flags & ImGuiColorEditFlags_DataTypeMask_))\n        flags |= (g.ColorEditOptions & ImGuiColorEditFlags_DataTypeMask_);\n    if (!(flags & ImGuiColorEditFlags_PickerMask_))\n        flags |= (g.ColorEditOptions & ImGuiColorEditFlags_PickerMask_);\n    if (!(flags & ImGuiColorEditFlags_InputMask_))\n        flags |= (g.ColorEditOptions & ImGuiColorEditFlags_InputMask_);\n    flags |= (g.ColorEditOptions & ~(ImGuiColorEditFlags_DisplayMask_ | ImGuiColorEditFlags_DataTypeMask_ | ImGuiColorEditFlags_PickerMask_ | ImGuiColorEditFlags_InputMask_));\n    IM_ASSERT(ImIsPowerOfTwo(flags & ImGuiColorEditFlags_DisplayMask_)); // Check that only 1 is selected\n    IM_ASSERT(ImIsPowerOfTwo(flags & ImGuiColorEditFlags_InputMask_));   // Check that only 1 is selected\n\n    const bool alpha = (flags & ImGuiColorEditFlags_NoAlpha) == 0;\n    const bool hdr = (flags & ImGuiColorEditFlags_HDR) != 0;\n    const int components = alpha ? 4 : 3;\n    const float w_button = (flags & ImGuiColorEditFlags_NoSmallPreview) ? 0.0f : (square_sz + style.ItemInnerSpacing.x);\n    const float w_inputs = ImMax(w_full - w_button, 1.0f);\n    w_full = w_inputs + w_button;\n\n    // Convert to the formats we need\n    float f[4] = { col[0], col[1], col[2], alpha ? col[3] : 1.0f };\n    if ((flags & ImGuiColorEditFlags_InputHSV) && (flags & ImGuiColorEditFlags_DisplayRGB))\n        ColorConvertHSVtoRGB(f[0], f[1], f[2], f[0], f[1], f[2]);\n    else if ((flags & ImGuiColorEditFlags_InputRGB) && (flags & ImGuiColorEditFlags_DisplayHSV))\n    {\n        // Hue is lost when converting from grayscale rgb (saturation=0). Restore it.\n        ColorConvertRGBtoHSV(f[0], f[1], f[2], f[0], f[1], f[2]);\n        ColorEditRestoreHS(col, &f[0], &f[1], &f[2]);\n    }\n    int i[4] = { IM_F32_TO_INT8_UNBOUND(f[0]), IM_F32_TO_INT8_UNBOUND(f[1]), IM_F32_TO_INT8_UNBOUND(f[2]), IM_F32_TO_INT8_UNBOUND(f[3]) };\n\n    bool value_changed = false;\n    bool value_changed_as_float = false;\n\n    const ImVec2 pos = window->DC.CursorPos;\n    const float inputs_offset_x = (style.ColorButtonPosition == ImGuiDir_Left) ? w_button : 0.0f;\n    window->DC.CursorPos.x = pos.x + inputs_offset_x;\n\n    if ((flags & (ImGuiColorEditFlags_DisplayRGB | ImGuiColorEditFlags_DisplayHSV)) != 0 && (flags & ImGuiColorEditFlags_NoInputs) == 0)\n    {\n        // RGB/HSV 0..255 Sliders\n        const float w_items = w_inputs - style.ItemInnerSpacing.x * (components - 1);\n\n        const bool hide_prefix = (IM_TRUNC(w_items / components) <= CalcTextSize((flags & ImGuiColorEditFlags_Float) ? \"M:0.000\" : \"M:000\").x);\n        static const char* ids[4] = { \"##X\", \"##Y\", \"##Z\", \"##W\" };\n        static const char* fmt_table_int[3][4] =\n        {\n            {   \"%3d\",   \"%3d\",   \"%3d\",   \"%3d\" }, // Short display\n            { \"R:%3d\", \"G:%3d\", \"B:%3d\", \"A:%3d\" }, // Long display for RGBA\n            { \"H:%3d\", \"S:%3d\", \"V:%3d\", \"A:%3d\" }  // Long display for HSVA\n        };\n        static const char* fmt_table_float[3][4] =\n        {\n            {   \"%0.3f\",   \"%0.3f\",   \"%0.3f\",   \"%0.3f\" }, // Short display\n            { \"R:%0.3f\", \"G:%0.3f\", \"B:%0.3f\", \"A:%0.3f\" }, // Long display for RGBA\n            { \"H:%0.3f\", \"S:%0.3f\", \"V:%0.3f\", \"A:%0.3f\" }  // Long display for HSVA\n        };\n        const int fmt_idx = hide_prefix ? 0 : (flags & ImGuiColorEditFlags_DisplayHSV) ? 2 : 1;\n\n        float prev_split = 0.0f;\n        for (int n = 0; n < components; n++)\n        {\n            if (n > 0)\n                SameLine(0, style.ItemInnerSpacing.x);\n            float next_split = IM_TRUNC(w_items * (n + 1) / components);\n            SetNextItemWidth(ImMax(next_split - prev_split, 1.0f));\n            prev_split = next_split;\n\n            // FIXME: When ImGuiColorEditFlags_HDR flag is passed HS values snap in weird ways when SV values go below 0.\n            if (flags & ImGuiColorEditFlags_Float)\n            {\n                value_changed |= DragFloat(ids[n], &f[n], 1.0f / 255.0f, 0.0f, hdr ? 0.0f : 1.0f, fmt_table_float[fmt_idx][n]);\n                value_changed_as_float |= value_changed;\n            }\n            else\n            {\n                value_changed |= DragInt(ids[n], &i[n], 1.0f, 0, hdr ? 0 : 255, fmt_table_int[fmt_idx][n]);\n            }\n            if (!(flags & ImGuiColorEditFlags_NoOptions))\n                OpenPopupOnItemClick(\"context\", ImGuiPopupFlags_MouseButtonRight);\n        }\n    }\n    else if ((flags & ImGuiColorEditFlags_DisplayHex) != 0 && (flags & ImGuiColorEditFlags_NoInputs) == 0)\n    {\n        // RGB Hexadecimal Input\n        char buf[64];\n        if (alpha)\n            ImFormatString(buf, IM_ARRAYSIZE(buf), \"#%02X%02X%02X%02X\", ImClamp(i[0], 0, 255), ImClamp(i[1], 0, 255), ImClamp(i[2], 0, 255), ImClamp(i[3], 0, 255));\n        else\n            ImFormatString(buf, IM_ARRAYSIZE(buf), \"#%02X%02X%02X\", ImClamp(i[0], 0, 255), ImClamp(i[1], 0, 255), ImClamp(i[2], 0, 255));\n        SetNextItemWidth(w_inputs);\n        if (InputText(\"##Text\", buf, IM_ARRAYSIZE(buf), ImGuiInputTextFlags_CharsUppercase))\n        {\n            value_changed = true;\n            char* p = buf;\n            while (*p == '#' || ImCharIsBlankA(*p))\n                p++;\n            i[0] = i[1] = i[2] = 0;\n            i[3] = 0xFF; // alpha default to 255 is not parsed by scanf (e.g. inputting #FFFFFF omitting alpha)\n            int r;\n            if (alpha)\n                r = sscanf(p, \"%02X%02X%02X%02X\", (unsigned int*)&i[0], (unsigned int*)&i[1], (unsigned int*)&i[2], (unsigned int*)&i[3]); // Treat at unsigned (%X is unsigned)\n            else\n                r = sscanf(p, \"%02X%02X%02X\", (unsigned int*)&i[0], (unsigned int*)&i[1], (unsigned int*)&i[2]);\n            IM_UNUSED(r); // Fixes C6031: Return value ignored: 'sscanf'.\n        }\n        if (!(flags & ImGuiColorEditFlags_NoOptions))\n            OpenPopupOnItemClick(\"context\", ImGuiPopupFlags_MouseButtonRight);\n    }\n\n    ImGuiWindow* picker_active_window = NULL;\n    if (!(flags & ImGuiColorEditFlags_NoSmallPreview))\n    {\n        const float button_offset_x = ((flags & ImGuiColorEditFlags_NoInputs) || (style.ColorButtonPosition == ImGuiDir_Left)) ? 0.0f : w_inputs + style.ItemInnerSpacing.x;\n        window->DC.CursorPos = ImVec2(pos.x + button_offset_x, pos.y);\n\n        const ImVec4 col_v4(col[0], col[1], col[2], alpha ? col[3] : 1.0f);\n        if (ColorButton(\"##ColorButton\", col_v4, flags))\n        {\n            if (!(flags & ImGuiColorEditFlags_NoPicker))\n            {\n                // Store current color and open a picker\n                g.ColorPickerRef = col_v4;\n                OpenPopup(\"picker\");\n                SetNextWindowPos(g.LastItemData.Rect.GetBL() + ImVec2(0.0f, style.ItemSpacing.y));\n            }\n        }\n        if (!(flags & ImGuiColorEditFlags_NoOptions))\n            OpenPopupOnItemClick(\"context\", ImGuiPopupFlags_MouseButtonRight);\n\n        if (BeginPopup(\"picker\"))\n        {\n            if (g.CurrentWindow->BeginCount == 1)\n            {\n                picker_active_window = g.CurrentWindow;\n                if (label != label_display_end)\n                {\n                    TextEx(label, label_display_end);\n                    Spacing();\n                }\n                ImGuiColorEditFlags picker_flags_to_forward = ImGuiColorEditFlags_DataTypeMask_ | ImGuiColorEditFlags_PickerMask_ | ImGuiColorEditFlags_InputMask_ | ImGuiColorEditFlags_HDR | ImGuiColorEditFlags_NoAlpha | ImGuiColorEditFlags_AlphaBar;\n                ImGuiColorEditFlags picker_flags = (flags_untouched & picker_flags_to_forward) | ImGuiColorEditFlags_DisplayMask_ | ImGuiColorEditFlags_NoLabel | ImGuiColorEditFlags_AlphaPreviewHalf;\n                SetNextItemWidth(square_sz * 12.0f); // Use 256 + bar sizes?\n                value_changed |= ColorPicker4(\"##picker\", col, picker_flags, &g.ColorPickerRef.x);\n            }\n            EndPopup();\n        }\n    }\n\n    if (label != label_display_end && !(flags & ImGuiColorEditFlags_NoLabel))\n    {\n        // Position not necessarily next to last submitted button (e.g. if style.ColorButtonPosition == ImGuiDir_Left),\n        // but we need to use SameLine() to setup baseline correctly. Might want to refactor SameLine() to simplify this.\n        SameLine(0.0f, style.ItemInnerSpacing.x);\n        window->DC.CursorPos.x = pos.x + ((flags & ImGuiColorEditFlags_NoInputs) ? w_button : w_full + style.ItemInnerSpacing.x);\n        TextEx(label, label_display_end);\n    }\n\n    // Convert back\n    if (value_changed && picker_active_window == NULL)\n    {\n        if (!value_changed_as_float)\n            for (int n = 0; n < 4; n++)\n                f[n] = i[n] / 255.0f;\n        if ((flags & ImGuiColorEditFlags_DisplayHSV) && (flags & ImGuiColorEditFlags_InputRGB))\n        {\n            g.ColorEditSavedHue = f[0];\n            g.ColorEditSavedSat = f[1];\n            ColorConvertHSVtoRGB(f[0], f[1], f[2], f[0], f[1], f[2]);\n            g.ColorEditSavedID = g.ColorEditCurrentID;\n            g.ColorEditSavedColor = ColorConvertFloat4ToU32(ImVec4(f[0], f[1], f[2], 0));\n        }\n        if ((flags & ImGuiColorEditFlags_DisplayRGB) && (flags & ImGuiColorEditFlags_InputHSV))\n            ColorConvertRGBtoHSV(f[0], f[1], f[2], f[0], f[1], f[2]);\n\n        col[0] = f[0];\n        col[1] = f[1];\n        col[2] = f[2];\n        if (alpha)\n            col[3] = f[3];\n    }\n\n    if (set_current_color_edit_id)\n        g.ColorEditCurrentID = 0;\n    PopID();\n    EndGroup();\n\n    // Drag and Drop Target\n    // NB: The flag test is merely an optional micro-optimization, BeginDragDropTarget() does the same test.\n    if ((g.LastItemData.StatusFlags & ImGuiItemStatusFlags_HoveredRect) && !(g.LastItemData.ItemFlags & ImGuiItemFlags_ReadOnly) && !(flags & ImGuiColorEditFlags_NoDragDrop) && BeginDragDropTarget())\n    {\n        bool accepted_drag_drop = false;\n        if (const ImGuiPayload* payload = AcceptDragDropPayload(IMGUI_PAYLOAD_TYPE_COLOR_3F))\n        {\n            memcpy((float*)col, payload->Data, sizeof(float) * 3); // Preserve alpha if any //-V512 //-V1086\n            value_changed = accepted_drag_drop = true;\n        }\n        if (const ImGuiPayload* payload = AcceptDragDropPayload(IMGUI_PAYLOAD_TYPE_COLOR_4F))\n        {\n            memcpy((float*)col, payload->Data, sizeof(float) * components);\n            value_changed = accepted_drag_drop = true;\n        }\n\n        // Drag-drop payloads are always RGB\n        if (accepted_drag_drop && (flags & ImGuiColorEditFlags_InputHSV))\n            ColorConvertRGBtoHSV(col[0], col[1], col[2], col[0], col[1], col[2]);\n        EndDragDropTarget();\n    }\n\n    // When picker is being actively used, use its active id so IsItemActive() will function on ColorEdit4().\n    if (picker_active_window && g.ActiveId != 0 && g.ActiveIdWindow == picker_active_window)\n        g.LastItemData.ID = g.ActiveId;\n\n    if (value_changed && g.LastItemData.ID != 0) // In case of ID collision, the second EndGroup() won't catch g.ActiveId\n        MarkItemEdited(g.LastItemData.ID);\n\n    return value_changed;\n}\n\nbool ImGui::ColorPicker3(const char* label, float col[3], ImGuiColorEditFlags flags)\n{\n    float col4[4] = { col[0], col[1], col[2], 1.0f };\n    if (!ColorPicker4(label, col4, flags | ImGuiColorEditFlags_NoAlpha))\n        return false;\n    col[0] = col4[0]; col[1] = col4[1]; col[2] = col4[2];\n    return true;\n}\n\n// Helper for ColorPicker4()\nstatic void RenderArrowsForVerticalBar(ImDrawList* draw_list, ImVec2 pos, ImVec2 half_sz, float bar_w, float alpha)\n{\n    ImU32 alpha8 = IM_F32_TO_INT8_SAT(alpha);\n    ImGui::RenderArrowPointingAt(draw_list, ImVec2(pos.x + half_sz.x + 1,         pos.y), ImVec2(half_sz.x + 2, half_sz.y + 1), ImGuiDir_Right, IM_COL32(0,0,0,alpha8));\n    ImGui::RenderArrowPointingAt(draw_list, ImVec2(pos.x + half_sz.x,             pos.y), half_sz,                              ImGuiDir_Right, IM_COL32(255,255,255,alpha8));\n    ImGui::RenderArrowPointingAt(draw_list, ImVec2(pos.x + bar_w - half_sz.x - 1, pos.y), ImVec2(half_sz.x + 2, half_sz.y + 1), ImGuiDir_Left,  IM_COL32(0,0,0,alpha8));\n    ImGui::RenderArrowPointingAt(draw_list, ImVec2(pos.x + bar_w - half_sz.x,     pos.y), half_sz,                              ImGuiDir_Left,  IM_COL32(255,255,255,alpha8));\n}\n\n// Note: ColorPicker4() only accesses 3 floats if ImGuiColorEditFlags_NoAlpha flag is set.\n// (In C++ the 'float col[4]' notation for a function argument is equivalent to 'float* col', we only specify a size to facilitate understanding of the code.)\n// FIXME: we adjust the big color square height based on item width, which may cause a flickering feedback loop (if automatic height makes a vertical scrollbar appears, affecting automatic width..)\n// FIXME: this is trying to be aware of style.Alpha but not fully correct. Also, the color wheel will have overlapping glitches with (style.Alpha < 1.0)\nbool ImGui::ColorPicker4(const char* label, float col[4], ImGuiColorEditFlags flags, const float* ref_col)\n{\n    ImGuiContext& g = *GImGui;\n    ImGuiWindow* window = GetCurrentWindow();\n    if (window->SkipItems)\n        return false;\n\n    ImDrawList* draw_list = window->DrawList;\n    ImGuiStyle& style = g.Style;\n    ImGuiIO& io = g.IO;\n\n    const float width = CalcItemWidth();\n    const bool is_readonly = ((g.NextItemData.ItemFlags | g.CurrentItemFlags) & ImGuiItemFlags_ReadOnly) != 0;\n    g.NextItemData.ClearFlags();\n\n    PushID(label);\n    const bool set_current_color_edit_id = (g.ColorEditCurrentID == 0);\n    if (set_current_color_edit_id)\n        g.ColorEditCurrentID = window->IDStack.back();\n    BeginGroup();\n\n    if (!(flags & ImGuiColorEditFlags_NoSidePreview))\n        flags |= ImGuiColorEditFlags_NoSmallPreview;\n\n    // Context menu: display and store options.\n    if (!(flags & ImGuiColorEditFlags_NoOptions))\n        ColorPickerOptionsPopup(col, flags);\n\n    // Read stored options\n    if (!(flags & ImGuiColorEditFlags_PickerMask_))\n        flags |= ((g.ColorEditOptions & ImGuiColorEditFlags_PickerMask_) ? g.ColorEditOptions : ImGuiColorEditFlags_DefaultOptions_) & ImGuiColorEditFlags_PickerMask_;\n    if (!(flags & ImGuiColorEditFlags_InputMask_))\n        flags |= ((g.ColorEditOptions & ImGuiColorEditFlags_InputMask_) ? g.ColorEditOptions : ImGuiColorEditFlags_DefaultOptions_) & ImGuiColorEditFlags_InputMask_;\n    IM_ASSERT(ImIsPowerOfTwo(flags & ImGuiColorEditFlags_PickerMask_)); // Check that only 1 is selected\n    IM_ASSERT(ImIsPowerOfTwo(flags & ImGuiColorEditFlags_InputMask_));  // Check that only 1 is selected\n    if (!(flags & ImGuiColorEditFlags_NoOptions))\n        flags |= (g.ColorEditOptions & ImGuiColorEditFlags_AlphaBar);\n\n    // Setup\n    int components = (flags & ImGuiColorEditFlags_NoAlpha) ? 3 : 4;\n    bool alpha_bar = (flags & ImGuiColorEditFlags_AlphaBar) && !(flags & ImGuiColorEditFlags_NoAlpha);\n    ImVec2 picker_pos = window->DC.CursorPos;\n    float square_sz = GetFrameHeight();\n    float bars_width = square_sz; // Arbitrary smallish width of Hue/Alpha picking bars\n    float sv_picker_size = ImMax(bars_width * 1, width - (alpha_bar ? 2 : 1) * (bars_width + style.ItemInnerSpacing.x)); // Saturation/Value picking box\n    float bar0_pos_x = picker_pos.x + sv_picker_size + style.ItemInnerSpacing.x;\n    float bar1_pos_x = bar0_pos_x + bars_width + style.ItemInnerSpacing.x;\n    float bars_triangles_half_sz = IM_TRUNC(bars_width * 0.20f);\n\n    float backup_initial_col[4];\n    memcpy(backup_initial_col, col, components * sizeof(float));\n\n    float wheel_thickness = sv_picker_size * 0.08f;\n    float wheel_r_outer = sv_picker_size * 0.50f;\n    float wheel_r_inner = wheel_r_outer - wheel_thickness;\n    ImVec2 wheel_center(picker_pos.x + (sv_picker_size + bars_width)*0.5f, picker_pos.y + sv_picker_size * 0.5f);\n\n    // Note: the triangle is displayed rotated with triangle_pa pointing to Hue, but most coordinates stays unrotated for logic.\n    float triangle_r = wheel_r_inner - (int)(sv_picker_size * 0.027f);\n    ImVec2 triangle_pa = ImVec2(triangle_r, 0.0f); // Hue point.\n    ImVec2 triangle_pb = ImVec2(triangle_r * -0.5f, triangle_r * -0.866025f); // Black point.\n    ImVec2 triangle_pc = ImVec2(triangle_r * -0.5f, triangle_r * +0.866025f); // White point.\n\n    float H = col[0], S = col[1], V = col[2];\n    float R = col[0], G = col[1], B = col[2];\n    if (flags & ImGuiColorEditFlags_InputRGB)\n    {\n        // Hue is lost when converting from grayscale rgb (saturation=0). Restore it.\n        ColorConvertRGBtoHSV(R, G, B, H, S, V);\n        ColorEditRestoreHS(col, &H, &S, &V);\n    }\n    else if (flags & ImGuiColorEditFlags_InputHSV)\n    {\n        ColorConvertHSVtoRGB(H, S, V, R, G, B);\n    }\n\n    bool value_changed = false, value_changed_h = false, value_changed_sv = false;\n\n    PushItemFlag(ImGuiItemFlags_NoNav, true);\n    if (flags & ImGuiColorEditFlags_PickerHueWheel)\n    {\n        // Hue wheel + SV triangle logic\n        InvisibleButton(\"hsv\", ImVec2(sv_picker_size + style.ItemInnerSpacing.x + bars_width, sv_picker_size));\n        if (IsItemActive() && !is_readonly)\n        {\n            ImVec2 initial_off = g.IO.MouseClickedPos[0] - wheel_center;\n            ImVec2 current_off = g.IO.MousePos - wheel_center;\n            float initial_dist2 = ImLengthSqr(initial_off);\n            if (initial_dist2 >= (wheel_r_inner - 1) * (wheel_r_inner - 1) && initial_dist2 <= (wheel_r_outer + 1) * (wheel_r_outer + 1))\n            {\n                // Interactive with Hue wheel\n                H = ImAtan2(current_off.y, current_off.x) / IM_PI * 0.5f;\n                if (H < 0.0f)\n                    H += 1.0f;\n                value_changed = value_changed_h = true;\n            }\n            float cos_hue_angle = ImCos(-H * 2.0f * IM_PI);\n            float sin_hue_angle = ImSin(-H * 2.0f * IM_PI);\n            if (ImTriangleContainsPoint(triangle_pa, triangle_pb, triangle_pc, ImRotate(initial_off, cos_hue_angle, sin_hue_angle)))\n            {\n                // Interacting with SV triangle\n                ImVec2 current_off_unrotated = ImRotate(current_off, cos_hue_angle, sin_hue_angle);\n                if (!ImTriangleContainsPoint(triangle_pa, triangle_pb, triangle_pc, current_off_unrotated))\n                    current_off_unrotated = ImTriangleClosestPoint(triangle_pa, triangle_pb, triangle_pc, current_off_unrotated);\n                float uu, vv, ww;\n                ImTriangleBarycentricCoords(triangle_pa, triangle_pb, triangle_pc, current_off_unrotated, uu, vv, ww);\n                V = ImClamp(1.0f - vv, 0.0001f, 1.0f);\n                S = ImClamp(uu / V, 0.0001f, 1.0f);\n                value_changed = value_changed_sv = true;\n            }\n        }\n        if (!(flags & ImGuiColorEditFlags_NoOptions))\n            OpenPopupOnItemClick(\"context\", ImGuiPopupFlags_MouseButtonRight);\n    }\n    else if (flags & ImGuiColorEditFlags_PickerHueBar)\n    {\n        // SV rectangle logic\n        InvisibleButton(\"sv\", ImVec2(sv_picker_size, sv_picker_size));\n        if (IsItemActive() && !is_readonly)\n        {\n            S = ImSaturate((io.MousePos.x - picker_pos.x) / (sv_picker_size - 1));\n            V = 1.0f - ImSaturate((io.MousePos.y - picker_pos.y) / (sv_picker_size - 1));\n            ColorEditRestoreH(col, &H); // Greatly reduces hue jitter and reset to 0 when hue == 255 and color is rapidly modified using SV square.\n            value_changed = value_changed_sv = true;\n        }\n        if (!(flags & ImGuiColorEditFlags_NoOptions))\n            OpenPopupOnItemClick(\"context\", ImGuiPopupFlags_MouseButtonRight);\n\n        // Hue bar logic\n        SetCursorScreenPos(ImVec2(bar0_pos_x, picker_pos.y));\n        InvisibleButton(\"hue\", ImVec2(bars_width, sv_picker_size));\n        if (IsItemActive() && !is_readonly)\n        {\n            H = ImSaturate((io.MousePos.y - picker_pos.y) / (sv_picker_size - 1));\n            value_changed = value_changed_h = true;\n        }\n    }\n\n    // Alpha bar logic\n    if (alpha_bar)\n    {\n        SetCursorScreenPos(ImVec2(bar1_pos_x, picker_pos.y));\n        InvisibleButton(\"alpha\", ImVec2(bars_width, sv_picker_size));\n        if (IsItemActive())\n        {\n            col[3] = 1.0f - ImSaturate((io.MousePos.y - picker_pos.y) / (sv_picker_size - 1));\n            value_changed = true;\n        }\n    }\n    PopItemFlag(); // ImGuiItemFlags_NoNav\n\n    if (!(flags & ImGuiColorEditFlags_NoSidePreview))\n    {\n        SameLine(0, style.ItemInnerSpacing.x);\n        BeginGroup();\n    }\n\n    if (!(flags & ImGuiColorEditFlags_NoLabel))\n    {\n        const char* label_display_end = FindRenderedTextEnd(label);\n        if (label != label_display_end)\n        {\n            if ((flags & ImGuiColorEditFlags_NoSidePreview))\n                SameLine(0, style.ItemInnerSpacing.x);\n            TextEx(label, label_display_end);\n        }\n    }\n\n    if (!(flags & ImGuiColorEditFlags_NoSidePreview))\n    {\n        PushItemFlag(ImGuiItemFlags_NoNavDefaultFocus, true);\n        ImVec4 col_v4(col[0], col[1], col[2], (flags & ImGuiColorEditFlags_NoAlpha) ? 1.0f : col[3]);\n        if ((flags & ImGuiColorEditFlags_NoLabel))\n            Text(\"Current\");\n\n        ImGuiColorEditFlags sub_flags_to_forward = ImGuiColorEditFlags_InputMask_ | ImGuiColorEditFlags_HDR | ImGuiColorEditFlags_AlphaMask_ | ImGuiColorEditFlags_NoTooltip;\n        ColorButton(\"##current\", col_v4, (flags & sub_flags_to_forward), ImVec2(square_sz * 3, square_sz * 2));\n        if (ref_col != NULL)\n        {\n            Text(\"Original\");\n            ImVec4 ref_col_v4(ref_col[0], ref_col[1], ref_col[2], (flags & ImGuiColorEditFlags_NoAlpha) ? 1.0f : ref_col[3]);\n            if (ColorButton(\"##original\", ref_col_v4, (flags & sub_flags_to_forward), ImVec2(square_sz * 3, square_sz * 2)))\n            {\n                memcpy(col, ref_col, components * sizeof(float));\n                value_changed = true;\n            }\n        }\n        PopItemFlag();\n        EndGroup();\n    }\n\n    // Convert back color to RGB\n    if (value_changed_h || value_changed_sv)\n    {\n        if (flags & ImGuiColorEditFlags_InputRGB)\n        {\n            ColorConvertHSVtoRGB(H, S, V, col[0], col[1], col[2]);\n            g.ColorEditSavedHue = H;\n            g.ColorEditSavedSat = S;\n            g.ColorEditSavedID = g.ColorEditCurrentID;\n            g.ColorEditSavedColor = ColorConvertFloat4ToU32(ImVec4(col[0], col[1], col[2], 0));\n        }\n        else if (flags & ImGuiColorEditFlags_InputHSV)\n        {\n            col[0] = H;\n            col[1] = S;\n            col[2] = V;\n        }\n    }\n\n    // R,G,B and H,S,V slider color editor\n    bool value_changed_fix_hue_wrap = false;\n    if ((flags & ImGuiColorEditFlags_NoInputs) == 0)\n    {\n        PushItemWidth((alpha_bar ? bar1_pos_x : bar0_pos_x) + bars_width - picker_pos.x);\n        ImGuiColorEditFlags sub_flags_to_forward = ImGuiColorEditFlags_DataTypeMask_ | ImGuiColorEditFlags_InputMask_ | ImGuiColorEditFlags_HDR | ImGuiColorEditFlags_AlphaMask_ | ImGuiColorEditFlags_NoOptions | ImGuiColorEditFlags_NoTooltip | ImGuiColorEditFlags_NoSmallPreview;\n        ImGuiColorEditFlags sub_flags = (flags & sub_flags_to_forward) | ImGuiColorEditFlags_NoPicker;\n        if (flags & ImGuiColorEditFlags_DisplayRGB || (flags & ImGuiColorEditFlags_DisplayMask_) == 0)\n            if (ColorEdit4(\"##rgb\", col, sub_flags | ImGuiColorEditFlags_DisplayRGB))\n            {\n                // FIXME: Hackily differentiating using the DragInt (ActiveId != 0 && !ActiveIdAllowOverlap) vs. using the InputText or DropTarget.\n                // For the later we don't want to run the hue-wrap canceling code. If you are well versed in HSV picker please provide your input! (See #2050)\n                value_changed_fix_hue_wrap = (g.ActiveId != 0 && !g.ActiveIdAllowOverlap);\n                value_changed = true;\n            }\n        if (flags & ImGuiColorEditFlags_DisplayHSV || (flags & ImGuiColorEditFlags_DisplayMask_) == 0)\n            value_changed |= ColorEdit4(\"##hsv\", col, sub_flags | ImGuiColorEditFlags_DisplayHSV);\n        if (flags & ImGuiColorEditFlags_DisplayHex || (flags & ImGuiColorEditFlags_DisplayMask_) == 0)\n            value_changed |= ColorEdit4(\"##hex\", col, sub_flags | ImGuiColorEditFlags_DisplayHex);\n        PopItemWidth();\n    }\n\n    // Try to cancel hue wrap (after ColorEdit4 call), if any\n    if (value_changed_fix_hue_wrap && (flags & ImGuiColorEditFlags_InputRGB))\n    {\n        float new_H, new_S, new_V;\n        ColorConvertRGBtoHSV(col[0], col[1], col[2], new_H, new_S, new_V);\n        if (new_H <= 0 && H > 0)\n        {\n            if (new_V <= 0 && V != new_V)\n                ColorConvertHSVtoRGB(H, S, new_V <= 0 ? V * 0.5f : new_V, col[0], col[1], col[2]);\n            else if (new_S <= 0)\n                ColorConvertHSVtoRGB(H, new_S <= 0 ? S * 0.5f : new_S, new_V, col[0], col[1], col[2]);\n        }\n    }\n\n    if (value_changed)\n    {\n        if (flags & ImGuiColorEditFlags_InputRGB)\n        {\n            R = col[0];\n            G = col[1];\n            B = col[2];\n            ColorConvertRGBtoHSV(R, G, B, H, S, V);\n            ColorEditRestoreHS(col, &H, &S, &V);   // Fix local Hue as display below will use it immediately.\n        }\n        else if (flags & ImGuiColorEditFlags_InputHSV)\n        {\n            H = col[0];\n            S = col[1];\n            V = col[2];\n            ColorConvertHSVtoRGB(H, S, V, R, G, B);\n        }\n    }\n\n    const int style_alpha8 = IM_F32_TO_INT8_SAT(style.Alpha);\n    const ImU32 col_black = IM_COL32(0,0,0,style_alpha8);\n    const ImU32 col_white = IM_COL32(255,255,255,style_alpha8);\n    const ImU32 col_midgrey = IM_COL32(128,128,128,style_alpha8);\n    const ImU32 col_hues[6 + 1] = { IM_COL32(255,0,0,style_alpha8), IM_COL32(255,255,0,style_alpha8), IM_COL32(0,255,0,style_alpha8), IM_COL32(0,255,255,style_alpha8), IM_COL32(0,0,255,style_alpha8), IM_COL32(255,0,255,style_alpha8), IM_COL32(255,0,0,style_alpha8) };\n\n    ImVec4 hue_color_f(1, 1, 1, style.Alpha); ColorConvertHSVtoRGB(H, 1, 1, hue_color_f.x, hue_color_f.y, hue_color_f.z);\n    ImU32 hue_color32 = ColorConvertFloat4ToU32(hue_color_f);\n    ImU32 user_col32_striped_of_alpha = ColorConvertFloat4ToU32(ImVec4(R, G, B, style.Alpha)); // Important: this is still including the main rendering/style alpha!!\n\n    ImVec2 sv_cursor_pos;\n\n    if (flags & ImGuiColorEditFlags_PickerHueWheel)\n    {\n        // Render Hue Wheel\n        const float aeps = 0.5f / wheel_r_outer; // Half a pixel arc length in radians (2pi cancels out).\n        const int segment_per_arc = ImMax(4, (int)wheel_r_outer / 12);\n        for (int n = 0; n < 6; n++)\n        {\n            const float a0 = (n)     /6.0f * 2.0f * IM_PI - aeps;\n            const float a1 = (n+1.0f)/6.0f * 2.0f * IM_PI + aeps;\n            const int vert_start_idx = draw_list->VtxBuffer.Size;\n            draw_list->PathArcTo(wheel_center, (wheel_r_inner + wheel_r_outer)*0.5f, a0, a1, segment_per_arc);\n            draw_list->PathStroke(col_white, 0, wheel_thickness);\n            const int vert_end_idx = draw_list->VtxBuffer.Size;\n\n            // Paint colors over existing vertices\n            ImVec2 gradient_p0(wheel_center.x + ImCos(a0) * wheel_r_inner, wheel_center.y + ImSin(a0) * wheel_r_inner);\n            ImVec2 gradient_p1(wheel_center.x + ImCos(a1) * wheel_r_inner, wheel_center.y + ImSin(a1) * wheel_r_inner);\n            ShadeVertsLinearColorGradientKeepAlpha(draw_list, vert_start_idx, vert_end_idx, gradient_p0, gradient_p1, col_hues[n], col_hues[n + 1]);\n        }\n\n        // Render Cursor + preview on Hue Wheel\n        float cos_hue_angle = ImCos(H * 2.0f * IM_PI);\n        float sin_hue_angle = ImSin(H * 2.0f * IM_PI);\n        ImVec2 hue_cursor_pos(wheel_center.x + cos_hue_angle * (wheel_r_inner + wheel_r_outer) * 0.5f, wheel_center.y + sin_hue_angle * (wheel_r_inner + wheel_r_outer) * 0.5f);\n        float hue_cursor_rad = value_changed_h ? wheel_thickness * 0.65f : wheel_thickness * 0.55f;\n        int hue_cursor_segments = draw_list->_CalcCircleAutoSegmentCount(hue_cursor_rad); // Lock segment count so the +1 one matches others.\n        draw_list->AddCircleFilled(hue_cursor_pos, hue_cursor_rad, hue_color32, hue_cursor_segments);\n        draw_list->AddCircle(hue_cursor_pos, hue_cursor_rad + 1, col_midgrey, hue_cursor_segments);\n        draw_list->AddCircle(hue_cursor_pos, hue_cursor_rad, col_white, hue_cursor_segments);\n\n        // Render SV triangle (rotated according to hue)\n        ImVec2 tra = wheel_center + ImRotate(triangle_pa, cos_hue_angle, sin_hue_angle);\n        ImVec2 trb = wheel_center + ImRotate(triangle_pb, cos_hue_angle, sin_hue_angle);\n        ImVec2 trc = wheel_center + ImRotate(triangle_pc, cos_hue_angle, sin_hue_angle);\n        ImVec2 uv_white = GetFontTexUvWhitePixel();\n        draw_list->PrimReserve(3, 3);\n        draw_list->PrimVtx(tra, uv_white, hue_color32);\n        draw_list->PrimVtx(trb, uv_white, col_black);\n        draw_list->PrimVtx(trc, uv_white, col_white);\n        draw_list->AddTriangle(tra, trb, trc, col_midgrey, 1.5f);\n        sv_cursor_pos = ImLerp(ImLerp(trc, tra, ImSaturate(S)), trb, ImSaturate(1 - V));\n    }\n    else if (flags & ImGuiColorEditFlags_PickerHueBar)\n    {\n        // Render SV Square\n        draw_list->AddRectFilledMultiColor(picker_pos, picker_pos + ImVec2(sv_picker_size, sv_picker_size), col_white, hue_color32, hue_color32, col_white);\n        draw_list->AddRectFilledMultiColor(picker_pos, picker_pos + ImVec2(sv_picker_size, sv_picker_size), 0, 0, col_black, col_black);\n        RenderFrameBorder(picker_pos, picker_pos + ImVec2(sv_picker_size, sv_picker_size), 0.0f);\n        sv_cursor_pos.x = ImClamp(IM_ROUND(picker_pos.x + ImSaturate(S)     * sv_picker_size), picker_pos.x + 2, picker_pos.x + sv_picker_size - 2); // Sneakily prevent the circle to stick out too much\n        sv_cursor_pos.y = ImClamp(IM_ROUND(picker_pos.y + ImSaturate(1 - V) * sv_picker_size), picker_pos.y + 2, picker_pos.y + sv_picker_size - 2);\n\n        // Render Hue Bar\n        for (int i = 0; i < 6; ++i)\n            draw_list->AddRectFilledMultiColor(ImVec2(bar0_pos_x, picker_pos.y + i * (sv_picker_size / 6)), ImVec2(bar0_pos_x + bars_width, picker_pos.y + (i + 1) * (sv_picker_size / 6)), col_hues[i], col_hues[i], col_hues[i + 1], col_hues[i + 1]);\n        float bar0_line_y = IM_ROUND(picker_pos.y + H * sv_picker_size);\n        RenderFrameBorder(ImVec2(bar0_pos_x, picker_pos.y), ImVec2(bar0_pos_x + bars_width, picker_pos.y + sv_picker_size), 0.0f);\n        RenderArrowsForVerticalBar(draw_list, ImVec2(bar0_pos_x - 1, bar0_line_y), ImVec2(bars_triangles_half_sz + 1, bars_triangles_half_sz), bars_width + 2.0f, style.Alpha);\n    }\n\n    // Render cursor/preview circle (clamp S/V within 0..1 range because floating points colors may lead HSV values to be out of range)\n    float sv_cursor_rad = value_changed_sv ? wheel_thickness * 0.55f : wheel_thickness * 0.40f;\n    int sv_cursor_segments = draw_list->_CalcCircleAutoSegmentCount(sv_cursor_rad); // Lock segment count so the +1 one matches others.\n    draw_list->AddCircleFilled(sv_cursor_pos, sv_cursor_rad, user_col32_striped_of_alpha, sv_cursor_segments);\n    draw_list->AddCircle(sv_cursor_pos, sv_cursor_rad + 1, col_midgrey, sv_cursor_segments);\n    draw_list->AddCircle(sv_cursor_pos, sv_cursor_rad, col_white, sv_cursor_segments);\n\n    // Render alpha bar\n    if (alpha_bar)\n    {\n        float alpha = ImSaturate(col[3]);\n        ImRect bar1_bb(bar1_pos_x, picker_pos.y, bar1_pos_x + bars_width, picker_pos.y + sv_picker_size);\n        RenderColorRectWithAlphaCheckerboard(draw_list, bar1_bb.Min, bar1_bb.Max, 0, bar1_bb.GetWidth() / 2.0f, ImVec2(0.0f, 0.0f));\n        draw_list->AddRectFilledMultiColor(bar1_bb.Min, bar1_bb.Max, user_col32_striped_of_alpha, user_col32_striped_of_alpha, user_col32_striped_of_alpha & ~IM_COL32_A_MASK, user_col32_striped_of_alpha & ~IM_COL32_A_MASK);\n        float bar1_line_y = IM_ROUND(picker_pos.y + (1.0f - alpha) * sv_picker_size);\n        RenderFrameBorder(bar1_bb.Min, bar1_bb.Max, 0.0f);\n        RenderArrowsForVerticalBar(draw_list, ImVec2(bar1_pos_x - 1, bar1_line_y), ImVec2(bars_triangles_half_sz + 1, bars_triangles_half_sz), bars_width + 2.0f, style.Alpha);\n    }\n\n    EndGroup();\n\n    if (value_changed && memcmp(backup_initial_col, col, components * sizeof(float)) == 0)\n        value_changed = false;\n    if (value_changed && g.LastItemData.ID != 0) // In case of ID collision, the second EndGroup() won't catch g.ActiveId\n        MarkItemEdited(g.LastItemData.ID);\n\n    if (set_current_color_edit_id)\n        g.ColorEditCurrentID = 0;\n    PopID();\n\n    return value_changed;\n}\n\n// A little color square. Return true when clicked.\n// FIXME: May want to display/ignore the alpha component in the color display? Yet show it in the tooltip.\n// 'desc_id' is not called 'label' because we don't display it next to the button, but only in the tooltip.\n// Note that 'col' may be encoded in HSV if ImGuiColorEditFlags_InputHSV is set.\nbool ImGui::ColorButton(const char* desc_id, const ImVec4& col, ImGuiColorEditFlags flags, const ImVec2& size_arg)\n{\n    ImGuiWindow* window = GetCurrentWindow();\n    if (window->SkipItems)\n        return false;\n\n    ImGuiContext& g = *GImGui;\n    const ImGuiID id = window->GetID(desc_id);\n    const float default_size = GetFrameHeight();\n    const ImVec2 size(size_arg.x == 0.0f ? default_size : size_arg.x, size_arg.y == 0.0f ? default_size : size_arg.y);\n    const ImRect bb(window->DC.CursorPos, window->DC.CursorPos + size);\n    ItemSize(bb, (size.y >= default_size) ? g.Style.FramePadding.y : 0.0f);\n    if (!ItemAdd(bb, id))\n        return false;\n\n    bool hovered, held;\n    bool pressed = ButtonBehavior(bb, id, &hovered, &held);\n\n    if (flags & (ImGuiColorEditFlags_NoAlpha | ImGuiColorEditFlags_AlphaOpaque))\n        flags &= ~(ImGuiColorEditFlags_AlphaNoBg | ImGuiColorEditFlags_AlphaPreviewHalf);\n\n    ImVec4 col_rgb = col;\n    if (flags & ImGuiColorEditFlags_InputHSV)\n        ColorConvertHSVtoRGB(col_rgb.x, col_rgb.y, col_rgb.z, col_rgb.x, col_rgb.y, col_rgb.z);\n\n    ImVec4 col_rgb_without_alpha(col_rgb.x, col_rgb.y, col_rgb.z, 1.0f);\n    float grid_step = ImMin(size.x, size.y) / 2.99f;\n    float rounding = ImMin(g.Style.FrameRounding, grid_step * 0.5f);\n    ImRect bb_inner = bb;\n    float off = 0.0f;\n    if ((flags & ImGuiColorEditFlags_NoBorder) == 0)\n    {\n        off = -0.75f; // The border (using Col_FrameBg) tends to look off when color is near-opaque and rounding is enabled. This offset seemed like a good middle ground to reduce those artifacts.\n        bb_inner.Expand(off);\n    }\n    if ((flags & ImGuiColorEditFlags_AlphaPreviewHalf) && col_rgb.w < 1.0f)\n    {\n        float mid_x = IM_ROUND((bb_inner.Min.x + bb_inner.Max.x) * 0.5f);\n        if ((flags & ImGuiColorEditFlags_AlphaNoBg) == 0)\n            RenderColorRectWithAlphaCheckerboard(window->DrawList, ImVec2(bb_inner.Min.x + grid_step, bb_inner.Min.y), bb_inner.Max, GetColorU32(col_rgb), grid_step, ImVec2(-grid_step + off, off), rounding, ImDrawFlags_RoundCornersRight);\n        else\n            window->DrawList->AddRectFilled(ImVec2(bb_inner.Min.x + grid_step, bb_inner.Min.y), bb_inner.Max, GetColorU32(col_rgb), rounding, ImDrawFlags_RoundCornersRight);\n        window->DrawList->AddRectFilled(bb_inner.Min, ImVec2(mid_x, bb_inner.Max.y), GetColorU32(col_rgb_without_alpha), rounding, ImDrawFlags_RoundCornersLeft);\n    }\n    else\n    {\n        // Because GetColorU32() multiplies by the global style Alpha and we don't want to display a checkerboard if the source code had no alpha\n        ImVec4 col_source = (flags & ImGuiColorEditFlags_AlphaOpaque) ? col_rgb_without_alpha : col_rgb;\n        if (col_source.w < 1.0f && (flags & ImGuiColorEditFlags_AlphaNoBg) == 0)\n            RenderColorRectWithAlphaCheckerboard(window->DrawList, bb_inner.Min, bb_inner.Max, GetColorU32(col_source), grid_step, ImVec2(off, off), rounding);\n        else\n            window->DrawList->AddRectFilled(bb_inner.Min, bb_inner.Max, GetColorU32(col_source), rounding);\n    }\n    RenderNavCursor(bb, id);\n    if ((flags & ImGuiColorEditFlags_NoBorder) == 0)\n    {\n        if (g.Style.FrameBorderSize > 0.0f)\n            RenderFrameBorder(bb.Min, bb.Max, rounding);\n        else\n            window->DrawList->AddRect(bb.Min, bb.Max, GetColorU32(ImGuiCol_FrameBg), rounding); // Color buttons are often in need of some sort of border\n    }\n\n    // Drag and Drop Source\n    // NB: The ActiveId test is merely an optional micro-optimization, BeginDragDropSource() does the same test.\n    if (g.ActiveId == id && !(flags & ImGuiColorEditFlags_NoDragDrop) && BeginDragDropSource())\n    {\n        if (flags & ImGuiColorEditFlags_NoAlpha)\n            SetDragDropPayload(IMGUI_PAYLOAD_TYPE_COLOR_3F, &col_rgb, sizeof(float) * 3, ImGuiCond_Once);\n        else\n            SetDragDropPayload(IMGUI_PAYLOAD_TYPE_COLOR_4F, &col_rgb, sizeof(float) * 4, ImGuiCond_Once);\n        ColorButton(desc_id, col, flags);\n        SameLine();\n        TextEx(\"Color\");\n        EndDragDropSource();\n    }\n\n    // Tooltip\n    if (!(flags & ImGuiColorEditFlags_NoTooltip) && hovered && IsItemHovered(ImGuiHoveredFlags_ForTooltip))\n        ColorTooltip(desc_id, &col.x, flags & (ImGuiColorEditFlags_InputMask_ | ImGuiColorEditFlags_AlphaMask_));\n\n    return pressed;\n}\n\n// Initialize/override default color options\nvoid ImGui::SetColorEditOptions(ImGuiColorEditFlags flags)\n{\n    ImGuiContext& g = *GImGui;\n    if ((flags & ImGuiColorEditFlags_DisplayMask_) == 0)\n        flags |= ImGuiColorEditFlags_DefaultOptions_ & ImGuiColorEditFlags_DisplayMask_;\n    if ((flags & ImGuiColorEditFlags_DataTypeMask_) == 0)\n        flags |= ImGuiColorEditFlags_DefaultOptions_ & ImGuiColorEditFlags_DataTypeMask_;\n    if ((flags & ImGuiColorEditFlags_PickerMask_) == 0)\n        flags |= ImGuiColorEditFlags_DefaultOptions_ & ImGuiColorEditFlags_PickerMask_;\n    if ((flags & ImGuiColorEditFlags_InputMask_) == 0)\n        flags |= ImGuiColorEditFlags_DefaultOptions_ & ImGuiColorEditFlags_InputMask_;\n    IM_ASSERT(ImIsPowerOfTwo(flags & ImGuiColorEditFlags_DisplayMask_));    // Check only 1 option is selected\n    IM_ASSERT(ImIsPowerOfTwo(flags & ImGuiColorEditFlags_DataTypeMask_));   // Check only 1 option is selected\n    IM_ASSERT(ImIsPowerOfTwo(flags & ImGuiColorEditFlags_PickerMask_));     // Check only 1 option is selected\n    IM_ASSERT(ImIsPowerOfTwo(flags & ImGuiColorEditFlags_InputMask_));      // Check only 1 option is selected\n    g.ColorEditOptions = flags;\n}\n\n// Note: only access 3 floats if ImGuiColorEditFlags_NoAlpha flag is set.\nvoid ImGui::ColorTooltip(const char* text, const float* col, ImGuiColorEditFlags flags)\n{\n    ImGuiContext& g = *GImGui;\n\n    if (!BeginTooltipEx(ImGuiTooltipFlags_OverridePrevious, ImGuiWindowFlags_None))\n        return;\n    const char* text_end = text ? FindRenderedTextEnd(text, NULL) : text;\n    if (text_end > text)\n    {\n        TextEx(text, text_end);\n        Separator();\n    }\n\n    ImVec2 sz(g.FontSize * 3 + g.Style.FramePadding.y * 2, g.FontSize * 3 + g.Style.FramePadding.y * 2);\n    ImVec4 cf(col[0], col[1], col[2], (flags & ImGuiColorEditFlags_NoAlpha) ? 1.0f : col[3]);\n    int cr = IM_F32_TO_INT8_SAT(col[0]), cg = IM_F32_TO_INT8_SAT(col[1]), cb = IM_F32_TO_INT8_SAT(col[2]), ca = (flags & ImGuiColorEditFlags_NoAlpha) ? 255 : IM_F32_TO_INT8_SAT(col[3]);\n    ImGuiColorEditFlags flags_to_forward = ImGuiColorEditFlags_InputMask_ | ImGuiColorEditFlags_AlphaMask_;\n    ColorButton(\"##preview\", cf, (flags & flags_to_forward) | ImGuiColorEditFlags_NoTooltip, sz);\n    SameLine();\n    if ((flags & ImGuiColorEditFlags_InputRGB) || !(flags & ImGuiColorEditFlags_InputMask_))\n    {\n        if (flags & ImGuiColorEditFlags_NoAlpha)\n            Text(\"#%02X%02X%02X\\nR: %d, G: %d, B: %d\\n(%.3f, %.3f, %.3f)\", cr, cg, cb, cr, cg, cb, col[0], col[1], col[2]);\n        else\n            Text(\"#%02X%02X%02X%02X\\nR:%d, G:%d, B:%d, A:%d\\n(%.3f, %.3f, %.3f, %.3f)\", cr, cg, cb, ca, cr, cg, cb, ca, col[0], col[1], col[2], col[3]);\n    }\n    else if (flags & ImGuiColorEditFlags_InputHSV)\n    {\n        if (flags & ImGuiColorEditFlags_NoAlpha)\n            Text(\"H: %.3f, S: %.3f, V: %.3f\", col[0], col[1], col[2]);\n        else\n            Text(\"H: %.3f, S: %.3f, V: %.3f, A: %.3f\", col[0], col[1], col[2], col[3]);\n    }\n    EndTooltip();\n}\n\nvoid ImGui::ColorEditOptionsPopup(const float* col, ImGuiColorEditFlags flags)\n{\n    bool allow_opt_inputs = !(flags & ImGuiColorEditFlags_DisplayMask_);\n    bool allow_opt_datatype = !(flags & ImGuiColorEditFlags_DataTypeMask_);\n    if ((!allow_opt_inputs && !allow_opt_datatype) || !BeginPopup(\"context\"))\n        return;\n\n    ImGuiContext& g = *GImGui;\n    PushItemFlag(ImGuiItemFlags_NoMarkEdited, true);\n    ImGuiColorEditFlags opts = g.ColorEditOptions;\n    if (allow_opt_inputs)\n    {\n        if (RadioButton(\"RGB\", (opts & ImGuiColorEditFlags_DisplayRGB) != 0)) opts = (opts & ~ImGuiColorEditFlags_DisplayMask_) | ImGuiColorEditFlags_DisplayRGB;\n        if (RadioButton(\"HSV\", (opts & ImGuiColorEditFlags_DisplayHSV) != 0)) opts = (opts & ~ImGuiColorEditFlags_DisplayMask_) | ImGuiColorEditFlags_DisplayHSV;\n        if (RadioButton(\"Hex\", (opts & ImGuiColorEditFlags_DisplayHex) != 0)) opts = (opts & ~ImGuiColorEditFlags_DisplayMask_) | ImGuiColorEditFlags_DisplayHex;\n    }\n    if (allow_opt_datatype)\n    {\n        if (allow_opt_inputs) Separator();\n        if (RadioButton(\"0..255\",     (opts & ImGuiColorEditFlags_Uint8) != 0)) opts = (opts & ~ImGuiColorEditFlags_DataTypeMask_) | ImGuiColorEditFlags_Uint8;\n        if (RadioButton(\"0.00..1.00\", (opts & ImGuiColorEditFlags_Float) != 0)) opts = (opts & ~ImGuiColorEditFlags_DataTypeMask_) | ImGuiColorEditFlags_Float;\n    }\n\n    if (allow_opt_inputs || allow_opt_datatype)\n        Separator();\n    if (Button(\"Copy as..\", ImVec2(-1, 0)))\n        OpenPopup(\"Copy\");\n    if (BeginPopup(\"Copy\"))\n    {\n        int cr = IM_F32_TO_INT8_SAT(col[0]), cg = IM_F32_TO_INT8_SAT(col[1]), cb = IM_F32_TO_INT8_SAT(col[2]), ca = (flags & ImGuiColorEditFlags_NoAlpha) ? 255 : IM_F32_TO_INT8_SAT(col[3]);\n        char buf[64];\n        ImFormatString(buf, IM_ARRAYSIZE(buf), \"(%.3ff, %.3ff, %.3ff, %.3ff)\", col[0], col[1], col[2], (flags & ImGuiColorEditFlags_NoAlpha) ? 1.0f : col[3]);\n        if (Selectable(buf))\n            SetClipboardText(buf);\n        ImFormatString(buf, IM_ARRAYSIZE(buf), \"(%d,%d,%d,%d)\", cr, cg, cb, ca);\n        if (Selectable(buf))\n            SetClipboardText(buf);\n        ImFormatString(buf, IM_ARRAYSIZE(buf), \"#%02X%02X%02X\", cr, cg, cb);\n        if (Selectable(buf))\n            SetClipboardText(buf);\n        if (!(flags & ImGuiColorEditFlags_NoAlpha))\n        {\n            ImFormatString(buf, IM_ARRAYSIZE(buf), \"#%02X%02X%02X%02X\", cr, cg, cb, ca);\n            if (Selectable(buf))\n                SetClipboardText(buf);\n        }\n        EndPopup();\n    }\n\n    g.ColorEditOptions = opts;\n    PopItemFlag();\n    EndPopup();\n}\n\nvoid ImGui::ColorPickerOptionsPopup(const float* ref_col, ImGuiColorEditFlags flags)\n{\n    bool allow_opt_picker = !(flags & ImGuiColorEditFlags_PickerMask_);\n    bool allow_opt_alpha_bar = !(flags & ImGuiColorEditFlags_NoAlpha) && !(flags & ImGuiColorEditFlags_AlphaBar);\n    if ((!allow_opt_picker && !allow_opt_alpha_bar) || !BeginPopup(\"context\"))\n        return;\n\n    ImGuiContext& g = *GImGui;\n    PushItemFlag(ImGuiItemFlags_NoMarkEdited, true);\n    if (allow_opt_picker)\n    {\n        ImVec2 picker_size(g.FontSize * 8, ImMax(g.FontSize * 8 - (GetFrameHeight() + g.Style.ItemInnerSpacing.x), 1.0f)); // FIXME: Picker size copied from main picker function\n        PushItemWidth(picker_size.x);\n        for (int picker_type = 0; picker_type < 2; picker_type++)\n        {\n            // Draw small/thumbnail version of each picker type (over an invisible button for selection)\n            if (picker_type > 0) Separator();\n            PushID(picker_type);\n            ImGuiColorEditFlags picker_flags = ImGuiColorEditFlags_NoInputs | ImGuiColorEditFlags_NoOptions | ImGuiColorEditFlags_NoLabel | ImGuiColorEditFlags_NoSidePreview | (flags & ImGuiColorEditFlags_NoAlpha);\n            if (picker_type == 0) picker_flags |= ImGuiColorEditFlags_PickerHueBar;\n            if (picker_type == 1) picker_flags |= ImGuiColorEditFlags_PickerHueWheel;\n            ImVec2 backup_pos = GetCursorScreenPos();\n            if (Selectable(\"##selectable\", false, 0, picker_size)) // By default, Selectable() is closing popup\n                g.ColorEditOptions = (g.ColorEditOptions & ~ImGuiColorEditFlags_PickerMask_) | (picker_flags & ImGuiColorEditFlags_PickerMask_);\n            SetCursorScreenPos(backup_pos);\n            ImVec4 previewing_ref_col;\n            memcpy(&previewing_ref_col, ref_col, sizeof(float) * ((picker_flags & ImGuiColorEditFlags_NoAlpha) ? 3 : 4));\n            ColorPicker4(\"##previewing_picker\", &previewing_ref_col.x, picker_flags);\n            PopID();\n        }\n        PopItemWidth();\n    }\n    if (allow_opt_alpha_bar)\n    {\n        if (allow_opt_picker) Separator();\n        CheckboxFlags(\"Alpha Bar\", &g.ColorEditOptions, ImGuiColorEditFlags_AlphaBar);\n    }\n    PopItemFlag();\n    EndPopup();\n}\n\n//-------------------------------------------------------------------------\n// [SECTION] Widgets: TreeNode, CollapsingHeader, etc.\n//-------------------------------------------------------------------------\n// - TreeNode()\n// - TreeNodeV()\n// - TreeNodeEx()\n// - TreeNodeExV()\n// - TreeNodeBehavior() [Internal]\n// - TreePush()\n// - TreePop()\n// - GetTreeNodeToLabelSpacing()\n// - SetNextItemOpen()\n// - CollapsingHeader()\n//-------------------------------------------------------------------------\n\nbool ImGui::TreeNode(const char* str_id, const char* fmt, ...)\n{\n    va_list args;\n    va_start(args, fmt);\n    bool is_open = TreeNodeExV(str_id, 0, fmt, args);\n    va_end(args);\n    return is_open;\n}\n\nbool ImGui::TreeNode(const void* ptr_id, const char* fmt, ...)\n{\n    va_list args;\n    va_start(args, fmt);\n    bool is_open = TreeNodeExV(ptr_id, 0, fmt, args);\n    va_end(args);\n    return is_open;\n}\n\nbool ImGui::TreeNode(const char* label)\n{\n    ImGuiWindow* window = GetCurrentWindow();\n    if (window->SkipItems)\n        return false;\n    ImGuiID id = window->GetID(label);\n    return TreeNodeBehavior(id, ImGuiTreeNodeFlags_None, label, NULL);\n}\n\nbool ImGui::TreeNodeV(const char* str_id, const char* fmt, va_list args)\n{\n    return TreeNodeExV(str_id, 0, fmt, args);\n}\n\nbool ImGui::TreeNodeV(const void* ptr_id, const char* fmt, va_list args)\n{\n    return TreeNodeExV(ptr_id, 0, fmt, args);\n}\n\nbool ImGui::TreeNodeEx(const char* label, ImGuiTreeNodeFlags flags)\n{\n    ImGuiWindow* window = GetCurrentWindow();\n    if (window->SkipItems)\n        return false;\n    ImGuiID id = window->GetID(label);\n    return TreeNodeBehavior(id, flags, label, NULL);\n}\n\nbool ImGui::TreeNodeEx(const char* str_id, ImGuiTreeNodeFlags flags, const char* fmt, ...)\n{\n    va_list args;\n    va_start(args, fmt);\n    bool is_open = TreeNodeExV(str_id, flags, fmt, args);\n    va_end(args);\n    return is_open;\n}\n\nbool ImGui::TreeNodeEx(const void* ptr_id, ImGuiTreeNodeFlags flags, const char* fmt, ...)\n{\n    va_list args;\n    va_start(args, fmt);\n    bool is_open = TreeNodeExV(ptr_id, flags, fmt, args);\n    va_end(args);\n    return is_open;\n}\n\nbool ImGui::TreeNodeExV(const char* str_id, ImGuiTreeNodeFlags flags, const char* fmt, va_list args)\n{\n    ImGuiWindow* window = GetCurrentWindow();\n    if (window->SkipItems)\n        return false;\n\n    ImGuiID id = window->GetID(str_id);\n    const char* label, *label_end;\n    ImFormatStringToTempBufferV(&label, &label_end, fmt, args);\n    return TreeNodeBehavior(id, flags, label, label_end);\n}\n\nbool ImGui::TreeNodeExV(const void* ptr_id, ImGuiTreeNodeFlags flags, const char* fmt, va_list args)\n{\n    ImGuiWindow* window = GetCurrentWindow();\n    if (window->SkipItems)\n        return false;\n\n    ImGuiID id = window->GetID(ptr_id);\n    const char* label, *label_end;\n    ImFormatStringToTempBufferV(&label, &label_end, fmt, args);\n    return TreeNodeBehavior(id, flags, label, label_end);\n}\n\nbool ImGui::TreeNodeGetOpen(ImGuiID storage_id)\n{\n    ImGuiContext& g = *GImGui;\n    ImGuiStorage* storage = g.CurrentWindow->DC.StateStorage;\n    return storage->GetInt(storage_id, 0) != 0;\n}\n\nvoid ImGui::TreeNodeSetOpen(ImGuiID storage_id, bool open)\n{\n    ImGuiContext& g = *GImGui;\n    ImGuiStorage* storage = g.CurrentWindow->DC.StateStorage;\n    storage->SetInt(storage_id, open ? 1 : 0);\n}\n\nbool ImGui::TreeNodeUpdateNextOpen(ImGuiID storage_id, ImGuiTreeNodeFlags flags)\n{\n    if (flags & ImGuiTreeNodeFlags_Leaf)\n        return true;\n\n    // We only write to the tree storage if the user clicks, or explicitly use the SetNextItemOpen function\n    ImGuiContext& g = *GImGui;\n    ImGuiWindow* window = g.CurrentWindow;\n    ImGuiStorage* storage = window->DC.StateStorage;\n\n    bool is_open;\n    if (g.NextItemData.HasFlags & ImGuiNextItemDataFlags_HasOpen)\n    {\n        if (g.NextItemData.OpenCond & ImGuiCond_Always)\n        {\n            is_open = g.NextItemData.OpenVal;\n            TreeNodeSetOpen(storage_id, is_open);\n        }\n        else\n        {\n            // We treat ImGuiCond_Once and ImGuiCond_FirstUseEver the same because tree node state are not saved persistently.\n            const int stored_value = storage->GetInt(storage_id, -1);\n            if (stored_value == -1)\n            {\n                is_open = g.NextItemData.OpenVal;\n                TreeNodeSetOpen(storage_id, is_open);\n            }\n            else\n            {\n                is_open = stored_value != 0;\n            }\n        }\n    }\n    else\n    {\n        is_open = storage->GetInt(storage_id, (flags & ImGuiTreeNodeFlags_DefaultOpen) ? 1 : 0) != 0;\n    }\n\n    // When logging is enabled, we automatically expand tree nodes (but *NOT* collapsing headers.. seems like sensible behavior).\n    // NB- If we are above max depth we still allow manually opened nodes to be logged.\n    if (g.LogEnabled && !(flags & ImGuiTreeNodeFlags_NoAutoOpenOnLog) && (window->DC.TreeDepth - g.LogDepthRef) < g.LogDepthToExpand)\n        is_open = true;\n\n    return is_open;\n}\n\n// Store ImGuiTreeNodeStackData for just submitted node.\n// Currently only supports 32 level deep and we are fine with (1 << Depth) overflowing into a zero, easy to increase.\nstatic void TreeNodeStoreStackData(ImGuiTreeNodeFlags flags)\n{\n    ImGuiContext& g = *GImGui;\n    ImGuiWindow* window = g.CurrentWindow;\n\n    g.TreeNodeStack.resize(g.TreeNodeStack.Size + 1);\n    ImGuiTreeNodeStackData* tree_node_data = &g.TreeNodeStack.back();\n    tree_node_data->ID = g.LastItemData.ID;\n    tree_node_data->TreeFlags = flags;\n    tree_node_data->ItemFlags = g.LastItemData.ItemFlags;\n    tree_node_data->NavRect = g.LastItemData.NavRect;\n    window->DC.TreeHasStackDataDepthMask |= (1 << window->DC.TreeDepth);\n}\n\n// When using public API, currently 'id == storage_id' is always true, but we separate the values to facilitate advanced user code doing storage queries outside of UI loop.\nbool ImGui::TreeNodeBehavior(ImGuiID id, ImGuiTreeNodeFlags flags, const char* label, const char* label_end)\n{\n    ImGuiWindow* window = GetCurrentWindow();\n    if (window->SkipItems)\n        return false;\n\n    ImGuiContext& g = *GImGui;\n    const ImGuiStyle& style = g.Style;\n    const bool display_frame = (flags & ImGuiTreeNodeFlags_Framed) != 0;\n    const ImVec2 padding = (display_frame || (flags & ImGuiTreeNodeFlags_FramePadding)) ? style.FramePadding : ImVec2(style.FramePadding.x, ImMin(window->DC.CurrLineTextBaseOffset, style.FramePadding.y));\n\n    if (!label_end)\n        label_end = FindRenderedTextEnd(label);\n    const ImVec2 label_size = CalcTextSize(label, label_end, false);\n\n    const float text_offset_x = g.FontSize + (display_frame ? padding.x * 3 : padding.x * 2);   // Collapsing arrow width + Spacing\n    const float text_offset_y = ImMax(padding.y, window->DC.CurrLineTextBaseOffset);            // Latch before ItemSize changes it\n    const float text_width = g.FontSize + label_size.x + padding.x * 2;                         // Include collapsing arrow\n\n    // We vertically grow up to current line height up the typical widget height.\n    const float frame_height = ImMax(ImMin(window->DC.CurrLineSize.y, g.FontSize + style.FramePadding.y * 2), label_size.y + padding.y * 2);\n    const bool span_all_columns = (flags & ImGuiTreeNodeFlags_SpanAllColumns) != 0 && (g.CurrentTable != NULL);\n    const bool span_all_columns_label = (flags & ImGuiTreeNodeFlags_LabelSpanAllColumns) != 0 && (g.CurrentTable != NULL);\n    ImRect frame_bb;\n    frame_bb.Min.x = span_all_columns ? window->ParentWorkRect.Min.x : (flags & ImGuiTreeNodeFlags_SpanFullWidth) ? window->WorkRect.Min.x : window->DC.CursorPos.x;\n    frame_bb.Min.y = window->DC.CursorPos.y;\n    frame_bb.Max.x = span_all_columns ? window->ParentWorkRect.Max.x : (flags & ImGuiTreeNodeFlags_SpanLabelWidth) ? window->DC.CursorPos.x + text_width + padding.x : window->WorkRect.Max.x;\n    frame_bb.Max.y = window->DC.CursorPos.y + frame_height;\n    if (display_frame)\n    {\n        const float outer_extend = IM_TRUNC(window->WindowPadding.x * 0.5f); // Framed header expand a little outside of current limits\n        frame_bb.Min.x -= outer_extend;\n        frame_bb.Max.x += outer_extend;\n    }\n\n    ImVec2 text_pos(window->DC.CursorPos.x + text_offset_x, window->DC.CursorPos.y + text_offset_y);\n    ItemSize(ImVec2(text_width, frame_height), padding.y);\n\n    // For regular tree nodes, we arbitrary allow to click past 2 worth of ItemSpacing\n    ImRect interact_bb = frame_bb;\n    if ((flags & (ImGuiTreeNodeFlags_Framed | ImGuiTreeNodeFlags_SpanAvailWidth | ImGuiTreeNodeFlags_SpanFullWidth | ImGuiTreeNodeFlags_SpanLabelWidth | ImGuiTreeNodeFlags_SpanAllColumns)) == 0)\n        interact_bb.Max.x = frame_bb.Min.x + text_width + (label_size.x > 0.0f ? style.ItemSpacing.x * 2.0f : 0.0f);\n\n    // Compute open and multi-select states before ItemAdd() as it clear NextItem data.\n    ImGuiID storage_id = (g.NextItemData.HasFlags & ImGuiNextItemDataFlags_HasStorageID) ? g.NextItemData.StorageId : id;\n    bool is_open = TreeNodeUpdateNextOpen(storage_id, flags);\n\n    bool is_visible;\n    if (span_all_columns || span_all_columns_label)\n    {\n        // Modify ClipRect for the ItemAdd(), faster than doing a PushColumnsBackground/PushTableBackgroundChannel for every Selectable..\n        const float backup_clip_rect_min_x = window->ClipRect.Min.x;\n        const float backup_clip_rect_max_x = window->ClipRect.Max.x;\n        window->ClipRect.Min.x = window->ParentWorkRect.Min.x;\n        window->ClipRect.Max.x = window->ParentWorkRect.Max.x;\n        is_visible = ItemAdd(interact_bb, id);\n        window->ClipRect.Min.x = backup_clip_rect_min_x;\n        window->ClipRect.Max.x = backup_clip_rect_max_x;\n    }\n    else\n    {\n        is_visible = ItemAdd(interact_bb, id);\n    }\n    g.LastItemData.StatusFlags |= ImGuiItemStatusFlags_HasDisplayRect;\n    g.LastItemData.DisplayRect = frame_bb;\n\n    // If a NavLeft request is happening and ImGuiTreeNodeFlags_NavLeftJumpsBackHere enabled:\n    // Store data for the current depth to allow returning to this node from any child item.\n    // For this purpose we essentially compare if g.NavIdIsAlive went from 0 to 1 between TreeNode() and TreePop().\n    // It will become tempting to enable ImGuiTreeNodeFlags_NavLeftJumpsBackHere by default or move it to ImGuiStyle.\n    bool store_tree_node_stack_data = false;\n    if (!(flags & ImGuiTreeNodeFlags_NoTreePushOnOpen))\n    {\n        if ((flags & ImGuiTreeNodeFlags_NavLeftJumpsBackHere) && is_open && !g.NavIdIsAlive)\n            if (g.NavMoveDir == ImGuiDir_Left && g.NavWindow == window && NavMoveRequestButNoResultYet())\n                store_tree_node_stack_data = true;\n    }\n\n    const bool is_leaf = (flags & ImGuiTreeNodeFlags_Leaf) != 0;\n    if (!is_visible)\n    {\n        if (store_tree_node_stack_data && is_open)\n            TreeNodeStoreStackData(flags); // Call before TreePushOverrideID()\n        if (is_open && !(flags & ImGuiTreeNodeFlags_NoTreePushOnOpen))\n            TreePushOverrideID(id);\n        IMGUI_TEST_ENGINE_ITEM_INFO(g.LastItemData.ID, label, g.LastItemData.StatusFlags | (is_leaf ? 0 : ImGuiItemStatusFlags_Openable) | (is_open ? ImGuiItemStatusFlags_Opened : 0));\n        return is_open;\n    }\n\n    if (span_all_columns || span_all_columns_label)\n    {\n        TablePushBackgroundChannel();\n        g.LastItemData.StatusFlags |= ImGuiItemStatusFlags_HasClipRect;\n        g.LastItemData.ClipRect = window->ClipRect;\n    }\n\n    ImGuiButtonFlags button_flags = ImGuiTreeNodeFlags_None;\n    if ((flags & ImGuiTreeNodeFlags_AllowOverlap) || (g.LastItemData.ItemFlags & ImGuiItemFlags_AllowOverlap))\n        button_flags |= ImGuiButtonFlags_AllowOverlap;\n    if (!is_leaf)\n        button_flags |= ImGuiButtonFlags_PressedOnDragDropHold;\n\n    // We allow clicking on the arrow section with keyboard modifiers held, in order to easily\n    // allow browsing a tree while preserving selection with code implementing multi-selection patterns.\n    // When clicking on the rest of the tree node we always disallow keyboard modifiers.\n    const float arrow_hit_x1 = (text_pos.x - text_offset_x) - style.TouchExtraPadding.x;\n    const float arrow_hit_x2 = (text_pos.x - text_offset_x) + (g.FontSize + padding.x * 2.0f) + style.TouchExtraPadding.x;\n    const bool is_mouse_x_over_arrow = (g.IO.MousePos.x >= arrow_hit_x1 && g.IO.MousePos.x < arrow_hit_x2);\n\n    const bool is_multi_select = (g.LastItemData.ItemFlags & ImGuiItemFlags_IsMultiSelect) != 0;\n    if (is_multi_select) // We absolutely need to distinguish open vs select so _OpenOnArrow comes by default\n        flags |= (flags & ImGuiTreeNodeFlags_OpenOnMask_) == 0 ? ImGuiTreeNodeFlags_OpenOnArrow | ImGuiTreeNodeFlags_OpenOnDoubleClick : ImGuiTreeNodeFlags_OpenOnArrow;\n\n    // Open behaviors can be altered with the _OpenOnArrow and _OnOnDoubleClick flags.\n    // Some alteration have subtle effects (e.g. toggle on MouseUp vs MouseDown events) due to requirements for multi-selection and drag and drop support.\n    // - Single-click on label = Toggle on MouseUp (default, when _OpenOnArrow=0)\n    // - Single-click on arrow = Toggle on MouseDown (when _OpenOnArrow=0)\n    // - Single-click on arrow = Toggle on MouseDown (when _OpenOnArrow=1)\n    // - Double-click on label = Toggle on MouseDoubleClick (when _OpenOnDoubleClick=1)\n    // - Double-click on arrow = Toggle on MouseDoubleClick (when _OpenOnDoubleClick=1 and _OpenOnArrow=0)\n    // It is rather standard that arrow click react on Down rather than Up.\n    // We set ImGuiButtonFlags_PressedOnClickRelease on OpenOnDoubleClick because we want the item to be active on the initial MouseDown in order for drag and drop to work.\n    if (is_mouse_x_over_arrow)\n        button_flags |= ImGuiButtonFlags_PressedOnClick;\n    else if (flags & ImGuiTreeNodeFlags_OpenOnDoubleClick)\n        button_flags |= ImGuiButtonFlags_PressedOnClickRelease | ImGuiButtonFlags_PressedOnDoubleClick;\n    else\n        button_flags |= ImGuiButtonFlags_PressedOnClickRelease;\n\n    bool selected = (flags & ImGuiTreeNodeFlags_Selected) != 0;\n    const bool was_selected = selected;\n\n    // Multi-selection support (header)\n    if (is_multi_select)\n    {\n        // Handle multi-select + alter button flags for it\n        MultiSelectItemHeader(id, &selected, &button_flags);\n        if (is_mouse_x_over_arrow)\n            button_flags = (button_flags | ImGuiButtonFlags_PressedOnClick) & ~ImGuiButtonFlags_PressedOnClickRelease;\n    }\n    else\n    {\n        if (window != g.HoveredWindow || !is_mouse_x_over_arrow)\n            button_flags |= ImGuiButtonFlags_NoKeyModsAllowed;\n    }\n\n    bool hovered, held;\n    bool pressed = ButtonBehavior(interact_bb, id, &hovered, &held, button_flags);\n    bool toggled = false;\n    if (!is_leaf)\n    {\n        if (pressed && g.DragDropHoldJustPressedId != id)\n        {\n            if ((flags & ImGuiTreeNodeFlags_OpenOnMask_) == 0 || (g.NavActivateId == id && !is_multi_select))\n                toggled = true; // Single click\n            if (flags & ImGuiTreeNodeFlags_OpenOnArrow)\n                toggled |= is_mouse_x_over_arrow && !g.NavHighlightItemUnderNav; // Lightweight equivalent of IsMouseHoveringRect() since ButtonBehavior() already did the job\n            if ((flags & ImGuiTreeNodeFlags_OpenOnDoubleClick) && g.IO.MouseClickedCount[0] == 2)\n                toggled = true; // Double click\n        }\n        else if (pressed && g.DragDropHoldJustPressedId == id)\n        {\n            IM_ASSERT(button_flags & ImGuiButtonFlags_PressedOnDragDropHold);\n            if (!is_open) // When using Drag and Drop \"hold to open\" we keep the node highlighted after opening, but never close it again.\n                toggled = true;\n            else\n                pressed = false; // Cancel press so it doesn't trigger selection.\n        }\n\n        if (g.NavId == id && g.NavMoveDir == ImGuiDir_Left && is_open)\n        {\n            toggled = true;\n            NavClearPreferredPosForAxis(ImGuiAxis_X);\n            NavMoveRequestCancel();\n        }\n        if (g.NavId == id && g.NavMoveDir == ImGuiDir_Right && !is_open) // If there's something upcoming on the line we may want to give it the priority?\n        {\n            toggled = true;\n            NavClearPreferredPosForAxis(ImGuiAxis_X);\n            NavMoveRequestCancel();\n        }\n\n        if (toggled)\n        {\n            is_open = !is_open;\n            window->DC.StateStorage->SetInt(storage_id, is_open);\n            g.LastItemData.StatusFlags |= ImGuiItemStatusFlags_ToggledOpen;\n        }\n    }\n\n    // Multi-selection support (footer)\n    if (is_multi_select)\n    {\n        bool pressed_copy = pressed && !toggled;\n        MultiSelectItemFooter(id, &selected, &pressed_copy);\n        if (pressed)\n            SetNavID(id, window->DC.NavLayerCurrent, g.CurrentFocusScopeId, interact_bb);\n    }\n\n    if (selected != was_selected)\n        g.LastItemData.StatusFlags |= ImGuiItemStatusFlags_ToggledSelection;\n\n    // Render\n    {\n        const ImU32 text_col = GetColorU32(ImGuiCol_Text);\n        ImGuiNavRenderCursorFlags nav_render_cursor_flags = ImGuiNavRenderCursorFlags_Compact;\n        if (is_multi_select)\n            nav_render_cursor_flags |= ImGuiNavRenderCursorFlags_AlwaysDraw; // Always show the nav rectangle\n        if (display_frame)\n        {\n            // Framed type\n            const ImU32 bg_col = GetColorU32((held && hovered) ? ImGuiCol_HeaderActive : hovered ? ImGuiCol_HeaderHovered : ImGuiCol_Header);\n            RenderFrame(frame_bb.Min, frame_bb.Max, bg_col, true, style.FrameRounding);\n            RenderNavCursor(frame_bb, id, nav_render_cursor_flags);\n            if (flags & ImGuiTreeNodeFlags_Bullet)\n                RenderBullet(window->DrawList, ImVec2(text_pos.x - text_offset_x * 0.60f, text_pos.y + g.FontSize * 0.5f), text_col);\n            else if (!is_leaf)\n                RenderArrow(window->DrawList, ImVec2(text_pos.x - text_offset_x + padding.x, text_pos.y), text_col, is_open ? ((flags & ImGuiTreeNodeFlags_UpsideDownArrow) ? ImGuiDir_Up : ImGuiDir_Down) : ImGuiDir_Right, 1.0f);\n            else // Leaf without bullet, left-adjusted text\n                text_pos.x -= text_offset_x - padding.x;\n            if (flags & ImGuiTreeNodeFlags_ClipLabelForTrailingButton)\n                frame_bb.Max.x -= g.FontSize + style.FramePadding.x;\n            if (g.LogEnabled)\n                LogSetNextTextDecoration(\"###\", \"###\");\n        }\n        else\n        {\n            // Unframed typed for tree nodes\n            if (hovered || selected)\n            {\n                const ImU32 bg_col = GetColorU32((held && hovered) ? ImGuiCol_HeaderActive : hovered ? ImGuiCol_HeaderHovered : ImGuiCol_Header);\n                RenderFrame(frame_bb.Min, frame_bb.Max, bg_col, false);\n            }\n            RenderNavCursor(frame_bb, id, nav_render_cursor_flags);\n            if (flags & ImGuiTreeNodeFlags_Bullet)\n                RenderBullet(window->DrawList, ImVec2(text_pos.x - text_offset_x * 0.5f, text_pos.y + g.FontSize * 0.5f), text_col);\n            else if (!is_leaf)\n                RenderArrow(window->DrawList, ImVec2(text_pos.x - text_offset_x + padding.x, text_pos.y + g.FontSize * 0.15f), text_col, is_open ? ((flags & ImGuiTreeNodeFlags_UpsideDownArrow) ? ImGuiDir_Up : ImGuiDir_Down) : ImGuiDir_Right, 0.70f);\n            if (g.LogEnabled)\n                LogSetNextTextDecoration(\">\", NULL);\n        }\n\n        if (span_all_columns && !span_all_columns_label)\n            TablePopBackgroundChannel();\n\n        // Label\n        if (display_frame)\n            RenderTextClipped(text_pos, frame_bb.Max, label, label_end, &label_size);\n        else\n            RenderText(text_pos, label, label_end, false);\n\n        if (span_all_columns_label)\n            TablePopBackgroundChannel();\n    }\n\n    if (store_tree_node_stack_data && is_open)\n        TreeNodeStoreStackData(flags); // Call before TreePushOverrideID()\n    if (is_open && !(flags & ImGuiTreeNodeFlags_NoTreePushOnOpen))\n        TreePushOverrideID(id); // Could use TreePush(label) but this avoid computing twice\n\n    IMGUI_TEST_ENGINE_ITEM_INFO(id, label, g.LastItemData.StatusFlags | (is_leaf ? 0 : ImGuiItemStatusFlags_Openable) | (is_open ? ImGuiItemStatusFlags_Opened : 0));\n    return is_open;\n}\n\nvoid ImGui::TreePush(const char* str_id)\n{\n    ImGuiWindow* window = GetCurrentWindow();\n    Indent();\n    window->DC.TreeDepth++;\n    PushID(str_id);\n}\n\nvoid ImGui::TreePush(const void* ptr_id)\n{\n    ImGuiWindow* window = GetCurrentWindow();\n    Indent();\n    window->DC.TreeDepth++;\n    PushID(ptr_id);\n}\n\nvoid ImGui::TreePushOverrideID(ImGuiID id)\n{\n    ImGuiContext& g = *GImGui;\n    ImGuiWindow* window = g.CurrentWindow;\n    Indent();\n    window->DC.TreeDepth++;\n    PushOverrideID(id);\n}\n\nvoid ImGui::TreePop()\n{\n    ImGuiContext& g = *GImGui;\n    ImGuiWindow* window = g.CurrentWindow;\n    Unindent();\n\n    window->DC.TreeDepth--;\n    ImU32 tree_depth_mask = (1 << window->DC.TreeDepth);\n\n    if (window->DC.TreeHasStackDataDepthMask & tree_depth_mask) // Only set during request\n    {\n        ImGuiTreeNodeStackData* data = &g.TreeNodeStack.back();\n        IM_ASSERT(data->ID == window->IDStack.back());\n        if (data->TreeFlags & ImGuiTreeNodeFlags_NavLeftJumpsBackHere)\n        {\n            // Handle Left arrow to move to parent tree node (when ImGuiTreeNodeFlags_NavLeftJumpsBackHere is enabled)\n            if (g.NavIdIsAlive && g.NavMoveDir == ImGuiDir_Left && g.NavWindow == window && NavMoveRequestButNoResultYet())\n                NavMoveRequestResolveWithPastTreeNode(&g.NavMoveResultLocal, data);\n        }\n        g.TreeNodeStack.pop_back();\n        window->DC.TreeHasStackDataDepthMask &= ~tree_depth_mask;\n    }\n\n    IM_ASSERT(window->IDStack.Size > 1); // There should always be 1 element in the IDStack (pushed during window creation). If this triggers you called TreePop/PopID too much.\n    PopID();\n}\n\n// Horizontal distance preceding label when using TreeNode() or Bullet()\nfloat ImGui::GetTreeNodeToLabelSpacing()\n{\n    ImGuiContext& g = *GImGui;\n    return g.FontSize + (g.Style.FramePadding.x * 2.0f);\n}\n\n// Set next TreeNode/CollapsingHeader open state.\nvoid ImGui::SetNextItemOpen(bool is_open, ImGuiCond cond)\n{\n    ImGuiContext& g = *GImGui;\n    if (g.CurrentWindow->SkipItems)\n        return;\n    g.NextItemData.HasFlags |= ImGuiNextItemDataFlags_HasOpen;\n    g.NextItemData.OpenVal = is_open;\n    g.NextItemData.OpenCond = (ImU8)(cond ? cond : ImGuiCond_Always);\n}\n\n// Set next TreeNode/CollapsingHeader storage id.\nvoid ImGui::SetNextItemStorageID(ImGuiID storage_id)\n{\n    ImGuiContext& g = *GImGui;\n    if (g.CurrentWindow->SkipItems)\n        return;\n    g.NextItemData.HasFlags |= ImGuiNextItemDataFlags_HasStorageID;\n    g.NextItemData.StorageId = storage_id;\n}\n\n// CollapsingHeader returns true when opened but do not indent nor push into the ID stack (because of the ImGuiTreeNodeFlags_NoTreePushOnOpen flag).\n// This is basically the same as calling TreeNodeEx(label, ImGuiTreeNodeFlags_CollapsingHeader). You can remove the _NoTreePushOnOpen flag if you want behavior closer to normal TreeNode().\nbool ImGui::CollapsingHeader(const char* label, ImGuiTreeNodeFlags flags)\n{\n    ImGuiWindow* window = GetCurrentWindow();\n    if (window->SkipItems)\n        return false;\n    ImGuiID id = window->GetID(label);\n    return TreeNodeBehavior(id, flags | ImGuiTreeNodeFlags_CollapsingHeader, label);\n}\n\n// p_visible == NULL                        : regular collapsing header\n// p_visible != NULL && *p_visible == true  : show a small close button on the corner of the header, clicking the button will set *p_visible = false\n// p_visible != NULL && *p_visible == false : do not show the header at all\n// Do not mistake this with the Open state of the header itself, which you can adjust with SetNextItemOpen() or ImGuiTreeNodeFlags_DefaultOpen.\nbool ImGui::CollapsingHeader(const char* label, bool* p_visible, ImGuiTreeNodeFlags flags)\n{\n    ImGuiWindow* window = GetCurrentWindow();\n    if (window->SkipItems)\n        return false;\n\n    if (p_visible && !*p_visible)\n        return false;\n\n    ImGuiID id = window->GetID(label);\n    flags |= ImGuiTreeNodeFlags_CollapsingHeader;\n    if (p_visible)\n        flags |= ImGuiTreeNodeFlags_AllowOverlap | (ImGuiTreeNodeFlags)ImGuiTreeNodeFlags_ClipLabelForTrailingButton;\n    bool is_open = TreeNodeBehavior(id, flags, label);\n    if (p_visible != NULL)\n    {\n        // Create a small overlapping close button\n        // FIXME: We can evolve this into user accessible helpers to add extra buttons on title bars, headers, etc.\n        // FIXME: CloseButton can overlap into text, need find a way to clip the text somehow.\n        ImGuiContext& g = *GImGui;\n        ImGuiLastItemData last_item_backup = g.LastItemData;\n        float button_size = g.FontSize;\n        float button_x = ImMax(g.LastItemData.Rect.Min.x, g.LastItemData.Rect.Max.x - g.Style.FramePadding.x - button_size);\n        float button_y = g.LastItemData.Rect.Min.y + g.Style.FramePadding.y;\n        ImGuiID close_button_id = GetIDWithSeed(\"#CLOSE\", NULL, id);\n        if (CloseButton(close_button_id, ImVec2(button_x, button_y)))\n            *p_visible = false;\n        g.LastItemData = last_item_backup;\n    }\n\n    return is_open;\n}\n\n//-------------------------------------------------------------------------\n// [SECTION] Widgets: Selectable\n//-------------------------------------------------------------------------\n// - Selectable()\n//-------------------------------------------------------------------------\n\n// Tip: pass a non-visible label (e.g. \"##hello\") then you can use the space to draw other text or image.\n// But you need to make sure the ID is unique, e.g. enclose calls in PushID/PopID or use ##unique_id.\n// With this scheme, ImGuiSelectableFlags_SpanAllColumns and ImGuiSelectableFlags_AllowOverlap are also frequently used flags.\n// FIXME: Selectable() with (size.x == 0.0f) and (SelectableTextAlign.x > 0.0f) followed by SameLine() is currently not supported.\nbool ImGui::Selectable(const char* label, bool selected, ImGuiSelectableFlags flags, const ImVec2& size_arg)\n{\n    ImGuiWindow* window = GetCurrentWindow();\n    if (window->SkipItems)\n        return false;\n\n    ImGuiContext& g = *GImGui;\n    const ImGuiStyle& style = g.Style;\n\n    // Submit label or explicit size to ItemSize(), whereas ItemAdd() will submit a larger/spanning rectangle.\n    ImGuiID id = window->GetID(label);\n    ImVec2 label_size = CalcTextSize(label, NULL, true);\n    ImVec2 size(size_arg.x != 0.0f ? size_arg.x : label_size.x, size_arg.y != 0.0f ? size_arg.y : label_size.y);\n    ImVec2 pos = window->DC.CursorPos;\n    pos.y += window->DC.CurrLineTextBaseOffset;\n    ItemSize(size, 0.0f);\n\n    // Fill horizontal space\n    // We don't support (size < 0.0f) in Selectable() because the ItemSpacing extension would make explicitly right-aligned sizes not visibly match other widgets.\n    const bool span_all_columns = (flags & ImGuiSelectableFlags_SpanAllColumns) != 0;\n    const float min_x = span_all_columns ? window->ParentWorkRect.Min.x : pos.x;\n    const float max_x = span_all_columns ? window->ParentWorkRect.Max.x : window->WorkRect.Max.x;\n    if (size_arg.x == 0.0f || (flags & ImGuiSelectableFlags_SpanAvailWidth))\n        size.x = ImMax(label_size.x, max_x - min_x);\n\n    // Selectables are meant to be tightly packed together with no click-gap, so we extend their box to cover spacing between selectable.\n    // FIXME: Not part of layout so not included in clipper calculation, but ItemSize currently doesn't allow offsetting CursorPos.\n    ImRect bb(min_x, pos.y, min_x + size.x, pos.y + size.y);\n    if ((flags & ImGuiSelectableFlags_NoPadWithHalfSpacing) == 0)\n    {\n        const float spacing_x = span_all_columns ? 0.0f : style.ItemSpacing.x;\n        const float spacing_y = style.ItemSpacing.y;\n        const float spacing_L = IM_TRUNC(spacing_x * 0.50f);\n        const float spacing_U = IM_TRUNC(spacing_y * 0.50f);\n        bb.Min.x -= spacing_L;\n        bb.Min.y -= spacing_U;\n        bb.Max.x += (spacing_x - spacing_L);\n        bb.Max.y += (spacing_y - spacing_U);\n    }\n    //if (g.IO.KeyCtrl) { GetForegroundDrawList()->AddRect(bb.Min, bb.Max, IM_COL32(0, 255, 0, 255)); }\n\n    const bool disabled_item = (flags & ImGuiSelectableFlags_Disabled) != 0;\n    const ImGuiItemFlags extra_item_flags = disabled_item ? (ImGuiItemFlags)ImGuiItemFlags_Disabled : ImGuiItemFlags_None;\n    bool is_visible;\n    if (span_all_columns)\n    {\n        // Modify ClipRect for the ItemAdd(), faster than doing a PushColumnsBackground/PushTableBackgroundChannel for every Selectable..\n        const float backup_clip_rect_min_x = window->ClipRect.Min.x;\n        const float backup_clip_rect_max_x = window->ClipRect.Max.x;\n        window->ClipRect.Min.x = window->ParentWorkRect.Min.x;\n        window->ClipRect.Max.x = window->ParentWorkRect.Max.x;\n        is_visible = ItemAdd(bb, id, NULL, extra_item_flags);\n        window->ClipRect.Min.x = backup_clip_rect_min_x;\n        window->ClipRect.Max.x = backup_clip_rect_max_x;\n    }\n    else\n    {\n        is_visible = ItemAdd(bb, id, NULL, extra_item_flags);\n    }\n\n    const bool is_multi_select = (g.LastItemData.ItemFlags & ImGuiItemFlags_IsMultiSelect) != 0;\n    if (!is_visible)\n        if (!is_multi_select || !g.BoxSelectState.UnclipMode || !g.BoxSelectState.UnclipRect.Overlaps(bb)) // Extra layer of \"no logic clip\" for box-select support (would be more overhead to add to ItemAdd)\n            return false;\n\n    const bool disabled_global = (g.CurrentItemFlags & ImGuiItemFlags_Disabled) != 0;\n    if (disabled_item && !disabled_global) // Only testing this as an optimization\n        BeginDisabled();\n\n    // FIXME: We can standardize the behavior of those two, we could also keep the fast path of override ClipRect + full push on render only,\n    // which would be advantageous since most selectable are not selected.\n    if (span_all_columns)\n    {\n        if (g.CurrentTable)\n            TablePushBackgroundChannel();\n        else if (window->DC.CurrentColumns)\n            PushColumnsBackground();\n        g.LastItemData.StatusFlags |= ImGuiItemStatusFlags_HasClipRect;\n        g.LastItemData.ClipRect = window->ClipRect;\n    }\n\n    // We use NoHoldingActiveID on menus so user can click and _hold_ on a menu then drag to browse child entries\n    ImGuiButtonFlags button_flags = 0;\n    if (flags & ImGuiSelectableFlags_NoHoldingActiveID) { button_flags |= ImGuiButtonFlags_NoHoldingActiveId; }\n    if (flags & ImGuiSelectableFlags_NoSetKeyOwner)     { button_flags |= ImGuiButtonFlags_NoSetKeyOwner; }\n    if (flags & ImGuiSelectableFlags_SelectOnClick)     { button_flags |= ImGuiButtonFlags_PressedOnClick; }\n    if (flags & ImGuiSelectableFlags_SelectOnRelease)   { button_flags |= ImGuiButtonFlags_PressedOnRelease; }\n    if (flags & ImGuiSelectableFlags_AllowDoubleClick)  { button_flags |= ImGuiButtonFlags_PressedOnClickRelease | ImGuiButtonFlags_PressedOnDoubleClick; }\n    if ((flags & ImGuiSelectableFlags_AllowOverlap) || (g.LastItemData.ItemFlags & ImGuiItemFlags_AllowOverlap)) { button_flags |= ImGuiButtonFlags_AllowOverlap; }\n\n    // Multi-selection support (header)\n    const bool was_selected = selected;\n    if (is_multi_select)\n    {\n        // Handle multi-select + alter button flags for it\n        MultiSelectItemHeader(id, &selected, &button_flags);\n    }\n\n    bool hovered, held;\n    bool pressed = ButtonBehavior(bb, id, &hovered, &held, button_flags);\n\n    // Multi-selection support (footer)\n    if (is_multi_select)\n    {\n        MultiSelectItemFooter(id, &selected, &pressed);\n    }\n    else\n    {\n        // Auto-select when moved into\n        // - This will be more fully fleshed in the range-select branch\n        // - This is not exposed as it won't nicely work with some user side handling of shift/control\n        // - We cannot do 'if (g.NavJustMovedToId != id) { selected = false; pressed = was_selected; }' for two reasons\n        //   - (1) it would require focus scope to be set, need exposing PushFocusScope() or equivalent (e.g. BeginSelection() calling PushFocusScope())\n        //   - (2) usage will fail with clipped items\n        //   The multi-select API aim to fix those issues, e.g. may be replaced with a BeginSelection() API.\n        if ((flags & ImGuiSelectableFlags_SelectOnNav) && g.NavJustMovedToId != 0 && g.NavJustMovedToFocusScopeId == g.CurrentFocusScopeId)\n            if (g.NavJustMovedToId == id)\n                selected = pressed = true;\n    }\n\n    // Update NavId when clicking or when Hovering (this doesn't happen on most widgets), so navigation can be resumed with keyboard/gamepad\n    if (pressed || (hovered && (flags & ImGuiSelectableFlags_SetNavIdOnHover)))\n    {\n        if (!g.NavHighlightItemUnderNav && g.NavWindow == window && g.NavLayer == window->DC.NavLayerCurrent)\n        {\n            SetNavID(id, window->DC.NavLayerCurrent, g.CurrentFocusScopeId, WindowRectAbsToRel(window, bb)); // (bb == NavRect)\n            if (g.IO.ConfigNavCursorVisibleAuto)\n                g.NavCursorVisible = false;\n        }\n    }\n    if (pressed)\n        MarkItemEdited(id);\n\n    if (selected != was_selected)\n        g.LastItemData.StatusFlags |= ImGuiItemStatusFlags_ToggledSelection;\n\n    // Render\n    if (is_visible)\n    {\n        const bool highlighted = hovered || (flags & ImGuiSelectableFlags_Highlight);\n        if (highlighted || selected)\n        {\n            // Between 1.91.0 and 1.91.4 we made selected Selectable use an arbitrary lerp between _Header and _HeaderHovered. Removed that now. (#8106)\n            ImU32 col = GetColorU32((held && highlighted) ? ImGuiCol_HeaderActive : highlighted ? ImGuiCol_HeaderHovered : ImGuiCol_Header);\n            RenderFrame(bb.Min, bb.Max, col, false, 0.0f);\n        }\n        if (g.NavId == id)\n        {\n            ImGuiNavRenderCursorFlags nav_render_cursor_flags = ImGuiNavRenderCursorFlags_Compact | ImGuiNavRenderCursorFlags_NoRounding;\n            if (is_multi_select)\n                nav_render_cursor_flags |= ImGuiNavRenderCursorFlags_AlwaysDraw; // Always show the nav rectangle\n            RenderNavCursor(bb, id, nav_render_cursor_flags);\n        }\n    }\n\n    if (span_all_columns)\n    {\n        if (g.CurrentTable)\n            TablePopBackgroundChannel();\n        else if (window->DC.CurrentColumns)\n            PopColumnsBackground();\n    }\n\n    // Text stays at the submission position. Alignment/clipping extents ignore SpanAllColumns.\n    if (is_visible)\n        RenderTextClipped(pos, ImVec2(ImMin(pos.x + size.x, window->WorkRect.Max.x), pos.y + size.y), label, NULL, &label_size, style.SelectableTextAlign, &bb);\n\n    // Automatically close popups\n    if (pressed && (window->Flags & ImGuiWindowFlags_Popup) && !(flags & ImGuiSelectableFlags_NoAutoClosePopups) && (g.LastItemData.ItemFlags & ImGuiItemFlags_AutoClosePopups))\n        CloseCurrentPopup();\n\n    if (disabled_item && !disabled_global)\n        EndDisabled();\n\n    // Selectable() always returns a pressed state!\n    // Users of BeginMultiSelect()/EndMultiSelect() scope: you may call ImGui::IsItemToggledSelection() to retrieve\n    // selection toggle, only useful if you need that state updated (e.g. for rendering purpose) before reaching EndMultiSelect().\n    IMGUI_TEST_ENGINE_ITEM_INFO(id, label, g.LastItemData.StatusFlags);\n    return pressed; //-V1020\n}\n\nbool ImGui::Selectable(const char* label, bool* p_selected, ImGuiSelectableFlags flags, const ImVec2& size_arg)\n{\n    if (Selectable(label, *p_selected, flags, size_arg))\n    {\n        *p_selected = !*p_selected;\n        return true;\n    }\n    return false;\n}\n\n\n//-------------------------------------------------------------------------\n// [SECTION] Widgets: Typing-Select support\n//-------------------------------------------------------------------------\n\n// [Experimental] Currently not exposed in public API.\n// Consume character inputs and return search request, if any.\n// This would typically only be called on the focused window or location you want to grab inputs for, e.g.\n//   if (ImGui::IsWindowFocused(...))\n//       if (ImGuiTypingSelectRequest* req = ImGui::GetTypingSelectRequest())\n//           focus_idx = ImGui::TypingSelectFindMatch(req, my_items.size(), [](void*, int n) { return my_items[n]->Name; }, &my_items, -1);\n// However the code is written in a way where calling it from multiple locations is safe (e.g. to obtain buffer).\nImGuiTypingSelectRequest* ImGui::GetTypingSelectRequest(ImGuiTypingSelectFlags flags)\n{\n    ImGuiContext& g = *GImGui;\n    ImGuiTypingSelectState* data = &g.TypingSelectState;\n    ImGuiTypingSelectRequest* out_request = &data->Request;\n\n    // Clear buffer\n    const float TYPING_SELECT_RESET_TIMER = 1.80f;          // FIXME: Potentially move to IO config.\n    const int TYPING_SELECT_SINGLE_CHAR_COUNT_FOR_LOCK = 4; // Lock single char matching when repeating same char 4 times\n    if (data->SearchBuffer[0] != 0)\n    {\n        bool clear_buffer = false;\n        clear_buffer |= (g.NavFocusScopeId != data->FocusScope);\n        clear_buffer |= (data->LastRequestTime + TYPING_SELECT_RESET_TIMER < g.Time);\n        clear_buffer |= g.NavAnyRequest;\n        clear_buffer |= g.ActiveId != 0 && g.NavActivateId == 0; // Allow temporary SPACE activation to not interfere\n        clear_buffer |= IsKeyPressed(ImGuiKey_Escape) || IsKeyPressed(ImGuiKey_Enter);\n        clear_buffer |= IsKeyPressed(ImGuiKey_Backspace) && (flags & ImGuiTypingSelectFlags_AllowBackspace) == 0;\n        //if (clear_buffer) { IMGUI_DEBUG_LOG(\"GetTypingSelectRequest(): Clear SearchBuffer.\\n\"); }\n        if (clear_buffer)\n            data->Clear();\n    }\n\n    // Append to buffer\n    const int buffer_max_len = IM_ARRAYSIZE(data->SearchBuffer) - 1;\n    int buffer_len = (int)ImStrlen(data->SearchBuffer);\n    bool select_request = false;\n    for (ImWchar w : g.IO.InputQueueCharacters)\n    {\n        const int w_len = ImTextCountUtf8BytesFromStr(&w, &w + 1);\n        if (w < 32 || (buffer_len == 0 && ImCharIsBlankW(w)) || (buffer_len + w_len > buffer_max_len)) // Ignore leading blanks\n            continue;\n        char w_buf[5];\n        ImTextCharToUtf8(w_buf, (unsigned int)w);\n        if (data->SingleCharModeLock && w_len == out_request->SingleCharSize && memcmp(w_buf, data->SearchBuffer, w_len) == 0)\n        {\n            select_request = true; // Same character: don't need to append to buffer.\n            continue;\n        }\n        if (data->SingleCharModeLock)\n        {\n            data->Clear(); // Different character: clear\n            buffer_len = 0;\n        }\n        memcpy(data->SearchBuffer + buffer_len, w_buf, w_len + 1); // Append\n        buffer_len += w_len;\n        select_request = true;\n    }\n    g.IO.InputQueueCharacters.resize(0);\n\n    // Handle backspace\n    if ((flags & ImGuiTypingSelectFlags_AllowBackspace) && IsKeyPressed(ImGuiKey_Backspace, ImGuiInputFlags_Repeat))\n    {\n        char* p = (char*)(void*)ImTextFindPreviousUtf8Codepoint(data->SearchBuffer, data->SearchBuffer + buffer_len);\n        *p = 0;\n        buffer_len = (int)(p - data->SearchBuffer);\n    }\n\n    // Return request if any\n    if (buffer_len == 0)\n        return NULL;\n    if (select_request)\n    {\n        data->FocusScope = g.NavFocusScopeId;\n        data->LastRequestFrame = g.FrameCount;\n        data->LastRequestTime = (float)g.Time;\n    }\n    out_request->Flags = flags;\n    out_request->SearchBufferLen = buffer_len;\n    out_request->SearchBuffer = data->SearchBuffer;\n    out_request->SelectRequest = (data->LastRequestFrame == g.FrameCount);\n    out_request->SingleCharMode = false;\n    out_request->SingleCharSize = 0;\n\n    // Calculate if buffer contains the same character repeated.\n    // - This can be used to implement a special search mode on first character.\n    // - Performed on UTF-8 codepoint for correctness.\n    // - SingleCharMode is always set for first input character, because it usually leads to a \"next\".\n    if (flags & ImGuiTypingSelectFlags_AllowSingleCharMode)\n    {\n        const char* buf_begin = out_request->SearchBuffer;\n        const char* buf_end = out_request->SearchBuffer + out_request->SearchBufferLen;\n        const int c0_len = ImTextCountUtf8BytesFromChar(buf_begin, buf_end);\n        const char* p = buf_begin + c0_len;\n        for (; p < buf_end; p += c0_len)\n            if (memcmp(buf_begin, p, (size_t)c0_len) != 0)\n                break;\n        const int single_char_count = (p == buf_end) ? (out_request->SearchBufferLen / c0_len) : 0;\n        out_request->SingleCharMode = (single_char_count > 0 || data->SingleCharModeLock);\n        out_request->SingleCharSize = (ImS8)c0_len;\n        data->SingleCharModeLock |= (single_char_count >= TYPING_SELECT_SINGLE_CHAR_COUNT_FOR_LOCK); // From now on we stop search matching to lock to single char mode.\n    }\n\n    return out_request;\n}\n\nstatic int ImStrimatchlen(const char* s1, const char* s1_end, const char* s2)\n{\n    int match_len = 0;\n    while (s1 < s1_end && ImToUpper(*s1++) == ImToUpper(*s2++))\n        match_len++;\n    return match_len;\n}\n\n// Default handler for finding a result for typing-select. You may implement your own.\n// You might want to display a tooltip to visualize the current request SearchBuffer\n// When SingleCharMode is set:\n// - it is better to NOT display a tooltip of other on-screen display indicator.\n// - the index of the currently focused item is required.\n//   if your SetNextItemSelectionUserData() values are indices, you can obtain it from ImGuiMultiSelectIO::NavIdItem, otherwise from g.NavLastValidSelectionUserData.\nint ImGui::TypingSelectFindMatch(ImGuiTypingSelectRequest* req, int items_count, const char* (*get_item_name_func)(void*, int), void* user_data, int nav_item_idx)\n{\n    if (req == NULL || req->SelectRequest == false) // Support NULL parameter so both calls can be done from same spot.\n        return -1;\n    int idx = -1;\n    if (req->SingleCharMode && (req->Flags & ImGuiTypingSelectFlags_AllowSingleCharMode))\n        idx = TypingSelectFindNextSingleCharMatch(req, items_count, get_item_name_func, user_data, nav_item_idx);\n    else\n        idx = TypingSelectFindBestLeadingMatch(req, items_count, get_item_name_func, user_data);\n    if (idx != -1)\n        SetNavCursorVisibleAfterMove();\n    return idx;\n}\n\n// Special handling when a single character is repeated: perform search on a single letter and goes to next.\nint ImGui::TypingSelectFindNextSingleCharMatch(ImGuiTypingSelectRequest* req, int items_count, const char* (*get_item_name_func)(void*, int), void* user_data, int nav_item_idx)\n{\n    // FIXME: Assume selection user data is index. Would be extremely practical.\n    //if (nav_item_idx == -1)\n    //    nav_item_idx = (int)g.NavLastValidSelectionUserData;\n\n    int first_match_idx = -1;\n    bool return_next_match = false;\n    for (int idx = 0; idx < items_count; idx++)\n    {\n        const char* item_name = get_item_name_func(user_data, idx);\n        if (ImStrimatchlen(req->SearchBuffer, req->SearchBuffer + req->SingleCharSize, item_name) < req->SingleCharSize)\n            continue;\n        if (return_next_match)                           // Return next matching item after current item.\n            return idx;\n        if (first_match_idx == -1 && nav_item_idx == -1) // Return first match immediately if we don't have a nav_item_idx value.\n            return idx;\n        if (first_match_idx == -1)                       // Record first match for wrapping.\n            first_match_idx = idx;\n        if (nav_item_idx == idx)                         // Record that we encountering nav_item so we can return next match.\n            return_next_match = true;\n    }\n    return first_match_idx; // First result\n}\n\nint ImGui::TypingSelectFindBestLeadingMatch(ImGuiTypingSelectRequest* req, int items_count, const char* (*get_item_name_func)(void*, int), void* user_data)\n{\n    int longest_match_idx = -1;\n    int longest_match_len = 0;\n    for (int idx = 0; idx < items_count; idx++)\n    {\n        const char* item_name = get_item_name_func(user_data, idx);\n        const int match_len = ImStrimatchlen(req->SearchBuffer, req->SearchBuffer + req->SearchBufferLen, item_name);\n        if (match_len <= longest_match_len)\n            continue;\n        longest_match_idx = idx;\n        longest_match_len = match_len;\n        if (match_len == req->SearchBufferLen)\n            break;\n    }\n    return longest_match_idx;\n}\n\nvoid ImGui::DebugNodeTypingSelectState(ImGuiTypingSelectState* data)\n{\n#ifndef IMGUI_DISABLE_DEBUG_TOOLS\n    Text(\"SearchBuffer = \\\"%s\\\"\", data->SearchBuffer);\n    Text(\"SingleCharMode = %d, Size = %d, Lock = %d\", data->Request.SingleCharMode, data->Request.SingleCharSize, data->SingleCharModeLock);\n    Text(\"LastRequest = time: %.2f, frame: %d\", data->LastRequestTime, data->LastRequestFrame);\n#else\n    IM_UNUSED(data);\n#endif\n}\n\n//-------------------------------------------------------------------------\n// [SECTION] Widgets: Box-Select support\n// This has been extracted away from Multi-Select logic in the hope that it could eventually be used elsewhere, but hasn't been yet.\n//-------------------------------------------------------------------------\n// Extra logic in MultiSelectItemFooter() and ImGuiListClipper::Step()\n//-------------------------------------------------------------------------\n// - BoxSelectPreStartDrag() [Internal]\n// - BoxSelectActivateDrag() [Internal]\n// - BoxSelectDeactivateDrag() [Internal]\n// - BoxSelectScrollWithMouseDrag() [Internal]\n// - BeginBoxSelect() [Internal]\n// - EndBoxSelect() [Internal]\n//-------------------------------------------------------------------------\n\n// Call on the initial click.\nstatic void BoxSelectPreStartDrag(ImGuiID id, ImGuiSelectionUserData clicked_item)\n{\n    ImGuiContext& g = *GImGui;\n    ImGuiBoxSelectState* bs = &g.BoxSelectState;\n    bs->ID = id;\n    bs->IsStarting = true; // Consider starting box-select.\n    bs->IsStartedFromVoid = (clicked_item == ImGuiSelectionUserData_Invalid);\n    bs->IsStartedSetNavIdOnce = bs->IsStartedFromVoid;\n    bs->KeyMods = g.IO.KeyMods;\n    bs->StartPosRel = bs->EndPosRel = ImGui::WindowPosAbsToRel(g.CurrentWindow, g.IO.MousePos);\n    bs->ScrollAccum = ImVec2(0.0f, 0.0f);\n}\n\nstatic void BoxSelectActivateDrag(ImGuiBoxSelectState* bs, ImGuiWindow* window)\n{\n    ImGuiContext& g = *GImGui;\n    IMGUI_DEBUG_LOG_SELECTION(\"[selection] BeginBoxSelect() 0X%08X: Activate\\n\", bs->ID);\n    bs->IsActive = true;\n    bs->Window = window;\n    bs->IsStarting = false;\n    ImGui::SetActiveID(bs->ID, window);\n    ImGui::SetActiveIdUsingAllKeyboardKeys();\n    if (bs->IsStartedFromVoid && (bs->KeyMods & (ImGuiMod_Ctrl | ImGuiMod_Shift)) == 0)\n        bs->RequestClear = true;\n}\n\nstatic void BoxSelectDeactivateDrag(ImGuiBoxSelectState* bs)\n{\n    ImGuiContext& g = *GImGui;\n    bs->IsActive = bs->IsStarting = false;\n    if (g.ActiveId == bs->ID)\n    {\n        IMGUI_DEBUG_LOG_SELECTION(\"[selection] BeginBoxSelect() 0X%08X: Deactivate\\n\", bs->ID);\n        ImGui::ClearActiveID();\n    }\n    bs->ID = 0;\n}\n\nstatic void BoxSelectScrollWithMouseDrag(ImGuiBoxSelectState* bs, ImGuiWindow* window, const ImRect& inner_r)\n{\n    ImGuiContext& g = *GImGui;\n    IM_ASSERT(bs->Window == window);\n    for (int n = 0; n < 2; n++) // each axis\n    {\n        const float mouse_pos = g.IO.MousePos[n];\n        const float dist = (mouse_pos > inner_r.Max[n]) ? mouse_pos - inner_r.Max[n] : (mouse_pos < inner_r.Min[n]) ? mouse_pos - inner_r.Min[n] : 0.0f;\n        const float scroll_curr = window->Scroll[n];\n        if (dist == 0.0f || (dist < 0.0f && scroll_curr < 0.0f) || (dist > 0.0f && scroll_curr >= window->ScrollMax[n]))\n            continue;\n\n        const float speed_multiplier = ImLinearRemapClamp(g.FontSize, g.FontSize * 5.0f, 1.0f, 4.0f, ImAbs(dist)); // x1 to x4 depending on distance\n        const float scroll_step = g.FontSize * 35.0f * speed_multiplier * ImSign(dist) * g.IO.DeltaTime;\n        bs->ScrollAccum[n] += scroll_step;\n\n        // Accumulate into a stored value so we can handle high-framerate\n        const float scroll_step_i = ImFloor(bs->ScrollAccum[n]);\n        if (scroll_step_i == 0.0f)\n            continue;\n        if (n == 0)\n            ImGui::SetScrollX(window, scroll_curr + scroll_step_i);\n        else\n            ImGui::SetScrollY(window, scroll_curr + scroll_step_i);\n        bs->ScrollAccum[n] -= scroll_step_i;\n    }\n}\n\nbool ImGui::BeginBoxSelect(const ImRect& scope_rect, ImGuiWindow* window, ImGuiID box_select_id, ImGuiMultiSelectFlags ms_flags)\n{\n    ImGuiContext& g = *GImGui;\n    ImGuiBoxSelectState* bs = &g.BoxSelectState;\n    KeepAliveID(box_select_id);\n    if (bs->ID != box_select_id)\n        return false;\n\n    // IsStarting is set by MultiSelectItemFooter() when considering a possible box-select. We validate it here and lock geometry.\n    bs->UnclipMode = false;\n    bs->RequestClear = false;\n    if (bs->IsStarting && IsMouseDragPastThreshold(0))\n        BoxSelectActivateDrag(bs, window);\n    else if ((bs->IsStarting || bs->IsActive) && g.IO.MouseDown[0] == false)\n        BoxSelectDeactivateDrag(bs);\n    if (!bs->IsActive)\n        return false;\n\n    // Current frame absolute prev/current rectangles are used to toggle selection.\n    // They are derived from positions relative to scrolling space.\n    ImVec2 start_pos_abs = WindowPosRelToAbs(window, bs->StartPosRel);\n    ImVec2 prev_end_pos_abs = WindowPosRelToAbs(window, bs->EndPosRel); // Clamped already\n    ImVec2 curr_end_pos_abs = g.IO.MousePos;\n    if (ms_flags & ImGuiMultiSelectFlags_ScopeWindow) // Box-select scrolling only happens with ScopeWindow\n        curr_end_pos_abs = ImClamp(curr_end_pos_abs, scope_rect.Min, scope_rect.Max);\n    bs->BoxSelectRectPrev.Min = ImMin(start_pos_abs, prev_end_pos_abs);\n    bs->BoxSelectRectPrev.Max = ImMax(start_pos_abs, prev_end_pos_abs);\n    bs->BoxSelectRectCurr.Min = ImMin(start_pos_abs, curr_end_pos_abs);\n    bs->BoxSelectRectCurr.Max = ImMax(start_pos_abs, curr_end_pos_abs);\n\n    // Box-select 2D mode detects horizontal changes (vertical ones are already picked by Clipper)\n    // Storing an extra rect used by widgets supporting box-select.\n    if (ms_flags & ImGuiMultiSelectFlags_BoxSelect2d)\n        if (bs->BoxSelectRectPrev.Min.x != bs->BoxSelectRectCurr.Min.x || bs->BoxSelectRectPrev.Max.x != bs->BoxSelectRectCurr.Max.x)\n        {\n            bs->UnclipMode = true;\n            bs->UnclipRect = bs->BoxSelectRectPrev; // FIXME-OPT: UnclipRect x coordinates could be intersection of Prev and Curr rect on X axis.\n            bs->UnclipRect.Add(bs->BoxSelectRectCurr);\n        }\n\n    //GetForegroundDrawList()->AddRect(bs->UnclipRect.Min, bs->UnclipRect.Max, IM_COL32(255,0,0,200), 0.0f, 0, 3.0f);\n    //GetForegroundDrawList()->AddRect(bs->BoxSelectRectPrev.Min, bs->BoxSelectRectPrev.Max, IM_COL32(255,0,0,200), 0.0f, 0, 3.0f);\n    //GetForegroundDrawList()->AddRect(bs->BoxSelectRectCurr.Min, bs->BoxSelectRectCurr.Max, IM_COL32(0,255,0,200), 0.0f, 0, 1.0f);\n    return true;\n}\n\nvoid ImGui::EndBoxSelect(const ImRect& scope_rect, ImGuiMultiSelectFlags ms_flags)\n{\n    ImGuiContext& g = *GImGui;\n    ImGuiWindow* window = g.CurrentWindow;\n    ImGuiBoxSelectState* bs = &g.BoxSelectState;\n    IM_ASSERT(bs->IsActive);\n    bs->UnclipMode = false;\n\n    // Render selection rectangle\n    bs->EndPosRel = WindowPosAbsToRel(window, ImClamp(g.IO.MousePos, scope_rect.Min, scope_rect.Max)); // Clamp stored position according to current scrolling view\n    ImRect box_select_r = bs->BoxSelectRectCurr;\n    box_select_r.ClipWith(scope_rect);\n    window->DrawList->AddRectFilled(box_select_r.Min, box_select_r.Max, GetColorU32(ImGuiCol_SeparatorHovered, 0.30f)); // FIXME-MULTISELECT: Styling\n    window->DrawList->AddRect(box_select_r.Min, box_select_r.Max, GetColorU32(ImGuiCol_NavCursor)); // FIXME-MULTISELECT: Styling\n\n    // Scroll\n    const bool enable_scroll = (ms_flags & ImGuiMultiSelectFlags_ScopeWindow) && (ms_flags & ImGuiMultiSelectFlags_BoxSelectNoScroll) == 0;\n    if (enable_scroll)\n    {\n        ImRect scroll_r = scope_rect;\n        scroll_r.Expand(-g.FontSize);\n        //GetForegroundDrawList()->AddRect(scroll_r.Min, scroll_r.Max, IM_COL32(0, 255, 0, 255));\n        if (!scroll_r.Contains(g.IO.MousePos))\n            BoxSelectScrollWithMouseDrag(bs, window, scroll_r);\n    }\n}\n\n//-------------------------------------------------------------------------\n// [SECTION] Widgets: Multi-Select support\n//-------------------------------------------------------------------------\n// - DebugLogMultiSelectRequests() [Internal]\n// - CalcScopeRect() [Internal]\n// - BeginMultiSelect()\n// - EndMultiSelect()\n// - SetNextItemSelectionUserData()\n// - MultiSelectItemHeader() [Internal]\n// - MultiSelectItemFooter() [Internal]\n// - DebugNodeMultiSelectState() [Internal]\n//-------------------------------------------------------------------------\n\nstatic void DebugLogMultiSelectRequests(const char* function, const ImGuiMultiSelectIO* io)\n{\n    ImGuiContext& g = *GImGui;\n    IM_UNUSED(function);\n    for (const ImGuiSelectionRequest& req : io->Requests)\n    {\n        if (req.Type == ImGuiSelectionRequestType_SetAll)    IMGUI_DEBUG_LOG_SELECTION(\"[selection] %s: Request: SetAll %d (= %s)\\n\", function, req.Selected, req.Selected ? \"SelectAll\" : \"Clear\");\n        if (req.Type == ImGuiSelectionRequestType_SetRange)  IMGUI_DEBUG_LOG_SELECTION(\"[selection] %s: Request: SetRange %\" IM_PRId64 \"..%\" IM_PRId64 \" (0x%\" IM_PRIX64 \"..0x%\" IM_PRIX64 \") = %d (dir %d)\\n\", function, req.RangeFirstItem, req.RangeLastItem, req.RangeFirstItem, req.RangeLastItem, req.Selected, req.RangeDirection);\n    }\n}\n\nstatic ImRect CalcScopeRect(ImGuiMultiSelectTempData* ms, ImGuiWindow* window)\n{\n    ImGuiContext& g = *GImGui;\n    if (ms->Flags & ImGuiMultiSelectFlags_ScopeRect)\n    {\n        // Warning: this depends on CursorMaxPos so it means to be called by EndMultiSelect() only\n        return ImRect(ms->ScopeRectMin, ImMax(window->DC.CursorMaxPos, ms->ScopeRectMin));\n    }\n    else\n    {\n        // When a table, pull HostClipRect, which allows us to predict ClipRect before first row/layout is performed. (#7970)\n        ImRect scope_rect = window->InnerClipRect;\n        if (g.CurrentTable != NULL)\n            scope_rect = g.CurrentTable->HostClipRect;\n\n        // Add inner table decoration (#7821) // FIXME: Why not baking in InnerClipRect?\n        scope_rect.Min = ImMin(scope_rect.Min + ImVec2(window->DecoInnerSizeX1, window->DecoInnerSizeY1), scope_rect.Max);\n        return scope_rect;\n    }\n}\n\n// Return ImGuiMultiSelectIO structure.\n// Lifetime: don't hold on ImGuiMultiSelectIO* pointers over multiple frames or past any subsequent call to BeginMultiSelect() or EndMultiSelect().\n// Passing 'selection_size' and 'items_count' parameters is currently optional.\n// - 'selection_size' is useful to disable some shortcut routing: e.g. ImGuiMultiSelectFlags_ClearOnEscape won't claim Escape key when selection_size 0,\n//    allowing a first press to clear selection THEN the second press to leave child window and return to parent.\n// - 'items_count' is stored in ImGuiMultiSelectIO which makes it a convenient way to pass the information to your ApplyRequest() handler (but you may pass it differently).\n// - If they are costly for you to compute (e.g. external intrusive selection without maintaining size), you may avoid them and pass -1.\n//   - If you can easily tell if your selection is empty or not, you may pass 0/1, or you may enable ImGuiMultiSelectFlags_ClearOnEscape flag dynamically.\nImGuiMultiSelectIO* ImGui::BeginMultiSelect(ImGuiMultiSelectFlags flags, int selection_size, int items_count)\n{\n    ImGuiContext& g = *GImGui;\n    ImGuiWindow* window = g.CurrentWindow;\n\n    if (++g.MultiSelectTempDataStacked > g.MultiSelectTempData.Size)\n        g.MultiSelectTempData.resize(g.MultiSelectTempDataStacked, ImGuiMultiSelectTempData());\n    ImGuiMultiSelectTempData* ms = &g.MultiSelectTempData[g.MultiSelectTempDataStacked - 1];\n    IM_STATIC_ASSERT(offsetof(ImGuiMultiSelectTempData, IO) == 0); // Clear() relies on that.\n    g.CurrentMultiSelect = ms;\n    if ((flags & (ImGuiMultiSelectFlags_ScopeWindow | ImGuiMultiSelectFlags_ScopeRect)) == 0)\n        flags |= ImGuiMultiSelectFlags_ScopeWindow;\n    if (flags & ImGuiMultiSelectFlags_SingleSelect)\n        flags &= ~(ImGuiMultiSelectFlags_BoxSelect2d | ImGuiMultiSelectFlags_BoxSelect1d);\n    if (flags & ImGuiMultiSelectFlags_BoxSelect2d)\n        flags &= ~ImGuiMultiSelectFlags_BoxSelect1d;\n\n    // FIXME: Workaround to the fact we override CursorMaxPos, meaning size measurement are lost. (#8250)\n    // They should perhaps be stacked properly?\n    if (ImGuiTable* table = g.CurrentTable)\n        if (table->CurrentColumn != -1)\n            TableEndCell(table); // This is currently safe to call multiple time. If that properly is lost we can extract the \"save measurement\" part of it.\n\n    // FIXME: BeginFocusScope()\n    const ImGuiID id = window->IDStack.back();\n    ms->Clear();\n    ms->FocusScopeId = id;\n    ms->Flags = flags;\n    ms->IsFocused = (ms->FocusScopeId == g.NavFocusScopeId);\n    ms->BackupCursorMaxPos = window->DC.CursorMaxPos;\n    ms->ScopeRectMin = window->DC.CursorMaxPos = window->DC.CursorPos;\n    PushFocusScope(ms->FocusScopeId);\n    if (flags & ImGuiMultiSelectFlags_ScopeWindow) // Mark parent child window as navigable into, with highlight. Assume user will always submit interactive items.\n        window->DC.NavLayersActiveMask |= 1 << ImGuiNavLayer_Main;\n\n    // Use copy of keyboard mods at the time of the request, otherwise we would requires mods to be held for an extra frame.\n    ms->KeyMods = g.NavJustMovedToId ? (g.NavJustMovedToIsTabbing ? 0 : g.NavJustMovedToKeyMods) : g.IO.KeyMods;\n    if (flags & ImGuiMultiSelectFlags_NoRangeSelect)\n        ms->KeyMods &= ~ImGuiMod_Shift;\n\n    // Bind storage\n    ImGuiMultiSelectState* storage = g.MultiSelectStorage.GetOrAddByKey(id);\n    storage->ID = id;\n    storage->LastFrameActive = g.FrameCount;\n    storage->LastSelectionSize = selection_size;\n    storage->Window = window;\n    ms->Storage = storage;\n\n    // Output to user\n    ms->IO.Requests.resize(0);\n    ms->IO.RangeSrcItem = storage->RangeSrcItem;\n    ms->IO.NavIdItem = storage->NavIdItem;\n    ms->IO.NavIdSelected = (storage->NavIdSelected == 1) ? true : false;\n    ms->IO.ItemsCount = items_count;\n\n    // Clear when using Navigation to move within the scope\n    // (we compare FocusScopeId so it possible to use multiple selections inside a same window)\n    bool request_clear = false;\n    bool request_select_all = false;\n    if (g.NavJustMovedToId != 0 && g.NavJustMovedToFocusScopeId == ms->FocusScopeId && g.NavJustMovedToHasSelectionData)\n    {\n        if (ms->KeyMods & ImGuiMod_Shift)\n            ms->IsKeyboardSetRange = true;\n        if (ms->IsKeyboardSetRange)\n            IM_ASSERT(storage->RangeSrcItem != ImGuiSelectionUserData_Invalid); // Not ready -> could clear?\n        if ((ms->KeyMods & (ImGuiMod_Ctrl | ImGuiMod_Shift)) == 0 && (flags & (ImGuiMultiSelectFlags_NoAutoClear | ImGuiMultiSelectFlags_NoAutoSelect)) == 0)\n            request_clear = true;\n    }\n    else if (g.NavJustMovedFromFocusScopeId == ms->FocusScopeId)\n    {\n        // Also clear on leaving scope (may be optional?)\n        if ((ms->KeyMods & (ImGuiMod_Ctrl | ImGuiMod_Shift)) == 0 && (flags & (ImGuiMultiSelectFlags_NoAutoClear | ImGuiMultiSelectFlags_NoAutoSelect)) == 0)\n            request_clear = true;\n    }\n\n    // Box-select handling: update active state.\n    ImGuiBoxSelectState* bs = &g.BoxSelectState;\n    if (flags & (ImGuiMultiSelectFlags_BoxSelect1d | ImGuiMultiSelectFlags_BoxSelect2d))\n    {\n        ms->BoxSelectId = GetID(\"##BoxSelect\");\n        if (BeginBoxSelect(CalcScopeRect(ms, window), window, ms->BoxSelectId, flags))\n            request_clear |= bs->RequestClear;\n    }\n\n    if (ms->IsFocused)\n    {\n        // Shortcut: Clear selection (Escape)\n        // - Only claim shortcut if selection is not empty, allowing further presses on Escape to e.g. leave current child window.\n        // - Box select also handle Escape and needs to pass an id to bypass ActiveIdUsingAllKeyboardKeys lock.\n        if (flags & ImGuiMultiSelectFlags_ClearOnEscape)\n        {\n            if (selection_size != 0 || bs->IsActive)\n                if (Shortcut(ImGuiKey_Escape, ImGuiInputFlags_None, bs->IsActive ? bs->ID : 0))\n                {\n                    request_clear = true;\n                    if (bs->IsActive)\n                        BoxSelectDeactivateDrag(bs);\n                }\n        }\n\n        // Shortcut: Select all (CTRL+A)\n        if (!(flags & ImGuiMultiSelectFlags_SingleSelect) && !(flags & ImGuiMultiSelectFlags_NoSelectAll))\n            if (Shortcut(ImGuiMod_Ctrl | ImGuiKey_A))\n                request_select_all = true;\n    }\n\n    if (request_clear || request_select_all)\n    {\n        MultiSelectAddSetAll(ms, request_select_all);\n        if (!request_select_all)\n            storage->LastSelectionSize = 0;\n    }\n    ms->LoopRequestSetAll = request_select_all ? 1 : request_clear ? 0 : -1;\n    ms->LastSubmittedItem = ImGuiSelectionUserData_Invalid;\n\n    if (g.DebugLogFlags & ImGuiDebugLogFlags_EventSelection)\n        DebugLogMultiSelectRequests(\"BeginMultiSelect\", &ms->IO);\n\n    return &ms->IO;\n}\n\n// Return updated ImGuiMultiSelectIO structure.\n// Lifetime: don't hold on ImGuiMultiSelectIO* pointers over multiple frames or past any subsequent call to BeginMultiSelect() or EndMultiSelect().\nImGuiMultiSelectIO* ImGui::EndMultiSelect()\n{\n    ImGuiContext& g = *GImGui;\n    ImGuiMultiSelectTempData* ms = g.CurrentMultiSelect;\n    ImGuiMultiSelectState* storage = ms->Storage;\n    ImGuiWindow* window = g.CurrentWindow;\n    IM_ASSERT_USER_ERROR(ms->FocusScopeId == g.CurrentFocusScopeId, \"EndMultiSelect() FocusScope mismatch!\");\n    IM_ASSERT(g.CurrentMultiSelect != NULL && storage->Window == g.CurrentWindow);\n    IM_ASSERT(g.MultiSelectTempDataStacked > 0 && &g.MultiSelectTempData[g.MultiSelectTempDataStacked - 1] == g.CurrentMultiSelect);\n\n    ImRect scope_rect = CalcScopeRect(ms, window);\n    if (ms->IsFocused)\n    {\n        // We currently don't allow user code to modify RangeSrcItem by writing to BeginIO's version, but that would be an easy change here.\n        if (ms->IO.RangeSrcReset || (ms->RangeSrcPassedBy == false && ms->IO.RangeSrcItem != ImGuiSelectionUserData_Invalid)) // Can't read storage->RangeSrcItem here -> we want the state at begining of the scope (see tests for easy failure)\n        {\n            IMGUI_DEBUG_LOG_SELECTION(\"[selection] EndMultiSelect: Reset RangeSrcItem.\\n\"); // Will set be to NavId.\n            storage->RangeSrcItem = ImGuiSelectionUserData_Invalid;\n        }\n        if (ms->NavIdPassedBy == false && storage->NavIdItem != ImGuiSelectionUserData_Invalid)\n        {\n            IMGUI_DEBUG_LOG_SELECTION(\"[selection] EndMultiSelect: Reset NavIdItem.\\n\");\n            storage->NavIdItem = ImGuiSelectionUserData_Invalid;\n            storage->NavIdSelected = -1;\n        }\n\n        if ((ms->Flags & (ImGuiMultiSelectFlags_BoxSelect1d | ImGuiMultiSelectFlags_BoxSelect2d)) && GetBoxSelectState(ms->BoxSelectId))\n            EndBoxSelect(scope_rect, ms->Flags);\n    }\n\n    if (ms->IsEndIO == false)\n        ms->IO.Requests.resize(0);\n\n    // Clear selection when clicking void?\n    // We specifically test for IsMouseDragPastThreshold(0) == false to allow box-selection!\n    // The InnerRect test is necessary for non-child/decorated windows.\n    bool scope_hovered = IsWindowHovered() && window->InnerRect.Contains(g.IO.MousePos);\n    if (scope_hovered && (ms->Flags & ImGuiMultiSelectFlags_ScopeRect))\n        scope_hovered &= scope_rect.Contains(g.IO.MousePos);\n    if (scope_hovered && g.HoveredId == 0 && g.ActiveId == 0)\n    {\n        if (ms->Flags & (ImGuiMultiSelectFlags_BoxSelect1d | ImGuiMultiSelectFlags_BoxSelect2d))\n        {\n            if (!g.BoxSelectState.IsActive && !g.BoxSelectState.IsStarting && g.IO.MouseClickedCount[0] == 1)\n            {\n                BoxSelectPreStartDrag(ms->BoxSelectId, ImGuiSelectionUserData_Invalid);\n                FocusWindow(window, ImGuiFocusRequestFlags_UnlessBelowModal);\n                SetHoveredID(ms->BoxSelectId);\n                if (ms->Flags & ImGuiMultiSelectFlags_ScopeRect)\n                    SetNavID(0, ImGuiNavLayer_Main, ms->FocusScopeId, ImRect(g.IO.MousePos, g.IO.MousePos)); // Automatically switch FocusScope for initial click from void to box-select.\n            }\n        }\n\n        if (ms->Flags & ImGuiMultiSelectFlags_ClearOnClickVoid)\n            if (IsMouseReleased(0) && IsMouseDragPastThreshold(0) == false && g.IO.KeyMods == ImGuiMod_None)\n                MultiSelectAddSetAll(ms, false);\n    }\n\n    // Courtesy nav wrapping helper flag\n    if (ms->Flags & ImGuiMultiSelectFlags_NavWrapX)\n    {\n        IM_ASSERT(ms->Flags & ImGuiMultiSelectFlags_ScopeWindow); // Only supported at window scope\n        ImGui::NavMoveRequestTryWrapping(ImGui::GetCurrentWindow(), ImGuiNavMoveFlags_WrapX);\n    }\n\n    // Unwind\n    window->DC.CursorMaxPos = ImMax(ms->BackupCursorMaxPos, window->DC.CursorMaxPos);\n    PopFocusScope();\n\n    if (g.DebugLogFlags & ImGuiDebugLogFlags_EventSelection)\n        DebugLogMultiSelectRequests(\"EndMultiSelect\", &ms->IO);\n\n    ms->FocusScopeId = 0;\n    ms->Flags = ImGuiMultiSelectFlags_None;\n    g.CurrentMultiSelect = (--g.MultiSelectTempDataStacked > 0) ? &g.MultiSelectTempData[g.MultiSelectTempDataStacked - 1] : NULL;\n\n    return &ms->IO;\n}\n\nvoid ImGui::SetNextItemSelectionUserData(ImGuiSelectionUserData selection_user_data)\n{\n    // Note that flags will be cleared by ItemAdd(), so it's only useful for Navigation code!\n    // This designed so widgets can also cheaply set this before calling ItemAdd(), so we are not tied to MultiSelect api.\n    ImGuiContext& g = *GImGui;\n    g.NextItemData.SelectionUserData = selection_user_data;\n    g.NextItemData.FocusScopeId = g.CurrentFocusScopeId;\n\n    if (ImGuiMultiSelectTempData* ms = g.CurrentMultiSelect)\n    {\n        // Auto updating RangeSrcPassedBy for cases were clipper is not used (done before ItemAdd() clipping)\n        g.NextItemData.ItemFlags |= ImGuiItemFlags_HasSelectionUserData | ImGuiItemFlags_IsMultiSelect;\n        if (ms->IO.RangeSrcItem == selection_user_data)\n            ms->RangeSrcPassedBy = true;\n    }\n    else\n    {\n        g.NextItemData.ItemFlags |= ImGuiItemFlags_HasSelectionUserData;\n    }\n}\n\n// In charge of:\n// - Applying SetAll for submitted items.\n// - Applying SetRange for submitted items and record end points.\n// - Altering button behavior flags to facilitate use with drag and drop.\nvoid ImGui::MultiSelectItemHeader(ImGuiID id, bool* p_selected, ImGuiButtonFlags* p_button_flags)\n{\n    ImGuiContext& g = *GImGui;\n    ImGuiMultiSelectTempData* ms = g.CurrentMultiSelect;\n\n    bool selected = *p_selected;\n    if (ms->IsFocused)\n    {\n        ImGuiMultiSelectState* storage = ms->Storage;\n        ImGuiSelectionUserData item_data = g.NextItemData.SelectionUserData;\n        IM_ASSERT(g.NextItemData.FocusScopeId == g.CurrentFocusScopeId && \"Forgot to call SetNextItemSelectionUserData() prior to item, required in BeginMultiSelect()/EndMultiSelect() scope\");\n\n        // Apply SetAll (Clear/SelectAll) requests requested by BeginMultiSelect().\n        // This is only useful if the user hasn't processed them already, and this only works if the user isn't using the clipper.\n        // If you are using a clipper you need to process the SetAll request after calling BeginMultiSelect()\n        if (ms->LoopRequestSetAll != -1)\n            selected = (ms->LoopRequestSetAll == 1);\n\n        // When using SHIFT+Nav: because it can incur scrolling we cannot afford a frame of lag with the selection highlight (otherwise scrolling would happen before selection)\n        // For this to work, we need someone to set 'RangeSrcPassedBy = true' at some point (either clipper either SetNextItemSelectionUserData() function)\n        if (ms->IsKeyboardSetRange)\n        {\n            IM_ASSERT(id != 0 && (ms->KeyMods & ImGuiMod_Shift) != 0);\n            const bool is_range_dst = (ms->RangeDstPassedBy == false) && g.NavJustMovedToId == id;     // Assume that g.NavJustMovedToId is not clipped.\n            if (is_range_dst)\n                ms->RangeDstPassedBy = true;\n            if (is_range_dst && storage->RangeSrcItem == ImGuiSelectionUserData_Invalid) // If we don't have RangeSrc, assign RangeSrc = RangeDst\n            {\n                storage->RangeSrcItem = item_data;\n                storage->RangeSelected = selected ? 1 : 0;\n            }\n            const bool is_range_src = storage->RangeSrcItem == item_data;\n            if (is_range_src || is_range_dst || ms->RangeSrcPassedBy != ms->RangeDstPassedBy)\n            {\n                // Apply range-select value to visible items\n                IM_ASSERT(storage->RangeSrcItem != ImGuiSelectionUserData_Invalid && storage->RangeSelected != -1);\n                selected = (storage->RangeSelected != 0);\n            }\n            else if ((ms->KeyMods & ImGuiMod_Ctrl) == 0 && (ms->Flags & ImGuiMultiSelectFlags_NoAutoClear) == 0)\n            {\n                // Clear other items\n                selected = false;\n            }\n        }\n        *p_selected = selected;\n    }\n\n    // Alter button behavior flags\n    // To handle drag and drop of multiple items we need to avoid clearing selection on click.\n    // Enabling this test makes actions using CTRL+SHIFT delay their effect on MouseUp which is annoying, but it allows drag and drop of multiple items.\n    if (p_button_flags != NULL)\n    {\n        ImGuiButtonFlags button_flags = *p_button_flags;\n        button_flags |= ImGuiButtonFlags_NoHoveredOnFocus;\n        if ((!selected || (g.ActiveId == id && g.ActiveIdHasBeenPressedBefore)) && !(ms->Flags & ImGuiMultiSelectFlags_SelectOnClickRelease))\n            button_flags = (button_flags | ImGuiButtonFlags_PressedOnClick) & ~ImGuiButtonFlags_PressedOnClickRelease;\n        else\n            button_flags |= ImGuiButtonFlags_PressedOnClickRelease;\n        *p_button_flags = button_flags;\n    }\n}\n\n// In charge of:\n// - Auto-select on navigation.\n// - Box-select toggle handling.\n// - Right-click handling.\n// - Altering selection based on Ctrl/Shift modifiers, both for keyboard and mouse.\n// - Record current selection state for RangeSrc\n// This is all rather complex, best to run and refer to \"widgets_multiselect_xxx\" tests in imgui_test_suite.\nvoid ImGui::MultiSelectItemFooter(ImGuiID id, bool* p_selected, bool* p_pressed)\n{\n    ImGuiContext& g = *GImGui;\n    ImGuiWindow* window = g.CurrentWindow;\n\n    bool selected = *p_selected;\n    bool pressed = *p_pressed;\n    ImGuiMultiSelectTempData* ms = g.CurrentMultiSelect;\n    ImGuiMultiSelectState* storage = ms->Storage;\n    if (pressed)\n        ms->IsFocused = true;\n\n    bool hovered = false;\n    if (g.LastItemData.StatusFlags & ImGuiItemStatusFlags_HoveredRect)\n        hovered = IsItemHovered(ImGuiHoveredFlags_AllowWhenBlockedByPopup);\n    if (!ms->IsFocused && !hovered)\n        return;\n\n    ImGuiSelectionUserData item_data = g.NextItemData.SelectionUserData;\n\n    ImGuiMultiSelectFlags flags = ms->Flags;\n    const bool is_singleselect = (flags & ImGuiMultiSelectFlags_SingleSelect) != 0;\n    bool is_ctrl = (ms->KeyMods & ImGuiMod_Ctrl) != 0;\n    bool is_shift = (ms->KeyMods & ImGuiMod_Shift) != 0;\n\n    bool apply_to_range_src = false;\n\n    if (g.NavId == id && storage->RangeSrcItem == ImGuiSelectionUserData_Invalid)\n        apply_to_range_src = true;\n    if (ms->IsEndIO == false)\n    {\n        ms->IO.Requests.resize(0);\n        ms->IsEndIO = true;\n    }\n\n    // Auto-select as you navigate a list\n    if (g.NavJustMovedToId == id)\n    {\n        if ((flags & ImGuiMultiSelectFlags_NoAutoSelect) == 0)\n        {\n            if (is_ctrl && is_shift)\n                pressed = true;\n            else if (!is_ctrl)\n                selected = pressed = true;\n        }\n        else\n        {\n            // With NoAutoSelect, using Shift+keyboard performs a write/copy\n            if (is_shift)\n                pressed = true;\n            else if (!is_ctrl)\n                apply_to_range_src = true; // Since if (pressed) {} main block is not running we update this\n        }\n    }\n\n    if (apply_to_range_src)\n    {\n        storage->RangeSrcItem = item_data;\n        storage->RangeSelected = selected; // Will be updated at the end of this function anyway.\n    }\n\n    // Box-select toggle handling\n    if (ms->BoxSelectId != 0)\n        if (ImGuiBoxSelectState* bs = GetBoxSelectState(ms->BoxSelectId))\n        {\n            const bool rect_overlap_curr = bs->BoxSelectRectCurr.Overlaps(g.LastItemData.Rect);\n            const bool rect_overlap_prev = bs->BoxSelectRectPrev.Overlaps(g.LastItemData.Rect);\n            if ((rect_overlap_curr && !rect_overlap_prev && !selected) || (rect_overlap_prev && !rect_overlap_curr))\n            {\n                if (storage->LastSelectionSize <= 0 && bs->IsStartedSetNavIdOnce)\n                {\n                    pressed = true; // First item act as a pressed: code below will emit selection request and set NavId (whatever we emit here will be overridden anyway)\n                    bs->IsStartedSetNavIdOnce = false;\n                }\n                else\n                {\n                    selected = !selected;\n                    MultiSelectAddSetRange(ms, selected, +1, item_data, item_data);\n                }\n                storage->LastSelectionSize = ImMax(storage->LastSelectionSize + 1, 1);\n            }\n        }\n\n    // Right-click handling.\n    // FIXME-MULTISELECT: Currently filtered out by ImGuiMultiSelectFlags_NoAutoSelect but maybe should be moved to Selectable(). See https://github.com/ocornut/imgui/pull/5816\n    if (hovered && IsMouseClicked(1) && (flags & ImGuiMultiSelectFlags_NoAutoSelect) == 0)\n    {\n        if (g.ActiveId != 0 && g.ActiveId != id)\n            ClearActiveID();\n        SetFocusID(id, window);\n        if (!pressed && !selected)\n        {\n            pressed = true;\n            is_ctrl = is_shift = false;\n        }\n    }\n\n    // Unlike Space, Enter doesn't alter selection (but can still return a press) unless current item is not selected.\n    // The later, \"unless current item is not select\", may become optional? It seems like a better default if Enter doesn't necessarily open something\n    // (unlike e.g. Windows explorer). For use case where Enter always open something, we might decide to make this optional?\n    const bool enter_pressed = pressed && (g.NavActivateId == id) && (g.NavActivateFlags & ImGuiActivateFlags_PreferInput);\n\n    // Alter selection\n    if (pressed && (!enter_pressed || !selected))\n    {\n        // Box-select\n        ImGuiInputSource input_source = (g.NavJustMovedToId == id || g.NavActivateId == id) ? g.NavInputSource : ImGuiInputSource_Mouse;\n        if (flags & (ImGuiMultiSelectFlags_BoxSelect1d | ImGuiMultiSelectFlags_BoxSelect2d))\n            if (selected == false && !g.BoxSelectState.IsActive && !g.BoxSelectState.IsStarting && input_source == ImGuiInputSource_Mouse && g.IO.MouseClickedCount[0] == 1)\n                BoxSelectPreStartDrag(ms->BoxSelectId, item_data);\n\n        //----------------------------------------------------------------------------------------\n        // ACTION                      | Begin  | Pressed/Activated  | End\n        //----------------------------------------------------------------------------------------\n        // Keys Navigated:             | Clear  | Src=item, Sel=1               SetRange 1\n        // Keys Navigated: Ctrl        | n/a    | n/a\n        // Keys Navigated:      Shift  | n/a    | Dst=item, Sel=1,   => Clear + SetRange 1\n        // Keys Navigated: Ctrl+Shift  | n/a    | Dst=item, Sel=Src  => Clear + SetRange Src-Dst\n        // Keys Activated:             | n/a    | Src=item, Sel=1    => Clear + SetRange 1\n        // Keys Activated: Ctrl        | n/a    | Src=item, Sel=!Sel =>         SetSange 1\n        // Keys Activated:      Shift  | n/a    | Dst=item, Sel=1    => Clear + SetSange 1\n        //----------------------------------------------------------------------------------------\n        // Mouse Pressed:              | n/a    | Src=item, Sel=1,   => Clear + SetRange 1\n        // Mouse Pressed:  Ctrl        | n/a    | Src=item, Sel=!Sel =>         SetRange 1\n        // Mouse Pressed:       Shift  | n/a    | Dst=item, Sel=1,   => Clear + SetRange 1\n        // Mouse Pressed:  Ctrl+Shift  | n/a    | Dst=item, Sel=!Sel =>         SetRange Src-Dst\n        //----------------------------------------------------------------------------------------\n\n        if ((flags & ImGuiMultiSelectFlags_NoAutoClear) == 0)\n        {\n            bool request_clear = false;\n            if (is_singleselect)\n                request_clear = true;\n            else if ((input_source == ImGuiInputSource_Mouse || g.NavActivateId == id) && !is_ctrl)\n                request_clear = (flags & ImGuiMultiSelectFlags_NoAutoClearOnReselect) ? !selected : true;\n            else if ((input_source == ImGuiInputSource_Keyboard || input_source == ImGuiInputSource_Gamepad) && is_shift && !is_ctrl)\n                request_clear = true; // With is_shift==false the RequestClear was done in BeginIO, not necessary to do again.\n            if (request_clear)\n                MultiSelectAddSetAll(ms, false);\n        }\n\n        int range_direction;\n        bool range_selected;\n        if (is_shift && !is_singleselect)\n        {\n            //IM_ASSERT(storage->HasRangeSrc && storage->HasRangeValue);\n            if (storage->RangeSrcItem == ImGuiSelectionUserData_Invalid)\n                storage->RangeSrcItem = item_data;\n            if ((flags & ImGuiMultiSelectFlags_NoAutoSelect) == 0)\n            {\n                // Shift+Arrow always select\n                // Ctrl+Shift+Arrow copy source selection state (already stored by BeginMultiSelect() in storage->RangeSelected)\n                range_selected = (is_ctrl && storage->RangeSelected != -1) ? (storage->RangeSelected != 0) : true;\n            }\n            else\n            {\n                // Shift+Arrow copy source selection state\n                // Shift+Click always copy from target selection state\n                if (ms->IsKeyboardSetRange)\n                    range_selected = (storage->RangeSelected != -1) ? (storage->RangeSelected != 0) : true;\n                else\n                    range_selected = !selected;\n            }\n            range_direction = ms->RangeSrcPassedBy ? +1 : -1;\n        }\n        else\n        {\n            // Ctrl inverts selection, otherwise always select\n            if ((flags & ImGuiMultiSelectFlags_NoAutoSelect) == 0)\n                selected = is_ctrl ? !selected : true;\n            else\n                selected = !selected;\n            storage->RangeSrcItem = item_data;\n            range_selected = selected;\n            range_direction = +1;\n        }\n        MultiSelectAddSetRange(ms, range_selected, range_direction, storage->RangeSrcItem, item_data);\n    }\n\n    // Update/store the selection state of the Source item (used by CTRL+SHIFT, when Source is unselected we perform a range unselect)\n    if (storage->RangeSrcItem == item_data)\n        storage->RangeSelected = selected ? 1 : 0;\n\n    // Update/store the selection state of focused item\n    if (g.NavId == id)\n    {\n        storage->NavIdItem = item_data;\n        storage->NavIdSelected = selected ? 1 : 0;\n    }\n    if (storage->NavIdItem == item_data)\n        ms->NavIdPassedBy = true;\n    ms->LastSubmittedItem = item_data;\n\n    *p_selected = selected;\n    *p_pressed = pressed;\n}\n\nvoid ImGui::MultiSelectAddSetAll(ImGuiMultiSelectTempData* ms, bool selected)\n{\n    ImGuiSelectionRequest req = { ImGuiSelectionRequestType_SetAll, selected, 0, ImGuiSelectionUserData_Invalid, ImGuiSelectionUserData_Invalid };\n    ms->IO.Requests.resize(0);      // Can always clear previous requests\n    ms->IO.Requests.push_back(req); // Add new request\n}\n\nvoid ImGui::MultiSelectAddSetRange(ImGuiMultiSelectTempData* ms, bool selected, int range_dir, ImGuiSelectionUserData first_item, ImGuiSelectionUserData last_item)\n{\n    // Merge contiguous spans into same request (unless NoRangeSelect is set which guarantees single-item ranges)\n    if (ms->IO.Requests.Size > 0 && first_item == last_item && (ms->Flags & ImGuiMultiSelectFlags_NoRangeSelect) == 0)\n    {\n        ImGuiSelectionRequest* prev = &ms->IO.Requests.Data[ms->IO.Requests.Size - 1];\n        if (prev->Type == ImGuiSelectionRequestType_SetRange && prev->RangeLastItem == ms->LastSubmittedItem && prev->Selected == selected)\n        {\n            prev->RangeLastItem = last_item;\n            return;\n        }\n    }\n\n    ImGuiSelectionRequest req = { ImGuiSelectionRequestType_SetRange, selected, (ImS8)range_dir, (range_dir > 0) ? first_item : last_item, (range_dir > 0) ? last_item : first_item };\n    ms->IO.Requests.push_back(req); // Add new request\n}\n\nvoid ImGui::DebugNodeMultiSelectState(ImGuiMultiSelectState* storage)\n{\n#ifndef IMGUI_DISABLE_DEBUG_TOOLS\n    const bool is_active = (storage->LastFrameActive >= GetFrameCount() - 2); // Note that fully clipped early out scrolling tables will appear as inactive here.\n    if (!is_active) { PushStyleColor(ImGuiCol_Text, GetStyleColorVec4(ImGuiCol_TextDisabled)); }\n    bool open = TreeNode((void*)(intptr_t)storage->ID, \"MultiSelect 0x%08X in '%s'%s\", storage->ID, storage->Window ? storage->Window->Name : \"N/A\", is_active ? \"\" : \" *Inactive*\");\n    if (!is_active) { PopStyleColor(); }\n    if (!open)\n        return;\n    Text(\"RangeSrcItem = %\" IM_PRId64 \" (0x%\" IM_PRIX64 \"), RangeSelected = %d\", storage->RangeSrcItem, storage->RangeSrcItem, storage->RangeSelected);\n    Text(\"NavIdItem = %\" IM_PRId64 \" (0x%\" IM_PRIX64 \"), NavIdSelected = %d\", storage->NavIdItem, storage->NavIdItem, storage->NavIdSelected);\n    Text(\"LastSelectionSize = %d\", storage->LastSelectionSize); // Provided by user\n    TreePop();\n#else\n    IM_UNUSED(storage);\n#endif\n}\n\n//-------------------------------------------------------------------------\n// [SECTION] Widgets: Multi-Select helpers\n//-------------------------------------------------------------------------\n// - ImGuiSelectionBasicStorage\n// - ImGuiSelectionExternalStorage\n//-------------------------------------------------------------------------\n\nImGuiSelectionBasicStorage::ImGuiSelectionBasicStorage()\n{\n    Size = 0;\n    PreserveOrder = false;\n    UserData = NULL;\n    AdapterIndexToStorageId = [](ImGuiSelectionBasicStorage*, int idx) { return (ImGuiID)idx; };\n    _SelectionOrder = 1; // Always >0\n}\n\nvoid ImGuiSelectionBasicStorage::Clear()\n{\n    Size = 0;\n    _SelectionOrder = 1; // Always >0\n    _Storage.Data.resize(0);\n}\n\nvoid ImGuiSelectionBasicStorage::Swap(ImGuiSelectionBasicStorage& r)\n{\n    ImSwap(Size, r.Size);\n    ImSwap(_SelectionOrder, r._SelectionOrder);\n    _Storage.Data.swap(r._Storage.Data);\n}\n\nbool ImGuiSelectionBasicStorage::Contains(ImGuiID id) const\n{\n    return _Storage.GetInt(id, 0) != 0;\n}\n\nstatic int IMGUI_CDECL PairComparerByValueInt(const void* lhs, const void* rhs)\n{\n    int lhs_v = ((const ImGuiStoragePair*)lhs)->val_i;\n    int rhs_v = ((const ImGuiStoragePair*)rhs)->val_i;\n    return (lhs_v > rhs_v ? +1 : lhs_v < rhs_v ? -1 : 0);\n}\n\n// GetNextSelectedItem() is an abstraction allowing us to change our underlying actual storage system without impacting user.\n// (e.g. store unselected vs compact down, compact down on demand, use raw ImVector<ImGuiID> instead of ImGuiStorage...)\nbool ImGuiSelectionBasicStorage::GetNextSelectedItem(void** opaque_it, ImGuiID* out_id)\n{\n    ImGuiStoragePair* it = (ImGuiStoragePair*)*opaque_it;\n    ImGuiStoragePair* it_end = _Storage.Data.Data + _Storage.Data.Size;\n    if (PreserveOrder && it == NULL && it_end != NULL)\n        ImQsort(_Storage.Data.Data, (size_t)_Storage.Data.Size, sizeof(ImGuiStoragePair), PairComparerByValueInt); // ~ImGuiStorage::BuildSortByValueInt()\n    if (it == NULL)\n        it = _Storage.Data.Data;\n    IM_ASSERT(it >= _Storage.Data.Data && it <= it_end);\n    if (it != it_end)\n        while (it->val_i == 0 && it < it_end)\n            it++;\n    const bool has_more = (it != it_end);\n    *opaque_it = has_more ? (void**)(it + 1) : (void**)(it);\n    *out_id = has_more ? it->key : 0;\n    if (PreserveOrder && !has_more)\n        _Storage.BuildSortByKey();\n    return has_more;\n}\n\nvoid ImGuiSelectionBasicStorage::SetItemSelected(ImGuiID id, bool selected)\n{\n    int* p_int = _Storage.GetIntRef(id, 0);\n    if (selected && *p_int == 0) { *p_int = _SelectionOrder++; Size++; }\n    else if (!selected && *p_int != 0) { *p_int = 0; Size--; }\n}\n\n// Optimized for batch edits (with same value of 'selected')\nstatic void ImGuiSelectionBasicStorage_BatchSetItemSelected(ImGuiSelectionBasicStorage* selection, ImGuiID id, bool selected, int size_before_amends, int selection_order)\n{\n    ImGuiStorage* storage = &selection->_Storage;\n    ImGuiStoragePair* it = ImLowerBound(storage->Data.Data, storage->Data.Data + size_before_amends, id);\n    const bool is_contained = (it != storage->Data.Data + size_before_amends) && (it->key == id);\n    if (selected == (is_contained && it->val_i != 0))\n        return;\n    if (selected && !is_contained)\n        storage->Data.push_back(ImGuiStoragePair(id, selection_order)); // Push unsorted at end of vector, will be sorted in SelectionMultiAmendsFinish()\n    else if (is_contained)\n        it->val_i = selected ? selection_order : 0; // Modify in-place.\n    selection->Size += selected ? +1 : -1;\n}\n\nstatic void ImGuiSelectionBasicStorage_BatchFinish(ImGuiSelectionBasicStorage* selection, bool selected, int size_before_amends)\n{\n    ImGuiStorage* storage = &selection->_Storage;\n    if (selected && selection->Size != size_before_amends)\n        storage->BuildSortByKey(); // When done selecting: sort everything\n}\n\n// Apply requests coming from BeginMultiSelect() and EndMultiSelect().\n// - Enable 'Demo->Tools->Debug Log->Selection' to see selection requests as they happen.\n// - Honoring SetRange requests requires that you can iterate/interpolate between RangeFirstItem and RangeLastItem.\n//   - In this demo we often submit indices to SetNextItemSelectionUserData() + store the same indices in persistent selection.\n//   - Your code may do differently. If you store pointers or objects ID in ImGuiSelectionUserData you may need to perform\n//     a lookup in order to have some way to iterate/interpolate between two items.\n// - A full-featured application is likely to allow search/filtering which is likely to lead to using indices\n//   and constructing a view index <> object id/ptr data structure anyway.\n// WHEN YOUR APPLICATION SETTLES ON A CHOICE, YOU WILL PROBABLY PREFER TO GET RID OF THIS UNNECESSARY 'ImGuiSelectionBasicStorage' INDIRECTION LOGIC.\n// Notice that with the simplest adapter (using indices everywhere), all functions return their parameters.\n// The most simple implementation (using indices everywhere) would look like:\n//   for (ImGuiSelectionRequest& req : ms_io->Requests)\n//   {\n//      if (req.Type == ImGuiSelectionRequestType_SetAll)    { Clear(); if (req.Selected) { for (int n = 0; n < items_count; n++) { SetItemSelected(n, true); } }\n//      if (req.Type == ImGuiSelectionRequestType_SetRange)  { for (int n = (int)ms_io->RangeFirstItem; n <= (int)ms_io->RangeLastItem; n++) { SetItemSelected(n, ms_io->Selected); } }\n//   }\nvoid ImGuiSelectionBasicStorage::ApplyRequests(ImGuiMultiSelectIO* ms_io)\n{\n    // For convenience we obtain ItemsCount as passed to BeginMultiSelect(), which is optional.\n    // It makes sense when using ImGuiSelectionBasicStorage to simply pass your items count to BeginMultiSelect().\n    // Other scheme may handle SetAll differently.\n    IM_ASSERT(ms_io->ItemsCount != -1 && \"Missing value for items_count in BeginMultiSelect() call!\");\n    IM_ASSERT(AdapterIndexToStorageId != NULL);\n\n    // This is optimized/specialized to cope with very large selections (e.g. 100k+ items)\n    // - A simpler version could call SetItemSelected() directly instead of ImGuiSelectionBasicStorage_BatchSetItemSelected() + ImGuiSelectionBasicStorage_BatchFinish().\n    // - Optimized select can append unsorted, then sort in a second pass. Optimized unselect can clear in-place then compact in a second pass.\n    // - A more optimal version wouldn't even use ImGuiStorage but directly a ImVector<ImGuiID> to reduce bandwidth, but this is a reasonable trade off to reuse code.\n    // - There are many ways this could be better optimized. The worse case scenario being: using BoxSelect2d in a grid, box-select scrolling down while wiggling\n    //   left and right: it affects coarse clipping + can emit multiple SetRange with 1 item each.)\n    // FIXME-OPT: For each block of consecutive SetRange request:\n    // - add all requests to a sorted list, store ID, selected, offset in ImGuiStorage.\n    // - rewrite sorted storage a single time.\n    for (ImGuiSelectionRequest& req : ms_io->Requests)\n    {\n        if (req.Type == ImGuiSelectionRequestType_SetAll)\n        {\n            Clear();\n            if (req.Selected)\n            {\n                _Storage.Data.reserve(ms_io->ItemsCount);\n                const int size_before_amends = _Storage.Data.Size;\n                for (int idx = 0; idx < ms_io->ItemsCount; idx++, _SelectionOrder++)\n                    ImGuiSelectionBasicStorage_BatchSetItemSelected(this, GetStorageIdFromIndex(idx), req.Selected, size_before_amends, _SelectionOrder);\n                ImGuiSelectionBasicStorage_BatchFinish(this, req.Selected, size_before_amends);\n            }\n        }\n        else if (req.Type == ImGuiSelectionRequestType_SetRange)\n        {\n            const int selection_changes = (int)req.RangeLastItem - (int)req.RangeFirstItem + 1;\n            //ImGuiContext& g = *GImGui; IMGUI_DEBUG_LOG_SELECTION(\"Req %d/%d: set %d to %d\\n\", ms_io->Requests.index_from_ptr(&req), ms_io->Requests.Size, selection_changes, req.Selected);\n            if (selection_changes == 1 || (selection_changes < Size / 100))\n            {\n                // Multiple sorted insertion + copy likely to be faster.\n                // Technically we could do a single copy with a little more work (sort sequential SetRange requests)\n                for (int idx = (int)req.RangeFirstItem; idx <= (int)req.RangeLastItem; idx++)\n                    SetItemSelected(GetStorageIdFromIndex(idx), req.Selected);\n            }\n            else\n            {\n                // Append insertion + single sort likely be faster.\n                // Use req.RangeDirection to set order field so that shift+clicking from 1 to 5 is different than shift+clicking from 5 to 1\n                const int size_before_amends = _Storage.Data.Size;\n                int selection_order = _SelectionOrder + ((req.RangeDirection < 0) ? selection_changes - 1 : 0);\n                for (int idx = (int)req.RangeFirstItem; idx <= (int)req.RangeLastItem; idx++, selection_order += req.RangeDirection)\n                    ImGuiSelectionBasicStorage_BatchSetItemSelected(this, GetStorageIdFromIndex(idx), req.Selected, size_before_amends, selection_order);\n                if (req.Selected)\n                    _SelectionOrder += selection_changes;\n                ImGuiSelectionBasicStorage_BatchFinish(this, req.Selected, size_before_amends);\n            }\n        }\n    }\n}\n\n//-------------------------------------------------------------------------\n\nImGuiSelectionExternalStorage::ImGuiSelectionExternalStorage()\n{\n    UserData = NULL;\n    AdapterSetItemSelected = NULL;\n}\n\n// Apply requests coming from BeginMultiSelect() and EndMultiSelect().\n// We also pull 'ms_io->ItemsCount' as passed for BeginMultiSelect() for consistency with ImGuiSelectionBasicStorage\n// This makes no assumption about underlying storage.\nvoid ImGuiSelectionExternalStorage::ApplyRequests(ImGuiMultiSelectIO* ms_io)\n{\n    IM_ASSERT(AdapterSetItemSelected);\n    for (ImGuiSelectionRequest& req : ms_io->Requests)\n    {\n        if (req.Type == ImGuiSelectionRequestType_SetAll)\n            for (int idx = 0; idx < ms_io->ItemsCount; idx++)\n                AdapterSetItemSelected(this, idx, req.Selected);\n        if (req.Type == ImGuiSelectionRequestType_SetRange)\n            for (int idx = (int)req.RangeFirstItem; idx <= (int)req.RangeLastItem; idx++)\n                AdapterSetItemSelected(this, idx, req.Selected);\n    }\n}\n\n//-------------------------------------------------------------------------\n// [SECTION] Widgets: ListBox\n//-------------------------------------------------------------------------\n// - BeginListBox()\n// - EndListBox()\n// - ListBox()\n//-------------------------------------------------------------------------\n\n// This is essentially a thin wrapper to using BeginChild/EndChild with the ImGuiChildFlags_FrameStyle flag for stylistic changes + displaying a label.\n// This handle some subtleties with capturing info from the label.\n// If you don't need a label you can pretty much directly use ImGui::BeginChild() with ImGuiChildFlags_FrameStyle.\n// Tip: To have a list filling the entire window width, use size.x = -FLT_MIN and pass an non-visible label e.g. \"##empty\"\n// Tip: If your vertical size is calculated from an item count (e.g. 10 * item_height) consider adding a fractional part to facilitate seeing scrolling boundaries (e.g. 10.5f * item_height).\nbool ImGui::BeginListBox(const char* label, const ImVec2& size_arg)\n{\n    ImGuiContext& g = *GImGui;\n    ImGuiWindow* window = GetCurrentWindow();\n    if (window->SkipItems)\n        return false;\n\n    const ImGuiStyle& style = g.Style;\n    const ImGuiID id = GetID(label);\n    const ImVec2 label_size = CalcTextSize(label, NULL, true);\n\n    // Size default to hold ~7.25 items.\n    // Fractional number of items helps seeing that we can scroll down/up without looking at scrollbar.\n    ImVec2 size = ImTrunc(CalcItemSize(size_arg, CalcItemWidth(), GetTextLineHeightWithSpacing() * 7.25f + style.FramePadding.y * 2.0f));\n    ImVec2 frame_size = ImVec2(size.x, ImMax(size.y, label_size.y));\n    ImRect frame_bb(window->DC.CursorPos, window->DC.CursorPos + frame_size);\n    ImRect bb(frame_bb.Min, frame_bb.Max + ImVec2(label_size.x > 0.0f ? style.ItemInnerSpacing.x + label_size.x : 0.0f, 0.0f));\n    g.NextItemData.ClearFlags();\n\n    if (!IsRectVisible(bb.Min, bb.Max))\n    {\n        ItemSize(bb.GetSize(), style.FramePadding.y);\n        ItemAdd(bb, 0, &frame_bb);\n        g.NextWindowData.ClearFlags(); // We behave like Begin() and need to consume those values\n        return false;\n    }\n\n    // FIXME-OPT: We could omit the BeginGroup() if label_size.x == 0.0f but would need to omit the EndGroup() as well.\n    BeginGroup();\n    if (label_size.x > 0.0f)\n    {\n        ImVec2 label_pos = ImVec2(frame_bb.Max.x + style.ItemInnerSpacing.x, frame_bb.Min.y + style.FramePadding.y);\n        RenderText(label_pos, label);\n        window->DC.CursorMaxPos = ImMax(window->DC.CursorMaxPos, label_pos + label_size);\n        AlignTextToFramePadding();\n    }\n\n    BeginChild(id, frame_bb.GetSize(), ImGuiChildFlags_FrameStyle);\n    return true;\n}\n\nvoid ImGui::EndListBox()\n{\n    ImGuiContext& g = *GImGui;\n    ImGuiWindow* window = g.CurrentWindow;\n    IM_ASSERT((window->Flags & ImGuiWindowFlags_ChildWindow) && \"Mismatched BeginListBox/EndListBox calls. Did you test the return value of BeginListBox?\");\n    IM_UNUSED(window);\n\n    EndChild();\n    EndGroup(); // This is only required to be able to do IsItemXXX query on the whole ListBox including label\n}\n\nbool ImGui::ListBox(const char* label, int* current_item, const char* const items[], int items_count, int height_items)\n{\n    const bool value_changed = ListBox(label, current_item, Items_ArrayGetter, (void*)items, items_count, height_items);\n    return value_changed;\n}\n\n// This is merely a helper around BeginListBox(), EndListBox().\n// Considering using those directly to submit custom data or store selection differently.\nbool ImGui::ListBox(const char* label, int* current_item, const char* (*getter)(void* user_data, int idx), void* user_data, int items_count, int height_in_items)\n{\n    ImGuiContext& g = *GImGui;\n\n    // Calculate size from \"height_in_items\"\n    if (height_in_items < 0)\n        height_in_items = ImMin(items_count, 7);\n    float height_in_items_f = height_in_items + 0.25f;\n    ImVec2 size(0.0f, ImTrunc(GetTextLineHeightWithSpacing() * height_in_items_f + g.Style.FramePadding.y * 2.0f));\n\n    if (!BeginListBox(label, size))\n        return false;\n\n    // Assume all items have even height (= 1 line of text). If you need items of different height,\n    // you can create a custom version of ListBox() in your code without using the clipper.\n    bool value_changed = false;\n    ImGuiListClipper clipper;\n    clipper.Begin(items_count, GetTextLineHeightWithSpacing()); // We know exactly our line height here so we pass it as a minor optimization, but generally you don't need to.\n    clipper.IncludeItemByIndex(*current_item);\n    while (clipper.Step())\n        for (int i = clipper.DisplayStart; i < clipper.DisplayEnd; i++)\n        {\n            const char* item_text = getter(user_data, i);\n            if (item_text == NULL)\n                item_text = \"*Unknown item*\";\n\n            PushID(i);\n            const bool item_selected = (i == *current_item);\n            if (Selectable(item_text, item_selected))\n            {\n                *current_item = i;\n                value_changed = true;\n            }\n            if (item_selected)\n                SetItemDefaultFocus();\n            PopID();\n        }\n    EndListBox();\n\n    if (value_changed)\n        MarkItemEdited(g.LastItemData.ID);\n\n    return value_changed;\n}\n\n//-------------------------------------------------------------------------\n// [SECTION] Widgets: PlotLines, PlotHistogram\n//-------------------------------------------------------------------------\n// - PlotEx() [Internal]\n// - PlotLines()\n// - PlotHistogram()\n//-------------------------------------------------------------------------\n// Plot/Graph widgets are not very good.\n// Consider writing your own, or using a third-party one, see:\n// - ImPlot https://github.com/epezent/implot\n// - others https://github.com/ocornut/imgui/wiki/Useful-Extensions\n//-------------------------------------------------------------------------\n\nint ImGui::PlotEx(ImGuiPlotType plot_type, const char* label, float (*values_getter)(void* data, int idx), void* data, int values_count, int values_offset, const char* overlay_text, float scale_min, float scale_max, const ImVec2& size_arg)\n{\n    ImGuiContext& g = *GImGui;\n    ImGuiWindow* window = GetCurrentWindow();\n    if (window->SkipItems)\n        return -1;\n\n    const ImGuiStyle& style = g.Style;\n    const ImGuiID id = window->GetID(label);\n\n    const ImVec2 label_size = CalcTextSize(label, NULL, true);\n    const ImVec2 frame_size = CalcItemSize(size_arg, CalcItemWidth(), label_size.y + style.FramePadding.y * 2.0f);\n\n    const ImRect frame_bb(window->DC.CursorPos, window->DC.CursorPos + frame_size);\n    const ImRect inner_bb(frame_bb.Min + style.FramePadding, frame_bb.Max - style.FramePadding);\n    const ImRect total_bb(frame_bb.Min, frame_bb.Max + ImVec2(label_size.x > 0.0f ? style.ItemInnerSpacing.x + label_size.x : 0.0f, 0));\n    ItemSize(total_bb, style.FramePadding.y);\n    if (!ItemAdd(total_bb, id, &frame_bb, ImGuiItemFlags_NoNav))\n        return -1;\n    bool hovered;\n    ButtonBehavior(frame_bb, id, &hovered, NULL);\n\n    // Determine scale from values if not specified\n    if (scale_min == FLT_MAX || scale_max == FLT_MAX)\n    {\n        float v_min = FLT_MAX;\n        float v_max = -FLT_MAX;\n        for (int i = 0; i < values_count; i++)\n        {\n            const float v = values_getter(data, i);\n            if (v != v) // Ignore NaN values\n                continue;\n            v_min = ImMin(v_min, v);\n            v_max = ImMax(v_max, v);\n        }\n        if (scale_min == FLT_MAX)\n            scale_min = v_min;\n        if (scale_max == FLT_MAX)\n            scale_max = v_max;\n    }\n\n    RenderFrame(frame_bb.Min, frame_bb.Max, GetColorU32(ImGuiCol_FrameBg), true, style.FrameRounding);\n\n    const int values_count_min = (plot_type == ImGuiPlotType_Lines) ? 2 : 1;\n    int idx_hovered = -1;\n    if (values_count >= values_count_min)\n    {\n        int res_w = ImMin((int)frame_size.x, values_count) + ((plot_type == ImGuiPlotType_Lines) ? -1 : 0);\n        int item_count = values_count + ((plot_type == ImGuiPlotType_Lines) ? -1 : 0);\n\n        // Tooltip on hover\n        if (hovered && inner_bb.Contains(g.IO.MousePos))\n        {\n            const float t = ImClamp((g.IO.MousePos.x - inner_bb.Min.x) / (inner_bb.Max.x - inner_bb.Min.x), 0.0f, 0.9999f);\n            const int v_idx = (int)(t * item_count);\n            IM_ASSERT(v_idx >= 0 && v_idx < values_count);\n\n            const float v0 = values_getter(data, (v_idx + values_offset) % values_count);\n            const float v1 = values_getter(data, (v_idx + 1 + values_offset) % values_count);\n            if (plot_type == ImGuiPlotType_Lines)\n                SetTooltip(\"%d: %8.4g\\n%d: %8.4g\", v_idx, v0, v_idx + 1, v1);\n            else if (plot_type == ImGuiPlotType_Histogram)\n                SetTooltip(\"%d: %8.4g\", v_idx, v0);\n            idx_hovered = v_idx;\n        }\n\n        const float t_step = 1.0f / (float)res_w;\n        const float inv_scale = (scale_min == scale_max) ? 0.0f : (1.0f / (scale_max - scale_min));\n\n        float v0 = values_getter(data, (0 + values_offset) % values_count);\n        float t0 = 0.0f;\n        ImVec2 tp0 = ImVec2( t0, 1.0f - ImSaturate((v0 - scale_min) * inv_scale) );                       // Point in the normalized space of our target rectangle\n        float histogram_zero_line_t = (scale_min * scale_max < 0.0f) ? (1 + scale_min * inv_scale) : (scale_min < 0.0f ? 0.0f : 1.0f);   // Where does the zero line stands\n\n        const ImU32 col_base = GetColorU32((plot_type == ImGuiPlotType_Lines) ? ImGuiCol_PlotLines : ImGuiCol_PlotHistogram);\n        const ImU32 col_hovered = GetColorU32((plot_type == ImGuiPlotType_Lines) ? ImGuiCol_PlotLinesHovered : ImGuiCol_PlotHistogramHovered);\n\n        for (int n = 0; n < res_w; n++)\n        {\n            const float t1 = t0 + t_step;\n            const int v1_idx = (int)(t0 * item_count + 0.5f);\n            IM_ASSERT(v1_idx >= 0 && v1_idx < values_count);\n            const float v1 = values_getter(data, (v1_idx + values_offset + 1) % values_count);\n            const ImVec2 tp1 = ImVec2( t1, 1.0f - ImSaturate((v1 - scale_min) * inv_scale) );\n\n            // NB: Draw calls are merged together by the DrawList system. Still, we should render our batch are lower level to save a bit of CPU.\n            ImVec2 pos0 = ImLerp(inner_bb.Min, inner_bb.Max, tp0);\n            ImVec2 pos1 = ImLerp(inner_bb.Min, inner_bb.Max, (plot_type == ImGuiPlotType_Lines) ? tp1 : ImVec2(tp1.x, histogram_zero_line_t));\n            if (plot_type == ImGuiPlotType_Lines)\n            {\n                window->DrawList->AddLine(pos0, pos1, idx_hovered == v1_idx ? col_hovered : col_base);\n            }\n            else if (plot_type == ImGuiPlotType_Histogram)\n            {\n                if (pos1.x >= pos0.x + 2.0f)\n                    pos1.x -= 1.0f;\n                window->DrawList->AddRectFilled(pos0, pos1, idx_hovered == v1_idx ? col_hovered : col_base);\n            }\n\n            t0 = t1;\n            tp0 = tp1;\n        }\n    }\n\n    // Text overlay\n    if (overlay_text)\n        RenderTextClipped(ImVec2(frame_bb.Min.x, frame_bb.Min.y + style.FramePadding.y), frame_bb.Max, overlay_text, NULL, NULL, ImVec2(0.5f, 0.0f));\n\n    if (label_size.x > 0.0f)\n        RenderText(ImVec2(frame_bb.Max.x + style.ItemInnerSpacing.x, inner_bb.Min.y), label);\n\n    // Return hovered index or -1 if none are hovered.\n    // This is currently not exposed in the public API because we need a larger redesign of the whole thing, but in the short-term we are making it available in PlotEx().\n    return idx_hovered;\n}\n\nstruct ImGuiPlotArrayGetterData\n{\n    const float* Values;\n    int Stride;\n\n    ImGuiPlotArrayGetterData(const float* values, int stride) { Values = values; Stride = stride; }\n};\n\nstatic float Plot_ArrayGetter(void* data, int idx)\n{\n    ImGuiPlotArrayGetterData* plot_data = (ImGuiPlotArrayGetterData*)data;\n    const float v = *(const float*)(const void*)((const unsigned char*)plot_data->Values + (size_t)idx * plot_data->Stride);\n    return v;\n}\n\nvoid ImGui::PlotLines(const char* label, const float* values, int values_count, int values_offset, const char* overlay_text, float scale_min, float scale_max, ImVec2 graph_size, int stride)\n{\n    ImGuiPlotArrayGetterData data(values, stride);\n    PlotEx(ImGuiPlotType_Lines, label, &Plot_ArrayGetter, (void*)&data, values_count, values_offset, overlay_text, scale_min, scale_max, graph_size);\n}\n\nvoid ImGui::PlotLines(const char* label, float (*values_getter)(void* data, int idx), void* data, int values_count, int values_offset, const char* overlay_text, float scale_min, float scale_max, ImVec2 graph_size)\n{\n    PlotEx(ImGuiPlotType_Lines, label, values_getter, data, values_count, values_offset, overlay_text, scale_min, scale_max, graph_size);\n}\n\nvoid ImGui::PlotHistogram(const char* label, const float* values, int values_count, int values_offset, const char* overlay_text, float scale_min, float scale_max, ImVec2 graph_size, int stride)\n{\n    ImGuiPlotArrayGetterData data(values, stride);\n    PlotEx(ImGuiPlotType_Histogram, label, &Plot_ArrayGetter, (void*)&data, values_count, values_offset, overlay_text, scale_min, scale_max, graph_size);\n}\n\nvoid ImGui::PlotHistogram(const char* label, float (*values_getter)(void* data, int idx), void* data, int values_count, int values_offset, const char* overlay_text, float scale_min, float scale_max, ImVec2 graph_size)\n{\n    PlotEx(ImGuiPlotType_Histogram, label, values_getter, data, values_count, values_offset, overlay_text, scale_min, scale_max, graph_size);\n}\n\n//-------------------------------------------------------------------------\n// [SECTION] Widgets: Value helpers\n// Those is not very useful, legacy API.\n//-------------------------------------------------------------------------\n// - Value()\n//-------------------------------------------------------------------------\n\nvoid ImGui::Value(const char* prefix, bool b)\n{\n    Text(\"%s: %s\", prefix, (b ? \"true\" : \"false\"));\n}\n\nvoid ImGui::Value(const char* prefix, int v)\n{\n    Text(\"%s: %d\", prefix, v);\n}\n\nvoid ImGui::Value(const char* prefix, unsigned int v)\n{\n    Text(\"%s: %d\", prefix, v);\n}\n\nvoid ImGui::Value(const char* prefix, float v, const char* float_format)\n{\n    if (float_format)\n    {\n        char fmt[64];\n        ImFormatString(fmt, IM_ARRAYSIZE(fmt), \"%%s: %s\", float_format);\n        Text(fmt, prefix, v);\n    }\n    else\n    {\n        Text(\"%s: %.3f\", prefix, v);\n    }\n}\n\n//-------------------------------------------------------------------------\n// [SECTION] MenuItem, BeginMenu, EndMenu, etc.\n//-------------------------------------------------------------------------\n// - ImGuiMenuColumns [Internal]\n// - BeginMenuBar()\n// - EndMenuBar()\n// - BeginMainMenuBar()\n// - EndMainMenuBar()\n// - BeginMenu()\n// - EndMenu()\n// - MenuItemEx() [Internal]\n// - MenuItem()\n//-------------------------------------------------------------------------\n\n// Helpers for internal use\nvoid ImGuiMenuColumns::Update(float spacing, bool window_reappearing)\n{\n    if (window_reappearing)\n        memset(Widths, 0, sizeof(Widths));\n    Spacing = (ImU16)spacing;\n    CalcNextTotalWidth(true);\n    memset(Widths, 0, sizeof(Widths));\n    TotalWidth = NextTotalWidth;\n    NextTotalWidth = 0;\n}\n\nvoid ImGuiMenuColumns::CalcNextTotalWidth(bool update_offsets)\n{\n    ImU16 offset = 0;\n    bool want_spacing = false;\n    for (int i = 0; i < IM_ARRAYSIZE(Widths); i++)\n    {\n        ImU16 width = Widths[i];\n        if (want_spacing && width > 0)\n            offset += Spacing;\n        want_spacing |= (width > 0);\n        if (update_offsets)\n        {\n            if (i == 1) { OffsetLabel = offset; }\n            if (i == 2) { OffsetShortcut = offset; }\n            if (i == 3) { OffsetMark = offset; }\n        }\n        offset += width;\n    }\n    NextTotalWidth = offset;\n}\n\nfloat ImGuiMenuColumns::DeclColumns(float w_icon, float w_label, float w_shortcut, float w_mark)\n{\n    Widths[0] = ImMax(Widths[0], (ImU16)w_icon);\n    Widths[1] = ImMax(Widths[1], (ImU16)w_label);\n    Widths[2] = ImMax(Widths[2], (ImU16)w_shortcut);\n    Widths[3] = ImMax(Widths[3], (ImU16)w_mark);\n    CalcNextTotalWidth(false);\n    return (float)ImMax(TotalWidth, NextTotalWidth);\n}\n\n// FIXME: Provided a rectangle perhaps e.g. a BeginMenuBarEx() could be used anywhere..\n// Currently the main responsibility of this function being to setup clip-rect + horizontal layout + menu navigation layer.\n// Ideally we also want this to be responsible for claiming space out of the main window scrolling rectangle, in which case ImGuiWindowFlags_MenuBar will become unnecessary.\n// Then later the same system could be used for multiple menu-bars, scrollbars, side-bars.\nbool ImGui::BeginMenuBar()\n{\n    ImGuiWindow* window = GetCurrentWindow();\n    if (window->SkipItems)\n        return false;\n    if (!(window->Flags & ImGuiWindowFlags_MenuBar))\n        return false;\n\n    IM_ASSERT(!window->DC.MenuBarAppending);\n    BeginGroup(); // Backup position on layer 0 // FIXME: Misleading to use a group for that backup/restore\n    PushID(\"##MenuBar\");\n\n    // We don't clip with current window clipping rectangle as it is already set to the area below. However we clip with window full rect.\n    // We remove 1 worth of rounding to Max.x to that text in long menus and small windows don't tend to display over the lower-right rounded area, which looks particularly glitchy.\n    const float border_top = ImMax(window->WindowBorderSize * 0.5f - window->TitleBarHeight, 0.0f);\n    ImRect bar_rect = window->MenuBarRect();\n    ImRect clip_rect(IM_ROUND(bar_rect.Min.x + window->WindowBorderSize * 0.5f), IM_ROUND(bar_rect.Min.y + border_top), IM_ROUND(ImMax(bar_rect.Min.x, bar_rect.Max.x - ImMax(window->WindowRounding, window->WindowBorderSize * 0.5f))), IM_ROUND(bar_rect.Max.y));\n    clip_rect.ClipWith(window->OuterRectClipped);\n    PushClipRect(clip_rect.Min, clip_rect.Max, false);\n\n    // We overwrite CursorMaxPos because BeginGroup sets it to CursorPos (essentially the .EmitItem hack in EndMenuBar() would need something analogous here, maybe a BeginGroupEx() with flags).\n    window->DC.CursorPos = window->DC.CursorMaxPos = ImVec2(bar_rect.Min.x + window->DC.MenuBarOffset.x, bar_rect.Min.y + window->DC.MenuBarOffset.y);\n    window->DC.LayoutType = ImGuiLayoutType_Horizontal;\n    window->DC.IsSameLine = false;\n    window->DC.NavLayerCurrent = ImGuiNavLayer_Menu;\n    window->DC.MenuBarAppending = true;\n    AlignTextToFramePadding();\n    return true;\n}\n\nvoid ImGui::EndMenuBar()\n{\n    ImGuiWindow* window = GetCurrentWindow();\n    if (window->SkipItems)\n        return;\n    ImGuiContext& g = *GImGui;\n\n    IM_MSVC_WARNING_SUPPRESS(6011); // Static Analysis false positive \"warning C6011: Dereferencing NULL pointer 'window'\"\n    IM_ASSERT(window->Flags & ImGuiWindowFlags_MenuBar);\n    IM_ASSERT(window->DC.MenuBarAppending);\n\n    // Nav: When a move request within one of our child menu failed, capture the request to navigate among our siblings.\n    if (NavMoveRequestButNoResultYet() && (g.NavMoveDir == ImGuiDir_Left || g.NavMoveDir == ImGuiDir_Right) && (g.NavWindow->Flags & ImGuiWindowFlags_ChildMenu))\n    {\n        // Try to find out if the request is for one of our child menu\n        ImGuiWindow* nav_earliest_child = g.NavWindow;\n        while (nav_earliest_child->ParentWindow && (nav_earliest_child->ParentWindow->Flags & ImGuiWindowFlags_ChildMenu))\n            nav_earliest_child = nav_earliest_child->ParentWindow;\n        if (nav_earliest_child->ParentWindow == window && nav_earliest_child->DC.ParentLayoutType == ImGuiLayoutType_Horizontal && (g.NavMoveFlags & ImGuiNavMoveFlags_Forwarded) == 0)\n        {\n            // To do so we claim focus back, restore NavId and then process the movement request for yet another frame.\n            // This involve a one-frame delay which isn't very problematic in this situation. We could remove it by scoring in advance for multiple window (probably not worth bothering)\n            const ImGuiNavLayer layer = ImGuiNavLayer_Menu;\n            IM_ASSERT(window->DC.NavLayersActiveMaskNext & (1 << layer)); // Sanity check (FIXME: Seems unnecessary)\n            FocusWindow(window);\n            SetNavID(window->NavLastIds[layer], layer, 0, window->NavRectRel[layer]);\n            // FIXME-NAV: How to deal with this when not using g.IO.ConfigNavCursorVisibleAuto?\n            if (g.NavCursorVisible)\n            {\n                g.NavCursorVisible = false; // Hide nav cursor for the current frame so we don't see the intermediary selection. Will be set again\n                g.NavCursorHideFrames = 2;\n            }\n            g.NavHighlightItemUnderNav = g.NavMousePosDirty = true;\n            NavMoveRequestForward(g.NavMoveDir, g.NavMoveClipDir, g.NavMoveFlags, g.NavMoveScrollFlags); // Repeat\n        }\n    }\n\n    PopClipRect();\n    PopID();\n    window->DC.MenuBarOffset.x = window->DC.CursorPos.x - window->Pos.x; // Save horizontal position so next append can reuse it. This is kinda equivalent to a per-layer CursorPos.\n\n    // FIXME: Extremely confusing, cleanup by (a) working on WorkRect stack system (b) not using a Group confusingly here.\n    ImGuiGroupData& group_data = g.GroupStack.back();\n    group_data.EmitItem = false;\n    ImVec2 restore_cursor_max_pos = group_data.BackupCursorMaxPos;\n    window->DC.IdealMaxPos.x = ImMax(window->DC.IdealMaxPos.x, window->DC.CursorMaxPos.x - window->Scroll.x); // Convert ideal extents for scrolling layer equivalent.\n    EndGroup(); // Restore position on layer 0 // FIXME: Misleading to use a group for that backup/restore\n    window->DC.LayoutType = ImGuiLayoutType_Vertical;\n    window->DC.IsSameLine = false;\n    window->DC.NavLayerCurrent = ImGuiNavLayer_Main;\n    window->DC.MenuBarAppending = false;\n    window->DC.CursorMaxPos = restore_cursor_max_pos;\n}\n\n// Important: calling order matters!\n// FIXME: Somehow overlapping with docking tech.\n// FIXME: The \"rect-cut\" aspect of this could be formalized into a lower-level helper (rect-cut: https://halt.software/dead-simple-layouts)\nbool ImGui::BeginViewportSideBar(const char* name, ImGuiViewport* viewport_p, ImGuiDir dir, float axis_size, ImGuiWindowFlags window_flags)\n{\n    IM_ASSERT(dir != ImGuiDir_None);\n\n    ImGuiWindow* bar_window = FindWindowByName(name);\n    if (bar_window == NULL || bar_window->BeginCount == 0)\n    {\n        // Calculate and set window size/position\n        ImGuiViewportP* viewport = (ImGuiViewportP*)(void*)(viewport_p ? viewport_p : GetMainViewport());\n        ImRect avail_rect = viewport->GetBuildWorkRect();\n        ImGuiAxis axis = (dir == ImGuiDir_Up || dir == ImGuiDir_Down) ? ImGuiAxis_Y : ImGuiAxis_X;\n        ImVec2 pos = avail_rect.Min;\n        if (dir == ImGuiDir_Right || dir == ImGuiDir_Down)\n            pos[axis] = avail_rect.Max[axis] - axis_size;\n        ImVec2 size = avail_rect.GetSize();\n        size[axis] = axis_size;\n        SetNextWindowPos(pos);\n        SetNextWindowSize(size);\n\n        // Report our size into work area (for next frame) using actual window size\n        if (dir == ImGuiDir_Up || dir == ImGuiDir_Left)\n            viewport->BuildWorkInsetMin[axis] += axis_size;\n        else if (dir == ImGuiDir_Down || dir == ImGuiDir_Right)\n            viewport->BuildWorkInsetMax[axis] += axis_size;\n    }\n\n    window_flags |= ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove;\n    PushStyleVar(ImGuiStyleVar_WindowRounding, 0.0f);\n    PushStyleVar(ImGuiStyleVar_WindowMinSize, ImVec2(0, 0)); // Lift normal size constraint\n    bool is_open = Begin(name, NULL, window_flags);\n    PopStyleVar(2);\n\n    return is_open;\n}\n\nbool ImGui::BeginMainMenuBar()\n{\n    ImGuiContext& g = *GImGui;\n    ImGuiViewportP* viewport = (ImGuiViewportP*)(void*)GetMainViewport();\n\n    // For the main menu bar, which cannot be moved, we honor g.Style.DisplaySafeAreaPadding to ensure text can be visible on a TV set.\n    // FIXME: This could be generalized as an opt-in way to clamp window->DC.CursorStartPos to avoid SafeArea?\n    // FIXME: Consider removing support for safe area down the line... it's messy. Nowadays consoles have support for TV calibration in OS settings.\n    g.NextWindowData.MenuBarOffsetMinVal = ImVec2(g.Style.DisplaySafeAreaPadding.x, ImMax(g.Style.DisplaySafeAreaPadding.y - g.Style.FramePadding.y, 0.0f));\n    ImGuiWindowFlags window_flags = ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_MenuBar;\n    float height = GetFrameHeight();\n    bool is_open = BeginViewportSideBar(\"##MainMenuBar\", viewport, ImGuiDir_Up, height, window_flags);\n    g.NextWindowData.MenuBarOffsetMinVal = ImVec2(0.0f, 0.0f);\n    if (!is_open)\n    {\n        End();\n        return false;\n    }\n\n    // Temporarily disable _NoSavedSettings, in the off-chance that tables or child windows submitted within the menu-bar may want to use settings. (#8356)\n    g.CurrentWindow->Flags &= ~ImGuiWindowFlags_NoSavedSettings;\n    BeginMenuBar();\n    return is_open;\n}\n\nvoid ImGui::EndMainMenuBar()\n{\n    ImGuiContext& g = *GImGui;\n    if (!g.CurrentWindow->DC.MenuBarAppending)\n    {\n        IM_ASSERT_USER_ERROR(0, \"Calling EndMainMenuBar() not from a menu-bar!\"); // Not technically testing that it is the main menu bar\n        return;\n    }\n\n    EndMenuBar();\n    g.CurrentWindow->Flags |= ImGuiWindowFlags_NoSavedSettings; // Restore _NoSavedSettings (#8356)\n\n    // When the user has left the menu layer (typically: closed menus through activation of an item), we restore focus to the previous window\n    // FIXME: With this strategy we won't be able to restore a NULL focus.\n    if (g.CurrentWindow == g.NavWindow && g.NavLayer == ImGuiNavLayer_Main && !g.NavAnyRequest && g.ActiveId == 0)\n        FocusTopMostWindowUnderOne(g.NavWindow, NULL, NULL, ImGuiFocusRequestFlags_UnlessBelowModal | ImGuiFocusRequestFlags_RestoreFocusedChild);\n\n    End();\n}\n\nstatic bool IsRootOfOpenMenuSet()\n{\n    ImGuiContext& g = *GImGui;\n    ImGuiWindow* window = g.CurrentWindow;\n    if ((g.OpenPopupStack.Size <= g.BeginPopupStack.Size) || (window->Flags & ImGuiWindowFlags_ChildMenu))\n        return false;\n\n    // Initially we used 'upper_popup->OpenParentId == window->IDStack.back()' to differentiate multiple menu sets from each others\n    // (e.g. inside menu bar vs loose menu items) based on parent ID.\n    // This would however prevent the use of e.g. PushID() user code submitting menus.\n    // Previously this worked between popup and a first child menu because the first child menu always had the _ChildWindow flag,\n    // making hovering on parent popup possible while first child menu was focused - but this was generally a bug with other side effects.\n    // Instead we don't treat Popup specifically (in order to consistently support menu features in them), maybe the first child menu of a Popup\n    // doesn't have the _ChildWindow flag, and we rely on this IsRootOfOpenMenuSet() check to allow hovering between root window/popup and first child menu.\n    // In the end, lack of ID check made it so we could no longer differentiate between separate menu sets. To compensate for that, we at least check parent window nav layer.\n    // This fixes the most common case of menu opening on hover when moving between window content and menu bar. Multiple different menu sets in same nav layer would still\n    // open on hover, but that should be a lesser problem, because if such menus are close in proximity in window content then it won't feel weird and if they are far apart\n    // it likely won't be a problem anyone runs into.\n    const ImGuiPopupData* upper_popup = &g.OpenPopupStack[g.BeginPopupStack.Size];\n    if (window->DC.NavLayerCurrent != upper_popup->ParentNavLayer)\n        return false;\n    return upper_popup->Window && (upper_popup->Window->Flags & ImGuiWindowFlags_ChildMenu) && ImGui::IsWindowChildOf(upper_popup->Window, window, true);\n}\n\nbool ImGui::BeginMenuEx(const char* label, const char* icon, bool enabled)\n{\n    ImGuiWindow* window = GetCurrentWindow();\n    if (window->SkipItems)\n        return false;\n\n    ImGuiContext& g = *GImGui;\n    const ImGuiStyle& style = g.Style;\n    const ImGuiID id = window->GetID(label);\n    bool menu_is_open = IsPopupOpen(id, ImGuiPopupFlags_None);\n\n    // Sub-menus are ChildWindow so that mouse can be hovering across them (otherwise top-most popup menu would steal focus and not allow hovering on parent menu)\n    // The first menu in a hierarchy isn't so hovering doesn't get across (otherwise e.g. resizing borders with ImGuiButtonFlags_FlattenChildren would react), but top-most BeginMenu() will bypass that limitation.\n    ImGuiWindowFlags window_flags = ImGuiWindowFlags_ChildMenu | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_NoNavFocus;\n    if (window->Flags & ImGuiWindowFlags_ChildMenu)\n        window_flags |= ImGuiWindowFlags_ChildWindow;\n\n    // If a menu with same the ID was already submitted, we will append to it, matching the behavior of Begin().\n    // We are relying on a O(N) search - so O(N log N) over the frame - which seems like the most efficient for the expected small amount of BeginMenu() calls per frame.\n    // If somehow this is ever becoming a problem we can switch to use e.g. ImGuiStorage mapping key to last frame used.\n    if (g.MenusIdSubmittedThisFrame.contains(id))\n    {\n        if (menu_is_open)\n            menu_is_open = BeginPopupMenuEx(id, label, window_flags); // menu_is_open can be 'false' when the popup is completely clipped (e.g. zero size display)\n        else\n            g.NextWindowData.ClearFlags();          // we behave like Begin() and need to consume those values\n        return menu_is_open;\n    }\n\n    // Tag menu as used. Next time BeginMenu() with same ID is called it will append to existing menu\n    g.MenusIdSubmittedThisFrame.push_back(id);\n\n    ImVec2 label_size = CalcTextSize(label, NULL, true);\n\n    // Odd hack to allow hovering across menus of a same menu-set (otherwise we wouldn't be able to hover parent without always being a Child window)\n    // This is only done for items for the menu set and not the full parent window.\n    const bool menuset_is_open = IsRootOfOpenMenuSet();\n    if (menuset_is_open)\n        PushItemFlag(ImGuiItemFlags_NoWindowHoverableCheck, true);\n\n    // The reference position stored in popup_pos will be used by Begin() to find a suitable position for the child menu,\n    // However the final position is going to be different! It is chosen by FindBestWindowPosForPopup().\n    // e.g. Menus tend to overlap each other horizontally to amplify relative Z-ordering.\n    ImVec2 popup_pos, pos = window->DC.CursorPos;\n    PushID(label);\n    if (!enabled)\n        BeginDisabled();\n    const ImGuiMenuColumns* offsets = &window->DC.MenuColumns;\n    bool pressed;\n\n    // We use ImGuiSelectableFlags_NoSetKeyOwner to allow down on one menu item, move, up on another.\n    const ImGuiSelectableFlags selectable_flags = ImGuiSelectableFlags_NoHoldingActiveID | ImGuiSelectableFlags_NoSetKeyOwner | ImGuiSelectableFlags_SelectOnClick | ImGuiSelectableFlags_NoAutoClosePopups;\n    if (window->DC.LayoutType == ImGuiLayoutType_Horizontal)\n    {\n        // Menu inside a horizontal menu bar\n        // Selectable extend their highlight by half ItemSpacing in each direction.\n        // For ChildMenu, the popup position will be overwritten by the call to FindBestWindowPosForPopup() in Begin()\n        popup_pos = ImVec2(pos.x - 1.0f - IM_TRUNC(style.ItemSpacing.x * 0.5f), pos.y - style.FramePadding.y + window->MenuBarHeight);\n        window->DC.CursorPos.x += IM_TRUNC(style.ItemSpacing.x * 0.5f);\n        PushStyleVarX(ImGuiStyleVar_ItemSpacing, style.ItemSpacing.x * 2.0f);\n        float w = label_size.x;\n        ImVec2 text_pos(window->DC.CursorPos.x + offsets->OffsetLabel, window->DC.CursorPos.y + window->DC.CurrLineTextBaseOffset);\n        pressed = Selectable(\"\", menu_is_open, selectable_flags, ImVec2(w, label_size.y));\n        LogSetNextTextDecoration(\"[\", \"]\");\n        RenderText(text_pos, label);\n        PopStyleVar();\n        window->DC.CursorPos.x += IM_TRUNC(style.ItemSpacing.x * (-1.0f + 0.5f)); // -1 spacing to compensate the spacing added when Selectable() did a SameLine(). It would also work to call SameLine() ourselves after the PopStyleVar().\n    }\n    else\n    {\n        // Menu inside a regular/vertical menu\n        // (In a typical menu window where all items are BeginMenu() or MenuItem() calls, extra_w will always be 0.0f.\n        //  Only when they are other items sticking out we're going to add spacing, yet only register minimum width into the layout system.\n        popup_pos = ImVec2(pos.x, pos.y - style.WindowPadding.y);\n        float icon_w = (icon && icon[0]) ? CalcTextSize(icon, NULL).x : 0.0f;\n        float checkmark_w = IM_TRUNC(g.FontSize * 1.20f);\n        float min_w = window->DC.MenuColumns.DeclColumns(icon_w, label_size.x, 0.0f, checkmark_w); // Feedback to next frame\n        float extra_w = ImMax(0.0f, GetContentRegionAvail().x - min_w);\n        ImVec2 text_pos(window->DC.CursorPos.x + offsets->OffsetLabel, window->DC.CursorPos.y + window->DC.CurrLineTextBaseOffset);\n        pressed = Selectable(\"\", menu_is_open, selectable_flags | ImGuiSelectableFlags_SpanAvailWidth, ImVec2(min_w, label_size.y));\n        LogSetNextTextDecoration(\"\", \">\");\n        RenderText(text_pos, label);\n        if (icon_w > 0.0f)\n            RenderText(pos + ImVec2(offsets->OffsetIcon, 0.0f), icon);\n        RenderArrow(window->DrawList, pos + ImVec2(offsets->OffsetMark + extra_w + g.FontSize * 0.30f, 0.0f), GetColorU32(ImGuiCol_Text), ImGuiDir_Right);\n    }\n    if (!enabled)\n        EndDisabled();\n\n    const bool hovered = (g.HoveredId == id) && enabled && !g.NavHighlightItemUnderNav;\n    if (menuset_is_open)\n        PopItemFlag();\n\n    bool want_open = false;\n    bool want_open_nav_init = false;\n    bool want_close = false;\n    if (window->DC.LayoutType == ImGuiLayoutType_Vertical) // (window->Flags & (ImGuiWindowFlags_Popup|ImGuiWindowFlags_ChildMenu))\n    {\n        // Close menu when not hovering it anymore unless we are moving roughly in the direction of the menu\n        // Implement http://bjk5.com/post/44698559168/breaking-down-amazons-mega-dropdown to avoid using timers, so menus feels more reactive.\n        bool moving_toward_child_menu = false;\n        ImGuiPopupData* child_popup = (g.BeginPopupStack.Size < g.OpenPopupStack.Size) ? &g.OpenPopupStack[g.BeginPopupStack.Size] : NULL; // Popup candidate (testing below)\n        ImGuiWindow* child_menu_window = (child_popup && child_popup->Window && child_popup->Window->ParentWindow == window) ? child_popup->Window : NULL;\n        if (g.HoveredWindow == window && child_menu_window != NULL)\n        {\n            const float ref_unit = g.FontSize; // FIXME-DPI\n            const float child_dir = (window->Pos.x < child_menu_window->Pos.x) ? 1.0f : -1.0f;\n            const ImRect next_window_rect = child_menu_window->Rect();\n            ImVec2 ta = (g.IO.MousePos - g.IO.MouseDelta);\n            ImVec2 tb = (child_dir > 0.0f) ? next_window_rect.GetTL() : next_window_rect.GetTR();\n            ImVec2 tc = (child_dir > 0.0f) ? next_window_rect.GetBL() : next_window_rect.GetBR();\n            const float pad_farmost_h = ImClamp(ImFabs(ta.x - tb.x) * 0.30f, ref_unit * 0.5f, ref_unit * 2.5f); // Add a bit of extra slack.\n            ta.x += child_dir * -0.5f;\n            tb.x += child_dir * ref_unit;\n            tc.x += child_dir * ref_unit;\n            tb.y = ta.y + ImMax((tb.y - pad_farmost_h) - ta.y, -ref_unit * 8.0f); // Triangle has maximum height to limit the slope and the bias toward large sub-menus\n            tc.y = ta.y + ImMin((tc.y + pad_farmost_h) - ta.y, +ref_unit * 8.0f);\n            moving_toward_child_menu = ImTriangleContainsPoint(ta, tb, tc, g.IO.MousePos);\n            //GetForegroundDrawList()->AddTriangleFilled(ta, tb, tc, moving_toward_child_menu ? IM_COL32(0,128,0,128) : IM_COL32(128,0,0,128)); // [DEBUG]\n        }\n\n        // The 'HovereWindow == window' check creates an inconsistency (e.g. moving away from menu slowly tends to hit same window, whereas moving away fast does not)\n        // But we also need to not close the top-menu menu when moving over void. Perhaps we should extend the triangle check to a larger polygon.\n        // (Remember to test this on BeginPopup(\"A\")->BeginMenu(\"B\") sequence which behaves slightly differently as B isn't a Child of A and hovering isn't shared.)\n        if (menu_is_open && !hovered && g.HoveredWindow == window && !moving_toward_child_menu && !g.NavHighlightItemUnderNav && g.ActiveId == 0)\n            want_close = true;\n\n        // Open\n        // (note: at this point 'hovered' actually includes the NavDisableMouseHover == false test)\n        if (!menu_is_open && pressed) // Click/activate to open\n            want_open = true;\n        else if (!menu_is_open && hovered && !moving_toward_child_menu) // Hover to open\n            want_open = true;\n        else if (!menu_is_open && hovered && g.HoveredIdTimer >= 0.30f && g.MouseStationaryTimer >= 0.30f) // Hover to open (timer fallback)\n            want_open = true;\n        if (g.NavId == id && g.NavMoveDir == ImGuiDir_Right) // Nav-Right to open\n        {\n            want_open = want_open_nav_init = true;\n            NavMoveRequestCancel();\n            SetNavCursorVisibleAfterMove();\n        }\n    }\n    else\n    {\n        // Menu bar\n        if (menu_is_open && pressed && menuset_is_open) // Click an open menu again to close it\n        {\n            want_close = true;\n            want_open = menu_is_open = false;\n        }\n        else if (pressed || (hovered && menuset_is_open && !menu_is_open)) // First click to open, then hover to open others\n        {\n            want_open = true;\n        }\n        else if (g.NavId == id && g.NavMoveDir == ImGuiDir_Down) // Nav-Down to open\n        {\n            want_open = true;\n            NavMoveRequestCancel();\n        }\n    }\n\n    if (!enabled) // explicitly close if an open menu becomes disabled, facilitate users code a lot in pattern such as 'if (BeginMenu(\"options\", has_object)) { ..use object.. }'\n        want_close = true;\n    if (want_close && IsPopupOpen(id, ImGuiPopupFlags_None))\n        ClosePopupToLevel(g.BeginPopupStack.Size, true);\n\n    IMGUI_TEST_ENGINE_ITEM_INFO(id, label, g.LastItemData.StatusFlags | ImGuiItemStatusFlags_Openable | (menu_is_open ? ImGuiItemStatusFlags_Opened : 0));\n    PopID();\n\n    if (want_open && !menu_is_open && g.OpenPopupStack.Size > g.BeginPopupStack.Size)\n    {\n        // Don't reopen/recycle same menu level in the same frame if it is a different menu ID, first close the other menu and yield for a frame.\n        OpenPopup(label);\n    }\n    else if (want_open)\n    {\n        menu_is_open = true;\n        OpenPopup(label, ImGuiPopupFlags_NoReopen);// | (want_open_nav_init ? ImGuiPopupFlags_NoReopenAlwaysNavInit : 0));\n    }\n\n    if (menu_is_open)\n    {\n        ImGuiLastItemData last_item_in_parent = g.LastItemData;\n        SetNextWindowPos(popup_pos, ImGuiCond_Always);                  // Note: misleading: the value will serve as reference for FindBestWindowPosForPopup(), not actual pos.\n        PushStyleVar(ImGuiStyleVar_ChildRounding, style.PopupRounding); // First level will use _PopupRounding, subsequent will use _ChildRounding\n        menu_is_open = BeginPopupMenuEx(id, label, window_flags); // menu_is_open may be 'false' when the popup is completely clipped (e.g. zero size display)\n        PopStyleVar();\n        if (menu_is_open)\n        {\n            // Implement what ImGuiPopupFlags_NoReopenAlwaysNavInit would do:\n            // Perform an init request in the case the popup was already open (via a previous mouse hover)\n            if (want_open && want_open_nav_init && !g.NavInitRequest)\n            {\n                FocusWindow(g.CurrentWindow, ImGuiFocusRequestFlags_UnlessBelowModal);\n                NavInitWindow(g.CurrentWindow, false);\n            }\n\n            // Restore LastItemData so IsItemXXXX functions can work after BeginMenu()/EndMenu()\n            // (This fixes using IsItemClicked() and IsItemHovered(), but IsItemHovered() also relies on its support for ImGuiItemFlags_NoWindowHoverableCheck)\n            g.LastItemData = last_item_in_parent;\n            if (g.HoveredWindow == window)\n                g.LastItemData.StatusFlags |= ImGuiItemStatusFlags_HoveredWindow;\n        }\n    }\n    else\n    {\n        g.NextWindowData.ClearFlags(); // We behave like Begin() and need to consume those values\n    }\n\n    return menu_is_open;\n}\n\nbool ImGui::BeginMenu(const char* label, bool enabled)\n{\n    return BeginMenuEx(label, NULL, enabled);\n}\n\nvoid ImGui::EndMenu()\n{\n    // Nav: When a left move request our menu failed, close ourselves.\n    ImGuiContext& g = *GImGui;\n    ImGuiWindow* window = g.CurrentWindow;\n    IM_ASSERT(window->Flags & ImGuiWindowFlags_Popup);  // Mismatched BeginMenu()/EndMenu() calls\n    ImGuiWindow* parent_window = window->ParentWindow;  // Should always be != NULL is we passed assert.\n    if (window->BeginCount == window->BeginCountPreviousFrame)\n        if (g.NavMoveDir == ImGuiDir_Left && NavMoveRequestButNoResultYet())\n            if (g.NavWindow && (g.NavWindow->RootWindowForNav == window) && parent_window->DC.LayoutType == ImGuiLayoutType_Vertical)\n            {\n                ClosePopupToLevel(g.BeginPopupStack.Size - 1, true);\n                NavMoveRequestCancel();\n            }\n\n    EndPopup();\n}\n\nbool ImGui::MenuItemEx(const char* label, const char* icon, const char* shortcut, bool selected, bool enabled)\n{\n    ImGuiWindow* window = GetCurrentWindow();\n    if (window->SkipItems)\n        return false;\n\n    ImGuiContext& g = *GImGui;\n    ImGuiStyle& style = g.Style;\n    ImVec2 pos = window->DC.CursorPos;\n    ImVec2 label_size = CalcTextSize(label, NULL, true);\n\n    // See BeginMenuEx() for comments about this.\n    const bool menuset_is_open = IsRootOfOpenMenuSet();\n    if (menuset_is_open)\n        PushItemFlag(ImGuiItemFlags_NoWindowHoverableCheck, true);\n\n    // We've been using the equivalent of ImGuiSelectableFlags_SetNavIdOnHover on all Selectable() since early Nav system days (commit 43ee5d73),\n    // but I am unsure whether this should be kept at all. For now moved it to be an opt-in feature used by menus only.\n    bool pressed;\n    PushID(label);\n    if (!enabled)\n        BeginDisabled();\n\n    // We use ImGuiSelectableFlags_NoSetKeyOwner to allow down on one menu item, move, up on another.\n    const ImGuiSelectableFlags selectable_flags = ImGuiSelectableFlags_SelectOnRelease | ImGuiSelectableFlags_NoSetKeyOwner | ImGuiSelectableFlags_SetNavIdOnHover;\n    const ImGuiMenuColumns* offsets = &window->DC.MenuColumns;\n    if (window->DC.LayoutType == ImGuiLayoutType_Horizontal)\n    {\n        // Mimic the exact layout spacing of BeginMenu() to allow MenuItem() inside a menu bar, which is a little misleading but may be useful\n        // Note that in this situation: we don't render the shortcut, we render a highlight instead of the selected tick mark.\n        float w = label_size.x;\n        window->DC.CursorPos.x += IM_TRUNC(style.ItemSpacing.x * 0.5f);\n        ImVec2 text_pos(window->DC.CursorPos.x + offsets->OffsetLabel, window->DC.CursorPos.y + window->DC.CurrLineTextBaseOffset);\n        PushStyleVarX(ImGuiStyleVar_ItemSpacing, style.ItemSpacing.x * 2.0f);\n        pressed = Selectable(\"\", selected, selectable_flags, ImVec2(w, 0.0f));\n        PopStyleVar();\n        if (g.LastItemData.StatusFlags & ImGuiItemStatusFlags_Visible)\n            RenderText(text_pos, label);\n        window->DC.CursorPos.x += IM_TRUNC(style.ItemSpacing.x * (-1.0f + 0.5f)); // -1 spacing to compensate the spacing added when Selectable() did a SameLine(). It would also work to call SameLine() ourselves after the PopStyleVar().\n    }\n    else\n    {\n        // Menu item inside a vertical menu\n        // (In a typical menu window where all items are BeginMenu() or MenuItem() calls, extra_w will always be 0.0f.\n        //  Only when they are other items sticking out we're going to add spacing, yet only register minimum width into the layout system.\n        float icon_w = (icon && icon[0]) ? CalcTextSize(icon, NULL).x : 0.0f;\n        float shortcut_w = (shortcut && shortcut[0]) ? CalcTextSize(shortcut, NULL).x : 0.0f;\n        float checkmark_w = IM_TRUNC(g.FontSize * 1.20f);\n        float min_w = window->DC.MenuColumns.DeclColumns(icon_w, label_size.x, shortcut_w, checkmark_w); // Feedback for next frame\n        float stretch_w = ImMax(0.0f, GetContentRegionAvail().x - min_w);\n        pressed = Selectable(\"\", false, selectable_flags | ImGuiSelectableFlags_SpanAvailWidth, ImVec2(min_w, label_size.y));\n        if (g.LastItemData.StatusFlags & ImGuiItemStatusFlags_Visible)\n        {\n            RenderText(pos + ImVec2(offsets->OffsetLabel, 0.0f), label);\n            if (icon_w > 0.0f)\n                RenderText(pos + ImVec2(offsets->OffsetIcon, 0.0f), icon);\n            if (shortcut_w > 0.0f)\n            {\n                PushStyleColor(ImGuiCol_Text, style.Colors[ImGuiCol_TextDisabled]);\n                LogSetNextTextDecoration(\"(\", \")\");\n                RenderText(pos + ImVec2(offsets->OffsetShortcut + stretch_w, 0.0f), shortcut, NULL, false);\n                PopStyleColor();\n            }\n            if (selected)\n                RenderCheckMark(window->DrawList, pos + ImVec2(offsets->OffsetMark + stretch_w + g.FontSize * 0.40f, g.FontSize * 0.134f * 0.5f), GetColorU32(ImGuiCol_Text), g.FontSize * 0.866f);\n        }\n    }\n    IMGUI_TEST_ENGINE_ITEM_INFO(g.LastItemData.ID, label, g.LastItemData.StatusFlags | ImGuiItemStatusFlags_Checkable | (selected ? ImGuiItemStatusFlags_Checked : 0));\n    if (!enabled)\n        EndDisabled();\n    PopID();\n    if (menuset_is_open)\n        PopItemFlag();\n\n    return pressed;\n}\n\nbool ImGui::MenuItem(const char* label, const char* shortcut, bool selected, bool enabled)\n{\n    return MenuItemEx(label, NULL, shortcut, selected, enabled);\n}\n\nbool ImGui::MenuItem(const char* label, const char* shortcut, bool* p_selected, bool enabled)\n{\n    if (MenuItemEx(label, NULL, shortcut, p_selected ? *p_selected : false, enabled))\n    {\n        if (p_selected)\n            *p_selected = !*p_selected;\n        return true;\n    }\n    return false;\n}\n\n//-------------------------------------------------------------------------\n// [SECTION] Widgets: BeginTabBar, EndTabBar, etc.\n//-------------------------------------------------------------------------\n// - BeginTabBar()\n// - BeginTabBarEx() [Internal]\n// - EndTabBar()\n// - TabBarLayout() [Internal]\n// - TabBarCalcTabID() [Internal]\n// - TabBarCalcMaxTabWidth() [Internal]\n// - TabBarFindTabById() [Internal]\n// - TabBarFindTabByOrder() [Internal]\n// - TabBarGetCurrentTab() [Internal]\n// - TabBarGetTabName() [Internal]\n// - TabBarRemoveTab() [Internal]\n// - TabBarCloseTab() [Internal]\n// - TabBarScrollClamp() [Internal]\n// - TabBarScrollToTab() [Internal]\n// - TabBarQueueFocus() [Internal]\n// - TabBarQueueReorder() [Internal]\n// - TabBarProcessReorderFromMousePos() [Internal]\n// - TabBarProcessReorder() [Internal]\n// - TabBarScrollingButtons() [Internal]\n// - TabBarTabListPopupButton() [Internal]\n//-------------------------------------------------------------------------\n\nstruct ImGuiTabBarSection\n{\n    int                 TabCount;               // Number of tabs in this section.\n    float               Width;                  // Sum of width of tabs in this section (after shrinking down)\n    float               Spacing;                // Horizontal spacing at the end of the section.\n\n    ImGuiTabBarSection() { memset(this, 0, sizeof(*this)); }\n};\n\nnamespace ImGui\n{\n    static void             TabBarLayout(ImGuiTabBar* tab_bar);\n    static ImU32            TabBarCalcTabID(ImGuiTabBar* tab_bar, const char* label, ImGuiWindow* docked_window);\n    static float            TabBarCalcMaxTabWidth();\n    static float            TabBarScrollClamp(ImGuiTabBar* tab_bar, float scrolling);\n    static void             TabBarScrollToTab(ImGuiTabBar* tab_bar, ImGuiID tab_id, ImGuiTabBarSection* sections);\n    static ImGuiTabItem*    TabBarScrollingButtons(ImGuiTabBar* tab_bar);\n    static ImGuiTabItem*    TabBarTabListPopupButton(ImGuiTabBar* tab_bar);\n}\n\nImGuiTabBar::ImGuiTabBar()\n{\n    memset(this, 0, sizeof(*this));\n    CurrFrameVisible = PrevFrameVisible = -1;\n    LastTabItemIdx = -1;\n}\n\nstatic inline int TabItemGetSectionIdx(const ImGuiTabItem* tab)\n{\n    return (tab->Flags & ImGuiTabItemFlags_Leading) ? 0 : (tab->Flags & ImGuiTabItemFlags_Trailing) ? 2 : 1;\n}\n\nstatic int IMGUI_CDECL TabItemComparerBySection(const void* lhs, const void* rhs)\n{\n    const ImGuiTabItem* a = (const ImGuiTabItem*)lhs;\n    const ImGuiTabItem* b = (const ImGuiTabItem*)rhs;\n    const int a_section = TabItemGetSectionIdx(a);\n    const int b_section = TabItemGetSectionIdx(b);\n    if (a_section != b_section)\n        return a_section - b_section;\n    return (int)(a->IndexDuringLayout - b->IndexDuringLayout);\n}\n\nstatic int IMGUI_CDECL TabItemComparerByBeginOrder(const void* lhs, const void* rhs)\n{\n    const ImGuiTabItem* a = (const ImGuiTabItem*)lhs;\n    const ImGuiTabItem* b = (const ImGuiTabItem*)rhs;\n    return (int)(a->BeginOrder - b->BeginOrder);\n}\n\nstatic ImGuiTabBar* GetTabBarFromTabBarRef(const ImGuiPtrOrIndex& ref)\n{\n    ImGuiContext& g = *GImGui;\n    return ref.Ptr ? (ImGuiTabBar*)ref.Ptr : g.TabBars.GetByIndex(ref.Index);\n}\n\nstatic ImGuiPtrOrIndex GetTabBarRefFromTabBar(ImGuiTabBar* tab_bar)\n{\n    ImGuiContext& g = *GImGui;\n    if (g.TabBars.Contains(tab_bar))\n        return ImGuiPtrOrIndex(g.TabBars.GetIndex(tab_bar));\n    return ImGuiPtrOrIndex(tab_bar);\n}\n\nbool    ImGui::BeginTabBar(const char* str_id, ImGuiTabBarFlags flags)\n{\n    ImGuiContext& g = *GImGui;\n    ImGuiWindow* window = g.CurrentWindow;\n    if (window->SkipItems)\n        return false;\n\n    ImGuiID id = window->GetID(str_id);\n    ImGuiTabBar* tab_bar = g.TabBars.GetOrAddByKey(id);\n    ImRect tab_bar_bb = ImRect(window->DC.CursorPos.x, window->DC.CursorPos.y, window->WorkRect.Max.x, window->DC.CursorPos.y + g.FontSize + g.Style.FramePadding.y * 2);\n    tab_bar->ID = id;\n    tab_bar->SeparatorMinX = tab_bar->BarRect.Min.x - IM_TRUNC(window->WindowPadding.x * 0.5f);\n    tab_bar->SeparatorMaxX = tab_bar->BarRect.Max.x + IM_TRUNC(window->WindowPadding.x * 0.5f);\n    //if (g.NavWindow && IsWindowChildOf(g.NavWindow, window, false, false))\n    flags |= ImGuiTabBarFlags_IsFocused;\n    return BeginTabBarEx(tab_bar, tab_bar_bb, flags);\n}\n\nbool    ImGui::BeginTabBarEx(ImGuiTabBar* tab_bar, const ImRect& tab_bar_bb, ImGuiTabBarFlags flags)\n{\n    ImGuiContext& g = *GImGui;\n    ImGuiWindow* window = g.CurrentWindow;\n    if (window->SkipItems)\n        return false;\n\n    IM_ASSERT(tab_bar->ID != 0);\n    if ((flags & ImGuiTabBarFlags_DockNode) == 0)\n        PushOverrideID(tab_bar->ID);\n\n    // Add to stack\n    g.CurrentTabBarStack.push_back(GetTabBarRefFromTabBar(tab_bar));\n    g.CurrentTabBar = tab_bar;\n    tab_bar->Window = window;\n\n    // Append with multiple BeginTabBar()/EndTabBar() pairs.\n    tab_bar->BackupCursorPos = window->DC.CursorPos;\n    if (tab_bar->CurrFrameVisible == g.FrameCount)\n    {\n        window->DC.CursorPos = ImVec2(tab_bar->BarRect.Min.x, tab_bar->BarRect.Max.y + tab_bar->ItemSpacingY);\n        tab_bar->BeginCount++;\n        return true;\n    }\n\n    // Ensure correct ordering when toggling ImGuiTabBarFlags_Reorderable flag, or when a new tab was added while being not reorderable\n    if ((flags & ImGuiTabBarFlags_Reorderable) != (tab_bar->Flags & ImGuiTabBarFlags_Reorderable) || (tab_bar->TabsAddedNew && !(flags & ImGuiTabBarFlags_Reorderable)))\n        ImQsort(tab_bar->Tabs.Data, tab_bar->Tabs.Size, sizeof(ImGuiTabItem), TabItemComparerByBeginOrder);\n    tab_bar->TabsAddedNew = false;\n\n    // Flags\n    if ((flags & ImGuiTabBarFlags_FittingPolicyMask_) == 0)\n        flags |= ImGuiTabBarFlags_FittingPolicyDefault_;\n\n    tab_bar->Flags = flags;\n    tab_bar->BarRect = tab_bar_bb;\n    tab_bar->WantLayout = true; // Layout will be done on the first call to ItemTab()\n    tab_bar->PrevFrameVisible = tab_bar->CurrFrameVisible;\n    tab_bar->CurrFrameVisible = g.FrameCount;\n    tab_bar->PrevTabsContentsHeight = tab_bar->CurrTabsContentsHeight;\n    tab_bar->CurrTabsContentsHeight = 0.0f;\n    tab_bar->ItemSpacingY = g.Style.ItemSpacing.y;\n    tab_bar->FramePadding = g.Style.FramePadding;\n    tab_bar->TabsActiveCount = 0;\n    tab_bar->LastTabItemIdx = -1;\n    tab_bar->BeginCount = 1;\n\n    // Set cursor pos in a way which only be used in the off-chance the user erroneously submits item before BeginTabItem(): items will overlap\n    window->DC.CursorPos = ImVec2(tab_bar->BarRect.Min.x, tab_bar->BarRect.Max.y + tab_bar->ItemSpacingY);\n\n    // Draw separator\n    // (it would be misleading to draw this in EndTabBar() suggesting that it may be drawn over tabs, as tab bar are appendable)\n    const ImU32 col = GetColorU32((flags & ImGuiTabBarFlags_IsFocused) ? ImGuiCol_TabSelected : ImGuiCol_TabDimmedSelected);\n    if (g.Style.TabBarBorderSize > 0.0f)\n    {\n        const float y = tab_bar->BarRect.Max.y;\n        window->DrawList->AddRectFilled(ImVec2(tab_bar->SeparatorMinX, y - g.Style.TabBarBorderSize), ImVec2(tab_bar->SeparatorMaxX, y), col);\n    }\n    return true;\n}\n\nvoid    ImGui::EndTabBar()\n{\n    ImGuiContext& g = *GImGui;\n    ImGuiWindow* window = g.CurrentWindow;\n    if (window->SkipItems)\n        return;\n\n    ImGuiTabBar* tab_bar = g.CurrentTabBar;\n    if (tab_bar == NULL)\n    {\n        IM_ASSERT_USER_ERROR(tab_bar != NULL, \"Mismatched BeginTabBar()/EndTabBar()!\");\n        return;\n    }\n\n    // Fallback in case no TabItem have been submitted\n    if (tab_bar->WantLayout)\n        TabBarLayout(tab_bar);\n\n    // Restore the last visible height if no tab is visible, this reduce vertical flicker/movement when a tabs gets removed without calling SetTabItemClosed().\n    const bool tab_bar_appearing = (tab_bar->PrevFrameVisible + 1 < g.FrameCount);\n    if (tab_bar->VisibleTabWasSubmitted || tab_bar->VisibleTabId == 0 || tab_bar_appearing)\n    {\n        tab_bar->CurrTabsContentsHeight = ImMax(window->DC.CursorPos.y - tab_bar->BarRect.Max.y, tab_bar->CurrTabsContentsHeight);\n        window->DC.CursorPos.y = tab_bar->BarRect.Max.y + tab_bar->CurrTabsContentsHeight;\n    }\n    else\n    {\n        window->DC.CursorPos.y = tab_bar->BarRect.Max.y + tab_bar->PrevTabsContentsHeight;\n    }\n    if (tab_bar->BeginCount > 1)\n        window->DC.CursorPos = tab_bar->BackupCursorPos;\n\n    tab_bar->LastTabItemIdx = -1;\n    if ((tab_bar->Flags & ImGuiTabBarFlags_DockNode) == 0)\n        PopID();\n\n    g.CurrentTabBarStack.pop_back();\n    g.CurrentTabBar = g.CurrentTabBarStack.empty() ? NULL : GetTabBarFromTabBarRef(g.CurrentTabBarStack.back());\n}\n\n// Scrolling happens only in the central section (leading/trailing sections are not scrolling)\nstatic float TabBarCalcScrollableWidth(ImGuiTabBar* tab_bar, ImGuiTabBarSection* sections)\n{\n    return tab_bar->BarRect.GetWidth() - sections[0].Width - sections[2].Width - sections[1].Spacing;\n}\n\n// This is called only once a frame before by the first call to ItemTab()\n// The reason we're not calling it in BeginTabBar() is to leave a chance to the user to call the SetTabItemClosed() functions.\nstatic void ImGui::TabBarLayout(ImGuiTabBar* tab_bar)\n{\n    ImGuiContext& g = *GImGui;\n    tab_bar->WantLayout = false;\n\n    // Garbage collect by compacting list\n    // Detect if we need to sort out tab list (e.g. in rare case where a tab changed section)\n    int tab_dst_n = 0;\n    bool need_sort_by_section = false;\n    ImGuiTabBarSection sections[3]; // Layout sections: Leading, Central, Trailing\n    for (int tab_src_n = 0; tab_src_n < tab_bar->Tabs.Size; tab_src_n++)\n    {\n        ImGuiTabItem* tab = &tab_bar->Tabs[tab_src_n];\n        if (tab->LastFrameVisible < tab_bar->PrevFrameVisible || tab->WantClose)\n        {\n            // Remove tab\n            if (tab_bar->VisibleTabId == tab->ID) { tab_bar->VisibleTabId = 0; }\n            if (tab_bar->SelectedTabId == tab->ID) { tab_bar->SelectedTabId = 0; }\n            if (tab_bar->NextSelectedTabId == tab->ID) { tab_bar->NextSelectedTabId = 0; }\n            continue;\n        }\n        if (tab_dst_n != tab_src_n)\n            tab_bar->Tabs[tab_dst_n] = tab_bar->Tabs[tab_src_n];\n\n        tab = &tab_bar->Tabs[tab_dst_n];\n        tab->IndexDuringLayout = (ImS16)tab_dst_n;\n\n        // We will need sorting if tabs have changed section (e.g. moved from one of Leading/Central/Trailing to another)\n        int curr_tab_section_n = TabItemGetSectionIdx(tab);\n        if (tab_dst_n > 0)\n        {\n            ImGuiTabItem* prev_tab = &tab_bar->Tabs[tab_dst_n - 1];\n            int prev_tab_section_n = TabItemGetSectionIdx(prev_tab);\n            if (curr_tab_section_n == 0 && prev_tab_section_n != 0)\n                need_sort_by_section = true;\n            if (prev_tab_section_n == 2 && curr_tab_section_n != 2)\n                need_sort_by_section = true;\n        }\n\n        sections[curr_tab_section_n].TabCount++;\n        tab_dst_n++;\n    }\n    if (tab_bar->Tabs.Size != tab_dst_n)\n        tab_bar->Tabs.resize(tab_dst_n);\n\n    if (need_sort_by_section)\n        ImQsort(tab_bar->Tabs.Data, tab_bar->Tabs.Size, sizeof(ImGuiTabItem), TabItemComparerBySection);\n\n    // Calculate spacing between sections\n    sections[0].Spacing = sections[0].TabCount > 0 && (sections[1].TabCount + sections[2].TabCount) > 0 ? g.Style.ItemInnerSpacing.x : 0.0f;\n    sections[1].Spacing = sections[1].TabCount > 0 && sections[2].TabCount > 0 ? g.Style.ItemInnerSpacing.x : 0.0f;\n\n    // Setup next selected tab\n    ImGuiID scroll_to_tab_id = 0;\n    if (tab_bar->NextSelectedTabId)\n    {\n        tab_bar->SelectedTabId = tab_bar->NextSelectedTabId;\n        tab_bar->NextSelectedTabId = 0;\n        scroll_to_tab_id = tab_bar->SelectedTabId;\n    }\n\n    // Process order change request (we could probably process it when requested but it's just saner to do it in a single spot).\n    if (tab_bar->ReorderRequestTabId != 0)\n    {\n        if (TabBarProcessReorder(tab_bar))\n            if (tab_bar->ReorderRequestTabId == tab_bar->SelectedTabId)\n                scroll_to_tab_id = tab_bar->ReorderRequestTabId;\n        tab_bar->ReorderRequestTabId = 0;\n    }\n\n    // Tab List Popup (will alter tab_bar->BarRect and therefore the available width!)\n    const bool tab_list_popup_button = (tab_bar->Flags & ImGuiTabBarFlags_TabListPopupButton) != 0;\n    if (tab_list_popup_button)\n        if (ImGuiTabItem* tab_to_select = TabBarTabListPopupButton(tab_bar)) // NB: Will alter BarRect.Min.x!\n            scroll_to_tab_id = tab_bar->SelectedTabId = tab_to_select->ID;\n\n    // Leading/Trailing tabs will be shrink only if central one aren't visible anymore, so layout the shrink data as: leading, trailing, central\n    // (whereas our tabs are stored as: leading, central, trailing)\n    int shrink_buffer_indexes[3] = { 0, sections[0].TabCount + sections[2].TabCount, sections[0].TabCount };\n    g.ShrinkWidthBuffer.resize(tab_bar->Tabs.Size);\n\n    // Compute ideal tabs widths + store them into shrink buffer\n    ImGuiTabItem* most_recently_selected_tab = NULL;\n    int curr_section_n = -1;\n    bool found_selected_tab_id = false;\n    for (int tab_n = 0; tab_n < tab_bar->Tabs.Size; tab_n++)\n    {\n        ImGuiTabItem* tab = &tab_bar->Tabs[tab_n];\n        IM_ASSERT(tab->LastFrameVisible >= tab_bar->PrevFrameVisible);\n\n        if ((most_recently_selected_tab == NULL || most_recently_selected_tab->LastFrameSelected < tab->LastFrameSelected) && !(tab->Flags & ImGuiTabItemFlags_Button))\n            most_recently_selected_tab = tab;\n        if (tab->ID == tab_bar->SelectedTabId)\n            found_selected_tab_id = true;\n        if (scroll_to_tab_id == 0 && g.NavJustMovedToId == tab->ID)\n            scroll_to_tab_id = tab->ID;\n\n        // Refresh tab width immediately, otherwise changes of style e.g. style.FramePadding.x would noticeably lag in the tab bar.\n        // Additionally, when using TabBarAddTab() to manipulate tab bar order we occasionally insert new tabs that don't have a width yet,\n        // and we cannot wait for the next BeginTabItem() call. We cannot compute this width within TabBarAddTab() because font size depends on the active window.\n        const char* tab_name = TabBarGetTabName(tab_bar, tab);\n        const bool has_close_button_or_unsaved_marker = (tab->Flags & ImGuiTabItemFlags_NoCloseButton) == 0 || (tab->Flags & ImGuiTabItemFlags_UnsavedDocument);\n        tab->ContentWidth = (tab->RequestedWidth >= 0.0f) ? tab->RequestedWidth : TabItemCalcSize(tab_name, has_close_button_or_unsaved_marker).x;\n\n        int section_n = TabItemGetSectionIdx(tab);\n        ImGuiTabBarSection* section = &sections[section_n];\n        section->Width += tab->ContentWidth + (section_n == curr_section_n ? g.Style.ItemInnerSpacing.x : 0.0f);\n        curr_section_n = section_n;\n\n        // Store data so we can build an array sorted by width if we need to shrink tabs down\n        IM_MSVC_WARNING_SUPPRESS(6385);\n        ImGuiShrinkWidthItem* shrink_width_item = &g.ShrinkWidthBuffer[shrink_buffer_indexes[section_n]++];\n        shrink_width_item->Index = tab_n;\n        shrink_width_item->Width = shrink_width_item->InitialWidth = tab->ContentWidth;\n        tab->Width = ImMax(tab->ContentWidth, 1.0f);\n    }\n\n    // Compute total ideal width (used for e.g. auto-resizing a window)\n    tab_bar->WidthAllTabsIdeal = 0.0f;\n    for (int section_n = 0; section_n < 3; section_n++)\n        tab_bar->WidthAllTabsIdeal += sections[section_n].Width + sections[section_n].Spacing;\n\n    // Horizontal scrolling buttons\n    // (note that TabBarScrollButtons() will alter BarRect.Max.x)\n    if ((tab_bar->WidthAllTabsIdeal > tab_bar->BarRect.GetWidth() && tab_bar->Tabs.Size > 1) && !(tab_bar->Flags & ImGuiTabBarFlags_NoTabListScrollingButtons) && (tab_bar->Flags & ImGuiTabBarFlags_FittingPolicyScroll))\n        if (ImGuiTabItem* scroll_and_select_tab = TabBarScrollingButtons(tab_bar))\n        {\n            scroll_to_tab_id = scroll_and_select_tab->ID;\n            if ((scroll_and_select_tab->Flags & ImGuiTabItemFlags_Button) == 0)\n                tab_bar->SelectedTabId = scroll_to_tab_id;\n        }\n\n    // Shrink widths if full tabs don't fit in their allocated space\n    float section_0_w = sections[0].Width + sections[0].Spacing;\n    float section_1_w = sections[1].Width + sections[1].Spacing;\n    float section_2_w = sections[2].Width + sections[2].Spacing;\n    bool central_section_is_visible = (section_0_w + section_2_w) < tab_bar->BarRect.GetWidth();\n    float width_excess;\n    if (central_section_is_visible)\n        width_excess = ImMax(section_1_w - (tab_bar->BarRect.GetWidth() - section_0_w - section_2_w), 0.0f); // Excess used to shrink central section\n    else\n        width_excess = (section_0_w + section_2_w) - tab_bar->BarRect.GetWidth(); // Excess used to shrink leading/trailing section\n\n    // With ImGuiTabBarFlags_FittingPolicyScroll policy, we will only shrink leading/trailing if the central section is not visible anymore\n    if (width_excess >= 1.0f && ((tab_bar->Flags & ImGuiTabBarFlags_FittingPolicyResizeDown) || !central_section_is_visible))\n    {\n        int shrink_data_count = (central_section_is_visible ? sections[1].TabCount : sections[0].TabCount + sections[2].TabCount);\n        int shrink_data_offset = (central_section_is_visible ? sections[0].TabCount + sections[2].TabCount : 0);\n        ShrinkWidths(g.ShrinkWidthBuffer.Data + shrink_data_offset, shrink_data_count, width_excess);\n\n        // Apply shrunk values into tabs and sections\n        for (int tab_n = shrink_data_offset; tab_n < shrink_data_offset + shrink_data_count; tab_n++)\n        {\n            ImGuiTabItem* tab = &tab_bar->Tabs[g.ShrinkWidthBuffer[tab_n].Index];\n            float shrinked_width = IM_TRUNC(g.ShrinkWidthBuffer[tab_n].Width);\n            if (shrinked_width < 0.0f)\n                continue;\n\n            shrinked_width = ImMax(1.0f, shrinked_width);\n            int section_n = TabItemGetSectionIdx(tab);\n            sections[section_n].Width -= (tab->Width - shrinked_width);\n            tab->Width = shrinked_width;\n        }\n    }\n\n    // Layout all active tabs\n    int section_tab_index = 0;\n    float tab_offset = 0.0f;\n    tab_bar->WidthAllTabs = 0.0f;\n    for (int section_n = 0; section_n < 3; section_n++)\n    {\n        ImGuiTabBarSection* section = &sections[section_n];\n        if (section_n == 2)\n            tab_offset = ImMin(ImMax(0.0f, tab_bar->BarRect.GetWidth() - section->Width), tab_offset);\n\n        for (int tab_n = 0; tab_n < section->TabCount; tab_n++)\n        {\n            ImGuiTabItem* tab = &tab_bar->Tabs[section_tab_index + tab_n];\n            tab->Offset = tab_offset;\n            tab->NameOffset = -1;\n            tab_offset += tab->Width + (tab_n < section->TabCount - 1 ? g.Style.ItemInnerSpacing.x : 0.0f);\n        }\n        tab_bar->WidthAllTabs += ImMax(section->Width + section->Spacing, 0.0f);\n        tab_offset += section->Spacing;\n        section_tab_index += section->TabCount;\n    }\n\n    // Clear name buffers\n    tab_bar->TabsNames.Buf.resize(0);\n\n    // If we have lost the selected tab, select the next most recently active one\n    if (found_selected_tab_id == false)\n        tab_bar->SelectedTabId = 0;\n    if (tab_bar->SelectedTabId == 0 && tab_bar->NextSelectedTabId == 0 && most_recently_selected_tab != NULL)\n        scroll_to_tab_id = tab_bar->SelectedTabId = most_recently_selected_tab->ID;\n\n    // Lock in visible tab\n    tab_bar->VisibleTabId = tab_bar->SelectedTabId;\n    tab_bar->VisibleTabWasSubmitted = false;\n\n    // Apply request requests\n    if (scroll_to_tab_id != 0)\n        TabBarScrollToTab(tab_bar, scroll_to_tab_id, sections);\n    else if ((tab_bar->Flags & ImGuiTabBarFlags_FittingPolicyScroll) && IsMouseHoveringRect(tab_bar->BarRect.Min, tab_bar->BarRect.Max, true) && IsWindowContentHoverable(g.CurrentWindow))\n    {\n        const float wheel = g.IO.MouseWheelRequestAxisSwap ? g.IO.MouseWheel : g.IO.MouseWheelH;\n        const ImGuiKey wheel_key = g.IO.MouseWheelRequestAxisSwap ? ImGuiKey_MouseWheelY : ImGuiKey_MouseWheelX;\n        if (TestKeyOwner(wheel_key, tab_bar->ID) && wheel != 0.0f)\n        {\n            const float scroll_step = wheel * TabBarCalcScrollableWidth(tab_bar, sections) / 3.0f;\n            tab_bar->ScrollingTargetDistToVisibility = 0.0f;\n            tab_bar->ScrollingTarget = TabBarScrollClamp(tab_bar, tab_bar->ScrollingTarget - scroll_step);\n        }\n        SetKeyOwner(wheel_key, tab_bar->ID);\n    }\n\n    // Update scrolling\n    tab_bar->ScrollingAnim = TabBarScrollClamp(tab_bar, tab_bar->ScrollingAnim);\n    tab_bar->ScrollingTarget = TabBarScrollClamp(tab_bar, tab_bar->ScrollingTarget);\n    if (tab_bar->ScrollingAnim != tab_bar->ScrollingTarget)\n    {\n        // Scrolling speed adjust itself so we can always reach our target in 1/3 seconds.\n        // Teleport if we are aiming far off the visible line\n        tab_bar->ScrollingSpeed = ImMax(tab_bar->ScrollingSpeed, 70.0f * g.FontSize);\n        tab_bar->ScrollingSpeed = ImMax(tab_bar->ScrollingSpeed, ImFabs(tab_bar->ScrollingTarget - tab_bar->ScrollingAnim) / 0.3f);\n        const bool teleport = (tab_bar->PrevFrameVisible + 1 < g.FrameCount) || (tab_bar->ScrollingTargetDistToVisibility > 10.0f * g.FontSize);\n        tab_bar->ScrollingAnim = teleport ? tab_bar->ScrollingTarget : ImLinearSweep(tab_bar->ScrollingAnim, tab_bar->ScrollingTarget, g.IO.DeltaTime * tab_bar->ScrollingSpeed);\n    }\n    else\n    {\n        tab_bar->ScrollingSpeed = 0.0f;\n    }\n    tab_bar->ScrollingRectMinX = tab_bar->BarRect.Min.x + sections[0].Width + sections[0].Spacing;\n    tab_bar->ScrollingRectMaxX = tab_bar->BarRect.Max.x - sections[2].Width - sections[1].Spacing;\n\n    // Actual layout in host window (we don't do it in BeginTabBar() so as not to waste an extra frame)\n    ImGuiWindow* window = g.CurrentWindow;\n    window->DC.CursorPos = tab_bar->BarRect.Min;\n    ItemSize(ImVec2(tab_bar->WidthAllTabs, tab_bar->BarRect.GetHeight()), tab_bar->FramePadding.y);\n    window->DC.IdealMaxPos.x = ImMax(window->DC.IdealMaxPos.x, tab_bar->BarRect.Min.x + tab_bar->WidthAllTabsIdeal);\n}\n\n// Dockable windows uses Name/ID in the global namespace. Non-dockable items use the ID stack.\nstatic ImU32   ImGui::TabBarCalcTabID(ImGuiTabBar* tab_bar, const char* label, ImGuiWindow* docked_window)\n{\n    IM_ASSERT(docked_window == NULL); // master branch only\n    IM_UNUSED(docked_window);\n    if (tab_bar->Flags & ImGuiTabBarFlags_DockNode)\n    {\n        ImGuiID id = ImHashStr(label);\n        KeepAliveID(id);\n        return id;\n    }\n    else\n    {\n        ImGuiWindow* window = GImGui->CurrentWindow;\n        return window->GetID(label);\n    }\n}\n\nstatic float ImGui::TabBarCalcMaxTabWidth()\n{\n    ImGuiContext& g = *GImGui;\n    return g.FontSize * 20.0f;\n}\n\nImGuiTabItem* ImGui::TabBarFindTabByID(ImGuiTabBar* tab_bar, ImGuiID tab_id)\n{\n    if (tab_id != 0)\n        for (int n = 0; n < tab_bar->Tabs.Size; n++)\n            if (tab_bar->Tabs[n].ID == tab_id)\n                return &tab_bar->Tabs[n];\n    return NULL;\n}\n\n// Order = visible order, not submission order! (which is tab->BeginOrder)\nImGuiTabItem* ImGui::TabBarFindTabByOrder(ImGuiTabBar* tab_bar, int order)\n{\n    if (order < 0 || order >= tab_bar->Tabs.Size)\n        return NULL;\n    return &tab_bar->Tabs[order];\n}\n\nImGuiTabItem* ImGui::TabBarGetCurrentTab(ImGuiTabBar* tab_bar)\n{\n    if (tab_bar->LastTabItemIdx < 0 || tab_bar->LastTabItemIdx >= tab_bar->Tabs.Size)\n        return NULL;\n    return &tab_bar->Tabs[tab_bar->LastTabItemIdx];\n}\n\nconst char* ImGui::TabBarGetTabName(ImGuiTabBar* tab_bar, ImGuiTabItem* tab)\n{\n    if (tab->NameOffset == -1)\n        return \"N/A\";\n    IM_ASSERT(tab->NameOffset < tab_bar->TabsNames.Buf.Size);\n    return tab_bar->TabsNames.Buf.Data + tab->NameOffset;\n}\n\n// The *TabId fields are already set by the docking system _before_ the actual TabItem was created, so we clear them regardless.\nvoid ImGui::TabBarRemoveTab(ImGuiTabBar* tab_bar, ImGuiID tab_id)\n{\n    if (ImGuiTabItem* tab = TabBarFindTabByID(tab_bar, tab_id))\n        tab_bar->Tabs.erase(tab);\n    if (tab_bar->VisibleTabId == tab_id)      { tab_bar->VisibleTabId = 0; }\n    if (tab_bar->SelectedTabId == tab_id)     { tab_bar->SelectedTabId = 0; }\n    if (tab_bar->NextSelectedTabId == tab_id) { tab_bar->NextSelectedTabId = 0; }\n}\n\n// Called on manual closure attempt\nvoid ImGui::TabBarCloseTab(ImGuiTabBar* tab_bar, ImGuiTabItem* tab)\n{\n    if (tab->Flags & ImGuiTabItemFlags_Button)\n        return; // A button appended with TabItemButton().\n\n    if ((tab->Flags & (ImGuiTabItemFlags_UnsavedDocument | ImGuiTabItemFlags_NoAssumedClosure)) == 0)\n    {\n        // This will remove a frame of lag for selecting another tab on closure.\n        // However we don't run it in the case where the 'Unsaved' flag is set, so user gets a chance to fully undo the closure\n        tab->WantClose = true;\n        if (tab_bar->VisibleTabId == tab->ID)\n        {\n            tab->LastFrameVisible = -1;\n            tab_bar->SelectedTabId = tab_bar->NextSelectedTabId = 0;\n        }\n    }\n    else\n    {\n        // Actually select before expecting closure attempt (on an UnsavedDocument tab user is expect to e.g. show a popup)\n        if (tab_bar->VisibleTabId != tab->ID)\n            TabBarQueueFocus(tab_bar, tab);\n    }\n}\n\nstatic float ImGui::TabBarScrollClamp(ImGuiTabBar* tab_bar, float scrolling)\n{\n    scrolling = ImMin(scrolling, tab_bar->WidthAllTabs - tab_bar->BarRect.GetWidth());\n    return ImMax(scrolling, 0.0f);\n}\n\n// Note: we may scroll to tab that are not selected! e.g. using keyboard arrow keys\nstatic void ImGui::TabBarScrollToTab(ImGuiTabBar* tab_bar, ImGuiID tab_id, ImGuiTabBarSection* sections)\n{\n    ImGuiTabItem* tab = TabBarFindTabByID(tab_bar, tab_id);\n    if (tab == NULL)\n        return;\n    if (tab->Flags & ImGuiTabItemFlags_SectionMask_)\n        return;\n\n    ImGuiContext& g = *GImGui;\n    float margin = g.FontSize * 1.0f; // When to scroll to make Tab N+1 visible always make a bit of N visible to suggest more scrolling area (since we don't have a scrollbar)\n    int order = TabBarGetTabOrder(tab_bar, tab);\n\n    // Scrolling happens only in the central section (leading/trailing sections are not scrolling)\n    float scrollable_width = TabBarCalcScrollableWidth(tab_bar, sections);\n\n    // We make all tabs positions all relative Sections[0].Width to make code simpler\n    float tab_x1 = tab->Offset - sections[0].Width + (order > sections[0].TabCount - 1 ? -margin : 0.0f);\n    float tab_x2 = tab->Offset - sections[0].Width + tab->Width + (order + 1 < tab_bar->Tabs.Size - sections[2].TabCount ? margin : 1.0f);\n    tab_bar->ScrollingTargetDistToVisibility = 0.0f;\n    if (tab_bar->ScrollingTarget > tab_x1 || (tab_x2 - tab_x1 >= scrollable_width))\n    {\n        // Scroll to the left\n        tab_bar->ScrollingTargetDistToVisibility = ImMax(tab_bar->ScrollingAnim - tab_x2, 0.0f);\n        tab_bar->ScrollingTarget = tab_x1;\n    }\n    else if (tab_bar->ScrollingTarget < tab_x2 - scrollable_width)\n    {\n        // Scroll to the right\n        tab_bar->ScrollingTargetDistToVisibility = ImMax((tab_x1 - scrollable_width) - tab_bar->ScrollingAnim, 0.0f);\n        tab_bar->ScrollingTarget = tab_x2 - scrollable_width;\n    }\n}\n\nvoid ImGui::TabBarQueueFocus(ImGuiTabBar* tab_bar, ImGuiTabItem* tab)\n{\n    tab_bar->NextSelectedTabId = tab->ID;\n}\n\nvoid ImGui::TabBarQueueFocus(ImGuiTabBar* tab_bar, const char* tab_name)\n{\n    IM_ASSERT((tab_bar->Flags & ImGuiTabBarFlags_DockNode) == 0); // Only supported for manual/explicit tab bars\n    ImGuiID tab_id = TabBarCalcTabID(tab_bar, tab_name, NULL);\n    tab_bar->NextSelectedTabId = tab_id;\n}\n\nvoid ImGui::TabBarQueueReorder(ImGuiTabBar* tab_bar, ImGuiTabItem* tab, int offset)\n{\n    IM_ASSERT(offset != 0);\n    IM_ASSERT(tab_bar->ReorderRequestTabId == 0);\n    tab_bar->ReorderRequestTabId = tab->ID;\n    tab_bar->ReorderRequestOffset = (ImS16)offset;\n}\n\nvoid ImGui::TabBarQueueReorderFromMousePos(ImGuiTabBar* tab_bar, ImGuiTabItem* src_tab, ImVec2 mouse_pos)\n{\n    ImGuiContext& g = *GImGui;\n    IM_ASSERT(tab_bar->ReorderRequestTabId == 0);\n    if ((tab_bar->Flags & ImGuiTabBarFlags_Reorderable) == 0)\n        return;\n\n    const bool is_central_section = (src_tab->Flags & ImGuiTabItemFlags_SectionMask_) == 0;\n    const float bar_offset = tab_bar->BarRect.Min.x - (is_central_section ? tab_bar->ScrollingTarget : 0);\n\n    // Count number of contiguous tabs we are crossing over\n    const int dir = (bar_offset + src_tab->Offset) > mouse_pos.x ? -1 : +1;\n    const int src_idx = tab_bar->Tabs.index_from_ptr(src_tab);\n    int dst_idx = src_idx;\n    for (int i = src_idx; i >= 0 && i < tab_bar->Tabs.Size; i += dir)\n    {\n        // Reordered tabs must share the same section\n        const ImGuiTabItem* dst_tab = &tab_bar->Tabs[i];\n        if (dst_tab->Flags & ImGuiTabItemFlags_NoReorder)\n            break;\n        if ((dst_tab->Flags & ImGuiTabItemFlags_SectionMask_) != (src_tab->Flags & ImGuiTabItemFlags_SectionMask_))\n            break;\n        dst_idx = i;\n\n        // Include spacing after tab, so when mouse cursor is between tabs we would not continue checking further tabs that are not hovered.\n        const float x1 = bar_offset + dst_tab->Offset - g.Style.ItemInnerSpacing.x;\n        const float x2 = bar_offset + dst_tab->Offset + dst_tab->Width + g.Style.ItemInnerSpacing.x;\n        //GetForegroundDrawList()->AddRect(ImVec2(x1, tab_bar->BarRect.Min.y), ImVec2(x2, tab_bar->BarRect.Max.y), IM_COL32(255, 0, 0, 255));\n        if ((dir < 0 && mouse_pos.x > x1) || (dir > 0 && mouse_pos.x < x2))\n            break;\n    }\n\n    if (dst_idx != src_idx)\n        TabBarQueueReorder(tab_bar, src_tab, dst_idx - src_idx);\n}\n\nbool ImGui::TabBarProcessReorder(ImGuiTabBar* tab_bar)\n{\n    ImGuiTabItem* tab1 = TabBarFindTabByID(tab_bar, tab_bar->ReorderRequestTabId);\n    if (tab1 == NULL || (tab1->Flags & ImGuiTabItemFlags_NoReorder))\n        return false;\n\n    //IM_ASSERT(tab_bar->Flags & ImGuiTabBarFlags_Reorderable); // <- this may happen when using debug tools\n    int tab2_order = TabBarGetTabOrder(tab_bar, tab1) + tab_bar->ReorderRequestOffset;\n    if (tab2_order < 0 || tab2_order >= tab_bar->Tabs.Size)\n        return false;\n\n    // Reordered tabs must share the same section\n    // (Note: TabBarQueueReorderFromMousePos() also has a similar test but since we allow direct calls to TabBarQueueReorder() we do it here too)\n    ImGuiTabItem* tab2 = &tab_bar->Tabs[tab2_order];\n    if (tab2->Flags & ImGuiTabItemFlags_NoReorder)\n        return false;\n    if ((tab1->Flags & ImGuiTabItemFlags_SectionMask_) != (tab2->Flags & ImGuiTabItemFlags_SectionMask_))\n        return false;\n\n    ImGuiTabItem item_tmp = *tab1;\n    ImGuiTabItem* src_tab = (tab_bar->ReorderRequestOffset > 0) ? tab1 + 1 : tab2;\n    ImGuiTabItem* dst_tab = (tab_bar->ReorderRequestOffset > 0) ? tab1 : tab2 + 1;\n    const int move_count = (tab_bar->ReorderRequestOffset > 0) ? tab_bar->ReorderRequestOffset : -tab_bar->ReorderRequestOffset;\n    memmove(dst_tab, src_tab, move_count * sizeof(ImGuiTabItem));\n    *tab2 = item_tmp;\n\n    if (tab_bar->Flags & ImGuiTabBarFlags_SaveSettings)\n        MarkIniSettingsDirty();\n    return true;\n}\n\nstatic ImGuiTabItem* ImGui::TabBarScrollingButtons(ImGuiTabBar* tab_bar)\n{\n    ImGuiContext& g = *GImGui;\n    ImGuiWindow* window = g.CurrentWindow;\n\n    const ImVec2 arrow_button_size(g.FontSize - 2.0f, g.FontSize + g.Style.FramePadding.y * 2.0f);\n    const float scrolling_buttons_width = arrow_button_size.x * 2.0f;\n\n    const ImVec2 backup_cursor_pos = window->DC.CursorPos;\n    //window->DrawList->AddRect(ImVec2(tab_bar->BarRect.Max.x - scrolling_buttons_width, tab_bar->BarRect.Min.y), ImVec2(tab_bar->BarRect.Max.x, tab_bar->BarRect.Max.y), IM_COL32(255,0,0,255));\n\n    int select_dir = 0;\n    ImVec4 arrow_col = g.Style.Colors[ImGuiCol_Text];\n    arrow_col.w *= 0.5f;\n\n    PushStyleColor(ImGuiCol_Text, arrow_col);\n    PushStyleColor(ImGuiCol_Button, ImVec4(0, 0, 0, 0));\n    PushItemFlag(ImGuiItemFlags_ButtonRepeat, true);\n    const float backup_repeat_delay = g.IO.KeyRepeatDelay;\n    const float backup_repeat_rate = g.IO.KeyRepeatRate;\n    g.IO.KeyRepeatDelay = 0.250f;\n    g.IO.KeyRepeatRate = 0.200f;\n    float x = ImMax(tab_bar->BarRect.Min.x, tab_bar->BarRect.Max.x - scrolling_buttons_width);\n    window->DC.CursorPos = ImVec2(x, tab_bar->BarRect.Min.y);\n    if (ArrowButtonEx(\"##<\", ImGuiDir_Left, arrow_button_size, ImGuiButtonFlags_PressedOnClick))\n        select_dir = -1;\n    window->DC.CursorPos = ImVec2(x + arrow_button_size.x, tab_bar->BarRect.Min.y);\n    if (ArrowButtonEx(\"##>\", ImGuiDir_Right, arrow_button_size, ImGuiButtonFlags_PressedOnClick))\n        select_dir = +1;\n    PopItemFlag();\n    PopStyleColor(2);\n    g.IO.KeyRepeatRate = backup_repeat_rate;\n    g.IO.KeyRepeatDelay = backup_repeat_delay;\n\n    ImGuiTabItem* tab_to_scroll_to = NULL;\n    if (select_dir != 0)\n        if (ImGuiTabItem* tab_item = TabBarFindTabByID(tab_bar, tab_bar->SelectedTabId))\n        {\n            int selected_order = TabBarGetTabOrder(tab_bar, tab_item);\n            int target_order = selected_order + select_dir;\n\n            // Skip tab item buttons until another tab item is found or end is reached\n            while (tab_to_scroll_to == NULL)\n            {\n                // If we are at the end of the list, still scroll to make our tab visible\n                tab_to_scroll_to = &tab_bar->Tabs[(target_order >= 0 && target_order < tab_bar->Tabs.Size) ? target_order : selected_order];\n\n                // Cross through buttons\n                // (even if first/last item is a button, return it so we can update the scroll)\n                if (tab_to_scroll_to->Flags & ImGuiTabItemFlags_Button)\n                {\n                    target_order += select_dir;\n                    selected_order += select_dir;\n                    tab_to_scroll_to = (target_order < 0 || target_order >= tab_bar->Tabs.Size) ? tab_to_scroll_to : NULL;\n                }\n            }\n        }\n    window->DC.CursorPos = backup_cursor_pos;\n    tab_bar->BarRect.Max.x -= scrolling_buttons_width + 1.0f;\n\n    return tab_to_scroll_to;\n}\n\nstatic ImGuiTabItem* ImGui::TabBarTabListPopupButton(ImGuiTabBar* tab_bar)\n{\n    ImGuiContext& g = *GImGui;\n    ImGuiWindow* window = g.CurrentWindow;\n\n    // We use g.Style.FramePadding.y to match the square ArrowButton size\n    const float tab_list_popup_button_width = g.FontSize + g.Style.FramePadding.y;\n    const ImVec2 backup_cursor_pos = window->DC.CursorPos;\n    window->DC.CursorPos = ImVec2(tab_bar->BarRect.Min.x - g.Style.FramePadding.y, tab_bar->BarRect.Min.y);\n    tab_bar->BarRect.Min.x += tab_list_popup_button_width;\n\n    ImVec4 arrow_col = g.Style.Colors[ImGuiCol_Text];\n    arrow_col.w *= 0.5f;\n    PushStyleColor(ImGuiCol_Text, arrow_col);\n    PushStyleColor(ImGuiCol_Button, ImVec4(0, 0, 0, 0));\n    bool open = BeginCombo(\"##v\", NULL, ImGuiComboFlags_NoPreview | ImGuiComboFlags_HeightLargest);\n    PopStyleColor(2);\n\n    ImGuiTabItem* tab_to_select = NULL;\n    if (open)\n    {\n        for (int tab_n = 0; tab_n < tab_bar->Tabs.Size; tab_n++)\n        {\n            ImGuiTabItem* tab = &tab_bar->Tabs[tab_n];\n            if (tab->Flags & ImGuiTabItemFlags_Button)\n                continue;\n\n            const char* tab_name = TabBarGetTabName(tab_bar, tab);\n            if (Selectable(tab_name, tab_bar->SelectedTabId == tab->ID))\n                tab_to_select = tab;\n        }\n        EndCombo();\n    }\n\n    window->DC.CursorPos = backup_cursor_pos;\n    return tab_to_select;\n}\n\n//-------------------------------------------------------------------------\n// [SECTION] Widgets: BeginTabItem, EndTabItem, etc.\n//-------------------------------------------------------------------------\n// - BeginTabItem()\n// - EndTabItem()\n// - TabItemButton()\n// - TabItemEx() [Internal]\n// - SetTabItemClosed()\n// - TabItemCalcSize() [Internal]\n// - TabItemBackground() [Internal]\n// - TabItemLabelAndCloseButton() [Internal]\n//-------------------------------------------------------------------------\n\nbool    ImGui::BeginTabItem(const char* label, bool* p_open, ImGuiTabItemFlags flags)\n{\n    ImGuiContext& g = *GImGui;\n    ImGuiWindow* window = g.CurrentWindow;\n    if (window->SkipItems)\n        return false;\n\n    ImGuiTabBar* tab_bar = g.CurrentTabBar;\n    if (tab_bar == NULL)\n    {\n        IM_ASSERT_USER_ERROR(tab_bar, \"Needs to be called between BeginTabBar() and EndTabBar()!\");\n        return false;\n    }\n    IM_ASSERT((flags & ImGuiTabItemFlags_Button) == 0); // BeginTabItem() Can't be used with button flags, use TabItemButton() instead!\n\n    bool ret = TabItemEx(tab_bar, label, p_open, flags, NULL);\n    if (ret && !(flags & ImGuiTabItemFlags_NoPushId))\n    {\n        ImGuiTabItem* tab = &tab_bar->Tabs[tab_bar->LastTabItemIdx];\n        PushOverrideID(tab->ID); // We already hashed 'label' so push into the ID stack directly instead of doing another hash through PushID(label)\n    }\n    return ret;\n}\n\nvoid    ImGui::EndTabItem()\n{\n    ImGuiContext& g = *GImGui;\n    ImGuiWindow* window = g.CurrentWindow;\n    if (window->SkipItems)\n        return;\n\n    ImGuiTabBar* tab_bar = g.CurrentTabBar;\n    if (tab_bar == NULL)\n    {\n        IM_ASSERT_USER_ERROR(tab_bar != NULL, \"Needs to be called between BeginTabBar() and EndTabBar()!\");\n        return;\n    }\n    IM_ASSERT(tab_bar->LastTabItemIdx >= 0);\n    ImGuiTabItem* tab = &tab_bar->Tabs[tab_bar->LastTabItemIdx];\n    if (!(tab->Flags & ImGuiTabItemFlags_NoPushId))\n        PopID();\n}\n\nbool    ImGui::TabItemButton(const char* label, ImGuiTabItemFlags flags)\n{\n    ImGuiContext& g = *GImGui;\n    ImGuiWindow* window = g.CurrentWindow;\n    if (window->SkipItems)\n        return false;\n\n    ImGuiTabBar* tab_bar = g.CurrentTabBar;\n    if (tab_bar == NULL)\n    {\n        IM_ASSERT_USER_ERROR(tab_bar != NULL, \"Needs to be called between BeginTabBar() and EndTabBar()!\");\n        return false;\n    }\n    return TabItemEx(tab_bar, label, NULL, flags | ImGuiTabItemFlags_Button | ImGuiTabItemFlags_NoReorder, NULL);\n}\n\nvoid    ImGui::TabItemSpacing(const char* str_id, ImGuiTabItemFlags flags, float width)\n{\n    ImGuiContext& g = *GImGui;\n    ImGuiWindow* window = g.CurrentWindow;\n    if (window->SkipItems)\n        return;\n\n    ImGuiTabBar* tab_bar = g.CurrentTabBar;\n    if (tab_bar == NULL)\n    {\n        IM_ASSERT_USER_ERROR(tab_bar != NULL, \"Needs to be called between BeginTabBar() and EndTabBar()!\");\n        return;\n    }\n    SetNextItemWidth(width);\n    TabItemEx(tab_bar, str_id, NULL, flags | ImGuiTabItemFlags_Button | ImGuiTabItemFlags_NoReorder | ImGuiTabItemFlags_Invisible, NULL);\n}\n\nbool    ImGui::TabItemEx(ImGuiTabBar* tab_bar, const char* label, bool* p_open, ImGuiTabItemFlags flags, ImGuiWindow* docked_window)\n{\n    // Layout whole tab bar if not already done\n    ImGuiContext& g = *GImGui;\n    if (tab_bar->WantLayout)\n    {\n        ImGuiNextItemData backup_next_item_data = g.NextItemData;\n        TabBarLayout(tab_bar);\n        g.NextItemData = backup_next_item_data;\n    }\n    ImGuiWindow* window = g.CurrentWindow;\n    if (window->SkipItems)\n        return false;\n\n    const ImGuiStyle& style = g.Style;\n    const ImGuiID id = TabBarCalcTabID(tab_bar, label, docked_window);\n\n    // If the user called us with *p_open == false, we early out and don't render.\n    // We make a call to ItemAdd() so that attempts to use a contextual popup menu with an implicit ID won't use an older ID.\n    IMGUI_TEST_ENGINE_ITEM_INFO(id, label, g.LastItemData.StatusFlags);\n    if (p_open && !*p_open)\n    {\n        ItemAdd(ImRect(), id, NULL, ImGuiItemFlags_NoNav);\n        return false;\n    }\n\n    IM_ASSERT(!p_open || !(flags & ImGuiTabItemFlags_Button));\n    IM_ASSERT((flags & (ImGuiTabItemFlags_Leading | ImGuiTabItemFlags_Trailing)) != (ImGuiTabItemFlags_Leading | ImGuiTabItemFlags_Trailing)); // Can't use both Leading and Trailing\n\n    // Store into ImGuiTabItemFlags_NoCloseButton, also honor ImGuiTabItemFlags_NoCloseButton passed by user (although not documented)\n    if (flags & ImGuiTabItemFlags_NoCloseButton)\n        p_open = NULL;\n    else if (p_open == NULL)\n        flags |= ImGuiTabItemFlags_NoCloseButton;\n\n    // Acquire tab data\n    ImGuiTabItem* tab = TabBarFindTabByID(tab_bar, id);\n    bool tab_is_new = false;\n    if (tab == NULL)\n    {\n        tab_bar->Tabs.push_back(ImGuiTabItem());\n        tab = &tab_bar->Tabs.back();\n        tab->ID = id;\n        tab_bar->TabsAddedNew = tab_is_new = true;\n    }\n    tab_bar->LastTabItemIdx = (ImS16)tab_bar->Tabs.index_from_ptr(tab);\n\n    // Calculate tab contents size\n    ImVec2 size = TabItemCalcSize(label, (p_open != NULL) || (flags & ImGuiTabItemFlags_UnsavedDocument));\n    tab->RequestedWidth = -1.0f;\n    if (g.NextItemData.HasFlags & ImGuiNextItemDataFlags_HasWidth)\n        size.x = tab->RequestedWidth = g.NextItemData.Width;\n    if (tab_is_new)\n        tab->Width = ImMax(1.0f, size.x);\n    tab->ContentWidth = size.x;\n    tab->BeginOrder = tab_bar->TabsActiveCount++;\n\n    const bool tab_bar_appearing = (tab_bar->PrevFrameVisible + 1 < g.FrameCount);\n    const bool tab_bar_focused = (tab_bar->Flags & ImGuiTabBarFlags_IsFocused) != 0;\n    const bool tab_appearing = (tab->LastFrameVisible + 1 < g.FrameCount);\n    const bool tab_just_unsaved = (flags & ImGuiTabItemFlags_UnsavedDocument) && !(tab->Flags & ImGuiTabItemFlags_UnsavedDocument);\n    const bool is_tab_button = (flags & ImGuiTabItemFlags_Button) != 0;\n    tab->LastFrameVisible = g.FrameCount;\n    tab->Flags = flags;\n\n    // Append name _WITH_ the zero-terminator\n    if (docked_window != NULL)\n    {\n        IM_ASSERT(docked_window == NULL); // master branch only\n    }\n    else\n    {\n        tab->NameOffset = (ImS32)tab_bar->TabsNames.size();\n        tab_bar->TabsNames.append(label, label + ImStrlen(label) + 1);\n    }\n\n    // Update selected tab\n    if (!is_tab_button)\n    {\n        if (tab_appearing && (tab_bar->Flags & ImGuiTabBarFlags_AutoSelectNewTabs) && tab_bar->NextSelectedTabId == 0)\n            if (!tab_bar_appearing || tab_bar->SelectedTabId == 0)\n                TabBarQueueFocus(tab_bar, tab); // New tabs gets activated\n        if ((flags & ImGuiTabItemFlags_SetSelected) && (tab_bar->SelectedTabId != id)) // _SetSelected can only be passed on explicit tab bar\n            TabBarQueueFocus(tab_bar, tab);\n    }\n\n    // Lock visibility\n    // (Note: tab_contents_visible != tab_selected... because CTRL+TAB operations may preview some tabs without selecting them!)\n    bool tab_contents_visible = (tab_bar->VisibleTabId == id);\n    if (tab_contents_visible)\n        tab_bar->VisibleTabWasSubmitted = true;\n\n    // On the very first frame of a tab bar we let first tab contents be visible to minimize appearing glitches\n    if (!tab_contents_visible && tab_bar->SelectedTabId == 0 && tab_bar_appearing)\n        if (tab_bar->Tabs.Size == 1 && !(tab_bar->Flags & ImGuiTabBarFlags_AutoSelectNewTabs))\n            tab_contents_visible = true;\n\n    // Note that tab_is_new is not necessarily the same as tab_appearing! When a tab bar stops being submitted\n    // and then gets submitted again, the tabs will have 'tab_appearing=true' but 'tab_is_new=false'.\n    if (tab_appearing && (!tab_bar_appearing || tab_is_new))\n    {\n        ItemAdd(ImRect(), id, NULL, ImGuiItemFlags_NoNav);\n        if (is_tab_button)\n            return false;\n        return tab_contents_visible;\n    }\n\n    if (tab_bar->SelectedTabId == id)\n        tab->LastFrameSelected = g.FrameCount;\n\n    // Backup current layout position\n    const ImVec2 backup_main_cursor_pos = window->DC.CursorPos;\n\n    // Layout\n    const bool is_central_section = (tab->Flags & ImGuiTabItemFlags_SectionMask_) == 0;\n    size.x = tab->Width;\n    if (is_central_section)\n        window->DC.CursorPos = tab_bar->BarRect.Min + ImVec2(IM_TRUNC(tab->Offset - tab_bar->ScrollingAnim), 0.0f);\n    else\n        window->DC.CursorPos = tab_bar->BarRect.Min + ImVec2(tab->Offset, 0.0f);\n    ImVec2 pos = window->DC.CursorPos;\n    ImRect bb(pos, pos + size);\n\n    // We don't have CPU clipping primitives to clip the CloseButton (until it becomes a texture), so need to add an extra draw call (temporary in the case of vertical animation)\n    const bool want_clip_rect = is_central_section && (bb.Min.x < tab_bar->ScrollingRectMinX || bb.Max.x > tab_bar->ScrollingRectMaxX);\n    if (want_clip_rect)\n        PushClipRect(ImVec2(ImMax(bb.Min.x, tab_bar->ScrollingRectMinX), bb.Min.y - 1), ImVec2(tab_bar->ScrollingRectMaxX, bb.Max.y), true);\n\n    ImVec2 backup_cursor_max_pos = window->DC.CursorMaxPos;\n    ItemSize(bb.GetSize(), style.FramePadding.y);\n    window->DC.CursorMaxPos = backup_cursor_max_pos;\n\n    if (!ItemAdd(bb, id))\n    {\n        if (want_clip_rect)\n            PopClipRect();\n        window->DC.CursorPos = backup_main_cursor_pos;\n        return tab_contents_visible;\n    }\n\n    // Click to Select a tab\n    // Allow the close button to overlap\n    ImGuiButtonFlags button_flags = ((is_tab_button ? ImGuiButtonFlags_PressedOnClickRelease : ImGuiButtonFlags_PressedOnClick) | ImGuiButtonFlags_AllowOverlap);\n    if (g.DragDropActive)\n        button_flags |= ImGuiButtonFlags_PressedOnDragDropHold;\n    bool hovered, held, pressed;\n    if (flags & ImGuiTabItemFlags_Invisible)\n        hovered = held = pressed = false;\n    else\n        pressed = ButtonBehavior(bb, id, &hovered, &held, button_flags);\n    if (pressed && !is_tab_button)\n        TabBarQueueFocus(tab_bar, tab);\n\n    // Drag and drop: re-order tabs\n    if (held && !tab_appearing && IsMouseDragging(0))\n    {\n        if (!g.DragDropActive && (tab_bar->Flags & ImGuiTabBarFlags_Reorderable))\n        {\n            // While moving a tab it will jump on the other side of the mouse, so we also test for MouseDelta.x\n            if (g.IO.MouseDelta.x < 0.0f && g.IO.MousePos.x < bb.Min.x)\n            {\n                TabBarQueueReorderFromMousePos(tab_bar, tab, g.IO.MousePos);\n            }\n            else if (g.IO.MouseDelta.x > 0.0f && g.IO.MousePos.x > bb.Max.x)\n            {\n                TabBarQueueReorderFromMousePos(tab_bar, tab, g.IO.MousePos);\n            }\n        }\n    }\n\n#if 0\n    if (hovered && g.HoveredIdNotActiveTimer > TOOLTIP_DELAY && bb.GetWidth() < tab->ContentWidth)\n    {\n        // Enlarge tab display when hovering\n        bb.Max.x = bb.Min.x + IM_TRUNC(ImLerp(bb.GetWidth(), tab->ContentWidth, ImSaturate((g.HoveredIdNotActiveTimer - 0.40f) * 6.0f)));\n        display_draw_list = GetForegroundDrawList(window);\n        TabItemBackground(display_draw_list, bb, flags, GetColorU32(ImGuiCol_TitleBgActive));\n    }\n#endif\n\n    // Render tab shape\n    const bool is_visible = (g.LastItemData.StatusFlags & ImGuiItemStatusFlags_Visible) && !(flags & ImGuiTabItemFlags_Invisible);\n    if (is_visible)\n    {\n        ImDrawList* display_draw_list = window->DrawList;\n        const ImU32 tab_col = GetColorU32((held || hovered) ? ImGuiCol_TabHovered : tab_contents_visible ? (tab_bar_focused ? ImGuiCol_TabSelected : ImGuiCol_TabDimmedSelected) : (tab_bar_focused ? ImGuiCol_Tab : ImGuiCol_TabDimmed));\n        TabItemBackground(display_draw_list, bb, flags, tab_col);\n        if (tab_contents_visible && (tab_bar->Flags & ImGuiTabBarFlags_DrawSelectedOverline) && style.TabBarOverlineSize > 0.0f)\n        {\n            // Might be moved to TabItemBackground() ?\n            ImVec2 tl = bb.GetTL() + ImVec2(0, 1.0f * g.CurrentDpiScale);\n            ImVec2 tr = bb.GetTR() + ImVec2(0, 1.0f * g.CurrentDpiScale);\n            ImU32 overline_col = GetColorU32(tab_bar_focused ? ImGuiCol_TabSelectedOverline : ImGuiCol_TabDimmedSelectedOverline);\n            if (style.TabRounding > 0.0f)\n            {\n                float rounding = style.TabRounding;\n                display_draw_list->PathArcToFast(tl + ImVec2(+rounding, +rounding), rounding, 7, 9);\n                display_draw_list->PathArcToFast(tr + ImVec2(-rounding, +rounding), rounding, 9, 11);\n                display_draw_list->PathStroke(overline_col, 0, style.TabBarOverlineSize);\n            }\n            else\n            {\n                display_draw_list->AddLine(tl - ImVec2(0.5f, 0.5f), tr - ImVec2(0.5f, 0.5f), overline_col, style.TabBarOverlineSize);\n            }\n        }\n        RenderNavCursor(bb, id);\n\n        // Select with right mouse button. This is so the common idiom for context menu automatically highlight the current widget.\n        const bool hovered_unblocked = IsItemHovered(ImGuiHoveredFlags_AllowWhenBlockedByPopup);\n        if (tab_bar->SelectedTabId != tab->ID && hovered_unblocked && (IsMouseClicked(1) || IsMouseReleased(1)) && !is_tab_button)\n            TabBarQueueFocus(tab_bar, tab);\n\n        if (tab_bar->Flags & ImGuiTabBarFlags_NoCloseWithMiddleMouseButton)\n            flags |= ImGuiTabItemFlags_NoCloseWithMiddleMouseButton;\n\n        // Render tab label, process close button\n        const ImGuiID close_button_id = p_open ? GetIDWithSeed(\"#CLOSE\", NULL, id) : 0;\n        bool just_closed;\n        bool text_clipped;\n        TabItemLabelAndCloseButton(display_draw_list, bb, tab_just_unsaved ? (flags & ~ImGuiTabItemFlags_UnsavedDocument) : flags, tab_bar->FramePadding, label, id, close_button_id, tab_contents_visible, &just_closed, &text_clipped);\n        if (just_closed && p_open != NULL)\n        {\n            *p_open = false;\n            TabBarCloseTab(tab_bar, tab);\n        }\n\n        // Tooltip\n        // (Won't work over the close button because ItemOverlap systems messes up with HoveredIdTimer-> seems ok)\n        // (We test IsItemHovered() to discard e.g. when another item is active or drag and drop over the tab bar, which g.HoveredId ignores)\n        // FIXME: This is a mess.\n        // FIXME: We may want disabled tab to still display the tooltip?\n        if (text_clipped && g.HoveredId == id && !held)\n            if (!(tab_bar->Flags & ImGuiTabBarFlags_NoTooltip) && !(tab->Flags & ImGuiTabItemFlags_NoTooltip))\n                SetItemTooltip(\"%.*s\", (int)(FindRenderedTextEnd(label) - label), label);\n    }\n\n    // Restore main window position so user can draw there\n    if (want_clip_rect)\n        PopClipRect();\n    window->DC.CursorPos = backup_main_cursor_pos;\n\n    IM_ASSERT(!is_tab_button || !(tab_bar->SelectedTabId == tab->ID && is_tab_button)); // TabItemButton should not be selected\n    if (is_tab_button)\n        return pressed;\n    return tab_contents_visible;\n}\n\n// [Public] This is call is 100% optional but it allows to remove some one-frame glitches when a tab has been unexpectedly removed.\n// To use it to need to call the function SetTabItemClosed() between BeginTabBar() and EndTabBar().\n// Tabs closed by the close button will automatically be flagged to avoid this issue.\nvoid    ImGui::SetTabItemClosed(const char* label)\n{\n    ImGuiContext& g = *GImGui;\n    bool is_within_manual_tab_bar = g.CurrentTabBar && !(g.CurrentTabBar->Flags & ImGuiTabBarFlags_DockNode);\n    if (is_within_manual_tab_bar)\n    {\n        ImGuiTabBar* tab_bar = g.CurrentTabBar;\n        ImGuiID tab_id = TabBarCalcTabID(tab_bar, label, NULL);\n        if (ImGuiTabItem* tab = TabBarFindTabByID(tab_bar, tab_id))\n            tab->WantClose = true; // Will be processed by next call to TabBarLayout()\n    }\n}\n\nImVec2 ImGui::TabItemCalcSize(const char* label, bool has_close_button_or_unsaved_marker)\n{\n    ImGuiContext& g = *GImGui;\n    ImVec2 label_size = CalcTextSize(label, NULL, true);\n    ImVec2 size = ImVec2(label_size.x + g.Style.FramePadding.x, label_size.y + g.Style.FramePadding.y * 2.0f);\n    if (has_close_button_or_unsaved_marker)\n        size.x += g.Style.FramePadding.x + (g.Style.ItemInnerSpacing.x + g.FontSize); // We use Y intentionally to fit the close button circle.\n    else\n        size.x += g.Style.FramePadding.x + 1.0f;\n    return ImVec2(ImMin(size.x, TabBarCalcMaxTabWidth()), size.y);\n}\n\nImVec2 ImGui::TabItemCalcSize(ImGuiWindow*)\n{\n    IM_ASSERT(0); // This function exists to facilitate merge with 'docking' branch.\n    return ImVec2(0.0f, 0.0f);\n}\n\nvoid ImGui::TabItemBackground(ImDrawList* draw_list, const ImRect& bb, ImGuiTabItemFlags flags, ImU32 col)\n{\n    // While rendering tabs, we trim 1 pixel off the top of our bounding box so they can fit within a regular frame height while looking \"detached\" from it.\n    ImGuiContext& g = *GImGui;\n    const float width = bb.GetWidth();\n    IM_UNUSED(flags);\n    IM_ASSERT(width > 0.0f);\n    const float rounding = ImMax(0.0f, ImMin((flags & ImGuiTabItemFlags_Button) ? g.Style.FrameRounding : g.Style.TabRounding, width * 0.5f - 1.0f));\n    const float y1 = bb.Min.y + 1.0f;\n    const float y2 = bb.Max.y - g.Style.TabBarBorderSize;\n    draw_list->PathLineTo(ImVec2(bb.Min.x, y2));\n    draw_list->PathArcToFast(ImVec2(bb.Min.x + rounding, y1 + rounding), rounding, 6, 9);\n    draw_list->PathArcToFast(ImVec2(bb.Max.x - rounding, y1 + rounding), rounding, 9, 12);\n    draw_list->PathLineTo(ImVec2(bb.Max.x, y2));\n    draw_list->PathFillConvex(col);\n    if (g.Style.TabBorderSize > 0.0f)\n    {\n        draw_list->PathLineTo(ImVec2(bb.Min.x + 0.5f, y2));\n        draw_list->PathArcToFast(ImVec2(bb.Min.x + rounding + 0.5f, y1 + rounding + 0.5f), rounding, 6, 9);\n        draw_list->PathArcToFast(ImVec2(bb.Max.x - rounding - 0.5f, y1 + rounding + 0.5f), rounding, 9, 12);\n        draw_list->PathLineTo(ImVec2(bb.Max.x - 0.5f, y2));\n        draw_list->PathStroke(GetColorU32(ImGuiCol_Border), 0, g.Style.TabBorderSize);\n    }\n}\n\n// Render text label (with custom clipping) + Unsaved Document marker + Close Button logic\n// We tend to lock style.FramePadding for a given tab-bar, hence the 'frame_padding' parameter.\nvoid ImGui::TabItemLabelAndCloseButton(ImDrawList* draw_list, const ImRect& bb, ImGuiTabItemFlags flags, ImVec2 frame_padding, const char* label, ImGuiID tab_id, ImGuiID close_button_id, bool is_contents_visible, bool* out_just_closed, bool* out_text_clipped)\n{\n    ImGuiContext& g = *GImGui;\n    ImVec2 label_size = CalcTextSize(label, NULL, true);\n\n    if (out_just_closed)\n        *out_just_closed = false;\n    if (out_text_clipped)\n        *out_text_clipped = false;\n\n    if (bb.GetWidth() <= 1.0f)\n        return;\n\n    // In Style V2 we'll have full override of all colors per state (e.g. focused, selected)\n    // But right now if you want to alter text color of tabs this is what you need to do.\n#if 0\n    const float backup_alpha = g.Style.Alpha;\n    if (!is_contents_visible)\n        g.Style.Alpha *= 0.7f;\n#endif\n\n    // Render text label (with clipping + alpha gradient) + unsaved marker\n    ImRect text_pixel_clip_bb(bb.Min.x + frame_padding.x, bb.Min.y + frame_padding.y, bb.Max.x - frame_padding.x, bb.Max.y);\n    ImRect text_ellipsis_clip_bb = text_pixel_clip_bb;\n\n    // Return clipped state ignoring the close button\n    if (out_text_clipped)\n    {\n        *out_text_clipped = (text_ellipsis_clip_bb.Min.x + label_size.x) > text_pixel_clip_bb.Max.x;\n        //draw_list->AddCircle(text_ellipsis_clip_bb.Min, 3.0f, *out_text_clipped ? IM_COL32(255, 0, 0, 255) : IM_COL32(0, 255, 0, 255));\n    }\n\n    const float button_sz = g.FontSize;\n    const ImVec2 button_pos(ImMax(bb.Min.x, bb.Max.x - frame_padding.x - button_sz), bb.Min.y + frame_padding.y);\n\n    // Close Button & Unsaved Marker\n    // We are relying on a subtle and confusing distinction between 'hovered' and 'g.HoveredId' which happens because we are using ImGuiButtonFlags_AllowOverlapMode + SetItemAllowOverlap()\n    //  'hovered' will be true when hovering the Tab but NOT when hovering the close button\n    //  'g.HoveredId==id' will be true when hovering the Tab including when hovering the close button\n    //  'g.ActiveId==close_button_id' will be true when we are holding on the close button, in which case both hovered booleans are false\n    bool close_button_pressed = false;\n    bool close_button_visible = false;\n    bool is_hovered = g.HoveredId == tab_id || g.HoveredId == close_button_id || g.ActiveId == tab_id || g.ActiveId == close_button_id; // Any interaction account for this too.\n\n    if (close_button_id != 0)\n    {\n        if (is_contents_visible)\n            close_button_visible = (g.Style.TabCloseButtonMinWidthSelected < 0.0f) ? true : (is_hovered && bb.GetWidth() >= ImMax(button_sz, g.Style.TabCloseButtonMinWidthSelected));\n        else\n            close_button_visible = (g.Style.TabCloseButtonMinWidthUnselected < 0.0f) ? true : (is_hovered && bb.GetWidth() >= ImMax(button_sz, g.Style.TabCloseButtonMinWidthUnselected));\n    }\n\n    // When tabs/document is unsaved, the unsaved marker takes priority over the close button.\n    const bool unsaved_marker_visible = (flags & ImGuiTabItemFlags_UnsavedDocument) != 0 && (button_pos.x + button_sz <= bb.Max.x) && (!close_button_visible || !is_hovered);\n    if (unsaved_marker_visible)\n    {\n        const ImRect bullet_bb(button_pos, button_pos + ImVec2(button_sz, button_sz));\n        RenderBullet(draw_list, bullet_bb.GetCenter(), GetColorU32(ImGuiCol_Text));\n    }\n    else if (close_button_visible)\n    {\n        ImGuiLastItemData last_item_backup = g.LastItemData;\n        if (CloseButton(close_button_id, button_pos))\n            close_button_pressed = true;\n        g.LastItemData = last_item_backup;\n\n        // Close with middle mouse button\n        if (is_hovered && !(flags & ImGuiTabItemFlags_NoCloseWithMiddleMouseButton) && IsMouseClicked(2))\n            close_button_pressed = true;\n    }\n\n    // This is all rather complicated\n    // (the main idea is that because the close button only appears on hover, we don't want it to alter the ellipsis position)\n    // FIXME: if FramePadding is noticeably large, ellipsis_max_x will be wrong here (e.g. #3497), maybe for consistency that parameter of RenderTextEllipsis() shouldn't exist..\n    float ellipsis_max_x = close_button_visible ? text_pixel_clip_bb.Max.x : bb.Max.x - 1.0f;\n    if (close_button_visible || unsaved_marker_visible)\n    {\n        text_pixel_clip_bb.Max.x -= close_button_visible ? (button_sz) : (button_sz * 0.80f);\n        text_ellipsis_clip_bb.Max.x -= unsaved_marker_visible ? (button_sz * 0.80f) : 0.0f;\n        ellipsis_max_x = text_pixel_clip_bb.Max.x;\n    }\n    LogSetNextTextDecoration(\"/\", \"\\\\\");\n    RenderTextEllipsis(draw_list, text_ellipsis_clip_bb.Min, text_ellipsis_clip_bb.Max, text_pixel_clip_bb.Max.x, ellipsis_max_x, label, NULL, &label_size);\n\n#if 0\n    if (!is_contents_visible)\n        g.Style.Alpha = backup_alpha;\n#endif\n\n    if (out_just_closed)\n        *out_just_closed = close_button_pressed;\n}\n\n\n#endif // #ifndef IMGUI_DISABLE\n"
  },
  {
    "path": "src/DesktopPlusUI/imgui/imstb_rectpack.h",
    "content": "// [DEAR IMGUI]\n// This is a slightly modified version of stb_rect_pack.h 1.01.\n// Grep for [DEAR IMGUI] to find the changes.\n// \n// stb_rect_pack.h - v1.01 - public domain - rectangle packing\n// Sean Barrett 2014\n//\n// Useful for e.g. packing rectangular textures into an atlas.\n// Does not do rotation.\n//\n// Before #including,\n//\n//    #define STB_RECT_PACK_IMPLEMENTATION\n//\n// in the file that you want to have the implementation.\n//\n// Not necessarily the awesomest packing method, but better than\n// the totally naive one in stb_truetype (which is primarily what\n// this is meant to replace).\n//\n// Has only had a few tests run, may have issues.\n//\n// More docs to come.\n//\n// No memory allocations; uses qsort() and assert() from stdlib.\n// Can override those by defining STBRP_SORT and STBRP_ASSERT.\n//\n// This library currently uses the Skyline Bottom-Left algorithm.\n//\n// Please note: better rectangle packers are welcome! Please\n// implement them to the same API, but with a different init\n// function.\n//\n// Credits\n//\n//  Library\n//    Sean Barrett\n//  Minor features\n//    Martins Mozeiko\n//    github:IntellectualKitty\n//\n//  Bugfixes / warning fixes\n//    Jeremy Jaussaud\n//    Fabian Giesen\n//\n// Version history:\n//\n//     1.01  (2021-07-11)  always use large rect mode, expose STBRP__MAXVAL in public section\n//     1.00  (2019-02-25)  avoid small space waste; gracefully fail too-wide rectangles\n//     0.99  (2019-02-07)  warning fixes\n//     0.11  (2017-03-03)  return packing success/fail result\n//     0.10  (2016-10-25)  remove cast-away-const to avoid warnings\n//     0.09  (2016-08-27)  fix compiler warnings\n//     0.08  (2015-09-13)  really fix bug with empty rects (w=0 or h=0)\n//     0.07  (2015-09-13)  fix bug with empty rects (w=0 or h=0)\n//     0.06  (2015-04-15)  added STBRP_SORT to allow replacing qsort\n//     0.05:  added STBRP_ASSERT to allow replacing assert\n//     0.04:  fixed minor bug in STBRP_LARGE_RECTS support\n//     0.01:  initial release\n//\n// LICENSE\n//\n//   See end of file for license information.\n\n//////////////////////////////////////////////////////////////////////////////\n//\n//       INCLUDE SECTION\n//\n\n#ifndef STB_INCLUDE_STB_RECT_PACK_H\n#define STB_INCLUDE_STB_RECT_PACK_H\n\n#define STB_RECT_PACK_VERSION  1\n\n#ifdef STBRP_STATIC\n#define STBRP_DEF static\n#else\n#define STBRP_DEF extern\n#endif\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif\n\ntypedef struct stbrp_context stbrp_context;\ntypedef struct stbrp_node    stbrp_node;\ntypedef struct stbrp_rect    stbrp_rect;\n\ntypedef int            stbrp_coord;\n\n#define STBRP__MAXVAL  0x7fffffff\n// Mostly for internal use, but this is the maximum supported coordinate value.\n\nSTBRP_DEF int stbrp_pack_rects (stbrp_context *context, stbrp_rect *rects, int num_rects);\n// Assign packed locations to rectangles. The rectangles are of type\n// 'stbrp_rect' defined below, stored in the array 'rects', and there\n// are 'num_rects' many of them.\n//\n// Rectangles which are successfully packed have the 'was_packed' flag\n// set to a non-zero value and 'x' and 'y' store the minimum location\n// on each axis (i.e. bottom-left in cartesian coordinates, top-left\n// if you imagine y increasing downwards). Rectangles which do not fit\n// have the 'was_packed' flag set to 0.\n//\n// You should not try to access the 'rects' array from another thread\n// while this function is running, as the function temporarily reorders\n// the array while it executes.\n//\n// To pack into another rectangle, you need to call stbrp_init_target\n// again. To continue packing into the same rectangle, you can call\n// this function again. Calling this multiple times with multiple rect\n// arrays will probably produce worse packing results than calling it\n// a single time with the full rectangle array, but the option is\n// available.\n//\n// The function returns 1 if all of the rectangles were successfully\n// packed and 0 otherwise.\n\nstruct stbrp_rect\n{\n   // reserved for your use:\n   int            id;\n\n   // input:\n   stbrp_coord    w, h;\n\n   // output:\n   stbrp_coord    x, y;\n   int            was_packed;  // non-zero if valid packing\n\n}; // 16 bytes, nominally\n\n\nSTBRP_DEF void stbrp_init_target (stbrp_context *context, int width, int height, stbrp_node *nodes, int num_nodes);\n// Initialize a rectangle packer to:\n//    pack a rectangle that is 'width' by 'height' in dimensions\n//    using temporary storage provided by the array 'nodes', which is 'num_nodes' long\n//\n// You must call this function every time you start packing into a new target.\n//\n// There is no \"shutdown\" function. The 'nodes' memory must stay valid for\n// the following stbrp_pack_rects() call (or calls), but can be freed after\n// the call (or calls) finish.\n//\n// Note: to guarantee best results, either:\n//       1. make sure 'num_nodes' >= 'width'\n//   or  2. call stbrp_allow_out_of_mem() defined below with 'allow_out_of_mem = 1'\n//\n// If you don't do either of the above things, widths will be quantized to multiples\n// of small integers to guarantee the algorithm doesn't run out of temporary storage.\n//\n// If you do #2, then the non-quantized algorithm will be used, but the algorithm\n// may run out of temporary storage and be unable to pack some rectangles.\n\nSTBRP_DEF void stbrp_setup_allow_out_of_mem (stbrp_context *context, int allow_out_of_mem);\n// Optionally call this function after init but before doing any packing to\n// change the handling of the out-of-temp-memory scenario, described above.\n// If you call init again, this will be reset to the default (false).\n\n\nSTBRP_DEF void stbrp_setup_heuristic (stbrp_context *context, int heuristic);\n// Optionally select which packing heuristic the library should use. Different\n// heuristics will produce better/worse results for different data sets.\n// If you call init again, this will be reset to the default.\n\nenum\n{\n   STBRP_HEURISTIC_Skyline_default=0,\n   STBRP_HEURISTIC_Skyline_BL_sortHeight = STBRP_HEURISTIC_Skyline_default,\n   STBRP_HEURISTIC_Skyline_BF_sortHeight\n};\n\n\n//////////////////////////////////////////////////////////////////////////////\n//\n// the details of the following structures don't matter to you, but they must\n// be visible so you can handle the memory allocations for them\n\nstruct stbrp_node\n{\n   stbrp_coord  x,y;\n   stbrp_node  *next;\n};\n\nstruct stbrp_context\n{\n   int width;\n   int height;\n   int align;\n   int init_mode;\n   int heuristic;\n   int num_nodes;\n   stbrp_node *active_head;\n   stbrp_node *free_head;\n   stbrp_node extra[2]; // we allocate two extra nodes so optimal user-node-count is 'width' not 'width+2'\n};\n\n#ifdef __cplusplus\n}\n#endif\n\n#endif\n\n//////////////////////////////////////////////////////////////////////////////\n//\n//     IMPLEMENTATION SECTION\n//\n\n#ifdef STB_RECT_PACK_IMPLEMENTATION\n#ifndef STBRP_SORT\n#include <stdlib.h>\n#define STBRP_SORT qsort\n#endif\n\n#ifndef STBRP_ASSERT\n#include <assert.h>\n#define STBRP_ASSERT assert\n#endif\n\n#ifdef _MSC_VER\n#define STBRP__NOTUSED(v)  (void)(v)\n#define STBRP__CDECL       __cdecl\n#else\n#define STBRP__NOTUSED(v)  (void)sizeof(v)\n#define STBRP__CDECL\n#endif\n\nenum\n{\n   STBRP__INIT_skyline = 1\n};\n\nSTBRP_DEF void stbrp_setup_heuristic(stbrp_context *context, int heuristic)\n{\n   switch (context->init_mode) {\n      case STBRP__INIT_skyline:\n         STBRP_ASSERT(heuristic == STBRP_HEURISTIC_Skyline_BL_sortHeight || heuristic == STBRP_HEURISTIC_Skyline_BF_sortHeight);\n         context->heuristic = heuristic;\n         break;\n      default:\n         STBRP_ASSERT(0);\n   }\n}\n\nSTBRP_DEF void stbrp_setup_allow_out_of_mem(stbrp_context *context, int allow_out_of_mem)\n{\n   if (allow_out_of_mem)\n      // if it's ok to run out of memory, then don't bother aligning them;\n      // this gives better packing, but may fail due to OOM (even though\n      // the rectangles easily fit). @TODO a smarter approach would be to only\n      // quantize once we've hit OOM, then we could get rid of this parameter.\n      context->align = 1;\n   else {\n      // if it's not ok to run out of memory, then quantize the widths\n      // so that num_nodes is always enough nodes.\n      //\n      // I.e. num_nodes * align >= width\n      //                  align >= width / num_nodes\n      //                  align = ceil(width/num_nodes)\n\n      context->align = (context->width + context->num_nodes-1) / context->num_nodes;\n   }\n}\n\nSTBRP_DEF void stbrp_init_target(stbrp_context *context, int width, int height, stbrp_node *nodes, int num_nodes)\n{\n   int i;\n\n   for (i=0; i < num_nodes-1; ++i)\n      nodes[i].next = &nodes[i+1];\n   nodes[i].next = NULL;\n   context->init_mode = STBRP__INIT_skyline;\n   context->heuristic = STBRP_HEURISTIC_Skyline_default;\n   context->free_head = &nodes[0];\n   context->active_head = &context->extra[0];\n   context->width = width;\n   context->height = height;\n   context->num_nodes = num_nodes;\n   stbrp_setup_allow_out_of_mem(context, 0);\n\n   // node 0 is the full width, node 1 is the sentinel (lets us not store width explicitly)\n   context->extra[0].x = 0;\n   context->extra[0].y = 0;\n   context->extra[0].next = &context->extra[1];\n   context->extra[1].x = (stbrp_coord) width;\n   context->extra[1].y = (1<<30);\n   context->extra[1].next = NULL;\n}\n\n// find minimum y position if it starts at x1\nstatic int stbrp__skyline_find_min_y(stbrp_context *c, stbrp_node *first, int x0, int width, int *pwaste)\n{\n   stbrp_node *node = first;\n   int x1 = x0 + width;\n   int min_y, visited_width, waste_area;\n\n   STBRP__NOTUSED(c);\n\n   STBRP_ASSERT(first->x <= x0);\n\n   #if 0\n   // skip in case we're past the node\n   while (node->next->x <= x0)\n      ++node;\n   #else\n   STBRP_ASSERT(node->next->x > x0); // we ended up handling this in the caller for efficiency\n   #endif\n\n   STBRP_ASSERT(node->x <= x0);\n\n   min_y = 0;\n   waste_area = 0;\n   visited_width = 0;\n   while (node->x < x1) {\n      if (node->y > min_y) {\n         // raise min_y higher.\n         // we've accounted for all waste up to min_y,\n         // but we'll now add more waste for everything we've visted\n         waste_area += visited_width * (node->y - min_y);\n         min_y = node->y;\n         // the first time through, visited_width might be reduced\n         if (node->x < x0)\n            visited_width += node->next->x - x0;\n         else\n            visited_width += node->next->x - node->x;\n      } else {\n         // add waste area\n         int under_width = node->next->x - node->x;\n         if (under_width + visited_width > width)\n            under_width = width - visited_width;\n         waste_area += under_width * (min_y - node->y);\n         visited_width += under_width;\n      }\n      node = node->next;\n   }\n\n   *pwaste = waste_area;\n   return min_y;\n}\n\ntypedef struct\n{\n   int x,y;\n   stbrp_node **prev_link;\n} stbrp__findresult;\n\nstatic stbrp__findresult stbrp__skyline_find_best_pos(stbrp_context *c, int width, int height)\n{\n   int best_waste = (1<<30), best_x, best_y = (1 << 30);\n   stbrp__findresult fr;\n   stbrp_node **prev, *node, *tail, **best = NULL;\n\n   // align to multiple of c->align\n   width = (width + c->align - 1);\n   width -= width % c->align;\n   STBRP_ASSERT(width % c->align == 0);\n\n   // if it can't possibly fit, bail immediately\n   if (width > c->width || height > c->height) {\n      fr.prev_link = NULL;\n      fr.x = fr.y = 0;\n      return fr;\n   }\n\n   node = c->active_head;\n   prev = &c->active_head;\n   while (node->x + width <= c->width) {\n      int y,waste;\n      y = stbrp__skyline_find_min_y(c, node, node->x, width, &waste);\n      if (c->heuristic == STBRP_HEURISTIC_Skyline_BL_sortHeight) { // actually just want to test BL\n         // bottom left\n         if (y < best_y) {\n            best_y = y;\n            best = prev;\n         }\n      } else {\n         // best-fit\n         if (y + height <= c->height) {\n            // can only use it if it first vertically\n            if (y < best_y || (y == best_y && waste < best_waste)) {\n               best_y = y;\n               best_waste = waste;\n               best = prev;\n            }\n         }\n      }\n      prev = &node->next;\n      node = node->next;\n   }\n\n   best_x = (best == NULL) ? 0 : (*best)->x;\n\n   // if doing best-fit (BF), we also have to try aligning right edge to each node position\n   //\n   // e.g, if fitting\n   //\n   //     ____________________\n   //    |____________________|\n   //\n   //            into\n   //\n   //   |                         |\n   //   |             ____________|\n   //   |____________|\n   //\n   // then right-aligned reduces waste, but bottom-left BL is always chooses left-aligned\n   //\n   // This makes BF take about 2x the time\n\n   if (c->heuristic == STBRP_HEURISTIC_Skyline_BF_sortHeight) {\n      tail = c->active_head;\n      node = c->active_head;\n      prev = &c->active_head;\n      // find first node that's admissible\n      while (tail->x < width)\n         tail = tail->next;\n      while (tail) {\n         int xpos = tail->x - width;\n         int y,waste;\n         STBRP_ASSERT(xpos >= 0);\n         // find the left position that matches this\n         while (node->next->x <= xpos) {\n            prev = &node->next;\n            node = node->next;\n         }\n         STBRP_ASSERT(node->next->x > xpos && node->x <= xpos);\n         y = stbrp__skyline_find_min_y(c, node, xpos, width, &waste);\n         if (y + height <= c->height) {\n            if (y <= best_y) {\n               if (y < best_y || waste < best_waste || (waste==best_waste && xpos < best_x)) {\n                  best_x = xpos;\n                  //STBRP_ASSERT(y <= best_y); [DEAR IMGUI]\n                  best_y = y;\n                  best_waste = waste;\n                  best = prev;\n               }\n            }\n         }\n         tail = tail->next;\n      }\n   }\n\n   fr.prev_link = best;\n   fr.x = best_x;\n   fr.y = best_y;\n   return fr;\n}\n\nstatic stbrp__findresult stbrp__skyline_pack_rectangle(stbrp_context *context, int width, int height)\n{\n   // find best position according to heuristic\n   stbrp__findresult res = stbrp__skyline_find_best_pos(context, width, height);\n   stbrp_node *node, *cur;\n\n   // bail if:\n   //    1. it failed\n   //    2. the best node doesn't fit (we don't always check this)\n   //    3. we're out of memory\n   if (res.prev_link == NULL || res.y + height > context->height || context->free_head == NULL) {\n      res.prev_link = NULL;\n      return res;\n   }\n\n   // on success, create new node\n   node = context->free_head;\n   node->x = (stbrp_coord) res.x;\n   node->y = (stbrp_coord) (res.y + height);\n\n   context->free_head = node->next;\n\n   // insert the new node into the right starting point, and\n   // let 'cur' point to the remaining nodes needing to be\n   // stiched back in\n\n   cur = *res.prev_link;\n   if (cur->x < res.x) {\n      // preserve the existing one, so start testing with the next one\n      stbrp_node *next = cur->next;\n      cur->next = node;\n      cur = next;\n   } else {\n      *res.prev_link = node;\n   }\n\n   // from here, traverse cur and free the nodes, until we get to one\n   // that shouldn't be freed\n   while (cur->next && cur->next->x <= res.x + width) {\n      stbrp_node *next = cur->next;\n      // move the current node to the free list\n      cur->next = context->free_head;\n      context->free_head = cur;\n      cur = next;\n   }\n\n   // stitch the list back in\n   node->next = cur;\n\n   if (cur->x < res.x + width)\n      cur->x = (stbrp_coord) (res.x + width);\n\n#ifdef _DEBUG\n   cur = context->active_head;\n   while (cur->x < context->width) {\n      STBRP_ASSERT(cur->x < cur->next->x);\n      cur = cur->next;\n   }\n   STBRP_ASSERT(cur->next == NULL);\n\n   {\n      int count=0;\n      cur = context->active_head;\n      while (cur) {\n         cur = cur->next;\n         ++count;\n      }\n      cur = context->free_head;\n      while (cur) {\n         cur = cur->next;\n         ++count;\n      }\n      STBRP_ASSERT(count == context->num_nodes+2);\n   }\n#endif\n\n   return res;\n}\n\nstatic int STBRP__CDECL rect_height_compare(const void *a, const void *b)\n{\n   const stbrp_rect *p = (const stbrp_rect *) a;\n   const stbrp_rect *q = (const stbrp_rect *) b;\n   if (p->h > q->h)\n      return -1;\n   if (p->h < q->h)\n      return  1;\n   return (p->w > q->w) ? -1 : (p->w < q->w);\n}\n\nstatic int STBRP__CDECL rect_original_order(const void *a, const void *b)\n{\n   const stbrp_rect *p = (const stbrp_rect *) a;\n   const stbrp_rect *q = (const stbrp_rect *) b;\n   return (p->was_packed < q->was_packed) ? -1 : (p->was_packed > q->was_packed);\n}\n\nSTBRP_DEF int stbrp_pack_rects(stbrp_context *context, stbrp_rect *rects, int num_rects)\n{\n   int i, all_rects_packed = 1;\n\n   // we use the 'was_packed' field internally to allow sorting/unsorting\n   for (i=0; i < num_rects; ++i) {\n      rects[i].was_packed = i;\n   }\n\n   // sort according to heuristic\n   STBRP_SORT(rects, num_rects, sizeof(rects[0]), rect_height_compare);\n\n   for (i=0; i < num_rects; ++i) {\n      if (rects[i].w == 0 || rects[i].h == 0) {\n         rects[i].x = rects[i].y = 0;  // empty rect needs no space\n      } else {\n         stbrp__findresult fr = stbrp__skyline_pack_rectangle(context, rects[i].w, rects[i].h);\n         if (fr.prev_link) {\n            rects[i].x = (stbrp_coord) fr.x;\n            rects[i].y = (stbrp_coord) fr.y;\n         } else {\n            rects[i].x = rects[i].y = STBRP__MAXVAL;\n         }\n      }\n   }\n\n   // unsort\n   STBRP_SORT(rects, num_rects, sizeof(rects[0]), rect_original_order);\n\n   // set was_packed flags and all_rects_packed status\n   for (i=0; i < num_rects; ++i) {\n      rects[i].was_packed = !(rects[i].x == STBRP__MAXVAL && rects[i].y == STBRP__MAXVAL);\n      if (!rects[i].was_packed)\n         all_rects_packed = 0;\n   }\n\n   // return the all_rects_packed status\n   return all_rects_packed;\n}\n#endif\n\n/*\n------------------------------------------------------------------------------\nThis software is available under 2 licenses -- choose whichever you prefer.\n------------------------------------------------------------------------------\nALTERNATIVE A - MIT License\nCopyright (c) 2017 Sean Barrett\nPermission is hereby granted, free of charge, to any person obtaining a copy of\nthis software and associated documentation files (the \"Software\"), to deal in\nthe Software without restriction, including without limitation the rights to\nuse, copy, modify, merge, publish, distribute, sublicense, and/or sell copies\nof the Software, and to permit persons to whom the Software is furnished to do\nso, subject to the following conditions:\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n------------------------------------------------------------------------------\nALTERNATIVE B - Public Domain (www.unlicense.org)\nThis is free and unencumbered software released into the public domain.\nAnyone is free to copy, modify, publish, use, compile, sell, or distribute this\nsoftware, either in source code form or as a compiled binary, for any purpose,\ncommercial or non-commercial, and by any means.\nIn jurisdictions that recognize copyright laws, the author or authors of this\nsoftware dedicate any and all copyright interest in the software to the public\ndomain. We make this dedication for the benefit of the public at large and to\nthe detriment of our heirs and successors. We intend this dedication to be an\novert act of relinquishment in perpetuity of all present and future rights to\nthis software under copyright law.\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN\nACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION\nWITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n------------------------------------------------------------------------------\n*/\n"
  },
  {
    "path": "src/DesktopPlusUI/imgui/imstb_textedit.h",
    "content": "// [DEAR IMGUI]\n// This is a slightly modified version of stb_textedit.h 1.14.\n// Those changes would need to be pushed into nothings/stb:\n// - Fix in stb_textedit_discard_redo (see https://github.com/nothings/stb/issues/321)\n// - Fix in stb_textedit_find_charpos to handle last line (see https://github.com/ocornut/imgui/issues/6000 + #6783)\n// - Added name to struct or it may be forward declared in our code.\n// - Added UTF-8 support (see https://github.com/nothings/stb/issues/188 + https://github.com/ocornut/imgui/pull/7925)\n// Grep for [DEAR IMGUI] to find the changes.\n// - Also renamed macros used or defined outside of IMSTB_TEXTEDIT_IMPLEMENTATION block from STB_TEXTEDIT_* to IMSTB_TEXTEDIT_*\n\n// stb_textedit.h - v1.14  - public domain - Sean Barrett\n// Development of this library was sponsored by RAD Game Tools\n//\n// This C header file implements the guts of a multi-line text-editing\n// widget; you implement display, word-wrapping, and low-level string\n// insertion/deletion, and stb_textedit will map user inputs into\n// insertions & deletions, plus updates to the cursor position,\n// selection state, and undo state.\n//\n// It is intended for use in games and other systems that need to build\n// their own custom widgets and which do not have heavy text-editing\n// requirements (this library is not recommended for use for editing large\n// texts, as its performance does not scale and it has limited undo).\n//\n// Non-trivial behaviors are modelled after Windows text controls.\n//\n//\n// LICENSE\n//\n// See end of file for license information.\n//\n//\n// DEPENDENCIES\n//\n// Uses the C runtime function 'memmove', which you can override\n// by defining IMSTB_TEXTEDIT_memmove before the implementation.\n// Uses no other functions. Performs no runtime allocations.\n//\n//\n// VERSION HISTORY\n//\n//   1.14 (2021-07-11) page up/down, various fixes\n//   1.13 (2019-02-07) fix bug in undo size management\n//   1.12 (2018-01-29) user can change STB_TEXTEDIT_KEYTYPE, fix redo to avoid crash\n//   1.11 (2017-03-03) fix HOME on last line, dragging off single-line textfield\n//   1.10 (2016-10-25) suppress warnings about casting away const with -Wcast-qual\n//   1.9  (2016-08-27) customizable move-by-word\n//   1.8  (2016-04-02) better keyboard handling when mouse button is down\n//   1.7  (2015-09-13) change y range handling in case baseline is non-0\n//   1.6  (2015-04-15) allow STB_TEXTEDIT_memmove\n//   1.5  (2014-09-10) add support for secondary keys for OS X\n//   1.4  (2014-08-17) fix signed/unsigned warnings\n//   1.3  (2014-06-19) fix mouse clicking to round to nearest char boundary\n//   1.2  (2014-05-27) fix some RAD types that had crept into the new code\n//   1.1  (2013-12-15) move-by-word (requires STB_TEXTEDIT_IS_SPACE )\n//   1.0  (2012-07-26) improve documentation, initial public release\n//   0.3  (2012-02-24) bugfixes, single-line mode; insert mode\n//   0.2  (2011-11-28) fixes to undo/redo\n//   0.1  (2010-07-08) initial version\n//\n// ADDITIONAL CONTRIBUTORS\n//\n//   Ulf Winklemann: move-by-word in 1.1\n//   Fabian Giesen: secondary key inputs in 1.5\n//   Martins Mozeiko: STB_TEXTEDIT_memmove in 1.6\n//   Louis Schnellbach: page up/down in 1.14\n//\n//   Bugfixes:\n//      Scott Graham\n//      Daniel Keller\n//      Omar Cornut\n//      Dan Thompson\n//\n// USAGE\n//\n// This file behaves differently depending on what symbols you define\n// before including it.\n//\n//\n// Header-file mode:\n//\n//   If you do not define STB_TEXTEDIT_IMPLEMENTATION before including this,\n//   it will operate in \"header file\" mode. In this mode, it declares a\n//   single public symbol, STB_TexteditState, which encapsulates the current\n//   state of a text widget (except for the string, which you will store\n//   separately).\n//\n//   To compile in this mode, you must define STB_TEXTEDIT_CHARTYPE to a\n//   primitive type that defines a single character (e.g. char, wchar_t, etc).\n//\n//   To save space or increase undo-ability, you can optionally define the\n//   following things that are used by the undo system:\n//\n//      STB_TEXTEDIT_POSITIONTYPE         small int type encoding a valid cursor position\n//      STB_TEXTEDIT_UNDOSTATECOUNT       the number of undo states to allow\n//      STB_TEXTEDIT_UNDOCHARCOUNT        the number of characters to store in the undo buffer\n//\n//   If you don't define these, they are set to permissive types and\n//   moderate sizes. The undo system does no memory allocations, so\n//   it grows STB_TexteditState by the worst-case storage which is (in bytes):\n//\n//        [4 + 3 * sizeof(STB_TEXTEDIT_POSITIONTYPE)] * STB_TEXTEDIT_UNDOSTATECOUNT\n//      +          sizeof(STB_TEXTEDIT_CHARTYPE)      * STB_TEXTEDIT_UNDOCHARCOUNT\n//\n//\n// Implementation mode:\n//\n//   If you define STB_TEXTEDIT_IMPLEMENTATION before including this, it\n//   will compile the implementation of the text edit widget, depending\n//   on a large number of symbols which must be defined before the include.\n//\n//   The implementation is defined only as static functions. You will then\n//   need to provide your own APIs in the same file which will access the\n//   static functions.\n//\n//   The basic concept is that you provide a \"string\" object which\n//   behaves like an array of characters. stb_textedit uses indices to\n//   refer to positions in the string, implicitly representing positions\n//   in the displayed textedit. This is true for both plain text and\n//   rich text; even with rich text stb_truetype interacts with your\n//   code as if there was an array of all the displayed characters.\n//\n// Symbols that must be the same in header-file and implementation mode:\n//\n//     STB_TEXTEDIT_CHARTYPE             the character type\n//     STB_TEXTEDIT_POSITIONTYPE         small type that is a valid cursor position\n//     STB_TEXTEDIT_UNDOSTATECOUNT       the number of undo states to allow\n//     STB_TEXTEDIT_UNDOCHARCOUNT        the number of characters to store in the undo buffer\n//\n// Symbols you must define for implementation mode:\n//\n//    STB_TEXTEDIT_STRING               the type of object representing a string being edited,\n//                                      typically this is a wrapper object with other data you need\n//\n//    STB_TEXTEDIT_STRINGLEN(obj)       the length of the string (ideally O(1))\n//    STB_TEXTEDIT_LAYOUTROW(&r,obj,n)  returns the results of laying out a line of characters\n//                                        starting from character #n (see discussion below)\n//    STB_TEXTEDIT_GETWIDTH(obj,n,i)    returns the pixel delta from the xpos of the i'th character\n//                                        to the xpos of the i+1'th char for a line of characters\n//                                        starting at character #n (i.e. accounts for kerning\n//                                        with previous char)\n//    STB_TEXTEDIT_KEYTOTEXT(k)         maps a keyboard input to an insertable character\n//                                        (return type is int, -1 means not valid to insert)\n//    STB_TEXTEDIT_GETCHAR(obj,i)       returns the i'th character of obj, 0-based\n//    STB_TEXTEDIT_NEWLINE              the character returned by _GETCHAR() we recognize\n//                                        as manually wordwrapping for end-of-line positioning\n//\n//    STB_TEXTEDIT_DELETECHARS(obj,i,n)      delete n characters starting at i\n//    STB_TEXTEDIT_INSERTCHARS(obj,i,c*,n)   insert n characters at i (pointed to by STB_TEXTEDIT_CHARTYPE*)\n//\n//    STB_TEXTEDIT_K_SHIFT       a power of two that is or'd in to a keyboard input to represent the shift key\n//\n//    STB_TEXTEDIT_K_LEFT        keyboard input to move cursor left\n//    STB_TEXTEDIT_K_RIGHT       keyboard input to move cursor right\n//    STB_TEXTEDIT_K_UP          keyboard input to move cursor up\n//    STB_TEXTEDIT_K_DOWN        keyboard input to move cursor down\n//    STB_TEXTEDIT_K_PGUP        keyboard input to move cursor up a page\n//    STB_TEXTEDIT_K_PGDOWN      keyboard input to move cursor down a page\n//    STB_TEXTEDIT_K_LINESTART   keyboard input to move cursor to start of line  // e.g. HOME\n//    STB_TEXTEDIT_K_LINEEND     keyboard input to move cursor to end of line    // e.g. END\n//    STB_TEXTEDIT_K_TEXTSTART   keyboard input to move cursor to start of text  // e.g. ctrl-HOME\n//    STB_TEXTEDIT_K_TEXTEND     keyboard input to move cursor to end of text    // e.g. ctrl-END\n//    STB_TEXTEDIT_K_DELETE      keyboard input to delete selection or character under cursor\n//    STB_TEXTEDIT_K_BACKSPACE   keyboard input to delete selection or character left of cursor\n//    STB_TEXTEDIT_K_UNDO        keyboard input to perform undo\n//    STB_TEXTEDIT_K_REDO        keyboard input to perform redo\n//\n// Optional:\n//    STB_TEXTEDIT_K_INSERT              keyboard input to toggle insert mode\n//    STB_TEXTEDIT_IS_SPACE(ch)          true if character is whitespace (e.g. 'isspace'),\n//                                          required for default WORDLEFT/WORDRIGHT handlers\n//    STB_TEXTEDIT_MOVEWORDLEFT(obj,i)   custom handler for WORDLEFT, returns index to move cursor to\n//    STB_TEXTEDIT_MOVEWORDRIGHT(obj,i)  custom handler for WORDRIGHT, returns index to move cursor to\n//    STB_TEXTEDIT_K_WORDLEFT            keyboard input to move cursor left one word // e.g. ctrl-LEFT\n//    STB_TEXTEDIT_K_WORDRIGHT           keyboard input to move cursor right one word // e.g. ctrl-RIGHT\n//    STB_TEXTEDIT_K_LINESTART2          secondary keyboard input to move cursor to start of line\n//    STB_TEXTEDIT_K_LINEEND2            secondary keyboard input to move cursor to end of line\n//    STB_TEXTEDIT_K_TEXTSTART2          secondary keyboard input to move cursor to start of text\n//    STB_TEXTEDIT_K_TEXTEND2            secondary keyboard input to move cursor to end of text\n//\n// Keyboard input must be encoded as a single integer value; e.g. a character code\n// and some bitflags that represent shift states. to simplify the interface, SHIFT must\n// be a bitflag, so we can test the shifted state of cursor movements to allow selection,\n// i.e. (STB_TEXTEDIT_K_RIGHT|STB_TEXTEDIT_K_SHIFT) should be shifted right-arrow.\n//\n// You can encode other things, such as CONTROL or ALT, in additional bits, and\n// then test for their presence in e.g. STB_TEXTEDIT_K_WORDLEFT. For example,\n// my Windows implementations add an additional CONTROL bit, and an additional KEYDOWN\n// bit. Then all of the STB_TEXTEDIT_K_ values bitwise-or in the KEYDOWN bit,\n// and I pass both WM_KEYDOWN and WM_CHAR events to the \"key\" function in the\n// API below. The control keys will only match WM_KEYDOWN events because of the\n// keydown bit I add, and STB_TEXTEDIT_KEYTOTEXT only tests for the KEYDOWN\n// bit so it only decodes WM_CHAR events.\n//\n// STB_TEXTEDIT_LAYOUTROW returns information about the shape of one displayed\n// row of characters assuming they start on the i'th character--the width and\n// the height and the number of characters consumed. This allows this library\n// to traverse the entire layout incrementally. You need to compute word-wrapping\n// here.\n//\n// Each textfield keeps its own insert mode state, which is not how normal\n// applications work. To keep an app-wide insert mode, update/copy the\n// \"insert_mode\" field of STB_TexteditState before/after calling API functions.\n//\n// API\n//\n//    void stb_textedit_initialize_state(STB_TexteditState *state, int is_single_line)\n//\n//    void stb_textedit_click(STB_TEXTEDIT_STRING *str, STB_TexteditState *state, float x, float y)\n//    void stb_textedit_drag(STB_TEXTEDIT_STRING *str, STB_TexteditState *state, float x, float y)\n//    int  stb_textedit_cut(STB_TEXTEDIT_STRING *str, STB_TexteditState *state)\n//    int  stb_textedit_paste(STB_TEXTEDIT_STRING *str, STB_TexteditState *state, STB_TEXTEDIT_CHARTYPE *text, int len)\n//    void stb_textedit_key(STB_TEXTEDIT_STRING *str, STB_TexteditState *state, STB_TEXEDIT_KEYTYPE key)\n//    void stb_textedit_text(STB_TEXTEDIT_STRING *str, STB_TexteditState *state, STB_TEXTEDIT_CHARTYPE *text, int text_len)\n//\n//    Each of these functions potentially updates the string and updates the\n//    state.\n//\n//      initialize_state:\n//          set the textedit state to a known good default state when initially\n//          constructing the textedit.\n//\n//      click:\n//          call this with the mouse x,y on a mouse down; it will update the cursor\n//          and reset the selection start/end to the cursor point. the x,y must\n//          be relative to the text widget, with (0,0) being the top left.\n//\n//      drag:\n//          call this with the mouse x,y on a mouse drag/up; it will update the\n//          cursor and the selection end point\n//\n//      cut:\n//          call this to delete the current selection; returns true if there was\n//          one. you should FIRST copy the current selection to the system paste buffer.\n//          (To copy, just copy the current selection out of the string yourself.)\n//\n//      paste:\n//          call this to paste text at the current cursor point or over the current\n//          selection if there is one.\n//\n//      key:\n//          call this for keyboard inputs sent to the textfield. you can use it\n//          for \"key down\" events or for \"translated\" key events. if you need to\n//          do both (as in Win32), or distinguish Unicode characters from control\n//          inputs, set a high bit to distinguish the two; then you can define the\n//          various definitions like STB_TEXTEDIT_K_LEFT have the is-key-event bit\n//          set, and make STB_TEXTEDIT_KEYTOCHAR check that the is-key-event bit is\n//          clear. STB_TEXTEDIT_KEYTYPE defaults to int, but you can #define it to\n//          anything other type you want before including.\n//          if the STB_TEXTEDIT_KEYTOTEXT function is defined, selected keys are\n//          transformed into text and stb_textedit_text() is automatically called.\n//\n//      text: [DEAR IMGUI] added 2024-09\n//          call this to text inputs sent to the textfield.\n//\n//\n//   When rendering, you can read the cursor position and selection state from\n//   the STB_TexteditState.\n//\n//\n// Notes:\n//\n// This is designed to be usable in IMGUI, so it allows for the possibility of\n// running in an IMGUI that has NOT cached the multi-line layout. For this\n// reason, it provides an interface that is compatible with computing the\n// layout incrementally--we try to make sure we make as few passes through\n// as possible. (For example, to locate the mouse pointer in the text, we\n// could define functions that return the X and Y positions of characters\n// and binary search Y and then X, but if we're doing dynamic layout this\n// will run the layout algorithm many times, so instead we manually search\n// forward in one pass. Similar logic applies to e.g. up-arrow and\n// down-arrow movement.)\n//\n// If it's run in a widget that *has* cached the layout, then this is less\n// efficient, but it's not horrible on modern computers. But you wouldn't\n// want to edit million-line files with it.\n\n\n////////////////////////////////////////////////////////////////////////////\n////////////////////////////////////////////////////////////////////////////\n////\n////   Header-file mode\n////\n////\n\n#ifndef INCLUDE_IMSTB_TEXTEDIT_H\n#define INCLUDE_IMSTB_TEXTEDIT_H\n\n////////////////////////////////////////////////////////////////////////\n//\n//     STB_TexteditState\n//\n// Definition of STB_TexteditState which you should store\n// per-textfield; it includes cursor position, selection state,\n// and undo state.\n//\n\n#ifndef IMSTB_TEXTEDIT_UNDOSTATECOUNT\n#define IMSTB_TEXTEDIT_UNDOSTATECOUNT   99\n#endif\n#ifndef IMSTB_TEXTEDIT_UNDOCHARCOUNT\n#define IMSTB_TEXTEDIT_UNDOCHARCOUNT   999\n#endif\n#ifndef IMSTB_TEXTEDIT_CHARTYPE\n#define IMSTB_TEXTEDIT_CHARTYPE        int\n#endif\n#ifndef IMSTB_TEXTEDIT_POSITIONTYPE\n#define IMSTB_TEXTEDIT_POSITIONTYPE    int\n#endif\n\ntypedef struct\n{\n   // private data\n   IMSTB_TEXTEDIT_POSITIONTYPE  where;\n   IMSTB_TEXTEDIT_POSITIONTYPE  insert_length;\n   IMSTB_TEXTEDIT_POSITIONTYPE  delete_length;\n   int                        char_storage;\n} StbUndoRecord;\n\ntypedef struct\n{\n   // private data\n   StbUndoRecord          undo_rec [IMSTB_TEXTEDIT_UNDOSTATECOUNT];\n   IMSTB_TEXTEDIT_CHARTYPE  undo_char[IMSTB_TEXTEDIT_UNDOCHARCOUNT];\n   short undo_point, redo_point;\n   int undo_char_point, redo_char_point;\n} StbUndoState;\n\ntypedef struct STB_TexteditState\n{\n   /////////////////////\n   //\n   // public data\n   //\n\n   int cursor;\n   // position of the text cursor within the string\n\n   int select_start;          // selection start point\n   int select_end;\n   // selection start and end point in characters; if equal, no selection.\n   // note that start may be less than or greater than end (e.g. when\n   // dragging the mouse, start is where the initial click was, and you\n   // can drag in either direction)\n\n   unsigned char insert_mode;\n   // each textfield keeps its own insert mode state. to keep an app-wide\n   // insert mode, copy this value in/out of the app state\n\n   int row_count_per_page;\n   // page size in number of row.\n   // this value MUST be set to >0 for pageup or pagedown in multilines documents.\n\n   /////////////////////\n   //\n   // private data\n   //\n   unsigned char cursor_at_end_of_line; // not implemented yet\n   unsigned char initialized;\n   unsigned char has_preferred_x;\n   unsigned char single_line;\n   unsigned char padding1, padding2, padding3;\n   float preferred_x; // this determines where the cursor up/down tries to seek to along x\n   StbUndoState undostate;\n} STB_TexteditState;\n\n\n////////////////////////////////////////////////////////////////////////\n//\n//     StbTexteditRow\n//\n// Result of layout query, used by stb_textedit to determine where\n// the text in each row is.\n\n// result of layout query\ntypedef struct\n{\n   float x0,x1;             // starting x location, end x location (allows for align=right, etc)\n   float baseline_y_delta;  // position of baseline relative to previous row's baseline\n   float ymin,ymax;         // height of row above and below baseline\n   int num_chars;\n} StbTexteditRow;\n#endif //INCLUDE_IMSTB_TEXTEDIT_H\n\n\n////////////////////////////////////////////////////////////////////////////\n////////////////////////////////////////////////////////////////////////////\n////\n////   Implementation mode\n////\n////\n\n\n// implementation isn't include-guarded, since it might have indirectly\n// included just the \"header\" portion\n#ifdef IMSTB_TEXTEDIT_IMPLEMENTATION\n\n#ifndef IMSTB_TEXTEDIT_memmove\n#include <string.h>\n#define IMSTB_TEXTEDIT_memmove memmove\n#endif\n\n\n/////////////////////////////////////////////////////////////////////////////\n//\n//      Mouse input handling\n//\n\n// traverse the layout to locate the nearest character to a display position\nstatic int stb_text_locate_coord(IMSTB_TEXTEDIT_STRING *str, float x, float y)\n{\n   StbTexteditRow r;\n   int n = STB_TEXTEDIT_STRINGLEN(str);\n   float base_y = 0, prev_x;\n   int i=0, k;\n\n   r.x0 = r.x1 = 0;\n   r.ymin = r.ymax = 0;\n   r.num_chars = 0;\n\n   // search rows to find one that straddles 'y'\n   while (i < n) {\n      STB_TEXTEDIT_LAYOUTROW(&r, str, i);\n      if (r.num_chars <= 0)\n         return n;\n\n      if (i==0 && y < base_y + r.ymin)\n         return 0;\n\n      if (y < base_y + r.ymax)\n         break;\n\n      i += r.num_chars;\n      base_y += r.baseline_y_delta;\n   }\n\n   // below all text, return 'after' last character\n   if (i >= n)\n      return n;\n\n   // check if it's before the beginning of the line\n   if (x < r.x0)\n      return i;\n\n   // check if it's before the end of the line\n   if (x < r.x1) {\n      // search characters in row for one that straddles 'x'\n      prev_x = r.x0;\n      for (k=0; k < r.num_chars; k = IMSTB_TEXTEDIT_GETNEXTCHARINDEX(str, i + k) - i) {\n         float w = STB_TEXTEDIT_GETWIDTH(str, i, k);\n         if (x < prev_x+w) {\n            if (x < prev_x+w/2)\n               return k+i;\n            else\n               return IMSTB_TEXTEDIT_GETNEXTCHARINDEX(str, i + k);\n         }\n         prev_x += w;\n      }\n      // shouldn't happen, but if it does, fall through to end-of-line case\n   }\n\n   // if the last character is a newline, return that. otherwise return 'after' the last character\n   if (STB_TEXTEDIT_GETCHAR(str, i+r.num_chars-1) == STB_TEXTEDIT_NEWLINE)\n      return i+r.num_chars-1;\n   else\n      return i+r.num_chars;\n}\n\n// API click: on mouse down, move the cursor to the clicked location, and reset the selection\nstatic void stb_textedit_click(IMSTB_TEXTEDIT_STRING *str, STB_TexteditState *state, float x, float y)\n{\n   // In single-line mode, just always make y = 0. This lets the drag keep working if the mouse\n   // goes off the top or bottom of the text\n   if( state->single_line )\n   {\n      StbTexteditRow r;\n      STB_TEXTEDIT_LAYOUTROW(&r, str, 0);\n      y = r.ymin;\n   }\n\n   state->cursor = stb_text_locate_coord(str, x, y);\n   state->select_start = state->cursor;\n   state->select_end = state->cursor;\n   state->has_preferred_x = 0;\n}\n\n// API drag: on mouse drag, move the cursor and selection endpoint to the clicked location\nstatic void stb_textedit_drag(IMSTB_TEXTEDIT_STRING *str, STB_TexteditState *state, float x, float y)\n{\n   int p = 0;\n\n   // In single-line mode, just always make y = 0. This lets the drag keep working if the mouse\n   // goes off the top or bottom of the text\n   if( state->single_line )\n   {\n      StbTexteditRow r;\n      STB_TEXTEDIT_LAYOUTROW(&r, str, 0);\n      y = r.ymin;\n   }\n\n   if (state->select_start == state->select_end)\n      state->select_start = state->cursor;\n\n   p = stb_text_locate_coord(str, x, y);\n   state->cursor = state->select_end = p;\n}\n\n/////////////////////////////////////////////////////////////////////////////\n//\n//      Keyboard input handling\n//\n\n// forward declarations\nstatic void stb_text_undo(IMSTB_TEXTEDIT_STRING *str, STB_TexteditState *state);\nstatic void stb_text_redo(IMSTB_TEXTEDIT_STRING *str, STB_TexteditState *state);\nstatic void stb_text_makeundo_delete(IMSTB_TEXTEDIT_STRING *str, STB_TexteditState *state, int where, int length);\nstatic void stb_text_makeundo_insert(STB_TexteditState *state, int where, int length);\nstatic void stb_text_makeundo_replace(IMSTB_TEXTEDIT_STRING *str, STB_TexteditState *state, int where, int old_length, int new_length);\n\ntypedef struct\n{\n   float x,y;    // position of n'th character\n   float height; // height of line\n   int first_char, length; // first char of row, and length\n   int prev_first;  // first char of previous row\n} StbFindState;\n\n// find the x/y location of a character, and remember info about the previous row in\n// case we get a move-up event (for page up, we'll have to rescan)\nstatic void stb_textedit_find_charpos(StbFindState *find, IMSTB_TEXTEDIT_STRING *str, int n, int single_line)\n{\n   StbTexteditRow r;\n   int prev_start = 0;\n   int z = STB_TEXTEDIT_STRINGLEN(str);\n   int i=0, first;\n\n   if (n == z && single_line) {\n      // special case if it's at the end (may not be needed?)\n      STB_TEXTEDIT_LAYOUTROW(&r, str, 0);\n      find->y = 0;\n      find->first_char = 0;\n      find->length = z;\n      find->height = r.ymax - r.ymin;\n      find->x = r.x1;\n      return;\n   }\n\n   // search rows to find the one that straddles character n\n   find->y = 0;\n\n   for(;;) {\n      STB_TEXTEDIT_LAYOUTROW(&r, str, i);\n      if (n < i + r.num_chars)\n         break;\n      if (i + r.num_chars == z && z > 0 && STB_TEXTEDIT_GETCHAR(str, z - 1) != STB_TEXTEDIT_NEWLINE)  // [DEAR IMGUI] special handling for last line\n         break;   // [DEAR IMGUI]\n      prev_start = i;\n      i += r.num_chars;\n      find->y += r.baseline_y_delta;\n      if (i == z) // [DEAR IMGUI]\n      {\n         r.num_chars = 0; // [DEAR IMGUI]\n         break;   // [DEAR IMGUI]\n      }\n   }\n\n   find->first_char = first = i;\n   find->length = r.num_chars;\n   find->height = r.ymax - r.ymin;\n   find->prev_first = prev_start;\n\n   // now scan to find xpos\n   find->x = r.x0;\n   for (i=0; first+i < n; i = IMSTB_TEXTEDIT_GETNEXTCHARINDEX(str, first + i) - first)\n      find->x += STB_TEXTEDIT_GETWIDTH(str, first, i);\n}\n\n#define STB_TEXT_HAS_SELECTION(s)   ((s)->select_start != (s)->select_end)\n\n// make the selection/cursor state valid if client altered the string\nstatic void stb_textedit_clamp(IMSTB_TEXTEDIT_STRING *str, STB_TexteditState *state)\n{\n   int n = STB_TEXTEDIT_STRINGLEN(str);\n   if (STB_TEXT_HAS_SELECTION(state)) {\n      if (state->select_start > n) state->select_start = n;\n      if (state->select_end   > n) state->select_end = n;\n      // if clamping forced them to be equal, move the cursor to match\n      if (state->select_start == state->select_end)\n         state->cursor = state->select_start;\n   }\n   if (state->cursor > n) state->cursor = n;\n}\n\n// delete characters while updating undo\nstatic void stb_textedit_delete(IMSTB_TEXTEDIT_STRING *str, STB_TexteditState *state, int where, int len)\n{\n   stb_text_makeundo_delete(str, state, where, len);\n   STB_TEXTEDIT_DELETECHARS(str, where, len);\n   state->has_preferred_x = 0;\n}\n\n// delete the section\nstatic void stb_textedit_delete_selection(IMSTB_TEXTEDIT_STRING *str, STB_TexteditState *state)\n{\n   stb_textedit_clamp(str, state);\n   if (STB_TEXT_HAS_SELECTION(state)) {\n      if (state->select_start < state->select_end) {\n         stb_textedit_delete(str, state, state->select_start, state->select_end - state->select_start);\n         state->select_end = state->cursor = state->select_start;\n      } else {\n         stb_textedit_delete(str, state, state->select_end, state->select_start - state->select_end);\n         state->select_start = state->cursor = state->select_end;\n      }\n      state->has_preferred_x = 0;\n   }\n}\n\n// canoncialize the selection so start <= end\nstatic void stb_textedit_sortselection(STB_TexteditState *state)\n{\n   if (state->select_end < state->select_start) {\n      int temp = state->select_end;\n      state->select_end = state->select_start;\n      state->select_start = temp;\n   }\n}\n\n// move cursor to first character of selection\nstatic void stb_textedit_move_to_first(STB_TexteditState *state)\n{\n   if (STB_TEXT_HAS_SELECTION(state)) {\n      stb_textedit_sortselection(state);\n      state->cursor = state->select_start;\n      state->select_end = state->select_start;\n      state->has_preferred_x = 0;\n   }\n}\n\n// move cursor to last character of selection\nstatic void stb_textedit_move_to_last(IMSTB_TEXTEDIT_STRING *str, STB_TexteditState *state)\n{\n   if (STB_TEXT_HAS_SELECTION(state)) {\n      stb_textedit_sortselection(state);\n      stb_textedit_clamp(str, state);\n      state->cursor = state->select_end;\n      state->select_start = state->select_end;\n      state->has_preferred_x = 0;\n   }\n}\n\n// [DEAR IMGUI]\n// Functions must be implemented for UTF8 support\n// Code in this file that uses those functions is modified for [DEAR IMGUI] and deviates from the original stb_textedit.\n// There is not necessarily a '[DEAR IMGUI]' at the usage sites.\n#ifndef IMSTB_TEXTEDIT_GETPREVCHARINDEX\n#define IMSTB_TEXTEDIT_GETPREVCHARINDEX(obj, idx) (idx - 1)\n#endif\n#ifndef IMSTB_TEXTEDIT_GETNEXTCHARINDEX\n#define IMSTB_TEXTEDIT_GETNEXTCHARINDEX(obj, idx) (idx + 1)\n#endif\n\n#ifdef STB_TEXTEDIT_IS_SPACE\nstatic int is_word_boundary( IMSTB_TEXTEDIT_STRING *str, int idx )\n{\n   return idx > 0 ? (STB_TEXTEDIT_IS_SPACE( STB_TEXTEDIT_GETCHAR(str,idx-1) ) && !STB_TEXTEDIT_IS_SPACE( STB_TEXTEDIT_GETCHAR(str, idx) ) ) : 1;\n}\n\n#ifndef STB_TEXTEDIT_MOVEWORDLEFT\nstatic int stb_textedit_move_to_word_previous( IMSTB_TEXTEDIT_STRING *str, int c )\n{\n   --c; // always move at least one character\n   while( c >= 0 && !is_word_boundary( str, c ) )\n      --c;\n\n   if( c < 0 )\n      c = 0;\n\n   return c;\n}\n#define STB_TEXTEDIT_MOVEWORDLEFT stb_textedit_move_to_word_previous\n#endif\n\n#ifndef STB_TEXTEDIT_MOVEWORDRIGHT\nstatic int stb_textedit_move_to_word_next( IMSTB_TEXTEDIT_STRING *str, int c )\n{\n   const int len = STB_TEXTEDIT_STRINGLEN(str);\n   ++c; // always move at least one character\n   while( c < len && !is_word_boundary( str, c ) )\n      ++c;\n\n   if( c > len )\n      c = len;\n\n   return c;\n}\n#define STB_TEXTEDIT_MOVEWORDRIGHT stb_textedit_move_to_word_next\n#endif\n\n#endif\n\n// update selection and cursor to match each other\nstatic void stb_textedit_prep_selection_at_cursor(STB_TexteditState *state)\n{\n   if (!STB_TEXT_HAS_SELECTION(state))\n      state->select_start = state->select_end = state->cursor;\n   else\n      state->cursor = state->select_end;\n}\n\n// API cut: delete selection\nstatic int stb_textedit_cut(IMSTB_TEXTEDIT_STRING *str, STB_TexteditState *state)\n{\n   if (STB_TEXT_HAS_SELECTION(state)) {\n      stb_textedit_delete_selection(str,state); // implicitly clamps\n      state->has_preferred_x = 0;\n      return 1;\n   }\n   return 0;\n}\n\n// API paste: replace existing selection with passed-in text\nstatic int stb_textedit_paste_internal(IMSTB_TEXTEDIT_STRING *str, STB_TexteditState *state, IMSTB_TEXTEDIT_CHARTYPE *text, int len)\n{\n   // if there's a selection, the paste should delete it\n   stb_textedit_clamp(str, state);\n   stb_textedit_delete_selection(str,state);\n   // try to insert the characters\n   if (STB_TEXTEDIT_INSERTCHARS(str, state->cursor, text, len)) {\n      stb_text_makeundo_insert(state, state->cursor, len);\n      state->cursor += len;\n      state->has_preferred_x = 0;\n      return 1;\n   }\n   // note: paste failure will leave deleted selection, may be restored with an undo (see https://github.com/nothings/stb/issues/734 for details)\n   return 0;\n}\n\n#ifndef STB_TEXTEDIT_KEYTYPE\n#define STB_TEXTEDIT_KEYTYPE int\n#endif\n\n// [DEAR IMGUI] Added stb_textedit_text(), extracted out and called by stb_textedit_key() for backward compatibility.\nstatic void stb_textedit_text(IMSTB_TEXTEDIT_STRING* str, STB_TexteditState* state, const IMSTB_TEXTEDIT_CHARTYPE* text, int text_len)\n{\n   // can't add newline in single-line mode\n   if (text[0] == '\\n' && state->single_line)\n      return;\n\n   if (state->insert_mode && !STB_TEXT_HAS_SELECTION(state) && state->cursor < STB_TEXTEDIT_STRINGLEN(str)) {\n      stb_text_makeundo_replace(str, state, state->cursor, 1, 1);\n      STB_TEXTEDIT_DELETECHARS(str, state->cursor, 1);\n      if (STB_TEXTEDIT_INSERTCHARS(str, state->cursor, text, text_len)) {\n         state->cursor += text_len;\n         state->has_preferred_x = 0;\n      }\n   }\n   else {\n      stb_textedit_delete_selection(str, state); // implicitly clamps\n      if (STB_TEXTEDIT_INSERTCHARS(str, state->cursor, text, text_len)) {\n         stb_text_makeundo_insert(state, state->cursor, text_len);\n         state->cursor += text_len;\n         state->has_preferred_x = 0;\n      }\n   }\n}\n\n// API key: process a keyboard input\nstatic void stb_textedit_key(IMSTB_TEXTEDIT_STRING *str, STB_TexteditState *state, STB_TEXTEDIT_KEYTYPE key)\n{\nretry:\n   switch (key) {\n      default: {\n#ifdef STB_TEXTEDIT_KEYTOTEXT\n         int c = STB_TEXTEDIT_KEYTOTEXT(key);\n         if (c > 0) {\n            IMSTB_TEXTEDIT_CHARTYPE ch = (IMSTB_TEXTEDIT_CHARTYPE)c;\n            stb_textedit_text(str, state, &ch, 1);\n         }\n#endif\n         break;\n      }\n\n#ifdef STB_TEXTEDIT_K_INSERT\n      case STB_TEXTEDIT_K_INSERT:\n         state->insert_mode = !state->insert_mode;\n         break;\n#endif\n\n      case STB_TEXTEDIT_K_UNDO:\n         stb_text_undo(str, state);\n         state->has_preferred_x = 0;\n         break;\n\n      case STB_TEXTEDIT_K_REDO:\n         stb_text_redo(str, state);\n         state->has_preferred_x = 0;\n         break;\n\n      case STB_TEXTEDIT_K_LEFT:\n         // if currently there's a selection, move cursor to start of selection\n         if (STB_TEXT_HAS_SELECTION(state))\n            stb_textedit_move_to_first(state);\n         else\n            if (state->cursor > 0)\n               state->cursor = IMSTB_TEXTEDIT_GETPREVCHARINDEX(str, state->cursor);\n         state->has_preferred_x = 0;\n         break;\n\n      case STB_TEXTEDIT_K_RIGHT:\n         // if currently there's a selection, move cursor to end of selection\n         if (STB_TEXT_HAS_SELECTION(state))\n            stb_textedit_move_to_last(str, state);\n         else\n            state->cursor = IMSTB_TEXTEDIT_GETNEXTCHARINDEX(str, state->cursor);\n         stb_textedit_clamp(str, state);\n         state->has_preferred_x = 0;\n         break;\n\n      case STB_TEXTEDIT_K_LEFT | STB_TEXTEDIT_K_SHIFT:\n         stb_textedit_clamp(str, state);\n         stb_textedit_prep_selection_at_cursor(state);\n         // move selection left\n         if (state->select_end > 0)\n            state->select_end = IMSTB_TEXTEDIT_GETPREVCHARINDEX(str, state->select_end);\n         state->cursor = state->select_end;\n         state->has_preferred_x = 0;\n         break;\n\n#ifdef STB_TEXTEDIT_MOVEWORDLEFT\n      case STB_TEXTEDIT_K_WORDLEFT:\n         if (STB_TEXT_HAS_SELECTION(state))\n            stb_textedit_move_to_first(state);\n         else {\n            state->cursor = STB_TEXTEDIT_MOVEWORDLEFT(str, state->cursor);\n            stb_textedit_clamp( str, state );\n         }\n         break;\n\n      case STB_TEXTEDIT_K_WORDLEFT | STB_TEXTEDIT_K_SHIFT:\n         if( !STB_TEXT_HAS_SELECTION( state ) )\n            stb_textedit_prep_selection_at_cursor(state);\n\n         state->cursor = STB_TEXTEDIT_MOVEWORDLEFT(str, state->cursor);\n         state->select_end = state->cursor;\n\n         stb_textedit_clamp( str, state );\n         break;\n#endif\n\n#ifdef STB_TEXTEDIT_MOVEWORDRIGHT\n      case STB_TEXTEDIT_K_WORDRIGHT:\n         if (STB_TEXT_HAS_SELECTION(state))\n            stb_textedit_move_to_last(str, state);\n         else {\n            state->cursor = STB_TEXTEDIT_MOVEWORDRIGHT(str, state->cursor);\n            stb_textedit_clamp( str, state );\n         }\n         break;\n\n      case STB_TEXTEDIT_K_WORDRIGHT | STB_TEXTEDIT_K_SHIFT:\n         if( !STB_TEXT_HAS_SELECTION( state ) )\n            stb_textedit_prep_selection_at_cursor(state);\n\n         state->cursor = STB_TEXTEDIT_MOVEWORDRIGHT(str, state->cursor);\n         state->select_end = state->cursor;\n\n         stb_textedit_clamp( str, state );\n         break;\n#endif\n\n      case STB_TEXTEDIT_K_RIGHT | STB_TEXTEDIT_K_SHIFT:\n         stb_textedit_prep_selection_at_cursor(state);\n         // move selection right\n         state->select_end = IMSTB_TEXTEDIT_GETNEXTCHARINDEX(str, state->select_end);\n         stb_textedit_clamp(str, state);\n         state->cursor = state->select_end;\n         state->has_preferred_x = 0;\n         break;\n\n      case STB_TEXTEDIT_K_DOWN:\n      case STB_TEXTEDIT_K_DOWN | STB_TEXTEDIT_K_SHIFT:\n      case STB_TEXTEDIT_K_PGDOWN:\n      case STB_TEXTEDIT_K_PGDOWN | STB_TEXTEDIT_K_SHIFT: {\n         StbFindState find;\n         StbTexteditRow row;\n         int i, j, sel = (key & STB_TEXTEDIT_K_SHIFT) != 0;\n         int is_page = (key & ~STB_TEXTEDIT_K_SHIFT) == STB_TEXTEDIT_K_PGDOWN;\n         int row_count = is_page ? state->row_count_per_page : 1;\n\n         if (!is_page && state->single_line) {\n            // on windows, up&down in single-line behave like left&right\n            key = STB_TEXTEDIT_K_RIGHT | (key & STB_TEXTEDIT_K_SHIFT);\n            goto retry;\n         }\n\n         if (sel)\n            stb_textedit_prep_selection_at_cursor(state);\n         else if (STB_TEXT_HAS_SELECTION(state))\n            stb_textedit_move_to_last(str, state);\n\n         // compute current position of cursor point\n         stb_textedit_clamp(str, state);\n         stb_textedit_find_charpos(&find, str, state->cursor, state->single_line);\n\n         for (j = 0; j < row_count; ++j) {\n            float x, goal_x = state->has_preferred_x ? state->preferred_x : find.x;\n            int start = find.first_char + find.length;\n\n            if (find.length == 0)\n               break;\n\n            // [DEAR IMGUI]\n            // going down while being on the last line shouldn't bring us to that line end\n            if (STB_TEXTEDIT_GETCHAR(str, find.first_char + find.length - 1) != STB_TEXTEDIT_NEWLINE)\n               break;\n\n            // now find character position down a row\n            state->cursor = start;\n            STB_TEXTEDIT_LAYOUTROW(&row, str, state->cursor);\n            x = row.x0;\n            for (i=0; i < row.num_chars; ++i) {\n               float dx = STB_TEXTEDIT_GETWIDTH(str, start, i);\n               #ifdef IMSTB_TEXTEDIT_GETWIDTH_NEWLINE\n               if (dx == IMSTB_TEXTEDIT_GETWIDTH_NEWLINE)\n                  break;\n               #endif\n               x += dx;\n               if (x > goal_x)\n                  break;\n               state->cursor = IMSTB_TEXTEDIT_GETNEXTCHARINDEX(str, state->cursor);\n            }\n            stb_textedit_clamp(str, state);\n\n            state->has_preferred_x = 1;\n            state->preferred_x = goal_x;\n\n            if (sel)\n               state->select_end = state->cursor;\n\n            // go to next line\n            find.first_char = find.first_char + find.length;\n            find.length = row.num_chars;\n         }\n         break;\n      }\n\n      case STB_TEXTEDIT_K_UP:\n      case STB_TEXTEDIT_K_UP | STB_TEXTEDIT_K_SHIFT:\n      case STB_TEXTEDIT_K_PGUP:\n      case STB_TEXTEDIT_K_PGUP | STB_TEXTEDIT_K_SHIFT: {\n         StbFindState find;\n         StbTexteditRow row;\n         int i, j, prev_scan, sel = (key & STB_TEXTEDIT_K_SHIFT) != 0;\n         int is_page = (key & ~STB_TEXTEDIT_K_SHIFT) == STB_TEXTEDIT_K_PGUP;\n         int row_count = is_page ? state->row_count_per_page : 1;\n\n         if (!is_page && state->single_line) {\n            // on windows, up&down become left&right\n            key = STB_TEXTEDIT_K_LEFT | (key & STB_TEXTEDIT_K_SHIFT);\n            goto retry;\n         }\n\n         if (sel)\n            stb_textedit_prep_selection_at_cursor(state);\n         else if (STB_TEXT_HAS_SELECTION(state))\n            stb_textedit_move_to_first(state);\n\n         // compute current position of cursor point\n         stb_textedit_clamp(str, state);\n         stb_textedit_find_charpos(&find, str, state->cursor, state->single_line);\n\n         for (j = 0; j < row_count; ++j) {\n            float  x, goal_x = state->has_preferred_x ? state->preferred_x : find.x;\n\n            // can only go up if there's a previous row\n            if (find.prev_first == find.first_char)\n               break;\n\n            // now find character position up a row\n            state->cursor = find.prev_first;\n            STB_TEXTEDIT_LAYOUTROW(&row, str, state->cursor);\n            x = row.x0;\n            for (i=0; i < row.num_chars; ++i) {\n               float dx = STB_TEXTEDIT_GETWIDTH(str, find.prev_first, i);\n               #ifdef IMSTB_TEXTEDIT_GETWIDTH_NEWLINE\n               if (dx == IMSTB_TEXTEDIT_GETWIDTH_NEWLINE)\n                  break;\n               #endif\n               x += dx;\n               if (x > goal_x)\n                  break;\n               state->cursor = IMSTB_TEXTEDIT_GETNEXTCHARINDEX(str, state->cursor);\n            }\n            stb_textedit_clamp(str, state);\n\n            state->has_preferred_x = 1;\n            state->preferred_x = goal_x;\n\n            if (sel)\n               state->select_end = state->cursor;\n\n            // go to previous line\n            // (we need to scan previous line the hard way. maybe we could expose this as a new API function?)\n            prev_scan = find.prev_first > 0 ? find.prev_first - 1 : 0;\n            while (prev_scan > 0 && STB_TEXTEDIT_GETCHAR(str, prev_scan - 1) != STB_TEXTEDIT_NEWLINE)\n               --prev_scan;\n            find.first_char = find.prev_first;\n            find.prev_first = prev_scan;\n         }\n         break;\n      }\n\n      case STB_TEXTEDIT_K_DELETE:\n      case STB_TEXTEDIT_K_DELETE | STB_TEXTEDIT_K_SHIFT:\n         if (STB_TEXT_HAS_SELECTION(state))\n            stb_textedit_delete_selection(str, state);\n         else {\n            int n = STB_TEXTEDIT_STRINGLEN(str);\n            if (state->cursor < n)\n               stb_textedit_delete(str, state, state->cursor, IMSTB_TEXTEDIT_GETNEXTCHARINDEX(str, state->cursor) - state->cursor);\n         }\n         state->has_preferred_x = 0;\n         break;\n\n      case STB_TEXTEDIT_K_BACKSPACE:\n      case STB_TEXTEDIT_K_BACKSPACE | STB_TEXTEDIT_K_SHIFT:\n         if (STB_TEXT_HAS_SELECTION(state))\n            stb_textedit_delete_selection(str, state);\n         else {\n            stb_textedit_clamp(str, state);\n            if (state->cursor > 0) {\n               int prev = IMSTB_TEXTEDIT_GETPREVCHARINDEX(str, state->cursor);\n               stb_textedit_delete(str, state, prev, state->cursor - prev);\n               state->cursor = prev;\n            }\n         }\n         state->has_preferred_x = 0;\n         break;\n\n#ifdef STB_TEXTEDIT_K_TEXTSTART2\n      case STB_TEXTEDIT_K_TEXTSTART2:\n#endif\n      case STB_TEXTEDIT_K_TEXTSTART:\n         state->cursor = state->select_start = state->select_end = 0;\n         state->has_preferred_x = 0;\n         break;\n\n#ifdef STB_TEXTEDIT_K_TEXTEND2\n      case STB_TEXTEDIT_K_TEXTEND2:\n#endif\n      case STB_TEXTEDIT_K_TEXTEND:\n         state->cursor = STB_TEXTEDIT_STRINGLEN(str);\n         state->select_start = state->select_end = 0;\n         state->has_preferred_x = 0;\n         break;\n\n#ifdef STB_TEXTEDIT_K_TEXTSTART2\n      case STB_TEXTEDIT_K_TEXTSTART2 | STB_TEXTEDIT_K_SHIFT:\n#endif\n      case STB_TEXTEDIT_K_TEXTSTART | STB_TEXTEDIT_K_SHIFT:\n         stb_textedit_prep_selection_at_cursor(state);\n         state->cursor = state->select_end = 0;\n         state->has_preferred_x = 0;\n         break;\n\n#ifdef STB_TEXTEDIT_K_TEXTEND2\n      case STB_TEXTEDIT_K_TEXTEND2 | STB_TEXTEDIT_K_SHIFT:\n#endif\n      case STB_TEXTEDIT_K_TEXTEND | STB_TEXTEDIT_K_SHIFT:\n         stb_textedit_prep_selection_at_cursor(state);\n         state->cursor = state->select_end = STB_TEXTEDIT_STRINGLEN(str);\n         state->has_preferred_x = 0;\n         break;\n\n\n#ifdef STB_TEXTEDIT_K_LINESTART2\n      case STB_TEXTEDIT_K_LINESTART2:\n#endif\n      case STB_TEXTEDIT_K_LINESTART:\n         stb_textedit_clamp(str, state);\n         stb_textedit_move_to_first(state);\n         if (state->single_line)\n            state->cursor = 0;\n         else while (state->cursor > 0 && STB_TEXTEDIT_GETCHAR(str, state->cursor-1) != STB_TEXTEDIT_NEWLINE)\n            --state->cursor;\n         state->has_preferred_x = 0;\n         break;\n\n#ifdef STB_TEXTEDIT_K_LINEEND2\n      case STB_TEXTEDIT_K_LINEEND2:\n#endif\n      case STB_TEXTEDIT_K_LINEEND: {\n         int n = STB_TEXTEDIT_STRINGLEN(str);\n         stb_textedit_clamp(str, state);\n         stb_textedit_move_to_first(state);\n         if (state->single_line)\n             state->cursor = n;\n         else while (state->cursor < n && STB_TEXTEDIT_GETCHAR(str, state->cursor) != STB_TEXTEDIT_NEWLINE)\n             ++state->cursor;\n         state->has_preferred_x = 0;\n         break;\n      }\n\n#ifdef STB_TEXTEDIT_K_LINESTART2\n      case STB_TEXTEDIT_K_LINESTART2 | STB_TEXTEDIT_K_SHIFT:\n#endif\n      case STB_TEXTEDIT_K_LINESTART | STB_TEXTEDIT_K_SHIFT:\n         stb_textedit_clamp(str, state);\n         stb_textedit_prep_selection_at_cursor(state);\n         if (state->single_line)\n            state->cursor = 0;\n         else while (state->cursor > 0 && STB_TEXTEDIT_GETCHAR(str, state->cursor-1) != STB_TEXTEDIT_NEWLINE)\n            --state->cursor;\n         state->select_end = state->cursor;\n         state->has_preferred_x = 0;\n         break;\n\n#ifdef STB_TEXTEDIT_K_LINEEND2\n      case STB_TEXTEDIT_K_LINEEND2 | STB_TEXTEDIT_K_SHIFT:\n#endif\n      case STB_TEXTEDIT_K_LINEEND | STB_TEXTEDIT_K_SHIFT: {\n         int n = STB_TEXTEDIT_STRINGLEN(str);\n         stb_textedit_clamp(str, state);\n         stb_textedit_prep_selection_at_cursor(state);\n         if (state->single_line)\n             state->cursor = n;\n         else while (state->cursor < n && STB_TEXTEDIT_GETCHAR(str, state->cursor) != STB_TEXTEDIT_NEWLINE)\n            ++state->cursor;\n         state->select_end = state->cursor;\n         state->has_preferred_x = 0;\n         break;\n      }\n   }\n}\n\n/////////////////////////////////////////////////////////////////////////////\n//\n//      Undo processing\n//\n// @OPTIMIZE: the undo/redo buffer should be circular\n\nstatic void stb_textedit_flush_redo(StbUndoState *state)\n{\n   state->redo_point = IMSTB_TEXTEDIT_UNDOSTATECOUNT;\n   state->redo_char_point = IMSTB_TEXTEDIT_UNDOCHARCOUNT;\n}\n\n// discard the oldest entry in the undo list\nstatic void stb_textedit_discard_undo(StbUndoState *state)\n{\n   if (state->undo_point > 0) {\n      // if the 0th undo state has characters, clean those up\n      if (state->undo_rec[0].char_storage >= 0) {\n         int n = state->undo_rec[0].insert_length, i;\n         // delete n characters from all other records\n         state->undo_char_point -= n;\n         IMSTB_TEXTEDIT_memmove(state->undo_char, state->undo_char + n, (size_t) (state->undo_char_point*sizeof(IMSTB_TEXTEDIT_CHARTYPE)));\n         for (i=0; i < state->undo_point; ++i)\n            if (state->undo_rec[i].char_storage >= 0)\n               state->undo_rec[i].char_storage -= n; // @OPTIMIZE: get rid of char_storage and infer it\n      }\n      --state->undo_point;\n      IMSTB_TEXTEDIT_memmove(state->undo_rec, state->undo_rec+1, (size_t) (state->undo_point*sizeof(state->undo_rec[0])));\n   }\n}\n\n// discard the oldest entry in the redo list--it's bad if this\n// ever happens, but because undo & redo have to store the actual\n// characters in different cases, the redo character buffer can\n// fill up even though the undo buffer didn't\nstatic void stb_textedit_discard_redo(StbUndoState *state)\n{\n   int k = IMSTB_TEXTEDIT_UNDOSTATECOUNT-1;\n\n   if (state->redo_point <= k) {\n      // if the k'th undo state has characters, clean those up\n      if (state->undo_rec[k].char_storage >= 0) {\n         int n = state->undo_rec[k].insert_length, i;\n         // move the remaining redo character data to the end of the buffer\n         state->redo_char_point += n;\n         IMSTB_TEXTEDIT_memmove(state->undo_char + state->redo_char_point, state->undo_char + state->redo_char_point-n, (size_t) ((IMSTB_TEXTEDIT_UNDOCHARCOUNT - state->redo_char_point)*sizeof(IMSTB_TEXTEDIT_CHARTYPE)));\n         // adjust the position of all the other records to account for above memmove\n         for (i=state->redo_point; i < k; ++i)\n            if (state->undo_rec[i].char_storage >= 0)\n               state->undo_rec[i].char_storage += n;\n      }\n      // now move all the redo records towards the end of the buffer; the first one is at 'redo_point'\n      // [DEAR IMGUI]\n      size_t move_size = (size_t)((IMSTB_TEXTEDIT_UNDOSTATECOUNT - state->redo_point - 1) * sizeof(state->undo_rec[0]));\n      const char* buf_begin = (char*)state->undo_rec; (void)buf_begin;\n      const char* buf_end   = (char*)state->undo_rec + sizeof(state->undo_rec); (void)buf_end;\n      IM_ASSERT(((char*)(state->undo_rec + state->redo_point)) >= buf_begin);\n      IM_ASSERT(((char*)(state->undo_rec + state->redo_point + 1) + move_size) <= buf_end);\n      IMSTB_TEXTEDIT_memmove(state->undo_rec + state->redo_point+1, state->undo_rec + state->redo_point, move_size);\n\n      // now move redo_point to point to the new one\n      ++state->redo_point;\n   }\n}\n\nstatic StbUndoRecord *stb_text_create_undo_record(StbUndoState *state, int numchars)\n{\n   // any time we create a new undo record, we discard redo\n   stb_textedit_flush_redo(state);\n\n   // if we have no free records, we have to make room, by sliding the\n   // existing records down\n   if (state->undo_point == IMSTB_TEXTEDIT_UNDOSTATECOUNT)\n      stb_textedit_discard_undo(state);\n\n   // if the characters to store won't possibly fit in the buffer, we can't undo\n   if (numchars > IMSTB_TEXTEDIT_UNDOCHARCOUNT) {\n      state->undo_point = 0;\n      state->undo_char_point = 0;\n      return NULL;\n   }\n\n   // if we don't have enough free characters in the buffer, we have to make room\n   while (state->undo_char_point + numchars > IMSTB_TEXTEDIT_UNDOCHARCOUNT)\n      stb_textedit_discard_undo(state);\n\n   return &state->undo_rec[state->undo_point++];\n}\n\nstatic IMSTB_TEXTEDIT_CHARTYPE *stb_text_createundo(StbUndoState *state, int pos, int insert_len, int delete_len)\n{\n   StbUndoRecord *r = stb_text_create_undo_record(state, insert_len);\n   if (r == NULL)\n      return NULL;\n\n   r->where = pos;\n   r->insert_length = (IMSTB_TEXTEDIT_POSITIONTYPE) insert_len;\n   r->delete_length = (IMSTB_TEXTEDIT_POSITIONTYPE) delete_len;\n\n   if (insert_len == 0) {\n      r->char_storage = -1;\n      return NULL;\n   } else {\n      r->char_storage = state->undo_char_point;\n      state->undo_char_point += insert_len;\n      return &state->undo_char[r->char_storage];\n   }\n}\n\nstatic void stb_text_undo(IMSTB_TEXTEDIT_STRING *str, STB_TexteditState *state)\n{\n   StbUndoState *s = &state->undostate;\n   StbUndoRecord u, *r;\n   if (s->undo_point == 0)\n      return;\n\n   // we need to do two things: apply the undo record, and create a redo record\n   u = s->undo_rec[s->undo_point-1];\n   r = &s->undo_rec[s->redo_point-1];\n   r->char_storage = -1;\n\n   r->insert_length = u.delete_length;\n   r->delete_length = u.insert_length;\n   r->where = u.where;\n\n   if (u.delete_length) {\n      // if the undo record says to delete characters, then the redo record will\n      // need to re-insert the characters that get deleted, so we need to store\n      // them.\n\n      // there are three cases:\n      //    there's enough room to store the characters\n      //    characters stored for *redoing* don't leave room for redo\n      //    characters stored for *undoing* don't leave room for redo\n      // if the last is true, we have to bail\n\n      if (s->undo_char_point + u.delete_length >= IMSTB_TEXTEDIT_UNDOCHARCOUNT) {\n         // the undo records take up too much character space; there's no space to store the redo characters\n         r->insert_length = 0;\n      } else {\n         int i;\n\n         // there's definitely room to store the characters eventually\n         while (s->undo_char_point + u.delete_length > s->redo_char_point) {\n            // should never happen:\n            if (s->redo_point == IMSTB_TEXTEDIT_UNDOSTATECOUNT)\n               return;\n            // there's currently not enough room, so discard a redo record\n            stb_textedit_discard_redo(s);\n         }\n         r = &s->undo_rec[s->redo_point-1];\n\n         r->char_storage = s->redo_char_point - u.delete_length;\n         s->redo_char_point = s->redo_char_point - u.delete_length;\n\n         // now save the characters\n         for (i=0; i < u.delete_length; ++i)\n            s->undo_char[r->char_storage + i] = STB_TEXTEDIT_GETCHAR(str, u.where + i);\n      }\n\n      // now we can carry out the deletion\n      STB_TEXTEDIT_DELETECHARS(str, u.where, u.delete_length);\n   }\n\n   // check type of recorded action:\n   if (u.insert_length) {\n      // easy case: was a deletion, so we need to insert n characters\n      STB_TEXTEDIT_INSERTCHARS(str, u.where, &s->undo_char[u.char_storage], u.insert_length);\n      s->undo_char_point -= u.insert_length;\n   }\n\n   state->cursor = u.where + u.insert_length;\n\n   s->undo_point--;\n   s->redo_point--;\n}\n\nstatic void stb_text_redo(IMSTB_TEXTEDIT_STRING *str, STB_TexteditState *state)\n{\n   StbUndoState *s = &state->undostate;\n   StbUndoRecord *u, r;\n   if (s->redo_point == IMSTB_TEXTEDIT_UNDOSTATECOUNT)\n      return;\n\n   // we need to do two things: apply the redo record, and create an undo record\n   u = &s->undo_rec[s->undo_point];\n   r = s->undo_rec[s->redo_point];\n\n   // we KNOW there must be room for the undo record, because the redo record\n   // was derived from an undo record\n\n   u->delete_length = r.insert_length;\n   u->insert_length = r.delete_length;\n   u->where = r.where;\n   u->char_storage = -1;\n\n   if (r.delete_length) {\n      // the redo record requires us to delete characters, so the undo record\n      // needs to store the characters\n\n      if (s->undo_char_point + u->insert_length > s->redo_char_point) {\n         u->insert_length = 0;\n         u->delete_length = 0;\n      } else {\n         int i;\n         u->char_storage = s->undo_char_point;\n         s->undo_char_point = s->undo_char_point + u->insert_length;\n\n         // now save the characters\n         for (i=0; i < u->insert_length; ++i)\n            s->undo_char[u->char_storage + i] = STB_TEXTEDIT_GETCHAR(str, u->where + i);\n      }\n\n      STB_TEXTEDIT_DELETECHARS(str, r.where, r.delete_length);\n   }\n\n   if (r.insert_length) {\n      // easy case: need to insert n characters\n      STB_TEXTEDIT_INSERTCHARS(str, r.where, &s->undo_char[r.char_storage], r.insert_length);\n      s->redo_char_point += r.insert_length;\n   }\n\n   state->cursor = r.where + r.insert_length;\n\n   s->undo_point++;\n   s->redo_point++;\n}\n\nstatic void stb_text_makeundo_insert(STB_TexteditState *state, int where, int length)\n{\n   stb_text_createundo(&state->undostate, where, 0, length);\n}\n\nstatic void stb_text_makeundo_delete(IMSTB_TEXTEDIT_STRING *str, STB_TexteditState *state, int where, int length)\n{\n   int i;\n   IMSTB_TEXTEDIT_CHARTYPE *p = stb_text_createundo(&state->undostate, where, length, 0);\n   if (p) {\n      for (i=0; i < length; ++i)\n         p[i] = STB_TEXTEDIT_GETCHAR(str, where+i);\n   }\n}\n\nstatic void stb_text_makeundo_replace(IMSTB_TEXTEDIT_STRING *str, STB_TexteditState *state, int where, int old_length, int new_length)\n{\n   int i;\n   IMSTB_TEXTEDIT_CHARTYPE *p = stb_text_createundo(&state->undostate, where, old_length, new_length);\n   if (p) {\n      for (i=0; i < old_length; ++i)\n         p[i] = STB_TEXTEDIT_GETCHAR(str, where+i);\n   }\n}\n\n// reset the state to default\nstatic void stb_textedit_clear_state(STB_TexteditState *state, int is_single_line)\n{\n   state->undostate.undo_point = 0;\n   state->undostate.undo_char_point = 0;\n   state->undostate.redo_point = IMSTB_TEXTEDIT_UNDOSTATECOUNT;\n   state->undostate.redo_char_point = IMSTB_TEXTEDIT_UNDOCHARCOUNT;\n   state->select_end = state->select_start = 0;\n   state->cursor = 0;\n   state->has_preferred_x = 0;\n   state->preferred_x = 0;\n   state->cursor_at_end_of_line = 0;\n   state->initialized = 1;\n   state->single_line = (unsigned char) is_single_line;\n   state->insert_mode = 0;\n   state->row_count_per_page = 0;\n}\n\n// API initialize\nstatic void stb_textedit_initialize_state(STB_TexteditState *state, int is_single_line)\n{\n   stb_textedit_clear_state(state, is_single_line);\n}\n\n#if defined(__GNUC__) || defined(__clang__)\n#pragma GCC diagnostic push\n#pragma GCC diagnostic ignored \"-Wcast-qual\"\n#endif\n\nstatic int stb_textedit_paste(IMSTB_TEXTEDIT_STRING *str, STB_TexteditState *state, IMSTB_TEXTEDIT_CHARTYPE const *ctext, int len)\n{\n   return stb_textedit_paste_internal(str, state, (IMSTB_TEXTEDIT_CHARTYPE *) ctext, len);\n}\n\n#if defined(__GNUC__) || defined(__clang__)\n#pragma GCC diagnostic pop\n#endif\n\n#endif//IMSTB_TEXTEDIT_IMPLEMENTATION\n\n/*\n------------------------------------------------------------------------------\nThis software is available under 2 licenses -- choose whichever you prefer.\n------------------------------------------------------------------------------\nALTERNATIVE A - MIT License\nCopyright (c) 2017 Sean Barrett\nPermission is hereby granted, free of charge, to any person obtaining a copy of\nthis software and associated documentation files (the \"Software\"), to deal in\nthe Software without restriction, including without limitation the rights to\nuse, copy, modify, merge, publish, distribute, sublicense, and/or sell copies\nof the Software, and to permit persons to whom the Software is furnished to do\nso, subject to the following conditions:\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n------------------------------------------------------------------------------\nALTERNATIVE B - Public Domain (www.unlicense.org)\nThis is free and unencumbered software released into the public domain.\nAnyone is free to copy, modify, publish, use, compile, sell, or distribute this\nsoftware, either in source code form or as a compiled binary, for any purpose,\ncommercial or non-commercial, and by any means.\nIn jurisdictions that recognize copyright laws, the author or authors of this\nsoftware dedicate any and all copyright interest in the software to the public\ndomain. We make this dedication for the benefit of the public at large and to\nthe detriment of our heirs and successors. We intend this dedication to be an\novert act of relinquishment in perpetuity of all present and future rights to\nthis software under copyright law.\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN\nACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION\nWITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n------------------------------------------------------------------------------\n*/\n"
  },
  {
    "path": "src/DesktopPlusUI/imgui/imstb_truetype.h",
    "content": "// [DEAR IMGUI]\n// This is a slightly modified version of stb_truetype.h 1.26.\n// Mostly fixing for compiler and static analyzer warnings.\n// Grep for [DEAR IMGUI] to find the changes.\n\n// stb_truetype.h - v1.26 - public domain\n// authored from 2009-2021 by Sean Barrett / RAD Game Tools\n//\n// =======================================================================\n//\n//    NO SECURITY GUARANTEE -- DO NOT USE THIS ON UNTRUSTED FONT FILES\n//\n// This library does no range checking of the offsets found in the file,\n// meaning an attacker can use it to read arbitrary memory.\n//\n// =======================================================================\n//\n//   This library processes TrueType files:\n//        parse files\n//        extract glyph metrics\n//        extract glyph shapes\n//        render glyphs to one-channel bitmaps with antialiasing (box filter)\n//        render glyphs to one-channel SDF bitmaps (signed-distance field/function)\n//\n//   Todo:\n//        non-MS cmaps\n//        crashproof on bad data\n//        hinting? (no longer patented)\n//        cleartype-style AA?\n//        optimize: use simple memory allocator for intermediates\n//        optimize: build edge-list directly from curves\n//        optimize: rasterize directly from curves?\n//\n// ADDITIONAL CONTRIBUTORS\n//\n//   Mikko Mononen: compound shape support, more cmap formats\n//   Tor Andersson: kerning, subpixel rendering\n//   Dougall Johnson: OpenType / Type 2 font handling\n//   Daniel Ribeiro Maciel: basic GPOS-based kerning\n//\n//   Misc other:\n//       Ryan Gordon\n//       Simon Glass\n//       github:IntellectualKitty\n//       Imanol Celaya\n//       Daniel Ribeiro Maciel\n//\n//   Bug/warning reports/fixes:\n//       \"Zer\" on mollyrocket       Fabian \"ryg\" Giesen   github:NiLuJe\n//       Cass Everitt               Martins Mozeiko       github:aloucks\n//       stoiko (Haemimont Games)   Cap Petschulat        github:oyvindjam\n//       Brian Hook                 Omar Cornut           github:vassvik\n//       Walter van Niftrik         Ryan Griege\n//       David Gow                  Peter LaValle\n//       David Given                Sergey Popov\n//       Ivan-Assen Ivanov          Giumo X. Clanjor\n//       Anthony Pesch              Higor Euripedes\n//       Johan Duparc               Thomas Fields\n//       Hou Qiming                 Derek Vinyard\n//       Rob Loach                  Cort Stratton\n//       Kenney Phillis Jr.         Brian Costabile\n//       Ken Voskuil (kaesve)\n//\n// VERSION HISTORY\n//\n//   1.26 (2021-08-28) fix broken rasterizer\n//   1.25 (2021-07-11) many fixes\n//   1.24 (2020-02-05) fix warning\n//   1.23 (2020-02-02) query SVG data for glyphs; query whole kerning table (but only kern not GPOS)\n//   1.22 (2019-08-11) minimize missing-glyph duplication; fix kerning if both 'GPOS' and 'kern' are defined\n//   1.21 (2019-02-25) fix warning\n//   1.20 (2019-02-07) PackFontRange skips missing codepoints; GetScaleFontVMetrics()\n//   1.19 (2018-02-11) GPOS kerning, STBTT_fmod\n//   1.18 (2018-01-29) add missing function\n//   1.17 (2017-07-23) make more arguments const; doc fix\n//   1.16 (2017-07-12) SDF support\n//   1.15 (2017-03-03) make more arguments const\n//   1.14 (2017-01-16) num-fonts-in-TTC function\n//   1.13 (2017-01-02) support OpenType fonts, certain Apple fonts\n//   1.12 (2016-10-25) suppress warnings about casting away const with -Wcast-qual\n//   1.11 (2016-04-02) fix unused-variable warning\n//   1.10 (2016-04-02) user-defined fabs(); rare memory leak; remove duplicate typedef\n//   1.09 (2016-01-16) warning fix; avoid crash on outofmem; use allocation userdata properly\n//   1.08 (2015-09-13) document stbtt_Rasterize(); fixes for vertical & horizontal edges\n//   1.07 (2015-08-01) allow PackFontRanges to accept arrays of sparse codepoints;\n//                     variant PackFontRanges to pack and render in separate phases;\n//                     fix stbtt_GetFontOFfsetForIndex (never worked for non-0 input?);\n//                     fixed an assert() bug in the new rasterizer\n//                     replace assert() with STBTT_assert() in new rasterizer\n//\n//   Full history can be found at the end of this file.\n//\n// LICENSE\n//\n//   See end of file for license information.\n//\n// USAGE\n//\n//   Include this file in whatever places need to refer to it. In ONE C/C++\n//   file, write:\n//      #define STB_TRUETYPE_IMPLEMENTATION\n//   before the #include of this file. This expands out the actual\n//   implementation into that C/C++ file.\n//\n//   To make the implementation private to the file that generates the implementation,\n//      #define STBTT_STATIC\n//\n//   Simple 3D API (don't ship this, but it's fine for tools and quick start)\n//           stbtt_BakeFontBitmap()               -- bake a font to a bitmap for use as texture\n//           stbtt_GetBakedQuad()                 -- compute quad to draw for a given char\n//\n//   Improved 3D API (more shippable):\n//           #include \"stb_rect_pack.h\"           -- optional, but you really want it\n//           stbtt_PackBegin()\n//           stbtt_PackSetOversampling()          -- for improved quality on small fonts\n//           stbtt_PackFontRanges()               -- pack and renders\n//           stbtt_PackEnd()\n//           stbtt_GetPackedQuad()\n//\n//   \"Load\" a font file from a memory buffer (you have to keep the buffer loaded)\n//           stbtt_InitFont()\n//           stbtt_GetFontOffsetForIndex()        -- indexing for TTC font collections\n//           stbtt_GetNumberOfFonts()             -- number of fonts for TTC font collections\n//\n//   Render a unicode codepoint to a bitmap\n//           stbtt_GetCodepointBitmap()           -- allocates and returns a bitmap\n//           stbtt_MakeCodepointBitmap()          -- renders into bitmap you provide\n//           stbtt_GetCodepointBitmapBox()        -- how big the bitmap must be\n//\n//   Character advance/positioning\n//           stbtt_GetCodepointHMetrics()\n//           stbtt_GetFontVMetrics()\n//           stbtt_GetFontVMetricsOS2()\n//           stbtt_GetCodepointKernAdvance()\n//\n//   Starting with version 1.06, the rasterizer was replaced with a new,\n//   faster and generally-more-precise rasterizer. The new rasterizer more\n//   accurately measures pixel coverage for anti-aliasing, except in the case\n//   where multiple shapes overlap, in which case it overestimates the AA pixel\n//   coverage. Thus, anti-aliasing of intersecting shapes may look wrong. If\n//   this turns out to be a problem, you can re-enable the old rasterizer with\n//        #define STBTT_RASTERIZER_VERSION 1\n//   which will incur about a 15% speed hit.\n//\n// ADDITIONAL DOCUMENTATION\n//\n//   Immediately after this block comment are a series of sample programs.\n//\n//   After the sample programs is the \"header file\" section. This section\n//   includes documentation for each API function.\n//\n//   Some important concepts to understand to use this library:\n//\n//      Codepoint\n//         Characters are defined by unicode codepoints, e.g. 65 is\n//         uppercase A, 231 is lowercase c with a cedilla, 0x7e30 is\n//         the hiragana for \"ma\".\n//\n//      Glyph\n//         A visual character shape (every codepoint is rendered as\n//         some glyph)\n//\n//      Glyph index\n//         A font-specific integer ID representing a glyph\n//\n//      Baseline\n//         Glyph shapes are defined relative to a baseline, which is the\n//         bottom of uppercase characters. Characters extend both above\n//         and below the baseline.\n//\n//      Current Point\n//         As you draw text to the screen, you keep track of a \"current point\"\n//         which is the origin of each character. The current point's vertical\n//         position is the baseline. Even \"baked fonts\" use this model.\n//\n//      Vertical Font Metrics\n//         The vertical qualities of the font, used to vertically position\n//         and space the characters. See docs for stbtt_GetFontVMetrics.\n//\n//      Font Size in Pixels or Points\n//         The preferred interface for specifying font sizes in stb_truetype\n//         is to specify how tall the font's vertical extent should be in pixels.\n//         If that sounds good enough, skip the next paragraph.\n//\n//         Most font APIs instead use \"points\", which are a common typographic\n//         measurement for describing font size, defined as 72 points per inch.\n//         stb_truetype provides a point API for compatibility. However, true\n//         \"per inch\" conventions don't make much sense on computer displays\n//         since different monitors have different number of pixels per\n//         inch. For example, Windows traditionally uses a convention that\n//         there are 96 pixels per inch, thus making 'inch' measurements have\n//         nothing to do with inches, and thus effectively defining a point to\n//         be 1.333 pixels. Additionally, the TrueType font data provides\n//         an explicit scale factor to scale a given font's glyphs to points,\n//         but the author has observed that this scale factor is often wrong\n//         for non-commercial fonts, thus making fonts scaled in points\n//         according to the TrueType spec incoherently sized in practice.\n//\n// DETAILED USAGE:\n//\n//  Scale:\n//    Select how high you want the font to be, in points or pixels.\n//    Call ScaleForPixelHeight or ScaleForMappingEmToPixels to compute\n//    a scale factor SF that will be used by all other functions.\n//\n//  Baseline:\n//    You need to select a y-coordinate that is the baseline of where\n//    your text will appear. Call GetFontBoundingBox to get the baseline-relative\n//    bounding box for all characters. SF*-y0 will be the distance in pixels\n//    that the worst-case character could extend above the baseline, so if\n//    you want the top edge of characters to appear at the top of the\n//    screen where y=0, then you would set the baseline to SF*-y0.\n//\n//  Current point:\n//    Set the current point where the first character will appear. The\n//    first character could extend left of the current point; this is font\n//    dependent. You can either choose a current point that is the leftmost\n//    point and hope, or add some padding, or check the bounding box or\n//    left-side-bearing of the first character to be displayed and set\n//    the current point based on that.\n//\n//  Displaying a character:\n//    Compute the bounding box of the character. It will contain signed values\n//    relative to <current_point, baseline>. I.e. if it returns x0,y0,x1,y1,\n//    then the character should be displayed in the rectangle from\n//    <current_point+SF*x0, baseline+SF*y0> to <current_point+SF*x1,baseline+SF*y1).\n//\n//  Advancing for the next character:\n//    Call GlyphHMetrics, and compute 'current_point += SF * advance'.\n//\n//\n// ADVANCED USAGE\n//\n//   Quality:\n//\n//    - Use the functions with Subpixel at the end to allow your characters\n//      to have subpixel positioning. Since the font is anti-aliased, not\n//      hinted, this is very import for quality. (This is not possible with\n//      baked fonts.)\n//\n//    - Kerning is now supported, and if you're supporting subpixel rendering\n//      then kerning is worth using to give your text a polished look.\n//\n//   Performance:\n//\n//    - Convert Unicode codepoints to glyph indexes and operate on the glyphs;\n//      if you don't do this, stb_truetype is forced to do the conversion on\n//      every call.\n//\n//    - There are a lot of memory allocations. We should modify it to take\n//      a temp buffer and allocate from the temp buffer (without freeing),\n//      should help performance a lot.\n//\n// NOTES\n//\n//   The system uses the raw data found in the .ttf file without changing it\n//   and without building auxiliary data structures. This is a bit inefficient\n//   on little-endian systems (the data is big-endian), but assuming you're\n//   caching the bitmaps or glyph shapes this shouldn't be a big deal.\n//\n//   It appears to be very hard to programmatically determine what font a\n//   given file is in a general way. I provide an API for this, but I don't\n//   recommend it.\n//\n//\n// PERFORMANCE MEASUREMENTS FOR 1.06:\n//\n//                      32-bit     64-bit\n//   Previous release:  8.83 s     7.68 s\n//   Pool allocations:  7.72 s     6.34 s\n//   Inline sort     :  6.54 s     5.65 s\n//   New rasterizer  :  5.63 s     5.00 s\n\n//////////////////////////////////////////////////////////////////////////////\n//////////////////////////////////////////////////////////////////////////////\n////\n////  SAMPLE PROGRAMS\n////\n//\n//  Incomplete text-in-3d-api example, which draws quads properly aligned to be lossless.\n//  See \"tests/truetype_demo_win32.c\" for a complete version.\n#if 0\n#define STB_TRUETYPE_IMPLEMENTATION  // force following include to generate implementation\n#include \"stb_truetype.h\"\n\nunsigned char ttf_buffer[1<<20];\nunsigned char temp_bitmap[512*512];\n\nstbtt_bakedchar cdata[96]; // ASCII 32..126 is 95 glyphs\nGLuint ftex;\n\nvoid my_stbtt_initfont(void)\n{\n   fread(ttf_buffer, 1, 1<<20, fopen(\"c:/windows/fonts/times.ttf\", \"rb\"));\n   stbtt_BakeFontBitmap(ttf_buffer,0, 32.0, temp_bitmap,512,512, 32,96, cdata); // no guarantee this fits!\n   // can free ttf_buffer at this point\n   glGenTextures(1, &ftex);\n   glBindTexture(GL_TEXTURE_2D, ftex);\n   glTexImage2D(GL_TEXTURE_2D, 0, GL_ALPHA, 512,512, 0, GL_ALPHA, GL_UNSIGNED_BYTE, temp_bitmap);\n   // can free temp_bitmap at this point\n   glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);\n}\n\nvoid my_stbtt_print(float x, float y, char *text)\n{\n   // assume orthographic projection with units = screen pixels, origin at top left\n   glEnable(GL_BLEND);\n   glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);\n   glEnable(GL_TEXTURE_2D);\n   glBindTexture(GL_TEXTURE_2D, ftex);\n   glBegin(GL_QUADS);\n   while (*text) {\n      if (*text >= 32 && *text < 128) {\n         stbtt_aligned_quad q;\n         stbtt_GetBakedQuad(cdata, 512,512, *text-32, &x,&y,&q,1);//1=opengl & d3d10+,0=d3d9\n         glTexCoord2f(q.s0,q.t0); glVertex2f(q.x0,q.y0);\n         glTexCoord2f(q.s1,q.t0); glVertex2f(q.x1,q.y0);\n         glTexCoord2f(q.s1,q.t1); glVertex2f(q.x1,q.y1);\n         glTexCoord2f(q.s0,q.t1); glVertex2f(q.x0,q.y1);\n      }\n      ++text;\n   }\n   glEnd();\n}\n#endif\n//\n//\n//////////////////////////////////////////////////////////////////////////////\n//\n// Complete program (this compiles): get a single bitmap, print as ASCII art\n//\n#if 0\n#include <stdio.h>\n#define STB_TRUETYPE_IMPLEMENTATION  // force following include to generate implementation\n#include \"stb_truetype.h\"\n\nchar ttf_buffer[1<<25];\n\nint main(int argc, char **argv)\n{\n   stbtt_fontinfo font;\n   unsigned char *bitmap;\n   int w,h,i,j,c = (argc > 1 ? atoi(argv[1]) : 'a'), s = (argc > 2 ? atoi(argv[2]) : 20);\n\n   fread(ttf_buffer, 1, 1<<25, fopen(argc > 3 ? argv[3] : \"c:/windows/fonts/arialbd.ttf\", \"rb\"));\n\n   stbtt_InitFont(&font, ttf_buffer, stbtt_GetFontOffsetForIndex(ttf_buffer,0));\n   bitmap = stbtt_GetCodepointBitmap(&font, 0,stbtt_ScaleForPixelHeight(&font, s), c, &w, &h, 0,0);\n\n   for (j=0; j < h; ++j) {\n      for (i=0; i < w; ++i)\n         putchar(\" .:ioVM@\"[bitmap[j*w+i]>>5]);\n      putchar('\\n');\n   }\n   return 0;\n}\n#endif\n//\n// Output:\n//\n//     .ii.\n//    @@@@@@.\n//   V@Mio@@o\n//   :i.  V@V\n//     :oM@@M\n//   :@@@MM@M\n//   @@o  o@M\n//  :@@.  M@M\n//   @@@o@@@@\n//   :M@@V:@@.\n//\n//////////////////////////////////////////////////////////////////////////////\n//\n// Complete program: print \"Hello World!\" banner, with bugs\n//\n#if 0\nchar buffer[24<<20];\nunsigned char screen[20][79];\n\nint main(int arg, char **argv)\n{\n   stbtt_fontinfo font;\n   int i,j,ascent,baseline,ch=0;\n   float scale, xpos=2; // leave a little padding in case the character extends left\n   char *text = \"Heljo World!\"; // intentionally misspelled to show 'lj' brokenness\n\n   fread(buffer, 1, 1000000, fopen(\"c:/windows/fonts/arialbd.ttf\", \"rb\"));\n   stbtt_InitFont(&font, buffer, 0);\n\n   scale = stbtt_ScaleForPixelHeight(&font, 15);\n   stbtt_GetFontVMetrics(&font, &ascent,0,0);\n   baseline = (int) (ascent*scale);\n\n   while (text[ch]) {\n      int advance,lsb,x0,y0,x1,y1;\n      float x_shift = xpos - (float) floor(xpos);\n      stbtt_GetCodepointHMetrics(&font, text[ch], &advance, &lsb);\n      stbtt_GetCodepointBitmapBoxSubpixel(&font, text[ch], scale,scale,x_shift,0, &x0,&y0,&x1,&y1);\n      stbtt_MakeCodepointBitmapSubpixel(&font, &screen[baseline + y0][(int) xpos + x0], x1-x0,y1-y0, 79, scale,scale,x_shift,0, text[ch]);\n      // note that this stomps the old data, so where character boxes overlap (e.g. 'lj') it's wrong\n      // because this API is really for baking character bitmaps into textures. if you want to render\n      // a sequence of characters, you really need to render each bitmap to a temp buffer, then\n      // \"alpha blend\" that into the working buffer\n      xpos += (advance * scale);\n      if (text[ch+1])\n         xpos += scale*stbtt_GetCodepointKernAdvance(&font, text[ch],text[ch+1]);\n      ++ch;\n   }\n\n   for (j=0; j < 20; ++j) {\n      for (i=0; i < 78; ++i)\n         putchar(\" .:ioVM@\"[screen[j][i]>>5]);\n      putchar('\\n');\n   }\n\n   return 0;\n}\n#endif\n\n\n//////////////////////////////////////////////////////////////////////////////\n//////////////////////////////////////////////////////////////////////////////\n////\n////   INTEGRATION WITH YOUR CODEBASE\n////\n////   The following sections allow you to supply alternate definitions\n////   of C library functions used by stb_truetype, e.g. if you don't\n////   link with the C runtime library.\n\n#ifdef STB_TRUETYPE_IMPLEMENTATION\n   // #define your own (u)stbtt_int8/16/32 before including to override this\n   #ifndef stbtt_uint8\n   typedef unsigned char   stbtt_uint8;\n   typedef signed   char   stbtt_int8;\n   typedef unsigned short  stbtt_uint16;\n   typedef signed   short  stbtt_int16;\n   typedef unsigned int    stbtt_uint32;\n   typedef signed   int    stbtt_int32;\n   #endif\n\n   typedef char stbtt__check_size32[sizeof(stbtt_int32)==4 ? 1 : -1];\n   typedef char stbtt__check_size16[sizeof(stbtt_int16)==2 ? 1 : -1];\n\n   // e.g. #define your own STBTT_ifloor/STBTT_iceil() to avoid math.h\n   #ifndef STBTT_ifloor\n   #include <math.h>\n   #define STBTT_ifloor(x)   ((int) floor(x))\n   #define STBTT_iceil(x)    ((int) ceil(x))\n   #endif\n\n   #ifndef STBTT_sqrt\n   #include <math.h>\n   #define STBTT_sqrt(x)      sqrt(x)\n   #define STBTT_pow(x,y)     pow(x,y)\n   #endif\n\n   #ifndef STBTT_fmod\n   #include <math.h>\n   #define STBTT_fmod(x,y)    fmod(x,y)\n   #endif\n\n   #ifndef STBTT_cos\n   #include <math.h>\n   #define STBTT_cos(x)       cos(x)\n   #define STBTT_acos(x)      acos(x)\n   #endif\n\n   #ifndef STBTT_fabs\n   #include <math.h>\n   #define STBTT_fabs(x)      fabs(x)\n   #endif\n\n   // #define your own functions \"STBTT_malloc\" / \"STBTT_free\" to avoid malloc.h\n   #ifndef STBTT_malloc\n   #include <stdlib.h>\n   #define STBTT_malloc(x,u)  ((void)(u),malloc(x))\n   #define STBTT_free(x,u)    ((void)(u),free(x))\n   #endif\n\n   #ifndef STBTT_assert\n   #include <assert.h>\n   #define STBTT_assert(x)    assert(x)\n   #endif\n\n   #ifndef STBTT_strlen\n   #include <string.h>\n   #define STBTT_strlen(x)    strlen(x)\n   #endif\n\n   #ifndef STBTT_memcpy\n   #include <string.h>\n   #define STBTT_memcpy       memcpy\n   #define STBTT_memset       memset\n   #endif\n#endif\n\n///////////////////////////////////////////////////////////////////////////////\n///////////////////////////////////////////////////////////////////////////////\n////\n////   INTERFACE\n////\n////\n\n#ifndef __STB_INCLUDE_STB_TRUETYPE_H__\n#define __STB_INCLUDE_STB_TRUETYPE_H__\n\n#ifdef STBTT_STATIC\n#define STBTT_DEF static\n#else\n#define STBTT_DEF extern\n#endif\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif\n\n// private structure\ntypedef struct\n{\n   unsigned char *data;\n   int cursor;\n   int size;\n} stbtt__buf;\n\n//////////////////////////////////////////////////////////////////////////////\n//\n// TEXTURE BAKING API\n//\n// If you use this API, you only have to call two functions ever.\n//\n\ntypedef struct\n{\n   unsigned short x0,y0,x1,y1; // coordinates of bbox in bitmap\n   float xoff,yoff,xadvance;\n} stbtt_bakedchar;\n\nSTBTT_DEF int stbtt_BakeFontBitmap(const unsigned char *data, int offset,  // font location (use offset=0 for plain .ttf)\n                                float pixel_height,                     // height of font in pixels\n                                unsigned char *pixels, int pw, int ph,  // bitmap to be filled in\n                                int first_char, int num_chars,          // characters to bake\n                                stbtt_bakedchar *chardata);             // you allocate this, it's num_chars long\n// if return is positive, the first unused row of the bitmap\n// if return is negative, returns the negative of the number of characters that fit\n// if return is 0, no characters fit and no rows were used\n// This uses a very crappy packing.\n\ntypedef struct\n{\n   float x0,y0,s0,t0; // top-left\n   float x1,y1,s1,t1; // bottom-right\n} stbtt_aligned_quad;\n\nSTBTT_DEF void stbtt_GetBakedQuad(const stbtt_bakedchar *chardata, int pw, int ph,  // same data as above\n                               int char_index,             // character to display\n                               float *xpos, float *ypos,   // pointers to current position in screen pixel space\n                               stbtt_aligned_quad *q,      // output: quad to draw\n                               int opengl_fillrule);       // true if opengl fill rule; false if DX9 or earlier\n// Call GetBakedQuad with char_index = 'character - first_char', and it\n// creates the quad you need to draw and advances the current position.\n//\n// The coordinate system used assumes y increases downwards.\n//\n// Characters will extend both above and below the current position;\n// see discussion of \"BASELINE\" above.\n//\n// It's inefficient; you might want to c&p it and optimize it.\n\nSTBTT_DEF void stbtt_GetScaledFontVMetrics(const unsigned char *fontdata, int index, float size, float *ascent, float *descent, float *lineGap);\n// Query the font vertical metrics without having to create a font first.\n\n\n//////////////////////////////////////////////////////////////////////////////\n//\n// NEW TEXTURE BAKING API\n//\n// This provides options for packing multiple fonts into one atlas, not\n// perfectly but better than nothing.\n\ntypedef struct\n{\n   unsigned short x0,y0,x1,y1; // coordinates of bbox in bitmap\n   float xoff,yoff,xadvance;\n   float xoff2,yoff2;\n} stbtt_packedchar;\n\ntypedef struct stbtt_pack_context stbtt_pack_context;\ntypedef struct stbtt_fontinfo stbtt_fontinfo;\n#ifndef STB_RECT_PACK_VERSION\ntypedef struct stbrp_rect stbrp_rect;\n#endif\n\nSTBTT_DEF int  stbtt_PackBegin(stbtt_pack_context *spc, unsigned char *pixels, int width, int height, int stride_in_bytes, int padding, void *alloc_context);\n// Initializes a packing context stored in the passed-in stbtt_pack_context.\n// Future calls using this context will pack characters into the bitmap passed\n// in here: a 1-channel bitmap that is width * height. stride_in_bytes is\n// the distance from one row to the next (or 0 to mean they are packed tightly\n// together). \"padding\" is the amount of padding to leave between each\n// character (normally you want '1' for bitmaps you'll use as textures with\n// bilinear filtering).\n//\n// Returns 0 on failure, 1 on success.\n\nSTBTT_DEF void stbtt_PackEnd  (stbtt_pack_context *spc);\n// Cleans up the packing context and frees all memory.\n\n#define STBTT_POINT_SIZE(x)   (-(x))\n\nSTBTT_DEF int  stbtt_PackFontRange(stbtt_pack_context *spc, const unsigned char *fontdata, int font_index, float font_size,\n                                int first_unicode_char_in_range, int num_chars_in_range, stbtt_packedchar *chardata_for_range);\n// Creates character bitmaps from the font_index'th font found in fontdata (use\n// font_index=0 if you don't know what that is). It creates num_chars_in_range\n// bitmaps for characters with unicode values starting at first_unicode_char_in_range\n// and increasing. Data for how to render them is stored in chardata_for_range;\n// pass these to stbtt_GetPackedQuad to get back renderable quads.\n//\n// font_size is the full height of the character from ascender to descender,\n// as computed by stbtt_ScaleForPixelHeight. To use a point size as computed\n// by stbtt_ScaleForMappingEmToPixels, wrap the point size in STBTT_POINT_SIZE()\n// and pass that result as 'font_size':\n//       ...,                  20 , ... // font max minus min y is 20 pixels tall\n//       ..., STBTT_POINT_SIZE(20), ... // 'M' is 20 pixels tall\n\ntypedef struct\n{\n   float font_size;\n   int first_unicode_codepoint_in_range;  // if non-zero, then the chars are continuous, and this is the first codepoint\n   int *array_of_unicode_codepoints;       // if non-zero, then this is an array of unicode codepoints\n   int num_chars;\n   stbtt_packedchar *chardata_for_range; // output\n   unsigned char h_oversample, v_oversample; // don't set these, they're used internally\n} stbtt_pack_range;\n\nSTBTT_DEF int  stbtt_PackFontRanges(stbtt_pack_context *spc, const unsigned char *fontdata, int font_index, stbtt_pack_range *ranges, int num_ranges);\n// Creates character bitmaps from multiple ranges of characters stored in\n// ranges. This will usually create a better-packed bitmap than multiple\n// calls to stbtt_PackFontRange. Note that you can call this multiple\n// times within a single PackBegin/PackEnd.\n\nSTBTT_DEF void stbtt_PackSetOversampling(stbtt_pack_context *spc, unsigned int h_oversample, unsigned int v_oversample);\n// Oversampling a font increases the quality by allowing higher-quality subpixel\n// positioning, and is especially valuable at smaller text sizes.\n//\n// This function sets the amount of oversampling for all following calls to\n// stbtt_PackFontRange(s) or stbtt_PackFontRangesGatherRects for a given\n// pack context. The default (no oversampling) is achieved by h_oversample=1\n// and v_oversample=1. The total number of pixels required is\n// h_oversample*v_oversample larger than the default; for example, 2x2\n// oversampling requires 4x the storage of 1x1. For best results, render\n// oversampled textures with bilinear filtering. Look at the readme in\n// stb/tests/oversample for information about oversampled fonts\n//\n// To use with PackFontRangesGather etc., you must set it before calls\n// call to PackFontRangesGatherRects.\n\nSTBTT_DEF void stbtt_PackSetSkipMissingCodepoints(stbtt_pack_context *spc, int skip);\n// If skip != 0, this tells stb_truetype to skip any codepoints for which\n// there is no corresponding glyph. If skip=0, which is the default, then\n// codepoints without a glyph received the font's \"missing character\" glyph,\n// typically an empty box by convention.\n\nSTBTT_DEF void stbtt_GetPackedQuad(const stbtt_packedchar *chardata, int pw, int ph,  // same data as above\n                               int char_index,             // character to display\n                               float *xpos, float *ypos,   // pointers to current position in screen pixel space\n                               stbtt_aligned_quad *q,      // output: quad to draw\n                               int align_to_integer);\n\nSTBTT_DEF int  stbtt_PackFontRangesGatherRects(stbtt_pack_context *spc, const stbtt_fontinfo *info, stbtt_pack_range *ranges, int num_ranges, stbrp_rect *rects);\nSTBTT_DEF void stbtt_PackFontRangesPackRects(stbtt_pack_context *spc, stbrp_rect *rects, int num_rects);\nSTBTT_DEF int  stbtt_PackFontRangesRenderIntoRects(stbtt_pack_context *spc, const stbtt_fontinfo *info, stbtt_pack_range *ranges, int num_ranges, stbrp_rect *rects);\n// Calling these functions in sequence is roughly equivalent to calling\n// stbtt_PackFontRanges(). If you more control over the packing of multiple\n// fonts, or if you want to pack custom data into a font texture, take a look\n// at the source to of stbtt_PackFontRanges() and create a custom version\n// using these functions, e.g. call GatherRects multiple times,\n// building up a single array of rects, then call PackRects once,\n// then call RenderIntoRects repeatedly. This may result in a\n// better packing than calling PackFontRanges multiple times\n// (or it may not).\n\n// this is an opaque structure that you shouldn't mess with which holds\n// all the context needed from PackBegin to PackEnd.\nstruct stbtt_pack_context {\n   void *user_allocator_context;\n   void *pack_info;\n   int   width;\n   int   height;\n   int   stride_in_bytes;\n   int   padding;\n   int   skip_missing;\n   unsigned int   h_oversample, v_oversample;\n   unsigned char *pixels;\n   void  *nodes;\n};\n\n//////////////////////////////////////////////////////////////////////////////\n//\n// FONT LOADING\n//\n//\n\nSTBTT_DEF int stbtt_GetNumberOfFonts(const unsigned char *data);\n// This function will determine the number of fonts in a font file.  TrueType\n// collection (.ttc) files may contain multiple fonts, while TrueType font\n// (.ttf) files only contain one font. The number of fonts can be used for\n// indexing with the previous function where the index is between zero and one\n// less than the total fonts. If an error occurs, -1 is returned.\n\nSTBTT_DEF int stbtt_GetFontOffsetForIndex(const unsigned char *data, int index);\n// Each .ttf/.ttc file may have more than one font. Each font has a sequential\n// index number starting from 0. Call this function to get the font offset for\n// a given index; it returns -1 if the index is out of range. A regular .ttf\n// file will only define one font and it always be at offset 0, so it will\n// return '0' for index 0, and -1 for all other indices.\n\n// The following structure is defined publicly so you can declare one on\n// the stack or as a global or etc, but you should treat it as opaque.\nstruct stbtt_fontinfo\n{\n   void           * userdata;\n   unsigned char  * data;              // pointer to .ttf file\n   int              fontstart;         // offset of start of font\n\n   int numGlyphs;                     // number of glyphs, needed for range checking\n\n   int loca,head,glyf,hhea,hmtx,kern,gpos,svg; // table locations as offset from start of .ttf\n   int index_map;                     // a cmap mapping for our chosen character encoding\n   int indexToLocFormat;              // format needed to map from glyph index to glyph\n\n   stbtt__buf cff;                    // cff font data\n   stbtt__buf charstrings;            // the charstring index\n   stbtt__buf gsubrs;                 // global charstring subroutines index\n   stbtt__buf subrs;                  // private charstring subroutines index\n   stbtt__buf fontdicts;              // array of font dicts\n   stbtt__buf fdselect;               // map from glyph to fontdict\n};\n\nSTBTT_DEF int stbtt_InitFont(stbtt_fontinfo *info, const unsigned char *data, int offset);\n// Given an offset into the file that defines a font, this function builds\n// the necessary cached info for the rest of the system. You must allocate\n// the stbtt_fontinfo yourself, and stbtt_InitFont will fill it out. You don't\n// need to do anything special to free it, because the contents are pure\n// value data with no additional data structures. Returns 0 on failure.\n\n\n//////////////////////////////////////////////////////////////////////////////\n//\n// CHARACTER TO GLYPH-INDEX CONVERSIOn\n\nSTBTT_DEF int stbtt_FindGlyphIndex(const stbtt_fontinfo *info, int unicode_codepoint);\n// If you're going to perform multiple operations on the same character\n// and you want a speed-up, call this function with the character you're\n// going to process, then use glyph-based functions instead of the\n// codepoint-based functions.\n// Returns 0 if the character codepoint is not defined in the font.\n\n\n//////////////////////////////////////////////////////////////////////////////\n//\n// CHARACTER PROPERTIES\n//\n\nSTBTT_DEF float stbtt_ScaleForPixelHeight(const stbtt_fontinfo *info, float pixels);\n// computes a scale factor to produce a font whose \"height\" is 'pixels' tall.\n// Height is measured as the distance from the highest ascender to the lowest\n// descender; in other words, it's equivalent to calling stbtt_GetFontVMetrics\n// and computing:\n//       scale = pixels / (ascent - descent)\n// so if you prefer to measure height by the ascent only, use a similar calculation.\n\nSTBTT_DEF float stbtt_ScaleForMappingEmToPixels(const stbtt_fontinfo *info, float pixels);\n// computes a scale factor to produce a font whose EM size is mapped to\n// 'pixels' tall. This is probably what traditional APIs compute, but\n// I'm not positive.\n\nSTBTT_DEF void stbtt_GetFontVMetrics(const stbtt_fontinfo *info, int *ascent, int *descent, int *lineGap);\n// ascent is the coordinate above the baseline the font extends; descent\n// is the coordinate below the baseline the font extends (i.e. it is typically negative)\n// lineGap is the spacing between one row's descent and the next row's ascent...\n// so you should advance the vertical position by \"*ascent - *descent + *lineGap\"\n//   these are expressed in unscaled coordinates, so you must multiply by\n//   the scale factor for a given size\n\nSTBTT_DEF int  stbtt_GetFontVMetricsOS2(const stbtt_fontinfo *info, int *typoAscent, int *typoDescent, int *typoLineGap);\n// analogous to GetFontVMetrics, but returns the \"typographic\" values from the OS/2\n// table (specific to MS/Windows TTF files).\n//\n// Returns 1 on success (table present), 0 on failure.\n\nSTBTT_DEF void stbtt_GetFontBoundingBox(const stbtt_fontinfo *info, int *x0, int *y0, int *x1, int *y1);\n// the bounding box around all possible characters\n\nSTBTT_DEF void stbtt_GetCodepointHMetrics(const stbtt_fontinfo *info, int codepoint, int *advanceWidth, int *leftSideBearing);\n// leftSideBearing is the offset from the current horizontal position to the left edge of the character\n// advanceWidth is the offset from the current horizontal position to the next horizontal position\n//   these are expressed in unscaled coordinates\n\nSTBTT_DEF int  stbtt_GetCodepointKernAdvance(const stbtt_fontinfo *info, int ch1, int ch2);\n// an additional amount to add to the 'advance' value between ch1 and ch2\n\nSTBTT_DEF int stbtt_GetCodepointBox(const stbtt_fontinfo *info, int codepoint, int *x0, int *y0, int *x1, int *y1);\n// Gets the bounding box of the visible part of the glyph, in unscaled coordinates\n\nSTBTT_DEF void stbtt_GetGlyphHMetrics(const stbtt_fontinfo *info, int glyph_index, int *advanceWidth, int *leftSideBearing);\nSTBTT_DEF int  stbtt_GetGlyphKernAdvance(const stbtt_fontinfo *info, int glyph1, int glyph2);\nSTBTT_DEF int  stbtt_GetGlyphBox(const stbtt_fontinfo *info, int glyph_index, int *x0, int *y0, int *x1, int *y1);\n// as above, but takes one or more glyph indices for greater efficiency\n\ntypedef struct stbtt_kerningentry\n{\n   int glyph1; // use stbtt_FindGlyphIndex\n   int glyph2;\n   int advance;\n} stbtt_kerningentry;\n\nSTBTT_DEF int  stbtt_GetKerningTableLength(const stbtt_fontinfo *info);\nSTBTT_DEF int  stbtt_GetKerningTable(const stbtt_fontinfo *info, stbtt_kerningentry* table, int table_length);\n// Retrieves a complete list of all of the kerning pairs provided by the font\n// stbtt_GetKerningTable never writes more than table_length entries and returns how many entries it did write.\n// The table will be sorted by (a.glyph1 == b.glyph1)?(a.glyph2 < b.glyph2):(a.glyph1 < b.glyph1)\n\n//////////////////////////////////////////////////////////////////////////////\n//\n// GLYPH SHAPES (you probably don't need these, but they have to go before\n// the bitmaps for C declaration-order reasons)\n//\n\n#ifndef STBTT_vmove // you can predefine these to use different values (but why?)\n   enum {\n      STBTT_vmove=1,\n      STBTT_vline,\n      STBTT_vcurve,\n      STBTT_vcubic\n   };\n#endif\n\n#ifndef stbtt_vertex // you can predefine this to use different values\n                   // (we share this with other code at RAD)\n   #define stbtt_vertex_type short // can't use stbtt_int16 because that's not visible in the header file\n   typedef struct\n   {\n      stbtt_vertex_type x,y,cx,cy,cx1,cy1;\n      unsigned char type,padding;\n   } stbtt_vertex;\n#endif\n\nSTBTT_DEF int stbtt_IsGlyphEmpty(const stbtt_fontinfo *info, int glyph_index);\n// returns non-zero if nothing is drawn for this glyph\n\nSTBTT_DEF int stbtt_GetCodepointShape(const stbtt_fontinfo *info, int unicode_codepoint, stbtt_vertex **vertices);\nSTBTT_DEF int stbtt_GetGlyphShape(const stbtt_fontinfo *info, int glyph_index, stbtt_vertex **vertices);\n// returns # of vertices and fills *vertices with the pointer to them\n//   these are expressed in \"unscaled\" coordinates\n//\n// The shape is a series of contours. Each one starts with\n// a STBTT_moveto, then consists of a series of mixed\n// STBTT_lineto and STBTT_curveto segments. A lineto\n// draws a line from previous endpoint to its x,y; a curveto\n// draws a quadratic bezier from previous endpoint to\n// its x,y, using cx,cy as the bezier control point.\n\nSTBTT_DEF void stbtt_FreeShape(const stbtt_fontinfo *info, stbtt_vertex *vertices);\n// frees the data allocated above\n\nSTBTT_DEF unsigned char *stbtt_FindSVGDoc(const stbtt_fontinfo *info, int gl);\nSTBTT_DEF int stbtt_GetCodepointSVG(const stbtt_fontinfo *info, int unicode_codepoint, const char **svg);\nSTBTT_DEF int stbtt_GetGlyphSVG(const stbtt_fontinfo *info, int gl, const char **svg);\n// fills svg with the character's SVG data.\n// returns data size or 0 if SVG not found.\n\n//////////////////////////////////////////////////////////////////////////////\n//\n// BITMAP RENDERING\n//\n\nSTBTT_DEF void stbtt_FreeBitmap(unsigned char *bitmap, void *userdata);\n// frees the bitmap allocated below\n\nSTBTT_DEF unsigned char *stbtt_GetCodepointBitmap(const stbtt_fontinfo *info, float scale_x, float scale_y, int codepoint, int *width, int *height, int *xoff, int *yoff);\n// allocates a large-enough single-channel 8bpp bitmap and renders the\n// specified character/glyph at the specified scale into it, with\n// antialiasing. 0 is no coverage (transparent), 255 is fully covered (opaque).\n// *width & *height are filled out with the width & height of the bitmap,\n// which is stored left-to-right, top-to-bottom.\n//\n// xoff/yoff are the offset it pixel space from the glyph origin to the top-left of the bitmap\n\nSTBTT_DEF unsigned char *stbtt_GetCodepointBitmapSubpixel(const stbtt_fontinfo *info, float scale_x, float scale_y, float shift_x, float shift_y, int codepoint, int *width, int *height, int *xoff, int *yoff);\n// the same as stbtt_GetCodepoitnBitmap, but you can specify a subpixel\n// shift for the character\n\nSTBTT_DEF void stbtt_MakeCodepointBitmap(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, int codepoint);\n// the same as stbtt_GetCodepointBitmap, but you pass in storage for the bitmap\n// in the form of 'output', with row spacing of 'out_stride' bytes. the bitmap\n// is clipped to out_w/out_h bytes. Call stbtt_GetCodepointBitmapBox to get the\n// width and height and positioning info for it first.\n\nSTBTT_DEF void stbtt_MakeCodepointBitmapSubpixel(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int codepoint);\n// same as stbtt_MakeCodepointBitmap, but you can specify a subpixel\n// shift for the character\n\nSTBTT_DEF void stbtt_MakeCodepointBitmapSubpixelPrefilter(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int oversample_x, int oversample_y, float *sub_x, float *sub_y, int codepoint);\n// same as stbtt_MakeCodepointBitmapSubpixel, but prefiltering\n// is performed (see stbtt_PackSetOversampling)\n\nSTBTT_DEF void stbtt_GetCodepointBitmapBox(const stbtt_fontinfo *font, int codepoint, float scale_x, float scale_y, int *ix0, int *iy0, int *ix1, int *iy1);\n// get the bbox of the bitmap centered around the glyph origin; so the\n// bitmap width is ix1-ix0, height is iy1-iy0, and location to place\n// the bitmap top left is (leftSideBearing*scale,iy0).\n// (Note that the bitmap uses y-increases-down, but the shape uses\n// y-increases-up, so CodepointBitmapBox and CodepointBox are inverted.)\n\nSTBTT_DEF void stbtt_GetCodepointBitmapBoxSubpixel(const stbtt_fontinfo *font, int codepoint, float scale_x, float scale_y, float shift_x, float shift_y, int *ix0, int *iy0, int *ix1, int *iy1);\n// same as stbtt_GetCodepointBitmapBox, but you can specify a subpixel\n// shift for the character\n\n// the following functions are equivalent to the above functions, but operate\n// on glyph indices instead of Unicode codepoints (for efficiency)\nSTBTT_DEF unsigned char *stbtt_GetGlyphBitmap(const stbtt_fontinfo *info, float scale_x, float scale_y, int glyph, int *width, int *height, int *xoff, int *yoff);\nSTBTT_DEF unsigned char *stbtt_GetGlyphBitmapSubpixel(const stbtt_fontinfo *info, float scale_x, float scale_y, float shift_x, float shift_y, int glyph, int *width, int *height, int *xoff, int *yoff);\nSTBTT_DEF void stbtt_MakeGlyphBitmap(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, int glyph);\nSTBTT_DEF void stbtt_MakeGlyphBitmapSubpixel(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int glyph);\nSTBTT_DEF void stbtt_MakeGlyphBitmapSubpixelPrefilter(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int oversample_x, int oversample_y, float *sub_x, float *sub_y, int glyph);\nSTBTT_DEF void stbtt_GetGlyphBitmapBox(const stbtt_fontinfo *font, int glyph, float scale_x, float scale_y, int *ix0, int *iy0, int *ix1, int *iy1);\nSTBTT_DEF void stbtt_GetGlyphBitmapBoxSubpixel(const stbtt_fontinfo *font, int glyph, float scale_x, float scale_y,float shift_x, float shift_y, int *ix0, int *iy0, int *ix1, int *iy1);\n\n\n// @TODO: don't expose this structure\ntypedef struct\n{\n   int w,h,stride;\n   unsigned char *pixels;\n} stbtt__bitmap;\n\n// rasterize a shape with quadratic beziers into a bitmap\nSTBTT_DEF void stbtt_Rasterize(stbtt__bitmap *result,        // 1-channel bitmap to draw into\n                               float flatness_in_pixels,     // allowable error of curve in pixels\n                               stbtt_vertex *vertices,       // array of vertices defining shape\n                               int num_verts,                // number of vertices in above array\n                               float scale_x, float scale_y, // scale applied to input vertices\n                               float shift_x, float shift_y, // translation applied to input vertices\n                               int x_off, int y_off,         // another translation applied to input\n                               int invert,                   // if non-zero, vertically flip shape\n                               void *userdata);              // context for to STBTT_MALLOC\n\n//////////////////////////////////////////////////////////////////////////////\n//\n// Signed Distance Function (or Field) rendering\n\nSTBTT_DEF void stbtt_FreeSDF(unsigned char *bitmap, void *userdata);\n// frees the SDF bitmap allocated below\n\nSTBTT_DEF unsigned char * stbtt_GetGlyphSDF(const stbtt_fontinfo *info, float scale, int glyph, int padding, unsigned char onedge_value, float pixel_dist_scale, int *width, int *height, int *xoff, int *yoff);\nSTBTT_DEF unsigned char * stbtt_GetCodepointSDF(const stbtt_fontinfo *info, float scale, int codepoint, int padding, unsigned char onedge_value, float pixel_dist_scale, int *width, int *height, int *xoff, int *yoff);\n// These functions compute a discretized SDF field for a single character, suitable for storing\n// in a single-channel texture, sampling with bilinear filtering, and testing against\n// larger than some threshold to produce scalable fonts.\n//        info              --  the font\n//        scale             --  controls the size of the resulting SDF bitmap, same as it would be creating a regular bitmap\n//        glyph/codepoint   --  the character to generate the SDF for\n//        padding           --  extra \"pixels\" around the character which are filled with the distance to the character (not 0),\n//                                 which allows effects like bit outlines\n//        onedge_value      --  value 0-255 to test the SDF against to reconstruct the character (i.e. the isocontour of the character)\n//        pixel_dist_scale  --  what value the SDF should increase by when moving one SDF \"pixel\" away from the edge (on the 0..255 scale)\n//                                 if positive, > onedge_value is inside; if negative, < onedge_value is inside\n//        width,height      --  output height & width of the SDF bitmap (including padding)\n//        xoff,yoff         --  output origin of the character\n//        return value      --  a 2D array of bytes 0..255, width*height in size\n//\n// pixel_dist_scale & onedge_value are a scale & bias that allows you to make\n// optimal use of the limited 0..255 for your application, trading off precision\n// and special effects. SDF values outside the range 0..255 are clamped to 0..255.\n//\n// Example:\n//      scale = stbtt_ScaleForPixelHeight(22)\n//      padding = 5\n//      onedge_value = 180\n//      pixel_dist_scale = 180/5.0 = 36.0\n//\n//      This will create an SDF bitmap in which the character is about 22 pixels\n//      high but the whole bitmap is about 22+5+5=32 pixels high. To produce a filled\n//      shape, sample the SDF at each pixel and fill the pixel if the SDF value\n//      is greater than or equal to 180/255. (You'll actually want to antialias,\n//      which is beyond the scope of this example.) Additionally, you can compute\n//      offset outlines (e.g. to stroke the character border inside & outside,\n//      or only outside). For example, to fill outside the character up to 3 SDF\n//      pixels, you would compare against (180-36.0*3)/255 = 72/255. The above\n//      choice of variables maps a range from 5 pixels outside the shape to\n//      2 pixels inside the shape to 0..255; this is intended primarily for apply\n//      outside effects only (the interior range is needed to allow proper\n//      antialiasing of the font at *smaller* sizes)\n//\n// The function computes the SDF analytically at each SDF pixel, not by e.g.\n// building a higher-res bitmap and approximating it. In theory the quality\n// should be as high as possible for an SDF of this size & representation, but\n// unclear if this is true in practice (perhaps building a higher-res bitmap\n// and computing from that can allow drop-out prevention).\n//\n// The algorithm has not been optimized at all, so expect it to be slow\n// if computing lots of characters or very large sizes.\n\n\n\n//////////////////////////////////////////////////////////////////////////////\n//\n// Finding the right font...\n//\n// You should really just solve this offline, keep your own tables\n// of what font is what, and don't try to get it out of the .ttf file.\n// That's because getting it out of the .ttf file is really hard, because\n// the names in the file can appear in many possible encodings, in many\n// possible languages, and e.g. if you need a case-insensitive comparison,\n// the details of that depend on the encoding & language in a complex way\n// (actually underspecified in truetype, but also gigantic).\n//\n// But you can use the provided functions in two possible ways:\n//     stbtt_FindMatchingFont() will use *case-sensitive* comparisons on\n//             unicode-encoded names to try to find the font you want;\n//             you can run this before calling stbtt_InitFont()\n//\n//     stbtt_GetFontNameString() lets you get any of the various strings\n//             from the file yourself and do your own comparisons on them.\n//             You have to have called stbtt_InitFont() first.\n\n\nSTBTT_DEF int stbtt_FindMatchingFont(const unsigned char *fontdata, const char *name, int flags);\n// returns the offset (not index) of the font that matches, or -1 if none\n//   if you use STBTT_MACSTYLE_DONTCARE, use a font name like \"Arial Bold\".\n//   if you use any other flag, use a font name like \"Arial\"; this checks\n//     the 'macStyle' header field; i don't know if fonts set this consistently\n#define STBTT_MACSTYLE_DONTCARE     0\n#define STBTT_MACSTYLE_BOLD         1\n#define STBTT_MACSTYLE_ITALIC       2\n#define STBTT_MACSTYLE_UNDERSCORE   4\n#define STBTT_MACSTYLE_NONE         8   // <= not same as 0, this makes us check the bitfield is 0\n\nSTBTT_DEF int stbtt_CompareUTF8toUTF16_bigendian(const char *s1, int len1, const char *s2, int len2);\n// returns 1/0 whether the first string interpreted as utf8 is identical to\n// the second string interpreted as big-endian utf16... useful for strings from next func\n\nSTBTT_DEF const char *stbtt_GetFontNameString(const stbtt_fontinfo *font, int *length, int platformID, int encodingID, int languageID, int nameID);\n// returns the string (which may be big-endian double byte, e.g. for unicode)\n// and puts the length in bytes in *length.\n//\n// some of the values for the IDs are below; for more see the truetype spec:\n//     http://developer.apple.com/textfonts/TTRefMan/RM06/Chap6name.html\n//     http://www.microsoft.com/typography/otspec/name.htm\n\nenum { // platformID\n   STBTT_PLATFORM_ID_UNICODE   =0,\n   STBTT_PLATFORM_ID_MAC       =1,\n   STBTT_PLATFORM_ID_ISO       =2,\n   STBTT_PLATFORM_ID_MICROSOFT =3\n};\n\nenum { // encodingID for STBTT_PLATFORM_ID_UNICODE\n   STBTT_UNICODE_EID_UNICODE_1_0    =0,\n   STBTT_UNICODE_EID_UNICODE_1_1    =1,\n   STBTT_UNICODE_EID_ISO_10646      =2,\n   STBTT_UNICODE_EID_UNICODE_2_0_BMP=3,\n   STBTT_UNICODE_EID_UNICODE_2_0_FULL=4\n};\n\nenum { // encodingID for STBTT_PLATFORM_ID_MICROSOFT\n   STBTT_MS_EID_SYMBOL        =0,\n   STBTT_MS_EID_UNICODE_BMP   =1,\n   STBTT_MS_EID_SHIFTJIS      =2,\n   STBTT_MS_EID_UNICODE_FULL  =10\n};\n\nenum { // encodingID for STBTT_PLATFORM_ID_MAC; same as Script Manager codes\n   STBTT_MAC_EID_ROMAN        =0,   STBTT_MAC_EID_ARABIC       =4,\n   STBTT_MAC_EID_JAPANESE     =1,   STBTT_MAC_EID_HEBREW       =5,\n   STBTT_MAC_EID_CHINESE_TRAD =2,   STBTT_MAC_EID_GREEK        =6,\n   STBTT_MAC_EID_KOREAN       =3,   STBTT_MAC_EID_RUSSIAN      =7\n};\n\nenum { // languageID for STBTT_PLATFORM_ID_MICROSOFT; same as LCID...\n       // problematic because there are e.g. 16 english LCIDs and 16 arabic LCIDs\n   STBTT_MS_LANG_ENGLISH     =0x0409,   STBTT_MS_LANG_ITALIAN     =0x0410,\n   STBTT_MS_LANG_CHINESE     =0x0804,   STBTT_MS_LANG_JAPANESE    =0x0411,\n   STBTT_MS_LANG_DUTCH       =0x0413,   STBTT_MS_LANG_KOREAN      =0x0412,\n   STBTT_MS_LANG_FRENCH      =0x040c,   STBTT_MS_LANG_RUSSIAN     =0x0419,\n   STBTT_MS_LANG_GERMAN      =0x0407,   STBTT_MS_LANG_SPANISH     =0x0409,\n   STBTT_MS_LANG_HEBREW      =0x040d,   STBTT_MS_LANG_SWEDISH     =0x041D\n};\n\nenum { // languageID for STBTT_PLATFORM_ID_MAC\n   STBTT_MAC_LANG_ENGLISH      =0 ,   STBTT_MAC_LANG_JAPANESE     =11,\n   STBTT_MAC_LANG_ARABIC       =12,   STBTT_MAC_LANG_KOREAN       =23,\n   STBTT_MAC_LANG_DUTCH        =4 ,   STBTT_MAC_LANG_RUSSIAN      =32,\n   STBTT_MAC_LANG_FRENCH       =1 ,   STBTT_MAC_LANG_SPANISH      =6 ,\n   STBTT_MAC_LANG_GERMAN       =2 ,   STBTT_MAC_LANG_SWEDISH      =5 ,\n   STBTT_MAC_LANG_HEBREW       =10,   STBTT_MAC_LANG_CHINESE_SIMPLIFIED =33,\n   STBTT_MAC_LANG_ITALIAN      =3 ,   STBTT_MAC_LANG_CHINESE_TRAD =19\n};\n\n#ifdef __cplusplus\n}\n#endif\n\n#endif // __STB_INCLUDE_STB_TRUETYPE_H__\n\n///////////////////////////////////////////////////////////////////////////////\n///////////////////////////////////////////////////////////////////////////////\n////\n////   IMPLEMENTATION\n////\n////\n\n#ifdef STB_TRUETYPE_IMPLEMENTATION\n\n#ifndef STBTT_MAX_OVERSAMPLE\n#define STBTT_MAX_OVERSAMPLE   8\n#endif\n\n#if STBTT_MAX_OVERSAMPLE > 255\n#error \"STBTT_MAX_OVERSAMPLE cannot be > 255\"\n#endif\n\ntypedef int stbtt__test_oversample_pow2[(STBTT_MAX_OVERSAMPLE & (STBTT_MAX_OVERSAMPLE-1)) == 0 ? 1 : -1];\n\n#ifndef STBTT_RASTERIZER_VERSION\n#define STBTT_RASTERIZER_VERSION 2\n#endif\n\n#ifdef _MSC_VER\n#define STBTT__NOTUSED(v)  (void)(v)\n#else\n#define STBTT__NOTUSED(v)  (void)sizeof(v)\n#endif\n\n//////////////////////////////////////////////////////////////////////////\n//\n// stbtt__buf helpers to parse data from file\n//\n\nstatic stbtt_uint8 stbtt__buf_get8(stbtt__buf *b)\n{\n   if (b->cursor >= b->size)\n      return 0;\n   return b->data[b->cursor++];\n}\n\nstatic stbtt_uint8 stbtt__buf_peek8(stbtt__buf *b)\n{\n   if (b->cursor >= b->size)\n      return 0;\n   return b->data[b->cursor];\n}\n\nstatic void stbtt__buf_seek(stbtt__buf *b, int o)\n{\n   STBTT_assert(!(o > b->size || o < 0));\n   b->cursor = (o > b->size || o < 0) ? b->size : o;\n}\n\nstatic void stbtt__buf_skip(stbtt__buf *b, int o)\n{\n   stbtt__buf_seek(b, b->cursor + o);\n}\n\nstatic stbtt_uint32 stbtt__buf_get(stbtt__buf *b, int n)\n{\n   stbtt_uint32 v = 0;\n   int i;\n   STBTT_assert(n >= 1 && n <= 4);\n   for (i = 0; i < n; i++)\n      v = (v << 8) | stbtt__buf_get8(b);\n   return v;\n}\n\nstatic stbtt__buf stbtt__new_buf(const void *p, size_t size)\n{\n   stbtt__buf r;\n   STBTT_assert(size < 0x40000000);\n   r.data = (stbtt_uint8*) p;\n   r.size = (int) size;\n   r.cursor = 0;\n   return r;\n}\n\n#define stbtt__buf_get16(b)  stbtt__buf_get((b), 2)\n#define stbtt__buf_get32(b)  stbtt__buf_get((b), 4)\n\nstatic stbtt__buf stbtt__buf_range(const stbtt__buf *b, int o, int s)\n{\n   stbtt__buf r = stbtt__new_buf(NULL, 0);\n   if (o < 0 || s < 0 || o > b->size || s > b->size - o) return r;\n   r.data = b->data + o;\n   r.size = s;\n   return r;\n}\n\nstatic stbtt__buf stbtt__cff_get_index(stbtt__buf *b)\n{\n   int count, start, offsize;\n   start = b->cursor;\n   count = stbtt__buf_get16(b);\n   if (count) {\n      offsize = stbtt__buf_get8(b);\n      STBTT_assert(offsize >= 1 && offsize <= 4);\n      stbtt__buf_skip(b, offsize * count);\n      stbtt__buf_skip(b, stbtt__buf_get(b, offsize) - 1);\n   }\n   return stbtt__buf_range(b, start, b->cursor - start);\n}\n\nstatic stbtt_uint32 stbtt__cff_int(stbtt__buf *b)\n{\n   int b0 = stbtt__buf_get8(b);\n   if (b0 >= 32 && b0 <= 246)       return b0 - 139;\n   else if (b0 >= 247 && b0 <= 250) return (b0 - 247)*256 + stbtt__buf_get8(b) + 108;\n   else if (b0 >= 251 && b0 <= 254) return -(b0 - 251)*256 - stbtt__buf_get8(b) - 108;\n   else if (b0 == 28)               return stbtt__buf_get16(b);\n   else if (b0 == 29)               return stbtt__buf_get32(b);\n   STBTT_assert(0);\n   return 0;\n}\n\nstatic void stbtt__cff_skip_operand(stbtt__buf *b) {\n   int v, b0 = stbtt__buf_peek8(b);\n   STBTT_assert(b0 >= 28);\n   if (b0 == 30) {\n      stbtt__buf_skip(b, 1);\n      while (b->cursor < b->size) {\n         v = stbtt__buf_get8(b);\n         if ((v & 0xF) == 0xF || (v >> 4) == 0xF)\n            break;\n      }\n   } else {\n      stbtt__cff_int(b);\n   }\n}\n\nstatic stbtt__buf stbtt__dict_get(stbtt__buf *b, int key)\n{\n   stbtt__buf_seek(b, 0);\n   while (b->cursor < b->size) {\n      int start = b->cursor, end, op;\n      while (stbtt__buf_peek8(b) >= 28)\n         stbtt__cff_skip_operand(b);\n      end = b->cursor;\n      op = stbtt__buf_get8(b);\n      if (op == 12)  op = stbtt__buf_get8(b) | 0x100;\n      if (op == key) return stbtt__buf_range(b, start, end-start);\n   }\n   return stbtt__buf_range(b, 0, 0);\n}\n\nstatic void stbtt__dict_get_ints(stbtt__buf *b, int key, int outcount, stbtt_uint32 *out)\n{\n   int i;\n   stbtt__buf operands = stbtt__dict_get(b, key);\n   for (i = 0; i < outcount && operands.cursor < operands.size; i++)\n      out[i] = stbtt__cff_int(&operands);\n}\n\nstatic int stbtt__cff_index_count(stbtt__buf *b)\n{\n   stbtt__buf_seek(b, 0);\n   return stbtt__buf_get16(b);\n}\n\nstatic stbtt__buf stbtt__cff_index_get(stbtt__buf b, int i)\n{\n   int count, offsize, start, end;\n   stbtt__buf_seek(&b, 0);\n   count = stbtt__buf_get16(&b);\n   offsize = stbtt__buf_get8(&b);\n   STBTT_assert(i >= 0 && i < count);\n   STBTT_assert(offsize >= 1 && offsize <= 4);\n   stbtt__buf_skip(&b, i*offsize);\n   start = stbtt__buf_get(&b, offsize);\n   end = stbtt__buf_get(&b, offsize);\n   return stbtt__buf_range(&b, 2+(count+1)*offsize+start, end - start);\n}\n\n//////////////////////////////////////////////////////////////////////////\n//\n// accessors to parse data from file\n//\n\n// on platforms that don't allow misaligned reads, if we want to allow\n// truetype fonts that aren't padded to alignment, define ALLOW_UNALIGNED_TRUETYPE\n\n#define ttBYTE(p)     (* (stbtt_uint8 *) (p))\n#define ttCHAR(p)     (* (stbtt_int8 *) (p))\n#define ttFixed(p)    ttLONG(p)\n\nstatic stbtt_uint16 ttUSHORT(stbtt_uint8 *p) { return p[0]*256 + p[1]; }\nstatic stbtt_int16 ttSHORT(stbtt_uint8 *p)   { return p[0]*256 + p[1]; }\nstatic stbtt_uint32 ttULONG(stbtt_uint8 *p)  { return (p[0]<<24) + (p[1]<<16) + (p[2]<<8) + p[3]; }\nstatic stbtt_int32 ttLONG(stbtt_uint8 *p)    { return (p[0]<<24) + (p[1]<<16) + (p[2]<<8) + p[3]; }\n\n#define stbtt_tag4(p,c0,c1,c2,c3) ((p)[0] == (c0) && (p)[1] == (c1) && (p)[2] == (c2) && (p)[3] == (c3))\n#define stbtt_tag(p,str)           stbtt_tag4(p,str[0],str[1],str[2],str[3])\n\nstatic int stbtt__isfont(stbtt_uint8 *font)\n{\n   // check the version number\n   if (stbtt_tag4(font, '1',0,0,0))  return 1; // TrueType 1\n   if (stbtt_tag(font, \"typ1\"))   return 1; // TrueType with type 1 font -- we don't support this!\n   if (stbtt_tag(font, \"OTTO\"))   return 1; // OpenType with CFF\n   if (stbtt_tag4(font, 0,1,0,0)) return 1; // OpenType 1.0\n   if (stbtt_tag(font, \"true\"))   return 1; // Apple specification for TrueType fonts\n   return 0;\n}\n\n// @OPTIMIZE: binary search\nstatic stbtt_uint32 stbtt__find_table(stbtt_uint8 *data, stbtt_uint32 fontstart, const char *tag)\n{\n   stbtt_int32 num_tables = ttUSHORT(data+fontstart+4);\n   stbtt_uint32 tabledir = fontstart + 12;\n   stbtt_int32 i;\n   for (i=0; i < num_tables; ++i) {\n      stbtt_uint32 loc = tabledir + 16*i;\n      if (stbtt_tag(data+loc+0, tag))\n         return ttULONG(data+loc+8);\n   }\n   return 0;\n}\n\nstatic int stbtt_GetFontOffsetForIndex_internal(unsigned char *font_collection, int index)\n{\n   // if it's just a font, there's only one valid index\n   if (stbtt__isfont(font_collection))\n      return index == 0 ? 0 : -1;\n\n   // check if it's a TTC\n   if (stbtt_tag(font_collection, \"ttcf\")) {\n      // version 1?\n      if (ttULONG(font_collection+4) == 0x00010000 || ttULONG(font_collection+4) == 0x00020000) {\n         stbtt_int32 n = ttLONG(font_collection+8);\n         if (index >= n)\n            return -1;\n         return ttULONG(font_collection+12+index*4);\n      }\n   }\n   return -1;\n}\n\nstatic int stbtt_GetNumberOfFonts_internal(unsigned char *font_collection)\n{\n   // if it's just a font, there's only one valid font\n   if (stbtt__isfont(font_collection))\n      return 1;\n\n   // check if it's a TTC\n   if (stbtt_tag(font_collection, \"ttcf\")) {\n      // version 1?\n      if (ttULONG(font_collection+4) == 0x00010000 || ttULONG(font_collection+4) == 0x00020000) {\n         return ttLONG(font_collection+8);\n      }\n   }\n   return 0;\n}\n\nstatic stbtt__buf stbtt__get_subrs(stbtt__buf cff, stbtt__buf fontdict)\n{\n   stbtt_uint32 subrsoff = 0, private_loc[2] = { 0, 0 };\n   stbtt__buf pdict;\n   stbtt__dict_get_ints(&fontdict, 18, 2, private_loc);\n   if (!private_loc[1] || !private_loc[0]) return stbtt__new_buf(NULL, 0);\n   pdict = stbtt__buf_range(&cff, private_loc[1], private_loc[0]);\n   stbtt__dict_get_ints(&pdict, 19, 1, &subrsoff);\n   if (!subrsoff) return stbtt__new_buf(NULL, 0);\n   stbtt__buf_seek(&cff, private_loc[1]+subrsoff);\n   return stbtt__cff_get_index(&cff);\n}\n\n// since most people won't use this, find this table the first time it's needed\nstatic int stbtt__get_svg(stbtt_fontinfo *info)\n{\n   stbtt_uint32 t;\n   if (info->svg < 0) {\n      t = stbtt__find_table(info->data, info->fontstart, \"SVG \");\n      if (t) {\n         stbtt_uint32 offset = ttULONG(info->data + t + 2);\n         info->svg = t + offset;\n      } else {\n         info->svg = 0;\n      }\n   }\n   return info->svg;\n}\n\nstatic int stbtt_InitFont_internal(stbtt_fontinfo *info, unsigned char *data, int fontstart)\n{\n   stbtt_uint32 cmap, t;\n   stbtt_int32 i,numTables;\n\n   info->data = data;\n   info->fontstart = fontstart;\n   info->cff = stbtt__new_buf(NULL, 0);\n\n   cmap = stbtt__find_table(data, fontstart, \"cmap\");       // required\n   info->loca = stbtt__find_table(data, fontstart, \"loca\"); // required\n   info->head = stbtt__find_table(data, fontstart, \"head\"); // required\n   info->glyf = stbtt__find_table(data, fontstart, \"glyf\"); // required\n   info->hhea = stbtt__find_table(data, fontstart, \"hhea\"); // required\n   info->hmtx = stbtt__find_table(data, fontstart, \"hmtx\"); // required\n   info->kern = stbtt__find_table(data, fontstart, \"kern\"); // not required\n   info->gpos = stbtt__find_table(data, fontstart, \"GPOS\"); // not required\n\n   if (!cmap || !info->head || !info->hhea || !info->hmtx)\n      return 0;\n   if (info->glyf) {\n      // required for truetype\n      if (!info->loca) return 0;\n   } else {\n      // initialization for CFF / Type2 fonts (OTF)\n      stbtt__buf b, topdict, topdictidx;\n      stbtt_uint32 cstype = 2, charstrings = 0, fdarrayoff = 0, fdselectoff = 0;\n      stbtt_uint32 cff;\n\n      cff = stbtt__find_table(data, fontstart, \"CFF \");\n      if (!cff) return 0;\n\n      info->fontdicts = stbtt__new_buf(NULL, 0);\n      info->fdselect = stbtt__new_buf(NULL, 0);\n\n      // @TODO this should use size from table (not 512MB)\n      info->cff = stbtt__new_buf(data+cff, 512*1024*1024);\n      b = info->cff;\n\n      // read the header\n      stbtt__buf_skip(&b, 2);\n      stbtt__buf_seek(&b, stbtt__buf_get8(&b)); // hdrsize\n\n      // @TODO the name INDEX could list multiple fonts,\n      // but we just use the first one.\n      stbtt__cff_get_index(&b);  // name INDEX\n      topdictidx = stbtt__cff_get_index(&b);\n      topdict = stbtt__cff_index_get(topdictidx, 0);\n      stbtt__cff_get_index(&b);  // string INDEX\n      info->gsubrs = stbtt__cff_get_index(&b);\n\n      stbtt__dict_get_ints(&topdict, 17, 1, &charstrings);\n      stbtt__dict_get_ints(&topdict, 0x100 | 6, 1, &cstype);\n      stbtt__dict_get_ints(&topdict, 0x100 | 36, 1, &fdarrayoff);\n      stbtt__dict_get_ints(&topdict, 0x100 | 37, 1, &fdselectoff);\n      info->subrs = stbtt__get_subrs(b, topdict);\n\n      // we only support Type 2 charstrings\n      if (cstype != 2) return 0;\n      if (charstrings == 0) return 0;\n\n      if (fdarrayoff) {\n         // looks like a CID font\n         if (!fdselectoff) return 0;\n         stbtt__buf_seek(&b, fdarrayoff);\n         info->fontdicts = stbtt__cff_get_index(&b);\n         info->fdselect = stbtt__buf_range(&b, fdselectoff, b.size-fdselectoff);\n      }\n\n      stbtt__buf_seek(&b, charstrings);\n      info->charstrings = stbtt__cff_get_index(&b);\n   }\n\n   t = stbtt__find_table(data, fontstart, \"maxp\");\n   if (t)\n      info->numGlyphs = ttUSHORT(data+t+4);\n   else\n      info->numGlyphs = 0xffff;\n\n   info->svg = -1;\n\n   // find a cmap encoding table we understand *now* to avoid searching\n   // later. (todo: could make this installable)\n   // the same regardless of glyph.\n   numTables = ttUSHORT(data + cmap + 2);\n   info->index_map = 0;\n   for (i=0; i < numTables; ++i) {\n      stbtt_uint32 encoding_record = cmap + 4 + 8 * i;\n      // find an encoding we understand:\n      switch(ttUSHORT(data+encoding_record)) {\n         case STBTT_PLATFORM_ID_MICROSOFT:\n            switch (ttUSHORT(data+encoding_record+2)) {\n               case STBTT_MS_EID_UNICODE_BMP:\n               case STBTT_MS_EID_UNICODE_FULL:\n                  // MS/Unicode\n                  info->index_map = cmap + ttULONG(data+encoding_record+4);\n                  break;\n            }\n            break;\n        case STBTT_PLATFORM_ID_UNICODE:\n            // Mac/iOS has these\n            // all the encodingIDs are unicode, so we don't bother to check it\n            info->index_map = cmap + ttULONG(data+encoding_record+4);\n            break;\n      }\n   }\n   if (info->index_map == 0)\n      return 0;\n\n   info->indexToLocFormat = ttUSHORT(data+info->head + 50);\n   return 1;\n}\n\nSTBTT_DEF int stbtt_FindGlyphIndex(const stbtt_fontinfo *info, int unicode_codepoint)\n{\n   stbtt_uint8 *data = info->data;\n   stbtt_uint32 index_map = info->index_map;\n\n   stbtt_uint16 format = ttUSHORT(data + index_map + 0);\n   if (format == 0) { // apple byte encoding\n      stbtt_int32 bytes = ttUSHORT(data + index_map + 2);\n      if (unicode_codepoint < bytes-6)\n         return ttBYTE(data + index_map + 6 + unicode_codepoint);\n      return 0;\n   } else if (format == 6) {\n      stbtt_uint32 first = ttUSHORT(data + index_map + 6);\n      stbtt_uint32 count = ttUSHORT(data + index_map + 8);\n      if ((stbtt_uint32) unicode_codepoint >= first && (stbtt_uint32) unicode_codepoint < first+count)\n         return ttUSHORT(data + index_map + 10 + (unicode_codepoint - first)*2);\n      return 0;\n   } else if (format == 2) {\n      STBTT_assert(0); // @TODO: high-byte mapping for japanese/chinese/korean\n      return 0;\n   } else if (format == 4) { // standard mapping for windows fonts: binary search collection of ranges\n      stbtt_uint16 segcount = ttUSHORT(data+index_map+6) >> 1;\n      stbtt_uint16 searchRange = ttUSHORT(data+index_map+8) >> 1;\n      stbtt_uint16 entrySelector = ttUSHORT(data+index_map+10);\n      stbtt_uint16 rangeShift = ttUSHORT(data+index_map+12) >> 1;\n\n      // do a binary search of the segments\n      stbtt_uint32 endCount = index_map + 14;\n      stbtt_uint32 search = endCount;\n\n      if (unicode_codepoint > 0xffff)\n         return 0;\n\n      // they lie from endCount .. endCount + segCount\n      // but searchRange is the nearest power of two, so...\n      if (unicode_codepoint >= ttUSHORT(data + search + rangeShift*2))\n         search += rangeShift*2;\n\n      // now decrement to bias correctly to find smallest\n      search -= 2;\n      while (entrySelector) {\n         stbtt_uint16 end;\n         searchRange >>= 1;\n         end = ttUSHORT(data + search + searchRange*2);\n         if (unicode_codepoint > end)\n            search += searchRange*2;\n         --entrySelector;\n      }\n      search += 2;\n\n      {\n         stbtt_uint16 offset, start, last;\n         stbtt_uint16 item = (stbtt_uint16) ((search - endCount) >> 1);\n\n         start = ttUSHORT(data + index_map + 14 + segcount*2 + 2 + 2*item);\n         last = ttUSHORT(data + endCount + 2*item);\n         if (unicode_codepoint < start || unicode_codepoint > last)\n            return 0;\n\n         offset = ttUSHORT(data + index_map + 14 + segcount*6 + 2 + 2*item);\n         if (offset == 0)\n            return (stbtt_uint16) (unicode_codepoint + ttSHORT(data + index_map + 14 + segcount*4 + 2 + 2*item));\n\n         return ttUSHORT(data + offset + (unicode_codepoint-start)*2 + index_map + 14 + segcount*6 + 2 + 2*item);\n      }\n   } else if (format == 12 || format == 13) {\n      stbtt_uint32 ngroups = ttULONG(data+index_map+12);\n      stbtt_int32 low,high;\n      low = 0; high = (stbtt_int32)ngroups;\n      // Binary search the right group.\n      while (low < high) {\n         stbtt_int32 mid = low + ((high-low) >> 1); // rounds down, so low <= mid < high\n         stbtt_uint32 start_char = ttULONG(data+index_map+16+mid*12);\n         stbtt_uint32 end_char = ttULONG(data+index_map+16+mid*12+4);\n         if ((stbtt_uint32) unicode_codepoint < start_char)\n            high = mid;\n         else if ((stbtt_uint32) unicode_codepoint > end_char)\n            low = mid+1;\n         else {\n            stbtt_uint32 start_glyph = ttULONG(data+index_map+16+mid*12+8);\n            if (format == 12)\n               return start_glyph + unicode_codepoint-start_char;\n            else // format == 13\n               return start_glyph;\n         }\n      }\n      return 0; // not found\n   }\n   // @TODO\n   STBTT_assert(0);\n   return 0;\n}\n\nSTBTT_DEF int stbtt_GetCodepointShape(const stbtt_fontinfo *info, int unicode_codepoint, stbtt_vertex **vertices)\n{\n   return stbtt_GetGlyphShape(info, stbtt_FindGlyphIndex(info, unicode_codepoint), vertices);\n}\n\nstatic void stbtt_setvertex(stbtt_vertex *v, stbtt_uint8 type, stbtt_int32 x, stbtt_int32 y, stbtt_int32 cx, stbtt_int32 cy)\n{\n   v->type = type;\n   v->x = (stbtt_int16) x;\n   v->y = (stbtt_int16) y;\n   v->cx = (stbtt_int16) cx;\n   v->cy = (stbtt_int16) cy;\n}\n\nstatic int stbtt__GetGlyfOffset(const stbtt_fontinfo *info, int glyph_index)\n{\n   int g1,g2;\n\n   STBTT_assert(!info->cff.size);\n\n   if (glyph_index >= info->numGlyphs) return -1; // glyph index out of range\n   if (info->indexToLocFormat >= 2)    return -1; // unknown index->glyph map format\n\n   if (info->indexToLocFormat == 0) {\n      g1 = info->glyf + ttUSHORT(info->data + info->loca + glyph_index * 2) * 2;\n      g2 = info->glyf + ttUSHORT(info->data + info->loca + glyph_index * 2 + 2) * 2;\n   } else {\n      g1 = info->glyf + ttULONG (info->data + info->loca + glyph_index * 4);\n      g2 = info->glyf + ttULONG (info->data + info->loca + glyph_index * 4 + 4);\n   }\n\n   return g1==g2 ? -1 : g1; // if length is 0, return -1\n}\n\nstatic int stbtt__GetGlyphInfoT2(const stbtt_fontinfo *info, int glyph_index, int *x0, int *y0, int *x1, int *y1);\n\nSTBTT_DEF int stbtt_GetGlyphBox(const stbtt_fontinfo *info, int glyph_index, int *x0, int *y0, int *x1, int *y1)\n{\n   if (info->cff.size) {\n      stbtt__GetGlyphInfoT2(info, glyph_index, x0, y0, x1, y1);\n   } else {\n      int g = stbtt__GetGlyfOffset(info, glyph_index);\n      if (g < 0) return 0;\n\n      if (x0) *x0 = ttSHORT(info->data + g + 2);\n      if (y0) *y0 = ttSHORT(info->data + g + 4);\n      if (x1) *x1 = ttSHORT(info->data + g + 6);\n      if (y1) *y1 = ttSHORT(info->data + g + 8);\n   }\n   return 1;\n}\n\nSTBTT_DEF int stbtt_GetCodepointBox(const stbtt_fontinfo *info, int codepoint, int *x0, int *y0, int *x1, int *y1)\n{\n   return stbtt_GetGlyphBox(info, stbtt_FindGlyphIndex(info,codepoint), x0,y0,x1,y1);\n}\n\nSTBTT_DEF int stbtt_IsGlyphEmpty(const stbtt_fontinfo *info, int glyph_index)\n{\n   stbtt_int16 numberOfContours;\n   int g;\n   if (info->cff.size)\n      return stbtt__GetGlyphInfoT2(info, glyph_index, NULL, NULL, NULL, NULL) == 0;\n   g = stbtt__GetGlyfOffset(info, glyph_index);\n   if (g < 0) return 1;\n   numberOfContours = ttSHORT(info->data + g);\n   return numberOfContours == 0;\n}\n\nstatic int stbtt__close_shape(stbtt_vertex *vertices, int num_vertices, int was_off, int start_off,\n    stbtt_int32 sx, stbtt_int32 sy, stbtt_int32 scx, stbtt_int32 scy, stbtt_int32 cx, stbtt_int32 cy)\n{\n   if (start_off) {\n      if (was_off)\n         stbtt_setvertex(&vertices[num_vertices++], STBTT_vcurve, (cx+scx)>>1, (cy+scy)>>1, cx,cy);\n      stbtt_setvertex(&vertices[num_vertices++], STBTT_vcurve, sx,sy,scx,scy);\n   } else {\n      if (was_off)\n         stbtt_setvertex(&vertices[num_vertices++], STBTT_vcurve,sx,sy,cx,cy);\n      else\n         stbtt_setvertex(&vertices[num_vertices++], STBTT_vline,sx,sy,0,0);\n   }\n   return num_vertices;\n}\n\nstatic int stbtt__GetGlyphShapeTT(const stbtt_fontinfo *info, int glyph_index, stbtt_vertex **pvertices)\n{\n   stbtt_int16 numberOfContours;\n   stbtt_uint8 *endPtsOfContours;\n   stbtt_uint8 *data = info->data;\n   stbtt_vertex *vertices=0;\n   int num_vertices=0;\n   int g = stbtt__GetGlyfOffset(info, glyph_index);\n\n   *pvertices = NULL;\n\n   if (g < 0) return 0;\n\n   numberOfContours = ttSHORT(data + g);\n\n   if (numberOfContours > 0) {\n      stbtt_uint8 flags=0,flagcount;\n      stbtt_int32 ins, i,j=0,m,n, next_move, was_off=0, off, start_off=0;\n      stbtt_int32 x,y,cx,cy,sx,sy, scx,scy;\n      stbtt_uint8 *points;\n      endPtsOfContours = (data + g + 10);\n      ins = ttUSHORT(data + g + 10 + numberOfContours * 2);\n      points = data + g + 10 + numberOfContours * 2 + 2 + ins;\n\n      n = 1+ttUSHORT(endPtsOfContours + numberOfContours*2-2);\n\n      m = n + 2*numberOfContours;  // a loose bound on how many vertices we might need\n      vertices = (stbtt_vertex *) STBTT_malloc(m * sizeof(vertices[0]), info->userdata);\n      if (vertices == 0)\n         return 0;\n\n      next_move = 0;\n      flagcount=0;\n\n      // in first pass, we load uninterpreted data into the allocated array\n      // above, shifted to the end of the array so we won't overwrite it when\n      // we create our final data starting from the front\n\n      off = m - n; // starting offset for uninterpreted data, regardless of how m ends up being calculated\n\n      // first load flags\n\n      for (i=0; i < n; ++i) {\n         if (flagcount == 0) {\n            flags = *points++;\n            if (flags & 8)\n               flagcount = *points++;\n         } else\n            --flagcount;\n         vertices[off+i].type = flags;\n      }\n\n      // now load x coordinates\n      x=0;\n      for (i=0; i < n; ++i) {\n         flags = vertices[off+i].type;\n         if (flags & 2) {\n            stbtt_int16 dx = *points++;\n            x += (flags & 16) ? dx : -dx; // ???\n         } else {\n            if (!(flags & 16)) {\n               x = x + (stbtt_int16) (points[0]*256 + points[1]);\n               points += 2;\n            }\n         }\n         vertices[off+i].x = (stbtt_int16) x;\n      }\n\n      // now load y coordinates\n      y=0;\n      for (i=0; i < n; ++i) {\n         flags = vertices[off+i].type;\n         if (flags & 4) {\n            stbtt_int16 dy = *points++;\n            y += (flags & 32) ? dy : -dy; // ???\n         } else {\n            if (!(flags & 32)) {\n               y = y + (stbtt_int16) (points[0]*256 + points[1]);\n               points += 2;\n            }\n         }\n         vertices[off+i].y = (stbtt_int16) y;\n      }\n\n      // now convert them to our format\n      num_vertices=0;\n      sx = sy = cx = cy = scx = scy = 0;\n      for (i=0; i < n; ++i) {\n         flags = vertices[off+i].type;\n         x     = (stbtt_int16) vertices[off+i].x;\n         y     = (stbtt_int16) vertices[off+i].y;\n\n         if (next_move == i) {\n            if (i != 0)\n               num_vertices = stbtt__close_shape(vertices, num_vertices, was_off, start_off, sx,sy,scx,scy,cx,cy);\n\n            // now start the new one\n            start_off = !(flags & 1);\n            if (start_off) {\n               // if we start off with an off-curve point, then when we need to find a point on the curve\n               // where we can start, and we need to save some state for when we wraparound.\n               scx = x;\n               scy = y;\n               if (!(vertices[off+i+1].type & 1)) {\n                  // next point is also a curve point, so interpolate an on-point curve\n                  sx = (x + (stbtt_int32) vertices[off+i+1].x) >> 1;\n                  sy = (y + (stbtt_int32) vertices[off+i+1].y) >> 1;\n               } else {\n                  // otherwise just use the next point as our start point\n                  sx = (stbtt_int32) vertices[off+i+1].x;\n                  sy = (stbtt_int32) vertices[off+i+1].y;\n                  ++i; // we're using point i+1 as the starting point, so skip it\n               }\n            } else {\n               sx = x;\n               sy = y;\n            }\n            stbtt_setvertex(&vertices[num_vertices++], STBTT_vmove,sx,sy,0,0);\n            was_off = 0;\n            next_move = 1 + ttUSHORT(endPtsOfContours+j*2);\n            ++j;\n         } else {\n            if (!(flags & 1)) { // if it's a curve\n               if (was_off) // two off-curve control points in a row means interpolate an on-curve midpoint\n                  stbtt_setvertex(&vertices[num_vertices++], STBTT_vcurve, (cx+x)>>1, (cy+y)>>1, cx, cy);\n               cx = x;\n               cy = y;\n               was_off = 1;\n            } else {\n               if (was_off)\n                  stbtt_setvertex(&vertices[num_vertices++], STBTT_vcurve, x,y, cx, cy);\n               else\n                  stbtt_setvertex(&vertices[num_vertices++], STBTT_vline, x,y,0,0);\n               was_off = 0;\n            }\n         }\n      }\n      num_vertices = stbtt__close_shape(vertices, num_vertices, was_off, start_off, sx,sy,scx,scy,cx,cy);\n   } else if (numberOfContours < 0) {\n      // Compound shapes.\n      int more = 1;\n      stbtt_uint8 *comp = data + g + 10;\n      num_vertices = 0;\n      vertices = 0;\n      while (more) {\n         stbtt_uint16 flags, gidx;\n         int comp_num_verts = 0, i;\n         stbtt_vertex *comp_verts = 0, *tmp = 0;\n         float mtx[6] = {1,0,0,1,0,0}, m, n;\n\n         flags = ttSHORT(comp); comp+=2;\n         gidx = ttSHORT(comp); comp+=2;\n\n         if (flags & 2) { // XY values\n            if (flags & 1) { // shorts\n               mtx[4] = ttSHORT(comp); comp+=2;\n               mtx[5] = ttSHORT(comp); comp+=2;\n            } else {\n               mtx[4] = ttCHAR(comp); comp+=1;\n               mtx[5] = ttCHAR(comp); comp+=1;\n            }\n         }\n         else {\n            // @TODO handle matching point\n            STBTT_assert(0);\n         }\n         if (flags & (1<<3)) { // WE_HAVE_A_SCALE\n            mtx[0] = mtx[3] = ttSHORT(comp)/16384.0f; comp+=2;\n            mtx[1] = mtx[2] = 0;\n         } else if (flags & (1<<6)) { // WE_HAVE_AN_X_AND_YSCALE\n            mtx[0] = ttSHORT(comp)/16384.0f; comp+=2;\n            mtx[1] = mtx[2] = 0;\n            mtx[3] = ttSHORT(comp)/16384.0f; comp+=2;\n         } else if (flags & (1<<7)) { // WE_HAVE_A_TWO_BY_TWO\n            mtx[0] = ttSHORT(comp)/16384.0f; comp+=2;\n            mtx[1] = ttSHORT(comp)/16384.0f; comp+=2;\n            mtx[2] = ttSHORT(comp)/16384.0f; comp+=2;\n            mtx[3] = ttSHORT(comp)/16384.0f; comp+=2;\n         }\n\n         // Find transformation scales.\n         m = (float) STBTT_sqrt(mtx[0]*mtx[0] + mtx[1]*mtx[1]);\n         n = (float) STBTT_sqrt(mtx[2]*mtx[2] + mtx[3]*mtx[3]);\n\n         // Get indexed glyph.\n         comp_num_verts = stbtt_GetGlyphShape(info, gidx, &comp_verts);\n         if (comp_num_verts > 0) {\n            // Transform vertices.\n            for (i = 0; i < comp_num_verts; ++i) {\n               stbtt_vertex* v = &comp_verts[i];\n               stbtt_vertex_type x,y;\n               x=v->x; y=v->y;\n               v->x = (stbtt_vertex_type)(m * (mtx[0]*x + mtx[2]*y + mtx[4]));\n               v->y = (stbtt_vertex_type)(n * (mtx[1]*x + mtx[3]*y + mtx[5]));\n               x=v->cx; y=v->cy;\n               v->cx = (stbtt_vertex_type)(m * (mtx[0]*x + mtx[2]*y + mtx[4]));\n               v->cy = (stbtt_vertex_type)(n * (mtx[1]*x + mtx[3]*y + mtx[5]));\n            }\n            // Append vertices.\n            tmp = (stbtt_vertex*)STBTT_malloc((num_vertices+comp_num_verts)*sizeof(stbtt_vertex), info->userdata);\n            if (!tmp) {\n               if (vertices) STBTT_free(vertices, info->userdata);\n               if (comp_verts) STBTT_free(comp_verts, info->userdata);\n               return 0;\n            }\n            if (num_vertices > 0 && vertices) STBTT_memcpy(tmp, vertices, num_vertices*sizeof(stbtt_vertex));\n            STBTT_memcpy(tmp+num_vertices, comp_verts, comp_num_verts*sizeof(stbtt_vertex));\n            if (vertices) STBTT_free(vertices, info->userdata);\n            vertices = tmp;\n            STBTT_free(comp_verts, info->userdata);\n            num_vertices += comp_num_verts;\n         }\n         // More components ?\n         more = flags & (1<<5);\n      }\n   } else {\n      // numberOfCounters == 0, do nothing\n   }\n\n   *pvertices = vertices;\n   return num_vertices;\n}\n\ntypedef struct\n{\n   int bounds;\n   int started;\n   float first_x, first_y;\n   float x, y;\n   stbtt_int32 min_x, max_x, min_y, max_y;\n\n   stbtt_vertex *pvertices;\n   int num_vertices;\n} stbtt__csctx;\n\n#define STBTT__CSCTX_INIT(bounds) {bounds,0, 0,0, 0,0, 0,0,0,0, NULL, 0}\n\nstatic void stbtt__track_vertex(stbtt__csctx *c, stbtt_int32 x, stbtt_int32 y)\n{\n   if (x > c->max_x || !c->started) c->max_x = x;\n   if (y > c->max_y || !c->started) c->max_y = y;\n   if (x < c->min_x || !c->started) c->min_x = x;\n   if (y < c->min_y || !c->started) c->min_y = y;\n   c->started = 1;\n}\n\nstatic void stbtt__csctx_v(stbtt__csctx *c, stbtt_uint8 type, stbtt_int32 x, stbtt_int32 y, stbtt_int32 cx, stbtt_int32 cy, stbtt_int32 cx1, stbtt_int32 cy1)\n{\n   if (c->bounds) {\n      stbtt__track_vertex(c, x, y);\n      if (type == STBTT_vcubic) {\n         stbtt__track_vertex(c, cx, cy);\n         stbtt__track_vertex(c, cx1, cy1);\n      }\n   } else {\n      stbtt_setvertex(&c->pvertices[c->num_vertices], type, x, y, cx, cy);\n      c->pvertices[c->num_vertices].cx1 = (stbtt_int16) cx1;\n      c->pvertices[c->num_vertices].cy1 = (stbtt_int16) cy1;\n   }\n   c->num_vertices++;\n}\n\nstatic void stbtt__csctx_close_shape(stbtt__csctx *ctx)\n{\n   if (ctx->first_x != ctx->x || ctx->first_y != ctx->y)\n      stbtt__csctx_v(ctx, STBTT_vline, (int)ctx->first_x, (int)ctx->first_y, 0, 0, 0, 0);\n}\n\nstatic void stbtt__csctx_rmove_to(stbtt__csctx *ctx, float dx, float dy)\n{\n   stbtt__csctx_close_shape(ctx);\n   ctx->first_x = ctx->x = ctx->x + dx;\n   ctx->first_y = ctx->y = ctx->y + dy;\n   stbtt__csctx_v(ctx, STBTT_vmove, (int)ctx->x, (int)ctx->y, 0, 0, 0, 0);\n}\n\nstatic void stbtt__csctx_rline_to(stbtt__csctx *ctx, float dx, float dy)\n{\n   ctx->x += dx;\n   ctx->y += dy;\n   stbtt__csctx_v(ctx, STBTT_vline, (int)ctx->x, (int)ctx->y, 0, 0, 0, 0);\n}\n\nstatic void stbtt__csctx_rccurve_to(stbtt__csctx *ctx, float dx1, float dy1, float dx2, float dy2, float dx3, float dy3)\n{\n   float cx1 = ctx->x + dx1;\n   float cy1 = ctx->y + dy1;\n   float cx2 = cx1 + dx2;\n   float cy2 = cy1 + dy2;\n   ctx->x = cx2 + dx3;\n   ctx->y = cy2 + dy3;\n   stbtt__csctx_v(ctx, STBTT_vcubic, (int)ctx->x, (int)ctx->y, (int)cx1, (int)cy1, (int)cx2, (int)cy2);\n}\n\nstatic stbtt__buf stbtt__get_subr(stbtt__buf idx, int n)\n{\n   int count = stbtt__cff_index_count(&idx);\n   int bias = 107;\n   if (count >= 33900)\n      bias = 32768;\n   else if (count >= 1240)\n      bias = 1131;\n   n += bias;\n   if (n < 0 || n >= count)\n      return stbtt__new_buf(NULL, 0);\n   return stbtt__cff_index_get(idx, n);\n}\n\nstatic stbtt__buf stbtt__cid_get_glyph_subrs(const stbtt_fontinfo *info, int glyph_index)\n{\n   stbtt__buf fdselect = info->fdselect;\n   int nranges, start, end, v, fmt, fdselector = -1, i;\n\n   stbtt__buf_seek(&fdselect, 0);\n   fmt = stbtt__buf_get8(&fdselect);\n   if (fmt == 0) {\n      // untested\n      stbtt__buf_skip(&fdselect, glyph_index);\n      fdselector = stbtt__buf_get8(&fdselect);\n   } else if (fmt == 3) {\n      nranges = stbtt__buf_get16(&fdselect);\n      start = stbtt__buf_get16(&fdselect);\n      for (i = 0; i < nranges; i++) {\n         v = stbtt__buf_get8(&fdselect);\n         end = stbtt__buf_get16(&fdselect);\n         if (glyph_index >= start && glyph_index < end) {\n            fdselector = v;\n            break;\n         }\n         start = end;\n      }\n   }\n   if (fdselector == -1) return stbtt__new_buf(NULL, 0); // [DEAR IMGUI] fixed, see #6007 and nothings/stb#1422\n   return stbtt__get_subrs(info->cff, stbtt__cff_index_get(info->fontdicts, fdselector));\n}\n\nstatic int stbtt__run_charstring(const stbtt_fontinfo *info, int glyph_index, stbtt__csctx *c)\n{\n   int in_header = 1, maskbits = 0, subr_stack_height = 0, sp = 0, v, i, b0;\n   int has_subrs = 0, clear_stack;\n   float s[48];\n   stbtt__buf subr_stack[10], subrs = info->subrs, b;\n   float f;\n\n#define STBTT__CSERR(s) (0)\n\n   // this currently ignores the initial width value, which isn't needed if we have hmtx\n   b = stbtt__cff_index_get(info->charstrings, glyph_index);\n   while (b.cursor < b.size) {\n      i = 0;\n      clear_stack = 1;\n      b0 = stbtt__buf_get8(&b);\n      switch (b0) {\n      // @TODO implement hinting\n      case 0x13: // hintmask\n      case 0x14: // cntrmask\n         if (in_header)\n            maskbits += (sp / 2); // implicit \"vstem\"\n         in_header = 0;\n         stbtt__buf_skip(&b, (maskbits + 7) / 8);\n         break;\n\n      case 0x01: // hstem\n      case 0x03: // vstem\n      case 0x12: // hstemhm\n      case 0x17: // vstemhm\n         maskbits += (sp / 2);\n         break;\n\n      case 0x15: // rmoveto\n         in_header = 0;\n         if (sp < 2) return STBTT__CSERR(\"rmoveto stack\");\n         stbtt__csctx_rmove_to(c, s[sp-2], s[sp-1]);\n         break;\n      case 0x04: // vmoveto\n         in_header = 0;\n         if (sp < 1) return STBTT__CSERR(\"vmoveto stack\");\n         stbtt__csctx_rmove_to(c, 0, s[sp-1]);\n         break;\n      case 0x16: // hmoveto\n         in_header = 0;\n         if (sp < 1) return STBTT__CSERR(\"hmoveto stack\");\n         stbtt__csctx_rmove_to(c, s[sp-1], 0);\n         break;\n\n      case 0x05: // rlineto\n         if (sp < 2) return STBTT__CSERR(\"rlineto stack\");\n         for (; i + 1 < sp; i += 2)\n            stbtt__csctx_rline_to(c, s[i], s[i+1]);\n         break;\n\n      // hlineto/vlineto and vhcurveto/hvcurveto alternate horizontal and vertical\n      // starting from a different place.\n\n      case 0x07: // vlineto\n         if (sp < 1) return STBTT__CSERR(\"vlineto stack\");\n         goto vlineto;\n      case 0x06: // hlineto\n         if (sp < 1) return STBTT__CSERR(\"hlineto stack\");\n         for (;;) {\n            if (i >= sp) break;\n            stbtt__csctx_rline_to(c, s[i], 0);\n            i++;\n      vlineto:\n            if (i >= sp) break;\n            stbtt__csctx_rline_to(c, 0, s[i]);\n            i++;\n         }\n         break;\n\n      case 0x1F: // hvcurveto\n         if (sp < 4) return STBTT__CSERR(\"hvcurveto stack\");\n         goto hvcurveto;\n      case 0x1E: // vhcurveto\n         if (sp < 4) return STBTT__CSERR(\"vhcurveto stack\");\n         for (;;) {\n            if (i + 3 >= sp) break;\n            stbtt__csctx_rccurve_to(c, 0, s[i], s[i+1], s[i+2], s[i+3], (sp - i == 5) ? s[i + 4] : 0.0f);\n            i += 4;\n      hvcurveto:\n            if (i + 3 >= sp) break;\n            stbtt__csctx_rccurve_to(c, s[i], 0, s[i+1], s[i+2], (sp - i == 5) ? s[i+4] : 0.0f, s[i+3]);\n            i += 4;\n         }\n         break;\n\n      case 0x08: // rrcurveto\n         if (sp < 6) return STBTT__CSERR(\"rcurveline stack\");\n         for (; i + 5 < sp; i += 6)\n            stbtt__csctx_rccurve_to(c, s[i], s[i+1], s[i+2], s[i+3], s[i+4], s[i+5]);\n         break;\n\n      case 0x18: // rcurveline\n         if (sp < 8) return STBTT__CSERR(\"rcurveline stack\");\n         for (; i + 5 < sp - 2; i += 6)\n            stbtt__csctx_rccurve_to(c, s[i], s[i+1], s[i+2], s[i+3], s[i+4], s[i+5]);\n         if (i + 1 >= sp) return STBTT__CSERR(\"rcurveline stack\");\n         stbtt__csctx_rline_to(c, s[i], s[i+1]);\n         break;\n\n      case 0x19: // rlinecurve\n         if (sp < 8) return STBTT__CSERR(\"rlinecurve stack\");\n         for (; i + 1 < sp - 6; i += 2)\n            stbtt__csctx_rline_to(c, s[i], s[i+1]);\n         if (i + 5 >= sp) return STBTT__CSERR(\"rlinecurve stack\");\n         stbtt__csctx_rccurve_to(c, s[i], s[i+1], s[i+2], s[i+3], s[i+4], s[i+5]);\n         break;\n\n      case 0x1A: // vvcurveto\n      case 0x1B: // hhcurveto\n         if (sp < 4) return STBTT__CSERR(\"(vv|hh)curveto stack\");\n         f = 0.0;\n         if (sp & 1) { f = s[i]; i++; }\n         for (; i + 3 < sp; i += 4) {\n            if (b0 == 0x1B)\n               stbtt__csctx_rccurve_to(c, s[i], f, s[i+1], s[i+2], s[i+3], 0.0);\n            else\n               stbtt__csctx_rccurve_to(c, f, s[i], s[i+1], s[i+2], 0.0, s[i+3]);\n            f = 0.0;\n         }\n         break;\n\n      case 0x0A: // callsubr\n         if (!has_subrs) {\n            if (info->fdselect.size)\n               subrs = stbtt__cid_get_glyph_subrs(info, glyph_index);\n            has_subrs = 1;\n         }\n         // FALLTHROUGH\n      case 0x1D: // callgsubr\n         if (sp < 1) return STBTT__CSERR(\"call(g|)subr stack\");\n         v = (int) s[--sp];\n         if (subr_stack_height >= 10) return STBTT__CSERR(\"recursion limit\");\n         subr_stack[subr_stack_height++] = b;\n         b = stbtt__get_subr(b0 == 0x0A ? subrs : info->gsubrs, v);\n         if (b.size == 0) return STBTT__CSERR(\"subr not found\");\n         b.cursor = 0;\n         clear_stack = 0;\n         break;\n\n      case 0x0B: // return\n         if (subr_stack_height <= 0) return STBTT__CSERR(\"return outside subr\");\n         b = subr_stack[--subr_stack_height];\n         clear_stack = 0;\n         break;\n\n      case 0x0E: // endchar\n         stbtt__csctx_close_shape(c);\n         return 1;\n\n      case 0x0C: { // two-byte escape\n         float dx1, dx2, dx3, dx4, dx5, dx6, dy1, dy2, dy3, dy4, dy5, dy6;\n         float dx, dy;\n         int b1 = stbtt__buf_get8(&b);\n         switch (b1) {\n         // @TODO These \"flex\" implementations ignore the flex-depth and resolution,\n         // and always draw beziers.\n         case 0x22: // hflex\n            if (sp < 7) return STBTT__CSERR(\"hflex stack\");\n            dx1 = s[0];\n            dx2 = s[1];\n            dy2 = s[2];\n            dx3 = s[3];\n            dx4 = s[4];\n            dx5 = s[5];\n            dx6 = s[6];\n            stbtt__csctx_rccurve_to(c, dx1, 0, dx2, dy2, dx3, 0);\n            stbtt__csctx_rccurve_to(c, dx4, 0, dx5, -dy2, dx6, 0);\n            break;\n\n         case 0x23: // flex\n            if (sp < 13) return STBTT__CSERR(\"flex stack\");\n            dx1 = s[0];\n            dy1 = s[1];\n            dx2 = s[2];\n            dy2 = s[3];\n            dx3 = s[4];\n            dy3 = s[5];\n            dx4 = s[6];\n            dy4 = s[7];\n            dx5 = s[8];\n            dy5 = s[9];\n            dx6 = s[10];\n            dy6 = s[11];\n            //fd is s[12]\n            stbtt__csctx_rccurve_to(c, dx1, dy1, dx2, dy2, dx3, dy3);\n            stbtt__csctx_rccurve_to(c, dx4, dy4, dx5, dy5, dx6, dy6);\n            break;\n\n         case 0x24: // hflex1\n            if (sp < 9) return STBTT__CSERR(\"hflex1 stack\");\n            dx1 = s[0];\n            dy1 = s[1];\n            dx2 = s[2];\n            dy2 = s[3];\n            dx3 = s[4];\n            dx4 = s[5];\n            dx5 = s[6];\n            dy5 = s[7];\n            dx6 = s[8];\n            stbtt__csctx_rccurve_to(c, dx1, dy1, dx2, dy2, dx3, 0);\n            stbtt__csctx_rccurve_to(c, dx4, 0, dx5, dy5, dx6, -(dy1+dy2+dy5));\n            break;\n\n         case 0x25: // flex1\n            if (sp < 11) return STBTT__CSERR(\"flex1 stack\");\n            dx1 = s[0];\n            dy1 = s[1];\n            dx2 = s[2];\n            dy2 = s[3];\n            dx3 = s[4];\n            dy3 = s[5];\n            dx4 = s[6];\n            dy4 = s[7];\n            dx5 = s[8];\n            dy5 = s[9];\n            dx6 = dy6 = s[10];\n            dx = dx1+dx2+dx3+dx4+dx5;\n            dy = dy1+dy2+dy3+dy4+dy5;\n            if (STBTT_fabs(dx) > STBTT_fabs(dy))\n               dy6 = -dy;\n            else\n               dx6 = -dx;\n            stbtt__csctx_rccurve_to(c, dx1, dy1, dx2, dy2, dx3, dy3);\n            stbtt__csctx_rccurve_to(c, dx4, dy4, dx5, dy5, dx6, dy6);\n            break;\n\n         default:\n            return STBTT__CSERR(\"unimplemented\");\n         }\n      } break;\n\n      default:\n         if (b0 != 255 && b0 != 28 && b0 < 32)\n            return STBTT__CSERR(\"reserved operator\");\n\n         // push immediate\n         if (b0 == 255) {\n            f = (float)(stbtt_int32)stbtt__buf_get32(&b) / 0x10000;\n         } else {\n            stbtt__buf_skip(&b, -1);\n            f = (float)(stbtt_int16)stbtt__cff_int(&b);\n         }\n         if (sp >= 48) return STBTT__CSERR(\"push stack overflow\");\n         s[sp++] = f;\n         clear_stack = 0;\n         break;\n      }\n      if (clear_stack) sp = 0;\n   }\n   return STBTT__CSERR(\"no endchar\");\n\n#undef STBTT__CSERR\n}\n\nstatic int stbtt__GetGlyphShapeT2(const stbtt_fontinfo *info, int glyph_index, stbtt_vertex **pvertices)\n{\n   // runs the charstring twice, once to count and once to output (to avoid realloc)\n   stbtt__csctx count_ctx = STBTT__CSCTX_INIT(1);\n   stbtt__csctx output_ctx = STBTT__CSCTX_INIT(0);\n   if (stbtt__run_charstring(info, glyph_index, &count_ctx)) {\n      *pvertices = (stbtt_vertex*)STBTT_malloc(count_ctx.num_vertices*sizeof(stbtt_vertex), info->userdata);\n      output_ctx.pvertices = *pvertices;\n      if (stbtt__run_charstring(info, glyph_index, &output_ctx)) {\n         STBTT_assert(output_ctx.num_vertices == count_ctx.num_vertices);\n         return output_ctx.num_vertices;\n      }\n   }\n   *pvertices = NULL;\n   return 0;\n}\n\nstatic int stbtt__GetGlyphInfoT2(const stbtt_fontinfo *info, int glyph_index, int *x0, int *y0, int *x1, int *y1)\n{\n   stbtt__csctx c = STBTT__CSCTX_INIT(1);\n   int r = stbtt__run_charstring(info, glyph_index, &c);\n   if (x0)  *x0 = r ? c.min_x : 0;\n   if (y0)  *y0 = r ? c.min_y : 0;\n   if (x1)  *x1 = r ? c.max_x : 0;\n   if (y1)  *y1 = r ? c.max_y : 0;\n   return r ? c.num_vertices : 0;\n}\n\nSTBTT_DEF int stbtt_GetGlyphShape(const stbtt_fontinfo *info, int glyph_index, stbtt_vertex **pvertices)\n{\n   if (!info->cff.size)\n      return stbtt__GetGlyphShapeTT(info, glyph_index, pvertices);\n   else\n      return stbtt__GetGlyphShapeT2(info, glyph_index, pvertices);\n}\n\nSTBTT_DEF void stbtt_GetGlyphHMetrics(const stbtt_fontinfo *info, int glyph_index, int *advanceWidth, int *leftSideBearing)\n{\n   stbtt_uint16 numOfLongHorMetrics = ttUSHORT(info->data+info->hhea + 34);\n   if (glyph_index < numOfLongHorMetrics) {\n      if (advanceWidth)     *advanceWidth    = ttSHORT(info->data + info->hmtx + 4*glyph_index);\n      if (leftSideBearing)  *leftSideBearing = ttSHORT(info->data + info->hmtx + 4*glyph_index + 2);\n   } else {\n      if (advanceWidth)     *advanceWidth    = ttSHORT(info->data + info->hmtx + 4*(numOfLongHorMetrics-1));\n      if (leftSideBearing)  *leftSideBearing = ttSHORT(info->data + info->hmtx + 4*numOfLongHorMetrics + 2*(glyph_index - numOfLongHorMetrics));\n   }\n}\n\nSTBTT_DEF int  stbtt_GetKerningTableLength(const stbtt_fontinfo *info)\n{\n   stbtt_uint8 *data = info->data + info->kern;\n\n   // we only look at the first table. it must be 'horizontal' and format 0.\n   if (!info->kern)\n      return 0;\n   if (ttUSHORT(data+2) < 1) // number of tables, need at least 1\n      return 0;\n   if (ttUSHORT(data+8) != 1) // horizontal flag must be set in format\n      return 0;\n\n   return ttUSHORT(data+10);\n}\n\nSTBTT_DEF int stbtt_GetKerningTable(const stbtt_fontinfo *info, stbtt_kerningentry* table, int table_length)\n{\n   stbtt_uint8 *data = info->data + info->kern;\n   int k, length;\n\n   // we only look at the first table. it must be 'horizontal' and format 0.\n   if (!info->kern)\n      return 0;\n   if (ttUSHORT(data+2) < 1) // number of tables, need at least 1\n      return 0;\n   if (ttUSHORT(data+8) != 1) // horizontal flag must be set in format\n      return 0;\n\n   length = ttUSHORT(data+10);\n   if (table_length < length)\n      length = table_length;\n\n   for (k = 0; k < length; k++)\n   {\n      table[k].glyph1 = ttUSHORT(data+18+(k*6));\n      table[k].glyph2 = ttUSHORT(data+20+(k*6));\n      table[k].advance = ttSHORT(data+22+(k*6));\n   }\n\n   return length;\n}\n\nstatic int stbtt__GetGlyphKernInfoAdvance(const stbtt_fontinfo *info, int glyph1, int glyph2)\n{\n   stbtt_uint8 *data = info->data + info->kern;\n   stbtt_uint32 needle, straw;\n   int l, r, m;\n\n   // we only look at the first table. it must be 'horizontal' and format 0.\n   if (!info->kern)\n      return 0;\n   if (ttUSHORT(data+2) < 1) // number of tables, need at least 1\n      return 0;\n   if (ttUSHORT(data+8) != 1) // horizontal flag must be set in format\n      return 0;\n\n   l = 0;\n   r = ttUSHORT(data+10) - 1;\n   needle = glyph1 << 16 | glyph2;\n   while (l <= r) {\n      m = (l + r) >> 1;\n      straw = ttULONG(data+18+(m*6)); // note: unaligned read\n      if (needle < straw)\n         r = m - 1;\n      else if (needle > straw)\n         l = m + 1;\n      else\n         return ttSHORT(data+22+(m*6));\n   }\n   return 0;\n}\n\nstatic stbtt_int32 stbtt__GetCoverageIndex(stbtt_uint8 *coverageTable, int glyph)\n{\n   stbtt_uint16 coverageFormat = ttUSHORT(coverageTable);\n   switch (coverageFormat) {\n      case 1: {\n         stbtt_uint16 glyphCount = ttUSHORT(coverageTable + 2);\n\n         // Binary search.\n         stbtt_int32 l=0, r=glyphCount-1, m;\n         int straw, needle=glyph;\n         while (l <= r) {\n            stbtt_uint8 *glyphArray = coverageTable + 4;\n            stbtt_uint16 glyphID;\n            m = (l + r) >> 1;\n            glyphID = ttUSHORT(glyphArray + 2 * m);\n            straw = glyphID;\n            if (needle < straw)\n               r = m - 1;\n            else if (needle > straw)\n               l = m + 1;\n            else {\n               return m;\n            }\n         }\n         break;\n      }\n\n      case 2: {\n         stbtt_uint16 rangeCount = ttUSHORT(coverageTable + 2);\n         stbtt_uint8 *rangeArray = coverageTable + 4;\n\n         // Binary search.\n         stbtt_int32 l=0, r=rangeCount-1, m;\n         int strawStart, strawEnd, needle=glyph;\n         while (l <= r) {\n            stbtt_uint8 *rangeRecord;\n            m = (l + r) >> 1;\n            rangeRecord = rangeArray + 6 * m;\n            strawStart = ttUSHORT(rangeRecord);\n            strawEnd = ttUSHORT(rangeRecord + 2);\n            if (needle < strawStart)\n               r = m - 1;\n            else if (needle > strawEnd)\n               l = m + 1;\n            else {\n               stbtt_uint16 startCoverageIndex = ttUSHORT(rangeRecord + 4);\n               return startCoverageIndex + glyph - strawStart;\n            }\n         }\n         break;\n      }\n\n      default: return -1; // unsupported\n   }\n\n   return -1;\n}\n\nstatic stbtt_int32  stbtt__GetGlyphClass(stbtt_uint8 *classDefTable, int glyph)\n{\n   stbtt_uint16 classDefFormat = ttUSHORT(classDefTable);\n   switch (classDefFormat)\n   {\n      case 1: {\n         stbtt_uint16 startGlyphID = ttUSHORT(classDefTable + 2);\n         stbtt_uint16 glyphCount = ttUSHORT(classDefTable + 4);\n         stbtt_uint8 *classDef1ValueArray = classDefTable + 6;\n\n         if (glyph >= startGlyphID && glyph < startGlyphID + glyphCount)\n            return (stbtt_int32)ttUSHORT(classDef1ValueArray + 2 * (glyph - startGlyphID));\n         break;\n      }\n\n      case 2: {\n         stbtt_uint16 classRangeCount = ttUSHORT(classDefTable + 2);\n         stbtt_uint8 *classRangeRecords = classDefTable + 4;\n\n         // Binary search.\n         stbtt_int32 l=0, r=classRangeCount-1, m;\n         int strawStart, strawEnd, needle=glyph;\n         while (l <= r) {\n            stbtt_uint8 *classRangeRecord;\n            m = (l + r) >> 1;\n            classRangeRecord = classRangeRecords + 6 * m;\n            strawStart = ttUSHORT(classRangeRecord);\n            strawEnd = ttUSHORT(classRangeRecord + 2);\n            if (needle < strawStart)\n               r = m - 1;\n            else if (needle > strawEnd)\n               l = m + 1;\n            else\n               return (stbtt_int32)ttUSHORT(classRangeRecord + 4);\n         }\n         break;\n      }\n\n      default:\n         return -1; // Unsupported definition type, return an error.\n   }\n\n   // \"All glyphs not assigned to a class fall into class 0\". (OpenType spec)\n   return 0;\n}\n\n// Define to STBTT_assert(x) if you want to break on unimplemented formats.\n#define STBTT_GPOS_TODO_assert(x)\n\nstatic stbtt_int32 stbtt__GetGlyphGPOSInfoAdvance(const stbtt_fontinfo *info, int glyph1, int glyph2)\n{\n   stbtt_uint16 lookupListOffset;\n   stbtt_uint8 *lookupList;\n   stbtt_uint16 lookupCount;\n   stbtt_uint8 *data;\n   stbtt_int32 i, sti;\n\n   if (!info->gpos) return 0;\n\n   data = info->data + info->gpos;\n\n   if (ttUSHORT(data+0) != 1) return 0; // Major version 1\n   if (ttUSHORT(data+2) != 0) return 0; // Minor version 0\n\n   lookupListOffset = ttUSHORT(data+8);\n   lookupList = data + lookupListOffset;\n   lookupCount = ttUSHORT(lookupList);\n\n   for (i=0; i<lookupCount; ++i) {\n      stbtt_uint16 lookupOffset = ttUSHORT(lookupList + 2 + 2 * i);\n      stbtt_uint8 *lookupTable = lookupList + lookupOffset;\n\n      stbtt_uint16 lookupType = ttUSHORT(lookupTable);\n      stbtt_uint16 subTableCount = ttUSHORT(lookupTable + 4);\n      stbtt_uint8 *subTableOffsets = lookupTable + 6;\n      if (lookupType != 2) // Pair Adjustment Positioning Subtable\n         continue;\n\n      for (sti=0; sti<subTableCount; sti++) {\n         stbtt_uint16 subtableOffset = ttUSHORT(subTableOffsets + 2 * sti);\n         stbtt_uint8 *table = lookupTable + subtableOffset;\n         stbtt_uint16 posFormat = ttUSHORT(table);\n         stbtt_uint16 coverageOffset = ttUSHORT(table + 2);\n         stbtt_int32 coverageIndex = stbtt__GetCoverageIndex(table + coverageOffset, glyph1);\n         if (coverageIndex == -1) continue;\n\n         switch (posFormat) {\n            case 1: {\n               stbtt_int32 l, r, m;\n               int straw, needle;\n               stbtt_uint16 valueFormat1 = ttUSHORT(table + 4);\n               stbtt_uint16 valueFormat2 = ttUSHORT(table + 6);\n               if (valueFormat1 == 4 && valueFormat2 == 0) { // Support more formats?\n                  stbtt_int32 valueRecordPairSizeInBytes = 2;\n                  stbtt_uint16 pairSetCount = ttUSHORT(table + 8);\n                  stbtt_uint16 pairPosOffset = ttUSHORT(table + 10 + 2 * coverageIndex);\n                  stbtt_uint8 *pairValueTable = table + pairPosOffset;\n                  stbtt_uint16 pairValueCount = ttUSHORT(pairValueTable);\n                  stbtt_uint8 *pairValueArray = pairValueTable + 2;\n\n                  if (coverageIndex >= pairSetCount) return 0;\n\n                  needle=glyph2;\n                  r=pairValueCount-1;\n                  l=0;\n\n                  // Binary search.\n                  while (l <= r) {\n                     stbtt_uint16 secondGlyph;\n                     stbtt_uint8 *pairValue;\n                     m = (l + r) >> 1;\n                     pairValue = pairValueArray + (2 + valueRecordPairSizeInBytes) * m;\n                     secondGlyph = ttUSHORT(pairValue);\n                     straw = secondGlyph;\n                     if (needle < straw)\n                        r = m - 1;\n                     else if (needle > straw)\n                        l = m + 1;\n                     else {\n                        stbtt_int16 xAdvance = ttSHORT(pairValue + 2);\n                        return xAdvance;\n                     }\n                  }\n               } else\n                  return 0;\n               break;\n            }\n\n            case 2: {\n               stbtt_uint16 valueFormat1 = ttUSHORT(table + 4);\n               stbtt_uint16 valueFormat2 = ttUSHORT(table + 6);\n               if (valueFormat1 == 4 && valueFormat2 == 0) { // Support more formats?\n                  stbtt_uint16 classDef1Offset = ttUSHORT(table + 8);\n                  stbtt_uint16 classDef2Offset = ttUSHORT(table + 10);\n                  int glyph1class = stbtt__GetGlyphClass(table + classDef1Offset, glyph1);\n                  int glyph2class = stbtt__GetGlyphClass(table + classDef2Offset, glyph2);\n\n                  stbtt_uint16 class1Count = ttUSHORT(table + 12);\n                  stbtt_uint16 class2Count = ttUSHORT(table + 14);\n                  stbtt_uint8 *class1Records, *class2Records;\n                  stbtt_int16 xAdvance;\n\n                  if (glyph1class < 0 || glyph1class >= class1Count) return 0; // malformed\n                  if (glyph2class < 0 || glyph2class >= class2Count) return 0; // malformed\n\n                  class1Records = table + 16;\n                  class2Records = class1Records + 2 * (glyph1class * class2Count);\n                  xAdvance = ttSHORT(class2Records + 2 * glyph2class);\n                  return xAdvance;\n               } else\n                  return 0;\n               break;\n            }\n\n            default:\n               return 0; // Unsupported position format\n         }\n      }\n   }\n\n   return 0;\n}\n\nSTBTT_DEF int  stbtt_GetGlyphKernAdvance(const stbtt_fontinfo *info, int g1, int g2)\n{\n   int xAdvance = 0;\n\n   if (info->gpos)\n      xAdvance += stbtt__GetGlyphGPOSInfoAdvance(info, g1, g2);\n   else if (info->kern)\n      xAdvance += stbtt__GetGlyphKernInfoAdvance(info, g1, g2);\n\n   return xAdvance;\n}\n\nSTBTT_DEF int  stbtt_GetCodepointKernAdvance(const stbtt_fontinfo *info, int ch1, int ch2)\n{\n   if (!info->kern && !info->gpos) // if no kerning table, don't waste time looking up both codepoint->glyphs\n      return 0;\n   return stbtt_GetGlyphKernAdvance(info, stbtt_FindGlyphIndex(info,ch1), stbtt_FindGlyphIndex(info,ch2));\n}\n\nSTBTT_DEF void stbtt_GetCodepointHMetrics(const stbtt_fontinfo *info, int codepoint, int *advanceWidth, int *leftSideBearing)\n{\n   stbtt_GetGlyphHMetrics(info, stbtt_FindGlyphIndex(info,codepoint), advanceWidth, leftSideBearing);\n}\n\nSTBTT_DEF void stbtt_GetFontVMetrics(const stbtt_fontinfo *info, int *ascent, int *descent, int *lineGap)\n{\n   if (ascent ) *ascent  = ttSHORT(info->data+info->hhea + 4);\n   if (descent) *descent = ttSHORT(info->data+info->hhea + 6);\n   if (lineGap) *lineGap = ttSHORT(info->data+info->hhea + 8);\n}\n\nSTBTT_DEF int  stbtt_GetFontVMetricsOS2(const stbtt_fontinfo *info, int *typoAscent, int *typoDescent, int *typoLineGap)\n{\n   int tab = stbtt__find_table(info->data, info->fontstart, \"OS/2\");\n   if (!tab)\n      return 0;\n   if (typoAscent ) *typoAscent  = ttSHORT(info->data+tab + 68);\n   if (typoDescent) *typoDescent = ttSHORT(info->data+tab + 70);\n   if (typoLineGap) *typoLineGap = ttSHORT(info->data+tab + 72);\n   return 1;\n}\n\nSTBTT_DEF void stbtt_GetFontBoundingBox(const stbtt_fontinfo *info, int *x0, int *y0, int *x1, int *y1)\n{\n   *x0 = ttSHORT(info->data + info->head + 36);\n   *y0 = ttSHORT(info->data + info->head + 38);\n   *x1 = ttSHORT(info->data + info->head + 40);\n   *y1 = ttSHORT(info->data + info->head + 42);\n}\n\nSTBTT_DEF float stbtt_ScaleForPixelHeight(const stbtt_fontinfo *info, float height)\n{\n   int fheight = ttSHORT(info->data + info->hhea + 4) - ttSHORT(info->data + info->hhea + 6);\n   return (float) height / fheight;\n}\n\nSTBTT_DEF float stbtt_ScaleForMappingEmToPixels(const stbtt_fontinfo *info, float pixels)\n{\n   int unitsPerEm = ttUSHORT(info->data + info->head + 18);\n   return pixels / unitsPerEm;\n}\n\nSTBTT_DEF void stbtt_FreeShape(const stbtt_fontinfo *info, stbtt_vertex *v)\n{\n   STBTT_free(v, info->userdata);\n}\n\nSTBTT_DEF stbtt_uint8 *stbtt_FindSVGDoc(const stbtt_fontinfo *info, int gl)\n{\n   int i;\n   stbtt_uint8 *data = info->data;\n   stbtt_uint8 *svg_doc_list = data + stbtt__get_svg((stbtt_fontinfo *) info);\n\n   int numEntries = ttUSHORT(svg_doc_list);\n   stbtt_uint8 *svg_docs = svg_doc_list + 2;\n\n   for(i=0; i<numEntries; i++) {\n      stbtt_uint8 *svg_doc = svg_docs + (12 * i);\n      if ((gl >= ttUSHORT(svg_doc)) && (gl <= ttUSHORT(svg_doc + 2)))\n         return svg_doc;\n   }\n   return 0;\n}\n\nSTBTT_DEF int stbtt_GetGlyphSVG(const stbtt_fontinfo *info, int gl, const char **svg)\n{\n   stbtt_uint8 *data = info->data;\n   stbtt_uint8 *svg_doc;\n\n   if (info->svg == 0)\n      return 0;\n\n   svg_doc = stbtt_FindSVGDoc(info, gl);\n   if (svg_doc != NULL) {\n      *svg = (char *) data + info->svg + ttULONG(svg_doc + 4);\n      return ttULONG(svg_doc + 8);\n   } else {\n      return 0;\n   }\n}\n\nSTBTT_DEF int stbtt_GetCodepointSVG(const stbtt_fontinfo *info, int unicode_codepoint, const char **svg)\n{\n   return stbtt_GetGlyphSVG(info, stbtt_FindGlyphIndex(info, unicode_codepoint), svg);\n}\n\n//////////////////////////////////////////////////////////////////////////////\n//\n// antialiasing software rasterizer\n//\n\nSTBTT_DEF void stbtt_GetGlyphBitmapBoxSubpixel(const stbtt_fontinfo *font, int glyph, float scale_x, float scale_y,float shift_x, float shift_y, int *ix0, int *iy0, int *ix1, int *iy1)\n{\n   int x0=0,y0=0,x1,y1; // =0 suppresses compiler warning\n   if (!stbtt_GetGlyphBox(font, glyph, &x0,&y0,&x1,&y1)) {\n      // e.g. space character\n      if (ix0) *ix0 = 0;\n      if (iy0) *iy0 = 0;\n      if (ix1) *ix1 = 0;\n      if (iy1) *iy1 = 0;\n   } else {\n      // move to integral bboxes (treating pixels as little squares, what pixels get touched)?\n      if (ix0) *ix0 = STBTT_ifloor( x0 * scale_x + shift_x);\n      if (iy0) *iy0 = STBTT_ifloor(-y1 * scale_y + shift_y);\n      if (ix1) *ix1 = STBTT_iceil ( x1 * scale_x + shift_x);\n      if (iy1) *iy1 = STBTT_iceil (-y0 * scale_y + shift_y);\n   }\n}\n\nSTBTT_DEF void stbtt_GetGlyphBitmapBox(const stbtt_fontinfo *font, int glyph, float scale_x, float scale_y, int *ix0, int *iy0, int *ix1, int *iy1)\n{\n   stbtt_GetGlyphBitmapBoxSubpixel(font, glyph, scale_x, scale_y,0.0f,0.0f, ix0, iy0, ix1, iy1);\n}\n\nSTBTT_DEF void stbtt_GetCodepointBitmapBoxSubpixel(const stbtt_fontinfo *font, int codepoint, float scale_x, float scale_y, float shift_x, float shift_y, int *ix0, int *iy0, int *ix1, int *iy1)\n{\n   stbtt_GetGlyphBitmapBoxSubpixel(font, stbtt_FindGlyphIndex(font,codepoint), scale_x, scale_y,shift_x,shift_y, ix0,iy0,ix1,iy1);\n}\n\nSTBTT_DEF void stbtt_GetCodepointBitmapBox(const stbtt_fontinfo *font, int codepoint, float scale_x, float scale_y, int *ix0, int *iy0, int *ix1, int *iy1)\n{\n   stbtt_GetCodepointBitmapBoxSubpixel(font, codepoint, scale_x, scale_y,0.0f,0.0f, ix0,iy0,ix1,iy1);\n}\n\n//////////////////////////////////////////////////////////////////////////////\n//\n//  Rasterizer\n\ntypedef struct stbtt__hheap_chunk\n{\n   struct stbtt__hheap_chunk *next;\n} stbtt__hheap_chunk;\n\ntypedef struct stbtt__hheap\n{\n   struct stbtt__hheap_chunk *head;\n   void   *first_free;\n   int    num_remaining_in_head_chunk;\n} stbtt__hheap;\n\nstatic void *stbtt__hheap_alloc(stbtt__hheap *hh, size_t size, void *userdata)\n{\n   if (hh->first_free) {\n      void *p = hh->first_free;\n      hh->first_free = * (void **) p;\n      return p;\n   } else {\n      if (hh->num_remaining_in_head_chunk == 0) {\n         int count = (size < 32 ? 2000 : size < 128 ? 800 : 100);\n         stbtt__hheap_chunk *c = (stbtt__hheap_chunk *) STBTT_malloc(sizeof(stbtt__hheap_chunk) + size * count, userdata);\n         if (c == NULL)\n            return NULL;\n         c->next = hh->head;\n         hh->head = c;\n         hh->num_remaining_in_head_chunk = count;\n      }\n      --hh->num_remaining_in_head_chunk;\n      return (char *) (hh->head) + sizeof(stbtt__hheap_chunk) + size * hh->num_remaining_in_head_chunk;\n   }\n}\n\nstatic void stbtt__hheap_free(stbtt__hheap *hh, void *p)\n{\n   *(void **) p = hh->first_free;\n   hh->first_free = p;\n}\n\nstatic void stbtt__hheap_cleanup(stbtt__hheap *hh, void *userdata)\n{\n   stbtt__hheap_chunk *c = hh->head;\n   while (c) {\n      stbtt__hheap_chunk *n = c->next;\n      STBTT_free(c, userdata);\n      c = n;\n   }\n}\n\ntypedef struct stbtt__edge {\n   float x0,y0, x1,y1;\n   int invert;\n} stbtt__edge;\n\n\ntypedef struct stbtt__active_edge\n{\n   struct stbtt__active_edge *next;\n   #if STBTT_RASTERIZER_VERSION==1\n   int x,dx;\n   float ey;\n   int direction;\n   #elif STBTT_RASTERIZER_VERSION==2\n   float fx,fdx,fdy;\n   float direction;\n   float sy;\n   float ey;\n   #else\n   #error \"Unrecognized value of STBTT_RASTERIZER_VERSION\"\n   #endif\n} stbtt__active_edge;\n\n#if STBTT_RASTERIZER_VERSION == 1\n#define STBTT_FIXSHIFT   10\n#define STBTT_FIX        (1 << STBTT_FIXSHIFT)\n#define STBTT_FIXMASK    (STBTT_FIX-1)\n\nstatic stbtt__active_edge *stbtt__new_active(stbtt__hheap *hh, stbtt__edge *e, int off_x, float start_point, void *userdata)\n{\n   stbtt__active_edge *z = (stbtt__active_edge *) stbtt__hheap_alloc(hh, sizeof(*z), userdata);\n   float dxdy = (e->x1 - e->x0) / (e->y1 - e->y0);\n   STBTT_assert(z != NULL);\n   if (!z) return z;\n\n   // round dx down to avoid overshooting\n   if (dxdy < 0)\n      z->dx = -STBTT_ifloor(STBTT_FIX * -dxdy);\n   else\n      z->dx = STBTT_ifloor(STBTT_FIX * dxdy);\n\n   z->x = STBTT_ifloor(STBTT_FIX * e->x0 + z->dx * (start_point - e->y0)); // use z->dx so when we offset later it's by the same amount\n   z->x -= off_x * STBTT_FIX;\n\n   z->ey = e->y1;\n   z->next = 0;\n   z->direction = e->invert ? 1 : -1;\n   return z;\n}\n#elif STBTT_RASTERIZER_VERSION == 2\nstatic stbtt__active_edge *stbtt__new_active(stbtt__hheap *hh, stbtt__edge *e, int off_x, float start_point, void *userdata)\n{\n   stbtt__active_edge *z = (stbtt__active_edge *) stbtt__hheap_alloc(hh, sizeof(*z), userdata);\n   float dxdy = (e->x1 - e->x0) / (e->y1 - e->y0);\n   STBTT_assert(z != NULL);\n   //STBTT_assert(e->y0 <= start_point);\n   if (!z) return z;\n   z->fdx = dxdy;\n   z->fdy = dxdy != 0.0f ? (1.0f/dxdy) : 0.0f;\n   z->fx = e->x0 + dxdy * (start_point - e->y0);\n   z->fx -= off_x;\n   z->direction = e->invert ? 1.0f : -1.0f;\n   z->sy = e->y0;\n   z->ey = e->y1;\n   z->next = 0;\n   return z;\n}\n#else\n#error \"Unrecognized value of STBTT_RASTERIZER_VERSION\"\n#endif\n\n#if STBTT_RASTERIZER_VERSION == 1\n// note: this routine clips fills that extend off the edges... ideally this\n// wouldn't happen, but it could happen if the truetype glyph bounding boxes\n// are wrong, or if the user supplies a too-small bitmap\nstatic void stbtt__fill_active_edges(unsigned char *scanline, int len, stbtt__active_edge *e, int max_weight)\n{\n   // non-zero winding fill\n   int x0=0, w=0;\n\n   while (e) {\n      if (w == 0) {\n         // if we're currently at zero, we need to record the edge start point\n         x0 = e->x; w += e->direction;\n      } else {\n         int x1 = e->x; w += e->direction;\n         // if we went to zero, we need to draw\n         if (w == 0) {\n            int i = x0 >> STBTT_FIXSHIFT;\n            int j = x1 >> STBTT_FIXSHIFT;\n\n            if (i < len && j >= 0) {\n               if (i == j) {\n                  // x0,x1 are the same pixel, so compute combined coverage\n                  scanline[i] = scanline[i] + (stbtt_uint8) ((x1 - x0) * max_weight >> STBTT_FIXSHIFT);\n               } else {\n                  if (i >= 0) // add antialiasing for x0\n                     scanline[i] = scanline[i] + (stbtt_uint8) (((STBTT_FIX - (x0 & STBTT_FIXMASK)) * max_weight) >> STBTT_FIXSHIFT);\n                  else\n                     i = -1; // clip\n\n                  if (j < len) // add antialiasing for x1\n                     scanline[j] = scanline[j] + (stbtt_uint8) (((x1 & STBTT_FIXMASK) * max_weight) >> STBTT_FIXSHIFT);\n                  else\n                     j = len; // clip\n\n                  for (++i; i < j; ++i) // fill pixels between x0 and x1\n                     scanline[i] = scanline[i] + (stbtt_uint8) max_weight;\n               }\n            }\n         }\n      }\n\n      e = e->next;\n   }\n}\n\nstatic void stbtt__rasterize_sorted_edges(stbtt__bitmap *result, stbtt__edge *e, int n, int vsubsample, int off_x, int off_y, void *userdata)\n{\n   stbtt__hheap hh = { 0, 0, 0 };\n   stbtt__active_edge *active = NULL;\n   int y,j=0;\n   int max_weight = (255 / vsubsample);  // weight per vertical scanline\n   int s; // vertical subsample index\n   unsigned char scanline_data[512], *scanline;\n\n   if (result->w > 512)\n      scanline = (unsigned char *) STBTT_malloc(result->w, userdata);\n   else\n      scanline = scanline_data;\n\n   y = off_y * vsubsample;\n   e[n].y0 = (off_y + result->h) * (float) vsubsample + 1;\n\n   while (j < result->h) {\n      STBTT_memset(scanline, 0, result->w);\n      for (s=0; s < vsubsample; ++s) {\n         // find center of pixel for this scanline\n         float scan_y = y + 0.5f;\n         stbtt__active_edge **step = &active;\n\n         // update all active edges;\n         // remove all active edges that terminate before the center of this scanline\n         while (*step) {\n            stbtt__active_edge * z = *step;\n            if (z->ey <= scan_y) {\n               *step = z->next; // delete from list\n               STBTT_assert(z->direction);\n               z->direction = 0;\n               stbtt__hheap_free(&hh, z);\n            } else {\n               z->x += z->dx; // advance to position for current scanline\n               step = &((*step)->next); // advance through list\n            }\n         }\n\n         // resort the list if needed\n         for(;;) {\n            int changed=0;\n            step = &active;\n            while (*step && (*step)->next) {\n               if ((*step)->x > (*step)->next->x) {\n                  stbtt__active_edge *t = *step;\n                  stbtt__active_edge *q = t->next;\n\n                  t->next = q->next;\n                  q->next = t;\n                  *step = q;\n                  changed = 1;\n               }\n               step = &(*step)->next;\n            }\n            if (!changed) break;\n         }\n\n         // insert all edges that start before the center of this scanline -- omit ones that also end on this scanline\n         while (e->y0 <= scan_y) {\n            if (e->y1 > scan_y) {\n               stbtt__active_edge *z = stbtt__new_active(&hh, e, off_x, scan_y, userdata);\n               if (z != NULL) {\n                  // find insertion point\n                  if (active == NULL)\n                     active = z;\n                  else if (z->x < active->x) {\n                     // insert at front\n                     z->next = active;\n                     active = z;\n                  } else {\n                     // find thing to insert AFTER\n                     stbtt__active_edge *p = active;\n                     while (p->next && p->next->x < z->x)\n                        p = p->next;\n                     // at this point, p->next->x is NOT < z->x\n                     z->next = p->next;\n                     p->next = z;\n                  }\n               }\n            }\n            ++e;\n         }\n\n         // now process all active edges in XOR fashion\n         if (active)\n            stbtt__fill_active_edges(scanline, result->w, active, max_weight);\n\n         ++y;\n      }\n      STBTT_memcpy(result->pixels + j * result->stride, scanline, result->w);\n      ++j;\n   }\n\n   stbtt__hheap_cleanup(&hh, userdata);\n\n   if (scanline != scanline_data)\n      STBTT_free(scanline, userdata);\n}\n\n#elif STBTT_RASTERIZER_VERSION == 2\n\n// the edge passed in here does not cross the vertical line at x or the vertical line at x+1\n// (i.e. it has already been clipped to those)\nstatic void stbtt__handle_clipped_edge(float *scanline, int x, stbtt__active_edge *e, float x0, float y0, float x1, float y1)\n{\n   if (y0 == y1) return;\n   STBTT_assert(y0 < y1);\n   STBTT_assert(e->sy <= e->ey);\n   if (y0 > e->ey) return;\n   if (y1 < e->sy) return;\n   if (y0 < e->sy) {\n      x0 += (x1-x0) * (e->sy - y0) / (y1-y0);\n      y0 = e->sy;\n   }\n   if (y1 > e->ey) {\n      x1 += (x1-x0) * (e->ey - y1) / (y1-y0);\n      y1 = e->ey;\n   }\n\n   if (x0 == x)\n      STBTT_assert(x1 <= x+1);\n   else if (x0 == x+1)\n      STBTT_assert(x1 >= x);\n   else if (x0 <= x)\n      STBTT_assert(x1 <= x);\n   else if (x0 >= x+1)\n      STBTT_assert(x1 >= x+1);\n   else\n      STBTT_assert(x1 >= x && x1 <= x+1);\n\n   if (x0 <= x && x1 <= x)\n      scanline[x] += e->direction * (y1-y0);\n   else if (x0 >= x+1 && x1 >= x+1)\n      ;\n   else {\n      STBTT_assert(x0 >= x && x0 <= x+1 && x1 >= x && x1 <= x+1);\n      scanline[x] += e->direction * (y1-y0) * (1-((x0-x)+(x1-x))/2); // coverage = 1 - average x position\n   }\n}\n\nstatic float stbtt__sized_trapezoid_area(float height, float top_width, float bottom_width)\n{\n   STBTT_assert(top_width >= 0);\n   STBTT_assert(bottom_width >= 0);\n   return (top_width + bottom_width) / 2.0f * height;\n}\n\nstatic float stbtt__position_trapezoid_area(float height, float tx0, float tx1, float bx0, float bx1)\n{\n   return stbtt__sized_trapezoid_area(height, tx1 - tx0, bx1 - bx0);\n}\n\nstatic float stbtt__sized_triangle_area(float height, float width)\n{\n   return height * width / 2;\n}\n\nstatic void stbtt__fill_active_edges_new(float *scanline, float *scanline_fill, int len, stbtt__active_edge *e, float y_top)\n{\n   float y_bottom = y_top+1;\n\n   while (e) {\n      // brute force every pixel\n\n      // compute intersection points with top & bottom\n      STBTT_assert(e->ey >= y_top);\n\n      if (e->fdx == 0) {\n         float x0 = e->fx;\n         if (x0 < len) {\n            if (x0 >= 0) {\n               stbtt__handle_clipped_edge(scanline,(int) x0,e, x0,y_top, x0,y_bottom);\n               stbtt__handle_clipped_edge(scanline_fill-1,(int) x0+1,e, x0,y_top, x0,y_bottom);\n            } else {\n               stbtt__handle_clipped_edge(scanline_fill-1,0,e, x0,y_top, x0,y_bottom);\n            }\n         }\n      } else {\n         float x0 = e->fx;\n         float dx = e->fdx;\n         float xb = x0 + dx;\n         float x_top, x_bottom;\n         float sy0,sy1;\n         float dy = e->fdy;\n         STBTT_assert(e->sy <= y_bottom && e->ey >= y_top);\n\n         // compute endpoints of line segment clipped to this scanline (if the\n         // line segment starts on this scanline. x0 is the intersection of the\n         // line with y_top, but that may be off the line segment.\n         if (e->sy > y_top) {\n            x_top = x0 + dx * (e->sy - y_top);\n            sy0 = e->sy;\n         } else {\n            x_top = x0;\n            sy0 = y_top;\n         }\n         if (e->ey < y_bottom) {\n            x_bottom = x0 + dx * (e->ey - y_top);\n            sy1 = e->ey;\n         } else {\n            x_bottom = xb;\n            sy1 = y_bottom;\n         }\n\n         if (x_top >= 0 && x_bottom >= 0 && x_top < len && x_bottom < len) {\n            // from here on, we don't have to range check x values\n\n            if ((int) x_top == (int) x_bottom) {\n               float height;\n               // simple case, only spans one pixel\n               int x = (int) x_top;\n               height = (sy1 - sy0) * e->direction;\n               STBTT_assert(x >= 0 && x < len);\n               scanline[x]      += stbtt__position_trapezoid_area(height, x_top, x+1.0f, x_bottom, x+1.0f);\n               scanline_fill[x] += height; // everything right of this pixel is filled\n            } else {\n               int x,x1,x2;\n               float y_crossing, y_final, step, sign, area;\n               // covers 2+ pixels\n               if (x_top > x_bottom) {\n                  // flip scanline vertically; signed area is the same\n                  float t;\n                  sy0 = y_bottom - (sy0 - y_top);\n                  sy1 = y_bottom - (sy1 - y_top);\n                  t = sy0, sy0 = sy1, sy1 = t;\n                  t = x_bottom, x_bottom = x_top, x_top = t;\n                  dx = -dx;\n                  dy = -dy;\n                  t = x0, x0 = xb, xb = t;\n               }\n               STBTT_assert(dy >= 0);\n               STBTT_assert(dx >= 0);\n\n               x1 = (int) x_top;\n               x2 = (int) x_bottom;\n               // compute intersection with y axis at x1+1\n               y_crossing = y_top + dy * (x1+1 - x0);\n\n               // compute intersection with y axis at x2\n               y_final = y_top + dy * (x2 - x0);\n\n               //           x1    x_top                            x2    x_bottom\n               //     y_top  +------|-----+------------+------------+--------|---+------------+\n               //            |            |            |            |            |            |\n               //            |            |            |            |            |            |\n               //       sy0  |      Txxxxx|............|............|............|............|\n               // y_crossing |            *xxxxx.......|............|............|............|\n               //            |            |     xxxxx..|............|............|............|\n               //            |            |     /-   xx*xxxx........|............|............|\n               //            |            | dy <       |    xxxxxx..|............|............|\n               //   y_final  |            |     \\-     |          xx*xxx.........|............|\n               //       sy1  |            |            |            |   xxxxxB...|............|\n               //            |            |            |            |            |            |\n               //            |            |            |            |            |            |\n               //  y_bottom  +------------+------------+------------+------------+------------+\n               //\n               // goal is to measure the area covered by '.' in each pixel\n\n               // if x2 is right at the right edge of x1, y_crossing can blow up, github #1057\n               // @TODO: maybe test against sy1 rather than y_bottom?\n               if (y_crossing > y_bottom)\n                  y_crossing = y_bottom;\n\n               sign = e->direction;\n\n               // area of the rectangle covered from sy0..y_crossing\n               area = sign * (y_crossing-sy0);\n\n               // area of the triangle (x_top,sy0), (x1+1,sy0), (x1+1,y_crossing)\n               scanline[x1] += stbtt__sized_triangle_area(area, x1+1 - x_top);\n\n               // check if final y_crossing is blown up; no test case for this\n               if (y_final > y_bottom) {\n                  int denom = (x2 - (x1+1));\n                  y_final = y_bottom;\n                  if (denom != 0) { // [DEAR IMGUI] Avoid div by zero (https://github.com/nothings/stb/issues/1316)\n                     dy = (y_final - y_crossing ) / denom; // if denom=0, y_final = y_crossing, so y_final <= y_bottom\n                  }\n               }\n\n               // in second pixel, area covered by line segment found in first pixel\n               // is always a rectangle 1 wide * the height of that line segment; this\n               // is exactly what the variable 'area' stores. it also gets a contribution\n               // from the line segment within it. the THIRD pixel will get the first\n               // pixel's rectangle contribution, the second pixel's rectangle contribution,\n               // and its own contribution. the 'own contribution' is the same in every pixel except\n               // the leftmost and rightmost, a trapezoid that slides down in each pixel.\n               // the second pixel's contribution to the third pixel will be the\n               // rectangle 1 wide times the height change in the second pixel, which is dy.\n\n               step = sign * dy * 1; // dy is dy/dx, change in y for every 1 change in x,\n               // which multiplied by 1-pixel-width is how much pixel area changes for each step in x\n               // so the area advances by 'step' every time\n\n               for (x = x1+1; x < x2; ++x) {\n                  scanline[x] += area + step/2; // area of trapezoid is 1*step/2\n                  area += step;\n               }\n               STBTT_assert(STBTT_fabs(area) <= 1.01f); // accumulated error from area += step unless we round step down\n               STBTT_assert(sy1 > y_final-0.01f);\n\n               // area covered in the last pixel is the rectangle from all the pixels to the left,\n               // plus the trapezoid filled by the line segment in this pixel all the way to the right edge\n               scanline[x2] += area + sign * stbtt__position_trapezoid_area(sy1-y_final, (float) x2, x2+1.0f, x_bottom, x2+1.0f);\n\n               // the rest of the line is filled based on the total height of the line segment in this pixel\n               scanline_fill[x2] += sign * (sy1-sy0);\n            }\n         } else {\n            // if edge goes outside of box we're drawing, we require\n            // clipping logic. since this does not match the intended use\n            // of this library, we use a different, very slow brute\n            // force implementation\n            // note though that this does happen some of the time because\n            // x_top and x_bottom can be extrapolated at the top & bottom of\n            // the shape and actually lie outside the bounding box\n            int x;\n            for (x=0; x < len; ++x) {\n               // cases:\n               //\n               // there can be up to two intersections with the pixel. any intersection\n               // with left or right edges can be handled by splitting into two (or three)\n               // regions. intersections with top & bottom do not necessitate case-wise logic.\n               //\n               // the old way of doing this found the intersections with the left & right edges,\n               // then used some simple logic to produce up to three segments in sorted order\n               // from top-to-bottom. however, this had a problem: if an x edge was epsilon\n               // across the x border, then the corresponding y position might not be distinct\n               // from the other y segment, and it might ignored as an empty segment. to avoid\n               // that, we need to explicitly produce segments based on x positions.\n\n               // rename variables to clearly-defined pairs\n               float y0 = y_top;\n               float x1 = (float) (x);\n               float x2 = (float) (x+1);\n               float x3 = xb;\n               float y3 = y_bottom;\n\n               // x = e->x + e->dx * (y-y_top)\n               // (y-y_top) = (x - e->x) / e->dx\n               // y = (x - e->x) / e->dx + y_top\n               float y1 = (x - x0) / dx + y_top;\n               float y2 = (x+1 - x0) / dx + y_top;\n\n               if (x0 < x1 && x3 > x2) {         // three segments descending down-right\n                  stbtt__handle_clipped_edge(scanline,x,e, x0,y0, x1,y1);\n                  stbtt__handle_clipped_edge(scanline,x,e, x1,y1, x2,y2);\n                  stbtt__handle_clipped_edge(scanline,x,e, x2,y2, x3,y3);\n               } else if (x3 < x1 && x0 > x2) {  // three segments descending down-left\n                  stbtt__handle_clipped_edge(scanline,x,e, x0,y0, x2,y2);\n                  stbtt__handle_clipped_edge(scanline,x,e, x2,y2, x1,y1);\n                  stbtt__handle_clipped_edge(scanline,x,e, x1,y1, x3,y3);\n               } else if (x0 < x1 && x3 > x1) {  // two segments across x, down-right\n                  stbtt__handle_clipped_edge(scanline,x,e, x0,y0, x1,y1);\n                  stbtt__handle_clipped_edge(scanline,x,e, x1,y1, x3,y3);\n               } else if (x3 < x1 && x0 > x1) {  // two segments across x, down-left\n                  stbtt__handle_clipped_edge(scanline,x,e, x0,y0, x1,y1);\n                  stbtt__handle_clipped_edge(scanline,x,e, x1,y1, x3,y3);\n               } else if (x0 < x2 && x3 > x2) {  // two segments across x+1, down-right\n                  stbtt__handle_clipped_edge(scanline,x,e, x0,y0, x2,y2);\n                  stbtt__handle_clipped_edge(scanline,x,e, x2,y2, x3,y3);\n               } else if (x3 < x2 && x0 > x2) {  // two segments across x+1, down-left\n                  stbtt__handle_clipped_edge(scanline,x,e, x0,y0, x2,y2);\n                  stbtt__handle_clipped_edge(scanline,x,e, x2,y2, x3,y3);\n               } else {  // one segment\n                  stbtt__handle_clipped_edge(scanline,x,e, x0,y0, x3,y3);\n               }\n            }\n         }\n      }\n      e = e->next;\n   }\n}\n\n// directly AA rasterize edges w/o supersampling\nstatic void stbtt__rasterize_sorted_edges(stbtt__bitmap *result, stbtt__edge *e, int n, int vsubsample, int off_x, int off_y, void *userdata)\n{\n   stbtt__hheap hh = { 0, 0, 0 };\n   stbtt__active_edge *active = NULL;\n   int y,j=0, i;\n   float scanline_data[129], *scanline, *scanline2;\n\n   STBTT__NOTUSED(vsubsample);\n\n   if (result->w > 64)\n      scanline = (float *) STBTT_malloc((result->w*2+1) * sizeof(float), userdata);\n   else\n      scanline = scanline_data;\n\n   scanline2 = scanline + result->w;\n\n   y = off_y;\n   e[n].y0 = (float) (off_y + result->h) + 1;\n\n   while (j < result->h) {\n      // find center of pixel for this scanline\n      float scan_y_top    = y + 0.0f;\n      float scan_y_bottom = y + 1.0f;\n      stbtt__active_edge **step = &active;\n\n      STBTT_memset(scanline , 0, result->w*sizeof(scanline[0]));\n      STBTT_memset(scanline2, 0, (result->w+1)*sizeof(scanline[0]));\n\n      // update all active edges;\n      // remove all active edges that terminate before the top of this scanline\n      while (*step) {\n         stbtt__active_edge * z = *step;\n         if (z->ey <= scan_y_top) {\n            *step = z->next; // delete from list\n            STBTT_assert(z->direction);\n            z->direction = 0;\n            stbtt__hheap_free(&hh, z);\n         } else {\n            step = &((*step)->next); // advance through list\n         }\n      }\n\n      // insert all edges that start before the bottom of this scanline\n      while (e->y0 <= scan_y_bottom) {\n         if (e->y0 != e->y1) {\n            stbtt__active_edge *z = stbtt__new_active(&hh, e, off_x, scan_y_top, userdata);\n            if (z != NULL) {\n               if (j == 0 && off_y != 0) {\n                  if (z->ey < scan_y_top) {\n                     // this can happen due to subpixel positioning and some kind of fp rounding error i think\n                     z->ey = scan_y_top;\n                  }\n               }\n               STBTT_assert(z->ey >= scan_y_top); // if we get really unlucky a tiny bit of an edge can be out of bounds\n               // insert at front\n               z->next = active;\n               active = z;\n            }\n         }\n         ++e;\n      }\n\n      // now process all active edges\n      if (active)\n         stbtt__fill_active_edges_new(scanline, scanline2+1, result->w, active, scan_y_top);\n\n      {\n         float sum = 0;\n         for (i=0; i < result->w; ++i) {\n            float k;\n            int m;\n            sum += scanline2[i];\n            k = scanline[i] + sum;\n            k = (float) STBTT_fabs(k)*255 + 0.5f;\n            m = (int) k;\n            if (m > 255) m = 255;\n            result->pixels[j*result->stride + i] = (unsigned char) m;\n         }\n      }\n      // advance all the edges\n      step = &active;\n      while (*step) {\n         stbtt__active_edge *z = *step;\n         z->fx += z->fdx; // advance to position for current scanline\n         step = &((*step)->next); // advance through list\n      }\n\n      ++y;\n      ++j;\n   }\n\n   stbtt__hheap_cleanup(&hh, userdata);\n\n   if (scanline != scanline_data)\n      STBTT_free(scanline, userdata);\n}\n#else\n#error \"Unrecognized value of STBTT_RASTERIZER_VERSION\"\n#endif\n\n#define STBTT__COMPARE(a,b)  ((a)->y0 < (b)->y0)\n\nstatic void stbtt__sort_edges_ins_sort(stbtt__edge *p, int n)\n{\n   int i,j;\n   for (i=1; i < n; ++i) {\n      stbtt__edge t = p[i], *a = &t;\n      j = i;\n      while (j > 0) {\n         stbtt__edge *b = &p[j-1];\n         int c = STBTT__COMPARE(a,b);\n         if (!c) break;\n         p[j] = p[j-1];\n         --j;\n      }\n      if (i != j)\n         p[j] = t;\n   }\n}\n\nstatic void stbtt__sort_edges_quicksort(stbtt__edge *p, int n)\n{\n   /* threshold for transitioning to insertion sort */\n   while (n > 12) {\n      stbtt__edge t;\n      int c01,c12,c,m,i,j;\n\n      /* compute median of three */\n      m = n >> 1;\n      c01 = STBTT__COMPARE(&p[0],&p[m]);\n      c12 = STBTT__COMPARE(&p[m],&p[n-1]);\n      /* if 0 >= mid >= end, or 0 < mid < end, then use mid */\n      if (c01 != c12) {\n         /* otherwise, we'll need to swap something else to middle */\n         int z;\n         c = STBTT__COMPARE(&p[0],&p[n-1]);\n         /* 0>mid && mid<n:  0>n => n; 0<n => 0 */\n         /* 0<mid && mid>n:  0>n => 0; 0<n => n */\n         z = (c == c12) ? 0 : n-1;\n         t = p[z];\n         p[z] = p[m];\n         p[m] = t;\n      }\n      /* now p[m] is the median-of-three */\n      /* swap it to the beginning so it won't move around */\n      t = p[0];\n      p[0] = p[m];\n      p[m] = t;\n\n      /* partition loop */\n      i=1;\n      j=n-1;\n      for(;;) {\n         /* handling of equality is crucial here */\n         /* for sentinels & efficiency with duplicates */\n         for (;;++i) {\n            if (!STBTT__COMPARE(&p[i], &p[0])) break;\n         }\n         for (;;--j) {\n            if (!STBTT__COMPARE(&p[0], &p[j])) break;\n         }\n         /* make sure we haven't crossed */\n         if (i >= j) break;\n         t = p[i];\n         p[i] = p[j];\n         p[j] = t;\n\n         ++i;\n         --j;\n      }\n      /* recurse on smaller side, iterate on larger */\n      if (j < (n-i)) {\n         stbtt__sort_edges_quicksort(p,j);\n         p = p+i;\n         n = n-i;\n      } else {\n         stbtt__sort_edges_quicksort(p+i, n-i);\n         n = j;\n      }\n   }\n}\n\nstatic void stbtt__sort_edges(stbtt__edge *p, int n)\n{\n   stbtt__sort_edges_quicksort(p, n);\n   stbtt__sort_edges_ins_sort(p, n);\n}\n\ntypedef struct\n{\n   float x,y;\n} stbtt__point;\n\nstatic void stbtt__rasterize(stbtt__bitmap *result, stbtt__point *pts, int *wcount, int windings, float scale_x, float scale_y, float shift_x, float shift_y, int off_x, int off_y, int invert, void *userdata)\n{\n   float y_scale_inv = invert ? -scale_y : scale_y;\n   stbtt__edge *e;\n   int n,i,j,k,m;\n#if STBTT_RASTERIZER_VERSION == 1\n   int vsubsample = result->h < 8 ? 15 : 5;\n#elif STBTT_RASTERIZER_VERSION == 2\n   int vsubsample = 1;\n#else\n   #error \"Unrecognized value of STBTT_RASTERIZER_VERSION\"\n#endif\n   // vsubsample should divide 255 evenly; otherwise we won't reach full opacity\n\n   // now we have to blow out the windings into explicit edge lists\n   n = 0;\n   for (i=0; i < windings; ++i)\n      n += wcount[i];\n\n   e = (stbtt__edge *) STBTT_malloc(sizeof(*e) * (n+1), userdata); // add an extra one as a sentinel\n   if (e == 0) return;\n   n = 0;\n\n   m=0;\n   for (i=0; i < windings; ++i) {\n      stbtt__point *p = pts + m;\n      m += wcount[i];\n      j = wcount[i]-1;\n      for (k=0; k < wcount[i]; j=k++) {\n         int a=k,b=j;\n         // skip the edge if horizontal\n         if (p[j].y == p[k].y)\n            continue;\n         // add edge from j to k to the list\n         e[n].invert = 0;\n         if (invert ? p[j].y > p[k].y : p[j].y < p[k].y) {\n            e[n].invert = 1;\n            a=j,b=k;\n         }\n         e[n].x0 = p[a].x * scale_x + shift_x;\n         e[n].y0 = (p[a].y * y_scale_inv + shift_y) * vsubsample;\n         e[n].x1 = p[b].x * scale_x + shift_x;\n         e[n].y1 = (p[b].y * y_scale_inv + shift_y) * vsubsample;\n         ++n;\n      }\n   }\n\n   // now sort the edges by their highest point (should snap to integer, and then by x)\n   //STBTT_sort(e, n, sizeof(e[0]), stbtt__edge_compare);\n   stbtt__sort_edges(e, n);\n\n   // now, traverse the scanlines and find the intersections on each scanline, use xor winding rule\n   stbtt__rasterize_sorted_edges(result, e, n, vsubsample, off_x, off_y, userdata);\n\n   STBTT_free(e, userdata);\n}\n\nstatic void stbtt__add_point(stbtt__point *points, int n, float x, float y)\n{\n   if (!points) return; // during first pass, it's unallocated\n   points[n].x = x;\n   points[n].y = y;\n}\n\n// tessellate until threshold p is happy... @TODO warped to compensate for non-linear stretching\nstatic int stbtt__tesselate_curve(stbtt__point *points, int *num_points, float x0, float y0, float x1, float y1, float x2, float y2, float objspace_flatness_squared, int n)\n{\n   // midpoint\n   float mx = (x0 + 2*x1 + x2)/4;\n   float my = (y0 + 2*y1 + y2)/4;\n   // versus directly drawn line\n   float dx = (x0+x2)/2 - mx;\n   float dy = (y0+y2)/2 - my;\n   if (n > 16) // 65536 segments on one curve better be enough!\n      return 1;\n   if (dx*dx+dy*dy > objspace_flatness_squared) { // half-pixel error allowed... need to be smaller if AA\n      stbtt__tesselate_curve(points, num_points, x0,y0, (x0+x1)/2.0f,(y0+y1)/2.0f, mx,my, objspace_flatness_squared,n+1);\n      stbtt__tesselate_curve(points, num_points, mx,my, (x1+x2)/2.0f,(y1+y2)/2.0f, x2,y2, objspace_flatness_squared,n+1);\n   } else {\n      stbtt__add_point(points, *num_points,x2,y2);\n      *num_points = *num_points+1;\n   }\n   return 1;\n}\n\nstatic void stbtt__tesselate_cubic(stbtt__point *points, int *num_points, float x0, float y0, float x1, float y1, float x2, float y2, float x3, float y3, float objspace_flatness_squared, int n)\n{\n   // @TODO this \"flatness\" calculation is just made-up nonsense that seems to work well enough\n   float dx0 = x1-x0;\n   float dy0 = y1-y0;\n   float dx1 = x2-x1;\n   float dy1 = y2-y1;\n   float dx2 = x3-x2;\n   float dy2 = y3-y2;\n   float dx = x3-x0;\n   float dy = y3-y0;\n   float longlen = (float) (STBTT_sqrt(dx0*dx0+dy0*dy0)+STBTT_sqrt(dx1*dx1+dy1*dy1)+STBTT_sqrt(dx2*dx2+dy2*dy2));\n   float shortlen = (float) STBTT_sqrt(dx*dx+dy*dy);\n   float flatness_squared = longlen*longlen-shortlen*shortlen;\n\n   if (n > 16) // 65536 segments on one curve better be enough!\n      return;\n\n   if (flatness_squared > objspace_flatness_squared) {\n      float x01 = (x0+x1)/2;\n      float y01 = (y0+y1)/2;\n      float x12 = (x1+x2)/2;\n      float y12 = (y1+y2)/2;\n      float x23 = (x2+x3)/2;\n      float y23 = (y2+y3)/2;\n\n      float xa = (x01+x12)/2;\n      float ya = (y01+y12)/2;\n      float xb = (x12+x23)/2;\n      float yb = (y12+y23)/2;\n\n      float mx = (xa+xb)/2;\n      float my = (ya+yb)/2;\n\n      stbtt__tesselate_cubic(points, num_points, x0,y0, x01,y01, xa,ya, mx,my, objspace_flatness_squared,n+1);\n      stbtt__tesselate_cubic(points, num_points, mx,my, xb,yb, x23,y23, x3,y3, objspace_flatness_squared,n+1);\n   } else {\n      stbtt__add_point(points, *num_points,x3,y3);\n      *num_points = *num_points+1;\n   }\n}\n\n// returns number of contours\nstatic stbtt__point *stbtt_FlattenCurves(stbtt_vertex *vertices, int num_verts, float objspace_flatness, int **contour_lengths, int *num_contours, void *userdata)\n{\n   stbtt__point *points=0;\n   int num_points=0;\n\n   float objspace_flatness_squared = objspace_flatness * objspace_flatness;\n   int i,n=0,start=0, pass;\n\n   // count how many \"moves\" there are to get the contour count\n   for (i=0; i < num_verts; ++i)\n      if (vertices[i].type == STBTT_vmove)\n         ++n;\n\n   *num_contours = n;\n   if (n == 0) return 0;\n\n   *contour_lengths = (int *) STBTT_malloc(sizeof(**contour_lengths) * n, userdata);\n\n   if (*contour_lengths == 0) {\n      *num_contours = 0;\n      return 0;\n   }\n\n   // make two passes through the points so we don't need to realloc\n   for (pass=0; pass < 2; ++pass) {\n      float x=0,y=0;\n      if (pass == 1) {\n         points = (stbtt__point *) STBTT_malloc(num_points * sizeof(points[0]), userdata);\n         if (points == NULL) goto error;\n      }\n      num_points = 0;\n      n= -1;\n      for (i=0; i < num_verts; ++i) {\n         switch (vertices[i].type) {\n            case STBTT_vmove:\n               // start the next contour\n               if (n >= 0)\n                  (*contour_lengths)[n] = num_points - start;\n               ++n;\n               start = num_points;\n\n               x = vertices[i].x, y = vertices[i].y;\n               stbtt__add_point(points, num_points++, x,y);\n               break;\n            case STBTT_vline:\n               x = vertices[i].x, y = vertices[i].y;\n               stbtt__add_point(points, num_points++, x, y);\n               break;\n            case STBTT_vcurve:\n               stbtt__tesselate_curve(points, &num_points, x,y,\n                                        vertices[i].cx, vertices[i].cy,\n                                        vertices[i].x,  vertices[i].y,\n                                        objspace_flatness_squared, 0);\n               x = vertices[i].x, y = vertices[i].y;\n               break;\n            case STBTT_vcubic:\n               stbtt__tesselate_cubic(points, &num_points, x,y,\n                                        vertices[i].cx, vertices[i].cy,\n                                        vertices[i].cx1, vertices[i].cy1,\n                                        vertices[i].x,  vertices[i].y,\n                                        objspace_flatness_squared, 0);\n               x = vertices[i].x, y = vertices[i].y;\n               break;\n         }\n      }\n      (*contour_lengths)[n] = num_points - start;\n   }\n\n   return points;\nerror:\n   STBTT_free(points, userdata);\n   STBTT_free(*contour_lengths, userdata);\n   *contour_lengths = 0;\n   *num_contours = 0;\n   return NULL;\n}\n\nSTBTT_DEF void stbtt_Rasterize(stbtt__bitmap *result, float flatness_in_pixels, stbtt_vertex *vertices, int num_verts, float scale_x, float scale_y, float shift_x, float shift_y, int x_off, int y_off, int invert, void *userdata)\n{\n   float scale            = scale_x > scale_y ? scale_y : scale_x;\n   int winding_count      = 0;\n   int *winding_lengths   = NULL;\n   stbtt__point *windings = stbtt_FlattenCurves(vertices, num_verts, flatness_in_pixels / scale, &winding_lengths, &winding_count, userdata);\n   if (windings) {\n      stbtt__rasterize(result, windings, winding_lengths, winding_count, scale_x, scale_y, shift_x, shift_y, x_off, y_off, invert, userdata);\n      STBTT_free(winding_lengths, userdata);\n      STBTT_free(windings, userdata);\n   }\n}\n\nSTBTT_DEF void stbtt_FreeBitmap(unsigned char *bitmap, void *userdata)\n{\n   STBTT_free(bitmap, userdata);\n}\n\nSTBTT_DEF unsigned char *stbtt_GetGlyphBitmapSubpixel(const stbtt_fontinfo *info, float scale_x, float scale_y, float shift_x, float shift_y, int glyph, int *width, int *height, int *xoff, int *yoff)\n{\n   int ix0,iy0,ix1,iy1;\n   stbtt__bitmap gbm;\n   stbtt_vertex *vertices;\n   int num_verts = stbtt_GetGlyphShape(info, glyph, &vertices);\n\n   if (scale_x == 0) scale_x = scale_y;\n   if (scale_y == 0) {\n      if (scale_x == 0) {\n         STBTT_free(vertices, info->userdata);\n         return NULL;\n      }\n      scale_y = scale_x;\n   }\n\n   stbtt_GetGlyphBitmapBoxSubpixel(info, glyph, scale_x, scale_y, shift_x, shift_y, &ix0,&iy0,&ix1,&iy1);\n\n   // now we get the size\n   gbm.w = (ix1 - ix0);\n   gbm.h = (iy1 - iy0);\n   gbm.pixels = NULL; // in case we error\n\n   if (width ) *width  = gbm.w;\n   if (height) *height = gbm.h;\n   if (xoff  ) *xoff   = ix0;\n   if (yoff  ) *yoff   = iy0;\n\n   if (gbm.w && gbm.h) {\n      gbm.pixels = (unsigned char *) STBTT_malloc(gbm.w * gbm.h, info->userdata);\n      if (gbm.pixels) {\n         gbm.stride = gbm.w;\n\n         stbtt_Rasterize(&gbm, 0.35f, vertices, num_verts, scale_x, scale_y, shift_x, shift_y, ix0, iy0, 1, info->userdata);\n      }\n   }\n   STBTT_free(vertices, info->userdata);\n   return gbm.pixels;\n}\n\nSTBTT_DEF unsigned char *stbtt_GetGlyphBitmap(const stbtt_fontinfo *info, float scale_x, float scale_y, int glyph, int *width, int *height, int *xoff, int *yoff)\n{\n   return stbtt_GetGlyphBitmapSubpixel(info, scale_x, scale_y, 0.0f, 0.0f, glyph, width, height, xoff, yoff);\n}\n\nSTBTT_DEF void stbtt_MakeGlyphBitmapSubpixel(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int glyph)\n{\n   int ix0,iy0;\n   stbtt_vertex *vertices;\n   int num_verts = stbtt_GetGlyphShape(info, glyph, &vertices);\n   stbtt__bitmap gbm;\n\n   stbtt_GetGlyphBitmapBoxSubpixel(info, glyph, scale_x, scale_y, shift_x, shift_y, &ix0,&iy0,0,0);\n   gbm.pixels = output;\n   gbm.w = out_w;\n   gbm.h = out_h;\n   gbm.stride = out_stride;\n\n   if (gbm.w && gbm.h)\n      stbtt_Rasterize(&gbm, 0.35f, vertices, num_verts, scale_x, scale_y, shift_x, shift_y, ix0,iy0, 1, info->userdata);\n\n   STBTT_free(vertices, info->userdata);\n}\n\nSTBTT_DEF void stbtt_MakeGlyphBitmap(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, int glyph)\n{\n   stbtt_MakeGlyphBitmapSubpixel(info, output, out_w, out_h, out_stride, scale_x, scale_y, 0.0f,0.0f, glyph);\n}\n\nSTBTT_DEF unsigned char *stbtt_GetCodepointBitmapSubpixel(const stbtt_fontinfo *info, float scale_x, float scale_y, float shift_x, float shift_y, int codepoint, int *width, int *height, int *xoff, int *yoff)\n{\n   return stbtt_GetGlyphBitmapSubpixel(info, scale_x, scale_y,shift_x,shift_y, stbtt_FindGlyphIndex(info,codepoint), width,height,xoff,yoff);\n}\n\nSTBTT_DEF void stbtt_MakeCodepointBitmapSubpixelPrefilter(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int oversample_x, int oversample_y, float *sub_x, float *sub_y, int codepoint)\n{\n   stbtt_MakeGlyphBitmapSubpixelPrefilter(info, output, out_w, out_h, out_stride, scale_x, scale_y, shift_x, shift_y, oversample_x, oversample_y, sub_x, sub_y, stbtt_FindGlyphIndex(info,codepoint));\n}\n\nSTBTT_DEF void stbtt_MakeCodepointBitmapSubpixel(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int codepoint)\n{\n   stbtt_MakeGlyphBitmapSubpixel(info, output, out_w, out_h, out_stride, scale_x, scale_y, shift_x, shift_y, stbtt_FindGlyphIndex(info,codepoint));\n}\n\nSTBTT_DEF unsigned char *stbtt_GetCodepointBitmap(const stbtt_fontinfo *info, float scale_x, float scale_y, int codepoint, int *width, int *height, int *xoff, int *yoff)\n{\n   return stbtt_GetCodepointBitmapSubpixel(info, scale_x, scale_y, 0.0f,0.0f, codepoint, width,height,xoff,yoff);\n}\n\nSTBTT_DEF void stbtt_MakeCodepointBitmap(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, int codepoint)\n{\n   stbtt_MakeCodepointBitmapSubpixel(info, output, out_w, out_h, out_stride, scale_x, scale_y, 0.0f,0.0f, codepoint);\n}\n\n//////////////////////////////////////////////////////////////////////////////\n//\n// bitmap baking\n//\n// This is SUPER-CRAPPY packing to keep source code small\n\nstatic int stbtt_BakeFontBitmap_internal(unsigned char *data, int offset,  // font location (use offset=0 for plain .ttf)\n                                float pixel_height,                     // height of font in pixels\n                                unsigned char *pixels, int pw, int ph,  // bitmap to be filled in\n                                int first_char, int num_chars,          // characters to bake\n                                stbtt_bakedchar *chardata)\n{\n   float scale;\n   int x,y,bottom_y, i;\n   stbtt_fontinfo f;\n   f.userdata = NULL;\n   if (!stbtt_InitFont(&f, data, offset))\n      return -1;\n   STBTT_memset(pixels, 0, pw*ph); // background of 0 around pixels\n   x=y=1;\n   bottom_y = 1;\n\n   scale = stbtt_ScaleForPixelHeight(&f, pixel_height);\n\n   for (i=0; i < num_chars; ++i) {\n      int advance, lsb, x0,y0,x1,y1,gw,gh;\n      int g = stbtt_FindGlyphIndex(&f, first_char + i);\n      stbtt_GetGlyphHMetrics(&f, g, &advance, &lsb);\n      stbtt_GetGlyphBitmapBox(&f, g, scale,scale, &x0,&y0,&x1,&y1);\n      gw = x1-x0;\n      gh = y1-y0;\n      if (x + gw + 1 >= pw)\n         y = bottom_y, x = 1; // advance to next row\n      if (y + gh + 1 >= ph) // check if it fits vertically AFTER potentially moving to next row\n         return -i;\n      STBTT_assert(x+gw < pw);\n      STBTT_assert(y+gh < ph);\n      stbtt_MakeGlyphBitmap(&f, pixels+x+y*pw, gw,gh,pw, scale,scale, g);\n      chardata[i].x0 = (stbtt_int16) x;\n      chardata[i].y0 = (stbtt_int16) y;\n      chardata[i].x1 = (stbtt_int16) (x + gw);\n      chardata[i].y1 = (stbtt_int16) (y + gh);\n      chardata[i].xadvance = scale * advance;\n      chardata[i].xoff     = (float) x0;\n      chardata[i].yoff     = (float) y0;\n      x = x + gw + 1;\n      if (y+gh+1 > bottom_y)\n         bottom_y = y+gh+1;\n   }\n   return bottom_y;\n}\n\nSTBTT_DEF void stbtt_GetBakedQuad(const stbtt_bakedchar *chardata, int pw, int ph, int char_index, float *xpos, float *ypos, stbtt_aligned_quad *q, int opengl_fillrule)\n{\n   float d3d_bias = opengl_fillrule ? 0 : -0.5f;\n   float ipw = 1.0f / pw, iph = 1.0f / ph;\n   const stbtt_bakedchar *b = chardata + char_index;\n   int round_x = STBTT_ifloor((*xpos + b->xoff) + 0.5f);\n   int round_y = STBTT_ifloor((*ypos + b->yoff) + 0.5f);\n\n   q->x0 = round_x + d3d_bias;\n   q->y0 = round_y + d3d_bias;\n   q->x1 = round_x + b->x1 - b->x0 + d3d_bias;\n   q->y1 = round_y + b->y1 - b->y0 + d3d_bias;\n\n   q->s0 = b->x0 * ipw;\n   q->t0 = b->y0 * iph;\n   q->s1 = b->x1 * ipw;\n   q->t1 = b->y1 * iph;\n\n   *xpos += b->xadvance;\n}\n\n//////////////////////////////////////////////////////////////////////////////\n//\n// rectangle packing replacement routines if you don't have stb_rect_pack.h\n//\n\n#ifndef STB_RECT_PACK_VERSION\n\ntypedef int stbrp_coord;\n\n////////////////////////////////////////////////////////////////////////////////////\n//                                                                                //\n//                                                                                //\n// COMPILER WARNING ?!?!?                                                         //\n//                                                                                //\n//                                                                                //\n// if you get a compile warning due to these symbols being defined more than      //\n// once, move #include \"stb_rect_pack.h\" before #include \"stb_truetype.h\"         //\n//                                                                                //\n////////////////////////////////////////////////////////////////////////////////////\n\ntypedef struct\n{\n   int width,height;\n   int x,y,bottom_y;\n} stbrp_context;\n\ntypedef struct\n{\n   unsigned char x;\n} stbrp_node;\n\nstruct stbrp_rect\n{\n   stbrp_coord x,y;\n   int id,w,h,was_packed;\n};\n\nstatic void stbrp_init_target(stbrp_context *con, int pw, int ph, stbrp_node *nodes, int num_nodes)\n{\n   con->width  = pw;\n   con->height = ph;\n   con->x = 0;\n   con->y = 0;\n   con->bottom_y = 0;\n   STBTT__NOTUSED(nodes);\n   STBTT__NOTUSED(num_nodes);\n}\n\nstatic void stbrp_pack_rects(stbrp_context *con, stbrp_rect *rects, int num_rects)\n{\n   int i;\n   for (i=0; i < num_rects; ++i) {\n      if (con->x + rects[i].w > con->width) {\n         con->x = 0;\n         con->y = con->bottom_y;\n      }\n      if (con->y + rects[i].h > con->height)\n         break;\n      rects[i].x = con->x;\n      rects[i].y = con->y;\n      rects[i].was_packed = 1;\n      con->x += rects[i].w;\n      if (con->y + rects[i].h > con->bottom_y)\n         con->bottom_y = con->y + rects[i].h;\n   }\n   for (   ; i < num_rects; ++i)\n      rects[i].was_packed = 0;\n}\n#endif\n\n//////////////////////////////////////////////////////////////////////////////\n//\n// bitmap baking\n//\n// This is SUPER-AWESOME (tm Ryan Gordon) packing using stb_rect_pack.h. If\n// stb_rect_pack.h isn't available, it uses the BakeFontBitmap strategy.\n\nSTBTT_DEF int stbtt_PackBegin(stbtt_pack_context *spc, unsigned char *pixels, int pw, int ph, int stride_in_bytes, int padding, void *alloc_context)\n{\n   stbrp_context *context = (stbrp_context *) STBTT_malloc(sizeof(*context)            ,alloc_context);\n   int            num_nodes = pw - padding;\n   stbrp_node    *nodes   = (stbrp_node    *) STBTT_malloc(sizeof(*nodes  ) * num_nodes,alloc_context);\n\n   if (context == NULL || nodes == NULL) {\n      if (context != NULL) STBTT_free(context, alloc_context);\n      if (nodes   != NULL) STBTT_free(nodes  , alloc_context);\n      return 0;\n   }\n\n   spc->user_allocator_context = alloc_context;\n   spc->width = pw;\n   spc->height = ph;\n   spc->pixels = pixels;\n   spc->pack_info = context;\n   spc->nodes = nodes;\n   spc->padding = padding;\n   spc->stride_in_bytes = stride_in_bytes != 0 ? stride_in_bytes : pw;\n   spc->h_oversample = 1;\n   spc->v_oversample = 1;\n   spc->skip_missing = 0;\n\n   stbrp_init_target(context, pw-padding, ph-padding, nodes, num_nodes);\n\n   if (pixels)\n      STBTT_memset(pixels, 0, pw*ph); // background of 0 around pixels\n\n   return 1;\n}\n\nSTBTT_DEF void stbtt_PackEnd  (stbtt_pack_context *spc)\n{\n   STBTT_free(spc->nodes    , spc->user_allocator_context);\n   STBTT_free(spc->pack_info, spc->user_allocator_context);\n}\n\nSTBTT_DEF void stbtt_PackSetOversampling(stbtt_pack_context *spc, unsigned int h_oversample, unsigned int v_oversample)\n{\n   STBTT_assert(h_oversample <= STBTT_MAX_OVERSAMPLE);\n   STBTT_assert(v_oversample <= STBTT_MAX_OVERSAMPLE);\n   if (h_oversample <= STBTT_MAX_OVERSAMPLE)\n      spc->h_oversample = h_oversample;\n   if (v_oversample <= STBTT_MAX_OVERSAMPLE)\n      spc->v_oversample = v_oversample;\n}\n\nSTBTT_DEF void stbtt_PackSetSkipMissingCodepoints(stbtt_pack_context *spc, int skip)\n{\n   spc->skip_missing = skip;\n}\n\n#define STBTT__OVER_MASK  (STBTT_MAX_OVERSAMPLE-1)\n\nstatic void stbtt__h_prefilter(unsigned char *pixels, int w, int h, int stride_in_bytes, unsigned int kernel_width)\n{\n   unsigned char buffer[STBTT_MAX_OVERSAMPLE];\n   int safe_w = w - kernel_width;\n   int j;\n   STBTT_memset(buffer, 0, STBTT_MAX_OVERSAMPLE); // suppress bogus warning from VS2013 -analyze\n   for (j=0; j < h; ++j) {\n      int i;\n      unsigned int total;\n      STBTT_memset(buffer, 0, kernel_width);\n\n      total = 0;\n\n      // make kernel_width a constant in common cases so compiler can optimize out the divide\n      switch (kernel_width) {\n         case 2:\n            for (i=0; i <= safe_w; ++i) {\n               total += pixels[i] - buffer[i & STBTT__OVER_MASK];\n               buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i];\n               pixels[i] = (unsigned char) (total / 2);\n            }\n            break;\n         case 3:\n            for (i=0; i <= safe_w; ++i) {\n               total += pixels[i] - buffer[i & STBTT__OVER_MASK];\n               buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i];\n               pixels[i] = (unsigned char) (total / 3);\n            }\n            break;\n         case 4:\n            for (i=0; i <= safe_w; ++i) {\n               total += pixels[i] - buffer[i & STBTT__OVER_MASK];\n               buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i];\n               pixels[i] = (unsigned char) (total / 4);\n            }\n            break;\n         case 5:\n            for (i=0; i <= safe_w; ++i) {\n               total += pixels[i] - buffer[i & STBTT__OVER_MASK];\n               buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i];\n               pixels[i] = (unsigned char) (total / 5);\n            }\n            break;\n         default:\n            for (i=0; i <= safe_w; ++i) {\n               total += pixels[i] - buffer[i & STBTT__OVER_MASK];\n               buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i];\n               pixels[i] = (unsigned char) (total / kernel_width);\n            }\n            break;\n      }\n\n      for (; i < w; ++i) {\n         STBTT_assert(pixels[i] == 0);\n         total -= buffer[i & STBTT__OVER_MASK];\n         pixels[i] = (unsigned char) (total / kernel_width);\n      }\n\n      pixels += stride_in_bytes;\n   }\n}\n\nstatic void stbtt__v_prefilter(unsigned char *pixels, int w, int h, int stride_in_bytes, unsigned int kernel_width)\n{\n   unsigned char buffer[STBTT_MAX_OVERSAMPLE];\n   int safe_h = h - kernel_width;\n   int j;\n   STBTT_memset(buffer, 0, STBTT_MAX_OVERSAMPLE); // suppress bogus warning from VS2013 -analyze\n   for (j=0; j < w; ++j) {\n      int i;\n      unsigned int total;\n      STBTT_memset(buffer, 0, kernel_width);\n\n      total = 0;\n\n      // make kernel_width a constant in common cases so compiler can optimize out the divide\n      switch (kernel_width) {\n         case 2:\n            for (i=0; i <= safe_h; ++i) {\n               total += pixels[i*stride_in_bytes] - buffer[i & STBTT__OVER_MASK];\n               buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i*stride_in_bytes];\n               pixels[i*stride_in_bytes] = (unsigned char) (total / 2);\n            }\n            break;\n         case 3:\n            for (i=0; i <= safe_h; ++i) {\n               total += pixels[i*stride_in_bytes] - buffer[i & STBTT__OVER_MASK];\n               buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i*stride_in_bytes];\n               pixels[i*stride_in_bytes] = (unsigned char) (total / 3);\n            }\n            break;\n         case 4:\n            for (i=0; i <= safe_h; ++i) {\n               total += pixels[i*stride_in_bytes] - buffer[i & STBTT__OVER_MASK];\n               buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i*stride_in_bytes];\n               pixels[i*stride_in_bytes] = (unsigned char) (total / 4);\n            }\n            break;\n         case 5:\n            for (i=0; i <= safe_h; ++i) {\n               total += pixels[i*stride_in_bytes] - buffer[i & STBTT__OVER_MASK];\n               buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i*stride_in_bytes];\n               pixels[i*stride_in_bytes] = (unsigned char) (total / 5);\n            }\n            break;\n         default:\n            for (i=0; i <= safe_h; ++i) {\n               total += pixels[i*stride_in_bytes] - buffer[i & STBTT__OVER_MASK];\n               buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i*stride_in_bytes];\n               pixels[i*stride_in_bytes] = (unsigned char) (total / kernel_width);\n            }\n            break;\n      }\n\n      for (; i < h; ++i) {\n         STBTT_assert(pixels[i*stride_in_bytes] == 0);\n         total -= buffer[i & STBTT__OVER_MASK];\n         pixels[i*stride_in_bytes] = (unsigned char) (total / kernel_width);\n      }\n\n      pixels += 1;\n   }\n}\n\nstatic float stbtt__oversample_shift(int oversample)\n{\n   if (!oversample)\n      return 0.0f;\n\n   // The prefilter is a box filter of width \"oversample\",\n   // which shifts phase by (oversample - 1)/2 pixels in\n   // oversampled space. We want to shift in the opposite\n   // direction to counter this.\n   return (float)-(oversample - 1) / (2.0f * (float)oversample);\n}\n\n// rects array must be big enough to accommodate all characters in the given ranges\nSTBTT_DEF int stbtt_PackFontRangesGatherRects(stbtt_pack_context *spc, const stbtt_fontinfo *info, stbtt_pack_range *ranges, int num_ranges, stbrp_rect *rects)\n{\n   int i,j,k;\n   int missing_glyph_added = 0;\n\n   k=0;\n   for (i=0; i < num_ranges; ++i) {\n      float fh = ranges[i].font_size;\n      float scale = fh > 0 ? stbtt_ScaleForPixelHeight(info, fh) : stbtt_ScaleForMappingEmToPixels(info, -fh);\n      ranges[i].h_oversample = (unsigned char) spc->h_oversample;\n      ranges[i].v_oversample = (unsigned char) spc->v_oversample;\n      for (j=0; j < ranges[i].num_chars; ++j) {\n         int x0,y0,x1,y1;\n         int codepoint = ranges[i].array_of_unicode_codepoints == NULL ? ranges[i].first_unicode_codepoint_in_range + j : ranges[i].array_of_unicode_codepoints[j];\n         int glyph = stbtt_FindGlyphIndex(info, codepoint);\n         if (glyph == 0 && (spc->skip_missing || missing_glyph_added)) {\n            rects[k].w = rects[k].h = 0;\n         } else {\n            stbtt_GetGlyphBitmapBoxSubpixel(info,glyph,\n                                            scale * spc->h_oversample,\n                                            scale * spc->v_oversample,\n                                            0,0,\n                                            &x0,&y0,&x1,&y1);\n            rects[k].w = (stbrp_coord) (x1-x0 + spc->padding + spc->h_oversample-1);\n            rects[k].h = (stbrp_coord) (y1-y0 + spc->padding + spc->v_oversample-1);\n            if (glyph == 0)\n               missing_glyph_added = 1;\n         }\n         ++k;\n      }\n   }\n\n   return k;\n}\n\nSTBTT_DEF void stbtt_MakeGlyphBitmapSubpixelPrefilter(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int prefilter_x, int prefilter_y, float *sub_x, float *sub_y, int glyph)\n{\n   stbtt_MakeGlyphBitmapSubpixel(info,\n                                 output,\n                                 out_w - (prefilter_x - 1),\n                                 out_h - (prefilter_y - 1),\n                                 out_stride,\n                                 scale_x,\n                                 scale_y,\n                                 shift_x,\n                                 shift_y,\n                                 glyph);\n\n   if (prefilter_x > 1)\n      stbtt__h_prefilter(output, out_w, out_h, out_stride, prefilter_x);\n\n   if (prefilter_y > 1)\n      stbtt__v_prefilter(output, out_w, out_h, out_stride, prefilter_y);\n\n   *sub_x = stbtt__oversample_shift(prefilter_x);\n   *sub_y = stbtt__oversample_shift(prefilter_y);\n}\n\n// rects array must be big enough to accommodate all characters in the given ranges\nSTBTT_DEF int stbtt_PackFontRangesRenderIntoRects(stbtt_pack_context *spc, const stbtt_fontinfo *info, stbtt_pack_range *ranges, int num_ranges, stbrp_rect *rects)\n{\n   int i,j,k, missing_glyph = -1, return_value = 1;\n\n   // save current values\n   int old_h_over = spc->h_oversample;\n   int old_v_over = spc->v_oversample;\n\n   k = 0;\n   for (i=0; i < num_ranges; ++i) {\n      float fh = ranges[i].font_size;\n      float scale = fh > 0 ? stbtt_ScaleForPixelHeight(info, fh) : stbtt_ScaleForMappingEmToPixels(info, -fh);\n      float recip_h,recip_v,sub_x,sub_y;\n      spc->h_oversample = ranges[i].h_oversample;\n      spc->v_oversample = ranges[i].v_oversample;\n      recip_h = 1.0f / spc->h_oversample;\n      recip_v = 1.0f / spc->v_oversample;\n      sub_x = stbtt__oversample_shift(spc->h_oversample);\n      sub_y = stbtt__oversample_shift(spc->v_oversample);\n      for (j=0; j < ranges[i].num_chars; ++j) {\n         stbrp_rect *r = &rects[k];\n         if (r->was_packed && r->w != 0 && r->h != 0) {\n            stbtt_packedchar *bc = &ranges[i].chardata_for_range[j];\n            int advance, lsb, x0,y0,x1,y1;\n            int codepoint = ranges[i].array_of_unicode_codepoints == NULL ? ranges[i].first_unicode_codepoint_in_range + j : ranges[i].array_of_unicode_codepoints[j];\n            int glyph = stbtt_FindGlyphIndex(info, codepoint);\n            stbrp_coord pad = (stbrp_coord) spc->padding;\n\n            // pad on left and top\n            r->x += pad;\n            r->y += pad;\n            r->w -= pad;\n            r->h -= pad;\n            stbtt_GetGlyphHMetrics(info, glyph, &advance, &lsb);\n            stbtt_GetGlyphBitmapBox(info, glyph,\n                                    scale * spc->h_oversample,\n                                    scale * spc->v_oversample,\n                                    &x0,&y0,&x1,&y1);\n            stbtt_MakeGlyphBitmapSubpixel(info,\n                                          spc->pixels + r->x + r->y*spc->stride_in_bytes,\n                                          r->w - spc->h_oversample+1,\n                                          r->h - spc->v_oversample+1,\n                                          spc->stride_in_bytes,\n                                          scale * spc->h_oversample,\n                                          scale * spc->v_oversample,\n                                          0,0,\n                                          glyph);\n\n            if (spc->h_oversample > 1)\n               stbtt__h_prefilter(spc->pixels + r->x + r->y*spc->stride_in_bytes,\n                                  r->w, r->h, spc->stride_in_bytes,\n                                  spc->h_oversample);\n\n            if (spc->v_oversample > 1)\n               stbtt__v_prefilter(spc->pixels + r->x + r->y*spc->stride_in_bytes,\n                                  r->w, r->h, spc->stride_in_bytes,\n                                  spc->v_oversample);\n\n            bc->x0       = (stbtt_int16)  r->x;\n            bc->y0       = (stbtt_int16)  r->y;\n            bc->x1       = (stbtt_int16) (r->x + r->w);\n            bc->y1       = (stbtt_int16) (r->y + r->h);\n            bc->xadvance =                scale * advance;\n            bc->xoff     =       (float)  x0 * recip_h + sub_x;\n            bc->yoff     =       (float)  y0 * recip_v + sub_y;\n            bc->xoff2    =                (x0 + r->w) * recip_h + sub_x;\n            bc->yoff2    =                (y0 + r->h) * recip_v + sub_y;\n\n            if (glyph == 0)\n               missing_glyph = j;\n         } else if (spc->skip_missing) {\n            return_value = 0;\n         } else if (r->was_packed && r->w == 0 && r->h == 0 && missing_glyph >= 0) {\n            ranges[i].chardata_for_range[j] = ranges[i].chardata_for_range[missing_glyph];\n         } else {\n            return_value = 0; // if any fail, report failure\n         }\n\n         ++k;\n      }\n   }\n\n   // restore original values\n   spc->h_oversample = old_h_over;\n   spc->v_oversample = old_v_over;\n\n   return return_value;\n}\n\nSTBTT_DEF void stbtt_PackFontRangesPackRects(stbtt_pack_context *spc, stbrp_rect *rects, int num_rects)\n{\n   stbrp_pack_rects((stbrp_context *) spc->pack_info, rects, num_rects);\n}\n\nSTBTT_DEF int stbtt_PackFontRanges(stbtt_pack_context *spc, const unsigned char *fontdata, int font_index, stbtt_pack_range *ranges, int num_ranges)\n{\n   stbtt_fontinfo info;\n   int i, j, n, return_value; // [DEAR IMGUI] removed = 1;\n   //stbrp_context *context = (stbrp_context *) spc->pack_info;\n   stbrp_rect    *rects;\n\n   // flag all characters as NOT packed\n   for (i=0; i < num_ranges; ++i)\n      for (j=0; j < ranges[i].num_chars; ++j)\n         ranges[i].chardata_for_range[j].x0 =\n         ranges[i].chardata_for_range[j].y0 =\n         ranges[i].chardata_for_range[j].x1 =\n         ranges[i].chardata_for_range[j].y1 = 0;\n\n   n = 0;\n   for (i=0; i < num_ranges; ++i)\n      n += ranges[i].num_chars;\n\n   rects = (stbrp_rect *) STBTT_malloc(sizeof(*rects) * n, spc->user_allocator_context);\n   if (rects == NULL)\n      return 0;\n\n   info.userdata = spc->user_allocator_context;\n   stbtt_InitFont(&info, fontdata, stbtt_GetFontOffsetForIndex(fontdata,font_index));\n\n   n = stbtt_PackFontRangesGatherRects(spc, &info, ranges, num_ranges, rects);\n\n   stbtt_PackFontRangesPackRects(spc, rects, n);\n\n   return_value = stbtt_PackFontRangesRenderIntoRects(spc, &info, ranges, num_ranges, rects);\n\n   STBTT_free(rects, spc->user_allocator_context);\n   return return_value;\n}\n\nSTBTT_DEF int stbtt_PackFontRange(stbtt_pack_context *spc, const unsigned char *fontdata, int font_index, float font_size,\n            int first_unicode_codepoint_in_range, int num_chars_in_range, stbtt_packedchar *chardata_for_range)\n{\n   stbtt_pack_range range;\n   range.first_unicode_codepoint_in_range = first_unicode_codepoint_in_range;\n   range.array_of_unicode_codepoints = NULL;\n   range.num_chars                   = num_chars_in_range;\n   range.chardata_for_range          = chardata_for_range;\n   range.font_size                   = font_size;\n   return stbtt_PackFontRanges(spc, fontdata, font_index, &range, 1);\n}\n\nSTBTT_DEF void stbtt_GetScaledFontVMetrics(const unsigned char *fontdata, int index, float size, float *ascent, float *descent, float *lineGap)\n{\n   int i_ascent, i_descent, i_lineGap;\n   float scale;\n   stbtt_fontinfo info;\n   stbtt_InitFont(&info, fontdata, stbtt_GetFontOffsetForIndex(fontdata, index));\n   scale = size > 0 ? stbtt_ScaleForPixelHeight(&info, size) : stbtt_ScaleForMappingEmToPixels(&info, -size);\n   stbtt_GetFontVMetrics(&info, &i_ascent, &i_descent, &i_lineGap);\n   *ascent  = (float) i_ascent  * scale;\n   *descent = (float) i_descent * scale;\n   *lineGap = (float) i_lineGap * scale;\n}\n\nSTBTT_DEF void stbtt_GetPackedQuad(const stbtt_packedchar *chardata, int pw, int ph, int char_index, float *xpos, float *ypos, stbtt_aligned_quad *q, int align_to_integer)\n{\n   float ipw = 1.0f / pw, iph = 1.0f / ph;\n   const stbtt_packedchar *b = chardata + char_index;\n\n   if (align_to_integer) {\n      float x = (float) STBTT_ifloor((*xpos + b->xoff) + 0.5f);\n      float y = (float) STBTT_ifloor((*ypos + b->yoff) + 0.5f);\n      q->x0 = x;\n      q->y0 = y;\n      q->x1 = x + b->xoff2 - b->xoff;\n      q->y1 = y + b->yoff2 - b->yoff;\n   } else {\n      q->x0 = *xpos + b->xoff;\n      q->y0 = *ypos + b->yoff;\n      q->x1 = *xpos + b->xoff2;\n      q->y1 = *ypos + b->yoff2;\n   }\n\n   q->s0 = b->x0 * ipw;\n   q->t0 = b->y0 * iph;\n   q->s1 = b->x1 * ipw;\n   q->t1 = b->y1 * iph;\n\n   *xpos += b->xadvance;\n}\n\n//////////////////////////////////////////////////////////////////////////////\n//\n// sdf computation\n//\n\n#define STBTT_min(a,b)  ((a) < (b) ? (a) : (b))\n#define STBTT_max(a,b)  ((a) < (b) ? (b) : (a))\n\nstatic int stbtt__ray_intersect_bezier(float orig[2], float ray[2], float q0[2], float q1[2], float q2[2], float hits[2][2])\n{\n   float q0perp = q0[1]*ray[0] - q0[0]*ray[1];\n   float q1perp = q1[1]*ray[0] - q1[0]*ray[1];\n   float q2perp = q2[1]*ray[0] - q2[0]*ray[1];\n   float roperp = orig[1]*ray[0] - orig[0]*ray[1];\n\n   float a = q0perp - 2*q1perp + q2perp;\n   float b = q1perp - q0perp;\n   float c = q0perp - roperp;\n\n   float s0 = 0., s1 = 0.;\n   int num_s = 0;\n\n   if (a != 0.0) {\n      float discr = b*b - a*c;\n      if (discr > 0.0) {\n         float rcpna = -1 / a;\n         float d = (float) STBTT_sqrt(discr);\n         s0 = (b+d) * rcpna;\n         s1 = (b-d) * rcpna;\n         if (s0 >= 0.0 && s0 <= 1.0)\n            num_s = 1;\n         if (d > 0.0 && s1 >= 0.0 && s1 <= 1.0) {\n            if (num_s == 0) s0 = s1;\n            ++num_s;\n         }\n      }\n   } else {\n      // 2*b*s + c = 0\n      // s = -c / (2*b)\n      s0 = c / (-2 * b);\n      if (s0 >= 0.0 && s0 <= 1.0)\n         num_s = 1;\n   }\n\n   if (num_s == 0)\n      return 0;\n   else {\n      float rcp_len2 = 1 / (ray[0]*ray[0] + ray[1]*ray[1]);\n      float rayn_x = ray[0] * rcp_len2, rayn_y = ray[1] * rcp_len2;\n\n      float q0d =   q0[0]*rayn_x +   q0[1]*rayn_y;\n      float q1d =   q1[0]*rayn_x +   q1[1]*rayn_y;\n      float q2d =   q2[0]*rayn_x +   q2[1]*rayn_y;\n      float rod = orig[0]*rayn_x + orig[1]*rayn_y;\n\n      float q10d = q1d - q0d;\n      float q20d = q2d - q0d;\n      float q0rd = q0d - rod;\n\n      hits[0][0] = q0rd + s0*(2.0f - 2.0f*s0)*q10d + s0*s0*q20d;\n      hits[0][1] = a*s0+b;\n\n      if (num_s > 1) {\n         hits[1][0] = q0rd + s1*(2.0f - 2.0f*s1)*q10d + s1*s1*q20d;\n         hits[1][1] = a*s1+b;\n         return 2;\n      } else {\n         return 1;\n      }\n   }\n}\n\nstatic int equal(float *a, float *b)\n{\n   return (a[0] == b[0] && a[1] == b[1]);\n}\n\nstatic int stbtt__compute_crossings_x(float x, float y, int nverts, stbtt_vertex *verts)\n{\n   int i;\n   float orig[2], ray[2] = { 1, 0 };\n   float y_frac;\n   int winding = 0;\n\n   // make sure y never passes through a vertex of the shape\n   y_frac = (float) STBTT_fmod(y, 1.0f);\n   if (y_frac < 0.01f)\n      y += 0.01f;\n   else if (y_frac > 0.99f)\n      y -= 0.01f;\n\n   orig[0] = x;\n   orig[1] = y;\n\n   // test a ray from (-infinity,y) to (x,y)\n   for (i=0; i < nverts; ++i) {\n      if (verts[i].type == STBTT_vline) {\n         int x0 = (int) verts[i-1].x, y0 = (int) verts[i-1].y;\n         int x1 = (int) verts[i  ].x, y1 = (int) verts[i  ].y;\n         if (y > STBTT_min(y0,y1) && y < STBTT_max(y0,y1) && x > STBTT_min(x0,x1)) {\n            float x_inter = (y - y0) / (y1 - y0) * (x1-x0) + x0;\n            if (x_inter < x)\n               winding += (y0 < y1) ? 1 : -1;\n         }\n      }\n      if (verts[i].type == STBTT_vcurve) {\n         int x0 = (int) verts[i-1].x , y0 = (int) verts[i-1].y ;\n         int x1 = (int) verts[i  ].cx, y1 = (int) verts[i  ].cy;\n         int x2 = (int) verts[i  ].x , y2 = (int) verts[i  ].y ;\n         int ax = STBTT_min(x0,STBTT_min(x1,x2)), ay = STBTT_min(y0,STBTT_min(y1,y2));\n         int by = STBTT_max(y0,STBTT_max(y1,y2));\n         if (y > ay && y < by && x > ax) {\n            float q0[2],q1[2],q2[2];\n            float hits[2][2];\n            q0[0] = (float)x0;\n            q0[1] = (float)y0;\n            q1[0] = (float)x1;\n            q1[1] = (float)y1;\n            q2[0] = (float)x2;\n            q2[1] = (float)y2;\n            if (equal(q0,q1) || equal(q1,q2)) {\n               x0 = (int)verts[i-1].x;\n               y0 = (int)verts[i-1].y;\n               x1 = (int)verts[i  ].x;\n               y1 = (int)verts[i  ].y;\n               if (y > STBTT_min(y0,y1) && y < STBTT_max(y0,y1) && x > STBTT_min(x0,x1)) {\n                  float x_inter = (y - y0) / (y1 - y0) * (x1-x0) + x0;\n                  if (x_inter < x)\n                     winding += (y0 < y1) ? 1 : -1;\n               }\n            } else {\n               int num_hits = stbtt__ray_intersect_bezier(orig, ray, q0, q1, q2, hits);\n               if (num_hits >= 1)\n                  if (hits[0][0] < 0)\n                     winding += (hits[0][1] < 0 ? -1 : 1);\n               if (num_hits >= 2)\n                  if (hits[1][0] < 0)\n                     winding += (hits[1][1] < 0 ? -1 : 1);\n            }\n         }\n      }\n   }\n   return winding;\n}\n\nstatic float stbtt__cuberoot( float x )\n{\n   if (x<0)\n      return -(float) STBTT_pow(-x,1.0f/3.0f);\n   else\n      return  (float) STBTT_pow( x,1.0f/3.0f);\n}\n\n// x^3 + a*x^2 + b*x + c = 0\nstatic int stbtt__solve_cubic(float a, float b, float c, float* r)\n{\n   float s = -a / 3;\n   float p = b - a*a / 3;\n   float q = a * (2*a*a - 9*b) / 27 + c;\n   float p3 = p*p*p;\n   float d = q*q + 4*p3 / 27;\n   if (d >= 0) {\n      float z = (float) STBTT_sqrt(d);\n      float u = (-q + z) / 2;\n      float v = (-q - z) / 2;\n      u = stbtt__cuberoot(u);\n      v = stbtt__cuberoot(v);\n      r[0] = s + u + v;\n      return 1;\n   } else {\n      float u = (float) STBTT_sqrt(-p/3);\n      float v = (float) STBTT_acos(-STBTT_sqrt(-27/p3) * q / 2) / 3; // p3 must be negative, since d is negative\n      float m = (float) STBTT_cos(v);\n      float n = (float) STBTT_cos(v-3.141592/2)*1.732050808f;\n      r[0] = s + u * 2 * m;\n      r[1] = s - u * (m + n);\n      r[2] = s - u * (m - n);\n\n      //STBTT_assert( STBTT_fabs(((r[0]+a)*r[0]+b)*r[0]+c) < 0.05f);  // these asserts may not be safe at all scales, though they're in bezier t parameter units so maybe?\n      //STBTT_assert( STBTT_fabs(((r[1]+a)*r[1]+b)*r[1]+c) < 0.05f);\n      //STBTT_assert( STBTT_fabs(((r[2]+a)*r[2]+b)*r[2]+c) < 0.05f);\n      return 3;\n   }\n}\n\nSTBTT_DEF unsigned char * stbtt_GetGlyphSDF(const stbtt_fontinfo *info, float scale, int glyph, int padding, unsigned char onedge_value, float pixel_dist_scale, int *width, int *height, int *xoff, int *yoff)\n{\n   float scale_x = scale, scale_y = scale;\n   int ix0,iy0,ix1,iy1;\n   int w,h;\n   unsigned char *data;\n\n   if (scale == 0) return NULL;\n\n   stbtt_GetGlyphBitmapBoxSubpixel(info, glyph, scale, scale, 0.0f,0.0f, &ix0,&iy0,&ix1,&iy1);\n\n   // if empty, return NULL\n   if (ix0 == ix1 || iy0 == iy1)\n      return NULL;\n\n   ix0 -= padding;\n   iy0 -= padding;\n   ix1 += padding;\n   iy1 += padding;\n\n   w = (ix1 - ix0);\n   h = (iy1 - iy0);\n\n   if (width ) *width  = w;\n   if (height) *height = h;\n   if (xoff  ) *xoff   = ix0;\n   if (yoff  ) *yoff   = iy0;\n\n   // invert for y-downwards bitmaps\n   scale_y = -scale_y;\n\n   {\n      int x,y,i,j;\n      float *precompute;\n      stbtt_vertex *verts;\n      int num_verts = stbtt_GetGlyphShape(info, glyph, &verts);\n      data = (unsigned char *) STBTT_malloc(w * h, info->userdata);\n      precompute = (float *) STBTT_malloc(num_verts * sizeof(float), info->userdata);\n\n      for (i=0,j=num_verts-1; i < num_verts; j=i++) {\n         if (verts[i].type == STBTT_vline) {\n            float x0 = verts[i].x*scale_x, y0 = verts[i].y*scale_y;\n            float x1 = verts[j].x*scale_x, y1 = verts[j].y*scale_y;\n            float dist = (float) STBTT_sqrt((x1-x0)*(x1-x0) + (y1-y0)*(y1-y0));\n            precompute[i] = (dist == 0) ? 0.0f : 1.0f / dist;\n         } else if (verts[i].type == STBTT_vcurve) {\n            float x2 = verts[j].x *scale_x, y2 = verts[j].y *scale_y;\n            float x1 = verts[i].cx*scale_x, y1 = verts[i].cy*scale_y;\n            float x0 = verts[i].x *scale_x, y0 = verts[i].y *scale_y;\n            float bx = x0 - 2*x1 + x2, by = y0 - 2*y1 + y2;\n            float len2 = bx*bx + by*by;\n            if (len2 != 0.0f)\n               precompute[i] = 1.0f / (bx*bx + by*by);\n            else\n               precompute[i] = 0.0f;\n         } else\n            precompute[i] = 0.0f;\n      }\n\n      for (y=iy0; y < iy1; ++y) {\n         for (x=ix0; x < ix1; ++x) {\n            float val;\n            float min_dist = 999999.0f;\n            float sx = (float) x + 0.5f;\n            float sy = (float) y + 0.5f;\n            float x_gspace = (sx / scale_x);\n            float y_gspace = (sy / scale_y);\n\n            int winding = stbtt__compute_crossings_x(x_gspace, y_gspace, num_verts, verts); // @OPTIMIZE: this could just be a rasterization, but needs to be line vs. non-tesselated curves so a new path\n\n            for (i=0; i < num_verts; ++i) {\n               float x0 = verts[i].x*scale_x, y0 = verts[i].y*scale_y;\n\n               if (verts[i].type == STBTT_vline && precompute[i] != 0.0f) {\n                  float x1 = verts[i-1].x*scale_x, y1 = verts[i-1].y*scale_y;\n\n                  float dist,dist2 = (x0-sx)*(x0-sx) + (y0-sy)*(y0-sy);\n                  if (dist2 < min_dist*min_dist)\n                     min_dist = (float) STBTT_sqrt(dist2);\n\n                  // coarse culling against bbox\n                  //if (sx > STBTT_min(x0,x1)-min_dist && sx < STBTT_max(x0,x1)+min_dist &&\n                  //    sy > STBTT_min(y0,y1)-min_dist && sy < STBTT_max(y0,y1)+min_dist)\n                  dist = (float) STBTT_fabs((x1-x0)*(y0-sy) - (y1-y0)*(x0-sx)) * precompute[i];\n                  STBTT_assert(i != 0);\n                  if (dist < min_dist) {\n                     // check position along line\n                     // x' = x0 + t*(x1-x0), y' = y0 + t*(y1-y0)\n                     // minimize (x'-sx)*(x'-sx)+(y'-sy)*(y'-sy)\n                     float dx = x1-x0, dy = y1-y0;\n                     float px = x0-sx, py = y0-sy;\n                     // minimize (px+t*dx)^2 + (py+t*dy)^2 = px*px + 2*px*dx*t + t^2*dx*dx + py*py + 2*py*dy*t + t^2*dy*dy\n                     // derivative: 2*px*dx + 2*py*dy + (2*dx*dx+2*dy*dy)*t, set to 0 and solve\n                     float t = -(px*dx + py*dy) / (dx*dx + dy*dy);\n                     if (t >= 0.0f && t <= 1.0f)\n                        min_dist = dist;\n                  }\n               } else if (verts[i].type == STBTT_vcurve) {\n                  float x2 = verts[i-1].x *scale_x, y2 = verts[i-1].y *scale_y;\n                  float x1 = verts[i  ].cx*scale_x, y1 = verts[i  ].cy*scale_y;\n                  float box_x0 = STBTT_min(STBTT_min(x0,x1),x2);\n                  float box_y0 = STBTT_min(STBTT_min(y0,y1),y2);\n                  float box_x1 = STBTT_max(STBTT_max(x0,x1),x2);\n                  float box_y1 = STBTT_max(STBTT_max(y0,y1),y2);\n                  // coarse culling against bbox to avoid computing cubic unnecessarily\n                  if (sx > box_x0-min_dist && sx < box_x1+min_dist && sy > box_y0-min_dist && sy < box_y1+min_dist) {\n                     int num=0;\n                     float ax = x1-x0, ay = y1-y0;\n                     float bx = x0 - 2*x1 + x2, by = y0 - 2*y1 + y2;\n                     float mx = x0 - sx, my = y0 - sy;\n                     float res[3] = {0.f,0.f,0.f};\n                     float px,py,t,it,dist2;\n                     float a_inv = precompute[i];\n                     if (a_inv == 0.0) { // if a_inv is 0, it's 2nd degree so use quadratic formula\n                        float a = 3*(ax*bx + ay*by);\n                        float b = 2*(ax*ax + ay*ay) + (mx*bx+my*by);\n                        float c = mx*ax+my*ay;\n                        if (a == 0.0) { // if a is 0, it's linear\n                           if (b != 0.0) {\n                              res[num++] = -c/b;\n                           }\n                        } else {\n                           float discriminant = b*b - 4*a*c;\n                           if (discriminant < 0)\n                              num = 0;\n                           else {\n                              float root = (float) STBTT_sqrt(discriminant);\n                              res[0] = (-b - root)/(2*a);\n                              res[1] = (-b + root)/(2*a);\n                              num = 2; // don't bother distinguishing 1-solution case, as code below will still work\n                           }\n                        }\n                     } else {\n                        float b = 3*(ax*bx + ay*by) * a_inv; // could precompute this as it doesn't depend on sample point\n                        float c = (2*(ax*ax + ay*ay) + (mx*bx+my*by)) * a_inv;\n                        float d = (mx*ax+my*ay) * a_inv;\n                        num = stbtt__solve_cubic(b, c, d, res);\n                     }\n                     dist2 = (x0-sx)*(x0-sx) + (y0-sy)*(y0-sy);\n                     if (dist2 < min_dist*min_dist)\n                        min_dist = (float) STBTT_sqrt(dist2);\n\n                     if (num >= 1 && res[0] >= 0.0f && res[0] <= 1.0f) {\n                        t = res[0], it = 1.0f - t;\n                        px = it*it*x0 + 2*t*it*x1 + t*t*x2;\n                        py = it*it*y0 + 2*t*it*y1 + t*t*y2;\n                        dist2 = (px-sx)*(px-sx) + (py-sy)*(py-sy);\n                        if (dist2 < min_dist * min_dist)\n                           min_dist = (float) STBTT_sqrt(dist2);\n                     }\n                     if (num >= 2 && res[1] >= 0.0f && res[1] <= 1.0f) {\n                        t = res[1], it = 1.0f - t;\n                        px = it*it*x0 + 2*t*it*x1 + t*t*x2;\n                        py = it*it*y0 + 2*t*it*y1 + t*t*y2;\n                        dist2 = (px-sx)*(px-sx) + (py-sy)*(py-sy);\n                        if (dist2 < min_dist * min_dist)\n                           min_dist = (float) STBTT_sqrt(dist2);\n                     }\n                     if (num >= 3 && res[2] >= 0.0f && res[2] <= 1.0f) {\n                        t = res[2], it = 1.0f - t;\n                        px = it*it*x0 + 2*t*it*x1 + t*t*x2;\n                        py = it*it*y0 + 2*t*it*y1 + t*t*y2;\n                        dist2 = (px-sx)*(px-sx) + (py-sy)*(py-sy);\n                        if (dist2 < min_dist * min_dist)\n                           min_dist = (float) STBTT_sqrt(dist2);\n                     }\n                  }\n               }\n            }\n            if (winding == 0)\n               min_dist = -min_dist;  // if outside the shape, value is negative\n            val = onedge_value + pixel_dist_scale * min_dist;\n            if (val < 0)\n               val = 0;\n            else if (val > 255)\n               val = 255;\n            data[(y-iy0)*w+(x-ix0)] = (unsigned char) val;\n         }\n      }\n      STBTT_free(precompute, info->userdata);\n      STBTT_free(verts, info->userdata);\n   }\n   return data;\n}\n\nSTBTT_DEF unsigned char * stbtt_GetCodepointSDF(const stbtt_fontinfo *info, float scale, int codepoint, int padding, unsigned char onedge_value, float pixel_dist_scale, int *width, int *height, int *xoff, int *yoff)\n{\n   return stbtt_GetGlyphSDF(info, scale, stbtt_FindGlyphIndex(info, codepoint), padding, onedge_value, pixel_dist_scale, width, height, xoff, yoff);\n}\n\nSTBTT_DEF void stbtt_FreeSDF(unsigned char *bitmap, void *userdata)\n{\n   STBTT_free(bitmap, userdata);\n}\n\n//////////////////////////////////////////////////////////////////////////////\n//\n// font name matching -- recommended not to use this\n//\n\n// check if a utf8 string contains a prefix which is the utf16 string; if so return length of matching utf8 string\nstatic stbtt_int32 stbtt__CompareUTF8toUTF16_bigendian_prefix(stbtt_uint8 *s1, stbtt_int32 len1, stbtt_uint8 *s2, stbtt_int32 len2)\n{\n   stbtt_int32 i=0;\n\n   // convert utf16 to utf8 and compare the results while converting\n   while (len2) {\n      stbtt_uint16 ch = s2[0]*256 + s2[1];\n      if (ch < 0x80) {\n         if (i >= len1) return -1;\n         if (s1[i++] != ch) return -1;\n      } else if (ch < 0x800) {\n         if (i+1 >= len1) return -1;\n         if (s1[i++] != 0xc0 + (ch >> 6)) return -1;\n         if (s1[i++] != 0x80 + (ch & 0x3f)) return -1;\n      } else if (ch >= 0xd800 && ch < 0xdc00) {\n         stbtt_uint32 c;\n         stbtt_uint16 ch2 = s2[2]*256 + s2[3];\n         if (i+3 >= len1) return -1;\n         c = ((ch - 0xd800) << 10) + (ch2 - 0xdc00) + 0x10000;\n         if (s1[i++] != 0xf0 + (c >> 18)) return -1;\n         if (s1[i++] != 0x80 + ((c >> 12) & 0x3f)) return -1;\n         if (s1[i++] != 0x80 + ((c >>  6) & 0x3f)) return -1;\n         if (s1[i++] != 0x80 + ((c      ) & 0x3f)) return -1;\n         s2 += 2; // plus another 2 below\n         len2 -= 2;\n      } else if (ch >= 0xdc00 && ch < 0xe000) {\n         return -1;\n      } else {\n         if (i+2 >= len1) return -1;\n         if (s1[i++] != 0xe0 + (ch >> 12)) return -1;\n         if (s1[i++] != 0x80 + ((ch >> 6) & 0x3f)) return -1;\n         if (s1[i++] != 0x80 + ((ch     ) & 0x3f)) return -1;\n      }\n      s2 += 2;\n      len2 -= 2;\n   }\n   return i;\n}\n\nstatic int stbtt_CompareUTF8toUTF16_bigendian_internal(char *s1, int len1, char *s2, int len2)\n{\n   return len1 == stbtt__CompareUTF8toUTF16_bigendian_prefix((stbtt_uint8*) s1, len1, (stbtt_uint8*) s2, len2);\n}\n\n// returns results in whatever encoding you request... but note that 2-byte encodings\n// will be BIG-ENDIAN... use stbtt_CompareUTF8toUTF16_bigendian() to compare\nSTBTT_DEF const char *stbtt_GetFontNameString(const stbtt_fontinfo *font, int *length, int platformID, int encodingID, int languageID, int nameID)\n{\n   stbtt_int32 i,count,stringOffset;\n   stbtt_uint8 *fc = font->data;\n   stbtt_uint32 offset = font->fontstart;\n   stbtt_uint32 nm = stbtt__find_table(fc, offset, \"name\");\n   if (!nm) return NULL;\n\n   count = ttUSHORT(fc+nm+2);\n   stringOffset = nm + ttUSHORT(fc+nm+4);\n   for (i=0; i < count; ++i) {\n      stbtt_uint32 loc = nm + 6 + 12 * i;\n      if (platformID == ttUSHORT(fc+loc+0) && encodingID == ttUSHORT(fc+loc+2)\n          && languageID == ttUSHORT(fc+loc+4) && nameID == ttUSHORT(fc+loc+6)) {\n         *length = ttUSHORT(fc+loc+8);\n         return (const char *) (fc+stringOffset+ttUSHORT(fc+loc+10));\n      }\n   }\n   return NULL;\n}\n\nstatic int stbtt__matchpair(stbtt_uint8 *fc, stbtt_uint32 nm, stbtt_uint8 *name, stbtt_int32 nlen, stbtt_int32 target_id, stbtt_int32 next_id)\n{\n   stbtt_int32 i;\n   stbtt_int32 count = ttUSHORT(fc+nm+2);\n   stbtt_int32 stringOffset = nm + ttUSHORT(fc+nm+4);\n\n   for (i=0; i < count; ++i) {\n      stbtt_uint32 loc = nm + 6 + 12 * i;\n      stbtt_int32 id = ttUSHORT(fc+loc+6);\n      if (id == target_id) {\n         // find the encoding\n         stbtt_int32 platform = ttUSHORT(fc+loc+0), encoding = ttUSHORT(fc+loc+2), language = ttUSHORT(fc+loc+4);\n\n         // is this a Unicode encoding?\n         if (platform == 0 || (platform == 3 && encoding == 1) || (platform == 3 && encoding == 10)) {\n            stbtt_int32 slen = ttUSHORT(fc+loc+8);\n            stbtt_int32 off = ttUSHORT(fc+loc+10);\n\n            // check if there's a prefix match\n            stbtt_int32 matchlen = stbtt__CompareUTF8toUTF16_bigendian_prefix(name, nlen, fc+stringOffset+off,slen);\n            if (matchlen >= 0) {\n               // check for target_id+1 immediately following, with same encoding & language\n               if (i+1 < count && ttUSHORT(fc+loc+12+6) == next_id && ttUSHORT(fc+loc+12) == platform && ttUSHORT(fc+loc+12+2) == encoding && ttUSHORT(fc+loc+12+4) == language) {\n                  slen = ttUSHORT(fc+loc+12+8);\n                  off = ttUSHORT(fc+loc+12+10);\n                  if (slen == 0) {\n                     if (matchlen == nlen)\n                        return 1;\n                  } else if (matchlen < nlen && name[matchlen] == ' ') {\n                     ++matchlen;\n                     if (stbtt_CompareUTF8toUTF16_bigendian_internal((char*) (name+matchlen), nlen-matchlen, (char*)(fc+stringOffset+off),slen))\n                        return 1;\n                  }\n               } else {\n                  // if nothing immediately following\n                  if (matchlen == nlen)\n                     return 1;\n               }\n            }\n         }\n\n         // @TODO handle other encodings\n      }\n   }\n   return 0;\n}\n\nstatic int stbtt__matches(stbtt_uint8 *fc, stbtt_uint32 offset, stbtt_uint8 *name, stbtt_int32 flags)\n{\n   stbtt_int32 nlen = (stbtt_int32) STBTT_strlen((char *) name);\n   stbtt_uint32 nm,hd;\n   if (!stbtt__isfont(fc+offset)) return 0;\n\n   // check italics/bold/underline flags in macStyle...\n   if (flags) {\n      hd = stbtt__find_table(fc, offset, \"head\");\n      if ((ttUSHORT(fc+hd+44) & 7) != (flags & 7)) return 0;\n   }\n\n   nm = stbtt__find_table(fc, offset, \"name\");\n   if (!nm) return 0;\n\n   if (flags) {\n      // if we checked the macStyle flags, then just check the family and ignore the subfamily\n      if (stbtt__matchpair(fc, nm, name, nlen, 16, -1))  return 1;\n      if (stbtt__matchpair(fc, nm, name, nlen,  1, -1))  return 1;\n      if (stbtt__matchpair(fc, nm, name, nlen,  3, -1))  return 1;\n   } else {\n      if (stbtt__matchpair(fc, nm, name, nlen, 16, 17))  return 1;\n      if (stbtt__matchpair(fc, nm, name, nlen,  1,  2))  return 1;\n      if (stbtt__matchpair(fc, nm, name, nlen,  3, -1))  return 1;\n   }\n\n   return 0;\n}\n\nstatic int stbtt_FindMatchingFont_internal(unsigned char *font_collection, char *name_utf8, stbtt_int32 flags)\n{\n   stbtt_int32 i;\n   for (i=0;;++i) {\n      stbtt_int32 off = stbtt_GetFontOffsetForIndex(font_collection, i);\n      if (off < 0) return off;\n      if (stbtt__matches((stbtt_uint8 *) font_collection, off, (stbtt_uint8*) name_utf8, flags))\n         return off;\n   }\n}\n\n#if defined(__GNUC__) || defined(__clang__)\n#pragma GCC diagnostic push\n#pragma GCC diagnostic ignored \"-Wcast-qual\"\n#endif\n\nSTBTT_DEF int stbtt_BakeFontBitmap(const unsigned char *data, int offset,\n                                float pixel_height, unsigned char *pixels, int pw, int ph,\n                                int first_char, int num_chars, stbtt_bakedchar *chardata)\n{\n   return stbtt_BakeFontBitmap_internal((unsigned char *) data, offset, pixel_height, pixels, pw, ph, first_char, num_chars, chardata);\n}\n\nSTBTT_DEF int stbtt_GetFontOffsetForIndex(const unsigned char *data, int index)\n{\n   return stbtt_GetFontOffsetForIndex_internal((unsigned char *) data, index);\n}\n\nSTBTT_DEF int stbtt_GetNumberOfFonts(const unsigned char *data)\n{\n   return stbtt_GetNumberOfFonts_internal((unsigned char *) data);\n}\n\nSTBTT_DEF int stbtt_InitFont(stbtt_fontinfo *info, const unsigned char *data, int offset)\n{\n   return stbtt_InitFont_internal(info, (unsigned char *) data, offset);\n}\n\nSTBTT_DEF int stbtt_FindMatchingFont(const unsigned char *fontdata, const char *name, int flags)\n{\n   return stbtt_FindMatchingFont_internal((unsigned char *) fontdata, (char *) name, flags);\n}\n\nSTBTT_DEF int stbtt_CompareUTF8toUTF16_bigendian(const char *s1, int len1, const char *s2, int len2)\n{\n   return stbtt_CompareUTF8toUTF16_bigendian_internal((char *) s1, len1, (char *) s2, len2);\n}\n\n#if defined(__GNUC__) || defined(__clang__)\n#pragma GCC diagnostic pop\n#endif\n\n#endif // STB_TRUETYPE_IMPLEMENTATION\n\n\n// FULL VERSION HISTORY\n//\n//   1.25 (2021-07-11) many fixes\n//   1.24 (2020-02-05) fix warning\n//   1.23 (2020-02-02) query SVG data for glyphs; query whole kerning table (but only kern not GPOS)\n//   1.22 (2019-08-11) minimize missing-glyph duplication; fix kerning if both 'GPOS' and 'kern' are defined\n//   1.21 (2019-02-25) fix warning\n//   1.20 (2019-02-07) PackFontRange skips missing codepoints; GetScaleFontVMetrics()\n//   1.19 (2018-02-11) OpenType GPOS kerning (horizontal only), STBTT_fmod\n//   1.18 (2018-01-29) add missing function\n//   1.17 (2017-07-23) make more arguments const; doc fix\n//   1.16 (2017-07-12) SDF support\n//   1.15 (2017-03-03) make more arguments const\n//   1.14 (2017-01-16) num-fonts-in-TTC function\n//   1.13 (2017-01-02) support OpenType fonts, certain Apple fonts\n//   1.12 (2016-10-25) suppress warnings about casting away const with -Wcast-qual\n//   1.11 (2016-04-02) fix unused-variable warning\n//   1.10 (2016-04-02) allow user-defined fabs() replacement\n//                     fix memory leak if fontsize=0.0\n//                     fix warning from duplicate typedef\n//   1.09 (2016-01-16) warning fix; avoid crash on outofmem; use alloc userdata for PackFontRanges\n//   1.08 (2015-09-13) document stbtt_Rasterize(); fixes for vertical & horizontal edges\n//   1.07 (2015-08-01) allow PackFontRanges to accept arrays of sparse codepoints;\n//                     allow PackFontRanges to pack and render in separate phases;\n//                     fix stbtt_GetFontOFfsetForIndex (never worked for non-0 input?);\n//                     fixed an assert() bug in the new rasterizer\n//                     replace assert() with STBTT_assert() in new rasterizer\n//   1.06 (2015-07-14) performance improvements (~35% faster on x86 and x64 on test machine)\n//                     also more precise AA rasterizer, except if shapes overlap\n//                     remove need for STBTT_sort\n//   1.05 (2015-04-15) fix misplaced definitions for STBTT_STATIC\n//   1.04 (2015-04-15) typo in example\n//   1.03 (2015-04-12) STBTT_STATIC, fix memory leak in new packing, various fixes\n//   1.02 (2014-12-10) fix various warnings & compile issues w/ stb_rect_pack, C++\n//   1.01 (2014-12-08) fix subpixel position when oversampling to exactly match\n//                        non-oversampled; STBTT_POINT_SIZE for packed case only\n//   1.00 (2014-12-06) add new PackBegin etc. API, w/ support for oversampling\n//   0.99 (2014-09-18) fix multiple bugs with subpixel rendering (ryg)\n//   0.9  (2014-08-07) support certain mac/iOS fonts without an MS platformID\n//   0.8b (2014-07-07) fix a warning\n//   0.8  (2014-05-25) fix a few more warnings\n//   0.7  (2013-09-25) bugfix: subpixel glyph bug fixed in 0.5 had come back\n//   0.6c (2012-07-24) improve documentation\n//   0.6b (2012-07-20) fix a few more warnings\n//   0.6  (2012-07-17) fix warnings; added stbtt_ScaleForMappingEmToPixels,\n//                        stbtt_GetFontBoundingBox, stbtt_IsGlyphEmpty\n//   0.5  (2011-12-09) bugfixes:\n//                        subpixel glyph renderer computed wrong bounding box\n//                        first vertex of shape can be off-curve (FreeSans)\n//   0.4b (2011-12-03) fixed an error in the font baking example\n//   0.4  (2011-12-01) kerning, subpixel rendering (tor)\n//                    bugfixes for:\n//                        codepoint-to-glyph conversion using table fmt=12\n//                        codepoint-to-glyph conversion using table fmt=4\n//                        stbtt_GetBakedQuad with non-square texture (Zer)\n//                    updated Hello World! sample to use kerning and subpixel\n//                    fixed some warnings\n//   0.3  (2009-06-24) cmap fmt=12, compound shapes (MM)\n//                    userdata, malloc-from-userdata, non-zero fill (stb)\n//   0.2  (2009-03-11) Fix unsigned/signed char warnings\n//   0.1  (2009-03-09) First public release\n//\n\n/*\n------------------------------------------------------------------------------\nThis software is available under 2 licenses -- choose whichever you prefer.\n------------------------------------------------------------------------------\nALTERNATIVE A - MIT License\nCopyright (c) 2017 Sean Barrett\nPermission is hereby granted, free of charge, to any person obtaining a copy of\nthis software and associated documentation files (the \"Software\"), to deal in\nthe Software without restriction, including without limitation the rights to\nuse, copy, modify, merge, publish, distribute, sublicense, and/or sell copies\nof the Software, and to permit persons to whom the Software is furnished to do\nso, subject to the following conditions:\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n------------------------------------------------------------------------------\nALTERNATIVE B - Public Domain (www.unlicense.org)\nThis is free and unencumbered software released into the public domain.\nAnyone is free to copy, modify, publish, use, compile, sell, or distribute this\nsoftware, either in source code form or as a compiled binary, for any purpose,\ncommercial or non-commercial, and by any means.\nIn jurisdictions that recognize copyright laws, the author or authors of this\nsoftware dedicate any and all copyright interest in the software to the public\ndomain. We make this dedication for the benefit of the public at large and to\nthe detriment of our heirs and successors. We intend this dedication to be an\novert act of relinquishment in perpetuity of all present and future rights to\nthis software under copyright law.\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN\nACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION\nWITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n------------------------------------------------------------------------------\n*/\n"
  },
  {
    "path": "src/DesktopPlusUI/imgui_win32_dx11_openvr/PixelShaderImGui.hlsl",
    "content": "struct PS_INPUT\n{\n\tfloat4 pos : SV_POSITION;\n\tfloat4 col : COLOR0;\n\tfloat2 uv  : TEXCOORD0;\n};\n\nsampler sampler0;\nTexture2D texture0;\n\nfloat4 PS(PS_INPUT input) : SV_Target\n{\n\treturn input.col * texture0.Sample(sampler0, input.uv);\n}"
  },
  {
    "path": "src/DesktopPlusUI/imgui_win32_dx11_openvr/VertexShaderImGui.hlsl",
    "content": "cbuffer vertexBuffer : register(b0)\n{\n\tfloat4x4 ProjectionMatrix;\n};\n\nstruct VS_INPUT\n{\n\tfloat2 pos : POSITION;\n\tfloat4 col : COLOR0;\n\tfloat2 uv  : TEXCOORD0;\n};\n\nstruct PS_INPUT\n{\n\tfloat4 pos : SV_POSITION;\n\tfloat4 col : COLOR0;\n\tfloat2 uv  : TEXCOORD0;\n};\n\nPS_INPUT VS(VS_INPUT input)\n{\n\tPS_INPUT output; \n\toutput.pos = mul(ProjectionMatrix, float4(input.pos.xy, 0.f, 1.f));\n\toutput.col = input.col;\n\toutput.uv = input.uv;\n\treturn output;\n}"
  },
  {
    "path": "src/DesktopPlusUI/imgui_win32_dx11_openvr/imgui_impl_dx11_openvr.cpp",
    "content": "// Desktop+UI: Modified for OpenVR compatibility\n\n// dear imgui: Renderer Backend for DirectX11\n// This needs to be used along with a Platform Backend (e.g. Win32)\n\n// Implemented features:\n//  [X] Renderer: User texture binding. Use 'ID3D11ShaderResourceView*' as ImTextureID. Read the FAQ about ImTextureID!\n//  [X] Renderer: Large meshes support (64k+ vertices) even with 16-bit indices (ImGuiBackendFlags_RendererHasVtxOffset).\n//  [X] Renderer: Expose selected render state for draw callbacks to use. Access in '(ImGui_ImplXXXX_RenderState*)GetPlatformIO().Renderer_RenderState'.\n\n// You can use unmodified imgui_impl_* files in your project. See examples/ folder for examples of using this.\n// Prefer including the entire imgui/ repository into your project (either as a copy or as a submodule), and only build the backends you need.\n// Learn about Dear ImGui:\n// - FAQ                  https://dearimgui.com/faq\n// - Getting Started      https://dearimgui.com/getting-started\n// - Documentation        https://dearimgui.com/docs (same as your local docs/ folder).\n// - Introduction, links and more at the top of imgui.cpp\n\n// CHANGELOG\n// (minor and older changes stripped away, please see git history for details)\n//  2025-01-06: DirectX11: Expose VertexConstantBuffer in ImGui_ImplDX11_RenderState. Reset projection matrix in ImDrawCallback_ResetRenderState handler.\n//  2024-10-07: DirectX11: Changed default texture sampler to Clamp instead of Repeat/Wrap.\n//  2024-10-07: DirectX11: Expose selected render state in ImGui_ImplDX11_RenderState, which you can access in 'void* platform_io.Renderer_RenderState' during draw callbacks.\n//  2022-10-11: Using 'nullptr' instead of 'NULL' as per our switch to C++11.\n//  2021-06-29: Reorganized backend to pull data from a single structure to facilitate usage with multiple-contexts (all g_XXXX access changed to bd->XXXX).\n//  2021-05-19: DirectX11: Replaced direct access to ImDrawCmd::TextureId with a call to ImDrawCmd::GetTexID(). (will become a requirement)\n//  2021-02-18: DirectX11: Change blending equation to preserve alpha in output buffer.\n//  2019-08-01: DirectX11: Fixed code querying the Geometry Shader state (would generally error with Debug layer enabled).\n//  2019-07-21: DirectX11: Backup, clear and restore Geometry Shader is any is bound when calling ImGui_ImplDX11_RenderDrawData. Clearing Hull/Domain/Compute shaders without backup/restore.\n//  2019-05-29: DirectX11: Added support for large mesh (64K+ vertices), enable ImGuiBackendFlags_RendererHasVtxOffset flag.\n//  2019-04-30: DirectX11: Added support for special ImDrawCallback_ResetRenderState callback to reset render state.\n//  2018-12-03: Misc: Added #pragma comment statement to automatically link with d3dcompiler.lib when using D3DCompile().\n//  2018-11-30: Misc: Setting up io.BackendRendererName so it can be displayed in the About Window.\n//  2018-08-01: DirectX11: Querying for IDXGIFactory instead of IDXGIFactory1 to increase compatibility.\n//  2018-07-13: DirectX11: Fixed unreleased resources in Init and Shutdown functions.\n//  2018-06-08: Misc: Extracted imgui_impl_dx11.cpp/.h away from the old combined DX11+Win32 example.\n//  2018-06-08: DirectX11: Use draw_data->DisplayPos and draw_data->DisplaySize to setup projection matrix and clipping rectangle.\n//  2018-02-16: Misc: Obsoleted the io.RenderDrawListsFn callback and exposed ImGui_ImplDX11_RenderDrawData() in the .h file so you can call it yourself.\n//  2018-02-06: Misc: Removed call to ImGui::Shutdown() which is not available from 1.60 WIP, user needs to call CreateContext/DestroyContext themselves.\n//  2016-05-07: DirectX11: Disabling depth-write.\n\n#include \"imgui.h\"\n#ifndef IMGUI_DISABLE\n#include \"imgui_impl_dx11_openvr.h\"\n\n// DirectX\n#include <stdio.h>\n#include <d3d11.h>\n#include \"VertexShaderImGui.h\"\n#include \"PixelShaderImGui.h\"\n\n// DirectX11 data\nstruct ImGui_ImplDX11_Data\n{\n    ID3D11Device*               pd3dDevice;\n    ID3D11DeviceContext*        pd3dDeviceContext;\n    IDXGIFactory*               pFactory;\n    ID3D11Buffer*               pVB;\n    ID3D11Buffer*               pIB;\n    ID3D11VertexShader*         pVertexShader;\n    ID3D11InputLayout*          pInputLayout;\n    ID3D11Buffer*               pVertexConstantBuffer;\n    ID3D11PixelShader*          pPixelShader;\n    ID3D11SamplerState*         pFontSampler;\n    ID3D11ShaderResourceView*   pFontTextureView;\n    ID3D11RasterizerState*      pRasterizerState;\n    ID3D11BlendState*           pBlendState;\n    ID3D11DepthStencilState*    pDepthStencilState;\n    int                         VertexBufferSize;\n    int                         IndexBufferSize;\n\n    ImGui_ImplDX11_Data()       { memset((void*)this, 0, sizeof(*this)); VertexBufferSize = 5000; IndexBufferSize = 10000; }\n};\n\nstruct VERTEX_CONSTANT_BUFFER_DX11\n{\n    float   mvp[4][4];\n};\n\n// Backend data stored in io.BackendRendererUserData to allow support for multiple Dear ImGui contexts\n// It is STRONGLY preferred that you use docking branch with multi-viewports (== single Dear ImGui context + multiple windows) instead of multiple Dear ImGui contexts.\nstatic ImGui_ImplDX11_Data* ImGui_ImplDX11_GetBackendData()\n{\n    return ImGui::GetCurrentContext() ? (ImGui_ImplDX11_Data*)ImGui::GetIO().BackendRendererUserData : nullptr;\n}\n\n// Functions\nstatic void ImGui_ImplDX11_SetupRenderState(ImDrawData* draw_data, ID3D11DeviceContext* device_ctx)\n{\n    ImGui_ImplDX11_Data* bd = ImGui_ImplDX11_GetBackendData();\n\n    // Setup viewport\n    D3D11_VIEWPORT vp = {};\n    vp.Width = draw_data->DisplaySize.x;\n    vp.Height = draw_data->DisplaySize.y;\n    vp.MinDepth = 0.0f;\n    vp.MaxDepth = 1.0f;\n    vp.TopLeftX = vp.TopLeftY = 0;\n    device_ctx->RSSetViewports(1, &vp);\n\n    // Setup orthographic projection matrix into our constant buffer\n    // Our visible imgui space lies from draw_data->DisplayPos (top left) to draw_data->DisplayPos+data_data->DisplaySize (bottom right). DisplayPos is (0,0) for single viewport apps.\n    D3D11_MAPPED_SUBRESOURCE mapped_resource;\n    if (device_ctx->Map(bd->pVertexConstantBuffer, 0, D3D11_MAP_WRITE_DISCARD, 0, &mapped_resource) == S_OK)\n    {\n        VERTEX_CONSTANT_BUFFER_DX11* constant_buffer = (VERTEX_CONSTANT_BUFFER_DX11*)mapped_resource.pData;\n        float L = draw_data->DisplayPos.x;\n        float R = draw_data->DisplayPos.x + draw_data->DisplaySize.x;\n        float T = draw_data->DisplayPos.y;\n        float B = draw_data->DisplayPos.y + draw_data->DisplaySize.y;\n        float mvp[4][4] =\n        {\n            { 2.0f/(R-L),   0.0f,           0.0f,       0.0f },\n            { 0.0f,         2.0f/(T-B),     0.0f,       0.0f },\n            { 0.0f,         0.0f,           0.5f,       0.0f },\n            { (R+L)/(L-R),  (T+B)/(B-T),    0.5f,       1.0f },\n        };\n        memcpy(&constant_buffer->mvp, mvp, sizeof(mvp));\n        device_ctx->Unmap(bd->pVertexConstantBuffer, 0);\n    }\n\n    // Setup shader and vertex buffers\n    unsigned int stride = sizeof(ImDrawVert);\n    unsigned int offset = 0;\n    device_ctx->IASetInputLayout(bd->pInputLayout);\n    device_ctx->IASetVertexBuffers(0, 1, &bd->pVB, &stride, &offset);\n    device_ctx->IASetIndexBuffer(bd->pIB, sizeof(ImDrawIdx) == 2 ? DXGI_FORMAT_R16_UINT : DXGI_FORMAT_R32_UINT, 0);\n    device_ctx->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);\n    device_ctx->VSSetShader(bd->pVertexShader, nullptr, 0);\n    device_ctx->VSSetConstantBuffers(0, 1, &bd->pVertexConstantBuffer);\n    device_ctx->PSSetShader(bd->pPixelShader, nullptr, 0);\n    device_ctx->PSSetSamplers(0, 1, &bd->pFontSampler);\n    device_ctx->GSSetShader(nullptr, nullptr, 0);\n    device_ctx->HSSetShader(nullptr, nullptr, 0); // In theory we should backup and restore this as well.. very infrequently used..\n    device_ctx->DSSetShader(nullptr, nullptr, 0); // In theory we should backup and restore this as well.. very infrequently used..\n    device_ctx->CSSetShader(nullptr, nullptr, 0); // In theory we should backup and restore this as well.. very infrequently used..\n\n    // Setup render state\n    const float blend_factor[4] = { 0.f, 0.f, 0.f, 0.f };\n    device_ctx->OMSetBlendState(bd->pBlendState, blend_factor, 0xffffffff);\n    device_ctx->OMSetDepthStencilState(bd->pDepthStencilState, 0);\n    device_ctx->RSSetState(bd->pRasterizerState);\n}\n\n// Render function\nvoid ImGui_ImplDX11_RenderDrawData(ImDrawData* draw_data)\n{\n    // Avoid rendering when minimized\n    if (draw_data->DisplaySize.x <= 0.0f || draw_data->DisplaySize.y <= 0.0f)\n        return;\n\n    ImGui_ImplDX11_Data* bd = ImGui_ImplDX11_GetBackendData();\n    ID3D11DeviceContext* device = bd->pd3dDeviceContext;\n\n    // Create and grow vertex/index buffers if needed\n    if (!bd->pVB || bd->VertexBufferSize < draw_data->TotalVtxCount)\n    {\n        if (bd->pVB) { bd->pVB->Release(); bd->pVB = nullptr; }\n        bd->VertexBufferSize = draw_data->TotalVtxCount + 5000;\n        D3D11_BUFFER_DESC desc = {};\n        desc.Usage = D3D11_USAGE_DYNAMIC;\n        desc.ByteWidth = bd->VertexBufferSize * sizeof(ImDrawVert);\n        desc.BindFlags = D3D11_BIND_VERTEX_BUFFER;\n        desc.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE;\n        desc.MiscFlags = 0;\n        if (bd->pd3dDevice->CreateBuffer(&desc, nullptr, &bd->pVB) < 0)\n            return;\n    }\n    if (!bd->pIB || bd->IndexBufferSize < draw_data->TotalIdxCount)\n    {\n        if (bd->pIB) { bd->pIB->Release(); bd->pIB = nullptr; }\n        bd->IndexBufferSize = draw_data->TotalIdxCount + 10000;\n        D3D11_BUFFER_DESC desc = {};\n        desc.Usage = D3D11_USAGE_DYNAMIC;\n        desc.ByteWidth = bd->IndexBufferSize * sizeof(ImDrawIdx);\n        desc.BindFlags = D3D11_BIND_INDEX_BUFFER;\n        desc.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE;\n        if (bd->pd3dDevice->CreateBuffer(&desc, nullptr, &bd->pIB) < 0)\n            return;\n    }\n\n    // Upload vertex/index data into a single contiguous GPU buffer\n    D3D11_MAPPED_SUBRESOURCE vtx_resource, idx_resource;\n    if (device->Map(bd->pVB, 0, D3D11_MAP_WRITE_DISCARD, 0, &vtx_resource) != S_OK)\n        return;\n    if (device->Map(bd->pIB, 0, D3D11_MAP_WRITE_DISCARD, 0, &idx_resource) != S_OK)\n        return;\n    ImDrawVert* vtx_dst = (ImDrawVert*)vtx_resource.pData;\n    ImDrawIdx* idx_dst = (ImDrawIdx*)idx_resource.pData;\n    for (int n = 0; n < draw_data->CmdListsCount; n++)\n    {\n        const ImDrawList* draw_list = draw_data->CmdLists[n];\n        memcpy(vtx_dst, draw_list->VtxBuffer.Data, draw_list->VtxBuffer.Size * sizeof(ImDrawVert));\n        memcpy(idx_dst, draw_list->IdxBuffer.Data, draw_list->IdxBuffer.Size * sizeof(ImDrawIdx));\n        vtx_dst += draw_list->VtxBuffer.Size;\n        idx_dst += draw_list->IdxBuffer.Size;\n    }\n    device->Unmap(bd->pVB, 0);\n    device->Unmap(bd->pIB, 0);\n\n    // Backup DX state that will be modified to restore it afterwards (unfortunately this is very ugly looking and verbose. Close your eyes!)\n    struct BACKUP_DX11_STATE\n    {\n        UINT                        ScissorRectsCount, ViewportsCount;\n        D3D11_RECT                  ScissorRects[D3D11_VIEWPORT_AND_SCISSORRECT_OBJECT_COUNT_PER_PIPELINE];\n        D3D11_VIEWPORT              Viewports[D3D11_VIEWPORT_AND_SCISSORRECT_OBJECT_COUNT_PER_PIPELINE];\n        ID3D11RasterizerState*      RS;\n        ID3D11BlendState*           BlendState;\n        FLOAT                       BlendFactor[4];\n        UINT                        SampleMask;\n        UINT                        StencilRef;\n        ID3D11DepthStencilState*    DepthStencilState;\n        ID3D11ShaderResourceView*   PSShaderResource;\n        ID3D11SamplerState*         PSSampler;\n        ID3D11PixelShader*          PS;\n        ID3D11VertexShader*         VS;\n        ID3D11GeometryShader*       GS;\n        UINT                        PSInstancesCount, VSInstancesCount, GSInstancesCount;\n        ID3D11ClassInstance         *PSInstances[256], *VSInstances[256], *GSInstances[256];   // 256 is max according to PSSetShader documentation\n        D3D11_PRIMITIVE_TOPOLOGY    PrimitiveTopology;\n        ID3D11Buffer*               IndexBuffer, *VertexBuffer, *VSConstantBuffer;\n        UINT                        IndexBufferOffset, VertexBufferStride, VertexBufferOffset;\n        DXGI_FORMAT                 IndexBufferFormat;\n        ID3D11InputLayout*          InputLayout;\n    };\n    BACKUP_DX11_STATE old = {};\n    old.ScissorRectsCount = old.ViewportsCount = D3D11_VIEWPORT_AND_SCISSORRECT_OBJECT_COUNT_PER_PIPELINE;\n    device->RSGetScissorRects(&old.ScissorRectsCount, old.ScissorRects);\n    device->RSGetViewports(&old.ViewportsCount, old.Viewports);\n    device->RSGetState(&old.RS);\n    device->OMGetBlendState(&old.BlendState, old.BlendFactor, &old.SampleMask);\n    device->OMGetDepthStencilState(&old.DepthStencilState, &old.StencilRef);\n    device->PSGetShaderResources(0, 1, &old.PSShaderResource);\n    device->PSGetSamplers(0, 1, &old.PSSampler);\n    old.PSInstancesCount = old.VSInstancesCount = old.GSInstancesCount = 256;\n    device->PSGetShader(&old.PS, old.PSInstances, &old.PSInstancesCount);\n    device->VSGetShader(&old.VS, old.VSInstances, &old.VSInstancesCount);\n    device->VSGetConstantBuffers(0, 1, &old.VSConstantBuffer);\n    device->GSGetShader(&old.GS, old.GSInstances, &old.GSInstancesCount);\n\n    device->IAGetPrimitiveTopology(&old.PrimitiveTopology);\n    device->IAGetIndexBuffer(&old.IndexBuffer, &old.IndexBufferFormat, &old.IndexBufferOffset);\n    device->IAGetVertexBuffers(0, 1, &old.VertexBuffer, &old.VertexBufferStride, &old.VertexBufferOffset);\n    device->IAGetInputLayout(&old.InputLayout);\n\n    // Setup desired DX state\n    ImGui_ImplDX11_SetupRenderState(draw_data, device);\n\n    // Setup render state structure (for callbacks and custom texture bindings)\n    ImGuiPlatformIO& platform_io = ImGui::GetPlatformIO();\n    ImGui_ImplDX11_RenderState render_state;\n    render_state.Device = bd->pd3dDevice;\n    render_state.DeviceContext = bd->pd3dDeviceContext;\n    render_state.SamplerDefault = bd->pFontSampler;\n    render_state.VertexConstantBuffer = bd->pVertexConstantBuffer;\n    platform_io.Renderer_RenderState = &render_state;\n\n    // Render command lists\n    // (Because we merged all buffers into a single one, we maintain our own offset into them)\n    int global_idx_offset = 0;\n    int global_vtx_offset = 0;\n    ImVec2 clip_off = draw_data->DisplayPos;\n    for (int n = 0; n < draw_data->CmdListsCount; n++)\n    {\n        const ImDrawList* draw_list = draw_data->CmdLists[n];\n        for (int cmd_i = 0; cmd_i < draw_list->CmdBuffer.Size; cmd_i++)\n        {\n            const ImDrawCmd* pcmd = &draw_list->CmdBuffer[cmd_i];\n            if (pcmd->UserCallback != nullptr)\n            {\n                // User callback, registered via ImDrawList::AddCallback()\n                // (ImDrawCallback_ResetRenderState is a special callback value used by the user to request the renderer to reset render state.)\n                if (pcmd->UserCallback == ImDrawCallback_ResetRenderState)\n                    ImGui_ImplDX11_SetupRenderState(draw_data, device);\n                else\n                    pcmd->UserCallback(draw_list, pcmd);\n            }\n            else\n            {\n                // Project scissor/clipping rectangles into framebuffer space\n                ImVec2 clip_min(pcmd->ClipRect.x - clip_off.x, pcmd->ClipRect.y - clip_off.y);\n                ImVec2 clip_max(pcmd->ClipRect.z - clip_off.x, pcmd->ClipRect.w - clip_off.y);\n                if (clip_max.x <= clip_min.x || clip_max.y <= clip_min.y)\n                    continue;\n\n                // Apply scissor/clipping rectangle\n                const D3D11_RECT r = { (LONG)clip_min.x, (LONG)clip_min.y, (LONG)clip_max.x, (LONG)clip_max.y };\n                device->RSSetScissorRects(1, &r);\n\n                // Bind texture, Draw\n                ID3D11ShaderResourceView* texture_srv = (ID3D11ShaderResourceView*)pcmd->GetTexID();\n                device->PSSetShaderResources(0, 1, &texture_srv);\n                device->DrawIndexed(pcmd->ElemCount, pcmd->IdxOffset + global_idx_offset, pcmd->VtxOffset + global_vtx_offset);\n            }\n        }\n        global_idx_offset += draw_list->IdxBuffer.Size;\n        global_vtx_offset += draw_list->VtxBuffer.Size;\n    }\n    platform_io.Renderer_RenderState = nullptr;\n\n    // Restore modified DX state\n    device->RSSetScissorRects(old.ScissorRectsCount, old.ScissorRects);\n    device->RSSetViewports(old.ViewportsCount, old.Viewports);\n    device->RSSetState(old.RS); if (old.RS) old.RS->Release();\n    device->OMSetBlendState(old.BlendState, old.BlendFactor, old.SampleMask); if (old.BlendState) old.BlendState->Release();\n    device->OMSetDepthStencilState(old.DepthStencilState, old.StencilRef); if (old.DepthStencilState) old.DepthStencilState->Release();\n    device->PSSetShaderResources(0, 1, &old.PSShaderResource); if (old.PSShaderResource) old.PSShaderResource->Release();\n    device->PSSetSamplers(0, 1, &old.PSSampler); if (old.PSSampler) old.PSSampler->Release();\n    device->PSSetShader(old.PS, old.PSInstances, old.PSInstancesCount); if (old.PS) old.PS->Release();\n    for (UINT i = 0; i < old.PSInstancesCount; i++) if (old.PSInstances[i]) old.PSInstances[i]->Release();\n    device->VSSetShader(old.VS, old.VSInstances, old.VSInstancesCount); if (old.VS) old.VS->Release();\n    device->VSSetConstantBuffers(0, 1, &old.VSConstantBuffer); if (old.VSConstantBuffer) old.VSConstantBuffer->Release();\n    device->GSSetShader(old.GS, old.GSInstances, old.GSInstancesCount); if (old.GS) old.GS->Release();\n    for (UINT i = 0; i < old.VSInstancesCount; i++) if (old.VSInstances[i]) old.VSInstances[i]->Release();\n    device->IASetPrimitiveTopology(old.PrimitiveTopology);\n    device->IASetIndexBuffer(old.IndexBuffer, old.IndexBufferFormat, old.IndexBufferOffset); if (old.IndexBuffer) old.IndexBuffer->Release();\n    device->IASetVertexBuffers(0, 1, &old.VertexBuffer, &old.VertexBufferStride, &old.VertexBufferOffset); if (old.VertexBuffer) old.VertexBuffer->Release();\n    device->IASetInputLayout(old.InputLayout); if (old.InputLayout) old.InputLayout->Release();\n}\n\nstatic void ImGui_ImplDX11_CreateFontsTexture()\n{\n    // Build texture atlas\n    ImGuiIO& io = ImGui::GetIO();\n    ImGui_ImplDX11_Data* bd = ImGui_ImplDX11_GetBackendData();\n    unsigned char* pixels;\n    int width, height;\n    io.Fonts->GetTexDataAsRGBA32(&pixels, &width, &height);\n\n    // Upload texture to graphics system\n    {\n        D3D11_TEXTURE2D_DESC desc;\n        ZeroMemory(&desc, sizeof(desc));\n        desc.Width = width;\n        desc.Height = height;\n        desc.MipLevels = 1;\n        desc.ArraySize = 1;\n        desc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;\n        desc.SampleDesc.Count = 1;\n        desc.Usage = D3D11_USAGE_DEFAULT;\n        desc.BindFlags = D3D11_BIND_SHADER_RESOURCE;\n        desc.CPUAccessFlags = 0;\n\n        ID3D11Texture2D* pTexture = nullptr;\n        D3D11_SUBRESOURCE_DATA subResource;\n        subResource.pSysMem = pixels;\n        subResource.SysMemPitch = desc.Width * 4;\n        subResource.SysMemSlicePitch = 0;\n        bd->pd3dDevice->CreateTexture2D(&desc, &subResource, &pTexture);\n        IM_ASSERT(pTexture != nullptr);\n\n        // Create texture view\n        D3D11_SHADER_RESOURCE_VIEW_DESC srvDesc;\n        ZeroMemory(&srvDesc, sizeof(srvDesc));\n        srvDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;\n        srvDesc.ViewDimension = D3D11_SRV_DIMENSION_TEXTURE2D;\n        srvDesc.Texture2D.MipLevels = desc.MipLevels;\n        srvDesc.Texture2D.MostDetailedMip = 0;\n        bd->pd3dDevice->CreateShaderResourceView(pTexture, &srvDesc, &bd->pFontTextureView);\n        pTexture->Release();\n    }\n\n    // Store our identifier\n    io.Fonts->SetTexID((ImTextureID)bd->pFontTextureView);\n}\n\nstatic void ImGui_ImplDX11_DestroyFontsTexture()\n{\n    ImGui_ImplDX11_Data* bd = ImGui_ImplDX11_GetBackendData();\n    if (bd->pFontTextureView)\n    {\n        bd->pFontTextureView->Release();\n        bd->pFontTextureView = nullptr;\n        ImGui::GetIO().Fonts->SetTexID(0); // We copied data->pFontTextureView to io.Fonts->TexID so let's clear that as well.\n    }\n}\n\nbool    ImGui_ImplDX11_CreateDeviceObjects()\n{\n    ImGui_ImplDX11_Data* bd = ImGui_ImplDX11_GetBackendData();\n    if (!bd->pd3dDevice)\n        return false;\n    if (bd->pFontSampler)\n        ImGui_ImplDX11_InvalidateDeviceObjects();\n\n    // Create the vertex shader\n    {\n        if (bd->pd3dDevice->CreateVertexShader(g_VS, ARRAYSIZE(g_VS), nullptr, &bd->pVertexShader) != S_OK)\n            return false;\n\n        // Create the input layout\n        D3D11_INPUT_ELEMENT_DESC local_layout[] =\n        {\n            { \"POSITION\", 0, DXGI_FORMAT_R32G32_FLOAT,   0, (UINT)offsetof(ImDrawVert, pos), D3D11_INPUT_PER_VERTEX_DATA, 0 },\n            { \"TEXCOORD\", 0, DXGI_FORMAT_R32G32_FLOAT,   0, (UINT)offsetof(ImDrawVert, uv),  D3D11_INPUT_PER_VERTEX_DATA, 0 },\n            { \"COLOR\",    0, DXGI_FORMAT_R8G8B8A8_UNORM, 0, (UINT)offsetof(ImDrawVert, col), D3D11_INPUT_PER_VERTEX_DATA, 0 },\n        };\n        if (bd->pd3dDevice->CreateInputLayout(local_layout, 3, g_VS, ARRAYSIZE(g_VS), &bd->pInputLayout) != S_OK)\n            return false;\n\n        // Create the constant buffer\n        {\n            D3D11_BUFFER_DESC desc = {};\n            desc.ByteWidth = sizeof(VERTEX_CONSTANT_BUFFER_DX11);\n            desc.Usage = D3D11_USAGE_DYNAMIC;\n            desc.BindFlags = D3D11_BIND_CONSTANT_BUFFER;\n            desc.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE;\n            desc.MiscFlags = 0;\n            bd->pd3dDevice->CreateBuffer(&desc, nullptr, &bd->pVertexConstantBuffer);\n        }\n    }\n\n    // Create the pixel shader\n    {\n        if (bd->pd3dDevice->CreatePixelShader(g_PS, ARRAYSIZE(g_PS), nullptr, &bd->pPixelShader) != S_OK)\n            return false;\n    }\n\n    // Create the blending setup\n    {\n        D3D11_BLEND_DESC desc;\n        ZeroMemory(&desc, sizeof(desc));\n        desc.AlphaToCoverageEnable = false;\n        desc.RenderTarget[0].BlendEnable = true;\n        desc.RenderTarget[0].SrcBlend = D3D11_BLEND_SRC_ALPHA;\n        desc.RenderTarget[0].DestBlend = D3D11_BLEND_INV_SRC_ALPHA;\n        desc.RenderTarget[0].BlendOp = D3D11_BLEND_OP_ADD;\n        desc.RenderTarget[0].SrcBlendAlpha = D3D11_BLEND_ONE;\n        desc.RenderTarget[0].DestBlendAlpha = D3D11_BLEND_INV_SRC_ALPHA;\n        desc.RenderTarget[0].BlendOpAlpha = D3D11_BLEND_OP_ADD;\n        desc.RenderTarget[0].RenderTargetWriteMask = D3D11_COLOR_WRITE_ENABLE_ALL;\n        bd->pd3dDevice->CreateBlendState(&desc, &bd->pBlendState);\n    }\n\n    // Create the rasterizer state\n    {\n        D3D11_RASTERIZER_DESC desc;\n        ZeroMemory(&desc, sizeof(desc));\n        desc.FillMode = D3D11_FILL_SOLID;\n        desc.CullMode = D3D11_CULL_NONE;\n        desc.ScissorEnable = true;\n        desc.DepthClipEnable = true;\n        bd->pd3dDevice->CreateRasterizerState(&desc, &bd->pRasterizerState);\n    }\n\n    // Create depth-stencil State\n    {\n        D3D11_DEPTH_STENCIL_DESC desc;\n        ZeroMemory(&desc, sizeof(desc));\n        desc.DepthEnable = false;\n        desc.DepthWriteMask = D3D11_DEPTH_WRITE_MASK_ALL;\n        desc.DepthFunc = D3D11_COMPARISON_ALWAYS;\n        desc.StencilEnable = false;\n        desc.FrontFace.StencilFailOp = desc.FrontFace.StencilDepthFailOp = desc.FrontFace.StencilPassOp = D3D11_STENCIL_OP_KEEP;\n        desc.FrontFace.StencilFunc = D3D11_COMPARISON_ALWAYS;\n        desc.BackFace = desc.FrontFace;\n        bd->pd3dDevice->CreateDepthStencilState(&desc, &bd->pDepthStencilState);\n    }\n\n    // Create texture sampler\n    // (Bilinear sampling is required by default. Set 'io.Fonts->Flags |= ImFontAtlasFlags_NoBakedLines' or 'style.AntiAliasedLinesUseTex = false' to allow point/nearest sampling)\n    {\n        D3D11_SAMPLER_DESC desc;\n        ZeroMemory(&desc, sizeof(desc));\n        desc.Filter = D3D11_FILTER_MIN_MAG_MIP_LINEAR;\n        desc.AddressU = D3D11_TEXTURE_ADDRESS_CLAMP;\n        desc.AddressV = D3D11_TEXTURE_ADDRESS_CLAMP;\n        desc.AddressW = D3D11_TEXTURE_ADDRESS_CLAMP;\n        desc.MipLODBias = 0.f;\n        desc.ComparisonFunc = D3D11_COMPARISON_ALWAYS;\n        desc.MinLOD = 0.f;\n        desc.MaxLOD = 0.f;\n        bd->pd3dDevice->CreateSamplerState(&desc, &bd->pFontSampler);\n    }\n\n    ImGui_ImplDX11_CreateFontsTexture();\n\n    return true;\n}\n\nvoid    ImGui_ImplDX11_InvalidateDeviceObjects()\n{\n    ImGui_ImplDX11_Data* bd = ImGui_ImplDX11_GetBackendData();\n    if (!bd->pd3dDevice)\n        return;\n\n    ImGui_ImplDX11_DestroyFontsTexture();\n\n    if (bd->pFontSampler)           { bd->pFontSampler->Release(); bd->pFontSampler = nullptr; }\n    if (bd->pIB)                    { bd->pIB->Release(); bd->pIB = nullptr; }\n    if (bd->pVB)                    { bd->pVB->Release(); bd->pVB = nullptr; }\n    if (bd->pBlendState)            { bd->pBlendState->Release(); bd->pBlendState = nullptr; }\n    if (bd->pDepthStencilState)     { bd->pDepthStencilState->Release(); bd->pDepthStencilState = nullptr; }\n    if (bd->pRasterizerState)       { bd->pRasterizerState->Release(); bd->pRasterizerState = nullptr; }\n    if (bd->pPixelShader)           { bd->pPixelShader->Release(); bd->pPixelShader = nullptr; }\n    if (bd->pVertexConstantBuffer)  { bd->pVertexConstantBuffer->Release(); bd->pVertexConstantBuffer = nullptr; }\n    if (bd->pInputLayout)           { bd->pInputLayout->Release(); bd->pInputLayout = nullptr; }\n    if (bd->pVertexShader)          { bd->pVertexShader->Release(); bd->pVertexShader = nullptr; }\n}\n\nbool    ImGui_ImplDX11_Init(ID3D11Device* device, ID3D11DeviceContext* device_context)\n{\n    ImGuiIO& io = ImGui::GetIO();\n    IMGUI_CHECKVERSION();\n    IM_ASSERT(io.BackendRendererUserData == nullptr && \"Already initialized a renderer backend!\");\n\n    // Setup backend capabilities flags\n    ImGui_ImplDX11_Data* bd = IM_NEW(ImGui_ImplDX11_Data)();\n    io.BackendRendererUserData = (void*)bd;\n    io.BackendRendererName = \"imgui_impl_dx11_openvr\";\n    io.BackendFlags |= ImGuiBackendFlags_RendererHasVtxOffset;  // We can honor the ImDrawCmd::VtxOffset field, allowing for large meshes.\n\n    // Get factory from device\n    IDXGIDevice* pDXGIDevice = nullptr;\n    IDXGIAdapter* pDXGIAdapter = nullptr;\n    IDXGIFactory* pFactory = nullptr;\n\n    if (device->QueryInterface(IID_PPV_ARGS(&pDXGIDevice)) == S_OK)\n        if (pDXGIDevice->GetParent(IID_PPV_ARGS(&pDXGIAdapter)) == S_OK)\n            if (pDXGIAdapter->GetParent(IID_PPV_ARGS(&pFactory)) == S_OK)\n            {\n                bd->pd3dDevice = device;\n                bd->pd3dDeviceContext = device_context;\n                bd->pFactory = pFactory;\n            }\n    if (pDXGIDevice) pDXGIDevice->Release();\n    if (pDXGIAdapter) pDXGIAdapter->Release();\n    bd->pd3dDevice->AddRef();\n    bd->pd3dDeviceContext->AddRef();\n\n    return true;\n}\n\nvoid ImGui_ImplDX11_Shutdown()\n{\n    ImGui_ImplDX11_Data* bd = ImGui_ImplDX11_GetBackendData();\n    IM_ASSERT(bd != nullptr && \"No renderer backend to shutdown, or already shutdown?\");\n    ImGuiIO& io = ImGui::GetIO();\n\n    ImGui_ImplDX11_InvalidateDeviceObjects();\n    if (bd->pFactory)             { bd->pFactory->Release(); }\n    if (bd->pd3dDevice)           { bd->pd3dDevice->Release(); }\n    if (bd->pd3dDeviceContext)    { bd->pd3dDeviceContext->Release(); }\n    io.BackendRendererName = nullptr;\n    io.BackendRendererUserData = nullptr;\n    io.BackendFlags &= ~ImGuiBackendFlags_RendererHasVtxOffset;\n    IM_DELETE(bd);\n}\n\nvoid ImGui_ImplDX11_NewFrame()\n{\n    ImGui_ImplDX11_Data* bd = ImGui_ImplDX11_GetBackendData();\n    IM_ASSERT(bd != nullptr && \"Context or backend not initialized! Did you call ImGui_ImplDX11_Init()?\");\n\n    if (!bd->pFontSampler)\n        ImGui_ImplDX11_CreateDeviceObjects();\n}\n\n//-----------------------------------------------------------------------------\n\n#endif // #ifndef IMGUI_DISABLE\n"
  },
  {
    "path": "src/DesktopPlusUI/imgui_win32_dx11_openvr/imgui_impl_dx11_openvr.h",
    "content": "// Desktop+UI: Modified for OpenVR compatibility\n\n// dear imgui: Renderer Backend for DirectX11\n// This needs to be used along with a Platform Backend (e.g. Win32)\n\n// Implemented features:\n//  [X] Renderer: User texture binding. Use 'ID3D11ShaderResourceView*' as ImTextureID. Read the FAQ about ImTextureID!\n//  [X] Renderer: Large meshes support (64k+ vertices) even with 16-bit indices (ImGuiBackendFlags_RendererHasVtxOffset).\n//  [X] Renderer: Expose selected render state for draw callbacks to use. Access in '(ImGui_ImplXXXX_RenderState*)GetPlatformIO().Renderer_RenderState'.\n\n// You can use unmodified imgui_impl_* files in your project. See examples/ folder for examples of using this.\n// Prefer including the entire imgui/ repository into your project (either as a copy or as a submodule), and only build the backends you need.\n// Learn about Dear ImGui:\n// - FAQ                  https://dearimgui.com/faq\n// - Getting Started      https://dearimgui.com/getting-started\n// - Documentation        https://dearimgui.com/docs (same as your local docs/ folder).\n// - Introduction, links and more at the top of imgui.cpp\n\n#pragma once\n#include \"imgui.h\"      // IMGUI_IMPL_API\n#ifndef IMGUI_DISABLE\n\nstruct ID3D11Device;\nstruct ID3D11DeviceContext;\nstruct ID3D11SamplerState;\nstruct ID3D11Buffer;\n\n// Follow \"Getting Started\" link and check examples/ folder to learn about using backends!\nIMGUI_IMPL_API bool     ImGui_ImplDX11_Init(ID3D11Device* device, ID3D11DeviceContext* device_context);\nIMGUI_IMPL_API void     ImGui_ImplDX11_Shutdown();\nIMGUI_IMPL_API void     ImGui_ImplDX11_NewFrame();\nIMGUI_IMPL_API void     ImGui_ImplDX11_RenderDrawData(ImDrawData* draw_data);\n\n// Use if you want to reset your rendering device without losing Dear ImGui state.\nIMGUI_IMPL_API void     ImGui_ImplDX11_InvalidateDeviceObjects();\nIMGUI_IMPL_API bool     ImGui_ImplDX11_CreateDeviceObjects();\n\n// [BETA] Selected render state data shared with callbacks.\n// This is temporarily stored in GetPlatformIO().Renderer_RenderState during the ImGui_ImplDX11_RenderDrawData() call.\n// (Please open an issue if you feel you need access to more data)\nstruct ImGui_ImplDX11_RenderState\n{\n    ID3D11Device*           Device;\n    ID3D11DeviceContext*    DeviceContext;\n    ID3D11SamplerState*     SamplerDefault;\n    ID3D11Buffer*           VertexConstantBuffer;\n};\n\n#endif // #ifndef IMGUI_DISABLE\n"
  },
  {
    "path": "src/DesktopPlusUI/imgui_win32_dx11_openvr/imgui_impl_win32_openvr.cpp",
    "content": "// Desktop+UI: Modified for OpenVR compatibility\n\n// dear imgui: Platform Backend for Windows (standard windows API for 32-bits AND 64-bits applications)\n// This needs to be used along with a Renderer (e.g. DirectX11, OpenGL3, Vulkan..)\n\n// Implemented features (imgui_impl_win32):\n//  [X] Platform: Clipboard support (for Win32 this is actually part of core dear imgui)\n//  [X] Platform: Mouse support. Can discriminate Mouse/TouchScreen/Pen.\n//  [X] Platform: Keyboard support. Since 1.87 we are using the io.AddKeyEvent() function. Pass ImGuiKey values to all key functions e.g. ImGui::IsKeyPressed(ImGuiKey_Space). [Legacy VK_* values are obsolete since 1.87 and not supported since 1.91.5]\n//  [X] Platform: Gamepad support. Enabled with 'io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad'.\n//  [X] Platform: Mouse cursor shape and visibility (ImGuiBackendFlags_HasMouseCursors). Disable with 'io.ConfigFlags |= ImGuiConfigFlags_NoMouseCursorChange'.\n\n// You can use unmodified imgui_impl_* files in your project. See examples/ folder for examples of using this.\n// Prefer including the entire imgui/ repository into your project (either as a copy or as a submodule), and only build the backends you need.\n// Learn about Dear ImGui:\n// - FAQ                  https://dearimgui.com/faq\n// - Getting Started      https://dearimgui.com/getting-started\n// - Documentation        https://dearimgui.com/docs (same as your local docs/ folder).\n// - Introduction, links and more at the top of imgui.cpp\n\n// Configuration flags to add in your imconfig file:\n//#define IMGUI_IMPL_WIN32_DISABLE_GAMEPAD              // Disable gamepad support. This was meaningful before <1.81 but we now load XInput dynamically so the option is now less relevant.\n\n// CHANGELOG (imgui_impl_win32)\n// (minor and older changes stripped away, please see git history for details)\n//  2025-03-10: When dealing with OEM keys, use scancodes instead of translated keycodes to choose ImGuiKey values. (#7136, #7201, #7206, #7306, #7670, #7672, #8468)\n//  2025-02-18: Added ImGuiMouseCursor_Wait and ImGuiMouseCursor_Progress mouse cursor support.\n//  2024-07-08: Inputs: Fixed ImGuiMod_Super being mapped to VK_APPS instead of VK_LWIN||VK_RWIN. (#7768)\n//  2023-10-05: Inputs: Added support for extra ImGuiKey values: F13 to F24 function keys, app back/forward keys.\n//  2023-09-25: Inputs: Synthesize key-down event on key-up for VK_SNAPSHOT / ImGuiKey_PrintScreen as Windows doesn't emit it (same behavior as GLFW/SDL).\n//  2023-09-07: Inputs: Added support for keyboard codepage conversion for when application is compiled in MBCS mode and using a non-Unicode window.\n//  2023-04-19: Added ImGui_ImplWin32_InitForOpenGL() to facilitate combining raw Win32/Winapi with OpenGL. (#3218)\n//  2023-04-04: Inputs: Added support for io.AddMouseSourceEvent() to discriminate ImGuiMouseSource_Mouse/ImGuiMouseSource_TouchScreen/ImGuiMouseSource_Pen. (#2702)\n//  2023-02-15: Inputs: Use WM_NCMOUSEMOVE / WM_NCMOUSELEAVE to track mouse position over non-client area (e.g. OS decorations) when app is not focused. (#6045, #6162)\n//  2023-02-02: Inputs: Flipping WM_MOUSEHWHEEL (horizontal mouse-wheel) value to match other backends and offer consistent horizontal scrolling direction. (#4019, #6096, #1463)\n//  2022-10-11: Using 'nullptr' instead of 'NULL' as per our switch to C++11.\n//  2022-09-28: Inputs: Convert WM_CHAR values with MultiByteToWideChar() when window class was registered as MBCS (not Unicode).\n//  2022-09-26: Inputs: Renamed ImGuiKey_ModXXX introduced in 1.87 to ImGuiMod_XXX (old names still supported).\n//  2022-01-26: Inputs: replaced short-lived io.AddKeyModsEvent() (added two weeks ago) with io.AddKeyEvent() using ImGuiKey_ModXXX flags. Sorry for the confusion.\n//  2021-01-20: Inputs: calling new io.AddKeyAnalogEvent() for gamepad support, instead of writing directly to io.NavInputs[].\n//  2022-01-17: Inputs: calling new io.AddMousePosEvent(), io.AddMouseButtonEvent(), io.AddMouseWheelEvent() API (1.87+).\n//  2022-01-17: Inputs: always update key mods next and before a key event (not in NewFrame) to fix input queue with very low framerates.\n//  2022-01-12: Inputs: Update mouse inputs using WM_MOUSEMOVE/WM_MOUSELEAVE + fallback to provide it when focused but not hovered/captured. More standard and will allow us to pass it to future input queue API.\n//  2022-01-12: Inputs: Maintain our own copy of MouseButtonsDown mask instead of using ImGui::IsAnyMouseDown() which will be obsoleted.\n//  2022-01-10: Inputs: calling new io.AddKeyEvent(), io.AddKeyModsEvent() + io.SetKeyEventNativeData() API (1.87+). Support for full ImGuiKey range.\n//  2021-12-16: Inputs: Fill VK_LCONTROL/VK_RCONTROL/VK_LSHIFT/VK_RSHIFT/VK_LMENU/VK_RMENU for completeness.\n//  2021-08-17: Calling io.AddFocusEvent() on WM_SETFOCUS/WM_KILLFOCUS messages.\n//  2021-08-02: Inputs: Fixed keyboard modifiers being reported when host window doesn't have focus.\n//  2021-07-29: Inputs: MousePos is correctly reported when the host platform window is hovered but not focused (using TrackMouseEvent() to receive WM_MOUSELEAVE events).\n//  2021-06-29: Reorganized backend to pull data from a single structure to facilitate usage with multiple-contexts (all g_XXXX access changed to bd->XXXX).\n//  2021-06-08: Fixed ImGui_ImplWin32_EnableDpiAwareness() and ImGui_ImplWin32_GetDpiScaleForMonitor() to handle Windows 8.1/10 features without a manifest (per-monitor DPI, and properly calls SetProcessDpiAwareness() on 8.1).\n//  2021-03-23: Inputs: Clearing keyboard down array when losing focus (WM_KILLFOCUS).\n//  2021-02-18: Added ImGui_ImplWin32_EnableAlphaCompositing(). Non Visual Studio users will need to link with dwmapi.lib (MinGW/gcc: use -ldwmapi).\n//  2021-02-17: Fixed ImGui_ImplWin32_EnableDpiAwareness() attempting to get SetProcessDpiAwareness from shcore.dll on Windows 8 whereas it is only supported on Windows 8.1.\n//  2021-01-25: Inputs: Dynamically loading XInput DLL.\n//  2020-12-04: Misc: Fixed setting of io.DisplaySize to invalid/uninitialized data when after hwnd has been closed.\n//  2020-03-03: Inputs: Calling AddInputCharacterUTF16() to support surrogate pairs leading to codepoint >= 0x10000 (for more complete CJK inputs)\n//  2020-02-17: Added ImGui_ImplWin32_EnableDpiAwareness(), ImGui_ImplWin32_GetDpiScaleForHwnd(), ImGui_ImplWin32_GetDpiScaleForMonitor() helper functions.\n//  2020-01-14: Inputs: Added support for #define IMGUI_IMPL_WIN32_DISABLE_GAMEPAD/IMGUI_IMPL_WIN32_DISABLE_LINKING_XINPUT.\n//  2019-12-05: Inputs: Added support for ImGuiMouseCursor_NotAllowed mouse cursor.\n//  2019-05-11: Inputs: Don't filter value from WM_CHAR before calling AddInputCharacter().\n//  2019-01-17: Misc: Using GetForegroundWindow()+IsChild() instead of GetActiveWindow() to be compatible with windows created in a different thread or parent.\n//  2019-01-17: Inputs: Added support for mouse buttons 4 and 5 via WM_XBUTTON* messages.\n//  2019-01-15: Inputs: Added support for XInput gamepads (if ImGuiConfigFlags_NavEnableGamepad is set by user application).\n//  2018-11-30: Misc: Setting up io.BackendPlatformName so it can be displayed in the About Window.\n//  2018-06-29: Inputs: Added support for the ImGuiMouseCursor_Hand cursor.\n//  2018-06-10: Inputs: Fixed handling of mouse wheel messages to support fine position messages (typically sent by track-pads).\n//  2018-06-08: Misc: Extracted imgui_impl_win32.cpp/.h away from the old combined DX9/DX10/DX11/DX12 examples.\n//  2018-03-20: Misc: Setup io.BackendFlags ImGuiBackendFlags_HasMouseCursors and ImGuiBackendFlags_HasSetMousePos flags + honor ImGuiConfigFlags_NoMouseCursorChange flag.\n//  2018-02-20: Inputs: Added support for mouse cursors (ImGui::GetMouseCursor() value and WM_SETCURSOR message handling).\n//  2018-02-06: Inputs: Added mapping for ImGuiKey_Space.\n//  2018-02-06: Inputs: Honoring the io.WantSetMousePos by repositioning the mouse (when using navigation and ImGuiConfigFlags_NavMoveMouse is set).\n//  2018-02-06: Misc: Removed call to ImGui::Shutdown() which is not available from 1.60 WIP, user needs to call CreateContext/DestroyContext themselves.\n//  2018-01-20: Inputs: Added Horizontal Mouse Wheel support.\n//  2018-01-08: Inputs: Added mapping for ImGuiKey_Insert.\n//  2018-01-05: Inputs: Added WM_LBUTTONDBLCLK double-click handlers for window classes with the CS_DBLCLKS flag.\n//  2017-10-23: Inputs: Added WM_SYSKEYDOWN / WM_SYSKEYUP handlers so e.g. the VK_MENU key can be read.\n//  2017-10-23: Inputs: Using Win32 ::SetCapture/::GetCapture() to retrieve mouse positions outside the client area when dragging.\n//  2016-11-12: Inputs: Only call Win32 ::SetCursor(nullptr) when io.MouseDrawCursor is set.\n\n#include \"imgui.h\"\n#ifndef IMGUI_DISABLE\n#include \"imgui_internal.h\"\n#include \"imgui_impl_win32_openvr.h\"\n#ifndef WIN32_LEAN_AND_MEAN\n#define WIN32_LEAN_AND_MEAN\n#endif\n#include <windows.h>\n#include <windowsx.h> // GET_X_LPARAM(), GET_Y_LPARAM()\n#include <tchar.h>\n#include <dwmapi.h>\n\n// Using XInput for gamepad (will load DLL dynamically)\n#ifndef IMGUI_IMPL_WIN32_DISABLE_GAMEPAD\n#include <xinput.h>\ntypedef DWORD(WINAPI* PFN_XInputGetCapabilities)(DWORD, DWORD, XINPUT_CAPABILITIES*);\ntypedef DWORD(WINAPI* PFN_XInputGetState)(DWORD, XINPUT_STATE*);\n#endif\n\n// Clang/GCC warnings with -Weverything\n#if defined(__clang__)\n#pragma clang diagnostic push\n#pragma clang diagnostic ignored \"-Wcast-function-type\"     // warning: cast between incompatible function types (for loader)\n#endif\n#if defined(__GNUC__)\n#pragma GCC diagnostic push\n#pragma GCC diagnostic ignored \"-Wpragmas\"                  // warning: unknown option after '#pragma GCC diagnostic' kind\n#pragma GCC diagnostic ignored \"-Wcast-function-type\"       // warning: cast between incompatible function types (for loader)\n#endif\n\n#include <queue>\n#include <vector>\n\n//Additional VRMouseButton values only sent by Desktop+ laser pointer inputs, map to mouse button X1/X2\n#define VRMouseButton_DP_Aux01 0x0008\n#define VRMouseButton_DP_Aux02 0x0010\n\nstruct ImGui_ImplWin32_Data\n{\n    HWND                        hWnd;\n    HWND                        MouseHwnd;\n    int                         MouseTrackedArea;   // 0: not tracked, 1: client area, 2: non-client area\n    int                         MouseButtonsDown;\n    INT64                       Time;\n    INT64                       TicksPerSecond;\n    ImGuiMouseCursor            LastMouseCursor;\n    UINT32                      KeyboardCodePage;\n\n#ifndef IMGUI_IMPL_WIN32_DISABLE_GAMEPAD\n    bool                        HasGamepad;\n    bool                        WantUpdateHasGamepad;\n    HMODULE                     XInputDLL;\n    PFN_XInputGetCapabilities   XInputGetCapabilities;\n    PFN_XInputGetState          XInputGetState;\n#endif\n\n    ImGui_ImplWin32_Data()      { memset((void*)this, 0, sizeof(*this)); }\n};\n\n// Backend data stored in io.BackendPlatformUserData to allow support for multiple Dear ImGui contexts\n// It is STRONGLY preferred that you use docking branch with multi-viewports (== single Dear ImGui context + multiple windows) instead of multiple Dear ImGui contexts.\n// FIXME: multi-context support is not well tested and probably dysfunctional in this backend.\n// FIXME: some shared resources (mouse cursor shape, gamepad) are mishandled when using multi-context.\nstatic ImGui_ImplWin32_Data* ImGui_ImplWin32_GetBackendData()\n{\n    return ImGui::GetCurrentContext() ? (ImGui_ImplWin32_Data*)ImGui::GetIO().BackendPlatformUserData : nullptr;\n}\nstatic ImGui_ImplWin32_Data* ImGui_ImplWin32_GetBackendData(ImGuiIO& io)\n{\n    return (ImGui_ImplWin32_Data*)io.BackendPlatformUserData;\n}\n\n// OpenVR Data\nstatic std::queue<std::string> g_OnScreenKeyboardQueue;\nstatic int                     g_OnScreenKeyboardQueueDelay = 0;\nstatic bool                    g_OnScreenKeyboardShown = false;\nstatic bool                    g_OnScreenKeyboardDismissedLastFrame = false;\n\n// Functions\nstatic void ImGui_ImplWin32_UpdateKeyboardCodePage(ImGuiIO& io)\n{\n    // Retrieve keyboard code page, required for handling of non-Unicode Windows.\n    ImGui_ImplWin32_Data* bd = ImGui_ImplWin32_GetBackendData(io);\n    HKL keyboard_layout = ::GetKeyboardLayout(0);\n    LCID keyboard_lcid = MAKELCID(HIWORD(keyboard_layout), SORT_DEFAULT);\n    if (::GetLocaleInfoA(keyboard_lcid, (LOCALE_RETURN_NUMBER | LOCALE_IDEFAULTANSICODEPAGE), (LPSTR)&bd->KeyboardCodePage, sizeof(bd->KeyboardCodePage)) == 0)\n        bd->KeyboardCodePage = CP_ACP; // Fallback to default ANSI code page when fails.\n}\n\nstatic bool ImGui_ImplWin32_InitEx(void* hwnd, bool platform_has_own_dc)\n{\n    ImGuiIO& io = ImGui::GetIO();\n    IMGUI_CHECKVERSION();\n    IM_ASSERT(io.BackendPlatformUserData == nullptr && \"Already initialized a platform backend!\");\n\n    INT64 perf_frequency, perf_counter;\n    if (!::QueryPerformanceFrequency((LARGE_INTEGER*)&perf_frequency))\n        return false;\n    if (!::QueryPerformanceCounter((LARGE_INTEGER*)&perf_counter))\n        return false;\n\n    // Setup backend capabilities flags\n    ImGui_ImplWin32_Data* bd = IM_NEW(ImGui_ImplWin32_Data)();\n    io.BackendPlatformUserData = (void*)bd;\n    io.BackendPlatformName = \"imgui_impl_win32_openvr\";\n    io.BackendFlags |= ImGuiBackendFlags_HasMouseCursors;         // We can honor GetMouseCursor() values (optional)\n    io.BackendFlags |= ImGuiBackendFlags_HasSetMousePos;          // We can honor io.WantSetMousePos requests (optional, rarely used)\n\n    bd->hWnd = (HWND)hwnd;\n    bd->TicksPerSecond = perf_frequency;\n    bd->Time = perf_counter;\n    bd->LastMouseCursor = ImGuiMouseCursor_COUNT;\n    ImGui_ImplWin32_UpdateKeyboardCodePage(io);\n\n    // Set platform dependent data in viewport\n    ImGuiViewport* main_viewport = ImGui::GetMainViewport();\n    main_viewport->PlatformHandle = main_viewport->PlatformHandleRaw = (void*)bd->hWnd;\n    IM_UNUSED(platform_has_own_dc); // Used in 'docking' branch\n\n    // Dynamically load XInput library\n#ifndef IMGUI_IMPL_WIN32_DISABLE_GAMEPAD\n    bd->WantUpdateHasGamepad = true;\n    const char* xinput_dll_names[] =\n    {\n        \"xinput1_4.dll\",   // Windows 8+\n        \"xinput1_3.dll\",   // DirectX SDK\n        \"xinput9_1_0.dll\", // Windows Vista, Windows 7\n        \"xinput1_2.dll\",   // DirectX SDK\n        \"xinput1_1.dll\"    // DirectX SDK\n    };\n    for (int n = 0; n < IM_ARRAYSIZE(xinput_dll_names); n++)\n        if (HMODULE dll = ::LoadLibraryA(xinput_dll_names[n]))\n        {\n            bd->XInputDLL = dll;\n            bd->XInputGetCapabilities = (PFN_XInputGetCapabilities)::GetProcAddress(dll, \"XInputGetCapabilities\");\n            bd->XInputGetState = (PFN_XInputGetState)::GetProcAddress(dll, \"XInputGetState\");\n            break;\n        }\n#endif // IMGUI_IMPL_WIN32_DISABLE_GAMEPAD\n\n    return true;\n}\n\nIMGUI_IMPL_API bool     ImGui_ImplWin32_Init(void* hwnd)\n{\n    return ImGui_ImplWin32_InitEx(hwnd, false);\n}\n\nIMGUI_IMPL_API bool     ImGui_ImplWin32_InitForOpenGL(void* hwnd)\n{\n    // OpenGL needs CS_OWNDC\n    return ImGui_ImplWin32_InitEx(hwnd, true);\n}\n\nvoid    ImGui_ImplWin32_Shutdown()\n{\n    ImGui_ImplWin32_Data* bd = ImGui_ImplWin32_GetBackendData();\n    IM_ASSERT(bd != nullptr && \"No platform backend to shutdown, or already shutdown?\");\n    ImGuiIO& io = ImGui::GetIO();\n\n    // Unload XInput library\n#ifndef IMGUI_IMPL_WIN32_DISABLE_GAMEPAD\n    if (bd->XInputDLL)\n        ::FreeLibrary(bd->XInputDLL);\n#endif // IMGUI_IMPL_WIN32_DISABLE_GAMEPAD\n\n    io.BackendPlatformName = nullptr;\n    io.BackendPlatformUserData = nullptr;\n    io.BackendFlags &= ~(ImGuiBackendFlags_HasMouseCursors | ImGuiBackendFlags_HasSetMousePos | ImGuiBackendFlags_HasGamepad);\n    IM_DELETE(bd);\n}\n\nstatic bool ImGui_ImplWin32_UpdateMouseCursor(ImGuiIO& io, ImGuiMouseCursor imgui_cursor)\n{\n    if (io.ConfigFlags & ImGuiConfigFlags_NoMouseCursorChange)\n        return false;\n\n    if (imgui_cursor == ImGuiMouseCursor_None || io.MouseDrawCursor)\n    {\n        // Hide OS mouse cursor if imgui is drawing it or if it wants no cursor\n        ::SetCursor(nullptr);\n    }\n    else\n    {\n        // Show OS mouse cursor\n        LPTSTR win32_cursor = IDC_ARROW;\n        switch (imgui_cursor)\n        {\n        case ImGuiMouseCursor_Arrow:        win32_cursor = IDC_ARROW; break;\n        case ImGuiMouseCursor_TextInput:    win32_cursor = IDC_IBEAM; break;\n        case ImGuiMouseCursor_ResizeAll:    win32_cursor = IDC_SIZEALL; break;\n        case ImGuiMouseCursor_ResizeEW:     win32_cursor = IDC_SIZEWE; break;\n        case ImGuiMouseCursor_ResizeNS:     win32_cursor = IDC_SIZENS; break;\n        case ImGuiMouseCursor_ResizeNESW:   win32_cursor = IDC_SIZENESW; break;\n        case ImGuiMouseCursor_ResizeNWSE:   win32_cursor = IDC_SIZENWSE; break;\n        case ImGuiMouseCursor_Hand:         win32_cursor = IDC_HAND; break;\n        case ImGuiMouseCursor_Wait:         win32_cursor = IDC_WAIT; break;\n        case ImGuiMouseCursor_Progress:     win32_cursor = IDC_APPSTARTING; break;\n        case ImGuiMouseCursor_NotAllowed:   win32_cursor = IDC_NO; break;\n        }\n        ::SetCursor(::LoadCursor(nullptr, win32_cursor));\n    }\n    return true;\n}\n\nstatic bool IsVkDown(int vk)\n{\n    return (::GetKeyState(vk) & 0x8000) != 0;\n}\n\nstatic void ImGui_ImplWin32_AddKeyEvent(ImGuiIO& io, ImGuiKey key, bool down, int native_keycode, int native_scancode = -1)\n{\n    io.AddKeyEvent(key, down);\n    io.SetKeyEventNativeData(key, native_keycode, native_scancode); // To support legacy indexing (<1.87 user code)\n    IM_UNUSED(native_scancode);\n}\n\nstatic void ImGui_ImplWin32_ProcessKeyEventsWorkarounds(ImGuiIO& io)\n{\n    // Left & right Shift keys: when both are pressed together, Windows tend to not generate the WM_KEYUP event for the first released one.\n    if (ImGui::IsKeyDown(ImGuiKey_LeftShift) && !IsVkDown(VK_LSHIFT))\n        ImGui_ImplWin32_AddKeyEvent(io, ImGuiKey_LeftShift, false, VK_LSHIFT);\n    if (ImGui::IsKeyDown(ImGuiKey_RightShift) && !IsVkDown(VK_RSHIFT))\n        ImGui_ImplWin32_AddKeyEvent(io, ImGuiKey_RightShift, false, VK_RSHIFT);\n\n    // Sometimes WM_KEYUP for Win key is not passed down to the app (e.g. for Win+V on some setups, according to GLFW).\n    if (ImGui::IsKeyDown(ImGuiKey_LeftSuper) && !IsVkDown(VK_LWIN))\n        ImGui_ImplWin32_AddKeyEvent(io, ImGuiKey_LeftSuper, false, VK_LWIN);\n    if (ImGui::IsKeyDown(ImGuiKey_RightSuper) && !IsVkDown(VK_RWIN))\n        ImGui_ImplWin32_AddKeyEvent(io, ImGuiKey_RightSuper, false, VK_RWIN);\n}\n\nstatic void ImGui_ImplWin32_UpdateKeyModifiers(ImGuiIO& io)\n{\n    io.AddKeyEvent(ImGuiMod_Ctrl, IsVkDown(VK_CONTROL));\n    io.AddKeyEvent(ImGuiMod_Shift, IsVkDown(VK_SHIFT));\n    io.AddKeyEvent(ImGuiMod_Alt, IsVkDown(VK_MENU));\n    io.AddKeyEvent(ImGuiMod_Super, IsVkDown(VK_LWIN) || IsVkDown(VK_RWIN));\n}\n\nstatic void ImGui_ImplWin32_UpdateMouseData(ImGuiIO& io)\n{\n    ImGui_ImplWin32_Data* bd = ImGui_ImplWin32_GetBackendData(io);\n    IM_ASSERT(bd->hWnd != 0);\n\n    HWND focused_window = ::GetForegroundWindow();\n    const bool is_app_focused = (focused_window == bd->hWnd);\n    if (is_app_focused)\n    {\n        // (Optional) Set OS mouse position from Dear ImGui if requested (rarely used, only when io.ConfigNavMoveSetMousePos is enabled by user)\n        if (io.WantSetMousePos)\n        {\n            POINT pos = { (int)io.MousePos.x, (int)io.MousePos.y };\n            if (::ClientToScreen(bd->hWnd, &pos))\n                ::SetCursorPos(pos.x, pos.y);\n        }\n\n        // (Optional) Fallback to provide mouse position when focused (WM_MOUSEMOVE already provides this when hovered or captured)\n        // This also fills a short gap when clicking non-client area: WM_NCMOUSELEAVE -> modal OS move -> gap -> WM_NCMOUSEMOVE\n        if (!io.WantSetMousePos && bd->MouseTrackedArea == 0)\n        {\n            POINT pos;\n            if (::GetCursorPos(&pos) && ::ScreenToClient(bd->hWnd, &pos))\n                io.AddMousePosEvent((float)pos.x, (float)pos.y);\n        }\n    }\n}\n\n// Gamepad navigation mapping\nstatic void ImGui_ImplWin32_UpdateGamepads(ImGuiIO& io)\n{\n#ifndef IMGUI_IMPL_WIN32_DISABLE_GAMEPAD\n    ImGui_ImplWin32_Data* bd = ImGui_ImplWin32_GetBackendData(io);\n    //if ((io.ConfigFlags & ImGuiConfigFlags_NavEnableGamepad) == 0) // FIXME: Technically feeding gamepad shouldn't depend on this now that they are regular inputs.\n    //    return;\n\n    // Calling XInputGetState() every frame on disconnected gamepads is unfortunately too slow.\n    // Instead we refresh gamepad availability by calling XInputGetCapabilities() _only_ after receiving WM_DEVICECHANGE.\n    if (bd->WantUpdateHasGamepad)\n    {\n        XINPUT_CAPABILITIES caps = {};\n        bd->HasGamepad = bd->XInputGetCapabilities ? (bd->XInputGetCapabilities(0, XINPUT_FLAG_GAMEPAD, &caps) == ERROR_SUCCESS) : false;\n        bd->WantUpdateHasGamepad = false;\n    }\n\n    io.BackendFlags &= ~ImGuiBackendFlags_HasGamepad;\n    XINPUT_STATE xinput_state;\n    XINPUT_GAMEPAD& gamepad = xinput_state.Gamepad;\n    if (!bd->HasGamepad || bd->XInputGetState == nullptr || bd->XInputGetState(0, &xinput_state) != ERROR_SUCCESS)\n        return;\n    io.BackendFlags |= ImGuiBackendFlags_HasGamepad;\n\n    #define IM_SATURATE(V)                      (V < 0.0f ? 0.0f : V > 1.0f ? 1.0f : V)\n    #define MAP_BUTTON(KEY_NO, BUTTON_ENUM)     { io.AddKeyEvent(KEY_NO, (gamepad.wButtons & BUTTON_ENUM) != 0); }\n    #define MAP_ANALOG(KEY_NO, VALUE, V0, V1)   { float vn = (float)(VALUE - V0) / (float)(V1 - V0); io.AddKeyAnalogEvent(KEY_NO, vn > 0.10f, IM_SATURATE(vn)); }\n    MAP_BUTTON(ImGuiKey_GamepadStart,           XINPUT_GAMEPAD_START);\n    MAP_BUTTON(ImGuiKey_GamepadBack,            XINPUT_GAMEPAD_BACK);\n    MAP_BUTTON(ImGuiKey_GamepadFaceLeft,        XINPUT_GAMEPAD_X);\n    MAP_BUTTON(ImGuiKey_GamepadFaceRight,       XINPUT_GAMEPAD_B);\n    MAP_BUTTON(ImGuiKey_GamepadFaceUp,          XINPUT_GAMEPAD_Y);\n    MAP_BUTTON(ImGuiKey_GamepadFaceDown,        XINPUT_GAMEPAD_A);\n    MAP_BUTTON(ImGuiKey_GamepadDpadLeft,        XINPUT_GAMEPAD_DPAD_LEFT);\n    MAP_BUTTON(ImGuiKey_GamepadDpadRight,       XINPUT_GAMEPAD_DPAD_RIGHT);\n    MAP_BUTTON(ImGuiKey_GamepadDpadUp,          XINPUT_GAMEPAD_DPAD_UP);\n    MAP_BUTTON(ImGuiKey_GamepadDpadDown,        XINPUT_GAMEPAD_DPAD_DOWN);\n    MAP_BUTTON(ImGuiKey_GamepadL1,              XINPUT_GAMEPAD_LEFT_SHOULDER);\n    MAP_BUTTON(ImGuiKey_GamepadR1,              XINPUT_GAMEPAD_RIGHT_SHOULDER);\n    MAP_ANALOG(ImGuiKey_GamepadL2,              gamepad.bLeftTrigger, XINPUT_GAMEPAD_TRIGGER_THRESHOLD, 255);\n    MAP_ANALOG(ImGuiKey_GamepadR2,              gamepad.bRightTrigger, XINPUT_GAMEPAD_TRIGGER_THRESHOLD, 255);\n    MAP_BUTTON(ImGuiKey_GamepadL3,              XINPUT_GAMEPAD_LEFT_THUMB);\n    MAP_BUTTON(ImGuiKey_GamepadR3,              XINPUT_GAMEPAD_RIGHT_THUMB);\n    MAP_ANALOG(ImGuiKey_GamepadLStickLeft,      gamepad.sThumbLX, -XINPUT_GAMEPAD_LEFT_THUMB_DEADZONE, -32768);\n    MAP_ANALOG(ImGuiKey_GamepadLStickRight,     gamepad.sThumbLX, +XINPUT_GAMEPAD_LEFT_THUMB_DEADZONE, +32767);\n    MAP_ANALOG(ImGuiKey_GamepadLStickUp,        gamepad.sThumbLY, +XINPUT_GAMEPAD_LEFT_THUMB_DEADZONE, +32767);\n    MAP_ANALOG(ImGuiKey_GamepadLStickDown,      gamepad.sThumbLY, -XINPUT_GAMEPAD_LEFT_THUMB_DEADZONE, -32768);\n    MAP_ANALOG(ImGuiKey_GamepadRStickLeft,      gamepad.sThumbRX, -XINPUT_GAMEPAD_LEFT_THUMB_DEADZONE, -32768);\n    MAP_ANALOG(ImGuiKey_GamepadRStickRight,     gamepad.sThumbRX, +XINPUT_GAMEPAD_LEFT_THUMB_DEADZONE, +32767);\n    MAP_ANALOG(ImGuiKey_GamepadRStickUp,        gamepad.sThumbRY, +XINPUT_GAMEPAD_LEFT_THUMB_DEADZONE, +32767);\n    MAP_ANALOG(ImGuiKey_GamepadRStickDown,      gamepad.sThumbRY, -XINPUT_GAMEPAD_LEFT_THUMB_DEADZONE, -32768);\n    #undef MAP_BUTTON\n    #undef MAP_ANALOG\n#else // #ifndef IMGUI_IMPL_WIN32_DISABLE_GAMEPAD\n    IM_UNUSED(io);\n#endif\n}\n\nvoid    ImGui_ImplWin32_NewFrame()\n{\n    ImGui_ImplWin32_Data* bd = ImGui_ImplWin32_GetBackendData();\n    IM_ASSERT(bd != nullptr && \"Context or backend not initialized? Did you call ImGui_ImplWin32_Init()?\");\n    ImGuiIO& io = ImGui::GetIO();\n\n    // Setup display size (every frame to accommodate for window resizing)\n    RECT rect = { 0, 0, 0, 0 };\n    ::GetClientRect(bd->hWnd, &rect);\n    io.DisplaySize = ImVec2((float)(rect.right - rect.left), (float)(rect.bottom - rect.top));\n\n    // Setup time step\n    INT64 current_time = 0;\n    ::QueryPerformanceCounter((LARGE_INTEGER*)&current_time);\n    io.DeltaTime = (float)(current_time - bd->Time) / bd->TicksPerSecond;\n    bd->Time = current_time;\n\n    // Update OS mouse position\n    ImGui_ImplWin32_UpdateMouseData(io);\n\n    // Process workarounds for known Windows key handling issues\n    ImGui_ImplWin32_ProcessKeyEventsWorkarounds(io);\n\n    // Update OS mouse cursor with the cursor requested by imgui\n    ImGuiMouseCursor mouse_cursor = io.MouseDrawCursor ? ImGuiMouseCursor_None : ImGui::GetMouseCursor();\n    if (bd->LastMouseCursor != mouse_cursor)\n    {\n        bd->LastMouseCursor = mouse_cursor;\n        ImGui_ImplWin32_UpdateMouseCursor(io, mouse_cursor);\n    }\n\n    // Update game controllers (if enabled and available)\n    ImGui_ImplWin32_UpdateGamepads(io);\n}\n\n// Map VK_xxx to ImGuiKey_xxx.\n// Not static to allow third-party code to use that if they want to (but undocumented)\nImGuiKey ImGui_ImplWin32_KeyEventToImGuiKey(uint64_t wParam, int64_t lParam);\nImGuiKey ImGui_ImplWin32_KeyEventToImGuiKey(uint64_t wParam, int64_t lParam)\n{\n    // There is no distinct VK_xxx for keypad enter, instead it is VK_RETURN + KF_EXTENDED.\n    if ((wParam == VK_RETURN) && (HIWORD(lParam) & KF_EXTENDED))\n        return ImGuiKey_KeypadEnter;\n\n    const int scancode = (int)LOBYTE(HIWORD(lParam));\n    //IMGUI_DEBUG_LOG(\"scancode %3d, keycode = 0x%02X\\n\", scancode, wParam);\n    switch (wParam)\n    {\n        case VK_TAB: return ImGuiKey_Tab;\n        case VK_LEFT: return ImGuiKey_LeftArrow;\n        case VK_RIGHT: return ImGuiKey_RightArrow;\n        case VK_UP: return ImGuiKey_UpArrow;\n        case VK_DOWN: return ImGuiKey_DownArrow;\n        case VK_PRIOR: return ImGuiKey_PageUp;\n        case VK_NEXT: return ImGuiKey_PageDown;\n        case VK_HOME: return ImGuiKey_Home;\n        case VK_END: return ImGuiKey_End;\n        case VK_INSERT: return ImGuiKey_Insert;\n        case VK_DELETE: return ImGuiKey_Delete;\n        case VK_BACK: return ImGuiKey_Backspace;\n        case VK_SPACE: return ImGuiKey_Space;\n        case VK_RETURN: return ImGuiKey_Enter;\n        case VK_ESCAPE: return ImGuiKey_Escape;\n        //case VK_OEM_7: return ImGuiKey_Apostrophe;\n        case VK_OEM_COMMA: return ImGuiKey_Comma;\n        //case VK_OEM_MINUS: return ImGuiKey_Minus;\n        case VK_OEM_PERIOD: return ImGuiKey_Period;\n        //case VK_OEM_2: return ImGuiKey_Slash;\n        //case VK_OEM_1: return ImGuiKey_Semicolon;\n        //case VK_OEM_PLUS: return ImGuiKey_Equal;\n        //case VK_OEM_4: return ImGuiKey_LeftBracket;\n        //case VK_OEM_5: return ImGuiKey_Backslash;\n        //case VK_OEM_6: return ImGuiKey_RightBracket;\n        //case VK_OEM_3: return ImGuiKey_GraveAccent;\n        case VK_CAPITAL: return ImGuiKey_CapsLock;\n        case VK_SCROLL: return ImGuiKey_ScrollLock;\n        case VK_NUMLOCK: return ImGuiKey_NumLock;\n        case VK_SNAPSHOT: return ImGuiKey_PrintScreen;\n        case VK_PAUSE: return ImGuiKey_Pause;\n        case VK_NUMPAD0: return ImGuiKey_Keypad0;\n        case VK_NUMPAD1: return ImGuiKey_Keypad1;\n        case VK_NUMPAD2: return ImGuiKey_Keypad2;\n        case VK_NUMPAD3: return ImGuiKey_Keypad3;\n        case VK_NUMPAD4: return ImGuiKey_Keypad4;\n        case VK_NUMPAD5: return ImGuiKey_Keypad5;\n        case VK_NUMPAD6: return ImGuiKey_Keypad6;\n        case VK_NUMPAD7: return ImGuiKey_Keypad7;\n        case VK_NUMPAD8: return ImGuiKey_Keypad8;\n        case VK_NUMPAD9: return ImGuiKey_Keypad9;\n        case VK_DECIMAL: return ImGuiKey_KeypadDecimal;\n        case VK_DIVIDE: return ImGuiKey_KeypadDivide;\n        case VK_MULTIPLY: return ImGuiKey_KeypadMultiply;\n        case VK_SUBTRACT: return ImGuiKey_KeypadSubtract;\n        case VK_ADD: return ImGuiKey_KeypadAdd;\n        case VK_LSHIFT: return ImGuiKey_LeftShift;\n        case VK_LCONTROL: return ImGuiKey_LeftCtrl;\n        case VK_LMENU: return ImGuiKey_LeftAlt;\n        case VK_LWIN: return ImGuiKey_LeftSuper;\n        case VK_RSHIFT: return ImGuiKey_RightShift;\n        case VK_RCONTROL: return ImGuiKey_RightCtrl;\n        case VK_RMENU: return ImGuiKey_RightAlt;\n        case VK_RWIN: return ImGuiKey_RightSuper;\n        case VK_APPS: return ImGuiKey_Menu;\n        case '0': return ImGuiKey_0;\n        case '1': return ImGuiKey_1;\n        case '2': return ImGuiKey_2;\n        case '3': return ImGuiKey_3;\n        case '4': return ImGuiKey_4;\n        case '5': return ImGuiKey_5;\n        case '6': return ImGuiKey_6;\n        case '7': return ImGuiKey_7;\n        case '8': return ImGuiKey_8;\n        case '9': return ImGuiKey_9;\n        case 'A': return ImGuiKey_A;\n        case 'B': return ImGuiKey_B;\n        case 'C': return ImGuiKey_C;\n        case 'D': return ImGuiKey_D;\n        case 'E': return ImGuiKey_E;\n        case 'F': return ImGuiKey_F;\n        case 'G': return ImGuiKey_G;\n        case 'H': return ImGuiKey_H;\n        case 'I': return ImGuiKey_I;\n        case 'J': return ImGuiKey_J;\n        case 'K': return ImGuiKey_K;\n        case 'L': return ImGuiKey_L;\n        case 'M': return ImGuiKey_M;\n        case 'N': return ImGuiKey_N;\n        case 'O': return ImGuiKey_O;\n        case 'P': return ImGuiKey_P;\n        case 'Q': return ImGuiKey_Q;\n        case 'R': return ImGuiKey_R;\n        case 'S': return ImGuiKey_S;\n        case 'T': return ImGuiKey_T;\n        case 'U': return ImGuiKey_U;\n        case 'V': return ImGuiKey_V;\n        case 'W': return ImGuiKey_W;\n        case 'X': return ImGuiKey_X;\n        case 'Y': return ImGuiKey_Y;\n        case 'Z': return ImGuiKey_Z;\n        case VK_F1: return ImGuiKey_F1;\n        case VK_F2: return ImGuiKey_F2;\n        case VK_F3: return ImGuiKey_F3;\n        case VK_F4: return ImGuiKey_F4;\n        case VK_F5: return ImGuiKey_F5;\n        case VK_F6: return ImGuiKey_F6;\n        case VK_F7: return ImGuiKey_F7;\n        case VK_F8: return ImGuiKey_F8;\n        case VK_F9: return ImGuiKey_F9;\n        case VK_F10: return ImGuiKey_F10;\n        case VK_F11: return ImGuiKey_F11;\n        case VK_F12: return ImGuiKey_F12;\n        case VK_F13: return ImGuiKey_F13;\n        case VK_F14: return ImGuiKey_F14;\n        case VK_F15: return ImGuiKey_F15;\n        case VK_F16: return ImGuiKey_F16;\n        case VK_F17: return ImGuiKey_F17;\n        case VK_F18: return ImGuiKey_F18;\n        case VK_F19: return ImGuiKey_F19;\n        case VK_F20: return ImGuiKey_F20;\n        case VK_F21: return ImGuiKey_F21;\n        case VK_F22: return ImGuiKey_F22;\n        case VK_F23: return ImGuiKey_F23;\n        case VK_F24: return ImGuiKey_F24;\n        case VK_BROWSER_BACK: return ImGuiKey_AppBack;\n        case VK_BROWSER_FORWARD: return ImGuiKey_AppForward;\n        default: break;\n    }\n\n    // Fallback to scancode\n    // https://handmade.network/forums/t/2011-keyboard_inputs_-_scancodes,_raw_input,_text_input,_key_names\n    switch (scancode)\n    {\n    case 41: return ImGuiKey_GraveAccent;  // VK_OEM_8 in EN-UK, VK_OEM_3 in EN-US, VK_OEM_7 in FR, VK_OEM_5 in DE, etc.\n    case 12: return ImGuiKey_Minus;\n    case 13: return ImGuiKey_Equal;\n    case 26: return ImGuiKey_LeftBracket;\n    case 27: return ImGuiKey_RightBracket;\n    case 86: return ImGuiKey_Oem102;\n    case 43: return ImGuiKey_Backslash;\n    case 39: return ImGuiKey_Semicolon;\n    case 40: return ImGuiKey_Apostrophe;\n    case 51: return ImGuiKey_Comma;\n    case 52: return ImGuiKey_Period;\n    case 53: return ImGuiKey_Slash;\n    }\n\n    return ImGuiKey_None;\n}\n\n// Allow compilation with old Windows SDK. MinGW doesn't have default _WIN32_WINNT/WINVER versions.\n#ifndef WM_MOUSEHWHEEL\n#define WM_MOUSEHWHEEL 0x020E\n#endif\n#ifndef DBT_DEVNODES_CHANGED\n#define DBT_DEVNODES_CHANGED 0x0007\n#endif\n\n// Helper to obtain the source of mouse messages.\n// See https://learn.microsoft.com/en-us/windows/win32/tablet/system-events-and-mouse-messages\n// Prefer to call this at the top of the message handler to avoid the possibility of other Win32 calls interfering with this.\nstatic ImGuiMouseSource ImGui_ImplWin32_GetMouseSourceFromMessageExtraInfo()\n{\n    LPARAM extra_info = ::GetMessageExtraInfo();\n    if ((extra_info & 0xFFFFFF80) == 0xFF515700)\n        return ImGuiMouseSource_Pen;\n    if ((extra_info & 0xFFFFFF80) == 0xFF515780)\n        return ImGuiMouseSource_TouchScreen;\n    return ImGuiMouseSource_Mouse;\n}\n\n// Win32 message handler (process Win32 mouse/keyboard inputs, etc.)\n// Call from your application's message handler. Keep calling your message handler unless this function returns TRUE.\n// When implementing your own backend, you can read the io.WantCaptureMouse, io.WantCaptureKeyboard flags to tell if Dear ImGui wants to use your inputs.\n// - When io.WantCaptureMouse is true, do not dispatch mouse input data to your main application, or clear/overwrite your copy of the mouse data.\n// - When io.WantCaptureKeyboard is true, do not dispatch keyboard input data to your main application, or clear/overwrite your copy of the keyboard data.\n// Generally you may always pass all inputs to Dear ImGui, and hide them from your application based on those two flags.\n// PS: We treat DBLCLK messages as regular mouse down messages, so this code will work on windows classes that have the CS_DBLCLKS flag set. Our own example app code doesn't set this flag.\n\n// Copy either line into your .cpp file to forward declare the function:\nextern IMGUI_IMPL_API LRESULT ImGui_ImplWin32_WndProcHandler(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam);                // Use ImGui::GetCurrentContext()\nextern IMGUI_IMPL_API LRESULT ImGui_ImplWin32_WndProcHandlerEx(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam, ImGuiIO& io); // Doesn't use ImGui::GetCurrentContext()\n\nIMGUI_IMPL_API LRESULT ImGui_ImplWin32_WndProcHandler(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)\n{\n    // Most backends don't have silent checks like this one, but we need it because WndProc are called early in CreateWindow().\n    // We silently allow both context or just only backend data to be nullptr.\n    if (ImGui::GetCurrentContext() == nullptr)\n        return 0;\n    return ImGui_ImplWin32_WndProcHandlerEx(hwnd, msg, wParam, lParam, ImGui::GetIO());\n}\n\n// This version is in theory thread-safe in the sense that no path should access ImGui::GetCurrentContext().\nIMGUI_IMPL_API LRESULT ImGui_ImplWin32_WndProcHandlerEx(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam, ImGuiIO& io)\n{\n    ImGui_ImplWin32_Data* bd = ImGui_ImplWin32_GetBackendData(io);\n    if (bd == nullptr)\n        return 0;\n    switch (msg)\n    {\n    case WM_MOUSEMOVE:\n    case WM_NCMOUSEMOVE:\n    {\n        // We need to call TrackMouseEvent in order to receive WM_MOUSELEAVE events\n        ImGuiMouseSource mouse_source = ImGui_ImplWin32_GetMouseSourceFromMessageExtraInfo();\n        const int area = (msg == WM_MOUSEMOVE) ? 1 : 2;\n        bd->MouseHwnd = hwnd;\n        if (bd->MouseTrackedArea != area)\n        {\n            TRACKMOUSEEVENT tme_cancel = { sizeof(tme_cancel), TME_CANCEL, hwnd, 0 };\n            TRACKMOUSEEVENT tme_track = { sizeof(tme_track), (DWORD)((area == 2) ? (TME_LEAVE | TME_NONCLIENT) : TME_LEAVE), hwnd, 0 };\n            if (bd->MouseTrackedArea != 0)\n                ::TrackMouseEvent(&tme_cancel);\n            ::TrackMouseEvent(&tme_track);\n            bd->MouseTrackedArea = area;\n        }\n        POINT mouse_pos = { (LONG)GET_X_LPARAM(lParam), (LONG)GET_Y_LPARAM(lParam) };\n        if (msg == WM_NCMOUSEMOVE && ::ScreenToClient(hwnd, &mouse_pos) == FALSE) // WM_NCMOUSEMOVE are provided in absolute coordinates.\n            return 0;\n        io.AddMouseSourceEvent(mouse_source);\n        io.AddMousePosEvent((float)mouse_pos.x, (float)mouse_pos.y);\n        return 0;\n    }\n    case WM_MOUSELEAVE:\n    case WM_NCMOUSELEAVE:\n    {\n        const int area = (msg == WM_MOUSELEAVE) ? 1 : 2;\n        if (bd->MouseTrackedArea == area)\n        {\n            if (bd->MouseHwnd == hwnd)\n                bd->MouseHwnd = nullptr;\n            bd->MouseTrackedArea = 0;\n            io.AddMousePosEvent(-FLT_MAX, -FLT_MAX);\n        }\n        return 0;\n    }\n    case WM_DESTROY:\n        if (bd->MouseHwnd == hwnd && bd->MouseTrackedArea != 0)\n        {\n            TRACKMOUSEEVENT tme_cancel = { sizeof(tme_cancel), TME_CANCEL, hwnd, 0 };\n            ::TrackMouseEvent(&tme_cancel);\n            bd->MouseHwnd = nullptr;\n            bd->MouseTrackedArea = 0;\n            io.AddMousePosEvent(-FLT_MAX, -FLT_MAX);\n        }\n        return 0;\n    case WM_LBUTTONDOWN: case WM_LBUTTONDBLCLK:\n    case WM_RBUTTONDOWN: case WM_RBUTTONDBLCLK:\n    case WM_MBUTTONDOWN: case WM_MBUTTONDBLCLK:\n    case WM_XBUTTONDOWN: case WM_XBUTTONDBLCLK:\n    {\n        ImGuiMouseSource mouse_source = ImGui_ImplWin32_GetMouseSourceFromMessageExtraInfo();\n        int button = 0;\n        if (msg == WM_LBUTTONDOWN || msg == WM_LBUTTONDBLCLK) { button = 0; }\n        if (msg == WM_RBUTTONDOWN || msg == WM_RBUTTONDBLCLK) { button = 1; }\n        if (msg == WM_MBUTTONDOWN || msg == WM_MBUTTONDBLCLK) { button = 2; }\n        if (msg == WM_XBUTTONDOWN || msg == WM_XBUTTONDBLCLK) { button = (GET_XBUTTON_WPARAM(wParam) == XBUTTON1) ? 3 : 4; }\n        if (bd->MouseButtonsDown == 0 && ::GetCapture() == nullptr)\n            ::SetCapture(hwnd); // Allow us to read mouse coordinates when dragging mouse outside of our window bounds.\n        bd->MouseButtonsDown |= 1 << button;\n        io.AddMouseSourceEvent(mouse_source);\n        io.AddMouseButtonEvent(button, true);\n        return 0;\n    }\n    case WM_LBUTTONUP:\n    case WM_RBUTTONUP:\n    case WM_MBUTTONUP:\n    case WM_XBUTTONUP:\n    {\n        ImGuiMouseSource mouse_source = ImGui_ImplWin32_GetMouseSourceFromMessageExtraInfo();\n        int button = 0;\n        if (msg == WM_LBUTTONUP) { button = 0; }\n        if (msg == WM_RBUTTONUP) { button = 1; }\n        if (msg == WM_MBUTTONUP) { button = 2; }\n        if (msg == WM_XBUTTONUP) { button = (GET_XBUTTON_WPARAM(wParam) == XBUTTON1) ? 3 : 4; }\n        bd->MouseButtonsDown &= ~(1 << button);\n        if (bd->MouseButtonsDown == 0 && ::GetCapture() == hwnd)\n            ::ReleaseCapture();\n        io.AddMouseSourceEvent(mouse_source);\n        io.AddMouseButtonEvent(button, false);\n        return 0;\n    }\n    case WM_MOUSEWHEEL:\n        io.AddMouseWheelEvent(0.0f, (float)GET_WHEEL_DELTA_WPARAM(wParam) / (float)WHEEL_DELTA);\n        return 0;\n    case WM_MOUSEHWHEEL:\n        io.AddMouseWheelEvent(-(float)GET_WHEEL_DELTA_WPARAM(wParam) / (float)WHEEL_DELTA, 0.0f);\n        return 0;\n    case WM_KEYDOWN:\n    case WM_KEYUP:\n    case WM_SYSKEYDOWN:\n    case WM_SYSKEYUP:\n    {\n        const bool is_key_down = (msg == WM_KEYDOWN || msg == WM_SYSKEYDOWN);\n        if (wParam < 256)\n        {\n            // Submit modifiers\n            ImGui_ImplWin32_UpdateKeyModifiers(io);\n\n            // Obtain virtual key code and convert to ImGuiKey\n            const ImGuiKey key = ImGui_ImplWin32_KeyEventToImGuiKey(wParam, lParam);\n            const int vk = (int)wParam;\n            const int scancode = (int)LOBYTE(HIWORD(lParam));\n\n            // Special behavior for VK_SNAPSHOT / ImGuiKey_PrintScreen as Windows doesn't emit the key down event.\n            if (key == ImGuiKey_PrintScreen && !is_key_down)\n                ImGui_ImplWin32_AddKeyEvent(io, key, true, vk, scancode);\n\n            // Submit key event\n            if (key != ImGuiKey_None)\n                ImGui_ImplWin32_AddKeyEvent(io, key, is_key_down, vk, scancode);\n\n            // Submit individual left/right modifier events\n            if (vk == VK_SHIFT)\n            {\n                // Important: Shift keys tend to get stuck when pressed together, missing key-up events are corrected in ImGui_ImplWin32_ProcessKeyEventsWorkarounds()\n                if (IsVkDown(VK_LSHIFT) == is_key_down) { ImGui_ImplWin32_AddKeyEvent(io, ImGuiKey_LeftShift, is_key_down, VK_LSHIFT, scancode); }\n                if (IsVkDown(VK_RSHIFT) == is_key_down) { ImGui_ImplWin32_AddKeyEvent(io, ImGuiKey_RightShift, is_key_down, VK_RSHIFT, scancode); }\n            }\n            else if (vk == VK_CONTROL)\n            {\n                if (IsVkDown(VK_LCONTROL) == is_key_down) { ImGui_ImplWin32_AddKeyEvent(io, ImGuiKey_LeftCtrl, is_key_down, VK_LCONTROL, scancode); }\n                if (IsVkDown(VK_RCONTROL) == is_key_down) { ImGui_ImplWin32_AddKeyEvent(io, ImGuiKey_RightCtrl, is_key_down, VK_RCONTROL, scancode); }\n            }\n            else if (vk == VK_MENU)\n            {\n                if (IsVkDown(VK_LMENU) == is_key_down) { ImGui_ImplWin32_AddKeyEvent(io, ImGuiKey_LeftAlt, is_key_down, VK_LMENU, scancode); }\n                if (IsVkDown(VK_RMENU) == is_key_down) { ImGui_ImplWin32_AddKeyEvent(io, ImGuiKey_RightAlt, is_key_down, VK_RMENU, scancode); }\n            }\n        }\n        return 0;\n    }\n    case WM_SETFOCUS:\n    case WM_KILLFOCUS:\n        io.AddFocusEvent(msg == WM_SETFOCUS);\n        return 0;\n    case WM_INPUTLANGCHANGE:\n        ImGui_ImplWin32_UpdateKeyboardCodePage(io);\n        return 0;\n    case WM_CHAR:\n        if (::IsWindowUnicode(hwnd))\n        {\n            // You can also use ToAscii()+GetKeyboardState() to retrieve characters.\n            if (wParam > 0 && wParam < 0x10000)\n                io.AddInputCharacterUTF16((unsigned short)wParam);\n        }\n        else\n        {\n            wchar_t wch = 0;\n            ::MultiByteToWideChar(bd->KeyboardCodePage, MB_PRECOMPOSED, (char*)&wParam, 1, &wch, 1);\n            io.AddInputCharacter(wch);\n        }\n        return 0;\n    case WM_SETCURSOR:\n        // This is required to restore cursor when transitioning from e.g resize borders to client area.\n        if (LOWORD(lParam) == HTCLIENT && ImGui_ImplWin32_UpdateMouseCursor(io, bd->LastMouseCursor))\n            return 1;\n        return 0;\n    case WM_DEVICECHANGE:\n#ifndef IMGUI_IMPL_WIN32_DISABLE_GAMEPAD\n        if ((UINT)wParam == DBT_DEVNODES_CHANGED)\n            bd->WantUpdateHasGamepad = true;\n#endif\n        return 0;\n    }\n    return 0;\n}\n\n\n//--------------------------------------------------------------------------------------------------------\n// DPI-related helpers (optional)\n//--------------------------------------------------------------------------------------------------------\n// - Use to enable DPI awareness without having to create an application manifest.\n// - Your own app may already do this via a manifest or explicit calls. This is mostly useful for our examples/ apps.\n// - In theory we could call simple functions from Windows SDK such as SetProcessDPIAware(), SetProcessDpiAwareness(), etc.\n//   but most of the functions provided by Microsoft require Windows 8.1/10+ SDK at compile time and Windows 8/10+ at runtime,\n//   neither we want to require the user to have. So we dynamically select and load those functions to avoid dependencies.\n//---------------------------------------------------------------------------------------------------------\n// This is the scheme successfully used by GLFW (from which we borrowed some of the code) and other apps aiming to be highly portable.\n// ImGui_ImplWin32_EnableDpiAwareness() is just a helper called by main.cpp, we don't call it automatically.\n// If you are trying to implement your own backend for your own engine, you may ignore that noise.\n//---------------------------------------------------------------------------------------------------------\n\n// Perform our own check with RtlVerifyVersionInfo() instead of using functions from <VersionHelpers.h> as they\n// require a manifest to be functional for checks above 8.1. See https://github.com/ocornut/imgui/issues/4200\nstatic BOOL _IsWindowsVersionOrGreater(WORD major, WORD minor, WORD)\n{\n    typedef LONG(WINAPI* PFN_RtlVerifyVersionInfo)(OSVERSIONINFOEXW*, ULONG, ULONGLONG);\n    static PFN_RtlVerifyVersionInfo RtlVerifyVersionInfoFn = nullptr;\n    if (RtlVerifyVersionInfoFn == nullptr)\n        if (HMODULE ntdllModule = ::GetModuleHandleA(\"ntdll.dll\"))\n            RtlVerifyVersionInfoFn = (PFN_RtlVerifyVersionInfo)GetProcAddress(ntdllModule, \"RtlVerifyVersionInfo\");\n    if (RtlVerifyVersionInfoFn == nullptr)\n        return FALSE;\n\n    RTL_OSVERSIONINFOEXW versionInfo = { };\n    ULONGLONG conditionMask = 0;\n    versionInfo.dwOSVersionInfoSize = sizeof(RTL_OSVERSIONINFOEXW);\n    versionInfo.dwMajorVersion = major;\n    versionInfo.dwMinorVersion = minor;\n    VER_SET_CONDITION(conditionMask, VER_MAJORVERSION, VER_GREATER_EQUAL);\n    VER_SET_CONDITION(conditionMask, VER_MINORVERSION, VER_GREATER_EQUAL);\n    return (RtlVerifyVersionInfoFn(&versionInfo, VER_MAJORVERSION | VER_MINORVERSION, conditionMask) == 0) ? TRUE : FALSE;\n}\n\n#define _IsWindowsVistaOrGreater()   _IsWindowsVersionOrGreater(HIBYTE(0x0600), LOBYTE(0x0600), 0) // _WIN32_WINNT_VISTA\n#define _IsWindows8OrGreater()       _IsWindowsVersionOrGreater(HIBYTE(0x0602), LOBYTE(0x0602), 0) // _WIN32_WINNT_WIN8\n#define _IsWindows8Point1OrGreater() _IsWindowsVersionOrGreater(HIBYTE(0x0603), LOBYTE(0x0603), 0) // _WIN32_WINNT_WINBLUE\n#define _IsWindows10OrGreater()      _IsWindowsVersionOrGreater(HIBYTE(0x0A00), LOBYTE(0x0A00), 0) // _WIN32_WINNT_WINTHRESHOLD / _WIN32_WINNT_WIN10\n\n#ifndef DPI_ENUMS_DECLARED\ntypedef enum { PROCESS_DPI_UNAWARE = 0, PROCESS_SYSTEM_DPI_AWARE = 1, PROCESS_PER_MONITOR_DPI_AWARE = 2 } PROCESS_DPI_AWARENESS;\ntypedef enum { MDT_EFFECTIVE_DPI = 0, MDT_ANGULAR_DPI = 1, MDT_RAW_DPI = 2, MDT_DEFAULT = MDT_EFFECTIVE_DPI } MONITOR_DPI_TYPE;\n#endif\n#ifndef _DPI_AWARENESS_CONTEXTS_\nDECLARE_HANDLE(DPI_AWARENESS_CONTEXT);\n#define DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE    (DPI_AWARENESS_CONTEXT)-3\n#endif\n#ifndef DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2\n#define DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2 (DPI_AWARENESS_CONTEXT)-4\n#endif\ntypedef HRESULT(WINAPI* PFN_SetProcessDpiAwareness)(PROCESS_DPI_AWARENESS);                     // Shcore.lib + dll, Windows 8.1+\ntypedef HRESULT(WINAPI* PFN_GetDpiForMonitor)(HMONITOR, MONITOR_DPI_TYPE, UINT*, UINT*);        // Shcore.lib + dll, Windows 8.1+\ntypedef DPI_AWARENESS_CONTEXT(WINAPI* PFN_SetThreadDpiAwarenessContext)(DPI_AWARENESS_CONTEXT); // User32.lib + dll, Windows 10 v1607+ (Creators Update)\n\n// Helper function to enable DPI awareness without setting up a manifest\nvoid ImGui_ImplWin32_EnableDpiAwareness()\n{\n    if (_IsWindows10OrGreater())\n    {\n        static HINSTANCE user32_dll = ::LoadLibraryA(\"user32.dll\"); // Reference counted per-process\n        if (PFN_SetThreadDpiAwarenessContext SetThreadDpiAwarenessContextFn = (PFN_SetThreadDpiAwarenessContext)::GetProcAddress(user32_dll, \"SetThreadDpiAwarenessContext\"))\n        {\n            SetThreadDpiAwarenessContextFn(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2);\n            return;\n        }\n    }\n    if (_IsWindows8Point1OrGreater())\n    {\n        static HINSTANCE shcore_dll = ::LoadLibraryA(\"shcore.dll\"); // Reference counted per-process\n        if (PFN_SetProcessDpiAwareness SetProcessDpiAwarenessFn = (PFN_SetProcessDpiAwareness)::GetProcAddress(shcore_dll, \"SetProcessDpiAwareness\"))\n        {\n            SetProcessDpiAwarenessFn(PROCESS_PER_MONITOR_DPI_AWARE);\n            return;\n        }\n    }\n#if _WIN32_WINNT >= 0x0600\n    ::SetProcessDPIAware();\n#endif\n}\n\n#if defined(_MSC_VER) && !defined(NOGDI)\n#pragma comment(lib, \"gdi32\")   // Link with gdi32.lib for GetDeviceCaps(). MinGW will require linking with '-lgdi32'\n#endif\n\nfloat ImGui_ImplWin32_GetDpiScaleForMonitor(void* monitor)\n{\n    UINT xdpi = 96, ydpi = 96;\n    if (_IsWindows8Point1OrGreater())\n    {\n        static HINSTANCE shcore_dll = ::LoadLibraryA(\"shcore.dll\"); // Reference counted per-process\n        static PFN_GetDpiForMonitor GetDpiForMonitorFn = nullptr;\n        if (GetDpiForMonitorFn == nullptr && shcore_dll != nullptr)\n            GetDpiForMonitorFn = (PFN_GetDpiForMonitor)::GetProcAddress(shcore_dll, \"GetDpiForMonitor\");\n        if (GetDpiForMonitorFn != nullptr)\n        {\n            GetDpiForMonitorFn((HMONITOR)monitor, MDT_EFFECTIVE_DPI, &xdpi, &ydpi);\n            IM_ASSERT(xdpi == ydpi); // Please contact me if you hit this assert!\n            return xdpi / 96.0f;\n        }\n    }\n#ifndef NOGDI\n    const HDC dc = ::GetDC(nullptr);\n    xdpi = ::GetDeviceCaps(dc, LOGPIXELSX);\n    ydpi = ::GetDeviceCaps(dc, LOGPIXELSY);\n    IM_ASSERT(xdpi == ydpi); // Please contact me if you hit this assert!\n    ::ReleaseDC(nullptr, dc);\n#endif\n    return xdpi / 96.0f;\n}\n\nfloat ImGui_ImplWin32_GetDpiScaleForHwnd(void* hwnd)\n{\n    HMONITOR monitor = ::MonitorFromWindow((HWND)hwnd, MONITOR_DEFAULTTONEAREST);\n    return ImGui_ImplWin32_GetDpiScaleForMonitor(monitor);\n}\n\n//---------------------------------------------------------------------------------------------------------\n// Transparency related helpers (optional)\n//--------------------------------------------------------------------------------------------------------\n\n#if defined(_MSC_VER)\n#pragma comment(lib, \"dwmapi\")  // Link with dwmapi.lib. MinGW will require linking with '-ldwmapi'\n#endif\n\n// [experimental]\n// Borrowed from GLFW's function updateFramebufferTransparency() in src/win32_window.c\n// (the Dwm* functions are Vista era functions but we are borrowing logic from GLFW)\nvoid ImGui_ImplWin32_EnableAlphaCompositing(void* hwnd)\n{\n    if (!_IsWindowsVistaOrGreater())\n        return;\n\n    BOOL composition;\n    if (FAILED(::DwmIsCompositionEnabled(&composition)) || !composition)\n        return;\n\n    BOOL opaque;\n    DWORD color;\n    if (_IsWindows8OrGreater() || (SUCCEEDED(::DwmGetColorizationColor(&color, &opaque)) && !opaque))\n    {\n        HRGN region = ::CreateRectRgn(0, 0, -1, -1);\n        DWM_BLURBEHIND bb = {};\n        bb.dwFlags = DWM_BB_ENABLE | DWM_BB_BLURREGION;\n        bb.hRgnBlur = region;\n        bb.fEnable = TRUE;\n        ::DwmEnableBlurBehindWindow((HWND)hwnd, &bb);\n        ::DeleteObject(region);\n    }\n    else\n    {\n        DWM_BLURBEHIND bb = {};\n        bb.dwFlags = DWM_BB_ENABLE;\n        ::DwmEnableBlurBehindWindow((HWND)hwnd, &bb);\n    }\n}\n\n//---------------------------------------------------------------------------------------------------------\n\nIMGUI_IMPL_API void ImGui_ImplOpenVR_NewFrame()\n{\n    ImGuiIO& io = ImGui::GetIO();\n    ImGui_ImplWin32_Data* bd = ImGui_ImplWin32_GetBackendData();\n    IM_ASSERT(bd != NULL && \"Did you call ImGui_ImplWin32_Init()?\");\n\n    // Setup time step\n    INT64 current_time = 0;\n    ::QueryPerformanceCounter((LARGE_INTEGER*)&current_time);\n    io.DeltaTime = (float)(current_time - bd->Time) / bd->TicksPerSecond;\n    bd->Time = current_time;\n\n    //No Gamepad support for now\n\n    //Process on-screen keyboard queue\n    if ( (!ImGui::IsKeyDown(ImGuiKey_Backspace)) && (!ImGui::IsKeyDown(ImGuiKey_Enter)) )\n    {\n        if ( (io.InputQueueCharacters.empty()) && (!g_OnScreenKeyboardQueue.empty()) )\n        {\n            g_OnScreenKeyboardQueueDelay++;\n\n            //This delay stuff may make this hacky and not that visually pleasing, but it seems required to reliably execute \n            //the multiple backspaces SteamVR sends to autocorrect spelling when using suggestions on the on-screen keyboard\n            //...meanwhile Valve removed suggestions from the minimal on-screen keyboard so this isn't technically needed anymore. \n            //Doesn't hurt to have it, though.\n            if (g_OnScreenKeyboardQueueDelay == 2)  \n            {\n                g_OnScreenKeyboardQueueDelay = 0;\n                ImGui_ImplOpenVR_AddInputFromOSK(g_OnScreenKeyboardQueue.front().c_str());\n\n                g_OnScreenKeyboardQueue.pop();\n            }\n        }\n    }\n}\n\n// Process OpenVR events related to ImGui inputs\n// returns true if the event was handled\nIMGUI_IMPL_API bool ImGui_ImplOpenVR_InputEventHandler(const vr::VREvent_t& vr_event, ImVec4* mouse_clamp_rect)\n{\n    if (ImGui::GetCurrentContext() == nullptr)\n        return false;\n\n    ImGuiIO& io = ImGui::GetIO();\n\n    switch (vr_event.eventType)\n    {\n        case vr::VREvent_MouseMove:\n        {\n            ImVec2 mouse_pos(vr_event.data.mouse.x, -vr_event.data.mouse.y + io.DisplaySize.y);\n\n            if (mouse_clamp_rect != nullptr)\n            {\n                mouse_pos.x = ImClamp(mouse_pos.x, mouse_clamp_rect->x, mouse_clamp_rect->z);\n                mouse_pos.y = ImClamp(mouse_pos.y, mouse_clamp_rect->y, mouse_clamp_rect->w);\n            }\n\n            io.AddMousePosEvent(mouse_pos.x, mouse_pos.y);\n            return true;\n        }\n        case vr::VREvent_FocusLeave:\n        {\n            //Pointer left the overlay\n            io.AddMousePosEvent(-FLT_MAX, -FLT_MAX);\n\n            //Release mouse buttons if they're still down (not the case in most situations)\n            for (ImGuiMouseButton button = ImGuiMouseButton_Left; button < ImGuiMouseButton_COUNT; ++button)\n            {\n                io.AddMouseButtonEvent(button, false);\n            }\n\n            return true;\n        }\n        case vr::VREvent_MouseButtonDown:\n        case vr::VREvent_MouseButtonUp:\n        {\n            int button = ImGuiMouseButton_Left;\n\n            switch (vr_event.data.mouse.button)\n            {\n                case vr::VRMouseButton_Left:    button = ImGuiMouseButton_Left;       break;\n                case vr::VRMouseButton_Right:   button = ImGuiMouseButton_Right;      break;\n                case vr::VRMouseButton_Middle:  button = ImGuiMouseButton_Middle;     break;\n                case VRMouseButton_DP_Aux01:    button = ImGuiMouseButton_Middle + 1; break;\n                case VRMouseButton_DP_Aux02:    button = ImGuiMouseButton_Middle + 2; break;\n            }\n\n            io.AddMouseButtonEvent(button, (vr_event.eventType == vr::VREvent_MouseButtonDown));\n            return true;\n        }\n        case vr::VREvent_ScrollDiscrete:\n        case vr::VREvent_ScrollSmooth:\n        {\n            io.AddMouseWheelEvent(vr_event.data.scroll.xdelta, vr_event.data.scroll.ydelta);\n            return true;\n        }\n        case vr::VREvent_KeyboardCharInput:\n        {\n            ImGui_ImplOpenVR_AddInputFromOSK(vr_event.data.keyboard.cNewInput);\n\n            return true;\n        }\n        case vr::VREvent_KeyboardClosed:\n        {\n            ImGui_ImplOpenVR_InputOnVRKeyboardClosed();\n\n            return true;\n        }\n    }\n\n    return false;\n}\n\nIMGUI_IMPL_API vr::EVROverlayError ImGui_ImplOpenVR_InputResetVRKeyboard(vr::VROverlayHandle_t overlay_handle)\n{\n    vr::EVROverlayError keyboard_error = vr::VROverlayError_None;\n\n    ImGuiIO& io = ImGui::GetIO();\n\n    //Reset these keys in case they were pressed by the VR keyboard\n    io.AddKeyEvent(ImGuiKey_Backspace, false);\n    io.AddKeyEvent(ImGuiKey_Enter,     false);\n\n    //Show keyboard if needed\n    if (io.WantTextInput)\n    {\n        if ( (!g_OnScreenKeyboardShown) && (!g_OnScreenKeyboardDismissedLastFrame) )\n        {\n            keyboard_error = vr::VROverlay()->ShowKeyboardForOverlay(overlay_handle, vr::k_EGamepadTextInputModeNormal, vr::k_EGamepadTextInputLineModeSingleLine,\n                                                                     vr::KeyboardFlag_Minimal, \"ImGuiInput\", 1024, \"\", 0);\n\n            if (keyboard_error == vr::VROverlayError_None)\n            {\n                //Covers whole overlay\n                vr::HmdRect2_t keyrect;\n                keyrect.vTopLeft = {0.0f, 1.0f};\n                keyrect.vBottomRight = {1.0f, 0.0f};\n\n                vr::VROverlay()->SetKeyboardPositionForOverlay(overlay_handle, keyrect);    //Avoid covering the overlay with the keyboard\n\n                g_OnScreenKeyboardShown = true;\n            }\n        }\n    }\n    else if (g_OnScreenKeyboardShown)\n    {\n        vr::VROverlay()->HideKeyboard();\n\n        g_OnScreenKeyboardShown = false;\n    }\n\n    g_OnScreenKeyboardDismissedLastFrame = false;\n\n    return keyboard_error;\n}\n\nIMGUI_IMPL_API void ImGui_ImplOpenVR_InputOnVRKeyboardClosed()\n{\n    ImGuiIO& io = ImGui::GetIO();\n\n    if (io.WantTextInput)\n    {\n        ImGui::ClearActiveID();\n        io.WantTextInput = false;\n        g_OnScreenKeyboardDismissedLastFrame = true; //Widgets will request the keyboard for the next frame anyways, so we prevent showing it again right away with this flag\n    }\n\n    g_OnScreenKeyboardShown = false;\n}\n\nIMGUI_IMPL_API void ImGui_ImplOpenVR_SetIntersectionMaskFromWindows(vr::VROverlayHandle_t* overlay_handles, size_t overlay_count, std::vector<vr::VROverlayIntersectionMaskPrimitive_t>* primitives_out)\n{\n    ImGuiContext& g = *ImGui::GetCurrentContext();\n\n    std::vector<vr::VROverlayIntersectionMaskPrimitive_t> primitives;\n    primitives.reserve(g.Windows.Size); //Avoid resizing in the loop\n\n    for (int n = 0; n < g.Windows.Size; n++)\n    {\n        ImGuiWindow* window = g.Windows[n];\n        if (!window->WasActive)\n            continue;\n\n        if (!(window->Flags & ImGuiWindowFlags_ChildWindow))\n        {\n            vr::VROverlayIntersectionMaskPrimitive_t primitive;\n            primitive.m_nPrimitiveType = vr::OverlayIntersectionPrimitiveType_Rectangle;\n            primitive.m_Primitive.m_Rectangle.m_flTopLeftX = window->OuterRectClipped.GetTL().x;\n            primitive.m_Primitive.m_Rectangle.m_flTopLeftY = window->OuterRectClipped.GetTL().y;\n            primitive.m_Primitive.m_Rectangle.m_flWidth    = window->OuterRectClipped.GetWidth();\n            primitive.m_Primitive.m_Rectangle.m_flHeight   = window->OuterRectClipped.GetHeight();\n\n            primitives.push_back(primitive);\n        }\n    }\n\n    for (size_t i = 0; i < overlay_count; ++i)\n    {\n        vr::VROverlay()->SetOverlayIntersectionMask(overlay_handles[i], primitives.data(), (uint32_t)primitives.size());\n    }\n\n    if (primitives_out != nullptr)\n    {\n        *primitives_out = std::move(primitives);\n    }\n}\n\nvoid ImGui_ImplOpenVR_AddInputFromOSK(const char* input)\n{\n    ImGuiIO& io = ImGui::GetIO();\n\n    //If backspace is already being pressed, we have to queue the remaining inputs up and delay them 1 frame\n    if (ImGui::IsKeyDown(ImGuiKey_Backspace))\n    {\n        g_OnScreenKeyboardQueue.push(input);\n    }\n    else \n    {\n        if (*input == '\\b')\n        {\n            io.AddKeyEvent(ImGuiKey_Backspace, true);\n        }\n        else if (*input == '\\n')\n        {\n            io.AddKeyEvent(ImGuiKey_Enter, true);\n        }\n        else\n        {\n            io.AddInputCharactersUTF8(input);\n        }\n    }\n\n}\n\n#if defined(__GNUC__)\n#pragma GCC diagnostic pop\n#endif\n#if defined(__clang__)\n#pragma clang diagnostic pop\n#endif\n\n#endif // #ifndef IMGUI_DISABLE\n"
  },
  {
    "path": "src/DesktopPlusUI/imgui_win32_dx11_openvr/imgui_impl_win32_openvr.h",
    "content": "// Desktop+UI: Modified for OpenVR compatibility\n\n// dear imgui: Platform Backend for Windows (standard windows API for 32-bits AND 64-bits applications)\n// This needs to be used along with a Renderer (e.g. DirectX11, OpenGL3, Vulkan..)\n\n// Implemented features:\n//  [X] Platform: Clipboard support (for Win32 this is actually part of core dear imgui)\n//  [X] Platform: Mouse support. Can discriminate Mouse/TouchScreen/Pen.\n//  [X] Platform: Keyboard support. Since 1.87 we are using the io.AddKeyEvent() function. Pass ImGuiKey values to all key functions e.g. ImGui::IsKeyPressed(ImGuiKey_Space). [Legacy VK_* values are obsolete since 1.87 and not supported since 1.91.5]\n//  [X] Platform: Gamepad support. Enabled with 'io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad'.\n//  [X] Platform: Mouse cursor shape and visibility (ImGuiBackendFlags_HasMouseCursors). Disable with 'io.ConfigFlags |= ImGuiConfigFlags_NoMouseCursorChange'.\n\n// You can use unmodified imgui_impl_* files in your project. See examples/ folder for examples of using this.\n// Prefer including the entire imgui/ repository into your project (either as a copy or as a submodule), and only build the backends you need.\n// Learn about Dear ImGui:\n// - FAQ                  https://dearimgui.com/faq\n// - Getting Started      https://dearimgui.com/getting-started\n// - Documentation        https://dearimgui.com/docs (same as your local docs/ folder).\n// - Introduction, links and more at the top of imgui.cpp\n\n#pragma once\n#include \"imgui.h\"      // IMGUI_IMPL_API\n#ifndef IMGUI_DISABLE\n\n#include <cstdint>\n\n// Follow \"Getting Started\" link and check examples/ folder to learn about using backends!\nIMGUI_IMPL_API bool     ImGui_ImplWin32_Init(void* hwnd);\nIMGUI_IMPL_API bool     ImGui_ImplWin32_InitForOpenGL(void* hwnd);\nIMGUI_IMPL_API void     ImGui_ImplWin32_Shutdown();\nIMGUI_IMPL_API void     ImGui_ImplWin32_NewFrame();\nIMGUI_IMPL_API ImGuiKey ImGui_ImplWin32_KeyEventToImGuiKey(uint64_t wParam, int64_t lParam);  //Exposed for VRKeyboard\n\n// Win32 message handler your application need to call.\n// - Intentionally commented out in a '#if 0' block to avoid dragging dependencies on <windows.h> from this helper.\n// - You should COPY the line below into your .cpp code to forward declare the function and then you can call it.\n// - Call from your application's message handler. Keep calling your message handler unless this function returns TRUE.\n\n#if 0\nextern IMGUI_IMPL_API LRESULT ImGui_ImplWin32_WndProcHandler(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam);\n#endif\n\n// DPI-related helpers (optional)\n// - Use to enable DPI awareness without having to create an application manifest.\n// - Your own app may already do this via a manifest or explicit calls. This is mostly useful for our examples/ apps.\n// - In theory we could call simple functions from Windows SDK such as SetProcessDPIAware(), SetProcessDpiAwareness(), etc.\n//   but most of the functions provided by Microsoft require Windows 8.1/10+ SDK at compile time and Windows 8/10+ at runtime,\n//   neither we want to require the user to have. So we dynamically select and load those functions to avoid dependencies.\nIMGUI_IMPL_API void     ImGui_ImplWin32_EnableDpiAwareness();\nIMGUI_IMPL_API float    ImGui_ImplWin32_GetDpiScaleForHwnd(void* hwnd);       // HWND hwnd\nIMGUI_IMPL_API float    ImGui_ImplWin32_GetDpiScaleForMonitor(void* monitor); // HMONITOR monitor\n\n// Transparency related helpers (optional) [experimental]\n// - Use to enable alpha compositing transparency with the desktop.\n// - Use together with e.g. clearing your framebuffer with zero-alpha.\nIMGUI_IMPL_API void     ImGui_ImplWin32_EnableAlphaCompositing(void* hwnd);   // HWND hwnd\n\n#include <vector>\n#include \"openvr.h\"\n\n// If this is called, do not call ImGui_ImplWin32_NewFrame(). ImGui_ImplWin32_NewFrame() can still be used for a normal desktop mode or similar.\nIMGUI_IMPL_API void ImGui_ImplOpenVR_NewFrame();\n\n// Handler for OpenVR Events\n// mouse_clamp_rect is optional rectangle used to clamp mouse coordinates (x min, y min, x max, y max)\n// Returns true if the event was handled\nIMGUI_IMPL_API bool ImGui_ImplOpenVR_InputEventHandler(const vr::VREvent_t& vr_event, ImVec4* mouse_clamp_rect = nullptr);\n\n// Resets internal extra keyboard state for VR keyboard handling\n// This needs to be called before a NewFrame and handling the OpenVR events\n// Returns the overlay error returned by IVROverlay::ShowKeyboardForOverlay()\nIMGUI_IMPL_API vr::EVROverlayError ImGui_ImplOpenVR_InputResetVRKeyboard(vr::VROverlayHandle_t overlay_handle);\n\n// Called on VREvent_KeyboardClosed\n// Exposed for application to call if the keyboard is handled by something else\nIMGUI_IMPL_API void ImGui_ImplOpenVR_InputOnVRKeyboardClosed();\n\n// Called on VREvent_KeyboardCharInput\n// Exposed for application to call if the keyboard is handled by something else\nIMGUI_IMPL_API void ImGui_ImplOpenVR_AddInputFromOSK(const char* input);\n\n// Set overlay intersection mask from current top-level window outer rects, optionally writes primitives sent to SteamVR into primitves_out\nIMGUI_IMPL_API void ImGui_ImplOpenVR_SetIntersectionMaskFromWindows(vr::VROverlayHandle_t* overlay_handles, size_t overlay_count, \n                                                                    std::vector<vr::VROverlayIntersectionMaskPrimitive_t>* primitives_out = nullptr);\n#endif // #ifndef IMGUI_DISABLE\n"
  },
  {
    "path": "src/DesktopPlusUI/implot/implot.cpp",
    "content": "// MIT License\n\n// Copyright (c) 2023 Evan Pezent\n\n// Permission is hereby granted, free of charge, to any person obtaining a copy\n// of this software and associated documentation files (the \"Software\"), to deal\n// in the Software without restriction, including without limitation the rights\n// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n// copies of the Software, and to permit persons to whom the Software is\n// furnished to do so, subject to the following conditions:\n\n// The above copyright notice and this permission notice shall be included in all\n// copies or substantial portions of the Software.\n\n// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n// SOFTWARE.\n\n// ImPlot v0.16\n\n/*\n\nAPI BREAKING CHANGES\n====================\nOccasionally introducing changes that are breaking the API. We try to make the breakage minor and easy to fix.\nBelow is a change-log of API breaking changes only. If you are using one of the functions listed, expect to have to fix some code.\nWhen you are not sure about a old symbol or function name, try using the Search/Find function of your IDE to look for comments or references in all implot files.\nYou can read releases logs https://github.com/epezent/implot/releases for more details.\n\n- 2023/06/26 (0.15) - Various build fixes related to updates in Dear ImGui internals.\n- 2022/11/25 (0.15) - Make PlotText honor ImPlotItemFlags_NoFit.\n- 2022/06/19 (0.14) - The signature of ColormapScale has changed to accommodate a new ImPlotColormapScaleFlags parameter\n- 2022/06/17 (0.14) - **IMPORTANT** All PlotX functions now take an ImPlotX_Flags `flags` parameter. Where applicable, it is located before the existing `offset` and `stride` parameters.\n                      If you were providing offset and stride values, you will need to update your function call to include a `flags` value. If you fail to do this, you will likely see\n                      unexpected results or crashes without a compiler warning since these three are all default args. We apologize for the inconvenience, but this was a necessary evil.\n                    - PlotBarsH has been removed; use PlotBars + ImPlotBarsFlags_Horizontal instead\n                    - PlotErrorBarsH has been removed; use PlotErrorBars + ImPlotErrorBarsFlags_Horizontal\n                    - PlotHistogram/PlotHistogram2D signatures changed; `cumulative`, `density`, and `outliers` options now specified via ImPlotHistogramFlags\n                    - PlotPieChart signature changed; `normalize` option now specified via ImPlotPieChartFlags\n                    - PlotText signature changes; `vertical` option now specified via `ImPlotTextFlags_Vertical`\n                    - `PlotVLines` and `PlotHLines` replaced with `PlotInfLines` (+ ImPlotInfLinesFlags_Horizontal )\n                    - arguments of ImPlotGetter have been reversed to be consistent with other API callbacks\n                    - SetupAxisScale + ImPlotScale have replaced ImPlotAxisFlags_LogScale and ImPlotAxisFlags_Time flags\n                    - ImPlotFormatters should now return an int indicating the size written\n                    - the signature of ImPlotGetter has been reversed so that void* user_data is the last argument and consistent with other callbacks\n- 2021/10/19 (0.13) - MAJOR API OVERHAUL! See #168 and #272\n                    - TRIVIAL RENAME:\n                      - ImPlotLimits                              -> ImPlotRect\n                      - ImPlotYAxis_                              -> ImAxis_\n                      - SetPlotYAxis                              -> SetAxis\n                      - BeginDragDropTarget                       -> BeginDragDropTargetPlot\n                      - BeginDragDropSource                       -> BeginDragDropSourcePlot\n                      - ImPlotFlags_NoMousePos                    -> ImPlotFlags_NoMouseText\n                      - SetNextPlotLimits                         -> SetNextAxesLimits\n                      - SetMouseTextLocation                      -> SetupMouseText\n                    - SIGNATURE MODIFIED:\n                      - PixelsToPlot/PlotToPixels                 -> added optional X-Axis arg\n                      - GetPlotMousePos                           -> added optional X-Axis arg\n                      - GetPlotLimits                             -> added optional X-Axis arg\n                      - GetPlotSelection                          -> added optional X-Axis arg\n                      - DragLineX/Y/DragPoint                     -> now takes int id; removed labels (render with Annotation/Tag instead)\n                    - REPLACED:\n                      - IsPlotXAxisHovered/IsPlotXYAxisHovered    -> IsAxisHovered(ImAxis)\n                      - BeginDragDropTargetX/BeginDragDropTargetY -> BeginDragDropTargetAxis(ImAxis)\n                      - BeginDragDropSourceX/BeginDragDropSourceY -> BeginDragDropSourceAxis(ImAxis)\n                      - ImPlotCol_XAxis, ImPlotCol_YAxis1, etc.   -> ImPlotCol_AxisText (push/pop this around SetupAxis to style individual axes)\n                      - ImPlotCol_XAxisGrid, ImPlotCol_Y1AxisGrid -> ImPlotCol_AxisGrid (push/pop this around SetupAxis to style individual axes)\n                      - SetNextPlotLimitsX/Y                      -> SetNextAxisLimits(ImAxis)\n                      - LinkNextPlotLimits                        -> SetNextAxisLinks(ImAxis)\n                      - FitNextPlotAxes                           -> SetNextAxisToFit(ImAxis)/SetNextAxesToFit\n                      - SetLegendLocation                         -> SetupLegend\n                      - ImPlotFlags_NoHighlight                   -> ImPlotLegendFlags_NoHighlight\n                      - ImPlotOrientation                         -> ImPlotLegendFlags_Horizontal\n                      - Annotate                                  -> Annotation\n                    - REMOVED:\n                      - GetPlotQuery, SetPlotQuery, IsPlotQueried -> use DragRect\n                      - SetNextPlotTicksX, SetNextPlotTicksY      -> use SetupAxisTicks\n                      - SetNextPlotFormatX, SetNextPlotFormatY    -> use SetupAxisFormat\n                      - AnnotateClamped                           -> use Annotation(bool clamp = true)\n                    - OBSOLETED:\n                      - BeginPlot (original signature)            -> use simplified signature + Setup API\n- 2021/07/30 (0.12) - The offset argument of `PlotXG` functions was been removed. Implement offsetting in your getter callback instead.\n- 2021/03/08 (0.9)  - SetColormap and PushColormap(ImVec4*) were removed. Use AddColormap for custom colormap support. LerpColormap was changed to SampleColormap.\n                      ShowColormapScale was changed to ColormapScale and requires additional arguments.\n- 2021/03/07 (0.9)  - The signature of ShowColormapScale was modified to accept a ImVec2 size.\n- 2021/02/28 (0.9)  - BeginLegendDragDropSource was changed to BeginDragDropSourceItem with a number of other drag and drop improvements.\n- 2021/01/18 (0.9)  - The default behavior for opening context menus was change from double right-click to single right-click. ImPlotInputMap and related functions were moved\n                      to implot_internal.h due to its immaturity.\n- 2020/10/16 (0.8)  - ImPlotStyleVar_InfoPadding was changed to ImPlotStyleVar_MousePosPadding\n- 2020/09/10 (0.8)  - The single array versions of PlotLine, PlotScatter, PlotStems, and PlotShaded were given additional arguments for x-scale and x0.\n- 2020/09/07 (0.8)  - Plotting functions which accept a custom getter function pointer have been post-fixed with a G (e.g. PlotLineG)\n- 2020/09/06 (0.7)  - Several flags under ImPlotFlags and ImPlotAxisFlags were inverted (e.g. ImPlotFlags_Legend -> ImPlotFlags_NoLegend) so that the default flagset\n                      is simply 0. This more closely matches ImGui's style and makes it easier to enable non-default but commonly used flags (e.g. ImPlotAxisFlags_Time).\n- 2020/08/28 (0.5)  - ImPlotMarker_ can no longer be combined with bitwise OR, |. This features caused unecessary slow-down, and almost no one used it.\n- 2020/08/25 (0.5)  - ImPlotAxisFlags_Scientific was removed. Logarithmic axes automatically uses scientific notation.\n- 2020/08/17 (0.5)  - PlotText was changed so that text is centered horizontally and vertically about the desired point.\n- 2020/08/16 (0.5)  - An ImPlotContext must be explicitly created and destroyed now with `CreateContext` and `DestroyContext`. Previously, the context was statically initialized in this source file.\n- 2020/06/13 (0.4)  - The flags `ImPlotAxisFlag_Adaptive` and `ImPlotFlags_Cull` were removed. Both are now done internally by default.\n- 2020/06/03 (0.3)  - The signature and behavior of PlotPieChart was changed so that data with sum less than 1 can optionally be normalized. The label format can now be specified as well.\n- 2020/06/01 (0.3)  - SetPalette was changed to `SetColormap` for consistency with other plotting libraries. `RestorePalette` was removed. Use `SetColormap(ImPlotColormap_Default)`.\n- 2020/05/31 (0.3)  - Plot functions taking custom ImVec2* getters were removed. Use the ImPlotPoint* getter versions instead.\n- 2020/05/29 (0.3)  - The signature of ImPlotLimits::Contains was changed to take two doubles instead of ImVec2\n- 2020/05/16 (0.2)  - All plotting functions were reverted to being prefixed with \"Plot\" to maintain a consistent VerbNoun style. `Plot` was split into `PlotLine`\n                      and `PlotScatter` (however, `PlotLine` can still be used to plot scatter points as `Plot` did before.). `Bar` is not `PlotBars`, to indicate\n                      that multiple bars will be plotted.\n- 2020/05/13 (0.2)  - `ImMarker` was change to `ImPlotMarker` and `ImAxisFlags` was changed to `ImPlotAxisFlags`.\n- 2020/05/11 (0.2)  - `ImPlotFlags_Selection` was changed to `ImPlotFlags_BoxSelect`\n- 2020/05/11 (0.2)  - The namespace ImGui:: was replaced with ImPlot::. As a result, the following additional changes were made:\n                      - Functions that were prefixed or decorated with the word \"Plot\" have been truncated. E.g., `ImGui::PlotBars` is now just `ImPlot::Bar`.\n                        It should be fairly obvious what was what.\n                      - Some functions have been given names that would have otherwise collided with the ImGui namespace. This has been done to maintain a consistent\n                        style with ImGui. E.g., 'ImGui::PushPlotStyleVar` is now 'ImPlot::PushStyleVar'.\n- 2020/05/10 (0.2)  - The following function/struct names were changes:\n                     - ImPlotRange       -> ImPlotLimits\n                     - GetPlotRange()    -> GetPlotLimits()\n                     - SetNextPlotRange  -> SetNextPlotLimits\n                     - SetNextPlotRangeX -> SetNextPlotLimitsX\n                     - SetNextPlotRangeY -> SetNextPlotLimitsY\n- 2020/05/10 (0.2)  - Plot queries are pixel based by default. Query rects that maintain relative plot position have been removed. This was done to support multi-y-axis.\n\n*/\n\n#define IMGUI_DEFINE_MATH_OPERATORS\n#include \"implot.h\"\n#include \"implot_internal.h\"\n\n#include <stdlib.h>\n\n// Support for pre-1.82 versions. Users on 1.82+ can use 0 (default) flags to mean \"all corners\" but in order to support older versions we are more explicit.\n#if (IMGUI_VERSION_NUM < 18102) && !defined(ImDrawFlags_RoundCornersAll)\n#define ImDrawFlags_RoundCornersAll ImDrawCornerFlags_All\n#endif\n\n// Support for pre-1.89.7 versions.\n#if (IMGUI_VERSION_NUM < 18966)\n#define ImGuiButtonFlags_AllowOverlap ImGuiButtonFlags_AllowItemOverlap\n#endif\n\n// Visual Studio warnings\n#ifdef _MSC_VER\n#pragma warning (disable: 4996) // 'This function or variable may be unsafe': strcpy, strdup, sprintf, vsnprintf, sscanf, fopen\n#endif\n\n// Clang/GCC warnings with -Weverything\n#if defined(__clang__)\n#pragma clang diagnostic ignored \"-Wformat-nonliteral\"  // warning: format string is not a string literal\n#elif defined(__GNUC__)\n#pragma GCC diagnostic ignored \"-Wformat-nonliteral\"    // warning: format not a string literal, format string not checked\n#endif\n\n// Global plot context\n#ifndef GImPlot\nImPlotContext* GImPlot = nullptr;\n#endif\n\n//-----------------------------------------------------------------------------\n// Struct Implementations\n//-----------------------------------------------------------------------------\n\nImPlotInputMap::ImPlotInputMap() {\n    ImPlot::MapInputDefault(this);\n}\n\nImPlotStyle::ImPlotStyle() {\n\n    LineWeight         = 1;\n    Marker             = ImPlotMarker_None;\n    MarkerSize         = 4;\n    MarkerWeight       = 1;\n    FillAlpha          = 1;\n    ErrorBarSize       = 5;\n    ErrorBarWeight     = 1.5f;\n    DigitalBitHeight   = 8;\n    DigitalBitGap      = 4;\n\n    PlotBorderSize     = 1;\n    MinorAlpha         = 0.25f;\n    MajorTickLen       = ImVec2(10,10);\n    MinorTickLen       = ImVec2(5,5);\n    MajorTickSize      = ImVec2(1,1);\n    MinorTickSize      = ImVec2(1,1);\n    MajorGridSize      = ImVec2(1,1);\n    MinorGridSize      = ImVec2(1,1);\n    PlotPadding        = ImVec2(10,10);\n    LabelPadding       = ImVec2(5,5);\n    LegendPadding      = ImVec2(10,10);\n    LegendInnerPadding = ImVec2(5,5);\n    LegendSpacing      = ImVec2(5,0);\n    MousePosPadding    = ImVec2(10,10);\n    AnnotationPadding  = ImVec2(2,2);\n    FitPadding         = ImVec2(0,0);\n    PlotDefaultSize    = ImVec2(400,300);\n    PlotMinSize        = ImVec2(200,150);\n\n    ImPlot::StyleColorsAuto(this);\n\n    Colormap = ImPlotColormap_Deep;\n\n    UseLocalTime     = false;\n    Use24HourClock   = false;\n    UseISO8601       = false;\n}\n\n//-----------------------------------------------------------------------------\n// Style\n//-----------------------------------------------------------------------------\n\nnamespace ImPlot {\n\nconst char* GetStyleColorName(ImPlotCol col) {\n    static const char* col_names[ImPlotCol_COUNT] = {\n        \"Line\",\n        \"Fill\",\n        \"MarkerOutline\",\n        \"MarkerFill\",\n        \"ErrorBar\",\n        \"FrameBg\",\n        \"PlotBg\",\n        \"PlotBorder\",\n        \"LegendBg\",\n        \"LegendBorder\",\n        \"LegendText\",\n        \"TitleText\",\n        \"InlayText\",\n        \"AxisText\",\n        \"AxisGrid\",\n        \"AxisTick\",\n        \"AxisBg\",\n        \"AxisBgHovered\",\n        \"AxisBgActive\",\n        \"Selection\",\n        \"Crosshairs\"\n    };\n    return col_names[col];\n}\n\nconst char* GetMarkerName(ImPlotMarker marker) {\n    switch (marker) {\n        case ImPlotMarker_None:     return \"None\";\n        case ImPlotMarker_Circle:   return \"Circle\";\n        case ImPlotMarker_Square:   return \"Square\";\n        case ImPlotMarker_Diamond:  return \"Diamond\";\n        case ImPlotMarker_Up:       return \"Up\";\n        case ImPlotMarker_Down:     return \"Down\";\n        case ImPlotMarker_Left:     return \"Left\";\n        case ImPlotMarker_Right:    return \"Right\";\n        case ImPlotMarker_Cross:    return \"Cross\";\n        case ImPlotMarker_Plus:     return \"Plus\";\n        case ImPlotMarker_Asterisk: return \"Asterisk\";\n        default:                    return \"\";\n    }\n}\n\nImVec4 GetAutoColor(ImPlotCol idx) {\n    ImVec4 col(0,0,0,1);\n    switch(idx) {\n        case ImPlotCol_Line:          return col; // these are plot dependent!\n        case ImPlotCol_Fill:          return col; // these are plot dependent!\n        case ImPlotCol_MarkerOutline: return col; // these are plot dependent!\n        case ImPlotCol_MarkerFill:    return col; // these are plot dependent!\n        case ImPlotCol_ErrorBar:      return ImGui::GetStyleColorVec4(ImGuiCol_Text);\n        case ImPlotCol_FrameBg:       return ImGui::GetStyleColorVec4(ImGuiCol_FrameBg);\n        case ImPlotCol_PlotBg:        return ImGui::GetStyleColorVec4(ImGuiCol_WindowBg);\n        case ImPlotCol_PlotBorder:    return ImGui::GetStyleColorVec4(ImGuiCol_Border);\n        case ImPlotCol_LegendBg:      return ImGui::GetStyleColorVec4(ImGuiCol_PopupBg);\n        case ImPlotCol_LegendBorder:  return GetStyleColorVec4(ImPlotCol_PlotBorder);\n        case ImPlotCol_LegendText:    return GetStyleColorVec4(ImPlotCol_InlayText);\n        case ImPlotCol_TitleText:     return ImGui::GetStyleColorVec4(ImGuiCol_Text);\n        case ImPlotCol_InlayText:     return ImGui::GetStyleColorVec4(ImGuiCol_Text);\n        case ImPlotCol_AxisText:      return ImGui::GetStyleColorVec4(ImGuiCol_Text);\n        case ImPlotCol_AxisGrid:      return GetStyleColorVec4(ImPlotCol_AxisText) * ImVec4(1,1,1,0.25f);\n        case ImPlotCol_AxisTick:      return GetStyleColorVec4(ImPlotCol_AxisGrid);\n        case ImPlotCol_AxisBg:        return ImVec4(0,0,0,0);\n        case ImPlotCol_AxisBgHovered: return ImGui::GetStyleColorVec4(ImGuiCol_ButtonHovered);\n        case ImPlotCol_AxisBgActive:  return ImGui::GetStyleColorVec4(ImGuiCol_ButtonActive);\n        case ImPlotCol_Selection:     return ImVec4(1,1,0,1);\n        case ImPlotCol_Crosshairs:    return GetStyleColorVec4(ImPlotCol_PlotBorder);\n        default: return col;\n    }\n}\n\nstruct ImPlotStyleVarInfo {\n    ImGuiDataType   Type;\n    ImU32           Count;\n    ImU32           Offset;\n    void*           GetVarPtr(ImPlotStyle* style) const { return (void*)((unsigned char*)style + Offset); }\n};\n\nstatic const ImPlotStyleVarInfo GPlotStyleVarInfo[] =\n{\n    { ImGuiDataType_Float, 1, (ImU32)offsetof(ImPlotStyle, LineWeight)         }, // ImPlotStyleVar_LineWeight\n    { ImGuiDataType_S32,   1, (ImU32)offsetof(ImPlotStyle, Marker)             }, // ImPlotStyleVar_Marker\n    { ImGuiDataType_Float, 1, (ImU32)offsetof(ImPlotStyle, MarkerSize)         }, // ImPlotStyleVar_MarkerSize\n    { ImGuiDataType_Float, 1, (ImU32)offsetof(ImPlotStyle, MarkerWeight)       }, // ImPlotStyleVar_MarkerWeight\n    { ImGuiDataType_Float, 1, (ImU32)offsetof(ImPlotStyle, FillAlpha)          }, // ImPlotStyleVar_FillAlpha\n    { ImGuiDataType_Float, 1, (ImU32)offsetof(ImPlotStyle, ErrorBarSize)       }, // ImPlotStyleVar_ErrorBarSize\n    { ImGuiDataType_Float, 1, (ImU32)offsetof(ImPlotStyle, ErrorBarWeight)     }, // ImPlotStyleVar_ErrorBarWeight\n    { ImGuiDataType_Float, 1, (ImU32)offsetof(ImPlotStyle, DigitalBitHeight)   }, // ImPlotStyleVar_DigitalBitHeight\n    { ImGuiDataType_Float, 1, (ImU32)offsetof(ImPlotStyle, DigitalBitGap)      }, // ImPlotStyleVar_DigitalBitGap\n\n    { ImGuiDataType_Float, 1, (ImU32)offsetof(ImPlotStyle, PlotBorderSize)     }, // ImPlotStyleVar_PlotBorderSize\n    { ImGuiDataType_Float, 1, (ImU32)offsetof(ImPlotStyle, MinorAlpha)         }, // ImPlotStyleVar_MinorAlpha\n    { ImGuiDataType_Float, 2, (ImU32)offsetof(ImPlotStyle, MajorTickLen)       }, // ImPlotStyleVar_MajorTickLen\n    { ImGuiDataType_Float, 2, (ImU32)offsetof(ImPlotStyle, MinorTickLen)       }, // ImPlotStyleVar_MinorTickLen\n    { ImGuiDataType_Float, 2, (ImU32)offsetof(ImPlotStyle, MajorTickSize)      }, // ImPlotStyleVar_MajorTickSize\n    { ImGuiDataType_Float, 2, (ImU32)offsetof(ImPlotStyle, MinorTickSize)      }, // ImPlotStyleVar_MinorTickSize\n    { ImGuiDataType_Float, 2, (ImU32)offsetof(ImPlotStyle, MajorGridSize)      }, // ImPlotStyleVar_MajorGridSize\n    { ImGuiDataType_Float, 2, (ImU32)offsetof(ImPlotStyle, MinorGridSize)      }, // ImPlotStyleVar_MinorGridSize\n    { ImGuiDataType_Float, 2, (ImU32)offsetof(ImPlotStyle, PlotPadding)        }, // ImPlotStyleVar_PlotPadding\n    { ImGuiDataType_Float, 2, (ImU32)offsetof(ImPlotStyle, LabelPadding)       }, // ImPlotStyleVar_LabelPaddine\n    { ImGuiDataType_Float, 2, (ImU32)offsetof(ImPlotStyle, LegendPadding)      }, // ImPlotStyleVar_LegendPadding\n    { ImGuiDataType_Float, 2, (ImU32)offsetof(ImPlotStyle, LegendInnerPadding) }, // ImPlotStyleVar_LegendInnerPadding\n    { ImGuiDataType_Float, 2, (ImU32)offsetof(ImPlotStyle, LegendSpacing)      }, // ImPlotStyleVar_LegendSpacing\n\n    { ImGuiDataType_Float, 2, (ImU32)offsetof(ImPlotStyle, MousePosPadding)    }, // ImPlotStyleVar_MousePosPadding\n    { ImGuiDataType_Float, 2, (ImU32)offsetof(ImPlotStyle, AnnotationPadding)  }, // ImPlotStyleVar_AnnotationPadding\n    { ImGuiDataType_Float, 2, (ImU32)offsetof(ImPlotStyle, FitPadding)         }, // ImPlotStyleVar_FitPadding\n    { ImGuiDataType_Float, 2, (ImU32)offsetof(ImPlotStyle, PlotDefaultSize)    }, // ImPlotStyleVar_PlotDefaultSize\n    { ImGuiDataType_Float, 2, (ImU32)offsetof(ImPlotStyle, PlotMinSize)        }  // ImPlotStyleVar_PlotMinSize\n};\n\nstatic const ImPlotStyleVarInfo* GetPlotStyleVarInfo(ImPlotStyleVar idx) {\n    IM_ASSERT(idx >= 0 && idx < ImPlotStyleVar_COUNT);\n    IM_ASSERT(IM_ARRAYSIZE(GPlotStyleVarInfo) == ImPlotStyleVar_COUNT);\n    return &GPlotStyleVarInfo[idx];\n}\n\n//-----------------------------------------------------------------------------\n// Generic Helpers\n//-----------------------------------------------------------------------------\n\nvoid AddTextVertical(ImDrawList *DrawList, ImVec2 pos, ImU32 col, const char *text_begin, const char* text_end) {\n    // the code below is based loosely on ImFont::RenderText\n    if (!text_end)\n        text_end = text_begin + strlen(text_begin);\n    ImGuiContext& g = *GImGui;\n    ImFont* font = g.Font;\n    // Align to be pixel perfect\n    pos.x = IM_TRUNC(pos.x);\n    pos.y = IM_TRUNC(pos.y);\n    const float scale = g.FontSize / font->FontSize;\n    const char* s = text_begin;\n    int chars_exp = (int)(text_end - s);\n    int chars_rnd = 0;\n    const int vtx_count_max = chars_exp * 4;\n    const int idx_count_max = chars_exp * 6;\n    DrawList->PrimReserve(idx_count_max, vtx_count_max);\n    while (s < text_end) {\n        unsigned int c = (unsigned int)*s;\n        if (c < 0x80) {\n            s += 1;\n        }\n        else {\n            s += ImTextCharFromUtf8(&c, s, text_end);\n            if (c == 0) // Malformed UTF-8?\n                break;\n        }\n        const ImFontGlyph * glyph = font->FindGlyph((ImWchar)c);\n        if (glyph == nullptr) {\n            continue;\n        }\n        DrawList->PrimQuadUV(pos + ImVec2(glyph->Y0, -glyph->X0) * scale, pos + ImVec2(glyph->Y0, -glyph->X1) * scale,\n                             pos + ImVec2(glyph->Y1, -glyph->X1) * scale, pos + ImVec2(glyph->Y1, -glyph->X0) * scale,\n                             ImVec2(glyph->U0, glyph->V0), ImVec2(glyph->U1, glyph->V0),\n                             ImVec2(glyph->U1, glyph->V1), ImVec2(glyph->U0, glyph->V1),\n                             col);\n        pos.y -= glyph->AdvanceX * scale;\n        chars_rnd++;\n    }\n    // Give back unused vertices\n    int chars_skp = chars_exp-chars_rnd;\n    DrawList->PrimUnreserve(chars_skp*6, chars_skp*4);\n}\n\nvoid AddTextCentered(ImDrawList* DrawList, ImVec2 top_center, ImU32 col, const char* text_begin, const char* text_end) {\n    float txt_ht = ImGui::GetTextLineHeight();\n    const char* title_end = ImGui::FindRenderedTextEnd(text_begin, text_end);\n    ImVec2 text_size;\n    float  y = 0;\n    while (const char* tmp = (const char*)memchr(text_begin, '\\n', title_end-text_begin)) {\n        text_size = ImGui::CalcTextSize(text_begin,tmp,true);\n        DrawList->AddText(ImVec2(top_center.x - text_size.x * 0.5f, top_center.y+y),col,text_begin,tmp);\n        text_begin = tmp + 1;\n        y += txt_ht;\n    }\n    text_size = ImGui::CalcTextSize(text_begin,title_end,true);\n    DrawList->AddText(ImVec2(top_center.x - text_size.x * 0.5f, top_center.y+y),col,text_begin,title_end);\n}\n\ndouble NiceNum(double x, bool round) {\n    double f;\n    double nf;\n    int expv = (int)floor(ImLog10(x));\n    f = x / ImPow(10.0, (double)expv);\n    if (round)\n        if (f < 1.5)\n            nf = 1;\n        else if (f < 3)\n            nf = 2;\n        else if (f < 7)\n            nf = 5;\n        else\n            nf = 10;\n    else if (f <= 1)\n        nf = 1;\n    else if (f <= 2)\n        nf = 2;\n    else if (f <= 5)\n        nf = 5;\n    else\n        nf = 10;\n    return nf * ImPow(10.0, expv);\n}\n\n//-----------------------------------------------------------------------------\n// Context Utils\n//-----------------------------------------------------------------------------\n\nvoid SetImGuiContext(ImGuiContext* ctx) {\n    ImGui::SetCurrentContext(ctx);\n}\n\nImPlotContext* CreateContext() {\n    ImPlotContext* ctx = IM_NEW(ImPlotContext)();\n    Initialize(ctx);\n    if (GImPlot == nullptr)\n        SetCurrentContext(ctx);\n    return ctx;\n}\n\nvoid DestroyContext(ImPlotContext* ctx) {\n    if (ctx == nullptr)\n        ctx = GImPlot;\n    if (GImPlot == ctx)\n        SetCurrentContext(nullptr);\n    IM_DELETE(ctx);\n}\n\nImPlotContext* GetCurrentContext() {\n    return GImPlot;\n}\n\nvoid SetCurrentContext(ImPlotContext* ctx) {\n    GImPlot = ctx;\n}\n\n#define IMPLOT_APPEND_CMAP(name, qual) ctx->ColormapData.Append(#name, name, sizeof(name)/sizeof(ImU32), qual)\n#define IM_RGB(r,g,b) IM_COL32(r,g,b,255)\n\nvoid Initialize(ImPlotContext* ctx) {\n    ResetCtxForNextPlot(ctx);\n    ResetCtxForNextAlignedPlots(ctx);\n    ResetCtxForNextSubplot(ctx);\n\n    const ImU32 Deep[]     = {4289753676, 4283598045, 4285048917, 4283584196, 4289950337, 4284512403, 4291005402, 4287401100, 4285839820, 4291671396                        };\n    const ImU32 Dark[]     = {4280031972, 4290281015, 4283084621, 4288892568, 4278222847, 4281597951, 4280833702, 4290740727, 4288256409                                    };\n    const ImU32 Pastel[]   = {4289639675, 4293119411, 4291161036, 4293184478, 4289124862, 4291624959, 4290631909, 4293712637, 4294111986                                    };\n    const ImU32 Paired[]   = {4293119554, 4290017311, 4287291314, 4281114675, 4288256763, 4280031971, 4285513725, 4278222847, 4292260554, 4288298346, 4288282623, 4280834481};\n    const ImU32 Viridis[]  = {4283695428, 4285867080, 4287054913, 4287455029, 4287526954, 4287402273, 4286883874, 4285579076, 4283552122, 4280737725, 4280674301            };\n    const ImU32 Plasma[]   = {4287039501, 4288480321, 4289200234, 4288941455, 4287638193, 4286072780, 4284638433, 4283139314, 4281771772, 4280667900, 4280416752            };\n    const ImU32 Hot[]      = {4278190144, 4278190208, 4278190271, 4278190335, 4278206719, 4278223103, 4278239231, 4278255615, 4283826175, 4289396735, 4294967295            };\n    const ImU32 Cool[]     = {4294967040, 4294960666, 4294954035, 4294947661, 4294941030, 4294934656, 4294928025, 4294921651, 4294915020, 4294908646, 4294902015            };\n    const ImU32 Pink[]     = {4278190154, 4282532475, 4284308894, 4285690554, 4286879686, 4287870160, 4288794330, 4289651940, 4291685869, 4293392118, 4294967295            };\n    const ImU32 Jet[]      = {4289331200, 4294901760, 4294923520, 4294945280, 4294967040, 4289396565, 4283826090, 4278255615, 4278233855, 4278212095, 4278190335            };\n    const ImU32 Twilight[] = {IM_RGB(226,217,226),IM_RGB(166,191,202),IM_RGB(109,144,192),IM_RGB(95,88,176),IM_RGB(83,30,124),IM_RGB(47,20,54),IM_RGB(100,25,75),IM_RGB(159,60,80),IM_RGB(192,117,94),IM_RGB(208,179,158),IM_RGB(226,217,226)};\n    const ImU32 RdBu[]     = {IM_RGB(103,0,31),IM_RGB(178,24,43),IM_RGB(214,96,77),IM_RGB(244,165,130),IM_RGB(253,219,199),IM_RGB(247,247,247),IM_RGB(209,229,240),IM_RGB(146,197,222),IM_RGB(67,147,195),IM_RGB(33,102,172),IM_RGB(5,48,97)};\n    const ImU32 BrBG[]     = {IM_RGB(84,48,5),IM_RGB(140,81,10),IM_RGB(191,129,45),IM_RGB(223,194,125),IM_RGB(246,232,195),IM_RGB(245,245,245),IM_RGB(199,234,229),IM_RGB(128,205,193),IM_RGB(53,151,143),IM_RGB(1,102,94),IM_RGB(0,60,48)};\n    const ImU32 PiYG[]     = {IM_RGB(142,1,82),IM_RGB(197,27,125),IM_RGB(222,119,174),IM_RGB(241,182,218),IM_RGB(253,224,239),IM_RGB(247,247,247),IM_RGB(230,245,208),IM_RGB(184,225,134),IM_RGB(127,188,65),IM_RGB(77,146,33),IM_RGB(39,100,25)};\n    const ImU32 Spectral[] = {IM_RGB(158,1,66),IM_RGB(213,62,79),IM_RGB(244,109,67),IM_RGB(253,174,97),IM_RGB(254,224,139),IM_RGB(255,255,191),IM_RGB(230,245,152),IM_RGB(171,221,164),IM_RGB(102,194,165),IM_RGB(50,136,189),IM_RGB(94,79,162)};\n    const ImU32 Greys[]    = {IM_COL32_WHITE, IM_COL32_BLACK                                                                                                                };\n\n    IMPLOT_APPEND_CMAP(Deep, true);\n    IMPLOT_APPEND_CMAP(Dark, true);\n    IMPLOT_APPEND_CMAP(Pastel, true);\n    IMPLOT_APPEND_CMAP(Paired, true);\n    IMPLOT_APPEND_CMAP(Viridis, false);\n    IMPLOT_APPEND_CMAP(Plasma, false);\n    IMPLOT_APPEND_CMAP(Hot, false);\n    IMPLOT_APPEND_CMAP(Cool, false);\n    IMPLOT_APPEND_CMAP(Pink, false);\n    IMPLOT_APPEND_CMAP(Jet, false);\n    IMPLOT_APPEND_CMAP(Twilight, false);\n    IMPLOT_APPEND_CMAP(RdBu, false);\n    IMPLOT_APPEND_CMAP(BrBG, false);\n    IMPLOT_APPEND_CMAP(PiYG, false);\n    IMPLOT_APPEND_CMAP(Spectral, false);\n    IMPLOT_APPEND_CMAP(Greys, false);\n}\n\nvoid ResetCtxForNextPlot(ImPlotContext* ctx) {\n    // end child window if it was made\n    if (ctx->ChildWindowMade)\n        ImGui::EndChild();\n    ctx->ChildWindowMade = false;\n    // reset the next plot/item data\n    ctx->NextPlotData.Reset();\n    ctx->NextItemData.Reset();\n    // reset labels\n    ctx->Annotations.Reset();\n    ctx->Tags.Reset();\n    // reset extents/fit\n    ctx->OpenContextThisFrame = false;\n    // reset digital plot items count\n    ctx->DigitalPlotItemCnt = 0;\n    ctx->DigitalPlotOffset = 0;\n    // nullify plot\n    ctx->CurrentPlot  = nullptr;\n    ctx->CurrentItem  = nullptr;\n    ctx->PreviousItem = nullptr;\n}\n\nvoid ResetCtxForNextAlignedPlots(ImPlotContext* ctx) {\n    ctx->CurrentAlignmentH = nullptr;\n    ctx->CurrentAlignmentV = nullptr;\n}\n\nvoid ResetCtxForNextSubplot(ImPlotContext* ctx) {\n    ctx->CurrentSubplot      = nullptr;\n    ctx->CurrentAlignmentH   = nullptr;\n    ctx->CurrentAlignmentV   = nullptr;\n}\n\n//-----------------------------------------------------------------------------\n// Plot Utils\n//-----------------------------------------------------------------------------\n\nImPlotPlot* GetPlot(const char* title) {\n    ImGuiWindow*   Window = GImGui->CurrentWindow;\n    const ImGuiID  ID     = Window->GetID(title);\n    return GImPlot->Plots.GetByKey(ID);\n}\n\nImPlotPlot* GetCurrentPlot() {\n    return GImPlot->CurrentPlot;\n}\n\nvoid BustPlotCache() {\n    ImPlotContext& gp = *GImPlot;\n    gp.Plots.Clear();\n    gp.Subplots.Clear();\n}\n\n//-----------------------------------------------------------------------------\n// Legend Utils\n//-----------------------------------------------------------------------------\n\nImVec2 GetLocationPos(const ImRect& outer_rect, const ImVec2& inner_size, ImPlotLocation loc, const ImVec2& pad) {\n    ImVec2 pos;\n    if (ImHasFlag(loc, ImPlotLocation_West) && !ImHasFlag(loc, ImPlotLocation_East))\n        pos.x = outer_rect.Min.x + pad.x;\n    else if (!ImHasFlag(loc, ImPlotLocation_West) && ImHasFlag(loc, ImPlotLocation_East))\n        pos.x = outer_rect.Max.x - pad.x - inner_size.x;\n    else\n        pos.x = outer_rect.GetCenter().x - inner_size.x * 0.5f;\n    // legend reference point y\n    if (ImHasFlag(loc, ImPlotLocation_North) && !ImHasFlag(loc, ImPlotLocation_South))\n        pos.y = outer_rect.Min.y + pad.y;\n    else if (!ImHasFlag(loc, ImPlotLocation_North) && ImHasFlag(loc, ImPlotLocation_South))\n        pos.y = outer_rect.Max.y - pad.y - inner_size.y;\n    else\n        pos.y = outer_rect.GetCenter().y - inner_size.y * 0.5f;\n    pos.x = IM_ROUND(pos.x);\n    pos.y = IM_ROUND(pos.y);\n    return pos;\n}\n\nImVec2 CalcLegendSize(ImPlotItemGroup& items, const ImVec2& pad, const ImVec2& spacing, bool vertical) {\n    // vars\n    const int   nItems      = items.GetLegendCount();\n    const float txt_ht      = ImGui::GetTextLineHeight();\n    const float icon_size   = txt_ht;\n    // get label max width\n    float max_label_width = 0;\n    float sum_label_width = 0;\n    for (int i = 0; i < nItems; ++i) {\n        const char* label       = items.GetLegendLabel(i);\n        const float label_width = ImGui::CalcTextSize(label, nullptr, true).x;\n        max_label_width         = label_width > max_label_width ? label_width : max_label_width;\n        sum_label_width        += label_width;\n    }\n    // calc legend size\n    const ImVec2 legend_size = vertical ?\n                               ImVec2(pad.x * 2 + icon_size + max_label_width, pad.y * 2 + nItems * txt_ht + (nItems - 1) * spacing.y) :\n                               ImVec2(pad.x * 2 + icon_size * nItems + sum_label_width + (nItems - 1) * spacing.x, pad.y * 2 + txt_ht);\n    return legend_size;\n}\n\nint LegendSortingComp(const void* _a, const void* _b) {\n    ImPlotItemGroup* items = GImPlot->SortItems;\n    const int a = *(const int*)_a;\n    const int b = *(const int*)_b;\n    const char* label_a = items->GetLegendLabel(a);\n    const char* label_b = items->GetLegendLabel(b);\n    return strcmp(label_a,label_b);\n}\n\nbool ShowLegendEntries(ImPlotItemGroup& items, const ImRect& legend_bb, bool hovered, const ImVec2& pad, const ImVec2& spacing, bool vertical, ImDrawList& DrawList) {\n    // vars\n    const float txt_ht      = ImGui::GetTextLineHeight();\n    const float icon_size   = txt_ht;\n    const float icon_shrink = 2;\n    ImU32 col_txt           = GetStyleColorU32(ImPlotCol_LegendText);\n    ImU32 col_txt_dis       = ImAlphaU32(col_txt, 0.25f);\n    // render each legend item\n    float sum_label_width = 0;\n    bool any_item_hovered = false;\n\n    const int num_items = items.GetLegendCount();\n    if (num_items < 1)\n        return hovered;\n    // build render order\n    ImPlotContext& gp = *GImPlot;\n    ImVector<int>& indices = gp.TempInt1;\n    indices.resize(num_items);\n    for (int i = 0; i < num_items; ++i)\n        indices[i] = i;\n    if (ImHasFlag(items.Legend.Flags, ImPlotLegendFlags_Sort) && num_items > 1) {\n        gp.SortItems = &items;\n        qsort(indices.Data, num_items, sizeof(int), LegendSortingComp);\n    }\n    // render\n    for (int i = 0; i < num_items; ++i) {\n        const int idx           = indices[i];\n        ImPlotItem* item        = items.GetLegendItem(idx);\n        const char* label       = items.GetLegendLabel(idx);\n        const float label_width = ImGui::CalcTextSize(label, nullptr, true).x;\n        const ImVec2 top_left   = vertical ?\n                                  legend_bb.Min + pad + ImVec2(0, i * (txt_ht + spacing.y)) :\n                                  legend_bb.Min + pad + ImVec2(i * (icon_size + spacing.x) + sum_label_width, 0);\n        sum_label_width        += label_width;\n        ImRect icon_bb;\n        icon_bb.Min = top_left + ImVec2(icon_shrink,icon_shrink);\n        icon_bb.Max = top_left + ImVec2(icon_size - icon_shrink, icon_size - icon_shrink);\n        ImRect label_bb;\n        label_bb.Min = top_left;\n        label_bb.Max = top_left + ImVec2(label_width + icon_size, icon_size);\n        ImU32 col_txt_hl;\n        ImU32 col_item = ImAlphaU32(item->Color,1);\n\n        ImRect button_bb(icon_bb.Min, label_bb.Max);\n\n        ImGui::KeepAliveID(item->ID);\n\n        bool item_hov = false;\n        bool item_hld = false;\n        bool item_clk = ImHasFlag(items.Legend.Flags, ImPlotLegendFlags_NoButtons)\n                      ? false\n                      : ImGui::ButtonBehavior(button_bb, item->ID, &item_hov, &item_hld);\n\n        if (item_clk)\n            item->Show = !item->Show;\n\n\n        const bool can_hover = (item_hov)\n                             && (!ImHasFlag(items.Legend.Flags, ImPlotLegendFlags_NoHighlightItem)\n                             || !ImHasFlag(items.Legend.Flags, ImPlotLegendFlags_NoHighlightAxis));\n\n        if (can_hover) {\n            item->LegendHoverRect.Min = icon_bb.Min;\n            item->LegendHoverRect.Max = label_bb.Max;\n            item->LegendHovered = true;\n            col_txt_hl = ImMixU32(col_txt, col_item, 64);\n            any_item_hovered = true;\n        }\n        else {\n            col_txt_hl = ImGui::GetColorU32(col_txt);\n        }\n        ImU32 col_icon;\n        if (item_hld)\n            col_icon = item->Show ? ImAlphaU32(col_item,0.5f) : ImGui::GetColorU32(ImGuiCol_TextDisabled, 0.5f);\n        else if (item_hov)\n            col_icon = item->Show ? ImAlphaU32(col_item,0.75f) : ImGui::GetColorU32(ImGuiCol_TextDisabled, 0.75f);\n        else\n            col_icon = item->Show ? col_item : col_txt_dis;\n\n        DrawList.AddRectFilled(icon_bb.Min, icon_bb.Max, col_icon);\n        const char* text_display_end = ImGui::FindRenderedTextEnd(label, nullptr);\n        if (label != text_display_end)\n            DrawList.AddText(top_left + ImVec2(icon_size, 0), item->Show ? col_txt_hl  : col_txt_dis, label, text_display_end);\n    }\n    return hovered && !any_item_hovered;\n}\n\n//-----------------------------------------------------------------------------\n// Locators\n//-----------------------------------------------------------------------------\n\nstatic const float TICK_FILL_X = 0.8f;\nstatic const float TICK_FILL_Y = 1.0f;\n\nvoid Locator_Default(ImPlotTicker& ticker, const ImPlotRange& range, float pixels, bool vertical, ImPlotFormatter formatter, void* formatter_data) {\n    if (range.Min == range.Max)\n        return;\n    const int nMinor        = 10;\n    const int nMajor        = ImMax(2, (int)IM_ROUND(pixels / (vertical ? 300.0f : 400.0f)));\n    const double nice_range = NiceNum(range.Size() * 0.99, false);\n    const double interval   = NiceNum(nice_range / (nMajor - 1), true);\n    const double graphmin   = floor(range.Min / interval) * interval;\n    const double graphmax   = ceil(range.Max / interval) * interval;\n    bool first_major_set    = false;\n    int  first_major_idx    = 0;\n    const int idx0 = ticker.TickCount(); // ticker may have user custom ticks\n    ImVec2 total_size(0,0);\n    for (double major = graphmin; major < graphmax + 0.5 * interval; major += interval) {\n        // is this zero? combat zero formatting issues\n        if (major-interval < 0 && major+interval > 0)\n            major = 0;\n        if (range.Contains(major)) {\n            if (!first_major_set) {\n                first_major_idx = ticker.TickCount();\n                first_major_set = true;\n            }\n            total_size += ticker.AddTick(major, true, 0, true, formatter, formatter_data).LabelSize;\n        }\n        for (int i = 1; i < nMinor; ++i) {\n            double minor = major + i * interval / nMinor;\n            if (range.Contains(minor)) {\n                total_size += ticker.AddTick(minor, false, 0, true, formatter, formatter_data).LabelSize;\n            }\n        }\n    }\n    // prune if necessary\n    if ((!vertical && total_size.x > pixels*TICK_FILL_X) || (vertical && total_size.y > pixels*TICK_FILL_Y)) {\n        for (int i = first_major_idx-1; i >= idx0; i -= 2)\n            ticker.Ticks[i].ShowLabel = false;\n        for (int i = first_major_idx+1; i < ticker.TickCount(); i += 2)\n            ticker.Ticks[i].ShowLabel = false;\n    }\n}\n\nbool CalcLogarithmicExponents(const ImPlotRange& range, float pix, bool vertical, int& exp_min, int& exp_max, int& exp_step) {\n    if (range.Min * range.Max > 0) {\n        const int nMajor = vertical ? ImMax(2, (int)IM_ROUND(pix * 0.02f)) : ImMax(2, (int)IM_ROUND(pix * 0.01f)); // TODO: magic numbers\n        double log_min = ImLog10(ImAbs(range.Min));\n        double log_max = ImLog10(ImAbs(range.Max));\n        double log_a = ImMin(log_min,log_max);\n        double log_b = ImMax(log_min,log_max);\n        exp_step  = ImMax(1,(int)(log_b - log_a) / nMajor);\n        exp_min   = (int)log_a;\n        exp_max   = (int)log_b;\n        if (exp_step != 1) {\n            while(exp_step % 3 != 0)       exp_step++; // make step size multiple of three\n            while(exp_min % exp_step != 0) exp_min--;  // decrease exp_min until exp_min + N * exp_step will be 0\n        }\n        return true;\n    }\n    return false;\n}\n\nvoid AddTicksLogarithmic(const ImPlotRange& range, int exp_min, int exp_max, int exp_step, ImPlotTicker& ticker, ImPlotFormatter formatter, void* data) {\n    const double sign = ImSign(range.Max);\n    for (int e = exp_min - exp_step; e < (exp_max + exp_step); e += exp_step) {\n        double major1 = sign*ImPow(10, (double)(e));\n        double major2 = sign*ImPow(10, (double)(e + 1));\n        double interval = (major2 - major1) / 9;\n        if (major1 >= (range.Min - DBL_EPSILON) && major1 <= (range.Max + DBL_EPSILON))\n            ticker.AddTick(major1, true, 0, true, formatter, data);\n        for (int j = 0; j < exp_step; ++j) {\n            major1 = sign*ImPow(10, (double)(e+j));\n            major2 = sign*ImPow(10, (double)(e+j+1));\n            interval = (major2 - major1) / 9;\n            for (int i = 1; i < (9 + (int)(j < (exp_step - 1))); ++i) {\n                double minor = major1 + i * interval;\n                if (minor >= (range.Min - DBL_EPSILON) && minor <= (range.Max + DBL_EPSILON))\n                    ticker.AddTick(minor, false, 0, false, formatter, data);\n            }\n        }\n    }\n}\n\nvoid Locator_Log10(ImPlotTicker& ticker, const ImPlotRange& range, float pixels, bool vertical, ImPlotFormatter formatter, void* formatter_data) {\n    int exp_min, exp_max, exp_step;\n    if (CalcLogarithmicExponents(range, pixels, vertical, exp_min, exp_max, exp_step))\n        AddTicksLogarithmic(range, exp_min, exp_max, exp_step, ticker, formatter, formatter_data);\n}\n\nfloat CalcSymLogPixel(double plt, const ImPlotRange& range, float pixels) {\n    double scaleToPixels = pixels / range.Size();\n    double scaleMin      = TransformForward_SymLog(range.Min,nullptr);\n    double scaleMax      = TransformForward_SymLog(range.Max,nullptr);\n    double s             = TransformForward_SymLog(plt, nullptr);\n    double t             = (s - scaleMin) / (scaleMax - scaleMin);\n    plt                  = range.Min + range.Size() * t;\n\n    return (float)(0 + scaleToPixels * (plt - range.Min));\n}\n\nvoid Locator_SymLog(ImPlotTicker& ticker, const ImPlotRange& range, float pixels, bool vertical, ImPlotFormatter formatter, void* formatter_data) {\n    if (range.Min >= -1 && range.Max <= 1) {\n        Locator_Default(ticker, range, pixels, vertical, formatter, formatter_data);\n    }\n    else if (range.Min * range.Max < 0) { // cross zero\n        const float pix_min = 0;\n        const float pix_max = pixels;\n        const float pix_p1  = CalcSymLogPixel(1, range, pixels);\n        const float pix_n1  = CalcSymLogPixel(-1, range, pixels);\n        int exp_min_p, exp_max_p, exp_step_p;\n        int exp_min_n, exp_max_n, exp_step_n;\n        CalcLogarithmicExponents(ImPlotRange(1,range.Max), ImAbs(pix_max-pix_p1),vertical,exp_min_p,exp_max_p,exp_step_p);\n        CalcLogarithmicExponents(ImPlotRange(range.Min,-1),ImAbs(pix_n1-pix_min),vertical,exp_min_n,exp_max_n,exp_step_n);\n        int exp_step = ImMax(exp_step_n, exp_step_p);\n        ticker.AddTick(0,true,0,true,formatter,formatter_data);\n        AddTicksLogarithmic(ImPlotRange(1,range.Max), exp_min_p,exp_max_p,exp_step,ticker,formatter,formatter_data);\n        AddTicksLogarithmic(ImPlotRange(range.Min,-1),exp_min_n,exp_max_n,exp_step,ticker,formatter,formatter_data);\n    }\n    else {\n        Locator_Log10(ticker, range, pixels, vertical, formatter, formatter_data);\n    }\n}\n\nvoid AddTicksCustom(const double* values, const char* const labels[], int n, ImPlotTicker& ticker, ImPlotFormatter formatter, void* data) {\n    for (int i = 0; i < n; ++i) {\n        if (labels != nullptr)\n            ticker.AddTick(values[i], false, 0, true, labels[i]);\n        else\n            ticker.AddTick(values[i], false, 0, true, formatter, data);\n    }\n}\n\n//-----------------------------------------------------------------------------\n// Time Ticks and Utils\n//-----------------------------------------------------------------------------\n\n// this may not be thread safe?\nstatic const double TimeUnitSpans[ImPlotTimeUnit_COUNT] = {\n    0.000001,\n    0.001,\n    1,\n    60,\n    3600,\n    86400,\n    2629800,\n    31557600\n};\n\ninline ImPlotTimeUnit GetUnitForRange(double range) {\n    static double cutoffs[ImPlotTimeUnit_COUNT] = {0.001, 1, 60, 3600, 86400, 2629800, 31557600, IMPLOT_MAX_TIME};\n    for (int i = 0; i < ImPlotTimeUnit_COUNT; ++i) {\n        if (range <= cutoffs[i])\n            return (ImPlotTimeUnit)i;\n    }\n    return ImPlotTimeUnit_Yr;\n}\n\ninline int LowerBoundStep(int max_divs, const int* divs, const int* step, int size) {\n    if (max_divs < divs[0])\n        return 0;\n    for (int i = 1; i < size; ++i) {\n        if (max_divs < divs[i])\n            return step[i-1];\n    }\n    return step[size-1];\n}\n\ninline int GetTimeStep(int max_divs, ImPlotTimeUnit unit) {\n    if (unit == ImPlotTimeUnit_Ms || unit == ImPlotTimeUnit_Us) {\n        static const int step[] = {500,250,200,100,50,25,20,10,5,2,1};\n        static const int divs[] = {2,4,5,10,20,40,50,100,200,500,1000};\n        return LowerBoundStep(max_divs, divs, step, 11);\n    }\n    if (unit == ImPlotTimeUnit_S || unit == ImPlotTimeUnit_Min) {\n        static const int step[] = {30,15,10,5,1};\n        static const int divs[] = {2,4,6,12,60};\n        return LowerBoundStep(max_divs, divs, step, 5);\n    }\n    else if (unit == ImPlotTimeUnit_Hr) {\n        static const int step[] = {12,6,3,2,1};\n        static const int divs[] = {2,4,8,12,24};\n        return LowerBoundStep(max_divs, divs, step, 5);\n    }\n    else if (unit == ImPlotTimeUnit_Day) {\n        static const int step[] = {14,7,2,1};\n        static const int divs[] = {2,4,14,28};\n        return LowerBoundStep(max_divs, divs, step, 4);\n    }\n    else if (unit == ImPlotTimeUnit_Mo) {\n        static const int step[] = {6,3,2,1};\n        static const int divs[] = {2,4,6,12};\n        return LowerBoundStep(max_divs, divs, step, 4);\n    }\n    return 0;\n}\n\nImPlotTime MkGmtTime(struct tm *ptm) {\n    ImPlotTime t;\n#ifdef _WIN32\n    t.S = _mkgmtime(ptm);\n#else\n    t.S = timegm(ptm);\n#endif\n    if (t.S < 0)\n        t.S = 0;\n    return t;\n}\n\ntm* GetGmtTime(const ImPlotTime& t, tm* ptm)\n{\n#ifdef _WIN32\n  if (gmtime_s(ptm, &t.S) == 0)\n    return ptm;\n  else\n    return nullptr;\n#else\n  return gmtime_r(&t.S, ptm);\n#endif\n}\n\nImPlotTime MkLocTime(struct tm *ptm) {\n    ImPlotTime t;\n    t.S = mktime(ptm);\n    if (t.S < 0)\n        t.S = 0;\n    return t;\n}\n\ntm* GetLocTime(const ImPlotTime& t, tm* ptm) {\n#ifdef _WIN32\n  if (localtime_s(ptm, &t.S) == 0)\n    return ptm;\n  else\n    return nullptr;\n#else\n    return localtime_r(&t.S, ptm);\n#endif\n}\n\ninline ImPlotTime MkTime(struct tm *ptm) {\n    if (GetStyle().UseLocalTime)\n        return MkLocTime(ptm);\n    else\n        return MkGmtTime(ptm);\n}\n\ninline tm* GetTime(const ImPlotTime& t, tm* ptm) {\n    if (GetStyle().UseLocalTime)\n        return GetLocTime(t,ptm);\n    else\n        return GetGmtTime(t,ptm);\n}\n\nImPlotTime MakeTime(int year, int month, int day, int hour, int min, int sec, int us) {\n    tm& Tm = GImPlot->Tm;\n\n    int yr = year - 1900;\n    if (yr < 0)\n        yr = 0;\n\n    sec  = sec + us / 1000000;\n    us   = us % 1000000;\n\n    Tm.tm_sec  = sec;\n    Tm.tm_min  = min;\n    Tm.tm_hour = hour;\n    Tm.tm_mday = day;\n    Tm.tm_mon  = month;\n    Tm.tm_year = yr;\n\n    ImPlotTime t = MkTime(&Tm);\n\n    t.Us = us;\n    return t;\n}\n\nint GetYear(const ImPlotTime& t) {\n    tm& Tm = GImPlot->Tm;\n    GetTime(t, &Tm);\n    return Tm.tm_year + 1900;\n}\n\nImPlotTime AddTime(const ImPlotTime& t, ImPlotTimeUnit unit, int count) {\n    tm& Tm = GImPlot->Tm;\n    ImPlotTime t_out = t;\n    switch(unit) {\n        case ImPlotTimeUnit_Us:  t_out.Us += count;         break;\n        case ImPlotTimeUnit_Ms:  t_out.Us += count * 1000;  break;\n        case ImPlotTimeUnit_S:   t_out.S  += count;         break;\n        case ImPlotTimeUnit_Min: t_out.S  += count * 60;    break;\n        case ImPlotTimeUnit_Hr:  t_out.S  += count * 3600;  break;\n        case ImPlotTimeUnit_Day: t_out.S  += count * 86400; break;\n        case ImPlotTimeUnit_Mo:  for (int i = 0; i < abs(count); ++i) {\n                                     GetTime(t_out, &Tm);\n                                     if (count > 0)\n                                        t_out.S += 86400 * GetDaysInMonth(Tm.tm_year + 1900, Tm.tm_mon);\n                                     else if (count < 0)\n                                        t_out.S -= 86400 * GetDaysInMonth(Tm.tm_year + 1900 - (Tm.tm_mon == 0 ? 1 : 0), Tm.tm_mon == 0 ? 11 : Tm.tm_mon - 1); // NOT WORKING\n                                 }\n                                 break;\n        case ImPlotTimeUnit_Yr:  for (int i = 0; i < abs(count); ++i) {\n                                    if (count > 0)\n                                        t_out.S += 86400 * (365 + (int)IsLeapYear(GetYear(t_out)));\n                                    else if (count < 0)\n                                        t_out.S -= 86400 * (365 + (int)IsLeapYear(GetYear(t_out) - 1));\n                                    // this is incorrect if leap year and we are past Feb 28\n                                 }\n                                 break;\n        default:                 break;\n    }\n    t_out.RollOver();\n    return t_out;\n}\n\nImPlotTime FloorTime(const ImPlotTime& t, ImPlotTimeUnit unit) {\n    ImPlotContext& gp = *GImPlot;\n    GetTime(t, &gp.Tm);\n    switch (unit) {\n        case ImPlotTimeUnit_S:   return ImPlotTime(t.S, 0);\n        case ImPlotTimeUnit_Ms:  return ImPlotTime(t.S, (t.Us / 1000) * 1000);\n        case ImPlotTimeUnit_Us:  return t;\n        case ImPlotTimeUnit_Yr:  gp.Tm.tm_mon  = 0; // fall-through\n        case ImPlotTimeUnit_Mo:  gp.Tm.tm_mday = 1; // fall-through\n        case ImPlotTimeUnit_Day: gp.Tm.tm_hour = 0; // fall-through\n        case ImPlotTimeUnit_Hr:  gp.Tm.tm_min  = 0; // fall-through\n        case ImPlotTimeUnit_Min: gp.Tm.tm_sec  = 0; break;\n        default:                 return t;\n    }\n    return MkTime(&gp.Tm);\n}\n\nImPlotTime CeilTime(const ImPlotTime& t, ImPlotTimeUnit unit) {\n    return AddTime(FloorTime(t, unit), unit, 1);\n}\n\nImPlotTime RoundTime(const ImPlotTime& t, ImPlotTimeUnit unit) {\n    ImPlotTime t1 = FloorTime(t, unit);\n    ImPlotTime t2 = AddTime(t1,unit,1);\n    if (t1.S == t2.S)\n        return t.Us - t1.Us < t2.Us - t.Us ? t1 : t2;\n    return t.S - t1.S < t2.S - t.S ? t1 : t2;\n}\n\nImPlotTime CombineDateTime(const ImPlotTime& date_part, const ImPlotTime& tod_part) {\n    ImPlotContext& gp = *GImPlot;\n    tm& Tm = gp.Tm;\n    GetTime(date_part, &gp.Tm);\n    int y = Tm.tm_year;\n    int m = Tm.tm_mon;\n    int d = Tm.tm_mday;\n    GetTime(tod_part, &gp.Tm);\n    Tm.tm_year = y;\n    Tm.tm_mon  = m;\n    Tm.tm_mday = d;\n    ImPlotTime t = MkTime(&Tm);\n    t.Us = tod_part.Us;\n    return t;\n}\n\n// TODO: allow users to define these\nstatic const char* MONTH_NAMES[]  = {\"January\",\"February\",\"March\",\"April\",\"May\",\"June\",\"July\",\"August\",\"September\",\"October\",\"November\",\"December\"};\nstatic const char* WD_ABRVS[]     = {\"Su\",\"Mo\",\"Tu\",\"We\",\"Th\",\"Fr\",\"Sa\"};\nstatic const char* MONTH_ABRVS[]  = {\"Jan\",\"Feb\",\"Mar\",\"Apr\",\"May\",\"Jun\",\"Jul\",\"Aug\",\"Sep\",\"Oct\",\"Nov\",\"Dec\"};\n\nint FormatTime(const ImPlotTime& t, char* buffer, int size, ImPlotTimeFmt fmt, bool use_24_hr_clk) {\n    tm& Tm = GImPlot->Tm;\n    GetTime(t, &Tm);\n    const int us   = t.Us % 1000;\n    const int ms   = t.Us / 1000;\n    const int sec  = Tm.tm_sec;\n    const int min  = Tm.tm_min;\n    if (use_24_hr_clk) {\n        const int hr   = Tm.tm_hour;\n        switch(fmt) {\n            case ImPlotTimeFmt_Us:        return ImFormatString(buffer, size, \".%03d %03d\", ms, us);\n            case ImPlotTimeFmt_SUs:       return ImFormatString(buffer, size, \":%02d.%03d %03d\", sec, ms, us);\n            case ImPlotTimeFmt_SMs:       return ImFormatString(buffer, size, \":%02d.%03d\", sec, ms);\n            case ImPlotTimeFmt_S:         return ImFormatString(buffer, size, \":%02d\", sec);\n            case ImPlotTimeFmt_MinSMs:    return ImFormatString(buffer, size, \":%02d:%02d.%03d\", min, sec, ms);\n            case ImPlotTimeFmt_HrMinSMs:  return ImFormatString(buffer, size, \"%02d:%02d:%02d.%03d\", hr, min, sec, ms);\n            case ImPlotTimeFmt_HrMinS:    return ImFormatString(buffer, size, \"%02d:%02d:%02d\", hr, min, sec);\n            case ImPlotTimeFmt_HrMin:     return ImFormatString(buffer, size, \"%02d:%02d\", hr, min);\n            case ImPlotTimeFmt_Hr:        return ImFormatString(buffer, size, \"%02d:00\", hr);\n            default:                      return 0;\n        }\n    }\n    else {\n        const char* ap = Tm.tm_hour < 12 ? \"am\" : \"pm\";\n        const int hr   = (Tm.tm_hour == 0 || Tm.tm_hour == 12) ? 12 : Tm.tm_hour % 12;\n        switch(fmt) {\n            case ImPlotTimeFmt_Us:        return ImFormatString(buffer, size, \".%03d %03d\", ms, us);\n            case ImPlotTimeFmt_SUs:       return ImFormatString(buffer, size, \":%02d.%03d %03d\", sec, ms, us);\n            case ImPlotTimeFmt_SMs:       return ImFormatString(buffer, size, \":%02d.%03d\", sec, ms);\n            case ImPlotTimeFmt_S:         return ImFormatString(buffer, size, \":%02d\", sec);\n            case ImPlotTimeFmt_MinSMs:    return ImFormatString(buffer, size, \":%02d:%02d.%03d\", min, sec, ms);\n            case ImPlotTimeFmt_HrMinSMs:  return ImFormatString(buffer, size, \"%d:%02d:%02d.%03d%s\", hr, min, sec, ms, ap);\n            case ImPlotTimeFmt_HrMinS:    return ImFormatString(buffer, size, \"%d:%02d:%02d%s\", hr, min, sec, ap);\n            case ImPlotTimeFmt_HrMin:     return ImFormatString(buffer, size, \"%d:%02d%s\", hr, min, ap);\n            case ImPlotTimeFmt_Hr:        return ImFormatString(buffer, size, \"%d%s\", hr, ap);\n            default:                      return 0;\n        }\n    }\n}\n\nint FormatDate(const ImPlotTime& t, char* buffer, int size, ImPlotDateFmt fmt, bool use_iso_8601) {\n    tm& Tm = GImPlot->Tm;\n    GetTime(t, &Tm);\n    const int day  = Tm.tm_mday;\n    const int mon  = Tm.tm_mon + 1;\n    const int year = Tm.tm_year + 1900;\n    const int yr   = year % 100;\n    if (use_iso_8601) {\n        switch (fmt) {\n            case ImPlotDateFmt_DayMo:   return ImFormatString(buffer, size, \"--%02d-%02d\", mon, day);\n            case ImPlotDateFmt_DayMoYr: return ImFormatString(buffer, size, \"%d-%02d-%02d\", year, mon, day);\n            case ImPlotDateFmt_MoYr:    return ImFormatString(buffer, size, \"%d-%02d\", year, mon);\n            case ImPlotDateFmt_Mo:      return ImFormatString(buffer, size, \"--%02d\", mon);\n            case ImPlotDateFmt_Yr:      return ImFormatString(buffer, size, \"%d\", year);\n            default:                    return 0;\n        }\n    }\n    else {\n        switch (fmt) {\n            case ImPlotDateFmt_DayMo:   return ImFormatString(buffer, size, \"%d/%d\", mon, day);\n            case ImPlotDateFmt_DayMoYr: return ImFormatString(buffer, size, \"%d/%d/%02d\", mon, day, yr);\n            case ImPlotDateFmt_MoYr:    return ImFormatString(buffer, size, \"%s %d\", MONTH_ABRVS[Tm.tm_mon], year);\n            case ImPlotDateFmt_Mo:      return ImFormatString(buffer, size, \"%s\", MONTH_ABRVS[Tm.tm_mon]);\n            case ImPlotDateFmt_Yr:      return ImFormatString(buffer, size, \"%d\", year);\n            default:                    return 0;\n        }\n    }\n }\n\nint FormatDateTime(const ImPlotTime& t, char* buffer, int size, ImPlotDateTimeSpec fmt) {\n    int written = 0;\n    if (fmt.Date != ImPlotDateFmt_None)\n        written += FormatDate(t, buffer, size, fmt.Date, fmt.UseISO8601);\n    if (fmt.Time != ImPlotTimeFmt_None) {\n        if (fmt.Date != ImPlotDateFmt_None)\n            buffer[written++] = ' ';\n        written += FormatTime(t, &buffer[written], size - written, fmt.Time, fmt.Use24HourClock);\n    }\n    return written;\n}\n\ninline float GetDateTimeWidth(ImPlotDateTimeSpec fmt) {\n    static const ImPlotTime t_max_width = MakeTime(2888, 12, 22, 12, 58, 58, 888888); // best guess at time that maximizes pixel width\n    char buffer[32];\n    FormatDateTime(t_max_width, buffer, 32, fmt);\n    return ImGui::CalcTextSize(buffer).x;\n}\n\ninline bool TimeLabelSame(const char* l1, const char* l2) {\n    size_t len1 = strlen(l1);\n    size_t len2 = strlen(l2);\n    size_t n  = len1 < len2 ? len1 : len2;\n    return strcmp(l1 + len1 - n, l2 + len2 - n) == 0;\n}\n\nstatic const ImPlotDateTimeSpec TimeFormatLevel0[ImPlotTimeUnit_COUNT] = {\n    ImPlotDateTimeSpec(ImPlotDateFmt_None,  ImPlotTimeFmt_Us),\n    ImPlotDateTimeSpec(ImPlotDateFmt_None,  ImPlotTimeFmt_SMs),\n    ImPlotDateTimeSpec(ImPlotDateFmt_None,  ImPlotTimeFmt_S),\n    ImPlotDateTimeSpec(ImPlotDateFmt_None,  ImPlotTimeFmt_HrMin),\n    ImPlotDateTimeSpec(ImPlotDateFmt_None,  ImPlotTimeFmt_Hr),\n    ImPlotDateTimeSpec(ImPlotDateFmt_DayMo, ImPlotTimeFmt_None),\n    ImPlotDateTimeSpec(ImPlotDateFmt_Mo,    ImPlotTimeFmt_None),\n    ImPlotDateTimeSpec(ImPlotDateFmt_Yr,    ImPlotTimeFmt_None)\n};\n\nstatic const ImPlotDateTimeSpec TimeFormatLevel1[ImPlotTimeUnit_COUNT] = {\n    ImPlotDateTimeSpec(ImPlotDateFmt_None,    ImPlotTimeFmt_HrMin),\n    ImPlotDateTimeSpec(ImPlotDateFmt_None,    ImPlotTimeFmt_HrMinS),\n    ImPlotDateTimeSpec(ImPlotDateFmt_None,    ImPlotTimeFmt_HrMin),\n    ImPlotDateTimeSpec(ImPlotDateFmt_None,    ImPlotTimeFmt_HrMin),\n    ImPlotDateTimeSpec(ImPlotDateFmt_DayMoYr, ImPlotTimeFmt_None),\n    ImPlotDateTimeSpec(ImPlotDateFmt_DayMoYr, ImPlotTimeFmt_None),\n    ImPlotDateTimeSpec(ImPlotDateFmt_Yr,      ImPlotTimeFmt_None),\n    ImPlotDateTimeSpec(ImPlotDateFmt_Yr,      ImPlotTimeFmt_None)\n};\n\nstatic const ImPlotDateTimeSpec TimeFormatLevel1First[ImPlotTimeUnit_COUNT] = {\n    ImPlotDateTimeSpec(ImPlotDateFmt_DayMoYr, ImPlotTimeFmt_HrMinS),\n    ImPlotDateTimeSpec(ImPlotDateFmt_DayMoYr, ImPlotTimeFmt_HrMinS),\n    ImPlotDateTimeSpec(ImPlotDateFmt_DayMoYr, ImPlotTimeFmt_HrMin),\n    ImPlotDateTimeSpec(ImPlotDateFmt_DayMoYr, ImPlotTimeFmt_HrMin),\n    ImPlotDateTimeSpec(ImPlotDateFmt_DayMoYr, ImPlotTimeFmt_None),\n    ImPlotDateTimeSpec(ImPlotDateFmt_DayMoYr, ImPlotTimeFmt_None),\n    ImPlotDateTimeSpec(ImPlotDateFmt_Yr,      ImPlotTimeFmt_None),\n    ImPlotDateTimeSpec(ImPlotDateFmt_Yr,      ImPlotTimeFmt_None)\n};\n\nstatic const ImPlotDateTimeSpec TimeFormatMouseCursor[ImPlotTimeUnit_COUNT] = {\n    ImPlotDateTimeSpec(ImPlotDateFmt_None,     ImPlotTimeFmt_Us),\n    ImPlotDateTimeSpec(ImPlotDateFmt_None,     ImPlotTimeFmt_SUs),\n    ImPlotDateTimeSpec(ImPlotDateFmt_None,     ImPlotTimeFmt_SMs),\n    ImPlotDateTimeSpec(ImPlotDateFmt_None,     ImPlotTimeFmt_HrMinS),\n    ImPlotDateTimeSpec(ImPlotDateFmt_None,     ImPlotTimeFmt_HrMin),\n    ImPlotDateTimeSpec(ImPlotDateFmt_DayMo,    ImPlotTimeFmt_Hr),\n    ImPlotDateTimeSpec(ImPlotDateFmt_DayMoYr,  ImPlotTimeFmt_None),\n    ImPlotDateTimeSpec(ImPlotDateFmt_MoYr,     ImPlotTimeFmt_None)\n};\n\ninline ImPlotDateTimeSpec GetDateTimeFmt(const ImPlotDateTimeSpec* ctx, ImPlotTimeUnit idx) {\n    ImPlotStyle& style     = GetStyle();\n    ImPlotDateTimeSpec fmt  = ctx[idx];\n    fmt.UseISO8601         = style.UseISO8601;\n    fmt.Use24HourClock     = style.Use24HourClock;\n    return fmt;\n}\n\nvoid Locator_Time(ImPlotTicker& ticker, const ImPlotRange& range, float pixels, bool vertical, ImPlotFormatter formatter, void* formatter_data) {\n    IM_ASSERT_USER_ERROR(vertical == false, \"Cannot locate Time ticks on vertical axis!\");\n    (void)vertical;\n    // get units for level 0 and level 1 labels\n    const ImPlotTimeUnit unit0 = GetUnitForRange(range.Size() / (pixels / 100)); // level = 0 (top)\n    const ImPlotTimeUnit unit1 = ImClamp(unit0 + 1, 0, ImPlotTimeUnit_COUNT-1);  // level = 1 (bottom)\n    // get time format specs\n    const ImPlotDateTimeSpec fmt0 = GetDateTimeFmt(TimeFormatLevel0, unit0);\n    const ImPlotDateTimeSpec fmt1 = GetDateTimeFmt(TimeFormatLevel1, unit1);\n    const ImPlotDateTimeSpec fmtf = GetDateTimeFmt(TimeFormatLevel1First, unit1);\n    // min max times\n    const ImPlotTime t_min = ImPlotTime::FromDouble(range.Min);\n    const ImPlotTime t_max = ImPlotTime::FromDouble(range.Max);\n    // maximum allowable density of labels\n    const float max_density = 0.5f;\n    // book keeping\n    int last_major_offset = -1;\n    // formatter data\n    Formatter_Time_Data ftd;\n    ftd.UserFormatter = formatter;\n    ftd.UserFormatterData = formatter_data;\n    if (unit0 != ImPlotTimeUnit_Yr) {\n        // pixels per major (level 1) division\n        const float pix_per_major_div = pixels / (float)(range.Size() / TimeUnitSpans[unit1]);\n        // nominal pixels taken up by labels\n        const float fmt0_width = GetDateTimeWidth(fmt0);\n        const float fmt1_width = GetDateTimeWidth(fmt1);\n        const float fmtf_width = GetDateTimeWidth(fmtf);\n        // the maximum number of minor (level 0) labels that can fit between major (level 1) divisions\n        const int   minor_per_major   = (int)(max_density * pix_per_major_div / fmt0_width);\n        // the minor step size (level 0)\n        const int step = GetTimeStep(minor_per_major, unit0);\n        // generate ticks\n        ImPlotTime t1 = FloorTime(ImPlotTime::FromDouble(range.Min), unit1);\n        while (t1 < t_max) {\n            // get next major\n            const ImPlotTime t2 = AddTime(t1, unit1, 1);\n            // add major tick\n            if (t1 >= t_min && t1 <= t_max) {\n                // minor level 0 tick\n                ftd.Time = t1; ftd.Spec = fmt0;\n                ticker.AddTick(t1.ToDouble(), true, 0, true, Formatter_Time, &ftd);\n                // major level 1 tick\n                ftd.Time = t1; ftd.Spec = last_major_offset < 0 ? fmtf : fmt1;\n                ImPlotTick& tick_maj = ticker.AddTick(t1.ToDouble(), true, 1, true, Formatter_Time, &ftd);\n                const char* this_major = ticker.GetText(tick_maj);\n                if (last_major_offset >= 0 && TimeLabelSame(ticker.TextBuffer.Buf.Data + last_major_offset, this_major))\n                    tick_maj.ShowLabel = false;\n                last_major_offset = tick_maj.TextOffset;\n            }\n            // add minor ticks up until next major\n            if (minor_per_major > 1 && (t_min <= t2 && t1 <= t_max)) {\n                ImPlotTime t12 = AddTime(t1, unit0, step);\n                while (t12 < t2) {\n                    float px_to_t2 = (float)((t2 - t12).ToDouble()/range.Size()) * pixels;\n                    if (t12 >= t_min && t12 <= t_max) {\n                        ftd.Time = t12; ftd.Spec = fmt0;\n                        ticker.AddTick(t12.ToDouble(), false, 0, px_to_t2 >= fmt0_width, Formatter_Time, &ftd);\n                        if (last_major_offset < 0 && px_to_t2 >= fmt0_width && px_to_t2 >= (fmt1_width + fmtf_width) / 2) {\n                            ftd.Time = t12; ftd.Spec = fmtf;\n                            ImPlotTick& tick_maj = ticker.AddTick(t12.ToDouble(), true, 1, true, Formatter_Time, &ftd);\n                            last_major_offset = tick_maj.TextOffset;\n                        }\n                    }\n                    t12 = AddTime(t12, unit0, step);\n                }\n            }\n            t1 = t2;\n        }\n    }\n    else {\n        const ImPlotDateTimeSpec fmty = GetDateTimeFmt(TimeFormatLevel0, ImPlotTimeUnit_Yr);\n        const float label_width = GetDateTimeWidth(fmty);\n        const int   max_labels  = (int)(max_density * pixels / label_width);\n        const int year_min      = GetYear(t_min);\n        const int year_max      = GetYear(CeilTime(t_max, ImPlotTimeUnit_Yr));\n        const double nice_range = NiceNum((year_max - year_min)*0.99,false);\n        const double interval   = NiceNum(nice_range / (max_labels - 1), true);\n        const int graphmin      = (int)(floor(year_min / interval) * interval);\n        const int graphmax      = (int)(ceil(year_max  / interval) * interval);\n        const int step          = (int)interval <= 0 ? 1 : (int)interval;\n\n        for (int y = graphmin; y < graphmax; y += step) {\n            ImPlotTime t = MakeTime(y);\n            if (t >= t_min && t <= t_max) {\n                ftd.Time = t; ftd.Spec = fmty;\n                ticker.AddTick(t.ToDouble(), true, 0, true, Formatter_Time, &ftd);\n            }\n        }\n    }\n}\n\n//-----------------------------------------------------------------------------\n// Context Menu\n//-----------------------------------------------------------------------------\n\ntemplate <typename F>\nbool DragFloat(const char*, F*, float, F, F) {\n    return false;\n}\n\ntemplate <>\nbool DragFloat<double>(const char* label, double* v, float v_speed, double v_min, double v_max) {\n    return ImGui::DragScalar(label, ImGuiDataType_Double, v, v_speed, &v_min, &v_max, \"%.3g\", 1);\n}\n\ntemplate <>\nbool DragFloat<float>(const char* label, float* v, float v_speed, float v_min, float v_max) {\n    return ImGui::DragScalar(label, ImGuiDataType_Float, v, v_speed, &v_min, &v_max, \"%.3g\", 1);\n}\n\ninline void BeginDisabledControls(bool cond) {\n    if (cond) {\n        ImGui::PushItemFlag(ImGuiItemFlags_Disabled, true);\n        ImGui::PushStyleVar(ImGuiStyleVar_Alpha, ImGui::GetStyle().Alpha * 0.25f);\n    }\n}\n\ninline void EndDisabledControls(bool cond) {\n    if (cond) {\n        ImGui::PopItemFlag();\n        ImGui::PopStyleVar();\n    }\n}\n\nvoid ShowAxisContextMenu(ImPlotAxis& axis, ImPlotAxis* equal_axis, bool /*time_allowed*/) {\n\n    ImGui::PushItemWidth(75);\n    bool always_locked   = axis.IsRangeLocked() || axis.IsAutoFitting();\n    bool label           = axis.HasLabel();\n    bool grid            = axis.HasGridLines();\n    bool ticks           = axis.HasTickMarks();\n    bool labels          = axis.HasTickLabels();\n    double drag_speed    = (axis.Range.Size() <= DBL_EPSILON) ? DBL_EPSILON * 1.0e+13 : 0.01 * axis.Range.Size(); // recover from almost equal axis limits.\n\n    if (axis.Scale == ImPlotScale_Time) {\n        ImPlotTime tmin = ImPlotTime::FromDouble(axis.Range.Min);\n        ImPlotTime tmax = ImPlotTime::FromDouble(axis.Range.Max);\n\n        BeginDisabledControls(always_locked);\n        ImGui::CheckboxFlags(\"##LockMin\", (unsigned int*)&axis.Flags, ImPlotAxisFlags_LockMin);\n        EndDisabledControls(always_locked);\n        ImGui::SameLine();\n        BeginDisabledControls(axis.IsLockedMin() || always_locked);\n        if (ImGui::BeginMenu(\"Min Time\")) {\n            if (ShowTimePicker(\"mintime\", &tmin)) {\n                if (tmin >= tmax)\n                    tmax = AddTime(tmin, ImPlotTimeUnit_S, 1);\n                axis.SetRange(tmin.ToDouble(),tmax.ToDouble());\n            }\n            ImGui::Separator();\n            if (ShowDatePicker(\"mindate\",&axis.PickerLevel,&axis.PickerTimeMin,&tmin,&tmax)) {\n                tmin = CombineDateTime(axis.PickerTimeMin, tmin);\n                if (tmin >= tmax)\n                    tmax = AddTime(tmin, ImPlotTimeUnit_S, 1);\n                axis.SetRange(tmin.ToDouble(), tmax.ToDouble());\n            }\n            ImGui::EndMenu();\n        }\n        EndDisabledControls(axis.IsLockedMin() || always_locked);\n\n        BeginDisabledControls(always_locked);\n        ImGui::CheckboxFlags(\"##LockMax\", (unsigned int*)&axis.Flags, ImPlotAxisFlags_LockMax);\n        EndDisabledControls(always_locked);\n        ImGui::SameLine();\n        BeginDisabledControls(axis.IsLockedMax() || always_locked);\n        if (ImGui::BeginMenu(\"Max Time\")) {\n            if (ShowTimePicker(\"maxtime\", &tmax)) {\n                if (tmax <= tmin)\n                    tmin = AddTime(tmax, ImPlotTimeUnit_S, -1);\n                axis.SetRange(tmin.ToDouble(),tmax.ToDouble());\n            }\n            ImGui::Separator();\n            if (ShowDatePicker(\"maxdate\",&axis.PickerLevel,&axis.PickerTimeMax,&tmin,&tmax)) {\n                tmax = CombineDateTime(axis.PickerTimeMax, tmax);\n                if (tmax <= tmin)\n                    tmin = AddTime(tmax, ImPlotTimeUnit_S, -1);\n                axis.SetRange(tmin.ToDouble(), tmax.ToDouble());\n            }\n            ImGui::EndMenu();\n        }\n        EndDisabledControls(axis.IsLockedMax() || always_locked);\n    }\n    else {\n        BeginDisabledControls(always_locked);\n        ImGui::CheckboxFlags(\"##LockMin\", (unsigned int*)&axis.Flags, ImPlotAxisFlags_LockMin);\n        EndDisabledControls(always_locked);\n        ImGui::SameLine();\n        BeginDisabledControls(axis.IsLockedMin() || always_locked);\n        double temp_min = axis.Range.Min;\n        if (DragFloat(\"Min\", &temp_min, (float)drag_speed, -HUGE_VAL, axis.Range.Max - DBL_EPSILON)) {\n            axis.SetMin(temp_min,true);\n            if (equal_axis != nullptr)\n                equal_axis->SetAspect(axis.GetAspect());\n        }\n        EndDisabledControls(axis.IsLockedMin() || always_locked);\n\n        BeginDisabledControls(always_locked);\n        ImGui::CheckboxFlags(\"##LockMax\", (unsigned int*)&axis.Flags, ImPlotAxisFlags_LockMax);\n        EndDisabledControls(always_locked);\n        ImGui::SameLine();\n        BeginDisabledControls(axis.IsLockedMax() || always_locked);\n        double temp_max = axis.Range.Max;\n        if (DragFloat(\"Max\", &temp_max, (float)drag_speed, axis.Range.Min + DBL_EPSILON, HUGE_VAL)) {\n            axis.SetMax(temp_max,true);\n            if (equal_axis != nullptr)\n                equal_axis->SetAspect(axis.GetAspect());\n        }\n        EndDisabledControls(axis.IsLockedMax() || always_locked);\n    }\n\n    ImGui::Separator();\n\n    ImGui::CheckboxFlags(\"Auto-Fit\",(unsigned int*)&axis.Flags, ImPlotAxisFlags_AutoFit);\n    // TODO\n    // BeginDisabledControls(axis.IsTime() && time_allowed);\n    // ImGui::CheckboxFlags(\"Log Scale\",(unsigned int*)&axis.Flags, ImPlotAxisFlags_LogScale);\n    // EndDisabledControls(axis.IsTime() && time_allowed);\n    // if (time_allowed) {\n    //     BeginDisabledControls(axis.IsLog() || axis.IsSymLog());\n    //     ImGui::CheckboxFlags(\"Time\",(unsigned int*)&axis.Flags, ImPlotAxisFlags_Time);\n    //     EndDisabledControls(axis.IsLog() || axis.IsSymLog());\n    // }\n    ImGui::Separator();\n    ImGui::CheckboxFlags(\"Invert\",(unsigned int*)&axis.Flags, ImPlotAxisFlags_Invert);\n    ImGui::CheckboxFlags(\"Opposite\",(unsigned int*)&axis.Flags, ImPlotAxisFlags_Opposite);\n    ImGui::Separator();\n    BeginDisabledControls(axis.LabelOffset == -1);\n    if (ImGui::Checkbox(\"Label\", &label))\n        ImFlipFlag(axis.Flags, ImPlotAxisFlags_NoLabel);\n    EndDisabledControls(axis.LabelOffset == -1);\n    if (ImGui::Checkbox(\"Grid Lines\", &grid))\n        ImFlipFlag(axis.Flags, ImPlotAxisFlags_NoGridLines);\n    if (ImGui::Checkbox(\"Tick Marks\", &ticks))\n        ImFlipFlag(axis.Flags, ImPlotAxisFlags_NoTickMarks);\n    if (ImGui::Checkbox(\"Tick Labels\", &labels))\n        ImFlipFlag(axis.Flags, ImPlotAxisFlags_NoTickLabels);\n\n}\n\nbool ShowLegendContextMenu(ImPlotLegend& legend, bool visible) {\n    const float s = ImGui::GetFrameHeight();\n    bool ret = false;\n    if (ImGui::Checkbox(\"Show\",&visible))\n        ret = true;\n    if (legend.CanGoInside)\n        ImGui::CheckboxFlags(\"Outside\",(unsigned int*)&legend.Flags, ImPlotLegendFlags_Outside);\n    if (ImGui::RadioButton(\"H\", ImHasFlag(legend.Flags, ImPlotLegendFlags_Horizontal)))\n        legend.Flags |= ImPlotLegendFlags_Horizontal;\n    ImGui::SameLine();\n    if (ImGui::RadioButton(\"V\", !ImHasFlag(legend.Flags, ImPlotLegendFlags_Horizontal)))\n        legend.Flags &= ~ImPlotLegendFlags_Horizontal;\n    ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(2,2));\n    if (ImGui::Button(\"NW\",ImVec2(1.5f*s,s))) { legend.Location = ImPlotLocation_NorthWest; } ImGui::SameLine();\n    if (ImGui::Button(\"N\", ImVec2(1.5f*s,s))) { legend.Location = ImPlotLocation_North;     } ImGui::SameLine();\n    if (ImGui::Button(\"NE\",ImVec2(1.5f*s,s))) { legend.Location = ImPlotLocation_NorthEast; }\n    if (ImGui::Button(\"W\", ImVec2(1.5f*s,s))) { legend.Location = ImPlotLocation_West;      } ImGui::SameLine();\n    if (ImGui::InvisibleButton(\"C\", ImVec2(1.5f*s,s))) {     } ImGui::SameLine();\n    if (ImGui::Button(\"E\", ImVec2(1.5f*s,s))) { legend.Location = ImPlotLocation_East;      }\n    if (ImGui::Button(\"SW\",ImVec2(1.5f*s,s))) { legend.Location = ImPlotLocation_SouthWest; } ImGui::SameLine();\n    if (ImGui::Button(\"S\", ImVec2(1.5f*s,s))) { legend.Location = ImPlotLocation_South;     } ImGui::SameLine();\n    if (ImGui::Button(\"SE\",ImVec2(1.5f*s,s))) { legend.Location = ImPlotLocation_SouthEast; }\n    ImGui::PopStyleVar();\n    return ret;\n}\n\nvoid ShowSubplotsContextMenu(ImPlotSubplot& subplot) {\n    if ((ImGui::BeginMenu(\"Linking\"))) {\n        if (ImGui::MenuItem(\"Link Rows\",nullptr,ImHasFlag(subplot.Flags, ImPlotSubplotFlags_LinkRows)))\n            ImFlipFlag(subplot.Flags, ImPlotSubplotFlags_LinkRows);\n        if (ImGui::MenuItem(\"Link Cols\",nullptr,ImHasFlag(subplot.Flags, ImPlotSubplotFlags_LinkCols)))\n            ImFlipFlag(subplot.Flags, ImPlotSubplotFlags_LinkCols);\n        if (ImGui::MenuItem(\"Link All X\",nullptr,ImHasFlag(subplot.Flags, ImPlotSubplotFlags_LinkAllX)))\n            ImFlipFlag(subplot.Flags, ImPlotSubplotFlags_LinkAllX);\n        if (ImGui::MenuItem(\"Link All Y\",nullptr,ImHasFlag(subplot.Flags, ImPlotSubplotFlags_LinkAllY)))\n            ImFlipFlag(subplot.Flags, ImPlotSubplotFlags_LinkAllY);\n        ImGui::EndMenu();\n    }\n    if ((ImGui::BeginMenu(\"Settings\"))) {\n        BeginDisabledControls(!subplot.HasTitle);\n        if (ImGui::MenuItem(\"Title\",nullptr,subplot.HasTitle && !ImHasFlag(subplot.Flags, ImPlotSubplotFlags_NoTitle)))\n            ImFlipFlag(subplot.Flags, ImPlotSubplotFlags_NoTitle);\n        EndDisabledControls(!subplot.HasTitle);\n        if (ImGui::MenuItem(\"Resizable\",nullptr,!ImHasFlag(subplot.Flags, ImPlotSubplotFlags_NoResize)))\n            ImFlipFlag(subplot.Flags, ImPlotSubplotFlags_NoResize);\n        if (ImGui::MenuItem(\"Align\",nullptr,!ImHasFlag(subplot.Flags, ImPlotSubplotFlags_NoAlign)))\n            ImFlipFlag(subplot.Flags, ImPlotSubplotFlags_NoAlign);\n        if (ImGui::MenuItem(\"Share Items\",nullptr,ImHasFlag(subplot.Flags, ImPlotSubplotFlags_ShareItems)))\n            ImFlipFlag(subplot.Flags, ImPlotSubplotFlags_ShareItems);\n        ImGui::EndMenu();\n    }\n}\n\nvoid ShowPlotContextMenu(ImPlotPlot& plot) {\n    ImPlotContext& gp = *GImPlot;\n    const bool owns_legend = gp.CurrentItems == &plot.Items;\n    const bool equal = ImHasFlag(plot.Flags, ImPlotFlags_Equal);\n\n    char buf[16] = {};\n\n    for (int i = 0; i < IMPLOT_NUM_X_AXES; i++) {\n        ImPlotAxis& x_axis = plot.XAxis(i);\n        if (!x_axis.Enabled || !x_axis.HasMenus())\n            continue;\n        ImGui::PushID(i);\n        ImFormatString(buf, sizeof(buf) - 1, i == 0 ? \"X-Axis\" : \"X-Axis %d\", i + 1);\n        if (ImGui::BeginMenu(x_axis.HasLabel() ? plot.GetAxisLabel(x_axis) : buf)) {\n            ShowAxisContextMenu(x_axis, equal ? x_axis.OrthoAxis : nullptr, false);\n            ImGui::EndMenu();\n        }\n        ImGui::PopID();\n    }\n\n    for (int i = 0; i < IMPLOT_NUM_Y_AXES; i++) {\n        ImPlotAxis& y_axis = plot.YAxis(i);\n        if (!y_axis.Enabled || !y_axis.HasMenus())\n            continue;\n        ImGui::PushID(i);\n        ImFormatString(buf, sizeof(buf) - 1, i == 0 ? \"Y-Axis\" : \"Y-Axis %d\", i + 1);\n        if (ImGui::BeginMenu(y_axis.HasLabel() ? plot.GetAxisLabel(y_axis) : buf)) {\n            ShowAxisContextMenu(y_axis, equal ? y_axis.OrthoAxis : nullptr, false);\n            ImGui::EndMenu();\n        }\n        ImGui::PopID();\n    }\n\n    ImGui::Separator();\n    if (!ImHasFlag(gp.CurrentItems->Legend.Flags, ImPlotLegendFlags_NoMenus)) {\n        if ((ImGui::BeginMenu(\"Legend\"))) {\n            if (owns_legend) {\n                if (ShowLegendContextMenu(plot.Items.Legend, !ImHasFlag(plot.Flags, ImPlotFlags_NoLegend)))\n                    ImFlipFlag(plot.Flags, ImPlotFlags_NoLegend);\n            }\n            else if (gp.CurrentSubplot != nullptr) {\n                if (ShowLegendContextMenu(gp.CurrentSubplot->Items.Legend, !ImHasFlag(gp.CurrentSubplot->Flags, ImPlotSubplotFlags_NoLegend)))\n                    ImFlipFlag(gp.CurrentSubplot->Flags, ImPlotSubplotFlags_NoLegend);\n            }\n            ImGui::EndMenu();\n        }\n    }\n    if ((ImGui::BeginMenu(\"Settings\"))) {\n        if (ImGui::MenuItem(\"Equal\", nullptr, ImHasFlag(plot.Flags, ImPlotFlags_Equal)))\n            ImFlipFlag(plot.Flags, ImPlotFlags_Equal);\n        if (ImGui::MenuItem(\"Box Select\",nullptr,!ImHasFlag(plot.Flags, ImPlotFlags_NoBoxSelect)))\n            ImFlipFlag(plot.Flags, ImPlotFlags_NoBoxSelect);\n        BeginDisabledControls(plot.TitleOffset == -1);\n        if (ImGui::MenuItem(\"Title\",nullptr,plot.HasTitle()))\n            ImFlipFlag(plot.Flags, ImPlotFlags_NoTitle);\n        EndDisabledControls(plot.TitleOffset == -1);\n        if (ImGui::MenuItem(\"Mouse Position\",nullptr,!ImHasFlag(plot.Flags, ImPlotFlags_NoMouseText)))\n            ImFlipFlag(plot.Flags, ImPlotFlags_NoMouseText);\n        if (ImGui::MenuItem(\"Crosshairs\",nullptr,ImHasFlag(plot.Flags, ImPlotFlags_Crosshairs)))\n            ImFlipFlag(plot.Flags, ImPlotFlags_Crosshairs);\n        ImGui::EndMenu();\n    }\n    if (gp.CurrentSubplot != nullptr && !ImHasFlag(gp.CurrentSubplot->Flags, ImPlotSubplotFlags_NoMenus)) {\n        ImGui::Separator();\n        if ((ImGui::BeginMenu(\"Subplots\"))) {\n            ShowSubplotsContextMenu(*gp.CurrentSubplot);\n            ImGui::EndMenu();\n        }\n    }\n}\n\n//-----------------------------------------------------------------------------\n// Axis Utils\n//-----------------------------------------------------------------------------\n\nstatic inline int AxisPrecision(const ImPlotAxis& axis) {\n    const double range = axis.Ticker.TickCount() > 1 ? (axis.Ticker.Ticks[1].PlotPos - axis.Ticker.Ticks[0].PlotPos) : axis.Range.Size();\n    return Precision(range);\n}\n\nstatic inline double RoundAxisValue(const ImPlotAxis& axis, double value) {\n    return RoundTo(value, AxisPrecision(axis));\n}\n\nvoid LabelAxisValue(const ImPlotAxis& axis, double value, char* buff, int size, bool round) {\n    ImPlotContext& gp = *GImPlot;\n    // TODO: We shouldn't explicitly check that the axis is Time here. Ideally,\n    // Formatter_Time would handle the formatting for us, but the code below\n    // needs additional arguments which are not currently available in ImPlotFormatter\n    if (axis.Locator == Locator_Time) {\n        ImPlotTimeUnit unit = axis.Vertical\n                            ? GetUnitForRange(axis.Range.Size() / (gp.CurrentPlot->PlotRect.GetHeight() / 100)) // TODO: magic value!\n                            : GetUnitForRange(axis.Range.Size() / (gp.CurrentPlot->PlotRect.GetWidth() / 100)); // TODO: magic value!\n        FormatDateTime(ImPlotTime::FromDouble(value), buff, size, GetDateTimeFmt(TimeFormatMouseCursor, unit));\n    }\n    else {\n        if (round)\n            value = RoundAxisValue(axis, value);\n        axis.Formatter(value, buff, size, axis.FormatterData);\n    }\n}\n\nvoid UpdateAxisColors(ImPlotAxis& axis) {\n    const ImVec4 col_grid = GetStyleColorVec4(ImPlotCol_AxisGrid);\n    axis.ColorMaj         = ImGui::GetColorU32(col_grid);\n    axis.ColorMin         = ImGui::GetColorU32(col_grid*ImVec4(1,1,1,GImPlot->Style.MinorAlpha));\n    axis.ColorTick        = GetStyleColorU32(ImPlotCol_AxisTick);\n    axis.ColorTxt         = GetStyleColorU32(ImPlotCol_AxisText);\n    axis.ColorBg          = GetStyleColorU32(ImPlotCol_AxisBg);\n    axis.ColorHov         = GetStyleColorU32(ImPlotCol_AxisBgHovered);\n    axis.ColorAct         = GetStyleColorU32(ImPlotCol_AxisBgActive);\n    // axis.ColorHiLi     = IM_COL32_BLACK_TRANS;\n}\n\nvoid PadAndDatumAxesX(ImPlotPlot& plot, float& pad_T, float& pad_B, ImPlotAlignmentData* align) {\n\n    ImPlotContext& gp = *GImPlot;\n\n    const float T = ImGui::GetTextLineHeight();\n    const float P = gp.Style.LabelPadding.y;\n    const float K = gp.Style.MinorTickLen.x;\n\n    int   count_T = 0;\n    int   count_B = 0;\n    float last_T  = plot.AxesRect.Min.y;\n    float last_B  = plot.AxesRect.Max.y;\n\n    for (int i = IMPLOT_NUM_X_AXES; i-- > 0;) { // FYI: can iterate forward\n        ImPlotAxis& axis = plot.XAxis(i);\n        if (!axis.Enabled)\n            continue;\n        const bool label = axis.HasLabel();\n        const bool ticks = axis.HasTickLabels();\n        const bool opp   = axis.IsOpposite();\n        const bool time  = axis.Scale == ImPlotScale_Time;\n        if (opp) {\n            if (count_T++ > 0)\n                pad_T += K + P;\n            if (label)\n                pad_T += T + P;\n            if (ticks)\n                pad_T += ImMax(T, axis.Ticker.MaxSize.y) + P + (time ? T + P : 0);\n            axis.Datum1 = plot.CanvasRect.Min.y + pad_T;\n            axis.Datum2 = last_T;\n            last_T = axis.Datum1;\n        }\n        else {\n            if (count_B++ > 0)\n                pad_B += K + P;\n            if (label)\n                pad_B += T + P;\n            if (ticks)\n                pad_B += ImMax(T, axis.Ticker.MaxSize.y) + P + (time ? T + P : 0);\n            axis.Datum1 = plot.CanvasRect.Max.y - pad_B;\n            axis.Datum2 = last_B;\n            last_B = axis.Datum1;\n        }\n    }\n\n    if (align) {\n        count_T = count_B = 0;\n        float delta_T, delta_B;\n        align->Update(pad_T,pad_B,delta_T,delta_B);\n        for (int i = IMPLOT_NUM_X_AXES; i-- > 0;) {\n            ImPlotAxis& axis = plot.XAxis(i);\n            if (!axis.Enabled)\n                continue;\n            if (axis.IsOpposite()) {\n                axis.Datum1 += delta_T;\n                axis.Datum2 += count_T++ > 1 ? delta_T : 0;\n            }\n            else {\n                axis.Datum1 -= delta_B;\n                axis.Datum2 -= count_B++ > 1 ? delta_B : 0;\n            }\n        }\n    }\n}\n\nvoid PadAndDatumAxesY(ImPlotPlot& plot, float& pad_L, float& pad_R, ImPlotAlignmentData* align) {\n\n    //   [   pad_L   ]                 [   pad_R   ]\n    //   .................CanvasRect................\n    //   :TPWPK.PTPWP _____PlotRect____ PWPTP.KPWPT:\n    //   :A # |- A # |-               -| # A -| # A:\n    //   :X   |  X   |                 |   X  |   x:\n    //   :I # |- I # |-               -| # I -| # I:\n    //   :S   |  S   |                 |   S  |   S:\n    //   :3 # |- 0 # |-_______________-| # 1 -| # 2:\n    //   :.........................................:\n    //\n    //   T = text height\n    //   P = label padding\n    //   K = minor tick length\n    //   W = label width\n\n    ImPlotContext& gp = *GImPlot;\n\n    const float T = ImGui::GetTextLineHeight();\n    const float P = gp.Style.LabelPadding.x;\n    const float K = gp.Style.MinorTickLen.y;\n\n    int   count_L = 0;\n    int   count_R = 0;\n    float last_L  = plot.AxesRect.Min.x;\n    float last_R  = plot.AxesRect.Max.x;\n\n    for (int i = IMPLOT_NUM_Y_AXES; i-- > 0;) { // FYI: can iterate forward\n        ImPlotAxis& axis = plot.YAxis(i);\n        if (!axis.Enabled)\n            continue;\n        const bool label = axis.HasLabel();\n        const bool ticks = axis.HasTickLabels();\n        const bool opp   = axis.IsOpposite();\n        if (opp) {\n            if (count_R++ > 0)\n                pad_R += K + P;\n            if (label)\n                pad_R += T + P;\n            if (ticks)\n                pad_R += axis.Ticker.MaxSize.x + P;\n            axis.Datum1 = plot.CanvasRect.Max.x - pad_R;\n            axis.Datum2 = last_R;\n            last_R = axis.Datum1;\n        }\n        else {\n            if (count_L++ > 0)\n                pad_L += K + P;\n            if (label)\n                pad_L += T + P;\n            if (ticks)\n                pad_L += axis.Ticker.MaxSize.x + P;\n            axis.Datum1 = plot.CanvasRect.Min.x + pad_L;\n            axis.Datum2 = last_L;\n            last_L = axis.Datum1;\n        }\n    }\n\n    plot.PlotRect.Min.x = plot.CanvasRect.Min.x + pad_L;\n    plot.PlotRect.Max.x = plot.CanvasRect.Max.x - pad_R;\n\n    if (align) {\n        count_L = count_R = 0;\n        float delta_L, delta_R;\n        align->Update(pad_L,pad_R,delta_L,delta_R);\n        for (int i = IMPLOT_NUM_Y_AXES; i-- > 0;) {\n            ImPlotAxis& axis = plot.YAxis(i);\n            if (!axis.Enabled)\n                continue;\n            if (axis.IsOpposite()) {\n                axis.Datum1 -= delta_R;\n                axis.Datum2 -= count_R++ > 1 ? delta_R : 0;\n            }\n            else {\n                axis.Datum1 += delta_L;\n                axis.Datum2 += count_L++ > 1 ? delta_L : 0;\n            }\n        }\n    }\n}\n\n//-----------------------------------------------------------------------------\n// RENDERING\n//-----------------------------------------------------------------------------\n\nstatic inline void RenderGridLinesX(ImDrawList& DrawList, const ImPlotTicker& ticker, const ImRect& rect, ImU32 col_maj, ImU32 col_min, float size_maj, float size_min) {\n    const float density   = ticker.TickCount() / rect.GetWidth();\n    ImVec4 col_min4  = ImGui::ColorConvertU32ToFloat4(col_min);\n    col_min4.w      *= ImClamp(ImRemap(density, 0.1f, 0.2f, 1.0f, 0.0f), 0.0f, 1.0f);\n    col_min = ImGui::ColorConvertFloat4ToU32(col_min4);\n    for (int t = 0; t < ticker.TickCount(); t++) {\n        const ImPlotTick& xt = ticker.Ticks[t];\n        if (xt.PixelPos < rect.Min.x || xt.PixelPos > rect.Max.x)\n            continue;\n        if (xt.Level == 0) {\n            if (xt.Major)\n                DrawList.AddLine(ImVec2(xt.PixelPos, rect.Min.y), ImVec2(xt.PixelPos, rect.Max.y), col_maj, size_maj);\n            else if (density < 0.2f)\n                DrawList.AddLine(ImVec2(xt.PixelPos, rect.Min.y), ImVec2(xt.PixelPos, rect.Max.y), col_min, size_min);\n        }\n    }\n}\n\nstatic inline void RenderGridLinesY(ImDrawList& DrawList, const ImPlotTicker& ticker, const ImRect& rect, ImU32 col_maj, ImU32 col_min, float size_maj, float size_min) {\n    const float density   = ticker.TickCount() / rect.GetHeight();\n    ImVec4 col_min4  = ImGui::ColorConvertU32ToFloat4(col_min);\n    col_min4.w      *= ImClamp(ImRemap(density, 0.1f, 0.2f, 1.0f, 0.0f), 0.0f, 1.0f);\n    col_min = ImGui::ColorConvertFloat4ToU32(col_min4);\n    for (int t = 0; t < ticker.TickCount(); t++) {\n        const ImPlotTick& yt = ticker.Ticks[t];\n        if (yt.PixelPos < rect.Min.y || yt.PixelPos > rect.Max.y)\n            continue;\n        if (yt.Major)\n            DrawList.AddLine(ImVec2(rect.Min.x, yt.PixelPos), ImVec2(rect.Max.x, yt.PixelPos), col_maj, size_maj);\n        else if (density < 0.2f)\n            DrawList.AddLine(ImVec2(rect.Min.x, yt.PixelPos), ImVec2(rect.Max.x, yt.PixelPos), col_min, size_min);\n    }\n}\n\nstatic inline void RenderSelectionRect(ImDrawList& DrawList, const ImVec2& p_min, const ImVec2& p_max, const ImVec4& col) {\n    const ImU32 col_bg = ImGui::GetColorU32(col * ImVec4(1,1,1,0.25f));\n    const ImU32 col_bd = ImGui::GetColorU32(col);\n    DrawList.AddRectFilled(p_min, p_max, col_bg);\n    DrawList.AddRect(p_min, p_max, col_bd);\n}\n\n//-----------------------------------------------------------------------------\n// Input Handling\n//-----------------------------------------------------------------------------\n\nstatic const float MOUSE_CURSOR_DRAG_THRESHOLD = 5.0f;\nstatic const float BOX_SELECT_DRAG_THRESHOLD   = 4.0f;\n\nbool UpdateInput(ImPlotPlot& plot) {\n\n    bool changed = false;\n\n    ImPlotContext& gp = *GImPlot;\n    ImGuiIO& IO = ImGui::GetIO();\n\n    // BUTTON STATE -----------------------------------------------------------\n\n    const ImGuiButtonFlags plot_button_flags = ImGuiButtonFlags_AllowOverlap\n                                             | ImGuiButtonFlags_PressedOnClick\n                                             | ImGuiButtonFlags_PressedOnDoubleClick\n                                             | ImGuiButtonFlags_MouseButtonLeft\n                                             | ImGuiButtonFlags_MouseButtonRight\n                                             | ImGuiButtonFlags_MouseButtonMiddle;\n    const ImGuiButtonFlags axis_button_flags = ImGuiButtonFlags_FlattenChildren\n                                             | plot_button_flags;\n\n    const bool plot_clicked = ImGui::ButtonBehavior(plot.PlotRect,plot.ID,&plot.Hovered,&plot.Held,plot_button_flags);\n#if (IMGUI_VERSION_NUM < 18966)\n    ImGui::SetItemAllowOverlap(); // Handled by ButtonBehavior()\n#endif\n\n    if (plot_clicked) {\n        if (!ImHasFlag(plot.Flags, ImPlotFlags_NoBoxSelect) && IO.MouseClicked[gp.InputMap.Select] && ImHasFlag(IO.KeyMods, gp.InputMap.SelectMod)) {\n            plot.Selecting   = true;\n            plot.SelectStart = IO.MousePos;\n            plot.SelectRect  = ImRect(0,0,0,0);\n        }\n        if (IO.MouseDoubleClicked[gp.InputMap.Fit]) {\n            plot.FitThisFrame = true;\n            for (int i = 0; i < ImAxis_COUNT; ++i)\n                plot.Axes[i].FitThisFrame = true;\n        }\n    }\n\n    const bool can_pan = IO.MouseDown[gp.InputMap.Pan] && ImHasFlag(IO.KeyMods, gp.InputMap.PanMod);\n\n    plot.Held = plot.Held && can_pan;\n\n    bool x_click[IMPLOT_NUM_X_AXES] = {false};\n    bool x_held[IMPLOT_NUM_X_AXES]  = {false};\n    bool x_hov[IMPLOT_NUM_X_AXES]   = {false};\n\n    bool y_click[IMPLOT_NUM_Y_AXES] = {false};\n    bool y_held[IMPLOT_NUM_Y_AXES]  = {false};\n    bool y_hov[IMPLOT_NUM_Y_AXES]   = {false};\n\n    for (int i = 0; i < IMPLOT_NUM_X_AXES; ++i) {\n        ImPlotAxis& xax = plot.XAxis(i);\n        if (xax.Enabled) {\n            ImGui::KeepAliveID(xax.ID);\n            x_click[i]  = ImGui::ButtonBehavior(xax.HoverRect,xax.ID,&xax.Hovered,&xax.Held,axis_button_flags);\n            if (x_click[i] && IO.MouseDoubleClicked[gp.InputMap.Fit])\n                plot.FitThisFrame = xax.FitThisFrame = true;\n            xax.Held  = xax.Held && can_pan;\n            x_hov[i]  = xax.Hovered || plot.Hovered;\n            x_held[i] = xax.Held    || plot.Held;\n        }\n    }\n\n    for (int i = 0; i < IMPLOT_NUM_Y_AXES; ++i) {\n        ImPlotAxis& yax = plot.YAxis(i);\n        if (yax.Enabled) {\n            ImGui::KeepAliveID(yax.ID);\n            y_click[i]  = ImGui::ButtonBehavior(yax.HoverRect,yax.ID,&yax.Hovered,&yax.Held,axis_button_flags);\n            if (y_click[i] && IO.MouseDoubleClicked[gp.InputMap.Fit])\n                plot.FitThisFrame = yax.FitThisFrame = true;\n            yax.Held  = yax.Held && can_pan;\n            y_hov[i]  = yax.Hovered || plot.Hovered;\n            y_held[i] = yax.Held    || plot.Held;\n        }\n    }\n\n    // cancel due to DND activity\n    if (GImGui->DragDropActive || (IO.KeyMods == gp.InputMap.OverrideMod && gp.InputMap.OverrideMod != 0))\n        return false;\n\n    // STATE -------------------------------------------------------------------\n\n    const bool axis_equal      = ImHasFlag(plot.Flags, ImPlotFlags_Equal);\n\n    const bool any_x_hov       = plot.Hovered || AnyAxesHovered(&plot.Axes[ImAxis_X1], IMPLOT_NUM_X_AXES);\n    const bool any_x_held      = plot.Held    || AnyAxesHeld(&plot.Axes[ImAxis_X1], IMPLOT_NUM_X_AXES);\n    const bool any_y_hov       = plot.Hovered || AnyAxesHovered(&plot.Axes[ImAxis_Y1], IMPLOT_NUM_Y_AXES);\n    const bool any_y_held      = plot.Held    || AnyAxesHeld(&plot.Axes[ImAxis_Y1], IMPLOT_NUM_Y_AXES);\n    const bool any_hov         = any_x_hov    || any_y_hov;\n    const bool any_held        = any_x_held   || any_y_held;\n\n    const ImVec2 select_drag   = ImGui::GetMouseDragDelta(gp.InputMap.Select);\n    const ImVec2 pan_drag      = ImGui::GetMouseDragDelta(gp.InputMap.Pan);\n    const float select_drag_sq = ImLengthSqr(select_drag);\n    const float pan_drag_sq    = ImLengthSqr(pan_drag);\n    const bool selecting       = plot.Selecting && select_drag_sq > MOUSE_CURSOR_DRAG_THRESHOLD;\n    const bool panning         = any_held       && pan_drag_sq    > MOUSE_CURSOR_DRAG_THRESHOLD;\n\n    // CONTEXT MENU -----------------------------------------------------------\n\n    if (IO.MouseReleased[gp.InputMap.Menu] && !plot.ContextLocked)\n        gp.OpenContextThisFrame = true;\n\n    if (selecting || panning)\n        plot.ContextLocked = true;\n    else if (!(IO.MouseDown[gp.InputMap.Menu] || IO.MouseReleased[gp.InputMap.Menu]))\n        plot.ContextLocked = false;\n\n    // DRAG INPUT -------------------------------------------------------------\n\n    if (any_held && !plot.Selecting) {\n        int drag_direction = 0;\n        for (int i = 0; i < IMPLOT_NUM_X_AXES; i++) {\n            ImPlotAxis& x_axis = plot.XAxis(i);\n            if (x_held[i] && !x_axis.IsInputLocked()) {\n                drag_direction |= (1 << 1);\n                bool increasing = x_axis.IsInverted() ? IO.MouseDelta.x > 0 : IO.MouseDelta.x < 0;\n                if (IO.MouseDelta.x != 0 && !x_axis.IsPanLocked(increasing)) {\n                    const double plot_l = x_axis.PixelsToPlot(plot.PlotRect.Min.x - IO.MouseDelta.x);\n                    const double plot_r = x_axis.PixelsToPlot(plot.PlotRect.Max.x - IO.MouseDelta.x);\n                    x_axis.SetMin(x_axis.IsInverted() ? plot_r : plot_l);\n                    x_axis.SetMax(x_axis.IsInverted() ? plot_l : plot_r);\n                    if (axis_equal && x_axis.OrthoAxis != nullptr)\n                        x_axis.OrthoAxis->SetAspect(x_axis.GetAspect());\n                    changed = true;\n                }\n            }\n        }\n        for (int i = 0; i < IMPLOT_NUM_Y_AXES; i++) {\n            ImPlotAxis& y_axis = plot.YAxis(i);\n            if (y_held[i] && !y_axis.IsInputLocked()) {\n                drag_direction |= (1 << 2);\n                bool increasing = y_axis.IsInverted() ? IO.MouseDelta.y < 0 : IO.MouseDelta.y > 0;\n                if (IO.MouseDelta.y != 0 && !y_axis.IsPanLocked(increasing)) {\n                    const double plot_t = y_axis.PixelsToPlot(plot.PlotRect.Min.y - IO.MouseDelta.y);\n                    const double plot_b = y_axis.PixelsToPlot(plot.PlotRect.Max.y - IO.MouseDelta.y);\n                    y_axis.SetMin(y_axis.IsInverted() ? plot_t : plot_b);\n                    y_axis.SetMax(y_axis.IsInverted() ? plot_b : plot_t);\n                    if (axis_equal && y_axis.OrthoAxis != nullptr)\n                        y_axis.OrthoAxis->SetAspect(y_axis.GetAspect());\n                    changed = true;\n                }\n            }\n        }\n        if (IO.MouseDragMaxDistanceSqr[gp.InputMap.Pan] > MOUSE_CURSOR_DRAG_THRESHOLD) {\n            switch (drag_direction) {\n                case 0        : ImGui::SetMouseCursor(ImGuiMouseCursor_NotAllowed); break;\n                case (1 << 1) : ImGui::SetMouseCursor(ImGuiMouseCursor_ResizeEW);   break;\n                case (1 << 2) : ImGui::SetMouseCursor(ImGuiMouseCursor_ResizeNS);   break;\n                default       : ImGui::SetMouseCursor(ImGuiMouseCursor_ResizeAll);  break;\n            }\n        }\n    }\n\n    // SCROLL INPUT -----------------------------------------------------------\n\n    if (any_hov && IO.MouseWheel != 0 && ImHasFlag(IO.KeyMods, gp.InputMap.ZoomMod)) {\n\n        float zoom_rate = gp.InputMap.ZoomRate;\n        if (IO.MouseWheel > 0)\n            zoom_rate = (-zoom_rate) / (1.0f + (2.0f * zoom_rate));\n        ImVec2 rect_size = plot.PlotRect.GetSize();\n        float tx = ImRemap(IO.MousePos.x, plot.PlotRect.Min.x, plot.PlotRect.Max.x, 0.0f, 1.0f);\n        float ty = ImRemap(IO.MousePos.y, plot.PlotRect.Min.y, plot.PlotRect.Max.y, 0.0f, 1.0f);\n\n        for (int i = 0; i < IMPLOT_NUM_X_AXES; i++) {\n            ImPlotAxis& x_axis = plot.XAxis(i);\n            const bool equal_zoom   = axis_equal && x_axis.OrthoAxis != nullptr;\n            const bool equal_locked = (equal_zoom != false) && x_axis.OrthoAxis->IsInputLocked();\n            if (x_hov[i] && !x_axis.IsInputLocked() && !equal_locked) {\n                float correction = (plot.Hovered && equal_zoom) ? 0.5f : 1.0f;\n                const double plot_l = x_axis.PixelsToPlot(plot.PlotRect.Min.x - rect_size.x * tx * zoom_rate * correction);\n                const double plot_r = x_axis.PixelsToPlot(plot.PlotRect.Max.x + rect_size.x * (1 - tx) * zoom_rate * correction);\n                x_axis.SetMin(x_axis.IsInverted() ? plot_r : plot_l);\n                x_axis.SetMax(x_axis.IsInverted() ? plot_l : plot_r);\n                if (axis_equal && x_axis.OrthoAxis != nullptr)\n                    x_axis.OrthoAxis->SetAspect(x_axis.GetAspect());\n                changed = true;\n            }\n        }\n        for (int i = 0; i < IMPLOT_NUM_Y_AXES; i++) {\n            ImPlotAxis& y_axis = plot.YAxis(i);\n            const bool equal_zoom   = axis_equal && y_axis.OrthoAxis != nullptr;\n            const bool equal_locked = equal_zoom && y_axis.OrthoAxis->IsInputLocked();\n            if (y_hov[i] && !y_axis.IsInputLocked() && !equal_locked) {\n                float correction = (plot.Hovered && equal_zoom) ? 0.5f : 1.0f;\n                const double plot_t = y_axis.PixelsToPlot(plot.PlotRect.Min.y - rect_size.y * ty * zoom_rate * correction);\n                const double plot_b = y_axis.PixelsToPlot(plot.PlotRect.Max.y + rect_size.y * (1 - ty) * zoom_rate * correction);\n                y_axis.SetMin(y_axis.IsInverted() ? plot_t : plot_b);\n                y_axis.SetMax(y_axis.IsInverted() ? plot_b : plot_t);\n                if (axis_equal && y_axis.OrthoAxis != nullptr)\n                    y_axis.OrthoAxis->SetAspect(y_axis.GetAspect());\n                changed = true;\n            }\n        }\n    }\n\n    // BOX-SELECTION ----------------------------------------------------------\n\n    if (plot.Selecting) {\n        const ImVec2 d = plot.SelectStart - IO.MousePos;\n        const bool x_can_change = !ImHasFlag(IO.KeyMods,gp.InputMap.SelectHorzMod) && ImFabs(d.x) > 2;\n        const bool y_can_change = !ImHasFlag(IO.KeyMods,gp.InputMap.SelectVertMod) && ImFabs(d.y) > 2;\n        // confirm\n        if (IO.MouseReleased[gp.InputMap.Select]) {\n            for (int i = 0; i < IMPLOT_NUM_X_AXES; i++) {\n                ImPlotAxis& x_axis = plot.XAxis(i);\n                if (!x_axis.IsInputLocked() && x_can_change) {\n                    const double p1 = x_axis.PixelsToPlot(plot.SelectStart.x);\n                    const double p2 = x_axis.PixelsToPlot(IO.MousePos.x);\n                    x_axis.SetMin(ImMin(p1, p2));\n                    x_axis.SetMax(ImMax(p1, p2));\n                    changed = true;\n                }\n            }\n            for (int i = 0; i < IMPLOT_NUM_Y_AXES; i++) {\n                ImPlotAxis& y_axis = plot.YAxis(i);\n                if (!y_axis.IsInputLocked() && y_can_change) {\n                    const double p1 = y_axis.PixelsToPlot(plot.SelectStart.y);\n                    const double p2 = y_axis.PixelsToPlot(IO.MousePos.y);\n                    y_axis.SetMin(ImMin(p1, p2));\n                    y_axis.SetMax(ImMax(p1, p2));\n                    changed = true;\n                }\n            }\n            if (x_can_change || y_can_change || (ImHasFlag(IO.KeyMods,gp.InputMap.SelectHorzMod) && ImHasFlag(IO.KeyMods,gp.InputMap.SelectVertMod)))\n                gp.OpenContextThisFrame = false;\n            plot.Selected = plot.Selecting = false;\n        }\n        // cancel\n        else if (IO.MouseReleased[gp.InputMap.SelectCancel]) {\n            plot.Selected = plot.Selecting = false;\n            gp.OpenContextThisFrame = false;\n        }\n        else if (ImLengthSqr(d) > BOX_SELECT_DRAG_THRESHOLD) {\n            // bad selection\n            if (plot.IsInputLocked()) {\n                ImGui::SetMouseCursor(ImGuiMouseCursor_NotAllowed);\n                gp.OpenContextThisFrame = false;\n                plot.Selected      = false;\n            }\n            else {\n                // TODO: Handle only min or max locked cases\n                const bool full_width  = ImHasFlag(IO.KeyMods, gp.InputMap.SelectHorzMod) || AllAxesInputLocked(&plot.Axes[ImAxis_X1], IMPLOT_NUM_X_AXES);\n                const bool full_height = ImHasFlag(IO.KeyMods, gp.InputMap.SelectVertMod) || AllAxesInputLocked(&plot.Axes[ImAxis_Y1], IMPLOT_NUM_Y_AXES);\n                plot.SelectRect.Min.x = full_width  ? plot.PlotRect.Min.x : ImMin(plot.SelectStart.x, IO.MousePos.x);\n                plot.SelectRect.Max.x = full_width  ? plot.PlotRect.Max.x : ImMax(plot.SelectStart.x, IO.MousePos.x);\n                plot.SelectRect.Min.y = full_height ? plot.PlotRect.Min.y : ImMin(plot.SelectStart.y, IO.MousePos.y);\n                plot.SelectRect.Max.y = full_height ? plot.PlotRect.Max.y : ImMax(plot.SelectStart.y, IO.MousePos.y);\n                plot.SelectRect.Min  -= plot.PlotRect.Min;\n                plot.SelectRect.Max  -= plot.PlotRect.Min;\n                plot.Selected = true;\n            }\n        }\n        else {\n            plot.Selected = false;\n        }\n    }\n    return changed;\n}\n\n//-----------------------------------------------------------------------------\n// Next Plot Data (Legacy)\n//-----------------------------------------------------------------------------\n\nvoid ApplyNextPlotData(ImAxis idx) {\n    ImPlotContext& gp = *GImPlot;\n    ImPlotPlot& plot  = *gp.CurrentPlot;\n    ImPlotAxis& axis  = plot.Axes[idx];\n    if (!axis.Enabled)\n        return;\n    double*     npd_lmin = gp.NextPlotData.LinkedMin[idx];\n    double*     npd_lmax = gp.NextPlotData.LinkedMax[idx];\n    bool        npd_rngh = gp.NextPlotData.HasRange[idx];\n    ImPlotCond  npd_rngc = gp.NextPlotData.RangeCond[idx];\n    ImPlotRange     npd_rngv = gp.NextPlotData.Range[idx];\n    axis.LinkedMin = npd_lmin;\n    axis.LinkedMax = npd_lmax;\n    axis.PullLinks();\n    if (npd_rngh) {\n        if (!plot.Initialized || npd_rngc == ImPlotCond_Always)\n            axis.SetRange(npd_rngv);\n    }\n    axis.HasRange         = npd_rngh;\n    axis.RangeCond        = npd_rngc;\n}\n\n//-----------------------------------------------------------------------------\n// Setup\n//-----------------------------------------------------------------------------\n\nvoid SetupAxis(ImAxis idx, const char* label, ImPlotAxisFlags flags) {\n    ImPlotContext& gp = *GImPlot;\n    IM_ASSERT_USER_ERROR(gp.CurrentPlot != nullptr && !gp.CurrentPlot->SetupLocked,\n                         \"Setup needs to be called after BeginPlot and before any setup locking functions (e.g. PlotX)!\");\n    // get plot and axis\n    ImPlotPlot& plot = *gp.CurrentPlot;\n    ImPlotAxis& axis = plot.Axes[idx];\n    // set ID\n    axis.ID = plot.ID + idx + 1;\n    // check and set flags\n    if (plot.JustCreated || flags != axis.PreviousFlags)\n        axis.Flags = flags;\n    axis.PreviousFlags = flags;\n    // enable axis\n    axis.Enabled = true;\n    // set label\n    plot.SetAxisLabel(axis,label);\n    // cache colors\n    UpdateAxisColors(axis);\n}\n\nvoid SetupAxisLimits(ImAxis idx, double min_lim, double max_lim, ImPlotCond cond) {\n    ImPlotContext& gp = *GImPlot;\n    IM_ASSERT_USER_ERROR(gp.CurrentPlot != nullptr && !gp.CurrentPlot->SetupLocked,\n                         \"Setup needs to be called after BeginPlot and before any setup locking functions (e.g. PlotX)!\");    // get plot and axis\n    ImPlotPlot& plot = *gp.CurrentPlot;\n    ImPlotAxis& axis = plot.Axes[idx];\n    IM_ASSERT_USER_ERROR(axis.Enabled, \"Axis is not enabled! Did you forget to call SetupAxis()?\");\n    if (!plot.Initialized || cond == ImPlotCond_Always)\n        axis.SetRange(min_lim, max_lim);\n    axis.HasRange  = true;\n    axis.RangeCond = cond;\n}\n\nvoid SetupAxisFormat(ImAxis idx, const char* fmt) {\n    ImPlotContext& gp = *GImPlot;\n    IM_ASSERT_USER_ERROR(gp.CurrentPlot != nullptr && !gp.CurrentPlot->SetupLocked,\n                         \"Setup needs to be called after BeginPlot and before any setup locking functions (e.g. PlotX)!\");\n    ImPlotPlot& plot = *gp.CurrentPlot;\n    ImPlotAxis& axis = plot.Axes[idx];\n    IM_ASSERT_USER_ERROR(axis.Enabled, \"Axis is not enabled! Did you forget to call SetupAxis()?\");\n    axis.HasFormatSpec = fmt != nullptr;\n    if (fmt != nullptr)\n        ImStrncpy(axis.FormatSpec,fmt,sizeof(axis.FormatSpec));\n}\n\nvoid SetupAxisLinks(ImAxis idx, double* min_lnk, double* max_lnk) {\n    ImPlotContext& gp = *GImPlot;\n    IM_ASSERT_USER_ERROR(gp.CurrentPlot != nullptr && !gp.CurrentPlot->SetupLocked,\n                         \"Setup needs to be called after BeginPlot and before any setup locking functions (e.g. PlotX)!\");\n    ImPlotPlot& plot = *gp.CurrentPlot;\n    ImPlotAxis& axis = plot.Axes[idx];\n    IM_ASSERT_USER_ERROR(axis.Enabled, \"Axis is not enabled! Did you forget to call SetupAxis()?\");\n    axis.LinkedMin = min_lnk;\n    axis.LinkedMax = max_lnk;\n    axis.PullLinks();\n}\n\nvoid SetupAxisFormat(ImAxis idx, ImPlotFormatter formatter, void* data) {\n    ImPlotContext& gp = *GImPlot;\n    IM_ASSERT_USER_ERROR(gp.CurrentPlot != nullptr && !gp.CurrentPlot->SetupLocked,\n                         \"Setup needs to be called after BeginPlot and before any setup locking functions (e.g. PlotX)!\");\n    ImPlotPlot& plot = *gp.CurrentPlot;\n    ImPlotAxis& axis = plot.Axes[idx];\n    IM_ASSERT_USER_ERROR(axis.Enabled, \"Axis is not enabled! Did you forget to call SetupAxis()?\");\n    axis.Formatter = formatter;\n    axis.FormatterData = data;\n}\n\nvoid SetupAxisTicks(ImAxis idx, const double* values, int n_ticks, const char* const labels[], bool show_default) {\n    ImPlotContext& gp = *GImPlot;\n    IM_ASSERT_USER_ERROR(gp.CurrentPlot != nullptr && !gp.CurrentPlot->SetupLocked,\n                        \"Setup needs to be called after BeginPlot and before any setup locking functions (e.g. PlotX)!\");\n    ImPlotPlot& plot = *gp.CurrentPlot;\n    ImPlotAxis& axis = plot.Axes[idx];\n    IM_ASSERT_USER_ERROR(axis.Enabled, \"Axis is not enabled! Did you forget to call SetupAxis()?\");\n    axis.ShowDefaultTicks = show_default;\n    AddTicksCustom(values,\n                  labels,\n                  n_ticks,\n                  axis.Ticker,\n                  axis.Formatter ? axis.Formatter : Formatter_Default,\n                  (axis.Formatter && axis.FormatterData) ? axis.FormatterData : axis.HasFormatSpec ? axis.FormatSpec : (void*)IMPLOT_LABEL_FORMAT);\n}\n\nvoid SetupAxisTicks(ImAxis idx, double v_min, double v_max, int n_ticks, const char* const labels[], bool show_default) {\n    ImPlotContext& gp = *GImPlot;\n    IM_ASSERT_USER_ERROR(gp.CurrentPlot != nullptr && !gp.CurrentPlot->SetupLocked,\n                         \"Setup needs to be called after BeginPlot and before any setup locking functions (e.g. PlotX)!\");\n    n_ticks = n_ticks < 2 ? 2 : n_ticks;\n    FillRange(gp.TempDouble1, n_ticks, v_min, v_max);\n    SetupAxisTicks(idx, gp.TempDouble1.Data, n_ticks, labels, show_default);\n}\n\nvoid SetupAxisScale(ImAxis idx, ImPlotScale scale) {\n    ImPlotContext& gp = *GImPlot;\n    IM_ASSERT_USER_ERROR(gp.CurrentPlot != nullptr && !gp.CurrentPlot->SetupLocked,\n                        \"Setup needs to be called after BeginPlot and before any setup locking functions (e.g. PlotX)!\");\n    ImPlotPlot& plot = *gp.CurrentPlot;\n    ImPlotAxis& axis = plot.Axes[idx];\n    IM_ASSERT_USER_ERROR(axis.Enabled, \"Axis is not enabled! Did you forget to call SetupAxis()?\");\n    axis.Scale = scale;\n    switch (scale)\n    {\n    case ImPlotScale_Time:\n        axis.TransformForward = nullptr;\n        axis.TransformInverse = nullptr;\n        axis.TransformData    = nullptr;\n        axis.Locator          = Locator_Time;\n        axis.ConstraintRange  = ImPlotRange(IMPLOT_MIN_TIME, IMPLOT_MAX_TIME);\n        axis.Ticker.Levels    = 2;\n        break;\n    case ImPlotScale_Log10:\n        axis.TransformForward = TransformForward_Log10;\n        axis.TransformInverse = TransformInverse_Log10;\n        axis.TransformData    = nullptr;\n        axis.Locator          = Locator_Log10;\n        axis.ConstraintRange  = ImPlotRange(DBL_MIN, INFINITY);\n        break;\n    case ImPlotScale_SymLog:\n        axis.TransformForward = TransformForward_SymLog;\n        axis.TransformInverse = TransformInverse_SymLog;\n        axis.TransformData    = nullptr;\n        axis.Locator          = Locator_SymLog;\n        axis.ConstraintRange  = ImPlotRange(-INFINITY, INFINITY);\n        break;\n    default:\n        axis.TransformForward = nullptr;\n        axis.TransformInverse = nullptr;\n        axis.TransformData    = nullptr;\n        axis.Locator          = nullptr;\n        axis.ConstraintRange  = ImPlotRange(-INFINITY, INFINITY);\n        break;\n    }\n}\n\nvoid SetupAxisScale(ImAxis idx, ImPlotTransform fwd, ImPlotTransform inv, void* data) {\n    ImPlotContext& gp = *GImPlot;\n    IM_ASSERT_USER_ERROR(gp.CurrentPlot != nullptr && !gp.CurrentPlot->SetupLocked,\n                        \"Setup needs to be called after BeginPlot and before any setup locking functions (e.g. PlotX)!\");\n    ImPlotPlot& plot = *gp.CurrentPlot;\n    ImPlotAxis& axis = plot.Axes[idx];\n    IM_ASSERT_USER_ERROR(axis.Enabled, \"Axis is not enabled! Did you forget to call SetupAxis()?\");\n    axis.Scale = IMPLOT_AUTO;\n    axis.TransformForward = fwd;\n    axis.TransformInverse = inv;\n    axis.TransformData = data;\n}\n\nvoid SetupAxisLimitsConstraints(ImAxis idx, double v_min, double v_max) {\n    ImPlotContext& gp = *GImPlot;\n    IM_ASSERT_USER_ERROR(gp.CurrentPlot != nullptr && !gp.CurrentPlot->SetupLocked,\n                        \"Setup needs to be called after BeginPlot and before any setup locking functions (e.g. PlotX)!\");\n    ImPlotPlot& plot = *gp.CurrentPlot;\n    ImPlotAxis& axis = plot.Axes[idx];\n    IM_ASSERT_USER_ERROR(axis.Enabled, \"Axis is not enabled! Did you forget to call SetupAxis()?\");\n    axis.ConstraintRange.Min = v_min;\n    axis.ConstraintRange.Max = v_max;\n}\n\nvoid SetupAxisZoomConstraints(ImAxis idx, double z_min, double z_max) {\n    ImPlotContext& gp = *GImPlot;\n    IM_ASSERT_USER_ERROR(gp.CurrentPlot != nullptr && !gp.CurrentPlot->SetupLocked,\n                        \"Setup needs to be called after BeginPlot and before any setup locking functions (e.g. PlotX)!\");\n    ImPlotPlot& plot = *gp.CurrentPlot;\n    ImPlotAxis& axis = plot.Axes[idx];\n    IM_ASSERT_USER_ERROR(axis.Enabled, \"Axis is not enabled! Did you forget to call SetupAxis()?\");\n    axis.ConstraintZoom.Min = z_min;\n    axis.ConstraintZoom.Max = z_max;\n}\n\nvoid SetupAxes(const char* x_label, const char* y_label, ImPlotAxisFlags x_flags, ImPlotAxisFlags y_flags) {\n    SetupAxis(ImAxis_X1, x_label, x_flags);\n    SetupAxis(ImAxis_Y1, y_label, y_flags);\n}\n\nvoid SetupAxesLimits(double x_min, double x_max, double y_min, double y_max, ImPlotCond cond) {\n    SetupAxisLimits(ImAxis_X1, x_min, x_max, cond);\n    SetupAxisLimits(ImAxis_Y1, y_min, y_max, cond);\n}\n\nvoid SetupLegend(ImPlotLocation location, ImPlotLegendFlags flags) {\n    ImPlotContext& gp = *GImPlot;\n    IM_ASSERT_USER_ERROR(gp.CurrentPlot != nullptr && !gp.CurrentPlot->SetupLocked,\n                         \"Setup needs to be called after BeginPlot and before any setup locking functions (e.g. PlotX)!\");\n    IM_ASSERT_USER_ERROR(gp.CurrentItems != nullptr,\n                         \"SetupLegend() needs to be called within an itemized context!\");\n    ImPlotLegend& legend = gp.CurrentItems->Legend;\n    // check and set location\n    if (location != legend.PreviousLocation)\n        legend.Location = location;\n    legend.PreviousLocation = location;\n    // check and set flags\n    if (flags != legend.PreviousFlags)\n        legend.Flags = flags;\n    legend.PreviousFlags = flags;\n}\n\nvoid SetupMouseText(ImPlotLocation location, ImPlotMouseTextFlags flags) {\n    ImPlotContext& gp = *GImPlot;\n    IM_ASSERT_USER_ERROR(gp.CurrentPlot != nullptr && !gp.CurrentPlot->SetupLocked,\n                         \"Setup needs to be called after BeginPlot and before any setup locking functions (e.g. PlotX)!\");\n    gp.CurrentPlot->MouseTextLocation = location;\n    gp.CurrentPlot->MouseTextFlags = flags;\n}\n\n//-----------------------------------------------------------------------------\n// SetNext\n//-----------------------------------------------------------------------------\n\nvoid SetNextAxisLimits(ImAxis axis, double v_min, double v_max, ImPlotCond cond) {\n    ImPlotContext& gp = *GImPlot;\n    IM_ASSERT_USER_ERROR(gp.CurrentPlot == nullptr, \"SetNextAxisLimits() needs to be called before BeginPlot()!\");\n    IM_ASSERT(cond == 0 || ImIsPowerOfTwo(cond)); // Make sure the user doesn't attempt to combine multiple condition flags.\n    gp.NextPlotData.HasRange[axis]  = true;\n    gp.NextPlotData.RangeCond[axis] = cond;\n    gp.NextPlotData.Range[axis].Min = v_min;\n    gp.NextPlotData.Range[axis].Max = v_max;\n}\n\nvoid SetNextAxisLinks(ImAxis axis, double* link_min, double* link_max) {\n    ImPlotContext& gp = *GImPlot;\n    IM_ASSERT_USER_ERROR(gp.CurrentPlot == nullptr, \"SetNextAxisLinks() needs to be called before BeginPlot()!\");\n    gp.NextPlotData.LinkedMin[axis] = link_min;\n    gp.NextPlotData.LinkedMax[axis] = link_max;\n}\n\nvoid SetNextAxisToFit(ImAxis axis) {\n    ImPlotContext& gp = *GImPlot;\n    IM_ASSERT_USER_ERROR(gp.CurrentPlot == nullptr, \"SetNextAxisToFit() needs to be called before BeginPlot()!\");\n    gp.NextPlotData.Fit[axis] = true;\n}\n\nvoid SetNextAxesLimits(double x_min, double x_max, double y_min, double y_max, ImPlotCond cond) {\n    SetNextAxisLimits(ImAxis_X1, x_min, x_max, cond);\n    SetNextAxisLimits(ImAxis_Y1, y_min, y_max, cond);\n}\n\nvoid SetNextAxesToFit() {\n    for (int i = 0; i < ImAxis_COUNT; ++i)\n        SetNextAxisToFit(i);\n}\n\n//-----------------------------------------------------------------------------\n// BeginPlot\n//-----------------------------------------------------------------------------\n\nbool BeginPlot(const char* title_id, const ImVec2& size, ImPlotFlags flags) {\n    IM_ASSERT_USER_ERROR(GImPlot != nullptr, \"No current context. Did you call ImPlot::CreateContext() or ImPlot::SetCurrentContext()?\");\n    ImPlotContext& gp = *GImPlot;\n    IM_ASSERT_USER_ERROR(gp.CurrentPlot == nullptr, \"Mismatched BeginPlot()/EndPlot()!\");\n\n    // FRONT MATTER -----------------------------------------------------------\n\n    if (gp.CurrentSubplot != nullptr)\n        ImGui::PushID(gp.CurrentSubplot->CurrentIdx);\n\n    // get globals\n    ImGuiContext &G          = *GImGui;\n    ImGuiWindow* Window      = G.CurrentWindow;\n\n    // skip if needed\n    if (Window->SkipItems && !gp.CurrentSubplot) {\n        ResetCtxForNextPlot(GImPlot);\n        return false;\n    }\n\n    // ID and age (TODO: keep track of plot age in frames)\n    const ImGuiID ID         = Window->GetID(title_id);\n    const bool just_created  = gp.Plots.GetByKey(ID) == nullptr;\n    gp.CurrentPlot           = gp.Plots.GetOrAddByKey(ID);\n\n    ImPlotPlot &plot         = *gp.CurrentPlot;\n    plot.ID                  = ID;\n    plot.Items.ID            = ID - 1;\n    plot.JustCreated         = just_created;\n    plot.SetupLocked         = false;\n\n    // check flags\n    if (plot.JustCreated)\n        plot.Flags = flags;\n    else if (flags != plot.PreviousFlags)\n        plot.Flags = flags;\n    plot.PreviousFlags = flags;\n\n    // setup default axes\n    if (plot.JustCreated) {\n        SetupAxis(ImAxis_X1);\n        SetupAxis(ImAxis_Y1);\n    }\n\n    // reset axes\n    for (int i = 0; i < ImAxis_COUNT; ++i) {\n        plot.Axes[i].Reset();\n        UpdateAxisColors(plot.Axes[i]);\n    }\n    // ensure first axes enabled\n    plot.Axes[ImAxis_X1].Enabled = true;\n    plot.Axes[ImAxis_Y1].Enabled = true;\n    // set initial axes\n    plot.CurrentX = ImAxis_X1;\n    plot.CurrentY = ImAxis_Y1;\n\n    // process next plot data (legacy)\n    for (int i = 0; i < ImAxis_COUNT; ++i)\n        ApplyNextPlotData(i);\n\n    // capture scroll with a child region\n    if (!ImHasFlag(plot.Flags, ImPlotFlags_NoChild)) {\n        ImVec2 child_size;\n        if (gp.CurrentSubplot != nullptr)\n            child_size = gp.CurrentSubplot->CellSize;\n        else\n            child_size = ImVec2(size.x == 0 ? gp.Style.PlotDefaultSize.x : size.x, size.y == 0 ? gp.Style.PlotDefaultSize.y : size.y);\n        ImGui::BeginChild(title_id, child_size, false, ImGuiWindowFlags_NoScrollbar);\n        Window = ImGui::GetCurrentWindow();\n        Window->ScrollMax.y = 1.0f;\n        gp.ChildWindowMade = true;\n    }\n    else {\n        gp.ChildWindowMade = false;\n    }\n\n    // clear text buffers\n    plot.ClearTextBuffer();\n    plot.SetTitle(title_id);\n\n    // set frame size\n    ImVec2 frame_size;\n    if (gp.CurrentSubplot != nullptr)\n        frame_size = gp.CurrentSubplot->CellSize;\n    else\n        frame_size = ImGui::CalcItemSize(size, gp.Style.PlotDefaultSize.x, gp.Style.PlotDefaultSize.y);\n\n    if (frame_size.x < gp.Style.PlotMinSize.x && (size.x < 0.0f || gp.CurrentSubplot != nullptr))\n        frame_size.x = gp.Style.PlotMinSize.x;\n    if (frame_size.y < gp.Style.PlotMinSize.y && (size.y < 0.0f || gp.CurrentSubplot != nullptr))\n        frame_size.y = gp.Style.PlotMinSize.y;\n\n    plot.FrameRect = ImRect(Window->DC.CursorPos, Window->DC.CursorPos + frame_size);\n    ImGui::ItemSize(plot.FrameRect);\n    if (!ImGui::ItemAdd(plot.FrameRect, plot.ID, &plot.FrameRect) && !gp.CurrentSubplot) {\n        ResetCtxForNextPlot(GImPlot);\n        return false;\n    }\n\n    // setup items (or dont)\n    if (gp.CurrentItems == nullptr)\n        gp.CurrentItems = &plot.Items;\n\n    return true;\n}\n\n//-----------------------------------------------------------------------------\n// SetupFinish\n//-----------------------------------------------------------------------------\n\nvoid SetupFinish() {\n    IM_ASSERT_USER_ERROR(GImPlot != nullptr, \"No current context. Did you call ImPlot::CreateContext() or ImPlot::SetCurrentContext()?\");\n    ImPlotContext& gp = *GImPlot;\n    IM_ASSERT_USER_ERROR(gp.CurrentPlot != nullptr, \"SetupFinish needs to be called after BeginPlot!\");\n\n    ImGuiContext& G         = *GImGui;\n    ImDrawList& DrawList    = *G.CurrentWindow->DrawList;\n    const ImGuiStyle& Style = G.Style;\n\n    ImPlotPlot &plot  = *gp.CurrentPlot;\n\n    // lock setup\n    plot.SetupLocked = true;\n\n    // finalize axes and set default formatter/locator\n    for (int i = 0; i < ImAxis_COUNT; ++i) {\n        ImPlotAxis& axis = plot.Axes[i];\n        if (axis.Enabled) {\n            axis.Constrain();\n            if (!plot.Initialized && axis.CanInitFit())\n                plot.FitThisFrame = axis.FitThisFrame = true;\n        }\n        if (axis.Formatter == nullptr) {\n            axis.Formatter = Formatter_Default;\n            if (axis.HasFormatSpec)\n                axis.FormatterData = axis.FormatSpec;\n            else\n                axis.FormatterData = (void*)IMPLOT_LABEL_FORMAT;\n        }\n        if (axis.Locator == nullptr) {\n            axis.Locator = Locator_Default;\n        }\n    }\n\n    // setup nullptr orthogonal axes\n    const bool axis_equal = ImHasFlag(plot.Flags, ImPlotFlags_Equal);\n    for (int ix = ImAxis_X1, iy = ImAxis_Y1; ix < ImAxis_Y1 || iy < ImAxis_COUNT; ++ix, ++iy) {\n        ImPlotAxis& x_axis = plot.Axes[ix];\n        ImPlotAxis& y_axis = plot.Axes[iy];\n        if (x_axis.Enabled && y_axis.Enabled) {\n            if (x_axis.OrthoAxis == nullptr)\n                x_axis.OrthoAxis = &y_axis;\n            if (y_axis.OrthoAxis == nullptr)\n                y_axis.OrthoAxis = &x_axis;\n        }\n        else if (x_axis.Enabled)\n        {\n            if (x_axis.OrthoAxis == nullptr && !axis_equal)\n                x_axis.OrthoAxis = &plot.Axes[ImAxis_Y1];\n        }\n        else if (y_axis.Enabled) {\n            if (y_axis.OrthoAxis == nullptr && !axis_equal)\n                y_axis.OrthoAxis = &plot.Axes[ImAxis_X1];\n        }\n    }\n\n    // canvas/axes bb\n    plot.CanvasRect = ImRect(plot.FrameRect.Min + gp.Style.PlotPadding, plot.FrameRect.Max - gp.Style.PlotPadding);\n    plot.AxesRect   = plot.FrameRect;\n\n    // outside legend adjustments\n    if (!ImHasFlag(plot.Flags, ImPlotFlags_NoLegend) && plot.Items.GetLegendCount() > 0 && ImHasFlag(plot.Items.Legend.Flags, ImPlotLegendFlags_Outside)) {\n        ImPlotLegend& legend = plot.Items.Legend;\n        const bool horz = ImHasFlag(legend.Flags, ImPlotLegendFlags_Horizontal);\n        const ImVec2 legend_size = CalcLegendSize(plot.Items, gp.Style.LegendInnerPadding, gp.Style.LegendSpacing, !horz);\n        const bool west = ImHasFlag(legend.Location, ImPlotLocation_West) && !ImHasFlag(legend.Location, ImPlotLocation_East);\n        const bool east = ImHasFlag(legend.Location, ImPlotLocation_East) && !ImHasFlag(legend.Location, ImPlotLocation_West);\n        const bool north = ImHasFlag(legend.Location, ImPlotLocation_North) && !ImHasFlag(legend.Location, ImPlotLocation_South);\n        const bool south = ImHasFlag(legend.Location, ImPlotLocation_South) && !ImHasFlag(legend.Location, ImPlotLocation_North);\n        if ((west && !horz) || (west && horz && !north && !south)) {\n            plot.CanvasRect.Min.x += (legend_size.x + gp.Style.LegendPadding.x);\n            plot.AxesRect.Min.x   += (legend_size.x + gp.Style.PlotPadding.x);\n        }\n        if ((east && !horz) || (east && horz && !north && !south)) {\n            plot.CanvasRect.Max.x -= (legend_size.x + gp.Style.LegendPadding.x);\n            plot.AxesRect.Max.x   -= (legend_size.x + gp.Style.PlotPadding.x);\n        }\n        if ((north && horz) || (north && !horz && !west && !east)) {\n            plot.CanvasRect.Min.y += (legend_size.y + gp.Style.LegendPadding.y);\n            plot.AxesRect.Min.y   += (legend_size.y + gp.Style.PlotPadding.y);\n        }\n        if ((south && horz) || (south && !horz && !west && !east)) {\n            plot.CanvasRect.Max.y -= (legend_size.y + gp.Style.LegendPadding.y);\n            plot.AxesRect.Max.y   -= (legend_size.y + gp.Style.PlotPadding.y);\n        }\n    }\n\n    // plot bb\n    float pad_top = 0, pad_bot = 0, pad_left = 0, pad_right = 0;\n\n    // (0) calc top padding form title\n    ImVec2 title_size(0.0f, 0.0f);\n    if (plot.HasTitle())\n         title_size = ImGui::CalcTextSize(plot.GetTitle(), nullptr, true);\n    if (title_size.x > 0) {\n        pad_top += title_size.y + gp.Style.LabelPadding.y;\n        plot.AxesRect.Min.y += gp.Style.PlotPadding.y + pad_top;\n    }\n\n    // (1) calc addition top padding and bot padding\n    PadAndDatumAxesX(plot,pad_top,pad_bot,gp.CurrentAlignmentH);\n\n    const float plot_height = plot.CanvasRect.GetHeight() - pad_top - pad_bot;\n\n    // (2) get y tick labels (needed for left/right pad)\n    for (int i = 0; i < IMPLOT_NUM_Y_AXES; i++) {\n        ImPlotAxis& axis = plot.YAxis(i);\n        if (axis.WillRender() && axis.ShowDefaultTicks && plot_height > 0) {\n            axis.Locator(axis.Ticker, axis.Range, plot_height, true, axis.Formatter, axis.FormatterData);\n        }\n    }\n\n    // (3) calc left/right pad\n    PadAndDatumAxesY(plot,pad_left,pad_right,gp.CurrentAlignmentV);\n\n    const float plot_width = plot.CanvasRect.GetWidth() - pad_left - pad_right;\n\n    // (4) get x ticks\n    for (int i = 0; i < IMPLOT_NUM_X_AXES; i++) {\n        ImPlotAxis& axis = plot.XAxis(i);\n        if (axis.WillRender() && axis.ShowDefaultTicks && plot_width > 0) {\n            axis.Locator(axis.Ticker, axis.Range, plot_width, false, axis.Formatter, axis.FormatterData);\n        }\n    }\n\n    // (5) calc plot bb\n    plot.PlotRect = ImRect(plot.CanvasRect.Min + ImVec2(pad_left, pad_top), plot.CanvasRect.Max - ImVec2(pad_right, pad_bot));\n\n    // HOVER------------------------------------------------------------\n\n    // axes hover rect, pixel ranges\n    for (int i = 0; i < IMPLOT_NUM_X_AXES; ++i) {\n        ImPlotAxis& xax = plot.XAxis(i);\n        xax.HoverRect   = ImRect(ImVec2(plot.PlotRect.Min.x, ImMin(xax.Datum1,xax.Datum2)),\n                                 ImVec2(plot.PlotRect.Max.x, ImMax(xax.Datum1,xax.Datum2)));\n        xax.PixelMin    = xax.IsInverted() ? plot.PlotRect.Max.x : plot.PlotRect.Min.x;\n        xax.PixelMax    = xax.IsInverted() ? plot.PlotRect.Min.x : plot.PlotRect.Max.x;\n        xax.UpdateTransformCache();\n    }\n\n    for (int i = 0; i < IMPLOT_NUM_Y_AXES; ++i) {\n        ImPlotAxis& yax = plot.YAxis(i);\n        yax.HoverRect   = ImRect(ImVec2(ImMin(yax.Datum1,yax.Datum2),plot.PlotRect.Min.y),\n                                 ImVec2(ImMax(yax.Datum1,yax.Datum2),plot.PlotRect.Max.y));\n        yax.PixelMin    = yax.IsInverted() ? plot.PlotRect.Min.y : plot.PlotRect.Max.y;\n        yax.PixelMax    = yax.IsInverted() ? plot.PlotRect.Max.y : plot.PlotRect.Min.y;\n        yax.UpdateTransformCache();\n    }\n    // Equal axis constraint. Must happen after we set Pixels\n    // constrain equal axes for primary x and y if not approximately equal\n    // constrains x to y since x pixel size depends on y labels width, and causes feedback loops in opposite case\n    if (axis_equal) {\n        for (int i = 0; i < IMPLOT_NUM_X_AXES; ++i) {\n            ImPlotAxis& x_axis = plot.XAxis(i);\n            if (x_axis.OrthoAxis == nullptr)\n                continue;\n            double xar = x_axis.GetAspect();\n            double yar = x_axis.OrthoAxis->GetAspect();\n            // edge case: user has set x range this frame, so fit y to x so that we honor their request for x range\n            // NB: because of feedback across several frames, the user's x request may not be perfectly honored\n            if (x_axis.HasRange)\n                x_axis.OrthoAxis->SetAspect(xar);\n            else if (!ImAlmostEqual(xar,yar) && !x_axis.OrthoAxis->IsInputLocked())\n                 x_axis.SetAspect(yar);\n        }\n    }\n\n    // INPUT ------------------------------------------------------------------\n    if (!ImHasFlag(plot.Flags, ImPlotFlags_NoInputs))\n        UpdateInput(plot);\n\n    // fit from FitNextPlotAxes or auto fit\n    for (int i = 0; i < ImAxis_COUNT; ++i) {\n        if (gp.NextPlotData.Fit[i] || plot.Axes[i].IsAutoFitting()) {\n            plot.FitThisFrame = true;\n            plot.Axes[i].FitThisFrame = true;\n        }\n    }\n\n    // RENDER -----------------------------------------------------------------\n\n    const float txt_height = ImGui::GetTextLineHeight();\n\n    // render frame\n    if (!ImHasFlag(plot.Flags, ImPlotFlags_NoFrame))\n        ImGui::RenderFrame(plot.FrameRect.Min, plot.FrameRect.Max, GetStyleColorU32(ImPlotCol_FrameBg), true, Style.FrameRounding);\n\n    // grid bg\n    DrawList.AddRectFilled(plot.PlotRect.Min, plot.PlotRect.Max, GetStyleColorU32(ImPlotCol_PlotBg));\n\n    // transform ticks\n    for (int i = 0; i < ImAxis_COUNT; i++) {\n        ImPlotAxis& axis = plot.Axes[i];\n        if (axis.WillRender()) {\n            for (int t = 0; t < axis.Ticker.TickCount(); t++) {\n                ImPlotTick& tk = axis.Ticker.Ticks[t];\n                tk.PixelPos = IM_ROUND(axis.PlotToPixels(tk.PlotPos));\n            }\n        }\n    }\n\n    // render grid (background)\n    for (int i = 0; i < IMPLOT_NUM_X_AXES; i++) {\n        ImPlotAxis& x_axis = plot.XAxis(i);\n        if (x_axis.Enabled && x_axis.HasGridLines() && !x_axis.IsForeground())\n            RenderGridLinesX(DrawList, x_axis.Ticker, plot.PlotRect, x_axis.ColorMaj, x_axis.ColorMin, gp.Style.MajorGridSize.x, gp.Style.MinorGridSize.x);\n    }\n    for (int i = 0; i < IMPLOT_NUM_Y_AXES; i++) {\n        ImPlotAxis& y_axis = plot.YAxis(i);\n        if (y_axis.Enabled && y_axis.HasGridLines() && !y_axis.IsForeground())\n            RenderGridLinesY(DrawList, y_axis.Ticker, plot.PlotRect,  y_axis.ColorMaj, y_axis.ColorMin, gp.Style.MajorGridSize.y, gp.Style.MinorGridSize.y);\n    }\n\n    // render x axis button, label, tick labels\n    for (int i = 0; i < IMPLOT_NUM_X_AXES; i++) {\n        ImPlotAxis& ax = plot.XAxis(i);\n        if (!ax.Enabled)\n            continue;\n        if ((ax.Hovered || ax.Held) && !plot.Held && !ImHasFlag(ax.Flags, ImPlotAxisFlags_NoHighlight))\n            DrawList.AddRectFilled(ax.HoverRect.Min, ax.HoverRect.Max, ax.Held ? ax.ColorAct : ax.ColorHov);\n        else if (ax.ColorHiLi != IM_COL32_BLACK_TRANS) {\n            DrawList.AddRectFilled(ax.HoverRect.Min, ax.HoverRect.Max, ax.ColorHiLi);\n            ax.ColorHiLi = IM_COL32_BLACK_TRANS;\n        }\n        else if (ax.ColorBg != IM_COL32_BLACK_TRANS) {\n            DrawList.AddRectFilled(ax.HoverRect.Min, ax.HoverRect.Max, ax.ColorBg);\n        }\n        const ImPlotTicker& tkr = ax.Ticker;\n        const bool opp = ax.IsOpposite();\n        if (ax.HasLabel()) {\n            const char* label        = plot.GetAxisLabel(ax);\n            const ImVec2 label_size  = ImGui::CalcTextSize(label);\n            const float label_offset = (ax.HasTickLabels() ? tkr.MaxSize.y + gp.Style.LabelPadding.y : 0.0f)\n                                     + (tkr.Levels - 1) * (txt_height + gp.Style.LabelPadding.y)\n                                     + gp.Style.LabelPadding.y;\n            const ImVec2 label_pos(plot.PlotRect.GetCenter().x - label_size.x * 0.5f,\n                                   opp ? ax.Datum1 - label_offset - label_size.y : ax.Datum1 + label_offset);\n            DrawList.AddText(label_pos, ax.ColorTxt, label);\n        }\n        if (ax.HasTickLabels()) {\n            for (int j = 0; j < tkr.TickCount(); ++j) {\n                const ImPlotTick& tk = tkr.Ticks[j];\n                const float datum = ax.Datum1 + (opp ? (-gp.Style.LabelPadding.y -txt_height -tk.Level * (txt_height + gp.Style.LabelPadding.y))\n                                                     : gp.Style.LabelPadding.y + tk.Level * (txt_height + gp.Style.LabelPadding.y));\n                if (tk.ShowLabel && tk.PixelPos >= plot.PlotRect.Min.x - 1 && tk.PixelPos <= plot.PlotRect.Max.x + 1) {\n                    ImVec2 start(tk.PixelPos - 0.5f * tk.LabelSize.x, datum);\n                    DrawList.AddText(start, ax.ColorTxt, tkr.GetText(j));\n                }\n            }\n        }\n    }\n\n    // render y axis button, label, tick labels\n    for (int i = 0; i < IMPLOT_NUM_Y_AXES; i++) {\n        ImPlotAxis& ax = plot.YAxis(i);\n        if (!ax.Enabled)\n            continue;\n        if ((ax.Hovered || ax.Held) && !plot.Held && !ImHasFlag(ax.Flags, ImPlotAxisFlags_NoHighlight))\n            DrawList.AddRectFilled(ax.HoverRect.Min, ax.HoverRect.Max, ax.Held ? ax.ColorAct : ax.ColorHov);\n        else if (ax.ColorHiLi != IM_COL32_BLACK_TRANS) {\n            DrawList.AddRectFilled(ax.HoverRect.Min, ax.HoverRect.Max, ax.ColorHiLi);\n            ax.ColorHiLi = IM_COL32_BLACK_TRANS;\n        }\n        else if (ax.ColorBg != IM_COL32_BLACK_TRANS) {\n            DrawList.AddRectFilled(ax.HoverRect.Min, ax.HoverRect.Max, ax.ColorBg);\n        }\n        const ImPlotTicker& tkr = ax.Ticker;\n        const bool opp = ax.IsOpposite();\n        if (ax.HasLabel()) {\n            const char* label        = plot.GetAxisLabel(ax);\n            const ImVec2 label_size  = CalcTextSizeVertical(label);\n            const float label_offset = (ax.HasTickLabels() ? tkr.MaxSize.x + gp.Style.LabelPadding.x : 0.0f)\n                                     + gp.Style.LabelPadding.x;\n            const ImVec2 label_pos(opp ? ax.Datum1 + label_offset : ax.Datum1 - label_offset - label_size.x,\n                                   plot.PlotRect.GetCenter().y + label_size.y * 0.5f);\n            AddTextVertical(&DrawList, label_pos, ax.ColorTxt, label);\n        }\n        if (ax.HasTickLabels()) {\n            for (int j = 0; j < tkr.TickCount(); ++j) {\n                const ImPlotTick& tk = tkr.Ticks[j];\n                const float datum = ax.Datum1 + (opp ? gp.Style.LabelPadding.x : (-gp.Style.LabelPadding.x - tk.LabelSize.x));\n                if (tk.ShowLabel && tk.PixelPos >= plot.PlotRect.Min.y - 1 && tk.PixelPos <= plot.PlotRect.Max.y + 1) {\n                    ImVec2 start(datum, tk.PixelPos - 0.5f * tk.LabelSize.y);\n                    DrawList.AddText(start, ax.ColorTxt, tkr.GetText(j));\n                }\n            }\n        }\n    }\n\n\n    // clear legend (TODO: put elsewhere)\n    plot.Items.Legend.Reset();\n    // push ID to set item hashes (NB: !!!THIS PROBABLY NEEDS TO BE IN BEGIN PLOT!!!!)\n    ImGui::PushOverrideID(gp.CurrentItems->ID);\n}\n\n//-----------------------------------------------------------------------------\n// EndPlot()\n//-----------------------------------------------------------------------------\n\nvoid EndPlot() {\n    IM_ASSERT_USER_ERROR(GImPlot != nullptr, \"No current context. Did you call ImPlot::CreateContext() or ImPlot::SetCurrentContext()?\");\n    ImPlotContext& gp = *GImPlot;\n    IM_ASSERT_USER_ERROR(gp.CurrentPlot != nullptr, \"Mismatched BeginPlot()/EndPlot()!\");\n\n    SetupLock();\n\n    ImGuiContext &G       = *GImGui;\n    ImPlotPlot &plot      = *gp.CurrentPlot;\n    ImGuiWindow * Window  = G.CurrentWindow;\n    ImDrawList & DrawList = *Window->DrawList;\n    const ImGuiIO &   IO  = ImGui::GetIO();\n\n    // FINAL RENDER -----------------------------------------------------------\n\n    const bool render_border  = gp.Style.PlotBorderSize > 0 && GetStyleColorVec4(ImPlotCol_PlotBorder).w > 0;\n    const bool any_x_held = plot.Held    || AnyAxesHeld(&plot.Axes[ImAxis_X1], IMPLOT_NUM_X_AXES);\n    const bool any_y_held = plot.Held    || AnyAxesHeld(&plot.Axes[ImAxis_Y1], IMPLOT_NUM_Y_AXES);\n\n    ImGui::PushClipRect(plot.FrameRect.Min, plot.FrameRect.Max, true);\n\n    // render grid (foreground)\n    for (int i = 0; i < IMPLOT_NUM_X_AXES; i++) {\n        ImPlotAxis& x_axis = plot.XAxis(i);\n        if (x_axis.Enabled && x_axis.HasGridLines() && x_axis.IsForeground())\n            RenderGridLinesX(DrawList, x_axis.Ticker, plot.PlotRect, x_axis.ColorMaj, x_axis.ColorMin, gp.Style.MajorGridSize.x, gp.Style.MinorGridSize.x);\n    }\n    for (int i = 0; i < IMPLOT_NUM_Y_AXES; i++) {\n        ImPlotAxis& y_axis = plot.YAxis(i);\n        if (y_axis.Enabled && y_axis.HasGridLines() && y_axis.IsForeground())\n            RenderGridLinesY(DrawList, y_axis.Ticker, plot.PlotRect,  y_axis.ColorMaj, y_axis.ColorMin, gp.Style.MajorGridSize.y, gp.Style.MinorGridSize.y);\n    }\n\n\n    // render title\n    if (plot.HasTitle()) {\n        ImU32 col = GetStyleColorU32(ImPlotCol_TitleText);\n        AddTextCentered(&DrawList,ImVec2(plot.PlotRect.GetCenter().x, plot.CanvasRect.Min.y),col,plot.GetTitle());\n    }\n\n    // render x ticks\n    int count_B = 0, count_T = 0;\n    for (int i = 0; i < IMPLOT_NUM_X_AXES; i++) {\n        const ImPlotAxis& ax = plot.XAxis(i);\n        if (!ax.Enabled)\n            continue;\n        const ImPlotTicker& tkr = ax.Ticker;\n        const bool opp = ax.IsOpposite();\n        const bool aux = ((opp && count_T > 0)||(!opp && count_B > 0));\n        if (ax.HasTickMarks()) {\n            const float direction = opp ? 1.0f : -1.0f;\n            for (int j = 0; j < tkr.TickCount(); ++j) {\n                const ImPlotTick& tk = tkr.Ticks[j];\n                if (tk.Level != 0 || tk.PixelPos < plot.PlotRect.Min.x || tk.PixelPos > plot.PlotRect.Max.x)\n                    continue;\n                const ImVec2 start(tk.PixelPos, ax.Datum1);\n                const float len = (!aux && tk.Major) ? gp.Style.MajorTickLen.x  : gp.Style.MinorTickLen.x;\n                const float thk = (!aux && tk.Major) ? gp.Style.MajorTickSize.x : gp.Style.MinorTickSize.x;\n                DrawList.AddLine(start, start + ImVec2(0,direction*len), ax.ColorTick, thk);\n            }\n            if (aux || !render_border)\n                DrawList.AddLine(ImVec2(plot.PlotRect.Min.x,ax.Datum1), ImVec2(plot.PlotRect.Max.x,ax.Datum1), ax.ColorTick, gp.Style.MinorTickSize.x);\n        }\n        count_B += !opp;\n        count_T +=  opp;\n    }\n\n    // render y ticks\n    int count_L = 0, count_R = 0;\n    for (int i = 0; i < IMPLOT_NUM_Y_AXES; i++) {\n        const ImPlotAxis& ax = plot.YAxis(i);\n        if (!ax.Enabled)\n            continue;\n        const ImPlotTicker& tkr = ax.Ticker;\n        const bool opp = ax.IsOpposite();\n        const bool aux = ((opp && count_R > 0)||(!opp && count_L > 0));\n        if (ax.HasTickMarks()) {\n            const float direction = opp ? -1.0f : 1.0f;\n            for (int j = 0; j < tkr.TickCount(); ++j) {\n                const ImPlotTick& tk = tkr.Ticks[j];\n                if (tk.Level != 0 || tk.PixelPos < plot.PlotRect.Min.y || tk.PixelPos > plot.PlotRect.Max.y)\n                    continue;\n                const ImVec2 start(ax.Datum1, tk.PixelPos);\n                const float len = (!aux && tk.Major) ? gp.Style.MajorTickLen.y  : gp.Style.MinorTickLen.y;\n                const float thk = (!aux && tk.Major) ? gp.Style.MajorTickSize.y : gp.Style.MinorTickSize.y;\n                DrawList.AddLine(start, start + ImVec2(direction*len,0), ax.ColorTick, thk);\n            }\n            if (aux || !render_border)\n                DrawList.AddLine(ImVec2(ax.Datum1, plot.PlotRect.Min.y), ImVec2(ax.Datum1, plot.PlotRect.Max.y), ax.ColorTick, gp.Style.MinorTickSize.y);\n        }\n        count_L += !opp;\n        count_R +=  opp;\n    }\n    ImGui::PopClipRect();\n\n    // render annotations\n    PushPlotClipRect();\n    for (int i = 0; i < gp.Annotations.Size; ++i) {\n        const char* txt       = gp.Annotations.GetText(i);\n        ImPlotAnnotation& an  = gp.Annotations.Annotations[i];\n        const ImVec2 txt_size = ImGui::CalcTextSize(txt);\n        const ImVec2 size     = txt_size + gp.Style.AnnotationPadding * 2;\n        ImVec2 pos            = an.Pos;\n        if (an.Offset.x == 0)\n            pos.x -= size.x / 2;\n        else if (an.Offset.x > 0)\n            pos.x += an.Offset.x;\n        else\n            pos.x -= size.x - an.Offset.x;\n        if (an.Offset.y == 0)\n            pos.y -= size.y / 2;\n        else if (an.Offset.y > 0)\n            pos.y += an.Offset.y;\n        else\n            pos.y -= size.y - an.Offset.y;\n        if (an.Clamp)\n            pos = ClampLabelPos(pos, size, plot.PlotRect.Min, plot.PlotRect.Max);\n        ImRect rect(pos,pos+size);\n        if (an.Offset.x != 0 || an.Offset.y != 0) {\n            ImVec2 corners[4] = {rect.GetTL(), rect.GetTR(), rect.GetBR(), rect.GetBL()};\n            int min_corner = 0;\n            float min_len = FLT_MAX;\n            for (int c = 0; c < 4; ++c) {\n                float len = ImLengthSqr(an.Pos - corners[c]);\n                if (len < min_len) {\n                    min_corner = c;\n                    min_len = len;\n                }\n            }\n            DrawList.AddLine(an.Pos, corners[min_corner], an.ColorBg);\n        }\n        DrawList.AddRectFilled(rect.Min, rect.Max, an.ColorBg);\n        DrawList.AddText(pos + gp.Style.AnnotationPadding, an.ColorFg, txt);\n    }\n\n    // render selection\n    if (plot.Selected)\n        RenderSelectionRect(DrawList, plot.SelectRect.Min + plot.PlotRect.Min, plot.SelectRect.Max + plot.PlotRect.Min, GetStyleColorVec4(ImPlotCol_Selection));\n\n    // render crosshairs\n    if (ImHasFlag(plot.Flags, ImPlotFlags_Crosshairs) && plot.Hovered && !(any_x_held || any_y_held) && !plot.Selecting && !plot.Items.Legend.Hovered) {\n        ImGui::SetMouseCursor(ImGuiMouseCursor_None);\n        ImVec2 xy = IO.MousePos;\n        ImVec2 h1(plot.PlotRect.Min.x, xy.y);\n        ImVec2 h2(xy.x - 5, xy.y);\n        ImVec2 h3(xy.x + 5, xy.y);\n        ImVec2 h4(plot.PlotRect.Max.x, xy.y);\n        ImVec2 v1(xy.x, plot.PlotRect.Min.y);\n        ImVec2 v2(xy.x, xy.y - 5);\n        ImVec2 v3(xy.x, xy.y + 5);\n        ImVec2 v4(xy.x, plot.PlotRect.Max.y);\n        ImU32 col = GetStyleColorU32(ImPlotCol_Crosshairs);\n        DrawList.AddLine(h1, h2, col);\n        DrawList.AddLine(h3, h4, col);\n        DrawList.AddLine(v1, v2, col);\n        DrawList.AddLine(v3, v4, col);\n    }\n\n    // render mouse pos\n    if (!ImHasFlag(plot.Flags, ImPlotFlags_NoMouseText) && (plot.Hovered || ImHasFlag(plot.MouseTextFlags, ImPlotMouseTextFlags_ShowAlways))) {\n\n        const bool no_aux = ImHasFlag(plot.MouseTextFlags, ImPlotMouseTextFlags_NoAuxAxes);\n        const bool no_fmt = ImHasFlag(plot.MouseTextFlags, ImPlotMouseTextFlags_NoFormat);\n\n        ImGuiTextBuffer& builder = gp.MousePosStringBuilder;\n        builder.Buf.shrink(0);\n        char buff[IMPLOT_LABEL_MAX_SIZE];\n\n        const int num_x = no_aux ? 1 : IMPLOT_NUM_X_AXES;\n        for (int i = 0; i < num_x; ++i) {\n            ImPlotAxis& x_axis = plot.XAxis(i);\n            if (!x_axis.Enabled)\n                continue;\n            if (i > 0)\n                builder.append(\", (\");\n            double v = x_axis.PixelsToPlot(IO.MousePos.x);\n            if (no_fmt)\n                Formatter_Default(v,buff,IMPLOT_LABEL_MAX_SIZE,(void*)IMPLOT_LABEL_FORMAT);\n            else\n                LabelAxisValue(x_axis,v,buff,IMPLOT_LABEL_MAX_SIZE,true);\n            builder.append(buff);\n            if (i > 0)\n                builder.append(\")\");\n        }\n        builder.append(\", \");\n        const int num_y = no_aux ? 1 : IMPLOT_NUM_Y_AXES;\n        for (int i = 0; i < num_y; ++i) {\n            ImPlotAxis& y_axis = plot.YAxis(i);\n            if (!y_axis.Enabled)\n                continue;\n            if (i > 0)\n                builder.append(\", (\");\n            double v = y_axis.PixelsToPlot(IO.MousePos.y);\n            if (no_fmt)\n                Formatter_Default(v,buff,IMPLOT_LABEL_MAX_SIZE,(void*)IMPLOT_LABEL_FORMAT);\n            else\n                LabelAxisValue(y_axis,v,buff,IMPLOT_LABEL_MAX_SIZE,true);\n            builder.append(buff);\n            if (i > 0)\n                builder.append(\")\");\n        }\n\n        if (!builder.empty()) {\n            const ImVec2 size = ImGui::CalcTextSize(builder.c_str());\n            const ImVec2 pos = GetLocationPos(plot.PlotRect, size, plot.MouseTextLocation, gp.Style.MousePosPadding);\n            DrawList.AddText(pos, GetStyleColorU32(ImPlotCol_InlayText), builder.c_str());\n        }\n    }\n    PopPlotClipRect();\n\n    // axis side switch\n    if (!plot.Held) {\n        ImVec2 mouse_pos = ImGui::GetIO().MousePos;\n        ImRect trigger_rect = plot.PlotRect;\n        trigger_rect.Expand(-10);\n        for (int i = 0; i < IMPLOT_NUM_X_AXES; ++i) {\n            ImPlotAxis& x_axis = plot.XAxis(i);\n            if (ImHasFlag(x_axis.Flags, ImPlotAxisFlags_NoSideSwitch))\n                continue;\n            if (x_axis.Held && plot.PlotRect.Contains(mouse_pos)) {\n                const bool opp = ImHasFlag(x_axis.Flags, ImPlotAxisFlags_Opposite);\n                if (!opp) {\n                    ImRect rect(plot.PlotRect.Min.x - 5, plot.PlotRect.Min.y - 5,\n                                plot.PlotRect.Max.x + 5, plot.PlotRect.Min.y + 5);\n                    if (mouse_pos.y < plot.PlotRect.Max.y - 10)\n                        DrawList.AddRectFilled(rect.Min, rect.Max, x_axis.ColorHov);\n                    if (rect.Contains(mouse_pos))\n                        x_axis.Flags |= ImPlotAxisFlags_Opposite;\n                }\n                else {\n                    ImRect rect(plot.PlotRect.Min.x - 5, plot.PlotRect.Max.y - 5,\n                                plot.PlotRect.Max.x + 5, plot.PlotRect.Max.y + 5);\n                    if (mouse_pos.y > plot.PlotRect.Min.y + 10)\n                        DrawList.AddRectFilled(rect.Min, rect.Max, x_axis.ColorHov);\n                    if (rect.Contains(mouse_pos))\n                        x_axis.Flags &= ~ImPlotAxisFlags_Opposite;\n                }\n            }\n        }\n        for (int i = 0; i < IMPLOT_NUM_Y_AXES; ++i) {\n            ImPlotAxis& y_axis = plot.YAxis(i);\n            if (ImHasFlag(y_axis.Flags, ImPlotAxisFlags_NoSideSwitch))\n                continue;\n            if (y_axis.Held && plot.PlotRect.Contains(mouse_pos)) {\n                const bool opp = ImHasFlag(y_axis.Flags, ImPlotAxisFlags_Opposite);\n                if (!opp) {\n                    ImRect rect(plot.PlotRect.Max.x - 5, plot.PlotRect.Min.y - 5,\n                                plot.PlotRect.Max.x + 5, plot.PlotRect.Max.y + 5);\n                    if (mouse_pos.x > plot.PlotRect.Min.x + 10)\n                        DrawList.AddRectFilled(rect.Min, rect.Max, y_axis.ColorHov);\n                    if (rect.Contains(mouse_pos))\n                        y_axis.Flags |= ImPlotAxisFlags_Opposite;\n                }\n                else {\n                    ImRect rect(plot.PlotRect.Min.x - 5, plot.PlotRect.Min.y - 5,\n                                plot.PlotRect.Min.x + 5, plot.PlotRect.Max.y + 5);\n                    if (mouse_pos.x < plot.PlotRect.Max.x - 10)\n                        DrawList.AddRectFilled(rect.Min, rect.Max, y_axis.ColorHov);\n                    if (rect.Contains(mouse_pos))\n                        y_axis.Flags &= ~ImPlotAxisFlags_Opposite;\n                }\n            }\n        }\n    }\n\n    // reset legend hovers\n    plot.Items.Legend.Hovered = false;\n    for (int i = 0; i < plot.Items.GetItemCount(); ++i)\n        plot.Items.GetItemByIndex(i)->LegendHovered = false;\n    // render legend\n    if (!ImHasFlag(plot.Flags, ImPlotFlags_NoLegend) && plot.Items.GetLegendCount() > 0) {\n        ImPlotLegend& legend = plot.Items.Legend;\n        const bool   legend_out  = ImHasFlag(legend.Flags, ImPlotLegendFlags_Outside);\n        const bool   legend_horz = ImHasFlag(legend.Flags, ImPlotLegendFlags_Horizontal);\n        const ImVec2 legend_size = CalcLegendSize(plot.Items, gp.Style.LegendInnerPadding, gp.Style.LegendSpacing, !legend_horz);\n        const ImVec2 legend_pos  = GetLocationPos(legend_out ? plot.FrameRect : plot.PlotRect,\n                                                  legend_size,\n                                                  legend.Location,\n                                                  legend_out ? gp.Style.PlotPadding : gp.Style.LegendPadding);\n        legend.Rect = ImRect(legend_pos, legend_pos + legend_size);\n        // test hover\n        legend.Hovered = ImGui::IsWindowHovered() && legend.Rect.Contains(IO.MousePos);\n\n        if (legend_out)\n            ImGui::PushClipRect(plot.FrameRect.Min, plot.FrameRect.Max, true);\n        else\n            PushPlotClipRect();\n        ImU32  col_bg      = GetStyleColorU32(ImPlotCol_LegendBg);\n        ImU32  col_bd      = GetStyleColorU32(ImPlotCol_LegendBorder);\n        DrawList.AddRectFilled(legend.Rect.Min, legend.Rect.Max, col_bg);\n        DrawList.AddRect(legend.Rect.Min, legend.Rect.Max, col_bd);\n        bool legend_contextable = ShowLegendEntries(plot.Items, legend.Rect, legend.Hovered, gp.Style.LegendInnerPadding, gp.Style.LegendSpacing, !legend_horz, DrawList)\n                                && !ImHasFlag(legend.Flags, ImPlotLegendFlags_NoMenus);\n\n        // main ctx menu\n        if (gp.OpenContextThisFrame && legend_contextable && !ImHasFlag(plot.Flags, ImPlotFlags_NoMenus))\n            ImGui::OpenPopup(\"##LegendContext\");\n        ImGui::PopClipRect();\n        if (ImGui::BeginPopup(\"##LegendContext\")) {\n            ImGui::Text(\"Legend\"); ImGui::Separator();\n            if (ShowLegendContextMenu(legend, !ImHasFlag(plot.Flags, ImPlotFlags_NoLegend)))\n                ImFlipFlag(plot.Flags, ImPlotFlags_NoLegend);\n            ImGui::EndPopup();\n        }\n    }\n    else {\n        plot.Items.Legend.Rect = ImRect();\n    }\n\n    // render border\n    if (render_border)\n        DrawList.AddRect(plot.PlotRect.Min, plot.PlotRect.Max, GetStyleColorU32(ImPlotCol_PlotBorder), 0, ImDrawFlags_RoundCornersAll, gp.Style.PlotBorderSize);\n\n    // render tags\n    for (int i = 0; i < gp.Tags.Size; ++i) {\n        ImPlotTag& tag  = gp.Tags.Tags[i];\n        ImPlotAxis& axis = plot.Axes[tag.Axis];\n        if (!axis.Enabled || !axis.Range.Contains(tag.Value))\n            continue;\n        const char* txt = gp.Tags.GetText(i);\n        ImVec2 text_size = ImGui::CalcTextSize(txt);\n        ImVec2 size = text_size + gp.Style.AnnotationPadding * 2;\n        ImVec2 pos;\n        axis.Ticker.OverrideSizeLate(size);\n        float pix = IM_ROUND(axis.PlotToPixels(tag.Value));\n        if (axis.Vertical) {\n            if (axis.IsOpposite()) {\n                pos = ImVec2(axis.Datum1 + gp.Style.LabelPadding.x, pix - size.y * 0.5f);\n                DrawList.AddTriangleFilled(ImVec2(axis.Datum1,pix), pos, pos + ImVec2(0,size.y), tag.ColorBg);\n            }\n            else {\n                pos = ImVec2(axis.Datum1 - size.x - gp.Style.LabelPadding.x, pix - size.y * 0.5f);\n                DrawList.AddTriangleFilled(pos + ImVec2(size.x,0), ImVec2(axis.Datum1,pix), pos+size, tag.ColorBg);\n            }\n        }\n        else {\n            if (axis.IsOpposite()) {\n                pos = ImVec2(pix - size.x * 0.5f, axis.Datum1 - size.y - gp.Style.LabelPadding.y );\n                DrawList.AddTriangleFilled(pos + ImVec2(0,size.y), pos + size, ImVec2(pix,axis.Datum1), tag.ColorBg);\n            }\n            else {\n                pos = ImVec2(pix - size.x * 0.5f, axis.Datum1 + gp.Style.LabelPadding.y);\n                DrawList.AddTriangleFilled(pos, ImVec2(pix,axis.Datum1), pos + ImVec2(size.x, 0), tag.ColorBg);\n            }\n        }\n        DrawList.AddRectFilled(pos,pos+size,tag.ColorBg);\n        DrawList.AddText(pos+gp.Style.AnnotationPadding,tag.ColorFg,txt);\n    }\n\n    // FIT DATA --------------------------------------------------------------\n    const bool axis_equal = ImHasFlag(plot.Flags, ImPlotFlags_Equal);\n    if (plot.FitThisFrame) {\n        for (int i = 0; i < IMPLOT_NUM_X_AXES; i++) {\n            ImPlotAxis& x_axis = plot.XAxis(i);\n            if (x_axis.FitThisFrame) {\n                x_axis.ApplyFit(gp.Style.FitPadding.x);\n                if (axis_equal && x_axis.OrthoAxis != nullptr) {\n                    double aspect = x_axis.GetAspect();\n                    ImPlotAxis& y_axis = *x_axis.OrthoAxis;\n                    if (y_axis.FitThisFrame) {\n                        y_axis.ApplyFit(gp.Style.FitPadding.y);\n                        y_axis.FitThisFrame = false;\n                        aspect = ImMax(aspect, y_axis.GetAspect());\n                    }\n                    x_axis.SetAspect(aspect);\n                    y_axis.SetAspect(aspect);\n                }\n            }\n        }\n        for (int i = 0; i < IMPLOT_NUM_Y_AXES; i++) {\n            ImPlotAxis& y_axis = plot.YAxis(i);\n            if (y_axis.FitThisFrame) {\n                y_axis.ApplyFit(gp.Style.FitPadding.y);\n                if (axis_equal && y_axis.OrthoAxis != nullptr) {\n                    double aspect = y_axis.GetAspect();\n                    ImPlotAxis& x_axis = *y_axis.OrthoAxis;\n                    if (x_axis.FitThisFrame) {\n                        x_axis.ApplyFit(gp.Style.FitPadding.x);\n                        x_axis.FitThisFrame = false;\n                        aspect = ImMax(x_axis.GetAspect(), aspect);\n                    }\n                    x_axis.SetAspect(aspect);\n                    y_axis.SetAspect(aspect);\n                }\n            }\n        }\n        plot.FitThisFrame = false;\n    }\n\n    // CONTEXT MENUS -----------------------------------------------------------\n\n    ImGui::PushOverrideID(plot.ID);\n\n    const bool can_ctx = gp.OpenContextThisFrame &&\n                         !ImHasFlag(plot.Flags, ImPlotFlags_NoMenus) &&\n                         !plot.Items.Legend.Hovered;\n\n\n\n    // main ctx menu\n    if (can_ctx && plot.Hovered)\n        ImGui::OpenPopup(\"##PlotContext\");\n    if (ImGui::BeginPopup(\"##PlotContext\")) {\n        ShowPlotContextMenu(plot);\n        ImGui::EndPopup();\n    }\n\n    // axes ctx menus\n    for (int i = 0; i < IMPLOT_NUM_X_AXES; ++i) {\n        ImGui::PushID(i);\n        ImPlotAxis& x_axis = plot.XAxis(i);\n        if (can_ctx && x_axis.Hovered && x_axis.HasMenus())\n            ImGui::OpenPopup(\"##XContext\");\n        if (ImGui::BeginPopup(\"##XContext\")) {\n            ImGui::Text(x_axis.HasLabel() ? plot.GetAxisLabel(x_axis) :  i == 0 ? \"X-Axis\" : \"X-Axis %d\", i + 1);\n            ImGui::Separator();\n            ShowAxisContextMenu(x_axis, axis_equal ? x_axis.OrthoAxis : nullptr, true);\n            ImGui::EndPopup();\n        }\n        ImGui::PopID();\n    }\n    for (int i = 0; i < IMPLOT_NUM_Y_AXES; ++i) {\n        ImGui::PushID(i);\n        ImPlotAxis& y_axis = plot.YAxis(i);\n        if (can_ctx && y_axis.Hovered && y_axis.HasMenus())\n            ImGui::OpenPopup(\"##YContext\");\n        if (ImGui::BeginPopup(\"##YContext\")) {\n            ImGui::Text(y_axis.HasLabel() ? plot.GetAxisLabel(y_axis) : i == 0 ? \"Y-Axis\" : \"Y-Axis %d\", i + 1);\n            ImGui::Separator();\n            ShowAxisContextMenu(y_axis, axis_equal ? y_axis.OrthoAxis : nullptr, false);\n            ImGui::EndPopup();\n        }\n        ImGui::PopID();\n    }\n    ImGui::PopID();\n\n    // LINKED AXES ------------------------------------------------------------\n\n    for (int i = 0; i < ImAxis_COUNT; ++i)\n        plot.Axes[i].PushLinks();\n\n\n    // CLEANUP ----------------------------------------------------------------\n\n    // remove items\n    if (gp.CurrentItems == &plot.Items)\n        gp.CurrentItems = nullptr;\n    // reset the plot items for the next frame\n    for (int i = 0; i < plot.Items.GetItemCount(); ++i) {\n        plot.Items.GetItemByIndex(i)->SeenThisFrame = false;\n    }\n\n    // mark the plot as initialized, i.e. having made it through one frame completely\n    plot.Initialized = true;\n    // Pop ImGui::PushID at the end of BeginPlot\n    ImGui::PopID();\n    // Reset context for next plot\n    ResetCtxForNextPlot(GImPlot);\n\n    // setup next subplot\n    if (gp.CurrentSubplot != nullptr) {\n        ImGui::PopID();\n        SubplotNextCell();\n    }\n}\n\n//-----------------------------------------------------------------------------\n// BEGIN/END SUBPLOT\n//-----------------------------------------------------------------------------\n\nstatic const float SUBPLOT_BORDER_SIZE             = 1.0f;\nstatic const float SUBPLOT_SPLITTER_HALF_THICKNESS = 4.0f;\nstatic const float SUBPLOT_SPLITTER_FEEDBACK_TIMER = 0.06f;\n\nvoid SubplotSetCell(int row, int col) {\n    ImPlotContext& gp      = *GImPlot;\n    ImPlotSubplot& subplot = *gp.CurrentSubplot;\n    if (row >= subplot.Rows || col >= subplot.Cols)\n        return;\n    float xoff = 0;\n    float yoff = 0;\n    for (int c = 0; c < col; ++c)\n        xoff += subplot.ColRatios[c];\n    for (int r = 0; r < row; ++r)\n        yoff += subplot.RowRatios[r];\n    const ImVec2 grid_size = subplot.GridRect.GetSize();\n    ImVec2 cpos            = subplot.GridRect.Min + ImVec2(xoff*grid_size.x,yoff*grid_size.y);\n    cpos.x = IM_ROUND(cpos.x);\n    cpos.y = IM_ROUND(cpos.y);\n    ImGui::GetCurrentWindow()->DC.CursorPos =  cpos;\n    // set cell size\n    subplot.CellSize.x = IM_ROUND(subplot.GridRect.GetWidth()  * subplot.ColRatios[col]);\n    subplot.CellSize.y = IM_ROUND(subplot.GridRect.GetHeight() * subplot.RowRatios[row]);\n    // setup links\n    const bool lx = ImHasFlag(subplot.Flags, ImPlotSubplotFlags_LinkAllX);\n    const bool ly = ImHasFlag(subplot.Flags, ImPlotSubplotFlags_LinkAllY);\n    const bool lr = ImHasFlag(subplot.Flags, ImPlotSubplotFlags_LinkRows);\n    const bool lc = ImHasFlag(subplot.Flags, ImPlotSubplotFlags_LinkCols);\n\n    SetNextAxisLinks(ImAxis_X1, lx ? &subplot.ColLinkData[0].Min : lc ? &subplot.ColLinkData[col].Min : nullptr,\n                                lx ? &subplot.ColLinkData[0].Max : lc ? &subplot.ColLinkData[col].Max : nullptr);\n    SetNextAxisLinks(ImAxis_Y1, ly ? &subplot.RowLinkData[0].Min : lr ? &subplot.RowLinkData[row].Min : nullptr,\n                                ly ? &subplot.RowLinkData[0].Max : lr ? &subplot.RowLinkData[row].Max : nullptr);\n    // setup alignment\n    if (!ImHasFlag(subplot.Flags, ImPlotSubplotFlags_NoAlign)) {\n        gp.CurrentAlignmentH = &subplot.RowAlignmentData[row];\n        gp.CurrentAlignmentV = &subplot.ColAlignmentData[col];\n    }\n    // set idx\n    if (ImHasFlag(subplot.Flags, ImPlotSubplotFlags_ColMajor))\n        subplot.CurrentIdx = col * subplot.Rows + row;\n    else\n        subplot.CurrentIdx = row * subplot.Cols + col;\n}\n\nvoid SubplotSetCell(int idx) {\n    ImPlotContext& gp      = *GImPlot;\n    ImPlotSubplot& subplot = *gp.CurrentSubplot;\n    if (idx >= subplot.Rows * subplot.Cols)\n        return;\n    int row = 0, col = 0;\n    if (ImHasFlag(subplot.Flags, ImPlotSubplotFlags_ColMajor)) {\n        row = idx % subplot.Rows;\n        col = idx / subplot.Rows;\n    }\n    else {\n        row = idx / subplot.Cols;\n        col = idx % subplot.Cols;\n    }\n    return SubplotSetCell(row, col);\n}\n\nvoid SubplotNextCell() {\n    ImPlotContext& gp      = *GImPlot;\n    ImPlotSubplot& subplot = *gp.CurrentSubplot;\n    SubplotSetCell(++subplot.CurrentIdx);\n}\n\nbool BeginSubplots(const char* title, int rows, int cols, const ImVec2& size, ImPlotSubplotFlags flags, float* row_sizes, float* col_sizes) {\n    IM_ASSERT_USER_ERROR(rows > 0 && cols > 0, \"Invalid sizing arguments!\");\n    IM_ASSERT_USER_ERROR(GImPlot != nullptr, \"No current context. Did you call ImPlot::CreateContext() or ImPlot::SetCurrentContext()?\");\n    ImPlotContext& gp = *GImPlot;\n    IM_ASSERT_USER_ERROR(gp.CurrentSubplot == nullptr, \"Mismatched BeginSubplots()/EndSubplots()!\");\n    ImGuiContext &G = *GImGui;\n    ImGuiWindow * Window = G.CurrentWindow;\n    if (Window->SkipItems)\n        return false;\n    const ImGuiID ID = Window->GetID(title);\n    bool just_created = gp.Subplots.GetByKey(ID) == nullptr;\n    gp.CurrentSubplot = gp.Subplots.GetOrAddByKey(ID);\n    ImPlotSubplot& subplot = *gp.CurrentSubplot;\n    subplot.ID       = ID;\n    subplot.Items.ID = ID - 1;\n    subplot.HasTitle = ImGui::FindRenderedTextEnd(title, nullptr) != title;\n    // push ID\n    ImGui::PushID(ID);\n\n    if (just_created)\n        subplot.Flags = flags;\n    else if (flags != subplot.PreviousFlags)\n        subplot.Flags = flags;\n    subplot.PreviousFlags = flags;\n\n    // check for change in rows and cols\n    if (subplot.Rows != rows || subplot.Cols != cols) {\n        subplot.RowAlignmentData.resize(rows);\n        subplot.RowLinkData.resize(rows);\n        subplot.RowRatios.resize(rows);\n        for (int r = 0; r < rows; ++r) {\n            subplot.RowAlignmentData[r].Reset();\n            subplot.RowLinkData[r] = ImPlotRange(0,1);\n            subplot.RowRatios[r] = 1.0f / rows;\n        }\n        subplot.ColAlignmentData.resize(cols);\n        subplot.ColLinkData.resize(cols);\n        subplot.ColRatios.resize(cols);\n        for (int c = 0; c < cols; ++c) {\n            subplot.ColAlignmentData[c].Reset();\n            subplot.ColLinkData[c] = ImPlotRange(0,1);\n            subplot.ColRatios[c] = 1.0f / cols;\n        }\n    }\n    // check incoming size requests\n    float row_sum = 0, col_sum = 0;\n    if (row_sizes != nullptr) {\n        row_sum = ImSum(row_sizes, rows);\n        for (int r = 0; r < rows; ++r)\n            subplot.RowRatios[r] = row_sizes[r] / row_sum;\n    }\n    if (col_sizes != nullptr) {\n        col_sum = ImSum(col_sizes, cols);\n        for (int c = 0; c < cols; ++c)\n            subplot.ColRatios[c] = col_sizes[c] / col_sum;\n    }\n    subplot.Rows = rows;\n    subplot.Cols = cols;\n\n    // calc plot frame sizes\n    ImVec2 title_size(0.0f, 0.0f);\n    if (!ImHasFlag(subplot.Flags, ImPlotSubplotFlags_NoTitle))\n         title_size = ImGui::CalcTextSize(title, nullptr, true);\n    const float pad_top = title_size.x > 0.0f ? title_size.y + gp.Style.LabelPadding.y : 0;\n    const ImVec2 half_pad = gp.Style.PlotPadding/2;\n    const ImVec2 frame_size = ImGui::CalcItemSize(size, gp.Style.PlotDefaultSize.x, gp.Style.PlotDefaultSize.y);\n    subplot.FrameRect = ImRect(Window->DC.CursorPos, Window->DC.CursorPos + frame_size);\n    subplot.GridRect.Min = subplot.FrameRect.Min + half_pad + ImVec2(0,pad_top);\n    subplot.GridRect.Max = subplot.FrameRect.Max - half_pad;\n    subplot.FrameHovered = subplot.FrameRect.Contains(ImGui::GetMousePos()) && ImGui::IsWindowHovered(ImGuiHoveredFlags_ChildWindows|ImGuiHoveredFlags_AllowWhenBlockedByActiveItem);\n\n    // outside legend adjustments (TODO: make function)\n    const bool share_items = ImHasFlag(subplot.Flags, ImPlotSubplotFlags_ShareItems);\n    if (share_items)\n        gp.CurrentItems = &subplot.Items;\n    if (share_items && !ImHasFlag(subplot.Flags, ImPlotSubplotFlags_NoLegend) && subplot.Items.GetLegendCount() > 0) {\n        ImPlotLegend& legend = subplot.Items.Legend;\n        const bool horz = ImHasFlag(legend.Flags, ImPlotLegendFlags_Horizontal);\n        const ImVec2 legend_size = CalcLegendSize(subplot.Items, gp.Style.LegendInnerPadding, gp.Style.LegendSpacing, !horz);\n        const bool west = ImHasFlag(legend.Location, ImPlotLocation_West) && !ImHasFlag(legend.Location, ImPlotLocation_East);\n        const bool east = ImHasFlag(legend.Location, ImPlotLocation_East) && !ImHasFlag(legend.Location, ImPlotLocation_West);\n        const bool north = ImHasFlag(legend.Location, ImPlotLocation_North) && !ImHasFlag(legend.Location, ImPlotLocation_South);\n        const bool south = ImHasFlag(legend.Location, ImPlotLocation_South) && !ImHasFlag(legend.Location, ImPlotLocation_North);\n        if ((west && !horz) || (west && horz && !north && !south))\n            subplot.GridRect.Min.x += (legend_size.x + gp.Style.LegendPadding.x);\n        if ((east && !horz) || (east && horz && !north && !south))\n            subplot.GridRect.Max.x -= (legend_size.x + gp.Style.LegendPadding.x);\n        if ((north && horz) || (north && !horz && !west && !east))\n            subplot.GridRect.Min.y += (legend_size.y + gp.Style.LegendPadding.y);\n        if ((south && horz) || (south && !horz && !west && !east))\n            subplot.GridRect.Max.y -= (legend_size.y + gp.Style.LegendPadding.y);\n    }\n\n    // render single background frame\n    ImGui::RenderFrame(subplot.FrameRect.Min, subplot.FrameRect.Max, GetStyleColorU32(ImPlotCol_FrameBg), true, ImGui::GetStyle().FrameRounding);\n    // render title\n    if (title_size.x > 0.0f && !ImHasFlag(subplot.Flags, ImPlotFlags_NoTitle)) {\n        const ImU32 col = GetStyleColorU32(ImPlotCol_TitleText);\n        AddTextCentered(ImGui::GetWindowDrawList(),ImVec2(subplot.GridRect.GetCenter().x, subplot.GridRect.Min.y - pad_top + half_pad.y),col,title);\n    }\n\n    // render splitters\n    if (!ImHasFlag(subplot.Flags, ImPlotSubplotFlags_NoResize)) {\n        ImDrawList& DrawList = *ImGui::GetWindowDrawList();\n        const ImU32 hov_col = ImGui::ColorConvertFloat4ToU32(GImGui->Style.Colors[ImGuiCol_SeparatorHovered]);\n        const ImU32 act_col = ImGui::ColorConvertFloat4ToU32(GImGui->Style.Colors[ImGuiCol_SeparatorActive]);\n        float xpos = subplot.GridRect.Min.x;\n        float ypos = subplot.GridRect.Min.y;\n        int separator = 1;\n        // bool pass = false;\n        for (int r = 0; r < subplot.Rows-1; ++r) {\n            ypos += subplot.RowRatios[r] * subplot.GridRect.GetHeight();\n            const ImGuiID sep_id = subplot.ID + separator;\n            ImGui::KeepAliveID(sep_id);\n            const ImRect sep_bb = ImRect(subplot.GridRect.Min.x, ypos-SUBPLOT_SPLITTER_HALF_THICKNESS, subplot.GridRect.Max.x, ypos+SUBPLOT_SPLITTER_HALF_THICKNESS);\n            bool sep_hov = false, sep_hld = false;\n            const bool sep_clk = ImGui::ButtonBehavior(sep_bb, sep_id, &sep_hov, &sep_hld, ImGuiButtonFlags_FlattenChildren | ImGuiButtonFlags_PressedOnClick | ImGuiButtonFlags_PressedOnDoubleClick);\n            if ((sep_hov && G.HoveredIdTimer > SUBPLOT_SPLITTER_FEEDBACK_TIMER) || sep_hld) {\n                if (sep_clk && ImGui::IsMouseDoubleClicked(0)) {\n                    float p = (subplot.RowRatios[r] + subplot.RowRatios[r+1])/2;\n                    subplot.RowRatios[r] = subplot.RowRatios[r+1] = p;\n                }\n                if (sep_clk) {\n                    subplot.TempSizes[0] = subplot.RowRatios[r];\n                    subplot.TempSizes[1] = subplot.RowRatios[r+1];\n                }\n                if (sep_hld) {\n                    float dp = ImGui::GetMouseDragDelta(0).y  / subplot.GridRect.GetHeight();\n                    if (subplot.TempSizes[0] + dp > 0.1f && subplot.TempSizes[1] - dp > 0.1f) {\n                        subplot.RowRatios[r]   = subplot.TempSizes[0] + dp;\n                        subplot.RowRatios[r+1] = subplot.TempSizes[1] - dp;\n                    }\n                }\n                DrawList.AddLine(ImVec2(IM_ROUND(subplot.GridRect.Min.x),IM_ROUND(ypos)),\n                                 ImVec2(IM_ROUND(subplot.GridRect.Max.x),IM_ROUND(ypos)),\n                                 sep_hld ? act_col : hov_col, SUBPLOT_BORDER_SIZE);\n                ImGui::SetMouseCursor(ImGuiMouseCursor_ResizeNS);\n            }\n            separator++;\n        }\n        for (int c = 0; c < subplot.Cols-1; ++c) {\n            xpos += subplot.ColRatios[c] * subplot.GridRect.GetWidth();\n            const ImGuiID sep_id = subplot.ID + separator;\n            ImGui::KeepAliveID(sep_id);\n            const ImRect sep_bb = ImRect(xpos-SUBPLOT_SPLITTER_HALF_THICKNESS, subplot.GridRect.Min.y, xpos+SUBPLOT_SPLITTER_HALF_THICKNESS, subplot.GridRect.Max.y);\n            bool sep_hov = false, sep_hld = false;\n            const bool sep_clk = ImGui::ButtonBehavior(sep_bb, sep_id, &sep_hov, &sep_hld, ImGuiButtonFlags_FlattenChildren | ImGuiButtonFlags_PressedOnClick | ImGuiButtonFlags_PressedOnDoubleClick);\n            if ((sep_hov && G.HoveredIdTimer > SUBPLOT_SPLITTER_FEEDBACK_TIMER) || sep_hld) {\n                if (sep_clk && ImGui::IsMouseDoubleClicked(0)) {\n                    float p = (subplot.ColRatios[c] + subplot.ColRatios[c+1])/2;\n                    subplot.ColRatios[c] = subplot.ColRatios[c+1] = p;\n                }\n                if (sep_clk) {\n                    subplot.TempSizes[0] = subplot.ColRatios[c];\n                    subplot.TempSizes[1] = subplot.ColRatios[c+1];\n                }\n                if (sep_hld) {\n                    float dp = ImGui::GetMouseDragDelta(0).x / subplot.GridRect.GetWidth();\n                    if (subplot.TempSizes[0] + dp > 0.1f && subplot.TempSizes[1] - dp > 0.1f) {\n                        subplot.ColRatios[c]   = subplot.TempSizes[0] + dp;\n                        subplot.ColRatios[c+1] = subplot.TempSizes[1] - dp;\n                    }\n                }\n                DrawList.AddLine(ImVec2(IM_ROUND(xpos),IM_ROUND(subplot.GridRect.Min.y)),\n                                 ImVec2(IM_ROUND(xpos),IM_ROUND(subplot.GridRect.Max.y)),\n                                 sep_hld ? act_col : hov_col, SUBPLOT_BORDER_SIZE);\n                ImGui::SetMouseCursor(ImGuiMouseCursor_ResizeEW);\n            }\n            separator++;\n        }\n    }\n\n    // set outgoing sizes\n    if (row_sizes != nullptr) {\n        for (int r = 0; r < rows; ++r)\n            row_sizes[r] = subplot.RowRatios[r] * row_sum;\n    }\n    if (col_sizes != nullptr) {\n        for (int c = 0; c < cols; ++c)\n            col_sizes[c] = subplot.ColRatios[c] * col_sum;\n    }\n\n    // push styling\n    PushStyleColor(ImPlotCol_FrameBg, IM_COL32_BLACK_TRANS);\n    PushStyleVar(ImPlotStyleVar_PlotPadding, half_pad);\n    PushStyleVar(ImPlotStyleVar_PlotMinSize, ImVec2(0,0));\n    ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize,0);\n\n    // set initial cursor pos\n    Window->DC.CursorPos = subplot.GridRect.Min;\n    // begin alignments\n    for (int r = 0; r < subplot.Rows; ++r)\n        subplot.RowAlignmentData[r].Begin();\n    for (int c = 0; c < subplot.Cols; ++c)\n        subplot.ColAlignmentData[c].Begin();\n    // clear legend data\n    subplot.Items.Legend.Reset();\n    // Setup first subplot\n    SubplotSetCell(0,0);\n    return true;\n}\n\nvoid EndSubplots() {\n    IM_ASSERT_USER_ERROR(GImPlot != nullptr, \"No current context. Did you call ImPlot::CreateContext() or ImPlot::SetCurrentContext()?\");\n    ImPlotContext& gp = *GImPlot;\n    IM_ASSERT_USER_ERROR(gp.CurrentSubplot != nullptr, \"Mismatched BeginSubplots()/EndSubplots()!\");\n    ImPlotSubplot& subplot = *gp.CurrentSubplot;\n    // set alignments\n    for (int r = 0; r < subplot.Rows; ++r)\n        subplot.RowAlignmentData[r].End();\n    for (int c = 0; c < subplot.Cols; ++c)\n        subplot.ColAlignmentData[c].End();\n    // pop styling\n    PopStyleColor();\n    PopStyleVar();\n    PopStyleVar();\n    ImGui::PopStyleVar();\n    // legend\n    subplot.Items.Legend.Hovered = false;\n    for (int i = 0; i < subplot.Items.GetItemCount(); ++i)\n        subplot.Items.GetItemByIndex(i)->LegendHovered = false;\n    // render legend\n    const bool share_items = ImHasFlag(subplot.Flags, ImPlotSubplotFlags_ShareItems);\n    ImDrawList& DrawList = *ImGui::GetWindowDrawList();\n    if (share_items && !ImHasFlag(subplot.Flags, ImPlotSubplotFlags_NoLegend) && subplot.Items.GetLegendCount() > 0) {\n        const bool   legend_horz = ImHasFlag(subplot.Items.Legend.Flags, ImPlotLegendFlags_Horizontal);\n        const ImVec2 legend_size = CalcLegendSize(subplot.Items, gp.Style.LegendInnerPadding, gp.Style.LegendSpacing, !legend_horz);\n        const ImVec2 legend_pos  = GetLocationPos(subplot.FrameRect, legend_size, subplot.Items.Legend.Location, gp.Style.PlotPadding);\n        subplot.Items.Legend.Rect = ImRect(legend_pos, legend_pos + legend_size);\n        subplot.Items.Legend.Hovered = subplot.FrameHovered && subplot.Items.Legend.Rect.Contains(ImGui::GetIO().MousePos);\n        ImGui::PushClipRect(subplot.FrameRect.Min, subplot.FrameRect.Max, true);\n        ImU32  col_bg      = GetStyleColorU32(ImPlotCol_LegendBg);\n        ImU32  col_bd      = GetStyleColorU32(ImPlotCol_LegendBorder);\n        DrawList.AddRectFilled(subplot.Items.Legend.Rect.Min, subplot.Items.Legend.Rect.Max, col_bg);\n        DrawList.AddRect(subplot.Items.Legend.Rect.Min, subplot.Items.Legend.Rect.Max, col_bd);\n        bool legend_contextable = ShowLegendEntries(subplot.Items, subplot.Items.Legend.Rect, subplot.Items.Legend.Hovered, gp.Style.LegendInnerPadding, gp.Style.LegendSpacing, !legend_horz, DrawList)\n                                && !ImHasFlag(subplot.Items.Legend.Flags, ImPlotLegendFlags_NoMenus);\n        if (legend_contextable && !ImHasFlag(subplot.Flags, ImPlotSubplotFlags_NoMenus) && ImGui::GetIO().MouseReleased[gp.InputMap.Menu])\n            ImGui::OpenPopup(\"##LegendContext\");\n        ImGui::PopClipRect();\n        if (ImGui::BeginPopup(\"##LegendContext\")) {\n            ImGui::Text(\"Legend\"); ImGui::Separator();\n            if (ShowLegendContextMenu(subplot.Items.Legend, !ImHasFlag(subplot.Flags, ImPlotFlags_NoLegend)))\n                ImFlipFlag(subplot.Flags, ImPlotFlags_NoLegend);\n            ImGui::EndPopup();\n        }\n    }\n    else {\n        subplot.Items.Legend.Rect = ImRect();\n    }\n    // remove items\n    if (gp.CurrentItems == &subplot.Items)\n        gp.CurrentItems = nullptr;\n    // reset the plot items for the next frame (TODO: put this elswhere)\n    for (int i = 0; i < subplot.Items.GetItemCount(); ++i) {\n        subplot.Items.GetItemByIndex(i)->SeenThisFrame = false;\n    }\n    // pop id\n    ImGui::PopID();\n    // set DC back correctly\n    GImGui->CurrentWindow->DC.CursorPos = subplot.FrameRect.Min;\n    ImGui::Dummy(subplot.FrameRect.GetSize());\n    ResetCtxForNextSubplot(GImPlot);\n\n}\n\n//-----------------------------------------------------------------------------\n// [SECTION] Plot Utils\n//-----------------------------------------------------------------------------\n\nvoid SetAxis(ImAxis axis) {\n    ImPlotContext& gp = *GImPlot;\n    IM_ASSERT_USER_ERROR(gp.CurrentPlot != nullptr, \"SetAxis() needs to be called between BeginPlot() and EndPlot()!\");\n    IM_ASSERT_USER_ERROR(axis >= ImAxis_X1 && axis < ImAxis_COUNT, \"Axis index out of bounds!\");\n    IM_ASSERT_USER_ERROR(gp.CurrentPlot->Axes[axis].Enabled, \"Axis is not enabled! Did you forget to call SetupAxis()?\");\n    SetupLock();\n    if (axis < ImAxis_Y1)\n        gp.CurrentPlot->CurrentX = axis;\n    else\n        gp.CurrentPlot->CurrentY = axis;\n}\n\nvoid SetAxes(ImAxis x_idx, ImAxis y_idx) {\n    ImPlotContext& gp = *GImPlot;\n    IM_ASSERT_USER_ERROR(gp.CurrentPlot != nullptr, \"SetAxes() needs to be called between BeginPlot() and EndPlot()!\");\n    IM_ASSERT_USER_ERROR(x_idx >= ImAxis_X1 && x_idx < ImAxis_Y1, \"X-Axis index out of bounds!\");\n    IM_ASSERT_USER_ERROR(y_idx >= ImAxis_Y1 && y_idx < ImAxis_COUNT, \"Y-Axis index out of bounds!\");\n    IM_ASSERT_USER_ERROR(gp.CurrentPlot->Axes[x_idx].Enabled, \"Axis is not enabled! Did you forget to call SetupAxis()?\");\n    IM_ASSERT_USER_ERROR(gp.CurrentPlot->Axes[y_idx].Enabled, \"Axis is not enabled! Did you forget to call SetupAxis()?\");\n    SetupLock();\n    gp.CurrentPlot->CurrentX = x_idx;\n    gp.CurrentPlot->CurrentY = y_idx;\n}\n\nImPlotPoint PixelsToPlot(float x, float y, ImAxis x_idx, ImAxis y_idx) {\n    ImPlotContext& gp = *GImPlot;\n    IM_ASSERT_USER_ERROR(gp.CurrentPlot != nullptr, \"PixelsToPlot() needs to be called between BeginPlot() and EndPlot()!\");\n    IM_ASSERT_USER_ERROR(x_idx == IMPLOT_AUTO || (x_idx >= ImAxis_X1 && x_idx < ImAxis_Y1),    \"X-Axis index out of bounds!\");\n    IM_ASSERT_USER_ERROR(y_idx == IMPLOT_AUTO || (y_idx >= ImAxis_Y1 && y_idx < ImAxis_COUNT), \"Y-Axis index out of bounds!\");\n    SetupLock();\n    ImPlotPlot& plot   = *gp.CurrentPlot;\n    ImPlotAxis& x_axis = x_idx == IMPLOT_AUTO ? plot.Axes[plot.CurrentX] : plot.Axes[x_idx];\n    ImPlotAxis& y_axis = y_idx == IMPLOT_AUTO ? plot.Axes[plot.CurrentY] : plot.Axes[y_idx];\n    return ImPlotPoint( x_axis.PixelsToPlot(x), y_axis.PixelsToPlot(y) );\n}\n\nImPlotPoint PixelsToPlot(const ImVec2& pix, ImAxis x_idx, ImAxis y_idx) {\n    return PixelsToPlot(pix.x, pix.y, x_idx, y_idx);\n}\n\nImVec2 PlotToPixels(double x, double y, ImAxis x_idx, ImAxis y_idx) {\n    ImPlotContext& gp = *GImPlot;\n    IM_ASSERT_USER_ERROR(gp.CurrentPlot != nullptr, \"PlotToPixels() needs to be called between BeginPlot() and EndPlot()!\");\n    IM_ASSERT_USER_ERROR(x_idx == IMPLOT_AUTO || (x_idx >= ImAxis_X1 && x_idx < ImAxis_Y1),    \"X-Axis index out of bounds!\");\n    IM_ASSERT_USER_ERROR(y_idx == IMPLOT_AUTO || (y_idx >= ImAxis_Y1 && y_idx < ImAxis_COUNT), \"Y-Axis index out of bounds!\");\n    SetupLock();\n    ImPlotPlot& plot = *gp.CurrentPlot;\n    ImPlotAxis& x_axis = x_idx == IMPLOT_AUTO ? plot.Axes[plot.CurrentX] : plot.Axes[x_idx];\n    ImPlotAxis& y_axis = y_idx == IMPLOT_AUTO ? plot.Axes[plot.CurrentY] : plot.Axes[y_idx];\n    return ImVec2( x_axis.PlotToPixels(x), y_axis.PlotToPixels(y) );\n}\n\nImVec2 PlotToPixels(const ImPlotPoint& plt, ImAxis x_idx, ImAxis y_idx) {\n    return PlotToPixels(plt.x, plt.y, x_idx, y_idx);\n}\n\nImVec2 GetPlotPos() {\n    ImPlotContext& gp = *GImPlot;\n    IM_ASSERT_USER_ERROR(gp.CurrentPlot != nullptr, \"GetPlotPos() needs to be called between BeginPlot() and EndPlot()!\");\n    SetupLock();\n    return gp.CurrentPlot->PlotRect.Min;\n}\n\nImVec2 GetPlotSize() {\n    ImPlotContext& gp = *GImPlot;\n    IM_ASSERT_USER_ERROR(gp.CurrentPlot != nullptr, \"GetPlotSize() needs to be called between BeginPlot() and EndPlot()!\");\n    SetupLock();\n    return gp.CurrentPlot->PlotRect.GetSize();\n}\n\nImPlotPoint GetPlotMousePos(ImAxis x_idx, ImAxis y_idx) {\n    IM_ASSERT_USER_ERROR(GImPlot->CurrentPlot != nullptr, \"GetPlotMousePos() needs to be called between BeginPlot() and EndPlot()!\");\n    SetupLock();\n    return PixelsToPlot(ImGui::GetMousePos(), x_idx, y_idx);\n}\n\nImPlotRect GetPlotLimits(ImAxis x_idx, ImAxis y_idx) {\n    ImPlotContext& gp = *GImPlot;\n    IM_ASSERT_USER_ERROR(gp.CurrentPlot != nullptr, \"GetPlotLimits() needs to be called between BeginPlot() and EndPlot()!\");\n    IM_ASSERT_USER_ERROR(x_idx == IMPLOT_AUTO || (x_idx >= ImAxis_X1 && x_idx < ImAxis_Y1),    \"X-Axis index out of bounds!\");\n    IM_ASSERT_USER_ERROR(y_idx == IMPLOT_AUTO || (y_idx >= ImAxis_Y1 && y_idx < ImAxis_COUNT), \"Y-Axis index out of bounds!\");\n    SetupLock();\n    ImPlotPlot& plot = *gp.CurrentPlot;\n    ImPlotAxis& x_axis = x_idx == IMPLOT_AUTO ? plot.Axes[plot.CurrentX] : plot.Axes[x_idx];\n    ImPlotAxis& y_axis = y_idx == IMPLOT_AUTO ? plot.Axes[plot.CurrentY] : plot.Axes[y_idx];\n    ImPlotRect limits;\n    limits.X = x_axis.Range;\n    limits.Y = y_axis.Range;\n    return limits;\n}\n\nbool IsPlotHovered() {\n    ImPlotContext& gp = *GImPlot;\n    IM_ASSERT_USER_ERROR(gp.CurrentPlot != nullptr, \"IsPlotHovered() needs to be called between BeginPlot() and EndPlot()!\");\n    SetupLock();\n    return gp.CurrentPlot->Hovered;\n}\n\nbool IsAxisHovered(ImAxis axis) {\n    ImPlotContext& gp = *GImPlot;\n    IM_ASSERT_USER_ERROR(gp.CurrentPlot != nullptr, \"IsPlotXAxisHovered() needs to be called between BeginPlot() and EndPlot()!\");\n    SetupLock();\n    return gp.CurrentPlot->Axes[axis].Hovered;\n}\n\nbool IsSubplotsHovered() {\n    ImPlotContext& gp = *GImPlot;\n    IM_ASSERT_USER_ERROR(gp.CurrentSubplot != nullptr, \"IsSubplotsHovered() needs to be called between BeginSubplots() and EndSubplots()!\");\n    return gp.CurrentSubplot->FrameHovered;\n}\n\nbool IsPlotSelected() {\n    ImPlotContext& gp = *GImPlot;\n    IM_ASSERT_USER_ERROR(gp.CurrentPlot != nullptr, \"IsPlotSelected() needs to be called between BeginPlot() and EndPlot()!\");\n    SetupLock();\n    return gp.CurrentPlot->Selected;\n}\n\nImPlotRect GetPlotSelection(ImAxis x_idx, ImAxis y_idx) {\n    ImPlotContext& gp = *GImPlot;\n    IM_ASSERT_USER_ERROR(gp.CurrentPlot != nullptr, \"GetPlotSelection() needs to be called between BeginPlot() and EndPlot()!\");\n    SetupLock();\n    ImPlotPlot& plot = *gp.CurrentPlot;\n    if (!plot.Selected)\n        return ImPlotRect(0,0,0,0);\n    ImPlotPoint p1 = PixelsToPlot(plot.SelectRect.Min + plot.PlotRect.Min, x_idx, y_idx);\n    ImPlotPoint p2 = PixelsToPlot(plot.SelectRect.Max + plot.PlotRect.Min, x_idx, y_idx);\n    ImPlotRect result;\n    result.X.Min = ImMin(p1.x, p2.x);\n    result.X.Max = ImMax(p1.x, p2.x);\n    result.Y.Min = ImMin(p1.y, p2.y);\n    result.Y.Max = ImMax(p1.y, p2.y);\n    return result;\n}\n\nvoid CancelPlotSelection() {\n    ImPlotContext& gp = *GImPlot;\n    IM_ASSERT_USER_ERROR(gp.CurrentPlot != nullptr, \"CancelPlotSelection() needs to be called between BeginPlot() and EndPlot()!\");\n    SetupLock();\n    ImPlotPlot& plot = *gp.CurrentPlot;\n    if (plot.Selected)\n        plot.Selected = plot.Selecting = false;\n}\n\nvoid HideNextItem(bool hidden, ImPlotCond cond) {\n    ImPlotContext& gp = *GImPlot;\n    gp.NextItemData.HasHidden  = true;\n    gp.NextItemData.Hidden     = hidden;\n    gp.NextItemData.HiddenCond = cond;\n}\n\n//-----------------------------------------------------------------------------\n// [SECTION] Plot Tools\n//-----------------------------------------------------------------------------\n\nvoid Annotation(double x, double y, const ImVec4& col, const ImVec2& offset, bool clamp, bool round) {\n    ImPlotContext& gp = *GImPlot;\n    IM_ASSERT_USER_ERROR(gp.CurrentPlot != nullptr, \"Annotation() needs to be called between BeginPlot() and EndPlot()!\");\n    SetupLock();\n    char x_buff[IMPLOT_LABEL_MAX_SIZE];\n    char y_buff[IMPLOT_LABEL_MAX_SIZE];\n    ImPlotAxis& x_axis = gp.CurrentPlot->Axes[gp.CurrentPlot->CurrentX];\n    ImPlotAxis& y_axis = gp.CurrentPlot->Axes[gp.CurrentPlot->CurrentY];\n    LabelAxisValue(x_axis, x, x_buff, sizeof(x_buff), round);\n    LabelAxisValue(y_axis, y, y_buff, sizeof(y_buff), round);\n    Annotation(x,y,col,offset,clamp,\"%s, %s\",x_buff,y_buff);\n}\n\nvoid AnnotationV(double x, double y, const ImVec4& col, const ImVec2& offset, bool clamp, const char* fmt, va_list args) {\n    ImPlotContext& gp = *GImPlot;\n    IM_ASSERT_USER_ERROR(gp.CurrentPlot != nullptr, \"Annotation() needs to be called between BeginPlot() and EndPlot()!\");\n    SetupLock();\n    ImVec2 pos = PlotToPixels(x,y,IMPLOT_AUTO,IMPLOT_AUTO);\n    ImU32  bg  = ImGui::GetColorU32(col);\n    ImU32  fg  = col.w == 0 ? GetStyleColorU32(ImPlotCol_InlayText) : CalcTextColor(col);\n    gp.Annotations.AppendV(pos, offset, bg, fg, clamp, fmt, args);\n}\n\nvoid Annotation(double x, double y, const ImVec4& col, const ImVec2& offset, bool clamp, const char* fmt, ...) {\n    va_list args;\n    va_start(args, fmt);\n    AnnotationV(x,y,col,offset,clamp,fmt,args);\n    va_end(args);\n}\n\nvoid TagV(ImAxis axis, double v, const ImVec4& col, const char* fmt, va_list args) {\n    ImPlotContext& gp = *GImPlot;\n    SetupLock();\n    ImU32 bg = ImGui::GetColorU32(col);\n    ImU32 fg = col.w == 0 ? GetStyleColorU32(ImPlotCol_AxisText) : CalcTextColor(col);\n    gp.Tags.AppendV(axis,v,bg,fg,fmt,args);\n}\n\nvoid Tag(ImAxis axis, double v, const ImVec4& col, const char* fmt, ...) {\n    va_list args;\n    va_start(args, fmt);\n    TagV(axis,v,col,fmt,args);\n    va_end(args);\n}\n\nvoid Tag(ImAxis axis, double v, const ImVec4& color, bool round) {\n    ImPlotContext& gp = *GImPlot;\n    SetupLock();\n    char buff[IMPLOT_LABEL_MAX_SIZE];\n    ImPlotAxis& ax = gp.CurrentPlot->Axes[axis];\n    LabelAxisValue(ax, v, buff, sizeof(buff), round);\n    Tag(axis,v,color,\"%s\",buff);\n}\n\nIMPLOT_API void TagX(double x, const ImVec4& color, bool round) {\n    ImPlotContext& gp = *GImPlot;\n    IM_ASSERT_USER_ERROR(gp.CurrentPlot != nullptr, \"TagX() needs to be called between BeginPlot() and EndPlot()!\");\n    Tag(gp.CurrentPlot->CurrentX, x, color, round);\n}\n\nIMPLOT_API void TagX(double x, const ImVec4& color, const char* fmt, ...) {\n    ImPlotContext& gp = *GImPlot;\n    IM_ASSERT_USER_ERROR(gp.CurrentPlot != nullptr, \"TagX() needs to be called between BeginPlot() and EndPlot()!\");\n    va_list args;\n    va_start(args, fmt);\n    TagV(gp.CurrentPlot->CurrentX,x,color,fmt,args);\n    va_end(args);\n}\n\nIMPLOT_API void TagXV(double x, const ImVec4& color, const char* fmt, va_list args) {\n    ImPlotContext& gp = *GImPlot;\n    IM_ASSERT_USER_ERROR(gp.CurrentPlot != nullptr, \"TagX() needs to be called between BeginPlot() and EndPlot()!\");\n    TagV(gp.CurrentPlot->CurrentX, x, color, fmt, args);\n}\n\nIMPLOT_API void TagY(double y, const ImVec4& color, bool round) {\n    ImPlotContext& gp = *GImPlot;\n    IM_ASSERT_USER_ERROR(gp.CurrentPlot != nullptr, \"TagY() needs to be called between BeginPlot() and EndPlot()!\");\n    Tag(gp.CurrentPlot->CurrentY, y, color, round);\n}\n\nIMPLOT_API void TagY(double y, const ImVec4& color, const char* fmt, ...) {\n    ImPlotContext& gp = *GImPlot;\n    IM_ASSERT_USER_ERROR(gp.CurrentPlot != nullptr, \"TagY() needs to be called between BeginPlot() and EndPlot()!\");\n    va_list args;\n    va_start(args, fmt);\n    TagV(gp.CurrentPlot->CurrentY,y,color,fmt,args);\n    va_end(args);\n}\n\nIMPLOT_API void TagYV(double y, const ImVec4& color, const char* fmt, va_list args) {\n    ImPlotContext& gp = *GImPlot;\n    IM_ASSERT_USER_ERROR(gp.CurrentPlot != nullptr, \"TagY() needs to be called between BeginPlot() and EndPlot()!\");\n    TagV(gp.CurrentPlot->CurrentY, y, color, fmt, args);\n}\n\nstatic const float DRAG_GRAB_HALF_SIZE = 4.0f;\n\nbool DragPoint(int n_id, double* x, double* y, const ImVec4& col, float radius, ImPlotDragToolFlags flags) {\n    ImGui::PushID(\"#IMPLOT_DRAG_POINT\");\n    IM_ASSERT_USER_ERROR(GImPlot->CurrentPlot != nullptr, \"DragPoint() needs to be called between BeginPlot() and EndPlot()!\");\n    SetupLock();\n\n    if (!ImHasFlag(flags,ImPlotDragToolFlags_NoFit) && FitThisFrame()) {\n        FitPoint(ImPlotPoint(*x,*y));\n    }\n\n    const bool input = !ImHasFlag(flags, ImPlotDragToolFlags_NoInputs);\n    const bool show_curs = !ImHasFlag(flags, ImPlotDragToolFlags_NoCursors);\n    const bool no_delay = !ImHasFlag(flags, ImPlotDragToolFlags_Delayed);\n    const float grab_half_size = ImMax(DRAG_GRAB_HALF_SIZE, radius);\n    const ImVec4 color = IsColorAuto(col) ? ImGui::GetStyleColorVec4(ImGuiCol_Text) : col;\n    const ImU32 col32 = ImGui::ColorConvertFloat4ToU32(color);\n\n    ImVec2 pos = PlotToPixels(*x,*y,IMPLOT_AUTO,IMPLOT_AUTO);\n    const ImGuiID id = ImGui::GetCurrentWindow()->GetID(n_id);\n    ImRect rect(pos.x-grab_half_size,pos.y-grab_half_size,pos.x+grab_half_size,pos.y+grab_half_size);\n    bool hovered = false, held = false;\n\n    ImGui::KeepAliveID(id);\n    if (input)\n        ImGui::ButtonBehavior(rect,id,&hovered,&held);\n\n    bool dragging = false;\n    if (held && ImGui::IsMouseDragging(0)) {\n        *x = ImPlot::GetPlotMousePos(IMPLOT_AUTO,IMPLOT_AUTO).x;\n        *y = ImPlot::GetPlotMousePos(IMPLOT_AUTO,IMPLOT_AUTO).y;\n        dragging = true;\n    }\n\n    PushPlotClipRect();\n    ImDrawList& DrawList = *GetPlotDrawList();\n    if ((hovered || held) && show_curs)\n        ImGui::SetMouseCursor(ImGuiMouseCursor_Hand);\n    if (dragging && no_delay)\n        pos = PlotToPixels(*x,*y,IMPLOT_AUTO,IMPLOT_AUTO);\n    DrawList.AddCircleFilled(pos, radius, col32);\n    PopPlotClipRect();\n\n    ImGui::PopID();\n    return dragging;\n}\n\nbool DragLineX(int n_id, double* value, const ImVec4& col, float thickness, ImPlotDragToolFlags flags) {\n    // ImGui::PushID(\"#IMPLOT_DRAG_LINE_X\");\n    ImPlotContext& gp = *GImPlot;\n    IM_ASSERT_USER_ERROR(gp.CurrentPlot != nullptr, \"DragLineX() needs to be called between BeginPlot() and EndPlot()!\");\n    SetupLock();\n\n    if (!ImHasFlag(flags,ImPlotDragToolFlags_NoFit) && FitThisFrame()) {\n        FitPointX(*value);\n    }\n\n    const bool input = !ImHasFlag(flags, ImPlotDragToolFlags_NoInputs);\n    const bool show_curs = !ImHasFlag(flags, ImPlotDragToolFlags_NoCursors);\n    const bool no_delay = !ImHasFlag(flags, ImPlotDragToolFlags_Delayed);\n    const float grab_half_size = ImMax(DRAG_GRAB_HALF_SIZE, thickness/2);\n    float yt = gp.CurrentPlot->PlotRect.Min.y;\n    float yb = gp.CurrentPlot->PlotRect.Max.y;\n    float x  = IM_ROUND(PlotToPixels(*value,0,IMPLOT_AUTO,IMPLOT_AUTO).x);\n    const ImGuiID id = ImGui::GetCurrentWindow()->GetID(n_id);\n    ImRect rect(x-grab_half_size,yt,x+grab_half_size,yb);\n    bool hovered = false, held = false;\n\n    ImGui::KeepAliveID(id);\n    if (input)\n        ImGui::ButtonBehavior(rect,id,&hovered,&held);\n\n    if ((hovered || held) && show_curs)\n        ImGui::SetMouseCursor(ImGuiMouseCursor_ResizeEW);\n\n    float len = gp.Style.MajorTickLen.x;\n    ImVec4 color = IsColorAuto(col) ? ImGui::GetStyleColorVec4(ImGuiCol_Text) : col;\n    ImU32 col32 = ImGui::ColorConvertFloat4ToU32(color);\n\n    bool dragging = false;\n    if (held && ImGui::IsMouseDragging(0)) {\n        *value = ImPlot::GetPlotMousePos(IMPLOT_AUTO,IMPLOT_AUTO).x;\n        dragging = true;\n    }\n\n    PushPlotClipRect();\n    ImDrawList& DrawList = *GetPlotDrawList();\n    if (dragging && no_delay)\n        x  = IM_ROUND(PlotToPixels(*value,0,IMPLOT_AUTO,IMPLOT_AUTO).x);\n    DrawList.AddLine(ImVec2(x,yt), ImVec2(x,yb),     col32,   thickness);\n    DrawList.AddLine(ImVec2(x,yt), ImVec2(x,yt+len), col32, 3*thickness);\n    DrawList.AddLine(ImVec2(x,yb), ImVec2(x,yb-len), col32, 3*thickness);\n    PopPlotClipRect();\n\n    // ImGui::PopID();\n    return dragging;\n}\n\nbool DragLineY(int n_id, double* value, const ImVec4& col, float thickness, ImPlotDragToolFlags flags) {\n    ImGui::PushID(\"#IMPLOT_DRAG_LINE_Y\");\n    ImPlotContext& gp = *GImPlot;\n    IM_ASSERT_USER_ERROR(gp.CurrentPlot != nullptr, \"DragLineY() needs to be called between BeginPlot() and EndPlot()!\");\n    SetupLock();\n\n    if (!ImHasFlag(flags,ImPlotDragToolFlags_NoFit) && FitThisFrame()) {\n        FitPointY(*value);\n    }\n\n    const bool input = !ImHasFlag(flags, ImPlotDragToolFlags_NoInputs);\n    const bool show_curs = !ImHasFlag(flags, ImPlotDragToolFlags_NoCursors);\n    const bool no_delay = !ImHasFlag(flags, ImPlotDragToolFlags_Delayed);\n    const float grab_half_size = ImMax(DRAG_GRAB_HALF_SIZE, thickness/2);\n    float xl = gp.CurrentPlot->PlotRect.Min.x;\n    float xr = gp.CurrentPlot->PlotRect.Max.x;\n    float y  = IM_ROUND(PlotToPixels(0, *value,IMPLOT_AUTO,IMPLOT_AUTO).y);\n\n    const ImGuiID id = ImGui::GetCurrentWindow()->GetID(n_id);\n    ImRect rect(xl,y-grab_half_size,xr,y+grab_half_size);\n    bool hovered = false, held = false;\n\n    ImGui::KeepAliveID(id);\n    if (input)\n        ImGui::ButtonBehavior(rect,id,&hovered,&held);\n\n    if ((hovered || held) && show_curs)\n        ImGui::SetMouseCursor(ImGuiMouseCursor_ResizeNS);\n\n    float len = gp.Style.MajorTickLen.y;\n    ImVec4 color = IsColorAuto(col) ? ImGui::GetStyleColorVec4(ImGuiCol_Text) : col;\n    ImU32 col32 = ImGui::ColorConvertFloat4ToU32(color);\n\n    bool dragging = false;\n    if (held && ImGui::IsMouseDragging(0)) {\n        *value = ImPlot::GetPlotMousePos(IMPLOT_AUTO,IMPLOT_AUTO).y;\n        dragging = true;\n    }\n\n    PushPlotClipRect();\n    ImDrawList& DrawList = *GetPlotDrawList();\n    if (dragging && no_delay)\n        y  = IM_ROUND(PlotToPixels(0, *value,IMPLOT_AUTO,IMPLOT_AUTO).y);\n    DrawList.AddLine(ImVec2(xl,y), ImVec2(xr,y),     col32,   thickness);\n    DrawList.AddLine(ImVec2(xl,y), ImVec2(xl+len,y), col32, 3*thickness);\n    DrawList.AddLine(ImVec2(xr,y), ImVec2(xr-len,y), col32, 3*thickness);\n    PopPlotClipRect();\n\n    ImGui::PopID();\n    return dragging;\n}\n\nbool DragRect(int n_id, double* x_min, double* y_min, double* x_max, double* y_max, const ImVec4& col, ImPlotDragToolFlags flags) {\n    ImGui::PushID(\"#IMPLOT_DRAG_RECT\");\n    IM_ASSERT_USER_ERROR(GImPlot->CurrentPlot != nullptr, \"DragRect() needs to be called between BeginPlot() and EndPlot()!\");\n    SetupLock();\n\n    if (!ImHasFlag(flags,ImPlotDragToolFlags_NoFit) && FitThisFrame()) {\n        FitPoint(ImPlotPoint(*x_min,*y_min));\n        FitPoint(ImPlotPoint(*x_max,*y_max));\n    }\n\n    const bool input = !ImHasFlag(flags, ImPlotDragToolFlags_NoInputs);\n    const bool show_curs = !ImHasFlag(flags, ImPlotDragToolFlags_NoCursors);\n    const bool no_delay = !ImHasFlag(flags, ImPlotDragToolFlags_Delayed);\n    bool    h[] = {true,false,true,false};\n    double* x[] = {x_min,x_max,x_max,x_min};\n    double* y[] = {y_min,y_min,y_max,y_max};\n    ImVec2 p[4];\n    for (int i = 0; i < 4; ++i)\n        p[i] = PlotToPixels(*x[i],*y[i],IMPLOT_AUTO,IMPLOT_AUTO);\n    ImVec2 pc = PlotToPixels((*x_min+*x_max)/2,(*y_min+*y_max)/2,IMPLOT_AUTO,IMPLOT_AUTO);\n    ImRect rect(ImMin(p[0],p[2]),ImMax(p[0],p[2]));\n    ImRect rect_grab = rect; rect_grab.Expand(DRAG_GRAB_HALF_SIZE);\n\n    ImGuiMouseCursor cur[4];\n    if (show_curs) {\n        cur[0] = (rect.Min.x == p[0].x && rect.Min.y == p[0].y) || (rect.Max.x == p[0].x && rect.Max.y == p[0].y) ? ImGuiMouseCursor_ResizeNWSE : ImGuiMouseCursor_ResizeNESW;\n        cur[1] = cur[0] == ImGuiMouseCursor_ResizeNWSE ? ImGuiMouseCursor_ResizeNESW : ImGuiMouseCursor_ResizeNWSE;\n        cur[2] = cur[1] == ImGuiMouseCursor_ResizeNWSE ? ImGuiMouseCursor_ResizeNESW : ImGuiMouseCursor_ResizeNWSE;\n        cur[3] = cur[2] == ImGuiMouseCursor_ResizeNWSE ? ImGuiMouseCursor_ResizeNESW : ImGuiMouseCursor_ResizeNWSE;\n    }\n\n    ImVec4 color = IsColorAuto(col) ? ImGui::GetStyleColorVec4(ImGuiCol_Text) : col;\n    ImU32 col32 = ImGui::ColorConvertFloat4ToU32(color);\n    color.w *= 0.25f;\n    ImU32 col32_a = ImGui::ColorConvertFloat4ToU32(color);\n    const ImGuiID id = ImGui::GetCurrentWindow()->GetID(n_id);\n\n    bool dragging = false;\n    bool hovered = false, held = false;\n    ImRect b_rect(pc.x-DRAG_GRAB_HALF_SIZE,pc.y-DRAG_GRAB_HALF_SIZE,pc.x+DRAG_GRAB_HALF_SIZE,pc.y+DRAG_GRAB_HALF_SIZE);\n\n    ImGui::KeepAliveID(id);\n    if (input)\n        ImGui::ButtonBehavior(b_rect,id,&hovered,&held);\n\n    if ((hovered || held) && show_curs)\n        ImGui::SetMouseCursor(ImGuiMouseCursor_ResizeAll);\n    if (held && ImGui::IsMouseDragging(0)) {\n        for (int i = 0; i < 4; ++i) {\n            ImPlotPoint pp = PixelsToPlot(p[i] + ImGui::GetIO().MouseDelta,IMPLOT_AUTO,IMPLOT_AUTO);\n            *y[i] = pp.y;\n            *x[i] = pp.x;\n        }\n        dragging = true;\n    }\n\n    for (int i = 0; i < 4; ++i) {\n        // points\n        b_rect = ImRect(p[i].x-DRAG_GRAB_HALF_SIZE,p[i].y-DRAG_GRAB_HALF_SIZE,p[i].x+DRAG_GRAB_HALF_SIZE,p[i].y+DRAG_GRAB_HALF_SIZE);\n        ImGuiID p_id = id + i + 1;\n        ImGui::KeepAliveID(p_id);\n        if (input)\n            ImGui::ButtonBehavior(b_rect,p_id,&hovered,&held);\n        if ((hovered || held) && show_curs)\n            ImGui::SetMouseCursor(cur[i]);\n\n        if (held && ImGui::IsMouseDragging(0)) {\n            *x[i] = ImPlot::GetPlotMousePos(IMPLOT_AUTO,IMPLOT_AUTO).x;\n            *y[i] = ImPlot::GetPlotMousePos(IMPLOT_AUTO,IMPLOT_AUTO).y;\n            dragging = true;\n        }\n\n        // edges\n        ImVec2 e_min = ImMin(p[i],p[(i+1)%4]);\n        ImVec2 e_max = ImMax(p[i],p[(i+1)%4]);\n        b_rect = h[i] ? ImRect(e_min.x + DRAG_GRAB_HALF_SIZE, e_min.y - DRAG_GRAB_HALF_SIZE, e_max.x - DRAG_GRAB_HALF_SIZE, e_max.y + DRAG_GRAB_HALF_SIZE)\n                    : ImRect(e_min.x - DRAG_GRAB_HALF_SIZE, e_min.y + DRAG_GRAB_HALF_SIZE, e_max.x + DRAG_GRAB_HALF_SIZE, e_max.y - DRAG_GRAB_HALF_SIZE);\n        ImGuiID e_id = id + i + 5;\n        ImGui::KeepAliveID(e_id);\n        if (input)\n            ImGui::ButtonBehavior(b_rect,e_id,&hovered,&held);\n        if ((hovered || held) && show_curs)\n            h[i] ? ImGui::SetMouseCursor(ImGuiMouseCursor_ResizeNS) : ImGui::SetMouseCursor(ImGuiMouseCursor_ResizeEW);\n        if (held && ImGui::IsMouseDragging(0)) {\n            if (h[i])\n                *y[i] = ImPlot::GetPlotMousePos(IMPLOT_AUTO,IMPLOT_AUTO).y;\n            else\n                *x[i] = ImPlot::GetPlotMousePos(IMPLOT_AUTO,IMPLOT_AUTO).x;\n            dragging = true;\n        }\n        if (hovered && ImGui::IsMouseDoubleClicked(0))\n        {\n            ImPlotRect b = GetPlotLimits(IMPLOT_AUTO,IMPLOT_AUTO);\n            if (h[i])\n                *y[i] = ((y[i] == y_min && *y_min < *y_max) || (y[i] == y_max && *y_max < *y_min)) ? b.Y.Min : b.Y.Max;\n            else\n                *x[i] = ((x[i] == x_min && *x_min < *x_max) || (x[i] == x_max && *x_max < *x_min)) ? b.X.Min : b.X.Max;\n            dragging = true;\n        }\n    }\n\n\n    PushPlotClipRect();\n    ImDrawList& DrawList = *GetPlotDrawList();\n    if (dragging && no_delay) {\n        for (int i = 0; i < 4; ++i)\n            p[i] = PlotToPixels(*x[i],*y[i],IMPLOT_AUTO,IMPLOT_AUTO);\n        pc = PlotToPixels((*x_min+*x_max)/2,(*y_min+*y_max)/2,IMPLOT_AUTO,IMPLOT_AUTO);\n        rect = ImRect(ImMin(p[0],p[2]),ImMax(p[0],p[2]));\n    }\n    DrawList.AddRectFilled(rect.Min, rect.Max, col32_a);\n    DrawList.AddRect(rect.Min, rect.Max, col32);\n    if (input && (dragging || rect_grab.Contains(ImGui::GetMousePos()))) {\n        DrawList.AddCircleFilled(pc,DRAG_GRAB_HALF_SIZE,col32);\n        for (int i = 0; i < 4; ++i)\n            DrawList.AddCircleFilled(p[i],DRAG_GRAB_HALF_SIZE,col32);\n    }\n    PopPlotClipRect();\n    ImGui::PopID();\n    return dragging;\n}\n\nbool DragRect(int id, ImPlotRect* bounds, const ImVec4& col, ImPlotDragToolFlags flags) {\n    return DragRect(id, &bounds->X.Min, &bounds->Y.Min,&bounds->X.Max, &bounds->Y.Max, col, flags);\n}\n\n//-----------------------------------------------------------------------------\n// [SECTION] Legend Utils and Tools\n//-----------------------------------------------------------------------------\n\nbool IsLegendEntryHovered(const char* label_id) {\n    ImPlotContext& gp = *GImPlot;\n    IM_ASSERT_USER_ERROR(gp.CurrentItems != nullptr, \"IsPlotItemHighlight() needs to be called within an itemized context!\");\n    SetupLock();\n    ImGuiID id = ImGui::GetIDWithSeed(label_id, nullptr, gp.CurrentItems->ID);\n    ImPlotItem* item = gp.CurrentItems->GetItem(id);\n    return item && item->LegendHovered;\n}\n\nbool BeginLegendPopup(const char* label_id, ImGuiMouseButton mouse_button) {\n    ImPlotContext& gp = *GImPlot;\n    IM_ASSERT_USER_ERROR(gp.CurrentItems != nullptr, \"BeginLegendPopup() needs to be called within an itemized context!\");\n    SetupLock();\n    ImGuiWindow* window = GImGui->CurrentWindow;\n    if (window->SkipItems)\n        return false;\n    ImGuiID id = ImGui::GetIDWithSeed(label_id, nullptr, gp.CurrentItems->ID);\n    if (ImGui::IsMouseReleased(mouse_button)) {\n        ImPlotItem* item = gp.CurrentItems->GetItem(id);\n        if (item && item->LegendHovered)\n            ImGui::OpenPopupEx(id);\n    }\n    return ImGui::BeginPopupEx(id, ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoSavedSettings);\n}\n\nvoid EndLegendPopup() {\n    SetupLock();\n    ImGui::EndPopup();\n}\n\nvoid ShowAltLegend(const char* title_id, bool vertical, const ImVec2 size, bool interactable) {\n    ImPlotContext& gp    = *GImPlot;\n    ImGuiContext &G      = *GImGui;\n    ImGuiWindow * Window = G.CurrentWindow;\n    if (Window->SkipItems)\n        return;\n    ImDrawList &DrawList = *Window->DrawList;\n    ImPlotPlot* plot = GetPlot(title_id);\n    ImVec2 legend_size;\n    ImVec2 default_size = gp.Style.LegendPadding * 2;\n    if (plot != nullptr) {\n        legend_size  = CalcLegendSize(plot->Items, gp.Style.LegendInnerPadding, gp.Style.LegendSpacing, vertical);\n        default_size = legend_size + gp.Style.LegendPadding * 2;\n    }\n    ImVec2 frame_size = ImGui::CalcItemSize(size, default_size.x, default_size.y);\n    ImRect bb_frame = ImRect(Window->DC.CursorPos, Window->DC.CursorPos + frame_size);\n    ImGui::ItemSize(bb_frame);\n    if (!ImGui::ItemAdd(bb_frame, 0, &bb_frame))\n        return;\n    ImGui::RenderFrame(bb_frame.Min, bb_frame.Max, GetStyleColorU32(ImPlotCol_FrameBg), true, G.Style.FrameRounding);\n    DrawList.PushClipRect(bb_frame.Min, bb_frame.Max, true);\n    if (plot != nullptr) {\n        const ImVec2 legend_pos  = GetLocationPos(bb_frame, legend_size, 0, gp.Style.LegendPadding);\n        const ImRect legend_bb(legend_pos, legend_pos + legend_size);\n        interactable = interactable && bb_frame.Contains(ImGui::GetIO().MousePos);\n        // render legend box\n        ImU32  col_bg      = GetStyleColorU32(ImPlotCol_LegendBg);\n        ImU32  col_bd      = GetStyleColorU32(ImPlotCol_LegendBorder);\n        DrawList.AddRectFilled(legend_bb.Min, legend_bb.Max, col_bg);\n        DrawList.AddRect(legend_bb.Min, legend_bb.Max, col_bd);\n        // render entries\n        ShowLegendEntries(plot->Items, legend_bb, interactable, gp.Style.LegendInnerPadding, gp.Style.LegendSpacing, vertical, DrawList);\n    }\n    DrawList.PopClipRect();\n}\n\n//-----------------------------------------------------------------------------\n// [SECTION] Drag and Drop Utils\n//-----------------------------------------------------------------------------\n\nbool BeginDragDropTargetPlot() {\n    SetupLock();\n    ImPlotContext& gp = *GImPlot;\n    ImRect rect = gp.CurrentPlot->PlotRect;\n    return ImGui::BeginDragDropTargetCustom(rect, gp.CurrentPlot->ID);\n}\n\nbool BeginDragDropTargetAxis(ImAxis axis) {\n    SetupLock();\n    ImPlotPlot& plot = *GImPlot->CurrentPlot;\n    ImPlotAxis& ax = plot.Axes[axis];\n    ImRect rect = ax.HoverRect;\n    rect.Expand(-3.5f);\n    return ImGui::BeginDragDropTargetCustom(rect, ax.ID);\n}\n\nbool BeginDragDropTargetLegend() {\n    SetupLock();\n    ImPlotItemGroup& items = *GImPlot->CurrentItems;\n    ImRect rect = items.Legend.Rect;\n    return ImGui::BeginDragDropTargetCustom(rect, items.ID);\n}\n\nvoid EndDragDropTarget() {\n    SetupLock();\n\tImGui::EndDragDropTarget();\n}\n\nbool BeginDragDropSourcePlot(ImGuiDragDropFlags flags) {\n    SetupLock();\n    ImPlotContext& gp = *GImPlot;\n    ImPlotPlot* plot = gp.CurrentPlot;\n    if (GImGui->IO.KeyMods == gp.InputMap.OverrideMod || GImGui->DragDropPayload.SourceId == plot->ID)\n        return ImGui::ItemAdd(plot->PlotRect, plot->ID) && ImGui::BeginDragDropSource(flags);\n    return false;\n}\n\nbool BeginDragDropSourceAxis(ImAxis idx, ImGuiDragDropFlags flags) {\n    SetupLock();\n    ImPlotContext& gp = *GImPlot;\n    ImPlotAxis& axis = gp.CurrentPlot->Axes[idx];\n    if (GImGui->IO.KeyMods == gp.InputMap.OverrideMod || GImGui->DragDropPayload.SourceId == axis.ID)\n        return ImGui::ItemAdd(axis.HoverRect, axis.ID) && ImGui::BeginDragDropSource(flags);\n    return false;\n}\n\nbool BeginDragDropSourceItem(const char* label_id, ImGuiDragDropFlags flags) {\n    SetupLock();\n    ImPlotContext& gp = *GImPlot;\n    IM_ASSERT_USER_ERROR(gp.CurrentItems != nullptr, \"BeginDragDropSourceItem() needs to be called within an itemized context!\");\n    ImGuiID item_id = ImGui::GetIDWithSeed(label_id, nullptr, gp.CurrentItems->ID);\n    ImPlotItem* item = gp.CurrentItems->GetItem(item_id);\n    if (item != nullptr) {\n        return ImGui::ItemAdd(item->LegendHoverRect, item->ID) && ImGui::BeginDragDropSource(flags);\n    }\n    return false;\n}\n\nvoid EndDragDropSource() {\n    SetupLock();\n    ImGui::EndDragDropSource();\n}\n\n//-----------------------------------------------------------------------------\n// [SECTION] Aligned Plots\n//-----------------------------------------------------------------------------\n\nbool BeginAlignedPlots(const char* group_id, bool vertical) {\n    IM_ASSERT_USER_ERROR(GImPlot != nullptr, \"No current context. Did you call ImPlot::CreateContext() or ImPlot::SetCurrentContext()?\");\n    ImPlotContext& gp = *GImPlot;\n    IM_ASSERT_USER_ERROR(gp.CurrentAlignmentH == nullptr && gp.CurrentAlignmentV == nullptr, \"Mismatched BeginAlignedPlots()/EndAlignedPlots()!\");\n    ImGuiContext &G = *GImGui;\n    ImGuiWindow * Window = G.CurrentWindow;\n    if (Window->SkipItems)\n        return false;\n    const ImGuiID ID = Window->GetID(group_id);\n    ImPlotAlignmentData* alignment = gp.AlignmentData.GetOrAddByKey(ID);\n    if (vertical)\n        gp.CurrentAlignmentV = alignment;\n    else\n        gp.CurrentAlignmentH = alignment;\n    if (alignment->Vertical != vertical)\n        alignment->Reset();\n    alignment->Vertical = vertical;\n    alignment->Begin();\n    return true;\n}\n\nvoid EndAlignedPlots() {\n    IM_ASSERT_USER_ERROR(GImPlot != nullptr, \"No current context. Did you call ImPlot::CreateContext() or ImPlot::SetCurrentContext()?\");\n    ImPlotContext& gp = *GImPlot;\n    IM_ASSERT_USER_ERROR(gp.CurrentAlignmentH != nullptr || gp.CurrentAlignmentV != nullptr, \"Mismatched BeginAlignedPlots()/EndAlignedPlots()!\");\n    ImPlotAlignmentData* alignment = gp.CurrentAlignmentH != nullptr ? gp.CurrentAlignmentH : (gp.CurrentAlignmentV != nullptr ? gp.CurrentAlignmentV : nullptr);\n    if (alignment)\n        alignment->End();\n    ResetCtxForNextAlignedPlots(GImPlot);\n}\n\n//-----------------------------------------------------------------------------\n// [SECTION] Plot and Item Styling\n//-----------------------------------------------------------------------------\n\nImPlotStyle& GetStyle() {\n    IM_ASSERT_USER_ERROR(GImPlot != nullptr, \"No current context. Did you call ImPlot::CreateContext() or ImPlot::SetCurrentContext()?\");\n    ImPlotContext& gp = *GImPlot;\n    return gp.Style;\n}\n\nvoid PushStyleColor(ImPlotCol idx, ImU32 col) {\n    ImPlotContext& gp = *GImPlot;\n    ImGuiColorMod backup;\n    backup.Col = (ImGuiCol)idx;\n    backup.BackupValue = gp.Style.Colors[idx];\n    gp.ColorModifiers.push_back(backup);\n    gp.Style.Colors[idx] = ImGui::ColorConvertU32ToFloat4(col);\n}\n\nvoid PushStyleColor(ImPlotCol idx, const ImVec4& col) {\n    ImPlotContext& gp = *GImPlot;\n    ImGuiColorMod backup;\n    backup.Col = (ImGuiCol)idx;\n    backup.BackupValue = gp.Style.Colors[idx];\n    gp.ColorModifiers.push_back(backup);\n    gp.Style.Colors[idx] = col;\n}\n\nvoid PopStyleColor(int count) {\n    ImPlotContext& gp = *GImPlot;\n    IM_ASSERT_USER_ERROR(count <= gp.ColorModifiers.Size, \"You can't pop more modifiers than have been pushed!\");\n    while (count > 0)\n    {\n        ImGuiColorMod& backup = gp.ColorModifiers.back();\n        gp.Style.Colors[backup.Col] = backup.BackupValue;\n        gp.ColorModifiers.pop_back();\n        count--;\n    }\n}\n\nvoid PushStyleVar(ImPlotStyleVar idx, float val) {\n    ImPlotContext& gp = *GImPlot;\n    const ImPlotStyleVarInfo* var_info = GetPlotStyleVarInfo(idx);\n    if (var_info->Type == ImGuiDataType_Float && var_info->Count == 1) {\n        float* pvar = (float*)var_info->GetVarPtr(&gp.Style);\n        gp.StyleModifiers.push_back(ImGuiStyleMod((ImGuiStyleVar)idx, *pvar));\n        *pvar = val;\n        return;\n    }\n    IM_ASSERT(0 && \"Called PushStyleVar() float variant but variable is not a float!\");\n}\n\nvoid PushStyleVar(ImPlotStyleVar idx, int val) {\n    ImPlotContext& gp = *GImPlot;\n    const ImPlotStyleVarInfo* var_info = GetPlotStyleVarInfo(idx);\n    if (var_info->Type == ImGuiDataType_S32 && var_info->Count == 1) {\n        int* pvar = (int*)var_info->GetVarPtr(&gp.Style);\n        gp.StyleModifiers.push_back(ImGuiStyleMod((ImGuiStyleVar)idx, *pvar));\n        *pvar = val;\n        return;\n    }\n    else if (var_info->Type == ImGuiDataType_Float && var_info->Count == 1) {\n        float* pvar = (float*)var_info->GetVarPtr(&gp.Style);\n        gp.StyleModifiers.push_back(ImGuiStyleMod((ImGuiStyleVar)idx, *pvar));\n        *pvar = (float)val;\n        return;\n    }\n    IM_ASSERT(0 && \"Called PushStyleVar() int variant but variable is not a int!\");\n}\n\nvoid PushStyleVar(ImPlotStyleVar idx, const ImVec2& val)\n{\n    ImPlotContext& gp = *GImPlot;\n    const ImPlotStyleVarInfo* var_info = GetPlotStyleVarInfo(idx);\n    if (var_info->Type == ImGuiDataType_Float && var_info->Count == 2)\n    {\n        ImVec2* pvar = (ImVec2*)var_info->GetVarPtr(&gp.Style);\n        gp.StyleModifiers.push_back(ImGuiStyleMod((ImGuiStyleVar)idx, *pvar));\n        *pvar = val;\n        return;\n    }\n    IM_ASSERT(0 && \"Called PushStyleVar() ImVec2 variant but variable is not a ImVec2!\");\n}\n\nvoid PopStyleVar(int count) {\n    ImPlotContext& gp = *GImPlot;\n    IM_ASSERT_USER_ERROR(count <= gp.StyleModifiers.Size, \"You can't pop more modifiers than have been pushed!\");\n    while (count > 0) {\n        ImGuiStyleMod& backup = gp.StyleModifiers.back();\n        const ImPlotStyleVarInfo* info = GetPlotStyleVarInfo(backup.VarIdx);\n        void* data = info->GetVarPtr(&gp.Style);\n        if (info->Type == ImGuiDataType_Float && info->Count == 1) {\n            ((float*)data)[0] = backup.BackupFloat[0];\n        }\n        else if (info->Type == ImGuiDataType_Float && info->Count == 2) {\n             ((float*)data)[0] = backup.BackupFloat[0];\n             ((float*)data)[1] = backup.BackupFloat[1];\n        }\n        else if (info->Type == ImGuiDataType_S32 && info->Count == 1) {\n            ((int*)data)[0] = backup.BackupInt[0];\n        }\n        gp.StyleModifiers.pop_back();\n        count--;\n    }\n}\n\n//------------------------------------------------------------------------------\n// [Section] Colormaps\n//------------------------------------------------------------------------------\n\nImPlotColormap AddColormap(const char* name, const ImVec4* colormap, int size, bool qual) {\n    ImPlotContext& gp = *GImPlot;\n    IM_ASSERT_USER_ERROR(size > 1, \"The colormap size must be greater than 1!\");\n    IM_ASSERT_USER_ERROR(gp.ColormapData.GetIndex(name) == -1, \"The colormap name has already been used!\");\n    ImVector<ImU32> buffer;\n    buffer.resize(size);\n    for (int i = 0; i < size; ++i)\n        buffer[i] = ImGui::ColorConvertFloat4ToU32(colormap[i]);\n    return gp.ColormapData.Append(name, buffer.Data, size, qual);\n}\n\nImPlotColormap AddColormap(const char* name, const ImU32*  colormap, int size, bool qual) {\n    ImPlotContext& gp = *GImPlot;\n    IM_ASSERT_USER_ERROR(size > 1, \"The colormap size must be greater than 1!\");\n    IM_ASSERT_USER_ERROR(gp.ColormapData.GetIndex(name) == -1, \"The colormap name has already be used!\");\n    return gp.ColormapData.Append(name, colormap, size, qual);\n}\n\nint GetColormapCount() {\n    ImPlotContext& gp = *GImPlot;\n    return gp.ColormapData.Count;\n}\n\nconst char* GetColormapName(ImPlotColormap colormap) {\n    ImPlotContext& gp = *GImPlot;\n    return gp.ColormapData.GetName(colormap);\n}\n\nImPlotColormap GetColormapIndex(const char* name) {\n    ImPlotContext& gp = *GImPlot;\n    return gp.ColormapData.GetIndex(name);\n}\n\nvoid PushColormap(ImPlotColormap colormap) {\n    ImPlotContext& gp = *GImPlot;\n    IM_ASSERT_USER_ERROR(colormap >= 0 && colormap < gp.ColormapData.Count, \"The colormap index is invalid!\");\n    gp.ColormapModifiers.push_back(gp.Style.Colormap);\n    gp.Style.Colormap = colormap;\n}\n\nvoid PushColormap(const char* name) {\n    ImPlotContext& gp = *GImPlot;\n    ImPlotColormap idx = gp.ColormapData.GetIndex(name);\n    IM_ASSERT_USER_ERROR(idx != -1, \"The colormap name is invalid!\");\n    PushColormap(idx);\n}\n\nvoid PopColormap(int count) {\n    ImPlotContext& gp = *GImPlot;\n    IM_ASSERT_USER_ERROR(count <= gp.ColormapModifiers.Size, \"You can't pop more modifiers than have been pushed!\");\n    while (count > 0) {\n        const ImPlotColormap& backup = gp.ColormapModifiers.back();\n        gp.Style.Colormap     = backup;\n        gp.ColormapModifiers.pop_back();\n        count--;\n    }\n}\n\nImU32 NextColormapColorU32() {\n    ImPlotContext& gp = *GImPlot;\n    IM_ASSERT_USER_ERROR(gp.CurrentItems != nullptr, \"NextColormapColor() needs to be called between BeginPlot() and EndPlot()!\");\n    int idx = gp.CurrentItems->ColormapIdx % gp.ColormapData.GetKeyCount(gp.Style.Colormap);\n    ImU32 col  = gp.ColormapData.GetKeyColor(gp.Style.Colormap, idx);\n    gp.CurrentItems->ColormapIdx++;\n    return col;\n}\n\nImVec4 NextColormapColor() {\n    return ImGui::ColorConvertU32ToFloat4(NextColormapColorU32());\n}\n\nint GetColormapSize(ImPlotColormap cmap) {\n    ImPlotContext& gp = *GImPlot;\n    cmap = cmap == IMPLOT_AUTO ? gp.Style.Colormap : cmap;\n    IM_ASSERT_USER_ERROR(cmap >= 0 && cmap < gp.ColormapData.Count, \"Invalid colormap index!\");\n    return gp.ColormapData.GetKeyCount(cmap);\n}\n\nImU32 GetColormapColorU32(int idx, ImPlotColormap cmap) {\n    ImPlotContext& gp = *GImPlot;\n    cmap = cmap == IMPLOT_AUTO ? gp.Style.Colormap : cmap;\n    IM_ASSERT_USER_ERROR(cmap >= 0 && cmap < gp.ColormapData.Count, \"Invalid colormap index!\");\n    idx = idx % gp.ColormapData.GetKeyCount(cmap);\n    return gp.ColormapData.GetKeyColor(cmap, idx);\n}\n\nImVec4 GetColormapColor(int idx, ImPlotColormap cmap) {\n    return ImGui::ColorConvertU32ToFloat4(GetColormapColorU32(idx,cmap));\n}\n\nImU32  SampleColormapU32(float t, ImPlotColormap cmap) {\n    ImPlotContext& gp = *GImPlot;\n    cmap = cmap == IMPLOT_AUTO ? gp.Style.Colormap : cmap;\n    IM_ASSERT_USER_ERROR(cmap >= 0 && cmap < gp.ColormapData.Count, \"Invalid colormap index!\");\n    return gp.ColormapData.LerpTable(cmap, t);\n}\n\nImVec4 SampleColormap(float t, ImPlotColormap cmap) {\n    return ImGui::ColorConvertU32ToFloat4(SampleColormapU32(t,cmap));\n}\n\nvoid RenderColorBar(const ImU32* colors, int size, ImDrawList& DrawList, const ImRect& bounds, bool vert, bool reversed, bool continuous) {\n    const int n = continuous ? size - 1 : size;\n    ImU32 col1, col2;\n    if (vert) {\n        const float step = bounds.GetHeight() / n;\n        ImRect rect(bounds.Min.x, bounds.Min.y, bounds.Max.x, bounds.Min.y + step);\n        for (int i = 0; i < n; ++i) {\n            if (reversed) {\n                col1 = colors[size-i-1];\n                col2 = continuous ? colors[size-i-2] : col1;\n            }\n            else {\n                col1 = colors[i];\n                col2 = continuous ? colors[i+1] : col1;\n            }\n            DrawList.AddRectFilledMultiColor(rect.Min, rect.Max, col1, col1, col2, col2);\n            rect.TranslateY(step);\n        }\n    }\n    else {\n        const float step = bounds.GetWidth() / n;\n        ImRect rect(bounds.Min.x, bounds.Min.y, bounds.Min.x + step, bounds.Max.y);\n        for (int i = 0; i < n; ++i) {\n            if (reversed) {\n                col1 = colors[size-i-1];\n                col2 = continuous ? colors[size-i-2] : col1;\n            }\n            else {\n                col1 = colors[i];\n                col2 = continuous ? colors[i+1] : col1;\n            }\n            DrawList.AddRectFilledMultiColor(rect.Min, rect.Max, col1, col2, col2, col1);\n            rect.TranslateX(step);\n        }\n    }\n}\n\nvoid ColormapScale(const char* label, double scale_min, double scale_max, const ImVec2& size, const char* format, ImPlotColormapScaleFlags flags, ImPlotColormap cmap) {\n    ImGuiContext &G      = *GImGui;\n    ImGuiWindow * Window = G.CurrentWindow;\n    if (Window->SkipItems)\n        return;\n\n    const ImGuiID ID = Window->GetID(label);\n    ImVec2 label_size(0,0);\n    if (!ImHasFlag(flags, ImPlotColormapScaleFlags_NoLabel)) {\n        label_size = ImGui::CalcTextSize(label,nullptr,true);\n    }\n\n    ImPlotContext& gp = *GImPlot;\n    cmap = cmap == IMPLOT_AUTO ? gp.Style.Colormap : cmap;\n    IM_ASSERT_USER_ERROR(cmap >= 0 && cmap < gp.ColormapData.Count, \"Invalid colormap index!\");\n\n    ImVec2 frame_size  = ImGui::CalcItemSize(size, 0, gp.Style.PlotDefaultSize.y);\n    if (frame_size.y < gp.Style.PlotMinSize.y && size.y < 0.0f)\n        frame_size.y = gp.Style.PlotMinSize.y;\n\n    ImPlotRange range(ImMin(scale_min,scale_max), ImMax(scale_min,scale_max));\n    gp.CTicker.Reset();\n    Locator_Default(gp.CTicker, range, frame_size.y, true, Formatter_Default, (void*)format);\n\n    const bool rend_label = label_size.x > 0;\n    const float txt_off   = gp.Style.LabelPadding.x;\n    const float pad       = txt_off + gp.CTicker.MaxSize.x + (rend_label ? txt_off + label_size.y : 0);\n    float bar_w           = 20;\n    if (frame_size.x == 0)\n        frame_size.x = bar_w + pad + 2 * gp.Style.PlotPadding.x;\n    else {\n        bar_w = frame_size.x - (pad + 2 * gp.Style.PlotPadding.x);\n        if (bar_w < gp.Style.MajorTickLen.y)\n            bar_w = gp.Style.MajorTickLen.y;\n    }\n\n    ImDrawList &DrawList = *Window->DrawList;\n    ImRect bb_frame = ImRect(Window->DC.CursorPos, Window->DC.CursorPos + frame_size);\n    ImGui::ItemSize(bb_frame);\n    if (!ImGui::ItemAdd(bb_frame, ID, &bb_frame))\n        return;\n\n    ImGui::RenderFrame(bb_frame.Min, bb_frame.Max, GetStyleColorU32(ImPlotCol_FrameBg), true, G.Style.FrameRounding);\n\n    const bool opposite = ImHasFlag(flags, ImPlotColormapScaleFlags_Opposite);\n    const bool inverted = ImHasFlag(flags, ImPlotColormapScaleFlags_Invert);\n    const bool reversed = scale_min > scale_max;\n\n    float bb_grad_shift = opposite ? pad : 0;\n    ImRect bb_grad(bb_frame.Min + gp.Style.PlotPadding + ImVec2(bb_grad_shift, 0),\n                   bb_frame.Min + ImVec2(bar_w + gp.Style.PlotPadding.x + bb_grad_shift,\n                                         frame_size.y - gp.Style.PlotPadding.y));\n\n    ImGui::PushClipRect(bb_frame.Min, bb_frame.Max, true);\n    const ImU32 col_text = ImGui::GetColorU32(ImGuiCol_Text);\n\n    const bool invert_scale = inverted ? (reversed ? false : true) : (reversed ? true : false);\n    const float y_min = invert_scale ? bb_grad.Max.y : bb_grad.Min.y;\n    const float y_max = invert_scale ? bb_grad.Min.y : bb_grad.Max.y;\n\n    RenderColorBar(gp.ColormapData.GetKeys(cmap), gp.ColormapData.GetKeyCount(cmap), DrawList, bb_grad, true, !inverted, !gp.ColormapData.IsQual(cmap));\n    for (int i = 0; i < gp.CTicker.TickCount(); ++i) {\n        const double y_pos_plt = gp.CTicker.Ticks[i].PlotPos;\n        const float y_pos = ImRemap((float)y_pos_plt, (float)range.Max, (float)range.Min, y_min, y_max);\n        const float tick_width = gp.CTicker.Ticks[i].Major ? gp.Style.MajorTickLen.y : gp.Style.MinorTickLen.y;\n        const float tick_thick = gp.CTicker.Ticks[i].Major ? gp.Style.MajorTickSize.y : gp.Style.MinorTickSize.y;\n        const float tick_t     = (float)((y_pos_plt - scale_min) / (scale_max - scale_min));\n        const ImU32 tick_col = CalcTextColor(gp.ColormapData.LerpTable(cmap,tick_t));\n        if (y_pos < bb_grad.Max.y - 2 && y_pos > bb_grad.Min.y + 2) {\n            DrawList.AddLine(opposite ? ImVec2(bb_grad.Min.x+1, y_pos) : ImVec2(bb_grad.Max.x-1, y_pos),\n                             opposite ? ImVec2(bb_grad.Min.x + tick_width, y_pos) : ImVec2(bb_grad.Max.x - tick_width, y_pos),\n                             tick_col,\n                             tick_thick);\n        }\n        const float txt_x = opposite ? bb_grad.Min.x - txt_off - gp.CTicker.Ticks[i].LabelSize.x : bb_grad.Max.x + txt_off;\n        const float txt_y = y_pos - gp.CTicker.Ticks[i].LabelSize.y * 0.5f;\n        DrawList.AddText(ImVec2(txt_x, txt_y), col_text, gp.CTicker.GetText(i));\n    }\n\n    if (rend_label) {\n        const float pos_x = opposite ? bb_frame.Min.x + gp.Style.PlotPadding.x : bb_grad.Max.x + 2 * txt_off + gp.CTicker.MaxSize.x;\n        const float pos_y = bb_grad.GetCenter().y + label_size.x * 0.5f;\n        const char* label_end = ImGui::FindRenderedTextEnd(label);\n        AddTextVertical(&DrawList,ImVec2(pos_x,pos_y),col_text,label,label_end);\n    }\n    DrawList.AddRect(bb_grad.Min, bb_grad.Max, GetStyleColorU32(ImPlotCol_PlotBorder));\n    ImGui::PopClipRect();\n}\n\nbool ColormapSlider(const char* label, float* t, ImVec4* out, const char* format, ImPlotColormap cmap) {\n    *t = ImClamp(*t,0.0f,1.0f);\n    ImGuiContext &G      = *GImGui;\n    ImGuiWindow * Window = G.CurrentWindow;\n    if (Window->SkipItems)\n        return false;\n    ImPlotContext& gp = *GImPlot;\n    cmap = cmap == IMPLOT_AUTO ? gp.Style.Colormap : cmap;\n    IM_ASSERT_USER_ERROR(cmap >= 0 && cmap < gp.ColormapData.Count, \"Invalid colormap index!\");\n    const ImU32* keys  = gp.ColormapData.GetKeys(cmap);\n    const int    count = gp.ColormapData.GetKeyCount(cmap);\n    const bool   qual  = gp.ColormapData.IsQual(cmap);\n    const ImVec2 pos  = ImGui::GetCurrentWindow()->DC.CursorPos;\n    const float w     = ImGui::CalcItemWidth();\n    const float h     = ImGui::GetFrameHeight();\n    const ImRect rect = ImRect(pos.x,pos.y,pos.x+w,pos.y+h);\n    RenderColorBar(keys,count,*ImGui::GetWindowDrawList(),rect,false,false,!qual);\n    const ImU32 grab = CalcTextColor(gp.ColormapData.LerpTable(cmap,*t));\n    // const ImU32 text = CalcTextColor(gp.ColormapData.LerpTable(cmap,0.5f));\n    ImGui::PushStyleColor(ImGuiCol_FrameBg,IM_COL32_BLACK_TRANS);\n    ImGui::PushStyleColor(ImGuiCol_FrameBgActive,IM_COL32_BLACK_TRANS);\n    ImGui::PushStyleColor(ImGuiCol_FrameBgHovered,ImVec4(1,1,1,0.1f));\n    ImGui::PushStyleColor(ImGuiCol_SliderGrab,grab);\n    ImGui::PushStyleColor(ImGuiCol_SliderGrabActive, grab);\n    ImGui::PushStyleVar(ImGuiStyleVar_GrabMinSize,2);\n    ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding,0);\n    const bool changed = ImGui::SliderFloat(label,t,0,1,format);\n    ImGui::PopStyleColor(5);\n    ImGui::PopStyleVar(2);\n    if (out != nullptr)\n        *out = ImGui::ColorConvertU32ToFloat4(gp.ColormapData.LerpTable(cmap,*t));\n    return changed;\n}\n\nbool ColormapButton(const char* label, const ImVec2& size_arg, ImPlotColormap cmap) {\n    ImGuiContext &G      = *GImGui;\n    const ImGuiStyle& style = G.Style;\n    ImGuiWindow * Window = G.CurrentWindow;\n    if (Window->SkipItems)\n        return false;\n    ImPlotContext& gp = *GImPlot;\n    cmap = cmap == IMPLOT_AUTO ? gp.Style.Colormap : cmap;\n    IM_ASSERT_USER_ERROR(cmap >= 0 && cmap < gp.ColormapData.Count, \"Invalid colormap index!\");\n    const ImU32* keys  = gp.ColormapData.GetKeys(cmap);\n    const int    count = gp.ColormapData.GetKeyCount(cmap);\n    const bool   qual  = gp.ColormapData.IsQual(cmap);\n    const ImVec2 pos  = ImGui::GetCurrentWindow()->DC.CursorPos;\n    const ImVec2 label_size = ImGui::CalcTextSize(label, nullptr, true);\n    ImVec2 size = ImGui::CalcItemSize(size_arg, label_size.x + style.FramePadding.x * 2.0f, label_size.y + style.FramePadding.y * 2.0f);\n    const ImRect rect = ImRect(pos.x,pos.y,pos.x+size.x,pos.y+size.y);\n    RenderColorBar(keys,count,*ImGui::GetWindowDrawList(),rect,false,false,!qual);\n    const ImU32 text = CalcTextColor(gp.ColormapData.LerpTable(cmap,G.Style.ButtonTextAlign.x));\n    ImGui::PushStyleColor(ImGuiCol_Button,IM_COL32_BLACK_TRANS);\n    ImGui::PushStyleColor(ImGuiCol_ButtonHovered,ImVec4(1,1,1,0.1f));\n    ImGui::PushStyleColor(ImGuiCol_ButtonActive,ImVec4(1,1,1,0.2f));\n    ImGui::PushStyleColor(ImGuiCol_Text,text);\n    ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding,0);\n    const bool pressed = ImGui::Button(label,size);\n    ImGui::PopStyleColor(4);\n    ImGui::PopStyleVar(1);\n    return pressed;\n}\n\n//-----------------------------------------------------------------------------\n// [Section] Miscellaneous\n//-----------------------------------------------------------------------------\n\nImPlotInputMap& GetInputMap() {\n    IM_ASSERT_USER_ERROR(GImPlot != nullptr, \"No current context. Did you call ImPlot::CreateContext() or ImPlot::SetCurrentContext()?\");\n    ImPlotContext& gp = *GImPlot;\n    return gp.InputMap;\n}\n\nvoid MapInputDefault(ImPlotInputMap* dst) {\n    ImPlotInputMap& map = dst ? *dst : GetInputMap();\n    map.Pan             = ImGuiMouseButton_Left;\n    map.PanMod          = ImGuiMod_None;\n    map.Fit             = ImGuiMouseButton_Left;\n    map.Menu            = ImGuiMouseButton_Right;\n    map.Select          = ImGuiMouseButton_Right;\n    map.SelectMod       = ImGuiMod_None;\n    map.SelectCancel    = ImGuiMouseButton_Left;\n    map.SelectHorzMod   = ImGuiMod_Alt;\n    map.SelectVertMod   = ImGuiMod_Shift;\n    map.OverrideMod     = ImGuiMod_Ctrl;\n    map.ZoomMod         = ImGuiMod_None;\n    map.ZoomRate        = 0.1f;\n}\n\nvoid MapInputReverse(ImPlotInputMap* dst) {\n    ImPlotInputMap& map = dst ? *dst : GetInputMap();\n    map.Pan             = ImGuiMouseButton_Right;\n    map.PanMod          = ImGuiMod_None;\n    map.Fit             = ImGuiMouseButton_Left;\n    map.Menu            = ImGuiMouseButton_Right;\n    map.Select          = ImGuiMouseButton_Left;\n    map.SelectMod       = ImGuiMod_None;\n    map.SelectCancel    = ImGuiMouseButton_Right;\n    map.SelectHorzMod   = ImGuiMod_Alt;\n    map.SelectVertMod   = ImGuiMod_Shift;\n    map.OverrideMod     = ImGuiMod_Ctrl;\n    map.ZoomMod         = ImGuiMod_None;\n    map.ZoomRate        = 0.1f;\n}\n\n//-----------------------------------------------------------------------------\n// [Section] Miscellaneous\n//-----------------------------------------------------------------------------\n\nvoid ItemIcon(const ImVec4& col) {\n    ItemIcon(ImGui::ColorConvertFloat4ToU32(col));\n}\n\nvoid ItemIcon(ImU32 col) {\n    const float txt_size = ImGui::GetTextLineHeight();\n    ImVec2 size(txt_size-4,txt_size);\n    ImGuiWindow* window = ImGui::GetCurrentWindow();\n    ImVec2 pos = window->DC.CursorPos;\n    ImGui::GetWindowDrawList()->AddRectFilled(pos + ImVec2(0,2), pos + size - ImVec2(0,2), col);\n    ImGui::Dummy(size);\n}\n\nvoid ColormapIcon(ImPlotColormap cmap) {\n    ImPlotContext& gp = *GImPlot;\n    const float txt_size = ImGui::GetTextLineHeight();\n    ImVec2 size(txt_size-4,txt_size);\n    ImGuiWindow* window = ImGui::GetCurrentWindow();\n    ImVec2 pos = window->DC.CursorPos;\n    ImRect rect(pos+ImVec2(0,2),pos+size-ImVec2(0,2));\n    ImDrawList& DrawList = *ImGui::GetWindowDrawList();\n    RenderColorBar(gp.ColormapData.GetKeys(cmap),gp.ColormapData.GetKeyCount(cmap),DrawList,rect,false,false,!gp.ColormapData.IsQual(cmap));\n    ImGui::Dummy(size);\n}\n\nImDrawList* GetPlotDrawList() {\n    return ImGui::GetWindowDrawList();\n}\n\nvoid PushPlotClipRect(float expand) {\n    ImPlotContext& gp = *GImPlot;\n    IM_ASSERT_USER_ERROR(gp.CurrentPlot != nullptr, \"PushPlotClipRect() needs to be called between BeginPlot() and EndPlot()!\");\n    SetupLock();\n    ImRect rect = gp.CurrentPlot->PlotRect;\n    rect.Expand(expand);\n    ImGui::PushClipRect(rect.Min, rect.Max, true);\n}\n\nvoid PopPlotClipRect() {\n    SetupLock();\n    ImGui::PopClipRect();\n}\n\nstatic void HelpMarker(const char* desc) {\n    ImGui::TextDisabled(\"(?)\");\n    if (ImGui::IsItemHovered()) {\n        ImGui::BeginTooltip();\n        ImGui::PushTextWrapPos(ImGui::GetFontSize() * 35.0f);\n        ImGui::TextUnformatted(desc);\n        ImGui::PopTextWrapPos();\n        ImGui::EndTooltip();\n    }\n}\n\nbool ShowStyleSelector(const char* label)\n{\n    static int style_idx = -1;\n    if (ImGui::Combo(label, &style_idx, \"Auto\\0Classic\\0Dark\\0Light\\0\"))\n    {\n        switch (style_idx)\n        {\n        case 0: StyleColorsAuto(); break;\n        case 1: StyleColorsClassic(); break;\n        case 2: StyleColorsDark(); break;\n        case 3: StyleColorsLight(); break;\n        }\n        return true;\n    }\n    return false;\n}\n\nbool ShowColormapSelector(const char* label) {\n    ImPlotContext& gp = *GImPlot;\n    bool set = false;\n    if (ImGui::BeginCombo(label, gp.ColormapData.GetName(gp.Style.Colormap))) {\n        for (int i = 0; i < gp.ColormapData.Count; ++i) {\n            const char* name = gp.ColormapData.GetName(i);\n            if (ImGui::Selectable(name, gp.Style.Colormap == i)) {\n                gp.Style.Colormap = i;\n                ImPlot::BustItemCache();\n                set = true;\n            }\n        }\n        ImGui::EndCombo();\n    }\n    return set;\n}\n\nbool ShowInputMapSelector(const char* label) {\n    static int map_idx = -1;\n    if (ImGui::Combo(label, &map_idx, \"Default\\0Reversed\\0\"))\n    {\n        switch (map_idx)\n        {\n        case 0: MapInputDefault(); break;\n        case 1: MapInputReverse(); break;\n        }\n        return true;\n    }\n    return false;\n}\n\n\nvoid ShowStyleEditor(ImPlotStyle* ref) {\n    ImPlotContext& gp = *GImPlot;\n    ImPlotStyle& style = GetStyle();\n    static ImPlotStyle ref_saved_style;\n    // Default to using internal storage as reference\n    static bool init = true;\n    if (init && ref == nullptr)\n        ref_saved_style = style;\n    init = false;\n    if (ref == nullptr)\n        ref = &ref_saved_style;\n\n    if (ImPlot::ShowStyleSelector(\"Colors##Selector\"))\n        ref_saved_style = style;\n\n    // Save/Revert button\n    if (ImGui::Button(\"Save Ref\"))\n        *ref = ref_saved_style = style;\n    ImGui::SameLine();\n    if (ImGui::Button(\"Revert Ref\"))\n        style = *ref;\n    ImGui::SameLine();\n    HelpMarker(\"Save/Revert in local non-persistent storage. Default Colors definition are not affected. \"\n               \"Use \\\"Export\\\" below to save them somewhere.\");\n    if (ImGui::BeginTabBar(\"##StyleEditor\")) {\n        if (ImGui::BeginTabItem(\"Variables\")) {\n            ImGui::Text(\"Item Styling\");\n            ImGui::SliderFloat(\"LineWeight\", &style.LineWeight, 0.0f, 5.0f, \"%.1f\");\n            ImGui::SliderFloat(\"MarkerSize\", &style.MarkerSize, 2.0f, 10.0f, \"%.1f\");\n            ImGui::SliderFloat(\"MarkerWeight\", &style.MarkerWeight, 0.0f, 5.0f, \"%.1f\");\n            ImGui::SliderFloat(\"FillAlpha\", &style.FillAlpha, 0.0f, 1.0f, \"%.2f\");\n            ImGui::SliderFloat(\"ErrorBarSize\", &style.ErrorBarSize, 0.0f, 10.0f, \"%.1f\");\n            ImGui::SliderFloat(\"ErrorBarWeight\", &style.ErrorBarWeight, 0.0f, 5.0f, \"%.1f\");\n            ImGui::SliderFloat(\"DigitalBitHeight\", &style.DigitalBitHeight, 0.0f, 20.0f, \"%.1f\");\n            ImGui::SliderFloat(\"DigitalBitGap\", &style.DigitalBitGap, 0.0f, 20.0f, \"%.1f\");\n            ImGui::Text(\"Plot Styling\");\n            ImGui::SliderFloat(\"PlotBorderSize\", &style.PlotBorderSize, 0.0f, 2.0f, \"%.0f\");\n            ImGui::SliderFloat(\"MinorAlpha\", &style.MinorAlpha, 0.0f, 1.0f, \"%.2f\");\n            ImGui::SliderFloat2(\"MajorTickLen\", (float*)&style.MajorTickLen, 0.0f, 20.0f, \"%.0f\");\n            ImGui::SliderFloat2(\"MinorTickLen\", (float*)&style.MinorTickLen, 0.0f, 20.0f, \"%.0f\");\n            ImGui::SliderFloat2(\"MajorTickSize\",  (float*)&style.MajorTickSize, 0.0f, 2.0f, \"%.1f\");\n            ImGui::SliderFloat2(\"MinorTickSize\", (float*)&style.MinorTickSize, 0.0f, 2.0f, \"%.1f\");\n            ImGui::SliderFloat2(\"MajorGridSize\", (float*)&style.MajorGridSize, 0.0f, 2.0f, \"%.1f\");\n            ImGui::SliderFloat2(\"MinorGridSize\", (float*)&style.MinorGridSize, 0.0f, 2.0f, \"%.1f\");\n            ImGui::SliderFloat2(\"PlotDefaultSize\", (float*)&style.PlotDefaultSize, 0.0f, 1000, \"%.0f\");\n            ImGui::SliderFloat2(\"PlotMinSize\", (float*)&style.PlotMinSize, 0.0f, 300, \"%.0f\");\n            ImGui::Text(\"Plot Padding\");\n            ImGui::SliderFloat2(\"PlotPadding\", (float*)&style.PlotPadding, 0.0f, 20.0f, \"%.0f\");\n            ImGui::SliderFloat2(\"LabelPadding\", (float*)&style.LabelPadding, 0.0f, 20.0f, \"%.0f\");\n            ImGui::SliderFloat2(\"LegendPadding\", (float*)&style.LegendPadding, 0.0f, 20.0f, \"%.0f\");\n            ImGui::SliderFloat2(\"LegendInnerPadding\", (float*)&style.LegendInnerPadding, 0.0f, 10.0f, \"%.0f\");\n            ImGui::SliderFloat2(\"LegendSpacing\", (float*)&style.LegendSpacing, 0.0f, 5.0f, \"%.0f\");\n            ImGui::SliderFloat2(\"MousePosPadding\", (float*)&style.MousePosPadding, 0.0f, 20.0f, \"%.0f\");\n            ImGui::SliderFloat2(\"AnnotationPadding\", (float*)&style.AnnotationPadding, 0.0f, 5.0f, \"%.0f\");\n            ImGui::SliderFloat2(\"FitPadding\", (float*)&style.FitPadding, 0, 0.2f, \"%.2f\");\n\n            ImGui::EndTabItem();\n        }\n        if (ImGui::BeginTabItem(\"Colors\")) {\n            static int output_dest = 0;\n            static bool output_only_modified = false;\n\n            if (ImGui::Button(\"Export\", ImVec2(75,0))) {\n                if (output_dest == 0)\n                    ImGui::LogToClipboard();\n                else\n                    ImGui::LogToTTY();\n                ImGui::LogText(\"ImVec4* colors = ImPlot::GetStyle().Colors;\\n\");\n                for (int i = 0; i < ImPlotCol_COUNT; i++) {\n                    const ImVec4& col = style.Colors[i];\n                    const char* name = ImPlot::GetStyleColorName(i);\n                    if (!output_only_modified || memcmp(&col, &ref->Colors[i], sizeof(ImVec4)) != 0) {\n                        if (IsColorAuto(i))\n                            ImGui::LogText(\"colors[ImPlotCol_%s]%*s= IMPLOT_AUTO_COL;\\n\",name,14 - (int)strlen(name), \"\");\n                        else\n                            ImGui::LogText(\"colors[ImPlotCol_%s]%*s= ImVec4(%.2ff, %.2ff, %.2ff, %.2ff);\\n\",\n                                        name, 14 - (int)strlen(name), \"\", col.x, col.y, col.z, col.w);\n                    }\n                }\n                ImGui::LogFinish();\n            }\n            ImGui::SameLine(); ImGui::SetNextItemWidth(120); ImGui::Combo(\"##output_type\", &output_dest, \"To Clipboard\\0To TTY\\0\");\n            ImGui::SameLine(); ImGui::Checkbox(\"Only Modified Colors\", &output_only_modified);\n\n            static ImGuiTextFilter filter;\n            filter.Draw(\"Filter colors\", ImGui::GetFontSize() * 16);\n\n            static ImGuiColorEditFlags alpha_flags = ImGuiColorEditFlags_AlphaPreviewHalf;\n            if (ImGui::RadioButton(\"Opaque\", alpha_flags == ImGuiColorEditFlags_AlphaOpaque))      { alpha_flags = ImGuiColorEditFlags_AlphaOpaque; } ImGui::SameLine();\n            if (ImGui::RadioButton(\"Alpha\",  alpha_flags == ImGuiColorEditFlags_None))             { alpha_flags = ImGuiColorEditFlags_None; } ImGui::SameLine();\n            if (ImGui::RadioButton(\"Both\",   alpha_flags == ImGuiColorEditFlags_AlphaPreviewHalf)) { alpha_flags = ImGuiColorEditFlags_AlphaPreviewHalf; } ImGui::SameLine();\n            HelpMarker(\n                \"In the color list:\\n\"\n                \"Left-click on colored square to open color picker,\\n\"\n                \"Right-click to open edit options menu.\");\n            ImGui::Separator();\n            ImGui::PushItemWidth(-160);\n            for (int i = 0; i < ImPlotCol_COUNT; i++) {\n                const char* name = ImPlot::GetStyleColorName(i);\n                if (!filter.PassFilter(name))\n                    continue;\n                ImGui::PushID(i);\n                ImVec4 temp = GetStyleColorVec4(i);\n                const bool is_auto = IsColorAuto(i);\n                if (!is_auto)\n                    ImGui::PushStyleVar(ImGuiStyleVar_Alpha, 0.25f);\n                if (ImGui::Button(\"Auto\")) {\n                    if (is_auto)\n                        style.Colors[i] = temp;\n                    else\n                        style.Colors[i] = IMPLOT_AUTO_COL;\n                    BustItemCache();\n                }\n                if (!is_auto)\n                    ImGui::PopStyleVar();\n                ImGui::SameLine();\n                if (ImGui::ColorEdit4(name, &temp.x, ImGuiColorEditFlags_NoInputs | alpha_flags)) {\n                    style.Colors[i] = temp;\n                    BustItemCache();\n                }\n                if (memcmp(&style.Colors[i], &ref->Colors[i], sizeof(ImVec4)) != 0) {\n                    ImGui::SameLine(175); if (ImGui::Button(\"Save\")) { ref->Colors[i] = style.Colors[i]; }\n                    ImGui::SameLine(); if (ImGui::Button(\"Revert\")) {\n                        style.Colors[i] = ref->Colors[i];\n                        BustItemCache();\n                    }\n                }\n                ImGui::PopID();\n            }\n            ImGui::PopItemWidth();\n            ImGui::Separator();\n            ImGui::Text(\"Colors that are set to Auto (i.e. IMPLOT_AUTO_COL) will\\n\"\n                        \"be automatically deduced from your ImGui style or the\\n\"\n                        \"current ImPlot Colormap. If you want to style individual\\n\"\n                        \"plot items, use Push/PopStyleColor around its function.\");\n            ImGui::EndTabItem();\n        }\n        if (ImGui::BeginTabItem(\"Colormaps\")) {\n            static int output_dest = 0;\n            if (ImGui::Button(\"Export\", ImVec2(75,0))) {\n                if (output_dest == 0)\n                    ImGui::LogToClipboard();\n                else\n                    ImGui::LogToTTY();\n                int size = GetColormapSize();\n                const char* name = GetColormapName(gp.Style.Colormap);\n                ImGui::LogText(\"static const ImU32 %s_Data[%d] = {\\n\", name, size);\n                for (int i = 0; i < size; ++i) {\n                    ImU32 col = GetColormapColorU32(i,gp.Style.Colormap);\n                    ImGui::LogText(\"    %u%s\\n\", col, i == size - 1 ? \"\" : \",\");\n                }\n                ImGui::LogText(\"};\\nImPlotColormap %s = ImPlot::AddColormap(\\\"%s\\\", %s_Data, %d);\", name, name, name, size);\n                ImGui::LogFinish();\n            }\n            ImGui::SameLine(); ImGui::SetNextItemWidth(120); ImGui::Combo(\"##output_type\", &output_dest, \"To Clipboard\\0To TTY\\0\");\n            ImGui::SameLine();\n            static bool edit = false;\n            ImGui::Checkbox(\"Edit Mode\",&edit);\n\n            // built-in/added\n            ImGui::Separator();\n            for (int i = 0; i < gp.ColormapData.Count; ++i) {\n                ImGui::PushID(i);\n                int size = gp.ColormapData.GetKeyCount(i);\n                bool selected = i == gp.Style.Colormap;\n\n                const char* name = GetColormapName(i);\n                if (!selected)\n                    ImGui::PushStyleVar(ImGuiStyleVar_Alpha, 0.25f);\n                if (ImGui::Button(name, ImVec2(100,0))) {\n                    gp.Style.Colormap = i;\n                    BustItemCache();\n                }\n                if (!selected)\n                    ImGui::PopStyleVar();\n                ImGui::SameLine();\n                ImGui::BeginGroup();\n                if (edit) {\n                    for (int c = 0; c < size; ++c) {\n                        ImGui::PushID(c);\n                        ImVec4 col4 = ImGui::ColorConvertU32ToFloat4(gp.ColormapData.GetKeyColor(i,c));\n                        if (ImGui::ColorEdit4(\"\",&col4.x,ImGuiColorEditFlags_NoInputs)) {\n                            ImU32 col32 = ImGui::ColorConvertFloat4ToU32(col4);\n                            gp.ColormapData.SetKeyColor(i,c,col32);\n                            BustItemCache();\n                        }\n                        if ((c + 1) % 12 != 0 && c != size -1)\n                            ImGui::SameLine();\n                        ImGui::PopID();\n                    }\n                }\n                else {\n                    if (ImPlot::ColormapButton(\"##\",ImVec2(-1,0),i))\n                        edit = true;\n                }\n                ImGui::EndGroup();\n                ImGui::PopID();\n            }\n\n\n            static ImVector<ImVec4> custom;\n            if (custom.Size == 0) {\n                custom.push_back(ImVec4(1,0,0,1));\n                custom.push_back(ImVec4(0,1,0,1));\n                custom.push_back(ImVec4(0,0,1,1));\n            }\n            ImGui::Separator();\n            ImGui::BeginGroup();\n            static char name[16] = \"MyColormap\";\n\n\n            if (ImGui::Button(\"+\", ImVec2((100 - ImGui::GetStyle().ItemSpacing.x)/2,0)))\n                custom.push_back(ImVec4(0,0,0,1));\n            ImGui::SameLine();\n            if (ImGui::Button(\"-\", ImVec2((100 - ImGui::GetStyle().ItemSpacing.x)/2,0)) && custom.Size > 2)\n                custom.pop_back();\n            ImGui::SetNextItemWidth(100);\n            ImGui::InputText(\"##Name\",name,16,ImGuiInputTextFlags_CharsNoBlank);\n            static bool qual = true;\n            ImGui::Checkbox(\"Qualitative\",&qual);\n            if (ImGui::Button(\"Add\", ImVec2(100, 0)) && gp.ColormapData.GetIndex(name)==-1)\n                AddColormap(name,custom.Data,custom.Size,qual);\n\n            ImGui::EndGroup();\n            ImGui::SameLine();\n            ImGui::BeginGroup();\n            for (int c = 0; c < custom.Size; ++c) {\n                ImGui::PushID(c);\n                if (ImGui::ColorEdit4(\"##Col1\", &custom[c].x, ImGuiColorEditFlags_NoInputs)) {\n\n                }\n                if ((c + 1) % 12 != 0)\n                    ImGui::SameLine();\n                ImGui::PopID();\n            }\n            ImGui::EndGroup();\n\n\n            ImGui::EndTabItem();\n        }\n        ImGui::EndTabBar();\n    }\n}\n\nvoid ShowUserGuide() {\n        ImGui::BulletText(\"Left-click drag within the plot area to pan X and Y axes.\");\n    ImGui::Indent();\n        ImGui::BulletText(\"Left-click drag on axis labels to pan an individual axis.\");\n    ImGui::Unindent();\n    ImGui::BulletText(\"Scroll in the plot area to zoom both X any Y axes.\");\n    ImGui::Indent();\n        ImGui::BulletText(\"Scroll on axis labels to zoom an individual axis.\");\n    ImGui::Unindent();\n    ImGui::BulletText(\"Right-click drag to box select data.\");\n    ImGui::Indent();\n        ImGui::BulletText(\"Hold Alt to expand box selection horizontally.\");\n        ImGui::BulletText(\"Hold Shift to expand box selection vertically.\");\n        ImGui::BulletText(\"Left-click while box selecting to cancel the selection.\");\n    ImGui::Unindent();\n    ImGui::BulletText(\"Double left-click to fit all visible data.\");\n    ImGui::Indent();\n        ImGui::BulletText(\"Double left-click axis labels to fit the individual axis.\");\n    ImGui::Unindent();\n    ImGui::BulletText(\"Right-click open the full plot context menu.\");\n    ImGui::Indent();\n        ImGui::BulletText(\"Right-click axis labels to open an individual axis context menu.\");\n    ImGui::Unindent();\n    ImGui::BulletText(\"Click legend label icons to show/hide plot items.\");\n}\n\nvoid ShowTicksMetrics(const ImPlotTicker& ticker) {\n    ImGui::BulletText(\"Size: %d\", ticker.TickCount());\n    ImGui::BulletText(\"MaxSize: [%f,%f]\", ticker.MaxSize.x, ticker.MaxSize.y);\n}\n\nvoid ShowAxisMetrics(const ImPlotPlot& plot, const ImPlotAxis& axis) {\n    ImGui::BulletText(\"Label: %s\", axis.LabelOffset == -1 ? \"[none]\" : plot.GetAxisLabel(axis));\n    ImGui::BulletText(\"Flags: 0x%08X\", axis.Flags);\n    ImGui::BulletText(\"Range: [%f,%f]\",axis.Range.Min, axis.Range.Max);\n    ImGui::BulletText(\"Pixels: %f\", axis.PixelSize());\n    ImGui::BulletText(\"Aspect: %f\", axis.GetAspect());\n    ImGui::BulletText(axis.OrthoAxis == nullptr ? \"OrtherAxis: NULL\" : \"OrthoAxis: 0x%08X\", axis.OrthoAxis->ID);\n    ImGui::BulletText(\"LinkedMin: %p\", (void*)axis.LinkedMin);\n    ImGui::BulletText(\"LinkedMax: %p\", (void*)axis.LinkedMax);\n    ImGui::BulletText(\"HasRange: %s\", axis.HasRange ? \"true\" : \"false\");\n    ImGui::BulletText(\"Hovered: %s\", axis.Hovered ? \"true\" : \"false\");\n    ImGui::BulletText(\"Held: %s\", axis.Held ? \"true\" : \"false\");\n\n    if (ImGui::TreeNode(\"Transform\")) {\n        ImGui::BulletText(\"PixelMin: %f\", axis.PixelMin);\n        ImGui::BulletText(\"PixelMax: %f\", axis.PixelMax);\n        ImGui::BulletText(\"ScaleToPixel: %f\", axis.ScaleToPixel);\n        ImGui::BulletText(\"ScaleMax: %f\", axis.ScaleMax);\n        ImGui::TreePop();\n    }\n\n    if (ImGui::TreeNode(\"Ticks\")) {\n        ShowTicksMetrics(axis.Ticker);\n        ImGui::TreePop();\n    }\n}\n\nvoid ShowMetricsWindow(bool* p_popen) {\n\n    static bool show_plot_rects = false;\n    static bool show_axes_rects = false;\n    static bool show_axis_rects = false;\n    static bool show_canvas_rects = false;\n    static bool show_frame_rects = false;\n    static bool show_subplot_frame_rects = false;\n    static bool show_subplot_grid_rects = false;\n\n    ImDrawList& fg = *ImGui::GetForegroundDrawList();\n\n    ImPlotContext& gp = *GImPlot;\n    // ImGuiContext& g = *GImGui;\n    ImGuiIO& io = ImGui::GetIO();\n    ImGui::Begin(\"ImPlot Metrics\", p_popen);\n    ImGui::Text(\"ImPlot \" IMPLOT_VERSION);\n    ImGui::Text(\"Application average %.3f ms/frame (%.1f FPS)\", 1000.0f / io.Framerate, io.Framerate);\n    ImGui::Text(\"Mouse Position: [%.0f,%.0f]\", io.MousePos.x, io.MousePos.y);\n    ImGui::Separator();\n    if (ImGui::TreeNode(\"Tools\")) {\n        if (ImGui::Button(\"Bust Plot Cache\"))\n            BustPlotCache();\n        ImGui::SameLine();\n        if (ImGui::Button(\"Bust Item Cache\"))\n            BustItemCache();\n        ImGui::Checkbox(\"Show Frame Rects\", &show_frame_rects);\n        ImGui::Checkbox(\"Show Canvas Rects\",&show_canvas_rects);\n        ImGui::Checkbox(\"Show Plot Rects\",  &show_plot_rects);\n        ImGui::Checkbox(\"Show Axes Rects\",  &show_axes_rects);\n        ImGui::Checkbox(\"Show Axis Rects\",  &show_axis_rects);\n        ImGui::Checkbox(\"Show Subplot Frame Rects\",  &show_subplot_frame_rects);\n        ImGui::Checkbox(\"Show Subplot Grid Rects\",  &show_subplot_grid_rects);\n        ImGui::TreePop();\n    }\n    const int n_plots = gp.Plots.GetBufSize();\n    const int n_subplots = gp.Subplots.GetBufSize();\n    // render rects\n    for (int p = 0; p < n_plots; ++p) {\n        ImPlotPlot* plot = gp.Plots.GetByIndex(p);\n        if (show_frame_rects)\n            fg.AddRect(plot->FrameRect.Min, plot->FrameRect.Max, IM_COL32(255,0,255,255));\n        if (show_canvas_rects)\n            fg.AddRect(plot->CanvasRect.Min, plot->CanvasRect.Max, IM_COL32(0,255,255,255));\n        if (show_plot_rects)\n            fg.AddRect(plot->PlotRect.Min, plot->PlotRect.Max, IM_COL32(255,255,0,255));\n        if (show_axes_rects)\n            fg.AddRect(plot->AxesRect.Min, plot->AxesRect.Max, IM_COL32(0,255,128,255));\n        if (show_axis_rects) {\n            for (int i = 0; i < ImAxis_COUNT; ++i) {\n                if (plot->Axes[i].Enabled)\n                    fg.AddRect(plot->Axes[i].HoverRect.Min, plot->Axes[i].HoverRect.Max, IM_COL32(0,255,0,255));\n            }\n        }\n    }\n    for (int p = 0; p < n_subplots; ++p) {\n        ImPlotSubplot* subplot = gp.Subplots.GetByIndex(p);\n        if (show_subplot_frame_rects)\n            fg.AddRect(subplot->FrameRect.Min, subplot->FrameRect.Max, IM_COL32(255,0,0,255));\n        if (show_subplot_grid_rects)\n            fg.AddRect(subplot->GridRect.Min, subplot->GridRect.Max, IM_COL32(0,0,255,255));\n    }\n    if (ImGui::TreeNode(\"Plots\",\"Plots (%d)\", n_plots)) {\n        for (int p = 0; p < n_plots; ++p) {\n            // plot\n            ImPlotPlot& plot = *gp.Plots.GetByIndex(p);\n            ImGui::PushID(p);\n            if (ImGui::TreeNode(\"Plot\", \"Plot [0x%08X]\", plot.ID)) {\n                int n_items = plot.Items.GetItemCount();\n                if (ImGui::TreeNode(\"Items\", \"Items (%d)\", n_items)) {\n                    for (int i = 0; i < n_items; ++i) {\n                        ImPlotItem* item = plot.Items.GetItemByIndex(i);\n                        ImGui::PushID(i);\n                        if (ImGui::TreeNode(\"Item\", \"Item [0x%08X]\", item->ID)) {\n                            ImGui::Bullet(); ImGui::Checkbox(\"Show\", &item->Show);\n                            ImGui::Bullet();\n                            ImVec4 temp = ImGui::ColorConvertU32ToFloat4(item->Color);\n                            if (ImGui::ColorEdit4(\"Color\",&temp.x, ImGuiColorEditFlags_NoInputs))\n                                item->Color = ImGui::ColorConvertFloat4ToU32(temp);\n\n                            ImGui::BulletText(\"NameOffset: %d\",item->NameOffset);\n                            ImGui::BulletText(\"Name: %s\", item->NameOffset != -1 ? plot.Items.Legend.Labels.Buf.Data + item->NameOffset : \"N/A\");\n                            ImGui::BulletText(\"Hovered: %s\",item->LegendHovered ? \"true\" : \"false\");\n                            ImGui::TreePop();\n                        }\n                        ImGui::PopID();\n                    }\n                    ImGui::TreePop();\n                }\n                char buff[16];\n                for (int i = 0; i < IMPLOT_NUM_X_AXES; ++i) {\n                    ImFormatString(buff,16,\"X-Axis %d\", i+1);\n                    if (plot.XAxis(i).Enabled && ImGui::TreeNode(buff, \"X-Axis %d [0x%08X]\", i+1, plot.XAxis(i).ID)) {\n                        ShowAxisMetrics(plot, plot.XAxis(i));\n                        ImGui::TreePop();\n                    }\n                }\n                for (int i = 0; i < IMPLOT_NUM_Y_AXES; ++i) {\n                    ImFormatString(buff,16,\"Y-Axis %d\", i+1);\n                    if (plot.YAxis(i).Enabled && ImGui::TreeNode(buff, \"Y-Axis %d [0x%08X]\", i+1, plot.YAxis(i).ID)) {\n                        ShowAxisMetrics(plot, plot.YAxis(i));\n                        ImGui::TreePop();\n                    }\n                }\n                ImGui::BulletText(\"Title: %s\", plot.HasTitle() ? plot.GetTitle() : \"none\");\n                ImGui::BulletText(\"Flags: 0x%08X\", plot.Flags);\n                ImGui::BulletText(\"Initialized: %s\", plot.Initialized ? \"true\" : \"false\");\n                ImGui::BulletText(\"Selecting: %s\", plot.Selecting ? \"true\" : \"false\");\n                ImGui::BulletText(\"Selected: %s\", plot.Selected ? \"true\" : \"false\");\n                ImGui::BulletText(\"Hovered: %s\", plot.Hovered ? \"true\" : \"false\");\n                ImGui::BulletText(\"Held: %s\", plot.Held ? \"true\" : \"false\");\n                ImGui::BulletText(\"LegendHovered: %s\", plot.Items.Legend.Hovered ? \"true\" : \"false\");\n                ImGui::BulletText(\"ContextLocked: %s\", plot.ContextLocked ? \"true\" : \"false\");\n                ImGui::TreePop();\n            }\n            ImGui::PopID();\n        }\n        ImGui::TreePop();\n    }\n\n    if (ImGui::TreeNode(\"Subplots\",\"Subplots (%d)\", n_subplots)) {\n        for (int p = 0; p < n_subplots; ++p) {\n            // plot\n            ImPlotSubplot& plot = *gp.Subplots.GetByIndex(p);\n            ImGui::PushID(p);\n            if (ImGui::TreeNode(\"Subplot\", \"Subplot [0x%08X]\", plot.ID)) {\n                int n_items = plot.Items.GetItemCount();\n                if (ImGui::TreeNode(\"Items\", \"Items (%d)\", n_items)) {\n                    for (int i = 0; i < n_items; ++i) {\n                        ImPlotItem* item = plot.Items.GetItemByIndex(i);\n                        ImGui::PushID(i);\n                        if (ImGui::TreeNode(\"Item\", \"Item [0x%08X]\", item->ID)) {\n                            ImGui::Bullet(); ImGui::Checkbox(\"Show\", &item->Show);\n                            ImGui::Bullet();\n                            ImVec4 temp = ImGui::ColorConvertU32ToFloat4(item->Color);\n                            if (ImGui::ColorEdit4(\"Color\",&temp.x, ImGuiColorEditFlags_NoInputs))\n                                item->Color = ImGui::ColorConvertFloat4ToU32(temp);\n\n                            ImGui::BulletText(\"NameOffset: %d\",item->NameOffset);\n                            ImGui::BulletText(\"Name: %s\", item->NameOffset != -1 ? plot.Items.Legend.Labels.Buf.Data + item->NameOffset : \"N/A\");\n                            ImGui::BulletText(\"Hovered: %s\",item->LegendHovered ? \"true\" : \"false\");\n                            ImGui::TreePop();\n                        }\n                        ImGui::PopID();\n                    }\n                    ImGui::TreePop();\n                }\n                ImGui::BulletText(\"Flags: 0x%08X\", plot.Flags);\n                ImGui::BulletText(\"FrameHovered: %s\", plot.FrameHovered ? \"true\" : \"false\");\n                ImGui::BulletText(\"LegendHovered: %s\", plot.Items.Legend.Hovered ? \"true\" : \"false\");\n                ImGui::TreePop();\n            }\n            ImGui::PopID();\n        }\n        ImGui::TreePop();\n    }\n    if (ImGui::TreeNode(\"Colormaps\")) {\n        ImGui::BulletText(\"Colormaps:  %d\", gp.ColormapData.Count);\n        ImGui::BulletText(\"Memory: %d bytes\", gp.ColormapData.Tables.Size * 4);\n        if (ImGui::TreeNode(\"Data\")) {\n            for (int m = 0; m < gp.ColormapData.Count; ++m) {\n                if (ImGui::TreeNode(gp.ColormapData.GetName(m))) {\n                    int count = gp.ColormapData.GetKeyCount(m);\n                    int size = gp.ColormapData.GetTableSize(m);\n                    bool qual = gp.ColormapData.IsQual(m);\n                    ImGui::BulletText(\"Qualitative: %s\", qual ? \"true\" : \"false\");\n                    ImGui::BulletText(\"Key Count: %d\", count);\n                    ImGui::BulletText(\"Table Size: %d\", size);\n                    ImGui::Indent();\n\n                    static float t = 0.5;\n                    ImVec4 samp;\n                    float wid = 32 * 10 - ImGui::GetFrameHeight() - ImGui::GetStyle().ItemSpacing.x;\n                    ImGui::SetNextItemWidth(wid);\n                    ImPlot::ColormapSlider(\"##Sample\",&t,&samp,\"%.3f\",m);\n                    ImGui::SameLine();\n                    ImGui::ColorButton(\"Sampler\",samp);\n                    ImGui::PushStyleColor(ImGuiCol_FrameBg, ImVec4(0,0,0,0));\n                    ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0,0));\n                    for (int c = 0; c < size; ++c) {\n                        ImVec4 col = ImGui::ColorConvertU32ToFloat4(gp.ColormapData.GetTableColor(m,c));\n                        ImGui::PushID(m*1000+c);\n                        ImGui::ColorButton(\"\",col,0,ImVec2(10,10));\n                        ImGui::PopID();\n                        if ((c + 1) % 32 != 0 && c != size - 1)\n                            ImGui::SameLine();\n                    }\n                    ImGui::PopStyleVar();\n                    ImGui::PopStyleColor();\n                    ImGui::Unindent();\n                    ImGui::TreePop();\n                }\n            }\n            ImGui::TreePop();\n        }\n        ImGui::TreePop();\n    }\n    ImGui::End();\n}\n\nbool ShowDatePicker(const char* id, int* level, ImPlotTime* t, const ImPlotTime* t1, const ImPlotTime* t2) {\n\n    ImGui::PushID(id);\n    ImGui::BeginGroup();\n\n    ImGuiStyle& style = ImGui::GetStyle();\n    ImVec4 col_txt    = style.Colors[ImGuiCol_Text];\n    ImVec4 col_dis    = style.Colors[ImGuiCol_TextDisabled];\n    ImVec4 col_btn    = style.Colors[ImGuiCol_Button];\n    ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0,0,0,0));\n    ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0,0));\n\n    const float ht    = ImGui::GetFrameHeight();\n    ImVec2 cell_size(ht*1.25f,ht);\n    char buff[32];\n    bool clk = false;\n    tm& Tm = GImPlot->Tm;\n\n    const int min_yr = 1970;\n    const int max_yr = 2999;\n\n    // t1 parts\n    int t1_mo = 0; int t1_md = 0; int t1_yr = 0;\n    if (t1 != nullptr) {\n        GetTime(*t1,&Tm);\n        t1_mo = Tm.tm_mon;\n        t1_md = Tm.tm_mday;\n        t1_yr = Tm.tm_year + 1900;\n    }\n\n     // t2 parts\n    int t2_mo = 0; int t2_md = 0; int t2_yr = 0;\n    if (t2 != nullptr) {\n        GetTime(*t2,&Tm);\n        t2_mo = Tm.tm_mon;\n        t2_md = Tm.tm_mday;\n        t2_yr = Tm.tm_year + 1900;\n    }\n\n    // day widget\n    if (*level == 0) {\n        *t = FloorTime(*t, ImPlotTimeUnit_Day);\n        GetTime(*t, &Tm);\n        const int this_year = Tm.tm_year + 1900;\n        const int last_year = this_year - 1;\n        const int next_year = this_year + 1;\n        const int this_mon  = Tm.tm_mon;\n        const int last_mon  = this_mon == 0 ? 11 : this_mon - 1;\n        const int next_mon  = this_mon == 11 ? 0 : this_mon + 1;\n        const int days_this_mo = GetDaysInMonth(this_year, this_mon);\n        const int days_last_mo = GetDaysInMonth(this_mon == 0 ? last_year : this_year, last_mon);\n        ImPlotTime t_first_mo = FloorTime(*t,ImPlotTimeUnit_Mo);\n        GetTime(t_first_mo,&Tm);\n        const int first_wd = Tm.tm_wday;\n        // month year\n        ImFormatString(buff, 32, \"%s %d\", MONTH_NAMES[this_mon], this_year);\n        if (ImGui::Button(buff))\n            *level = 1;\n        ImGui::SameLine(5*cell_size.x);\n        BeginDisabledControls(this_year <= min_yr && this_mon == 0);\n        if (ImGui::ArrowButtonEx(\"##Up\",ImGuiDir_Up,cell_size))\n            *t = AddTime(*t, ImPlotTimeUnit_Mo, -1);\n        EndDisabledControls(this_year <= min_yr && this_mon == 0);\n        ImGui::SameLine();\n        BeginDisabledControls(this_year >= max_yr && this_mon == 11);\n        if (ImGui::ArrowButtonEx(\"##Down\",ImGuiDir_Down,cell_size))\n            *t = AddTime(*t, ImPlotTimeUnit_Mo, 1);\n        EndDisabledControls(this_year >= max_yr && this_mon == 11);\n        // render weekday abbreviations\n        ImGui::PushItemFlag(ImGuiItemFlags_Disabled, true);\n        for (int i = 0; i < 7; ++i) {\n            ImGui::Button(WD_ABRVS[i],cell_size);\n            if (i != 6) { ImGui::SameLine(); }\n        }\n        ImGui::PopItemFlag();\n        // 0 = last mo, 1 = this mo, 2 = next mo\n        int mo = first_wd > 0 ? 0 : 1;\n        int day = mo == 1 ? 1 : days_last_mo - first_wd + 1;\n        for (int i = 0; i < 6; ++i) {\n            for (int j = 0; j < 7; ++j) {\n                if (mo == 0 && day > days_last_mo) {\n                    mo = 1;\n                    day = 1;\n                }\n                else if (mo == 1 && day > days_this_mo) {\n                    mo = 2;\n                    day = 1;\n                }\n                const int now_yr = (mo == 0 && this_mon == 0) ? last_year : ((mo == 2 && this_mon == 11) ? next_year : this_year);\n                const int now_mo = mo == 0 ? last_mon : (mo == 1 ? this_mon : next_mon);\n                const int now_md = day;\n\n                const bool off_mo   = mo == 0 || mo == 2;\n                const bool t1_or_t2 = (t1 != nullptr && t1_mo == now_mo && t1_yr == now_yr && t1_md == now_md) ||\n                                      (t2 != nullptr && t2_mo == now_mo && t2_yr == now_yr && t2_md == now_md);\n\n                if (off_mo)\n                    ImGui::PushStyleColor(ImGuiCol_Text, col_dis);\n                if (t1_or_t2) {\n                    ImGui::PushStyleColor(ImGuiCol_Button, col_btn);\n                    ImGui::PushStyleColor(ImGuiCol_Text, col_txt);\n                }\n                ImGui::PushID(i*7+j);\n                ImFormatString(buff,32,\"%d\",day);\n                if (now_yr == min_yr-1 || now_yr == max_yr+1) {\n                    ImGui::Dummy(cell_size);\n                }\n                else if (ImGui::Button(buff,cell_size) && !clk) {\n                    *t = MakeTime(now_yr, now_mo, now_md);\n                    clk = true;\n                }\n                ImGui::PopID();\n                if (t1_or_t2)\n                    ImGui::PopStyleColor(2);\n                if (off_mo)\n                    ImGui::PopStyleColor();\n                if (j != 6)\n                    ImGui::SameLine();\n                day++;\n            }\n        }\n    }\n    // month widget\n    else if (*level == 1) {\n        *t = FloorTime(*t, ImPlotTimeUnit_Mo);\n        GetTime(*t, &Tm);\n        int this_yr  = Tm.tm_year + 1900;\n        ImFormatString(buff, 32, \"%d\", this_yr);\n        if (ImGui::Button(buff))\n            *level = 2;\n        BeginDisabledControls(this_yr <= min_yr);\n        ImGui::SameLine(5*cell_size.x);\n        if (ImGui::ArrowButtonEx(\"##Up\",ImGuiDir_Up,cell_size))\n            *t = AddTime(*t, ImPlotTimeUnit_Yr, -1);\n        EndDisabledControls(this_yr <= min_yr);\n        ImGui::SameLine();\n        BeginDisabledControls(this_yr >= max_yr);\n        if (ImGui::ArrowButtonEx(\"##Down\",ImGuiDir_Down,cell_size))\n            *t = AddTime(*t, ImPlotTimeUnit_Yr, 1);\n        EndDisabledControls(this_yr >= max_yr);\n        // ImGui::Dummy(cell_size);\n        cell_size.x *= 7.0f/4.0f;\n        cell_size.y *= 7.0f/3.0f;\n        int mo = 0;\n        for (int i = 0; i < 3; ++i) {\n            for (int j = 0; j < 4; ++j) {\n                const bool t1_or_t2 = (t1 != nullptr && t1_yr == this_yr && t1_mo == mo) ||\n                                      (t2 != nullptr && t2_yr == this_yr && t2_mo == mo);\n                if (t1_or_t2)\n                    ImGui::PushStyleColor(ImGuiCol_Button, col_btn);\n                if (ImGui::Button(MONTH_ABRVS[mo],cell_size) && !clk) {\n                    *t = MakeTime(this_yr, mo);\n                    *level = 0;\n                }\n                if (t1_or_t2)\n                    ImGui::PopStyleColor();\n                if (j != 3)\n                    ImGui::SameLine();\n                mo++;\n            }\n        }\n    }\n    else if (*level == 2) {\n        *t = FloorTime(*t, ImPlotTimeUnit_Yr);\n        int this_yr = GetYear(*t);\n        int yr = this_yr  - this_yr % 20;\n        ImGui::PushItemFlag(ImGuiItemFlags_Disabled, true);\n        ImFormatString(buff,32,\"%d-%d\",yr,yr+19);\n        ImGui::Button(buff);\n        ImGui::PopItemFlag();\n        ImGui::SameLine(5*cell_size.x);\n        BeginDisabledControls(yr <= min_yr);\n        if (ImGui::ArrowButtonEx(\"##Up\",ImGuiDir_Up,cell_size))\n            *t = MakeTime(yr-20);\n        EndDisabledControls(yr <= min_yr);\n        ImGui::SameLine();\n        BeginDisabledControls(yr + 20 >= max_yr);\n        if (ImGui::ArrowButtonEx(\"##Down\",ImGuiDir_Down,cell_size))\n            *t = MakeTime(yr+20);\n        EndDisabledControls(yr+ 20 >= max_yr);\n        // ImGui::Dummy(cell_size);\n        cell_size.x *= 7.0f/4.0f;\n        cell_size.y *= 7.0f/5.0f;\n        for (int i = 0; i < 5; ++i) {\n            for (int j = 0; j < 4; ++j) {\n                const bool t1_or_t2 = (t1 != nullptr && t1_yr == yr) || (t2 != nullptr && t2_yr == yr);\n                if (t1_or_t2)\n                    ImGui::PushStyleColor(ImGuiCol_Button, col_btn);\n                ImFormatString(buff,32,\"%d\",yr);\n                if (yr<1970||yr>3000) {\n                    ImGui::Dummy(cell_size);\n                }\n                else if (ImGui::Button(buff,cell_size)) {\n                    *t = MakeTime(yr);\n                    *level = 1;\n                }\n                if (t1_or_t2)\n                    ImGui::PopStyleColor();\n                if (j != 3)\n                    ImGui::SameLine();\n                yr++;\n            }\n        }\n    }\n    ImGui::PopStyleVar();\n    ImGui::PopStyleColor();\n    ImGui::EndGroup();\n    ImGui::PopID();\n    return clk;\n}\n\nbool ShowTimePicker(const char* id, ImPlotTime* t) {\n    ImPlotContext& gp = *GImPlot;\n    ImGui::PushID(id);\n    tm& Tm = gp.Tm;\n    GetTime(*t,&Tm);\n\n    static const char* nums[] = { \"00\",\"01\",\"02\",\"03\",\"04\",\"05\",\"06\",\"07\",\"08\",\"09\",\n                                  \"10\",\"11\",\"12\",\"13\",\"14\",\"15\",\"16\",\"17\",\"18\",\"19\",\n                                  \"20\",\"21\",\"22\",\"23\",\"24\",\"25\",\"26\",\"27\",\"28\",\"29\",\n                                  \"30\",\"31\",\"32\",\"33\",\"34\",\"35\",\"36\",\"37\",\"38\",\"39\",\n                                  \"40\",\"41\",\"42\",\"43\",\"44\",\"45\",\"46\",\"47\",\"48\",\"49\",\n                                  \"50\",\"51\",\"52\",\"53\",\"54\",\"55\",\"56\",\"57\",\"58\",\"59\"};\n\n    static const char* am_pm[] = {\"am\",\"pm\"};\n\n    bool hour24 = gp.Style.Use24HourClock;\n\n    int hr  = hour24 ? Tm.tm_hour : ((Tm.tm_hour == 0 || Tm.tm_hour == 12) ? 12 : Tm.tm_hour % 12);\n    int min = Tm.tm_min;\n    int sec = Tm.tm_sec;\n    int ap  = Tm.tm_hour < 12 ? 0 : 1;\n\n    bool changed = false;\n\n    ImVec2 spacing = ImGui::GetStyle().ItemSpacing;\n    spacing.x = 0;\n    float width    = ImGui::CalcTextSize(\"888\").x;\n    float height   = ImGui::GetFrameHeight();\n\n    ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, spacing);\n    ImGui::PushStyleVar(ImGuiStyleVar_ScrollbarSize,2.0f);\n    ImGui::PushStyleColor(ImGuiCol_FrameBg, ImVec4(0,0,0,0));\n    ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0,0,0,0));\n    ImGui::PushStyleColor(ImGuiCol_FrameBgHovered, ImGui::GetStyleColorVec4(ImGuiCol_ButtonHovered));\n\n    ImGui::SetNextItemWidth(width);\n    if (ImGui::BeginCombo(\"##hr\",nums[hr],ImGuiComboFlags_NoArrowButton)) {\n        const int ia = hour24 ? 0 : 1;\n        const int ib = hour24 ? 24 : 13;\n        for (int i = ia; i < ib; ++i) {\n            if (ImGui::Selectable(nums[i],i==hr)) {\n                hr = i;\n                changed = true;\n            }\n        }\n        ImGui::EndCombo();\n    }\n    ImGui::SameLine();\n    ImGui::Text(\":\");\n    ImGui::SameLine();\n    ImGui::SetNextItemWidth(width);\n    if (ImGui::BeginCombo(\"##min\",nums[min],ImGuiComboFlags_NoArrowButton)) {\n        for (int i = 0; i < 60; ++i) {\n            if (ImGui::Selectable(nums[i],i==min)) {\n                min = i;\n                changed = true;\n            }\n        }\n        ImGui::EndCombo();\n    }\n    ImGui::SameLine();\n    ImGui::Text(\":\");\n    ImGui::SameLine();\n    ImGui::SetNextItemWidth(width);\n    if (ImGui::BeginCombo(\"##sec\",nums[sec],ImGuiComboFlags_NoArrowButton)) {\n        for (int i = 0; i < 60; ++i) {\n            if (ImGui::Selectable(nums[i],i==sec)) {\n                sec = i;\n                changed = true;\n            }\n        }\n        ImGui::EndCombo();\n    }\n    if (!hour24) {\n        ImGui::SameLine();\n        if (ImGui::Button(am_pm[ap],ImVec2(0,height))) {\n            ap = 1 - ap;\n            changed = true;\n        }\n    }\n\n    ImGui::PopStyleColor(3);\n    ImGui::PopStyleVar(2);\n    ImGui::PopID();\n\n    if (changed) {\n        if (!hour24)\n            hr = hr % 12 + ap * 12;\n        Tm.tm_hour = hr;\n        Tm.tm_min  = min;\n        Tm.tm_sec  = sec;\n        *t = MkTime(&Tm);\n    }\n\n    return changed;\n}\n\nvoid StyleColorsAuto(ImPlotStyle* dst) {\n    ImPlotStyle* style              = dst ? dst : &ImPlot::GetStyle();\n    ImVec4* colors                  = style->Colors;\n\n    style->MinorAlpha               = 0.25f;\n\n    colors[ImPlotCol_Line]          = IMPLOT_AUTO_COL;\n    colors[ImPlotCol_Fill]          = IMPLOT_AUTO_COL;\n    colors[ImPlotCol_MarkerOutline] = IMPLOT_AUTO_COL;\n    colors[ImPlotCol_MarkerFill]    = IMPLOT_AUTO_COL;\n    colors[ImPlotCol_ErrorBar]      = IMPLOT_AUTO_COL;\n    colors[ImPlotCol_FrameBg]       = IMPLOT_AUTO_COL;\n    colors[ImPlotCol_PlotBg]        = IMPLOT_AUTO_COL;\n    colors[ImPlotCol_PlotBorder]    = IMPLOT_AUTO_COL;\n    colors[ImPlotCol_LegendBg]      = IMPLOT_AUTO_COL;\n    colors[ImPlotCol_LegendBorder]  = IMPLOT_AUTO_COL;\n    colors[ImPlotCol_LegendText]    = IMPLOT_AUTO_COL;\n    colors[ImPlotCol_TitleText]     = IMPLOT_AUTO_COL;\n    colors[ImPlotCol_InlayText]     = IMPLOT_AUTO_COL;\n    colors[ImPlotCol_PlotBorder]    = IMPLOT_AUTO_COL;\n    colors[ImPlotCol_AxisText]      = IMPLOT_AUTO_COL;\n    colors[ImPlotCol_AxisGrid]      = IMPLOT_AUTO_COL;\n    colors[ImPlotCol_AxisTick]      = IMPLOT_AUTO_COL;\n    colors[ImPlotCol_AxisBg]        = IMPLOT_AUTO_COL;\n    colors[ImPlotCol_AxisBgHovered] = IMPLOT_AUTO_COL;\n    colors[ImPlotCol_AxisBgActive]  = IMPLOT_AUTO_COL;\n    colors[ImPlotCol_Selection]     = IMPLOT_AUTO_COL;\n    colors[ImPlotCol_Crosshairs]    = IMPLOT_AUTO_COL;\n}\n\nvoid StyleColorsClassic(ImPlotStyle* dst) {\n    ImPlotStyle* style              = dst ? dst : &ImPlot::GetStyle();\n    ImVec4* colors                  = style->Colors;\n\n    style->MinorAlpha               = 0.5f;\n\n    colors[ImPlotCol_Line]          = IMPLOT_AUTO_COL;\n    colors[ImPlotCol_Fill]          = IMPLOT_AUTO_COL;\n    colors[ImPlotCol_MarkerOutline] = IMPLOT_AUTO_COL;\n    colors[ImPlotCol_MarkerFill]    = IMPLOT_AUTO_COL;\n    colors[ImPlotCol_ErrorBar]      = ImVec4(0.90f, 0.90f, 0.90f, 1.00f);\n    colors[ImPlotCol_FrameBg]       = ImVec4(0.43f, 0.43f, 0.43f, 0.39f);\n    colors[ImPlotCol_PlotBg]        = ImVec4(0.00f, 0.00f, 0.00f, 0.35f);\n    colors[ImPlotCol_PlotBorder]    = ImVec4(0.50f, 0.50f, 0.50f, 0.50f);\n    colors[ImPlotCol_LegendBg]      = ImVec4(0.11f, 0.11f, 0.14f, 0.92f);\n    colors[ImPlotCol_LegendBorder]  = ImVec4(0.50f, 0.50f, 0.50f, 0.50f);\n    colors[ImPlotCol_LegendText]    = ImVec4(0.90f, 0.90f, 0.90f, 1.00f);\n    colors[ImPlotCol_TitleText]     = ImVec4(0.90f, 0.90f, 0.90f, 1.00f);\n    colors[ImPlotCol_InlayText]     = ImVec4(0.90f, 0.90f, 0.90f, 1.00f);\n    colors[ImPlotCol_AxisText]      = ImVec4(0.90f, 0.90f, 0.90f, 1.00f);\n    colors[ImPlotCol_AxisGrid]      = ImVec4(0.90f, 0.90f, 0.90f, 0.25f);\n    colors[ImPlotCol_AxisTick]      = IMPLOT_AUTO_COL; // TODO\n    colors[ImPlotCol_AxisBg]        = IMPLOT_AUTO_COL; // TODO\n    colors[ImPlotCol_AxisBgHovered] = IMPLOT_AUTO_COL; // TODO\n    colors[ImPlotCol_AxisBgActive]  = IMPLOT_AUTO_COL; // TODO\n    colors[ImPlotCol_Selection]     = ImVec4(0.97f, 0.97f, 0.39f, 1.00f);\n    colors[ImPlotCol_Crosshairs]    = ImVec4(0.50f, 0.50f, 0.50f, 0.75f);\n}\n\nvoid StyleColorsDark(ImPlotStyle* dst) {\n    ImPlotStyle* style              = dst ? dst : &ImPlot::GetStyle();\n    ImVec4* colors                  = style->Colors;\n\n    style->MinorAlpha               = 0.25f;\n\n    colors[ImPlotCol_Line]          = IMPLOT_AUTO_COL;\n    colors[ImPlotCol_Fill]          = IMPLOT_AUTO_COL;\n    colors[ImPlotCol_MarkerOutline] = IMPLOT_AUTO_COL;\n    colors[ImPlotCol_MarkerFill]    = IMPLOT_AUTO_COL;\n    colors[ImPlotCol_ErrorBar]      = IMPLOT_AUTO_COL;\n    colors[ImPlotCol_FrameBg]       = ImVec4(1.00f, 1.00f, 1.00f, 0.07f);\n    colors[ImPlotCol_PlotBg]        = ImVec4(0.00f, 0.00f, 0.00f, 0.50f);\n    colors[ImPlotCol_PlotBorder]    = ImVec4(0.43f, 0.43f, 0.50f, 0.50f);\n    colors[ImPlotCol_LegendBg]      = ImVec4(0.08f, 0.08f, 0.08f, 0.94f);\n    colors[ImPlotCol_LegendBorder]  = ImVec4(0.43f, 0.43f, 0.50f, 0.50f);\n    colors[ImPlotCol_LegendText]    = ImVec4(1.00f, 1.00f, 1.00f, 1.00f);\n    colors[ImPlotCol_TitleText]     = ImVec4(1.00f, 1.00f, 1.00f, 1.00f);\n    colors[ImPlotCol_InlayText]     = ImVec4(1.00f, 1.00f, 1.00f, 1.00f);\n    colors[ImPlotCol_AxisText]      = ImVec4(1.00f, 1.00f, 1.00f, 1.00f);\n    colors[ImPlotCol_AxisGrid]      = ImVec4(1.00f, 1.00f, 1.00f, 0.25f);\n    colors[ImPlotCol_AxisTick]      = IMPLOT_AUTO_COL; // TODO\n    colors[ImPlotCol_AxisBg]        = IMPLOT_AUTO_COL; // TODO\n    colors[ImPlotCol_AxisBgHovered] = IMPLOT_AUTO_COL; // TODO\n    colors[ImPlotCol_AxisBgActive]  = IMPLOT_AUTO_COL; // TODO\n    colors[ImPlotCol_Selection]     = ImVec4(1.00f, 0.60f, 0.00f, 1.00f);\n    colors[ImPlotCol_Crosshairs]    = ImVec4(1.00f, 1.00f, 1.00f, 0.50f);\n}\n\nvoid StyleColorsLight(ImPlotStyle* dst) {\n    ImPlotStyle* style              = dst ? dst : &ImPlot::GetStyle();\n    ImVec4* colors                  = style->Colors;\n\n    style->MinorAlpha               = 1.0f;\n\n    colors[ImPlotCol_Line]          = IMPLOT_AUTO_COL;\n    colors[ImPlotCol_Fill]          = IMPLOT_AUTO_COL;\n    colors[ImPlotCol_MarkerOutline] = IMPLOT_AUTO_COL;\n    colors[ImPlotCol_MarkerFill]    = IMPLOT_AUTO_COL;\n    colors[ImPlotCol_ErrorBar]      = IMPLOT_AUTO_COL;\n    colors[ImPlotCol_FrameBg]       = ImVec4(1.00f, 1.00f, 1.00f, 1.00f);\n    colors[ImPlotCol_PlotBg]        = ImVec4(0.42f, 0.57f, 1.00f, 0.13f);\n    colors[ImPlotCol_PlotBorder]    = ImVec4(0.00f, 0.00f, 0.00f, 0.00f);\n    colors[ImPlotCol_LegendBg]      = ImVec4(1.00f, 1.00f, 1.00f, 0.98f);\n    colors[ImPlotCol_LegendBorder]  = ImVec4(0.82f, 0.82f, 0.82f, 0.80f);\n    colors[ImPlotCol_LegendText]    = ImVec4(0.00f, 0.00f, 0.00f, 1.00f);\n    colors[ImPlotCol_TitleText]     = ImVec4(0.00f, 0.00f, 0.00f, 1.00f);\n    colors[ImPlotCol_InlayText]     = ImVec4(0.00f, 0.00f, 0.00f, 1.00f);\n    colors[ImPlotCol_AxisText]      = ImVec4(0.00f, 0.00f, 0.00f, 1.00f);\n    colors[ImPlotCol_AxisGrid]      = ImVec4(1.00f, 1.00f, 1.00f, 1.00f);\n    colors[ImPlotCol_AxisTick]      = ImVec4(0.00f, 0.00f, 0.00f, 0.25f);\n    colors[ImPlotCol_AxisBg]        = IMPLOT_AUTO_COL; // TODO\n    colors[ImPlotCol_AxisBgHovered] = IMPLOT_AUTO_COL; // TODO\n    colors[ImPlotCol_AxisBgActive]  = IMPLOT_AUTO_COL; // TODO\n    colors[ImPlotCol_Selection]     = ImVec4(0.82f, 0.64f, 0.03f, 1.00f);\n    colors[ImPlotCol_Crosshairs]    = ImVec4(0.00f, 0.00f, 0.00f, 0.50f);\n}\n\n//-----------------------------------------------------------------------------\n// [SECTION] Obsolete Functions/Types\n//-----------------------------------------------------------------------------\n\n#ifndef IMPLOT_DISABLE_OBSOLETE_FUNCTIONS\n\nbool BeginPlot(const char* title, const char* x_label, const char* y1_label, const ImVec2& size,\n               ImPlotFlags flags, ImPlotAxisFlags x_flags, ImPlotAxisFlags y1_flags, ImPlotAxisFlags y2_flags, ImPlotAxisFlags y3_flags,\n               const char* y2_label, const char* y3_label)\n{\n    if (!BeginPlot(title, size, flags))\n        return false;\n    SetupAxis(ImAxis_X1, x_label, x_flags);\n    SetupAxis(ImAxis_Y1, y1_label, y1_flags);\n    if (ImHasFlag(flags, ImPlotFlags_YAxis2))\n        SetupAxis(ImAxis_Y2, y2_label, y2_flags);\n    if (ImHasFlag(flags, ImPlotFlags_YAxis3))\n        SetupAxis(ImAxis_Y3, y3_label, y3_flags);\n    return true;\n}\n\n#endif\n\n}  // namespace ImPlot\n"
  },
  {
    "path": "src/DesktopPlusUI/implot/implot.h",
    "content": "// MIT License\n\n// Copyright (c) 2023 Evan Pezent\n\n// Permission is hereby granted, free of charge, to any person obtaining a copy\n// of this software and associated documentation files (the \"Software\"), to deal\n// in the Software without restriction, including without limitation the rights\n// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n// copies of the Software, and to permit persons to whom the Software is\n// furnished to do so, subject to the following conditions:\n\n// The above copyright notice and this permission notice shall be included in all\n// copies or substantial portions of the Software.\n\n// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n// SOFTWARE.\n\n// ImPlot v0.16\n\n// Table of Contents:\n//\n// [SECTION] Macros and Defines\n// [SECTION] Enums and Types\n// [SECTION] Callbacks\n// [SECTION] Contexts\n// [SECTION] Begin/End Plot\n// [SECTION] Begin/End Subplot\n// [SECTION] Setup\n// [SECTION] SetNext\n// [SECTION] Plot Items\n// [SECTION] Plot Tools\n// [SECTION] Plot Utils\n// [SECTION] Legend Utils\n// [SECTION] Drag and Drop\n// [SECTION] Styling\n// [SECTION] Colormaps\n// [SECTION] Input Mapping\n// [SECTION] Miscellaneous\n// [SECTION] Demo\n// [SECTION] Obsolete API\n\n#pragma once\n#include \"imgui.h\"\n\n//-----------------------------------------------------------------------------\n// [SECTION] Macros and Defines\n//-----------------------------------------------------------------------------\n\n// Define attributes of all API symbols declarations (e.g. for DLL under Windows)\n// Using ImPlot via a shared library is not recommended, because we don't guarantee\n// backward nor forward ABI compatibility and also function call overhead. If you\n// do use ImPlot as a DLL, be sure to call SetImGuiContext (see Miscellanous section).\n#ifndef IMPLOT_API\n#define IMPLOT_API\n#endif\n\n// ImPlot version string.\n#define IMPLOT_VERSION \"0.16\"\n// Indicates variable should deduced automatically.\n#define IMPLOT_AUTO -1\n// Special color used to indicate that a color should be deduced automatically.\n#define IMPLOT_AUTO_COL ImVec4(0,0,0,-1)\n// Macro for templated plotting functions; keeps header clean.\n#define IMPLOT_TMP template <typename T> IMPLOT_API\n\n//-----------------------------------------------------------------------------\n// [SECTION] Enums and Types\n//-----------------------------------------------------------------------------\n\n// Forward declarations\nstruct ImPlotContext;             // ImPlot context (opaque struct, see implot_internal.h)\n\n// Enums/Flags\ntypedef int ImAxis;                   // -> enum ImAxis_\ntypedef int ImPlotFlags;              // -> enum ImPlotFlags_\ntypedef int ImPlotAxisFlags;          // -> enum ImPlotAxisFlags_\ntypedef int ImPlotSubplotFlags;       // -> enum ImPlotSubplotFlags_\ntypedef int ImPlotLegendFlags;        // -> enum ImPlotLegendFlags_\ntypedef int ImPlotMouseTextFlags;     // -> enum ImPlotMouseTextFlags_\ntypedef int ImPlotDragToolFlags;      // -> ImPlotDragToolFlags_\ntypedef int ImPlotColormapScaleFlags; // -> ImPlotColormapScaleFlags_\n\ntypedef int ImPlotItemFlags;          // -> ImPlotItemFlags_\ntypedef int ImPlotLineFlags;          // -> ImPlotLineFlags_\ntypedef int ImPlotScatterFlags;       // -> ImPlotScatterFlags\ntypedef int ImPlotStairsFlags;        // -> ImPlotStairsFlags_\ntypedef int ImPlotShadedFlags;        // -> ImPlotShadedFlags_\ntypedef int ImPlotBarsFlags;          // -> ImPlotBarsFlags_\ntypedef int ImPlotBarGroupsFlags;     // -> ImPlotBarGroupsFlags_\ntypedef int ImPlotErrorBarsFlags;     // -> ImPlotErrorBarsFlags_\ntypedef int ImPlotStemsFlags;         // -> ImPlotStemsFlags_\ntypedef int ImPlotInfLinesFlags;      // -> ImPlotInfLinesFlags_\ntypedef int ImPlotPieChartFlags;      // -> ImPlotPieChartFlags_\ntypedef int ImPlotHeatmapFlags;       // -> ImPlotHeatmapFlags_\ntypedef int ImPlotHistogramFlags;     // -> ImPlotHistogramFlags_\ntypedef int ImPlotDigitalFlags;       // -> ImPlotDigitalFlags_\ntypedef int ImPlotImageFlags;         // -> ImPlotImageFlags_\ntypedef int ImPlotTextFlags;          // -> ImPlotTextFlags_\ntypedef int ImPlotDummyFlags;         // -> ImPlotDummyFlags_\n\ntypedef int ImPlotCond;               // -> enum ImPlotCond_\ntypedef int ImPlotCol;                // -> enum ImPlotCol_\ntypedef int ImPlotStyleVar;           // -> enum ImPlotStyleVar_\ntypedef int ImPlotScale;              // -> enum ImPlotScale_\ntypedef int ImPlotMarker;             // -> enum ImPlotMarker_\ntypedef int ImPlotColormap;           // -> enum ImPlotColormap_\ntypedef int ImPlotLocation;           // -> enum ImPlotLocation_\ntypedef int ImPlotBin;                // -> enum ImPlotBin_\n\n// Axis indices. The values assigned may change; NEVER hardcode these.\nenum ImAxis_ {\n    // horizontal axes\n    ImAxis_X1 = 0, // enabled by default\n    ImAxis_X2,     // disabled by default\n    ImAxis_X3,     // disabled by default\n    // vertical axes\n    ImAxis_Y1,     // enabled by default\n    ImAxis_Y2,     // disabled by default\n    ImAxis_Y3,     // disabled by default\n    // bookeeping\n    ImAxis_COUNT\n};\n\n// Options for plots (see BeginPlot).\nenum ImPlotFlags_ {\n    ImPlotFlags_None          = 0,       // default\n    ImPlotFlags_NoTitle       = 1 << 0,  // the plot title will not be displayed (titles are also hidden if preceeded by double hashes, e.g. \"##MyPlot\")\n    ImPlotFlags_NoLegend      = 1 << 1,  // the legend will not be displayed\n    ImPlotFlags_NoMouseText   = 1 << 2,  // the mouse position, in plot coordinates, will not be displayed inside of the plot\n    ImPlotFlags_NoInputs      = 1 << 3,  // the user will not be able to interact with the plot\n    ImPlotFlags_NoMenus       = 1 << 4,  // the user will not be able to open context menus\n    ImPlotFlags_NoBoxSelect   = 1 << 5,  // the user will not be able to box-select\n    ImPlotFlags_NoChild       = 1 << 6,  // a child window region will not be used to capture mouse scroll (can boost performance for single ImGui window applications)\n    ImPlotFlags_NoFrame       = 1 << 7,  // the ImGui frame will not be rendered\n    ImPlotFlags_Equal         = 1 << 8,  // x and y axes pairs will be constrained to have the same units/pixel\n    ImPlotFlags_Crosshairs    = 1 << 9,  // the default mouse cursor will be replaced with a crosshair when hovered\n    ImPlotFlags_CanvasOnly    = ImPlotFlags_NoTitle | ImPlotFlags_NoLegend | ImPlotFlags_NoMenus | ImPlotFlags_NoBoxSelect | ImPlotFlags_NoMouseText\n};\n\n// Options for plot axes (see SetupAxis).\nenum ImPlotAxisFlags_ {\n    ImPlotAxisFlags_None          = 0,       // default\n    ImPlotAxisFlags_NoLabel       = 1 << 0,  // the axis label will not be displayed (axis labels are also hidden if the supplied string name is nullptr)\n    ImPlotAxisFlags_NoGridLines   = 1 << 1,  // no grid lines will be displayed\n    ImPlotAxisFlags_NoTickMarks   = 1 << 2,  // no tick marks will be displayed\n    ImPlotAxisFlags_NoTickLabels  = 1 << 3,  // no text labels will be displayed\n    ImPlotAxisFlags_NoInitialFit  = 1 << 4,  // axis will not be initially fit to data extents on the first rendered frame\n    ImPlotAxisFlags_NoMenus       = 1 << 5,  // the user will not be able to open context menus with right-click\n    ImPlotAxisFlags_NoSideSwitch  = 1 << 6,  // the user will not be able to switch the axis side by dragging it\n    ImPlotAxisFlags_NoHighlight   = 1 << 7,  // the axis will not have its background highlighted when hovered or held\n    ImPlotAxisFlags_Opposite      = 1 << 8,  // axis ticks and labels will be rendered on the conventionally opposite side (i.e, right or top)\n    ImPlotAxisFlags_Foreground    = 1 << 9,  // grid lines will be displayed in the foreground (i.e. on top of data) instead of the background\n    ImPlotAxisFlags_Invert        = 1 << 10, // the axis will be inverted\n    ImPlotAxisFlags_AutoFit       = 1 << 11, // axis will be auto-fitting to data extents\n    ImPlotAxisFlags_RangeFit      = 1 << 12, // axis will only fit points if the point is in the visible range of the **orthogonal** axis\n    ImPlotAxisFlags_PanStretch    = 1 << 13, // panning in a locked or constrained state will cause the axis to stretch if possible\n    ImPlotAxisFlags_LockMin       = 1 << 14, // the axis minimum value will be locked when panning/zooming\n    ImPlotAxisFlags_LockMax       = 1 << 15, // the axis maximum value will be locked when panning/zooming\n    ImPlotAxisFlags_Lock          = ImPlotAxisFlags_LockMin | ImPlotAxisFlags_LockMax,\n    ImPlotAxisFlags_NoDecorations = ImPlotAxisFlags_NoLabel | ImPlotAxisFlags_NoGridLines | ImPlotAxisFlags_NoTickMarks | ImPlotAxisFlags_NoTickLabels,\n    ImPlotAxisFlags_AuxDefault    = ImPlotAxisFlags_NoGridLines | ImPlotAxisFlags_Opposite\n};\n\n// Options for subplots (see BeginSubplot)\nenum ImPlotSubplotFlags_ {\n    ImPlotSubplotFlags_None        = 0,       // default\n    ImPlotSubplotFlags_NoTitle     = 1 << 0,  // the subplot title will not be displayed (titles are also hidden if preceeded by double hashes, e.g. \"##MySubplot\")\n    ImPlotSubplotFlags_NoLegend    = 1 << 1,  // the legend will not be displayed (only applicable if ImPlotSubplotFlags_ShareItems is enabled)\n    ImPlotSubplotFlags_NoMenus     = 1 << 2,  // the user will not be able to open context menus with right-click\n    ImPlotSubplotFlags_NoResize    = 1 << 3,  // resize splitters between subplot cells will be not be provided\n    ImPlotSubplotFlags_NoAlign     = 1 << 4,  // subplot edges will not be aligned vertically or horizontally\n    ImPlotSubplotFlags_ShareItems  = 1 << 5,  // items across all subplots will be shared and rendered into a single legend entry\n    ImPlotSubplotFlags_LinkRows    = 1 << 6,  // link the y-axis limits of all plots in each row (does not apply to auxiliary axes)\n    ImPlotSubplotFlags_LinkCols    = 1 << 7,  // link the x-axis limits of all plots in each column (does not apply to auxiliary axes)\n    ImPlotSubplotFlags_LinkAllX    = 1 << 8,  // link the x-axis limits in every plot in the subplot (does not apply to auxiliary axes)\n    ImPlotSubplotFlags_LinkAllY    = 1 << 9,  // link the y-axis limits in every plot in the subplot (does not apply to auxiliary axes)\n    ImPlotSubplotFlags_ColMajor    = 1 << 10  // subplots are added in column major order instead of the default row major order\n};\n\n// Options for legends (see SetupLegend)\nenum ImPlotLegendFlags_ {\n    ImPlotLegendFlags_None            = 0,      // default\n    ImPlotLegendFlags_NoButtons       = 1 << 0, // legend icons will not function as hide/show buttons\n    ImPlotLegendFlags_NoHighlightItem = 1 << 1, // plot items will not be highlighted when their legend entry is hovered\n    ImPlotLegendFlags_NoHighlightAxis = 1 << 2, // axes will not be highlighted when legend entries are hovered (only relevant if x/y-axis count > 1)\n    ImPlotLegendFlags_NoMenus         = 1 << 3, // the user will not be able to open context menus with right-click\n    ImPlotLegendFlags_Outside         = 1 << 4, // legend will be rendered outside of the plot area\n    ImPlotLegendFlags_Horizontal      = 1 << 5, // legend entries will be displayed horizontally\n    ImPlotLegendFlags_Sort            = 1 << 6, // legend entries will be displayed in alphabetical order\n};\n\n// Options for mouse hover text (see SetupMouseText)\nenum ImPlotMouseTextFlags_ {\n    ImPlotMouseTextFlags_None        = 0,      // default\n    ImPlotMouseTextFlags_NoAuxAxes   = 1 << 0, // only show the mouse position for primary axes\n    ImPlotMouseTextFlags_NoFormat    = 1 << 1, // axes label formatters won't be used to render text\n    ImPlotMouseTextFlags_ShowAlways  = 1 << 2, // always display mouse position even if plot not hovered\n};\n\n// Options for DragPoint, DragLine, DragRect\nenum ImPlotDragToolFlags_ {\n    ImPlotDragToolFlags_None      = 0,      // default\n    ImPlotDragToolFlags_NoCursors = 1 << 0, // drag tools won't change cursor icons when hovered or held\n    ImPlotDragToolFlags_NoFit     = 1 << 1, // the drag tool won't be considered for plot fits\n    ImPlotDragToolFlags_NoInputs  = 1 << 2, // lock the tool from user inputs\n    ImPlotDragToolFlags_Delayed   = 1 << 3, // tool rendering will be delayed one frame; useful when applying position-constraints\n};\n\n// Flags for ColormapScale\nenum ImPlotColormapScaleFlags_ {\n    ImPlotColormapScaleFlags_None     = 0,      // default\n    ImPlotColormapScaleFlags_NoLabel  = 1 << 0, // the colormap axis label will not be displayed\n    ImPlotColormapScaleFlags_Opposite = 1 << 1, // render the colormap label and tick labels on the opposite side\n    ImPlotColormapScaleFlags_Invert   = 1 << 2, // invert the colormap bar and axis scale (this only affects rendering; if you only want to reverse the scale mapping, make scale_min > scale_max)\n};\n\n// Flags for ANY PlotX function\nenum ImPlotItemFlags_ {\n    ImPlotItemFlags_None     = 0,\n    ImPlotItemFlags_NoLegend = 1 << 0, // the item won't have a legend entry displayed\n    ImPlotItemFlags_NoFit    = 1 << 1, // the item won't be considered for plot fits\n};\n\n// Flags for PlotLine\nenum ImPlotLineFlags_ {\n    ImPlotLineFlags_None        = 0,       // default\n    ImPlotLineFlags_Segments    = 1 << 10, // a line segment will be rendered from every two consecutive points\n    ImPlotLineFlags_Loop        = 1 << 11, // the last and first point will be connected to form a closed loop\n    ImPlotLineFlags_SkipNaN     = 1 << 12, // NaNs values will be skipped instead of rendered as missing data\n    ImPlotLineFlags_NoClip      = 1 << 13, // markers (if displayed) on the edge of a plot will not be clipped\n    ImPlotLineFlags_Shaded      = 1 << 14, // a filled region between the line and horizontal origin will be rendered; use PlotShaded for more advanced cases\n};\n\n// Flags for PlotScatter\nenum ImPlotScatterFlags_ {\n    ImPlotScatterFlags_None   = 0,       // default\n    ImPlotScatterFlags_NoClip = 1 << 10, // markers on the edge of a plot will not be clipped\n};\n\n// Flags for PlotStairs\nenum ImPlotStairsFlags_ {\n    ImPlotStairsFlags_None     = 0,       // default\n    ImPlotStairsFlags_PreStep  = 1 << 10, // the y value is continued constantly to the left from every x position, i.e. the interval (x[i-1], x[i]] has the value y[i]\n    ImPlotStairsFlags_Shaded   = 1 << 11  // a filled region between the stairs and horizontal origin will be rendered; use PlotShaded for more advanced cases\n};\n\n// Flags for PlotShaded (placeholder)\nenum ImPlotShadedFlags_ {\n    ImPlotShadedFlags_None  = 0 // default\n};\n\n// Flags for PlotBars\nenum ImPlotBarsFlags_ {\n    ImPlotBarsFlags_None         = 0,       // default\n    ImPlotBarsFlags_Horizontal   = 1 << 10, // bars will be rendered horizontally on the current y-axis\n};\n\n// Flags for PlotBarGroups\nenum ImPlotBarGroupsFlags_ {\n    ImPlotBarGroupsFlags_None        = 0,       // default\n    ImPlotBarGroupsFlags_Horizontal  = 1 << 10, // bar groups will be rendered horizontally on the current y-axis\n    ImPlotBarGroupsFlags_Stacked     = 1 << 11, // items in a group will be stacked on top of each other\n};\n\n// Flags for PlotErrorBars\nenum ImPlotErrorBarsFlags_ {\n    ImPlotErrorBarsFlags_None       = 0,       // default\n    ImPlotErrorBarsFlags_Horizontal = 1 << 10, // error bars will be rendered horizontally on the current y-axis\n};\n\n// Flags for PlotStems\nenum ImPlotStemsFlags_ {\n    ImPlotStemsFlags_None       = 0,       // default\n    ImPlotStemsFlags_Horizontal = 1 << 10, // stems will be rendered horizontally on the current y-axis\n};\n\n// Flags for PlotInfLines\nenum ImPlotInfLinesFlags_ {\n    ImPlotInfLinesFlags_None       = 0,      // default\n    ImPlotInfLinesFlags_Horizontal = 1 << 10 // lines will be rendered horizontally on the current y-axis\n};\n\n// Flags for PlotPieChart\nenum ImPlotPieChartFlags_ {\n    ImPlotPieChartFlags_None      = 0,      // default\n    ImPlotPieChartFlags_Normalize = 1 << 10 // force normalization of pie chart values (i.e. always make a full circle if sum < 0)\n};\n\n// Flags for PlotHeatmap\nenum ImPlotHeatmapFlags_ {\n    ImPlotHeatmapFlags_None     = 0,       // default\n    ImPlotHeatmapFlags_ColMajor = 1 << 10, // data will be read in column major order\n};\n\n// Flags for PlotHistogram and PlotHistogram2D\nenum ImPlotHistogramFlags_ {\n    ImPlotHistogramFlags_None       = 0,       // default\n    ImPlotHistogramFlags_Horizontal = 1 << 10, // histogram bars will be rendered horizontally (not supported by PlotHistogram2D)\n    ImPlotHistogramFlags_Cumulative = 1 << 11, // each bin will contain its count plus the counts of all previous bins (not supported by PlotHistogram2D)\n    ImPlotHistogramFlags_Density    = 1 << 12, // counts will be normalized, i.e. the PDF will be visualized, or the CDF will be visualized if Cumulative is also set\n    ImPlotHistogramFlags_NoOutliers = 1 << 13, // exclude values outside the specifed histogram range from the count toward normalizing and cumulative counts\n    ImPlotHistogramFlags_ColMajor   = 1 << 14  // data will be read in column major order (not supported by PlotHistogram)\n};\n\n// Flags for PlotDigital (placeholder)\nenum ImPlotDigitalFlags_ {\n    ImPlotDigitalFlags_None = 0 // default\n};\n\n// Flags for PlotImage (placeholder)\nenum ImPlotImageFlags_ {\n    ImPlotImageFlags_None = 0 // default\n};\n\n// Flags for PlotText\nenum ImPlotTextFlags_ {\n    ImPlotTextFlags_None     = 0,       // default\n    ImPlotTextFlags_Vertical = 1 << 10  // text will be rendered vertically\n};\n\n// Flags for PlotDummy (placeholder)\nenum ImPlotDummyFlags_ {\n    ImPlotDummyFlags_None = 0 // default\n};\n\n// Represents a condition for SetupAxisLimits etc. (same as ImGuiCond, but we only support a subset of those enums)\nenum ImPlotCond_\n{\n    ImPlotCond_None   = ImGuiCond_None,    // No condition (always set the variable), same as _Always\n    ImPlotCond_Always = ImGuiCond_Always,  // No condition (always set the variable)\n    ImPlotCond_Once   = ImGuiCond_Once,    // Set the variable once per runtime session (only the first call will succeed)\n};\n\n// Plot styling colors.\nenum ImPlotCol_ {\n    // item styling colors\n    ImPlotCol_Line,          // plot line/outline color (defaults to next unused color in current colormap)\n    ImPlotCol_Fill,          // plot fill color for bars (defaults to the current line color)\n    ImPlotCol_MarkerOutline, // marker outline color (defaults to the current line color)\n    ImPlotCol_MarkerFill,    // marker fill color (defaults to the current line color)\n    ImPlotCol_ErrorBar,      // error bar color (defaults to ImGuiCol_Text)\n    // plot styling colors\n    ImPlotCol_FrameBg,       // plot frame background color (defaults to ImGuiCol_FrameBg)\n    ImPlotCol_PlotBg,        // plot area background color (defaults to ImGuiCol_WindowBg)\n    ImPlotCol_PlotBorder,    // plot area border color (defaults to ImGuiCol_Border)\n    ImPlotCol_LegendBg,      // legend background color (defaults to ImGuiCol_PopupBg)\n    ImPlotCol_LegendBorder,  // legend border color (defaults to ImPlotCol_PlotBorder)\n    ImPlotCol_LegendText,    // legend text color (defaults to ImPlotCol_InlayText)\n    ImPlotCol_TitleText,     // plot title text color (defaults to ImGuiCol_Text)\n    ImPlotCol_InlayText,     // color of text appearing inside of plots (defaults to ImGuiCol_Text)\n    ImPlotCol_AxisText,      // axis label and tick lables color (defaults to ImGuiCol_Text)\n    ImPlotCol_AxisGrid,      // axis grid color (defaults to 25% ImPlotCol_AxisText)\n    ImPlotCol_AxisTick,      // axis tick color (defaults to AxisGrid)\n    ImPlotCol_AxisBg,        // background color of axis hover region (defaults to transparent)\n    ImPlotCol_AxisBgHovered, // axis hover color (defaults to ImGuiCol_ButtonHovered)\n    ImPlotCol_AxisBgActive,  // axis active color (defaults to ImGuiCol_ButtonActive)\n    ImPlotCol_Selection,     // box-selection color (defaults to yellow)\n    ImPlotCol_Crosshairs,    // crosshairs color (defaults to ImPlotCol_PlotBorder)\n    ImPlotCol_COUNT\n};\n\n// Plot styling variables.\nenum ImPlotStyleVar_ {\n    // item styling variables\n    ImPlotStyleVar_LineWeight,         // float,  plot item line weight in pixels\n    ImPlotStyleVar_Marker,             // int,    marker specification\n    ImPlotStyleVar_MarkerSize,         // float,  marker size in pixels (roughly the marker's \"radius\")\n    ImPlotStyleVar_MarkerWeight,       // float,  plot outline weight of markers in pixels\n    ImPlotStyleVar_FillAlpha,          // float,  alpha modifier applied to all plot item fills\n    ImPlotStyleVar_ErrorBarSize,       // float,  error bar whisker width in pixels\n    ImPlotStyleVar_ErrorBarWeight,     // float,  error bar whisker weight in pixels\n    ImPlotStyleVar_DigitalBitHeight,   // float,  digital channels bit height (at 1) in pixels\n    ImPlotStyleVar_DigitalBitGap,      // float,  digital channels bit padding gap in pixels\n    // plot styling variables\n    ImPlotStyleVar_PlotBorderSize,     // float,  thickness of border around plot area\n    ImPlotStyleVar_MinorAlpha,         // float,  alpha multiplier applied to minor axis grid lines\n    ImPlotStyleVar_MajorTickLen,       // ImVec2, major tick lengths for X and Y axes\n    ImPlotStyleVar_MinorTickLen,       // ImVec2, minor tick lengths for X and Y axes\n    ImPlotStyleVar_MajorTickSize,      // ImVec2, line thickness of major ticks\n    ImPlotStyleVar_MinorTickSize,      // ImVec2, line thickness of minor ticks\n    ImPlotStyleVar_MajorGridSize,      // ImVec2, line thickness of major grid lines\n    ImPlotStyleVar_MinorGridSize,      // ImVec2, line thickness of minor grid lines\n    ImPlotStyleVar_PlotPadding,        // ImVec2, padding between widget frame and plot area, labels, or outside legends (i.e. main padding)\n    ImPlotStyleVar_LabelPadding,       // ImVec2, padding between axes labels, tick labels, and plot edge\n    ImPlotStyleVar_LegendPadding,      // ImVec2, legend padding from plot edges\n    ImPlotStyleVar_LegendInnerPadding, // ImVec2, legend inner padding from legend edges\n    ImPlotStyleVar_LegendSpacing,      // ImVec2, spacing between legend entries\n    ImPlotStyleVar_MousePosPadding,    // ImVec2, padding between plot edge and interior info text\n    ImPlotStyleVar_AnnotationPadding,  // ImVec2, text padding around annotation labels\n    ImPlotStyleVar_FitPadding,         // ImVec2, additional fit padding as a percentage of the fit extents (e.g. ImVec2(0.1f,0.1f) adds 10% to the fit extents of X and Y)\n    ImPlotStyleVar_PlotDefaultSize,    // ImVec2, default size used when ImVec2(0,0) is passed to BeginPlot\n    ImPlotStyleVar_PlotMinSize,        // ImVec2, minimum size plot frame can be when shrunk\n    ImPlotStyleVar_COUNT\n};\n\n// Axis scale\nenum ImPlotScale_ {\n    ImPlotScale_Linear = 0, // default linear scale\n    ImPlotScale_Time,       // date/time scale\n    ImPlotScale_Log10,      // base 10 logartithmic scale\n    ImPlotScale_SymLog,     // symmetric log scale\n};\n\n// Marker specifications.\nenum ImPlotMarker_ {\n    ImPlotMarker_None = -1, // no marker\n    ImPlotMarker_Circle,    // a circle marker (default)\n    ImPlotMarker_Square,    // a square maker\n    ImPlotMarker_Diamond,   // a diamond marker\n    ImPlotMarker_Up,        // an upward-pointing triangle marker\n    ImPlotMarker_Down,      // an downward-pointing triangle marker\n    ImPlotMarker_Left,      // an leftward-pointing triangle marker\n    ImPlotMarker_Right,     // an rightward-pointing triangle marker\n    ImPlotMarker_Cross,     // a cross marker (not fillable)\n    ImPlotMarker_Plus,      // a plus marker (not fillable)\n    ImPlotMarker_Asterisk,  // a asterisk marker (not fillable)\n    ImPlotMarker_COUNT\n};\n\n// Built-in colormaps\nenum ImPlotColormap_ {\n    ImPlotColormap_Deep     = 0,   // a.k.a. seaborn deep             (qual=true,  n=10) (default)\n    ImPlotColormap_Dark     = 1,   // a.k.a. matplotlib \"Set1\"        (qual=true,  n=9 )\n    ImPlotColormap_Pastel   = 2,   // a.k.a. matplotlib \"Pastel1\"     (qual=true,  n=9 )\n    ImPlotColormap_Paired   = 3,   // a.k.a. matplotlib \"Paired\"      (qual=true,  n=12)\n    ImPlotColormap_Viridis  = 4,   // a.k.a. matplotlib \"viridis\"     (qual=false, n=11)\n    ImPlotColormap_Plasma   = 5,   // a.k.a. matplotlib \"plasma\"      (qual=false, n=11)\n    ImPlotColormap_Hot      = 6,   // a.k.a. matplotlib/MATLAB \"hot\"  (qual=false, n=11)\n    ImPlotColormap_Cool     = 7,   // a.k.a. matplotlib/MATLAB \"cool\" (qual=false, n=11)\n    ImPlotColormap_Pink     = 8,   // a.k.a. matplotlib/MATLAB \"pink\" (qual=false, n=11)\n    ImPlotColormap_Jet      = 9,   // a.k.a. MATLAB \"jet\"             (qual=false, n=11)\n    ImPlotColormap_Twilight = 10,  // a.k.a. matplotlib \"twilight\"    (qual=false, n=11)\n    ImPlotColormap_RdBu     = 11,  // red/blue, Color Brewer          (qual=false, n=11)\n    ImPlotColormap_BrBG     = 12,  // brown/blue-green, Color Brewer  (qual=false, n=11)\n    ImPlotColormap_PiYG     = 13,  // pink/yellow-green, Color Brewer (qual=false, n=11)\n    ImPlotColormap_Spectral = 14,  // color spectrum, Color Brewer    (qual=false, n=11)\n    ImPlotColormap_Greys    = 15,  // white/black                     (qual=false, n=2 )\n};\n\n// Used to position items on a plot (e.g. legends, labels, etc.)\nenum ImPlotLocation_ {\n    ImPlotLocation_Center    = 0,                                          // center-center\n    ImPlotLocation_North     = 1 << 0,                                     // top-center\n    ImPlotLocation_South     = 1 << 1,                                     // bottom-center\n    ImPlotLocation_West      = 1 << 2,                                     // center-left\n    ImPlotLocation_East      = 1 << 3,                                     // center-right\n    ImPlotLocation_NorthWest = ImPlotLocation_North | ImPlotLocation_West, // top-left\n    ImPlotLocation_NorthEast = ImPlotLocation_North | ImPlotLocation_East, // top-right\n    ImPlotLocation_SouthWest = ImPlotLocation_South | ImPlotLocation_West, // bottom-left\n    ImPlotLocation_SouthEast = ImPlotLocation_South | ImPlotLocation_East  // bottom-right\n};\n\n// Enums for different automatic histogram binning methods (k = bin count or w = bin width)\nenum ImPlotBin_ {\n    ImPlotBin_Sqrt    = -1, // k = sqrt(n)\n    ImPlotBin_Sturges = -2, // k = 1 + log2(n)\n    ImPlotBin_Rice    = -3, // k = 2 * cbrt(n)\n    ImPlotBin_Scott   = -4, // w = 3.49 * sigma / cbrt(n)\n};\n\n// Double precision version of ImVec2 used by ImPlot. Extensible by end users.\nstruct ImPlotPoint {\n    double x, y;\n    ImPlotPoint()                         { x = y = 0.0;      }\n    ImPlotPoint(double _x, double _y)     { x = _x; y = _y;   }\n    ImPlotPoint(const ImVec2& p)          { x = (double)p.x; y = (double)p.y; }\n    double  operator[] (size_t idx) const { return (&x)[idx]; }\n    double& operator[] (size_t idx)       { return (&x)[idx]; }\n#ifdef IMPLOT_POINT_CLASS_EXTRA\n    IMPLOT_POINT_CLASS_EXTRA     // Define additional constructors and implicit cast operators in imconfig.h\n                                 // to convert back and forth between your math types and ImPlotPoint.\n#endif\n};\n\n// Range defined by a min/max value.\nstruct ImPlotRange {\n    double Min, Max;\n    ImPlotRange()                         { Min = 0; Max = 0;                                         }\n    ImPlotRange(double _min, double _max) { Min = _min; Max = _max;                                   }\n    bool Contains(double value) const     { return value >= Min && value <= Max;                      }\n    double Size() const                   { return Max - Min;                                         }\n    double Clamp(double value) const      { return (value < Min) ? Min : (value > Max) ? Max : value; }\n};\n\n// Combination of two range limits for X and Y axes. Also an AABB defined by Min()/Max().\nstruct ImPlotRect {\n    ImPlotRange X, Y;\n    ImPlotRect()                                                       {                                                               }\n    ImPlotRect(double x_min, double x_max, double y_min, double y_max) { X.Min = x_min; X.Max = x_max; Y.Min = y_min; Y.Max = y_max;   }\n    bool Contains(const ImPlotPoint& p) const                          { return Contains(p.x, p.y);                                    }\n    bool Contains(double x, double y) const                            { return X.Contains(x) && Y.Contains(y);                        }\n    ImPlotPoint Size() const                                           { return ImPlotPoint(X.Size(), Y.Size());                       }\n    ImPlotPoint Clamp(const ImPlotPoint& p)                            { return Clamp(p.x, p.y);                                       }\n    ImPlotPoint Clamp(double x, double y)                              { return ImPlotPoint(X.Clamp(x),Y.Clamp(y));                    }\n    ImPlotPoint Min() const                                            { return ImPlotPoint(X.Min, Y.Min);                             }\n    ImPlotPoint Max() const                                            { return ImPlotPoint(X.Max, Y.Max);                             }\n};\n\n// Plot style structure\nstruct ImPlotStyle {\n    // item styling variables\n    float   LineWeight;              // = 1,      item line weight in pixels\n    int     Marker;                  // = ImPlotMarker_None, marker specification\n    float   MarkerSize;              // = 4,      marker size in pixels (roughly the marker's \"radius\")\n    float   MarkerWeight;            // = 1,      outline weight of markers in pixels\n    float   FillAlpha;               // = 1,      alpha modifier applied to plot fills\n    float   ErrorBarSize;            // = 5,      error bar whisker width in pixels\n    float   ErrorBarWeight;          // = 1.5,    error bar whisker weight in pixels\n    float   DigitalBitHeight;        // = 8,      digital channels bit height (at y = 1.0f) in pixels\n    float   DigitalBitGap;           // = 4,      digital channels bit padding gap in pixels\n    // plot styling variables\n    float   PlotBorderSize;          // = 1,      line thickness of border around plot area\n    float   MinorAlpha;              // = 0.25    alpha multiplier applied to minor axis grid lines\n    ImVec2  MajorTickLen;            // = 10,10   major tick lengths for X and Y axes\n    ImVec2  MinorTickLen;            // = 5,5     minor tick lengths for X and Y axes\n    ImVec2  MajorTickSize;           // = 1,1     line thickness of major ticks\n    ImVec2  MinorTickSize;           // = 1,1     line thickness of minor ticks\n    ImVec2  MajorGridSize;           // = 1,1     line thickness of major grid lines\n    ImVec2  MinorGridSize;           // = 1,1     line thickness of minor grid lines\n    ImVec2  PlotPadding;             // = 10,10   padding between widget frame and plot area, labels, or outside legends (i.e. main padding)\n    ImVec2  LabelPadding;            // = 5,5     padding between axes labels, tick labels, and plot edge\n    ImVec2  LegendPadding;           // = 10,10   legend padding from plot edges\n    ImVec2  LegendInnerPadding;      // = 5,5     legend inner padding from legend edges\n    ImVec2  LegendSpacing;           // = 5,0     spacing between legend entries\n    ImVec2  MousePosPadding;         // = 10,10   padding between plot edge and interior mouse location text\n    ImVec2  AnnotationPadding;       // = 2,2     text padding around annotation labels\n    ImVec2  FitPadding;              // = 0,0     additional fit padding as a percentage of the fit extents (e.g. ImVec2(0.1f,0.1f) adds 10% to the fit extents of X and Y)\n    ImVec2  PlotDefaultSize;         // = 400,300 default size used when ImVec2(0,0) is passed to BeginPlot\n    ImVec2  PlotMinSize;             // = 200,150 minimum size plot frame can be when shrunk\n    // style colors\n    ImVec4  Colors[ImPlotCol_COUNT]; // Array of styling colors. Indexable with ImPlotCol_ enums.\n    // colormap\n    ImPlotColormap Colormap;         // The current colormap. Set this to either an ImPlotColormap_ enum or an index returned by AddColormap.\n    // settings/flags\n    bool    UseLocalTime;            // = false,  axis labels will be formatted for your timezone when ImPlotAxisFlag_Time is enabled\n    bool    UseISO8601;              // = false,  dates will be formatted according to ISO 8601 where applicable (e.g. YYYY-MM-DD, YYYY-MM, --MM-DD, etc.)\n    bool    Use24HourClock;          // = false,  times will be formatted using a 24 hour clock\n    IMPLOT_API ImPlotStyle();\n};\n\n// Support for legacy versions\n#if (IMGUI_VERSION_NUM < 18716) // Renamed in 1.88\n#define ImGuiMod_None       0\n#define ImGuiMod_Ctrl       ImGuiKeyModFlags_Ctrl\n#define ImGuiMod_Shift      ImGuiKeyModFlags_Shift\n#define ImGuiMod_Alt        ImGuiKeyModFlags_Alt\n#define ImGuiMod_Super      ImGuiKeyModFlags_Super\n#elif (IMGUI_VERSION_NUM < 18823) // Renamed in 1.89, sorry\n#define ImGuiMod_None       0\n#define ImGuiMod_Ctrl       ImGuiModFlags_Ctrl\n#define ImGuiMod_Shift      ImGuiModFlags_Shift\n#define ImGuiMod_Alt        ImGuiModFlags_Alt\n#define ImGuiMod_Super      ImGuiModFlags_Super\n#endif\n\n// Input mapping structure. Default values listed. See also MapInputDefault, MapInputReverse.\nstruct ImPlotInputMap {\n    ImGuiMouseButton Pan;           // LMB    enables panning when held,\n    int              PanMod;        // none   optional modifier that must be held for panning/fitting\n    ImGuiMouseButton Fit;           // LMB    initiates fit when double clicked\n    ImGuiMouseButton Select;        // RMB    begins box selection when pressed and confirms selection when released\n    ImGuiMouseButton SelectCancel;  // LMB    cancels active box selection when pressed; cannot be same as Select\n    int              SelectMod;     // none   optional modifier that must be held for box selection\n    int              SelectHorzMod; // Alt    expands active box selection horizontally to plot edge when held\n    int              SelectVertMod; // Shift  expands active box selection vertically to plot edge when held\n    ImGuiMouseButton Menu;          // RMB    opens context menus (if enabled) when clicked\n    int              OverrideMod;   // Ctrl   when held, all input is ignored; used to enable axis/plots as DND sources\n    int              ZoomMod;       // none   optional modifier that must be held for scroll wheel zooming\n    float            ZoomRate;      // 0.1f   zoom rate for scroll (e.g. 0.1f = 10% plot range every scroll click); make negative to invert\n    IMPLOT_API ImPlotInputMap();\n};\n\n//-----------------------------------------------------------------------------\n// [SECTION] Callbacks\n//-----------------------------------------------------------------------------\n\n// Callback signature for axis tick label formatter.\ntypedef int (*ImPlotFormatter)(double value, char* buff, int size, void* user_data);\n\n// Callback signature for data getter.\ntypedef ImPlotPoint (*ImPlotGetter)(int idx, void* user_data);\n\n// Callback signature for axis transform.\ntypedef double (*ImPlotTransform)(double value, void* user_data);\n\nnamespace ImPlot {\n\n//-----------------------------------------------------------------------------\n// [SECTION] Contexts\n//-----------------------------------------------------------------------------\n\n// Creates a new ImPlot context. Call this after ImGui::CreateContext.\nIMPLOT_API ImPlotContext* CreateContext();\n// Destroys an ImPlot context. Call this before ImGui::DestroyContext. nullptr = destroy current context.\nIMPLOT_API void DestroyContext(ImPlotContext* ctx = nullptr);\n// Returns the current ImPlot context. nullptr if no context has ben set.\nIMPLOT_API ImPlotContext* GetCurrentContext();\n// Sets the current ImPlot context.\nIMPLOT_API void SetCurrentContext(ImPlotContext* ctx);\n\n// Sets the current **ImGui** context. This is ONLY necessary if you are compiling\n// ImPlot as a DLL (not recommended) separate from your ImGui compilation. It\n// sets the global variable GImGui, which is not shared across DLL boundaries.\n// See GImGui documentation in imgui.cpp for more details.\nIMPLOT_API void SetImGuiContext(ImGuiContext* ctx);\n\n//-----------------------------------------------------------------------------\n// [SECTION] Begin/End Plot\n//-----------------------------------------------------------------------------\n\n// Starts a 2D plotting context. If this function returns true, EndPlot() MUST\n// be called! You are encouraged to use the following convention:\n//\n// if (BeginPlot(...)) {\n//     PlotLine(...);\n//     ...\n//     EndPlot();\n// }\n//\n// Important notes:\n//\n// - #title_id must be unique to the current ImGui ID scope. If you need to avoid ID\n//   collisions or don't want to display a title in the plot, use double hashes\n//   (e.g. \"MyPlot##HiddenIdText\" or \"##NoTitle\").\n// - #size is the **frame** size of the plot widget, not the plot area. The default\n//   size of plots (i.e. when ImVec2(0,0)) can be modified in your ImPlotStyle.\nIMPLOT_API bool BeginPlot(const char* title_id, const ImVec2& size=ImVec2(-1,0), ImPlotFlags flags=0);\n\n// Only call EndPlot() if BeginPlot() returns true! Typically called at the end\n// of an if statement conditioned on BeginPlot(). See example above.\nIMPLOT_API void EndPlot();\n\n//-----------------------------------------------------------------------------\n// [SECTION] Begin/End Subplots\n//-----------------------------------------------------------------------------\n\n// Starts a subdivided plotting context. If the function returns true,\n// EndSubplots() MUST be called! Call BeginPlot/EndPlot AT MOST [rows*cols]\n// times in  between the begining and end of the subplot context. Plots are\n// added in row major order.\n//\n// Example:\n//\n// if (BeginSubplots(\"My Subplot\",2,3,ImVec2(800,400)) {\n//     for (int i = 0; i < 6; ++i) {\n//         if (BeginPlot(...)) {\n//             ImPlot::PlotLine(...);\n//             ...\n//             EndPlot();\n//         }\n//     }\n//     EndSubplots();\n// }\n//\n// Produces:\n//\n// [0] | [1] | [2]\n// ----|-----|----\n// [3] | [4] | [5]\n//\n// Important notes:\n//\n// - #title_id must be unique to the current ImGui ID scope. If you need to avoid ID\n//   collisions or don't want to display a title in the plot, use double hashes\n//   (e.g. \"MySubplot##HiddenIdText\" or \"##NoTitle\").\n// - #rows and #cols must be greater than 0.\n// - #size is the size of the entire grid of subplots, not the individual plots\n// - #row_ratios and #col_ratios must have AT LEAST #rows and #cols elements,\n//   respectively. These are the sizes of the rows and columns expressed in ratios.\n//   If the user adjusts the dimensions, the arrays are updated with new ratios.\n//\n// Important notes regarding BeginPlot from inside of BeginSubplots:\n//\n// - The #title_id parameter of _BeginPlot_ (see above) does NOT have to be\n//   unique when called inside of a subplot context. Subplot IDs are hashed\n//   for your convenience so you don't have call PushID or generate unique title\n//   strings. Simply pass an empty string to BeginPlot unless you want to title\n//   each subplot.\n// - The #size parameter of _BeginPlot_ (see above) is ignored when inside of a\n//   subplot context. The actual size of the subplot will be based on the\n//   #size value you pass to _BeginSubplots_ and #row/#col_ratios if provided.\n\nIMPLOT_API bool BeginSubplots(const char* title_id,\n                             int rows,\n                             int cols,\n                             const ImVec2& size,\n                             ImPlotSubplotFlags flags = 0,\n                             float* row_ratios        = nullptr,\n                             float* col_ratios        = nullptr);\n\n// Only call EndSubplots() if BeginSubplots() returns true! Typically called at the end\n// of an if statement conditioned on BeginSublots(). See example above.\nIMPLOT_API void EndSubplots();\n\n//-----------------------------------------------------------------------------\n// [SECTION] Setup\n//-----------------------------------------------------------------------------\n\n// The following API allows you to setup and customize various aspects of the\n// current plot. The functions should be called immediately after BeginPlot\n// and before any other API calls. Typical usage is as follows:\n\n// if (BeginPlot(...)) {                     1) begin a new plot\n//     SetupAxis(ImAxis_X1, \"My X-Axis\");    2) make Setup calls\n//     SetupAxis(ImAxis_Y1, \"My Y-Axis\");\n//     SetupLegend(ImPlotLocation_North);\n//     ...\n//     SetupFinish();                        3) [optional] explicitly finish setup\n//     PlotLine(...);                        4) plot items\n//     ...\n//     EndPlot();                            5) end the plot\n// }\n//\n// Important notes:\n//\n// - Always call Setup code at the top of your BeginPlot conditional statement.\n// - Setup is locked once you start plotting or explicitly call SetupFinish.\n//   Do NOT call Setup code after you begin plotting or after you make\n//   any non-Setup API calls (e.g. utils like PlotToPixels also lock Setup)\n// - Calling SetupFinish is OPTIONAL, but probably good practice. If you do not\n//   call it yourself, then the first subsequent plotting or utility function will\n//   call it for you.\n\n// Enables an axis or sets the label and/or flags for an existing axis. Leave #label = nullptr for no label.\nIMPLOT_API void SetupAxis(ImAxis axis, const char* label=nullptr, ImPlotAxisFlags flags=0);\n// Sets an axis range limits. If ImPlotCond_Always is used, the axes limits will be locked. Inversion with v_min > v_max is not supported; use SetupAxisLimits instead.\nIMPLOT_API void SetupAxisLimits(ImAxis axis, double v_min, double v_max, ImPlotCond cond = ImPlotCond_Once);\n// Links an axis range limits to external values. Set to nullptr for no linkage. The pointer data must remain valid until EndPlot.\nIMPLOT_API void SetupAxisLinks(ImAxis axis, double* link_min, double* link_max);\n// Sets the format of numeric axis labels via formater specifier (default=\"%g\"). Formated values will be double (i.e. use %f).\nIMPLOT_API void SetupAxisFormat(ImAxis axis, const char* fmt);\n// Sets the format of numeric axis labels via formatter callback. Given #value, write a label into #buff. Optionally pass user data.\nIMPLOT_API void SetupAxisFormat(ImAxis axis, ImPlotFormatter formatter, void* data=nullptr);\n// Sets an axis' ticks and optionally the labels. To keep the default ticks, set #keep_default=true.\nIMPLOT_API void SetupAxisTicks(ImAxis axis, const double* values, int n_ticks, const char* const labels[]=nullptr, bool keep_default=false);\n// Sets an axis' ticks and optionally the labels for the next plot. To keep the default ticks, set #keep_default=true.\nIMPLOT_API void SetupAxisTicks(ImAxis axis, double v_min, double v_max, int n_ticks, const char* const labels[]=nullptr, bool keep_default=false);\n// Sets an axis' scale using built-in options.\nIMPLOT_API void SetupAxisScale(ImAxis axis, ImPlotScale scale);\n// Sets an axis' scale using user supplied forward and inverse transfroms.\nIMPLOT_API void SetupAxisScale(ImAxis axis, ImPlotTransform forward, ImPlotTransform inverse, void* data=nullptr);\n// Sets an axis' limits constraints.\nIMPLOT_API void SetupAxisLimitsConstraints(ImAxis axis, double v_min, double v_max);\n// Sets an axis' zoom constraints.\nIMPLOT_API void SetupAxisZoomConstraints(ImAxis axis, double z_min, double z_max);\n\n// Sets the label and/or flags for primary X and Y axes (shorthand for two calls to SetupAxis).\nIMPLOT_API void SetupAxes(const char* x_label, const char* y_label, ImPlotAxisFlags x_flags=0, ImPlotAxisFlags y_flags=0);\n// Sets the primary X and Y axes range limits. If ImPlotCond_Always is used, the axes limits will be locked (shorthand for two calls to SetupAxisLimits).\nIMPLOT_API void SetupAxesLimits(double x_min, double x_max, double y_min, double y_max, ImPlotCond cond = ImPlotCond_Once);\n\n// Sets up the plot legend.\nIMPLOT_API void SetupLegend(ImPlotLocation location, ImPlotLegendFlags flags=0);\n// Set the location of the current plot's mouse position text (default = South|East).\nIMPLOT_API void SetupMouseText(ImPlotLocation location, ImPlotMouseTextFlags flags=0);\n\n// Explicitly finalize plot setup. Once you call this, you cannot make anymore Setup calls for the current plot!\n// Note that calling this function is OPTIONAL; it will be called by the first subsequent setup-locking API call.\nIMPLOT_API void SetupFinish();\n\n//-----------------------------------------------------------------------------\n// [SECTION] SetNext\n//-----------------------------------------------------------------------------\n\n// Though you should default to the `Setup` API above, there are some scenarios\n// where (re)configuring a plot or axis before `BeginPlot` is needed (e.g. if\n// using a preceding button or slider widget to change the plot limits). In\n// this case, you can use the `SetNext` API below. While this is not as feature\n// rich as the Setup API, most common needs are provided. These functions can be\n// called anwhere except for inside of `Begin/EndPlot`. For example:\n\n// if (ImGui::Button(\"Center Plot\"))\n//     ImPlot::SetNextPlotLimits(-1,1,-1,1);\n// if (ImPlot::BeginPlot(...)) {\n//     ...\n//     ImPlot::EndPlot();\n// }\n//\n// Important notes:\n//\n// - You must still enable non-default axes with SetupAxis for these functions\n//   to work properly.\n\n// Sets an upcoming axis range limits. If ImPlotCond_Always is used, the axes limits will be locked.\nIMPLOT_API void SetNextAxisLimits(ImAxis axis, double v_min, double v_max, ImPlotCond cond = ImPlotCond_Once);\n// Links an upcoming axis range limits to external values. Set to nullptr for no linkage. The pointer data must remain valid until EndPlot!\nIMPLOT_API void SetNextAxisLinks(ImAxis axis, double* link_min, double* link_max);\n// Set an upcoming axis to auto fit to its data.\nIMPLOT_API void SetNextAxisToFit(ImAxis axis);\n\n// Sets the upcoming primary X and Y axes range limits. If ImPlotCond_Always is used, the axes limits will be locked (shorthand for two calls to SetupAxisLimits).\nIMPLOT_API void SetNextAxesLimits(double x_min, double x_max, double y_min, double y_max, ImPlotCond cond = ImPlotCond_Once);\n// Sets all upcoming axes to auto fit to their data.\nIMPLOT_API void SetNextAxesToFit();\n\n//-----------------------------------------------------------------------------\n// [SECTION] Plot Items\n//-----------------------------------------------------------------------------\n\n// The main plotting API is provied below. Call these functions between\n// Begin/EndPlot and after any Setup API calls. Each plots data on the current\n// x and y axes, which can be changed with `SetAxis/Axes`.\n//\n// The templated functions are explicitly instantiated in implot_items.cpp.\n// They are not intended to be used generically with custom types. You will get\n// a linker error if you try! All functions support the following scalar types:\n//\n// float, double, ImS8, ImU8, ImS16, ImU16, ImS32, ImU32, ImS64, ImU64\n//\n//\n// If you need to plot custom or non-homogenous data you have a few options:\n//\n// 1. If your data is a simple struct/class (e.g. Vector2f), you can use striding.\n//    This is the most performant option if applicable.\n//\n//    struct Vector2f { float X, Y; };\n//    ...\n//    Vector2f data[42];\n//    ImPlot::PlotLine(\"line\", &data[0].x, &data[0].y, 42, 0, 0, sizeof(Vector2f));\n//\n// 2. Write a custom getter C function or C++ lambda and pass it and optionally your data to\n//    an ImPlot function post-fixed with a G (e.g. PlotScatterG). This has a slight performance\n//    cost, but probably not enough to worry about unless your data is very large. Examples:\n//\n//    ImPlotPoint MyDataGetter(void* data, int idx) {\n//        MyData* my_data = (MyData*)data;\n//        ImPlotPoint p;\n//        p.x = my_data->GetTime(idx);\n//        p.y = my_data->GetValue(idx);\n//        return p\n//    }\n//    ...\n//    auto my_lambda = [](int idx, void*) {\n//        double t = idx / 999.0;\n//        return ImPlotPoint(t, 0.5+0.5*std::sin(2*PI*10*t));\n//    };\n//    ...\n//    if (ImPlot::BeginPlot(\"MyPlot\")) {\n//        MyData my_data;\n//        ImPlot::PlotScatterG(\"scatter\", MyDataGetter, &my_data, my_data.Size());\n//        ImPlot::PlotLineG(\"line\", my_lambda, nullptr, 1000);\n//        ImPlot::EndPlot();\n//    }\n//\n// NB: All types are converted to double before plotting. You may lose information\n// if you try plotting extremely large 64-bit integral types. Proceed with caution!\n\n// Plots a standard 2D line plot.\nIMPLOT_TMP void PlotLine(const char* label_id, const T* values, int count, double xscale=1, double xstart=0, ImPlotLineFlags flags=0, int offset=0, int stride=sizeof(T));\nIMPLOT_TMP void PlotLine(const char* label_id, const T* xs, const T* ys, int count, ImPlotLineFlags flags=0, int offset=0, int stride=sizeof(T));\nIMPLOT_API void PlotLineG(const char* label_id, ImPlotGetter getter, void* data, int count, ImPlotLineFlags flags=0);\n\n// Plots a standard 2D scatter plot. Default marker is ImPlotMarker_Circle.\nIMPLOT_TMP void PlotScatter(const char* label_id, const T* values, int count, double xscale=1, double xstart=0, ImPlotScatterFlags flags=0, int offset=0, int stride=sizeof(T));\nIMPLOT_TMP void PlotScatter(const char* label_id, const T* xs, const T* ys, int count, ImPlotScatterFlags flags=0, int offset=0, int stride=sizeof(T));\nIMPLOT_API void PlotScatterG(const char* label_id, ImPlotGetter getter, void* data, int count, ImPlotScatterFlags flags=0);\n\n// Plots a a stairstep graph. The y value is continued constantly to the right from every x position, i.e. the interval [x[i], x[i+1]) has the value y[i]\nIMPLOT_TMP void PlotStairs(const char* label_id, const T* values, int count, double xscale=1, double xstart=0, ImPlotStairsFlags flags=0, int offset=0, int stride=sizeof(T));\nIMPLOT_TMP void PlotStairs(const char* label_id, const T* xs, const T* ys, int count, ImPlotStairsFlags flags=0, int offset=0, int stride=sizeof(T));\nIMPLOT_API void PlotStairsG(const char* label_id, ImPlotGetter getter, void* data, int count, ImPlotStairsFlags flags=0);\n\n// Plots a shaded (filled) region between two lines, or a line and a horizontal reference. Set yref to +/-INFINITY for infinite fill extents.\nIMPLOT_TMP void PlotShaded(const char* label_id, const T* values, int count, double yref=0, double xscale=1, double xstart=0, ImPlotShadedFlags flags=0, int offset=0, int stride=sizeof(T));\nIMPLOT_TMP void PlotShaded(const char* label_id, const T* xs, const T* ys, int count, double yref=0, ImPlotShadedFlags flags=0, int offset=0, int stride=sizeof(T));\nIMPLOT_TMP void PlotShaded(const char* label_id, const T* xs, const T* ys1, const T* ys2, int count, ImPlotShadedFlags flags=0, int offset=0, int stride=sizeof(T));\nIMPLOT_API void PlotShadedG(const char* label_id, ImPlotGetter getter1, void* data1, ImPlotGetter getter2, void* data2, int count, ImPlotShadedFlags flags=0);\n\n// Plots a bar graph. Vertical by default. #bar_size and #shift are in plot units.\nIMPLOT_TMP void PlotBars(const char* label_id, const T* values, int count, double bar_size=0.67, double shift=0, ImPlotBarsFlags flags=0, int offset=0, int stride=sizeof(T));\nIMPLOT_TMP void PlotBars(const char* label_id, const T* xs, const T* ys, int count, double bar_size, ImPlotBarsFlags flags=0, int offset=0, int stride=sizeof(T));\nIMPLOT_API void PlotBarsG(const char* label_id, ImPlotGetter getter, void* data, int count, double bar_size, ImPlotBarsFlags flags=0);\n\n// Plots a group of bars. #values is a row-major matrix with #item_count rows and #group_count cols. #label_ids should have #item_count elements.\nIMPLOT_TMP void PlotBarGroups(const char* const label_ids[], const T* values, int item_count, int group_count, double group_size=0.67, double shift=0, ImPlotBarGroupsFlags flags=0);\n\n// Plots vertical error bar. The label_id should be the same as the label_id of the associated line or bar plot.\nIMPLOT_TMP void PlotErrorBars(const char* label_id, const T* xs, const T* ys, const T* err, int count, ImPlotErrorBarsFlags flags=0, int offset=0, int stride=sizeof(T));\nIMPLOT_TMP void PlotErrorBars(const char* label_id, const T* xs, const T* ys, const T* neg, const T* pos, int count, ImPlotErrorBarsFlags flags=0, int offset=0, int stride=sizeof(T));\n\n// Plots stems. Vertical by default.\nIMPLOT_TMP void PlotStems(const char* label_id, const T* values, int count, double ref=0, double scale=1, double start=0, ImPlotStemsFlags flags=0, int offset=0, int stride=sizeof(T));\nIMPLOT_TMP void PlotStems(const char* label_id, const T* xs, const T* ys, int count, double ref=0, ImPlotStemsFlags flags=0, int offset=0, int stride=sizeof(T));\n\n// Plots infinite vertical or horizontal lines (e.g. for references or asymptotes).\nIMPLOT_TMP void PlotInfLines(const char* label_id, const T* values, int count, ImPlotInfLinesFlags flags=0, int offset=0, int stride=sizeof(T));\n\n// Plots a pie chart. Center and radius are in plot units. #label_fmt can be set to nullptr for no labels.\nIMPLOT_TMP void PlotPieChart(const char* const label_ids[], const T* values, int count, double x, double y, double radius, const char* label_fmt=\"%.1f\", double angle0=90, ImPlotPieChartFlags flags=0);\n\n// Plots a 2D heatmap chart. Values are expected to be in row-major order by default. Leave #scale_min and scale_max both at 0 for automatic color scaling, or set them to a predefined range. #label_fmt can be set to nullptr for no labels.\nIMPLOT_TMP void PlotHeatmap(const char* label_id, const T* values, int rows, int cols, double scale_min=0, double scale_max=0, const char* label_fmt=\"%.1f\", const ImPlotPoint& bounds_min=ImPlotPoint(0,0), const ImPlotPoint& bounds_max=ImPlotPoint(1,1), ImPlotHeatmapFlags flags=0);\n\n// Plots a horizontal histogram. #bins can be a positive integer or an ImPlotBin_ method. If #range is left unspecified, the min/max of #values will be used as the range.\n// Otherwise, outlier values outside of the range are not binned. The largest bin count or density is returned.\nIMPLOT_TMP double PlotHistogram(const char* label_id, const T* values, int count, int bins=ImPlotBin_Sturges, double bar_scale=1.0, ImPlotRange range=ImPlotRange(), ImPlotHistogramFlags flags=0);\n\n// Plots two dimensional, bivariate histogram as a heatmap. #x_bins and #y_bins can be a positive integer or an ImPlotBin. If #range is left unspecified, the min/max of\n// #xs an #ys will be used as the ranges. Otherwise, outlier values outside of range are not binned. The largest bin count or density is returned.\nIMPLOT_TMP double PlotHistogram2D(const char* label_id, const T* xs, const T* ys, int count, int x_bins=ImPlotBin_Sturges, int y_bins=ImPlotBin_Sturges, ImPlotRect range=ImPlotRect(), ImPlotHistogramFlags flags=0);\n\n// Plots digital data. Digital plots do not respond to y drag or zoom, and are always referenced to the bottom of the plot.\nIMPLOT_TMP void PlotDigital(const char* label_id, const T* xs, const T* ys, int count, ImPlotDigitalFlags flags=0, int offset=0, int stride=sizeof(T));\nIMPLOT_API void PlotDigitalG(const char* label_id, ImPlotGetter getter, void* data, int count, ImPlotDigitalFlags flags=0);\n\n// Plots an axis-aligned image. #bounds_min/bounds_max are in plot coordinates (y-up) and #uv0/uv1 are in texture coordinates (y-down).\nIMPLOT_API void PlotImage(const char* label_id, ImTextureID user_texture_id, const ImPlotPoint& bounds_min, const ImPlotPoint& bounds_max, const ImVec2& uv0=ImVec2(0,0), const ImVec2& uv1=ImVec2(1,1), const ImVec4& tint_col=ImVec4(1,1,1,1), ImPlotImageFlags flags=0);\n\n// Plots a centered text label at point x,y with an optional pixel offset. Text color can be changed with ImPlot::PushStyleColor(ImPlotCol_InlayText, ...).\nIMPLOT_API void PlotText(const char* text, double x, double y, const ImVec2& pix_offset=ImVec2(0,0), ImPlotTextFlags flags=0);\n\n// Plots a dummy item (i.e. adds a legend entry colored by ImPlotCol_Line)\nIMPLOT_API void PlotDummy(const char* label_id, ImPlotDummyFlags flags=0);\n\n//-----------------------------------------------------------------------------\n// [SECTION] Plot Tools\n//-----------------------------------------------------------------------------\n\n// The following can be used to render interactive elements and/or annotations.\n// Like the item plotting functions above, they apply to the current x and y\n// axes, which can be changed with `SetAxis/SetAxes`.\n\n// Shows a draggable point at x,y. #col defaults to ImGuiCol_Text.\nIMPLOT_API bool DragPoint(int id, double* x, double* y, const ImVec4& col, float size = 4, ImPlotDragToolFlags flags=0);\n// Shows a draggable vertical guide line at an x-value. #col defaults to ImGuiCol_Text.\nIMPLOT_API bool DragLineX(int id, double* x, const ImVec4& col, float thickness = 1, ImPlotDragToolFlags flags=0);\n// Shows a draggable horizontal guide line at a y-value. #col defaults to ImGuiCol_Text.\nIMPLOT_API bool DragLineY(int id, double* y, const ImVec4& col, float thickness = 1, ImPlotDragToolFlags flags=0);\n// Shows a draggable and resizeable rectangle.\nIMPLOT_API bool DragRect(int id, double* x1, double* y1, double* x2, double* y2, const ImVec4& col, ImPlotDragToolFlags flags=0);\n\n// Shows an annotation callout at a chosen point. Clamping keeps annotations in the plot area. Annotations are always rendered on top.\nIMPLOT_API void Annotation(double x, double y, const ImVec4& col, const ImVec2& pix_offset, bool clamp, bool round = false);\nIMPLOT_API void Annotation(double x, double y, const ImVec4& col, const ImVec2& pix_offset, bool clamp, const char* fmt, ...)           IM_FMTARGS(6);\nIMPLOT_API void AnnotationV(double x, double y, const ImVec4& col, const ImVec2& pix_offset, bool clamp, const char* fmt, va_list args) IM_FMTLIST(6);\n\n// Shows a x-axis tag at the specified coordinate value.\nIMPLOT_API void TagX(double x, const ImVec4& col, bool round = false);\nIMPLOT_API void TagX(double x, const ImVec4& col, const char* fmt, ...)           IM_FMTARGS(3);\nIMPLOT_API void TagXV(double x, const ImVec4& col, const char* fmt, va_list args) IM_FMTLIST(3);\n\n// Shows a y-axis tag at the specified coordinate value.\nIMPLOT_API void TagY(double y, const ImVec4& col, bool round = false);\nIMPLOT_API void TagY(double y, const ImVec4& col, const char* fmt, ...)           IM_FMTARGS(3);\nIMPLOT_API void TagYV(double y, const ImVec4& col, const char* fmt, va_list args) IM_FMTLIST(3);\n\n//-----------------------------------------------------------------------------\n// [SECTION] Plot Utils\n//-----------------------------------------------------------------------------\n\n// Select which axis/axes will be used for subsequent plot elements.\nIMPLOT_API void SetAxis(ImAxis axis);\nIMPLOT_API void SetAxes(ImAxis x_axis, ImAxis y_axis);\n\n// Convert pixels to a position in the current plot's coordinate system. Passing IMPLOT_AUTO uses the current axes.\nIMPLOT_API ImPlotPoint PixelsToPlot(const ImVec2& pix, ImAxis x_axis = IMPLOT_AUTO, ImAxis y_axis = IMPLOT_AUTO);\nIMPLOT_API ImPlotPoint PixelsToPlot(float x, float y, ImAxis x_axis = IMPLOT_AUTO, ImAxis y_axis = IMPLOT_AUTO);\n\n// Convert a position in the current plot's coordinate system to pixels. Passing IMPLOT_AUTO uses the current axes.\nIMPLOT_API ImVec2 PlotToPixels(const ImPlotPoint& plt, ImAxis x_axis = IMPLOT_AUTO, ImAxis y_axis = IMPLOT_AUTO);\nIMPLOT_API ImVec2 PlotToPixels(double x, double y, ImAxis x_axis = IMPLOT_AUTO, ImAxis y_axis = IMPLOT_AUTO);\n\n// Get the current Plot position (top-left) in pixels.\nIMPLOT_API ImVec2 GetPlotPos();\n// Get the curent Plot size in pixels.\nIMPLOT_API ImVec2 GetPlotSize();\n\n// Returns the mouse position in x,y coordinates of the current plot. Passing IMPLOT_AUTO uses the current axes.\nIMPLOT_API ImPlotPoint GetPlotMousePos(ImAxis x_axis = IMPLOT_AUTO, ImAxis y_axis = IMPLOT_AUTO);\n// Returns the current plot axis range.\nIMPLOT_API ImPlotRect GetPlotLimits(ImAxis x_axis = IMPLOT_AUTO, ImAxis y_axis = IMPLOT_AUTO);\n\n// Returns true if the plot area in the current plot is hovered.\nIMPLOT_API bool IsPlotHovered();\n// Returns true if the axis label area in the current plot is hovered.\nIMPLOT_API bool IsAxisHovered(ImAxis axis);\n// Returns true if the bounding frame of a subplot is hovered.\nIMPLOT_API bool IsSubplotsHovered();\n\n// Returns true if the current plot is being box selected.\nIMPLOT_API bool IsPlotSelected();\n// Returns the current plot box selection bounds. Passing IMPLOT_AUTO uses the current axes.\nIMPLOT_API ImPlotRect GetPlotSelection(ImAxis x_axis = IMPLOT_AUTO, ImAxis y_axis = IMPLOT_AUTO);\n// Cancels a the current plot box selection.\nIMPLOT_API void CancelPlotSelection();\n\n// Hides or shows the next plot item (i.e. as if it were toggled from the legend).\n// Use ImPlotCond_Always if you need to forcefully set this every frame.\nIMPLOT_API void HideNextItem(bool hidden = true, ImPlotCond cond = ImPlotCond_Once);\n\n// Use the following around calls to Begin/EndPlot to align l/r/t/b padding.\n// Consider using Begin/EndSubplots first. They are more feature rich and\n// accomplish the same behaviour by default. The functions below offer lower\n// level control of plot alignment.\n\n// Align axis padding over multiple plots in a single row or column. #group_id must\n// be unique. If this function returns true, EndAlignedPlots() must be called.\nIMPLOT_API bool BeginAlignedPlots(const char* group_id, bool vertical = true);\n// Only call EndAlignedPlots() if BeginAlignedPlots() returns true!\nIMPLOT_API void EndAlignedPlots();\n\n//-----------------------------------------------------------------------------\n// [SECTION] Legend Utils\n//-----------------------------------------------------------------------------\n\n// Begin a popup for a legend entry.\nIMPLOT_API bool BeginLegendPopup(const char* label_id, ImGuiMouseButton mouse_button=1);\n// End a popup for a legend entry.\nIMPLOT_API void EndLegendPopup();\n// Returns true if a plot item legend entry is hovered.\nIMPLOT_API bool IsLegendEntryHovered(const char* label_id);\n\n//-----------------------------------------------------------------------------\n// [SECTION] Drag and Drop\n//-----------------------------------------------------------------------------\n\n// Turns the current plot's plotting area into a drag and drop target. Don't forget to call EndDragDropTarget!\nIMPLOT_API bool BeginDragDropTargetPlot();\n// Turns the current plot's X-axis into a drag and drop target. Don't forget to call EndDragDropTarget!\nIMPLOT_API bool BeginDragDropTargetAxis(ImAxis axis);\n// Turns the current plot's legend into a drag and drop target. Don't forget to call EndDragDropTarget!\nIMPLOT_API bool BeginDragDropTargetLegend();\n// Ends a drag and drop target (currently just an alias for ImGui::EndDragDropTarget).\nIMPLOT_API void EndDragDropTarget();\n\n// NB: By default, plot and axes drag and drop *sources* require holding the Ctrl modifier to initiate the drag.\n// You can change the modifier if desired. If ImGuiMod_None is provided, the axes will be locked from panning.\n\n// Turns the current plot's plotting area into a drag and drop source. You must hold Ctrl. Don't forget to call EndDragDropSource!\nIMPLOT_API bool BeginDragDropSourcePlot(ImGuiDragDropFlags flags=0);\n// Turns the current plot's X-axis into a drag and drop source. You must hold Ctrl. Don't forget to call EndDragDropSource!\nIMPLOT_API bool BeginDragDropSourceAxis(ImAxis axis, ImGuiDragDropFlags flags=0);\n// Turns an item in the current plot's legend into drag and drop source. Don't forget to call EndDragDropSource!\nIMPLOT_API bool BeginDragDropSourceItem(const char* label_id, ImGuiDragDropFlags flags=0);\n// Ends a drag and drop source (currently just an alias for ImGui::EndDragDropSource).\nIMPLOT_API void EndDragDropSource();\n\n//-----------------------------------------------------------------------------\n// [SECTION] Styling\n//-----------------------------------------------------------------------------\n\n// Styling colors in ImPlot works similarly to styling colors in ImGui, but\n// with one important difference. Like ImGui, all style colors are stored in an\n// indexable array in ImPlotStyle. You can permanently modify these values through\n// GetStyle().Colors, or temporarily modify them with Push/Pop functions below.\n// However, by default all style colors in ImPlot default to a special color\n// IMPLOT_AUTO_COL. The behavior of this color depends upon the style color to\n// which it as applied:\n//\n//     1) For style colors associated with plot items (e.g. ImPlotCol_Line),\n//        IMPLOT_AUTO_COL tells ImPlot to color the item with the next unused\n//        color in the current colormap. Thus, every item will have a different\n//        color up to the number of colors in the colormap, at which point the\n//        colormap will roll over. For most use cases, you should not need to\n//        set these style colors to anything but IMPLOT_COL_AUTO; you are\n//        probably better off changing the current colormap. However, if you\n//        need to explicitly color a particular item you may either Push/Pop\n//        the style color around the item in question, or use the SetNextXXXStyle\n//        API below. If you permanently set one of these style colors to a specific\n//        color, or forget to call Pop, then all subsequent items will be styled\n//        with the color you set.\n//\n//     2) For style colors associated with plot styling (e.g. ImPlotCol_PlotBg),\n//        IMPLOT_AUTO_COL tells ImPlot to set that color from color data in your\n//        **ImGuiStyle**. The ImGuiCol_ that these style colors default to are\n//        detailed above, and in general have been mapped to produce plots visually\n//        consistent with your current ImGui style. Of course, you are free to\n//        manually set these colors to whatever you like, and further can Push/Pop\n//        them around individual plots for plot-specific styling (e.g. coloring axes).\n\n// Provides access to plot style structure for permanant modifications to colors, sizes, etc.\nIMPLOT_API ImPlotStyle& GetStyle();\n\n// Style plot colors for current ImGui style (default).\nIMPLOT_API void StyleColorsAuto(ImPlotStyle* dst = nullptr);\n// Style plot colors for ImGui \"Classic\".\nIMPLOT_API void StyleColorsClassic(ImPlotStyle* dst = nullptr);\n// Style plot colors for ImGui \"Dark\".\nIMPLOT_API void StyleColorsDark(ImPlotStyle* dst = nullptr);\n// Style plot colors for ImGui \"Light\".\nIMPLOT_API void StyleColorsLight(ImPlotStyle* dst = nullptr);\n\n// Use PushStyleX to temporarily modify your ImPlotStyle. The modification\n// will last until the matching call to PopStyleX. You MUST call a pop for\n// every push, otherwise you will leak memory! This behaves just like ImGui.\n\n// Temporarily modify a style color. Don't forget to call PopStyleColor!\nIMPLOT_API void PushStyleColor(ImPlotCol idx, ImU32 col);\nIMPLOT_API void PushStyleColor(ImPlotCol idx, const ImVec4& col);\n// Undo temporary style color modification(s). Undo multiple pushes at once by increasing count.\nIMPLOT_API void PopStyleColor(int count = 1);\n\n// Temporarily modify a style variable of float type. Don't forget to call PopStyleVar!\nIMPLOT_API void PushStyleVar(ImPlotStyleVar idx, float val);\n// Temporarily modify a style variable of int type. Don't forget to call PopStyleVar!\nIMPLOT_API void PushStyleVar(ImPlotStyleVar idx, int val);\n// Temporarily modify a style variable of ImVec2 type. Don't forget to call PopStyleVar!\nIMPLOT_API void PushStyleVar(ImPlotStyleVar idx, const ImVec2& val);\n// Undo temporary style variable modification(s). Undo multiple pushes at once by increasing count.\nIMPLOT_API void PopStyleVar(int count = 1);\n\n// The following can be used to modify the style of the next plot item ONLY. They do\n// NOT require calls to PopStyleX. Leave style attributes you don't want modified to\n// IMPLOT_AUTO or IMPLOT_AUTO_COL. Automatic styles will be deduced from the current\n// values in your ImPlotStyle or from Colormap data.\n\n// Set the line color and weight for the next item only.\nIMPLOT_API void SetNextLineStyle(const ImVec4& col = IMPLOT_AUTO_COL, float weight = IMPLOT_AUTO);\n// Set the fill color for the next item only.\nIMPLOT_API void SetNextFillStyle(const ImVec4& col = IMPLOT_AUTO_COL, float alpha_mod = IMPLOT_AUTO);\n// Set the marker style for the next item only.\nIMPLOT_API void SetNextMarkerStyle(ImPlotMarker marker = IMPLOT_AUTO, float size = IMPLOT_AUTO, const ImVec4& fill = IMPLOT_AUTO_COL, float weight = IMPLOT_AUTO, const ImVec4& outline = IMPLOT_AUTO_COL);\n// Set the error bar style for the next item only.\nIMPLOT_API void SetNextErrorBarStyle(const ImVec4& col = IMPLOT_AUTO_COL, float size = IMPLOT_AUTO, float weight = IMPLOT_AUTO);\n\n// Gets the last item primary color (i.e. its legend icon color)\nIMPLOT_API ImVec4 GetLastItemColor();\n\n// Returns the null terminated string name for an ImPlotCol.\nIMPLOT_API const char* GetStyleColorName(ImPlotCol idx);\n// Returns the null terminated string name for an ImPlotMarker.\nIMPLOT_API const char* GetMarkerName(ImPlotMarker idx);\n\n//-----------------------------------------------------------------------------\n// [SECTION] Colormaps\n//-----------------------------------------------------------------------------\n\n// Item styling is based on colormaps when the relevant ImPlotCol_XXX is set to\n// IMPLOT_AUTO_COL (default). Several built-in colormaps are available. You can\n// add and then push/pop your own colormaps as well. To permanently set a colormap,\n// modify the Colormap index member of your ImPlotStyle.\n\n// Colormap data will be ignored and a custom color will be used if you have done one of the following:\n//     1) Modified an item style color in your ImPlotStyle to anything other than IMPLOT_AUTO_COL.\n//     2) Pushed an item style color using PushStyleColor().\n//     3) Set the next item style with a SetNextXXXStyle function.\n\n// Add a new colormap. The color data will be copied. The colormap can be used by pushing either the returned index or the\n// string name with PushColormap. The colormap name must be unique and the size must be greater than 1. You will receive\n// an assert otherwise! By default colormaps are considered to be qualitative (i.e. discrete). If you want to create a\n// continuous colormap, set #qual=false. This will treat the colors you provide as keys, and ImPlot will build a linearly\n// interpolated lookup table. The memory footprint of this table will be exactly ((size-1)*255+1)*4 bytes.\nIMPLOT_API ImPlotColormap AddColormap(const char* name, const ImVec4* cols, int size, bool qual=true);\nIMPLOT_API ImPlotColormap AddColormap(const char* name, const ImU32*  cols, int size, bool qual=true);\n\n// Returns the number of available colormaps (i.e. the built-in + user-added count).\nIMPLOT_API int GetColormapCount();\n// Returns a null terminated string name for a colormap given an index. Returns nullptr if index is invalid.\nIMPLOT_API const char* GetColormapName(ImPlotColormap cmap);\n// Returns an index number for a colormap given a valid string name. Returns -1 if name is invalid.\nIMPLOT_API ImPlotColormap GetColormapIndex(const char* name);\n\n// Temporarily switch to one of the built-in (i.e. ImPlotColormap_XXX) or user-added colormaps (i.e. a return value of AddColormap). Don't forget to call PopColormap!\nIMPLOT_API void PushColormap(ImPlotColormap cmap);\n// Push a colormap by string name. Use built-in names such as \"Default\", \"Deep\", \"Jet\", etc. or a string you provided to AddColormap. Don't forget to call PopColormap!\nIMPLOT_API void PushColormap(const char* name);\n// Undo temporary colormap modification(s). Undo multiple pushes at once by increasing count.\nIMPLOT_API void PopColormap(int count = 1);\n\n// Returns the next color from the current colormap and advances the colormap for the current plot.\n// Can also be used with no return value to skip colors if desired. You need to call this between Begin/EndPlot!\nIMPLOT_API ImVec4 NextColormapColor();\n\n// Colormap utils. If cmap = IMPLOT_AUTO (default), the current colormap is assumed.\n// Pass an explicit colormap index (built-in or user-added) to specify otherwise.\n\n// Returns the size of a colormap.\nIMPLOT_API int GetColormapSize(ImPlotColormap cmap = IMPLOT_AUTO);\n// Returns a color from a colormap given an index >= 0 (modulo will be performed).\nIMPLOT_API ImVec4 GetColormapColor(int idx, ImPlotColormap cmap = IMPLOT_AUTO);\n// Sample a color from the current colormap given t between 0 and 1.\nIMPLOT_API ImVec4 SampleColormap(float t, ImPlotColormap cmap = IMPLOT_AUTO);\n\n// Shows a vertical color scale with linear spaced ticks using the specified color map. Use double hashes to hide label (e.g. \"##NoLabel\"). If scale_min > scale_max, the scale to color mapping will be reversed.\nIMPLOT_API void ColormapScale(const char* label, double scale_min, double scale_max, const ImVec2& size = ImVec2(0,0), const char* format = \"%g\", ImPlotColormapScaleFlags flags = 0, ImPlotColormap cmap = IMPLOT_AUTO);\n// Shows a horizontal slider with a colormap gradient background. Optionally returns the color sampled at t in [0 1].\nIMPLOT_API bool ColormapSlider(const char* label, float* t, ImVec4* out = nullptr, const char* format = \"\", ImPlotColormap cmap = IMPLOT_AUTO);\n// Shows a button with a colormap gradient brackground.\nIMPLOT_API bool ColormapButton(const char* label, const ImVec2& size = ImVec2(0,0), ImPlotColormap cmap = IMPLOT_AUTO);\n\n// When items in a plot sample their color from a colormap, the color is cached and does not change\n// unless explicitly overriden. Therefore, if you change the colormap after the item has already been plotted,\n// item colors will NOT update. If you need item colors to resample the new colormap, then use this\n// function to bust the cached colors. If #plot_title_id is nullptr, then every item in EVERY existing plot\n// will be cache busted. Otherwise only the plot specified by #plot_title_id will be busted. For the\n// latter, this function must be called in the same ImGui ID scope that the plot is in. You should rarely if ever\n// need this function, but it is available for applications that require runtime colormap swaps (e.g. Heatmaps demo).\nIMPLOT_API void BustColorCache(const char* plot_title_id = nullptr);\n\n//-----------------------------------------------------------------------------\n// [SECTION] Input Mapping\n//-----------------------------------------------------------------------------\n\n// Provides access to input mapping structure for permanant modifications to controls for pan, select, etc.\nIMPLOT_API ImPlotInputMap& GetInputMap();\n\n// Default input mapping: pan = LMB drag, box select = RMB drag, fit = LMB double click, context menu = RMB click, zoom = scroll.\nIMPLOT_API void MapInputDefault(ImPlotInputMap* dst = nullptr);\n// Reverse input mapping: pan = RMB drag, box select = LMB drag, fit = LMB double click, context menu = RMB click, zoom = scroll.\nIMPLOT_API void MapInputReverse(ImPlotInputMap* dst = nullptr);\n\n//-----------------------------------------------------------------------------\n// [SECTION] Miscellaneous\n//-----------------------------------------------------------------------------\n\n// Render icons similar to those that appear in legends (nifty for data lists).\nIMPLOT_API void ItemIcon(const ImVec4& col);\nIMPLOT_API void ItemIcon(ImU32 col);\nIMPLOT_API void ColormapIcon(ImPlotColormap cmap);\n\n// Get the plot draw list for custom rendering to the current plot area. Call between Begin/EndPlot.\nIMPLOT_API ImDrawList* GetPlotDrawList();\n// Push clip rect for rendering to current plot area. The rect can be expanded or contracted by #expand pixels. Call between Begin/EndPlot.\nIMPLOT_API void PushPlotClipRect(float expand=0);\n// Pop plot clip rect. Call between Begin/EndPlot.\nIMPLOT_API void PopPlotClipRect();\n\n// Shows ImPlot style selector dropdown menu.\nIMPLOT_API bool ShowStyleSelector(const char* label);\n// Shows ImPlot colormap selector dropdown menu.\nIMPLOT_API bool ShowColormapSelector(const char* label);\n// Shows ImPlot input map selector dropdown menu.\nIMPLOT_API bool ShowInputMapSelector(const char* label);\n// Shows ImPlot style editor block (not a window).\nIMPLOT_API void ShowStyleEditor(ImPlotStyle* ref = nullptr);\n// Add basic help/info block for end users (not a window).\nIMPLOT_API void ShowUserGuide();\n// Shows ImPlot metrics/debug information window.\nIMPLOT_API void ShowMetricsWindow(bool* p_popen = nullptr);\n\n//-----------------------------------------------------------------------------\n// [SECTION] Demo\n//-----------------------------------------------------------------------------\n\n// Shows the ImPlot demo window (add implot_demo.cpp to your sources!)\nIMPLOT_API void ShowDemoWindow(bool* p_open = nullptr);\n\n}  // namespace ImPlot\n\n//-----------------------------------------------------------------------------\n// [SECTION] Obsolete API\n//-----------------------------------------------------------------------------\n\n// The following functions will be removed! Keep your copy of implot up to date!\n// Occasionally set '#define IMPLOT_DISABLE_OBSOLETE_FUNCTIONS' to stay ahead.\n// If you absolutely must use these functions and do not want to receive compiler\n// warnings, set '#define IMPLOT_DISABLE_OBSOLETE_WARNINGS'.\n\n#ifndef IMPLOT_DISABLE_OBSOLETE_FUNCTIONS\n\n#ifndef IMPLOT_DISABLE_DEPRECATED_WARNINGS\n#if __cplusplus > 201402L\n#define IMPLOT_DEPRECATED(method) [[deprecated]] method\n#elif defined( __GNUC__ ) && !defined( __INTEL_COMPILER ) && ( __GNUC__ > 3 || ( __GNUC__ == 3 && __GNUC_MINOR__ >= 1 ) )\n#define IMPLOT_DEPRECATED(method) method __attribute__( ( deprecated ) )\n#elif defined( _MSC_VER )\n#define IMPLOT_DEPRECATED(method) __declspec(deprecated) method\n#else\n#define IMPLOT_DEPRECATED(method) method\n#endif\n#else\n#define IMPLOT_DEPRECATED(method) method\n#endif\n\nenum ImPlotFlagsObsolete_ {\n    ImPlotFlags_YAxis2 = 1 << 20,\n    ImPlotFlags_YAxis3 = 1 << 21,\n};\n\nnamespace ImPlot {\n\n// OBSOLETED in v0.13 -> PLANNED REMOVAL in v1.0\nIMPLOT_DEPRECATED( IMPLOT_API bool BeginPlot(const char* title_id,\n                                             const char* x_label,  // = nullptr,\n                                             const char* y_label,  // = nullptr,\n                                             const ImVec2& size       = ImVec2(-1,0),\n                                             ImPlotFlags flags        = ImPlotFlags_None,\n                                             ImPlotAxisFlags x_flags  = 0,\n                                             ImPlotAxisFlags y_flags  = 0,\n                                             ImPlotAxisFlags y2_flags = ImPlotAxisFlags_AuxDefault,\n                                             ImPlotAxisFlags y3_flags = ImPlotAxisFlags_AuxDefault,\n                                             const char* y2_label     = nullptr,\n                                             const char* y3_label     = nullptr) );\n\n} // namespace ImPlot\n\n#endif\n"
  },
  {
    "path": "src/DesktopPlusUI/implot/implot_internal.h",
    "content": "// MIT License\n\n// Copyright (c) 2023 Evan Pezent\n\n// Permission is hereby granted, free of charge, to any person obtaining a copy\n// of this software and associated documentation files (the \"Software\"), to deal\n// in the Software without restriction, including without limitation the rights\n// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n// copies of the Software, and to permit persons to whom the Software is\n// furnished to do so, subject to the following conditions:\n\n// The above copyright notice and this permission notice shall be included in all\n// copies or substantial portions of the Software.\n\n// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n// SOFTWARE.\n\n// ImPlot v0.16\n\n// You may use this file to debug, understand or extend ImPlot features but we\n// don't provide any guarantee of forward compatibility!\n\n//-----------------------------------------------------------------------------\n// [SECTION] Header Mess\n//-----------------------------------------------------------------------------\n\n#pragma once\n\n#include <time.h>\n#include \"imgui_internal.h\"\n\n#ifndef IMPLOT_VERSION\n#error Must include implot.h before implot_internal.h\n#endif\n\n\n// Support for pre-1.84 versions. ImPool's GetSize() -> GetBufSize()\n#if (IMGUI_VERSION_NUM < 18303)\n#define GetBufSize GetSize\n#endif\n\n//-----------------------------------------------------------------------------\n// [SECTION] Constants\n//-----------------------------------------------------------------------------\n\n// Constants can be changed unless stated otherwise. We may move some of these\n// to ImPlotStyleVar_ over time.\n\n// Mimimum allowable timestamp value 01/01/1970 @ 12:00am (UTC) (DO NOT DECREASE THIS)\n#define IMPLOT_MIN_TIME  0\n// Maximum allowable timestamp value 01/01/3000 @ 12:00am (UTC) (DO NOT INCREASE THIS)\n#define IMPLOT_MAX_TIME  32503680000\n// Default label format for axis labels\n#define IMPLOT_LABEL_FORMAT \"%g\"\n// Max character size for tick labels\n#define IMPLOT_LABEL_MAX_SIZE 32\n\n//-----------------------------------------------------------------------------\n// [SECTION] Macros\n//-----------------------------------------------------------------------------\n\n#define IMPLOT_NUM_X_AXES ImAxis_Y1\n#define IMPLOT_NUM_Y_AXES (ImAxis_COUNT - IMPLOT_NUM_X_AXES)\n\n// Split ImU32 color into RGB components [0 255]\n#define IM_COL32_SPLIT_RGB(col,r,g,b) \\\n    ImU32 r = ((col >> IM_COL32_R_SHIFT) & 0xFF); \\\n    ImU32 g = ((col >> IM_COL32_G_SHIFT) & 0xFF); \\\n    ImU32 b = ((col >> IM_COL32_B_SHIFT) & 0xFF);\n\n//-----------------------------------------------------------------------------\n// [SECTION] Forward Declarations\n//-----------------------------------------------------------------------------\n\nstruct ImPlotTick;\nstruct ImPlotAxis;\nstruct ImPlotAxisColor;\nstruct ImPlotItem;\nstruct ImPlotLegend;\nstruct ImPlotPlot;\nstruct ImPlotNextPlotData;\nstruct ImPlotTicker;\n\n//-----------------------------------------------------------------------------\n// [SECTION] Context Pointer\n//-----------------------------------------------------------------------------\n\n#ifndef GImPlot\nextern IMPLOT_API ImPlotContext* GImPlot; // Current implicit context pointer\n#endif\n\n//-----------------------------------------------------------------------------\n// [SECTION] Generic Helpers\n//-----------------------------------------------------------------------------\n\n// Computes the common (base-10) logarithm\nstatic inline float  ImLog10(float x)  { return log10f(x); }\nstatic inline double ImLog10(double x) { return log10(x);  }\nstatic inline float  ImSinh(float x)   { return sinhf(x);  }\nstatic inline double ImSinh(double x)  { return sinh(x);   }\nstatic inline float  ImAsinh(float x)  { return asinhf(x); }\nstatic inline double ImAsinh(double x) { return asinh(x);  }\n// Returns true if a flag is set\ntemplate <typename TSet, typename TFlag>\nstatic inline bool ImHasFlag(TSet set, TFlag flag) { return (set & flag) == flag; }\n// Flips a flag in a flagset\ntemplate <typename TSet, typename TFlag>\nstatic inline void ImFlipFlag(TSet& set, TFlag flag) { ImHasFlag(set, flag) ? set &= ~flag : set |= flag; }\n// Linearly remaps x from [x0 x1] to [y0 y1].\ntemplate <typename T>\nstatic inline T ImRemap(T x, T x0, T x1, T y0, T y1) { return y0 + (x - x0) * (y1 - y0) / (x1 - x0); }\n// Linear rempas x from [x0 x1] to [0 1]\ntemplate <typename T>\nstatic inline T ImRemap01(T x, T x0, T x1) { return (x - x0) / (x1 - x0); }\n// Returns always positive modulo (assumes r != 0)\nstatic inline int ImPosMod(int l, int r) { return (l % r + r) % r; }\n// Returns true if val is NAN\nstatic inline bool ImNan(double val) { return isnan(val); }\n// Returns true if val is NAN or INFINITY\nstatic inline bool ImNanOrInf(double val) { return !(val >= -DBL_MAX && val <= DBL_MAX) || ImNan(val); }\n// Turns NANs to 0s\nstatic inline double ImConstrainNan(double val) { return ImNan(val) ? 0 : val; }\n// Turns infinity to floating point maximums\nstatic inline double ImConstrainInf(double val) { return val >= DBL_MAX ?  DBL_MAX : val <= -DBL_MAX ? - DBL_MAX : val; }\n// Turns numbers less than or equal to 0 to 0.001 (sort of arbitrary, is there a better way?)\nstatic inline double ImConstrainLog(double val) { return val <= 0 ? 0.001f : val; }\n// Turns numbers less than 0 to zero\nstatic inline double ImConstrainTime(double val) { return val < IMPLOT_MIN_TIME ? IMPLOT_MIN_TIME : (val > IMPLOT_MAX_TIME ? IMPLOT_MAX_TIME : val); }\n// True if two numbers are approximately equal using units in the last place.\nstatic inline bool ImAlmostEqual(double v1, double v2, int ulp = 2) { return ImAbs(v1-v2) < DBL_EPSILON * ImAbs(v1+v2) * ulp || ImAbs(v1-v2) < DBL_MIN; }\n// Finds min value in an unsorted array\ntemplate <typename T>\nstatic inline T ImMinArray(const T* values, int count) { T m = values[0]; for (int i = 1; i < count; ++i) { if (values[i] < m) { m = values[i]; } } return m; }\n// Finds the max value in an unsorted array\ntemplate <typename T>\nstatic inline T ImMaxArray(const T* values, int count) { T m = values[0]; for (int i = 1; i < count; ++i) { if (values[i] > m) { m = values[i]; } } return m; }\n// Finds the min and max value in an unsorted array\ntemplate <typename T>\nstatic inline void ImMinMaxArray(const T* values, int count, T* min_out, T* max_out) {\n    T Min = values[0]; T Max = values[0];\n    for (int i = 1; i < count; ++i) {\n        if (values[i] < Min) { Min = values[i]; }\n        if (values[i] > Max) { Max = values[i]; }\n    }\n    *min_out = Min; *max_out = Max;\n}\n// Finds the sim of an array\ntemplate <typename T>\nstatic inline T ImSum(const T* values, int count) {\n    T sum  = 0;\n    for (int i = 0; i < count; ++i)\n        sum += values[i];\n    return sum;\n}\n// Finds the mean of an array\ntemplate <typename T>\nstatic inline double ImMean(const T* values, int count) {\n    double den = 1.0 / count;\n    double mu  = 0;\n    for (int i = 0; i < count; ++i)\n        mu += (double)values[i] * den;\n    return mu;\n}\n// Finds the sample standard deviation of an array\ntemplate <typename T>\nstatic inline double ImStdDev(const T* values, int count) {\n    double den = 1.0 / (count - 1.0);\n    double mu  = ImMean(values, count);\n    double x   = 0;\n    for (int i = 0; i < count; ++i)\n        x += ((double)values[i] - mu) * ((double)values[i] - mu) * den;\n    return sqrt(x);\n}\n// Mix color a and b by factor s in [0 256]\nstatic inline ImU32 ImMixU32(ImU32 a, ImU32 b, ImU32 s) {\n#ifdef IMPLOT_MIX64\n    const ImU32 af = 256-s;\n    const ImU32 bf = s;\n    const ImU64 al = (a & 0x00ff00ff) | (((ImU64)(a & 0xff00ff00)) << 24);\n    const ImU64 bl = (b & 0x00ff00ff) | (((ImU64)(b & 0xff00ff00)) << 24);\n    const ImU64 mix = (al * af + bl * bf);\n    return ((mix >> 32) & 0xff00ff00) | ((mix & 0xff00ff00) >> 8);\n#else\n    const ImU32 af = 256-s;\n    const ImU32 bf = s;\n    const ImU32 al = (a & 0x00ff00ff);\n    const ImU32 ah = (a & 0xff00ff00) >> 8;\n    const ImU32 bl = (b & 0x00ff00ff);\n    const ImU32 bh = (b & 0xff00ff00) >> 8;\n    const ImU32 ml = (al * af + bl * bf);\n    const ImU32 mh = (ah * af + bh * bf);\n    return (mh & 0xff00ff00) | ((ml & 0xff00ff00) >> 8);\n#endif\n}\n\n// Lerp across an array of 32-bit collors given t in [0.0 1.0]\nstatic inline ImU32 ImLerpU32(const ImU32* colors, int size, float t) {\n    int i1 = (int)((size - 1 ) * t);\n    int i2 = i1 + 1;\n    if (i2 == size || size == 1)\n        return colors[i1];\n    float den = 1.0f / (size - 1);\n    float t1 = i1 * den;\n    float t2 = i2 * den;\n    float tr = ImRemap01(t, t1, t2);\n    return ImMixU32(colors[i1], colors[i2], (ImU32)(tr*256));\n}\n\n// Set alpha channel of 32-bit color from float in range [0.0 1.0]\nstatic inline ImU32 ImAlphaU32(ImU32 col, float alpha) {\n    return col & ~((ImU32)((1.0f-alpha)*255)<<IM_COL32_A_SHIFT);\n}\n\n// Returns true of two ranges overlap\ntemplate <typename T>\nstatic inline bool ImOverlaps(T min_a, T max_a, T min_b, T max_b) {\n    return min_a <= max_b && min_b <= max_a;\n}\n\n//-----------------------------------------------------------------------------\n// [SECTION] ImPlot Enums\n//-----------------------------------------------------------------------------\n\ntypedef int ImPlotTimeUnit;    // -> enum ImPlotTimeUnit_\ntypedef int ImPlotDateFmt;     // -> enum ImPlotDateFmt_\ntypedef int ImPlotTimeFmt;     // -> enum ImPlotTimeFmt_\n\nenum ImPlotTimeUnit_ {\n    ImPlotTimeUnit_Us,  // microsecond\n    ImPlotTimeUnit_Ms,  // millisecond\n    ImPlotTimeUnit_S,   // second\n    ImPlotTimeUnit_Min, // minute\n    ImPlotTimeUnit_Hr,  // hour\n    ImPlotTimeUnit_Day, // day\n    ImPlotTimeUnit_Mo,  // month\n    ImPlotTimeUnit_Yr,  // year\n    ImPlotTimeUnit_COUNT\n};\n\nenum ImPlotDateFmt_ {              // default        [ ISO 8601     ]\n    ImPlotDateFmt_None = 0,\n    ImPlotDateFmt_DayMo,           // 10/3           [ --10-03      ]\n    ImPlotDateFmt_DayMoYr,         // 10/3/91        [ 1991-10-03   ]\n    ImPlotDateFmt_MoYr,            // Oct 1991       [ 1991-10      ]\n    ImPlotDateFmt_Mo,              // Oct            [ --10         ]\n    ImPlotDateFmt_Yr               // 1991           [ 1991         ]\n};\n\nenum ImPlotTimeFmt_ {              // default        [ 24 Hour Clock ]\n    ImPlotTimeFmt_None = 0,\n    ImPlotTimeFmt_Us,              // .428 552       [ .428 552     ]\n    ImPlotTimeFmt_SUs,             // :29.428 552    [ :29.428 552  ]\n    ImPlotTimeFmt_SMs,             // :29.428        [ :29.428      ]\n    ImPlotTimeFmt_S,               // :29            [ :29          ]\n    ImPlotTimeFmt_MinSMs,          // 21:29.428      [ 21:29.428    ]\n    ImPlotTimeFmt_HrMinSMs,        // 7:21:29.428pm  [ 19:21:29.428 ]\n    ImPlotTimeFmt_HrMinS,          // 7:21:29pm      [ 19:21:29     ]\n    ImPlotTimeFmt_HrMin,           // 7:21pm         [ 19:21        ]\n    ImPlotTimeFmt_Hr               // 7pm            [ 19:00        ]\n};\n\n//-----------------------------------------------------------------------------\n// [SECTION] Callbacks\n//-----------------------------------------------------------------------------\n\ntypedef void (*ImPlotLocator)(ImPlotTicker& ticker, const ImPlotRange& range, float pixels, bool vertical, ImPlotFormatter formatter, void* formatter_data);\n\n//-----------------------------------------------------------------------------\n// [SECTION] Structs\n//-----------------------------------------------------------------------------\n\n// Combined date/time format spec\nstruct ImPlotDateTimeSpec {\n    ImPlotDateTimeSpec() {}\n    ImPlotDateTimeSpec(ImPlotDateFmt date_fmt, ImPlotTimeFmt time_fmt, bool use_24_hr_clk = false, bool use_iso_8601 = false) {\n        Date           = date_fmt;\n        Time           = time_fmt;\n        UseISO8601     = use_iso_8601;\n        Use24HourClock = use_24_hr_clk;\n    }\n    ImPlotDateFmt Date;\n    ImPlotTimeFmt Time;\n    bool UseISO8601;\n    bool Use24HourClock;\n};\n\n// Two part timestamp struct.\nstruct ImPlotTime {\n    time_t S;  // second part\n    int    Us; // microsecond part\n    ImPlotTime() { S = 0; Us = 0; }\n    ImPlotTime(time_t s, int us = 0) { S  = s + us / 1000000; Us = us % 1000000; }\n    void RollOver() { S  = S + Us / 1000000;  Us = Us % 1000000; }\n    double ToDouble() const { return (double)S + (double)Us / 1000000.0; }\n    static ImPlotTime FromDouble(double t) { return ImPlotTime((time_t)t, (int)(t * 1000000 - floor(t) * 1000000)); }\n};\n\nstatic inline ImPlotTime operator+(const ImPlotTime& lhs, const ImPlotTime& rhs)\n{ return ImPlotTime(lhs.S + rhs.S, lhs.Us + rhs.Us); }\nstatic inline ImPlotTime operator-(const ImPlotTime& lhs, const ImPlotTime& rhs)\n{ return ImPlotTime(lhs.S - rhs.S, lhs.Us - rhs.Us); }\nstatic inline bool operator==(const ImPlotTime& lhs, const ImPlotTime& rhs)\n{ return lhs.S == rhs.S && lhs.Us == rhs.Us; }\nstatic inline bool operator<(const ImPlotTime& lhs, const ImPlotTime& rhs)\n{ return lhs.S == rhs.S ? lhs.Us < rhs.Us : lhs.S < rhs.S; }\nstatic inline bool operator>(const ImPlotTime& lhs, const ImPlotTime& rhs)\n{ return rhs < lhs; }\nstatic inline bool operator<=(const ImPlotTime& lhs, const ImPlotTime& rhs)\n{ return lhs < rhs || lhs == rhs; }\nstatic inline bool operator>=(const ImPlotTime& lhs, const ImPlotTime& rhs)\n{ return lhs > rhs || lhs == rhs; }\n\n// Colormap data storage\nstruct ImPlotColormapData {\n    ImVector<ImU32> Keys;\n    ImVector<int>   KeyCounts;\n    ImVector<int>   KeyOffsets;\n    ImVector<ImU32> Tables;\n    ImVector<int>   TableSizes;\n    ImVector<int>   TableOffsets;\n    ImGuiTextBuffer Text;\n    ImVector<int>   TextOffsets;\n    ImVector<bool>  Quals;\n    ImGuiStorage    Map;\n    int             Count;\n\n    ImPlotColormapData() { Count = 0; }\n\n    int Append(const char* name, const ImU32* keys, int count, bool qual) {\n        if (GetIndex(name) != -1)\n            return -1;\n        KeyOffsets.push_back(Keys.size());\n        KeyCounts.push_back(count);\n        Keys.reserve(Keys.size()+count);\n        for (int i = 0; i < count; ++i)\n            Keys.push_back(keys[i]);\n        TextOffsets.push_back(Text.size());\n        Text.append(name, name + strlen(name) + 1);\n        Quals.push_back(qual);\n        ImGuiID id = ImHashStr(name);\n        int idx = Count++;\n        Map.SetInt(id,idx);\n        _AppendTable(idx);\n        return idx;\n    }\n\n    void _AppendTable(ImPlotColormap cmap) {\n        int key_count     = GetKeyCount(cmap);\n        const ImU32* keys = GetKeys(cmap);\n        int off = Tables.size();\n        TableOffsets.push_back(off);\n        if (IsQual(cmap)) {\n            Tables.reserve(key_count);\n            for (int i = 0; i < key_count; ++i)\n                Tables.push_back(keys[i]);\n            TableSizes.push_back(key_count);\n        }\n        else {\n            int max_size = 255 * (key_count-1) + 1;\n            Tables.reserve(off + max_size);\n            // ImU32 last = keys[0];\n            // Tables.push_back(last);\n            // int n = 1;\n            for (int i = 0; i < key_count-1; ++i) {\n                for (int s = 0; s < 255; ++s) {\n                    ImU32 a = keys[i];\n                    ImU32 b = keys[i+1];\n                    ImU32 c = ImMixU32(a,b,s);\n                    // if (c != last) {\n                        Tables.push_back(c);\n                        // last = c;\n                        // n++;\n                    // }\n                }\n            }\n            ImU32 c = keys[key_count-1];\n            // if (c != last) {\n                Tables.push_back(c);\n                // n++;\n            // }\n            // TableSizes.push_back(n);\n            TableSizes.push_back(max_size);\n        }\n    }\n\n    void RebuildTables() {\n        Tables.resize(0);\n        TableSizes.resize(0);\n        TableOffsets.resize(0);\n        for (int i = 0; i < Count; ++i)\n            _AppendTable(i);\n    }\n\n    inline bool           IsQual(ImPlotColormap cmap) const                      { return Quals[cmap];                                                }\n    inline const char*    GetName(ImPlotColormap cmap) const                     { return cmap < Count ? Text.Buf.Data + TextOffsets[cmap] : nullptr; }\n    inline ImPlotColormap GetIndex(const char* name) const                       { ImGuiID key = ImHashStr(name); return Map.GetInt(key,-1);          }\n\n    inline const ImU32*   GetKeys(ImPlotColormap cmap) const                     { return &Keys[KeyOffsets[cmap]];                                    }\n    inline int            GetKeyCount(ImPlotColormap cmap) const                 { return KeyCounts[cmap];                                            }\n    inline ImU32          GetKeyColor(ImPlotColormap cmap, int idx) const        { return Keys[KeyOffsets[cmap]+idx];                                 }\n    inline void           SetKeyColor(ImPlotColormap cmap, int idx, ImU32 value) { Keys[KeyOffsets[cmap]+idx] = value; RebuildTables();               }\n\n    inline const ImU32*   GetTable(ImPlotColormap cmap) const                    { return &Tables[TableOffsets[cmap]];                                }\n    inline int            GetTableSize(ImPlotColormap cmap) const                { return TableSizes[cmap];                                           }\n    inline ImU32          GetTableColor(ImPlotColormap cmap, int idx) const      { return Tables[TableOffsets[cmap]+idx];                             }\n\n    inline ImU32 LerpTable(ImPlotColormap cmap, float t) const {\n        int off = TableOffsets[cmap];\n        int siz = TableSizes[cmap];\n        int idx = Quals[cmap] ? ImClamp((int)(siz*t),0,siz-1) : (int)((siz - 1) * t + 0.5f);\n        return Tables[off + idx];\n    }\n};\n\n// ImPlotPoint with positive/negative error values\nstruct ImPlotPointError {\n    double X, Y, Neg, Pos;\n    ImPlotPointError(double x, double y, double neg, double pos) {\n        X = x; Y = y; Neg = neg; Pos = pos;\n    }\n};\n\n// Interior plot label/annotation\nstruct ImPlotAnnotation {\n    ImVec2 Pos;\n    ImVec2 Offset;\n    ImU32  ColorBg;\n    ImU32  ColorFg;\n    int    TextOffset;\n    bool   Clamp;\n    ImPlotAnnotation() {\n        ColorBg = ColorFg = 0;\n        TextOffset = 0;\n        Clamp = false;\n    }\n};\n\n// Collection of plot labels\nstruct ImPlotAnnotationCollection {\n\n    ImVector<ImPlotAnnotation> Annotations;\n    ImGuiTextBuffer            TextBuffer;\n    int                        Size;\n\n    ImPlotAnnotationCollection() { Reset(); }\n\n    void AppendV(const ImVec2& pos, const ImVec2& off, ImU32 bg, ImU32 fg, bool clamp, const char* fmt,  va_list args) IM_FMTLIST(7) {\n        ImPlotAnnotation an;\n        an.Pos = pos; an.Offset = off;\n        an.ColorBg = bg; an.ColorFg = fg;\n        an.TextOffset = TextBuffer.size();\n        an.Clamp = clamp;\n        Annotations.push_back(an);\n        TextBuffer.appendfv(fmt, args);\n        const char nul[] = \"\";\n        TextBuffer.append(nul,nul+1);\n        Size++;\n    }\n\n    void Append(const ImVec2& pos, const ImVec2& off, ImU32 bg, ImU32 fg, bool clamp, const char* fmt,  ...) IM_FMTARGS(7) {\n        va_list args;\n        va_start(args, fmt);\n        AppendV(pos, off, bg, fg, clamp, fmt, args);\n        va_end(args);\n    }\n\n    const char* GetText(int idx) {\n        return TextBuffer.Buf.Data + Annotations[idx].TextOffset;\n    }\n\n    void Reset() {\n        Annotations.shrink(0);\n        TextBuffer.Buf.shrink(0);\n        Size = 0;\n    }\n};\n\nstruct ImPlotTag {\n    ImAxis Axis;\n    double Value;\n    ImU32  ColorBg;\n    ImU32  ColorFg;\n    int    TextOffset;\n};\n\nstruct ImPlotTagCollection {\n\n    ImVector<ImPlotTag> Tags;\n    ImGuiTextBuffer     TextBuffer;\n    int                 Size;\n\n    ImPlotTagCollection() { Reset(); }\n\n    void AppendV(ImAxis axis, double value, ImU32 bg, ImU32 fg, const char* fmt, va_list args) IM_FMTLIST(6) {\n        ImPlotTag tag;\n        tag.Axis = axis;\n        tag.Value = value;\n        tag.ColorBg = bg;\n        tag.ColorFg = fg;\n        tag.TextOffset = TextBuffer.size();\n        Tags.push_back(tag);\n        TextBuffer.appendfv(fmt, args);\n        const char nul[] = \"\";\n        TextBuffer.append(nul,nul+1);\n        Size++;\n    }\n\n    void Append(ImAxis axis, double value, ImU32 bg, ImU32 fg, const char* fmt, ...) IM_FMTARGS(6) {\n        va_list args;\n        va_start(args, fmt);\n        AppendV(axis, value, bg, fg, fmt, args);\n        va_end(args);\n    }\n\n    const char* GetText(int idx) {\n        return TextBuffer.Buf.Data + Tags[idx].TextOffset;\n    }\n\n    void Reset() {\n        Tags.shrink(0);\n        TextBuffer.Buf.shrink(0);\n        Size = 0;\n    }\n};\n\n// Tick mark info\nstruct ImPlotTick\n{\n    double PlotPos;\n    float  PixelPos;\n    ImVec2 LabelSize;\n    int    TextOffset;\n    bool   Major;\n    bool   ShowLabel;\n    int    Level;\n    int    Idx;\n\n    ImPlotTick(double value, bool major, int level, bool show_label) {\n        PixelPos     = 0;\n        PlotPos      = value;\n        Major        = major;\n        ShowLabel    = show_label;\n        Level        = level;\n        TextOffset   = -1;\n    }\n};\n\n// Collection of ticks\nstruct ImPlotTicker {\n    ImVector<ImPlotTick> Ticks;\n    ImGuiTextBuffer      TextBuffer;\n    ImVec2               MaxSize;\n    ImVec2               LateSize;\n    int                  Levels;\n\n    ImPlotTicker() {\n        Reset();\n    }\n\n    ImPlotTick& AddTick(double value, bool major, int level, bool show_label, const char* label) {\n        ImPlotTick tick(value, major, level, show_label);\n        if (show_label && label != nullptr) {\n            tick.TextOffset = TextBuffer.size();\n            TextBuffer.append(label, label + strlen(label) + 1);\n            tick.LabelSize = ImGui::CalcTextSize(TextBuffer.Buf.Data + tick.TextOffset);\n        }\n        return AddTick(tick);\n    }\n\n    ImPlotTick& AddTick(double value, bool major, int level, bool show_label, ImPlotFormatter formatter, void* data) {\n        ImPlotTick tick(value, major, level, show_label);\n        if (show_label && formatter != nullptr) {\n            char buff[IMPLOT_LABEL_MAX_SIZE];\n            tick.TextOffset = TextBuffer.size();\n            formatter(tick.PlotPos, buff, sizeof(buff), data);\n            TextBuffer.append(buff, buff + strlen(buff) + 1);\n            tick.LabelSize = ImGui::CalcTextSize(TextBuffer.Buf.Data + tick.TextOffset);\n        }\n        return AddTick(tick);\n    }\n\n    inline ImPlotTick& AddTick(ImPlotTick tick) {\n        if (tick.ShowLabel) {\n            MaxSize.x     =  tick.LabelSize.x > MaxSize.x ? tick.LabelSize.x : MaxSize.x;\n            MaxSize.y     =  tick.LabelSize.y > MaxSize.y ? tick.LabelSize.y : MaxSize.y;\n        }\n        tick.Idx = Ticks.size();\n        Ticks.push_back(tick);\n        return Ticks.back();\n    }\n\n    const char* GetText(int idx) const {\n        return TextBuffer.Buf.Data + Ticks[idx].TextOffset;\n    }\n\n    const char* GetText(const ImPlotTick& tick) {\n        return GetText(tick.Idx);\n    }\n\n    void OverrideSizeLate(const ImVec2& size) {\n        LateSize.x = size.x > LateSize.x ? size.x : LateSize.x;\n        LateSize.y = size.y > LateSize.y ? size.y : LateSize.y;\n    }\n\n    void Reset() {\n        Ticks.shrink(0);\n        TextBuffer.Buf.shrink(0);\n        MaxSize = LateSize;\n        LateSize = ImVec2(0,0);\n        Levels = 1;\n    }\n\n    int TickCount() const {\n        return Ticks.Size;\n    }\n};\n\n// Axis state information that must persist after EndPlot\nstruct ImPlotAxis\n{\n    ImGuiID              ID;\n    ImPlotAxisFlags      Flags;\n    ImPlotAxisFlags      PreviousFlags;\n    ImPlotRange          Range;\n    ImPlotCond           RangeCond;\n    ImPlotScale          Scale;\n    ImPlotRange          FitExtents;\n    ImPlotAxis*          OrthoAxis;\n    ImPlotRange          ConstraintRange;\n    ImPlotRange          ConstraintZoom;\n\n    ImPlotTicker         Ticker;\n    ImPlotFormatter      Formatter;\n    void*                FormatterData;\n    char                 FormatSpec[16];\n    ImPlotLocator        Locator;\n\n    double*              LinkedMin;\n    double*              LinkedMax;\n\n    int                  PickerLevel;\n    ImPlotTime           PickerTimeMin, PickerTimeMax;\n\n    ImPlotTransform      TransformForward;\n    ImPlotTransform      TransformInverse;\n    void*                TransformData;\n    float                PixelMin, PixelMax;\n    double               ScaleMin, ScaleMax;\n    double               ScaleToPixel;\n    float                Datum1, Datum2;\n\n    ImRect               HoverRect;\n    int                  LabelOffset;\n    ImU32                ColorMaj, ColorMin, ColorTick, ColorTxt, ColorBg, ColorHov, ColorAct, ColorHiLi;\n\n    bool                 Enabled;\n    bool                 Vertical;\n    bool                 FitThisFrame;\n    bool                 HasRange;\n    bool                 HasFormatSpec;\n    bool                 ShowDefaultTicks;\n    bool                 Hovered;\n    bool                 Held;\n\n    ImPlotAxis() {\n        ID               = 0;\n        Flags            = PreviousFlags = ImPlotAxisFlags_None;\n        Range.Min        = 0;\n        Range.Max        = 1;\n        Scale            = ImPlotScale_Linear;\n        TransformForward = TransformInverse = nullptr;\n        TransformData    = nullptr;\n        FitExtents.Min   = HUGE_VAL;\n        FitExtents.Max   = -HUGE_VAL;\n        OrthoAxis        = nullptr;\n        ConstraintRange  = ImPlotRange(-INFINITY,INFINITY);\n        ConstraintZoom   = ImPlotRange(DBL_MIN,INFINITY);\n        LinkedMin        = LinkedMax = nullptr;\n        PickerLevel      = 0;\n        Datum1           = Datum2 = 0;\n        PixelMin         = PixelMax = 0;\n        LabelOffset      = -1;\n        ColorMaj         = ColorMin = ColorTick = ColorTxt = ColorBg = ColorHov = ColorAct = 0;\n        ColorHiLi        = IM_COL32_BLACK_TRANS;\n        Formatter        = nullptr;\n        FormatterData    = nullptr;\n        Locator          = nullptr;\n        Enabled          = Hovered = Held = FitThisFrame = HasRange = HasFormatSpec = false;\n        ShowDefaultTicks = true;\n    }\n\n    inline void Reset() {\n        Enabled          = false;\n        Scale            = ImPlotScale_Linear;\n        TransformForward = TransformInverse = nullptr;\n        TransformData    = nullptr;\n        LabelOffset      = -1;\n        HasFormatSpec    = false;\n        Formatter        = nullptr;\n        FormatterData    = nullptr;\n        Locator          = nullptr;\n        ShowDefaultTicks = true;\n        FitThisFrame     = false;\n        FitExtents.Min   = HUGE_VAL;\n        FitExtents.Max   = -HUGE_VAL;\n        OrthoAxis        = nullptr;\n        ConstraintRange  = ImPlotRange(-INFINITY,INFINITY);\n        ConstraintZoom   = ImPlotRange(DBL_MIN,INFINITY);\n        Ticker.Reset();\n    }\n\n    inline bool SetMin(double _min, bool force=false) {\n        if (!force && IsLockedMin())\n            return false;\n        _min = ImConstrainNan(ImConstrainInf(_min));\n        if (_min < ConstraintRange.Min)\n            _min = ConstraintRange.Min;\n        double z = Range.Max - _min;\n        if (z < ConstraintZoom.Min)\n            _min = Range.Max - ConstraintZoom.Min;\n        if (z > ConstraintZoom.Max)\n            _min = Range.Max - ConstraintZoom.Max;\n        if (_min >= Range.Max)\n            return false;\n        Range.Min = _min;\n        PickerTimeMin = ImPlotTime::FromDouble(Range.Min);\n        UpdateTransformCache();\n        return true;\n    };\n\n    inline bool SetMax(double _max, bool force=false) {\n        if (!force && IsLockedMax())\n            return false;\n        _max = ImConstrainNan(ImConstrainInf(_max));\n        if (_max > ConstraintRange.Max)\n            _max = ConstraintRange.Max;\n        double z = _max - Range.Min;\n        if (z < ConstraintZoom.Min)\n            _max = Range.Min + ConstraintZoom.Min;\n        if (z > ConstraintZoom.Max)\n            _max = Range.Min + ConstraintZoom.Max;\n        if (_max <= Range.Min)\n            return false;\n        Range.Max = _max;\n        PickerTimeMax = ImPlotTime::FromDouble(Range.Max);\n        UpdateTransformCache();\n        return true;\n    };\n\n    inline void SetRange(double v1, double v2) {\n        Range.Min = ImMin(v1,v2);\n        Range.Max = ImMax(v1,v2);\n        Constrain();\n        PickerTimeMin = ImPlotTime::FromDouble(Range.Min);\n        PickerTimeMax = ImPlotTime::FromDouble(Range.Max);\n        UpdateTransformCache();\n    }\n\n    inline void SetRange(const ImPlotRange& range) {\n        SetRange(range.Min, range.Max);\n    }\n\n    inline void SetAspect(double unit_per_pix) {\n        double new_size = unit_per_pix * PixelSize();\n        double delta    = (new_size - Range.Size()) * 0.5;\n        if (IsLocked())\n            return;\n        else if (IsLockedMin() && !IsLockedMax())\n            SetRange(Range.Min, Range.Max  + 2*delta);\n        else if (!IsLockedMin() && IsLockedMax())\n            SetRange(Range.Min - 2*delta, Range.Max);\n        else\n            SetRange(Range.Min - delta, Range.Max + delta);\n    }\n\n    inline float PixelSize() const { return ImAbs(PixelMax - PixelMin); }\n\n    inline double GetAspect() const { return Range.Size() / PixelSize(); }\n\n    inline void Constrain() {\n        Range.Min = ImConstrainNan(ImConstrainInf(Range.Min));\n        Range.Max = ImConstrainNan(ImConstrainInf(Range.Max));\n        if (Range.Min < ConstraintRange.Min)\n            Range.Min = ConstraintRange.Min;\n        if (Range.Max > ConstraintRange.Max)\n            Range.Max = ConstraintRange.Max;\n        double z = Range.Size();\n        if (z < ConstraintZoom.Min) {\n            double delta = (ConstraintZoom.Min - z) * 0.5;\n            Range.Min -= delta;\n            Range.Max += delta;\n        }\n        if (z > ConstraintZoom.Max) {\n            double delta = (z - ConstraintZoom.Max) * 0.5;\n            Range.Min += delta;\n            Range.Max -= delta;\n        }\n        if (Range.Max <= Range.Min)\n            Range.Max = Range.Min + DBL_EPSILON;\n    }\n\n    inline void UpdateTransformCache() {\n        ScaleToPixel = (PixelMax - PixelMin) / Range.Size();\n        if (TransformForward != nullptr) {\n            ScaleMin = TransformForward(Range.Min, TransformData);\n            ScaleMax = TransformForward(Range.Max, TransformData);\n        }\n        else {\n            ScaleMin = Range.Min;\n            ScaleMax = Range.Max;\n        }\n    }\n\n    inline float PlotToPixels(double plt) const {\n        if (TransformForward != nullptr) {\n            double s = TransformForward(plt, TransformData);\n            double t = (s - ScaleMin) / (ScaleMax - ScaleMin);\n            plt      = Range.Min + Range.Size() * t;\n        }\n        return (float)(PixelMin + ScaleToPixel * (plt - Range.Min));\n    }\n\n\n    inline double PixelsToPlot(float pix) const {\n        double plt = (pix - PixelMin) / ScaleToPixel + Range.Min;\n        if (TransformInverse != nullptr) {\n            double t = (plt - Range.Min) / Range.Size();\n            double s = t * (ScaleMax - ScaleMin) + ScaleMin;\n            plt = TransformInverse(s, TransformData);\n        }\n        return plt;\n    }\n\n    inline void ExtendFit(double v) {\n        if (!ImNanOrInf(v) && v >= ConstraintRange.Min && v <= ConstraintRange.Max) {\n            FitExtents.Min = v < FitExtents.Min ? v : FitExtents.Min;\n            FitExtents.Max = v > FitExtents.Max ? v : FitExtents.Max;\n        }\n    }\n\n    inline void ExtendFitWith(ImPlotAxis& alt, double v, double v_alt) {\n        if (ImHasFlag(Flags, ImPlotAxisFlags_RangeFit) && !alt.Range.Contains(v_alt))\n            return;\n        if (!ImNanOrInf(v) && v >= ConstraintRange.Min && v <= ConstraintRange.Max) {\n            FitExtents.Min = v < FitExtents.Min ? v : FitExtents.Min;\n            FitExtents.Max = v > FitExtents.Max ? v : FitExtents.Max;\n        }\n    }\n\n    inline void ApplyFit(float padding) {\n        const double ext_size = FitExtents.Size() * 0.5;\n        FitExtents.Min -= ext_size * padding;\n        FitExtents.Max += ext_size * padding;\n        if (!IsLockedMin() && !ImNanOrInf(FitExtents.Min))\n            Range.Min = FitExtents.Min;\n        if (!IsLockedMax() && !ImNanOrInf(FitExtents.Max))\n            Range.Max = FitExtents.Max;\n        if (ImAlmostEqual(Range.Min, Range.Max))  {\n            Range.Max += 0.5;\n            Range.Min -= 0.5;\n        }\n        Constrain();\n        UpdateTransformCache();\n    }\n\n    inline bool HasLabel()          const { return LabelOffset != -1 && !ImHasFlag(Flags, ImPlotAxisFlags_NoLabel);                          }\n    inline bool HasGridLines()      const { return !ImHasFlag(Flags, ImPlotAxisFlags_NoGridLines);                                           }\n    inline bool HasTickLabels()     const { return !ImHasFlag(Flags, ImPlotAxisFlags_NoTickLabels);                                          }\n    inline bool HasTickMarks()      const { return !ImHasFlag(Flags, ImPlotAxisFlags_NoTickMarks);                                           }\n    inline bool WillRender()        const { return Enabled && (HasGridLines() || HasTickLabels() || HasTickMarks());                         }\n    inline bool IsOpposite()        const { return ImHasFlag(Flags, ImPlotAxisFlags_Opposite);                                               }\n    inline bool IsInverted()        const { return ImHasFlag(Flags, ImPlotAxisFlags_Invert);                                                 }\n    inline bool IsForeground()      const { return ImHasFlag(Flags, ImPlotAxisFlags_Foreground);                                             }\n    inline bool IsAutoFitting()     const { return ImHasFlag(Flags, ImPlotAxisFlags_AutoFit);                                                }\n    inline bool CanInitFit()        const { return !ImHasFlag(Flags, ImPlotAxisFlags_NoInitialFit) && !HasRange && !LinkedMin && !LinkedMax; }\n    inline bool IsRangeLocked()     const { return HasRange && RangeCond == ImPlotCond_Always;                                               }\n    inline bool IsLockedMin()       const { return !Enabled || IsRangeLocked() || ImHasFlag(Flags, ImPlotAxisFlags_LockMin);                 }\n    inline bool IsLockedMax()       const { return !Enabled || IsRangeLocked() || ImHasFlag(Flags, ImPlotAxisFlags_LockMax);                 }\n    inline bool IsLocked()          const { return IsLockedMin() && IsLockedMax();                                                           }\n    inline bool IsInputLockedMin()  const { return IsLockedMin() || IsAutoFitting();                                                         }\n    inline bool IsInputLockedMax()  const { return IsLockedMax() || IsAutoFitting();                                                         }\n    inline bool IsInputLocked()     const { return IsLocked()    || IsAutoFitting();                                                         }\n    inline bool HasMenus()          const { return !ImHasFlag(Flags, ImPlotAxisFlags_NoMenus);                                               }\n\n    inline bool IsPanLocked(bool increasing) {\n        if (ImHasFlag(Flags, ImPlotAxisFlags_PanStretch)) {\n            return IsInputLocked();\n        }\n        else {\n            if (IsLockedMin() || IsLockedMax() || IsAutoFitting())\n                return false;\n            if (increasing)\n                return Range.Max == ConstraintRange.Max;\n            else\n                return Range.Min == ConstraintRange.Min;\n        }\n    }\n\n    void PushLinks() {\n        if (LinkedMin) { *LinkedMin = Range.Min; }\n        if (LinkedMax) { *LinkedMax = Range.Max; }\n    }\n\n    void PullLinks() {\n        if (LinkedMin && LinkedMax) { SetRange(*LinkedMin, *LinkedMax); }\n        else if (LinkedMin) { SetMin(*LinkedMin,true); }\n        else if (LinkedMax) { SetMax(*LinkedMax,true); }\n    }\n};\n\n// Align plots group data\nstruct ImPlotAlignmentData {\n    bool  Vertical;\n    float PadA;\n    float PadB;\n    float PadAMax;\n    float PadBMax;\n    ImPlotAlignmentData() {\n        Vertical    = true;\n        PadA = PadB = PadAMax = PadBMax = 0;\n    }\n    void Begin() { PadAMax = PadBMax = 0; }\n    void Update(float& pad_a, float& pad_b, float& delta_a, float& delta_b) {\n        float bak_a = pad_a; float bak_b = pad_b;\n        if (PadAMax < pad_a) { PadAMax = pad_a; }\n        if (PadBMax < pad_b) { PadBMax = pad_b; }\n        if (pad_a < PadA)    { pad_a = PadA; delta_a = pad_a - bak_a; } else { delta_a = 0; }\n        if (pad_b < PadB)    { pad_b = PadB; delta_b = pad_b - bak_b; } else { delta_b = 0; }\n    }\n    void End()   { PadA = PadAMax; PadB = PadBMax;      }\n    void Reset() { PadA = PadB = PadAMax = PadBMax = 0; }\n};\n\n// State information for Plot items\nstruct ImPlotItem\n{\n    ImGuiID      ID;\n    ImU32        Color;\n    ImRect       LegendHoverRect;\n    int          NameOffset;\n    bool         Show;\n    bool         LegendHovered;\n    bool         SeenThisFrame;\n\n    ImPlotItem() {\n        ID            = 0;\n        Color         = IM_COL32_WHITE;\n        NameOffset    = -1;\n        Show          = true;\n        SeenThisFrame = false;\n        LegendHovered = false;\n    }\n\n    ~ImPlotItem() { ID = 0; }\n};\n\n// Holds Legend state\nstruct ImPlotLegend\n{\n    ImPlotLegendFlags Flags;\n    ImPlotLegendFlags PreviousFlags;\n    ImPlotLocation    Location;\n    ImPlotLocation    PreviousLocation;\n    ImVector<int>     Indices;\n    ImGuiTextBuffer   Labels;\n    ImRect            Rect;\n    bool              Hovered;\n    bool              Held;\n    bool              CanGoInside;\n\n    ImPlotLegend() {\n        Flags        = PreviousFlags = ImPlotLegendFlags_None;\n        CanGoInside  = true;\n        Hovered      = Held = false;\n        Location     = PreviousLocation = ImPlotLocation_NorthWest;\n    }\n\n    void Reset() { Indices.shrink(0); Labels.Buf.shrink(0); }\n};\n\n// Holds Items and Legend data\nstruct ImPlotItemGroup\n{\n    ImGuiID            ID;\n    ImPlotLegend       Legend;\n    ImPool<ImPlotItem> ItemPool;\n    int                ColormapIdx;\n\n    ImPlotItemGroup() { ID = 0; ColormapIdx = 0; }\n\n    int         GetItemCount() const             { return ItemPool.GetBufSize();                                 }\n    ImGuiID     GetItemID(const char*  label_id) { return ImGui::GetID(label_id); /* GetIDWithSeed */            }\n    ImPlotItem* GetItem(ImGuiID id)              { return ItemPool.GetByKey(id);                                 }\n    ImPlotItem* GetItem(const char* label_id)    { return GetItem(GetItemID(label_id));                          }\n    ImPlotItem* GetOrAddItem(ImGuiID id)         { return ItemPool.GetOrAddByKey(id);                            }\n    ImPlotItem* GetItemByIndex(int i)            { return ItemPool.GetByIndex(i);                                }\n    int         GetItemIndex(ImPlotItem* item)   { return ItemPool.GetIndex(item);                               }\n    int         GetLegendCount() const           { return Legend.Indices.size();                                 }\n    ImPlotItem* GetLegendItem(int i)             { return ItemPool.GetByIndex(Legend.Indices[i]);                }\n    const char* GetLegendLabel(int i)            { return Legend.Labels.Buf.Data + GetLegendItem(i)->NameOffset; }\n    void        Reset()                          { ItemPool.Clear(); Legend.Reset(); ColormapIdx = 0;            }\n};\n\n// Holds Plot state information that must persist after EndPlot\nstruct ImPlotPlot\n{\n    ImGuiID              ID;\n    ImPlotFlags          Flags;\n    ImPlotFlags          PreviousFlags;\n    ImPlotLocation       MouseTextLocation;\n    ImPlotMouseTextFlags MouseTextFlags;\n    ImPlotAxis           Axes[ImAxis_COUNT];\n    ImGuiTextBuffer      TextBuffer;\n    ImPlotItemGroup      Items;\n    ImAxis               CurrentX;\n    ImAxis               CurrentY;\n    ImRect               FrameRect;\n    ImRect               CanvasRect;\n    ImRect               PlotRect;\n    ImRect               AxesRect;\n    ImRect               SelectRect;\n    ImVec2               SelectStart;\n    int                  TitleOffset;\n    bool                 JustCreated;\n    bool                 Initialized;\n    bool                 SetupLocked;\n    bool                 FitThisFrame;\n    bool                 Hovered;\n    bool                 Held;\n    bool                 Selecting;\n    bool                 Selected;\n    bool                 ContextLocked;\n\n    ImPlotPlot() {\n        Flags             = PreviousFlags = ImPlotFlags_None;\n        for (int i = 0; i < IMPLOT_NUM_X_AXES; ++i)\n            XAxis(i).Vertical = false;\n        for (int i = 0; i < IMPLOT_NUM_Y_AXES; ++i)\n            YAxis(i).Vertical = true;\n        SelectStart       = ImVec2(0,0);\n        CurrentX          = ImAxis_X1;\n        CurrentY          = ImAxis_Y1;\n        MouseTextLocation  = ImPlotLocation_South | ImPlotLocation_East;\n        MouseTextFlags     = ImPlotMouseTextFlags_None;\n        TitleOffset       = -1;\n        JustCreated       = true;\n        Initialized = SetupLocked = FitThisFrame = false;\n        Hovered = Held = Selected = Selecting = ContextLocked = false;\n    }\n\n    inline bool IsInputLocked() const {\n        for (int i = 0; i < IMPLOT_NUM_X_AXES; ++i) {\n            if (!XAxis(i).IsInputLocked())\n                return false;\n        }\n        for (int i = 0; i < IMPLOT_NUM_Y_AXES; ++i) {\n            if (!YAxis(i).IsInputLocked())\n                return false;\n        }\n        return true;\n    }\n\n    inline void ClearTextBuffer() { TextBuffer.Buf.shrink(0); }\n\n    inline void SetTitle(const char* title) {\n        if (title && ImGui::FindRenderedTextEnd(title, nullptr) != title) {\n            TitleOffset = TextBuffer.size();\n            TextBuffer.append(title, title + strlen(title) + 1);\n        }\n        else {\n            TitleOffset = -1;\n        }\n    }\n    inline bool HasTitle() const { return TitleOffset != -1 && !ImHasFlag(Flags, ImPlotFlags_NoTitle); }\n    inline const char* GetTitle() const { return TextBuffer.Buf.Data + TitleOffset; }\n\n    inline       ImPlotAxis& XAxis(int i)       { return Axes[ImAxis_X1 + i]; }\n    inline const ImPlotAxis& XAxis(int i) const { return Axes[ImAxis_X1 + i]; }\n    inline       ImPlotAxis& YAxis(int i)       { return Axes[ImAxis_Y1 + i]; }\n    inline const ImPlotAxis& YAxis(int i) const { return Axes[ImAxis_Y1 + i]; }\n\n    inline int EnabledAxesX() {\n        int cnt = 0;\n        for (int i = 0; i < IMPLOT_NUM_X_AXES; ++i)\n            cnt += XAxis(i).Enabled;\n        return cnt;\n    }\n\n    inline int EnabledAxesY() {\n        int cnt = 0;\n        for (int i = 0; i < IMPLOT_NUM_Y_AXES; ++i)\n            cnt += YAxis(i).Enabled;\n        return cnt;\n    }\n\n    inline void SetAxisLabel(ImPlotAxis& axis, const char* label) {\n        if (label && ImGui::FindRenderedTextEnd(label, nullptr) != label) {\n            axis.LabelOffset = TextBuffer.size();\n            TextBuffer.append(label, label + strlen(label) + 1);\n        }\n        else {\n            axis.LabelOffset = -1;\n        }\n    }\n\n    inline const char* GetAxisLabel(const ImPlotAxis& axis) const { return TextBuffer.Buf.Data + axis.LabelOffset; }\n};\n\n// Holds subplot data that must persist after EndSubplot\nstruct ImPlotSubplot {\n    ImGuiID                       ID;\n    ImPlotSubplotFlags            Flags;\n    ImPlotSubplotFlags            PreviousFlags;\n    ImPlotItemGroup               Items;\n    int                           Rows;\n    int                           Cols;\n    int                           CurrentIdx;\n    ImRect                        FrameRect;\n    ImRect                        GridRect;\n    ImVec2                        CellSize;\n    ImVector<ImPlotAlignmentData> RowAlignmentData;\n    ImVector<ImPlotAlignmentData> ColAlignmentData;\n    ImVector<float>               RowRatios;\n    ImVector<float>               ColRatios;\n    ImVector<ImPlotRange>         RowLinkData;\n    ImVector<ImPlotRange>         ColLinkData;\n    float                         TempSizes[2];\n    bool                          FrameHovered;\n    bool                          HasTitle;\n\n    ImPlotSubplot() {\n        ID                          = 0;\n        Flags = PreviousFlags       = ImPlotSubplotFlags_None;\n        Rows = Cols = CurrentIdx    = 0;\n        Items.Legend.Location       = ImPlotLocation_North;\n        Items.Legend.Flags          = ImPlotLegendFlags_Horizontal|ImPlotLegendFlags_Outside;\n        Items.Legend.CanGoInside    = false;\n        TempSizes[0] = TempSizes[1] = 0;\n        FrameHovered                = false;\n        HasTitle                    = false;\n    }\n};\n\n// Temporary data storage for upcoming plot\nstruct ImPlotNextPlotData\n{\n    ImPlotCond  RangeCond[ImAxis_COUNT];\n    ImPlotRange Range[ImAxis_COUNT];\n    bool        HasRange[ImAxis_COUNT];\n    bool        Fit[ImAxis_COUNT];\n    double*     LinkedMin[ImAxis_COUNT];\n    double*     LinkedMax[ImAxis_COUNT];\n\n    ImPlotNextPlotData() { Reset(); }\n\n    void Reset() {\n        for (int i = 0; i < ImAxis_COUNT; ++i) {\n            HasRange[i]                 = false;\n            Fit[i]                      = false;\n            LinkedMin[i] = LinkedMax[i] = nullptr;\n        }\n    }\n\n};\n\n// Temporary data storage for upcoming item\nstruct ImPlotNextItemData {\n    ImVec4          Colors[5]; // ImPlotCol_Line, ImPlotCol_Fill, ImPlotCol_MarkerOutline, ImPlotCol_MarkerFill, ImPlotCol_ErrorBar\n    float           LineWeight;\n    ImPlotMarker    Marker;\n    float           MarkerSize;\n    float           MarkerWeight;\n    float           FillAlpha;\n    float           ErrorBarSize;\n    float           ErrorBarWeight;\n    float           DigitalBitHeight;\n    float           DigitalBitGap;\n    bool            RenderLine;\n    bool            RenderFill;\n    bool            RenderMarkerLine;\n    bool            RenderMarkerFill;\n    bool            HasHidden;\n    bool            Hidden;\n    ImPlotCond      HiddenCond;\n    ImPlotNextItemData() { Reset(); }\n    void Reset() {\n        for (int i = 0; i < 5; ++i)\n            Colors[i] = IMPLOT_AUTO_COL;\n        LineWeight    = MarkerSize = MarkerWeight = FillAlpha = ErrorBarSize = ErrorBarWeight = DigitalBitHeight = DigitalBitGap = IMPLOT_AUTO;\n        Marker        = IMPLOT_AUTO;\n        HasHidden     = Hidden = false;\n    }\n};\n\n// Holds state information that must persist between calls to BeginPlot()/EndPlot()\nstruct ImPlotContext {\n    // Plot States\n    ImPool<ImPlotPlot>    Plots;\n    ImPool<ImPlotSubplot> Subplots;\n    ImPlotPlot*           CurrentPlot;\n    ImPlotSubplot*        CurrentSubplot;\n    ImPlotItemGroup*      CurrentItems;\n    ImPlotItem*           CurrentItem;\n    ImPlotItem*           PreviousItem;\n\n    // Tick Marks and Labels\n    ImPlotTicker CTicker;\n\n    // Annotation and Tabs\n    ImPlotAnnotationCollection Annotations;\n    ImPlotTagCollection        Tags;\n\n    // Flags\n    bool ChildWindowMade;\n\n    // Style and Colormaps\n    ImPlotStyle                 Style;\n    ImVector<ImGuiColorMod>     ColorModifiers;\n    ImVector<ImGuiStyleMod>     StyleModifiers;\n    ImPlotColormapData          ColormapData;\n    ImVector<ImPlotColormap>    ColormapModifiers;\n\n    // Time\n    tm Tm;\n\n    // Temp data for general use\n    ImVector<double>   TempDouble1, TempDouble2;\n    ImVector<int>      TempInt1;\n\n    // Misc\n    int                DigitalPlotItemCnt;\n    int                DigitalPlotOffset;\n    ImPlotNextPlotData NextPlotData;\n    ImPlotNextItemData NextItemData;\n    ImPlotInputMap     InputMap;\n    bool               OpenContextThisFrame;\n    ImGuiTextBuffer    MousePosStringBuilder;\n    ImPlotItemGroup*   SortItems;\n\n    // Align plots\n    ImPool<ImPlotAlignmentData> AlignmentData;\n    ImPlotAlignmentData*        CurrentAlignmentH;\n    ImPlotAlignmentData*        CurrentAlignmentV;\n};\n\n//-----------------------------------------------------------------------------\n// [SECTION] Internal API\n// No guarantee of forward compatibility here!\n//-----------------------------------------------------------------------------\n\nnamespace ImPlot {\n\n//-----------------------------------------------------------------------------\n// [SECTION] Context Utils\n//-----------------------------------------------------------------------------\n\n// Initializes an ImPlotContext\nIMPLOT_API void Initialize(ImPlotContext* ctx);\n// Resets an ImPlot context for the next call to BeginPlot\nIMPLOT_API void ResetCtxForNextPlot(ImPlotContext* ctx);\n// Resets an ImPlot context for the next call to BeginAlignedPlots\nIMPLOT_API void ResetCtxForNextAlignedPlots(ImPlotContext* ctx);\n// Resets an ImPlot context for the next call to BeginSubplot\nIMPLOT_API void ResetCtxForNextSubplot(ImPlotContext* ctx);\n\n//-----------------------------------------------------------------------------\n// [SECTION] Plot Utils\n//-----------------------------------------------------------------------------\n\n// Gets a plot from the current ImPlotContext\nIMPLOT_API ImPlotPlot* GetPlot(const char* title);\n// Gets the current plot from the current ImPlotContext\nIMPLOT_API ImPlotPlot* GetCurrentPlot();\n// Busts the cache for every plot in the current context\nIMPLOT_API void BustPlotCache();\n\n// Shows a plot's context menu.\nIMPLOT_API void ShowPlotContextMenu(ImPlotPlot& plot);\n\n//-----------------------------------------------------------------------------\n// [SECTION] Setup Utils\n//-----------------------------------------------------------------------------\n\n// Lock Setup and call SetupFinish if necessary.\nstatic inline void SetupLock() {\n    ImPlotContext& gp = *GImPlot;\n    if (!gp.CurrentPlot->SetupLocked)\n        SetupFinish();\n    gp.CurrentPlot->SetupLocked = true;\n}\n\n//-----------------------------------------------------------------------------\n// [SECTION] Subplot Utils\n//-----------------------------------------------------------------------------\n\n// Advances to next subplot\nIMPLOT_API void SubplotNextCell();\n\n// Shows a subplot's context menu.\nIMPLOT_API void ShowSubplotsContextMenu(ImPlotSubplot& subplot);\n\n//-----------------------------------------------------------------------------\n// [SECTION] Item Utils\n//-----------------------------------------------------------------------------\n\n// Begins a new item. Returns false if the item should not be plotted. Pushes PlotClipRect.\nIMPLOT_API bool BeginItem(const char* label_id, ImPlotItemFlags flags=0, ImPlotCol recolor_from=IMPLOT_AUTO);\n\n// Same as above but with fitting functionality.\ntemplate <typename _Fitter>\nbool BeginItemEx(const char* label_id, const _Fitter& fitter, ImPlotItemFlags flags=0, ImPlotCol recolor_from=IMPLOT_AUTO) {\n    if (BeginItem(label_id, flags, recolor_from)) {\n        ImPlotPlot& plot = *GetCurrentPlot();\n        if (plot.FitThisFrame && !ImHasFlag(flags, ImPlotItemFlags_NoFit))\n            fitter.Fit(plot.Axes[plot.CurrentX], plot.Axes[plot.CurrentY]);\n        return true;\n    }\n    return false;\n}\n\n// Ends an item (call only if BeginItem returns true). Pops PlotClipRect.\nIMPLOT_API void EndItem();\n\n// Register or get an existing item from the current plot.\nIMPLOT_API ImPlotItem* RegisterOrGetItem(const char* label_id, ImPlotItemFlags flags, bool* just_created = nullptr);\n// Get a plot item from the current plot.\nIMPLOT_API ImPlotItem* GetItem(const char* label_id);\n// Gets the current item.\nIMPLOT_API ImPlotItem* GetCurrentItem();\n// Busts the cache for every item for every plot in the current context.\nIMPLOT_API void BustItemCache();\n\n//-----------------------------------------------------------------------------\n// [SECTION] Axis Utils\n//-----------------------------------------------------------------------------\n\n// Returns true if any enabled axis is locked from user input.\nstatic inline bool AnyAxesInputLocked(ImPlotAxis* axes, int count) {\n    for (int i = 0; i < count; ++i) {\n        if (axes[i].Enabled && axes[i].IsInputLocked())\n            return true;\n    }\n    return false;\n}\n\n// Returns true if all enabled axes are locked from user input.\nstatic inline bool AllAxesInputLocked(ImPlotAxis* axes, int count) {\n    for (int i = 0; i < count; ++i) {\n        if (axes[i].Enabled && !axes[i].IsInputLocked())\n            return false;\n    }\n    return true;\n}\n\nstatic inline bool AnyAxesHeld(ImPlotAxis* axes, int count) {\n    for (int i = 0; i < count; ++i) {\n        if (axes[i].Enabled && axes[i].Held)\n            return true;\n    }\n    return false;\n}\n\nstatic inline bool AnyAxesHovered(ImPlotAxis* axes, int count) {\n    for (int i = 0; i < count; ++i) {\n        if (axes[i].Enabled && axes[i].Hovered)\n            return true;\n    }\n    return false;\n}\n\n// Returns true if the user has requested data to be fit.\nstatic inline bool FitThisFrame() {\n    return GImPlot->CurrentPlot->FitThisFrame;\n}\n\n// Extends the current plot's axes so that it encompasses a vertical line at x\nstatic inline void FitPointX(double x) {\n    ImPlotPlot& plot   = *GetCurrentPlot();\n    ImPlotAxis& x_axis = plot.Axes[plot.CurrentX];\n    x_axis.ExtendFit(x);\n}\n\n// Extends the current plot's axes so that it encompasses a horizontal line at y\nstatic inline void FitPointY(double y) {\n    ImPlotPlot& plot   = *GetCurrentPlot();\n    ImPlotAxis& y_axis = plot.Axes[plot.CurrentY];\n    y_axis.ExtendFit(y);\n}\n\n// Extends the current plot's axes so that it encompasses point p\nstatic inline void FitPoint(const ImPlotPoint& p) {\n    ImPlotPlot& plot   = *GetCurrentPlot();\n    ImPlotAxis& x_axis = plot.Axes[plot.CurrentX];\n    ImPlotAxis& y_axis = plot.Axes[plot.CurrentY];\n    x_axis.ExtendFitWith(y_axis, p.x, p.y);\n    y_axis.ExtendFitWith(x_axis, p.y, p.x);\n}\n\n// Returns true if two ranges overlap\nstatic inline bool RangesOverlap(const ImPlotRange& r1, const ImPlotRange& r2)\n{ return r1.Min <= r2.Max && r2.Min <= r1.Max; }\n\n// Shows an axis's context menu.\nIMPLOT_API void ShowAxisContextMenu(ImPlotAxis& axis, ImPlotAxis* equal_axis, bool time_allowed = false);\n\n//-----------------------------------------------------------------------------\n// [SECTION] Legend Utils\n//-----------------------------------------------------------------------------\n\n// Gets the position of an inner rect that is located inside of an outer rect according to an ImPlotLocation and padding amount.\nIMPLOT_API ImVec2 GetLocationPos(const ImRect& outer_rect, const ImVec2& inner_size, ImPlotLocation location, const ImVec2& pad = ImVec2(0,0));\n// Calculates the bounding box size of a legend\nIMPLOT_API ImVec2 CalcLegendSize(ImPlotItemGroup& items, const ImVec2& pad, const ImVec2& spacing, bool vertical);\n// Renders legend entries into a bounding box\nIMPLOT_API bool ShowLegendEntries(ImPlotItemGroup& items, const ImRect& legend_bb, bool interactable, const ImVec2& pad, const ImVec2& spacing, bool vertical, ImDrawList& DrawList);\n// Shows an alternate legend for the plot identified by #title_id, outside of the plot frame (can be called before or after of Begin/EndPlot but must occur in the same ImGui window!).\nIMPLOT_API void ShowAltLegend(const char* title_id, bool vertical = true, const ImVec2 size = ImVec2(0,0), bool interactable = true);\n// Shows an legends's context menu.\nIMPLOT_API bool ShowLegendContextMenu(ImPlotLegend& legend, bool visible);\n\n//-----------------------------------------------------------------------------\n// [SECTION] Label Utils\n//-----------------------------------------------------------------------------\n\n// Create a a string label for a an axis value\nIMPLOT_API void LabelAxisValue(const ImPlotAxis& axis, double value, char* buff, int size, bool round = false);\n\n//-----------------------------------------------------------------------------\n// [SECTION] Styling Utils\n//-----------------------------------------------------------------------------\n\n// Get styling data for next item (call between Begin/EndItem)\nstatic inline const ImPlotNextItemData& GetItemData() { return GImPlot->NextItemData; }\n\n// Returns true if a color is set to be automatically determined\nstatic inline bool IsColorAuto(const ImVec4& col) { return col.w == -1; }\n// Returns true if a style color is set to be automatically determined\nstatic inline bool IsColorAuto(ImPlotCol idx) { return IsColorAuto(GImPlot->Style.Colors[idx]); }\n// Returns the automatically deduced style color\nIMPLOT_API ImVec4 GetAutoColor(ImPlotCol idx);\n\n// Returns the style color whether it is automatic or custom set\nstatic inline ImVec4 GetStyleColorVec4(ImPlotCol idx) { return IsColorAuto(idx) ? GetAutoColor(idx) : GImPlot->Style.Colors[idx]; }\nstatic inline ImU32  GetStyleColorU32(ImPlotCol idx)  { return ImGui::ColorConvertFloat4ToU32(GetStyleColorVec4(idx)); }\n\n// Draws vertical text. The position is the bottom left of the text rect.\nIMPLOT_API void AddTextVertical(ImDrawList *DrawList, ImVec2 pos, ImU32 col, const char* text_begin, const char* text_end = nullptr);\n// Draws multiline horizontal text centered.\nIMPLOT_API void AddTextCentered(ImDrawList* DrawList, ImVec2 top_center, ImU32 col, const char* text_begin, const char* text_end = nullptr);\n// Calculates the size of vertical text\nstatic inline ImVec2 CalcTextSizeVertical(const char *text) {\n    ImVec2 sz = ImGui::CalcTextSize(text);\n    return ImVec2(sz.y, sz.x);\n}\n// Returns white or black text given background color\nstatic inline ImU32 CalcTextColor(const ImVec4& bg) { return (bg.x * 0.299f + bg.y * 0.587f + bg.z * 0.114f) > 0.5f ? IM_COL32_BLACK : IM_COL32_WHITE; }\nstatic inline ImU32 CalcTextColor(ImU32 bg)         { return CalcTextColor(ImGui::ColorConvertU32ToFloat4(bg)); }\n// Lightens or darkens a color for hover\nstatic inline ImU32 CalcHoverColor(ImU32 col)       {  return ImMixU32(col, CalcTextColor(col), 32); }\n\n// Clamps a label position so that it fits a rect defined by Min/Max\nstatic inline ImVec2 ClampLabelPos(ImVec2 pos, const ImVec2& size, const ImVec2& Min, const ImVec2& Max) {\n    if (pos.x < Min.x)              pos.x = Min.x;\n    if (pos.y < Min.y)              pos.y = Min.y;\n    if ((pos.x + size.x) > Max.x)   pos.x = Max.x - size.x;\n    if ((pos.y + size.y) > Max.y)   pos.y = Max.y - size.y;\n    return pos;\n}\n\n// Returns a color from the Color map given an index >= 0 (modulo will be performed).\nIMPLOT_API ImU32  GetColormapColorU32(int idx, ImPlotColormap cmap);\n// Returns the next unused colormap color and advances the colormap. Can be used to skip colors if desired.\nIMPLOT_API ImU32  NextColormapColorU32();\n// Linearly interpolates a color from the current colormap given t between 0 and 1.\nIMPLOT_API ImU32  SampleColormapU32(float t, ImPlotColormap cmap);\n\n// Render a colormap bar\nIMPLOT_API void RenderColorBar(const ImU32* colors, int size, ImDrawList& DrawList, const ImRect& bounds, bool vert, bool reversed, bool continuous);\n\n//-----------------------------------------------------------------------------\n// [SECTION] Math and Misc Utils\n//-----------------------------------------------------------------------------\n\n// Rounds x to powers of 2,5 and 10 for generating axis labels (from Graphics Gems 1 Chapter 11.2)\nIMPLOT_API double NiceNum(double x, bool round);\n// Computes order of magnitude of double.\nstatic inline int OrderOfMagnitude(double val) { return val == 0 ? 0 : (int)(floor(log10(fabs(val)))); }\n// Returns the precision required for a order of magnitude.\nstatic inline int OrderToPrecision(int order) { return order > 0 ? 0 : 1 - order; }\n// Returns a floating point precision to use given a value\nstatic inline int Precision(double val) { return OrderToPrecision(OrderOfMagnitude(val)); }\n// Round a value to a given precision\nstatic inline double RoundTo(double val, int prec) { double p = pow(10,(double)prec); return floor(val*p+0.5)/p; }\n\n// Returns the intersection point of two lines A and B (assumes they are not parallel!)\nstatic inline ImVec2 Intersection(const ImVec2& a1, const ImVec2& a2, const ImVec2& b1, const ImVec2& b2) {\n    float v1 = (a1.x * a2.y - a1.y * a2.x);  float v2 = (b1.x * b2.y - b1.y * b2.x);\n    float v3 = ((a1.x - a2.x) * (b1.y - b2.y) - (a1.y - a2.y) * (b1.x - b2.x));\n    return ImVec2((v1 * (b1.x - b2.x) - v2 * (a1.x - a2.x)) / v3, (v1 * (b1.y - b2.y) - v2 * (a1.y - a2.y)) / v3);\n}\n\n// Fills a buffer with n samples linear interpolated from vmin to vmax\ntemplate <typename T>\nvoid FillRange(ImVector<T>& buffer, int n, T vmin, T vmax) {\n    buffer.resize(n);\n    T step = (vmax - vmin) / (n - 1);\n    for (int i = 0; i < n; ++i) {\n        buffer[i] = vmin + i * step;\n    }\n}\n\n// Calculate histogram bin counts and widths\ntemplate <typename T>\nstatic inline void CalculateBins(const T* values, int count, ImPlotBin meth, const ImPlotRange& range, int& bins_out, double& width_out) {\n    switch (meth) {\n        case ImPlotBin_Sqrt:\n            bins_out  = (int)ceil(sqrt(count));\n            break;\n        case ImPlotBin_Sturges:\n            bins_out  = (int)ceil(1.0 + log2(count));\n            break;\n        case ImPlotBin_Rice:\n            bins_out  = (int)ceil(2 * cbrt(count));\n            break;\n        case ImPlotBin_Scott:\n            width_out = 3.49 * ImStdDev(values, count) / cbrt(count);\n            bins_out  = (int)round(range.Size() / width_out);\n            break;\n    }\n    width_out = range.Size() / bins_out;\n}\n\n//-----------------------------------------------------------------------------\n// Time Utils\n//-----------------------------------------------------------------------------\n\n// Returns true if year is leap year (366 days long)\nstatic inline bool IsLeapYear(int year) {\n    return  year % 4 == 0 && (year % 100 != 0 || year % 400 == 0);\n}\n// Returns the number of days in a month, accounting for Feb. leap years. #month is zero indexed.\nstatic inline int GetDaysInMonth(int year, int month) {\n    static const int days[12] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};\n    return  days[month] + (int)(month == 1 && IsLeapYear(year));\n}\n\n// Make a UNIX timestamp from a tm struct expressed in UTC time (i.e. GMT timezone).\nIMPLOT_API ImPlotTime MkGmtTime(struct tm *ptm);\n// Make a tm struct expressed in UTC time (i.e. GMT timezone) from a UNIX timestamp.\nIMPLOT_API tm* GetGmtTime(const ImPlotTime& t, tm* ptm);\n\n// Make a UNIX timestamp from a tm struct expressed in local time.\nIMPLOT_API ImPlotTime MkLocTime(struct tm *ptm);\n// Make a tm struct expressed in local time from a UNIX timestamp.\nIMPLOT_API tm* GetLocTime(const ImPlotTime& t, tm* ptm);\n\n// NB: The following functions only work if there is a current ImPlotContext because the\n// internal tm struct is owned by the context! They are aware of ImPlotStyle.UseLocalTime.\n\n// Make a timestamp from time components.\n// year[1970-3000], month[0-11], day[1-31], hour[0-23], min[0-59], sec[0-59], us[0,999999]\nIMPLOT_API ImPlotTime MakeTime(int year, int month = 0, int day = 1, int hour = 0, int min = 0, int sec = 0, int us = 0);\n// Get year component from timestamp [1970-3000]\nIMPLOT_API int GetYear(const ImPlotTime& t);\n\n// Adds or subtracts time from a timestamp. #count > 0 to add, < 0 to subtract.\nIMPLOT_API ImPlotTime AddTime(const ImPlotTime& t, ImPlotTimeUnit unit, int count);\n// Rounds a timestamp down to nearest unit.\nIMPLOT_API ImPlotTime FloorTime(const ImPlotTime& t, ImPlotTimeUnit unit);\n// Rounds a timestamp up to the nearest unit.\nIMPLOT_API ImPlotTime CeilTime(const ImPlotTime& t, ImPlotTimeUnit unit);\n// Rounds a timestamp up or down to the nearest unit.\nIMPLOT_API ImPlotTime RoundTime(const ImPlotTime& t, ImPlotTimeUnit unit);\n// Combines the date of one timestamp with the time-of-day of another timestamp.\nIMPLOT_API ImPlotTime CombineDateTime(const ImPlotTime& date_part, const ImPlotTime& time_part);\n\n// Formats the time part of timestamp t into a buffer according to #fmt\nIMPLOT_API int FormatTime(const ImPlotTime& t, char* buffer, int size, ImPlotTimeFmt fmt, bool use_24_hr_clk);\n// Formats the date part of timestamp t into a buffer according to #fmt\nIMPLOT_API int FormatDate(const ImPlotTime& t, char* buffer, int size, ImPlotDateFmt fmt, bool use_iso_8601);\n// Formats the time and/or date parts of a timestamp t into a buffer according to #fmt\nIMPLOT_API int FormatDateTime(const ImPlotTime& t, char* buffer, int size, ImPlotDateTimeSpec fmt);\n\n// Shows a date picker widget block (year/month/day).\n// #level = 0 for day, 1 for month, 2 for year. Modified by user interaction.\n// #t will be set when a day is clicked and the function will return true.\n// #t1 and #t2 are optional dates to highlight.\nIMPLOT_API bool ShowDatePicker(const char* id, int* level, ImPlotTime* t, const ImPlotTime* t1 = nullptr, const ImPlotTime* t2 = nullptr);\n// Shows a time picker widget block (hour/min/sec).\n// #t will be set when a new hour, minute, or sec is selected or am/pm is toggled, and the function will return true.\nIMPLOT_API bool ShowTimePicker(const char* id, ImPlotTime* t);\n\n//-----------------------------------------------------------------------------\n// [SECTION] Transforms\n//-----------------------------------------------------------------------------\n\nstatic inline double TransformForward_Log10(double v, void*) {\n    v = v <= 0.0 ? DBL_MIN : v;\n    return ImLog10(v);\n}\n\nstatic inline double TransformInverse_Log10(double v, void*) {\n    return ImPow(10, v);\n}\n\nstatic inline double TransformForward_SymLog(double v, void*) {\n    return 2.0 * ImAsinh(v / 2.0);\n}\n\nstatic inline double TransformInverse_SymLog(double v, void*) {\n    return 2.0 * ImSinh(v / 2.0);\n}\n\nstatic inline double TransformForward_Logit(double v, void*) {\n    v = ImClamp(v, DBL_MIN, 1.0 - DBL_EPSILON);\n    return ImLog10(v / (1 - v));\n}\n\nstatic inline double TransformInverse_Logit(double v, void*) {\n    return 1.0 / (1.0 + ImPow(10,-v));\n}\n\n//-----------------------------------------------------------------------------\n// [SECTION] Formatters\n//-----------------------------------------------------------------------------\n\nstatic inline int Formatter_Default(double value, char* buff, int size, void* data) {\n    char* fmt = (char*)data;\n    return ImFormatString(buff, size, fmt, value);\n}\n\nstatic inline int Formatter_Logit(double value, char* buff, int size, void*) {\n    if (value == 0.5)\n        return ImFormatString(buff,size,\"1/2\");\n    else if (value < 0.5)\n        return ImFormatString(buff,size,\"%g\", value);\n    else\n        return ImFormatString(buff,size,\"1 - %g\", 1 - value);\n}\n\nstruct Formatter_Time_Data {\n    ImPlotTime Time;\n    ImPlotDateTimeSpec Spec;\n    ImPlotFormatter UserFormatter;\n    void* UserFormatterData;\n};\n\nstatic inline int Formatter_Time(double, char* buff, int size, void* data) {\n    Formatter_Time_Data* ftd = (Formatter_Time_Data*)data;\n    return FormatDateTime(ftd->Time, buff, size, ftd->Spec);\n}\n\n//------------------------------------------------------------------------------\n// [SECTION] Locator\n//------------------------------------------------------------------------------\n\nvoid Locator_Default(ImPlotTicker& ticker, const ImPlotRange& range, float pixels, bool vertical, ImPlotFormatter formatter, void* formatter_data);\nvoid Locator_Time(ImPlotTicker& ticker, const ImPlotRange& range, float pixels, bool vertical, ImPlotFormatter formatter, void* formatter_data);\nvoid Locator_Log10(ImPlotTicker& ticker, const ImPlotRange& range, float pixels, bool vertical, ImPlotFormatter formatter, void* formatter_data);\nvoid Locator_SymLog(ImPlotTicker& ticker, const ImPlotRange& range, float pixels, bool vertical, ImPlotFormatter formatter, void* formatter_data);\n\n} // namespace ImPlot\n"
  },
  {
    "path": "src/DesktopPlusUI/implot/implot_items.cpp",
    "content": "// MIT License\n\n// Copyright (c) 2023 Evan Pezent\n\n// Permission is hereby granted, free of charge, to any person obtaining a copy\n// of this software and associated documentation files (the \"Software\"), to deal\n// in the Software without restriction, including without limitation the rights\n// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n// copies of the Software, and to permit persons to whom the Software is\n// furnished to do so, subject to the following conditions:\n\n// The above copyright notice and this permission notice shall be included in all\n// copies or substantial portions of the Software.\n\n// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n// SOFTWARE.\n\n// ImPlot v0.16\n\n#define IMGUI_DEFINE_MATH_OPERATORS\n#include \"implot.h\"\n#include \"implot_internal.h\"\n\n//-----------------------------------------------------------------------------\n// [SECTION] Macros and Defines\n//-----------------------------------------------------------------------------\n\n#define SQRT_1_2 0.70710678118f\n#define SQRT_3_2 0.86602540378f\n\n#ifndef IMPLOT_NO_FORCE_INLINE\n    #ifdef _MSC_VER\n        #define IMPLOT_INLINE __forceinline\n    #elif defined(__GNUC__)\n        #define IMPLOT_INLINE inline __attribute__((__always_inline__))\n    #elif defined(__CLANG__)\n        #if __has_attribute(__always_inline__)\n            #define IMPLOT_INLINE inline __attribute__((__always_inline__))\n        #else\n            #define IMPLOT_INLINE inline\n        #endif\n    #else\n        #define IMPLOT_INLINE inline\n    #endif\n#else\n    #define IMPLOT_INLINE inline\n#endif\n\n#if defined __SSE__ || defined __x86_64__ || defined _M_X64\n#ifndef IMGUI_ENABLE_SSE\n#include <immintrin.h>\n#endif\nstatic IMPLOT_INLINE float  ImInvSqrt(float x) { return _mm_cvtss_f32(_mm_rsqrt_ss(_mm_set_ss(x))); }\n#else\nstatic IMPLOT_INLINE float  ImInvSqrt(float x) { return 1.0f / sqrtf(x); }\n#endif\n\n#define IMPLOT_NORMALIZE2F_OVER_ZERO(VX,VY) do { float d2 = VX*VX + VY*VY; if (d2 > 0.0f) { float inv_len = ImInvSqrt(d2); VX *= inv_len; VY *= inv_len; } } while (0)\n\n// Support for pre-1.82 versions. Users on 1.82+ can use 0 (default) flags to mean \"all corners\" but in order to support older versions we are more explicit.\n#if (IMGUI_VERSION_NUM < 18102) && !defined(ImDrawFlags_RoundCornersAll)\n#define ImDrawFlags_RoundCornersAll ImDrawCornerFlags_All\n#endif\n\n//-----------------------------------------------------------------------------\n// [SECTION] Template instantiation utility\n//-----------------------------------------------------------------------------\n\n// By default, templates are instantiated for `float`, `double`, and for the following integer types, which are defined in imgui.h:\n//     signed char         ImS8;   // 8-bit signed integer\n//     unsigned char       ImU8;   // 8-bit unsigned integer\n//     signed short        ImS16;  // 16-bit signed integer\n//     unsigned short      ImU16;  // 16-bit unsigned integer\n//     signed int          ImS32;  // 32-bit signed integer == int\n//     unsigned int        ImU32;  // 32-bit unsigned integer\n//     signed   long long  ImS64;  // 64-bit signed integer\n//     unsigned long long  ImU64;  // 64-bit unsigned integer\n// (note: this list does *not* include `long`, `unsigned long` and `long double`)\n//\n// You can customize the supported types by defining IMPLOT_CUSTOM_NUMERIC_TYPES at compile time to define your own type list.\n//    As an example, you could use the compile time define given by the line below in order to support only float and double.\n//        -DIMPLOT_CUSTOM_NUMERIC_TYPES=\"(float)(double)\"\n//    In order to support all known C++ types, use:\n//        -DIMPLOT_CUSTOM_NUMERIC_TYPES=\"(signed char)(unsigned char)(signed short)(unsigned short)(signed int)(unsigned int)(signed long)(unsigned long)(signed long long)(unsigned long long)(float)(double)(long double)\"\n\n#ifdef IMPLOT_CUSTOM_NUMERIC_TYPES\n    #define IMPLOT_NUMERIC_TYPES IMPLOT_CUSTOM_NUMERIC_TYPES\n#else\n    #define IMPLOT_NUMERIC_TYPES (ImS8)(ImU8)(ImS16)(ImU16)(ImS32)(ImU32)(ImS64)(ImU64)(float)(double)\n#endif\n\n// CALL_INSTANTIATE_FOR_NUMERIC_TYPES will duplicate the template instantion code `INSTANTIATE_MACRO(T)` on supported types.\n#define _CAT(x, y) _CAT_(x, y)\n#define _CAT_(x,y) x ## y\n#define _INSTANTIATE_FOR_NUMERIC_TYPES(chain) _CAT(_INSTANTIATE_FOR_NUMERIC_TYPES_1 chain, _END)\n#define _INSTANTIATE_FOR_NUMERIC_TYPES_1(T) INSTANTIATE_MACRO(T) _INSTANTIATE_FOR_NUMERIC_TYPES_2\n#define _INSTANTIATE_FOR_NUMERIC_TYPES_2(T) INSTANTIATE_MACRO(T) _INSTANTIATE_FOR_NUMERIC_TYPES_1\n#define _INSTANTIATE_FOR_NUMERIC_TYPES_1_END\n#define _INSTANTIATE_FOR_NUMERIC_TYPES_2_END\n#define CALL_INSTANTIATE_FOR_NUMERIC_TYPES() _INSTANTIATE_FOR_NUMERIC_TYPES(IMPLOT_NUMERIC_TYPES)\n\nnamespace ImPlot {\n\n//-----------------------------------------------------------------------------\n// [SECTION] Utils\n//-----------------------------------------------------------------------------\n\n// Calc maximum index size of ImDrawIdx\ntemplate <typename T>\nstruct MaxIdx { static const unsigned int Value; };\ntemplate <> const unsigned int MaxIdx<unsigned short>::Value = 65535;\ntemplate <> const unsigned int MaxIdx<unsigned int>::Value   = 4294967295;\n\nIMPLOT_INLINE void GetLineRenderProps(const ImDrawList& draw_list, float& half_weight, ImVec2& tex_uv0, ImVec2& tex_uv1) {\n    const bool aa = ImHasFlag(draw_list.Flags, ImDrawListFlags_AntiAliasedLines) &&\n                    ImHasFlag(draw_list.Flags, ImDrawListFlags_AntiAliasedLinesUseTex);\n    if (aa) {\n        ImVec4 tex_uvs = draw_list._Data->TexUvLines[(int)(half_weight*2)];\n        tex_uv0 = ImVec2(tex_uvs.x, tex_uvs.y);\n        tex_uv1 = ImVec2(tex_uvs.z, tex_uvs.w);\n        half_weight += 1;\n    }\n    else {\n        tex_uv0 = tex_uv1 = draw_list._Data->TexUvWhitePixel;\n    }\n}\n\nIMPLOT_INLINE void PrimLine(ImDrawList& draw_list, const ImVec2& P1, const ImVec2& P2, float half_weight, ImU32 col, const ImVec2& tex_uv0, const ImVec2 tex_uv1) {\n    float dx = P2.x - P1.x;\n    float dy = P2.y - P1.y;\n    IMPLOT_NORMALIZE2F_OVER_ZERO(dx, dy);\n    dx *= half_weight;\n    dy *= half_weight;\n    draw_list._VtxWritePtr[0].pos.x = P1.x + dy;\n    draw_list._VtxWritePtr[0].pos.y = P1.y - dx;\n    draw_list._VtxWritePtr[0].uv    = tex_uv0;\n    draw_list._VtxWritePtr[0].col   = col;\n    draw_list._VtxWritePtr[1].pos.x = P2.x + dy;\n    draw_list._VtxWritePtr[1].pos.y = P2.y - dx;\n    draw_list._VtxWritePtr[1].uv    = tex_uv0;\n    draw_list._VtxWritePtr[1].col   = col;\n    draw_list._VtxWritePtr[2].pos.x = P2.x - dy;\n    draw_list._VtxWritePtr[2].pos.y = P2.y + dx;\n    draw_list._VtxWritePtr[2].uv    = tex_uv1;\n    draw_list._VtxWritePtr[2].col   = col;\n    draw_list._VtxWritePtr[3].pos.x = P1.x - dy;\n    draw_list._VtxWritePtr[3].pos.y = P1.y + dx;\n    draw_list._VtxWritePtr[3].uv    = tex_uv1;\n    draw_list._VtxWritePtr[3].col   = col;\n    draw_list._VtxWritePtr += 4;\n    draw_list._IdxWritePtr[0] = (ImDrawIdx)(draw_list._VtxCurrentIdx);\n    draw_list._IdxWritePtr[1] = (ImDrawIdx)(draw_list._VtxCurrentIdx + 1);\n    draw_list._IdxWritePtr[2] = (ImDrawIdx)(draw_list._VtxCurrentIdx + 2);\n    draw_list._IdxWritePtr[3] = (ImDrawIdx)(draw_list._VtxCurrentIdx);\n    draw_list._IdxWritePtr[4] = (ImDrawIdx)(draw_list._VtxCurrentIdx + 2);\n    draw_list._IdxWritePtr[5] = (ImDrawIdx)(draw_list._VtxCurrentIdx + 3);\n    draw_list._IdxWritePtr += 6;\n    draw_list._VtxCurrentIdx += 4;\n}\n\nIMPLOT_INLINE void PrimRectFill(ImDrawList& draw_list, const ImVec2& Pmin, const ImVec2& Pmax, ImU32 col, const ImVec2& uv) {\n    draw_list._VtxWritePtr[0].pos   = Pmin;\n    draw_list._VtxWritePtr[0].uv    = uv;\n    draw_list._VtxWritePtr[0].col   = col;\n    draw_list._VtxWritePtr[1].pos   = Pmax;\n    draw_list._VtxWritePtr[1].uv    = uv;\n    draw_list._VtxWritePtr[1].col   = col;\n    draw_list._VtxWritePtr[2].pos.x = Pmin.x;\n    draw_list._VtxWritePtr[2].pos.y = Pmax.y;\n    draw_list._VtxWritePtr[2].uv    = uv;\n    draw_list._VtxWritePtr[2].col   = col;\n    draw_list._VtxWritePtr[3].pos.x = Pmax.x;\n    draw_list._VtxWritePtr[3].pos.y = Pmin.y;\n    draw_list._VtxWritePtr[3].uv    = uv;\n    draw_list._VtxWritePtr[3].col   = col;\n    draw_list._VtxWritePtr += 4;\n    draw_list._IdxWritePtr[0] = (ImDrawIdx)(draw_list._VtxCurrentIdx);\n    draw_list._IdxWritePtr[1] = (ImDrawIdx)(draw_list._VtxCurrentIdx + 1);\n    draw_list._IdxWritePtr[2] = (ImDrawIdx)(draw_list._VtxCurrentIdx + 2);\n    draw_list._IdxWritePtr[3] = (ImDrawIdx)(draw_list._VtxCurrentIdx);\n    draw_list._IdxWritePtr[4] = (ImDrawIdx)(draw_list._VtxCurrentIdx + 1);\n    draw_list._IdxWritePtr[5] = (ImDrawIdx)(draw_list._VtxCurrentIdx + 3);\n    draw_list._IdxWritePtr += 6;\n    draw_list._VtxCurrentIdx += 4;\n}\n\nIMPLOT_INLINE void PrimRectLine(ImDrawList& draw_list, const ImVec2& Pmin, const ImVec2& Pmax, float weight, ImU32 col, const ImVec2& uv) {\n\n    draw_list._VtxWritePtr[0].pos.x = Pmin.x;\n    draw_list._VtxWritePtr[0].pos.y = Pmin.y;\n    draw_list._VtxWritePtr[0].uv    = uv;\n    draw_list._VtxWritePtr[0].col   = col;\n\n    draw_list._VtxWritePtr[1].pos.x = Pmin.x;\n    draw_list._VtxWritePtr[1].pos.y = Pmax.y;\n    draw_list._VtxWritePtr[1].uv    = uv;\n    draw_list._VtxWritePtr[1].col   = col;\n\n    draw_list._VtxWritePtr[2].pos.x = Pmax.x;\n    draw_list._VtxWritePtr[2].pos.y = Pmax.y;\n    draw_list._VtxWritePtr[2].uv    = uv;\n    draw_list._VtxWritePtr[2].col   = col;\n\n    draw_list._VtxWritePtr[3].pos.x = Pmax.x;\n    draw_list._VtxWritePtr[3].pos.y = Pmin.y;\n    draw_list._VtxWritePtr[3].uv    = uv;\n    draw_list._VtxWritePtr[3].col   = col;\n\n    draw_list._VtxWritePtr[4].pos.x = Pmin.x + weight;\n    draw_list._VtxWritePtr[4].pos.y = Pmin.y + weight;\n    draw_list._VtxWritePtr[4].uv    = uv;\n    draw_list._VtxWritePtr[4].col   = col;\n\n    draw_list._VtxWritePtr[5].pos.x = Pmin.x + weight;\n    draw_list._VtxWritePtr[5].pos.y = Pmax.y - weight;\n    draw_list._VtxWritePtr[5].uv    = uv;\n    draw_list._VtxWritePtr[5].col   = col;\n\n    draw_list._VtxWritePtr[6].pos.x = Pmax.x - weight;\n    draw_list._VtxWritePtr[6].pos.y = Pmax.y - weight;\n    draw_list._VtxWritePtr[6].uv    = uv;\n    draw_list._VtxWritePtr[6].col   = col;\n\n    draw_list._VtxWritePtr[7].pos.x = Pmax.x - weight;\n    draw_list._VtxWritePtr[7].pos.y = Pmin.y + weight;\n    draw_list._VtxWritePtr[7].uv    = uv;\n    draw_list._VtxWritePtr[7].col   = col;\n\n    draw_list._VtxWritePtr += 8;\n\n    draw_list._IdxWritePtr[0] = (ImDrawIdx)(draw_list._VtxCurrentIdx + 0);\n    draw_list._IdxWritePtr[1] = (ImDrawIdx)(draw_list._VtxCurrentIdx + 1);\n    draw_list._IdxWritePtr[2] = (ImDrawIdx)(draw_list._VtxCurrentIdx + 5);\n    draw_list._IdxWritePtr += 3;\n\n    draw_list._IdxWritePtr[0] = (ImDrawIdx)(draw_list._VtxCurrentIdx + 0);\n    draw_list._IdxWritePtr[1] = (ImDrawIdx)(draw_list._VtxCurrentIdx + 5);\n    draw_list._IdxWritePtr[2] = (ImDrawIdx)(draw_list._VtxCurrentIdx + 4);\n    draw_list._IdxWritePtr += 3;\n\n    draw_list._IdxWritePtr[0] = (ImDrawIdx)(draw_list._VtxCurrentIdx + 1);\n    draw_list._IdxWritePtr[1] = (ImDrawIdx)(draw_list._VtxCurrentIdx + 2);\n    draw_list._IdxWritePtr[2] = (ImDrawIdx)(draw_list._VtxCurrentIdx + 6);\n    draw_list._IdxWritePtr += 3;\n\n    draw_list._IdxWritePtr[0] = (ImDrawIdx)(draw_list._VtxCurrentIdx + 1);\n    draw_list._IdxWritePtr[1] = (ImDrawIdx)(draw_list._VtxCurrentIdx + 6);\n    draw_list._IdxWritePtr[2] = (ImDrawIdx)(draw_list._VtxCurrentIdx + 5);\n    draw_list._IdxWritePtr += 3;\n\n    draw_list._IdxWritePtr[0] = (ImDrawIdx)(draw_list._VtxCurrentIdx + 2);\n    draw_list._IdxWritePtr[1] = (ImDrawIdx)(draw_list._VtxCurrentIdx + 3);\n    draw_list._IdxWritePtr[2] = (ImDrawIdx)(draw_list._VtxCurrentIdx + 7);\n    draw_list._IdxWritePtr += 3;\n\n    draw_list._IdxWritePtr[0] = (ImDrawIdx)(draw_list._VtxCurrentIdx + 2);\n    draw_list._IdxWritePtr[1] = (ImDrawIdx)(draw_list._VtxCurrentIdx + 7);\n    draw_list._IdxWritePtr[2] = (ImDrawIdx)(draw_list._VtxCurrentIdx + 6);\n    draw_list._IdxWritePtr += 3;\n\n    draw_list._IdxWritePtr[0] = (ImDrawIdx)(draw_list._VtxCurrentIdx + 3);\n    draw_list._IdxWritePtr[1] = (ImDrawIdx)(draw_list._VtxCurrentIdx + 0);\n    draw_list._IdxWritePtr[2] = (ImDrawIdx)(draw_list._VtxCurrentIdx + 4);\n    draw_list._IdxWritePtr += 3;\n\n    draw_list._IdxWritePtr[0] = (ImDrawIdx)(draw_list._VtxCurrentIdx + 3);\n    draw_list._IdxWritePtr[1] = (ImDrawIdx)(draw_list._VtxCurrentIdx + 4);\n    draw_list._IdxWritePtr[2] = (ImDrawIdx)(draw_list._VtxCurrentIdx + 7);\n    draw_list._IdxWritePtr += 3;\n\n    draw_list._VtxCurrentIdx += 8;\n}\n\n\n//-----------------------------------------------------------------------------\n// [SECTION] Item Utils\n//-----------------------------------------------------------------------------\n\nImPlotItem* RegisterOrGetItem(const char* label_id, ImPlotItemFlags flags, bool* just_created) {\n    ImPlotContext& gp = *GImPlot;\n    ImPlotItemGroup& Items = *gp.CurrentItems;\n    ImGuiID id = Items.GetItemID(label_id);\n    if (just_created != nullptr)\n        *just_created = Items.GetItem(id) == nullptr;\n    ImPlotItem* item = Items.GetOrAddItem(id);\n    if (item->SeenThisFrame)\n        return item;\n    item->SeenThisFrame = true;\n    int idx = Items.GetItemIndex(item);\n    item->ID = id;\n    if (!ImHasFlag(flags, ImPlotItemFlags_NoLegend) && ImGui::FindRenderedTextEnd(label_id, nullptr) != label_id) {\n        Items.Legend.Indices.push_back(idx);\n        item->NameOffset = Items.Legend.Labels.size();\n        Items.Legend.Labels.append(label_id, label_id + strlen(label_id) + 1);\n    }\n    else {\n        item->Show = true;\n    }\n    return item;\n}\n\nImPlotItem* GetItem(const char* label_id) {\n    ImPlotContext& gp = *GImPlot;\n    return gp.CurrentItems->GetItem(label_id);\n}\n\nbool IsItemHidden(const char* label_id) {\n    ImPlotItem* item = GetItem(label_id);\n    return item != nullptr && !item->Show;\n}\n\nImPlotItem* GetCurrentItem() {\n    ImPlotContext& gp = *GImPlot;\n    return gp.CurrentItem;\n}\n\nvoid SetNextLineStyle(const ImVec4& col, float weight) {\n    ImPlotContext& gp = *GImPlot;\n    gp.NextItemData.Colors[ImPlotCol_Line] = col;\n    gp.NextItemData.LineWeight             = weight;\n}\n\nvoid SetNextFillStyle(const ImVec4& col, float alpha) {\n    ImPlotContext& gp = *GImPlot;\n    gp.NextItemData.Colors[ImPlotCol_Fill] = col;\n    gp.NextItemData.FillAlpha              = alpha;\n}\n\nvoid SetNextMarkerStyle(ImPlotMarker marker, float size, const ImVec4& fill, float weight, const ImVec4& outline) {\n    ImPlotContext& gp = *GImPlot;\n    gp.NextItemData.Marker                          = marker;\n    gp.NextItemData.Colors[ImPlotCol_MarkerFill]    = fill;\n    gp.NextItemData.MarkerSize                      = size;\n    gp.NextItemData.Colors[ImPlotCol_MarkerOutline] = outline;\n    gp.NextItemData.MarkerWeight                    = weight;\n}\n\nvoid SetNextErrorBarStyle(const ImVec4& col, float size, float weight) {\n    ImPlotContext& gp = *GImPlot;\n    gp.NextItemData.Colors[ImPlotCol_ErrorBar] = col;\n    gp.NextItemData.ErrorBarSize               = size;\n    gp.NextItemData.ErrorBarWeight             = weight;\n}\n\nImVec4 GetLastItemColor() {\n    ImPlotContext& gp = *GImPlot;\n    if (gp.PreviousItem)\n        return ImGui::ColorConvertU32ToFloat4(gp.PreviousItem->Color);\n    return ImVec4();\n}\n\nvoid BustItemCache() {\n    ImPlotContext& gp = *GImPlot;\n    for (int p = 0; p < gp.Plots.GetBufSize(); ++p) {\n        ImPlotPlot& plot = *gp.Plots.GetByIndex(p);\n        plot.Items.Reset();\n    }\n    for (int p = 0; p < gp.Subplots.GetBufSize(); ++p) {\n        ImPlotSubplot& subplot = *gp.Subplots.GetByIndex(p);\n        subplot.Items.Reset();\n    }\n}\n\nvoid BustColorCache(const char* plot_title_id) {\n    ImPlotContext& gp = *GImPlot;\n    if (plot_title_id == nullptr) {\n        BustItemCache();\n    }\n    else {\n        ImGuiID id = ImGui::GetCurrentWindow()->GetID(plot_title_id);\n        ImPlotPlot* plot = gp.Plots.GetByKey(id);\n        if (plot != nullptr)\n            plot->Items.Reset();\n        else {\n            ImPlotSubplot* subplot = gp.Subplots.GetByKey(id);\n            if (subplot != nullptr)\n                subplot->Items.Reset();\n        }\n    }\n}\n\n//-----------------------------------------------------------------------------\n// [SECTION] BeginItem / EndItem\n//-----------------------------------------------------------------------------\n\nstatic const float ITEM_HIGHLIGHT_LINE_SCALE = 2.0f;\nstatic const float ITEM_HIGHLIGHT_MARK_SCALE = 1.25f;\n\n// Begins a new item. Returns false if the item should not be plotted.\nbool BeginItem(const char* label_id, ImPlotItemFlags flags, ImPlotCol recolor_from) {\n    ImPlotContext& gp = *GImPlot;\n    IM_ASSERT_USER_ERROR(gp.CurrentPlot != nullptr, \"PlotX() needs to be called between BeginPlot() and EndPlot()!\");\n    SetupLock();\n    bool just_created;\n    ImPlotItem* item = RegisterOrGetItem(label_id, flags, &just_created);\n    // set current item\n    gp.CurrentItem = item;\n    ImPlotNextItemData& s = gp.NextItemData;\n    // set/override item color\n    if (recolor_from != -1) {\n        if (!IsColorAuto(s.Colors[recolor_from]))\n            item->Color = ImGui::ColorConvertFloat4ToU32(s.Colors[recolor_from]);\n        else if (!IsColorAuto(gp.Style.Colors[recolor_from]))\n            item->Color = ImGui::ColorConvertFloat4ToU32(gp.Style.Colors[recolor_from]);\n        else if (just_created)\n            item->Color = NextColormapColorU32();\n    }\n    else if (just_created) {\n        item->Color = NextColormapColorU32();\n    }\n    // hide/show item\n    if (gp.NextItemData.HasHidden) {\n        if (just_created || gp.NextItemData.HiddenCond == ImGuiCond_Always)\n            item->Show = !gp.NextItemData.Hidden;\n    }\n    if (!item->Show) {\n        // reset next item data\n        gp.NextItemData.Reset();\n        gp.PreviousItem = item;\n        gp.CurrentItem  = nullptr;\n        return false;\n    }\n    else {\n        ImVec4 item_color = ImGui::ColorConvertU32ToFloat4(item->Color);\n        // stage next item colors\n        s.Colors[ImPlotCol_Line]           = IsColorAuto(s.Colors[ImPlotCol_Line])          ? ( IsColorAuto(ImPlotCol_Line)           ? item_color                 : gp.Style.Colors[ImPlotCol_Line]          ) : s.Colors[ImPlotCol_Line];\n        s.Colors[ImPlotCol_Fill]           = IsColorAuto(s.Colors[ImPlotCol_Fill])          ? ( IsColorAuto(ImPlotCol_Fill)           ? item_color                 : gp.Style.Colors[ImPlotCol_Fill]          ) : s.Colors[ImPlotCol_Fill];\n        s.Colors[ImPlotCol_MarkerOutline]  = IsColorAuto(s.Colors[ImPlotCol_MarkerOutline]) ? ( IsColorAuto(ImPlotCol_MarkerOutline)  ? s.Colors[ImPlotCol_Line]   : gp.Style.Colors[ImPlotCol_MarkerOutline] ) : s.Colors[ImPlotCol_MarkerOutline];\n        s.Colors[ImPlotCol_MarkerFill]     = IsColorAuto(s.Colors[ImPlotCol_MarkerFill])    ? ( IsColorAuto(ImPlotCol_MarkerFill)     ? s.Colors[ImPlotCol_Line]   : gp.Style.Colors[ImPlotCol_MarkerFill]    ) : s.Colors[ImPlotCol_MarkerFill];\n        s.Colors[ImPlotCol_ErrorBar]       = IsColorAuto(s.Colors[ImPlotCol_ErrorBar])      ? ( GetStyleColorVec4(ImPlotCol_ErrorBar)                                                                         ) : s.Colors[ImPlotCol_ErrorBar];\n        // stage next item style vars\n        s.LineWeight         = s.LineWeight       < 0 ? gp.Style.LineWeight       : s.LineWeight;\n        s.Marker             = s.Marker           < 0 ? gp.Style.Marker           : s.Marker;\n        s.MarkerSize         = s.MarkerSize       < 0 ? gp.Style.MarkerSize       : s.MarkerSize;\n        s.MarkerWeight       = s.MarkerWeight     < 0 ? gp.Style.MarkerWeight     : s.MarkerWeight;\n        s.FillAlpha          = s.FillAlpha        < 0 ? gp.Style.FillAlpha        : s.FillAlpha;\n        s.ErrorBarSize       = s.ErrorBarSize     < 0 ? gp.Style.ErrorBarSize     : s.ErrorBarSize;\n        s.ErrorBarWeight     = s.ErrorBarWeight   < 0 ? gp.Style.ErrorBarWeight   : s.ErrorBarWeight;\n        s.DigitalBitHeight   = s.DigitalBitHeight < 0 ? gp.Style.DigitalBitHeight : s.DigitalBitHeight;\n        s.DigitalBitGap      = s.DigitalBitGap    < 0 ? gp.Style.DigitalBitGap    : s.DigitalBitGap;\n        // apply alpha modifier(s)\n        s.Colors[ImPlotCol_Fill].w       *= s.FillAlpha;\n        s.Colors[ImPlotCol_MarkerFill].w *= s.FillAlpha; // TODO: this should be separate, if it at all\n        // apply highlight mods\n        if (item->LegendHovered) {\n            if (!ImHasFlag(gp.CurrentItems->Legend.Flags, ImPlotLegendFlags_NoHighlightItem)) {\n                s.LineWeight   *= ITEM_HIGHLIGHT_LINE_SCALE;\n                s.MarkerSize   *= ITEM_HIGHLIGHT_MARK_SCALE;\n                s.MarkerWeight *= ITEM_HIGHLIGHT_LINE_SCALE;\n                // TODO: how to highlight fills?\n            }\n            if (!ImHasFlag(gp.CurrentItems->Legend.Flags, ImPlotLegendFlags_NoHighlightAxis)) {\n                if (gp.CurrentPlot->EnabledAxesX() > 1)\n                    gp.CurrentPlot->Axes[gp.CurrentPlot->CurrentX].ColorHiLi = item->Color;\n                if (gp.CurrentPlot->EnabledAxesY() > 1)\n                    gp.CurrentPlot->Axes[gp.CurrentPlot->CurrentY].ColorHiLi = item->Color;\n            }\n        }\n        // set render flags\n        s.RenderLine       = s.Colors[ImPlotCol_Line].w          > 0 && s.LineWeight > 0;\n        s.RenderFill       = s.Colors[ImPlotCol_Fill].w          > 0;\n        s.RenderMarkerFill = s.Colors[ImPlotCol_MarkerFill].w    > 0;\n        s.RenderMarkerLine = s.Colors[ImPlotCol_MarkerOutline].w > 0 && s.MarkerWeight > 0;\n        // push rendering clip rect\n        PushPlotClipRect();\n        return true;\n    }\n}\n\n// Ends an item (call only if BeginItem returns true)\nvoid EndItem() {\n    ImPlotContext& gp = *GImPlot;\n    // pop rendering clip rect\n    PopPlotClipRect();\n    // reset next item data\n    gp.NextItemData.Reset();\n    // set current item\n    gp.PreviousItem = gp.CurrentItem;\n    gp.CurrentItem  = nullptr;\n}\n\n//-----------------------------------------------------------------------------\n// [SECTION] Indexers\n//-----------------------------------------------------------------------------\n\ntemplate <typename T>\nIMPLOT_INLINE T IndexData(const T* data, int idx, int count, int offset, int stride) {\n    const int s = ((offset == 0) << 0) | ((stride == sizeof(T)) << 1);\n    switch (s) {\n        case 3 : return data[idx];\n        case 2 : return data[(offset + idx) % count];\n        case 1 : return *(const T*)(const void*)((const unsigned char*)data + (size_t)((idx) ) * stride);\n        case 0 : return *(const T*)(const void*)((const unsigned char*)data + (size_t)((offset + idx) % count) * stride);\n        default: return T(0);\n    }\n}\n\ntemplate <typename T>\nstruct IndexerIdx {\n    IndexerIdx(const T* data, int count, int offset = 0, int stride = sizeof(T)) :\n        Data(data),\n        Count(count),\n        Offset(count ? ImPosMod(offset, count) : 0),\n        Stride(stride)\n    { }\n    template <typename I> IMPLOT_INLINE double operator()(I idx) const {\n        return (double)IndexData(Data, idx, Count, Offset, Stride);\n    }\n    const T* Data;\n    int Count;\n    int Offset;\n    int Stride;\n};\n\ntemplate <typename _Indexer1, typename _Indexer2>\nstruct IndexerAdd {\n    IndexerAdd(const _Indexer1& indexer1, const _Indexer2& indexer2, double scale1 = 1, double scale2 = 1)\n        : Indexer1(indexer1),\n          Indexer2(indexer2),\n          Scale1(scale1),\n          Scale2(scale2),\n          Count(ImMin(Indexer1.Count, Indexer2.Count))\n    { }\n    template <typename I> IMPLOT_INLINE double operator()(I idx) const {\n        return Scale1 * Indexer1(idx) + Scale2 * Indexer2(idx);\n    }\n    const _Indexer1& Indexer1;\n    const _Indexer2& Indexer2;\n    double Scale1;\n    double Scale2;\n    int Count;\n};\n\nstruct IndexerLin {\n    IndexerLin(double m, double b) : M(m), B(b) { }\n    template <typename I> IMPLOT_INLINE double operator()(I idx) const {\n        return M * idx + B;\n    }\n    const double M;\n    const double B;\n};\n\nstruct IndexerConst {\n    IndexerConst(double ref) : Ref(ref) { }\n    template <typename I> IMPLOT_INLINE double operator()(I) const { return Ref; }\n    const double Ref;\n};\n\n//-----------------------------------------------------------------------------\n// [SECTION] Getters\n//-----------------------------------------------------------------------------\n\ntemplate <typename _IndexerX, typename _IndexerY>\nstruct GetterXY {\n    GetterXY(_IndexerX x, _IndexerY y, int count) : IndxerX(x), IndxerY(y), Count(count) { }\n    template <typename I> IMPLOT_INLINE ImPlotPoint operator()(I idx) const {\n        return ImPlotPoint(IndxerX(idx),IndxerY(idx));\n    }\n    const _IndexerX IndxerX;\n    const _IndexerY IndxerY;\n    const int Count;\n};\n\n/// Interprets a user's function pointer as ImPlotPoints\nstruct GetterFuncPtr {\n    GetterFuncPtr(ImPlotGetter getter, void* data, int count) :\n        Getter(getter),\n        Data(data),\n        Count(count)\n    { }\n    template <typename I> IMPLOT_INLINE ImPlotPoint operator()(I idx) const {\n        return Getter(idx, Data);\n    }\n    ImPlotGetter Getter;\n    void* const Data;\n    const int Count;\n};\n\ntemplate <typename _Getter>\nstruct GetterOverrideX {\n    GetterOverrideX(_Getter getter, double x) : Getter(getter), X(x), Count(getter.Count) { }\n    template <typename I> IMPLOT_INLINE ImPlotPoint operator()(I idx) const {\n        ImPlotPoint p = Getter(idx);\n        p.x = X;\n        return p;\n    }\n    const _Getter Getter;\n    const double X;\n    const int Count;\n};\n\ntemplate <typename _Getter>\nstruct GetterOverrideY {\n    GetterOverrideY(_Getter getter, double y) : Getter(getter), Y(y), Count(getter.Count) { }\n    template <typename I> IMPLOT_INLINE ImPlotPoint operator()(I idx) const {\n        ImPlotPoint p = Getter(idx);\n        p.y = Y;\n        return p;\n    }\n    const _Getter Getter;\n    const double Y;\n    const int Count;\n};\n\ntemplate <typename _Getter>\nstruct GetterLoop {\n    GetterLoop(_Getter getter) : Getter(getter), Count(getter.Count + 1) { }\n    template <typename I> IMPLOT_INLINE ImPlotPoint operator()(I idx) const {\n        idx = idx % (Count - 1);\n        return Getter(idx);\n    }\n    const _Getter Getter;\n    const int Count;\n};\n\ntemplate <typename T>\nstruct GetterError {\n    GetterError(const T* xs, const T* ys, const T* neg, const T* pos, int count, int offset, int stride) :\n        Xs(xs),\n        Ys(ys),\n        Neg(neg),\n        Pos(pos),\n        Count(count),\n        Offset(count ? ImPosMod(offset, count) : 0),\n        Stride(stride)\n    { }\n    template <typename I> IMPLOT_INLINE ImPlotPointError operator()(I idx) const {\n        return ImPlotPointError((double)IndexData(Xs,  idx, Count, Offset, Stride),\n                                (double)IndexData(Ys,  idx, Count, Offset, Stride),\n                                (double)IndexData(Neg, idx, Count, Offset, Stride),\n                                (double)IndexData(Pos, idx, Count, Offset, Stride));\n    }\n    const T* const Xs;\n    const T* const Ys;\n    const T* const Neg;\n    const T* const Pos;\n    const int Count;\n    const int Offset;\n    const int Stride;\n};\n\n//-----------------------------------------------------------------------------\n// [SECTION] Fitters\n//-----------------------------------------------------------------------------\n\ntemplate <typename _Getter1>\nstruct Fitter1 {\n    Fitter1(const _Getter1& getter) : Getter(getter) { }\n    void Fit(ImPlotAxis& x_axis, ImPlotAxis& y_axis) const {\n        for (int i = 0; i < Getter.Count; ++i) {\n            ImPlotPoint p = Getter(i);\n            x_axis.ExtendFitWith(y_axis, p.x, p.y);\n            y_axis.ExtendFitWith(x_axis, p.y, p.x);\n        }\n    }\n    const _Getter1& Getter;\n};\n\ntemplate <typename _Getter1>\nstruct FitterX {\n    FitterX(const _Getter1& getter) : Getter(getter) { }\n    void Fit(ImPlotAxis& x_axis, ImPlotAxis&) const {\n        for (int i = 0; i < Getter.Count; ++i) {\n            ImPlotPoint p = Getter(i);\n            x_axis.ExtendFit(p.x);\n        }\n    }\n    const _Getter1& Getter;\n};\n\ntemplate <typename _Getter1>\nstruct FitterY {\n    FitterY(const _Getter1& getter) : Getter(getter) { }\n    void Fit(ImPlotAxis&, ImPlotAxis& y_axis) const {\n        for (int i = 0; i < Getter.Count; ++i) {\n            ImPlotPoint p = Getter(i);\n            y_axis.ExtendFit(p.y);\n        }\n    }\n    const _Getter1& Getter;\n};\n\ntemplate <typename _Getter1, typename _Getter2>\nstruct Fitter2 {\n    Fitter2(const _Getter1& getter1, const _Getter2& getter2) : Getter1(getter1), Getter2(getter2) { }\n    void Fit(ImPlotAxis& x_axis, ImPlotAxis& y_axis) const {\n        for (int i = 0; i < Getter1.Count; ++i) {\n            ImPlotPoint p = Getter1(i);\n            x_axis.ExtendFitWith(y_axis, p.x, p.y);\n            y_axis.ExtendFitWith(x_axis, p.y, p.x);\n        }\n        for (int i = 0; i < Getter2.Count; ++i) {\n            ImPlotPoint p = Getter2(i);\n            x_axis.ExtendFitWith(y_axis, p.x, p.y);\n            y_axis.ExtendFitWith(x_axis, p.y, p.x);\n        }\n    }\n    const _Getter1& Getter1;\n    const _Getter2& Getter2;\n};\n\ntemplate <typename _Getter1, typename _Getter2>\nstruct FitterBarV {\n    FitterBarV(const _Getter1& getter1, const _Getter2& getter2, double width) :\n        Getter1(getter1),\n        Getter2(getter2),\n        HalfWidth(width*0.5)\n    { }\n    void Fit(ImPlotAxis& x_axis, ImPlotAxis& y_axis) const {\n        int count = ImMin(Getter1.Count, Getter2.Count);\n        for (int i = 0; i < count; ++i) {\n            ImPlotPoint p1 = Getter1(i); p1.x -= HalfWidth;\n            ImPlotPoint p2 = Getter2(i); p2.x += HalfWidth;\n            x_axis.ExtendFitWith(y_axis, p1.x, p1.y);\n            y_axis.ExtendFitWith(x_axis, p1.y, p1.x);\n            x_axis.ExtendFitWith(y_axis, p2.x, p2.y);\n            y_axis.ExtendFitWith(x_axis, p2.y, p2.x);\n        }\n    }\n    const _Getter1& Getter1;\n    const _Getter2& Getter2;\n    const double    HalfWidth;\n};\n\ntemplate <typename _Getter1, typename _Getter2>\nstruct FitterBarH {\n    FitterBarH(const _Getter1& getter1, const _Getter2& getter2, double height) :\n        Getter1(getter1),\n        Getter2(getter2),\n        HalfHeight(height*0.5)\n    { }\n    void Fit(ImPlotAxis& x_axis, ImPlotAxis& y_axis) const {\n        int count = ImMin(Getter1.Count, Getter2.Count);\n        for (int i = 0; i < count; ++i) {\n            ImPlotPoint p1 = Getter1(i); p1.y -= HalfHeight;\n            ImPlotPoint p2 = Getter2(i); p2.y += HalfHeight;\n            x_axis.ExtendFitWith(y_axis, p1.x, p1.y);\n            y_axis.ExtendFitWith(x_axis, p1.y, p1.x);\n            x_axis.ExtendFitWith(y_axis, p2.x, p2.y);\n            y_axis.ExtendFitWith(x_axis, p2.y, p2.x);\n        }\n    }\n    const _Getter1& Getter1;\n    const _Getter2& Getter2;\n    const double    HalfHeight;\n};\n\nstruct FitterRect {\n    FitterRect(const ImPlotPoint& pmin, const ImPlotPoint& pmax) :\n        Pmin(pmin),\n        Pmax(pmax)\n    { }\n    FitterRect(const ImPlotRect& rect) :\n        FitterRect(rect.Min(), rect.Max())\n    { }\n    void Fit(ImPlotAxis& x_axis, ImPlotAxis& y_axis) const {\n        x_axis.ExtendFitWith(y_axis, Pmin.x, Pmin.y);\n        y_axis.ExtendFitWith(x_axis, Pmin.y, Pmin.x);\n        x_axis.ExtendFitWith(y_axis, Pmax.x, Pmax.y);\n        y_axis.ExtendFitWith(x_axis, Pmax.y, Pmax.x);\n    }\n    const ImPlotPoint Pmin;\n    const ImPlotPoint Pmax;\n};\n\n//-----------------------------------------------------------------------------\n// [SECTION] Transformers\n//-----------------------------------------------------------------------------\n\nstruct Transformer1 {\n    Transformer1(double pixMin, double pltMin, double pltMax, double m, double scaMin, double scaMax, ImPlotTransform fwd, void* data) :\n        ScaMin(scaMin),\n        ScaMax(scaMax),\n        PltMin(pltMin),\n        PltMax(pltMax),\n        PixMin(pixMin),\n        M(m),\n        TransformFwd(fwd),\n        TransformData(data)\n    { }\n\n    template <typename T> IMPLOT_INLINE float operator()(T p) const {\n        if (TransformFwd != nullptr) {\n            double s = TransformFwd(p, TransformData);\n            double t = (s - ScaMin) / (ScaMax - ScaMin);\n            p = PltMin + (PltMax - PltMin) * t;\n        }\n        return (float)(PixMin + M * (p - PltMin));\n    }\n\n    double ScaMin, ScaMax, PltMin, PltMax, PixMin, M;\n    ImPlotTransform TransformFwd;\n    void*           TransformData;\n};\n\nstruct Transformer2 {\n    Transformer2(const ImPlotAxis& x_axis, const ImPlotAxis& y_axis) :\n        Tx(x_axis.PixelMin,\n           x_axis.Range.Min,\n           x_axis.Range.Max,\n           x_axis.ScaleToPixel,\n           x_axis.ScaleMin,\n           x_axis.ScaleMax,\n           x_axis.TransformForward,\n           x_axis.TransformData),\n        Ty(y_axis.PixelMin,\n           y_axis.Range.Min,\n           y_axis.Range.Max,\n           y_axis.ScaleToPixel,\n           y_axis.ScaleMin,\n           y_axis.ScaleMax,\n           y_axis.TransformForward,\n           y_axis.TransformData)\n    { }\n\n    Transformer2(const ImPlotPlot& plot) :\n        Transformer2(plot.Axes[plot.CurrentX], plot.Axes[plot.CurrentY])\n    { }\n\n    Transformer2() :\n        Transformer2(*GImPlot->CurrentPlot)\n    { }\n\n    template <typename P> IMPLOT_INLINE ImVec2 operator()(const P& plt) const {\n        ImVec2 out;\n        out.x = Tx(plt.x);\n        out.y = Ty(plt.y);\n        return out;\n    }\n\n    template <typename T> IMPLOT_INLINE ImVec2 operator()(T x, T y) const {\n        ImVec2 out;\n        out.x = Tx(x);\n        out.y = Ty(y);\n        return out;\n    }\n\n    Transformer1 Tx;\n    Transformer1 Ty;\n};\n\n//-----------------------------------------------------------------------------\n// [SECTION] Renderers\n//-----------------------------------------------------------------------------\n\nstruct RendererBase {\n    RendererBase(int prims, int idx_consumed, int vtx_consumed) :\n        Prims(prims),\n        IdxConsumed(idx_consumed),\n        VtxConsumed(vtx_consumed)\n    { }\n    const int Prims;\n    Transformer2 Transformer;\n    const int IdxConsumed;\n    const int VtxConsumed;\n};\n\ntemplate <class _Getter>\nstruct RendererLineStrip : RendererBase {\n    RendererLineStrip(const _Getter& getter, ImU32 col, float weight) :\n        RendererBase(getter.Count - 1, 6, 4),\n        Getter(getter),\n        Col(col),\n        HalfWeight(ImMax(1.0f,weight)*0.5f)\n    {\n        P1 = this->Transformer(Getter(0));\n    }\n    void Init(ImDrawList& draw_list) const {\n        GetLineRenderProps(draw_list, HalfWeight, UV0, UV1);\n    }\n    IMPLOT_INLINE bool Render(ImDrawList& draw_list, const ImRect& cull_rect, int prim) const {\n        ImVec2 P2 = this->Transformer(Getter(prim + 1));\n        if (!cull_rect.Overlaps(ImRect(ImMin(P1, P2), ImMax(P1, P2)))) {\n            P1 = P2;\n            return false;\n        }\n        PrimLine(draw_list,P1,P2,HalfWeight,Col,UV0,UV1);\n        P1 = P2;\n        return true;\n    }\n    const _Getter& Getter;\n    const ImU32 Col;\n    mutable float HalfWeight;\n    mutable ImVec2 P1;\n    mutable ImVec2 UV0;\n    mutable ImVec2 UV1;\n};\n\ntemplate <class _Getter>\nstruct RendererLineStripSkip : RendererBase {\n    RendererLineStripSkip(const _Getter& getter, ImU32 col, float weight) :\n        RendererBase(getter.Count - 1, 6, 4),\n        Getter(getter),\n        Col(col),\n        HalfWeight(ImMax(1.0f,weight)*0.5f)\n    {\n        P1 = this->Transformer(Getter(0));\n    }\n    void Init(ImDrawList& draw_list) const {\n        GetLineRenderProps(draw_list, HalfWeight, UV0, UV1);\n    }\n    IMPLOT_INLINE bool Render(ImDrawList& draw_list, const ImRect& cull_rect, int prim) const {\n        ImVec2 P2 = this->Transformer(Getter(prim + 1));\n        if (!cull_rect.Overlaps(ImRect(ImMin(P1, P2), ImMax(P1, P2)))) {\n            if (!ImNan(P2.x) && !ImNan(P2.y))\n                P1 = P2;\n            return false;\n        }\n        PrimLine(draw_list,P1,P2,HalfWeight,Col,UV0,UV1);\n        if (!ImNan(P2.x) && !ImNan(P2.y))\n            P1 = P2;\n        return true;\n    }\n    const _Getter& Getter;\n    const ImU32 Col;\n    mutable float HalfWeight;\n    mutable ImVec2 P1;\n    mutable ImVec2 UV0;\n    mutable ImVec2 UV1;\n};\n\ntemplate <class _Getter>\nstruct RendererLineSegments1 : RendererBase {\n    RendererLineSegments1(const _Getter& getter, ImU32 col, float weight) :\n        RendererBase(getter.Count / 2, 6, 4),\n        Getter(getter),\n        Col(col),\n        HalfWeight(ImMax(1.0f,weight)*0.5f)\n    { }\n    void Init(ImDrawList& draw_list) const {\n        GetLineRenderProps(draw_list, HalfWeight, UV0, UV1);\n    }\n    IMPLOT_INLINE bool Render(ImDrawList& draw_list, const ImRect& cull_rect, int prim) const {\n        ImVec2 P1 = this->Transformer(Getter(prim*2+0));\n        ImVec2 P2 = this->Transformer(Getter(prim*2+1));\n        if (!cull_rect.Overlaps(ImRect(ImMin(P1, P2), ImMax(P1, P2))))\n            return false;\n        PrimLine(draw_list,P1,P2,HalfWeight,Col,UV0,UV1);\n        return true;\n    }\n    const _Getter& Getter;\n    const ImU32 Col;\n    mutable float HalfWeight;\n    mutable ImVec2 UV0;\n    mutable ImVec2 UV1;\n};\n\ntemplate <class _Getter1, class _Getter2>\nstruct RendererLineSegments2 : RendererBase {\n    RendererLineSegments2(const _Getter1& getter1, const _Getter2& getter2, ImU32 col, float weight) :\n        RendererBase(ImMin(getter1.Count, getter1.Count), 6, 4),\n        Getter1(getter1),\n        Getter2(getter2),\n        Col(col),\n        HalfWeight(ImMax(1.0f,weight)*0.5f)\n    {}\n    void Init(ImDrawList& draw_list) const {\n        GetLineRenderProps(draw_list, HalfWeight, UV0, UV1);\n    }\n    IMPLOT_INLINE bool Render(ImDrawList& draw_list, const ImRect& cull_rect, int prim) const {\n        ImVec2 P1 = this->Transformer(Getter1(prim));\n        ImVec2 P2 = this->Transformer(Getter2(prim));\n        if (!cull_rect.Overlaps(ImRect(ImMin(P1, P2), ImMax(P1, P2))))\n            return false;\n        PrimLine(draw_list,P1,P2,HalfWeight,Col,UV0,UV1);\n        return true;\n    }\n    const _Getter1& Getter1;\n    const _Getter2& Getter2;\n    const ImU32 Col;\n    mutable float HalfWeight;\n    mutable ImVec2 UV0;\n    mutable ImVec2 UV1;\n};\n\ntemplate <class _Getter1, class _Getter2>\nstruct RendererBarsFillV : RendererBase {\n    RendererBarsFillV(const _Getter1& getter1, const _Getter2& getter2, ImU32 col, double width) :\n        RendererBase(ImMin(getter1.Count, getter1.Count), 6, 4),\n        Getter1(getter1),\n        Getter2(getter2),\n        Col(col),\n        HalfWidth(width/2)\n    {}\n    void Init(ImDrawList& draw_list) const {\n        UV = draw_list._Data->TexUvWhitePixel;\n    }\n    IMPLOT_INLINE bool Render(ImDrawList& draw_list, const ImRect& cull_rect, int prim) const {\n        ImPlotPoint p1 = Getter1(prim);\n        ImPlotPoint p2 = Getter2(prim);\n        p1.x += HalfWidth;\n        p2.x -= HalfWidth;\n        ImVec2 P1 = this->Transformer(p1);\n        ImVec2 P2 = this->Transformer(p2);\n        float width_px = ImAbs(P1.x-P2.x);\n        if (width_px < 1.0f) {\n            P1.x += P1.x > P2.x ? (1-width_px) / 2 : (width_px-1) / 2;\n            P2.x += P2.x > P1.x ? (1-width_px) / 2 : (width_px-1) / 2;\n        }\n        ImVec2 PMin = ImMin(P1, P2);\n        ImVec2 PMax = ImMax(P1, P2);\n        if (!cull_rect.Overlaps(ImRect(PMin, PMax)))\n            return false;\n        PrimRectFill(draw_list,PMin,PMax,Col,UV);\n        return true;\n    }\n    const _Getter1& Getter1;\n    const _Getter2& Getter2;\n    const ImU32 Col;\n    const double HalfWidth;\n    mutable ImVec2 UV;\n};\n\ntemplate <class _Getter1, class _Getter2>\nstruct RendererBarsFillH : RendererBase {\n    RendererBarsFillH(const _Getter1& getter1, const _Getter2& getter2, ImU32 col, double height) :\n        RendererBase(ImMin(getter1.Count, getter1.Count), 6, 4),\n        Getter1(getter1),\n        Getter2(getter2),\n        Col(col),\n        HalfHeight(height/2)\n    {}\n    void Init(ImDrawList& draw_list) const {\n        UV = draw_list._Data->TexUvWhitePixel;\n    }\n    IMPLOT_INLINE bool Render(ImDrawList& draw_list, const ImRect& cull_rect, int prim) const {\n        ImPlotPoint p1 = Getter1(prim);\n        ImPlotPoint p2 = Getter2(prim);\n        p1.y += HalfHeight;\n        p2.y -= HalfHeight;\n        ImVec2 P1 = this->Transformer(p1);\n        ImVec2 P2 = this->Transformer(p2);\n        float height_px = ImAbs(P1.y-P2.y);\n        if (height_px < 1.0f) {\n            P1.y += P1.y > P2.y ? (1-height_px) / 2 : (height_px-1) / 2;\n            P2.y += P2.y > P1.y ? (1-height_px) / 2 : (height_px-1) / 2;\n        }\n        ImVec2 PMin = ImMin(P1, P2);\n        ImVec2 PMax = ImMax(P1, P2);\n        if (!cull_rect.Overlaps(ImRect(PMin, PMax)))\n            return false;\n        PrimRectFill(draw_list,PMin,PMax,Col,UV);\n        return true;\n    }\n    const _Getter1& Getter1;\n    const _Getter2& Getter2;\n    const ImU32 Col;\n    const double HalfHeight;\n    mutable ImVec2 UV;\n};\n\ntemplate <class _Getter1, class _Getter2>\nstruct RendererBarsLineV : RendererBase {\n    RendererBarsLineV(const _Getter1& getter1, const _Getter2& getter2, ImU32 col, double width, float weight) :\n        RendererBase(ImMin(getter1.Count, getter1.Count), 24, 8),\n        Getter1(getter1),\n        Getter2(getter2),\n        Col(col),\n        HalfWidth(width/2),\n        Weight(weight)\n    {}\n    void Init(ImDrawList& draw_list) const {\n        UV = draw_list._Data->TexUvWhitePixel;\n    }\n    IMPLOT_INLINE bool Render(ImDrawList& draw_list, const ImRect& cull_rect, int prim) const {\n        ImPlotPoint p1 = Getter1(prim);\n        ImPlotPoint p2 = Getter2(prim);\n        p1.x += HalfWidth;\n        p2.x -= HalfWidth;\n        ImVec2 P1 = this->Transformer(p1);\n        ImVec2 P2 = this->Transformer(p2);\n        float width_px = ImAbs(P1.x-P2.x);\n        if (width_px < 1.0f) {\n            P1.x += P1.x > P2.x ? (1-width_px) / 2 : (width_px-1) / 2;\n            P2.x += P2.x > P1.x ? (1-width_px) / 2 : (width_px-1) / 2;\n        }\n        ImVec2 PMin = ImMin(P1, P2);\n        ImVec2 PMax = ImMax(P1, P2);\n        if (!cull_rect.Overlaps(ImRect(PMin, PMax)))\n            return false;\n        PrimRectLine(draw_list,PMin,PMax,Weight,Col,UV);\n        return true;\n    }\n    const _Getter1& Getter1;\n    const _Getter2& Getter2;\n    const ImU32 Col;\n    const double HalfWidth;\n    const float Weight;\n    mutable ImVec2 UV;\n};\n\ntemplate <class _Getter1, class _Getter2>\nstruct RendererBarsLineH : RendererBase {\n    RendererBarsLineH(const _Getter1& getter1, const _Getter2& getter2, ImU32 col, double height, float weight) :\n        RendererBase(ImMin(getter1.Count, getter1.Count), 24, 8),\n        Getter1(getter1),\n        Getter2(getter2),\n        Col(col),\n        HalfHeight(height/2),\n        Weight(weight)\n    {}\n    void Init(ImDrawList& draw_list) const {\n        UV = draw_list._Data->TexUvWhitePixel;\n    }\n    IMPLOT_INLINE bool Render(ImDrawList& draw_list, const ImRect& cull_rect, int prim) const {\n        ImPlotPoint p1 = Getter1(prim);\n        ImPlotPoint p2 = Getter2(prim);\n        p1.y += HalfHeight;\n        p2.y -= HalfHeight;\n        ImVec2 P1 = this->Transformer(p1);\n        ImVec2 P2 = this->Transformer(p2);\n        float height_px = ImAbs(P1.y-P2.y);\n        if (height_px < 1.0f) {\n            P1.y += P1.y > P2.y ? (1-height_px) / 2 : (height_px-1) / 2;\n            P2.y += P2.y > P1.y ? (1-height_px) / 2 : (height_px-1) / 2;\n        }\n        ImVec2 PMin = ImMin(P1, P2);\n        ImVec2 PMax = ImMax(P1, P2);\n        if (!cull_rect.Overlaps(ImRect(PMin, PMax)))\n            return false;\n        PrimRectLine(draw_list,PMin,PMax,Weight,Col,UV);\n        return true;\n    }\n    const _Getter1& Getter1;\n    const _Getter2& Getter2;\n    const ImU32 Col;\n    const double HalfHeight;\n    const float Weight;\n    mutable ImVec2 UV;\n};\n\n\ntemplate <class _Getter>\nstruct RendererStairsPre : RendererBase {\n    RendererStairsPre(const _Getter& getter, ImU32 col, float weight) :\n        RendererBase(getter.Count - 1, 12, 8),\n        Getter(getter),\n        Col(col),\n        HalfWeight(ImMax(1.0f,weight)*0.5f)\n    {\n        P1 = this->Transformer(Getter(0));\n    }\n    void Init(ImDrawList& draw_list) const {\n        UV = draw_list._Data->TexUvWhitePixel;\n    }\n    IMPLOT_INLINE bool Render(ImDrawList& draw_list, const ImRect& cull_rect, int prim) const {\n        ImVec2 P2 = this->Transformer(Getter(prim + 1));\n        if (!cull_rect.Overlaps(ImRect(ImMin(P1, P2), ImMax(P1, P2)))) {\n            P1 = P2;\n            return false;\n        }\n        PrimRectFill(draw_list, ImVec2(P1.x - HalfWeight, P1.y), ImVec2(P1.x + HalfWeight, P2.y), Col, UV);\n        PrimRectFill(draw_list, ImVec2(P1.x, P2.y + HalfWeight), ImVec2(P2.x, P2.y - HalfWeight), Col, UV);\n        P1 = P2;\n        return true;\n    }\n    const _Getter& Getter;\n    const ImU32 Col;\n    mutable float HalfWeight;\n    mutable ImVec2 P1;\n    mutable ImVec2 UV;\n};\n\ntemplate <class _Getter>\nstruct RendererStairsPost : RendererBase {\n    RendererStairsPost(const _Getter& getter, ImU32 col, float weight) :\n        RendererBase(getter.Count - 1, 12, 8),\n        Getter(getter),\n        Col(col),\n        HalfWeight(ImMax(1.0f,weight) * 0.5f)\n    {\n        P1 = this->Transformer(Getter(0));\n    }\n    void Init(ImDrawList& draw_list) const {\n        UV = draw_list._Data->TexUvWhitePixel;\n    }\n    IMPLOT_INLINE bool Render(ImDrawList& draw_list, const ImRect& cull_rect, int prim) const {\n        ImVec2 P2 = this->Transformer(Getter(prim + 1));\n        if (!cull_rect.Overlaps(ImRect(ImMin(P1, P2), ImMax(P1, P2)))) {\n            P1 = P2;\n            return false;\n        }\n        PrimRectFill(draw_list, ImVec2(P1.x, P1.y + HalfWeight), ImVec2(P2.x, P1.y - HalfWeight), Col, UV);\n        PrimRectFill(draw_list, ImVec2(P2.x - HalfWeight, P2.y), ImVec2(P2.x + HalfWeight, P1.y), Col, UV);\n        P1 = P2;\n        return true;\n    }\n    const _Getter& Getter;\n    const ImU32 Col;\n    mutable float HalfWeight;\n    mutable ImVec2 P1;\n    mutable ImVec2 UV;\n};\n\ntemplate <class _Getter>\nstruct RendererStairsPreShaded : RendererBase {\n    RendererStairsPreShaded(const _Getter& getter, ImU32 col) :\n        RendererBase(getter.Count - 1, 6, 4),\n        Getter(getter),\n        Col(col)\n    {\n        P1 = this->Transformer(Getter(0));\n        Y0 = this->Transformer(ImPlotPoint(0,0)).y;\n    }\n    void Init(ImDrawList& draw_list) const {\n        UV = draw_list._Data->TexUvWhitePixel;\n    }\n    IMPLOT_INLINE bool Render(ImDrawList& draw_list, const ImRect& cull_rect, int prim) const {\n        ImVec2 P2 = this->Transformer(Getter(prim + 1));\n        ImVec2 PMin(ImMin(P1.x, P2.x), ImMin(Y0, P2.y));\n        ImVec2 PMax(ImMax(P1.x, P2.x), ImMax(Y0, P2.y));\n        if (!cull_rect.Overlaps(ImRect(PMin, PMax))) {\n            P1 = P2;\n            return false;\n        }\n        PrimRectFill(draw_list, PMin, PMax, Col, UV);\n        P1 = P2;\n        return true;\n    }\n    const _Getter& Getter;\n    const ImU32 Col;\n    float Y0;\n    mutable ImVec2 P1;\n    mutable ImVec2 UV;\n};\n\ntemplate <class _Getter>\nstruct RendererStairsPostShaded : RendererBase {\n    RendererStairsPostShaded(const _Getter& getter, ImU32 col) :\n        RendererBase(getter.Count - 1, 6, 4),\n        Getter(getter),\n        Col(col)\n    {\n        P1 = this->Transformer(Getter(0));\n        Y0 = this->Transformer(ImPlotPoint(0,0)).y;\n    }\n    void Init(ImDrawList& draw_list) const {\n        UV = draw_list._Data->TexUvWhitePixel;\n    }\n    IMPLOT_INLINE bool Render(ImDrawList& draw_list, const ImRect& cull_rect, int prim) const {\n        ImVec2 P2 = this->Transformer(Getter(prim + 1));\n        ImVec2 PMin(ImMin(P1.x, P2.x), ImMin(P1.y, Y0));\n        ImVec2 PMax(ImMax(P1.x, P2.x), ImMax(P1.y, Y0));\n        if (!cull_rect.Overlaps(ImRect(PMin, PMax))) {\n            P1 = P2;\n            return false;\n        }\n        PrimRectFill(draw_list, PMin, PMax, Col, UV);\n        P1 = P2;\n        return true;\n    }\n    const _Getter& Getter;\n    const ImU32 Col;\n    float Y0;\n    mutable ImVec2 P1;\n    mutable ImVec2 UV;\n};\n\n\n\ntemplate <class _Getter1, class _Getter2>\nstruct RendererShaded : RendererBase {\n    RendererShaded(const _Getter1& getter1, const _Getter2& getter2, ImU32 col) :\n        RendererBase(ImMin(getter1.Count, getter2.Count) - 1, 6, 5),\n        Getter1(getter1),\n        Getter2(getter2),\n        Col(col)\n    {\n        P11 = this->Transformer(Getter1(0));\n        P12 = this->Transformer(Getter2(0));\n    }\n    void Init(ImDrawList& draw_list) const {\n        UV = draw_list._Data->TexUvWhitePixel;\n    }\n    IMPLOT_INLINE bool Render(ImDrawList& draw_list, const ImRect& cull_rect, int prim) const {\n        ImVec2 P21 = this->Transformer(Getter1(prim+1));\n        ImVec2 P22 = this->Transformer(Getter2(prim+1));\n        ImRect rect(ImMin(ImMin(ImMin(P11,P12),P21),P22), ImMax(ImMax(ImMax(P11,P12),P21),P22));\n        if (!cull_rect.Overlaps(rect)) {\n            P11 = P21;\n            P12 = P22;\n            return false;\n        }\n        const int intersect = (P11.y > P12.y && P22.y > P21.y) || (P12.y > P11.y && P21.y > P22.y);\n        ImVec2 intersection = Intersection(P11,P21,P12,P22);\n        draw_list._VtxWritePtr[0].pos = P11;\n        draw_list._VtxWritePtr[0].uv  = UV;\n        draw_list._VtxWritePtr[0].col = Col;\n        draw_list._VtxWritePtr[1].pos = P21;\n        draw_list._VtxWritePtr[1].uv  = UV;\n        draw_list._VtxWritePtr[1].col = Col;\n        draw_list._VtxWritePtr[2].pos = intersection;\n        draw_list._VtxWritePtr[2].uv  = UV;\n        draw_list._VtxWritePtr[2].col = Col;\n        draw_list._VtxWritePtr[3].pos = P12;\n        draw_list._VtxWritePtr[3].uv  = UV;\n        draw_list._VtxWritePtr[3].col = Col;\n        draw_list._VtxWritePtr[4].pos = P22;\n        draw_list._VtxWritePtr[4].uv  = UV;\n        draw_list._VtxWritePtr[4].col = Col;\n        draw_list._VtxWritePtr += 5;\n        draw_list._IdxWritePtr[0] = (ImDrawIdx)(draw_list._VtxCurrentIdx);\n        draw_list._IdxWritePtr[1] = (ImDrawIdx)(draw_list._VtxCurrentIdx + 1 + intersect);\n        draw_list._IdxWritePtr[2] = (ImDrawIdx)(draw_list._VtxCurrentIdx + 3);\n        draw_list._IdxWritePtr[3] = (ImDrawIdx)(draw_list._VtxCurrentIdx + 1);\n        draw_list._IdxWritePtr[4] = (ImDrawIdx)(draw_list._VtxCurrentIdx + 4);\n        draw_list._IdxWritePtr[5] = (ImDrawIdx)(draw_list._VtxCurrentIdx + 3 - intersect);\n        draw_list._IdxWritePtr += 6;\n        draw_list._VtxCurrentIdx += 5;\n        P11 = P21;\n        P12 = P22;\n        return true;\n    }\n    const _Getter1& Getter1;\n    const _Getter2& Getter2;\n    const ImU32 Col;\n    mutable ImVec2 P11;\n    mutable ImVec2 P12;\n    mutable ImVec2 UV;\n};\n\nstruct RectC {\n    ImPlotPoint Pos;\n    ImPlotPoint HalfSize;\n    ImU32 Color;\n};\n\ntemplate <typename _Getter>\nstruct RendererRectC : RendererBase {\n    RendererRectC(const _Getter& getter) :\n        RendererBase(getter.Count, 6, 4),\n        Getter(getter)\n    {}\n    void Init(ImDrawList& draw_list) const {\n        UV = draw_list._Data->TexUvWhitePixel;\n    }\n    IMPLOT_INLINE bool Render(ImDrawList& draw_list, const ImRect& cull_rect, int prim) const {\n        RectC rect = Getter(prim);\n        ImVec2 P1 = this->Transformer(rect.Pos.x - rect.HalfSize.x , rect.Pos.y - rect.HalfSize.y);\n        ImVec2 P2 = this->Transformer(rect.Pos.x + rect.HalfSize.x , rect.Pos.y + rect.HalfSize.y);\n        if ((rect.Color & IM_COL32_A_MASK) == 0 || !cull_rect.Overlaps(ImRect(ImMin(P1, P2), ImMax(P1, P2))))\n            return false;\n        PrimRectFill(draw_list,P1,P2,rect.Color,UV);\n        return true;\n    }\n    const _Getter& Getter;\n    mutable ImVec2 UV;\n};\n\n//-----------------------------------------------------------------------------\n// [SECTION] RenderPrimitives\n//-----------------------------------------------------------------------------\n\n/// Renders primitive shapes in bulk as efficiently as possible.\ntemplate <class _Renderer>\nvoid RenderPrimitivesEx(const _Renderer& renderer, ImDrawList& draw_list, const ImRect& cull_rect) {\n    unsigned int prims        = renderer.Prims;\n    unsigned int prims_culled = 0;\n    unsigned int idx          = 0;\n    renderer.Init(draw_list);\n    while (prims) {\n        // find how many can be reserved up to end of current draw command's limit\n        unsigned int cnt = ImMin(prims, (MaxIdx<ImDrawIdx>::Value - draw_list._VtxCurrentIdx) / renderer.VtxConsumed);\n        // make sure at least this many elements can be rendered to avoid situations where at the end of buffer this slow path is not taken all the time\n        if (cnt >= ImMin(64u, prims)) {\n            if (prims_culled >= cnt)\n                prims_culled -= cnt; // reuse previous reservation\n            else {\n                // add more elements to previous reservation\n                draw_list.PrimReserve((cnt - prims_culled) * renderer.IdxConsumed, (cnt - prims_culled) * renderer.VtxConsumed);\n                prims_culled = 0;\n            }\n        }\n        else\n        {\n            if (prims_culled > 0) {\n                draw_list.PrimUnreserve(prims_culled * renderer.IdxConsumed, prims_culled * renderer.VtxConsumed);\n                prims_culled = 0;\n            }\n            cnt = ImMin(prims, (MaxIdx<ImDrawIdx>::Value - 0/*draw_list._VtxCurrentIdx*/) / renderer.VtxConsumed);\n            // reserve new draw command\n            draw_list.PrimReserve(cnt * renderer.IdxConsumed, cnt * renderer.VtxConsumed);\n        }\n        prims -= cnt;\n        for (unsigned int ie = idx + cnt; idx != ie; ++idx) {\n            if (!renderer.Render(draw_list, cull_rect, idx))\n                prims_culled++;\n        }\n    }\n    if (prims_culled > 0)\n        draw_list.PrimUnreserve(prims_culled * renderer.IdxConsumed, prims_culled * renderer.VtxConsumed);\n}\n\ntemplate <template <class> class _Renderer, class _Getter, typename ...Args>\nvoid RenderPrimitives1(const _Getter& getter, Args... args) {\n    ImDrawList& draw_list = *GetPlotDrawList();\n    const ImRect& cull_rect = GetCurrentPlot()->PlotRect;\n    RenderPrimitivesEx(_Renderer<_Getter>(getter,args...), draw_list, cull_rect);\n}\n\ntemplate <template <class,class> class _Renderer, class _Getter1, class _Getter2, typename ...Args>\nvoid RenderPrimitives2(const _Getter1& getter1, const _Getter2& getter2, Args... args) {\n    ImDrawList& draw_list = *GetPlotDrawList();\n    const ImRect& cull_rect = GetCurrentPlot()->PlotRect;\n    RenderPrimitivesEx(_Renderer<_Getter1,_Getter2>(getter1,getter2,args...), draw_list, cull_rect);\n}\n\n//-----------------------------------------------------------------------------\n// [SECTION] Markers\n//-----------------------------------------------------------------------------\n\ntemplate <class _Getter>\nstruct RendererMarkersFill : RendererBase {\n    RendererMarkersFill(const _Getter& getter, const ImVec2* marker, int count, float size, ImU32 col) :\n        RendererBase(getter.Count, (count-2)*3, count),\n        Getter(getter),\n        Marker(marker),\n        Count(count),\n        Size(size),\n        Col(col)\n    { }\n    void Init(ImDrawList& draw_list) const {\n        UV = draw_list._Data->TexUvWhitePixel;\n    }\n    IMPLOT_INLINE bool Render(ImDrawList& draw_list, const ImRect& cull_rect, int prim) const {\n        ImVec2 p = this->Transformer(Getter(prim));\n        if (p.x >= cull_rect.Min.x && p.y >= cull_rect.Min.y && p.x <= cull_rect.Max.x && p.y <= cull_rect.Max.y) {\n            for (int i = 0; i < Count; i++) {\n                draw_list._VtxWritePtr[0].pos.x = p.x + Marker[i].x * Size;\n                draw_list._VtxWritePtr[0].pos.y = p.y + Marker[i].y * Size;\n                draw_list._VtxWritePtr[0].uv = UV;\n                draw_list._VtxWritePtr[0].col = Col;\n                draw_list._VtxWritePtr++;\n            }\n            for (int i = 2; i < Count; i++) {\n                draw_list._IdxWritePtr[0] = (ImDrawIdx)(draw_list._VtxCurrentIdx);\n                draw_list._IdxWritePtr[1] = (ImDrawIdx)(draw_list._VtxCurrentIdx + i - 1);\n                draw_list._IdxWritePtr[2] = (ImDrawIdx)(draw_list._VtxCurrentIdx + i);\n                draw_list._IdxWritePtr += 3;\n            }\n            draw_list._VtxCurrentIdx += (ImDrawIdx)Count;\n            return true;\n        }\n        return false;\n    }\n    const _Getter& Getter;\n    const ImVec2* Marker;\n    const int Count;\n    const float Size;\n    const ImU32 Col;\n    mutable ImVec2 UV;\n};\n\n\ntemplate <class _Getter>\nstruct RendererMarkersLine : RendererBase {\n    RendererMarkersLine(const _Getter& getter, const ImVec2* marker, int count, float size, float weight, ImU32 col) :\n        RendererBase(getter.Count, count/2*6, count/2*4),\n        Getter(getter),\n        Marker(marker),\n        Count(count),\n        HalfWeight(ImMax(1.0f,weight)*0.5f),\n        Size(size),\n        Col(col)\n    { }\n    void Init(ImDrawList& draw_list) const {\n        GetLineRenderProps(draw_list, HalfWeight, UV0, UV1);\n    }\n    IMPLOT_INLINE bool Render(ImDrawList& draw_list, const ImRect& cull_rect, int prim) const {\n        ImVec2 p = this->Transformer(Getter(prim));\n        if (p.x >= cull_rect.Min.x && p.y >= cull_rect.Min.y && p.x <= cull_rect.Max.x && p.y <= cull_rect.Max.y) {\n            for (int i = 0; i < Count; i = i + 2) {\n                ImVec2 p1(p.x + Marker[i].x * Size, p.y + Marker[i].y * Size);\n                ImVec2 p2(p.x + Marker[i+1].x * Size, p.y + Marker[i+1].y * Size);\n                PrimLine(draw_list, p1, p2, HalfWeight, Col, UV0, UV1);\n            }\n            return true;\n        }\n        return false;\n    }\n    const _Getter& Getter;\n    const ImVec2* Marker;\n    const int Count;\n    mutable float HalfWeight;\n    const float Size;\n    const ImU32 Col;\n    mutable ImVec2 UV0;\n    mutable ImVec2 UV1;\n};\n\nstatic const ImVec2 MARKER_FILL_CIRCLE[10]  = {ImVec2(1.0f, 0.0f), ImVec2(0.809017f, 0.58778524f),ImVec2(0.30901697f, 0.95105654f),ImVec2(-0.30901703f, 0.9510565f),ImVec2(-0.80901706f, 0.5877852f),ImVec2(-1.0f, 0.0f),ImVec2(-0.80901694f, -0.58778536f),ImVec2(-0.3090171f, -0.9510565f),ImVec2(0.30901712f, -0.9510565f),ImVec2(0.80901694f, -0.5877853f)};\nstatic const ImVec2 MARKER_FILL_SQUARE[4]   = {ImVec2(SQRT_1_2,SQRT_1_2), ImVec2(SQRT_1_2,-SQRT_1_2), ImVec2(-SQRT_1_2,-SQRT_1_2), ImVec2(-SQRT_1_2,SQRT_1_2)};\nstatic const ImVec2 MARKER_FILL_DIAMOND[4]  = {ImVec2(1, 0), ImVec2(0, -1), ImVec2(-1, 0), ImVec2(0, 1)};\nstatic const ImVec2 MARKER_FILL_UP[3]       = {ImVec2(SQRT_3_2,0.5f),ImVec2(0,-1),ImVec2(-SQRT_3_2,0.5f)};\nstatic const ImVec2 MARKER_FILL_DOWN[3]     = {ImVec2(SQRT_3_2,-0.5f),ImVec2(0,1),ImVec2(-SQRT_3_2,-0.5f)};\nstatic const ImVec2 MARKER_FILL_LEFT[3]     = {ImVec2(-1,0), ImVec2(0.5, SQRT_3_2), ImVec2(0.5, -SQRT_3_2)};\nstatic const ImVec2 MARKER_FILL_RIGHT[3]    = {ImVec2(1,0), ImVec2(-0.5, SQRT_3_2), ImVec2(-0.5, -SQRT_3_2)};\n\nstatic const ImVec2 MARKER_LINE_CIRCLE[20]  = {\n    ImVec2(1.0f, 0.0f),\n    ImVec2(0.809017f, 0.58778524f),\n    ImVec2(0.809017f, 0.58778524f),\n    ImVec2(0.30901697f, 0.95105654f),\n    ImVec2(0.30901697f, 0.95105654f),\n    ImVec2(-0.30901703f, 0.9510565f),\n    ImVec2(-0.30901703f, 0.9510565f),\n    ImVec2(-0.80901706f, 0.5877852f),\n    ImVec2(-0.80901706f, 0.5877852f),\n    ImVec2(-1.0f, 0.0f),\n    ImVec2(-1.0f, 0.0f),\n    ImVec2(-0.80901694f, -0.58778536f),\n    ImVec2(-0.80901694f, -0.58778536f),\n    ImVec2(-0.3090171f, -0.9510565f),\n    ImVec2(-0.3090171f, -0.9510565f),\n    ImVec2(0.30901712f, -0.9510565f),\n    ImVec2(0.30901712f, -0.9510565f),\n    ImVec2(0.80901694f, -0.5877853f),\n    ImVec2(0.80901694f, -0.5877853f),\n    ImVec2(1.0f, 0.0f)\n};\nstatic const ImVec2 MARKER_LINE_SQUARE[8]   = {ImVec2(SQRT_1_2,SQRT_1_2), ImVec2(SQRT_1_2,-SQRT_1_2), ImVec2(SQRT_1_2,-SQRT_1_2), ImVec2(-SQRT_1_2,-SQRT_1_2), ImVec2(-SQRT_1_2,-SQRT_1_2), ImVec2(-SQRT_1_2,SQRT_1_2), ImVec2(-SQRT_1_2,SQRT_1_2), ImVec2(SQRT_1_2,SQRT_1_2)};\nstatic const ImVec2 MARKER_LINE_DIAMOND[8]  = {ImVec2(1, 0), ImVec2(0, -1), ImVec2(0, -1), ImVec2(-1, 0), ImVec2(-1, 0), ImVec2(0, 1), ImVec2(0, 1), ImVec2(1, 0)};\nstatic const ImVec2 MARKER_LINE_UP[6]       = {ImVec2(SQRT_3_2,0.5f), ImVec2(0,-1),ImVec2(0,-1),ImVec2(-SQRT_3_2,0.5f),ImVec2(-SQRT_3_2,0.5f),ImVec2(SQRT_3_2,0.5f)};\nstatic const ImVec2 MARKER_LINE_DOWN[6]     = {ImVec2(SQRT_3_2,-0.5f),ImVec2(0,1),ImVec2(0,1),ImVec2(-SQRT_3_2,-0.5f), ImVec2(-SQRT_3_2,-0.5f), ImVec2(SQRT_3_2,-0.5f)};\nstatic const ImVec2 MARKER_LINE_LEFT[6]     = {ImVec2(-1,0), ImVec2(0.5, SQRT_3_2),  ImVec2(0.5, SQRT_3_2),  ImVec2(0.5, -SQRT_3_2) , ImVec2(0.5, -SQRT_3_2) , ImVec2(-1,0) };\nstatic const ImVec2 MARKER_LINE_RIGHT[6]    = {ImVec2(1,0),  ImVec2(-0.5, SQRT_3_2), ImVec2(-0.5, SQRT_3_2), ImVec2(-0.5, -SQRT_3_2), ImVec2(-0.5, -SQRT_3_2), ImVec2(1,0) };\nstatic const ImVec2 MARKER_LINE_ASTERISK[6] = {ImVec2(-SQRT_3_2, -0.5f), ImVec2(SQRT_3_2, 0.5f),  ImVec2(-SQRT_3_2, 0.5f), ImVec2(SQRT_3_2, -0.5f), ImVec2(0, -1), ImVec2(0, 1)};\nstatic const ImVec2 MARKER_LINE_PLUS[4]     = {ImVec2(-1, 0), ImVec2(1, 0), ImVec2(0, -1), ImVec2(0, 1)};\nstatic const ImVec2 MARKER_LINE_CROSS[4]    = {ImVec2(-SQRT_1_2,-SQRT_1_2),ImVec2(SQRT_1_2,SQRT_1_2),ImVec2(SQRT_1_2,-SQRT_1_2),ImVec2(-SQRT_1_2,SQRT_1_2)};\n\ntemplate <typename _Getter>\nvoid RenderMarkers(const _Getter& getter, ImPlotMarker marker, float size, bool rend_fill, ImU32 col_fill, bool rend_line, ImU32 col_line, float weight) {\n    if (rend_fill) {\n        switch (marker) {\n            case ImPlotMarker_Circle  : RenderPrimitives1<RendererMarkersFill>(getter,MARKER_FILL_CIRCLE,10,size,col_fill); break;\n            case ImPlotMarker_Square  : RenderPrimitives1<RendererMarkersFill>(getter,MARKER_FILL_SQUARE, 4,size,col_fill); break;\n            case ImPlotMarker_Diamond : RenderPrimitives1<RendererMarkersFill>(getter,MARKER_FILL_DIAMOND,4,size,col_fill); break;\n            case ImPlotMarker_Up      : RenderPrimitives1<RendererMarkersFill>(getter,MARKER_FILL_UP,     3,size,col_fill); break;\n            case ImPlotMarker_Down    : RenderPrimitives1<RendererMarkersFill>(getter,MARKER_FILL_DOWN,   3,size,col_fill); break;\n            case ImPlotMarker_Left    : RenderPrimitives1<RendererMarkersFill>(getter,MARKER_FILL_LEFT,   3,size,col_fill); break;\n            case ImPlotMarker_Right   : RenderPrimitives1<RendererMarkersFill>(getter,MARKER_FILL_RIGHT,  3,size,col_fill); break;\n        }\n    }\n    if (rend_line) {\n        switch (marker) {\n            case ImPlotMarker_Circle    : RenderPrimitives1<RendererMarkersLine>(getter,MARKER_LINE_CIRCLE, 20,size,weight,col_line); break;\n            case ImPlotMarker_Square    : RenderPrimitives1<RendererMarkersLine>(getter,MARKER_LINE_SQUARE,  8,size,weight,col_line); break;\n            case ImPlotMarker_Diamond   : RenderPrimitives1<RendererMarkersLine>(getter,MARKER_LINE_DIAMOND, 8,size,weight,col_line); break;\n            case ImPlotMarker_Up        : RenderPrimitives1<RendererMarkersLine>(getter,MARKER_LINE_UP,      6,size,weight,col_line); break;\n            case ImPlotMarker_Down      : RenderPrimitives1<RendererMarkersLine>(getter,MARKER_LINE_DOWN,    6,size,weight,col_line); break;\n            case ImPlotMarker_Left      : RenderPrimitives1<RendererMarkersLine>(getter,MARKER_LINE_LEFT,    6,size,weight,col_line); break;\n            case ImPlotMarker_Right     : RenderPrimitives1<RendererMarkersLine>(getter,MARKER_LINE_RIGHT,   6,size,weight,col_line); break;\n            case ImPlotMarker_Asterisk  : RenderPrimitives1<RendererMarkersLine>(getter,MARKER_LINE_ASTERISK,6,size,weight,col_line); break;\n            case ImPlotMarker_Plus      : RenderPrimitives1<RendererMarkersLine>(getter,MARKER_LINE_PLUS,    4,size,weight,col_line); break;\n            case ImPlotMarker_Cross     : RenderPrimitives1<RendererMarkersLine>(getter,MARKER_LINE_CROSS,   4,size,weight,col_line); break;\n        }\n    }\n}\n\n//-----------------------------------------------------------------------------\n// [SECTION] PlotLine\n//-----------------------------------------------------------------------------\n\ntemplate <typename _Getter>\nvoid PlotLineEx(const char* label_id, const _Getter& getter, ImPlotLineFlags flags) {\n    if (BeginItemEx(label_id, Fitter1<_Getter>(getter), flags, ImPlotCol_Line)) {\n        const ImPlotNextItemData& s = GetItemData();\n        if (getter.Count > 1) {\n            if (ImHasFlag(flags, ImPlotLineFlags_Shaded) && s.RenderFill) {\n                const ImU32 col_fill = ImGui::GetColorU32(s.Colors[ImPlotCol_Fill]);\n                GetterOverrideY<_Getter> getter2(getter, 0);\n                RenderPrimitives2<RendererShaded>(getter,getter2,col_fill);\n            }\n            if (s.RenderLine) {\n                const ImU32 col_line = ImGui::GetColorU32(s.Colors[ImPlotCol_Line]);\n                if (ImHasFlag(flags,ImPlotLineFlags_Segments)) {\n                    RenderPrimitives1<RendererLineSegments1>(getter,col_line,s.LineWeight);\n                }\n                else if (ImHasFlag(flags, ImPlotLineFlags_Loop)) {\n                    if (ImHasFlag(flags, ImPlotLineFlags_SkipNaN))\n                        RenderPrimitives1<RendererLineStripSkip>(GetterLoop<_Getter>(getter),col_line,s.LineWeight);\n                    else\n                        RenderPrimitives1<RendererLineStrip>(GetterLoop<_Getter>(getter),col_line,s.LineWeight);\n                }\n                else {\n                    if (ImHasFlag(flags, ImPlotLineFlags_SkipNaN))\n                        RenderPrimitives1<RendererLineStripSkip>(getter,col_line,s.LineWeight);\n                    else\n                        RenderPrimitives1<RendererLineStrip>(getter,col_line,s.LineWeight);\n                }\n            }\n        }\n        // render markers\n        if (s.Marker != ImPlotMarker_None) {\n            if (ImHasFlag(flags, ImPlotLineFlags_NoClip)) {\n                PopPlotClipRect();\n                PushPlotClipRect(s.MarkerSize);\n            }\n            const ImU32 col_line = ImGui::GetColorU32(s.Colors[ImPlotCol_MarkerOutline]);\n            const ImU32 col_fill = ImGui::GetColorU32(s.Colors[ImPlotCol_MarkerFill]);\n            RenderMarkers<_Getter>(getter, s.Marker, s.MarkerSize, s.RenderMarkerFill, col_fill, s.RenderMarkerLine, col_line, s.MarkerWeight);\n        }\n        EndItem();\n    }\n}\n\ntemplate <typename T>\nvoid PlotLine(const char* label_id, const T* values, int count, double xscale, double x0, ImPlotLineFlags flags, int offset, int stride) {\n    GetterXY<IndexerLin,IndexerIdx<T>> getter(IndexerLin(xscale,x0),IndexerIdx<T>(values,count,offset,stride),count);\n    PlotLineEx(label_id, getter, flags);\n}\n\ntemplate <typename T>\nvoid PlotLine(const char* label_id, const T* xs, const T* ys, int count, ImPlotLineFlags flags, int offset, int stride) {\n    GetterXY<IndexerIdx<T>,IndexerIdx<T>> getter(IndexerIdx<T>(xs,count,offset,stride),IndexerIdx<T>(ys,count,offset,stride),count);\n    PlotLineEx(label_id, getter, flags);\n}\n\n#define INSTANTIATE_MACRO(T) \\\n    template IMPLOT_API void PlotLine<T> (const char* label_id, const T* values, int count, double xscale, double x0, ImPlotLineFlags flags, int offset, int stride); \\\n    template IMPLOT_API void PlotLine<T>(const char* label_id, const T* xs, const T* ys, int count, ImPlotLineFlags flags, int offset, int stride);\nCALL_INSTANTIATE_FOR_NUMERIC_TYPES()\n#undef INSTANTIATE_MACRO\n\n// custom\nvoid PlotLineG(const char* label_id, ImPlotGetter getter_func, void* data, int count, ImPlotLineFlags flags) {\n    GetterFuncPtr getter(getter_func,data, count);\n    PlotLineEx(label_id, getter, flags);\n}\n\n//-----------------------------------------------------------------------------\n// [SECTION] PlotScatter\n//-----------------------------------------------------------------------------\n\ntemplate <typename Getter>\nvoid PlotScatterEx(const char* label_id, const Getter& getter, ImPlotScatterFlags flags) {\n    if (BeginItemEx(label_id, Fitter1<Getter>(getter), flags, ImPlotCol_MarkerOutline)) {\n        const ImPlotNextItemData& s = GetItemData();\n        ImPlotMarker marker = s.Marker == ImPlotMarker_None ? ImPlotMarker_Circle: s.Marker;\n        if (marker != ImPlotMarker_None) {\n            if (ImHasFlag(flags,ImPlotScatterFlags_NoClip)) {\n                PopPlotClipRect();\n                PushPlotClipRect(s.MarkerSize);\n            }\n            const ImU32 col_line = ImGui::GetColorU32(s.Colors[ImPlotCol_MarkerOutline]);\n            const ImU32 col_fill = ImGui::GetColorU32(s.Colors[ImPlotCol_MarkerFill]);\n            RenderMarkers<Getter>(getter, marker, s.MarkerSize, s.RenderMarkerFill, col_fill, s.RenderMarkerLine, col_line, s.MarkerWeight);\n        }\n        EndItem();\n    }\n}\n\ntemplate <typename T>\nvoid PlotScatter(const char* label_id, const T* values, int count, double xscale, double x0, ImPlotScatterFlags flags, int offset, int stride) {\n    GetterXY<IndexerLin,IndexerIdx<T>> getter(IndexerLin(xscale,x0),IndexerIdx<T>(values,count,offset,stride),count);\n    PlotScatterEx(label_id, getter, flags);\n}\n\ntemplate <typename T>\nvoid PlotScatter(const char* label_id, const T* xs, const T* ys, int count, ImPlotScatterFlags flags, int offset, int stride) {\n    GetterXY<IndexerIdx<T>,IndexerIdx<T>> getter(IndexerIdx<T>(xs,count,offset,stride),IndexerIdx<T>(ys,count,offset,stride),count);\n    return PlotScatterEx(label_id, getter, flags);\n}\n\n#define INSTANTIATE_MACRO(T) \\\n    template IMPLOT_API void PlotScatter<T>(const char* label_id, const T* values, int count, double xscale, double x0, ImPlotScatterFlags flags, int offset, int stride); \\\n    template IMPLOT_API void PlotScatter<T>(const char* label_id, const T* xs, const T* ys, int count, ImPlotScatterFlags flags, int offset, int stride);\nCALL_INSTANTIATE_FOR_NUMERIC_TYPES()\n#undef INSTANTIATE_MACRO\n\n// custom\nvoid PlotScatterG(const char* label_id, ImPlotGetter getter_func, void* data, int count, ImPlotScatterFlags flags) {\n    GetterFuncPtr getter(getter_func,data, count);\n    return PlotScatterEx(label_id, getter, flags);\n}\n\n//-----------------------------------------------------------------------------\n// [SECTION] PlotStairs\n//-----------------------------------------------------------------------------\n\ntemplate <typename Getter>\nvoid PlotStairsEx(const char* label_id, const Getter& getter, ImPlotStairsFlags flags) {\n    if (BeginItemEx(label_id, Fitter1<Getter>(getter), flags, ImPlotCol_Line)) {\n        const ImPlotNextItemData& s = GetItemData();\n        if (getter.Count > 1 ) {\n            if (s.RenderFill && ImHasFlag(flags,ImPlotStairsFlags_Shaded)) {\n                const ImU32 col_fill = ImGui::GetColorU32(s.Colors[ImPlotCol_Fill]);\n                if (ImHasFlag(flags, ImPlotStairsFlags_PreStep))\n                    RenderPrimitives1<RendererStairsPreShaded>(getter,col_fill);\n                else\n                    RenderPrimitives1<RendererStairsPostShaded>(getter,col_fill);\n            }\n            if (s.RenderLine) {\n                const ImU32 col_line = ImGui::GetColorU32(s.Colors[ImPlotCol_Line]);\n                if (ImHasFlag(flags, ImPlotStairsFlags_PreStep))\n                    RenderPrimitives1<RendererStairsPre>(getter,col_line,s.LineWeight);\n                else\n                    RenderPrimitives1<RendererStairsPost>(getter,col_line,s.LineWeight);\n            }\n        }\n        // render markers\n        if (s.Marker != ImPlotMarker_None) {\n            PopPlotClipRect();\n            PushPlotClipRect(s.MarkerSize);\n            const ImU32 col_line = ImGui::GetColorU32(s.Colors[ImPlotCol_MarkerOutline]);\n            const ImU32 col_fill = ImGui::GetColorU32(s.Colors[ImPlotCol_MarkerFill]);\n            RenderMarkers<Getter>(getter, s.Marker, s.MarkerSize, s.RenderMarkerFill, col_fill, s.RenderMarkerLine, col_line, s.MarkerWeight);\n        }\n        EndItem();\n    }\n}\n\ntemplate <typename T>\nvoid PlotStairs(const char* label_id, const T* values, int count, double xscale, double x0, ImPlotStairsFlags flags, int offset, int stride) {\n    GetterXY<IndexerLin,IndexerIdx<T>> getter(IndexerLin(xscale,x0),IndexerIdx<T>(values,count,offset,stride),count);\n    PlotStairsEx(label_id, getter, flags);\n}\n\ntemplate <typename T>\nvoid PlotStairs(const char* label_id, const T* xs, const T* ys, int count, ImPlotStairsFlags flags, int offset, int stride) {\n    GetterXY<IndexerIdx<T>,IndexerIdx<T>> getter(IndexerIdx<T>(xs,count,offset,stride),IndexerIdx<T>(ys,count,offset,stride),count);\n    return PlotStairsEx(label_id, getter, flags);\n}\n\n#define INSTANTIATE_MACRO(T) \\\n    template IMPLOT_API void PlotStairs<T> (const char* label_id, const T* values, int count, double xscale, double x0, ImPlotStairsFlags flags, int offset, int stride); \\\n    template IMPLOT_API void PlotStairs<T>(const char* label_id, const T* xs, const T* ys, int count, ImPlotStairsFlags flags, int offset, int stride);\nCALL_INSTANTIATE_FOR_NUMERIC_TYPES()\n#undef INSTANTIATE_MACRO\n\n// custom\nvoid PlotStairsG(const char* label_id, ImPlotGetter getter_func, void* data, int count, ImPlotStairsFlags flags) {\n    GetterFuncPtr getter(getter_func,data, count);\n    return PlotStairsEx(label_id, getter, flags);\n}\n\n//-----------------------------------------------------------------------------\n// [SECTION] PlotShaded\n//-----------------------------------------------------------------------------\n\ntemplate <typename Getter1, typename Getter2>\nvoid PlotShadedEx(const char* label_id, const Getter1& getter1, const Getter2& getter2, ImPlotShadedFlags flags) {\n    if (BeginItemEx(label_id, Fitter2<Getter1,Getter2>(getter1,getter2), flags, ImPlotCol_Fill)) {\n        const ImPlotNextItemData& s = GetItemData();\n        if (s.RenderFill) {\n            const ImU32 col = ImGui::GetColorU32(s.Colors[ImPlotCol_Fill]);\n            RenderPrimitives2<RendererShaded>(getter1,getter2,col);\n        }\n        EndItem();\n    }\n}\n\ntemplate <typename T>\nvoid PlotShaded(const char* label_id, const T* values, int count, double y_ref, double xscale, double x0, ImPlotShadedFlags flags, int offset, int stride) {\n    if (!(y_ref > -DBL_MAX))\n        y_ref = GetPlotLimits(IMPLOT_AUTO,IMPLOT_AUTO).Y.Min;\n    if (!(y_ref < DBL_MAX))\n        y_ref = GetPlotLimits(IMPLOT_AUTO,IMPLOT_AUTO).Y.Max;\n    GetterXY<IndexerLin,IndexerIdx<T>> getter1(IndexerLin(xscale,x0),IndexerIdx<T>(values,count,offset,stride),count);\n    GetterXY<IndexerLin,IndexerConst>  getter2(IndexerLin(xscale,x0),IndexerConst(y_ref),count);\n    PlotShadedEx(label_id, getter1, getter2, flags);\n}\n\ntemplate <typename T>\nvoid PlotShaded(const char* label_id, const T* xs, const T* ys, int count, double y_ref, ImPlotShadedFlags flags, int offset, int stride) {\n    if (y_ref == -HUGE_VAL)\n        y_ref = GetPlotLimits(IMPLOT_AUTO,IMPLOT_AUTO).Y.Min;\n    if (y_ref == HUGE_VAL)\n        y_ref = GetPlotLimits(IMPLOT_AUTO,IMPLOT_AUTO).Y.Max;\n    GetterXY<IndexerIdx<T>,IndexerIdx<T>> getter1(IndexerIdx<T>(xs,count,offset,stride),IndexerIdx<T>(ys,count,offset,stride),count);\n    GetterXY<IndexerIdx<T>,IndexerConst>  getter2(IndexerIdx<T>(xs,count,offset,stride),IndexerConst(y_ref),count);\n    PlotShadedEx(label_id, getter1, getter2, flags);\n}\n\n\ntemplate <typename T>\nvoid PlotShaded(const char* label_id, const T* xs, const T* ys1, const T* ys2, int count, ImPlotShadedFlags flags, int offset, int stride) {\n    GetterXY<IndexerIdx<T>,IndexerIdx<T>> getter1(IndexerIdx<T>(xs,count,offset,stride),IndexerIdx<T>(ys1,count,offset,stride),count);\n    GetterXY<IndexerIdx<T>,IndexerIdx<T>> getter2(IndexerIdx<T>(xs,count,offset,stride),IndexerIdx<T>(ys2,count,offset,stride),count);\n    PlotShadedEx(label_id, getter1, getter2, flags);\n}\n\n#define INSTANTIATE_MACRO(T) \\\n    template IMPLOT_API void PlotShaded<T>(const char* label_id, const T* values, int count, double y_ref, double xscale, double x0, ImPlotShadedFlags flags, int offset, int stride); \\\n    template IMPLOT_API void PlotShaded<T>(const char* label_id, const T* xs, const T* ys, int count, double y_ref, ImPlotShadedFlags flags, int offset, int stride); \\\n    template IMPLOT_API void PlotShaded<T>(const char* label_id, const T* xs, const T* ys1, const T* ys2, int count, ImPlotShadedFlags flags, int offset, int stride);\nCALL_INSTANTIATE_FOR_NUMERIC_TYPES()\n#undef INSTANTIATE_MACRO\n\n// custom\nvoid PlotShadedG(const char* label_id, ImPlotGetter getter_func1, void* data1, ImPlotGetter getter_func2, void* data2, int count, ImPlotShadedFlags flags) {\n    GetterFuncPtr getter1(getter_func1, data1, count);\n    GetterFuncPtr getter2(getter_func2, data2, count);\n    PlotShadedEx(label_id, getter1, getter2, flags);\n}\n\n//-----------------------------------------------------------------------------\n// [SECTION] PlotBars\n//-----------------------------------------------------------------------------\n\ntemplate <typename Getter1, typename Getter2>\nvoid PlotBarsVEx(const char* label_id, const Getter1& getter1, const Getter2 getter2, double width, ImPlotBarsFlags flags) {\n    if (BeginItemEx(label_id, FitterBarV<Getter1,Getter2>(getter1,getter2,width), flags, ImPlotCol_Fill)) {\n        const ImPlotNextItemData& s = GetItemData();\n        const ImU32 col_fill = ImGui::GetColorU32(s.Colors[ImPlotCol_Fill]);\n        const ImU32 col_line = ImGui::GetColorU32(s.Colors[ImPlotCol_Line]);\n        bool rend_fill = s.RenderFill;\n        bool rend_line = s.RenderLine;\n        if (rend_fill) {\n            RenderPrimitives2<RendererBarsFillV>(getter1,getter2,col_fill,width);\n            if (rend_line && col_fill == col_line)\n                rend_line = false;\n        }\n        if (rend_line) {\n            RenderPrimitives2<RendererBarsLineV>(getter1,getter2,col_line,width,s.LineWeight);\n        }\n        EndItem();\n    }\n}\n\ntemplate <typename Getter1, typename Getter2>\nvoid PlotBarsHEx(const char* label_id, const Getter1& getter1, const Getter2& getter2, double height, ImPlotBarsFlags flags) {\n    if (BeginItemEx(label_id, FitterBarH<Getter1,Getter2>(getter1,getter2,height), flags, ImPlotCol_Fill)) {\n        const ImPlotNextItemData& s = GetItemData();\n        const ImU32 col_fill = ImGui::GetColorU32(s.Colors[ImPlotCol_Fill]);\n        const ImU32 col_line = ImGui::GetColorU32(s.Colors[ImPlotCol_Line]);\n        bool rend_fill = s.RenderFill;\n        bool rend_line = s.RenderLine;\n        if (rend_fill) {\n            RenderPrimitives2<RendererBarsFillH>(getter1,getter2,col_fill,height);\n            if (rend_line && col_fill == col_line)\n                rend_line = false;\n        }\n        if (rend_line) {\n            RenderPrimitives2<RendererBarsLineH>(getter1,getter2,col_line,height,s.LineWeight);\n        }\n        EndItem();\n    }\n}\n\ntemplate <typename T>\nvoid PlotBars(const char* label_id, const T* values, int count, double bar_size, double shift, ImPlotBarsFlags flags, int offset, int stride) {\n    if (ImHasFlag(flags, ImPlotBarsFlags_Horizontal)) {\n        GetterXY<IndexerIdx<T>,IndexerLin> getter1(IndexerIdx<T>(values,count,offset,stride),IndexerLin(1.0,shift),count);\n        GetterXY<IndexerConst,IndexerLin>  getter2(IndexerConst(0),IndexerLin(1.0,shift),count);\n        PlotBarsHEx(label_id, getter1, getter2, bar_size, flags);\n    }\n    else {\n        GetterXY<IndexerLin,IndexerIdx<T>> getter1(IndexerLin(1.0,shift),IndexerIdx<T>(values,count,offset,stride),count);\n        GetterXY<IndexerLin,IndexerConst>  getter2(IndexerLin(1.0,shift),IndexerConst(0),count);\n        PlotBarsVEx(label_id, getter1, getter2, bar_size, flags);\n    }\n}\n\ntemplate <typename T>\nvoid PlotBars(const char* label_id, const T* xs, const T* ys, int count, double bar_size, ImPlotBarsFlags flags, int offset, int stride) {\n    if (ImHasFlag(flags, ImPlotBarsFlags_Horizontal)) {\n        GetterXY<IndexerIdx<T>,IndexerIdx<T>> getter1(IndexerIdx<T>(xs,count,offset,stride),IndexerIdx<T>(ys,count,offset,stride),count);\n        GetterXY<IndexerConst, IndexerIdx<T>> getter2(IndexerConst(0),IndexerIdx<T>(ys,count,offset,stride),count);\n        PlotBarsHEx(label_id, getter1, getter2, bar_size, flags);\n    }\n    else {\n        GetterXY<IndexerIdx<T>,IndexerIdx<T>> getter1(IndexerIdx<T>(xs,count,offset,stride),IndexerIdx<T>(ys,count,offset,stride),count);\n        GetterXY<IndexerIdx<T>,IndexerConst>  getter2(IndexerIdx<T>(xs,count,offset,stride),IndexerConst(0),count);\n        PlotBarsVEx(label_id, getter1, getter2, bar_size, flags);\n    }\n}\n\n#define INSTANTIATE_MACRO(T) \\\n    template IMPLOT_API void PlotBars<T>(const char* label_id, const T* values, int count, double bar_size, double shift, ImPlotBarsFlags flags, int offset, int stride); \\\n    template IMPLOT_API void PlotBars<T>(const char* label_id, const T* xs, const T* ys, int count, double bar_size, ImPlotBarsFlags flags, int offset, int stride);\nCALL_INSTANTIATE_FOR_NUMERIC_TYPES()\n#undef INSTANTIATE_MACRO\n\nvoid PlotBarsG(const char* label_id, ImPlotGetter getter_func, void* data, int count, double bar_size, ImPlotBarsFlags flags) {\n    if (ImHasFlag(flags, ImPlotBarsFlags_Horizontal)) {\n        GetterFuncPtr getter1(getter_func, data, count);\n        GetterOverrideX<GetterFuncPtr> getter2(getter1,0);\n        PlotBarsHEx(label_id, getter1, getter2, bar_size, flags);\n    }\n    else {\n        GetterFuncPtr getter1(getter_func, data, count);\n        GetterOverrideY<GetterFuncPtr> getter2(getter1,0);\n        PlotBarsVEx(label_id, getter1, getter2, bar_size, flags);\n    }\n}\n\n//-----------------------------------------------------------------------------\n// [SECTION] PlotBarGroups\n//-----------------------------------------------------------------------------\n\ntemplate <typename T>\nvoid PlotBarGroups(const char* const label_ids[], const T* values, int item_count, int group_count, double group_size, double shift, ImPlotBarGroupsFlags flags) {\n    const bool horz = ImHasFlag(flags, ImPlotBarGroupsFlags_Horizontal);\n    const bool stack = ImHasFlag(flags, ImPlotBarGroupsFlags_Stacked);\n    if (stack) {\n        SetupLock();\n        ImPlotContext& gp = *GImPlot;\n        gp.TempDouble1.resize(4*group_count);\n        double* temp = gp.TempDouble1.Data;\n        double* neg =      &temp[0];\n        double* pos =      &temp[group_count];\n        double* curr_min = &temp[group_count*2];\n        double* curr_max = &temp[group_count*3];\n        for (int g = 0; g < group_count*2; ++g)\n            temp[g] = 0;\n        if (horz) {\n            for (int i = 0; i < item_count; ++i) {\n                if (!IsItemHidden(label_ids[i])) {\n                    for (int g = 0; g < group_count; ++g) {\n                        double v = (double)values[i*group_count+g];\n                        if (v > 0) {\n                            curr_min[g] = pos[g];\n                            curr_max[g] = curr_min[g] + v;\n                            pos[g]      += v;\n                        }\n                        else {\n                            curr_max[g] = neg[g];\n                            curr_min[g] = curr_max[g] + v;\n                            neg[g]      += v;\n                        }\n                    }\n                }\n                GetterXY<IndexerIdx<double>,IndexerLin> getter1(IndexerIdx<double>(curr_min,group_count),IndexerLin(1.0,shift),group_count);\n                GetterXY<IndexerIdx<double>,IndexerLin> getter2(IndexerIdx<double>(curr_max,group_count),IndexerLin(1.0,shift),group_count);\n                PlotBarsHEx(label_ids[i],getter1,getter2,group_size,0);\n            }\n        }\n        else {\n            for (int i = 0; i < item_count; ++i) {\n                if (!IsItemHidden(label_ids[i])) {\n                    for (int g = 0; g < group_count; ++g) {\n                        double v = (double)values[i*group_count+g];\n                        if (v > 0) {\n                            curr_min[g] = pos[g];\n                            curr_max[g] = curr_min[g] + v;\n                            pos[g]      += v;\n                        }\n                        else {\n                            curr_max[g] = neg[g];\n                            curr_min[g] = curr_max[g] + v;\n                            neg[g]      += v;\n                        }\n                    }\n                }\n                GetterXY<IndexerLin,IndexerIdx<double>> getter1(IndexerLin(1.0,shift),IndexerIdx<double>(curr_min,group_count),group_count);\n                GetterXY<IndexerLin,IndexerIdx<double>> getter2(IndexerLin(1.0,shift),IndexerIdx<double>(curr_max,group_count),group_count);\n                PlotBarsVEx(label_ids[i],getter1,getter2,group_size,0);\n            }\n        }\n    }\n    else {\n        const double subsize = group_size / item_count;\n        if (horz) {\n            for (int i = 0; i < item_count; ++i) {\n                const double subshift = (i+0.5)*subsize - group_size/2;\n                PlotBars(label_ids[i],&values[i*group_count],group_count,subsize,subshift+shift,ImPlotBarsFlags_Horizontal);\n            }\n        }\n        else {\n            for (int i = 0; i < item_count; ++i) {\n                const double subshift = (i+0.5)*subsize - group_size/2;\n                PlotBars(label_ids[i],&values[i*group_count],group_count,subsize,subshift+shift);\n            }\n        }\n    }\n}\n\n#define INSTANTIATE_MACRO(T) template IMPLOT_API void PlotBarGroups<T>(const char* const label_ids[], const T* values, int items, int groups, double width, double shift, ImPlotBarGroupsFlags flags);\nCALL_INSTANTIATE_FOR_NUMERIC_TYPES()\n#undef INSTANTIATE_MACRO\n\n//-----------------------------------------------------------------------------\n// [SECTION] PlotErrorBars\n//-----------------------------------------------------------------------------\n\ntemplate <typename _GetterPos, typename _GetterNeg>\nvoid PlotErrorBarsVEx(const char* label_id, const _GetterPos& getter_pos, const _GetterNeg& getter_neg, ImPlotErrorBarsFlags flags) {\n    if (BeginItemEx(label_id, Fitter2<_GetterPos,_GetterNeg>(getter_pos, getter_neg), flags, IMPLOT_AUTO)) {\n        const ImPlotNextItemData& s = GetItemData();\n        ImDrawList& draw_list = *GetPlotDrawList();\n        const ImU32 col = ImGui::GetColorU32(s.Colors[ImPlotCol_ErrorBar]);\n        const bool rend_whisker  = s.ErrorBarSize > 0;\n        const float half_whisker = s.ErrorBarSize * 0.5f;\n        for (int i = 0; i < getter_pos.Count; ++i) {\n            ImVec2 p1 = PlotToPixels(getter_neg(i),IMPLOT_AUTO,IMPLOT_AUTO);\n            ImVec2 p2 = PlotToPixels(getter_pos(i),IMPLOT_AUTO,IMPLOT_AUTO);\n            draw_list.AddLine(p1,p2,col, s.ErrorBarWeight);\n            if (rend_whisker) {\n                draw_list.AddLine(p1 - ImVec2(half_whisker, 0), p1 + ImVec2(half_whisker, 0), col, s.ErrorBarWeight);\n                draw_list.AddLine(p2 - ImVec2(half_whisker, 0), p2 + ImVec2(half_whisker, 0), col, s.ErrorBarWeight);\n            }\n        }\n        EndItem();\n    }\n}\n\ntemplate <typename _GetterPos, typename _GetterNeg>\nvoid PlotErrorBarsHEx(const char* label_id, const _GetterPos& getter_pos, const _GetterNeg& getter_neg, ImPlotErrorBarsFlags flags) {\n    if (BeginItemEx(label_id, Fitter2<_GetterPos,_GetterNeg>(getter_pos, getter_neg), flags, IMPLOT_AUTO)) {\n        const ImPlotNextItemData& s = GetItemData();\n        ImDrawList& draw_list = *GetPlotDrawList();\n        const ImU32 col = ImGui::GetColorU32(s.Colors[ImPlotCol_ErrorBar]);\n        const bool rend_whisker  = s.ErrorBarSize > 0;\n        const float half_whisker = s.ErrorBarSize * 0.5f;\n        for (int i = 0; i < getter_pos.Count; ++i) {\n            ImVec2 p1 = PlotToPixels(getter_neg(i),IMPLOT_AUTO,IMPLOT_AUTO);\n            ImVec2 p2 = PlotToPixels(getter_pos(i),IMPLOT_AUTO,IMPLOT_AUTO);\n            draw_list.AddLine(p1, p2, col, s.ErrorBarWeight);\n            if (rend_whisker) {\n                draw_list.AddLine(p1 - ImVec2(0, half_whisker), p1 + ImVec2(0, half_whisker), col, s.ErrorBarWeight);\n                draw_list.AddLine(p2 - ImVec2(0, half_whisker), p2 + ImVec2(0, half_whisker), col, s.ErrorBarWeight);\n            }\n        }\n        EndItem();\n    }\n}\n\ntemplate <typename T>\nvoid PlotErrorBars(const char* label_id, const T* xs, const T* ys, const T* err, int count, ImPlotErrorBarsFlags flags, int offset, int stride) {\n    PlotErrorBars(label_id, xs, ys, err, err, count, flags, offset, stride);\n}\n\ntemplate <typename T>\nvoid PlotErrorBars(const char* label_id, const T* xs, const T* ys, const T* neg, const T* pos, int count, ImPlotErrorBarsFlags flags, int offset, int stride) {\n    IndexerIdx<T> indexer_x(xs, count,offset,stride);\n    IndexerIdx<T> indexer_y(ys, count,offset,stride);\n    IndexerIdx<T> indexer_n(neg,count,offset,stride);\n    IndexerIdx<T> indexer_p(pos,count,offset,stride);\n    GetterError<T> getter(xs, ys, neg, pos, count, offset, stride);\n    if (ImHasFlag(flags, ImPlotErrorBarsFlags_Horizontal)) {\n        IndexerAdd<IndexerIdx<T>,IndexerIdx<T>> indexer_xp(indexer_x, indexer_p, 1,  1);\n        IndexerAdd<IndexerIdx<T>,IndexerIdx<T>> indexer_xn(indexer_x, indexer_n, 1, -1);\n        GetterXY<IndexerAdd<IndexerIdx<T>,IndexerIdx<T>>,IndexerIdx<T>> getter_p(indexer_xp, indexer_y, count);\n        GetterXY<IndexerAdd<IndexerIdx<T>,IndexerIdx<T>>,IndexerIdx<T>> getter_n(indexer_xn, indexer_y, count);\n        PlotErrorBarsHEx(label_id, getter_p, getter_n, flags);\n    }\n    else {\n        IndexerAdd<IndexerIdx<T>,IndexerIdx<T>> indexer_yp(indexer_y, indexer_p, 1,  1);\n        IndexerAdd<IndexerIdx<T>,IndexerIdx<T>> indexer_yn(indexer_y, indexer_n, 1, -1);\n        GetterXY<IndexerIdx<T>,IndexerAdd<IndexerIdx<T>,IndexerIdx<T>>> getter_p(indexer_x, indexer_yp, count);\n        GetterXY<IndexerIdx<T>,IndexerAdd<IndexerIdx<T>,IndexerIdx<T>>> getter_n(indexer_x, indexer_yn, count);\n        PlotErrorBarsVEx(label_id, getter_p, getter_n, flags);\n    }\n}\n\n#define INSTANTIATE_MACRO(T) \\\n    template IMPLOT_API void PlotErrorBars<T>(const char* label_id, const T* xs, const T* ys, const T* err, int count, ImPlotErrorBarsFlags flags, int offset, int stride); \\\n    template IMPLOT_API void PlotErrorBars<T>(const char* label_id, const T* xs, const T* ys, const T* neg, const T* pos, int count, ImPlotErrorBarsFlags flags, int offset, int stride);\nCALL_INSTANTIATE_FOR_NUMERIC_TYPES()\n#undef INSTANTIATE_MACRO\n\n//-----------------------------------------------------------------------------\n// [SECTION] PlotStems\n//-----------------------------------------------------------------------------\n\ntemplate <typename _GetterM, typename _GetterB>\nvoid PlotStemsEx(const char* label_id, const _GetterM& get_mark, const _GetterB& get_base, ImPlotStemsFlags flags) {\n    if (BeginItemEx(label_id, Fitter2<_GetterM,_GetterB>(get_mark,get_base), flags, ImPlotCol_Line)) {\n        const ImPlotNextItemData& s = GetItemData();\n        // render stems\n        if (s.RenderLine) {\n            const ImU32 col_line = ImGui::GetColorU32(s.Colors[ImPlotCol_Line]);\n            RenderPrimitives2<RendererLineSegments2>(get_mark, get_base, col_line, s.LineWeight);\n        }\n        // render markers\n        if (s.Marker != ImPlotMarker_None) {\n            PopPlotClipRect();\n            PushPlotClipRect(s.MarkerSize);\n            const ImU32 col_line = ImGui::GetColorU32(s.Colors[ImPlotCol_MarkerOutline]);\n            const ImU32 col_fill = ImGui::GetColorU32(s.Colors[ImPlotCol_MarkerFill]);\n            RenderMarkers<_GetterM>(get_mark, s.Marker, s.MarkerSize, s.RenderMarkerFill, col_fill, s.RenderMarkerLine, col_line, s.MarkerWeight);\n        }\n        EndItem();\n    }\n}\n\ntemplate <typename T>\nvoid PlotStems(const char* label_id, const T* values, int count, double ref, double scale, double start, ImPlotStemsFlags flags, int offset, int stride) {\n    if (ImHasFlag(flags, ImPlotStemsFlags_Horizontal)) {\n        GetterXY<IndexerIdx<T>,IndexerLin> get_mark(IndexerIdx<T>(values,count,offset,stride),IndexerLin(scale,start),count);\n        GetterXY<IndexerConst,IndexerLin>  get_base(IndexerConst(ref),IndexerLin(scale,start),count);\n        PlotStemsEx(label_id, get_mark, get_base, flags);\n    }\n    else {\n        GetterXY<IndexerLin,IndexerIdx<T>> get_mark(IndexerLin(scale,start),IndexerIdx<T>(values,count,offset,stride),count);\n        GetterXY<IndexerLin,IndexerConst>  get_base(IndexerLin(scale,start),IndexerConst(ref),count);\n        PlotStemsEx(label_id, get_mark, get_base, flags);\n    }\n}\n\ntemplate <typename T>\nvoid PlotStems(const char* label_id, const T* xs, const T* ys, int count, double ref, ImPlotStemsFlags flags, int offset, int stride) {\n    if (ImHasFlag(flags, ImPlotStemsFlags_Horizontal)) {\n        GetterXY<IndexerIdx<T>,IndexerIdx<T>> get_mark(IndexerIdx<T>(xs,count,offset,stride),IndexerIdx<T>(ys,count,offset,stride),count);\n        GetterXY<IndexerConst,IndexerIdx<T>>  get_base(IndexerConst(ref),IndexerIdx<T>(ys,count,offset,stride),count);\n        PlotStemsEx(label_id, get_mark, get_base, flags);\n    }\n    else {\n        GetterXY<IndexerIdx<T>,IndexerIdx<T>> get_mark(IndexerIdx<T>(xs,count,offset,stride),IndexerIdx<T>(ys,count,offset,stride),count);\n        GetterXY<IndexerIdx<T>,IndexerConst>  get_base(IndexerIdx<T>(xs,count,offset,stride),IndexerConst(ref),count);\n        PlotStemsEx(label_id, get_mark, get_base, flags);\n    }\n}\n\n#define INSTANTIATE_MACRO(T) \\\n    template IMPLOT_API void PlotStems<T>(const char* label_id, const T* values, int count, double ref, double scale, double start, ImPlotStemsFlags flags, int offset, int stride); \\\n    template IMPLOT_API void PlotStems<T>(const char* label_id, const T* xs, const T* ys, int count, double ref, ImPlotStemsFlags flags, int offset, int stride);\nCALL_INSTANTIATE_FOR_NUMERIC_TYPES()\n#undef INSTANTIATE_MACRO\n\n\n//-----------------------------------------------------------------------------\n// [SECTION] PlotInfLines\n//-----------------------------------------------------------------------------\n\ntemplate <typename T>\nvoid PlotInfLines(const char* label_id, const T* values, int count, ImPlotInfLinesFlags flags, int offset, int stride) {\n    const ImPlotRect lims = GetPlotLimits(IMPLOT_AUTO,IMPLOT_AUTO);\n    if (ImHasFlag(flags, ImPlotInfLinesFlags_Horizontal)) {\n        GetterXY<IndexerConst,IndexerIdx<T>> get_min(IndexerConst(lims.X.Min),IndexerIdx<T>(values,count,offset,stride),count);\n        GetterXY<IndexerConst,IndexerIdx<T>> get_max(IndexerConst(lims.X.Max),IndexerIdx<T>(values,count,offset,stride),count);\n        if (BeginItemEx(label_id, FitterY<GetterXY<IndexerConst,IndexerIdx<T>>>(get_min), flags, ImPlotCol_Line)) {\n            const ImPlotNextItemData& s = GetItemData();\n            const ImU32 col_line = ImGui::GetColorU32(s.Colors[ImPlotCol_Line]);\n            if (s.RenderLine)\n                RenderPrimitives2<RendererLineSegments2>(get_min, get_max, col_line, s.LineWeight);\n            EndItem();\n        }\n    }\n    else {\n        GetterXY<IndexerIdx<T>,IndexerConst> get_min(IndexerIdx<T>(values,count,offset,stride),IndexerConst(lims.Y.Min),count);\n        GetterXY<IndexerIdx<T>,IndexerConst> get_max(IndexerIdx<T>(values,count,offset,stride),IndexerConst(lims.Y.Max),count);\n        if (BeginItemEx(label_id, FitterX<GetterXY<IndexerIdx<T>,IndexerConst>>(get_min), flags, ImPlotCol_Line)) {\n            const ImPlotNextItemData& s = GetItemData();\n            const ImU32 col_line = ImGui::GetColorU32(s.Colors[ImPlotCol_Line]);\n            if (s.RenderLine)\n                RenderPrimitives2<RendererLineSegments2>(get_min, get_max, col_line, s.LineWeight);\n            EndItem();\n        }\n    }\n}\n#define INSTANTIATE_MACRO(T) template IMPLOT_API void PlotInfLines<T>(const char* label_id, const T* xs, int count, ImPlotInfLinesFlags flags, int offset, int stride);\nCALL_INSTANTIATE_FOR_NUMERIC_TYPES()\n#undef INSTANTIATE_MACRO\n\n//-----------------------------------------------------------------------------\n// [SECTION] PlotPieChart\n//-----------------------------------------------------------------------------\n\nIMPLOT_INLINE void RenderPieSlice(ImDrawList& draw_list, const ImPlotPoint& center, double radius, double a0, double a1, ImU32 col) {\n    const float resolution = 50 / (2 * IM_PI);\n    ImVec2 buffer[52];\n    buffer[0] = PlotToPixels(center,IMPLOT_AUTO,IMPLOT_AUTO);\n    int n = ImMax(3, (int)((a1 - a0) * resolution));\n    double da = (a1 - a0) / (n - 1);\n    int i = 0;\n    for (; i < n; ++i) {\n        double a = a0 + i * da;\n        buffer[i + 1] = PlotToPixels(center.x + radius * cos(a), center.y + radius * sin(a),IMPLOT_AUTO,IMPLOT_AUTO);\n    }\n    buffer[i+1] = buffer[0];\n    // fill\n    draw_list.AddConvexPolyFilled(buffer, n + 1, col);\n    // border (for AA)\n    draw_list.AddPolyline(buffer, n + 2, col, 0, 2.0f);\n}\n\ntemplate <typename T>\nvoid PlotPieChart(const char* const label_ids[], const T* values, int count, double x, double y, double radius, const char* fmt, double angle0, ImPlotPieChartFlags flags) {\n    IM_ASSERT_USER_ERROR(GImPlot->CurrentPlot != nullptr, \"PlotPieChart() needs to be called between BeginPlot() and EndPlot()!\");\n    ImDrawList & draw_list = *GetPlotDrawList();\n    double sum = 0;\n    for (int i = 0; i < count; ++i)\n        sum += (double)values[i];\n    const bool normalize = ImHasFlag(flags,ImPlotPieChartFlags_Normalize) || sum > 1.0;\n    ImPlotPoint center(x,y);\n    PushPlotClipRect();\n    double a0 = angle0 * 2 * IM_PI / 360.0;\n    double a1 = angle0 * 2 * IM_PI / 360.0;\n    ImPlotPoint Pmin = ImPlotPoint(x-radius,y-radius);\n    ImPlotPoint Pmax = ImPlotPoint(x+radius,y+radius);\n    for (int i = 0; i < count; ++i) {\n        double percent = normalize ? (double)values[i] / sum : (double)values[i];\n        a1 = a0 + 2 * IM_PI * percent;\n        if (BeginItemEx(label_ids[i], FitterRect(Pmin,Pmax))) {\n            ImU32 col = GetCurrentItem()->Color;\n            if (percent < 0.5) {\n                RenderPieSlice(draw_list, center, radius, a0, a1, col);\n            }\n            else  {\n                RenderPieSlice(draw_list, center, radius, a0, a0 + (a1 - a0) * 0.5, col);\n                RenderPieSlice(draw_list, center, radius, a0 + (a1 - a0) * 0.5, a1, col);\n            }\n            EndItem();\n        }\n        a0 = a1;\n    }\n    if (fmt != nullptr) {\n        a0 = angle0 * 2 * IM_PI / 360.0;\n        a1 = angle0 * 2 * IM_PI / 360.0;\n        char buffer[32];\n        for (int i = 0; i < count; ++i) {\n            ImPlotItem* item = GetItem(label_ids[i]);\n            double percent = normalize ? (double)values[i] / sum : (double)values[i];\n            a1 = a0 + 2 * IM_PI * percent;\n            if (item->Show) {\n                ImFormatString(buffer, 32, fmt, (double)values[i]);\n                ImVec2 size = ImGui::CalcTextSize(buffer);\n                double angle = a0 + (a1 - a0) * 0.5;\n                ImVec2 pos = PlotToPixels(center.x + 0.5 * radius * cos(angle), center.y + 0.5 * radius * sin(angle),IMPLOT_AUTO,IMPLOT_AUTO);\n                ImU32 col  = CalcTextColor(ImGui::ColorConvertU32ToFloat4(item->Color));\n                draw_list.AddText(pos - size * 0.5f, col, buffer);\n            }\n            a0 = a1;\n        }\n    }\n    PopPlotClipRect();\n}\n#define INSTANTIATE_MACRO(T) template IMPLOT_API void PlotPieChart<T>(const char* const label_ids[], const T* values, int count, double x, double y, double radius, const char* fmt, double angle0, ImPlotPieChartFlags flags);\nCALL_INSTANTIATE_FOR_NUMERIC_TYPES()\n#undef INSTANTIATE_MACRO\n\n//-----------------------------------------------------------------------------\n// [SECTION] PlotHeatmap\n//-----------------------------------------------------------------------------\n\ntemplate <typename T>\nstruct GetterHeatmapRowMaj {\n    GetterHeatmapRowMaj(const T* values, int rows, int cols, double scale_min, double scale_max, double width, double height, double xref, double yref, double ydir) :\n        Values(values),\n        Count(rows*cols),\n        Rows(rows),\n        Cols(cols),\n        ScaleMin(scale_min),\n        ScaleMax(scale_max),\n        Width(width),\n        Height(height),\n        XRef(xref),\n        YRef(yref),\n        YDir(ydir),\n        HalfSize(Width*0.5, Height*0.5)\n    { }\n    template <typename I> IMPLOT_INLINE RectC operator()(I idx) const {\n        double val = (double)Values[idx];\n        const int r = idx / Cols;\n        const int c = idx % Cols;\n        const ImPlotPoint p(XRef + HalfSize.x + c*Width, YRef + YDir * (HalfSize.y + r*Height));\n        RectC rect;\n        rect.Pos = p;\n        rect.HalfSize = HalfSize;\n        const float t = ImClamp((float)ImRemap01(val, ScaleMin, ScaleMax),0.0f,1.0f);\n        ImPlotContext& gp = *GImPlot;\n        rect.Color = gp.ColormapData.LerpTable(gp.Style.Colormap, t);\n        return rect;\n    }\n    const T* const Values;\n    const int Count, Rows, Cols;\n    const double ScaleMin, ScaleMax, Width, Height, XRef, YRef, YDir;\n    const ImPlotPoint HalfSize;\n};\n\ntemplate <typename T>\nstruct GetterHeatmapColMaj {\n    GetterHeatmapColMaj(const T* values, int rows, int cols, double scale_min, double scale_max, double width, double height, double xref, double yref, double ydir) :\n        Values(values),\n        Count(rows*cols),\n        Rows(rows),\n        Cols(cols),\n        ScaleMin(scale_min),\n        ScaleMax(scale_max),\n        Width(width),\n        Height(height),\n        XRef(xref),\n        YRef(yref),\n        YDir(ydir),\n        HalfSize(Width*0.5, Height*0.5)\n    { }\n    template <typename I> IMPLOT_INLINE RectC operator()(I idx) const {\n        double val = (double)Values[idx];\n        const int r = idx % Cols;\n        const int c = idx / Cols;\n        const ImPlotPoint p(XRef + HalfSize.x + c*Width, YRef + YDir * (HalfSize.y + r*Height));\n        RectC rect;\n        rect.Pos = p;\n        rect.HalfSize = HalfSize;\n        const float t = ImClamp((float)ImRemap01(val, ScaleMin, ScaleMax),0.0f,1.0f);\n        ImPlotContext& gp = *GImPlot;\n        rect.Color = gp.ColormapData.LerpTable(gp.Style.Colormap, t);\n        return rect;\n    }\n    const T* const Values;\n    const int Count, Rows, Cols;\n    const double ScaleMin, ScaleMax, Width, Height, XRef, YRef, YDir;\n    const ImPlotPoint HalfSize;\n};\n\ntemplate <typename T>\nvoid RenderHeatmap(ImDrawList& draw_list, const T* values, int rows, int cols, double scale_min, double scale_max, const char* fmt, const ImPlotPoint& bounds_min, const ImPlotPoint& bounds_max, bool reverse_y, bool col_maj) {\n    ImPlotContext& gp = *GImPlot;\n    Transformer2 transformer;\n    if (scale_min == 0 && scale_max == 0) {\n        T temp_min, temp_max;\n        ImMinMaxArray(values,rows*cols,&temp_min,&temp_max);\n        scale_min = (double)temp_min;\n        scale_max = (double)temp_max;\n    }\n    if (scale_min == scale_max) {\n        ImVec2 a = transformer(bounds_min);\n        ImVec2 b = transformer(bounds_max);\n        ImU32  col = GetColormapColorU32(0,gp.Style.Colormap);\n        draw_list.AddRectFilled(a, b, col);\n        return;\n    }\n    const double yref = reverse_y ? bounds_max.y : bounds_min.y;\n    const double ydir = reverse_y ? -1 : 1;\n    if (col_maj) {\n        GetterHeatmapColMaj<T> getter(values, rows, cols, scale_min, scale_max, (bounds_max.x - bounds_min.x) / cols, (bounds_max.y - bounds_min.y) / rows, bounds_min.x, yref, ydir);\n        RenderPrimitives1<RendererRectC>(getter);\n    }\n    else {\n        GetterHeatmapRowMaj<T> getter(values, rows, cols, scale_min, scale_max, (bounds_max.x - bounds_min.x) / cols, (bounds_max.y - bounds_min.y) / rows, bounds_min.x, yref, ydir);\n        RenderPrimitives1<RendererRectC>(getter);\n    }\n    // labels\n    if (fmt != nullptr) {\n        const double w = (bounds_max.x - bounds_min.x) / cols;\n        const double h = (bounds_max.y - bounds_min.y) / rows;\n        const ImPlotPoint half_size(w*0.5,h*0.5);\n        int i = 0;\n        if (col_maj) {\n            for (int c = 0; c < cols; ++c) {\n                for (int r = 0; r < rows; ++r) {\n                    ImPlotPoint p;\n                    p.x = bounds_min.x + 0.5*w + c*w;\n                    p.y = yref + ydir * (0.5*h + r*h);\n                    ImVec2 px = transformer(p);\n                    char buff[32];\n                    ImFormatString(buff, 32, fmt, values[i]);\n                    ImVec2 size = ImGui::CalcTextSize(buff);\n                    double t = ImClamp(ImRemap01((double)values[i], scale_min, scale_max),0.0,1.0);\n                    ImVec4 color = SampleColormap((float)t);\n                    ImU32 col = CalcTextColor(color);\n                    draw_list.AddText(px - size * 0.5f, col, buff);\n                    i++;\n                }\n            }\n        }\n        else {\n            for (int r = 0; r < rows; ++r) {\n                for (int c = 0; c < cols; ++c) {\n                    ImPlotPoint p;\n                    p.x = bounds_min.x + 0.5*w + c*w;\n                    p.y = yref + ydir * (0.5*h + r*h);\n                    ImVec2 px = transformer(p);\n                    char buff[32];\n                    ImFormatString(buff, 32, fmt, values[i]);\n                    ImVec2 size = ImGui::CalcTextSize(buff);\n                    double t = ImClamp(ImRemap01((double)values[i], scale_min, scale_max),0.0,1.0);\n                    ImVec4 color = SampleColormap((float)t);\n                    ImU32 col = CalcTextColor(color);\n                    draw_list.AddText(px - size * 0.5f, col, buff);\n                    i++;\n                }\n            }\n        }\n    }\n}\n\ntemplate <typename T>\nvoid PlotHeatmap(const char* label_id, const T* values, int rows, int cols, double scale_min, double scale_max, const char* fmt, const ImPlotPoint& bounds_min, const ImPlotPoint& bounds_max, ImPlotHeatmapFlags flags) {\n    if (BeginItemEx(label_id, FitterRect(bounds_min, bounds_max))) {\n        ImDrawList& draw_list = *GetPlotDrawList();\n        const bool col_maj = ImHasFlag(flags, ImPlotHeatmapFlags_ColMajor);\n        RenderHeatmap(draw_list, values, rows, cols, scale_min, scale_max, fmt, bounds_min, bounds_max, true, col_maj);\n        EndItem();\n    }\n}\n#define INSTANTIATE_MACRO(T) template IMPLOT_API void PlotHeatmap<T>(const char* label_id, const T* values, int rows, int cols, double scale_min, double scale_max, const char* fmt, const ImPlotPoint& bounds_min, const ImPlotPoint& bounds_max, ImPlotHeatmapFlags flags);\nCALL_INSTANTIATE_FOR_NUMERIC_TYPES()\n#undef INSTANTIATE_MACRO\n\n//-----------------------------------------------------------------------------\n// [SECTION] PlotHistogram\n//-----------------------------------------------------------------------------\n\ntemplate <typename T>\ndouble PlotHistogram(const char* label_id, const T* values, int count, int bins, double bar_scale, ImPlotRange range, ImPlotHistogramFlags flags) {\n\n    const bool cumulative = ImHasFlag(flags, ImPlotHistogramFlags_Cumulative);\n    const bool density    = ImHasFlag(flags, ImPlotHistogramFlags_Density);\n    const bool outliers   = !ImHasFlag(flags, ImPlotHistogramFlags_NoOutliers);\n\n    if (count <= 0 || bins == 0)\n        return 0;\n\n    if (range.Min == 0 && range.Max == 0) {\n        T Min, Max;\n        ImMinMaxArray(values, count, &Min, &Max);\n        range.Min = (double)Min;\n        range.Max = (double)Max;\n    }\n\n    double width;\n    if (bins < 0)\n        CalculateBins(values, count, bins, range, bins, width);\n    else\n        width = range.Size() / bins;\n\n    ImPlotContext& gp = *GImPlot;\n    ImVector<double>& bin_centers = gp.TempDouble1;\n    ImVector<double>& bin_counts  = gp.TempDouble2;\n    bin_centers.resize(bins);\n    bin_counts.resize(bins);\n    int below = 0;\n\n    for (int b = 0; b < bins; ++b) {\n        bin_centers[b] = range.Min + b * width + width * 0.5;\n        bin_counts[b] = 0;\n    }\n    int counted = 0;\n    double max_count = 0;\n    for (int i = 0; i < count; ++i) {\n        double val = (double)values[i];\n        if (range.Contains(val)) {\n            const int b = ImClamp((int)((val - range.Min) / width), 0, bins - 1);\n            bin_counts[b] += 1.0;\n            if (bin_counts[b] > max_count)\n                max_count = bin_counts[b];\n            counted++;\n        }\n        else if (val < range.Min) {\n            below++;\n        }\n    }\n    if (cumulative && density) {\n        if (outliers)\n            bin_counts[0] += below;\n        for (int b = 1; b < bins; ++b)\n            bin_counts[b] += bin_counts[b-1];\n        double scale = 1.0 / (outliers ? count : counted);\n        for (int b = 0; b < bins; ++b)\n            bin_counts[b] *= scale;\n        max_count = bin_counts[bins-1];\n    }\n    else if (cumulative) {\n        if (outliers)\n            bin_counts[0] += below;\n        for (int b = 1; b < bins; ++b)\n            bin_counts[b] += bin_counts[b-1];\n        max_count = bin_counts[bins-1];\n    }\n    else if (density) {\n        double scale = 1.0 / ((outliers ? count : counted) * width);\n        for (int b = 0; b < bins; ++b)\n            bin_counts[b] *= scale;\n        max_count *= scale;\n    }\n    if (ImHasFlag(flags, ImPlotHistogramFlags_Horizontal))\n        PlotBars(label_id, &bin_counts.Data[0], &bin_centers.Data[0], bins, bar_scale*width, ImPlotBarsFlags_Horizontal);\n    else\n        PlotBars(label_id, &bin_centers.Data[0], &bin_counts.Data[0], bins, bar_scale*width);\n    return max_count;\n}\n#define INSTANTIATE_MACRO(T) template IMPLOT_API double PlotHistogram<T>(const char* label_id, const T* values, int count, int bins, double bar_scale, ImPlotRange range, ImPlotHistogramFlags flags);\nCALL_INSTANTIATE_FOR_NUMERIC_TYPES()\n#undef INSTANTIATE_MACRO\n\n//-----------------------------------------------------------------------------\n// [SECTION] PlotHistogram2D\n//-----------------------------------------------------------------------------\n\ntemplate <typename T>\ndouble PlotHistogram2D(const char* label_id, const T* xs, const T* ys, int count, int x_bins, int y_bins, ImPlotRect range, ImPlotHistogramFlags flags) {\n\n    // const bool cumulative = ImHasFlag(flags, ImPlotHistogramFlags_Cumulative); NOT SUPPORTED\n    const bool density  = ImHasFlag(flags, ImPlotHistogramFlags_Density);\n    const bool outliers = !ImHasFlag(flags, ImPlotHistogramFlags_NoOutliers);\n    const bool col_maj  = ImHasFlag(flags, ImPlotHistogramFlags_ColMajor);\n\n    if (count <= 0 || x_bins == 0 || y_bins == 0)\n        return 0;\n\n    if (range.X.Min == 0 && range.X.Max == 0) {\n        T Min, Max;\n        ImMinMaxArray(xs, count, &Min, &Max);\n        range.X.Min = (double)Min;\n        range.X.Max = (double)Max;\n    }\n    if (range.Y.Min == 0 && range.Y.Max == 0) {\n        T Min, Max;\n        ImMinMaxArray(ys, count, &Min, &Max);\n        range.Y.Min = (double)Min;\n        range.Y.Max = (double)Max;\n    }\n\n    double width, height;\n    if (x_bins < 0)\n        CalculateBins(xs, count, x_bins, range.X, x_bins, width);\n    else\n        width = range.X.Size() / x_bins;\n    if (y_bins < 0)\n        CalculateBins(ys, count, y_bins, range.Y, y_bins, height);\n    else\n        height = range.Y.Size() / y_bins;\n\n    const int bins = x_bins * y_bins;\n\n    ImPlotContext& gp = *GImPlot;\n    ImVector<double>& bin_counts = gp.TempDouble1;\n    bin_counts.resize(bins);\n\n    for (int b = 0; b < bins; ++b)\n        bin_counts[b] = 0;\n\n    int counted = 0;\n    double max_count = 0;\n    for (int i = 0; i < count; ++i) {\n        if (range.Contains((double)xs[i], (double)ys[i])) {\n            const int xb = ImClamp( (int)((double)(xs[i] - range.X.Min) / width)  , 0, x_bins - 1);\n            const int yb = ImClamp( (int)((double)(ys[i] - range.Y.Min) / height) , 0, y_bins - 1);\n            const int b  = yb * x_bins + xb;\n            bin_counts[b] += 1.0;\n            if (bin_counts[b] > max_count)\n                max_count = bin_counts[b];\n            counted++;\n        }\n    }\n    if (density) {\n        double scale = 1.0 / ((outliers ? count : counted) * width * height);\n        for (int b = 0; b < bins; ++b)\n            bin_counts[b] *= scale;\n        max_count *= scale;\n    }\n\n    if (BeginItemEx(label_id, FitterRect(range))) {\n        ImDrawList& draw_list = *GetPlotDrawList();\n        RenderHeatmap(draw_list, &bin_counts.Data[0], y_bins, x_bins, 0, max_count, nullptr, range.Min(), range.Max(), false, col_maj);\n        EndItem();\n    }\n    return max_count;\n}\n#define INSTANTIATE_MACRO(T) template IMPLOT_API double PlotHistogram2D<T>(const char* label_id,   const T*   xs, const T*   ys, int count, int x_bins, int y_bins, ImPlotRect range, ImPlotHistogramFlags flags);\nCALL_INSTANTIATE_FOR_NUMERIC_TYPES()\n#undef INSTANTIATE_MACRO\n\n//-----------------------------------------------------------------------------\n// [SECTION] PlotDigital\n//-----------------------------------------------------------------------------\n\n// TODO: Make this behave like all the other plot types (.e. not fixed in y axis)\n\ntemplate <typename Getter>\nvoid PlotDigitalEx(const char* label_id, Getter getter, ImPlotDigitalFlags flags) {\n    if (BeginItem(label_id, flags, ImPlotCol_Fill)) {\n        ImPlotContext& gp = *GImPlot;\n        ImDrawList& draw_list = *GetPlotDrawList();\n        const ImPlotNextItemData& s = GetItemData();\n        if (getter.Count > 1 && s.RenderFill) {\n            ImPlotPlot& plot   = *gp.CurrentPlot;\n            ImPlotAxis& x_axis = plot.Axes[plot.CurrentX];\n            ImPlotAxis& y_axis = plot.Axes[plot.CurrentY];\n\n            int pixYMax = 0;\n            ImPlotPoint itemData1 = getter(0);\n            for (int i = 0; i < getter.Count; ++i) {\n                ImPlotPoint itemData2 = getter(i);\n                if (ImNanOrInf(itemData1.y)) {\n                    itemData1 = itemData2;\n                    continue;\n                }\n                if (ImNanOrInf(itemData2.y)) itemData2.y = ImConstrainNan(ImConstrainInf(itemData2.y));\n                int pixY_0 = (int)(s.LineWeight);\n                itemData1.y = ImMax(0.0, itemData1.y);\n                float pixY_1_float = s.DigitalBitHeight * (float)itemData1.y;\n                int pixY_1 = (int)(pixY_1_float); //allow only positive values\n                int pixY_chPosOffset = (int)(ImMax(s.DigitalBitHeight, pixY_1_float) + s.DigitalBitGap);\n                pixYMax = ImMax(pixYMax, pixY_chPosOffset);\n                ImVec2 pMin = PlotToPixels(itemData1,IMPLOT_AUTO,IMPLOT_AUTO);\n                ImVec2 pMax = PlotToPixels(itemData2,IMPLOT_AUTO,IMPLOT_AUTO);\n                int pixY_Offset = 0; //20 pixel from bottom due to mouse cursor label\n                pMin.y = (y_axis.PixelMin) + ((-gp.DigitalPlotOffset)                   - pixY_Offset);\n                pMax.y = (y_axis.PixelMin) + ((-gp.DigitalPlotOffset) - pixY_0 - pixY_1 - pixY_Offset);\n                //plot only one rectangle for same digital state\n                while (((i+2) < getter.Count) && (itemData1.y == itemData2.y)) {\n                    const int in = (i + 1);\n                    itemData2 = getter(in);\n                    if (ImNanOrInf(itemData2.y)) break;\n                    pMax.x = PlotToPixels(itemData2,IMPLOT_AUTO,IMPLOT_AUTO).x;\n                    i++;\n                }\n                //do not extend plot outside plot range\n                if (pMin.x < x_axis.PixelMin) pMin.x = x_axis.PixelMin;\n                if (pMax.x < x_axis.PixelMin) pMax.x = x_axis.PixelMin;\n                if (pMin.x > x_axis.PixelMax) pMin.x = x_axis.PixelMax - 1; //fix issue related to https://github.com/ocornut/imgui/issues/3976\n                if (pMax.x > x_axis.PixelMax) pMax.x = x_axis.PixelMax - 1; //fix issue related to https://github.com/ocornut/imgui/issues/3976\n                //plot a rectangle that extends up to x2 with y1 height\n                if ((pMax.x > pMin.x) && (gp.CurrentPlot->PlotRect.Contains(pMin) || gp.CurrentPlot->PlotRect.Contains(pMax))) {\n                    // ImVec4 colAlpha = item->Color;\n                    // colAlpha.w = item->Highlight ? 1.0f : 0.9f;\n                    draw_list.AddRectFilled(pMin, pMax, ImGui::GetColorU32(s.Colors[ImPlotCol_Fill]));\n                }\n                itemData1 = itemData2;\n            }\n            gp.DigitalPlotItemCnt++;\n            gp.DigitalPlotOffset += pixYMax;\n        }\n        EndItem();\n    }\n}\n\n\ntemplate <typename T>\nvoid PlotDigital(const char* label_id, const T* xs, const T* ys, int count, ImPlotDigitalFlags flags, int offset, int stride) {\n    GetterXY<IndexerIdx<T>,IndexerIdx<T>> getter(IndexerIdx<T>(xs,count,offset,stride),IndexerIdx<T>(ys,count,offset,stride),count);\n    return PlotDigitalEx(label_id, getter, flags);\n}\n#define INSTANTIATE_MACRO(T) template IMPLOT_API void PlotDigital<T>(const char* label_id, const T* xs, const T* ys, int count, ImPlotDigitalFlags flags, int offset, int stride);\nCALL_INSTANTIATE_FOR_NUMERIC_TYPES()\n#undef INSTANTIATE_MACRO\n\n// custom\nvoid PlotDigitalG(const char* label_id, ImPlotGetter getter_func, void* data, int count, ImPlotDigitalFlags flags) {\n    GetterFuncPtr getter(getter_func,data,count);\n    return PlotDigitalEx(label_id, getter, flags);\n}\n\n//-----------------------------------------------------------------------------\n// [SECTION] PlotImage\n//-----------------------------------------------------------------------------\n\nvoid PlotImage(const char* label_id, ImTextureID user_texture_id, const ImPlotPoint& bmin, const ImPlotPoint& bmax, const ImVec2& uv0, const ImVec2& uv1, const ImVec4& tint_col, ImPlotImageFlags) {\n    if (BeginItemEx(label_id, FitterRect(bmin,bmax))) {\n        ImU32 tint_col32 = ImGui::ColorConvertFloat4ToU32(tint_col);\n        GetCurrentItem()->Color = tint_col32;\n        ImDrawList& draw_list = *GetPlotDrawList();\n        ImVec2 p1 = PlotToPixels(bmin.x, bmax.y,IMPLOT_AUTO,IMPLOT_AUTO);\n        ImVec2 p2 = PlotToPixels(bmax.x, bmin.y,IMPLOT_AUTO,IMPLOT_AUTO);\n        PushPlotClipRect();\n        draw_list.AddImage(user_texture_id, p1, p2, uv0, uv1, tint_col32);\n        PopPlotClipRect();\n        EndItem();\n    }\n}\n\n//-----------------------------------------------------------------------------\n// [SECTION] PlotText\n//-----------------------------------------------------------------------------\n\nvoid PlotText(const char* text, double x, double y, const ImVec2& pixel_offset, ImPlotTextFlags flags) {\n    IM_ASSERT_USER_ERROR(GImPlot->CurrentPlot != nullptr, \"PlotText() needs to be called between BeginPlot() and EndPlot()!\");\n    SetupLock();\n    ImDrawList & draw_list = *GetPlotDrawList();\n    PushPlotClipRect();\n    ImU32 colTxt = GetStyleColorU32(ImPlotCol_InlayText);\n    if (ImHasFlag(flags,ImPlotTextFlags_Vertical)) {\n        ImVec2 siz = CalcTextSizeVertical(text) * 0.5f;\n        ImVec2 ctr = siz * 0.5f;\n        ImVec2 pos = PlotToPixels(ImPlotPoint(x,y),IMPLOT_AUTO,IMPLOT_AUTO) + ImVec2(-ctr.x, ctr.y) + pixel_offset;\n        if (FitThisFrame() && !ImHasFlag(flags, ImPlotItemFlags_NoFit)) {\n            FitPoint(PixelsToPlot(pos));\n            FitPoint(PixelsToPlot(pos.x + siz.x, pos.y - siz.y));\n        }\n        AddTextVertical(&draw_list, pos, colTxt, text);\n    }\n    else {\n        ImVec2 siz = ImGui::CalcTextSize(text);\n        ImVec2 pos = PlotToPixels(ImPlotPoint(x,y),IMPLOT_AUTO,IMPLOT_AUTO) - siz * 0.5f + pixel_offset;\n        if (FitThisFrame() && !ImHasFlag(flags, ImPlotItemFlags_NoFit)) {\n            FitPoint(PixelsToPlot(pos));\n            FitPoint(PixelsToPlot(pos+siz));\n        }\n        draw_list.AddText(pos, colTxt, text);\n    }\n    PopPlotClipRect();\n}\n\n//-----------------------------------------------------------------------------\n// [SECTION] PlotDummy\n//-----------------------------------------------------------------------------\n\nvoid PlotDummy(const char* label_id, ImPlotDummyFlags flags) {\n    if (BeginItem(label_id, flags, ImPlotCol_Line))\n        EndItem();\n}\n\n} // namespace ImPlot\n"
  },
  {
    "path": "src/DesktopPlusUI/resource.h",
    "content": "//{{NO_DEPENDENCIES}}\n// Microsoft Visual C++ generated include file.\n// Used by DesktopPlusUI.rc\n//\n#define IDI_ICON1                       102\n#define IDI_DPLUS                       102\n\n// Next default values for new objects\n// \n#ifdef APSTUDIO_INVOKED\n#ifndef APSTUDIO_READONLY_SYMBOLS\n#define _APS_NEXT_RESOURCE_VALUE        103\n#define _APS_NEXT_COMMAND_VALUE         40001\n#define _APS_NEXT_CONTROL_VALUE         1001\n#define _APS_NEXT_SYMED_VALUE           101\n#endif\n#endif\n"
  },
  {
    "path": "src/DesktopPlusWinRT/CaptureManager.cpp",
    "content": "#ifndef DPLUSWINRT_STUB\n\n#include \"CommonHeaders.h\"\n#include \"CaptureManager.h\"\n\n#include \"DesktopPlusWinRT.h\"\n\nnamespace winrt\n{\n    using namespace Windows::Storage;\n    using namespace Windows::System;\n    using namespace Windows::Foundation;\n    using namespace Windows::UI;\n    using namespace Windows::UI::Composition;\n    using namespace Windows::UI::Popups;\n    using namespace Windows::Graphics::Capture;\n    using namespace Windows::Graphics::DirectX;\n}\n\nnamespace util\n{\n    using namespace desktop;\n}\n\nCaptureManager::CaptureManager(DPWinRTThreadData& thread_data, DWORD global_main_thread_id) : m_ThreadData(thread_data)\n{\n    m_CaptureMainThread = winrt::DispatcherQueue::GetForCurrentThread();\n    m_GlobalMainThreadID = global_main_thread_id;\n    WINRT_VERIFY(m_CaptureMainThread != nullptr);\n\n    //Get the adapter recommended by OpenVR\n    winrt::com_ptr<ID3D11Device> d3d_device;\n    winrt::com_ptr<IDXGIFactory1> factory_ptr;\n    winrt::com_ptr<IDXGIAdapter> adapter_ptr_vr;\n    int32_t vr_gpu_id;\n    vr::VRSystem()->GetDXGIOutputInfo(&vr_gpu_id);  \n\n    HRESULT hr = CreateDXGIFactory1(__uuidof(IDXGIFactory1), factory_ptr.put_void());\n    if (!FAILED(hr))\n    {\n        winrt::com_ptr<IDXGIAdapter> adapter_ptr;\n        UINT i = 0;\n\n        while (factory_ptr->EnumAdapters(i, adapter_ptr.put()) != DXGI_ERROR_NOT_FOUND)\n        {\n            if (i == vr_gpu_id)\n            {\n                adapter_ptr_vr = adapter_ptr;\n                break;\n            }\n\n            adapter_ptr = nullptr;\n            ++i;\n        }\n    }\n\n    if (adapter_ptr_vr != nullptr)\n    {\n        hr = D3D11CreateDevice(adapter_ptr_vr.get(), D3D_DRIVER_TYPE_UNKNOWN, nullptr, D3D11_CREATE_DEVICE_BGRA_SUPPORT, nullptr, 0, D3D11_SDK_VERSION, d3d_device.put(), nullptr, nullptr);\n    }\n    \n    if (d3d_device == nullptr)   //Try something else, but it probably won't work either\n    {\n        HRESULT hr = D3D11CreateDevice(nullptr, D3D_DRIVER_TYPE_HARDWARE, nullptr, D3D11_CREATE_DEVICE_BGRA_SUPPORT, nullptr, 0, D3D11_SDK_VERSION, d3d_device.put(), nullptr, nullptr);\n        if (FAILED(hr))\n        {\n            hr = D3D11CreateDevice(nullptr, D3D_DRIVER_TYPE_WARP, nullptr, D3D11_CREATE_DEVICE_BGRA_SUPPORT, nullptr, 0, D3D11_SDK_VERSION, d3d_device.put(), nullptr, nullptr);\n        }\n    }\n\n    //Get it as WinRT D3D11 device\n    auto dxgi_device = d3d_device.try_as<IDXGIDevice>();\n    m_Device = CreateDirect3DDevice(dxgi_device.get());\n}\n\nwinrt::GraphicsCaptureItem CaptureManager::StartCaptureFromWindowHandle(HWND hwnd)\n{\n    auto item = util::CreateCaptureItemForWindow(hwnd);\n    StartCaptureFromItem(item);\n    return item;\n}\n\nwinrt::GraphicsCaptureItem CaptureManager::StartCaptureFromMonitorHandle(HMONITOR hmon)\n{\n    auto item = util::CreateCaptureItemForMonitor(hmon);\n    StartCaptureFromItem(item);\n    return item;\n}\n\nvoid CaptureManager::StartCaptureFromItem(winrt::GraphicsCaptureItem item)\n{\n    m_Capture = std::make_unique<OverlayCapture>(m_Device, item, m_PixelFormat, m_GlobalMainThreadID, m_ThreadData.Overlays, m_ThreadData.SourceWindow);\n\n    m_Capture->StartCapture();\n    m_ItemClosedRevoker = item.Closed(winrt::auto_revoke, { this, &CaptureManager::OnCaptureItemClosed });\n\n    //Check if all overlays of this capture are already paused and pause the capture as well then\n    bool all_paused = true;\n    for (DPWinRTOverlayData& overlay_data : m_ThreadData.Overlays)\n    {\n        if (!overlay_data.IsPaused)\n        {\n            all_paused = false;\n            break;\n        }\n    }\n\n    if (all_paused)\n    {\n        m_Capture->PauseCapture(true);\n    }\n}\n\nvoid CaptureManager::OnCaptureItemClosed(winrt::GraphicsCaptureItem const&, winrt::Windows::Foundation::IInspectable const&)\n{\n    StopCapture();\n\n    //Send overlay status updates\n    for (const auto& overlay : m_ThreadData.Overlays)\n    {\n        ::PostThreadMessage(m_GlobalMainThreadID, WM_DPLUSWINRT_CAPTURE_LOST, overlay.Handle, 0);\n    }\n}\n\nbool CaptureManager::IsCursorEnabled()\n{\n    if (m_Capture != nullptr)\n    {\n        return m_Capture->IsCursorEnabled();\n    }\n    return false;\n}\n\nvoid CaptureManager::IsCursorEnabled(bool value)\n{\n    if (m_Capture != nullptr)\n    {\n        m_Capture->IsCursorEnabled(value);\n    }\n}\n\nbool CaptureManager::IsCapturePaused()\n{\n    return ( (m_Capture) && (m_Capture->IsPaused()) );\n}\n\nvoid CaptureManager::OnOverlayDataRefresh()\n{\n    if (m_Capture)\n    {\n        m_Capture->OnOverlayDataRefresh();\n    }\n}\n\nvoid CaptureManager::PauseCapture(bool pause)\n{\n    if (m_Capture)\n    {\n        m_Capture->PauseCapture(pause);\n    }\n}\n\nvoid CaptureManager::StopCapture()\n{\n    if (m_Capture)\n    {\n        m_Capture = nullptr;\n    }\n}\n\nvoid CaptureManager::PixelFormat(winrt::DirectXPixelFormat pixel_format)\n{\n    m_PixelFormat = pixel_format;\n\n    if (m_Capture)\n    {\n        auto item = m_Capture->CaptureItem();\n        bool is_cursor_enabled = m_Capture->IsCursorEnabled();\n\n        StopCapture();\n        StartCaptureFromItem(item);\n\n        if (!is_cursor_enabled)\n        {\n            m_Capture->IsCursorEnabled(is_cursor_enabled);\n        }\n    }\n}\n\n#endif //DPLUSWINRT_STUB"
  },
  {
    "path": "src/DesktopPlusWinRT/CaptureManager.h",
    "content": "#pragma once\n#include \"OverlayCapture.h\"\n\nclass CaptureManager\n{\n    public:\n        CaptureManager(DPWinRTThreadData& thread_data, DWORD global_main_thread_id);\n        ~CaptureManager() {}\n\n        winrt::Windows::Graphics::Capture::GraphicsCaptureItem StartCaptureFromWindowHandle(HWND hwnd);\n        winrt::Windows::Graphics::Capture::GraphicsCaptureItem StartCaptureFromMonitorHandle(HMONITOR hmon);\n        winrt::Windows::Graphics::DirectX::DirectXPixelFormat PixelFormat() { return m_PixelFormat; }\n        void PixelFormat(winrt::Windows::Graphics::DirectX::DirectXPixelFormat pixel_format);\n\n        bool IsCursorEnabled();\n        void IsCursorEnabled(bool value);\n        bool IsCapturePaused();\n\n        void OnOverlayDataRefresh();\n\n        void PauseCapture(bool pause);\n        void StopCapture();\n\n    private:\n        void StartCaptureFromItem(winrt::Windows::Graphics::Capture::GraphicsCaptureItem item);\n        void OnCaptureItemClosed(winrt::Windows::Graphics::Capture::GraphicsCaptureItem const&, winrt::Windows::Foundation::IInspectable const&);\n\n    private:\n        winrt::Windows::System::DispatcherQueue m_CaptureMainThread{ nullptr };\n\n        winrt::Windows::Graphics::DirectX::Direct3D11::IDirect3DDevice m_Device { nullptr };\n        std::unique_ptr<OverlayCapture> m_Capture { nullptr };\n        winrt::Windows::Graphics::Capture::GraphicsCaptureItem::Closed_revoker m_ItemClosedRevoker;\n        winrt::Windows::Graphics::DirectX::DirectXPixelFormat m_PixelFormat = winrt::Windows::Graphics::DirectX::DirectXPixelFormat::B8G8R8A8UIntNormalized;\n\n        DPWinRTThreadData& m_ThreadData;\n        DWORD m_GlobalMainThreadID;\n};"
  },
  {
    "path": "src/DesktopPlusWinRT/CommonHeaders.h",
    "content": "#pragma once\n\n#include <Unknwn.h>\n#include <inspectable.h>\n\n#include <wil/cppwinrt.h>\n\n// WinRT\n#include <winrt/Windows.Foundation.h>\n#include <winrt/Windows.Foundation.Collections.h>\n#include <winrt/Windows.Foundation.Metadata.h>\n#include <winrt/Windows.Storage.h>\n#include <winrt/Windows.Graphics.Capture.h>\n#include <winrt/Windows.Graphics.DirectX.h>\n#include <winrt/Windows.Graphics.DirectX.Direct3d11.h>\n\n#include <DispatcherQueue.h>\n\n// STL\n#include <atomic>\n#include <memory>\n#include <algorithm>\n#include <vector>\n\n// D3D\n#include <d3d11_4.h>\n#include <dxgi1_6.h>\n#include <d2d1_3.h>\n#include <wincodec.h>\n\n// DWM\n#include <dwmapi.h>\n\n// WIL\n#include <wil/resource.h>\n\n// Helpers\n#include \"util/direct3d11.interop.h\"\n#include \"util/capture.desktop.interop.h\"\n#include \"util/dispatcherqueue.desktop.interop.h\"\n#include \"util/hwnd.interop.h\""
  },
  {
    "path": "src/DesktopPlusWinRT/DesktopPlusWinRT.cpp",
    "content": "#include \"DesktopPlusWinRT.h\"\n\n#ifndef DPLUSWINRT_STUB\n\n#pragma comment (lib, \"windowsapp.lib\")\n\n#include <mutex>\n#include <utility>\n#include <limits.h>\n\n#include \"CommonHeaders.h\"\n#include \"CaptureManager.h\"\n\n#include \"ThreadData.h\"\n\n#include \"Util.h\"\n#include \"OpenVRExt.h\"\n\n#include \"Util/hwnd.interop.h\"\n\n//Globals\n//- Not modified after DPWinRT_Init()\nstatic DWORD g_MainThreadID;\nstatic bool g_IsCaptureSupported;\nstatic int  g_APIContractPresent;\n\n//- Protected by g_ThreadsMutex\nstatic std::mutex g_ThreadsMutex;\nstatic std::vector<DPWinRTThreadData> g_Threads;\n\n//- Only accessed by main thread\nstatic bool g_IsCursorEnabled;\n\n//- Rarely accessed atomics\nstatic std::atomic<bool> g_IsHDREnabled;\nstatic std::atomic<bool> g_DesktopEnumFlagIgnoreWMRScreens;\n\nnamespace winrt\n{\n    using namespace Windows::Foundation;\n    using namespace Windows::Foundation::Metadata;\n    using namespace Windows::Graphics::Capture;\n    using namespace Windows::Graphics::DirectX;\n}\n\nnamespace util\n{\n    using namespace desktop;\n}\n\n#endif //DPLUSWINRT_STUB\n\nBOOL APIENTRY DllMain(HMODULE hModule, DWORD  ul_reason_for_call, LPVOID lpReserved)\n{\n    switch (ul_reason_for_call)\n    {\n        case DLL_PROCESS_ATTACH:\n        case DLL_THREAD_ATTACH:\n        case DLL_THREAD_DETACH:\n        case DLL_PROCESS_DETACH:\n            break;\n    }\n    return TRUE;\n}\n\n#ifndef DPLUSWINRT_STUB\n\nDWORD WINAPI WinRTCaptureThreadEntry(_In_ void* Param);\n\nbool DPWinRT_Internal_StartCapture(vr::VROverlayHandle_t overlay_handle, const DPWinRTThreadData& data)\n{\n    //Make sure this overlay handle is not already used by a thread\n    DPWinRT_StopCapture(overlay_handle);\n\n    HANDLE thread_handle = nullptr;\n    DWORD thread_id = 0;\n\n    {\n        std::lock_guard<std::mutex> lock(g_ThreadsMutex);\n\n        DPWinRTOverlayData overlay_data;\n        overlay_data.Handle = overlay_handle;\n\n        //Try to find a thread already capturing this item\n        for (auto& thread : g_Threads)\n        {\n            if ((thread.DesktopID == data.DesktopID) && (thread.SourceWindow == data.SourceWindow))\n            {\n                thread.Overlays.push_back(overlay_data);\n\n                ::PostThreadMessage(thread.ThreadID, WM_DPLUSWINRT_UPDATE_DATA, 0, 0);\n                return true;\n            }\n        }\n\n        //Create new thread if no existing one was found\n        g_Threads.push_back(data);\n        g_Threads.back().Overlays.push_back(overlay_data);\n        g_Threads.back().IsCursorEnabledInitial = g_IsCursorEnabled;\n\n        //Thread may run before CreateThread() returns, but the thread will have to wait on the mutex so it's fine\n        thread_handle = ::CreateThread(nullptr, 0, WinRTCaptureThreadEntry, &g_Threads.back(), 0, &thread_id);\n\n        g_Threads.back().ThreadHandle = thread_handle;\n        g_Threads.back().ThreadID     = thread_id;\n    }\n\n    if (thread_handle == nullptr)\n    {\n        return false;\n    }\n\n    //Wait for the thread's message queue to be ready to not risk any messages getting lost\n    BOOL is_ready = false;\n\n    do\n    {\n        is_ready = ::PostThreadMessage(thread_id, WM_NULL, 0, 0);\n        ::Sleep(10);\n    }\n    while (is_ready == 0);\n\n    return true;\n}\n\n#endif //DPLUSWINRT_STUB\n\nvoid DPWinRT_Init()\n{\n    #ifndef DPLUSWINRT_STUB\n\n    g_MainThreadID = ::GetCurrentThreadId();\n    //Reserve thread data vector size to avoid potential race condition between resize and thread creation when many overlays are created back-to-back (while also avoiding explicit syncing there).\n    //100 should be pretty safe as a limit, considering k_unMaxOverlayCount (128) with system overlays and Desktop+ UI overlays... also 100 captures are insane either way\n    g_Threads.reserve(100);\n\n    //Init results of capability query functions so we don't need an apartment on the main thread\n    winrt::init_apartment(winrt::apartment_type::multi_threaded);\n    {\n        //Try getting feature support information. This pretty much only throws when on Windows 8/8.1, where the APIs are simply not present yet. \n        //We still want to support running on there, though\n        try\n        {\n            g_IsCaptureSupported = winrt::Windows::Graphics::Capture::GraphicsCaptureSession::IsSupported();\n\n            //This only checks for contract levels we care about\n            if (winrt::ApiInformation::IsApiContractPresent(L\"Windows.Foundation.UniversalApiContract\", 9))\n                g_APIContractPresent = 9;\n            else if (winrt::ApiInformation::IsApiContractPresent(L\"Windows.Foundation.UniversalApiContract\", 8))\n                g_APIContractPresent = 8;\n            else\n                g_APIContractPresent = 1;\n        }\n        catch (const winrt::hresult_error&)\n        {\n            //We can do nothing, but at least we didn't crash\n            g_IsCaptureSupported = false;\n            g_APIContractPresent = 0;\n        }\n        catch (const std::bad_alloc&) //GraphicsCaptureSession::IsSupported() may throw std::bad_alloc on Windows 10 in some situations, so we catch that to avoid crashing\n        {\n            g_IsCaptureSupported = false;\n            g_APIContractPresent = 0;\n        }\n    }\n    winrt::clear_factory_cache();\n    winrt::uninit_apartment();\n\n    g_IsCursorEnabled = true;\n    g_IsHDREnabled = true;\n    g_DesktopEnumFlagIgnoreWMRScreens = true;\n\n    #endif\n}\n\nbool DPWinRT_IsCaptureSupported()\n{\n    #ifndef DPLUSWINRT_STUB\n        return ((g_IsCaptureSupported) && (DPWinRT_IsCaptureFromHandleSupported()));\n    #else\n        return false;\n    #endif\n}\n\nbool DPWinRT_IsCaptureFromHandleSupported()\n{\n    #ifndef DPLUSWINRT_STUB\n        return (g_APIContractPresent >= 8);\n    #else\n        return false;\n    #endif\n}\n\nbool DPWinRT_IsCaptureFromCombinedDesktopSupported()\n{\n    #ifndef DPLUSWINRT_STUB\n        return (g_APIContractPresent >= 9);\n    #else\n        return false;\n    #endif\n}\n\nbool DPWinRT_IsCaptureCursorEnabledPropertySupported()\n{\n    #ifndef DPLUSWINRT_STUB\n        return (g_APIContractPresent >= 9);\n    #else\n        return false;\n    #endif\n}\n\nbool DPWinRT_IsBorderRequiredPropertySupported()\n{\n    #ifndef DPLUSWINRT_STUB\n        #if WINDOWS_FOUNDATION_UNIVERSALAPICONTRACT_VERSION >= 0xc0000\n        if (winrt::Metadata::ApiInformation::IsPropertyPresent(winrt::name_of<winrt::GraphicsCaptureSession>(), L\"IsBorderRequired\"))\n        {\n            return true;\n        }\n        #endif\n    #endif\n\n    return false;\n}\n\nbool DPWinRT_IsIncludeSecondaryWindowsPropertySupported()\n{\n    #ifndef DPLUSWINRT_STUB\n        #if WINDOWS_FOUNDATION_UNIVERSALAPICONTRACT_VERSION >= 0x130000\n        if (winrt::Metadata::ApiInformation::IsPropertyPresent(winrt::name_of<winrt::GraphicsCaptureSession>(), L\"IncludeSecondaryWindows\"))\n        {\n            return true;\n        }\n        #endif\n    #endif\n\n    return false;\n}\n\nbool DPWinRT_IsMinUpdateIntervalPropertySupported()\n{\n    #ifndef DPLUSWINRT_STUB\n        #if WINDOWS_FOUNDATION_UNIVERSALAPICONTRACT_VERSION >= 0x130000\n        if (winrt::Metadata::ApiInformation::IsPropertyPresent(winrt::name_of<winrt::GraphicsCaptureSession>(), L\"MinUpdateInterval\"))\n        {\n            return true;\n        }\n        #endif\n    #endif\n\n    return false;\n}\n\nbool DPWinRT_StartCaptureFromHWND(vr::VROverlayHandle_t overlay_handle, HWND handle)\n{\n    #ifndef DPLUSWINRT_STUB\n        DPWinRTThreadData data;\n        data.SourceWindow = handle;\n\n        return DPWinRT_Internal_StartCapture(overlay_handle, data);\n    #else\n        return false;\n    #endif\n}\n\nbool DPWinRT_StartCaptureFromDesktop(vr::VROverlayHandle_t overlay_handle, int desktop_id)\n{\n    #ifndef DPLUSWINRT_STUB\n        DPWinRTThreadData data;\n        data.DesktopID = desktop_id;\n\n        return DPWinRT_Internal_StartCapture(overlay_handle, data);\n    #else\n        return false;\n    #endif\n}\n\nbool DPWinRT_StartCaptureFromOverlay(vr::VROverlayHandle_t overlay_handle, vr::VROverlayHandle_t overlay_handle_source)\n{\n    #ifndef DPLUSWINRT_STUB\n\n    std::lock_guard<std::mutex> lock(g_ThreadsMutex);\n\n    //Find thread with the source overlay assigned and add the other overlay to it with duplicated state\n    //This means this function is only good for adding capture after an overlay was duplicated, otherwise some state needs to be adjusted right after\n    for (auto& thread : g_Threads)\n    {\n        auto it = std::find_if(thread.Overlays.begin(), thread.Overlays.end(), [&](const auto& data){ return (data.Handle == overlay_handle_source); });\n\n        if (it != thread.Overlays.end())\n        {\n            DPWinRTOverlayData overlay_data = *it;\n            overlay_data.Handle = overlay_handle;\n\n            thread.Overlays.push_back(overlay_data);\n\n            ::PostThreadMessage(thread.ThreadID, WM_DPLUSWINRT_UPDATE_DATA, 0, 0);\n            return true;\n        }\n    }\n\n    #endif //DPLUSWINRT_STUB\n\n    return false; //Overlay wasn't used in a capture in the first place\n}\n\nbool DPWinRT_PauseCapture(vr::VROverlayHandle_t overlay_handle, bool pause)\n{\n    #ifndef DPLUSWINRT_STUB\n\n    std::lock_guard<std::mutex> lock(g_ThreadsMutex);\n\n    //Find thread with the overlay assigned and tell it to set pause state\n    for (auto& thread : g_Threads)\n    {\n        auto it = std::find_if(thread.Overlays.begin(), thread.Overlays.end(), [&](const auto& data){ return (data.Handle == overlay_handle); });\n\n        if (it != thread.Overlays.end())\n        {\n            it->IsPaused = pause;\n            ::PostThreadMessage(thread.ThreadID, WM_DPLUSWINRT_CAPTURE_PAUSE, overlay_handle, pause);\n            return true;\n        }\n    }\n\n    #endif //DPLUSWINRT_STUB\n\n    return false; //Overlay wasn't used in a capture in the first place\n}\n\nbool DPWinRT_StopCapture(vr::VROverlayHandle_t overlay_handle)\n{\n    #ifndef DPLUSWINRT_STUB\n\n    bool wait_for_ack = false;\n    //Clear out existing WM_DPLUSWINRT_THREAD_ACK messages first just in case\n    MSG msg;\n    while (PeekMessage(&msg, nullptr, WM_DPLUSWINRT_THREAD_ACK, WM_DPLUSWINRT_THREAD_ACK, PM_REMOVE));\n\n    //Find thread with overlay and remove overlay from it\n    {\n        std::lock_guard<std::mutex> lock(g_ThreadsMutex);\n\n        for (auto thread_it = g_Threads.begin(); thread_it != g_Threads.end(); ++thread_it)\n        {\n            auto& thread = *thread_it;\n            auto it = std::find_if(thread.Overlays.begin(), thread.Overlays.end(), [&](const auto& data) { return (data.Handle == overlay_handle); });\n\n            if (it != thread.Overlays.end())\n            {\n                thread.Overlays.erase(it);\n\n                if (thread.Overlays.empty()) //Quit and remove thread when no overlays left\n                {\n                    ::PostThreadMessage(thread.ThreadID, WM_DPLUSWINRT_THREAD_QUIT, 0, 0);\n\n                    ::CloseHandle(thread.ThreadHandle);\n                    g_Threads.erase(thread_it);\n                }\n                else //otherwise, update data\n                {\n                    ::PostThreadMessage(thread.ThreadID, WM_DPLUSWINRT_UPDATE_DATA, 0, 0);\n                }\n\n                wait_for_ack = true;\n                break;\n            }\n        }\n    }\n\n    //Wait for WM_DPLUSWINRT_THREAD_ACK so we can be sure there won't be any additional overlay updates after this function returns\n    ULONGLONG start_tick = ::GetTickCount64();\n    while (wait_for_ack)\n    {\n        if ( (PeekMessage(&msg, nullptr, WM_DPLUSWINRT_THREAD_ACK, WM_DPLUSWINRT_THREAD_ACK, PM_REMOVE)) || (::GetTickCount64() >= start_tick + 500) ) //On message or 500ms timeout\n        {\n            return true;\n        }\n\n        ::Sleep(0);\n    }\n\n    //Release shared overlay texture if there is any and remove cached data\n    vr::VROverlayEx()->ReleaseSharedOverlayTexture(overlay_handle);\n\n    #endif //DPLUSWINRT_STUB\n\n    return false; //Overlay wasn't used in a capture in the first place\n}\n\nbool DPWinRT_SetOverlayUpdateLimitDelay(vr::VROverlayHandle_t overlay_handle, LONGLONG delay_quadpart)\n{\n    #ifndef DPLUSWINRT_STUB\n\n    std::lock_guard<std::mutex> lock(g_ThreadsMutex);\n\n    //Find thread with the overlay assigned and update the thread data\n    for (auto& thread : g_Threads)\n    {\n        auto it = std::find_if(thread.Overlays.begin(), thread.Overlays.end(), [&](const auto& data){ return (data.Handle == overlay_handle); });\n\n        if (it != thread.Overlays.end())\n        {\n            //If no change, back out\n            if (it->UpdateLimiterDelay.QuadPart == delay_quadpart)\n                return true;\n\n            it->UpdateLimiterDelay.QuadPart = delay_quadpart;\n\n            ::PostThreadMessage(thread.ThreadID, WM_DPLUSWINRT_UPDATE_DATA, 0, 0);\n            return true;\n        }\n    }\n\n    #endif //DPLUSWINRT_STUB\n\n    return false; //Overlay wasn't used in a capture in the first place\n}\n\nbool DPWinRT_SetOverlayOverUnder3D(vr::VROverlayHandle_t overlay_handle, bool is_over_under_3D, int crop_x, int crop_y, int crop_width, int crop_height)\n{\n    #ifndef DPLUSWINRT_STUB\n\n    std::lock_guard<std::mutex> lock(g_ThreadsMutex);\n\n    //Find thread with the overlay assigned and update the thread data\n    for (auto& thread : g_Threads)\n    {\n        auto it = std::find_if(thread.Overlays.begin(), thread.Overlays.end(), [&](const auto& data){ return (data.Handle == overlay_handle); });\n\n        if (it != thread.Overlays.end())\n        {\n            //If no change, back out\n            if (it->IsOverUnder3D == is_over_under_3D)\n            {\n                //Only check if crop matches if OU3D is on\n                if ( (!is_over_under_3D) || ( (it->OU3D_crop_x == crop_x) && (it->OU3D_crop_y == crop_y) && (it->OU3D_crop_width == crop_width) && (it->OU3D_crop_height == crop_height) ) )\n                {\n                    return true;\n                }\n            }\n\n            it->IsOverUnder3D    = is_over_under_3D;\n            it->OU3D_crop_x      = crop_x;\n            it->OU3D_crop_y      = crop_y;\n            it->OU3D_crop_width  = crop_width;\n            it->OU3D_crop_height = crop_height;\n\n            ::PostThreadMessage(thread.ThreadID, WM_DPLUSWINRT_UPDATE_DATA, 0, 0);\n            return true;\n        }\n    }\n\n    #endif //DPLUSWINRT_STUB\n\n    return false; //Overlay wasn't used in a capture in the first place\n}\n\nvoid DPWinRT_SetCaptureCursorEnabled(bool is_cursor_enabled)\n{\n    #ifndef DPLUSWINRT_STUB\n\n    //Send enable cursor message to all threads if the value changed\n    if (g_IsCursorEnabled != is_cursor_enabled)\n    {\n        std::lock_guard<std::mutex> lock(g_ThreadsMutex);\n\n        for (const auto& thread : g_Threads)\n        {\n            ::PostThreadMessage(thread.ThreadID, WM_DPLUSWINRT_ENABLE_CURSOR, is_cursor_enabled, 0);\n        }\n\n        g_IsCursorEnabled = is_cursor_enabled;\n    }\n    #endif //DPLUSWINRT_STUB\n}\n\nvoid DPWinRT_SetHDREnabled(bool is_hdr_enabled)\n{\n    #ifndef DPLUSWINRT_STUB\n\n    //Send enable cursor message to all threads if the value changed\n    if (g_IsHDREnabled != is_hdr_enabled)\n    {\n        std::lock_guard<std::mutex> lock(g_ThreadsMutex);\n\n        for (const auto& thread : g_Threads)\n        {\n            ::PostThreadMessage(thread.ThreadID, WM_DPLUSWINRT_ENABLE_HDR, is_hdr_enabled, 0);\n        }\n\n        g_IsHDREnabled = is_hdr_enabled;\n    }\n    #endif //DPLUSWINRT_STUB\n}\n\nvoid DPWinRT_SetDesktopEnumerationFlags(bool ignore_wmr_screens)\n{\n    #ifndef DPLUSWINRT_STUB\n\n    //This really is just a flag that could be hard coded to true in theory, but we keep our options open down the line even if it means carrying this everywhere\n    g_DesktopEnumFlagIgnoreWMRScreens = ignore_wmr_screens;\n\n    #endif\n}\n\n#ifndef DPLUSWINRT_STUB\n\nDWORD WINAPI WinRTCaptureThreadEntry(_In_ void* Param)\n{\n    //The thread shouldn't have been created in the first place then, but exit if it really happens\n    if (!DPWinRT_IsCaptureSupported())\n    {\n        return 0;\n    }\n\n    //Keep own copy of thread data\n    DPWinRTThreadData data;\n\n    {\n        std::lock_guard<std::mutex> lock(g_ThreadsMutex);\n        data = *(DPWinRTThreadData*)Param;\n    }\n\n    // Initialize WinRT and scope the rest of the code so it's cleaned up before unloading WinRT again\n    winrt::init_apartment(winrt::apartment_type::multi_threaded);\n\n    //Catch all unhandled WinRT exceptions in release builds so we can get rid of the thread instead of crashing the entire app\n    //This assumes that doing so is alright (i.e. no process-irrecoverable exceptions occur)\n    #ifndef _DEBUG\n    try\n    #endif\n    {\n        // Create the DispatcherQueue that the compositor needs to run\n        auto controller = util::CreateDispatcherQueueControllerForCurrentThread();\n\n        // Create the capture manager\n        auto capture_manager = std::make_unique<CaptureManager>(data, g_MainThreadID);\n        capture_manager->PixelFormat( (g_IsHDREnabled) ? winrt::DirectXPixelFormat::R16G16B16A16Float : winrt::DirectXPixelFormat::B8G8R8A8UIntNormalized );\n\n        //Start capture\n        if (DPWinRT_IsCaptureFromHandleSupported())\n        {\n            if (data.SourceWindow != nullptr)\n            {\n                capture_manager->StartCaptureFromWindowHandle(data.SourceWindow);\n            }\n            else if (data.DesktopID != -2)\n            {\n                if (data.DesktopID != -1)\n                {\n                    HMONITOR monitor_handle = nullptr;\n                    GetDevmodeForDisplayID(data.DesktopID, g_DesktopEnumFlagIgnoreWMRScreens, &monitor_handle);\n\n                    if (monitor_handle != nullptr)\n                    {\n                        capture_manager->StartCaptureFromMonitorHandle(monitor_handle);\n                    }\n                    else\n                    {\n                        //Failed to get monitor handle, drop the capture\n                        for (const auto& overlay : data.Overlays)\n                        {\n                            ::PostThreadMessage(g_MainThreadID, WM_DPLUSWINRT_CAPTURE_LOST, overlay.Handle, 0);\n                            //Thread will be quit by the response to the capture lost message\n                        }\n                    }\n                }\n                else if (DPWinRT_IsCaptureFromCombinedDesktopSupported())\n                {\n                    capture_manager->StartCaptureFromMonitorHandle(nullptr);\n                }\n            }\n\n            //Set initial cursor enabled state\n            capture_manager->IsCursorEnabled(data.IsCursorEnabledInitial);\n        }\n\n        //Ideally, capabilities are checked by before creating the thread, but if not and no capture starts, there will just be an idling thread until StopCapture is called\n\n        // Message pump\n        MSG msg;\n        while (GetMessageW(&msg, nullptr, 0, 0))\n        {\n            if ((msg.message >= WM_DPLUSWINRT) && (msg.message <= 0xBFFF))\n            {\n                switch (msg.message)\n                {\n                    case WM_DPLUSWINRT_UPDATE_DATA:\n                    {\n                        //Look for thread data and update local copy\n                        std::lock_guard<std::mutex> lock(g_ThreadsMutex);\n\n                        for (const auto& thread : g_Threads)\n                        {\n                            if (thread.ThreadID == data.ThreadID)\n                            {\n                                data = thread;\n                                capture_manager->OnOverlayDataRefresh();\n                                break;\n                            }\n                        }\n\n                        ::PostThreadMessage(g_MainThreadID, WM_DPLUSWINRT_THREAD_ACK, 0, 0);\n\n                        break;\n                    }\n                    case WM_DPLUSWINRT_CAPTURE_PAUSE:\n                    {\n                        const bool do_pause = msg.lParam;\n\n                        bool is_unchanged = false;\n                        bool all_paused = true;\n                        for (DPWinRTOverlayData& overlay_data : data.Overlays)\n                        {\n                            if (overlay_data.Handle == msg.wParam)\n                            {\n                                //No change, back out\n                                if (overlay_data.IsPaused == do_pause)\n                                {\n                                    is_unchanged = true;\n                                    break;\n                                }\n\n                                overlay_data.IsPaused = do_pause;\n                            }\n\n                            if (!overlay_data.IsPaused)\n                            {\n                                all_paused = false;\n                            }\n                        }\n\n                        if (!is_unchanged)\n                        {\n                            capture_manager->PauseCapture(all_paused);\n                        }\n                        break;\n                    }\n                    case WM_DPLUSWINRT_ENABLE_CURSOR:\n                    {\n                        capture_manager->IsCursorEnabled(msg.wParam);\n                        break;\n                    }\n                    case WM_DPLUSWINRT_ENABLE_HDR:\n                    {\n                        capture_manager->PixelFormat( (msg.wParam) ? winrt::DirectXPixelFormat::R16G16B16A16Float : winrt::DirectXPixelFormat::B8G8R8A8UIntNormalized );\n                        break;\n                    }\n                    case WM_DPLUSWINRT_THREAD_QUIT:\n                    {\n                        //Clear overlays here so they won't receive any more updates before the actually thread exits\n                        data.Overlays.clear();\n                        ::PostThreadMessage(g_MainThreadID, WM_DPLUSWINRT_THREAD_ACK, 0, 0);\n\n                        ::PostQuitMessage(0);\n                        break;\n                    }\n\n                }\n            }\n            else\n            {\n                TranslateMessage(&msg);\n                DispatchMessageW(&msg);\n            }\n        }\n\n        capture_manager = nullptr;\n        controller = nullptr;\n    }\n    #ifndef _DEBUG\n\n    catch (const winrt::hresult_error& e)\n    {\n        //It's worth noting that exceptions from WinRT are not supposed to get thrown on regular errors and only things that are coding mistakes\n        //But we know things will go wrong when they can, let's be honest. What can go wrong isn't really well documented either, so if something\n        //comes up, handle it somewhat gracefully\n\n        //Send capture lost messages for all overlays\n        //Resulting StopCapture() calls will cause cleanup of the thread book-keeping, even if this target thread is already gone\n        for (const auto& overlay : data.Overlays)\n        {\n            ::PostThreadMessage(g_MainThreadID, WM_DPLUSWINRT_CAPTURE_LOST, overlay.Handle, 0);\n        }\n\n        //Send thread error message\n        ::PostThreadMessage(g_MainThreadID, WM_DPLUSWINRT_THREAD_ERROR, data.ThreadID, e.code());\n\n        //...and then get out of this thread\n    }\n\n    #endif\n\n    winrt::clear_factory_cache();\n    winrt::uninit_apartment();\n\n    return 0;\n}\n\n#endif //DPLUSWINRT_STUB"
  },
  {
    "path": "src/DesktopPlusWinRT/DesktopPlusWinRT.h",
    "content": "#pragma once\n\n//This is the part of Desktop+ using Windows Runtime functions, separated from rest of the codebase as a DLL\n//Windows SDK 10.0.19041.0 or newer is required to build this, 10.0.20348.0 or newer recommended to allow removing the capture border\n//Windows SDK 10.0.26100.0 or newer is required for Windows 11 24H2 features\n//\n//If you wish to build Desktop+ without support for the functionality provided by this library, define DPLUSWINRT_STUB for the project,\n//remove the package references and adjust the project's Windows SDK version if needed\n\n//Based on the Win32CaptureSample by Robert Mikhayelyan: https://github.com/robmikh/Win32CaptureSample\n\n//For Graphics Capture, all overlay texture handling is handed off to this library. Everything else is still handled by OutputManager as usual, however.\n\n//As a general rule, the callee of the library functions is responsible to check for support first, otherwise it may throw or crash\n//In release builds, capture thread exceptions are caught and handled as unexpected errors, trying to just stop the thread. Ideally it never comes to that, of course.\n//The library relies on delay loading CoreMessaging and D3D11 in order to support running on Windows 8, so keep that in mind if ever using a different compiler.\n\n#define WIN32_LEAN_AND_MEAN\n#define NOMINMAX\n#include <windows.h>\n\n#include \"openvr.h\"\n\n#ifdef DPLUSWINRT_EXPORTS\n    #define DPLUSWINRT_API __declspec(dllexport)\n#else\n    #define DPLUSWINRT_API __declspec(dllimport)\n#endif\n\n//Thread message IDs\n#define WM_DPLUSWINRT               WM_APP           //Base ID, to allow changing it easily later\n#define WM_DPLUSWINRT_SIZE          WM_DPLUSWINRT    //Sent to main thread on size change. wParam = overlay handle, lParam = width & height (in low/high word order, signed)\n#define WM_DPLUSWINRT_UPDATE_DATA   WM_DPLUSWINRT+1  //Sent to capture thread to update its local data\n#define WM_DPLUSWINRT_CAPTURE_PAUSE WM_DPLUSWINRT+2  //Sent to capture thread to pause/resume capture. wParam = overlay handle, lParam = pause bool\n#define WM_DPLUSWINRT_CAPTURE_LOST  WM_DPLUSWINRT+3  //Sent to main thread when capture item was closed, should call StopCapture() in response. wParam = overlay handle\n#define WM_DPLUSWINRT_ENABLE_CURSOR WM_DPLUSWINRT+4  //Sent to capture thread to change cursor enabled state, wParam = cursor enabled bool\n#define WM_DPLUSWINRT_ENABLE_HDR    WM_DPLUSWINRT+5  //Sent to capture thread to change HDR enabled state, wParam = HDR enabled bool\n#define WM_DPLUSWINRT_THREAD_QUIT   WM_DPLUSWINRT+6  //Sent to capture thread to quit when no overlays are left to capture\n#define WM_DPLUSWINRT_THREAD_ERROR  WM_DPLUSWINRT+7  //Sent to main thread when an unexpected error occured in the capture thread. wParam = thread ID, lParam = hresult\n#define WM_DPLUSWINRT_THREAD_ACK    WM_DPLUSWINRT+8  //Sent to main thread to acknowledge thread messages from StopCapture() (main thread is blocked until this is received)\n#define WM_DPLUSWINRT_FPS           WM_DPLUSWINRT+9  //Sent to main thread when fps count has changed. wParam = overlay handle, lParam = frames per second\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif\n\nDPLUSWINRT_API void DPWinRT_Init();\n\nDPLUSWINRT_API bool DPWinRT_IsCaptureSupported();                         //Build 1903 (1803 in theory, but picker is no longer supported by this library)\nDPLUSWINRT_API bool DPWinRT_IsCaptureFromHandleSupported();               //Build 1903\nDPLUSWINRT_API bool DPWinRT_IsCaptureFromCombinedDesktopSupported();      //Build 2004\nDPLUSWINRT_API bool DPWinRT_IsCaptureCursorEnabledPropertySupported();    //Build 2004\nDPLUSWINRT_API bool DPWinRT_IsBorderRequiredPropertySupported();          //Windows 11\nDPLUSWINRT_API bool DPWinRT_IsIncludeSecondaryWindowsPropertySupported(); //Windows 11 24H2\nDPLUSWINRT_API bool DPWinRT_IsMinUpdateIntervalPropertySupported();       //Windows 11 24H2\n\nDPLUSWINRT_API bool DPWinRT_StartCaptureFromHWND(vr::VROverlayHandle_t overlay_handle, HWND handle);\nDPLUSWINRT_API bool DPWinRT_StartCaptureFromDesktop(vr::VROverlayHandle_t overlay_handle, int desktop_id); //-1 is combined desktop, as usual\nDPLUSWINRT_API bool DPWinRT_StartCaptureFromOverlay(vr::VROverlayHandle_t overlay_handle, vr::VROverlayHandle_t overlay_handle_source);\nDPLUSWINRT_API bool DPWinRT_PauseCapture(vr::VROverlayHandle_t overlay_handle, bool pause);\nDPLUSWINRT_API bool DPWinRT_StopCapture(vr::VROverlayHandle_t overlay_handle);\n\nDPLUSWINRT_API bool DPWinRT_SetOverlayUpdateLimitDelay(vr::VROverlayHandle_t overlay_handle, LONGLONG delay_quadpart);\nDPLUSWINRT_API bool DPWinRT_SetOverlayOverUnder3D(vr::VROverlayHandle_t overlay_handle, bool is_over_under_3D, int crop_x, int crop_y, int crop_width, int crop_height);\nDPLUSWINRT_API void DPWinRT_SetCaptureCursorEnabled(bool is_cursor_enabled);\nDPLUSWINRT_API void DPWinRT_SetHDREnabled(bool is_hdr_enabled);\nDPLUSWINRT_API void DPWinRT_SetDesktopEnumerationFlags(bool ignore_wmr_screens);\n\n\n#ifdef __cplusplus\n}\n#endif"
  },
  {
    "path": "src/DesktopPlusWinRT/DesktopPlusWinRT.vcxproj",
    "content": "﻿<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<Project DefaultTargets=\"Build\" ToolsVersion=\"15.0\" xmlns=\"http://schemas.microsoft.com/developer/msbuild/2003\">\n  <Import Project=\"..\\packages\\Microsoft.Windows.CppWinRT.2.0.240405.15\\build\\native\\Microsoft.Windows.CppWinRT.props\" Condition=\"Exists('..\\packages\\Microsoft.Windows.CppWinRT.2.0.240405.15\\build\\native\\Microsoft.Windows.CppWinRT.props')\" />\n  <ItemGroup Label=\"ProjectConfigurations\">\n    <ProjectConfiguration Include=\"Debug|x64\">\n      <Configuration>Debug</Configuration>\n      <Platform>x64</Platform>\n    </ProjectConfiguration>\n    <ProjectConfiguration Include=\"Release|x64\">\n      <Configuration>Release</Configuration>\n      <Platform>x64</Platform>\n    </ProjectConfiguration>\n  </ItemGroup>\n  <PropertyGroup Label=\"Globals\">\n    <VCProjectVersion>15.0</VCProjectVersion>\n    <ProjectGuid>{045FFB0E-D0D4-404D-8C33-13C7074B3236}</ProjectGuid>\n    <RootNamespace>DesktopPlusWinRT</RootNamespace>\n    <ProjectName>DesktopPlusWinRT</ProjectName>\n    <WindowsTargetPlatformVersion>10.0.26100.0</WindowsTargetPlatformVersion>\n  </PropertyGroup>\n  <Import Project=\"$(VCTargetsPath)\\Microsoft.Cpp.Default.props\" />\n  <PropertyGroup Condition=\"'$(Configuration)|$(Platform)'=='Debug|x64'\" Label=\"Configuration\">\n    <ConfigurationType>DynamicLibrary</ConfigurationType>\n    <UseDebugLibraries>true</UseDebugLibraries>\n    <PlatformToolset>v142</PlatformToolset>\n    <CharacterSet>Unicode</CharacterSet>\n  </PropertyGroup>\n  <PropertyGroup Condition=\"'$(Configuration)|$(Platform)'=='Release|x64'\" Label=\"Configuration\">\n    <ConfigurationType>DynamicLibrary</ConfigurationType>\n    <UseDebugLibraries>false</UseDebugLibraries>\n    <PlatformToolset>v142</PlatformToolset>\n    <WholeProgramOptimization>true</WholeProgramOptimization>\n    <CharacterSet>Unicode</CharacterSet>\n  </PropertyGroup>\n  <Import Project=\"$(VCTargetsPath)\\Microsoft.Cpp.props\" />\n  <ImportGroup Label=\"ExtensionSettings\">\n  </ImportGroup>\n  <ImportGroup Label=\"Shared\">\n  </ImportGroup>\n  <ImportGroup Label=\"PropertySheets\" Condition=\"'$(Configuration)|$(Platform)'=='Debug|x64'\">\n    <Import Project=\"$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props\" Condition=\"exists('$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props')\" Label=\"LocalAppDataPlatform\" />\n  </ImportGroup>\n  <ImportGroup Label=\"PropertySheets\" Condition=\"'$(Configuration)|$(Platform)'=='Release|x64'\">\n    <Import Project=\"$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props\" Condition=\"exists('$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props')\" Label=\"LocalAppDataPlatform\" />\n  </ImportGroup>\n  <PropertyGroup Label=\"UserMacros\" />\n  <PropertyGroup />\n  <ItemDefinitionGroup Condition=\"'$(Configuration)|$(Platform)'=='Debug|x64'\">\n    <ClCompile>\n      <WarningLevel>Level3</WarningLevel>\n      <Optimization>Disabled</Optimization>\n      <SDLCheck>true</SDLCheck>\n      <ConformanceMode>false</ConformanceMode>\n      <LanguageStandard>stdcpp17</LanguageStandard>\n      <AdditionalOptions>/await %(AdditionalOptions)</AdditionalOptions>\n      <RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary>\n      <PreprocessorDefinitions>DPLUSWINRT_EXPORTS;_USRDLL;_WINDOWS;_DEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>\n      <AdditionalIncludeDirectories>..\\Shared;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>\n      <MultiProcessorCompilation>true</MultiProcessorCompilation>\n    </ClCompile>\n    <Link>\n      <SubSystem>Windows</SubSystem>\n      <AdditionalDependencies>Windowscodecs.lib;dwmapi.lib;dxgi.lib;openvr_api.lib;%(AdditionalDependencies)</AdditionalDependencies>\n      <AdditionalLibraryDirectories>$(SolutionDir)Shared</AdditionalLibraryDirectories>\n      <DelayLoadDLLs>CoreMessaging.dll;D3D11.dll;%(DelayLoadDLLs)</DelayLoadDLLs>\n    </Link>\n  </ItemDefinitionGroup>\n  <ItemDefinitionGroup Condition=\"'$(Configuration)|$(Platform)'=='Release|x64'\">\n    <ClCompile>\n      <WarningLevel>Level3</WarningLevel>\n      <Optimization>Full</Optimization>\n      <FunctionLevelLinking>true</FunctionLevelLinking>\n      <IntrinsicFunctions>true</IntrinsicFunctions>\n      <SDLCheck>true</SDLCheck>\n      <ConformanceMode>false</ConformanceMode>\n      <LanguageStandard>stdcpp17</LanguageStandard>\n      <AdditionalOptions>/await %(AdditionalOptions)</AdditionalOptions>\n      <RuntimeLibrary>MultiThreaded</RuntimeLibrary>\n      <AdditionalIncludeDirectories>..\\Shared;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>\n      <PreprocessorDefinitions>DPLUSWINRT_EXPORTS;_USRDLL;_WINDOWS;%(PreprocessorDefinitions)</PreprocessorDefinitions>\n      <MultiProcessorCompilation>true</MultiProcessorCompilation>\n      <InlineFunctionExpansion>AnySuitable</InlineFunctionExpansion>\n      <FavorSizeOrSpeed>Speed</FavorSizeOrSpeed>\n      <OmitFramePointers>true</OmitFramePointers>\n    </ClCompile>\n    <Link>\n      <EnableCOMDATFolding>true</EnableCOMDATFolding>\n      <OptimizeReferences>true</OptimizeReferences>\n      <SubSystem>Windows</SubSystem>\n      <AdditionalDependencies>Windowscodecs.lib;dwmapi.lib;dxgi.lib;openvr_api.lib;%(AdditionalDependencies)</AdditionalDependencies>\n      <AdditionalLibraryDirectories>$(SolutionDir)Shared</AdditionalLibraryDirectories>\n      <DelayLoadDLLs>CoreMessaging.dll;D3D11.dll;%(DelayLoadDLLs)</DelayLoadDLLs>\n      <GenerateDebugInformation>false</GenerateDebugInformation>\n    </Link>\n  </ItemDefinitionGroup>\n  <ItemGroup>\n    <ClCompile Include=\"..\\Shared\\OpenVRExt.cpp\" />\n    <ClCompile Include=\"..\\Shared\\OUtoSBSConverter.cpp\" />\n    <ClCompile Include=\"..\\Shared\\Util.cpp\" />\n    <ClCompile Include=\"CaptureManager.cpp\" />\n    <ClCompile Include=\"DesktopPlusWinRT.cpp\" />\n    <ClCompile Include=\"OverlayCapture.cpp\" />\n  </ItemGroup>\n  <ItemGroup>\n    <ClInclude Include=\"..\\Shared\\DPRect.h\" />\n    <ClInclude Include=\"..\\Shared\\openvr.h\" />\n    <ClInclude Include=\"..\\Shared\\OpenVRExt.h\" />\n    <ClInclude Include=\"..\\Shared\\OUtoSBSConverter.h\" />\n    <ClInclude Include=\"..\\Shared\\Util.h\" />\n    <ClInclude Include=\"CaptureManager.h\" />\n    <ClInclude Include=\"CommonHeaders.h\" />\n    <ClInclude Include=\"DesktopPlusWinRT.h\" />\n    <ClInclude Include=\"resource.h\" />\n    <ClInclude Include=\"ThreadData.h\" />\n    <ClInclude Include=\"util\\capture.desktop.interop.h\" />\n    <ClInclude Include=\"util\\DesktopWindow.h\" />\n    <ClInclude Include=\"util\\direct3d11.interop.h\" />\n    <ClInclude Include=\"util\\dispatcherqueue.desktop.interop.h\" />\n    <ClInclude Include=\"OverlayCapture.h\" />\n    <ClInclude Include=\"util\\hwnd.interop.h\" />\n  </ItemGroup>\n  <ItemGroup>\n    <ResourceCompile Include=\"DesktopPlusWinRT.rc\" />\n  </ItemGroup>\n  <ItemGroup>\n    <None Include=\"packages.config\" />\n  </ItemGroup>\n  <Import Project=\"$(VCTargetsPath)\\Microsoft.Cpp.targets\" />\n  <ImportGroup Label=\"ExtensionTargets\">\n    <Import Project=\"..\\packages\\Microsoft.Windows.CppWinRT.2.0.240405.15\\build\\native\\Microsoft.Windows.CppWinRT.targets\" Condition=\"Exists('..\\packages\\Microsoft.Windows.CppWinRT.2.0.240405.15\\build\\native\\Microsoft.Windows.CppWinRT.targets')\" />\n    <Import Project=\"..\\packages\\Microsoft.Windows.ImplementationLibrary.1.0.200902.2\\build\\native\\Microsoft.Windows.ImplementationLibrary.targets\" Condition=\"Exists('..\\packages\\Microsoft.Windows.ImplementationLibrary.1.0.200902.2\\build\\native\\Microsoft.Windows.ImplementationLibrary.targets')\" />\n  </ImportGroup>\n  <Target Name=\"EnsureNuGetPackageBuildImports\" BeforeTargets=\"PrepareForBuild\">\n    <PropertyGroup>\n      <ErrorText>This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them.  For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText>\n    </PropertyGroup>\n    <Error Condition=\"!Exists('..\\packages\\Microsoft.Windows.CppWinRT.2.0.240405.15\\build\\native\\Microsoft.Windows.CppWinRT.props')\" Text=\"$([System.String]::Format('$(ErrorText)', '..\\packages\\Microsoft.Windows.CppWinRT.2.0.240405.15\\build\\native\\Microsoft.Windows.CppWinRT.props'))\" />\n    <Error Condition=\"!Exists('..\\packages\\Microsoft.Windows.CppWinRT.2.0.240405.15\\build\\native\\Microsoft.Windows.CppWinRT.targets')\" Text=\"$([System.String]::Format('$(ErrorText)', '..\\packages\\Microsoft.Windows.CppWinRT.2.0.240405.15\\build\\native\\Microsoft.Windows.CppWinRT.targets'))\" />\n    <Error Condition=\"!Exists('..\\packages\\Microsoft.Windows.ImplementationLibrary.1.0.200902.2\\build\\native\\Microsoft.Windows.ImplementationLibrary.targets')\" Text=\"$([System.String]::Format('$(ErrorText)', '..\\packages\\Microsoft.Windows.ImplementationLibrary.1.0.200902.2\\build\\native\\Microsoft.Windows.ImplementationLibrary.targets'))\" />\n  </Target>\n</Project>"
  },
  {
    "path": "src/DesktopPlusWinRT/DesktopPlusWinRT.vcxproj.filters",
    "content": "﻿<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<Project ToolsVersion=\"4.0\" xmlns=\"http://schemas.microsoft.com/developer/msbuild/2003\">\n  <ItemGroup>\n    <ClCompile Include=\"DesktopPlusWinRT.cpp\" />\n    <ClCompile Include=\"CaptureManager.cpp\" />\n    <ClCompile Include=\"..\\Shared\\Util.cpp\">\n      <Filter>Shared</Filter>\n    </ClCompile>\n    <ClCompile Include=\"..\\Shared\\OUtoSBSConverter.cpp\">\n      <Filter>Shared</Filter>\n    </ClCompile>\n    <ClCompile Include=\"OverlayCapture.cpp\" />\n    <ClCompile Include=\"..\\Shared\\OpenVRExt.cpp\">\n      <Filter>Shared</Filter>\n    </ClCompile>\n  </ItemGroup>\n  <ItemGroup>\n    <ClInclude Include=\"util\\capture.desktop.interop.h\">\n      <Filter>Util</Filter>\n    </ClInclude>\n    <ClInclude Include=\"util\\DesktopWindow.h\">\n      <Filter>Util</Filter>\n    </ClInclude>\n    <ClInclude Include=\"util\\dispatcherqueue.desktop.interop.h\">\n      <Filter>Util</Filter>\n    </ClInclude>\n    <ClInclude Include=\"util\\hwnd.interop.h\">\n      <Filter>Util</Filter>\n    </ClInclude>\n    <ClInclude Include=\"CommonHeaders.h\" />\n    <ClInclude Include=\"DesktopPlusWinRT.h\" />\n    <ClInclude Include=\"resource.h\" />\n    <ClInclude Include=\"ThreadData.h\" />\n    <ClInclude Include=\"CaptureManager.h\" />\n    <ClInclude Include=\"..\\Shared\\openvr.h\">\n      <Filter>Shared</Filter>\n    </ClInclude>\n    <ClInclude Include=\"..\\Shared\\Util.h\">\n      <Filter>Shared</Filter>\n    </ClInclude>\n    <ClInclude Include=\"util\\direct3d11.interop.h\">\n      <Filter>Util</Filter>\n    </ClInclude>\n    <ClInclude Include=\"..\\Shared\\OUtoSBSConverter.h\">\n      <Filter>Shared</Filter>\n    </ClInclude>\n    <ClInclude Include=\"..\\Shared\\DPRect.h\">\n      <Filter>Shared</Filter>\n    </ClInclude>\n    <ClInclude Include=\"OverlayCapture.h\" />\n    <ClInclude Include=\"..\\Shared\\OpenVRExt.h\">\n      <Filter>Shared</Filter>\n    </ClInclude>\n  </ItemGroup>\n  <ItemGroup>\n    <Filter Include=\"Util\">\n      <UniqueIdentifier>{bd52fd2f-19e3-47c1-afb1-969ba28603f5}</UniqueIdentifier>\n    </Filter>\n    <Filter Include=\"Shared\">\n      <UniqueIdentifier>{58894ab8-4129-462f-9ab7-503477dec788}</UniqueIdentifier>\n    </Filter>\n  </ItemGroup>\n  <ItemGroup>\n    <ResourceCompile Include=\"DesktopPlusWinRT.rc\" />\n  </ItemGroup>\n  <ItemGroup>\n    <None Include=\"packages.config\" />\n  </ItemGroup>\n</Project>"
  },
  {
    "path": "src/DesktopPlusWinRT/DesktopPlusWinRT.vcxproj.user",
    "content": "﻿<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<Project ToolsVersion=\"Current\" xmlns=\"http://schemas.microsoft.com/developer/msbuild/2003\">\n  <PropertyGroup Condition=\"'$(Configuration)|$(Platform)'=='Debug|x64'\">\n    <LocalDebuggerCommand>$(TargetDir)\\DesktopPlus.exe</LocalDebuggerCommand>\n    <DebuggerFlavor>WindowsLocalDebugger</DebuggerFlavor>\n    <LocalDebuggerWorkingDirectory>$(TargetDir)</LocalDebuggerWorkingDirectory>\n  </PropertyGroup>\n  <PropertyGroup Condition=\"'$(Configuration)|$(Platform)'=='Release|x64'\">\n    <LocalDebuggerCommand>$(TargetDir)\\DesktopPlus.exe</LocalDebuggerCommand>\n    <DebuggerFlavor>WindowsLocalDebugger</DebuggerFlavor>\n    <LocalDebuggerWorkingDirectory>$(TargetDir)</LocalDebuggerWorkingDirectory>\n  </PropertyGroup>\n</Project>"
  },
  {
    "path": "src/DesktopPlusWinRT/OverlayCapture.cpp",
    "content": "#ifndef DPLUSWINRT_STUB\n\n#include \"CommonHeaders.h\"\n#include \"OverlayCapture.h\"\n\n#include \"DesktopPlusWinRT.h\"\n#include \"Util.h\"\n#include \"OpenVRExt.h\"\n\nnamespace winrt\n{\n    using namespace Windows::Foundation;\n    using namespace Windows::System;\n    using namespace Windows::Graphics;\n    using namespace Windows::Graphics::Capture;\n    using namespace Windows::Graphics::DirectX;\n    using namespace Windows::Graphics::DirectX::Direct3D11;\n    using namespace Windows::Foundation::Numerics;\n}\n\nOverlayCapture::OverlayCapture(winrt::IDirect3DDevice const& device, winrt::GraphicsCaptureItem const& item, winrt::DirectXPixelFormat pixel_format, DWORD global_main_thread_id,\n                               const std::vector<DPWinRTOverlayData>& overlays, HWND source_window) :\n    m_Overlays(overlays),\n    m_SourceWindow(source_window)\n{\n    m_Item = item;\n    m_Device = device;\n    m_PixelFormat = pixel_format;\n    m_GlobalMainThreadID = global_main_thread_id;\n\n    auto d3d_device = GetDXGIInterfaceFromObject<ID3D11Device>(m_Device);\n    d3d_device->GetImmediateContext(m_D3DContext.put());\n\n    // Creating our frame pool with 'Create' instead of 'CreateFreeThreaded'\n    // means that the frame pool's FrameArrived event is called on the thread\n    // the frame pool was created on. This also means that the creating thread\n    // must have a DispatcherQueue. If you use this method, it's best not to do\n    // it on the UI thread. \n    m_FramePool = winrt::Direct3D11CaptureFramePool::Create(m_Device, m_PixelFormat, 2, m_Item.Size());\n    m_LastContentSize = m_Item.Size();\n    m_Session = m_FramePool.CreateCaptureSession(m_Item);\n    m_FramePool.FrameArrived({ this, &OverlayCapture::OnFrameArrived });\n\n    //Disable yellow capture border if possible (Windows SDK 10.0.20348.0 or newer + running on Windows 11)\n    #if WINDOWS_FOUNDATION_UNIVERSALAPICONTRACT_VERSION >= 0xc0000\n        if (DPWinRT_IsBorderRequiredPropertySupported())\n        {\n            //Request access... except it doesn't appear to prompt the user at all and just returns AppCapabilityAccessStatus_Allowed straight away when supported\n            //Still need to do it though\n            winrt::GraphicsCaptureAccess::RequestAccessAsync(winrt::GraphicsCaptureAccessKind::Borderless).get();\n            m_Session.IsBorderRequired(false);\n        }\n    #endif\n\n    //Include secondary windows if possible (Windows SDK 10.0.26100.0 or newer + running on Windows 11 24H2)\n    #if WINDOWS_FOUNDATION_UNIVERSALAPICONTRACT_VERSION >= 0x130000\n        if (DPWinRT_IsIncludeSecondaryWindowsPropertySupported())\n        {\n            m_Session.IncludeSecondaryWindows(true);\n        }\n    #endif\n\n    //Use native GraphicsCapture API update limiter if possible (Windows SDK 10.0.26100.0 or newer + running on Windows 11 24H2)\n    m_UseMinIntervalLimiter = DPWinRT_IsMinUpdateIntervalPropertySupported();\n\n    //Send size updates for all overlays to default them to -1 until we get the real size on the first frame update\n    for (const auto& overlay : m_Overlays)\n    {\n        ::PostThreadMessage(m_GlobalMainThreadID, WM_DPLUSWINRT_SIZE, overlay.Handle, MAKELPARAM(-1, -1));\n    }\n\n    //Init update limiter frequency (we don't init starting time until after the first frame)\n    ::QueryPerformanceFrequency(&m_UpdateLimiterFrequency);\n\n    OnOverlayDataRefresh();\n\n    WINRT_ASSERT(m_Session != nullptr);\n}\n\nvoid OverlayCapture::StartCapture()\n{\n    CheckClosed();\n    m_Session.StartCapture();\n}\n\nvoid OverlayCapture::RestartCapture()\n{\n    m_Session.Close();\n    m_FramePool.Close();\n\n    m_FramePool = nullptr;\n    m_Session = nullptr;\n\n    m_FramePool = m_FramePool.Create(m_Device, m_PixelFormat, 2, m_LastContentSize);\n    m_Session = m_FramePool.CreateCaptureSession(m_Item);\n    m_FramePool.FrameArrived({this, &OverlayCapture::OnFrameArrived});\n\n    m_Session.StartCapture();\n\n    m_CursorEnabledInternal = true;\n}\n\nvoid OverlayCapture::IsCursorEnabled(bool value)\n{\n    CheckClosed();\n    m_CursorEnabled = value;\n\n    //Only directly set it when it's either turning off or it's not a window capture (not auto-switching cursor state)\n    if ( (!m_CursorEnabled) || (m_SourceWindow == nullptr) )\n    {\n        m_Session.IsCursorCaptureEnabled(m_CursorEnabled);\n        m_CursorEnabledInternal = m_CursorEnabled;\n    }\n}\n\nvoid OverlayCapture::OnOverlayDataRefresh()\n{\n    //Find the smallest update limiter delay, count Over-Under & paused overlays\n    size_t ou_count = 0;\n    size_t pause_count = 0;\n    m_UpdateLimiterDelay.QuadPart = UINT_MAX;\n\n    for (const auto& overlay : m_Overlays)\n    {\n        if (!overlay.IsPaused)\n        {\n            if (overlay.UpdateLimiterDelay.QuadPart < m_UpdateLimiterDelay.QuadPart)\n            {\n                m_UpdateLimiterDelay = overlay.UpdateLimiterDelay;\n            }\n        }\n        else\n        {\n            pause_count++;\n        }\n\n        if (overlay.IsOverUnder3D)\n        {\n            ou_count++;\n        }\n\n        //And also send size again in case a fresh overlay was added\n        if (m_InitialSizingDone)\n        {\n            ::PostThreadMessage(m_GlobalMainThreadID, WM_DPLUSWINRT_SIZE, overlay.Handle, MAKELPARAM(m_LastTextureSize.Width, m_LastTextureSize.Height));\n        }\n    }\n\n    //Apply delay right away if we're using the GraphicsCapture limiter\n    #if WINDOWS_FOUNDATION_UNIVERSALAPICONTRACT_VERSION >= 0x130000\n        if (m_UseMinIntervalLimiter)\n        {\n            m_Session.MinUpdateInterval(std::chrono::microseconds(m_UpdateLimiterDelay.QuadPart));\n        }\n    #endif\n\n    //Adjust OUConverter cache size as needed\n    m_OUConverters.resize(ou_count);\n\n    //Make sure the shared textures are set up again on the next update\n    m_OverlaySharedTextureSetupsNeeded = 2;\n\n    //Pause/unpause capture if all overlays are set to be paused\n    m_Paused = (pause_count == m_Overlays.size()); //Don't call PauseCapture() since that calls this function\n}\n\nvoid OverlayCapture::Close()\n{\n    auto expected = false;\n    if (m_Closed.compare_exchange_strong(expected, true))\n    {\n        m_Session.Close();\n\n        //Wait for GraphicsCapture.dll thread to finish up\n        //\n        //When multiple captures are active and one stops, a fail-fast crash can occur sometimes.\n        //Always in internal frame pool cleanup code, as if there was a race condition somewhere...\n        //This may be a bug in Graphics Capture and seems only to happen on Windows 10 1809\n        //Waiting it out works and this thread is only cleaning up so it doesn't really matter if we sleep a bit before doing so... eh\n        Sleep(500); //A few ms is actually enough, but do 500 just to be safe\n\n        m_FramePool.Close();\n\n        m_FramePool = nullptr;\n        m_Session   = nullptr;\n        m_Item      = nullptr;\n    }\n}\n\nvoid OverlayCapture::OnFrameArrived(winrt::Direct3D11CaptureFramePool const& sender, winrt::IInspectable const&)\n{\n    auto frame = sender.TryGetNextFrame();\n\n    if ( (m_Paused) || (frame == nullptr) )\n        return;\n\n    //Update limiter/skipper\n    bool update_limiter_active = ((!m_UseMinIntervalLimiter) && (m_UpdateLimiterDelay.QuadPart != 0));\n\n    if (update_limiter_active)\n    {\n        LARGE_INTEGER UpdateLimiterEndingTime, UpdateLimiterElapsedMicroseconds;\n\n        QueryPerformanceCounter(&UpdateLimiterEndingTime);\n        UpdateLimiterElapsedMicroseconds.QuadPart = UpdateLimiterEndingTime.QuadPart - m_UpdateLimiterStartingTime.QuadPart;\n\n        UpdateLimiterElapsedMicroseconds.QuadPart *= 1000000;\n        UpdateLimiterElapsedMicroseconds.QuadPart /= m_UpdateLimiterFrequency.QuadPart;\n\n        if (UpdateLimiterElapsedMicroseconds.QuadPart < m_UpdateLimiterDelay.QuadPart)\n            return; //Skip frame\n    }\n\n    bool recreate_frame_pool = false;\n\n    //Scope surface texture to release it earlier\n    {\n        auto surface_texture = GetDXGIInterfaceFromObject<ID3D11Texture2D>(frame.Surface());\n        auto const frame_content_size = frame.ContentSize();\n        auto d3d_device = GetDXGIInterfaceFromObject<ID3D11Device>(m_Device);\n\n        //Recreate frame pool with new content size (not current texture size) if needed\n        if ((frame_content_size.Width != m_LastContentSize.Width) || (frame_content_size.Height != m_LastContentSize.Height))\n        {\n            m_LastContentSize = frame_content_size;\n            recreate_frame_pool = true; //Recreate frame pool after we're done with the frame\n        }\n\n        //Direct3D11CaptureFrame::ContentSize isn't always matching the size of the texture (lagging behind if content resized but frame pool hasn't yet)\n        //We'll grab the values from the texture itself instead.\n        D3D11_TEXTURE2D_DESC texture_desc;\n        surface_texture->GetDesc(&texture_desc);\n\n        //Check if size of the frame texture changed\n        if ((texture_desc.Width != m_LastTextureSize.Width) || (texture_desc.Height != m_LastTextureSize.Height))\n        {\n            m_LastTextureSize.Width  = texture_desc.Width;\n            m_LastTextureSize.Height = texture_desc.Height;\n\n            //Send overlay size updates\n            //If the initial sizing has not been done yet, wait until there's no frame pool recreation pending before setting it\n            //We do this because windows with native decorations are initially just reported with the client size.\n            //The real size follows on the next frame after having resized the frame pool\n            //This is necessary to not trip up adaptive overlay sizing\n            if ((m_InitialSizingDone) || (!recreate_frame_pool))\n            {\n                for (const auto& overlay : m_Overlays)\n                {\n                    ::PostThreadMessage(m_GlobalMainThreadID, WM_DPLUSWINRT_SIZE, overlay.Handle, MAKELPARAM(texture_desc.Width, texture_desc.Height));\n                }\n\n                m_InitialSizingDone = true;\n            }\n\n            ++m_OverlaySharedTextureSetupsNeeded;\n        }\n\n        //Set overlay textures\n        vr::Texture_t vrtex = {};\n        vrtex.eType = vr::TextureType_DirectX;\n        vrtex.eColorSpace = (m_PixelFormat == winrt::DirectXPixelFormat::R16G16B16A16Float) ? vr::ColorSpace_Linear : vr::ColorSpace_Gamma;\n        vrtex.handle = surface_texture.get();\n\n        size_t ou_count = 0;\n        vr::VROverlayHandle_t ovrl_shared_source = vr::k_ulOverlayHandleInvalid;\n        for (const auto& overlay : m_Overlays)\n        {\n            if (overlay.IsOverUnder3D)\n            {\n                if (ou_count < m_OUConverters.size())\n                {\n                    HRESULT hr = m_OUConverters[ou_count].Convert(d3d_device.get(), m_D3DContext.get(), nullptr, nullptr, surface_texture.get(), texture_desc.Width, texture_desc.Height,\n                                                                  overlay.OU3D_crop_x, overlay.OU3D_crop_y, overlay.OU3D_crop_width, overlay.OU3D_crop_height);\n\n                    if (hr == S_OK)\n                    {\n                        vr::Texture_t vrtex_ou = vrtex;\n                        vrtex_ou.handle = m_OUConverters[ou_count].GetTexture();\n\n                        vr::VROverlayEx()->SetOverlayTextureEx(overlay.Handle, &vrtex_ou, m_OUConverters[ou_count].GetTextureSizeSBS());\n                    }\n\n                    ou_count++;\n                }\n            }\n            else if (ovrl_shared_source == vr::k_ulOverlayHandleInvalid) //For the first non-OU3D overlay, set the texture as normal\n            {\n                bool is_shared_texture_setup_needed = false;\n                vr::VROverlayEx()->SetOverlayTextureEx(overlay.Handle, &vrtex, {(int)texture_desc.Width, (int)texture_desc.Height}, &is_shared_texture_setup_needed);\n                ovrl_shared_source = overlay.Handle;\n\n                if (is_shared_texture_setup_needed)\n                {\n                    ++m_OverlaySharedTextureSetupsNeeded;\n                }\n            }\n            else if (m_OverlaySharedTextureSetupsNeeded > 0) //For all others, set it shared from the normal overlay if an update is needed\n            {\n                vr::VROverlayEx()->SetSharedOverlayTexture(ovrl_shared_source, overlay.Handle, surface_texture.get());\n            }\n        }\n    }\n\n    //Release frame early\n    frame = nullptr;\n\n    //Due to a bug in Graphics Capture, the capture itself can get offset permanently on the texture when window borders change\n    //We combat this by checking if the texture size matches the DWM frame bounds and schedule a restart of the capture when that's case\n    //There are sometimes a few false positives on size change, but nothing terrible\n    if ( (m_RestartPending) && (m_OverlaySharedTextureSetupsNeeded == 0) )\n    {\n        RestartCapture();\n        m_RestartPending = false;\n        recreate_frame_pool = false;\n    }\n\n    //As noted in OverlayCapture::Close(), there's a bug with closing the capture on 1809\n    //Except sleeping doesn't help when restarting the capture, so we don't even try on versions older than 1903 (where capture from handle was added)\n    if ( (DPWinRT_IsCaptureFromHandleSupported()) && (m_SourceWindow != nullptr) && (m_OverlaySharedTextureSetupsNeeded == 0) )\n    {\n        RECT window_rect = {0};\n        if (::DwmGetWindowAttribute(m_SourceWindow, DWMWA_EXTENDED_FRAME_BOUNDS, &window_rect, sizeof(window_rect)) == S_OK)\n        {\n            int dwm_w = window_rect.right  - window_rect.left;\n            int dwm_h = window_rect.bottom - window_rect.top;\n\n            if ((m_LastTextureSize.Width != dwm_w) || (m_LastTextureSize.Height != dwm_h))\n            {\n                m_RestartPending = true;\n            }\n        }\n    }\n\n    if (m_OverlaySharedTextureSetupsNeeded > 0)\n    {\n        m_OverlaySharedTextureSetupsNeeded--;\n    }\n\n    //Hide cursor from capture if the window is not in front as it just adds more confusion when it's there\n    if ( (m_CursorEnabled) && (m_SourceWindow != nullptr) && (DPWinRT_IsCaptureCursorEnabledPropertySupported()) )\n    {\n        bool should_enable_cursor = (m_SourceWindow == ::GetForegroundWindow());\n\n        if (m_CursorEnabledInternal != should_enable_cursor)\n        {\n            m_Session.IsCursorCaptureEnabled(should_enable_cursor);\n            m_CursorEnabledInternal = should_enable_cursor;\n        }\n    }\n\n    //Recreate frame pool if it was scheduled earlier\n    if (recreate_frame_pool)\n    {\n        m_FramePool.Recreate(m_Device, m_PixelFormat, 2, m_LastContentSize);\n        ++m_OverlaySharedTextureSetupsNeeded;\n    }\n\n    //Frame counter\n    m_FrameCount++;\n    if (::GetTickCount64() >= m_FrameCountStartTick + 1000)\n    {\n        //A second has passed, send fps messages and reset the value\n        if (m_FrameCount != m_FrameCountLast)\n        {\n            m_FrameCountLast = m_FrameCount;\n\n            for (const auto& overlay : m_Overlays)\n            {\n                ::PostThreadMessage(m_GlobalMainThreadID, WM_DPLUSWINRT_FPS, overlay.Handle, m_FrameCount);\n            }\n        }\n\n        m_FrameCountStartTick = ::GetTickCount64();\n        m_FrameCount = 0;\n    }\n\n    //Set frame limiter starting time after we're done with everything\n    if (update_limiter_active)\n    {\n        ::QueryPerformanceCounter(&m_UpdateLimiterStartingTime);\n    }\n}\n\n#endif //DPLUSWINRT_STUB\n"
  },
  {
    "path": "src/DesktopPlusWinRT/OverlayCapture.h",
    "content": "#pragma once\n\n#include <mutex>\n\n#include \"ThreadData.h\"\n#include \"OUtoSBSConverter.h\"\n\nclass OverlayCapture\n{\npublic:\n    OverlayCapture(winrt::Windows::Graphics::DirectX::Direct3D11::IDirect3DDevice const& device, winrt::Windows::Graphics::Capture::GraphicsCaptureItem const& item,\n                  winrt::Windows::Graphics::DirectX::DirectXPixelFormat pixel_format, DWORD global_main_thread_id, const std::vector<DPWinRTOverlayData>& overlays, HWND source_window);\n    ~OverlayCapture() { Close(); }\n\n    void StartCapture();\n    void RestartCapture();\n\n    bool IsCursorEnabled()                                               { return m_CursorEnabled; }\n    void IsCursorEnabled(bool value);\n    winrt::Windows::Graphics::Capture::GraphicsCaptureItem CaptureItem() { return m_Item; }\n\n    void PauseCapture(bool pause)  { m_Paused = pause; OnOverlayDataRefresh(); }\n    bool IsPaused()                { return m_Paused; }\n\n    void OnOverlayDataRefresh();\n\n    void Close();\n\nprivate:\n    void OnFrameArrived(winrt::Windows::Graphics::Capture::Direct3D11CaptureFramePool const& sender, winrt::Windows::Foundation::IInspectable const& args);\n\n    inline void CheckClosed()\n    {\n        if (m_Closed.load() == true)\n        {\n            throw winrt::hresult_error(RO_E_CLOSED);\n        }\n    }\n\nprivate:\n    winrt::Windows::Graphics::Capture::GraphicsCaptureItem m_Item { nullptr };\n    winrt::Windows::Graphics::Capture::Direct3D11CaptureFramePool m_FramePool { nullptr };\n    winrt::Windows::Graphics::Capture::GraphicsCaptureSession m_Session { nullptr };\n    winrt::Windows::Graphics::SizeInt32 m_LastContentSize { 0, 0 };\n\n    winrt::Windows::Graphics::DirectX::Direct3D11::IDirect3DDevice m_Device { nullptr };\n    winrt::com_ptr<ID3D11DeviceContext> m_D3DContext { nullptr };\n    winrt::Windows::Graphics::DirectX::DirectXPixelFormat m_PixelFormat;\n\n    std::atomic<bool> m_Closed = false;\n\n    //Below are only accessed while on the capture's main thread\n    const std::vector<DPWinRTOverlayData>& m_Overlays;\n    const HWND m_SourceWindow;\n    DWORD m_GlobalMainThreadID = 0;\n\n    bool m_Paused = false;\n    bool m_CursorEnabled = true;            //Public cursor enabled state. False is always off, but true may be overridden by m_CursorEnabledInternal\n    bool m_CursorEnabledInternal = true;    //Internal cursor enabled state, which may override m_CursorEnabled during window capture\n    int m_OverlaySharedTextureSetupsNeeded = 2;\n\n    bool m_InitialSizingDone = false;\n    winrt::Windows::Graphics::SizeInt32 m_LastTextureSize { 0, 0 };\n    bool m_RestartPending = false;\n\n    bool m_UseMinIntervalLimiter = false;   //True if MinUpdateInterval is being used instead of our own limiter\n    LARGE_INTEGER m_UpdateLimiterStartingTime = {INT_MAX, INT_MAX}; //Init to high value so the first frame is never falls below the minimum interval\n    LARGE_INTEGER m_UpdateLimiterFrequency = {0, 0};\n    LARGE_INTEGER m_UpdateLimiterDelay = {0, 0};\n\n    int m_FrameCount = 0;\n    int m_FrameCountLast = -1;\n    ULONGLONG m_FrameCountStartTick = 0;\n\n    std::vector<OUtoSBSConverter> m_OUConverters; //Rarely used, so the cache is kept here instead of directly as part of the overlay data\n};"
  },
  {
    "path": "src/DesktopPlusWinRT/ThreadData.h",
    "content": "#pragma once\n\n#define WIN32_LEAN_AND_MEAN\n#define NOMINMAX\n#include <windows.h>\n\n#include <vector>\n#include \"openvr.h\"\n\nstruct DPWinRTOverlayData\n{\n    vr::VROverlayHandle_t Handle = vr::k_ulOverlayHandleInvalid;\n    bool IsPaused = false;\n    LARGE_INTEGER UpdateLimiterDelay = {0};\n    bool IsOverUnder3D = false;\n    int OU3D_crop_x = 0;\n    int OU3D_crop_y = 0;\n    int OU3D_crop_width  = 1;\n    int OU3D_crop_height = 1;\n};\n\nstruct DPWinRTThreadData\n{\n    HANDLE ThreadHandle = nullptr;\n    DWORD ThreadID = 0;\n    std::vector<DPWinRTOverlayData> Overlays;\n    HWND SourceWindow = nullptr;\n    int DesktopID = -2;\n    bool IsCursorEnabledInitial = true;\n};"
  },
  {
    "path": "src/DesktopPlusWinRT/packages.config",
    "content": "﻿<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<packages>\n  <package id=\"Microsoft.Windows.CppWinRT\" version=\"2.0.240405.15\" targetFramework=\"native\" />\n  <package id=\"Microsoft.Windows.ImplementationLibrary\" version=\"1.0.200902.2\" targetFramework=\"native\" />\n</packages>"
  },
  {
    "path": "src/DesktopPlusWinRT/resource.h",
    "content": "//{{NO_DEPENDENCIES}}\n// Microsoft Visual C++ generated include file.\n// Used by DesktopPlusWinRT.rc\n\n// Next default values for new objects\n// \n#ifdef APSTUDIO_INVOKED\n#ifndef APSTUDIO_READONLY_SYMBOLS\n#define _APS_NEXT_RESOURCE_VALUE        101\n#define _APS_NEXT_COMMAND_VALUE         40001\n#define _APS_NEXT_CONTROL_VALUE         1001\n#define _APS_NEXT_SYMED_VALUE           101\n#endif\n#endif\n"
  },
  {
    "path": "src/DesktopPlusWinRT/util/DesktopWindow.h",
    "content": "#pragma once\n#include \"hwnd.interop.h\"\n\nnamespace util::desktop\n{\n    // adapted from https://gist.github.com/kennykerr/64e0248323267b9b158acd26b51b3c8b\n    template <typename T>\n    struct DesktopWindow\n    {\n        using base_type = DesktopWindow<T>;\n        HWND m_window = nullptr;\n\n        static T* GetThisFromHandle(HWND const window) noexcept\n        {\n            return reinterpret_cast<T*>(GetWindowLongPtr(window, GWLP_USERDATA));\n        }\n\n        static LRESULT __stdcall WndProc(HWND const window, UINT const message, WPARAM const wparam, LPARAM const lparam) noexcept\n        {\n            WINRT_ASSERT(window);\n\n            if (WM_NCCREATE == message)\n            {\n                auto cs = reinterpret_cast<CREATESTRUCT*>(lparam);\n                T* that = static_cast<T*>(cs->lpCreateParams);\n                WINRT_ASSERT(that);\n                WINRT_ASSERT(!that->m_window);\n                that->m_window = window;\n                SetWindowLongPtr(window, GWLP_USERDATA, reinterpret_cast<LONG_PTR>(that));\n            }\n            else if (T* that = GetThisFromHandle(window))\n            {\n                return that->MessageHandler(message, wparam, lparam);\n            }\n\n            return DefWindowProc(window, message, wparam, lparam);\n        }\n\n        LRESULT MessageHandler(UINT const message, WPARAM const wparam, LPARAM const lparam) noexcept\n        {\n            if (WM_DESTROY == message)\n            {\n                PostQuitMessage(0);\n                return 0;\n            }\n\n            return DefWindowProc(m_window, message, wparam, lparam);\n        }\n\n        void InitializeObjectWithWindowHandle(winrt::Windows::Foundation::IUnknown const& object)\n        {\n            auto initializer = object.as<IInitializeWithWindow>();\n            winrt::check_hresult(initializer->Initialize(m_window));\n        }\n    };\n}\n\n"
  },
  {
    "path": "src/DesktopPlusWinRT/util/capture.desktop.interop.h",
    "content": "#pragma once\n#include <winrt/Windows.Graphics.Capture.h>\n#include <windows.graphics.capture.interop.h>\n#include <windows.graphics.capture.h>\n\nnamespace util\n{\n    inline auto CreateCaptureItemForWindow(HWND hwnd)\n    {\n        auto interop_factory = winrt::get_activation_factory<winrt::Windows::Graphics::Capture::GraphicsCaptureItem, IGraphicsCaptureItemInterop>();\n        winrt::Windows::Graphics::Capture::GraphicsCaptureItem item = { nullptr };\n        winrt::check_hresult(interop_factory->CreateForWindow(hwnd, winrt::guid_of<ABI::Windows::Graphics::Capture::IGraphicsCaptureItem>(), winrt::put_abi(item)));\n        return item;\n    }\n\n    inline auto CreateCaptureItemForMonitor(HMONITOR hmon)\n    {\n        auto interop_factory = winrt::get_activation_factory<winrt::Windows::Graphics::Capture::GraphicsCaptureItem, IGraphicsCaptureItemInterop>();\n        winrt::Windows::Graphics::Capture::GraphicsCaptureItem item = { nullptr };\n        winrt::check_hresult(interop_factory->CreateForMonitor(hmon, winrt::guid_of<ABI::Windows::Graphics::Capture::IGraphicsCaptureItem>(), winrt::put_abi(item)));\n        return item;\n    }\n}\n"
  },
  {
    "path": "src/DesktopPlusWinRT/util/direct3d11.interop.h",
    "content": "#pragma once\n#include <winrt/windows.graphics.directx.direct3d11.h>\n\nextern \"C\"\n{\n    HRESULT __stdcall CreateDirect3D11DeviceFromDXGIDevice(::IDXGIDevice* dxgiDevice,\n        ::IInspectable** graphicsDevice);\n\n    HRESULT __stdcall CreateDirect3D11SurfaceFromDXGISurface(::IDXGISurface* dgxiSurface,\n        ::IInspectable** graphicsSurface);\n}\n\nstruct __declspec(uuid(\"A9B3D012-3DF2-4EE3-B8D1-8695F457D3C1\"))\n    IDirect3DDxgiInterfaceAccess : ::IUnknown\n{\n    virtual HRESULT __stdcall GetInterface(GUID const& id, void** object) = 0;\n};\n\ninline auto CreateDirect3DDevice(IDXGIDevice* dxgi_device)\n{\n    winrt::com_ptr<::IInspectable> d3d_device;\n    winrt::check_hresult(CreateDirect3D11DeviceFromDXGIDevice(dxgi_device, d3d_device.put()));\n    return d3d_device.as<winrt::Windows::Graphics::DirectX::Direct3D11::IDirect3DDevice>();\n}\n\ninline auto CreateDirect3DSurface(IDXGISurface* dxgi_surface)\n{\n    winrt::com_ptr<::IInspectable> d3d_surface;\n    winrt::check_hresult(CreateDirect3D11SurfaceFromDXGISurface(dxgi_surface, d3d_surface.put()));\n    return d3d_surface.as<winrt::Windows::Graphics::DirectX::Direct3D11::IDirect3DSurface>();\n}\n\ntemplate <typename T>\nauto GetDXGIInterfaceFromObject(winrt::Windows::Foundation::IInspectable const& object)\n{\n    auto access = object.as<IDirect3DDxgiInterfaceAccess>();\n    winrt::com_ptr<T> result;\n    winrt::check_hresult(access->GetInterface(winrt::guid_of<T>(), result.put_void()));\n    return result;\n}"
  },
  {
    "path": "src/DesktopPlusWinRT/util/dispatcherqueue.desktop.interop.h",
    "content": "#pragma once\n#include <winrt/Windows.System.h>\n#include <dispatcherqueue.h>\n\nnamespace util::desktop\n{\n    inline auto CreateDispatcherQueueControllerForCurrentThread()\n    {\n        namespace abi = ABI::Windows::System;\n\n        DispatcherQueueOptions options\n        {\n            sizeof(DispatcherQueueOptions),\n            DQTYPE_THREAD_CURRENT,\n            DQTAT_COM_NONE\n        };\n\n        winrt::Windows::System::DispatcherQueueController controller{ nullptr };\n        winrt::check_hresult(CreateDispatcherQueueController(options, reinterpret_cast<abi::IDispatcherQueueController**>(winrt::put_abi(controller))));\n        return controller;\n    }\n}"
  },
  {
    "path": "src/DesktopPlusWinRT/util/hwnd.interop.h",
    "content": "#pragma once\n#include <Unknwn.h>\n\nnamespace util::desktop\n{\n    // Taken from shobjidl_core.h\n    struct __declspec(uuid(\"3E68D4BD-7135-4D10-8018-9FB6D9F33FA1\"))\n        IInitializeWithWindow : ::IUnknown\n    {\n        virtual HRESULT __stdcall Initialize(HWND hwnd) = 0;\n    };\n}\n"
  },
  {
    "path": "src/Shared/Actions.cpp",
    "content": "#include \"Actions.h\"\n\n#include <sstream>\n#include <random>\n#include <ctime>\n\n#ifndef NOMINMAX\n    #define NOMINMAX\n#endif\n#include <windowsx.h>\n#include <ShlDisp.h>\n\n#include \"ConfigManager.h\"\n#include \"InterprocessMessaging.h\"\n#include \"OverlayManager.h\"\n#include \"Util.h\"\n#include \"Logging.h\"\n#include \"Ini.h\"\n\n#include \"DPBrowserAPIClient.h\"\n\n#ifndef DPLUS_UI\n    #include \"OutputManager.h\"\n    #include \"WindowManager.h\"\n#endif\n\n#ifdef DPLUS_UI\n    #include \"ImGuiExt.h\"\n#endif\n\n/*\nCommand type data\n-\ncommand_key: UIntID = keycode, UIntArg = ToggleKeys bool\ncommand_mouse_pos: UIntID = X & Y (in low/high word order, signed)\ncommand_string: StrMain = string\ncommand_launch_app: StrMain = path, StrArg = arguments\ncommand_show_keyboard: UIntArg = CommandToggleArg\ncommand_crop_active_window: no data\ncommand_show_overlay: StrMain = tags string, UIntID = UseTargetTags bool & UndoOnRelease bool (in low/high word order), UIntArg = CommandToggleArg\ncommand_switch_task: no data\ncommand_load_overlay_profile: StrMain = profile name (empty = default), UIntID = ClearExisting bool\n*/\n\nconst char* ActionCommand::s_CommandTypeNames[ActionCommand::command_MAX] = \n{\n    \"None\",\n    \"Key\",\n    \"MousePos\",\n    \"String\",\n    \"LaunchApp\",\n    \"ShowKeyboard\",\n    \"CropActiveWindow\",\n    \"ShowOverlay\",\n    \"SwitchTask\",\n    \"LoadOverlayProfile\",\n    \"Unknown\"\n};\n\nstd::string ActionCommand::Serialize() const\n{\n    std::stringstream ss(std::ios::out | std::ios::binary);\n    size_t str_size = 0;\n\n    ss.write((const char*)&Type,     sizeof(Type));\n    ss.write((const char*)&UIntID,   sizeof(UIntID));\n    ss.write((const char*)&UIntArg,  sizeof(UIntArg));\n\n    str_size = StrMain.size();\n    ss.write((const char*)&str_size, sizeof(str_size));\n    ss.write(StrMain.data(),         str_size);\n\n    str_size = StrArg.size();\n    ss.write((const char*)&str_size, sizeof(str_size));\n    ss.write(StrArg.data(),          str_size);\n\n    return ss.str();\n}\n\nvoid ActionCommand::Deserialize(const std::string& str)\n{\n    std::stringstream ss(str, std::ios::in | std::ios::binary);\n\n    ActionCommand new_command;\n    size_t str_length = 0;\n\n    ss.read((char*)&new_command.Type,    sizeof(Type));\n    ss.read((char*)&new_command.UIntID,  sizeof(UIntID));\n    ss.read((char*)&new_command.UIntArg, sizeof(UIntArg));\n\n    ss.read((char*)&str_length, sizeof(str_length));\n    str_length = std::min(str_length, (size_t)4096);    //Arbitrary size limit to avoid large allocations on garbage data\n    new_command.StrMain.resize(str_length);\n    ss.read(&new_command.StrMain[0], str_length);\n\n    ss.read((char*)&str_length, sizeof(str_length));\n    str_length = std::min(str_length, (size_t)4096);\n    new_command.StrArg.resize(str_length);\n    ss.read(&new_command.StrArg[0], str_length);\n\n    //Replace all data with the read command if there were no stream errors\n    if (ss.good())\n        *this = new_command;\n}\n\n\nstd::string Action::Serialize() const\n{\n    std::stringstream ss(std::ios::out | std::ios::binary);\n    size_t str_size = 0;\n\n    ss.write((const char*)&UID, sizeof(UID));\n\n    str_size = Name.size();\n    ss.write((const char*)&str_size, sizeof(str_size));\n    ss.write(Name.data(),            str_size);\n\n    str_size = Label.size();\n    ss.write((const char*)&str_size, sizeof(str_size));\n    ss.write(Label.data(),           str_size);\n\n    size_t command_count = Commands.size();\n    ss.write((const char*)&command_count, sizeof(command_count));\n\n    for (const auto& command : Commands)\n    {\n        std::string command_serialized = command.Serialize();\n\n        str_size = command_serialized.size();\n        ss.write((const char*)&str_size,    sizeof(str_size));\n        ss.write(command_serialized.data(), str_size);\n    }\n\n    ss.write((const char*)&TargetUseTags, sizeof(TargetUseTags));\n\n    //Tags are still preserved even when not used\n    str_size = TargetTags.size();\n    ss.write((const char*)&str_size, sizeof(str_size));\n    ss.write(TargetTags.data(),      str_size);\n\n    str_size = IconFilename.size();\n    ss.write((const char*)&str_size, sizeof(str_size));\n    ss.write(IconFilename.data(),    str_size);\n\n    return ss.str();\n}\n\nvoid Action::Deserialize(const std::string& str)\n{\n    std::stringstream ss(str, std::ios::in | std::ios::binary);\n\n    Action new_action;\n    size_t str_length = 0;\n\n    ss.read((char*)&new_action.UID,    sizeof(UID));\n\n    ss.read((char*)&str_length, sizeof(str_length));\n    str_length = std::min(str_length, str.length());\n    new_action.Name.resize(str_length);\n    ss.read(&new_action.Name[0], str_length);\n\n    ss.read((char*)&str_length, sizeof(str_length));\n    str_length = std::min(str_length, str.length());\n    new_action.Label.resize(str_length);\n    ss.read(&new_action.Label[0], str_length);\n\n    size_t command_count = 0;\n    ss.read((char*)&command_count,     sizeof(command_count));\n\n    for (size_t i = 0; i < command_count; ++i)\n    {\n        ss.read((char*)&str_length, sizeof(str_length));\n        str_length = std::min(str_length, str.length());\n        std::string command_serialized(str_length, '\\0');\n        ss.read(&command_serialized[0], str_length);\n\n        ActionCommand new_command;\n        new_command.Deserialize(command_serialized);\n\n        new_action.Commands.push_back(new_command);\n    }\n\n    ss.read((char*)&new_action.TargetUseTags, sizeof(TargetUseTags));\n\n    ss.read((char*)&str_length, sizeof(str_length));\n    str_length = std::min(str_length, str.length());\n    new_action.TargetTags.resize(str_length);\n    ss.read(&new_action.TargetTags[0], str_length);\n\n    ss.read((char*)&str_length, sizeof(str_length));\n    str_length = std::min(str_length, str.length());\n    new_action.IconFilename.resize(str_length);\n    ss.read(&new_action.IconFilename[0], str_length);\n\n    //Replace all data with the read action if there were no stream errors\n    if (ss.good())\n    {\n        *this = new_action;\n\n        #ifdef DPLUS_UI\n            //Check for potential translation strings\n            NameTranslationID  = ActionManager::GetTranslationIDForName(Name);\n            LabelTranslationID = ActionManager::GetTranslationIDForName(Label);\n        #endif\n    }\n}\n\nActionManager::ActionManager()\n{\n    //Set name for null action in case it does get displayed from stale references somewhere\n    m_NullAction.Name = \"tstr_ActionNone\";\n\n    #ifdef DPLUS_UI\n        m_NullAction.NameTranslationID = tstr_ActionNone;\n    #endif\n}\n\n#ifndef DPLUS_UI\n\nvoid ActionManager::DoKeyCommand(const ActionCommand& command, OverlayIDList& overlay_targets, bool down) const\n{\n    bool has_pressed_for_desktop = false;   //Only do command once for inputs that end up on the desktop\n    unsigned char keycode = command.UIntID;\n\n    if (keycode == 0)\n        return;\n\n    for (unsigned int overlay_id : overlay_targets)\n    {\n        const Overlay& overlay = OverlayManager::Get().GetOverlay(overlay_id);\n        const OverlayConfigData& data = OverlayManager::Get().GetConfigData(overlay_id);\n\n        if (data.ConfigInt[configid_int_overlay_capture_source] == ovrl_capsource_browser)\n        {\n            if (command.UIntArg == 1 /*ToggleKeys*/)\n            {\n                DPBrowserAPIClient::Get().DPBrowser_KeyboardToggleKey(overlay.GetHandle(), keycode);\n            }\n            else\n            {\n                DPBrowserAPIClient::Get().DPBrowser_KeyboardSetKeyState(overlay.GetHandle(), (down) ? dpbrowser_ipckbd_keystate_flag_key_down : (DPBrowserIPCKeyboardKeystateFlags)0, keycode);\n            }\n        }\n        else if (!has_pressed_for_desktop)\n        {\n            if (OutputManager* outmgr = OutputManager::Get())\n            {\n                InputSimulator& input_sim = outmgr->GetInputSimulator();\n\n                if (command.UIntArg == 1 /*ToggleKeys*/)\n                {\n                    input_sim.KeyboardToggleState(keycode);\n                }\n                else\n                {\n                    (down) ? input_sim.KeyboardSetDown(keycode) : input_sim.KeyboardSetUp(keycode);\n                }\n            }\n\n            has_pressed_for_desktop = true;\n        }\n    }\n}\n\nvoid ActionManager::DoMousePosCommand(const ActionCommand& command, OverlayIDList& overlay_targets) const\n{\n    LPARAM pos_lparam = command.UIntID;\n    int mouse_x = GET_X_LPARAM(pos_lparam);\n    int mouse_y = GET_Y_LPARAM(pos_lparam);\n\n    bool has_moved_for_desktop = false;   //Only do command once for inputs that end up on the desktop\n\n    for (unsigned int overlay_id : overlay_targets)\n    {\n        const Overlay& overlay = OverlayManager::Get().GetOverlay(overlay_id);\n        const OverlayConfigData& data = OverlayManager::Get().GetConfigData(overlay_id);\n\n        if (data.ConfigInt[configid_int_overlay_capture_source] == ovrl_capsource_browser)\n        {\n            DPBrowserAPIClient::Get().DPBrowser_MouseMove(overlay.GetHandle(), mouse_x, mouse_y);\n        }\n        else if (!has_moved_for_desktop)\n        {\n            if (OutputManager* outmgr = OutputManager::Get())\n            {\n                outmgr->GetInputSimulator().MouseMove(mouse_x, mouse_y);\n            }\n\n            has_moved_for_desktop = true;\n        }\n    }\n}\n\nvoid ActionManager::DoStringCommand(const ActionCommand& command, OverlayIDList& overlay_targets) const\n{\n    bool has_typed_for_desktop = false;   //Only do command once for inputs that end up on the desktop\n\n    for (unsigned int overlay_id : overlay_targets)\n    {\n        const Overlay& overlay = OverlayManager::Get().GetOverlay(overlay_id);\n        const OverlayConfigData& data = OverlayManager::Get().GetConfigData(overlay_id);\n\n        if (data.ConfigInt[configid_int_overlay_capture_source] == ovrl_capsource_browser)\n        {\n            DPBrowserAPIClient::Get().DPBrowser_KeyboardTypeString(overlay.GetHandle(), command.StrMain);\n        }\n        else if (!has_typed_for_desktop)\n        {\n            if (OutputManager* outmgr = OutputManager::Get())\n            {\n                InputSimulator& input_sim = outmgr->GetInputSimulator();\n\n                input_sim.KeyboardText(command.StrMain.c_str(), true);\n                input_sim.KeyboardTextFinish();\n            }\n\n            has_typed_for_desktop = true;\n        }\n    }\n}\n\nvoid ActionManager::DoLaunchAppCommand(const ActionCommand& command, OverlayIDList& /*overlay_targets*/) const\n{\n    const std::string& path_utf8 = command.StrMain;\n    const std::string& arg_utf8  = command.StrArg;\n\n    if (ConfigManager::GetValue(configid_bool_state_misc_elevated_mode_active))\n    {\n        HWND source_window = nullptr;\n        if (OutputManager* outmgr = OutputManager::Get())\n        {\n            source_window = OutputManager::Get()->GetWindowHandle();\n        }\n\n        IPCManager::Get().SendStringToElevatedModeProcess(ipcestrid_launch_application_path, path_utf8, source_window);\n\n        if (!arg_utf8.empty())\n        {\n            IPCManager::Get().SendStringToElevatedModeProcess(ipcestrid_launch_application_arg, arg_utf8, source_window);\n        }\n\n        IPCManager::Get().PostMessageToElevatedModeProcess(ipcmsg_elevated_action, ipceact_launch_application);\n        return;\n    }\n\n    //Convert path and arg to utf16\n    std::wstring path_wstr = WStringConvertFromUTF8(path_utf8.c_str());\n    std::wstring arg_wstr  = WStringConvertFromUTF8(arg_utf8.c_str());\n\n    if (!path_wstr.empty())\n    {\n        if (OutputManager* outmgr = OutputManager::Get())\n        {\n            outmgr->InitComIfNeeded();\n\n            ::ShellExecute(nullptr, nullptr, path_wstr.c_str(), arg_wstr.c_str(), nullptr, SW_SHOWNORMAL);\n        }\n    }\n}\n\nvoid ActionManager::DoShowKeyboardCommand(const ActionCommand& command, OverlayIDList& overlay_targets) const\n{\n    //We cannot show the keyboard for multiple overlays, so use the first target if one is provided\n    unsigned int overlay_source_id = (!overlay_targets.empty()) ? overlay_targets[0] : k_ulOverlayID_None;\n\n    if (ConfigManager::GetValue(configid_bool_state_keyboard_visible))\n    {\n        if (command.UIntArg != ActionCommand::command_arg_always_show)  //Don't do anything if it's set to always show\n        {\n            IPCManager::Get().PostMessageToUIApp(ipcmsg_action, ipcact_keyboard_show, -1);\n        }\n    }\n    else if (command.UIntArg != ActionCommand::command_arg_always_hide)\n    {\n        //Tell UI to show keyboard assigned to overlay\n        IPCManager::Get().PostMessageToUIApp(ipcmsg_action, ipcact_keyboard_show, (overlay_source_id != k_ulOverlayID_None) ? (int)overlay_source_id : -2);\n\n        //Set focused ID to source overlay ID if there is one\n        if (overlay_source_id != k_ulOverlayID_None)\n        {\n            ConfigManager::Get().SetValue(configid_int_state_overlay_focused_id, (int)overlay_source_id);\n            IPCManager::Get().PostConfigMessageToUIApp(configid_int_state_overlay_focused_id, (int)overlay_source_id);\n        }\n    }\n}\n\nvoid ActionManager::DoCropActiveWindowCommand(const ActionCommand& command, OverlayIDList& overlay_targets) const\n{\n    if (OutputManager* outmgr = OutputManager::Get())\n    {\n        for (unsigned int overlay_id : overlay_targets)\n        {\n            outmgr->CropToActiveWindowToggle(overlay_id);\n        }\n    }\n}\n\nvoid ActionManager::DoShowOverlayCommand(const ActionCommand& command, OverlayIDList& overlay_targets, bool undo) const\n{\n    if (OutputManager* outmgr = OutputManager::Get())\n    {\n        OverlayIDList overlay_targets_command;\n        ActionCommand::CommandToggleArg command_arg = (ActionCommand::CommandToggleArg)command.UIntArg;\n        const bool use_command_tags = (LOWORD(command.UIntID) == 1);\n        const bool do_undo_command  = (HIWORD(command.UIntID) == 1);\n\n        if (use_command_tags)\n        {\n            overlay_targets_command = OverlayManager::Get().FindOverlaysWithTags(command.StrMain.c_str());\n        }\n\n        if ((do_undo_command) && (undo))\n        {\n            //Swap show and hide command arguments when undoing\n            switch (command.UIntArg)\n            {\n                case ActionCommand::command_arg_always_show: command_arg = ActionCommand::command_arg_always_hide; break;\n                case ActionCommand::command_arg_always_hide: command_arg = ActionCommand::command_arg_always_show; break;\n            }\n        }\n        else if (undo)\n        {\n            return; //Don't do anything on undo call when action isn't set to undo\n        }\n\n        for (unsigned int overlay_id : (use_command_tags) ? overlay_targets_command : overlay_targets)\n        {\n            const OverlayConfigData& data = OverlayManager::Get().GetConfigData(overlay_id);\n            \n            bool is_enabled = true;\n            switch (command_arg)\n            {\n                case ActionCommand::command_arg_toggle:      is_enabled = !data.ConfigBool[configid_bool_overlay_enabled]; break;\n                case ActionCommand::command_arg_always_show: is_enabled = true;                                            break;\n                case ActionCommand::command_arg_always_hide: is_enabled = false;                                           break;\n            }\n            \n            outmgr->SetOverlayEnabled(overlay_id, is_enabled);\n        }\n    }\n}\n\nvoid ActionManager::DoSwitchTaskCommand(const ActionCommand& command, OverlayIDList& /*overlay_targets*/) const\n{\n    const bool show_window_switcher = (command.UIntID == 0);\n\n    if (show_window_switcher)\n    {\n        if (OutputManager* outmgr = OutputManager::Get())\n        {\n            outmgr->ShowWindowSwitcher();\n        }\n    }\n    else    //Focus Window\n    {\n        const bool use_strict_matching = (LOWORD(command.UIntArg) == 1);\n        const bool warp_cursor         = (HIWORD(command.UIntArg) == 1);\n\n        //exe & class names are packed into StrArg, seperated by |\n        std::string exe_name;\n        std::string class_name;\n        size_t search_pos = command.StrArg.find(\"|\");\n\n        if (search_pos != std::string::npos)\n        {\n            exe_name   = command.StrArg.substr(0, search_pos);\n            class_name = command.StrArg.substr(search_pos + 1);\n        }\n\n        HWND window_handle = WindowInfo::FindClosestWindowForTitle(command.StrMain, class_name, exe_name, WindowManager::Get().WindowListGet(), use_strict_matching);\n\n        if (window_handle != nullptr)\n        {\n            if (OutputManager* outmgr = OutputManager::Get())\n            {\n                outmgr->SwitchToWindow(window_handle, warp_cursor);\n            }\n        }\n    }\n}\n\nvoid ActionManager::DoLoadOverlayProfileCommand(const ActionCommand& command, OverlayIDList& /*overlay_targets*/) const\n{\n    const bool clear_existing = (command.UIntID == 1);\n\n    LOG_F(INFO, \"Command LoadOverlayProfile:\");\n\n    if (command.StrMain.empty())\n    {\n        ConfigManager::Get().LoadOverlayProfileDefault(true);\n\n        //Tell UI app to load the profile as well\n        IPCManager::Get().PostMessageToUIApp(ipcmsg_action, ipcact_overlay_profile_load, MAKELPARAM(ipcactv_ovrl_profile_multi, -2));\n\n        OutputManager::Get()->ResetOverlays();\n    }\n    else\n    {\n        const bool has_loaded_profile = ConfigManager::Get().LoadMultiOverlayProfileFromFile(command.StrMain + \".ini\", clear_existing);\n\n        if (has_loaded_profile)\n        {\n            //Tell UI app to load the profile as well\n            HWND window = (OutputManager::Get() != nullptr) ? OutputManager::Get()->GetWindowHandle() : nullptr;\n\n            IPCManager::Get().SendStringToUIApp(configid_str_state_profile_name_load, command.StrMain, window);\n            IPCManager::Get().PostMessageToUIApp(ipcmsg_action, ipcact_overlay_profile_load, (clear_existing) ? ipcactv_ovrl_profile_multi : ipcactv_ovrl_profile_multi_add);\n\n            OutputManager::Get()->ResetOverlays();\n        }\n    }\n}\n\n#endif //ifdef DPLUS_UI\n\n#ifdef DPLUS_UI\n\nvoid ActionManager::UpdateActionOrderListUI()\n{\n    if (m_Actions.size() == m_ActionOrderUI.size())\n        return;\n\n    //Start fresh, put everything in and sort by name (this shouldn't happen unless default config is blank)\n    if (m_ActionOrderUI.empty())\n    {\n        LOG_F(INFO, \"Action order list is empty, recreating...\");\n\n        for (const auto& action_pair : m_Actions)\n        {\n            const Action& action = action_pair.second;\n\n            m_ActionOrderUI.push_back(action.UID);\n        }\n\n        std::sort(m_ActionOrderUI.begin(), m_ActionOrderUI.end(), \n                      [this](const auto& uid_a, const auto& uid_b)\n                      { \n                          return (strcmp(GetTranslatedName(uid_a), GetTranslatedName(uid_b)) < 0); \n                      } \n                 );\n\n        return;\n    }\n\n    //Remove any actions that don't exist first\n    ValidateActionOrderList(m_ActionOrderUI);\n\n    //Look for whatever is missing and add it (not fast but this doesn't happen in normal execution)\n    LOG_F(INFO, \"Action order list is missing entries, adding missing actions...\");\n\n    for (const auto& action_pair : m_Actions)\n    {\n        const Action& action = action_pair.second;\n\n        auto it = std::find_if(m_ActionOrderUI.begin(), m_ActionOrderUI.end(), [&](const auto& uid) { return (uid == action.UID); } );\n\n        if (it == m_ActionOrderUI.end())\n        {\n            m_ActionOrderUI.push_back(action.UID);\n\n            LOG_F(INFO, \"Added action %llu to action order list\", action.UID);\n        }\n    }\n}\n\nvoid ActionManager::ValidateActionOrderList(ActionList& ui_order) const\n{\n    //Remove any actions that don't exist anymore\n    auto it = std::remove_if(ui_order.begin(), ui_order.end(), [&](const auto& uid) { return !ActionExists(uid); } );\n    ui_order.erase(it, ui_order.end());\n}\n\n#endif //DPLUS_UI\n\nbool ActionManager::LoadActionsFromFile(const char* filename)\n{\n    m_Actions.clear();\n\n    const std::string filename_str = (filename == nullptr) ? \"actions.ini\" : filename;\n    const std::wstring wpath = WStringConvertFromUTF8( std::string(ConfigManager::Get().GetApplicationPath() + filename_str).c_str() );\n    const bool existed = FileExists(wpath.c_str());\n\n    if (!existed)\n        return false;\n\n    Ini afile(wpath.c_str());\n\n    for (const auto& uid_str : afile.GetSectionList())\n    {\n        if (uid_str.empty())\n            continue;\n\n        Action action;\n        action.UID = std::strtoull(uid_str.c_str(), nullptr, 10);\n        action.Name = afile.ReadString(uid_str.c_str(), \"Name\");\n\n        action.Label = afile.ReadString(uid_str.c_str(), \"Label\");\n        StringReplaceAll(action.Label, \"\\\\n\", \"\\n\");    //Unescape newlines\n\n        #ifdef DPLUS_UI\n            action.NameTranslationID  = GetTranslationIDForName(action.Name);\n            action.LabelTranslationID = GetTranslationIDForName(action.Label);\n        #endif\n        \n        int command_count = afile.ReadInt(uid_str.c_str(), \"CommandCount\", 0);\n\n        for (int i = 0; i < command_count; ++i)\n        {\n            ActionCommand command;\n            std::string command_key = \"Command\" + std::to_string(i);\n\n            //Get command type ID from string\n            command.Type = ActionCommand::command_unknown;  //Unknown when no match found\n            std::string str_type = afile.ReadString(uid_str.c_str(), (command_key + \"Type\").c_str(), ActionCommand::s_CommandTypeNames[ActionCommand::command_none]);\n\n            for (size_t i = ActionCommand::command_none; i < ActionCommand::command_MAX; ++i)\n            {\n                if (str_type == ActionCommand::s_CommandTypeNames[i])\n                {\n                    command.Type = (ActionCommand::CommandType)i;\n                    break;\n                }\n            }\n\n            command.UIntID  = afile.ReadInt(uid_str.c_str(),    (command_key + \"UIntID\").c_str(),  0);\n            command.UIntArg = afile.ReadInt(uid_str.c_str(),    (command_key + \"UIntArg\").c_str(), 0);\n            command.StrMain = afile.ReadString(uid_str.c_str(), (command_key + \"StrMain\").c_str());\n            command.StrArg  = afile.ReadString(uid_str.c_str(), (command_key + \"StrArg\").c_str());\n\n            action.Commands.push_back(command);\n        }\n\n        action.TargetUseTags = afile.ReadBool(  uid_str.c_str(), \"TargetUseTags\", false);\n        action.TargetTags    = afile.ReadString(uid_str.c_str(), \"TargetTags\");\n        action.IconFilename  = afile.ReadString(uid_str.c_str(), \"IconFilename\");\n\n        StoreAction(action);\n    }\n\n    return existed;\n}\n\nvoid ActionManager::SaveActionsToFile()\n{\n    std::wstring wpath = WStringConvertFromUTF8( std::string(ConfigManager::Get().GetApplicationPath() + \"actions.ini\").c_str() );\n\n    //Don't write if no actions\n    if (m_Actions.empty())\n    {\n        //Delete actions file instead of leaving an empty one behind\n        if (FileExists(wpath.c_str()))\n        {\n            ::DeleteFileW(wpath.c_str());\n        }\n\n        return;\n    }\n\n    Ini afile(wpath.c_str(), true);\n\n    for (const auto& action_pair : m_Actions)\n    {\n        const Action& action = action_pair.second;\n\n        LOG_IF_F(ERROR, action_pair.first != action.UID, \"Action UID values don't match! (%llu & %llu)\", action_pair.first, action.UID);\n\n        std::string uid_str = std::to_string(action.UID);\n\n        afile.WriteString(uid_str.c_str(), \"Name\", action.Name.c_str());\n\n        std::string label_escaped = action.Label;\n        StringReplaceAll(label_escaped, \"\\n\", \"\\\\n\");   //Escape newlines so they don't break the ini layout\n        afile.WriteString(uid_str.c_str(), \"Label\", label_escaped.c_str());\n\n        afile.WriteInt(uid_str.c_str(), \"CommandCount\", (int)action.Commands.size());\n\n        int i = 0;\n        for (const auto& command : action.Commands)\n        {\n            if ((command.Type == ActionCommand::command_none) || (command.Type >= ActionCommand::command_MAX))\n                continue;\n\n            std::string command_key = \"Command\" + std::to_string(i);\n\n            afile.WriteString(uid_str.c_str(), (command_key + \"Type\").c_str(),    ActionCommand::s_CommandTypeNames[command.Type]);\n            afile.WriteInt(uid_str.c_str(),    (command_key + \"UIntID\").c_str(),  (int)command.UIntID);\n            afile.WriteInt(uid_str.c_str(),    (command_key + \"UIntArg\").c_str(), (int)command.UIntArg);\n            afile.WriteString(uid_str.c_str(), (command_key + \"StrMain\").c_str(), command.StrMain.c_str());\n            afile.WriteString(uid_str.c_str(), (command_key + \"StrArg\").c_str(),  command.StrArg.c_str());\n\n            ++i;\n        }\n\n        afile.WriteBool(  uid_str.c_str(), \"TargetUseTags\", action.TargetUseTags);\n        afile.WriteString(uid_str.c_str(), \"TargetTags\",    action.TargetTags.c_str());\n        afile.WriteString(uid_str.c_str(), \"IconFilename\",  action.IconFilename.c_str());\n    }\n\n    afile.Save();\n}\n\nvoid ActionManager::RestoreActionsFromDefault()\n{\n    LoadActionsFromFile(\"actions_default.ini\");\n    #ifdef DPLUS_UI\n        ConfigManager::Get().RestoreActionOrdersFromDefault();\n    #endif\n}\n\nconst Action& ActionManager::GetAction(ActionUID action_uid) const\n{\n    auto it = m_Actions.find(action_uid);\n\n    return (it != m_Actions.end()) ? it->second : m_NullAction;\n}\n\nbool ActionManager::ActionExists(ActionUID action_uid) const\n{\n    return (m_Actions.find(action_uid) != m_Actions.end());\n}\n\nvoid ActionManager::StoreAction(const Action& action)\n{\n    size_t action_count_prev = m_Actions.size();\n\n    m_Actions[action.UID] = action;\n\n    #ifdef DPLUS_UI\n        //Add to UI order if this added a new action\n        if (action_count_prev != m_Actions.size())\n        {\n            m_ActionOrderUI.push_back(action.UID);\n        }\n    #endif\n}\n\nvoid ActionManager::RemoveAction(ActionUID action_uid)\n{\n    m_Actions.erase(action_uid);\n\n    #ifdef DPLUS_UI\n        //Find in UI order and remove\n        auto it = std::find_if(m_ActionOrderUI.begin(), m_ActionOrderUI.end(), [&](const auto& uid) { return (uid == action_uid); } );\n\n        if (it != m_ActionOrderUI.end())\n        {\n            m_ActionOrderUI.erase(it);\n        }\n    #endif\n}\n\nvoid ActionManager::StartAction(ActionUID action_uid, unsigned int overlay_source_id) const\n{\n    #ifdef DPLUS_UI\n        IPCManager::Get().PostMessageToDashboardApp(ipcmsg_action, ipcact_action_start, action_uid);\n    #else\n        const Action& action = GetAction(action_uid);\n\n        OverlayIDList overlay_targets;\n\n        if (action.TargetUseTags)\n        {\n            overlay_targets = OverlayManager::Get().FindOverlaysWithTags(action.TargetTags.c_str());\n        }\n        else\n        {\n            //Use focused ID if there is no source ID\n            if ( (overlay_source_id == k_ulOverlayID_None) && (ConfigManager::GetValue(configid_int_state_overlay_focused_id) != -1) )\n            {\n                overlay_source_id = (unsigned int)ConfigManager::GetValue(configid_int_state_overlay_focused_id);\n            }\n\n            overlay_targets.push_back(overlay_source_id);\n        }\n\n        for (const ActionCommand& command : action.Commands)\n        {\n            switch (command.Type)\n            {\n                case ActionCommand::command_key:                  DoKeyCommand(               command, overlay_targets, true);  break;\n                case ActionCommand::command_mouse_pos:            DoMousePosCommand(          command, overlay_targets);        break;\n                case ActionCommand::command_string:               DoStringCommand(            command, overlay_targets);        break;\n                case ActionCommand::command_launch_app:           DoLaunchAppCommand(         command, overlay_targets);        break;\n                case ActionCommand::command_show_keyboard:        DoShowKeyboardCommand(      command, overlay_targets);        break;\n                case ActionCommand::command_crop_active_window:   DoCropActiveWindowCommand(  command, overlay_targets);        break;\n                case ActionCommand::command_show_overlay:         DoShowOverlayCommand(       command, overlay_targets, false); break;\n                case ActionCommand::command_switch_task:          DoSwitchTaskCommand(        command, overlay_targets);        break;\n                case ActionCommand::command_load_overlay_profile: DoLoadOverlayProfileCommand(command, overlay_targets);        break;\n                default:                                          break;\n            }\n        }\n    #endif\n}\n\nvoid ActionManager::StopAction(ActionUID action_uid, unsigned int overlay_source_id) const\n{\n    #ifdef DPLUS_UI\n        IPCManager::Get().PostMessageToDashboardApp(ipcmsg_action, ipcact_action_stop, action_uid);\n    #else\n        const Action& action = GetAction(action_uid);\n\n        OverlayIDList overlay_targets;\n\n        if (action.TargetUseTags)\n        {\n            overlay_targets = OverlayManager::Get().FindOverlaysWithTags(action.TargetTags.c_str());\n        }\n        else\n        {\n            //Use focused ID if there is no source ID\n            if ( (overlay_source_id == k_ulOverlayID_None) && (ConfigManager::GetValue(configid_int_state_overlay_focused_id) != -1) )\n            {\n                overlay_source_id = (unsigned int)ConfigManager::GetValue(configid_int_state_overlay_focused_id);\n            }\n\n            overlay_targets.push_back(overlay_source_id);\n        }\n\n        //This function only needs to release keys previously pressed by StartAction() for now, in reverse order to avoid anything depending on that\n        for (auto it = action.Commands.crbegin(); it != action.Commands.crend(); ++it)\n        {\n            const ActionCommand& command = *it;\n\n            switch (command.Type)\n            {\n                case ActionCommand::command_key:          DoKeyCommand(        command, overlay_targets, false); break;\n                case ActionCommand::command_show_overlay: DoShowOverlayCommand(command, overlay_targets, true);  break;\n                default:                                  break;\n            }\n        }\n    #endif\n}\n\nvoid ActionManager::DoAction(ActionUID action_uid, unsigned int overlay_source_id) const\n{\n    StartAction(action_uid, overlay_source_id);\n    StopAction(action_uid, overlay_source_id);\n}\n\nuint64_t ActionManager::GenerateUID() const\n{\n    //32-bit timestamp, but at least unsigned (good till 2106) and nothing really bad happens after that either\n    uint32_t timestamp = (uint32_t)std::time(nullptr);\n\n    std::random_device seed;\n    std::mt19937 generator(seed());\n    std::uniform_int_distribution<uint32_t> distribute(0, UINT_MAX);\n    \n    //Create UID out of random number and timestamp until there's no ID conflict\n    uint64_t uid = 0;\n    do\n    {\n        uint64_t rnd = distribute(generator);\n        uid = (rnd << 32) | timestamp;\n    }\n    while (ActionExists(uid)); //Very unlikely, to be fair\n\n    return uid;\n}\n\nstd::string ActionManager::ActionOrderListToString(const ActionList& action_order)\n{\n    std::stringstream ss;\n\n    for (const ActionUID uid : action_order)\n    {\n        ss << uid << ';';\n    }\n\n    return ss.str();\n}\n\nActionManager::ActionList ActionManager::ActionOrderListFromString(const std::string& str)\n{\n    ActionList action_order;\n\n    std::stringstream ss(str);\n    ActionUID uid;\n    char sep;\n\n    for (;;)\n    {\n        ss >> uid >> sep;\n\n        if (ss.fail())\n            break;\n\n        action_order.push_back(uid);\n    }\n\n    return action_order;\n}\n\n#ifdef DPLUS_UI\n\nActionUID ActionManager::DuplicateAction(const Action& action)\n{\n    Action action_dup = action;\n    action_dup.UID = GenerateUID();\n\n    //Replace translation string with a normal one, so we can attach a copy marker to the string\n    if (action_dup.NameTranslationID != tstr_NONE)\n    {\n        action_dup.Name = TranslationManager::GetString(action_dup.NameTranslationID);\n        action_dup.NameTranslationID = tstr_NONE;\n    }\n\n    action_dup.Name += \" (Copy)\";\n\n    m_Actions[action_dup.UID] = action_dup;\n\n    //Put it below the source action in the order list\n    auto it = std::find(m_ActionOrderUI.begin(), m_ActionOrderUI.end(), action.UID);\n\n    if (it != m_ActionOrderUI.end())\n    {\n        m_ActionOrderUI.insert(++it, action_dup.UID);\n    }\n\n    return action_dup.UID;\n}\n\nvoid ActionManager::ClearIconData()\n{\n    for (auto& action_pair : m_Actions)\n    {\n        Action& action = action_pair.second;\n\n        action.IconImGuiRectID = -1;\n        action.IconAtlasSize   = ImVec2();\n        action.IconAtlasUV     = ImVec4();\n    }\n}\n\nconst ActionManager::ActionList& ActionManager::GetActionOrderListUI() const\n{\n    return m_ActionOrderUI;\n}\n\nvoid ActionManager::SetActionOrderListUI(const ActionList& ui_order)\n{\n    m_ActionOrderUI = ui_order;\n    UpdateActionOrderListUI();\n}\n\nActionManager::ActionList& ActionManager::GetActionOrderListBarDefault()\n{\n    return m_ActionOrderBarDefault;\n}\n\nconst ActionManager::ActionList& ActionManager::GetActionOrderListBarDefault() const\n{\n    return m_ActionOrderBarDefault;\n}\n\nvoid ActionManager::SetActionOrderListBarDefault(const ActionList& ui_order)\n{\n    m_ActionOrderBarDefault = ui_order;\n    ValidateActionOrderList(m_ActionOrderBarDefault);\n}\n\nActionManager::ActionList& ActionManager::GetActionOrderListOverlayBar()\n{\n    return m_ActionOrderOverlayBar;\n}\n\nconst ActionManager::ActionList& ActionManager::GetActionOrderListOverlayBar() const\n{\n    return m_ActionOrderOverlayBar;\n}\n\nvoid ActionManager::SetActionOrderListOverlayBar(const ActionList& ui_order)\n{\n    m_ActionOrderOverlayBar = ui_order;\n    ValidateActionOrderList(m_ActionOrderOverlayBar);\n}\n\nconst char* ActionManager::GetTranslatedName(ActionUID action_uid) const\n{\n    if (action_uid == k_ActionUID_Invalid)\n        return TranslationManager::GetString(tstr_ActionNone);\n\n    const Action& action = GetAction(action_uid);\n\n    return (action.NameTranslationID == tstr_NONE) ? action.Name.c_str() : TranslationManager::GetString(action.NameTranslationID);\n}\n\nconst char* ActionManager::GetTranslatedLabel(ActionUID action_uid) const\n{\n    const Action& action = GetAction(action_uid);\n\n    return (action.LabelTranslationID == tstr_NONE) ? action.Label.c_str() : TranslationManager::GetString(action.LabelTranslationID);\n}\n\nstd::vector<ActionManager::ActionNameListEntry> ActionManager::GetActionNameList()\n{\n    UpdateActionOrderListUI();\n\n    std::vector<ActionManager::ActionNameListEntry> vec;\n\n    for (const auto& uid : m_ActionOrderUI)\n    {\n        const Action& action = GetAction(uid);\n\n        vec.push_back({action.UID, GetTranslatedName(action.UID)});\n    }\n\n    return vec;\n}\n\nstd::vector<std::string> ActionManager::GetIconFileList()\n{\n    std::vector<std::string> file_list;\n\n    const std::wstring wpath = WStringConvertFromUTF8(std::string(ConfigManager::Get().GetApplicationPath() + \"images/icons/*.png\").c_str());\n    WIN32_FIND_DATA find_data;\n    HANDLE handle_find = ::FindFirstFileW(wpath.c_str(), &find_data);\n\n    if (handle_find != INVALID_HANDLE_VALUE)\n    {\n        do\n        {\n            file_list.push_back(StringConvertFromUTF16(find_data.cFileName));\n        }\n        while (::FindNextFileW(handle_find, &find_data) != 0);\n\n        ::FindClose(handle_find);\n    }\n\n    return file_list;\n}\n\nTRMGRStrID ActionManager::GetTranslationIDForName(const std::string& str)\n{\n    //If the name starts with the translation string prefix, try finding the ID for it\n    if (str.find(\"tstr_\") == 0)\n    {\n        return TranslationManager::Get().GetStringID(str.c_str());\n    }\n\n    return tstr_NONE;\n}\n\nstd::string ActionManager::GetCommandDescription(const ActionCommand& command, float max_width)\n{\n    std::string str;\n\n    switch (command.Type)\n    {\n        case ActionCommand::command_none: str = TranslationManager::GetString(tstr_SettingsActionsEditCommandDescNone); break;\n        case ActionCommand::command_key:\n        {\n            str = TranslationManager::GetString((command.UIntArg == 1) ? tstr_SettingsActionsEditCommandDescKeyToggle : tstr_SettingsActionsEditCommandDescKey);\n            StringReplaceAll(str, \"%KEYNAME%\", (command.UIntID == 0) ? TranslationManager::GetString(tstr_DialogKeyCodePickerKeyCodeNone) : GetStringForKeyCode(command.UIntID));\n            break;\n        }\n        case ActionCommand::command_mouse_pos:\n        {\n            str = TranslationManager::GetString(tstr_SettingsActionsEditCommandDescMousePos);\n            StringReplaceAll(str, \"%X%\", std::to_string( GET_X_LPARAM(command.UIntID) ));\n            StringReplaceAll(str, \"%Y%\", std::to_string( GET_Y_LPARAM(command.UIntID) ));\n            break;\n        }\n        case ActionCommand::command_string:\n        {\n            str = TranslationManager::GetString(tstr_SettingsActionsEditCommandDescString);\n\n            //Remove any kind of newline character\n            std::string strmain_single_line = command.StrMain;\n            StringReplaceAll(strmain_single_line, \"\\r\\n\", \" \");\n            StringReplaceAll(strmain_single_line, \"\\n\",   \" \");\n            StringReplaceAll(strmain_single_line, \"\\r\",   \" \");\n\n            StringReplaceAll(str, \"%STRING%\", strmain_single_line);\n            break;\n        }\n        case ActionCommand::command_launch_app:\n        {\n            str = TranslationManager::GetString(tstr_SettingsActionsEditCommandDescLaunchApp);\n            StringReplaceAll(str, \"%APP%\",  command.StrMain);\n            StringReplaceAll(str, \"%ARGSOPT%\", (!command.StrArg.empty()) ? TranslationManager::GetString(tstr_SettingsActionsEditCommandDescLaunchAppArgsOpt) : \"\");\n            StringReplaceAll(str, \"%ARGS%\", command.StrArg);\n            break;\n        }\n        case ActionCommand::command_show_keyboard:\n        {\n            switch (command.UIntArg)\n            {\n                case ActionCommand::command_arg_toggle:      str = TranslationManager::GetString(tstr_SettingsActionsEditCommandDescKeyboardToggle); break;\n                case ActionCommand::command_arg_always_show: str = TranslationManager::GetString(tstr_SettingsActionsEditCommandDescKeyboardShow);   break;\n                case ActionCommand::command_arg_always_hide: str = TranslationManager::GetString(tstr_SettingsActionsEditCommandDescKeyboardHide);   break;\n                default:                                     str = TranslationManager::GetString(tstr_SettingsActionsEditCommandDescUnknown);        break;\n            }\n            break;\n        }\n        case ActionCommand::command_crop_active_window:\n        {\n            str = TranslationManager::GetString(tstr_SettingsActionsEditCommandDescCropWindow);\n            break;\n        }\n        case ActionCommand::command_show_overlay:\n        {\n            switch (command.UIntArg)\n            {\n                case ActionCommand::command_arg_toggle:      str = TranslationManager::GetString(tstr_SettingsActionsEditCommandDescOverlayToggle); break;\n                case ActionCommand::command_arg_always_show: str = TranslationManager::GetString(tstr_SettingsActionsEditCommandDescOverlayShow);   break;\n                case ActionCommand::command_arg_always_hide: str = TranslationManager::GetString(tstr_SettingsActionsEditCommandDescOverlayHide);   break;\n                default:                                     str = TranslationManager::GetString(tstr_SettingsActionsEditCommandDescUnknown);       break;\n            }\n            \n            StringReplaceAll(str, \"%TAGS%\", (LOWORD(command.UIntID) == 1) ? command.StrMain : TranslationManager::GetString(tstr_SettingsActionsEditCommandDescOverlayTargetDefault));\n\n            break;\n        }\n        case ActionCommand::command_switch_task:\n        {\n            if (command.UIntID == 0)\n            {\n                str = TranslationManager::GetString(tstr_SettingsActionsEditCommandDescSwitchTask);\n            }\n            else\n            {\n                str = TranslationManager::GetString(tstr_SettingsActionsEditCommandDescSwitchTaskWindow);\n                StringReplaceAll(str, \"%WINDOW%\", (!command.StrMain.empty()) ? command.StrMain : TranslationManager::GetString(tstr_SettingsActionsEditCommandWindowNone));\n            }\n            break;\n        }\n        case ActionCommand::command_load_overlay_profile:\n        {\n            if (command.UIntID == 0)\n            {\n                str = TranslationManager::GetString(tstr_SettingsActionsEditCommandDescLoadOverlayProfileAdd);\n            }\n            else\n            {\n                str = TranslationManager::GetString(tstr_SettingsActionsEditCommandDescLoadOverlayProfile);\n            }\n\n            StringReplaceAll(str, \"%PROFILE%\", (!command.StrMain.empty()) ? command.StrMain : TranslationManager::GetString(tstr_SettingsProfilesOverlaysNameDefault));\n\n            break;\n        }\n        default: str = TranslationManager::GetString(tstr_SettingsActionsEditCommandDescUnknown); break;\n    }\n\n    str = ImGui::StringEllipsis(str.c_str(), max_width);\n\n    return str;\n}\n\n#endif //DPLUS_UI"
  },
  {
    "path": "src/Shared/Actions.h",
    "content": "#pragma once\n\n#include <string>\n#include <vector>\n#include <unordered_map>\n\n#ifdef DPLUS_UI\n    #include \"imgui.h\"\n    #include \"TranslationManager.h\"\n#endif\n\nstruct ActionCommand\n{\n    enum CommandType\n    {\n        command_none,\n        command_key,\n        command_mouse_pos,\n        command_string,\n        command_launch_app,\n        command_show_keyboard,\n        command_crop_active_window,\n        command_show_overlay,\n        command_switch_task,\n        command_load_overlay_profile,\n        command_unknown,                                //Set when loading unrecognized command\n        command_MAX\n    };\n\n    enum CommandToggleArg : unsigned int\n    {\n        command_arg_toggle,\n        command_arg_always_show,\n        command_arg_always_hide,\n    };\n\n    static const char* s_CommandTypeNames[command_MAX]; //Type names used when loading/saving config\n\n    CommandType Type = command_none;\n\n    unsigned int UIntID  = 0;\n    unsigned int UIntArg = 0;\n    std::string StrMain;\n    std::string StrArg;\n\n    std::string Serialize() const;              //Serializes into binary data stored as string (contains NUL bytes), not suitable for storage\n    void Deserialize(const std::string& str);   //Deserializes from strings created by above function\n};\n\ntypedef uint64_t ActionUID;\nstatic const ActionUID k_ActionUID_Invalid = 0;\ntypedef std::vector<unsigned int> OverlayIDList;\n\nstruct Action\n{\n    ActionUID UID = 0;\n    std::string Name;\n    std::string Label;\n    std::vector<ActionCommand> Commands;\n    bool TargetUseTags = false;\n    std::string TargetTags;\n    std::string IconFilename;\n\n    #ifdef DPLUS_UI\n        int IconImGuiRectID           = -1; //-1 when no icon loaded (ID on ImGui end is not valid after building the font)\n        ImVec2 IconAtlasSize;\n        ImVec4 IconAtlasUV;\n        TRMGRStrID NameTranslationID  = tstr_NONE;\n        TRMGRStrID LabelTranslationID = tstr_NONE;\n    #endif\n\n    std::string Serialize() const;              //Serializes into binary data stored as string (contains NUL bytes), not suitable for storage\n    void Deserialize(const std::string& str);   //Deserializes from strings created by above function\n};\n\nclass ActionManager\n{\n    public:\n        typedef std::vector<ActionUID> ActionList;\n\n        struct ActionNameListEntry\n        {\n            ActionUID UID;\n            std::string Name;\n        };\n\n    private:\n        std::unordered_map<ActionUID, Action> m_Actions;\n        Action m_NullAction;\n\n        #ifdef DPLUS_UI\n            ActionList m_ActionOrderUI;\n            ActionList m_ActionOrderBarDefault;\n            ActionList m_ActionOrderOverlayBar;\n        #endif\n\n        #ifndef DPLUS_UI\n            void DoKeyCommand(               const ActionCommand& command, OverlayIDList& overlay_targets, bool down) const;\n            void DoMousePosCommand(          const ActionCommand& command, OverlayIDList& overlay_targets)            const;\n            void DoStringCommand(            const ActionCommand& command, OverlayIDList& overlay_targets)            const;\n            void DoLaunchAppCommand(         const ActionCommand& command, OverlayIDList& overlay_targets)            const;\n            void DoShowKeyboardCommand(      const ActionCommand& command, OverlayIDList& overlay_targets)            const;\n            void DoCropActiveWindowCommand(  const ActionCommand& command, OverlayIDList& overlay_targets)            const;\n            void DoShowOverlayCommand(       const ActionCommand& command, OverlayIDList& overlay_targets, bool undo) const;\n            void DoSwitchTaskCommand(        const ActionCommand& command, OverlayIDList& overlay_targets)            const;\n            void DoLoadOverlayProfileCommand(const ActionCommand& command, OverlayIDList& overlay_targets)            const;\n        #endif\n\n        #ifdef DPLUS_UI\n            void UpdateActionOrderListUI();\n            void ValidateActionOrderList(ActionList& ui_order) const;\n        #endif\n\n    public:\n        ActionManager();\n\n        bool LoadActionsFromFile(const char* filename = nullptr);\n        void SaveActionsToFile();\n        void RestoreActionsFromDefault();\n\n        const Action& GetAction(ActionUID action_uid) const;\n        bool ActionExists(ActionUID action_uid) const;\n        void StoreAction(const Action& action);\n        void RemoveAction(ActionUID action_uid);\n\n        //Start/StopAction forward to the dashboard app if called from UI app, but other functions like Store/RemoveAction do *not* and have to be synced manually\n        void StartAction(ActionUID action_uid, unsigned int overlay_source_id = UINT_MAX) const;\n        void StopAction( ActionUID action_uid, unsigned int overlay_source_id = UINT_MAX) const;     //Releases keys pressed down in StartAction()\n        void DoAction(   ActionUID action_uid, unsigned int overlay_source_id = UINT_MAX) const;     //Just calls Start() and Stop() together\n\n        uint64_t GenerateUID() const;\n\n        static std::string ActionOrderListToString(const ActionList& action_order);\n        static ActionList ActionOrderListFromString(const std::string& str);\n\n        #ifdef DPLUS_UI\n            ActionUID DuplicateAction(const Action& action);        //Returns UID of new action\n            void ClearIconData();                                   //Resets icon-related values of all actions, used when reloading textures\n\n            const ActionList& GetActionOrderListUI() const;\n            void SetActionOrderListUI(const ActionList& ui_order);\n            ActionList&       GetActionOrderListBarDefault();\n            const ActionList& GetActionOrderListBarDefault() const;\n            void              SetActionOrderListBarDefault(const ActionList& ui_order);\n            ActionList&       GetActionOrderListOverlayBar();\n            const ActionList& GetActionOrderListOverlayBar() const;\n            void              SetActionOrderListOverlayBar(const ActionList& ui_order);\n\n            const char* GetTranslatedName(ActionUID action_uid) const;\n            const char* GetTranslatedLabel(ActionUID action_uid) const;\n            std::vector<ActionNameListEntry> GetActionNameList();\n            static std::vector<std::string> GetIconFileList();\n            static TRMGRStrID GetTranslationIDForName(const std::string& str);\n            static std::string GetCommandDescription(const ActionCommand& command, float max_width = -1.0f);\n        #endif\n};\n"
  },
  {
    "path": "src/Shared/AppProfiles.cpp",
    "content": "#include \"AppProfiles.h\"\n\n#include <sstream>\n\n#ifndef DPLUS_UI\n    #include \"OutputManager.h\"\n#endif\n\n#include \"ConfigManager.h\"\n#include \"Util.h\"\n#include \"Logging.h\"\n#include \"Ini.h\"\n\nstd::string AppProfileManager::GetCurrentSceneAppKey() const\n{\n    if (vr::VRApplications() != nullptr)\n    {\n        return GetProcessAppKey(vr::VRApplications()->GetCurrentSceneProcessId());\n    }\n\n    return \"\";\n}\n\nstd::string AppProfileManager::GetProcessAppKey(uint32_t pid) const\n{\n    char app_key_buffer[vr::k_unMaxApplicationKeyLength] = \"\";\n\n    if (vr::VRApplications() != nullptr)\n    {\n        vr::VRApplications()->GetApplicationKeyByProcessId(pid, app_key_buffer, vr::k_unMaxApplicationKeyLength);\n    }\n\n    return app_key_buffer;\n}\n\nbool AppProfileManager::LoadProfilesFromFile()\n{\n    m_Profiles.clear();\n\n    std::wstring wpath = WStringConvertFromUTF8( std::string(ConfigManager::Get().GetApplicationPath() + \"app_profiles.ini\").c_str() );\n    bool existed = FileExists(wpath.c_str());\n\n    if (!existed)\n        return false;\n\n    Ini pfile(wpath.c_str());\n\n    for (const auto& section_name : pfile.GetSectionList())\n    {\n        if (section_name.empty())\n            continue;\n\n        AppProfile profile;\n        profile.IsEnabled =                    pfile.ReadBool(  section_name.c_str(), \"Enabled\");\n        profile.LastApplicationName    =       pfile.ReadString(section_name.c_str(), \"LastApplicationName\");\n        profile.OverlayProfileFileName =       pfile.ReadString(section_name.c_str(), \"OverlayProfile\");\n        profile.ActionUIDEnter = std::strtoull(pfile.ReadString(section_name.c_str(), \"ActionEnter\", \"0\").c_str(), nullptr, 10);\n        profile.ActionUIDLeave = std::strtoull(pfile.ReadString(section_name.c_str(), \"ActionLeave\", \"0\").c_str(), nullptr, 10);\n\n        StoreProfile(section_name, profile);\n    }\n\n    return true;\n}\n\nvoid AppProfileManager::SaveProfilesToFile()\n{\n    std::wstring wpath = WStringConvertFromUTF8( std::string(ConfigManager::Get().GetApplicationPath() + \"app_profiles.ini\").c_str() );\n\n    //Don't write if no profiles\n    if (m_Profiles.empty())\n    {\n        //Delete application profile file instead of leaving an empty one behind\n        if (FileExists(wpath.c_str()))\n        {\n            ::DeleteFileW(wpath.c_str());\n        }\n\n        return;\n    }\n\n    Ini pfile(wpath.c_str());\n\n    char app_name_buffer[vr::k_unMaxPropertyStringSize]  = \"\";\n\n    for (const auto& profile_pair : m_Profiles)\n    {\n        const std::string& app_key = profile_pair.first;\n        const AppProfile& profile  = profile_pair.second;\n\n        pfile.WriteBool(app_key.c_str(), \"Enabled\", profile.IsEnabled);\n\n        //Write last known application name if SteamVR is running\n        bool has_updated_app_name = false;\n        if (vr::VRApplications() != nullptr)\n        {\n            vr::EVRApplicationError app_error = vr::VRApplicationError_None;\n            vr::VRApplications()->GetApplicationPropertyString(app_key.c_str(), vr::VRApplicationProperty_Name_String, app_name_buffer, vr::k_unMaxPropertyStringSize, &app_error);\n\n            if (app_error == vr::VRApplicationError_None)\n            {\n                pfile.WriteString(app_key.c_str(), \"LastApplicationName\", app_name_buffer);\n                has_updated_app_name = true;\n            }\n        }\n\n        //Write existing info from profile if we couldn't get a fresh one\n        if (!has_updated_app_name)\n        {\n            pfile.WriteString(app_key.c_str(), \"LastApplicationName\", profile.LastApplicationName.c_str());\n        }\n\n        pfile.WriteString(app_key.c_str(), \"OverlayProfile\", profile.OverlayProfileFileName.c_str());\n        pfile.WriteString(app_key.c_str(), \"ActionEnter\",    std::to_string(profile.ActionUIDEnter).c_str());\n        pfile.WriteString(app_key.c_str(), \"ActionLeave\",    std::to_string(profile.ActionUIDLeave).c_str());\n    }\n\n    pfile.Save();\n}\n\nconst AppProfile& AppProfileManager::GetProfile(const std::string& app_key)\n{\n    auto it = m_Profiles.find(app_key);\n\n    return (it != m_Profiles.end()) ? it->second : m_NullProfile;\n}\n\nbool AppProfileManager::ProfileExists(const std::string& app_key) const\n{\n    return (m_Profiles.find(app_key) != m_Profiles.end());\n}\n\nbool AppProfileManager::StoreProfile(const std::string& app_key, const AppProfile& profile)\n{\n    AppProfile profile_prev = GetProfile(app_key);\n    m_Profiles[app_key] = profile;\n\n    //Adjust active profile state from change if needed\n    if (m_AppKeyActiveProfile == app_key)\n    {\n        if (profile.IsEnabled)\n        {\n            //App profile is current active one & enabled and the overlay profile changed, re-activate\n            //(avoid doing unecessary activations as they'd override any temporary overlay changes)\n            if (profile_prev.OverlayProfileFileName != profile.OverlayProfileFileName)\n            {\n                return ActivateProfile(app_key);\n            }\n        }\n        else //App profile is current active one and has been disabled, activate blank app profile\n        {\n            return ActivateProfile(\"\");\n        }\n    }\n    else if ( (profile.IsEnabled) && (m_AppKeyActiveProfile.empty()) )\n    {\n        //Profile is currently not active but app key belongs to the current scene app, activate if newly enabled\n        if (app_key == GetCurrentSceneAppKey())\n        {\n            return ActivateProfile(app_key);\n        }\n    }\n\n    return false;\n}\n\nbool AppProfileManager::RemoveProfile(const std::string& app_key)\n{\n    LOG_F(INFO, \"Removing app profile \\\"%s\\\"...\", app_key.c_str());\n\n    m_Profiles.erase(app_key);\n\n    //If deleting active profile, activate blank app profile\n    if (m_AppKeyActiveProfile == app_key)\n    {\n        return ActivateProfile(\"\");\n    }\n\n    return false;\n}\n\nvoid AppProfileManager::RemoveAllProfiles()\n{\n    LOG_F(INFO, \"Removing all app profiles...\");\n\n    m_Profiles.clear();\n\n    //If a profile was active, activate blank app profile\n    if (!m_AppKeyActiveProfile.empty())\n    {\n        ActivateProfile(\"\");\n    }\n}\n\nbool AppProfileManager::ActivateProfile(const std::string& app_key)\n{\n    bool loaded_overlay_profile = false;\n    const bool is_reloading_profile = (app_key == m_AppKeyActiveProfile);   //Reloading same profile (i.e. from changing profile data), don't trigger enter/leave actions\n\n    //Execute profile leave action if an app profile is already active\n    #ifndef DPLUS_UI\n        if ( (!is_reloading_profile) && (!m_AppKeyActiveProfile.empty()) )\n        {\n            const AppProfile& profile_prev = GetProfile(m_AppKeyActiveProfile);\n\n            if ((profile_prev.IsEnabled) && (profile_prev.ActionUIDLeave != k_ActionUID_Invalid))\n            {\n                if (OutputManager* outmgr = OutputManager::Get())\n                {\n                    VLOG_F(1, \"Executing profile exit action %llu for app profile \\\"%s\\\"...\", profile_prev.ActionUIDLeave, m_AppKeyActiveProfile.c_str());\n\n                    ConfigManager::Get().GetActionManager().DoAction(profile_prev.ActionUIDLeave);\n                }\n            }\n        }\n    #endif\n\n    const AppProfile& profile = GetProfile(app_key);    //This will be m_NullProfile on missing or blank app_key\n\n    LOG_IF_F(INFO, (!is_reloading_profile) && (&profile != &m_NullProfile), \"Activating app profile \\\"%s\\\"...\", app_key.c_str());\n    LOG_IF_F(INFO,  (is_reloading_profile) && (&profile != &m_NullProfile), \"Reloading app profile \\\"%s\\\"...\", app_key.c_str());\n\n    //Look up and cache app name\n    if (!is_reloading_profile)\n    {\n        if (profile.IsEnabled)\n        {\n            m_AppNameActiveProfile = app_key;\n\n            if (vr::VRApplications() != nullptr)\n            {\n                char app_name_buffer[vr::k_unMaxPropertyStringSize] = \"\";\n                vr::EVRApplicationError app_error = vr::VRApplicationError_None;\n                vr::VRApplications()->GetApplicationPropertyString(app_key.c_str(), vr::VRApplicationProperty_Name_String, app_name_buffer, vr::k_unMaxPropertyStringSize, &app_error);\n\n                if (app_error == vr::VRApplicationError_None)\n                {\n                    m_AppNameActiveProfile = app_name_buffer;\n                }\n            }\n        }\n        else\n        {\n            m_AppNameActiveProfile.clear();\n        }\n    }\n\n    //Load app profile overlay config\n    if ((profile.IsEnabled) && (!profile.OverlayProfileFileName.empty()))\n    {\n        #ifdef DPLUS_UI\n            //If there's no app profile overlay config already active, make sure to save the current normal config first so we can restore it properly later\n            if (!m_IsProfileActiveWithOverlayProfile)\n            {\n                ConfigManager::Get().SaveConfigToFile();\n            }\n        #endif\n\n        if (ConfigManager::Get().LoadMultiOverlayProfileFromFile(profile.OverlayProfileFileName + \".ini\"))\n        {\n            loaded_overlay_profile = true;\n            m_IsProfileActiveWithOverlayProfile = true;\n        }\n    }\n\n    if ((!loaded_overlay_profile) && (m_IsProfileActiveWithOverlayProfile))  //Restore normal overlay config if previous profile loaded an overlay profile\n    {\n        ConfigManager::Get().LoadMultiOverlayProfileFromFile(\"../config.ini\");\n        loaded_overlay_profile = true;\n\n        m_IsProfileActiveWithOverlayProfile = false;\n    }\n\n    m_AppKeyActiveProfile = (profile.IsEnabled) ? app_key : \"\";\n\n    //Execute profile enter action\n    #ifndef DPLUS_UI\n        if ((!is_reloading_profile) && (profile.IsEnabled) && (profile.ActionUIDEnter != k_ActionUID_Invalid))\n        {\n            if (OutputManager* outmgr = OutputManager::Get())\n            {\n                VLOG_F(1, \"Executing profile enter action %llu for app profile \\\"%s\\\"...\", profile.ActionUIDEnter, m_AppKeyActiveProfile.c_str());\n                ConfigManager::Get().GetActionManager().DoAction(profile.ActionUIDEnter);\n            }\n        }\n    #endif\n\n    return loaded_overlay_profile;\n}\n\nbool AppProfileManager::ActivateProfileForCurrentSceneApp()\n{\n    return ActivateProfile(GetCurrentSceneAppKey());\n}\n\nbool AppProfileManager::ActivateProfileForProcess(uint32_t pid)\n{\n    return ActivateProfile(GetProcessAppKey(pid));\n}\n\nconst std::string& AppProfileManager::GetActiveProfileAppKey()\n{\n    return m_AppKeyActiveProfile;\n}\n\nconst std::string& AppProfileManager::GetActiveProfileAppName()\n{\n    return m_AppNameActiveProfile;\n}\n\nbool AppProfileManager::IsActiveProfileWithOverlayProfile() const\n{\n    return m_IsProfileActiveWithOverlayProfile;\n}\n\nstd::vector<std::string> AppProfileManager::GetProfileAppKeyList() const\n{\n    std::vector<std::string> app_keys;\n\n    for (const auto& profile_pair : m_Profiles)\n    {\n        app_keys.push_back(profile_pair.first);\n    }\n\n    return app_keys;\n}\n\nstd::string AppProfile::Serialize() const\n{\n    std::stringstream ss(std::ios::out | std::ios::binary);\n    size_t str_size = 0;\n\n    ss.write((const char*)&IsEnabled, sizeof(IsEnabled));\n\n    str_size = LastApplicationName.size();\n    ss.write((const char*)&str_size,        sizeof(str_size));\n    ss.write(LastApplicationName.data(),    str_size);\n\n    str_size = OverlayProfileFileName.size();\n    ss.write((const char*)&str_size,        sizeof(str_size));\n    ss.write(OverlayProfileFileName.data(), str_size);\n\n    ss.write((const char*)&ActionUIDEnter, sizeof(ActionUIDEnter));\n    ss.write((const char*)&ActionUIDLeave, sizeof(ActionUIDLeave));\n\n    return ss.str();\n}\n\nvoid AppProfile::Deserialize(const std::string& str)\n{\n    std::stringstream ss(str, std::ios::in | std::ios::binary);\n\n    AppProfile new_profile;\n    size_t str_length = 0;\n\n    ss.read((char*)&new_profile.IsEnabled, sizeof(IsEnabled));\n\n    ss.read((char*)&str_length, sizeof(str_length));\n    str_length = std::min(str_length, (size_t)4096);    //Arbitrary size limit to avoid large allocations on garbage data\n    new_profile.LastApplicationName.resize(str_length);\n    ss.read(&new_profile.LastApplicationName[0], str_length);\n\n    ss.read((char*)&str_length, sizeof(str_length));\n    str_length = std::min(str_length, (size_t)4096);\n    new_profile.OverlayProfileFileName.resize(str_length);\n    ss.read(&new_profile.OverlayProfileFileName[0], str_length);\n\n    ss.read((char*)&new_profile.ActionUIDEnter, sizeof(ActionUIDEnter));\n    ss.read((char*)&new_profile.ActionUIDLeave, sizeof(ActionUIDLeave));\n\n    //Replace all data with the read profile if there were no stream errors\n    if (ss.good())\n        *this = new_profile;\n}\n\n"
  },
  {
    "path": "src/Shared/AppProfiles.h",
    "content": "#pragma once\n\n#include <unordered_map>\n#include <vector>\n#include \"Actions.h\"\n\nstruct AppProfile\n{\n    bool IsEnabled = false;\n    std::string LastApplicationName;            //Used when SteamVR isn't running or can't find the application from the app key\n    std::string OverlayProfileFileName;\n    ActionUID ActionUIDEnter = k_ActionUID_Invalid;\n    ActionUID ActionUIDLeave = k_ActionUID_Invalid;\n\n    std::string Serialize() const;              //Serializes into binary data stored as string (contains NUL bytes), not suitable for storage\n    void Deserialize(const std::string& str);   //Deserializes from strings created by above function\n};\n\nclass AppProfileManager\n{\n    private:\n        std::unordered_map<std::string, AppProfile> m_Profiles;\n        AppProfile m_NullProfile;\n\n        std::string m_AppKeyActiveProfile;\n        std::string m_AppNameActiveProfile;\n        bool m_IsProfileActiveWithOverlayProfile = false;\n\n        std::string GetCurrentSceneAppKey() const;\n        std::string GetProcessAppKey(uint32_t pid) const;\n\n    public:\n        bool LoadProfilesFromFile();\n        void SaveProfilesToFile();\n\n        const AppProfile& GetProfile(const std::string& app_key);\n        bool ProfileExists(const std::string& app_key) const;\n        bool StoreProfile(const std::string& app_key, const AppProfile& profile);   //Returns true if a new overlay profile was loaded after change of active profile\n        bool RemoveProfile(const std::string& app_key);                             //Returns true if a new overlay profile was loaded after removal of active profile\n        void RemoveAllProfiles();\n\n        bool ActivateProfile(const std::string& app_key);                           //Returns true if a new overlay profile was loaded\n        bool ActivateProfileForCurrentSceneApp();                                   //^\n        bool ActivateProfileForProcess(uint32_t pid);                               //^\n        const std::string& GetActiveProfileAppKey();\n        const std::string& GetActiveProfileAppName();\n        bool IsActiveProfileWithOverlayProfile() const;                             //Returns if the active app profile loaded an overlay profile\n\n        std::vector<std::string> GetProfileAppKeyList() const;\n};"
  },
  {
    "path": "src/Shared/ConfigManager.cpp",
    "content": "#include \"ConfigManager.h\"\n\n#include <algorithm>\n#include <sstream>\n#include <fstream>\n\n#include \"Util.h\"\n#include \"OpenVRExt.h\"\n#include \"Logging.h\"\n#include \"Ini.h\"\n#include \"OverlayManager.h\"\n#include \"InterprocessMessaging.h\"\n#include \"WindowManager.h\"\n#include \"DesktopPlusWinRT.h\"\n#include \"DPBrowserAPIClient.h\"\n\n#ifdef DPLUS_UI\n    #include \"UIManager.h\"\n    #include \"TranslationManager.h\"\n#else\n    #include \"WindowManager.h\"\n#endif\n\nstatic ConfigManager g_ConfigManager;\nstatic const std::string g_EmptyString;       //This way we can still return a const reference. Worth it? iunno\n\nstatic const std::pair<OverlayOrigin, const char*> g_OvrlOriginConfigFileStrings[] = \n{\n    {ovrl_origin_room,            \"Room\"}, \n    {ovrl_origin_hmd_floor,       \"HMDFloor\"}, \n    {ovrl_origin_seated_universe, \"SeatedUniverse\"}, \n    {ovrl_origin_dashboard,       \"Dashboard\"}, \n    {ovrl_origin_hmd,             \"HMD\"},\n    {ovrl_origin_left_hand,       \"LeftHand\"}, \n    {ovrl_origin_right_hand,      \"RightHand\"}, \n    {ovrl_origin_aux,             \"Aux\"},\n    {ovrl_origin_theater_screen,  \"TheaterScreen\"},\n    //Legacy config compatibility names (old enum IDs)\n    {ovrl_origin_room,            \"0\"},\n    {ovrl_origin_hmd_floor,       \"1\"}, \n    {ovrl_origin_seated_universe, \"2\"}, \n    {ovrl_origin_dashboard,       \"3\"}, \n    {ovrl_origin_hmd,             \"4\"},\n    {ovrl_origin_left_hand,       \"5\"}, \n    {ovrl_origin_right_hand,      \"6\"}, \n    {ovrl_origin_aux,             \"7\"},\n};\n\n\nstd::string ConfigHotkey::Serialize() const\n{\n    std::stringstream ss(std::ios::out | std::ios::binary);\n\n    ss.write((const char*)&KeyCode,   sizeof(KeyCode));\n    ss.write((const char*)&Modifiers, sizeof(Modifiers));\n    ss.write((const char*)&ActionUID, sizeof(ActionUID));\n\n    return ss.str();\n}\n\nvoid ConfigHotkey::Deserialize(const std::string& str)\n{\n    std::stringstream ss(str, std::ios::in | std::ios::binary);\n\n    ConfigHotkey new_hotkey;\n\n    ss.read((char*)&new_hotkey.KeyCode,   sizeof(KeyCode));\n    ss.read((char*)&new_hotkey.Modifiers, sizeof(Modifiers));\n    ss.read((char*)&new_hotkey.ActionUID, sizeof(ActionUID));\n\n    //Replace all data with the read hotkey if there were no stream errors\n    if (ss.good())\n    {\n        *this = new_hotkey;\n    }\n}\n\n\nOverlayConfigData::OverlayConfigData()\n{\n    std::fill(std::begin(ConfigBool),   std::end(ConfigBool),   false);\n    std::fill(std::begin(ConfigInt),    std::end(ConfigInt),    -1);\n    std::fill(std::begin(ConfigFloat),  std::end(ConfigFloat),  0.0f);\n    std::fill(std::begin(ConfigHandle), std::end(ConfigHandle), 0);\n\n    //Default the transform matrix to zero as an indicator to reset them when possible later\n    float matrix_zero[16] = { 0.0f };\n    ConfigTransform = matrix_zero;\n}\n\nConfigManager::ConfigManager() : m_IsSteamInstall(false)\n{\n    std::fill(std::begin(m_ConfigBool),  std::end(m_ConfigBool),  false);\n    std::fill(std::begin(m_ConfigInt),   std::end(m_ConfigInt),   -1);\n    std::fill(std::begin(m_ConfigFloat), std::end(m_ConfigFloat), 0.0f);\n    //We don't need to initialize m_ConfigString\n\n    //Init desktop count to the system metric, which already correct for most users\n    m_ConfigInt[configid_int_state_interface_desktop_count] = ::GetSystemMetrics(SM_CMONITORS);\n\n    //Assume pen simulation is supported by default, as that's true for most users\n    m_ConfigBool[configid_bool_state_pen_simulation_supported] = true;\n\n    //Init laser pointer hint to HMD (not controllers since they could be disconnected)\n    m_ConfigInt[configid_int_state_laser_pointer_device_hint]  = vr::k_unTrackedDeviceIndex_Hmd;\n    m_ConfigInt[configid_int_state_dplus_laser_pointer_device] = vr::k_unTrackedDeviceIndexInvalid;\n\n    //Init application path\n    int buffer_size = 1024;\n    DWORD read_length;\n    WCHAR* buffer;\n\n    while (true)\n    {\n        buffer = new WCHAR[buffer_size];\n\n        read_length = ::GetModuleFileName(nullptr, buffer, buffer_size);\n\n        if ( (read_length == buffer_size) && (::GetLastError() == ERROR_INSUFFICIENT_BUFFER) )\n        {\n            delete[] buffer;\n            buffer_size += 1024;\n        }\n        else\n        {\n            break;\n        }\n    }\n\n    if (::GetLastError() == ERROR_SUCCESS)\n    {\n        std::string path_str = StringConvertFromUTF16(buffer);\n\n        //We got the full executable path, so let's get the folder part\n        std::size_t pos = path_str.find_last_of(\"\\\\\");\n        m_ApplicationPath = path_str.substr(0, pos + 1);\t//Includes trailing backslash\n        m_ExecutableName  = path_str.substr(pos + 1, std::string::npos);\n\n        //Somewhat naive way to check if this install is from Steam without using Steam API or shipping different binaries\n        //Convert to lower first since there can be capitalization differences for the Steam directories\n        std::wstring path_wstr = buffer;\n        ::CharLowerBuff(buffer, (DWORD)path_wstr.length());\n        path_wstr = buffer;\n\n        m_IsSteamInstall = (path_wstr.find(L\"\\\\steamapps\\\\common\\\\desktopplus\\\\desktopplus\") != std::wstring::npos); \n    }\n\n    delete[] buffer;\n\n    //Check if UIAccess is enabled\n    m_ConfigBool[configid_bool_state_misc_uiaccess_enabled] = IsUIAccessEnabled();\n}\n\nConfigManager& ConfigManager::Get()\n{\n    return g_ConfigManager;\n}\n\nvoid ConfigManager::LoadOverlayProfile(const Ini& config, unsigned int overlay_id)\n{\n    OverlayConfigData& data = OverlayManager::Get().GetCurrentConfigData();\n    unsigned int current_id = OverlayManager::Get().GetCurrentOverlayID();\n\n    std::stringstream ss;\n    ss << \"Overlay\" << overlay_id;\n\n    std::string section = ss.str();\n\n    data.ConfigNameStr = config.ReadString(section.c_str(), \"Name\");\n\n    data.ConfigBool[configid_bool_overlay_name_custom]                  = config.ReadBool(section.c_str(),   \"NameIsCustom\", false);\n    data.ConfigBool[configid_bool_overlay_enabled]                      = config.ReadBool(section.c_str(),   \"Enabled\", true);\n    data.ConfigInt[configid_int_overlay_desktop_id]                     = config.ReadInt(section.c_str(),    \"DesktopID\", 0);\n    data.ConfigInt[configid_int_overlay_capture_source]                 = config.ReadInt(section.c_str(),    \"CaptureSource\", ovrl_capsource_desktop_duplication);\n    data.ConfigInt[configid_int_overlay_duplication_id]                 = config.ReadInt(section.c_str(),    \"DuplicationID\", -1);\n    data.ConfigInt[configid_int_overlay_winrt_desktop_id]               = config.ReadInt(section.c_str(),    \"WinRTDesktopID\", -2);\n    data.ConfigBool[configid_bool_overlay_winrt_window_matching_strict] = config.ReadBool(section.c_str(),   \"WinRTWindowMatchingStrict\", false);\n    data.ConfigStr[configid_str_overlay_winrt_last_window_title]        = config.ReadString(section.c_str(), \"WinRTLastWindowTitle\");\n    data.ConfigStr[configid_str_overlay_winrt_last_window_class_name]   = config.ReadString(section.c_str(), \"WinRTLastWindowClassName\");\n    data.ConfigStr[configid_str_overlay_winrt_last_window_exe_name]     = config.ReadString(section.c_str(), \"WinRTLastWindowExeName\");\n    data.ConfigStr[configid_str_overlay_browser_url]                    = config.ReadString(section.c_str(), \"BrowserURL\");\n    data.ConfigStr[configid_str_overlay_browser_url_user_last]          = config.ReadString(section.c_str(), \"BrowserURLUserLast\");\n    data.ConfigStr[configid_str_overlay_browser_title]                  = config.ReadString(section.c_str(), \"BrowserTitle\");\n    data.ConfigBool[configid_bool_overlay_browser_allow_transparency]   = config.ReadBool(section.c_str(),   \"BrowserAllowTransparency\", false);\n    data.ConfigFloat[configid_float_overlay_width]                      = clamp(config.ReadInt(section.c_str(), \"Width\", 165)    / 100.0f, 0.00001f, 1000.0f);\n    data.ConfigFloat[configid_float_overlay_curvature]                  = clamp(config.ReadInt(section.c_str(), \"Curvature\", 17) / 100.0f, 0.0f,     1.0f);\n    data.ConfigFloat[configid_float_overlay_opacity]                    = config.ReadInt(section.c_str(),    \"Opacity\", 100) / 100.0f;\n    data.ConfigFloat[configid_float_overlay_brightness]                 = config.ReadInt(section.c_str(),    \"Brightness\", 100) / 100.0f;\n    data.ConfigFloat[configid_float_overlay_browser_zoom]               = config.ReadInt(section.c_str(),    \"BrowserZoom\", 100) / 100.0f;\n    data.ConfigFloat[configid_float_overlay_offset_right]               = config.ReadInt(section.c_str(),    \"OffsetRight\", 0) / 100.0f;\n    data.ConfigFloat[configid_float_overlay_offset_up]                  = config.ReadInt(section.c_str(),    \"OffsetUp\", 0) / 100.0f;\n    data.ConfigFloat[configid_float_overlay_offset_forward]             = config.ReadInt(section.c_str(),    \"OffsetForward\", 0) / 100.0f;\n    data.ConfigInt[configid_int_overlay_user_width]                     = config.ReadInt(section.c_str(),    \"UserWidth\", 1280);\n    data.ConfigInt[configid_int_overlay_user_height]                    = config.ReadInt(section.c_str(),    \"UserHeight\", 720);\n    data.ConfigInt[configid_int_overlay_display_mode]                   = config.ReadInt(section.c_str(),    \"DisplayMode\", ovrl_dispmode_always);\n    data.ConfigInt[configid_int_overlay_origin]                         = GetOverlayOriginFromConfigString(config.ReadString(section.c_str(), \"Origin\"));\n    data.ConfigBool[configid_bool_overlay_origin_hmd_floor_use_turning] = config.ReadBool(section.c_str(),   \"OriginHMDFloorTurning\", false);\n    data.ConfigInt[configid_int_overlay_origin_smoothing_level]         = config.ReadInt(section.c_str(),    \"OriginSmoothingLevel\", 0);\n    data.ConfigBool[configid_bool_overlay_transform_locked]             = config.ReadBool(section.c_str(),   \"TransformLocked\", false);\n\n    data.ConfigBool[configid_bool_overlay_crop_enabled]                 = config.ReadBool(section.c_str(), \"CroppingEnabled\", false);\n    data.ConfigInt[configid_int_overlay_crop_x]                         = config.ReadInt(section.c_str(),  \"CroppingX\", 0);\n    data.ConfigInt[configid_int_overlay_crop_y]                         = config.ReadInt(section.c_str(),  \"CroppingY\", 0);\n    data.ConfigInt[configid_int_overlay_crop_width]                     = config.ReadInt(section.c_str(),  \"CroppingWidth\", -1);\n    data.ConfigInt[configid_int_overlay_crop_height]                    = config.ReadInt(section.c_str(),  \"CroppingHeight\", -1);\n\n    data.ConfigBool[configid_bool_overlay_show_backside]                = config.ReadBool(section.c_str(),   \"ShowBackSide\", false);\n    data.ConfigBool[configid_bool_overlay_3D_enabled]                   = config.ReadBool(section.c_str(),   \"3DEnabled\", false);\n    data.ConfigInt[configid_int_overlay_3D_mode]                        = config.ReadInt(section.c_str(),    \"3DMode\", ovrl_3Dmode_hsbs);\n    data.ConfigBool[configid_bool_overlay_3D_swapped]                   = config.ReadBool(section.c_str(),   \"3DSwapped\", false);\n    data.ConfigBool[configid_bool_overlay_gazefade_enabled]             = config.ReadBool(section.c_str(),   \"GazeFade\", false);\n    data.ConfigFloat[configid_float_overlay_gazefade_distance]          = config.ReadInt(section.c_str(),    \"GazeFadeDistance\", 0) / 100.0f;\n    data.ConfigFloat[configid_float_overlay_gazefade_rate]              = config.ReadInt(section.c_str(),    \"GazeFadeRate\", 100) / 100.0f;\n    data.ConfigFloat[configid_float_overlay_gazefade_opacity]           = config.ReadInt(section.c_str(),    \"GazeFadeOpacity\", 0) / 100.0f;\n    data.ConfigInt[configid_int_overlay_update_limit_override_mode]     = config.ReadInt(section.c_str(),    \"UpdateLimitModeOverride\", update_limit_mode_off);\n    data.ConfigFloat[configid_float_overlay_update_limit_override_ms]   = config.ReadInt(section.c_str(),    \"UpdateLimitMS\", 0) / 100.0f;\n    data.ConfigInt[configid_int_overlay_update_limit_override_fps]      = config.ReadInt(section.c_str(),    \"UpdateLimitFPS\", update_limit_fps_30);\n    data.ConfigInt[configid_int_overlay_browser_max_fps_override]       = config.ReadInt(section.c_str(),    \"BrowserMaxFPSOverride\", -1);\n    data.ConfigBool[configid_bool_overlay_input_enabled]                = config.ReadBool(section.c_str(),   \"InputEnabled\", true);\n    data.ConfigBool[configid_bool_overlay_input_dplus_lp_enabled]       = config.ReadBool(section.c_str(),   \"InputDPlusLPEnabled\", true);\n    data.ConfigStr[configid_str_overlay_tags]                           = config.ReadString(section.c_str(), \"Tags\");\n    data.ConfigBool[configid_bool_overlay_update_invisible]             = config.ReadBool(section.c_str(),   \"UpdateInvisible\", false);\n\n    data.ConfigBool[configid_bool_overlay_floatingui_enabled]           = config.ReadBool(section.c_str(), \"ShowFloatingUI\", true);\n    data.ConfigBool[configid_bool_overlay_floatingui_desktops_enabled]  = config.ReadBool(section.c_str(), \"ShowDesktopButtons\", false);\n    data.ConfigBool[configid_bool_overlay_floatingui_extras_enabled]    = config.ReadBool(section.c_str(), \"ShowExtraButtons\", true);\n    data.ConfigBool[configid_bool_overlay_actionbar_enabled]            = config.ReadBool(section.c_str(), \"ShowActionBar\", false);\n    data.ConfigBool[configid_bool_overlay_actionbar_order_use_global]   = config.ReadBool(section.c_str(), \"ActionBarOrderUseGlobal\", true);\n    data.ConfigActionBarOrder                                           = m_ActionManager.ActionOrderListFromString( config.ReadString(section.c_str(), \"ActionBarOrderCustom\") );\n\n    bool do_set_auto_name = ( (!data.ConfigBool[configid_bool_overlay_name_custom]) && (data.ConfigNameStr.empty()) );\n\n    //Restore WinRT Capture state if possible\n    if ( (data.ConfigInt[configid_int_overlay_winrt_desktop_id] == -2) && (!data.ConfigStr[configid_str_overlay_winrt_last_window_title].empty()) )\n    {\n        HWND window = WindowInfo::FindClosestWindowForTitle(data.ConfigStr[configid_str_overlay_winrt_last_window_title], data.ConfigStr[configid_str_overlay_winrt_last_window_class_name],\n                                                            data.ConfigStr[configid_str_overlay_winrt_last_window_exe_name], WindowManager::Get().WindowListGet(),\n                                                            data.ConfigBool[configid_bool_overlay_winrt_window_matching_strict]);\n\n        data.ConfigHandle[configid_handle_overlay_state_winrt_hwnd] = (uint64_t)window;\n\n        //If we found a new match, adjust last window title and update the overlay name later (we want to keep the old name if the window is gone though)\n        if (window != nullptr)\n        {\n            WindowInfo info(window);\n            data.ConfigStr[configid_str_overlay_winrt_last_window_title] = StringConvertFromUTF16(info.GetTitle().c_str());\n            //Exe & class name is not gonna change\n\n            do_set_auto_name = true;\n        }\n        else if (m_ConfigInt[configid_int_windows_winrt_capture_lost_behavior] == window_caplost_hide_overlay) //Treat not found windows as lost capture and hide them if setting active\n        {\n            data.ConfigBool[configid_bool_overlay_enabled] = false;\n        }\n    }\n\n    //If single desktop mirroring is active, set desktop ID to the first one's\n    if ( (current_id != 0) && (m_ConfigBool[configid_bool_performance_single_desktop_mirroring]) )\n    {\n        data.ConfigInt[configid_int_overlay_desktop_id] = OverlayManager::Get().GetConfigData(0).ConfigInt[configid_int_overlay_desktop_id];\n    }\n\n    //Default the transform matrix to zero\n    float matrix_zero[16] = { 0.0f };\n    data.ConfigTransform = matrix_zero;\n\n    std::string transform_str; //Only set these when it's really present in the file, or else it defaults to identity instead of zero\n    transform_str = config.ReadString(section.c_str(), \"Transform\");\n    if (!transform_str.empty())\n        data.ConfigTransform = transform_str;\n\n    #ifdef DPLUS_UI\n    //When loading an UI overlay, send config state over to ensure the correct process has rendering access even if the UI was restarted at some point\n    if (data.ConfigInt[configid_int_overlay_capture_source] == ovrl_capsource_ui)\n    {\n        IPCManager::Get().PostConfigMessageToDashboardApp(configid_int_state_overlay_current_id_override, (int)overlay_id);\n        IPCManager::Get().PostConfigMessageToDashboardApp(configid_int_overlay_capture_source, ovrl_capsource_ui);\n        IPCManager::Get().PostConfigMessageToDashboardApp(configid_int_state_overlay_current_id_override, -1);\n\n        UIManager::Get()->GetPerformanceWindow().ScheduleOverlaySharedTextureUpdate();\n    }\n    else if ( (data.ConfigInt[configid_int_overlay_capture_source] == ovrl_capsource_browser) && (!DPBrowserAPIClient::Get().IsBrowserAvailable()) )\n    {\n        //Set warning if no browser available but overlays using it are loaded\n        m_ConfigBool[configid_bool_state_misc_browser_used_but_missing] = true;\n        UIManager::Get()->UpdateAnyWarningDisplayedState();\n    }\n\n    //Set auto name if there's a new window match\n    if (do_set_auto_name)\n    {\n        OverlayManager::Get().SetCurrentOverlayNameAuto();\n    }\n\n    #endif //DPLUS_UI\n}\n\nvoid ConfigManager::SaveOverlayProfile(Ini& config, unsigned int overlay_id)\n{\n    const OverlayConfigData& data = OverlayManager::Get().GetCurrentConfigData();\n\n    std::stringstream ss;\n    ss << \"Overlay\" << overlay_id;\n\n    std::string section = ss.str();\n\n    config.WriteString(section.c_str(), \"Name\", data.ConfigNameStr.c_str());\n\n    config.WriteBool(section.c_str(),   \"NameIsCustom\",           data.ConfigBool[configid_bool_overlay_name_custom]);\n    config.WriteBool(section.c_str(),   \"Enabled\",                data.ConfigBool[configid_bool_overlay_enabled]);\n    config.WriteInt( section.c_str(),   \"DesktopID\",              data.ConfigInt[configid_int_overlay_desktop_id]);\n    config.WriteInt( section.c_str(),   \"CaptureSource\",          data.ConfigInt[configid_int_overlay_capture_source]);\n    config.WriteInt( section.c_str(),   \"DuplicationID\",          data.ConfigInt[configid_int_overlay_duplication_id]);\n    config.WriteInt( section.c_str(),   \"Width\",              int(data.ConfigFloat[configid_float_overlay_width]           * 100.0f));\n    config.WriteInt( section.c_str(),   \"Curvature\",          int(data.ConfigFloat[configid_float_overlay_curvature]       * 100.0f));\n    config.WriteInt( section.c_str(),   \"Opacity\",            int(data.ConfigFloat[configid_float_overlay_opacity]         * 100.0f));\n    config.WriteInt( section.c_str(),   \"Brightness\",         int(data.ConfigFloat[configid_float_overlay_brightness]      * 100.0f));\n    config.WriteInt( section.c_str(),   \"BrowserZoom\",        int(data.ConfigFloat[configid_float_overlay_browser_zoom]    * 100.0f));\n    config.WriteInt( section.c_str(),   \"OffsetRight\",        int(data.ConfigFloat[configid_float_overlay_offset_right]    * 100.0f));\n    config.WriteInt( section.c_str(),   \"OffsetUp\",           int(data.ConfigFloat[configid_float_overlay_offset_up]       * 100.0f));\n    config.WriteInt( section.c_str(),   \"OffsetForward\",      int(data.ConfigFloat[configid_float_overlay_offset_forward]  * 100.0f));\n    config.WriteInt( section.c_str(),   \"UserWidth\",              data.ConfigInt[configid_int_overlay_user_width]);\n    config.WriteInt( section.c_str(),   \"UserHeight\",             data.ConfigInt[configid_int_overlay_user_height]);\n    config.WriteInt( section.c_str(),   \"DisplayMode\",            data.ConfigInt[configid_int_overlay_display_mode]);\n    config.WriteString(section.c_str(), \"Origin\",                 GetConfigStringForOverlayOrigin((OverlayOrigin)data.ConfigInt[configid_int_overlay_origin]));\n    config.WriteBool(section.c_str(),   \"OriginHMDFloorTurning\",  data.ConfigBool[configid_bool_overlay_origin_hmd_floor_use_turning]);\n    config.WriteInt(section.c_str(),    \"OriginSmoothingLevel\",   data.ConfigInt[configid_int_overlay_origin_smoothing_level]);\n    config.WriteBool(section.c_str(),   \"TransformLocked\",        data.ConfigBool[configid_bool_overlay_transform_locked]);\n\n    config.WriteBool(section.c_str(), \"CroppingEnabled\",        data.ConfigBool[configid_bool_overlay_crop_enabled]);\n    config.WriteInt( section.c_str(), \"CroppingX\",              data.ConfigInt[configid_int_overlay_crop_x]);\n    config.WriteInt( section.c_str(), \"CroppingY\",              data.ConfigInt[configid_int_overlay_crop_y]);\n    config.WriteInt( section.c_str(), \"CroppingWidth\",          data.ConfigInt[configid_int_overlay_crop_width]);\n    config.WriteInt( section.c_str(), \"CroppingHeight\",         data.ConfigInt[configid_int_overlay_crop_height]);\n\n    config.WriteBool(section.c_str(),   \"ShowBackside\",           data.ConfigBool[configid_bool_overlay_show_backside]);\n    config.WriteBool(section.c_str(),   \"3DEnabled\",              data.ConfigBool[configid_bool_overlay_3D_enabled]);\n    config.WriteInt( section.c_str(),   \"3DMode\",                 data.ConfigInt[configid_int_overlay_3D_mode]);\n    config.WriteBool(section.c_str(),   \"3DSwapped\",              data.ConfigBool[configid_bool_overlay_3D_swapped]);\n    config.WriteBool(section.c_str(),   \"GazeFade\",               data.ConfigBool[configid_bool_overlay_gazefade_enabled]);\n    config.WriteInt( section.c_str(),   \"GazeFadeDistance\",   int(data.ConfigFloat[configid_float_overlay_gazefade_distance] * 100.0f));\n    config.WriteInt( section.c_str(),   \"GazeFadeRate\",       int(data.ConfigFloat[configid_float_overlay_gazefade_rate]     * 100.0f));\n    config.WriteInt( section.c_str(),   \"GazeFadeOpacity\",    int(data.ConfigFloat[configid_float_overlay_gazefade_opacity]  * 100.0f));\n    config.WriteInt( section.c_str(),   \"UpdateLimitModeOverride\",data.ConfigInt[configid_int_overlay_update_limit_override_mode]);\n    config.WriteInt( section.c_str(),   \"UpdateLimitMS\",      int(data.ConfigFloat[configid_float_overlay_update_limit_override_ms] * 100.0f));\n    config.WriteInt( section.c_str(),   \"UpdateLimitFPS\",         data.ConfigInt[configid_int_overlay_update_limit_override_fps]);\n    config.WriteInt( section.c_str(),   \"BrowserMaxFPSOverride\",  data.ConfigInt[configid_int_overlay_browser_max_fps_override]);\n    config.WriteBool(section.c_str(),   \"InputEnabled\",           data.ConfigBool[configid_bool_overlay_input_enabled]);\n    config.WriteBool(section.c_str(),   \"InputDPlusLPEnabled\",    data.ConfigBool[configid_bool_overlay_input_dplus_lp_enabled]);\n    config.WriteString(section.c_str(), \"Tags\",                   data.ConfigStr[configid_str_overlay_tags].c_str());\n    config.WriteBool(section.c_str(),   \"UpdateInvisible\",        data.ConfigBool[configid_bool_overlay_update_invisible]);\n\n    config.WriteBool(  section.c_str(), \"ShowFloatingUI\",          data.ConfigBool[configid_bool_overlay_floatingui_enabled]);\n    config.WriteBool(  section.c_str(), \"ShowDesktopButtons\",      data.ConfigBool[configid_bool_overlay_floatingui_desktops_enabled]);\n    config.WriteBool(  section.c_str(), \"ShowExtraButtons\",        data.ConfigBool[configid_bool_overlay_floatingui_extras_enabled]);\n    config.WriteBool(  section.c_str(), \"ShowActionBar\",           data.ConfigBool[configid_bool_overlay_actionbar_enabled]);\n    config.WriteBool(  section.c_str(), \"ActionBarOrderUseGlobal\", data.ConfigBool[configid_bool_overlay_actionbar_order_use_global]);\n    config.WriteString(section.c_str(), \"ActionBarOrderCustom\",    ActionManager::ActionOrderListToString(data.ConfigActionBarOrder).c_str());\n\n    config.WriteString(section.c_str(), \"Transform\", data.ConfigTransform.toString().c_str());\n\n    //Save WinRT Capture state\n    HWND window_handle = (HWND)data.ConfigHandle[configid_handle_overlay_state_winrt_hwnd];\n    std::string last_window_title, last_window_class_name, last_window_exe_name;\n\n    if (window_handle != nullptr)\n    {\n        WindowInfo info(window_handle);\n\n        last_window_title      = StringConvertFromUTF16(info.GetTitle().c_str());\n        last_window_class_name = StringConvertFromUTF16(info.GetWindowClassName().c_str());\n        last_window_exe_name   = info.GetExeName();\n    }\n\n    if (last_window_title.empty()) //Save last known title and exe name even when handle is nullptr or getting title failed so we can still restore the window on the next load if it happens to exist\n    {\n        last_window_title      = data.ConfigStr[configid_str_overlay_winrt_last_window_title];\n        last_window_class_name = data.ConfigStr[configid_str_overlay_winrt_last_window_class_name];\n        last_window_exe_name   = data.ConfigStr[configid_str_overlay_winrt_last_window_exe_name];\n    }\n\n    config.WriteString(section.c_str(), \"WinRTLastWindowTitle\",      last_window_title.c_str());\n    config.WriteString(section.c_str(), \"WinRTLastWindowClassName\",  last_window_class_name.c_str());\n    config.WriteString(section.c_str(), \"WinRTLastWindowExeName\",    last_window_exe_name.c_str());\n    config.WriteInt(   section.c_str(), \"WinRTDesktopID\",            data.ConfigInt[configid_int_overlay_winrt_desktop_id]);\n    config.WriteBool(  section.c_str(), \"WinRTWindowMatchingStrict\", data.ConfigBool[configid_bool_overlay_winrt_window_matching_strict]);\n\n    //Browser\n    config.WriteString(section.c_str(), \"BrowserURL\",               data.ConfigStr[configid_str_overlay_browser_url].c_str());\n    config.WriteString(section.c_str(), \"BrowserURLUserLast\",       data.ConfigStr[configid_str_overlay_browser_url_user_last].c_str());\n    config.WriteString(section.c_str(), \"BrowserTitle\",             data.ConfigStr[configid_str_overlay_browser_title].c_str());\n    config.WriteBool(  section.c_str(), \"BrowserAllowTransparency\", data.ConfigBool[configid_bool_overlay_browser_allow_transparency]);\n}\n\nbool ConfigManager::LoadConfigFromFile()\n{\n    LOG_F(INFO, \"Loading config...\");\n\n    //Prioritize config_newui.ini if it exists (will be deleted on save to rename)\n    bool using_config_newui_file = true;\n    std::wstring wpath = WStringConvertFromUTF8( std::string(m_ApplicationPath + \"config_newui.ini\").c_str() );\n\n    bool existed = FileExists(wpath.c_str());\n    if (!existed)\n    {\n        wpath = WStringConvertFromUTF8( std::string(m_ApplicationPath + \"config.ini\").c_str() );\n        existed = FileExists(wpath.c_str());\n        using_config_newui_file = false;\n    }\n\n    //If config.ini doesn't exist (yet), load from config_default.ini instead, which hopefully does (would still work to a lesser extent though)\n    if (!existed)\n    {\n        wpath = WStringConvertFromUTF8( std::string(m_ApplicationPath + \"config_default.ini\").c_str() );\n        LOG_F(INFO, \"Config file not found. Loading default config file instead\");\n    }\n\n    Ini config(wpath.c_str());\n\n    const int config_version = config.ReadInt(\"Misc\", \"ConfigVersion\", 1);\n\n    //If config is versioned below 2 (or unversioned, really), assume legacy config\n    //Though if we're loading from config_newui.ini (not legacy config but no version key yet), we're only doing the renaming, nothing else\n    if ((existed) && (config_version < 2))\n    {\n        #ifdef DPLUS_UI\n            MigrateLegacyConfig(config, using_config_newui_file);\n            //We need to save and restart dashboard process, but only after we read everything else\n        #else   //Only UI process writes files, so dashboard process will have to wait and restart later\n            LOG_IF_F(INFO, !using_config_newui_file, \"Legacy config detected, expecting UI process to migrate and request restart...\");\n        #endif\n    }\n\n    //Do the actual config reading\n    m_ConfigBool[configid_bool_interface_no_ui]                            = config.ReadBool(  \"Interface\", \"NoUIAutoLaunch\", false);\n    m_ConfigBool[configid_bool_interface_no_notification_icon]             = config.ReadBool(  \"Interface\", \"NoNotificationIcon\", false);\n    m_ConfigString[configid_str_interface_language_file]                   = config.ReadString(\"Interface\", \"LanguageFile\");\n    m_ConfigBool[configid_bool_interface_show_advanced_settings]           = config.ReadBool(  \"Interface\", \"ShowAdvancedSettings\", false);\n    m_ConfigBool[configid_bool_interface_large_style]                      = config.ReadBool(  \"Interface\", \"DisplaySizeLarge\", false);\n    m_ConfigInt[configid_int_interface_overlay_current_id]                 = config.ReadInt(   \"Interface\", \"OverlayCurrentID\", 0);\n    m_ConfigInt[configid_int_interface_desktop_listing_style]              = config.ReadInt(   \"Interface\", \"DesktopButtonCyclingMode\", desktop_listing_style_individual);\n    m_ConfigBool[configid_bool_interface_desktop_buttons_include_combined] = config.ReadBool(  \"Interface\", \"DesktopButtonIncludeAll\", false);\n\n    //Read color string as unsigned int but store it as signed\n    m_ConfigInt[configid_int_interface_background_color] = pun_cast<unsigned int, int>( std::stoul(config.ReadString(\"Interface\", \"EnvironmentBackgroundColor\", \"00000080\"), nullptr, 16) );\n\n    m_ConfigInt[configid_int_interface_background_color_display_mode]             = config.ReadInt( \"Interface\", \"EnvironmentBackgroundColorDisplayMode\", ui_bgcolor_dispmode_never);\n    m_ConfigBool[configid_bool_interface_dim_ui]                                  = config.ReadBool(\"Interface\", \"DimUI\", false);\n    m_ConfigBool[configid_bool_interface_blank_space_drag_enabled]                = config.ReadBool(\"Interface\", \"BlankSpaceDragEnabled\", true);\n    m_ConfigFloat[configid_float_interface_last_vr_ui_scale]                      = config.ReadInt( \"Interface\", \"LastVRUIScale\", 100) / 100.0f;\n    m_ConfigFloat[configid_float_interface_desktop_ui_scale_override]             = config.ReadInt( \"Interface\", \"DesktopUIScaleOverride\", 0) / 100.0f;\n    m_ConfigBool[configid_bool_interface_warning_compositor_res_hidden]           = config.ReadBool(\"Interface\", \"WarningCompositorResolutionHidden\",   false);\n    m_ConfigBool[configid_bool_interface_warning_compositor_quality_hidden]       = config.ReadBool(\"Interface\", \"WarningCompositorQualityHidden\",      false);\n    m_ConfigBool[configid_bool_interface_warning_process_elevation_hidden]        = config.ReadBool(\"Interface\", \"WarningProcessElevationHidden\",       false);\n    m_ConfigBool[configid_bool_interface_warning_elevated_mode_hidden]            = config.ReadBool(\"Interface\", \"WarningElevatedModeHidden\",           false);\n    m_ConfigBool[configid_bool_interface_warning_browser_missing_hidden]          = config.ReadBool(\"Interface\", \"WarningBrowserMissingHidden\",         false);\n    m_ConfigBool[configid_bool_interface_warning_browser_version_mismatch_hidden] = config.ReadBool(\"Interface\", \"WarningBrowserVersionMismatchHidden\", false);\n    m_ConfigBool[configid_bool_interface_warning_app_profile_active_hidden]       = config.ReadBool(\"Interface\", \"WarningAppProfileActiveHidden\",       false);\n    m_ConfigBool[configid_bool_interface_window_settings_restore_state]           = config.ReadBool(\"Interface\", \"WindowSettingsRestoreState\",   false);\n    m_ConfigBool[configid_bool_interface_window_properties_restore_state]         = config.ReadBool(\"Interface\", \"WindowPropertiesRestoreState\", false);\n    m_ConfigBool[configid_bool_interface_window_keyboard_restore_state]           = config.ReadBool(\"Interface\", \"WindowKeyboardRestoreState\",   true);\n    m_ConfigBool[configid_bool_interface_quick_start_hidden]                      = config.ReadBool(\"Interface\", \"QuickStartGuideHidden\",        false);\n    m_ConfigInt[configid_int_interface_wmr_ignore_vscreens]                       = config.ReadInt( \"Interface\", \"WMRIgnoreVScreens\", -1);\n\n    OverlayManager::Get().SetCurrentOverlayID(m_ConfigInt[configid_int_interface_overlay_current_id]);\n\n    #ifdef DPLUS_UI\n        TranslationManager::Get().LoadTranslationFromFile( m_ConfigString[configid_str_interface_language_file].c_str() );\n        LoadConfigPersistentWindowState(config);\n    #endif\n\n    m_ConfigHandle[configid_handle_input_go_home_action_uid] = std::strtoull(config.ReadString(\"Input\", \"GoHomeButtonActionUID\", \"0\").c_str(), nullptr, 10);\n    m_ConfigHandle[configid_handle_input_go_back_action_uid] = std::strtoull(config.ReadString(\"Input\", \"GoBackButtonActionUID\", \"0\").c_str(), nullptr, 10);\n\n    //Global Shortcuts\n    m_ConfigInt[configid_int_input_global_shortcuts_max_count] = config.ReadInt(\"Input\", \"GlobalShortcutsMaxCount\", 20);\n\n    m_ConfigGlobalShortcuts.clear();\n    int shortcut_id = 0;\n    for (;;)\n    {\n        std::stringstream ss;\n        ss << \"GlobalShortcut\" << std::setfill('0') << std::setw(2) << shortcut_id + 1 << \"ActionUID\";   //Naming pattern is backwards-compatible to legacy shortcut 01-06 entries\n\n        if (config.KeyExists(\"Input\", ss.str().c_str()))\n        {\n            m_ConfigGlobalShortcuts.push_back(std::strtoull(config.ReadString(\"Input\", ss.str().c_str(), \"0\").c_str(), nullptr, 10));\n        }\n        else\n        {\n            break;\n        }\n\n        ++shortcut_id;\n    }\n\n    if (m_ConfigGlobalShortcuts.empty())    //Enforce minimum one entry\n    {\n        m_ConfigGlobalShortcuts.emplace_back();\n    }\n\n    //Hotkeys\n    m_ConfigHotkey.clear();\n    int hotkey_id = 0;\n    for (;;)\n    {\n        std::stringstream ss;\n        ss << \"GlobalHotkey\" << std::setfill('0') << std::setw(2) << hotkey_id + 1;   //Naming pattern is backwards-compatible to legacy hotkey 01-03 entries\n\n        if (config.KeyExists(\"Input\", (ss.str() + \"Modifiers\").c_str()))\n        {\n            ConfigHotkey hotkey;\n            hotkey.Modifiers = config.ReadInt(\"Input\", (ss.str() + \"Modifiers\").c_str(), 0);\n            hotkey.KeyCode   = config.ReadInt(\"Input\", (ss.str() + \"KeyCode\"  ).c_str(), 0);\n            hotkey.ActionUID = std::strtoull(config.ReadString(\"Input\", (ss.str() + \"ActionUID\").c_str(), \"0\").c_str(), nullptr, 10);\n\n            m_ConfigHotkey.push_back(hotkey);\n        }\n        else\n        {\n            break;\n        }\n\n        ++hotkey_id;\n    }\n\n    if (m_ConfigHotkey.empty())    //Enforce minimum one entry\n    {\n        m_ConfigHotkey.emplace_back();\n    }\n\n    m_ConfigFloat[configid_float_input_detached_interaction_max_distance]   = config.ReadInt( \"Input\", \"DetachedInteractionMaxDistance\", 200) / 100.0f;\n    m_ConfigBool[configid_bool_input_laser_pointer_block_input]             = config.ReadBool(\"Input\", \"LaserPointerBlockInput\", false);\n    m_ConfigBool[configid_bool_input_laser_pointer_hmd_device]              = config.ReadBool(\"Input\", \"GlobalHMDPointer\", false);\n    m_ConfigInt[configid_int_input_laser_pointer_hmd_device_keycode_toggle] = config.ReadInt( \"Input\", \"LaserPointerHMDKeyCodeToggle\", 0);\n    m_ConfigInt[configid_int_input_laser_pointer_hmd_device_keycode_left]   = config.ReadInt( \"Input\", \"LaserPointerHMDKeyCodeLeft\",   0);\n    m_ConfigInt[configid_int_input_laser_pointer_hmd_device_keycode_right]  = config.ReadInt( \"Input\", \"LaserPointerHMDKeyCodeRight\",  0);\n    m_ConfigInt[configid_int_input_laser_pointer_hmd_device_keycode_middle] = config.ReadInt( \"Input\", \"LaserPointerHMDKeyCodeMiddle\", 0);\n    m_ConfigInt[configid_int_input_laser_pointer_hmd_device_keycode_drag]   = config.ReadInt( \"Input\", \"LaserPointerHMDKeyCodeDrag\",   0);\n\n    m_ConfigBool[configid_bool_input_drag_auto_docking]                     = config.ReadBool(\"Input\", \"DragAutoDocking\", true);\n    m_ConfigBool[configid_bool_input_drag_fixed_distance]                   = config.ReadBool(\"Input\", \"DragFixedDistance\", false);\n    m_ConfigFloat[configid_float_input_drag_fixed_distance_m]               = config.ReadInt( \"Input\", \"DragFixedDistanceCM\", 200) / 100.0f;\n    m_ConfigInt[configid_int_input_drag_fixed_distance_shape]               = config.ReadInt( \"Input\", \"DragFixedDistanceShape\", 0);\n    m_ConfigBool[configid_bool_input_drag_fixed_distance_auto_curve]        = config.ReadBool(\"Input\", \"DragFixedDistanceAutoCurve\", true);\n    m_ConfigBool[configid_bool_input_drag_fixed_distance_auto_tilt]         = config.ReadBool(\"Input\", \"DragFixedDistanceAutoTilt\", true);\n    m_ConfigBool[configid_bool_input_drag_snap_position]                    = config.ReadBool(\"Input\", \"DragSnapPosition\", false);\n    m_ConfigFloat[configid_float_input_drag_snap_position_size]             = config.ReadInt( \"Input\", \"DragSnapPositionSize\", 10) / 100.0f;\n    m_ConfigBool[configid_bool_input_drag_snap_rotation]                    = config.ReadBool(\"Input\", \"DragSnapRotation\",  false);\n    m_ConfigBool[configid_bool_input_drag_snap_rotation_x]                  = config.ReadBool(\"Input\", \"DragSnapRotationX\", true);\n    m_ConfigBool[configid_bool_input_drag_snap_rotation_y]                  = config.ReadBool(\"Input\", \"DragSnapRotationY\", true);\n    m_ConfigBool[configid_bool_input_drag_snap_rotation_z]                  = config.ReadBool(\"Input\", \"DragSnapRotationZ\", true);\n    m_ConfigInt[configid_int_input_drag_snap_rotation_angle]                = config.ReadInt( \"Input\", \"DragSnapRotationAngle\", 45);\n\n    m_ConfigBool[configid_bool_input_mouse_render_cursor]                   = config.ReadBool(\"Mouse\", \"RenderCursor\", true);\n    m_ConfigBool[configid_bool_input_mouse_render_intersection_blob]        = config.ReadBool(\"Mouse\", \"RenderIntersectionBlob\", false);\n    m_ConfigBool[configid_bool_input_mouse_scroll_smooth]                   = config.ReadBool(\"Mouse\", \"ScrollSmooth\", false);\n    m_ConfigBool[configid_bool_input_mouse_allow_pointer_override]          = config.ReadBool(\"Mouse\", \"AllowPointerOverride\", true);\n    m_ConfigBool[configid_bool_input_mouse_simulate_pen_input]              = config.ReadBool(\"Mouse\", \"SimulatePenInput\", false);\n    m_ConfigInt[configid_int_input_mouse_dbl_click_assist_duration_ms]      = config.ReadInt( \"Mouse\", \"DoubleClickAssistDuration\", -1);\n    m_ConfigInt[configid_int_input_mouse_input_smoothing_level]             = config.ReadInt( \"Mouse\", \"InputSmoothingLevel\", 0);\n\n    m_ConfigString[configid_str_input_keyboard_layout_file]                 = config.ReadString(\"Keyboard\", \"LayoutFile\", \"qwerty_usa.ini\");\n    m_ConfigBool[configid_bool_input_keyboard_cluster_function_enabled]     = config.ReadBool(\"Keyboard\", \"LayoutClusterFunction\",   true);\n    m_ConfigBool[configid_bool_input_keyboard_cluster_navigation_enabled]   = config.ReadBool(\"Keyboard\", \"LayoutClusterNavigation\", true);\n    m_ConfigBool[configid_bool_input_keyboard_cluster_numpad_enabled]       = config.ReadBool(\"Keyboard\", \"LayoutClusterNumpad\",     false);\n    m_ConfigBool[configid_bool_input_keyboard_cluster_extra_enabled]        = config.ReadBool(\"Keyboard\", \"LayoutClusterExtra\",      false);\n    m_ConfigBool[configid_bool_input_keyboard_sticky_modifiers]             = config.ReadBool(\"Keyboard\", \"StickyModifiers\",         true);\n    m_ConfigBool[configid_bool_input_keyboard_key_repeat]                   = config.ReadBool(\"Keyboard\", \"KeyRepeat\",               true);\n    m_ConfigBool[configid_bool_input_keyboard_auto_show_desktop]            = config.ReadBool(\"Keyboard\", \"AutoShowDesktop\",         true);\n    m_ConfigBool[configid_bool_input_keyboard_auto_show_browser]            = config.ReadBool(\"Keyboard\", \"AutoShowBrowser\",         true);\n\n    m_ConfigBool[configid_bool_windows_auto_focus_scene_app_dashboard]      = config.ReadBool(\"Windows\", \"AutoFocusSceneAppDashboard\", false);\n    m_ConfigBool[configid_bool_windows_winrt_auto_focus]                    = config.ReadBool(\"Windows\", \"WinRTAutoFocus\", true);\n    m_ConfigBool[configid_bool_windows_winrt_keep_on_screen]                = config.ReadBool(\"Windows\", \"WinRTKeepOnScreen\", true);\n    m_ConfigInt[configid_int_windows_winrt_dragging_mode]                   = config.ReadInt( \"Windows\", \"WinRTDraggingMode\", window_dragging_overlay);\n    m_ConfigBool[configid_bool_windows_winrt_auto_size_overlay]             = config.ReadBool(\"Windows\", \"WinRTAutoSizeOverlay\", false);\n    m_ConfigBool[configid_bool_windows_winrt_auto_focus_scene_app]          = config.ReadBool(\"Windows\", \"WinRTAutoFocusSceneApp\", false);\n    m_ConfigInt[configid_int_windows_winrt_capture_lost_behavior]           = config.ReadInt( \"Windows\", \"WinRTOnCaptureLost\", window_caplost_hide_overlay);\n\n    m_ConfigString[configid_str_browser_extra_arguments]                    = config.ReadString(\"Browser\", \"CommandLineArguments\");\n    m_ConfigInt[configid_int_browser_max_fps]                               = config.ReadInt(   \"Browser\", \"BrowserMaxFPS\", 60);\n    m_ConfigBool[configid_bool_browser_content_blocker]                     = config.ReadBool(  \"Browser\", \"BrowserContentBlocker\", false);\n\n    m_ConfigInt[configid_int_performance_update_limit_mode]                 = config.ReadInt( \"Performance\", \"UpdateLimitMode\", update_limit_mode_off);\n    m_ConfigFloat[configid_float_performance_update_limit_ms]               = config.ReadInt( \"Performance\", \"UpdateLimitMS\", 0) / 100.0f;\n    m_ConfigInt[configid_int_performance_update_limit_fps]                  = config.ReadInt( \"Performance\", \"UpdateLimitFPS\", update_limit_fps_30);\n    m_ConfigBool[configid_bool_performance_rapid_laser_pointer_updates]     = config.ReadBool(\"Performance\", \"RapidLaserPointerUpdates\", false);\n    m_ConfigBool[configid_bool_performance_single_desktop_mirroring]        = config.ReadBool(\"Performance\", \"SingleDesktopMirroring\", false);\n    m_ConfigBool[configid_bool_performance_alternative_cursor_rendering]    = config.ReadBool(\"Performance\", \"AlternativeCursorRendering\", false);\n    m_ConfigBool[configid_bool_performance_hdr_mirroring]                   = config.ReadBool(\"Performance\", \"HDRMirroring\", false);\n    m_ConfigBool[configid_bool_performance_show_fps]                        = config.ReadBool(\"Performance\", \"ShowFPS\", false);\n    m_ConfigBool[configid_bool_performance_ui_auto_throttle]                = config.ReadBool(\"Performance\", \"UIAutoThrottle\", true);\n    m_ConfigInt[configid_int_performance_ui_frameskip]                      = config.ReadInt( \"Performance\", \"UIFrameSkip\", 0);\n    m_ConfigBool[configid_bool_performance_monitor_minimal_style]           = config.ReadBool(\"Performance\", \"PerformanceMonitorStyleMinimal\", false);\n    m_ConfigBool[configid_bool_performance_monitor_large_style]             = config.ReadBool(\"Performance\", \"PerformanceMonitorStyleLarge\", true);\n    m_ConfigBool[configid_bool_performance_monitor_style_show_window]       = config.ReadBool(\"Performance\", \"PerformanceMonitorStyleShowWindow\", true);\n    m_ConfigBool[configid_bool_performance_monitor_style_show_text_outline] = config.ReadBool(\"Performance\", \"PerformanceMonitorStyleShowTextOutline\", false);\n    m_ConfigBool[configid_bool_performance_monitor_style_minimal_show_more] = config.ReadBool(\"Performance\", \"PerformanceMonitorStyleMinimalShowMore\", false);\n    m_ConfigBool[configid_bool_performance_monitor_show_graphs]             = config.ReadBool(\"Performance\", \"PerformanceMonitorShowGraphs\", true);\n    m_ConfigBool[configid_bool_performance_monitor_show_time]               = config.ReadBool(\"Performance\", \"PerformanceMonitorShowTime\", false);\n    m_ConfigBool[configid_bool_performance_monitor_show_cpu]                = config.ReadBool(\"Performance\", \"PerformanceMonitorShowCPU\", true);\n    m_ConfigBool[configid_bool_performance_monitor_show_gpu]                = config.ReadBool(\"Performance\", \"PerformanceMonitorShowGPU\", true);\n    m_ConfigBool[configid_bool_performance_monitor_show_fps]                = config.ReadBool(\"Performance\", \"PerformanceMonitorShowFPS\", true);\n    m_ConfigBool[configid_bool_performance_monitor_show_battery]            = config.ReadBool(\"Performance\", \"PerformanceMonitorShowBattery\", true);\n    m_ConfigBool[configid_bool_performance_monitor_show_trackers]           = config.ReadBool(\"Performance\", \"PerformanceMonitorShowTrackers\", true);\n    m_ConfigBool[configid_bool_performance_monitor_show_vive_wireless]      = config.ReadBool(\"Performance\", \"PerformanceMonitorShowViveWireless\", false);\n    m_ConfigBool[configid_bool_performance_monitor_disable_gpu_counters]    = config.ReadBool(\"Performance\", \"PerformanceMonitorDisableGPUCounters\", false);\n\n    m_ConfigBool[configid_bool_misc_no_steam]             = config.ReadBool(\"Misc\", \"NoSteam\", false);\n    m_ConfigBool[configid_bool_misc_uiaccess_was_enabled] = config.ReadBool(\"Misc\", \"UIAccessWasEnabled\", false);\n    m_ConfigInt[configid_int_misc_force_gpu_deviceid]     = config.ReadInt(\"Misc\", \"ForceGPUDeviceID\", -1);\n    m_ConfigInt[configid_int_misc_force_gpu_vr_deviceid]  = config.ReadInt(\"Misc\", \"ForceGPUVRDeviceID\", -1);\n\n    //Load actions\n    if (!m_ActionManager.LoadActionsFromFile())\n    {\n        MigrateLegacyActionsFromConfig(config);\n    }\n\n    //Load action order lists (needs to happen after load as the UIDs are validated for existence)\n    #ifdef DPLUS_UI\n        m_ActionManager.SetActionOrderListUI(         m_ActionManager.ActionOrderListFromString( config.ReadString(\"Interface\", \"ActionOrder\") ));\n        m_ActionManager.SetActionOrderListBarDefault( m_ActionManager.ActionOrderListFromString( config.ReadString(\"Interface\", \"ActionOrderBarDefault\") ));\n        m_ActionManager.SetActionOrderListOverlayBar( m_ActionManager.ActionOrderListFromString( config.ReadString(\"Interface\", \"ActionOrderOverlayBar\") ));\n    #endif\n\n    //Validate action IDs for controller bindings too\n    if (!m_ActionManager.ActionExists(m_ConfigHandle[configid_handle_input_go_home_action_uid]))\n    {\n        m_ConfigHandle[configid_handle_input_go_home_action_uid] = k_ActionUID_Invalid;\n    }\n    if (!m_ActionManager.ActionExists(m_ConfigHandle[configid_handle_input_go_back_action_uid]))\n    {\n        m_ConfigHandle[configid_handle_input_go_back_action_uid] = k_ActionUID_Invalid;\n    }\n\n    shortcut_id = 0;\n    for (ActionUID& uid : m_ConfigGlobalShortcuts)\n    {\n        if ((uid != k_ActionUID_Invalid) && (!m_ActionManager.ActionExists(uid)))\n        {\n            LOG_F(WARNING, \"Global Shortcut %02d is referencing unknown action %llu, resetting to [None]\", shortcut_id + 1, uid);\n            uid = k_ActionUID_Invalid;\n        }\n\n        ++shortcut_id;\n    }\n\n    //Validate hotkey ActionUIDs\n    hotkey_id = 0;\n    for (ConfigHotkey& hotkey : m_ConfigHotkey)\n    {\n        if ((hotkey.ActionUID != k_ActionUID_Invalid) && (!m_ActionManager.ActionExists(hotkey.ActionUID)))\n        {\n            LOG_F(WARNING, \"Hotkey %02d is referencing unknown action %llu, resetting to [None]\", hotkey_id + 1, hotkey.ActionUID);\n            hotkey.ActionUID = k_ActionUID_Invalid;\n        }\n\n        ++hotkey_id;\n    }\n\n    #ifndef DPLUS_UI\n        //Apply render cursor setting for WinRT Capture\n        if (DPWinRT_IsCaptureCursorEnabledPropertySupported())\n        {\n            DPWinRT_SetCaptureCursorEnabled(m_ConfigBool[configid_bool_input_mouse_render_cursor]);\n        }\n\n        DPWinRT_SetHDREnabled(m_ConfigBool[configid_bool_performance_hdr_mirroring]);\n\n        //Apply global settings for DPBrowser\n        if (DPBrowserAPIClient::Get().IsBrowserAvailable())\n        {\n            DPBrowserAPIClient::Get().DPBrowser_GlobalSetFPS(m_ConfigInt[configid_int_browser_max_fps]);\n            DPBrowserAPIClient::Get().DPBrowser_ContentBlockSetEnabled(m_ConfigBool[configid_bool_browser_content_blocker]);\n        }\n    #endif\n\n    //Set WindowManager active (no longer gets deactivated during runtime)\n    WindowManager::Get().SetActive(true);\n\n    //Query elevated mode state\n    m_ConfigBool[configid_bool_state_misc_elevated_mode_active] = IPCManager::IsElevatedModeProcessRunning();\n    LOG_IF_F(INFO, m_ConfigBool[configid_bool_state_misc_elevated_mode_active], \"Elevated mode is active\");\n\n    #ifdef DPLUS_UI\n        UIManager::Get()->GetSettingsWindow().ApplyCurrentOverlayState();\n        UIManager::Get()->GetOverlayPropertiesWindow().ApplyCurrentOverlayState();\n        UIManager::Get()->GetVRKeyboard().LoadCurrentLayout();\n        UIManager::Get()->GetVRKeyboard().GetWindow().ApplyCurrentOverlayState();\n\n        UIManager::Get()->UpdateAnyWarningDisplayedState();\n    #endif\n\n    //Load last used overlay config\n    LoadMultiOverlayProfile(config);\n\n    m_AppProfileManager.LoadProfilesFromFile();\n\n    LOG_F(INFO, \"Loaded config\");\n\n    //Save after loading if we did a config migration\n    #ifdef DPLUS_UI\n        if (m_ConfigBool[configid_bool_state_misc_config_migrated])\n        {\n            if (IPCManager::Get().IsDashboardAppRunning())\n            {\n                LOG_F(INFO, \"Legacy config migration finished, saving new config and restarting dashboard app...\");\n                SaveConfigToFile();\n                UIManager::Get()->RestartDashboardApp();\n            }\n            else\n            {\n                LOG_F(INFO, \"Legacy config migration finished, saving new config...\");\n                SaveConfigToFile();\n            }\n        }\n    #endif\n\n    return existed; //We use default values if it doesn't, but still return if the file existed\n}\n\nvoid ConfigManager::LoadMultiOverlayProfile(const Ini& config, bool clear_existing_overlays, std::vector<char>* ovrl_inclusion_list)\n{\n    unsigned int current_overlay_old = OverlayManager::Get().GetCurrentOverlayID();\n\n    if (clear_existing_overlays)\n    {\n        OverlayManager::Get().RemoveAllOverlays();\n\n        //Reset browser missing warning. It'll get set again if appropriate.\n        //Other cases are unhandled (e.g. only removing offending overlay) as it'd require littering checks everywhere for little use\n        m_ConfigBool[configid_bool_state_misc_browser_used_but_missing] = false;\n    }\n\n    const int config_version = config.ReadInt(\"Misc\", \"ConfigVersion\", 1);\n\n    unsigned int overlay_id = 0;\n\n    std::stringstream ss;\n    ss << \"Overlay\" << overlay_id;\n\n    //Load all sequential overlay sections that exist\n    while (config.SectionExists(ss.str().c_str()))\n    {\n        //Don't add if not in list (or none was passed)\n        if ( (ovrl_inclusion_list == nullptr) || (ovrl_inclusion_list->size() <= overlay_id) || ((*ovrl_inclusion_list)[overlay_id] != 0) )\n        {\n            OverlayManager::Get().DuplicateOverlay(OverlayConfigData());\n            OverlayManager::Get().SetCurrentOverlayID(OverlayManager::Get().GetOverlayCount() - 1);\n\n            LoadOverlayProfile(config, overlay_id);\n        }\n\n        overlay_id++;\n\n        ss = std::stringstream();\n        ss << \"Overlay\" << overlay_id;\n    }\n\n    OverlayManager::Get().SetCurrentOverlayID( std::min(current_overlay_old, (OverlayManager::Get().GetOverlayCount() == 0) ? k_ulOverlayID_None : OverlayManager::Get().GetOverlayCount() - 1) );\n}\n\nvoid ConfigManager::SaveMultiOverlayProfile(Ini& config, std::vector<char>* ovrl_inclusion_list)\n{\n    //Remove single overlay section in case it still exists\n    config.RemoveSection(\"Overlay\");\n\n    unsigned int overlay_id = 0;\n    std::stringstream ss;\n    ss << \"Overlay\" << overlay_id;\n\n    //Remove all sequential overlay sections that exist first\n    while (config.SectionExists(ss.str().c_str()))\n    {\n        config.RemoveSection(ss.str().c_str());\n\n        overlay_id++;\n\n        ss = std::stringstream();\n        ss << \"Overlay\" << overlay_id;\n    }\n\n    config.WriteInt(\"Misc\", \"ConfigVersion\", k_nDesktopPlusConfigVersion);\n\n    unsigned int current_overlay_old = OverlayManager::Get().GetCurrentOverlayID();\n    overlay_id = 0;\n\n    //Save all overlays in separate sections\n    for (unsigned int i = 0; i < OverlayManager::Get().GetOverlayCount(); ++i)\n    {\n        //Don't save if not in list (or none was passed)\n        if ( (ovrl_inclusion_list == nullptr) || (ovrl_inclusion_list->size() <= i) || ((*ovrl_inclusion_list)[i] != 0) )\n        {\n            OverlayManager::Get().SetCurrentOverlayID(i);\n\n            SaveOverlayProfile(config, overlay_id);\n            overlay_id++;\n        }\n    }\n\n    OverlayManager::Get().SetCurrentOverlayID(current_overlay_old);\n}\n\n#ifdef DPLUS_UI\n\nvoid ConfigManager::LoadConfigPersistentWindowState(Ini& config)\n{\n    //Load persistent UI window state (not stored in config variables)\n    FloatingWindow* const windows[]  = { &UIManager::Get()->GetSettingsWindow(), &UIManager::Get()->GetOverlayPropertiesWindow(), &UIManager::Get()->GetVRKeyboard().GetWindow() };\n    const char* const window_names[] = { \"WindowSettings\", \"WindowProperties\", \"WindowKeyboard\" };\n\n    for (size_t i = 0; i < IM_ARRAYSIZE(windows); ++i)\n    {\n        FloatingWindow& window = *windows[i];\n        FloatingWindowOverlayState& ovrl_state_room          = window.GetOverlayState(floating_window_ovrl_state_room);\n        FloatingWindowOverlayState& ovrl_state_dashboard_tab = window.GetOverlayState(floating_window_ovrl_state_dashboard_tab);\n        const std::string key_base = window_names[i];\n        std::string transform_str;\n\n        //Skip if set to not restore state\n        if (!m_ConfigBool[configid_bool_interface_window_settings_restore_state + i])\n            continue;\n\n        ovrl_state_room.IsVisible = config.ReadBool(  \"Interface\",  (key_base + \"RoomVisible\").c_str(), false);\n        ovrl_state_room.IsPinned  = config.ReadBool(  \"Interface\",  (key_base + \"RoomPinned\").c_str(),  false);\n        ovrl_state_room.Size      = config.ReadInt(   \"Interface\",  (key_base + \"RoomSize\").c_str(),      100) / 100.0f;\n        transform_str             = config.ReadString(\"Interface\",  (key_base + \"RoomTransform\").c_str());\n\n        if (!transform_str.empty())\n        {\n            (ovrl_state_room.IsPinned) ? ovrl_state_room.TransformAbs = transform_str : ovrl_state_room.Transform = transform_str;\n        }\n\n        ovrl_state_dashboard_tab.IsVisible = config.ReadBool(  \"Interface\",  (key_base + \"DashboardTabVisible\").c_str(), false);\n        ovrl_state_dashboard_tab.IsPinned  = config.ReadBool(  \"Interface\",  (key_base + \"DashboardTabPinned\").c_str(),  false);\n        ovrl_state_dashboard_tab.Size      = config.ReadInt(   \"Interface\",  (key_base + \"DashboardTabSize\").c_str(),      100) / 100.0f;\n        transform_str                      = config.ReadString(\"Interface\",  (key_base + \"DashboardTabTransform\").c_str());\n\n        if (!transform_str.empty())\n        {\n            (ovrl_state_dashboard_tab.IsPinned) ? ovrl_state_dashboard_tab.TransformAbs = transform_str : ovrl_state_dashboard_tab.Transform = transform_str;\n        }\n\n        //Show window if it should be visible. For most windows this has no effect, but the keyboard one has overridden Show() for example.\n        if ( ((window.GetOverlayStateCurrentID() == floating_window_ovrl_state_room)          && (ovrl_state_room.IsVisible)) ||\n             ((window.GetOverlayStateCurrentID() == floating_window_ovrl_state_dashboard_tab) && (ovrl_state_dashboard_tab.IsVisible)) )\n        {\n            window.Show();\n        }\n    }\n\n    //Load other specific window state\n    //SetActiveOverlayID() will not be effective if the config load happens before overlays were loaded (so during startup), \n    //but it is also called again by UIManager::OnInitDone() on the ID set here, so it still works\n    int last_active_overlay_id = config.ReadInt(\"Interface\", \"WindowPropertiesLastOverlayID\", -1);\n    UIManager::Get()->GetOverlayPropertiesWindow().SetActiveOverlayID((last_active_overlay_id != -1) ? (unsigned int)last_active_overlay_id : k_ulOverlayID_None, true);\n\n    //Only room state's is saved/restored here\n    UIManager::Get()->GetVRKeyboard().GetWindow().SetAssignedOverlayID(config.ReadInt(\"Interface\", \"WindowKeyboardLastAssignedOverlayID\", -1), floating_window_ovrl_state_room);\n}\n\nvoid ConfigManager::SaveConfigPersistentWindowState(Ini& config)\n{\n    //Save persistent UI window state (not stored in config variables)\n    FloatingWindow* const windows[]  = { &UIManager::Get()->GetSettingsWindow(), &UIManager::Get()->GetOverlayPropertiesWindow(), &UIManager::Get()->GetVRKeyboard().GetWindow() };\n    const char* const window_names[] = { \"WindowSettings\", \"WindowProperties\", \"WindowKeyboard\" };\n\n    for (size_t i = 0; i < IM_ARRAYSIZE(windows); ++i)\n    {\n        const bool is_keyboard = (i == 2);\n        const FloatingWindowOverlayState& ovrl_state_room          = windows[i]->GetOverlayState(floating_window_ovrl_state_room);\n        const FloatingWindowOverlayState& ovrl_state_dashboard_tab = windows[i]->GetOverlayState(floating_window_ovrl_state_dashboard_tab);\n        std::string key_base = window_names[i];\n\n        const Matrix4& transform_room = (ovrl_state_room.IsPinned) ? ovrl_state_room.TransformAbs : ovrl_state_room.Transform;\n        config.WriteBool(  \"Interface\",  (key_base + \"RoomVisible\").c_str(),       ovrl_state_room.IsVisible);\n        config.WriteBool(  \"Interface\",  (key_base + \"RoomPinned\").c_str(),        ovrl_state_room.IsPinned);\n        config.WriteInt(   \"Interface\",  (key_base + \"RoomSize\").c_str(),      int(ovrl_state_room.Size * 100.0f));\n        config.WriteString(\"Interface\",  (key_base + \"RoomTransform\").c_str(),     transform_room.toString().c_str());\n\n        const Matrix4& transform_dashboard_tab = (ovrl_state_dashboard_tab.IsPinned) ? ovrl_state_dashboard_tab.TransformAbs : ovrl_state_dashboard_tab.Transform;\n        config.WriteBool(  \"Interface\",  (key_base + \"DashboardTabVisible\").c_str(),       ovrl_state_dashboard_tab.IsVisible);\n        config.WriteBool(  \"Interface\",  (key_base + \"DashboardTabPinned\").c_str(),        ovrl_state_dashboard_tab.IsPinned);\n        config.WriteInt(   \"Interface\",  (key_base + \"DashboardTabSize\").c_str(),      int(ovrl_state_dashboard_tab.Size * 100.0f));\n        config.WriteString(\"Interface\",  (key_base + \"DashboardTabTransform\").c_str(),     transform_dashboard_tab.toString().c_str());\n    }\n\n    //Store other specific window state\n    unsigned int last_active_overlay_id = UIManager::Get()->GetOverlayPropertiesWindow().GetActiveOverlayID();\n    config.WriteInt(\"Interface\", \"WindowPropertiesLastOverlayID\", (last_active_overlay_id != k_ulOverlayID_None) ? (int)last_active_overlay_id : -1);\n\n    //Only room state's is saved/restored here\n    config.WriteInt(\"Interface\", \"WindowKeyboardLastAssignedOverlayID\", UIManager::Get()->GetVRKeyboard().GetWindow().GetAssignedOverlayID(floating_window_ovrl_state_room));\n}\n\nvoid ConfigManager::MigrateLegacyConfig(Ini& config, bool only_rename_config_file)\n{\n    if (!only_rename_config_file)\n    {\n        LOG_F(INFO, \"Migrating legacy config...\");\n\n        //Do action migration first so we have the legacy ActionID to ActionUID mapping from it\n        LegacyActionIDtoActionUID legacy_id_to_uid;\n        if (!m_ActionManager.LoadActionsFromFile())\n        {\n            legacy_id_to_uid = MigrateLegacyActionsFromConfig(config);\n        }\n\n        //Rename and migrate action order key\n        config.WriteString(\"Interface\", \"ActionOrderBarDefault\", MigrateLegacyActionOrderString(config.ReadString(\"Interface\", \"ActionOrder\"), legacy_id_to_uid).c_str() );\n        config.RemoveKey(\"Interface\", \"ActionOrder\");\n\n        //Nothing else really has to be adapted from the global config, but we need to migrate every overlay profile\n        bool apply_steamvr2_dashboard_offset = config.ReadBool(\"Misc\", \"ApplySteamVR2DashboardOffset\", true);\n\n        LOG_F(INFO, \"Migrating legacy global overlay profile...\");\n        MigrateLegacyOverlayProfileFromConfig(config, apply_steamvr2_dashboard_offset, legacy_id_to_uid);\n\n        //We need to traverse all existing overlay profiles, migrate them and save them to the new file structure\n        const std::wstring wpath_dest   =  WStringConvertFromUTF8( std::string(m_ApplicationPath + \"profiles/\"               ).c_str() );\n        const std::wstring wpath_src[2] = {WStringConvertFromUTF8( std::string(m_ApplicationPath + \"profiles/overlays/\"      ).c_str() ),\n                                           WStringConvertFromUTF8( std::string(m_ApplicationPath + \"profiles/multi-overlays/\").c_str() )};\n        const std::wstring wpath_sub[2] = {L\" (single-overlay)\", L\" (multi-overlay)\"};\n\n        for (int i = 0; i < 2; ++i)\n        {\n            WIN32_FIND_DATA find_data = {};\n            HANDLE handle_find = ::FindFirstFileW((wpath_src[i] + L\"*.ini\").c_str(), &find_data);\n\n            if (handle_find != INVALID_HANDLE_VALUE)\n            {\n                do\n                {\n                    LOG_F(INFO, \"Migrating legacy overlay profile \\\"%s\\\"...\", StringConvertFromUTF16(find_data.cFileName).c_str());\n\n                    Ini config_profile(wpath_src[i] + find_data.cFileName);\n                    MigrateLegacyOverlayProfileFromConfig(config_profile, apply_steamvr2_dashboard_offset, legacy_id_to_uid);\n\n                    std::wstring save_path = wpath_dest + find_data.cFileName;\n\n                    //Avoid overwriting anything existing\n                    if (FileExists(save_path.c_str()))\n                    {\n                        //Try just adding a subfolder identifier\n                        std::wstring profile_name_with_sub = std::wstring(find_data.cFileName).substr(0, save_path.size() - 4);\n                        profile_name_with_sub += wpath_sub[i];\n\n                        std::wstring filename_log = profile_name_with_sub + L\".ini\";\n                        std::wstring save_path_with_sub = wpath_dest + filename_log;\n\n                        int duplicate_count = 1;\n                        while (FileExists(save_path_with_sub.c_str()))\n                        {\n                            //Add numbers if we really still run into conflicts\n                            duplicate_count++;\n\n                            filename_log = profile_name_with_sub + L\"(\" + std::to_wstring(duplicate_count) + L\")\" + L\".ini\";\n                            save_path_with_sub = wpath_dest + filename_log;\n                        }\n\n                        save_path = save_path_with_sub;\n                        LOG_F(INFO, \"Profile already existed in new location, saving as \\\"%s\\\" instead...\", StringConvertFromUTF16(filename_log.c_str()).c_str());\n                    }\n\n                    config_profile.Save(save_path);\n                }\n                while (::FindNextFileW(handle_find, &find_data) != 0);\n\n                ::FindClose(handle_find);\n            }\n        }\n    }\n\n    //Rename old config.ini to config_legacy.ini, which has loading priority in 2.8+ versions\n    const std::wstring wpath_config = WStringConvertFromUTF8( std::string(m_ApplicationPath + \"config.ini\").c_str() );\n    if (FileExists(wpath_config.c_str()))\n    {\n        ::MoveFileW(wpath_config.c_str(), WStringConvertFromUTF8( std::string(m_ApplicationPath + \"config_legacy.ini\").c_str() ).c_str());\n        LOG_F(INFO, \"Renamed legacy \\\"config.ini\\\" to \\\"config_legacy.ini\\\". Desktop+ 2.x will still be able to load this file\");\n    }\n\n    if (!only_rename_config_file)\n    {\n        m_ConfigBool[configid_bool_state_misc_config_migrated] = true;\n    }\n}\n\nvoid ConfigManager::MigrateLegacyOverlayProfileFromConfig(Ini& config, bool apply_steamvr2_dashboard_offset, LegacyActionIDtoActionUID& legacy_id_to_uid)\n{\n    //Rename unnumbered \"Overlay\" section if it exists (legacy single overlay profile)\n    config.RenameSection(\"Overlay\", \"Overlay0\");\n\n    unsigned int overlay_id = 0;\n\n    std::stringstream ss;\n    ss << \"Overlay\" << overlay_id;\n\n    std::string section = ss.str();\n\n    //Migrate all sequential overlay sections that exist\n    while (config.SectionExists(section.c_str()))\n    {\n        //Set 3D enabled value to true if any 3D is active\n        config.WriteBool(section.c_str(), \"3DEnabled\", (config.ReadInt(section.c_str(), \"3DMode\", 0) != 0) );\n\n        //Create tag string for group ID\n        int overlay_group_id = config.ReadInt(section.c_str(), \"GroupID\", 0);\n        if (overlay_group_id != 0)\n        {\n            std::string tag_str = \"OVRL_GROUP_\" + std::to_string(overlay_group_id);\n            config.WriteString(section.c_str(), \"Tags\", tag_str.c_str());\n        }\n\n        //0-Dashboard overlay had no Floating UI, always only showing the Action Bar, but didn't make use of the property value which defaulted and stayed false, so force it to true\n        if (overlay_id == 0)\n        {\n            config.WriteBool(section.c_str(), \"ShowActionBar\", true);\n        }\n\n        //Write Display Mode to new config string (0 is always Desktop+ tab)\n        config.WriteInt(section.c_str(), \"DisplayMode\", (overlay_id == 0) ? ovrl_dispmode_dplustab : config.ReadInt(section.c_str(), \"DetachedDisplayMode\", ovrl_dispmode_always) );\n\n        //Write Origin to new config string in new format (0 is always dashboard)\n        const OverlayOrigin transform_origin = (overlay_id == 0) ? ovrl_origin_dashboard : GetOverlayOriginFromConfigString(config.ReadString(section.c_str(), \"DetachedOrigin\"));\n        config.WriteString(section.c_str(), \"Origin\", GetConfigStringForOverlayOrigin(transform_origin) );\n\n        //Write single transform string, chosen by active origin\n        std::string transform_str;\n\n        switch (transform_origin)\n        {\n            case ovrl_origin_room:\n            {\n                transform_str = config.ReadString(section.c_str(), \"DetachedTransformPlaySpace\");\n                break;\n            }\n            case ovrl_origin_hmd_floor:\n            {\n                transform_str = config.ReadString(section.c_str(), \"DetachedTransformHMDFloor\");\n                break;\n            }\n            case ovrl_origin_seated_universe:\n            {\n                transform_str = config.ReadString(section.c_str(), \"DetachedTransformSeatedPosition\");\n                break;\n            }\n            case ovrl_origin_dashboard:\n            {\n                //Overlay 0 is always dashboard with no transform saved, set to identity scaled to counteract dashboard scale\n                if (overlay_id == 0)\n                {\n                    Matrix4 matrix;\n                    matrix.scale(2.08464f);  //We don't have OpenVR initialized at this step, but this hardcoded value will do the job for the gamepadui dashboard\n                    transform_str = matrix.toString();\n                }\n                else\n                {\n                    transform_str = config.ReadString(section.c_str(), \"DetachedTransformDashboard\");\n\n                    //Try to convert transforms that have implicit offsets used with apply_steamvr2_dashboard_offset disabled\n                    if (!apply_steamvr2_dashboard_offset)\n                    {\n                        Matrix4 matrix(transform_str);\n\n                        //Magic number, from taking the difference of both version's dashboard origins at the same HMD position\n                        Matrix4 matrix_to_old_dash(1.14634132f,      3.725290300e-09f, -3.725290300e-09f, 0.00000000f,\n                                                   0.00000000f,      0.878148496f,      0.736854136f,     0.00000000f,\n                                                   7.45058060e-09f, -0.736854076f,      0.878148496f,     0.00000000f,\n                                                  -5.96046448e-08f,  2.174717430f,      0.123533726f,     1.00000000f);\n\n                        //Move transform roughly back to where it was in the old dashboard\n                        matrix_to_old_dash.invert();\n                        matrix = matrix_to_old_dash * matrix;\n\n                        //Try to compensate for origin point differences\n                        //Without being able to know the overlay content height this is only an approximation for 16:9 overlays\n                        float width = clamp(config.ReadInt(section.c_str(), \"Width\", 165) / 100.0f, 0.00001f, 1000.0f);\n                        matrix.translate_relative(0.00f, 1.22f - (width * 0.5625f * 0.5f), -0.12f);\n\n                        transform_str = matrix.toString();\n                    }\n                }\n\n                break;\n            }\n            case ovrl_origin_hmd:\n            {\n                transform_str = config.ReadString(section.c_str(), \"DetachedTransformHMD\");\n                break;\n            }\n            case ovrl_origin_left_hand:\n            {\n                transform_str = config.ReadString(section.c_str(), \"DetachedTransformLeftHand\");\n                break;\n            }\n            case ovrl_origin_right_hand:\n            {\n                transform_str = config.ReadString(section.c_str(), \"DetachedTransformRightHand\");\n                break;\n            }\n            case ovrl_origin_aux:\n            {\n                transform_str = config.ReadString(section.c_str(), \"DetachedTransformAux\");\n                break;\n            }\n        }\n\n        //Only write transform when it was really present in the file, or else it defaults to identity instead of zero\n        if (!transform_str.empty())\n        {\n            config.WriteString(section.c_str(), \"Transform\", transform_str.c_str());\n        }\n\n        config.WriteString(section.c_str(), \"ActionBarOrderCustom\", MigrateLegacyActionOrderString(config.ReadString(section.c_str(), \"ActionBarOrderCustom\"), legacy_id_to_uid).c_str() );\n\n        overlay_id++;\n\n        std::stringstream ss;\n        ss << \"Overlay\" << overlay_id;\n\n        section = ss.str();\n    }\n\n    //Remove obsolte keys\n    //Other obsolete values may remain in the file but will be cleared the next time the profile is saved properly, so it's not really an issue\n    config.RemoveKey(section.c_str(), \"GroupID\");\n    config.RemoveKey(section.c_str(), \"DetachedDisplayMode\");\n    config.RemoveKey(section.c_str(), \"DetachedOrigin\");\n    config.RemoveKey(section.c_str(), \"DetachedTransformPlaySpace\");\n    config.RemoveKey(section.c_str(), \"DetachedTransformHMDFloor\");\n    config.RemoveKey(section.c_str(), \"DetachedTransformSeatedPosition\");\n    config.RemoveKey(section.c_str(), \"DetachedTransformDashboard\");\n    config.RemoveKey(section.c_str(), \"DetachedTransformHMD\");\n    config.RemoveKey(section.c_str(), \"DetachedTransformLeftHand\");\n    config.RemoveKey(section.c_str(), \"DetachedTransformRightHand\");\n    config.RemoveKey(section.c_str(), \"DetachedTransformAux\");\n}\n\nstd::string ConfigManager::MigrateLegacyActionOrderString(const std::string& order_str, LegacyActionIDtoActionUID& legacy_id_to_uid)\n{\n    //Migrate action order legacy IDs to UIDs and new string format\n    std::stringstream ss(order_str);\n    std::stringstream ss_out;\n    int id;\n    bool visible;\n    char sep;\n\n    for (;;)\n    {\n        ss >> id >> visible >> sep;\n\n        if (ss.fail())\n            break;\n\n        //Invisible/unselected actions are omitted in the new format\n        if (visible)\n        {\n            const ActionUID uid = legacy_id_to_uid[id];\n\n            if (uid != k_ActionUID_Invalid)\n            {\n                ss_out << legacy_id_to_uid[id] << ';';\n            }\n        }\n    }\n\n    return ss_out.str();\n}\n\n#endif //ifdef DPLUS_UI\n\nConfigManager::LegacyActionIDtoActionUID ConfigManager::MigrateLegacyActionsFromConfig(const Ini& config)\n{\n    //Add new defaults first\n    m_ActionManager.RestoreActionsFromDefault();\n\n    //Skip rest if CustomActions section doesn't exist (nothing to migrate)\n    if (!config.SectionExists(\"CustomActions\"))\n        return ConfigManager::LegacyActionIDtoActionUID();\n\n    LOG_F(INFO, \"Migrating legacy actions...\");\n\n    //Read legacy custom actions and create actions with equivalent commands\n    ConfigManager::LegacyActionIDtoActionUID legacy_id_to_uid;\n\n    //There's no surefire way to detect old default custom actions, but we at least match the old names and ignore them during migration to avoid double entries\n    const char* default_names[] = \n    {\n        \"Middle Mouse Button\",\n        \"Back Mouse Button\",\n        \"\\xE2\\x80\\x89\\xE2\\x80\\x89Open ReadMe\",  //Used two \"thin space\" characters for alignment\n        \"tstr_DefActionMiddleMouse\",\n        \"tstr_DefActionBackMouse\",\n        \"tstr_DefActionReadMe\"\n    };\n\n    //Map legacy built-in IDs to now default custom actions (same ID is still required as the mapping will default to 0 if unset)\n    legacy_id_to_uid.insert({1, 1}); //Show Keyboard\n    legacy_id_to_uid.insert({2, 2}); //Crop to Active Window\n    legacy_id_to_uid.insert({3, 4}); //Toggle Overlay Group 1 (these all just migrate to the \"toggle all\" default since they need manual setup anyhow)\n    legacy_id_to_uid.insert({4, 4}); //Toggle Overlay Group 2\n    legacy_id_to_uid.insert({5, 4}); //Toggle Overlay Group 3\n    legacy_id_to_uid.insert({6, 3}); //Switch Task\n\n    int custom_action_count = config.ReadInt(\"CustomActions\", \"Count\", 0);\n\n    for (int i = 0; i < custom_action_count; ++i)\n    {\n        std::string action_ini_name = \"Action\" + std::to_string(i);\n\n        Action action;\n        action.Name = config.ReadString(\"CustomActions\", (action_ini_name + \"Name\").c_str(), action_ini_name.c_str());\n\n        //Skip if name matches legacy default ones\n        const auto it = std::find(std::begin(default_names), std::end(default_names), action.Name);\n        if (it != std::end(default_names))\n        {\n            //Map detected legacy default name to new default IDs (a bit flaky but the name array won't change)\n            size_t default_name_id = std::distance(std::begin(default_names), it);\n            const int action_id_custom_base = 1000; //Old value of action_custom, minimum custon ID\n\n            if ((default_name_id == 0) || (default_name_id == 3))\n            {\n                legacy_id_to_uid.insert({action_id_custom_base + i, 5});\n            }\n            else if ((default_name_id == 1) || (default_name_id == 4))\n            {\n                legacy_id_to_uid.insert({action_id_custom_base + i, 6});\n            }\n            else if ((default_name_id == 2) || (default_name_id == 5))\n            {\n                legacy_id_to_uid.insert({action_id_custom_base + i, 7});\n            }\n\n            continue;\n        }\n\n        action.UID = m_ActionManager.GenerateUID();\n        legacy_id_to_uid.insert({i, action.UID});\n\n        const std::string function_type_str = config.ReadString(\"CustomActions\", (action_ini_name + \"FunctionType\").c_str());\n        if (function_type_str == \"PressKeys\")\n        {\n            ActionCommand command;\n            command.Type = ActionCommand::command_key;\n            command.UIntArg = config.ReadBool(\"CustomActions\", (action_ini_name + \"ToggleKeys\").c_str(), false);\n\n            command.UIntID = config.ReadInt( \"CustomActions\", (action_ini_name + \"KeyCode1\").c_str(), 0);\n            action.Commands.push_back(command);\n            command.UIntID = config.ReadInt( \"CustomActions\", (action_ini_name + \"KeyCode2\").c_str(), 0);\n            action.Commands.push_back(command);\n            command.UIntID = config.ReadInt( \"CustomActions\", (action_ini_name + \"KeyCode3\").c_str(), 0);\n            action.Commands.push_back(command);\n        }\n        else if (function_type_str == \"TypeString\")\n        {\n            ActionCommand command;\n            command.Type = ActionCommand::command_string;\n            command.StrMain = config.ReadString(\"CustomActions\", (action_ini_name + \"TypeString\").c_str());\n\n            action.Commands.push_back(command);\n        }\n        else if (function_type_str == \"LaunchApplication\")\n        {\n            ActionCommand command;\n            command.Type = ActionCommand::command_launch_app;\n            command.StrMain = config.ReadString(\"CustomActions\", (action_ini_name + \"ExecutablePath\").c_str());\n            command.StrArg  = config.ReadString(\"CustomActions\", (action_ini_name + \"ExecutableArg\").c_str());\n\n            action.Commands.push_back(command);\n        }\n        else if (function_type_str == \"ToggleOverlayEnabledState\")\n        {\n            //This function can't be migrated 1:1, but we leave a tag with original intent at least\n            ActionCommand command;\n            command.Type = ActionCommand::command_show_overlay;\n            command.StrMain = std::string(\"Overlay_\") + std::to_string( config.ReadInt(\"CustomActions\", (action_ini_name + \"OverlayID\").c_str(), 0) );\n            command.UIntArg = ActionCommand::command_arg_toggle;\n            command.UIntID  = MAKELPARAM(true, false);\n\n            action.Commands.push_back(command);\n        }\n\n        #ifdef DPLUS_UI\n            action.IconFilename = config.ReadString(\"CustomActions\", (action_ini_name + \"IconFilename\").c_str());\n\n            //Remove folder from path as it's no longer part of the string\n            if (action.IconFilename.find(\"images/icons/\") == 0)\n            {\n                action.IconFilename = action.IconFilename.substr(sizeof(\"images/icons/\") - 1);\n            }\n\n            action.NameTranslationID  = ActionManager::GetTranslationIDForName(action.Name);\n            action.LabelTranslationID = ActionManager::GetTranslationIDForName(action.Label);\n        #endif\n\n        m_ActionManager.StoreAction(action);\n    }\n\n    //Adapt references to legacy actions if possible (this might fail for the default ones though)\n    m_ConfigHandle[configid_handle_input_go_home_action_uid] = legacy_id_to_uid[config.ReadInt(\"Input\", \"GoHomeButtonActionID\", 0)];\n    m_ConfigHandle[configid_handle_input_go_back_action_uid] = legacy_id_to_uid[config.ReadInt(\"Input\", \"GoBackButtonActionID\", 0)];\n\n    for (size_t i = 0; i < 2; ++i)\n    {\n        if (i < m_ConfigGlobalShortcuts.size())\n        {\n            std::string config_name = \"GlobalShortcut0\" + std::to_string(i + 1) + \"ActionID\";\n            m_ConfigGlobalShortcuts[i] = legacy_id_to_uid[config.ReadInt(\"Input\", config_name.c_str(), 0)];\n        }\n    }\n\n    for (size_t i = 0; i < 2; ++i)\n    {\n        if (i < m_ConfigHotkey.size())\n        {\n            std::string config_name = \"GlobalHotkey0\" + std::to_string(i + 1) + \"ActionID\";\n            m_ConfigHotkey[i].ActionUID = legacy_id_to_uid[config.ReadInt(\"Input\", config_name.c_str(), 0)];\n        }\n    }\n\n    //Return the action ID mapping so action order can be migrated from it\n    return legacy_id_to_uid;\n}\n\nOverlayOrigin ConfigManager::GetOverlayOriginFromConfigString(const std::string& str)\n{\n    const auto it = std::find_if(std::begin(g_OvrlOriginConfigFileStrings), std::end(g_OvrlOriginConfigFileStrings), [&](const auto& pair){ return (pair.second == str); });\n\n    if (it != std::end(g_OvrlOriginConfigFileStrings))\n    {\n        return it->first;\n    }\n\n    LOG_F(WARNING, \"Overlay has origin with unknown config string \\\"%s\\\"\", str.c_str());\n\n    return ovrl_origin_room;    //Fallback to Room instead of throwing invalid values around\n}\n\nconst char* ConfigManager::GetConfigStringForOverlayOrigin(OverlayOrigin origin)\n{\n    const auto it = std::find_if(std::begin(g_OvrlOriginConfigFileStrings), std::end(g_OvrlOriginConfigFileStrings), [&](const auto& pair){ return (pair.first == origin); });\n\n    if (it != std::end(g_OvrlOriginConfigFileStrings))\n    {\n        return it->second;\n    }\n\n    LOG_F(WARNING, \"Overlay has origin with unknown ID %d\", (int)origin);\n\n    return \"UnknownOrigin\";\n}\n\nbool ConfigManager::IsUIAccessEnabled()\n{\n    std::ifstream file_manifest(\"DesktopPlus.exe.manifest\");\n\n    if (file_manifest.good())\n    {\n        //Read lines and see if 'uiAccess=\"true\"' can be found, otherwise assume it's not enabled\n        std::string line_str;\n\n        while (file_manifest.good())\n        {\n            std::getline(file_manifest, line_str);\n\n            if (line_str.find(\"uiAccess=\\\"true\\\"\") != std::string::npos)\n            {\n                return true;\n            }\n        }\n    }\n\n    return false;\n}\n\nvoid ConfigManager::RemoveScaleFromTransform(Matrix4& transform, float* width)\n{\n    Vector3 row_1(transform[0], transform[1], transform[2]);\n    float scale_x = row_1.length(); //Scaling is always uniform so we just check the x-axis\n\n    if (scale_x == 0.0f)\n        return;\n\n    Vector3 translation = transform.getTranslation();\n    transform.setTranslation({0.0f, 0.0f, 0.0f});\n\n    transform.scale(1.0f / scale_x);\n\n    transform.setTranslation(translation);\n\n    //Correct the width value so it gives the same visual result as before\n    if (width != nullptr)\n        *width *= scale_x;\n}\n\n#ifdef DPLUS_UI\n\nvoid ConfigManager::SaveConfigToFile()\n{\n    const std::wstring wpath = WStringConvertFromUTF8( std::string(m_ApplicationPath + \"config.ini\").c_str() );\n    Ini config(wpath.c_str());\n\n    //Only save overlay config if no app profile that has loaded an overlay profile is active\n    if (!m_AppProfileManager.IsActiveProfileWithOverlayProfile())\n    {\n        SaveMultiOverlayProfile(config);\n    }\n\n    config.WriteString(\"Interface\", \"LanguageFile\",             m_ConfigString[configid_str_interface_language_file].c_str());\n    config.WriteInt(   \"Interface\", \"OverlayCurrentID\",         m_ConfigInt[configid_int_interface_overlay_current_id]);\n    config.WriteInt(   \"Interface\", \"DesktopButtonCyclingMode\", m_ConfigInt[configid_int_interface_desktop_listing_style]);\n    config.WriteBool(  \"Interface\", \"ShowAdvancedSettings\",     m_ConfigBool[configid_bool_interface_show_advanced_settings]);\n    config.WriteBool(  \"Interface\", \"DisplaySizeLarge\",         m_ConfigBool[configid_bool_interface_large_style]);\n    config.WriteBool(  \"Interface\", \"DesktopButtonIncludeAll\",  m_ConfigBool[configid_bool_interface_desktop_buttons_include_combined]);\n\n    //Write color string\n    std::stringstream ss;\n    ss << std::setw(8) << std::setfill('0') << std::hex << pun_cast<unsigned int, int>(m_ConfigInt[configid_int_interface_background_color]);\n    config.WriteString(\"Interface\", \"EnvironmentBackgroundColor\", ss.str().c_str());\n\n    config.WriteInt( \"Interface\", \"EnvironmentBackgroundColorDisplayMode\", m_ConfigInt[configid_int_interface_background_color_display_mode]);\n    config.WriteBool(\"Interface\", \"DimUI\",                                 m_ConfigBool[configid_bool_interface_dim_ui]);\n    config.WriteBool(\"Interface\", \"BlankSpaceDragEnabled\",                 m_ConfigBool[configid_bool_interface_blank_space_drag_enabled]);\n    config.WriteInt( \"Interface\", \"LastVRUIScale\",                     int(m_ConfigFloat[configid_float_interface_last_vr_ui_scale] * 100.0f));\n    config.WriteBool(\"Interface\", \"WarningCompositorResolutionHidden\",     m_ConfigBool[configid_bool_interface_warning_compositor_res_hidden]);\n    config.WriteBool(\"Interface\", \"WarningCompositorQualityHidden\",        m_ConfigBool[configid_bool_interface_warning_compositor_quality_hidden]);\n    config.WriteBool(\"Interface\", \"WarningProcessElevationHidden\",         m_ConfigBool[configid_bool_interface_warning_process_elevation_hidden]);\n    config.WriteBool(\"Interface\", \"WarningElevatedModeHidden\",             m_ConfigBool[configid_bool_interface_warning_elevated_mode_hidden]);\n    config.WriteBool(\"Interface\", \"WarningBrowserMissingHidden\",           m_ConfigBool[configid_bool_interface_warning_browser_missing_hidden]);\n    config.WriteBool(\"Interface\", \"WarningBrowserVersionMismatchHidden\",   m_ConfigBool[configid_bool_interface_warning_browser_version_mismatch_hidden]);\n    config.WriteBool(\"Interface\", \"WarningAppProfileActiveHidden\",         m_ConfigBool[configid_bool_interface_warning_app_profile_active_hidden]);\n    config.WriteBool(\"Interface\", \"WindowSettingsRestoreState\",            m_ConfigBool[configid_bool_interface_window_settings_restore_state]);\n    config.WriteBool(\"Interface\", \"WindowPropertiesRestoreState\",          m_ConfigBool[configid_bool_interface_window_properties_restore_state]);\n    config.WriteBool(\"Interface\", \"WindowKeyboardRestoreState\",            m_ConfigBool[configid_bool_interface_window_keyboard_restore_state]);\n    config.WriteBool(\"Interface\", \"QuickStartGuideHidden\",                 m_ConfigBool[configid_bool_interface_quick_start_hidden]);\n\n    //Only write WMR settings when they're not -1 since they get set to that when using a non-WMR system. We want to preserve them for HMD-switching users\n    if (m_ConfigInt[configid_int_interface_wmr_ignore_vscreens] != -1)\n        config.WriteInt(\"Interface\", \"WMRIgnoreVScreens\", m_ConfigInt[configid_int_interface_wmr_ignore_vscreens]);\n\n    SaveConfigPersistentWindowState(config);\n\n    config.WriteString(\"Interface\", \"ActionOrder\",           m_ActionManager.ActionOrderListToString(m_ActionManager.GetActionOrderListUI()).c_str() );\n    config.WriteString(\"Interface\", \"ActionOrderBarDefault\", m_ActionManager.ActionOrderListToString(m_ActionManager.GetActionOrderListBarDefault()).c_str() );\n    config.WriteString(\"Interface\", \"ActionOrderOverlayBar\", m_ActionManager.ActionOrderListToString(m_ActionManager.GetActionOrderListOverlayBar()).c_str() );\n\n    config.WriteString(\"Input\", \"GoHomeButtonActionUID\", std::to_string(m_ConfigHandle[configid_handle_input_go_home_action_uid]).c_str());\n    config.WriteString(\"Input\", \"GoBackButtonActionUID\", std::to_string(m_ConfigHandle[configid_handle_input_go_back_action_uid]).c_str());\n\n    //Global Shorcuts\n    int shortcut_id = 0;\n\n    for (;;) //Remove any previous entries\n    {\n        ss = std::stringstream();\n        ss << \"GlobalShortcut\" << std::setfill('0') << std::setw(2) << shortcut_id + 1 << \"ActionUID\";\n\n        if (config.KeyExists(\"Input\", ss.str().c_str()))\n        {\n            config.RemoveKey(\"Input\", ss.str().c_str());\n        }\n        else\n        {\n            break;\n        }\n\n        ++shortcut_id;\n    }\n\n    shortcut_id = 0;\n    for (const ActionUID uid: m_ConfigGlobalShortcuts)\n    {\n        ss = std::stringstream();\n        ss << \"GlobalShortcut\" << std::setfill('0') << std::setw(2) << shortcut_id + 1 << \"ActionUID\";\n\n        config.WriteString(\"Input\", ss.str().c_str(), std::to_string(uid).c_str());\n\n        ++shortcut_id;\n    }\n\n    //Hotkeys\n    int hotkey_id = 0;\n\n    for (;;) //Remove any previous entries\n    {\n        ss = std::stringstream();\n        ss << \"GlobalHotkey\" << std::setfill('0') << std::setw(2) << hotkey_id + 1;\n\n        if (config.KeyExists(\"Input\", (ss.str() + \"Modifiers\").c_str()))\n        {\n            config.RemoveKey(\"Input\", (ss.str() + \"Modifiers\").c_str());\n            config.RemoveKey(\"Input\", (ss.str() + \"KeyCode\"  ).c_str());\n            config.RemoveKey(\"Input\", (ss.str() + \"ActionUID\").c_str());\n        }\n        else\n        {\n            break;\n        }\n\n        ++hotkey_id;\n    }\n\n    hotkey_id = 0;\n    for (const ConfigHotkey& hotkey : m_ConfigHotkey)\n    {\n        ss = std::stringstream();\n        ss << \"GlobalHotkey\" << std::setfill('0') << std::setw(2) << hotkey_id + 1;\n\n        config.WriteInt(   \"Input\", (ss.str() + \"Modifiers\").c_str(), hotkey.Modifiers);\n        config.WriteInt(   \"Input\", (ss.str() + \"KeyCode\"  ).c_str(), hotkey.KeyCode);\n        config.WriteString(\"Input\", (ss.str() + \"ActionUID\").c_str(), std::to_string(hotkey.ActionUID).c_str());\n\n        ++hotkey_id;\n    }\n\n    config.WriteInt( \"Input\", \"DetachedInteractionMaxDistance\", int(m_ConfigFloat[configid_float_input_detached_interaction_max_distance] * 100.0f));\n    config.WriteBool(\"Input\", \"LaserPointerBlockInput\",             m_ConfigBool[configid_bool_input_laser_pointer_block_input]);\n    config.WriteBool(\"Input\", \"GlobalHMDPointer\",                   m_ConfigBool[configid_bool_input_laser_pointer_hmd_device]);\n    config.WriteInt( \"Input\", \"LaserPointerHMDKeyCodeToggle\",       m_ConfigInt[configid_int_input_laser_pointer_hmd_device_keycode_toggle]);\n    config.WriteInt( \"Input\", \"LaserPointerHMDKeyCodeLeft\",         m_ConfigInt[configid_int_input_laser_pointer_hmd_device_keycode_left]);\n    config.WriteInt( \"Input\", \"LaserPointerHMDKeyCodeRight\",        m_ConfigInt[configid_int_input_laser_pointer_hmd_device_keycode_right]);\n    config.WriteInt( \"Input\", \"LaserPointerHMDKeyCodeMiddle\",       m_ConfigInt[configid_int_input_laser_pointer_hmd_device_keycode_middle]);\n    config.WriteInt( \"Input\", \"LaserPointerHMDKeyCodeDrag\",         m_ConfigInt[configid_int_input_laser_pointer_hmd_device_keycode_drag]);\n\n    config.WriteBool(\"Input\", \"DragAutoDocking\",                    m_ConfigBool[configid_bool_input_drag_auto_docking]);\n    config.WriteBool(\"Input\", \"DragFixedDistance\",                  m_ConfigBool[configid_bool_input_drag_fixed_distance]);\n    config.WriteInt( \"Input\", \"DragFixedDistanceCM\",            int(m_ConfigFloat[configid_float_input_drag_fixed_distance_m] * 100.0f));\n    config.WriteInt( \"Input\", \"DragFixedDistanceShape\",             m_ConfigInt[configid_int_input_drag_fixed_distance_shape]);\n    config.WriteBool(\"Input\", \"DragFixedDistanceAutoCurve\",         m_ConfigBool[configid_bool_input_drag_fixed_distance_auto_curve]);\n    config.WriteBool(\"Input\", \"DragFixedDistanceAutoTilt\",          m_ConfigBool[configid_bool_input_drag_fixed_distance_auto_tilt]);\n    config.WriteBool(\"Input\", \"DragSnapPosition\",                   m_ConfigBool[configid_bool_input_drag_snap_position]);\n    config.WriteInt( \"Input\", \"DragSnapPositionSize\",           int(m_ConfigFloat[configid_float_input_drag_snap_position_size] * 100.0f));\n    config.WriteBool(\"Input\", \"DragSnapRotation\",                   m_ConfigBool[configid_bool_input_drag_snap_rotation]);\n    config.WriteBool(\"Input\", \"DragSnapRotationX\",                  m_ConfigBool[configid_bool_input_drag_snap_rotation_x]);\n    config.WriteBool(\"Input\", \"DragSnapRotationY\",                  m_ConfigBool[configid_bool_input_drag_snap_rotation_y]);\n    config.WriteBool(\"Input\", \"DragSnapRotationZ\",                  m_ConfigBool[configid_bool_input_drag_snap_rotation_z]);\n    config.WriteInt( \"Input\", \"DragSnapRotationAngle\",              m_ConfigInt[configid_int_input_drag_snap_rotation_angle]);\n\n    config.WriteBool(\"Mouse\", \"RenderCursor\",              m_ConfigBool[configid_bool_input_mouse_render_cursor]);\n    config.WriteBool(\"Mouse\", \"RenderIntersectionBlob\",    m_ConfigBool[configid_bool_input_mouse_render_intersection_blob]);\n    config.WriteBool(\"Mouse\", \"ScrollSmooth\",              m_ConfigBool[configid_bool_input_mouse_scroll_smooth]);\n    config.WriteBool(\"Mouse\", \"AllowPointerOverride\",      m_ConfigBool[configid_bool_input_mouse_allow_pointer_override]);\n    config.WriteBool(\"Mouse\", \"SimulatePenInput\",          m_ConfigBool[configid_bool_input_mouse_simulate_pen_input]);\n    config.WriteInt( \"Mouse\", \"DoubleClickAssistDuration\", m_ConfigInt[configid_int_input_mouse_dbl_click_assist_duration_ms]);\n    config.WriteInt( \"Mouse\", \"InputSmoothingLevel\",       m_ConfigInt[configid_int_input_mouse_input_smoothing_level]);\n\n    config.WriteString(\"Keyboard\", \"LayoutFile\",                m_ConfigString[configid_str_input_keyboard_layout_file].c_str());\n    config.WriteBool(\"Keyboard\", \"LayoutClusterFunction\",       m_ConfigBool[configid_bool_input_keyboard_cluster_function_enabled]);\n    config.WriteBool(\"Keyboard\", \"LayoutClusterNavigation\",     m_ConfigBool[configid_bool_input_keyboard_cluster_navigation_enabled]);\n    config.WriteBool(\"Keyboard\", \"LayoutClusterNumpad\",         m_ConfigBool[configid_bool_input_keyboard_cluster_numpad_enabled]);\n    config.WriteBool(\"Keyboard\", \"LayoutClusterExtra\",          m_ConfigBool[configid_bool_input_keyboard_cluster_extra_enabled]);\n    config.WriteBool(\"Keyboard\", \"StickyModifiers\",             m_ConfigBool[configid_bool_input_keyboard_sticky_modifiers]);\n    config.WriteBool(\"Keyboard\", \"KeyRepeat\",                   m_ConfigBool[configid_bool_input_keyboard_key_repeat]);\n    config.WriteBool(\"Keyboard\", \"AutoShowDesktop\",             m_ConfigBool[configid_bool_input_keyboard_auto_show_desktop]);\n    config.WriteBool(\"Keyboard\", \"AutoShowBrowser\",             m_ConfigBool[configid_bool_input_keyboard_auto_show_browser]);\n\n    config.WriteBool(\"Windows\", \"AutoFocusSceneAppDashboard\",   m_ConfigBool[configid_bool_windows_auto_focus_scene_app_dashboard]);\n    config.WriteBool(\"Windows\", \"WinRTAutoFocus\",               m_ConfigBool[configid_bool_windows_winrt_auto_focus]);\n    config.WriteBool(\"Windows\", \"WinRTKeepOnScreen\",            m_ConfigBool[configid_bool_windows_winrt_keep_on_screen]);\n    config.WriteInt( \"Windows\", \"WinRTDraggingMode\",            m_ConfigInt[configid_int_windows_winrt_dragging_mode]);\n    config.WriteBool(\"Windows\", \"WinRTAutoSizeOverlay\",         m_ConfigBool[configid_bool_windows_winrt_auto_size_overlay]);\n    config.WriteBool(\"Windows\", \"WinRTAutoFocusSceneApp\",       m_ConfigBool[configid_bool_windows_winrt_auto_focus_scene_app]);\n    config.WriteInt( \"Windows\", \"WinRTOnCaptureLost\",           m_ConfigInt[configid_int_windows_winrt_capture_lost_behavior]);\n\n    config.WriteInt( \"Browser\", \"BrowserMaxFPS\",                m_ConfigInt[configid_int_browser_max_fps]);\n    config.WriteBool(\"Browser\", \"BrowserContentBlocker\",        m_ConfigBool[configid_bool_browser_content_blocker]);\n\n    config.WriteInt( \"Performance\", \"UpdateLimitMode\",                        m_ConfigInt[configid_int_performance_update_limit_mode]);\n    config.WriteInt( \"Performance\", \"UpdateLimitMS\",                      int(m_ConfigFloat[configid_float_performance_update_limit_ms] * 100.0f));\n    config.WriteInt( \"Performance\", \"UpdateLimitFPS\",                         m_ConfigInt[configid_int_performance_update_limit_fps]);\n    config.WriteBool(\"Performance\", \"RapidLaserPointerUpdates\",               m_ConfigBool[configid_bool_performance_rapid_laser_pointer_updates]);\n    config.WriteBool(\"Performance\", \"SingleDesktopMirroring\",                 m_ConfigBool[configid_bool_performance_single_desktop_mirroring]);\n    config.WriteBool(\"Performance\", \"AlternativeCursorRendering\",             m_ConfigBool[configid_bool_performance_alternative_cursor_rendering]);\n    config.WriteBool(\"Performance\", \"HDRMirroring\",                           m_ConfigBool[configid_bool_performance_hdr_mirroring]);\n    config.WriteBool(\"Performance\", \"ShowFPS\",                                m_ConfigBool[configid_bool_performance_show_fps]);\n    config.WriteBool(\"Performance\", \"UIAutoThrottle\",                         m_ConfigBool[configid_bool_performance_ui_auto_throttle]);\n    config.WriteBool(\"Performance\", \"PerformanceMonitorStyleMinimal\",         m_ConfigBool[configid_bool_performance_monitor_minimal_style]);\n    config.WriteBool(\"Performance\", \"PerformanceMonitorStyleLarge\",           m_ConfigBool[configid_bool_performance_monitor_large_style]);\n    config.WriteBool(\"Performance\", \"PerformanceMonitorStyleShowWindow\",      m_ConfigBool[configid_bool_performance_monitor_style_show_window]);\n    config.WriteBool(\"Performance\", \"PerformanceMonitorStyleShowTextOutline\", m_ConfigBool[configid_bool_performance_monitor_style_show_text_outline]);\n    config.WriteBool(\"Performance\", \"PerformanceMonitorStyleMinimalShowMore\", m_ConfigBool[configid_bool_performance_monitor_style_minimal_show_more]);\n    config.WriteBool(\"Performance\", \"PerformanceMonitorShowGraphs\",           m_ConfigBool[configid_bool_performance_monitor_show_graphs]);\n    config.WriteBool(\"Performance\", \"PerformanceMonitorShowTime\",             m_ConfigBool[configid_bool_performance_monitor_show_time]);\n    config.WriteBool(\"Performance\", \"PerformanceMonitorShowCPU\",              m_ConfigBool[configid_bool_performance_monitor_show_cpu]);\n    config.WriteBool(\"Performance\", \"PerformanceMonitorShowGPU\",              m_ConfigBool[configid_bool_performance_monitor_show_gpu]);\n    config.WriteBool(\"Performance\", \"PerformanceMonitorShowFPS\",              m_ConfigBool[configid_bool_performance_monitor_show_fps]);\n    config.WriteBool(\"Performance\", \"PerformanceMonitorShowBattery\",          m_ConfigBool[configid_bool_performance_monitor_show_battery]);\n    config.WriteBool(\"Performance\", \"PerformanceMonitorShowTrackers\",         m_ConfigBool[configid_bool_performance_monitor_show_trackers]);\n    config.WriteBool(\"Performance\", \"PerformanceMonitorShowViveWireless\",     m_ConfigBool[configid_bool_performance_monitor_show_vive_wireless]);\n\n    config.WriteInt( \"Misc\", \"ConfigVersion\",      k_nDesktopPlusConfigVersion);\n    config.WriteBool(\"Misc\", \"NoSteam\",            m_ConfigBool[configid_bool_misc_no_steam]);\n    config.WriteBool(\"Misc\", \"UIAccessWasEnabled\", (m_ConfigBool[configid_bool_misc_uiaccess_was_enabled] || m_ConfigBool[configid_bool_state_misc_uiaccess_enabled]));\n\n    //Remove old CustomSection section (actions are now saved separately)\n    config.RemoveSection(\"CustomActions\");\n\n    //Save config & if it succeeded, remove potential leftover unused config_newui.ini\n    if (config.Save())\n    {\n        std::wstring wpath_newui = WStringConvertFromUTF8( std::string(m_ApplicationPath + \"config_newui.ini\").c_str() );\n        if (FileExists(wpath_newui.c_str()))\n        {\n            ::DeleteFileW(wpath_newui.c_str());\n        }\n    }\n\n    m_ActionManager.SaveActionsToFile();\n    m_AppProfileManager.SaveProfilesToFile();\n}\n\n#endif //ifdef DPLUS_UI\n\nvoid ConfigManager::RestoreConfigFromDefault()\n{\n    //Basically delete the config files and then load it again which will fall back to config_default.ini\n    const std::wstring wpath_newui = WStringConvertFromUTF8( std::string(m_ApplicationPath + \"config_newui.ini\").c_str() );\n    const std::wstring wpath       = WStringConvertFromUTF8( std::string(m_ApplicationPath + \"config.ini\").c_str() );\n    ::DeleteFileW(wpath_newui.c_str());\n    ::DeleteFileW(wpath.c_str());\n\n    LoadConfigFromFile();\n}\n\nvoid ConfigManager::LoadOverlayProfileDefault(bool multi_overlay)\n{\n    //Multi-Overlay \"default\" config is loaded from the default configuration file\n    if (multi_overlay)\n    {\n        LoadMultiOverlayProfileFromFile(\"../config_default.ini\", true);     //This path is relative to the absolute profile path used in LoadMultiOverlayProfileFromFile()\n        return;\n    }\n    else\n    {\n        Ini config(L\"\");\n        LoadOverlayProfile(config, 0); //All read calls will fail end fill in default values as a result\n    }\n}\n\nbool ConfigManager::LoadMultiOverlayProfileFromFile(const std::string& filename, bool clear_existing_overlays, std::vector<char>* ovrl_inclusion_list)\n{\n    LOG_F(INFO, \"Loading overlay profile \\\"%s\\\"...\", filename.c_str());\n\n    std::wstring wpath = WStringConvertFromUTF8( std::string(m_ApplicationPath + \"profiles/\" + filename).c_str() );\n\n    if (FileExists(wpath.c_str()))\n    {\n        Ini config(wpath);\n        LoadMultiOverlayProfile(config, clear_existing_overlays, ovrl_inclusion_list);\n        return true;\n    }\n\n    return false;\n}\n\nbool ConfigManager::SaveMultiOverlayProfileToFile(const std::string& filename, std::vector<char>* ovrl_inclusion_list)\n{\n    LOG_F(INFO, \"Saving overlay profile \\\"%s\\\"...\", filename.c_str());\n\n    std::string path = m_ApplicationPath + \"profiles/\" + filename;\n    Ini config(WStringConvertFromUTF8(path.c_str()), true);\n\n    SaveMultiOverlayProfile(config, ovrl_inclusion_list);\n    return config.Save();\n}\n\nbool ConfigManager::DeleteOverlayProfile(const std::string& filename)\n{\n    LOG_F(INFO, \"Deleting overlay profile \\\"%s\\\"...\", filename.c_str());\n\n    std::string path = m_ApplicationPath + \"profiles/\" + filename;\n    bool ret = (::DeleteFileW(WStringConvertFromUTF8(path.c_str()).c_str()) != 0);\n\n    LOG_IF_F(WARNING, !ret, \"Failed to delete overlay profile!\");\n\n    return ret;\n}\n\nvoid ConfigManager::DeleteAllOverlayProfiles()\n{\n    LOG_F(INFO, \"Deleting all overlay profiles...\");\n\n    const std::wstring wpath = WStringConvertFromUTF8(std::string(m_ApplicationPath + \"profiles/*.ini\").c_str());\n    WIN32_FIND_DATA find_data;\n    HANDLE handle_find = ::FindFirstFileW(wpath.c_str(), &find_data);\n\n    if (handle_find != INVALID_HANDLE_VALUE)\n    {\n        do\n        {\n            if (!(find_data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY))\n            {\n                std::wstring wpath = WStringConvertFromUTF8(std::string(m_ApplicationPath + \"profiles/\").c_str()) + find_data.cFileName;\n                ::DeleteFileW(wpath.c_str());\n            }\n        }\n        while (::FindNextFileW(handle_find, &find_data) != 0);\n\n        ::FindClose(handle_find);\n    }\n}\n\n#ifdef DPLUS_UI\n\nstd::vector<std::string> ConfigManager::GetOverlayProfileList()\n{\n    std::vector<std::string> list;\n    list.emplace_back(TranslationManager::GetString(tstr_SettingsProfilesOverlaysNameDefault));\n\n    const std::wstring wpath = WStringConvertFromUTF8(std::string(m_ApplicationPath + \"profiles/*.ini\").c_str());\n    WIN32_FIND_DATA find_data;\n    HANDLE handle_find = ::FindFirstFileW(wpath.c_str(), &find_data);\n\n    if (handle_find != INVALID_HANDLE_VALUE)\n    {\n        do\n        {\n            std::string name = StringConvertFromUTF16(find_data.cFileName);\n            name = name.substr(0, name.length() - 4);   //Remove extension\n\n            list.push_back(name);\n        }\n        while (::FindNextFileW(handle_find, &find_data) != 0);\n\n        ::FindClose(handle_find);\n    }\n\n    list.emplace_back(TranslationManager::GetString(tstr_SettingsProfilesOverlaysNameNew));\n\n    return list;\n}\n\nstd::vector< std::pair<std::string, OverlayOrigin> > ConfigManager::GetOverlayProfileOverlayNameList(const std::string& filename)\n{\n    std::vector< std::pair<std::string, OverlayOrigin> > list;\n\n    std::wstring wpath = WStringConvertFromUTF8( std::string(m_ApplicationPath + \"profiles/\" + filename).c_str() );\n\n    if (FileExists(wpath.c_str()))\n    {\n        Ini config(wpath);\n        unsigned int overlay_id = 0;\n\n        std::stringstream ss;\n        ss << \"Overlay\" << overlay_id;\n\n        //Get names and origin from all sequential overlay sections that exist\n        while (config.SectionExists(ss.str().c_str()))\n        {\n            overlay_id++;\n\n            std::string name(config.ReadString(ss.str().c_str(), \"Name\"));\n            OverlayOrigin origin = GetOverlayOriginFromConfigString(config.ReadString(ss.str().c_str(), \"Origin\"));\n\n            list.push_back( std::make_pair(name.empty() ? ss.str() : name, origin) );   //Name should never be blank with compatible profiles, but offer alternative just in case\n\n            ss = std::stringstream();\n            ss << \"Overlay\" << overlay_id;\n        }\n    }\n\n    return list;\n}\n\nvoid ConfigManager::RestoreActionOrdersFromDefault()\n{\n    LOG_F(INFO, \"Restoring action orders from default config...\");\n\n    std::wstring wpath = WStringConvertFromUTF8( std::string(m_ApplicationPath + \"config_default.ini\").c_str() );\n\n    Ini config(wpath.c_str());\n    m_ActionManager.SetActionOrderListUI(         m_ActionManager.ActionOrderListFromString( config.ReadString(\"Interface\", \"ActionOrder\") ));\n    m_ActionManager.SetActionOrderListBarDefault( m_ActionManager.ActionOrderListFromString( config.ReadString(\"Interface\", \"ActionOrderBarDefault\") ));\n    m_ActionManager.SetActionOrderListOverlayBar( m_ActionManager.ActionOrderListFromString( config.ReadString(\"Interface\", \"ActionOrderOverlayBar\") ));\n}\n\n#endif //ifdef DPLUS_UI\n\nWPARAM ConfigManager::GetWParamForConfigID(ConfigID_Bool id)    //This is a no-op, but for consistencies' sake and in case anything changes there, it still exists\n{\n    return id;\n}\n\nWPARAM ConfigManager::GetWParamForConfigID(ConfigID_Int id)\n{\n    return id + configid_bool_MAX;\n}\n\nWPARAM ConfigManager::GetWParamForConfigID(ConfigID_Float id)\n{\n    return id + configid_bool_MAX + configid_int_MAX;\n}\n\nWPARAM ConfigManager::GetWParamForConfigID(ConfigID_Handle id)\n{\n    return id + configid_bool_MAX + configid_int_MAX + configid_float_MAX;\n}\n\nvoid ConfigManager::SetValue(ConfigID_Bool configid, bool value)\n{\n    if (configid < configid_bool_overlay_MAX)\n        OverlayManager::Get().GetCurrentConfigData().ConfigBool[configid] = value;\n    else if (configid < configid_bool_MAX)\n        Get().m_ConfigBool[configid] = value;\n}\n\nvoid ConfigManager::SetValue(ConfigID_Int configid, int value)\n{\n    if (configid < configid_int_overlay_MAX)\n        OverlayManager::Get().GetCurrentConfigData().ConfigInt[configid] = value;\n    else if (configid < configid_int_MAX)\n        Get().m_ConfigInt[configid] = value;\n}\n\nvoid ConfigManager::SetValue(ConfigID_Float configid, float value)\n{\n    if (configid < configid_float_overlay_MAX)\n        OverlayManager::Get().GetCurrentConfigData().ConfigFloat[configid] = value;\n    else if (configid < configid_float_MAX)\n        Get().m_ConfigFloat[configid] = value;\n}\n\nvoid ConfigManager::SetValue(ConfigID_Handle configid, uint64_t value)\n{\n    if (configid < configid_handle_overlay_MAX)\n        OverlayManager::Get().GetCurrentConfigData().ConfigHandle[configid] = value;\n    else if (configid < configid_handle_MAX)\n        Get().m_ConfigHandle[configid] = value;\n}\n\nvoid ConfigManager::SetValue(ConfigID_String configid, const std::string& value)\n{\n    if (configid < configid_str_overlay_MAX)\n        OverlayManager::Get().GetCurrentConfigData().ConfigStr[configid] = value;\n    else if (configid < configid_str_MAX)\n        Get().m_ConfigString[configid] = value;\n}\n\nbool ConfigManager::GetValue(ConfigID_Bool configid)\n{\n    return (configid < configid_bool_overlay_MAX) ? OverlayManager::Get().GetCurrentConfigData().ConfigBool[configid] : Get().m_ConfigBool[configid];\n}\n\nint ConfigManager::GetValue(ConfigID_Int configid)\n{\n    return (configid < configid_int_overlay_MAX) ? OverlayManager::Get().GetCurrentConfigData().ConfigInt[configid] : Get().m_ConfigInt[configid];\n}\n\nfloat ConfigManager::GetValue(ConfigID_Float configid)\n{\n    return (configid < configid_float_overlay_MAX) ? OverlayManager::Get().GetCurrentConfigData().ConfigFloat[configid] : Get().m_ConfigFloat[configid];\n}\n\nuint64_t ConfigManager::GetValue(ConfigID_Handle configid)\n{\n    return (configid < configid_handle_overlay_MAX) ? OverlayManager::Get().GetCurrentConfigData().ConfigHandle[configid] : Get().m_ConfigHandle[configid];\n}\n\nconst std::string& ConfigManager::GetValue(ConfigID_String configid)\n{\n    return (configid < configid_str_overlay_MAX) ? OverlayManager::Get().GetCurrentConfigData().ConfigStr[configid] : Get().m_ConfigString[configid];\n}\n\nbool& ConfigManager::GetRef(ConfigID_Bool configid)\n{\n    return (configid < configid_bool_overlay_MAX) ? OverlayManager::Get().GetCurrentConfigData().ConfigBool[configid] : Get().m_ConfigBool[configid];\n}\n\nint& ConfigManager::GetRef(ConfigID_Int configid)\n{\n    return (configid < configid_int_overlay_MAX) ? OverlayManager::Get().GetCurrentConfigData().ConfigInt[configid] : Get().m_ConfigInt[configid];\n}\n\nfloat& ConfigManager::GetRef(ConfigID_Float configid)\n{\n    return (configid < configid_float_overlay_MAX) ? OverlayManager::Get().GetCurrentConfigData().ConfigFloat[configid] : Get().m_ConfigFloat[configid];\n}\n\nuint64_t& ConfigManager::GetRef(ConfigID_Handle configid)\n{\n    return (configid < configid_handle_overlay_MAX) ? OverlayManager::Get().GetCurrentConfigData().ConfigHandle[configid] : Get().m_ConfigHandle[configid];\n}\n\nActionManager::ActionList& ConfigManager::GetGlobalShortcuts()\n{\n    return m_ConfigGlobalShortcuts;\n}\n\nconst ActionManager::ActionList& ConfigManager::GetGlobalShortcuts() const\n{\n    return m_ConfigGlobalShortcuts;\n}\n\nConfigHotkeyList& ConfigManager::GetHotkeys()\n{\n    return m_ConfigHotkey;\n}\n\nconst ConfigHotkeyList& ConfigManager::GetHotkeys() const\n{\n    return m_ConfigHotkey;\n}\n\nvoid ConfigManager::InitConfigForWMR()\n{\n    int& wmr_ignore_vscreens = m_ConfigInt[configid_int_interface_wmr_ignore_vscreens];\n\n    //Check if system is WMR and set WMR-specific default values if needed\n    char buffer[vr::k_unMaxPropertyStringSize];\n    vr::VRSystem()->GetStringTrackedDeviceProperty(vr::k_unTrackedDeviceIndex_Hmd, vr::Prop_TrackingSystemName_String, buffer, vr::k_unMaxPropertyStringSize);\n\n    bool is_wmr_system = (strcmp(buffer, \"holographic\") == 0);\n\n    if (is_wmr_system) //Is WMR, enable settings by default\n    {\n        if (wmr_ignore_vscreens == -1)\n        {\n            wmr_ignore_vscreens = 1;\n        }        \n    }\n    else //Not a WMR system, set values to -1. -1 settings will not be save to disk so a WMR user's settings is preserved if they switch around HMDs, but the setting is still false\n    {\n        wmr_ignore_vscreens = -1;\n    }\n}\n\nvoid ConfigManager::ResetConfigStateValues()\n{\n    std::fill(std::begin(m_ConfigBool) + configid_bool_state_overlay_dragmode,                std::begin(m_ConfigBool) + configid_bool_state_misc_process_started_by_steam,     false);\n    std::fill(std::begin(m_ConfigInt)  + configid_int_state_overlay_current_id_override,      std::begin(m_ConfigInt)  + configid_int_state_performance_duplication_fps,        -1);\n    //configid_int_state_interface_desktop_count is not reset\n    std::fill(std::begin(m_ConfigInt)  + configid_int_state_interface_floating_ui_hovered_id, std::begin(m_ConfigInt)  + configid_int_state_browser_content_blocker_list_count, -1);\n\n    //Also reset overlay states\n    for (unsigned int i = 0; i < OverlayManager::Get().GetOverlayCount(); ++i)\n    {\n        OverlayConfigData& data = OverlayManager::Get().GetConfigData(i);\n        std::fill(std::begin(data.ConfigBool) + configid_bool_overlay_state_browser_allow_transparency_is_pending, \n                  std::begin(data.ConfigBool) + configid_bool_overlay_state_browser_nav_is_loading, false);\n    }\n}\n\nActionManager& ConfigManager::GetActionManager()\n{\n    return m_ActionManager;\n}\n\nAppProfileManager& ConfigManager::GetAppProfileManager()\n{\n    return m_AppProfileManager;\n}\n\nMatrix4& ConfigManager::GetOverlayDetachedTransform()\n{\n    return OverlayManager::Get().GetCurrentConfigData().ConfigTransform;\n}\n\nconst std::string& ConfigManager::GetApplicationPath() const\n{\n    return m_ApplicationPath;\n}\n\nconst std::string& ConfigManager::GetExecutableName() const\n{\n    return m_ExecutableName;\n}\n\nbool ConfigManager::IsSteamInstall() const\n{\n    return m_IsSteamInstall;\n}\n\nvr::TrackedDeviceIndex_t ConfigManager::GetPrimaryLaserPointerDevice() const\n{\n    if (vr::VROverlay() == nullptr)\n        return vr::k_unTrackedDeviceIndexInvalid;\n\n    //No dashboard device, try Desktop+ laser pointer device\n    if (!vr::IVROverlayEx::IsSystemLaserPointerActive())\n        return (vr::TrackedDeviceIndex_t)m_ConfigInt[configid_int_state_dplus_laser_pointer_device];\n\n    return vr::VROverlay()->GetPrimaryDashboardDevice();\n}\n\nbool ConfigManager::IsLaserPointerTargetOverlay(vr::VROverlayHandle_t ulOverlayHandle, bool no_intersection_check) const\n{\n    if (vr::VROverlay() == nullptr)\n        return false;\n\n    bool ret = false;\n\n    if (vr::IVROverlayEx::IsSystemLaserPointerActive())\n    {\n        if (vr::VROverlay()->IsHoverTargetOverlay(ulOverlayHandle))\n        {\n            ret = true;\n\n            if (!no_intersection_check)\n            {\n                //Double check IsHoverTargetOverlay() with an intersection check as it's not guaranteed to always return false after leaving an overlay\n                //(it mostly does, but it's documented as returning the last overlay)\n                vr::VROverlayIntersectionResults_t results;\n                ret = vr::IVROverlayEx::ComputeOverlayIntersectionForDevice(ulOverlayHandle, vr::VROverlay()->GetPrimaryDashboardDevice(), vr::TrackingUniverseStanding, &results);\n            }\n        }\n    }\n\n    if (!ret)\n        return (ulOverlayHandle == m_ConfigHandle[configid_handle_state_dplus_laser_pointer_target_overlay]);\n\n    return ret;\n}\n"
  },
  {
    "path": "src/Shared/ConfigManager.h",
    "content": "//Both the Desktop+ dashboard app and the UI hold a copy of the current configuration\n//Both load the initial state from config.ini\n//Changes are generally done by the UI and sent to the dashboard application via Win32 messages\n//Only the UI writes to config.ini\n\n#pragma once\n\n#include <string>\n#include <vector>\n#define NOMINMAX\n#include <windows.h>\n\n#include \"Matrices.h\"\n#include \"Actions.h\"\n#include \"AppProfiles.h\"\n#include \"openvr.h\"\n\n//Settings enums\n//These IDs are also passed via IPC\n//configid_*_state entries are not stored/persistent and are just a simpler way to sync state\n\nenum ConfigID_Bool\n{\n    configid_bool_overlay_name_custom,\n    configid_bool_overlay_enabled,\n    configid_bool_overlay_crop_enabled,\n    configid_bool_overlay_show_backside,\n    configid_bool_overlay_browser_allow_transparency,\n    configid_bool_overlay_3D_enabled,\n    configid_bool_overlay_3D_swapped,\n    configid_bool_overlay_origin_hmd_floor_use_turning,\n    configid_bool_overlay_transform_locked,\n    configid_bool_overlay_gazefade_enabled,\n    configid_bool_overlay_input_enabled,\n    configid_bool_overlay_input_dplus_lp_enabled,\n    configid_bool_overlay_winrt_window_matching_strict,\n    configid_bool_overlay_update_invisible,\n    configid_bool_overlay_floatingui_enabled,\n    configid_bool_overlay_floatingui_desktops_enabled,\n    configid_bool_overlay_floatingui_extras_enabled,\n    configid_bool_overlay_actionbar_enabled,\n    configid_bool_overlay_actionbar_order_use_global,\n    configid_bool_overlay_state_no_output,\n    configid_bool_overlay_state_browser_allow_transparency_is_pending,\n    configid_bool_overlay_state_browser_nav_can_go_back,\n    configid_bool_overlay_state_browser_nav_can_go_forward,\n    configid_bool_overlay_state_browser_nav_is_loading,\n    configid_bool_overlay_MAX,\n    configid_bool_interface_no_ui,\n    configid_bool_interface_no_notification_icon,\n    configid_bool_interface_show_advanced_settings,\n    configid_bool_interface_large_style,\n    configid_bool_interface_dim_ui,\n    configid_bool_interface_blank_space_drag_enabled,\n    configid_bool_interface_desktop_buttons_include_combined,\n    configid_bool_interface_warning_compositor_res_hidden,\n    configid_bool_interface_warning_compositor_quality_hidden,\n    configid_bool_interface_warning_process_elevation_hidden,\n    configid_bool_interface_warning_elevated_mode_hidden,\n    configid_bool_interface_warning_browser_missing_hidden,\n    configid_bool_interface_warning_browser_version_mismatch_hidden,\n    configid_bool_interface_warning_app_profile_active_hidden,\n    configid_bool_interface_window_settings_restore_state,\n    configid_bool_interface_window_properties_restore_state,\n    configid_bool_interface_window_keyboard_restore_state,\n    configid_bool_interface_quick_start_hidden,\n    configid_bool_performance_rapid_laser_pointer_updates,\n    configid_bool_performance_single_desktop_mirroring,\n    configid_bool_performance_alternative_cursor_rendering,\n    configid_bool_performance_hdr_mirroring,\n    configid_bool_performance_show_fps,\n    configid_bool_performance_ui_auto_throttle,\n    configid_bool_performance_monitor_minimal_style,\n    configid_bool_performance_monitor_large_style,\n    configid_bool_performance_monitor_style_show_window,\n    configid_bool_performance_monitor_style_show_text_outline,\n    configid_bool_performance_monitor_style_minimal_show_more,\n    configid_bool_performance_monitor_show_graphs,\n    configid_bool_performance_monitor_show_time,\n    configid_bool_performance_monitor_show_cpu,\n    configid_bool_performance_monitor_show_gpu,\n    configid_bool_performance_monitor_show_fps,\n    configid_bool_performance_monitor_show_battery,\n    configid_bool_performance_monitor_show_trackers,\n    configid_bool_performance_monitor_show_vive_wireless,\n    configid_bool_performance_monitor_disable_gpu_counters,\n    configid_bool_input_mouse_render_cursor,\n    configid_bool_input_mouse_render_intersection_blob,\n    configid_bool_input_mouse_scroll_smooth,\n    configid_bool_input_mouse_allow_pointer_override,\n    configid_bool_input_mouse_simulate_pen_input,\n    configid_bool_input_keyboard_cluster_function_enabled,\n    configid_bool_input_keyboard_cluster_navigation_enabled,\n    configid_bool_input_keyboard_cluster_numpad_enabled,\n    configid_bool_input_keyboard_cluster_extra_enabled,\n    configid_bool_input_keyboard_sticky_modifiers,\n    configid_bool_input_keyboard_key_repeat,\n    configid_bool_input_keyboard_auto_show_desktop,\n    configid_bool_input_keyboard_auto_show_browser,\n    configid_bool_input_laser_pointer_block_input,\n    configid_bool_input_laser_pointer_hmd_device,\n    configid_bool_input_drag_auto_docking,\n    configid_bool_input_drag_fixed_distance,\n    configid_bool_input_drag_fixed_distance_auto_curve,\n    configid_bool_input_drag_fixed_distance_auto_tilt,\n    configid_bool_input_drag_snap_position,\n    configid_bool_input_drag_snap_rotation,\n    configid_bool_input_drag_snap_rotation_x,\n    configid_bool_input_drag_snap_rotation_y,\n    configid_bool_input_drag_snap_rotation_z,\n    configid_bool_windows_auto_focus_scene_app_dashboard,\n    configid_bool_windows_winrt_auto_focus,\n    configid_bool_windows_winrt_keep_on_screen,\n    configid_bool_windows_winrt_auto_size_overlay,\n    configid_bool_windows_winrt_auto_focus_scene_app,\n    configid_bool_browser_content_blocker,\n    configid_bool_misc_no_steam,                              //Restarts without Steam when it detects to have been launched by Steam\n    configid_bool_misc_uiaccess_was_enabled,                  //Tracks if UIAccess was enabled to show a warning after it isn't anymore due to updates or modified executable\n    configid_bool_state_overlay_dragmode,\n    configid_bool_state_overlay_selectmode,\n    configid_bool_state_overlay_dragselectmode_show_hidden,   //True if mode is from a popup\n    configid_bool_state_overlay_dragmode_temp,\n    configid_bool_state_pen_simulation_supported,\n    configid_bool_state_window_focused_process_elevated,\n    configid_bool_state_keyboard_visible,\n    configid_bool_state_performance_gpu_copy_active,\n    configid_bool_state_misc_process_elevated,                //True if the dashboard application is running with admin privileges\n    configid_bool_state_misc_elevated_mode_active,            //True if the elevated mode process is running\n    configid_bool_state_misc_browser_used_but_missing,        //True if the browser capture source is used, but browser component isn't available\n    configid_bool_state_misc_browser_version_mismatch,        //True if the browser is available, but the API version isn't compatible\n    configid_bool_state_misc_process_started_by_steam,\n    configid_bool_state_misc_uiaccess_enabled,\n    configid_bool_state_misc_config_migrated,                 //True if the config was migrated in the current session\n    configid_bool_MAX\n};\n\nenum ConfigID_Int\n{\n    configid_int_overlay_desktop_id,                        //-1 is combined desktop, -2 is a default value that initializes crop to desktop 0\n    configid_int_overlay_capture_source,\n    configid_int_overlay_duplication_id,                    //-1 is none, overlay id, only used for browser overlays\n    configid_int_overlay_winrt_desktop_id,                  //-1 is combined desktop, -2 is unset\n    configid_int_overlay_user_width,\n    configid_int_overlay_user_height,\n    configid_int_overlay_crop_x,\n    configid_int_overlay_crop_y,\n    configid_int_overlay_crop_width,\n    configid_int_overlay_crop_height,\n    configid_int_overlay_3D_mode,\n    configid_int_overlay_display_mode,\n    configid_int_overlay_origin,\n    configid_int_overlay_origin_smoothing_level,\n    configid_int_overlay_update_limit_override_mode,\n    configid_int_overlay_update_limit_override_fps,\n    configid_int_overlay_browser_max_fps_override,\n    configid_int_overlay_state_content_width,\n    configid_int_overlay_state_content_height,\n    configid_int_overlay_state_fps,\n    configid_int_overlay_MAX,\n    configid_int_interface_overlay_current_id,\n    configid_int_interface_desktop_listing_style,\n    configid_int_interface_background_color,\n    configid_int_interface_background_color_display_mode,\n    configid_int_interface_wmr_ignore_vscreens,             //-1 means auto/unset which is the value non-WMR users get\n    configid_int_input_mouse_dbl_click_assist_duration_ms,\n    configid_int_input_mouse_input_smoothing_level,\n    configid_int_input_drag_fixed_distance_shape,           //0 = Sphere, 1 = Cylinder\n    configid_int_input_drag_snap_rotation_angle,\n    configid_int_input_global_shortcuts_max_count,\n    configid_int_input_laser_pointer_hmd_device_keycode_toggle,\n    configid_int_input_laser_pointer_hmd_device_keycode_left,\n    configid_int_input_laser_pointer_hmd_device_keycode_right,\n    configid_int_input_laser_pointer_hmd_device_keycode_middle,\n    configid_int_input_laser_pointer_hmd_device_keycode_drag,\n    configid_int_windows_winrt_dragging_mode,\n    configid_int_windows_winrt_capture_lost_behavior,\n    configid_int_browser_max_fps,                           //Browser overlays use this instead of update limits\n    configid_int_performance_update_limit_mode,\n    configid_int_performance_update_limit_fps,              //This is the enum ID, not the actual number. See ApplySettingUpdateLimiter() code for more info\n    configid_int_performance_ui_frameskip,\n    configid_int_misc_force_gpu_deviceid,\n    configid_int_misc_force_gpu_vr_deviceid,\n    configid_int_state_overlay_current_id_override,         //This is used to send config changes to overlays which aren't the current, mainly to avoid the UI switching around (-1 is disabled)\n    configid_int_state_overlay_transform_sync_target_id,    //Target overlay ID for transform sync. -1 = None\n    configid_int_state_overlay_focused_id,                  //Focused overlay ID (set by last click) for keyboard overlay target if applicable. -1 = None\n    configid_int_state_mouse_dbl_click_assist_duration_ms,  //Internally used value, which will replace -1 with the current double-click delay automatically\n    configid_int_state_performance_duplication_fps,\n    configid_int_state_interface_desktop_count,             //Count of desktops after optionally filtering virtual WMR displays\n    configid_int_state_interface_floating_ui_hovered_id,    //Floating UI target overlay ID set only while the laser pointer is pointing at the Floating UI overlay. -1 = None\n    configid_int_state_auto_docking_state,                  //0 = Off, 1 = Left Hand, 2 = Right Hand (matches ETrackedControllerRole). +2 for detaching\n    configid_int_state_drag_hint_device,                    //Value is tracked device index. Set before changing configid_int_state_drag_hint_type\n    configid_int_state_drag_hint_type,                      //0 = Off, 1 = Overlay Position Locked, 2 = Theater Screen Overlay Drag Block\n    configid_int_state_laser_pointer_device_hint,           //Used by dragging functions when laser pointer device can't be determined via other means (value is tracked device index)\n    configid_int_state_dplus_laser_pointer_device,          //Tracked device index for active Desktop+ laser pointer\n    configid_int_state_browser_content_blocker_list_count,\n    configid_int_MAX\n};\n\nenum ConfigID_Float\n{\n    configid_float_overlay_width,\n    configid_float_overlay_curvature,\n    configid_float_overlay_opacity,\n    configid_float_overlay_brightness,\n    configid_float_overlay_browser_zoom,\n    configid_float_overlay_offset_right,\n    configid_float_overlay_offset_up,\n    configid_float_overlay_offset_forward,\n    configid_float_overlay_gazefade_distance,\n    configid_float_overlay_gazefade_rate,\n    configid_float_overlay_gazefade_opacity,\n    configid_float_overlay_update_limit_override_ms,\n    configid_float_overlay_state_brightness_extra_multiplier, //Multiplier applied on top of configid_float_overlay_brightness, currently used for HDR adjustment\n    configid_float_overlay_MAX,\n    configid_float_input_detached_interaction_max_distance,\n    configid_float_input_global_hmd_pointer_max_distance,\n    configid_float_input_drag_fixed_distance_m,\n    configid_float_input_drag_snap_position_size,\n    configid_float_interface_desktop_ui_scale_override,\n    configid_float_interface_last_vr_ui_scale,\n    configid_float_performance_update_limit_ms,\n    configid_float_state_overlay_transform_sync_value,        //Used for sending overlay transforms asynchronously. This is done by sending 16 of these after setting the target id\n    configid_float_MAX\n};\n\nenum ConfigID_Handle\n{\n    configid_handle_overlay_state_overlay_handle,             //Overlay handle for syncing with the UI. Shouldn't change during runtime, but does for Theater Screen handling\n    configid_handle_overlay_state_winrt_hwnd,                 //HWNDs are technically always in 32-bit range, but avoiding truncation warnings and perhaps some other issues here\n    configid_handle_overlay_state_winrt_last_hicon,           //HICON kept around for when window goes missing but the icon itself is still cached in UI app\n    configid_handle_overlay_MAX,\n    configid_handle_input_go_home_action_uid,\n    configid_handle_input_go_back_action_uid,\n    configid_handle_state_arg_hwnd,                           //Used when a HWND is needed as an ipcact message argument\n    configid_handle_state_dplus_laser_pointer_target_overlay, //Overlay handle for active Desktop+ laser pointer\n    configid_handle_state_action_uid,                         //Used when an action UID is needed as an ipcact message argument and message space is needed for something else\n    configid_handle_state_theater_orig_overlay_handle,        //Original overlay handle for the overlay current using the Theater Screen\n    configid_handle_MAX\n};\n\nenum ConfigID_String\n{\n    configid_str_overlay_winrt_last_window_title,\n    configid_str_overlay_winrt_last_window_class_name,\n    configid_str_overlay_winrt_last_window_exe_name,\n    configid_str_overlay_browser_url,                       //Current URL\n    configid_str_overlay_browser_url_user_last,             //Last manually entered URL\n    configid_str_overlay_browser_title,\n    configid_str_overlay_tags,\n    configid_str_overlay_MAX,\n    configid_str_interface_language_file,\n    configid_str_input_keyboard_layout_file,\n    configid_str_browser_extra_arguments,                   //Extra command-line arguments passed to DPBrowser executable\n    configid_str_state_ui_keyboard_string,                  //SteamVR keyboard input for the UI application\n    configid_str_state_keyboard_string,                     //VR keyboard input for the dashboard application\n    configid_str_state_dashboard_error_string,              //Error messages are displayed in VR through the UI app\n    configid_str_state_profile_name_load,                   //Name of the profile to load \n    configid_str_state_app_profile_key,                     //Target app key for app profile synching\n    configid_str_state_app_profile_data,                    //Serialized data string of app profile for synching\n    configid_str_state_action_data,                         //Serialized data string of action for synching\n    configid_str_state_hotkey_data,                         //Serialized data string of hotkey for synching\n    configid_str_MAX\n};\n\n//Actually stored as ints, but still have this for readability\nenum OverlayCaptureSource\n{\n    ovrl_capsource_desktop_duplication,\n    ovrl_capsource_winrt_capture,\n    ovrl_capsource_ui,\n    ovrl_capsource_browser\n};\n\nenum Overlay3DMode\n{\n    ovrl_3Dmode_hsbs,\n    ovrl_3Dmode_sbs,\n    ovrl_3Dmode_hou,\n    ovrl_3Dmode_ou,\n    ovrl_3Dmode_MAX\n};\n\nenum OverlayDisplayMode\n{\n    ovrl_dispmode_always,\n    ovrl_dispmode_dashboard,\n    ovrl_dispmode_scene,\n    ovrl_dispmode_dplustab,\n    ovrl_dispmode_MAX\n};\n\nenum OverlayOrigin\n{\n    ovrl_origin_room,\n    ovrl_origin_hmd_floor,\n    ovrl_origin_seated_universe,\n    ovrl_origin_dashboard,\n    ovrl_origin_hmd,\n    ovrl_origin_left_hand,\n    ovrl_origin_right_hand,\n    ovrl_origin_aux,            //Tracker or whatever. No proper autodetection of additional devices yet, maybe in the future\n    ovrl_origin_theater_screen,\n    ovrl_origin_dplus_tab,      //Desktop+ dashboard dummy overlay, more reliable than dashboard origin. Not used by user overlays\n    ovrl_origin_MAX\n};\n\n//Not stored in config but used to pass origin config values to functions dealing with overlay origins\nstruct OverlayOriginConfig\n{\n    bool HMDFloorUseTurning = false;\n};\n\nenum DesktopListingStyle\n{\n    desktop_listing_style_none,\n    desktop_listing_style_individual,\n    desktop_listing_style_cycle\n};\n\nenum InterfaceBGColorDisplayMode\n{\n    ui_bgcolor_dispmode_never,\n    ui_bgcolor_dispmode_dplustab,\n    ui_bgcolor_dispmode_always\n};\n\nenum UpdateLimitMode\n{\n    update_limit_mode_off,\n    update_limit_mode_ms,\n    update_limit_mode_fps\n};\n\nenum UpdateLimitFPS\n{\n    update_limit_fps_1,\n    update_limit_fps_2,\n    update_limit_fps_5,\n    update_limit_fps_10,\n    update_limit_fps_15,\n    update_limit_fps_20,\n    update_limit_fps_25,\n    update_limit_fps_30,\n    update_limit_fps_40,\n    update_limit_fps_50\n};\n\nenum WindowDraggingMode\n{\n    window_dragging_none,\n    window_dragging_block,\n    window_dragging_overlay,\n    window_dragging_MAX\n};\n\nenum WindowCaptureLostBehavior\n{\n    window_caplost_do_nothing,\n    window_caplost_hide_overlay,\n    window_caplost_remove_overlay,\n    window_caplost_MAX\n};\n\nstruct ConfigHotkey\n{\n    UINT KeyCode        = 0;\n    UINT Modifiers      = 0;\n    ActionUID ActionUID = k_ActionUID_Invalid;\n\n    std::string StateUIName;\n    bool StateIsDown    = false;\n\n    std::string Serialize() const;              //Serializes into binary data stored as string (contains NUL bytes), not suitable for storage, does not write state values\n    void Deserialize(const std::string& str);   //Deserializes from strings created by above function\n};\n\ntypedef std::vector<ConfigHotkey> ConfigHotkeyList;\n\nclass OverlayConfigData\n{\n    public:\n        std::string ConfigNameStr;\n        bool ConfigBool[configid_bool_overlay_MAX];\n        int ConfigInt[configid_int_overlay_MAX];\n        float ConfigFloat[configid_float_overlay_MAX];\n        uint64_t ConfigHandle[configid_handle_overlay_MAX];\n        std::string ConfigStr[configid_str_overlay_MAX];\n        Matrix4 ConfigTransform;\n        ActionManager::ActionList ConfigActionBarOrder;\n\n        OverlayConfigData();\n};\n\nclass Ini;\n\nclass ConfigManager\n{\n    private:\n        typedef std::unordered_map<int, ActionUID> LegacyActionIDtoActionUID;\n\n        bool m_ConfigBool[configid_bool_MAX];\n        int m_ConfigInt[configid_int_MAX];\n        float m_ConfigFloat[configid_float_MAX];\n        uint64_t m_ConfigHandle[configid_handle_MAX];\n        std::string m_ConfigString[configid_str_MAX];\n        ActionManager::ActionList m_ConfigGlobalShortcuts;\n        ConfigHotkeyList m_ConfigHotkey;\n\n        ActionManager m_ActionManager;\n        AppProfileManager m_AppProfileManager;\n\n        std::string m_ApplicationPath;\n        std::string m_ExecutableName;\n        bool m_IsSteamInstall;\n\n        void LoadOverlayProfile(const Ini& config, unsigned int overlay_id);\n        void SaveOverlayProfile(Ini& config, unsigned int overlay_id);\n        void LoadMultiOverlayProfile(const Ini& config, bool clear_existing_overlays = true, std::vector<char>* ovrl_inclusion_list = nullptr);\n        void SaveMultiOverlayProfile(Ini& config, std::vector<char>* ovrl_inclusion_list = nullptr);\n\n        #ifdef DPLUS_UI\n            void LoadConfigPersistentWindowState(Ini& config);\n            void SaveConfigPersistentWindowState(Ini& config);\n\n            void MigrateLegacyConfig(Ini& config, bool only_rename_config_file);\n            void MigrateLegacyOverlayProfileFromConfig(Ini& config, bool apply_steamvr2_dashboard_offset, LegacyActionIDtoActionUID& legacy_id_to_uid); //This writes to passed config, but doesn't save it\n            std::string MigrateLegacyActionOrderString(const std::string& order_str, LegacyActionIDtoActionUID& legacy_id_to_uid);\n        #endif\n\n        LegacyActionIDtoActionUID MigrateLegacyActionsFromConfig(const Ini& config);   //Returns post-migration legacy ActionID to ActionUID mapping\n\n        OverlayOrigin GetOverlayOriginFromConfigString(const std::string& str);\n        const char* GetConfigStringForOverlayOrigin(OverlayOrigin origin);\n\n        static bool IsUIAccessEnabled();\n        static void RemoveScaleFromTransform(Matrix4& transform, float* width);\n\n    public:\n        ConfigManager();\n        static ConfigManager& Get();\n\n        bool LoadConfigFromFile();\n        void SaveConfigToFile();\n        void RestoreConfigFromDefault();\n\n        void LoadOverlayProfileDefault(bool multi_overlay = false);\n        bool LoadMultiOverlayProfileFromFile(const std::string& filename, bool clear_existing_overlays = true, std::vector<char>* ovrl_inclusion_list = nullptr);\n        bool SaveMultiOverlayProfileToFile(const std::string& filename, std::vector<char>* ovrl_inclusion_list = nullptr);\n        bool DeleteOverlayProfile(const std::string& filename);\n        void DeleteAllOverlayProfiles();\n\n        #ifdef DPLUS_UI\n            std::vector<std::string> GetOverlayProfileList();\n            std::vector< std::pair<std::string, OverlayOrigin> > GetOverlayProfileOverlayNameList(const std::string& filename);\n            void RestoreActionOrdersFromDefault();\n        #endif\n\n        static WPARAM GetWParamForConfigID(ConfigID_Bool id);\n        static WPARAM GetWParamForConfigID(ConfigID_Int id);\n        static WPARAM GetWParamForConfigID(ConfigID_Float id);\n        static WPARAM GetWParamForConfigID(ConfigID_Handle id);\n\n        static void SetValue(ConfigID_Bool   configid, bool               value);\n        static void SetValue(ConfigID_Int    configid, int                value);\n        static void SetValue(ConfigID_Float  configid, float              value);\n        static void SetValue(ConfigID_Handle configid, uint64_t           value);\n        static void SetValue(ConfigID_String configid, const std::string& value);\n\n        static bool               GetValue(ConfigID_Bool   configid);\n        static int                GetValue(ConfigID_Int    configid);\n        static float              GetValue(ConfigID_Float  configid);\n        static uint64_t           GetValue(ConfigID_Handle configid);\n        static const std::string& GetValue(ConfigID_String configid);\n\n        static bool&     GetRef(ConfigID_Bool   configid);\n        static int&      GetRef(ConfigID_Int    configid);\n        static float&    GetRef(ConfigID_Float  configid);\n        static uint64_t& GetRef(ConfigID_Handle configid);\n\n        ActionManager::ActionList& GetGlobalShortcuts();\n        const ActionManager::ActionList& GetGlobalShortcuts() const;\n        ConfigHotkeyList& GetHotkeys();\n        const ConfigHotkeyList& GetHotkeys() const;\n\n        void InitConfigForWMR();        //Setup WMR-specific default values if needed\n        void ResetConfigStateValues();  //Reset all configid_*_state_* settings. Used when restarting a Desktop+ process\n\n        ActionManager& GetActionManager();\n        AppProfileManager& GetAppProfileManager();\n        Matrix4& GetOverlayDetachedTransform();\n\n        const std::string& GetApplicationPath() const;\n        const std::string& GetExecutableName() const;\n        bool IsSteamInstall() const;\n        vr::TrackedDeviceIndex_t GetPrimaryLaserPointerDevice() const;                 //GetPrimaryDashboardDevice() but works with Desktop+'s laser pointer as well\n\n        //IsHoverTargetOverlay() but works with Desktop+'s laser pointer as well, no_intersection_check skips additional intersection check for system laser pointer\n        bool IsLaserPointerTargetOverlay(vr::VROverlayHandle_t ulOverlayHandle, bool no_intersection_check = false) const;\n};"
  },
  {
    "path": "src/Shared/DPBrowserAPI.h",
    "content": "#pragma once\n\n#ifndef NOMINMAX\n    #define NOMINMAX\n#endif\n#include <windows.h>\n\n#include \"openvr.h\"\n\nstatic const int k_lDPBrowserAPIVersion = 5;\nLPCWSTR const g_WindowClassNameBrowserApp        = L\"elvdesktopbrowser\";\nLPCWSTR const g_WindowMessageNameBrowserApp      = L\"WMIPC_DPLUS_BrowserCommand\";\nconst char* const g_AppKeyBrowserApp             = \"elvissteinjr.DesktopPlusBrowser\";\nconst char* const g_RelativeWorkingDirBrowserApp = \"DesktopPlusBrowser\";                //Path relative to Desktop+ application directory\nconst char* const g_ExeNameBrowserApp            = \"DesktopPlusBrowser.exe\";            //Path relative to g_RelativeWorkingDirBrowserApp\n\nenum DPBrowserICPCommandID\n{\n    dpbrowser_ipccmd_get_api_version,           //No value in lParam, use SendMessage(), gets the supported API version from the browser process\n    dpbrowser_ipccmd_set_overlay_target,        //lParam = overlay_handle, sets target overlay for commands that use lParam for other arguments | Also sent by browser process\n    dpbrowser_ipccmd_start_browser,             //lParam = use_transparent_background bool, uses set_overlay_target & dpbrowser_ipcstr_url arg\n    dpbrowser_ipccmd_duplicate_browser_output,  //lParam = overlay_handle_dst, uses set_overlay_target arg (overlay_handle_src)\n    dpbrowser_ipccmd_pause_browser,             //lParam = pause bool, uses set_overlay_target arg\n    dpbrowser_ipccmd_recreate_browser,          //lParam = use_transparent_background bool, uses set_overlay_target & dpbrowser_ipcstr_url arg\n    dpbrowser_ipccmd_stop_browser,              //lParam = overlay_handle\n    dpbrowser_ipccmd_set_url,                   //lParam = overlay_handle, uses dpbrowser_ipcstr_url arg\n    dpbrowser_ipccmd_set_resoution,             //lParam = Width & Height (in low/high word order, signed), uses set_overlay_target arg\n    dpbrowser_ipccmd_set_fps,                   //lParam = fps, uses set_overlay_target arg\n    dpbrowser_ipccmd_set_zoom,                  //lParam = Zoom (float packed as LPARAM), uses set_overlay_target arg\n    dpbrowser_ipccmd_set_ou3d_crop,             //lParam = DPRect packed with DPRect::Pack16() or -1 to disable, uses set_overlay_target arg\n    dpbrowser_ipccmd_mouse_move,                //lParam = X & Y (in low/high word order, signed), uses set_overlay_target arg\n    dpbrowser_ipccmd_mouse_leave,               //lParam = overlay_handle\n    dpbrowser_ipccmd_mouse_down,                //lParam = EVRMouseButton, uses set_overlay_target arg\n    dpbrowser_ipccmd_mouse_up,                  //lParam = EVRMouseButton, uses set_overlay_target arg\n    dpbrowser_ipccmd_scroll,                    //lParam = X-Delta & Y-Delta (in low/high word order, floats packed as DWORDs), uses set_overlay_target arg\n    dpbrowser_ipccmd_keyboard_vkey,             //lParam = DPBrowserIPCKeyboardKeystateFlags + Win32 key code (low/high word order), uses set_overlay_target arg\n    dpbrowser_ipccmd_keyboard_vkey_toggle,      //lParam = Win32 key code, uses set_overlay_target arg\n    dpbrowser_ipccmd_keyboard_wchar,            //lParam = 1 wchar + key down bool (low/high word order), uses set_overlay_target arg\n    dpbrowser_ipccmd_keyboard_string,           //lParam = overlay_handle, uses dpbrowser_ipcstr_keyboard_string\n    dpbrowser_ipccmd_go_back,                   //lParam = overlay_handle\n    dpbrowser_ipccmd_go_forward,                //lParam = overlay_handle\n    dpbrowser_ipccmd_refresh,                   //lParam = overlay_handle, will stop if the page is currently loading\n    dpbrowser_ipccmd_global_set_fps,            //lParam = fps, global setting\n    dpbrowser_ipccmd_cblock_set_enabled,        //lParam = enabled bool\n    dpbrowser_ipccmd_error_set_strings,         //No value in lParam, uses dpbrowser_ipcstr_tstr_error_* args\n    dpbrowser_ipccmd_notify_ready,              //No value in lParam | Sent by browser process to dashboard & UI process\n    dpbrowser_ipccmd_notify_nav_state,          //lParam = DPBrowserIPCNavStateFlags, uses set_overlay_target arg | Sent by browser process to UI process\n    dpbrowser_ipccmd_notify_url_changed,        //lParam = overlay_handle, uses dpbrowser_ipcstr_url arg | Sent by browser process to UI process\n    dpbrowser_ipccmd_notify_title_changed,      //lParam = overlay_handle, uses dpbrowser_ipcstr_title arg | Sent by browser process to UI process\n    dpbrowser_ipccmd_notify_fps,                //lParam = fps, uses set_overlay_target arg | Sent by browser process to UI process\n    dpbrowser_ipccmd_notify_lpointer_haptics,   //No value in lParam, triggers short UI interaction burst on primary device | Sent by browser process to dashboard process\n    dpbrowser_ipccmd_notify_keyboard_show,      //lParam = show bool, uses set_overlay_target arg | Sent by browser process to UI process\n    dpbrowser_ipccmd_notify_cblock_list_count,  //lParam = list count | Sent by browser process to UI process\n};\n\nenum DPBrowserICPStringID\n{\n    dpbrowser_ipcstr_MIN = 1000,                //Start IDs at a higher value to avoid conflicts with config string messages on a client\n    dpbrowser_ipcstr_url = 1000,                //URL string for upcoming command/notification\n    dpbrowser_ipcstr_title,                     //Title string for upcoming notification\n    dpbrowser_ipcstr_keyboard_string,           //Keyboard string for upcoming command\n    dpbrowser_ipcstr_tstr_error_title,          //Error page translation string\n    dpbrowser_ipcstr_tstr_error_heading,        //Error page translation string\n    dpbrowser_ipcstr_tstr_error_message,        //Error page translation string\n    dpbrowser_ipcstr_MAX\n};\n\nenum DPBrowserIPCNavStateFlags : unsigned char\n{\n    dpbrowser_ipcnavstate_flag_can_go_back    = 1 << 0,\n    dpbrowser_ipcnavstate_flag_can_go_forward = 1 << 1,\n    dpbrowser_ipcnavstate_flag_is_loading     = 1 << 2,\n    dpbrowser_ipcnavstate_flag_MAX            = 1 << 7\n};\n\nenum DPBrowserIPCKeyboardKeystateFlags : unsigned char\n{\n    dpbrowser_ipckbd_keystate_flag_key_down         = 1 << 0,\n    dpbrowser_ipckbd_keystate_flag_lshift_down      = 1 << 1,\n    dpbrowser_ipckbd_keystate_flag_rshift_down      = 1 << 2,\n    dpbrowser_ipckbd_keystate_flag_lctrl_down       = 1 << 3,\n    dpbrowser_ipckbd_keystate_flag_rctrl_down       = 1 << 4,\n    dpbrowser_ipckbd_keystate_flag_lalt_down        = 1 << 5,\n    dpbrowser_ipckbd_keystate_flag_ralt_down        = 1 << 6,\n    dpbrowser_ipckbd_keystate_flag_capslock_toggled = 1 << 7,\n    dpbrowser_ipckbd_keystate_flag_MAX              = 1 << 7\n};\n\n//Common interface for implemented by DPBrowserAPIServer and DPBrowserAPIClient\nclass DPBrowserAPI\n{\n    public:\n        virtual void DPBrowser_StartBrowser(vr::VROverlayHandle_t overlay_handle, const std::string& url, bool use_transparent_background) = 0;\n        virtual void DPBrowser_DuplicateBrowserOutput(vr::VROverlayHandle_t overlay_handle_src, vr::VROverlayHandle_t overlay_handle_dst) = 0;\n        virtual void DPBrowser_PauseBrowser(vr::VROverlayHandle_t overlay_handle, bool pause) = 0;\n        virtual void DPBrowser_RecreateBrowser(vr::VROverlayHandle_t overlay_handle, bool use_transparent_background) = 0;\n        virtual void DPBrowser_StopBrowser(vr::VROverlayHandle_t overlay_handle) = 0;\n\n        virtual void DPBrowser_SetURL(vr::VROverlayHandle_t overlay_handle, const std::string& url) = 0;\n        virtual void DPBrowser_SetResolution(vr::VROverlayHandle_t overlay_handle, int width, int height) = 0;\n        virtual void DPBrowser_SetFPS(vr::VROverlayHandle_t overlay_handle, int fps) = 0;\n        virtual void DPBrowser_SetZoomLevel(vr::VROverlayHandle_t overlay_handle, float zoom_level) = 0;\n        virtual void DPBrowser_SetOverUnder3D(vr::VROverlayHandle_t overlay_handle, bool is_over_under_3D, int crop_x, int crop_y, int crop_width, int crop_height) = 0;\n\n        virtual void DPBrowser_MouseMove(vr::VROverlayHandle_t overlay_handle, int x, int y) = 0;\n        virtual void DPBrowser_MouseLeave(vr::VROverlayHandle_t overlay_handle) = 0;\n        virtual void DPBrowser_MouseDown(vr::VROverlayHandle_t overlay_handle, vr::EVRMouseButton button) = 0;\n        virtual void DPBrowser_MouseUp(vr::VROverlayHandle_t overlay_handle, vr::EVRMouseButton button) = 0;\n        virtual void DPBrowser_Scroll(vr::VROverlayHandle_t overlay_handle, float x_delta, float y_delta) = 0;\n\n        virtual void DPBrowser_KeyboardSetKeyState(vr::VROverlayHandle_t overlay_handle, DPBrowserIPCKeyboardKeystateFlags flags, unsigned char keycode) = 0;\n        virtual void DPBrowser_KeyboardToggleKey(vr::VROverlayHandle_t overlay_handle, unsigned char keycode) = 0;\n        virtual void DPBrowser_KeyboardTypeWChar(vr::VROverlayHandle_t overlay_handle, wchar_t wchar, bool down) = 0;\n        virtual void DPBrowser_KeyboardTypeString(vr::VROverlayHandle_t overlay_handle, const std::string& str) = 0;\n\n        virtual void DPBrowser_GoBack(vr::VROverlayHandle_t overlay_handle) = 0;\n        virtual void DPBrowser_GoForward(vr::VROverlayHandle_t overlay_handle) = 0;\n        virtual void DPBrowser_Refresh(vr::VROverlayHandle_t overlay_handle) = 0;\n\n        virtual void DPBrowser_GlobalSetFPS(int fps) = 0;\n        virtual void DPBrowser_ContentBlockSetEnabled(bool enable) = 0;\n        virtual void DPBrowser_ErrorPageSetStrings(const std::string& title, const std::string& heading, const std::string& message) = 0;\n};"
  },
  {
    "path": "src/Shared/DPBrowserAPIClient.cpp",
    "content": "#include \"DPBrowserAPIClient.h\"\n\n#include \"ConfigManager.h\"\n#include \"InterprocessMessaging.h\"\n#include \"Util.h\"\n#include \"Logging.h\"\n\n#ifdef DPLUS_UI\n    #include \"UIManager.h\"\n    #include \"OverlayManager.h\"\n    #include \"TranslationManager.h\"\n#else\n    #include \"OutputManager.h\"\n#endif\n\nstatic DPBrowserAPIClient g_DPBrowserAPIClient;\n\nbool DPBrowserAPIClient::LaunchServerIfNotRunning()\n{\n    //This is not going to work with the executable missing or previously discovered API mismatch\n    if ( (!m_IsServerAvailable) || (m_HasServerAPIMismatch) )\n        return false;\n\n    //Check if it's already running and update cached handle just in case\n    if (IsServerRunning())\n        return true;\n\n    //Prepare command-line\n    std::wstring browser_args_wstr = L\"--DPBrowserServer \";\n    browser_args_wstr += WStringConvertFromUTF8(ConfigManager::Get().GetValue(configid_str_browser_extra_arguments).c_str());\n\n    auto browser_arg_buffer = std::unique_ptr<WCHAR[]>{new WCHAR[browser_args_wstr.size() + 1]};        //CreateProcess() requires a modifiable buffer for the command-line\n    size_t copied_length = browser_args_wstr.copy(browser_arg_buffer.get(), browser_args_wstr.size());\n    browser_arg_buffer[copied_length] = '\\0';\n\n    //Launch the process\n    std::wstring browser_working_dir = WStringConvertFromUTF8( (ConfigManager::Get().GetApplicationPath() + g_RelativeWorkingDirBrowserApp).c_str() );\n    std::wstring browser_exe_path    = browser_working_dir + L\"/\" + WStringConvertFromUTF8(g_ExeNameBrowserApp);\n\n    #ifndef DPLUS_UI\n    //If the process is elevated, do *not* launch a web browser with admin privileges. We're not insane.\n    if (ConfigManager::Get().GetValue(configid_bool_state_misc_process_elevated))\n    {\n        //Only the dashboard app can be elevated in normal operation, so it's restricted to that\n        if (OutputManager* outmgr = OutputManager::Get())\n        {\n            outmgr->InitComIfNeeded();\n\n            //Launch browser process unelevated if possible. May fail under certain circumstances, but we won't fall back to normal launch\n            if (!ShellExecuteUnelevated(browser_exe_path.c_str(), browser_arg_buffer.get(), browser_working_dir.c_str()))\n            {\n                LOG_F(ERROR, \"Failed to launch Desktop+ Browser as unelevated process\");\n                return false;\n            }\n        }\n    }\n    else\n    #endif\n    {\n        STARTUPINFO si = {0};\n        PROCESS_INFORMATION pi = {0};\n        si.cb = sizeof(si);\n\n        if (::CreateProcess(browser_exe_path.c_str(), browser_arg_buffer.get(), nullptr, nullptr, FALSE, 0, nullptr, browser_working_dir.c_str(), &si, &pi) == 0)\n        {\n            //Don't try waiting if creating failed somehow\n            ::CloseHandle(pi.hProcess);\n            ::CloseHandle(pi.hThread);\n\n            LOG_F(ERROR, \"Failed to launch Desktop+ Browser process\");\n            return false;\n        }\n\n        ::CloseHandle(pi.hProcess);\n        ::CloseHandle(pi.hThread);\n    }\n\n    //Wait for it to be ready\n    ULONGLONG start_tick = ::GetTickCount64();\n\n    while ( (m_ServerWindowHandle = ::FindWindow(g_WindowClassNameBrowserApp, nullptr), m_ServerWindowHandle == nullptr) &&\n            (::GetTickCount64() - start_tick < 10000) )                                                                     //Wait 10 seconds max (should usually be faster though)\n    {\n        #ifndef DPLUS_UI\n            //Call busy update while waiting to appear a bit more responsive. Not ideal, but beats running in parallel and having to queue up all function calls when busy\n            if (OutputManager* outmgr = OutputManager::Get())\n            {\n                outmgr->BusyUpdate();\n                ::Sleep(outmgr->GetMaxRefreshDelay());\n            }\n        #else\n            ::Sleep(50);\n        #endif\n    }\n\n    //Check if the API versions match\n    if (m_ServerWindowHandle != nullptr)\n    {\n        const int server_api_version = (int)::SendMessage(m_ServerWindowHandle, m_Win32MessageID, dpbrowser_ipccmd_get_api_version, 0);\n\n        if (server_api_version != k_lDPBrowserAPIVersion)\n        {\n            m_HasServerAPIMismatch = true;\n\n            //Send quit message so the process doesn't linger around\n            ::PostMessage(m_ServerWindowHandle, WM_QUIT, 0, 0);\n\n            m_ServerWindowHandle = nullptr;\n\n            //Post config state to UI to allow displaying a warning since the UI doesn't try to launch the browser process on its own in most cases\n            IPCManager::Get().PostConfigMessageToUIApp(configid_bool_state_misc_browser_version_mismatch, true);\n\n            LOG_F(ERROR, \"Desktop+ Browser API version does not match! Expected version %d, but got %d\", k_lDPBrowserAPIVersion, server_api_version);\n\n            return false;\n        }\n    }\n\n    ApplyPendingSettings();\n\n    LOG_F(INFO, \"Launched Desktop+ Browser process\");\n\n    return true;\n}\n\nbool DPBrowserAPIClient::IsServerRunning()\n{\n    //This is not going to work with the executable missing or previously discovered API mismatch\n    if ( (!m_IsServerAvailable) || (m_HasServerAPIMismatch) )\n        return false;\n\n    //Check if it's already running and update cached handle\n    HWND window_handle = ::FindWindow(g_WindowClassNameBrowserApp, nullptr);\n\n    //Apply pending settings if window handle changed (only really useful after restarting UI process)\n    if (m_ServerWindowHandle != window_handle)\n    {\n        m_ServerWindowHandle = window_handle;\n        ApplyPendingSettings();\n    }\n\n    return (m_ServerWindowHandle != nullptr);\n}\n\nvoid DPBrowserAPIClient::ApplyPendingSettings()\n{\n    //Apply pending settings, if there are any\n    if (m_PendingSettingGlobalFPS != -1)\n    {\n        int pending_fps = m_PendingSettingGlobalFPS;\n        m_PendingSettingGlobalFPS = -1;                     //Important: Set these before calling the API function to avoid re-entrancy\n        DPBrowser_GlobalSetFPS(pending_fps);\n    }\n\n    if (m_PendingSettingContentBlockEnabled != -1)\n    {\n        bool is_content_block_enabled = (m_PendingSettingContentBlockEnabled != 0);\n        m_PendingSettingContentBlockEnabled = -1;\n        DPBrowser_ContentBlockSetEnabled(is_content_block_enabled);\n    }\n\n    //Send translation strings\n    #ifdef DPLUS_UI\n        if (m_PendingTranslationStrings)\n        {\n            m_PendingTranslationStrings = false;\n\n            DPBrowser_ErrorPageSetStrings(TranslationManager::GetString(tstr_BrowserErrorPageTitle), \n                                          TranslationManager::GetString(tstr_BrowserErrorPageHeading), \n                                          TranslationManager::GetString(tstr_BrowserErrorPageMessage));\n        }\n    #endif\n}\n\nvoid DPBrowserAPIClient::SendStringMessage(DPBrowserICPStringID str_id, const std::string& str) const\n{\n    HWND source_window = nullptr;\n    #ifdef DPLUS_UI\n        if (UIManager* uimgr = UIManager::Get())\n        {\n            source_window = uimgr->GetWindowHandle();\n        }\n    #else\n        if (OutputManager* outmgr = OutputManager::Get())\n        {\n            source_window = OutputManager::Get()->GetWindowHandle();\n        }\n    #endif\n\n    if (m_ServerWindowHandle != nullptr)\n    {\n        COPYDATASTRUCT cds = {0};\n        cds.dwData = str_id;\n        cds.cbData = (DWORD)str.length();  //We do not include the NUL byte\n        cds.lpData = (void*)str.c_str();\n\n        ::SendMessage(m_ServerWindowHandle, WM_COPYDATA, (WPARAM)source_window, (LPARAM)(LPVOID)&cds);\n    }\n}\n\nstd::string& DPBrowserAPIClient::GetIPCString(DPBrowserICPStringID str_id)\n{\n    return m_IPCStrings[str_id - dpbrowser_ipcstr_MIN];\n}\n\nDPBrowserAPIClient& DPBrowserAPIClient::Get()\n{\n    return g_DPBrowserAPIClient;\n}\n\nbool DPBrowserAPIClient::Init()\n{\n    //Check if Desktop+Browser server executable is available\n    std::string browser_exe_path = ConfigManager::Get().GetApplicationPath() + g_RelativeWorkingDirBrowserApp + \"/\" + g_ExeNameBrowserApp;\n    m_IsServerAvailable = FileExists(WStringConvertFromUTF8(browser_exe_path.c_str()).c_str());\n\n    //Register custom message ID\n    m_Win32MessageID = ::RegisterWindowMessage(g_WindowMessageNameBrowserApp);\n\n    LOG_F(INFO, \"Desktop+ Browser is %s\", (IsBrowserAvailable()) ? \"available\" : \"not available\");\n\n    return true;\n}\n\nvoid DPBrowserAPIClient::Quit()\n{\n    //We're not going to launch the server for this, but passing nullptr will send quit to ourselves, so don't do that\n    if (!IsServerRunning())\n        return;\n\n    //Ask browser process to quit cleanly\n    ::PostMessage(m_ServerWindowHandle, WM_QUIT, 0, 0);\n\n    //Reset some variables, though this usually is just called during Desktop+ shutdown\n    m_HasServerAPIMismatch = false;\n    m_ServerWindowHandle   = nullptr;\n}\n\nbool DPBrowserAPIClient::IsBrowserAvailable() const\n{\n    return m_IsServerAvailable;\n}\n\nDWORD DPBrowserAPIClient::GetServerAppProcessID()\n{\n    if (!LaunchServerIfNotRunning())\n        return 0;\n\n    DWORD pid = 0;\n    ::GetWindowThreadProcessId(m_ServerWindowHandle, &pid);\n\n    return pid;\n}\n\nUINT DPBrowserAPIClient::GetRegisteredMessageID() const\n{\n    return m_Win32MessageID;\n}\n\nvoid DPBrowserAPIClient::HandleIPCMessage(const MSG& msg)\n{\n    if (msg.message == WM_COPYDATA)\n    {\n        COPYDATASTRUCT* pcds = (COPYDATASTRUCT*)msg.lParam;\n\n        //Arbitrary size limit to prevent some malicous applications from sending bad data\n        if ( (pcds->dwData >= dpbrowser_ipcstr_MIN) && (pcds->dwData < dpbrowser_ipcstr_MAX) && (pcds->cbData <= 4096) ) \n        {\n            std::string copystr((char*)pcds->lpData, pcds->cbData); //We rely on the data length. The data is sent without the NUL byte\n\n            DPBrowserICPStringID str_id = (DPBrowserICPStringID)pcds->dwData;\n            GetIPCString(str_id) = copystr;\n        }\n\n        return;\n    }\n    else if (msg.message != m_Win32MessageID)\n    {\n        return;\n    }\n\n    switch (msg.wParam)\n    {\n        case dpbrowser_ipccmd_set_overlay_target:\n        {\n            m_IPCOverlayTarget = msg.lParam;\n            break;\n        }\n        case dpbrowser_ipccmd_notify_ready:\n        {\n            ApplyPendingSettings();\n            break;\n        }\n        case dpbrowser_ipccmd_notify_nav_state:\n        {\n            unsigned int overlay_id = OverlayManager::Get().FindOverlayID(m_IPCOverlayTarget);\n\n            if (overlay_id != k_ulOverlayID_None)\n            {\n                OverlayConfigData& data = OverlayManager::Get().GetConfigData(overlay_id);\n                data.ConfigBool[configid_bool_overlay_state_browser_nav_can_go_back]    = (msg.lParam & dpbrowser_ipcnavstate_flag_can_go_back);\n                data.ConfigBool[configid_bool_overlay_state_browser_nav_can_go_forward] = (msg.lParam & dpbrowser_ipcnavstate_flag_can_go_forward);\n                data.ConfigBool[configid_bool_overlay_state_browser_nav_is_loading]     = (msg.lParam & dpbrowser_ipcnavstate_flag_is_loading);\n\n                //Send to dashboard app as well so it can restore the state after an UI restart\n                IPCManager::Get().PostConfigMessageToDashboardApp(configid_int_state_overlay_current_id_override, (int)overlay_id);\n                IPCManager::Get().PostConfigMessageToDashboardApp(configid_bool_overlay_state_browser_nav_can_go_back,    data.ConfigBool[configid_bool_overlay_state_browser_nav_can_go_back]);\n                IPCManager::Get().PostConfigMessageToDashboardApp(configid_bool_overlay_state_browser_nav_can_go_forward, data.ConfigBool[configid_bool_overlay_state_browser_nav_can_go_forward]);\n                IPCManager::Get().PostConfigMessageToDashboardApp(configid_bool_overlay_state_browser_nav_is_loading,     data.ConfigBool[configid_bool_overlay_state_browser_nav_is_loading]);\n                IPCManager::Get().PostConfigMessageToDashboardApp(configid_int_state_overlay_current_id_override, -1);\n            }\n\n            m_IPCOverlayTarget = vr::k_ulOverlayHandleInvalid;\n            break;\n        }\n        case dpbrowser_ipccmd_notify_url_changed:\n        {\n            unsigned int overlay_id = OverlayManager::Get().FindOverlayID(msg.lParam);\n\n            if (overlay_id != k_ulOverlayID_None)\n            {\n                OverlayConfigData& data = OverlayManager::Get().GetConfigData(overlay_id);\n                data.ConfigStr[configid_str_overlay_browser_url] = GetIPCString(dpbrowser_ipcstr_url);\n\n                #ifdef DPLUS_UI\n                    if (UIManager* uimgr = UIManager::Get())\n                    {\n                        uimgr->GetOverlayPropertiesWindow().MarkBrowserURLChanged();\n                    }\n                #endif\n            }\n            break;\n        }\n        case dpbrowser_ipccmd_notify_title_changed:\n        {\n            unsigned int overlay_id = OverlayManager::Get().FindOverlayID(msg.lParam);\n\n            if (overlay_id != k_ulOverlayID_None)\n            {\n                OverlayConfigData& data = OverlayManager::Get().GetConfigData(overlay_id);\n                data.ConfigStr[configid_str_overlay_browser_title] = GetIPCString(dpbrowser_ipcstr_title);\n\n                #ifdef DPLUS_UI\n                    //Update auto name for the overlay and any using it as a duplication source\n                    OverlayManager::Get().SetOverlayNameAuto(overlay_id);\n\n                    for (unsigned int i = 0; i < OverlayManager::Get().GetOverlayCount(); ++i)\n                    {\n                        const OverlayConfigData& dup_data = OverlayManager::Get().GetConfigData(i);\n\n                        if ( (dup_data.ConfigInt[configid_int_overlay_capture_source] == ovrl_capsource_browser) && (dup_data.ConfigInt[configid_int_overlay_duplication_id] == (int)overlay_id) )\n                        {\n                            OverlayManager::Get().SetOverlayNameAuto(i);\n                        }\n                    }\n\n\n                    if (UIManager* uimgr = UIManager::Get())\n                    {\n                        uimgr->OnOverlayNameChanged();\n\n                        if (ImGui::StringContainsUnmappedCharacter(data.ConfigStr[configid_str_overlay_browser_title].c_str()))\n                        {\n                            TextureManager::Get().ReloadAllTexturesLater();\n                            uimgr->RepeatFrame();\n                        }\n                    }\n                #endif\n            }\n            break;\n        }\n        case dpbrowser_ipccmd_notify_fps:\n        {\n            unsigned int overlay_id = OverlayManager::Get().FindOverlayID(m_IPCOverlayTarget);\n\n            if (overlay_id != k_ulOverlayID_None)\n            {\n                OverlayManager::Get().GetConfigData(overlay_id).ConfigInt[configid_int_overlay_state_fps] = (int)msg.lParam;\n            }\n            break;\n        }\n        case dpbrowser_ipccmd_notify_lpointer_haptics:\n        {\n            //Forward to OutputManager as window message\n            IPCManager::Get().PostMessageToDashboardApp(ipcmsg_action, ipcact_lpointer_trigger_haptics, (vr::TrackedDeviceIndex_t)ConfigManager::GetValue(configid_int_state_dplus_laser_pointer_device));\n            break;\n        }\n        case dpbrowser_ipccmd_notify_keyboard_show:\n        {\n            unsigned int overlay_id = OverlayManager::Get().FindOverlayID(m_IPCOverlayTarget);\n\n            if ( (overlay_id != k_ulOverlayID_None) && (ConfigManager::GetValue(configid_bool_input_keyboard_auto_show_browser)) )\n            {\n                #ifdef DPLUS_UI\n                    if (UIManager* uimgr = UIManager::Get())\n                    {\n                        uimgr->GetVRKeyboard().GetWindow().SetAutoVisibility(overlay_id, msg.lParam);\n                    }\n                #endif\n            }\n            break;\n        }\n        case dpbrowser_ipccmd_notify_cblock_list_count:\n        {\n            ConfigManager::SetValue(configid_int_state_browser_content_blocker_list_count, (int)msg.lParam);\n            break;\n        }\n    }\n}\n\nvoid DPBrowserAPIClient::DPBrowser_StartBrowser(vr::VROverlayHandle_t overlay_handle, const std::string& url, bool use_transparent_background)\n{\n    if (!LaunchServerIfNotRunning())\n        return;\n\n    SendStringMessage(dpbrowser_ipcstr_url, url);\n    ::PostMessage(m_ServerWindowHandle, m_Win32MessageID, dpbrowser_ipccmd_set_overlay_target, overlay_handle);\n    ::PostMessage(m_ServerWindowHandle, m_Win32MessageID, dpbrowser_ipccmd_start_browser, use_transparent_background);\n}\n\nvoid DPBrowserAPIClient::DPBrowser_DuplicateBrowserOutput(vr::VROverlayHandle_t overlay_handle_src, vr::VROverlayHandle_t overlay_handle_dst)\n{\n    if (!LaunchServerIfNotRunning())\n        return;\n\n    ::PostMessage(m_ServerWindowHandle, m_Win32MessageID, dpbrowser_ipccmd_set_overlay_target, overlay_handle_src);\n    ::PostMessage(m_ServerWindowHandle, m_Win32MessageID, dpbrowser_ipccmd_duplicate_browser_output, overlay_handle_dst);\n}\n\nvoid DPBrowserAPIClient::DPBrowser_PauseBrowser(vr::VROverlayHandle_t overlay_handle, bool pause)\n{\n    if (!LaunchServerIfNotRunning())\n        return;\n\n    ::PostMessage(m_ServerWindowHandle, m_Win32MessageID, dpbrowser_ipccmd_set_overlay_target, overlay_handle);\n    ::PostMessage(m_ServerWindowHandle, m_Win32MessageID, dpbrowser_ipccmd_pause_browser, pause);\n}\n\nvoid DPBrowserAPIClient::DPBrowser_RecreateBrowser(vr::VROverlayHandle_t overlay_handle, bool use_transparent_background)\n{\n    if (!LaunchServerIfNotRunning())\n        return;\n\n    ::PostMessage(m_ServerWindowHandle, m_Win32MessageID, dpbrowser_ipccmd_set_overlay_target, overlay_handle);\n    ::PostMessage(m_ServerWindowHandle, m_Win32MessageID, dpbrowser_ipccmd_recreate_browser, use_transparent_background);\n}\n\nvoid DPBrowserAPIClient::DPBrowser_StopBrowser(vr::VROverlayHandle_t overlay_handle)\n{\n    if (!IsServerRunning())\n        return;\n\n    ::PostMessage(m_ServerWindowHandle, m_Win32MessageID, dpbrowser_ipccmd_stop_browser, overlay_handle);\n}\n\nvoid DPBrowserAPIClient::DPBrowser_SetURL(vr::VROverlayHandle_t overlay_handle, const std::string& url)\n{\n    if (!LaunchServerIfNotRunning())\n        return;\n\n    SendStringMessage(dpbrowser_ipcstr_url, url);\n    ::PostMessage(m_ServerWindowHandle, m_Win32MessageID, dpbrowser_ipccmd_set_url, overlay_handle);\n}\n\nvoid DPBrowserAPIClient::DPBrowser_SetResolution(vr::VROverlayHandle_t overlay_handle, int width, int height)\n{\n    if (!LaunchServerIfNotRunning())\n        return;\n\n    ::PostMessage(m_ServerWindowHandle, m_Win32MessageID, dpbrowser_ipccmd_set_overlay_target, overlay_handle);\n    ::PostMessage(m_ServerWindowHandle, m_Win32MessageID, dpbrowser_ipccmd_set_resoution, MAKELPARAM(width, height));\n}\n\nvoid DPBrowserAPIClient::DPBrowser_SetFPS(vr::VROverlayHandle_t overlay_handle, int fps)\n{\n    if (!LaunchServerIfNotRunning())\n        return;\n\n    ::PostMessage(m_ServerWindowHandle, m_Win32MessageID, dpbrowser_ipccmd_set_overlay_target, overlay_handle);\n    ::PostMessage(m_ServerWindowHandle, m_Win32MessageID, dpbrowser_ipccmd_set_fps, fps);\n}\n\nvoid DPBrowserAPIClient::DPBrowser_SetZoomLevel(vr::VROverlayHandle_t overlay_handle, float zoom_level)\n{\n    if (!LaunchServerIfNotRunning())\n        return;\n\n    ::PostMessage(m_ServerWindowHandle, m_Win32MessageID, dpbrowser_ipccmd_set_overlay_target, overlay_handle);\n    ::PostMessage(m_ServerWindowHandle, m_Win32MessageID, dpbrowser_ipccmd_set_zoom, pun_cast<LPARAM, float>(zoom_level));\n}\n\nvoid DPBrowserAPIClient::DPBrowser_SetOverUnder3D(vr::VROverlayHandle_t overlay_handle, bool is_over_under_3D, int crop_x, int crop_y, int crop_width, int crop_height)\n{\n    if (!LaunchServerIfNotRunning())\n        return;\n\n    ::PostMessage(m_ServerWindowHandle, m_Win32MessageID, dpbrowser_ipccmd_set_overlay_target, overlay_handle);\n\n    if (is_over_under_3D)\n    {\n        DPRect dp_rect(crop_x, crop_y, crop_x + crop_width, crop_y + crop_height);\n        ::PostMessage(m_ServerWindowHandle, m_Win32MessageID, dpbrowser_ipccmd_set_ou3d_crop, (LPARAM)dp_rect.Pack16());\n    }\n    else\n    {\n        ::PostMessage(m_ServerWindowHandle, m_Win32MessageID, dpbrowser_ipccmd_set_ou3d_crop, -1);\n    }\n}\n\nvoid DPBrowserAPIClient::DPBrowser_MouseMove(vr::VROverlayHandle_t overlay_handle, int x, int y)\n{\n    if (!IsServerRunning())\n        return;\n\n    ::PostMessage(m_ServerWindowHandle, m_Win32MessageID, dpbrowser_ipccmd_set_overlay_target, overlay_handle);\n    ::PostMessage(m_ServerWindowHandle, m_Win32MessageID, dpbrowser_ipccmd_mouse_move, MAKELPARAM(x, y));\n}\n\nvoid DPBrowserAPIClient::DPBrowser_MouseLeave(vr::VROverlayHandle_t overlay_handle)\n{\n    if (!IsServerRunning())\n        return;\n\n    ::PostMessage(m_ServerWindowHandle, m_Win32MessageID, dpbrowser_ipccmd_mouse_leave, overlay_handle);\n}\n\nvoid DPBrowserAPIClient::DPBrowser_MouseDown(vr::VROverlayHandle_t overlay_handle, vr::EVRMouseButton button)\n{\n    if (!IsServerRunning())\n        return;\n\n    ::PostMessage(m_ServerWindowHandle, m_Win32MessageID, dpbrowser_ipccmd_set_overlay_target, overlay_handle);\n    ::PostMessage(m_ServerWindowHandle, m_Win32MessageID, dpbrowser_ipccmd_mouse_down, button);\n}\n\nvoid DPBrowserAPIClient::DPBrowser_MouseUp(vr::VROverlayHandle_t overlay_handle, vr::EVRMouseButton button)\n{\n    if (!IsServerRunning())\n        return;\n\n    ::PostMessage(m_ServerWindowHandle, m_Win32MessageID, dpbrowser_ipccmd_set_overlay_target, overlay_handle);\n    ::PostMessage(m_ServerWindowHandle, m_Win32MessageID, dpbrowser_ipccmd_mouse_up, button);\n}\n\nvoid DPBrowserAPIClient::DPBrowser_Scroll(vr::VROverlayHandle_t overlay_handle, float x_delta, float y_delta)\n{\n    if (!IsServerRunning())\n        return;\n\n    //Squeeze the floats into DWORDs so they'll survive MAKEQWORD\n    DWORD x_delta_uint = pun_cast<DWORD, float>(x_delta);\n    DWORD y_delta_uint = pun_cast<DWORD, float>(y_delta);\n\n    ::PostMessage(m_ServerWindowHandle, m_Win32MessageID, dpbrowser_ipccmd_set_overlay_target, overlay_handle);\n    ::PostMessage(m_ServerWindowHandle, m_Win32MessageID, dpbrowser_ipccmd_scroll, MAKEQWORD(x_delta_uint, y_delta_uint));\n}\n\nvoid DPBrowserAPIClient::DPBrowser_KeyboardSetKeyState(vr::VROverlayHandle_t overlay_handle, DPBrowserIPCKeyboardKeystateFlags flags, unsigned char keycode)\n{\n    if (!LaunchServerIfNotRunning())\n        return;\n\n    ::PostMessage(m_ServerWindowHandle, m_Win32MessageID, dpbrowser_ipccmd_set_overlay_target, overlay_handle);\n    ::PostMessage(m_ServerWindowHandle, m_Win32MessageID, dpbrowser_ipccmd_keyboard_vkey, MAKELPARAM(flags, keycode));\n}\n\nvoid DPBrowserAPIClient::DPBrowser_KeyboardToggleKey(vr::VROverlayHandle_t overlay_handle, unsigned char keycode)\n{\n    if (!LaunchServerIfNotRunning())\n        return;\n\n    ::PostMessage(m_ServerWindowHandle, m_Win32MessageID, dpbrowser_ipccmd_set_overlay_target, overlay_handle);\n    ::PostMessage(m_ServerWindowHandle, m_Win32MessageID, dpbrowser_ipccmd_keyboard_vkey_toggle, keycode);\n}\n\nvoid DPBrowserAPIClient::DPBrowser_KeyboardTypeWChar(vr::VROverlayHandle_t overlay_handle, wchar_t wchar, bool down)\n{\n    if (!LaunchServerIfNotRunning())\n        return;\n\n    ::PostMessage(m_ServerWindowHandle, m_Win32MessageID, dpbrowser_ipccmd_set_overlay_target, overlay_handle);\n    ::PostMessage(m_ServerWindowHandle, m_Win32MessageID, dpbrowser_ipccmd_keyboard_wchar, MAKELPARAM(wchar, down));\n}\n\nvoid DPBrowserAPIClient::DPBrowser_KeyboardTypeString(vr::VROverlayHandle_t overlay_handle, const std::string& str)\n{\n    SendStringMessage(dpbrowser_ipcstr_keyboard_string, str);\n    ::PostMessage(m_ServerWindowHandle, m_Win32MessageID, dpbrowser_ipccmd_keyboard_string, overlay_handle);\n}\n\nvoid DPBrowserAPIClient::DPBrowser_GoBack(vr::VROverlayHandle_t overlay_handle)\n{\n    if (!LaunchServerIfNotRunning())\n        return;\n\n    ::PostMessage(m_ServerWindowHandle, m_Win32MessageID, dpbrowser_ipccmd_go_back, overlay_handle);\n}\n\nvoid DPBrowserAPIClient::DPBrowser_GoForward(vr::VROverlayHandle_t overlay_handle)\n{\n    if (!LaunchServerIfNotRunning())\n        return;\n\n    ::PostMessage(m_ServerWindowHandle, m_Win32MessageID, dpbrowser_ipccmd_go_forward, overlay_handle);\n}\n\nvoid DPBrowserAPIClient::DPBrowser_Refresh(vr::VROverlayHandle_t overlay_handle)\n{\n    if (!LaunchServerIfNotRunning())\n        return;\n\n    ::PostMessage(m_ServerWindowHandle, m_Win32MessageID, dpbrowser_ipccmd_refresh, overlay_handle);\n}\n\nvoid DPBrowserAPIClient::DPBrowser_GlobalSetFPS(int fps)\n{\n    if (!IsServerRunning())\n    {\n        m_PendingSettingGlobalFPS = fps;    //Will be applied after launching the server instead\n        return;\n    }\n\n    ::PostMessage(m_ServerWindowHandle, m_Win32MessageID, dpbrowser_ipccmd_global_set_fps, fps);\n}\n\nvoid DPBrowserAPIClient::DPBrowser_ContentBlockSetEnabled(bool enable)\n{\n    if (!IsServerRunning())\n    {\n        m_PendingSettingContentBlockEnabled = enable;    //Will be applied after launching the server instead\n        return;\n    }\n\n    ::PostMessage(m_ServerWindowHandle, m_Win32MessageID, dpbrowser_ipccmd_cblock_set_enabled, enable);\n}\n\nvoid DPBrowserAPIClient::DPBrowser_ErrorPageSetStrings(const std::string& title, const std::string& heading, const std::string& message)\n{\n    if (!IsServerRunning())\n    {\n        m_PendingTranslationStrings = true;\n        return;\n    }\n\n    SendStringMessage(dpbrowser_ipcstr_tstr_error_title,   title);\n    SendStringMessage(dpbrowser_ipcstr_tstr_error_heading, heading);\n    SendStringMessage(dpbrowser_ipcstr_tstr_error_message, message);\n\n    ::PostMessage(m_ServerWindowHandle, m_Win32MessageID, dpbrowser_ipccmd_error_set_strings, 0);\n}\n"
  },
  {
    "path": "src/Shared/DPBrowserAPIClient.h",
    "content": "#pragma once\n\n#include \"DPBrowserAPI.h\"\n\nclass DPBrowserAPIClient : public DPBrowserAPI\n{\n    private:\n        bool m_IsServerAvailable = false;\n        bool m_HasServerAPIMismatch = false;\n        HWND m_ServerWindowHandle = nullptr;\n        UINT m_Win32MessageID = 0;\n\n        std::string m_IPCStrings[dpbrowser_ipcstr_MAX - dpbrowser_ipcstr_MIN];\n        vr::VROverlayHandle_t m_IPCOverlayTarget = vr::k_ulOverlayHandleInvalid;\n\n        //Pending settings stored here when server isn't running yet and applied later on launch\n        int m_PendingSettingGlobalFPS = -1;\n        int m_PendingSettingContentBlockEnabled = -1;\n        bool m_PendingTranslationStrings = true;\n\n        bool LaunchServerIfNotRunning();                        //Should be called and checked for in most API implementations, also makes sure m_ServerWindowHandle is updated\n        bool IsServerRunning();                                 //Also makes sure m_ServerWindowHandle is updated\n        void ApplyPendingSettings();\n        void SendStringMessage(DPBrowserICPStringID str_id, const std::string& str) const;\n\n        std::string& GetIPCString(DPBrowserICPStringID str_id); //Abstracts the minimum string ID away when acccessing m_IPCStrings\n\n    public:\n        static DPBrowserAPIClient& Get();\n\n        bool Init();\n        void Quit();\n        bool IsBrowserAvailable() const;\n        DWORD GetServerAppProcessID();\n        UINT GetRegisteredMessageID() const;\n\n        void HandleIPCMessage(const MSG& msg);\n\n        //DPBrowserAPI:\n        virtual void DPBrowser_StartBrowser(vr::VROverlayHandle_t overlay_handle, const std::string& url, bool use_transparent_background) override;\n        virtual void DPBrowser_DuplicateBrowserOutput(vr::VROverlayHandle_t overlay_handle_src, vr::VROverlayHandle_t overlay_handle_dst) override;\n        virtual void DPBrowser_PauseBrowser(vr::VROverlayHandle_t overlay_handle, bool pause) override;\n        virtual void DPBrowser_RecreateBrowser(vr::VROverlayHandle_t overlay_handle, bool use_transparent_background) override;\n        virtual void DPBrowser_StopBrowser(vr::VROverlayHandle_t overlay_handle) override;\n\n        virtual void DPBrowser_SetURL(vr::VROverlayHandle_t overlay_handle, const std::string& url) override;\n        virtual void DPBrowser_SetResolution(vr::VROverlayHandle_t overlay_handle, int width, int height) override;\n        virtual void DPBrowser_SetFPS(vr::VROverlayHandle_t overlay_handle, int fps) override;\n        virtual void DPBrowser_SetZoomLevel(vr::VROverlayHandle_t overlay_handle, float zoom_level) override;\n        virtual void DPBrowser_SetOverUnder3D(vr::VROverlayHandle_t overlay_handle, bool is_over_under_3D, int crop_x, int crop_y, int crop_width, int crop_height) override;\n\n        virtual void DPBrowser_MouseMove(vr::VROverlayHandle_t overlay_handle, int x, int y) override;\n        virtual void DPBrowser_MouseLeave(vr::VROverlayHandle_t overlay_handle) override;\n        virtual void DPBrowser_MouseDown(vr::VROverlayHandle_t overlay_handle, vr::EVRMouseButton button) override;\n        virtual void DPBrowser_MouseUp(vr::VROverlayHandle_t overlay_handle, vr::EVRMouseButton button) override;\n        virtual void DPBrowser_Scroll(vr::VROverlayHandle_t overlay_handle, float x_delta, float y_delta) override;\n\n        virtual void DPBrowser_KeyboardSetKeyState(vr::VROverlayHandle_t overlay_handle, DPBrowserIPCKeyboardKeystateFlags flags, unsigned char keycode) override;\n        virtual void DPBrowser_KeyboardToggleKey(vr::VROverlayHandle_t overlay_handle, unsigned char keycode) override;\n        virtual void DPBrowser_KeyboardTypeWChar(vr::VROverlayHandle_t overlay_handle, wchar_t wchar, bool down) override;\n        virtual void DPBrowser_KeyboardTypeString(vr::VROverlayHandle_t overlay_handle, const std::string& str) override;\n\n        virtual void DPBrowser_GoBack(vr::VROverlayHandle_t overlay_handle) override;\n        virtual void DPBrowser_GoForward(vr::VROverlayHandle_t overlay_handle) override;\n        virtual void DPBrowser_Refresh(vr::VROverlayHandle_t overlay_handle) override;\n\n        virtual void DPBrowser_GlobalSetFPS(int fps) override;\n        virtual void DPBrowser_ContentBlockSetEnabled(bool enable) override;\n        virtual void DPBrowser_ErrorPageSetStrings(const std::string& title, const std::string& heading, const std::string& message) override;\n};"
  },
  {
    "path": "src/Shared/DPRect.h",
    "content": "//This is pretty much a straight adaption of Dear ImGui's internal ImRect class\n\n#pragma once\n\n#include \"Util.h\"\n#include \"Vectors.h\"\n\n// 2D axis aligned bounding-box\nclass DPRect\n{\npublic:\n    Vector2Int      Min;    // Upper-left\n    Vector2Int      Max;    // Lower-right\n\n    DPRect()                                             : Min(0, 0), Max(0, 0)             {}\n    DPRect(const Vector2Int& min, const Vector2Int& max) : Min(min), Max(max)               {}\n    DPRect(int x1, int y1, int x2, int y2)               : Min(x1, y1), Max(x2, y2)         {}\n\n    Vector2Int  GetCenter() const                   { return Vector2Int(int((Min.x + Max.x) * 0.5f), int((Min.y + Max.y) * 0.5f)); }\n    Vector2Int  GetSize() const                     { return Vector2Int(Max.x - Min.x, Max.y - Min.y); }\n    int         GetWidth() const                    { return Max.x - Min.x; }\n    int         GetHeight() const                   { return Max.y - Min.y; }\n    Vector2Int  GetTL() const                       { return Min; }                      // Top-left\n    Vector2Int  GetTR() const                       { return Vector2Int(Max.x, Min.y); } // Top-right\n    Vector2Int  GetBL() const                       { return Vector2Int(Min.x, Max.y); } // Bottom-left\n    Vector2Int  GetBR() const                       { return Max; }                      // Bottom-right\n    bool        Contains(const Vector2Int& p) const { return p.x     >= Min.x && p.y     >= Min.y && p.x     <  Max.x && p.y     <  Max.y; }\n    bool        Contains(const DPRect& r) const     { return r.Min.x >= Min.x && r.Min.y >= Min.y && r.Max.x <= Max.x && r.Max.y <= Max.y; }\n    bool        Overlaps(const DPRect& r) const     { return r.Min.y <  Max.y && r.Max.y >  Min.y && r.Min.x <  Max.x && r.Max.x >  Min.x; }\n    void        Add(const Vector2Int& p)            { if (Min.x > p.x)     Min.x = p.x;     if (Min.y > p.y)     Min.y = p.y;     if (Max.x < p.x)     Max.x = p.x;     if (Max.y < p.y)     Max.y = p.y;     }\n    void        Add(const DPRect& r)                { if (Min.x > r.Min.x) Min.x = r.Min.x; if (Min.y > r.Min.y) Min.y = r.Min.y; if (Max.x < r.Max.x) Max.x = r.Max.x; if (Max.y < r.Max.y) Max.y = r.Max.y; }\n    void        Expand(const int amount)            { Min.x -= amount;   Min.y -= amount;   Max.x += amount;   Max.y += amount;   }\n    void        Expand(const Vector2Int& amount)    { Min.x -= amount.x; Min.y -= amount.y; Max.x += amount.x; Max.y += amount.y; }\n    void        Translate(const Vector2Int& d)      { Min.x += d.x; Min.y += d.y; Max.x += d.x; Max.y += d.y; }\n    void        TranslateX(int dx)                  { Min.x += dx; Max.x += dx; }\n    void        TranslateY(int dy)                  { Min.y += dy; Max.y += dy; }\n    void        ClipWith(const DPRect& r)           { Min = Vector2Int::vec_max(Min, r.Min); Max = Vector2Int::vec_min(Max, r.Max); }         // Simple version, may lead to an inverted rectangle, which is fine for Contains/Overlaps test but not for display.\n    void        ClipWithFull(const DPRect& r)       { Min = Vector2Int::vec_clamp(Min, r.Min, r.Max); Max = Vector2Int::vec_clamp(Max, r.Min, r.Max); } // Full version, ensure both points are fully clipped.\n    bool        IsInverted() const                  { return Min.x > Max.x || Min.y > Max.y; }\n    uint64_t    Pack16() const\n    {\n        uint64_t min_x = (uint16_t)Min.x, min_y = (uint16_t)Min.y, max_x = (uint16_t)Max.x, max_y = (uint16_t)Max.y;\n        return (min_x << 48) | (min_y << 32) | (max_x << 16) | max_y;\n    }\n    void        Unpack16(uint64_t value)\n    {\n        Min.x = int16_t((value & 0xFFFF000000000000) >> 48);\n        Min.y = int16_t((value & 0x0000FFFF00000000) >> 32);\n        Max.x = int16_t((value & 0x00000000FFFF0000) >> 16);\n        Max.y = int16_t (value & 0x000000000000FFFF);\n    }\n\n    bool        operator==(const DPRect& r) const   { return r.Min == Min && r.Max == Max; }\n};"
  },
  {
    "path": "src/Shared/Ini.cpp",
    "content": "//Main implementation based on ini.h (https://github.com/mattiasgustavsson/libs)\n//C++ interface may be licensed under the same terms as ini.h itself if desired\n\n/*\n------------------------------------------------------------------------------\n          Licensing information can be found at the end of the file.\n------------------------------------------------------------------------------\n\nini.h - v1.2+(Desktop+) - Simple ini-file reader for C/C++.\n\nDo this:\n    #define INI_IMPLEMENTATION\nbefore you include this file in *one* C/C++ file to create the implementation.\n\nDesktop+: We don't do that since we use it directly and expose our custom interface instead\n*/\n\n//#ifndef ini_h\n//#define ini_h\n\n#define INI_GLOBAL_SECTION ( 0 )\n#define INI_NOT_FOUND ( -1 )\n\ntypedef struct ini_t ini_t;\n\nini_t* ini_create( void* memctx );\nini_t* ini_load( char const* data, void* memctx );\n\nint ini_save( ini_t const* ini, char* data, int size );\nvoid ini_destroy( ini_t* ini );\n\nint ini_section_count( ini_t const* ini );\nchar const* ini_section_name( ini_t const* ini, int section );\n\nint ini_property_count( ini_t const* ini, int section );\nchar const* ini_property_name( ini_t const* ini, int section, int property );\nchar const* ini_property_value( ini_t const* ini, int section, int property );\n\nint ini_find_section( ini_t const* ini, char const* name, int name_length );\nint ini_find_property( ini_t const* ini, int section, char const* name, int name_length );\n\nint ini_section_add( ini_t* ini, char const* name, int length );\nvoid ini_property_add( ini_t* ini, int section, char const* name, int name_length, char const* value, int value_length );\nvoid ini_section_remove( ini_t* ini, int section );\nvoid ini_property_remove( ini_t* ini, int section, int property );\n\nvoid ini_section_name_set( ini_t* ini, int section, char const* name, int length );\nvoid ini_property_name_set( ini_t* ini, int section, int property, char const* name, int length );\nvoid ini_property_value_set( ini_t* ini, int section, int property, char const* value, int length  );\n\n#undef _CRT_NONSTDC_NO_DEPRECATE \n#define _CRT_NONSTDC_NO_DEPRECATE \n#undef _CRT_SECURE_NO_WARNINGS\n#define _CRT_SECURE_NO_WARNINGS\n#pragma warning(disable : 4996)\n\n//#endif /* ini_h */\n\n//Desktop+: (Limited) C++ interface\n\n#include \"Ini.h\"\n\n#include <string>\n#define NOMINMAX\n#include <windows.h>\n\n#ifdef __STRICT_ANSI__\n#undef __STRICT_ANSI__ //MinGW won't have _wfopen in strict mode, but we need it for proper unicode path support\n#endif\n\n\nIni::Ini(const std::wstring& wfilename, bool replace_contents) : m_WFileName(wfilename), m_IniPtr(nullptr)\n{\n    if (!replace_contents)\n    {\n        FILE* fp = _wfopen(m_WFileName.c_str(), L\"rt\");\n        if (fp != nullptr)\n        {\n            //Read entire file into string\n            std::string contents;\n            fseek(fp, 0, SEEK_END);\n            contents.resize(ftell(fp));\n            rewind(fp);\n            size_t bytes_read = fread(&contents[0], 1, contents.size(), fp);\n            fclose(fp);\n\n            contents.resize(bytes_read);\n\n            m_IniPtr = ini_load(contents.data(), nullptr);\n            return;\n        }\n    }\n\n    m_IniPtr = ini_create(nullptr);\n}\n\nIni::~Ini()\n{\n    ini_destroy(m_IniPtr);\n}\n\nbool Ini::Save()\n{\n    return Save(m_WFileName);\n}\n\nbool Ini::Save(const std::wstring& filename)\n{\n    int size = ini_save(m_IniPtr, nullptr, 0); //Get required size\n    if (size > 0)\n    {\n        char* data = new (std::nothrow) char[size];\n\n        if (data == nullptr)\n        {\n            return false;\n        }\n\n        size = ini_save(m_IniPtr, data, size); //Store in data buffer\n\n        FILE* fp = _wfopen(filename.c_str(), L\"wt\");\n        if (fp != nullptr)\n        {\n            fwrite(data, 1, size - 1, fp); //data is 0-terminated when using size provided by ini_save(), so don't write the last byte\n            fclose(fp);\n        }\n        delete[] data;\n\n        return (fp != nullptr);\n    }\n\n    return false;\n}\n\nstd::string Ini::ReadString(const char* section, const char* key, const char* default_value) const\n{\n    int section_id = ini_find_section(m_IniPtr, section, 0);\n\n    if (section_id != INI_NOT_FOUND)\n    {\n        int property_id = ini_find_property(m_IniPtr, section_id, key, 0);\n\n        if (property_id != INI_NOT_FOUND)\n        {\n            return ini_property_value(m_IniPtr, section_id, property_id);\n        }\n    }\n\n    return (default_value != nullptr) ? default_value : \"\";\n}\n\nvoid Ini::WriteString(const char* section, const char* key, const char* value)\n{\n    int section_id = ini_find_section(m_IniPtr, section, 0);\n\n    if (section_id == INI_NOT_FOUND) //Add if not already existing\n    {\n        section_id = ini_section_add(m_IniPtr, section, 0);\n    }\n\n    int property_id = ini_find_property(m_IniPtr, section_id, key, 0);\n\n    if (property_id == INI_NOT_FOUND) //Add if not already existing\n    {\n        ini_property_add(m_IniPtr, section_id, key, 0, value, -1);\n    }\n    else\n    {\n        ini_property_value_set(m_IniPtr, section_id, property_id, value, 0);\n    }\n}\n\nint Ini::ReadInt(const char* section, const char* key, int default_value) const\n{\n    std::string str_value = ReadString(section, key, \"NAN\");\n\n    if (str_value != \"NAN\")\n        return atoi(str_value.c_str());\n    else\n        return default_value;\n}\n\nbool Ini::ReadBool(const char* section, const char* key, bool default_value) const\n{\n    std::string str_value = ReadString(section, key, \"NAN\");\n\n    //Allow these, because why not\n    if (str_value == \"true\")\n        return true;\n    else if (str_value == \"false\")\n        return false;\n\n    if (str_value != \"NAN\")\n        return (atoi(str_value.c_str()) != 0);\n    else\n        return default_value;\n}\n\nvoid Ini::WriteInt(const char* section, const char* key, int value)\n{\n    WriteString(section, key, std::to_string(value).c_str());\n}\n\nvoid Ini::WriteBool(const char* section, const char* key, bool value)\n{\n    if (value)\n        WriteString(section, key, \"true\");\n    else\n        WriteString(section, key, \"false\");\n}\n\nbool Ini::SectionExists(const char* section) const\n{\n    return (ini_find_section(m_IniPtr, section, 0) != INI_NOT_FOUND);\n}\n\nbool Ini::KeyExists(const char* section, const char* key) const\n{\n    int section_id = ini_find_section(m_IniPtr, section, 0);\n\n    if (section_id == INI_NOT_FOUND)\n    {\n        return false;\n    }\n\n    return (ini_find_property(m_IniPtr, section_id, key, 0) != INI_NOT_FOUND);\n}\n\nbool Ini::RenameSection(const char* section, const char* new_name)\n{\n    int section_id = ini_find_section(m_IniPtr, section, 0);\n\n    if (section_id == INI_NOT_FOUND)\n    {\n        return false;\n    }\n\n    ini_section_name_set(m_IniPtr, section_id, new_name, 0);\n\n    return true;\n}\n\nvoid Ini::RemoveSection(const char* section)\n{\n    //There is a bug in ini_section_remove() which causes sections to not be removed properly under certain conditions\n    //I've not been able to find the real cause, but removing the section until it's not found anymore works. Sounds like there's multiple section entries, but that's not it I think\n    int section_id;\n    while (section_id = ini_find_section(m_IniPtr, section, 0), section_id != INI_NOT_FOUND)\n    {\n        ini_section_remove(m_IniPtr, section_id);\n    }\n}\n\nvoid Ini::RemoveKey(const char* section, const char* key)\n{\n    int section_id = ini_find_section(m_IniPtr, section, 0);\n\n    if (section_id != INI_NOT_FOUND)\n    {\n        ini_property_remove(m_IniPtr, section_id, ini_find_property(m_IniPtr, section_id, key, 0)); //ini_property_remove checks for section_id range so this is fine\n    }\n}\n\nstd::vector<std::string> Ini::GetSectionList()\n{\n    std::vector<std::string> section_list;\n\n    for (int i = 0; i < ini_section_count(m_IniPtr); ++i)\n    {\n        section_list.push_back(ini_section_name(m_IniPtr, i));\n    }\n\n    return section_list;\n}\n//C++ Interface end. Below is normal ini.h code\n\n/**\n\nExamples\n========\n\nLoading an ini file and retrieving values\n-----------------------------------------\n\n    #define INI_IMPLEMENTATION\n    #include \"ini.h\"\n\n    #include <stdio.h>\n    #include <stdlib.h>\n\n    int main()\n        {\n        FILE* fp = fopen( \"test.ini\", \"r\" );\n        fseek( fp, 0, SEEK_END );\n        int size = ftell( fp );\n        fseek( fp, 0, SEEK_SET );\n        char* data = (char*) malloc( size + 1 );\n        fread( data, 1, size, fp );\n        data[ size ] = '\\0';\n        fclose( fp );\n\n        ini_t* ini = ini_load( data );\n        free( data );\n        int second_index = ini_find_property( ini, INI_GLOBAL_SECTION, \"SecondSetting\" );\n        char const* second = ini_property_value( ini, INI_GLOBAL_SECTION, second_index );\n        printf( \"%s=%s\\n\", \"SecondSetting\", second );\n        int section = ini_find_section( ini, \"MySection\" );\n        int third_index = ini_find_property( ini, section, \"ThirdSetting\" );\n        char const* third = ini_property_value( ini, section, third_index );\n        printf( \"%s=%s\\n\", \"ThirdSetting\", third );\n        ini_destroy( ini );\n\n        return 0;\n        }\n\n\nCreating a new ini file\n-----------------------\n\n    #define INI_IMPLEMENTATION\n    #include \"ini.h\"\n\n    #include <stdio.h>\n    #include <stdlib.h>\n\n    int main()\n        {       \n        ini_t* ini = ini_create();\n        ini_property_add( ini, INI_GLOBAL_SECTION, \"FirstSetting\", \"Test\" );\n        ini_property_add( ini, INI_GLOBAL_SECTION, \"SecondSetting\", \"2\" );\n        int section = ini_section_add( ini, \"MySection\" );\n        ini_property_add( ini, section, \"ThirdSetting\", \"Three\" );\n\n        int size = ini_save( ini, NULL, 0 ); // Find the size needed\n        char* data = (char*) malloc( size );\n        size = ini_save( ini, data, size ); // Actually save the file\n        ini_destroy( ini );\n\n        FILE* fp = fopen( \"test.ini\", \"w\" );\n        fwrite( data, 1, size, fp );\n        fclose( fp );\n        free( data );\n\n        return 0;\n        }\n\n\n\nAPI Documentation\n=================\n\nini.h is a small library for reading classic .ini files. It is a single-header library, and does not need any .lib files \nor other binaries, or any build scripts. To use it, you just include ini.h to get the API declarations. To get the \ndefinitions, you must include ini.h from *one* single C or C++ file, and #define the symbol `INI_IMPLEMENTATION` before \nyou do. \n\n\nCustomization\n-------------\nThere are a few different things in ini.h which are configurable by #defines. The customizations only affect the \nimplementation, so will only need to be defined in the file where you have the #define INI_IMPLEMENTATION.\n\nNote that if all customizations are utilized, ini.h will include no external files whatsoever, which might be useful\nif you need full control over what code is being built.\n\n\n### Custom memory allocators\n\nTo store the internal data structures, ini.h needs to do dynamic allocation by calling `malloc`. Programs might want to \nkeep track of allocations done, or use custom defined pools to allocate memory from. ini.h allows for specifying custom \nmemory allocation functions for `malloc` and `free`.\nThis is done with the following code:\n\n    #define INI_IMPLEMENTATION\n    #define INI_MALLOC( ctx, size ) ( my_custom_malloc( ctx, size ) )\n    #define INI_FREE( ctx, ptr ) ( my_custom_free( ctx, ptr ) )\n    #include \"ini.h\"\n\nwhere `my_custom_malloc` and `my_custom_free` are your own memory allocation/deallocation functions. The `ctx` parameter\nis an optional parameter of type `void*`. When `ini_create` or `ini_load` is called, you can pass in a `memctx` \nparameter, which can be a pointer to anything you like, and which will be passed through as the `ctx` parameter to every \n`INI_MALLOC`/`INI_FREE` call. For example, if you are doing memory tracking, you can pass a pointer to your tracking \ndata as `memctx`, and in your custom allocation/deallocation function, you can cast the `ctx` param back to the \nright type, and access the tracking data.\n\nIf no custom allocator is defined, ini.h will default to `malloc` and `free` from the C runtime library.\n\n\n### Custom C runtime function\n\nThe library makes use of three additional functions from the C runtime library, and for full flexibility, it allows you \nto substitute them for your own. Here's an example:\n\n    #define INI_IMPLEMENTATION\n    #define INI_MEMCPY( dst, src, cnt ) ( my_memcpy_func( dst, src, cnt ) )\n    #define INI_STRLEN( s ) ( my_strlen_func( s ) )\n    #define INI_STRNICMP( s1, s2, cnt ) ( my_strnicmp_func( s1, s2, cnt ) )\n    #include \"ini.h\"\n\nIf no custom function is defined, ini.h will default to the C runtime library equivalent.\n\n\nini_create\n----------\n    \n    ini_t* ini_create( void* memctx )\n\nInstantiates a new, empty ini structure, which can be manipulated with other API calls, to fill it with data. To save it\nout to an ini-file string, use `ini_save`. When no longer needed, it can be destroyed by calling `ini_destroy`.\n`memctx` is a pointer to user defined data which will be passed through to the custom INI_MALLOC/INI_FREE calls. It can \nbe NULL if no user defined data is needed.\n\n\nini_load\n--------\n\n    ini_t* ini_load( char const* data, void* memctx )\n\nParse the zero-terminated string `data` containing an ini-file, and create a new ini_t instance containing the data. \nThe instance can be manipulated with other API calls to enumerate sections/properties and retrieve values. When no \nlonger needed, it can be destroyed by calling `ini_destroy`. `memctx` is a pointer to user defined data which will be \npassed through to the custom INI_MALLOC/INI_FREE calls. It can be NULL if no user defined data is needed.\n\n\nini_save\n--------\n    \n    int ini_save( ini_t const* ini, char* data, int size )\n\nSaves an ini structure as a zero-terminated ini-file string, into the specified buffer. Returns the number of bytes \nwritten, including the zero terminator. If `data` is NULL, nothing is written, but `ini_save` still returns the number\nof bytes it would have written. If the size of `data`, as specified in the `size` parameter, is smaller than that \nrequired, only part of the ini-file string will be written. `ini_save` still returns the number of bytes it would have\nwritten had the buffer been large enough.\n\n\nini_destroy\n-----------\n\n    void ini_destroy( ini_t* ini )\n\nDestroy an `ini_t` instance created by calling `ini_load` or `ini_create`, releasing the memory allocated by it. No\nfurther API calls are valid on an `ini_t` instance after calling `ini_destroy` on it.\n\n\nini_section_count\n-----------------\n\n    int ini_section_count( ini_t const* ini )\n\nReturns the number of sections in an ini file. There's at least one section in an ini file (the global section), but \nthere can be many more, each specified in the file by the section name wrapped in square brackets [ ].\n\n\nini_section_name\n----------------\n\n    char const* ini_section_name( ini_t const* ini, int section )\n\nReturns the name of the section with the specified index. `section` must be non-negative and less than the value \nreturned by `ini_section_count`, or `ini_section_name` will return NULL. The defined constant `INI_GLOBAL_SECTION` can\nbe used to indicate the global section.\n\n\nini_property_count\n------------------\n\n    int ini_property_count( ini_t const* ini, int section )\n\nReturns the number of properties belonging to the section with the specified index. `section` must be non-negative and \nless than the value returned by `ini_section_count`, or `ini_section_name` will return 0. The defined constant \n`INI_GLOBAL_SECTION` can be used to indicate the global section. Properties are declared in the ini-file on he format\n`name=value`.\n\n\nini_property_name\n-----------------\n\n    char const* ini_property_name( ini_t const* ini, int section, int property )\n\nReturns the name of the property with the specified index `property` in the section with the specified index `section`.\n`section` must be non-negative and less than the value returned by `ini_section_count`, and `property` must be \nnon-negative and less than the value returned by `ini_property_count`, or `ini_property_name` will return NULL. The \ndefined constant `INI_GLOBAL_SECTION` can be used to indicate the global section.\n\n\nini_property_value\n------------------\n\n    char const* ini_property_value( ini_t const* ini, int section, int property )\n\nReturns the value of the property with the specified index `property` in the section with the specified index `section`.\n`section` must be non-negative and less than the value returned by `ini_section_count`, and `property` must be \nnon-negative and less than the value returned by `ini_property_count`, or `ini_property_value` will return NULL. The \ndefined constant `INI_GLOBAL_SECTION` can be used to indicate the global section.\n\n\nini_find_section\n----------------\n\n    int ini_find_section( ini_t const* ini, char const* name, int name_length )\n\nFinds the section with the specified name, and returns its index. `name_length` specifies the number of characters in\n`name`, which does not have to be zero-terminated. If `name_length` is zero, the length is determined automatically, but\nin this case `name` has to be zero-terminated. If no section with the specified name could be found, the value\n`INI_NOT_FOUND` is returned.\n\n\nini_find_property\n-----------------\n\n    int ini_find_property( ini_t const* ini, int section, char const* name, int name_length )\n\nFinds the property with the specified name, within the section with the specified index, and returns the index of the \nproperty. `name_length` specifies the number of characters in `name`, which does not have to be zero-terminated. If \n`name_length` is zero, the length is determined automatically, but in this case `name` has to be zero-terminated. If no \nproperty with the specified name could be found within the specified section, the value `INI_NOT_FOUND` is  returned.\n`section` must be non-negative and less than the value returned by `ini_section_count`, or `ini_find_property` will \nreturn `INI_NOT_FOUND`. The defined constant `INI_GLOBAL_SECTION` can be used to indicate the global section.\n\n\nini_section_add\n---------------\n\n    int ini_section_add( ini_t* ini, char const* name, int length )\n\nAdds a section with the specified name, and returns the index it was added at. There is no check done to see if a \nsection with the specified name already exists - multiple sections of the same name are allowed. `length` specifies the \nnumber of characters in `name`, which does not have to be zero-terminated. If `length` is zero, the length is determined \nautomatically, but in this case `name` has to be zero-terminated.\n\n\nini_property_add\n----------------\n    \n    void ini_property_add( ini_t* ini, int section, char const* name, int name_length, char const* value, int value_length )\n\nAdds a property with the specified name and value to the specified section, and returns the index it was added at. There \nis no check done to see if a property with the specified name already exists - multiple properties of the same name are \nallowed. `name_length` and `value_length` specifies the number of characters in `name` and `value`, which does not have \nto be zero-terminated. If `name_length` or `value_length` is zero, the length is determined automatically, but in this \ncase `name`/`value` has to be zero-terminated. `section` must be non-negative and less than the value returned by\n`ini_section_count`, or the property will not be added. The defined constant `INI_GLOBAL_SECTION` can be used to \nindicate the global section.\n! Desktop+: value_length has to be negative for auto-length\n\n\nini_section_remove\n------------------\n\n    void ini_section_remove( ini_t* ini, int section )\n\nRemoves the section with the specified index, and all properties within it. `section` must be non-negative and less than \nthe value returned by `ini_section_count`. The defined constant `INI_GLOBAL_SECTION` can be used to indicate the global \nsection. Note that removing a section will shuffle section indices, so that section indices you may have stored will no \nlonger indicate the same section as it did before the remove. Use the find functions to update your indices.\n\n\nini_property_remove\n-------------------\n\n    void ini_property_remove( ini_t* ini, int section, int property )\n\nRemoves the property with the specified index from the specified section. `section` must be non-negative and less than \nthe value returned by `ini_section_count`, and `property` must be non-negative and less than the value returned by \n`ini_property_count`. The defined constant `INI_GLOBAL_SECTION` can be used to indicate the global section. Note that \nremoving a property will shuffle property indices within the specified section, so that property indices you may have \nstored will no longer indicate the same property as it did before the remove. Use the find functions to update your \nindices.\n\n\nini_section_name_set\n--------------------\n\n    void ini_section_name_set( ini_t* ini, int section, char const* name, int length )\n\nChange the name of the section with the specified index. `section` must be non-negative and less than the value returned \nby `ini_section_count`. The defined constant `INI_GLOBAL_SECTION` can be used to indicate the global section. `length` \nspecifies the number of characters in `name`, which does not have to be zero-terminated. If `length` is zero, the length \nis determined automatically, but in this case `name` has to be zero-terminated.\n\n\nini_property_name_set\n---------------------\n\n    void ini_property_name_set( ini_t* ini, int section, int property, char const* name, int length )\n\nChange the name of the property with the specified index in the specified section. `section` must be non-negative and \nless than the value returned by `ini_section_count`, and `property` must be non-negative and less than the value \nreturned by `ini_property_count`. The defined constant `INI_GLOBAL_SECTION` can be used to indicate the global section.\n`length` specifies the number of characters in `name`, which does not have to be zero-terminated. If `length` is zero, \nthe length is determined automatically, but in this case `name` has to be zero-terminated.\n\n\nini_property_value_set\n----------------------\n\n    void ini_property_value_set( ini_t* ini, int section, int property, char const* value, int length  )\n\nChange the value of the property with the specified index in the specified section. `section` must be non-negative and \nless than the value returned by `ini_section_count`, and `property` must be non-negative and less than the value \nreturned by `ini_property_count`. The defined constant `INI_GLOBAL_SECTION` can be used to indicate the global section.\n`length` specifies the number of characters in `value`, which does not have to be zero-terminated. If `length` is zero, \nthe length is determined automatically, but in this case `value` has to be zero-terminated.\n\n**/\n\n\n/*\n----------------------\n    IMPLEMENTATION\n----------------------\n*/\n\n//#ifdef INI_IMPLEMENTATION\n//#undef INI_IMPLEMENTATION\n\n#define INITIAL_CAPACITY ( 256 )\n\n#include <stddef.h>\n\n#ifndef INI_MALLOC\n    #include <stdlib.h>\n    #define INI_MALLOC( ctx, size ) ( malloc( size ) )\n    #define INI_FREE( ctx, ptr ) ( free( ptr ) )\n#endif\n\n#ifndef INI_MEMCPY\n    #include <string.h>\n    #define INI_MEMCPY( dst, src, cnt ) ( memcpy( dst, src, cnt ) )\n#endif \n\n#ifndef INI_STRLEN\n    #include <string.h>\n    #define INI_STRLEN( s ) ( strlen( s ) )\n#endif \n\n#ifndef INI_STRNICMP\n    #ifdef _WIN32\n        #include <string.h>\n        #define INI_STRNICMP( s1, s2, cnt ) ( strnicmp( s1, s2, cnt ) )\n    #else                           \n        #include <string.h>         \n        #define INI_STRNICMP( s1, s2, cnt ) ( strncasecmp( s1, s2, cnt ) )        \n    #endif\n#endif \n\n\nstruct ini_internal_section_t\n    {\n    char name[ 32 ];\n    char* name_large;\n    };\n\n\nstruct ini_internal_property_t\n    {\n    int section;\n    char name[ 32 ];\n    char* name_large;\n    char value[ 64 ];\n    char* value_large;\n    };\n\n\nstruct ini_t\n    {\n    struct ini_internal_section_t* sections;\n    int section_capacity;\n    int section_count;\n\n    struct ini_internal_property_t* properties;\n    int property_capacity;\n    int property_count;\n\n    void* memctx;\n    };\n\n\nstatic int ini_internal_property_index( ini_t const* ini, int section, int property )\n    {\n    int i;\n    int p;\n\n    if( ini && section >= 0 && section < ini->section_count )\n        {\n        p = 0;\n        for( i = 0; i < ini->property_count; ++i )\n            {\n            if( ini->properties[ i ].section == section )\n                {\n                if( p == property ) return i;\n                ++p;\n                }\n            }\n        }\n\n    return INI_NOT_FOUND;\n    }\n\n\nini_t* ini_create( void* memctx )\n    {\n    ini_t* ini;\n\n    ini = (ini_t*) INI_MALLOC( memctx, sizeof( ini_t ) );\n    ini->memctx = memctx;\n    ini->sections = (struct ini_internal_section_t*) INI_MALLOC( ini->memctx, INITIAL_CAPACITY * sizeof( ini->sections[ 0 ] ) );\n    ini->section_capacity = INITIAL_CAPACITY;\n    ini->section_count = 1; /* global section */\n    ini->sections[ 0 ].name[ 0 ] = '\\0'; \n    ini->sections[ 0 ].name_large = 0;\n    ini->properties = (struct ini_internal_property_t*) INI_MALLOC( ini->memctx, INITIAL_CAPACITY * sizeof( ini->properties[ 0 ] ) );\n    ini->property_capacity = INITIAL_CAPACITY;\n    ini->property_count = 0;\n    return ini;\n    }\n\n\nini_t* ini_load( char const* data, void* memctx )\n    {\n    ini_t* ini;\n    char const* ptr;\n    int s;\n    char const* start;\n    char const* start2;\n    int l;\n    int l2;\n\n    ini = ini_create( memctx );\n\n    ptr = data;\n    if( ptr )\n        {\n        s = 0;\n        while( *ptr )\n            {\n            /* trim leading whitespace */\n            while( *ptr && *ptr <=' ' )\n                ++ptr;\n            \n            /* done? */\n            if( !*ptr ) break;\n\n            /* comment */\n            else if( *ptr == ';' )\n                {\n                while( *ptr && *ptr !='\\n' )\n                    ++ptr;\n                }\n            /* section */\n            else if( *ptr == '[' )\n                {\n                ++ptr;\n                start = ptr;\n                while( *ptr && *ptr !=']' && *ptr != '\\n' )\n                    ++ptr;\n\n                if( *ptr == ']' )\n                    {\n                    s = ini_section_add( ini, start, (int)( ptr - start) );\n                    ++ptr;\n                    }\n                }\n            /* property */\n            else\n                {\n                start = ptr;\n                while( *ptr && *ptr !='=' && *ptr != '\\n' )\n                    ++ptr;\n\n                if( *ptr == '=' )\n                    {\n                    l = (int)( ptr - start);\n                    ++ptr;\n                    while( *ptr && (unsigned char)*ptr <= ' ' && *ptr != '\\n' ) //characters past ASCII range will be negative, so we're casting here\n                        ptr++;\n                    start2 = ptr;\n                    while( *ptr && *ptr != '\\n' )\n                        ++ptr;\n                    while( (unsigned char)*(--ptr) < ' ' )                      //same as above, but also explicity allow trailing whitespace here\n                        (void)ptr;\n                    ptr++;\n                    //Treat whitespace-only value as empty\n                    l2 = (int)( ptr - start2);\n                    if (l2 < 0)\n                        {\n                            l2 = 0;\n                        }\n                    ini_property_add( ini, s, start, l, start2, l2 );\n                    }\n                }\n            }\n        }   \n\n    return ini;\n    }\n\n\nint ini_save( ini_t const* ini, char* data, int size )\n    {\n    int s;\n    int p;\n    int i;\n    int l;\n    char* n;\n    int pos;\n\n    if( ini )\n        {\n        pos = 0;\n        for( s = 0; s < ini->section_count; ++s )\n            {\n            n = ini->sections[ s ].name_large ? ini->sections[ s ].name_large : ini->sections[ s ].name;\n            l = (int) INI_STRLEN( n );\n            if( l > 0 )\n                {\n                if( data && pos < size ) data[ pos ] = '[';\n                ++pos;\n                for( i = 0; i < l; ++i )\n                    {\n                    if( data && pos < size ) data[ pos ] = n[ i ];\n                    ++pos;\n                    }\n                if( data && pos < size ) data[ pos ] = ']';\n                ++pos;\n                if( data && pos < size ) data[ pos ] = '\\n';\n                ++pos;\n                }\n\n            for( p = 0; p < ini->property_count; ++p )\n                {\n                if( ini->properties[ p ].section == s )\n                    {\n                    n = ini->properties[ p ].name_large ? ini->properties[ p ].name_large : ini->properties[ p ].name;\n                    l = (int) INI_STRLEN( n );\n                    for( i = 0; i < l; ++i )\n                        {\n                        if( data && pos < size ) data[ pos ] = n[ i ];\n                        ++pos;\n                        }\n                    if( data && pos < size ) data[ pos ] = '=';\n                    ++pos;\n                    n = ini->properties[ p ].value_large ? ini->properties[ p ].value_large : ini->properties[ p ].value;\n                    l = (int) INI_STRLEN( n );\n                    for( i = 0; i < l; ++i )\n                        {\n                        if( data && pos < size ) data[ pos ] = n[ i ];\n                        ++pos;\n                        }\n                    if( data && pos < size ) data[ pos ] = '\\n';\n                    ++pos;\n                    }\n                }\n\n            if( pos > 0 )\n                {\n                if( data && pos < size ) data[ pos ] = '\\n';\n                ++pos;\n                }\n            }\n\n        if( data && pos < size ) data[ pos ] = '\\0';\n        ++pos;\n\n        return pos;\n        }\n\n    return 0;\n    }\n\n\nvoid ini_destroy( ini_t* ini )\n    {\n    int i;\n\n    if( ini )\n        {\n        for( i = 0; i < ini->property_count; ++i )\n            {\n            if( ini->properties[ i ].value_large ) INI_FREE( ini->memctx, ini->properties[ i ].value_large );\n            if( ini->properties[ i ].name_large ) INI_FREE( ini->memctx, ini->properties[ i ].name_large );\n            }\n        for( i = 0; i < ini->section_count; ++i )\n            if( ini->sections[ i ].name_large ) INI_FREE( ini->memctx, ini->sections[ i ].name_large );\n        INI_FREE( ini->memctx, ini->properties );\n        INI_FREE( ini->memctx, ini->sections );\n        INI_FREE( ini->memctx, ini );\n        }\n    }\n\n\nint ini_section_count( ini_t const* ini )\n    {\n    if( ini ) return ini->section_count;\n    return 0;\n    }\n\n\nchar const* ini_section_name( ini_t const* ini, int section )\n    {\n    if( ini && section >= 0 && section < ini->section_count )\n        return ini->sections[ section ].name_large ? ini->sections[ section ].name_large : ini->sections[ section ].name;\n\n    return NULL;\n    }\n\n\nint ini_property_count( ini_t const* ini, int section )\n    {\n    int i;\n    int count;\n\n    if( ini )\n        {\n        count = 0;\n        for( i = 0; i < ini->property_count; ++i )\n            {\n            if( ini->properties[ i ].section == section ) ++count;\n            }\n        return count;\n        }\n\n    return 0;\n    }\n\n\nchar const* ini_property_name( ini_t const* ini, int section, int property )\n    {\n    int p;\n\n    if( ini && section >= 0 && section < ini->section_count )\n        {\n        p = ini_internal_property_index( ini, section, property );\n        if( p != INI_NOT_FOUND )\n            return ini->properties[ p ].name_large ? ini->properties[ p ].name_large : ini->properties[ p ].name;\n        }\n\n    return NULL;\n    }\n\n\nchar const* ini_property_value( ini_t const* ini, int section, int property )\n    {\n    int p;\n\n    if( ini && section >= 0 && section < ini->section_count )\n        {\n        p = ini_internal_property_index( ini, section, property );\n        if( p != INI_NOT_FOUND )\n            return ini->properties[ p ].value_large ? ini->properties[ p ].value_large : ini->properties[ p ].value;\n        }\n\n    return NULL;\n    }\n\n\nint ini_find_section( ini_t const* ini, char const* name, int name_length )\n    {\n    int i;\n\n    if( ini && name )\n        {\n        if( name_length <= 0 ) name_length = (int) INI_STRLEN( name );\n        for( i = 0; i < ini->section_count; ++i )\n            {\n            char const* const other = \n                ini->sections[ i ].name_large ? ini->sections[ i ].name_large : ini->sections[ i ].name;\n            if( ( INI_STRNICMP( name, other, name_length ) == 0 ) && ( other[name_length] == '\\0' ) )\n                return i;\n            }\n        }\n\n    return INI_NOT_FOUND;\n    }\n\n\nint ini_find_property( ini_t const* ini, int section, char const* name, int name_length )\n    {\n    int i;\n    int c;\n\n    if( ini && name && section >= 0 && section < ini->section_count)\n        {\n        if( name_length <= 0 ) name_length = (int) INI_STRLEN( name );\n        c = 0;\n        for( i = 0; i < ini->property_count; ++i )\n            {\n            if( ini->properties[ i ].section == section )\n                {\n                char const* const other = \n                    ini->properties[ i ].name_large ? ini->properties[ i ].name_large : ini->properties[ i ].name;\n                if( ( INI_STRNICMP( name, other, name_length ) == 0 ) && ( other[name_length] == '\\0' ) )\n                    return c;\n                ++c;\n                }\n            }\n        }\n\n    return INI_NOT_FOUND;\n    }\n\n\nint ini_section_add( ini_t* ini, char const* name, int length )\n    {\n    struct ini_internal_section_t* new_sections;\n    \n    if( ini && name )\n        {\n        if( length <= 0 ) length = (int) INI_STRLEN( name );\n        if( ini->section_count >= ini->section_capacity )\n            {\n            ini->section_capacity *= 2;\n            new_sections = (struct ini_internal_section_t*) INI_MALLOC( ini->memctx, \n                ini->section_capacity * sizeof( ini->sections[ 0 ] ) );\n            INI_MEMCPY( new_sections, ini->sections, ini->section_count * sizeof( ini->sections[ 0 ] ) );\n            INI_FREE( ini->memctx, ini->sections );\n            ini->sections = new_sections;\n            }\n\n        ini->sections[ ini->section_count ].name_large = 0;\n        if( length + 1 >= sizeof( ini->sections[ 0 ].name ) )\n            {\n            ini->sections[ ini->section_count ].name_large = (char*) INI_MALLOC( ini->memctx, (size_t) length + 1 );\n            INI_MEMCPY( ini->sections[ ini->section_count ].name_large, name, (size_t) length );\n            ini->sections[ ini->section_count ].name_large[ length ] = '\\0';\n            }\n        else\n            {\n            INI_MEMCPY( ini->sections[ ini->section_count ].name, name, (size_t) length );\n            ini->sections[ ini->section_count ].name[ length ] = '\\0';\n            }\n\n        return ini->section_count++;\n        }\n    return INI_NOT_FOUND;\n    }\n\n\nvoid ini_property_add( ini_t* ini, int section, char const* name, int name_length, char const* value, int value_length )\n    {\n    struct ini_internal_property_t* new_properties;\n\n    if( ini && name && section >= 0 && section < ini->section_count )\n        {\n        if( name_length <= 0 ) name_length = (int) INI_STRLEN( name );\n        if( value_length < 0 ) value_length = (int) INI_STRLEN( value ); //Fix for blank string values, but obviously needs -1 passed for auto-length now\n\n        if( ini->property_count >= ini->property_capacity )\n            {\n\n            ini->property_capacity *= 2;\n            new_properties = (struct ini_internal_property_t*) INI_MALLOC( ini->memctx, \n                ini->property_capacity * sizeof( ini->properties[ 0 ] ) );\n            INI_MEMCPY( new_properties, ini->properties, ini->property_count * sizeof( ini->properties[ 0 ] ) );\n            INI_FREE( ini->memctx, ini->properties );\n            ini->properties = new_properties;\n            }\n        \n        ini->properties[ ini->property_count ].section = section;\n        ini->properties[ ini->property_count ].name_large = 0;\n        ini->properties[ ini->property_count ].value_large = 0;\n\n        if( name_length + 1 >= sizeof( ini->properties[ 0 ].name ) )\n            {\n            ini->properties[ ini->property_count ].name_large = (char*) INI_MALLOC( ini->memctx, (size_t) name_length + 1 );\n            INI_MEMCPY( ini->properties[ ini->property_count ].name_large, name, (size_t) name_length );\n            ini->properties[ ini->property_count ].name_large[ name_length ] = '\\0';\n            }\n        else\n            {\n            INI_MEMCPY( ini->properties[ ini->property_count ].name, name, (size_t) name_length );\n            ini->properties[ ini->property_count ].name[ name_length ] = '\\0';\n            }\n\n        if( value_length + 1 >= sizeof( ini->properties[ 0 ].value ) )\n            {\n            ini->properties[ ini->property_count ].value_large = (char*) INI_MALLOC( ini->memctx, (size_t) value_length + 1 );\n            INI_MEMCPY( ini->properties[ ini->property_count ].value_large, value, (size_t) value_length );\n            ini->properties[ ini->property_count ].value_large[ value_length ] = '\\0';\n            }\n        else\n            {\n            INI_MEMCPY( ini->properties[ ini->property_count ].value, value, (size_t) value_length );\n            ini->properties[ ini->property_count ].value[ value_length ] = '\\0';\n            }\n\n        ++ini->property_count;\n        }\n    }\n\n\nvoid ini_section_remove( ini_t* ini, int section )\n    {\n    int p;\n\n    if( ini && section >= 0 && section < ini->section_count )\n        {\n        if( ini->sections[ section ].name_large ) INI_FREE( ini->memctx, ini->sections[ section ].name_large );\n        for( p = ini->property_count - 1; p >= 0; --p ) \n            {\n            if( ini->properties[ p ].section == section )\n                {\n                if( ini->properties[ p ].value_large ) INI_FREE( ini->memctx, ini->properties[ p ].value_large );\n                if( ini->properties[ p ].name_large ) INI_FREE( ini->memctx, ini->properties[ p ].name_large );\n                ini->properties[ p ] = ini->properties[ --ini->property_count ];\n                }\n            }\n\n        ini->sections[ section ] = ini->sections[ --ini->section_count  ];\n        \n        for( p = 0; p < ini->property_count; ++p ) \n            {\n            if( ini->properties[ p ].section == ini->section_count )\n                ini->properties[ p ].section = section;\n            }\n        }\n    }\n\n\nvoid ini_property_remove( ini_t* ini, int section, int property )\n    {\n    int p;\n\n    if( ini && section >= 0 && section < ini->section_count )\n        {\n        p = ini_internal_property_index( ini, section, property );\n        if( p != INI_NOT_FOUND )\n            {\n            if( ini->properties[ p ].value_large ) INI_FREE( ini->memctx, ini->properties[ p ].value_large );\n            if( ini->properties[ p ].name_large ) INI_FREE( ini->memctx, ini->properties[ p ].name_large );\n            ini->properties[ p ] = ini->properties[ --ini->property_count  ];\n            return;\n            }\n        }\n    }\n\n\nvoid ini_section_name_set( ini_t* ini, int section, char const* name, int length )\n    {\n    if( ini && name && section >= 0 && section < ini->section_count )\n        {\n        if( length <= 0 ) length = (int) INI_STRLEN( name );\n        if( ini->sections[ section ].name_large ) INI_FREE( ini->memctx, ini->sections[ section ].name_large );\n        ini->sections[ section ].name_large = 0;\n        \n        if( length + 1 >= sizeof( ini->sections[ 0 ].name ) )\n            {\n            ini->sections[ section ].name_large = (char*) INI_MALLOC( ini->memctx, (size_t) length + 1 );\n            INI_MEMCPY( ini->sections[ section ].name_large, name, (size_t) length );\n            ini->sections[ section ].name_large[ length ] = '\\0';\n            }\n        else\n            {\n            INI_MEMCPY( ini->sections[ section ].name, name, (size_t) length );\n            ini->sections[ section ].name[ length ] = '\\0';\n            }\n        }\n    }\n\n\nvoid ini_property_name_set( ini_t* ini, int section, int property, char const* name, int length )\n    {\n    int p;\n\n    if( ini && name && section >= 0 && section < ini->section_count )\n        {\n        if( length <= 0 ) length = (int) INI_STRLEN( name );\n        p = ini_internal_property_index( ini, section, property );\n        if( p != INI_NOT_FOUND )\n            {\n            if( ini->properties[ p ].name_large ) INI_FREE( ini->memctx, ini->properties[ p ].name_large );\n            ini->properties[ p ].name_large = 0; //This line used ini->property_count as an index before, which works when setting for the newest property, but can corrupt heap when not...\n\n            if( length + 1 >= sizeof( ini->properties[ 0 ].name ) )\n                {\n                ini->properties[ p ].name_large = (char*) INI_MALLOC( ini->memctx, (size_t) length + 1 );\n                INI_MEMCPY( ini->properties[ p ].name_large, name, (size_t) length );\n                ini->properties[ p ].name_large[ length ] = '\\0';\n                }\n            else\n                {\n                INI_MEMCPY( ini->properties[ p ].name, name, (size_t) length );\n                ini->properties[ p ].name[ length ] = '\\0';\n                }\n            }\n        }\n    }\n\n\nvoid ini_property_value_set( ini_t* ini, int section, int property, char const* value, int length )\n    {\n    int p;\n\n    if( ini && value && section >= 0 && section < ini->section_count )\n        {\n        if( length <= 0 ) length = (int) INI_STRLEN( value );\n        p = ini_internal_property_index( ini, section, property );\n        if( p != INI_NOT_FOUND )\n            {\n            if( ini->properties[ p ].value_large ) INI_FREE( ini->memctx, ini->properties[ p ].value_large );\n            ini->properties[ p ].value_large = 0; //This line used ini->property_count as an index before, see above comment\n\n            if( length + 1 >= sizeof( ini->properties[ 0 ].value ) )\n                {\n                ini->properties[ p ].value_large = (char*) INI_MALLOC( ini->memctx, (size_t) length + 1 );\n                INI_MEMCPY( ini->properties[ p ].value_large, value, (size_t) length );\n                ini->properties[ p ].value_large[ length ] = '\\0';\n                }\n            else\n                {\n                INI_MEMCPY( ini->properties[ p ].value, value, (size_t) length );\n                ini->properties[ p ].value[ length ] = '\\0';\n                }\n            }\n        }\n    }\n\n\n//#endif /* INI_IMPLEMENTATION */\n\n/*\n\ncontributors:\n    elvissteinjr (empty properties reading fix, ascii+ whitespace fix, wrong index deleting long property names/values fix, whitespace-only fix, allow trailing whitespace)\n    Andrej Redeky (section and properties find function fix)\n    Randy Gaul (copy-paste bug in ini_property_value_set)\n    Branimir Karadzic (INI_STRNICMP bugfix)\n\nrevision history:\n    Desktop+    apply WSSDude's return of wrong sections and properties by find functions fix, fix reading empty properties,\n                fix characters past ASCII range to be detected as whitespace, fix wrong index deleting long property names/values,\n                fix whitespace-only property values causing the rest of the file to be used instead, allow trailing whitespace in property values\n    1.2         using strnicmp for correct length compares, fixed copy-paste bug in ini_property_value_set\n    1.1         customization, added documentation, cleanup\n    1.0         first publicly released version\n\n*/\n\n/*\n------------------------------------------------------------------------------\n\nThis software is available under 2 licenses - you may choose the one you like.\n\n------------------------------------------------------------------------------\n\nALTERNATIVE A - MIT License\n\nCopyright (c) 2015 Mattias Gustavsson\n\nPermission is hereby granted, free of charge, to any person obtaining a copy of \nthis software and associated documentation files (the \"Software\"), to deal in \nthe Software without restriction, including without limitation the rights to \nuse, copy, modify, merge, publish, distribute, sublicense, and/or sell copies \nof the Software, and to permit persons to whom the Software is furnished to do \nso, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all \ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR \nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, \nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE \nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER \nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, \nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE \nSOFTWARE.\n\n------------------------------------------------------------------------------\n\nALTERNATIVE B - Public Domain (www.unlicense.org)\n\nThis is free and unencumbered software released into the public domain.\n\nAnyone is free to copy, modify, publish, use, compile, sell, or distribute this \nsoftware, either in source code form or as a compiled binary, for any purpose, \ncommercial or non-commercial, and by any means.\n\nIn jurisdictions that recognize copyright laws, the author or authors of this \nsoftware dedicate any and all copyright interest in the software to the public \ndomain. We make this dedication for the benefit of the public at large and to \nthe detriment of our heirs and successors. We intend this dedication to be an \novert act of relinquishment in perpetuity of all present and future rights to \nthis software under copyright law.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR \nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, \nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE \nAUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN \nACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION \nWITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n\n------------------------------------------------------------------------------\n*/\n"
  },
  {
    "path": "src/Shared/Ini.h",
    "content": "//Small Ini class to make handling settings a bit less painful, see Ini.cpp for details\n\n#pragma once\n\n#include <string>\n#include <vector>\n\ntypedef struct ini_t ini_t;\n\nclass Ini\n{\n    private:\n        std::wstring m_WFileName;\n        ini_t* m_IniPtr;\n\n    public:\n        Ini(const std::wstring& filename, bool replace_contents = false);\n        Ini(const Ini&) = delete;\n        ~Ini();\n\n        bool Save();\n        bool Save(const std::wstring& filename);\n\n        std::string ReadString(const char* section, const char* key, const char* default_value = \"\") const;\n        int ReadInt(const char* section, const char* key, int default_value = -1) const;\n        bool ReadBool(const char* section, const char* key, bool default_value = false) const;\n        void WriteString(const char* section, const char* key, const char* value);\n        void WriteInt(const char* section, const char* key, int value);\n        void WriteBool(const char* section, const char* key, bool value);\n\n        bool SectionExists(const char* section) const;\n        bool KeyExists(const char* section, const char* key) const;\n        bool RenameSection(const char* section, const char* new_name);\n        void RemoveSection(const char* section);\n        void RemoveKey(const char* section, const char* key);\n\n        std::vector<std::string> GetSectionList();\n};"
  },
  {
    "path": "src/Shared/InterprocessMessaging.cpp",
    "content": "#include \"InterprocessMessaging.h\"\n\n#include \"Util.h\"\n#include \"DPBrowserAPIClient.h\"\n\nstatic IPCManager g_IPCManager;\n\nIPCManager::IPCManager()\n{\n    //Register messages\n    m_RegisteredMessages[ipcmsg_action]          = ::RegisterWindowMessage(L\"WMIPC_DPLUS_Action\");\n    m_RegisteredMessages[ipcmsg_set_config]      = ::RegisterWindowMessage(L\"WMIPC_DPLUS_SetConfig\");\n    m_RegisteredMessages[ipcmsg_elevated_action] = ::RegisterWindowMessage(L\"WMIPC_DPLUS_ElevatedAction\");\n}\n\nIPCManager& IPCManager::Get()\n{\n    return g_IPCManager;\n}\n\nvoid IPCManager::DisableUIPForRegisteredMessages(HWND window_handle) const\n{\n    ::ChangeWindowMessageFilterEx(window_handle, WM_QUIT,     MSGFLT_ALLOW, nullptr);\n    ::ChangeWindowMessageFilterEx(window_handle, WM_COPYDATA, MSGFLT_ALLOW, nullptr); //This seems to be not the quite the safest thing to allow to be fair\n\n    for (UINT msgid : m_RegisteredMessages)\n    {\n        if (msgid != 0)\n        {\n            ::ChangeWindowMessageFilterEx(window_handle, msgid, MSGFLT_ALLOW, nullptr);\n        }\n    }\n\n    //Also allow message ID used by DPBrowserAPI\n    ::ChangeWindowMessageFilterEx(window_handle, DPBrowserAPIClient::Get().GetRegisteredMessageID(), MSGFLT_ALLOW, nullptr);\n    \n    //We could return the success state of this, but the resulting behavior wouldn't change. Should usually not fail anyways.\n}\n\nUINT IPCManager::GetWin32MessageID(IPCMsgID IPC_id) const\n{\n    return m_RegisteredMessages[IPC_id];\n}\n\nIPCMsgID IPCManager::GetIPCMessageID(UINT win32_id) const\n{\n    //Primitive lookup, but should be good enough for the low amount of message IDs\n    for (size_t i = 0; i < ipcmsg_MAX; ++i)\n    {\n        if (m_RegisteredMessages[i] == win32_id)\n            return (IPCMsgID)i;\n    }\n\n    return ipcmsg_MAX;\n}\n\nbool IPCManager::GetAllMesssagesRegistered() const\n{\n    for (UINT msgid : m_RegisteredMessages)\n    {\n        if (msgid == 0)\n            return false;\n    }\n\n    return true;\n}\n\nbool IPCManager::IsDashboardAppRunning()\n{\n    return (::FindWindow(g_WindowClassNameDashboardApp, nullptr) != 0);\n}\n\nbool IPCManager::IsUIAppRunning()\n{\n    return (::FindWindow(g_WindowClassNameUIApp, nullptr) != 0);\n}\n\nbool IPCManager::IsElevatedModeProcessRunning()\n{\n    return (::FindWindow(g_WindowClassNameElevatedMode, nullptr) != 0);\n}\n\nDWORD IPCManager::GetDashboardAppProcessID()\n{\n    DWORD pid = 0;\n\n    if (HWND window = ::FindWindow(g_WindowClassNameDashboardApp, nullptr))\n    {\n        ::GetWindowThreadProcessId(window, &pid);\n    }\n\n    return pid;\n}\n\nDWORD IPCManager::GetUIAppProcessID()\n{\n    DWORD pid = 0;\n\n    if (HWND window = ::FindWindow(g_WindowClassNameUIApp, nullptr))\n    {\n        ::GetWindowThreadProcessId(window, &pid);\n    }\n\n    return pid;\n}\n\nvoid IPCManager::PostMessageToDashboardApp(IPCMsgID IPC_id, WPARAM w_param, LPARAM l_param) const\n{\n    //We take the cost of finding the window for every message (which isn't frequent anyways) so we don't have to worry about the process going anywhere\n    if (HWND window = ::FindWindow(g_WindowClassNameDashboardApp, nullptr))\n    {\n        ::PostMessage(window, GetWin32MessageID(IPC_id), w_param, l_param);\n    }\n}\n\nvoid IPCManager::PostConfigMessageToDashboardApp(ConfigID_Bool configid, LPARAM l_param) const\n{\n    PostMessageToDashboardApp(ipcmsg_set_config, ConfigManager::Get().GetWParamForConfigID(configid), l_param);\n}\n\nvoid IPCManager::PostConfigMessageToDashboardApp(ConfigID_Int configid, LPARAM l_param) const\n{\n    PostMessageToDashboardApp(ipcmsg_set_config, ConfigManager::Get().GetWParamForConfigID(configid), l_param);\n}\n\nvoid IPCManager::PostConfigMessageToDashboardApp(ConfigID_Float configid, float value) const\n{\n    PostMessageToDashboardApp(ipcmsg_set_config, ConfigManager::Get().GetWParamForConfigID(configid), pun_cast<LPARAM, float>(value));\n}\n\nvoid IPCManager::PostConfigMessageToDashboardApp(ConfigID_Handle configid, LPARAM l_param) const\n{\n    PostMessageToDashboardApp(ipcmsg_set_config, ConfigManager::Get().GetWParamForConfigID(configid), l_param);\n}\n\nvoid IPCManager::PostMessageToUIApp(IPCMsgID IPC_id, WPARAM w_param, LPARAM l_param) const\n{\n    if (HWND window = ::FindWindow(g_WindowClassNameUIApp, nullptr))\n    {\n        ::PostMessage(window, GetWin32MessageID(IPC_id), w_param, l_param);\n    }\n}\n\nvoid IPCManager::PostConfigMessageToUIApp(ConfigID_Bool configid, LPARAM l_param) const\n{\n    PostMessageToUIApp(ipcmsg_set_config, ConfigManager::Get().GetWParamForConfigID(configid), l_param);\n}\n\nvoid IPCManager::PostConfigMessageToUIApp(ConfigID_Int configid, LPARAM l_param) const\n{\n    PostMessageToUIApp(ipcmsg_set_config, ConfigManager::Get().GetWParamForConfigID(configid), l_param);\n}\n\nvoid IPCManager::PostConfigMessageToUIApp(ConfigID_Float configid, float value) const\n{\n    PostMessageToUIApp(ipcmsg_set_config, ConfigManager::Get().GetWParamForConfigID(configid), pun_cast<LPARAM, float>(value));\n}\n\nvoid IPCManager::PostConfigMessageToUIApp(ConfigID_Handle configid, LPARAM l_param) const\n{\n    PostMessageToUIApp(ipcmsg_set_config, ConfigManager::Get().GetWParamForConfigID(configid), l_param);\n}\n\nvoid IPCManager::PostMessageToElevatedModeProcess(IPCMsgID IPC_id, WPARAM w_param, LPARAM l_param) const\n{\n    if (HWND window = ::FindWindow(g_WindowClassNameElevatedMode, nullptr))\n    {\n        ::PostMessage(window, GetWin32MessageID(IPC_id), w_param, l_param);\n    }\n}\n\nvoid IPCManager::SendStringToDashboardApp(ConfigID_String config_id, const std::string& str, HWND source_window) const\n{\n    if (HWND window = ::FindWindow(g_WindowClassNameDashboardApp, nullptr))\n    {\n        COPYDATASTRUCT cds;\n        cds.dwData = config_id;\n        cds.cbData = (DWORD)str.length();  //We do not include the NUL byte\n        cds.lpData = (void*)str.c_str();\n        ::SendMessage(window, WM_COPYDATA, (WPARAM)source_window, (LPARAM)(LPVOID)&cds);\n    }\n}\n\nvoid IPCManager::SendStringToUIApp(ConfigID_String config_id, const std::string& str, HWND source_window) const\n{\n    if (HWND window = ::FindWindow(g_WindowClassNameUIApp, nullptr))\n    {\n        COPYDATASTRUCT cds;\n        cds.dwData = config_id;\n        cds.cbData = (DWORD)str.length();  //We do not include the NUL byte\n        cds.lpData = (void*)str.c_str();\n        ::SendMessage(window, WM_COPYDATA, (WPARAM)source_window, (LPARAM)(LPVOID)&cds);\n    }\n}\n\nvoid IPCManager::SendStringToElevatedModeProcess(IPCElevatedStringID elevated_str_id, const std::string& str, HWND source_window) const\n{\n    if (HWND window = ::FindWindow(g_WindowClassNameElevatedMode, nullptr))\n    {\n        COPYDATASTRUCT cds;\n        cds.dwData = elevated_str_id;\n        cds.cbData = (DWORD)str.length();  //We do not include the NUL byte\n        cds.lpData = (void*)str.c_str();\n        ::SendMessage(window, WM_COPYDATA, (WPARAM)source_window, (LPARAM)(LPVOID)&cds);\n    }\n}\n"
  },
  {
    "path": "src/Shared/InterprocessMessaging.h",
    "content": "//Desktop+ uses custom Win32 messages sent across processes for interprocess communication\n//It's generally expected to use matching builds of the dashboard overlay and UI application, as the UI is launched by the dashboard process\n//Due to that, there's no version checking or similar, just some raw messages to get things done\n//This header and its implemenation is shared between both applications' code\n//The IPCManager class doesn't write to any variables after construction, so calling it from other threads is safe\n\n#pragma once\n\n#include <string>\n#define NOMINMAX\n#include <windows.h>\n\n#include \"ConfigManager.h\"\n\nLPCWSTR const g_WindowClassNameDashboardApp = L\"elvdesktop\";\nLPCWSTR const g_WindowClassNameUIApp        = L\"elvdesktopUI\";\nLPCWSTR const g_WindowClassNameElevatedMode = L\"elvdesktopelevated\";\nconst char* const g_AppKeyDashboardApp      = \"steam.overlay.1494460\";                  //1494460 is the appid on Steam, but we just use this for all builds\nconst char* const g_AppKeyUIApp             = \"elvissteinjr.DesktopPlusUI\";\nconst char* const g_AppKeyTheaterScreen     = \"elvissteinjr.DesktopPlusTheaterScreen\";  //We need an app with \"starts_theater_mode\", but we can't add that to the app manifest written by Steam\n\nenum IPCMsgID\n{\n    ipcmsg_action,            //wParam = IPCActionID, lParam = Action-specific value. Many things will get away with being stuffed inside this. Saves some global message IDs\n    ipcmsg_set_config,        //wParam = ConfigID, lParam = Value. Generic ConfigIDs are derived from their specific ID + predecending *_MAX values.\n                              //e.g. configid_float_stuff is configid_bool_MAX + configid_int_MAX + configid_float_stuff. Strings are handled separately\n    ipcmsg_elevated_action,   //wParam = IPCElevatedActionID, lParam = Action-specific value. Actions sent to the elevated mode process\n    ipcmsg_MAX\n};\n\nenum IPCActionID\n{\n    ipcact_nop,\n    ipcact_mirror_reset,                //Sent by dashboard application to itself when WndProc needs to trigger a mirror reset\n    ipcact_overlays_reset,              //Sent by dashboard application when all overlays were reset. No data in lParam\n    ipcact_overlays_ui_reset,           //Sent by dashboard application when UI overlays were reset or need shared textures re-applied. No data in lParam\n    ipcact_overlay_position_reset,      //Sent by UI application to reset the detached overlay position. No data in lParam\n    ipcact_overlay_position_adjust,     //Sent by UI application to adjust detached overlay position. lParam = IPCActionOverlayPosAdjustValue\n    ipcact_action_delete,               //Sent by UI application to delete an Action. lParam is Action UID\n    ipcact_action_do,                   //Sent by UI application to do an Action. lParam is Action UID\n    ipcact_action_start,                //Sent by UI application to start an Action. lParam is Action UID. This is currently only used for input actions, other things fall back to *_do\n    ipcact_action_stop,                 //Sent by UI application to stop an Action. lParam is Action UID. This is currently only used for input actions, other things fall back to *_do\n    ipcact_overlay_profile_load,        //Sent by UI application when loading a profile. lParam is IPCActionOverlayProfileLoadArg + profile overlay ID (low/high word order). -2 ID is default.\n                                        //Profile name is stored in configid_str_state_profile_name_load beforehand. ipcactv_ovrl_profile_multi_add queues profile IDs until called with -1\n    ipcact_crop_to_active_window,       //Sent by UI application to adjust crop values to the active window. No data in lParam\n    ipcact_switch_task,                 //Sent by UI application to do the Switch Task action command independent of any action\n    ipcact_overlay_duplicate,           //Sent by UI application to duplicate an overlay, also making it the active one. lParam is ID of overlay the config is copied from (typically the active ID)\n    ipcact_overlay_new,                 //Sent by UI application to add a new overlay. lParam is desktop ID or -2 for HWND, -3 for UI overlay\n                                        //HWND is stored in configid_handle_state_arg_hwnd beforehand\n    ipcact_overlay_new_drag,            //Sent by UI application to add a new overlay. lParam is desktop ID or -2 for HWND, -3 for UI overlay, + pointer distance * 100 (low/high word order, signed)\n                                        //HWND is stored in configid_handle_state_arg_hwnd beforehand\n    ipcact_overlay_remove,              //Sent by UI or dashboard application to remove a overlay. lParam is ID of overlay to remove (typically the active ID)\n    ipcact_overlay_creation_error,      //Sent by dashboard application when an error occured during overlay creation. lParam is EVROverlayError\n    ipcact_overlay_transform_sync,      //Sent by the UI application to request a sync of overlay transforms. lParam is ID of overlay to sync transform of (or -1 for full sync)\n    ipcact_overlay_swap,                //Sent by the UI application to swap two overlays. lParam is the ID of overlay to swap with the current overlay\n    ipcact_overlay_gaze_fade_auto,      //Sent by the UI application to automatically configure gaze fade values. No data in lParam\n    ipcact_overlay_make_standalone,     //Sent by the UI application to converted overlays with duplication ID to standalone ones. lParam is ID of overlay to convert (typically the active ID)\n    ipcact_winrt_thread_error,          //Sent by dashboard application when an error occured in a Graphics Capture thread. lParam is HRESULT\n    ipcact_notification_show,           //Sent by dashboard application to show a notification (currently only initial setup message). No data in lParam\n    ipcact_browser_navigate_to_url,     //Sent by UI application to have the overlay's browser navigate to configid_str_overlay_browser_url. lParam is ID of overlay\n    ipcact_browser_recreate_context,    //Sent by UI application to have the overlay's browser context be recreated. lParam is ID of overlay\n    ipcact_winmanager_drag_start,       //Sent by dashboard application's WindowManager thread to main thread to start an overlay drag. lParam is ID of overlay to drag\n    ipcact_winmanager_winlist_add,      //Sent by either application's WindowManager thread to main thread to add a window to the window list. lParam is HWND\n    ipcact_winmanager_winlist_update,   //Sent by WindowManager thread to main thread to update a window from the window list. lParam is HWND\n    ipcact_winmanager_winlist_remove,   //Sent by WindowManager thread to main thread to remove a window from the window list. lParam is HWND\n    ipcact_winmanager_focus_changed,    //Sent by dashboard application's WindowManager thread to UI app when the foreground window changed. No data in lParam\n    ipcact_winmanager_text_input_focus, //Sent by dashboard application's WindowManager thread to main thread to update text input focus state. lParam is focus bool\n    ipcact_sync_config_state,           //Sent by the UI application to request overlay and config state variables after a restart\n    ipcact_focus_window,                //Sent by the UI application to focus a window. lParam is HWND\n    ipcact_keyboard_show,               //Sent by dashboard application to show the VR keyboard. lParam is assigned overlay ID (-1 to hide, -2 for UI/global)\n    ipcact_keyboard_show_auto,          //Sent by dashboard application to show the VR keyboard with auto-visibility. lParam is assigned overlay ID (-1 to hide)\n    ipcact_keyboard_vkey,               //Sent by UI application in response of a VR keyboard press. lParam is IPCKeyboardKeystateFlags + Win32 key code (low/high word order)\n    ipcact_keyboard_wchar,              //Sent by UI application in response of a VR keyboard press. lParam is 1 wchar + key down bool (low/high word order)\n    ipcact_keyboard_ovrl_focus_enter,   //Sent by UI application in response to a VREvent_FocusEnter on the keyboard overlay. No data in lParam\n    ipcact_keyboard_ovrl_focus_leave,   //Sent by UI application in response to a VREvent_FocusLeave on the keyboard overlay. No data in lParam\n    ipcact_lpointer_trigger_haptics,    //Sent by UI application to trigger laser pointer haptics (short UI interaction burst). lParam is tracked device index\n    ipcact_lpointer_ui_mask_rect,       //Sent by UI application to update the UI intersection mask for the dplus laser pointer. lParam is DPRect packed with DPRect::Pack16() or -1 to finish the mask\n    ipcact_lpointer_ui_drag,            //Sent by dashboard application to start or finish an overlay drag of an UI laser pointer target overlay. lParam is 1 for start or 0 to finish\n    ipcact_app_profile_remove,          //Sent by UI application to remove an app profile. No data in lParam, uses app key stored in configid_str_state_app_profile_key beforehand\n    ipcact_global_shortcut_set,         //Sent by UI application to set a global shortcut. lParam is shortcut ID, uses Action UID stored in configid_handle_state_action_uid beforehand\n    ipcact_hotkey_set,                  //Sent by UI application to set a hotkey. lParam is hotkey ID (out of range ID to create new), uses configid_str_state_hotkey_data as source (blank to delete)\n    ipcact_MAX\n};\n\n//lParam for ipcact_overlay_position_adjust. First 4 bits are target, 5th bit sets increase operation\nenum IPCActionOverlayPosAdjustTarget \n{\n    ipcactv_ovrl_pos_adjust_updown = 0x0,\n    ipcactv_ovrl_pos_adjust_rightleft,\n    ipcactv_ovrl_pos_adjust_forwback,\n    ipcactv_ovrl_pos_adjust_rotx,\n    ipcactv_ovrl_pos_adjust_roty,\n    ipcactv_ovrl_pos_adjust_rotz,\n    ipcactv_ovrl_pos_adjust_lookat,\n    ipcactv_ovrl_pos_adjust_increase = 0x10\n};\n\nenum IPCActionOverlayProfileLoadArg\n{\n    ipcactv_ovrl_profile_multi,\n    ipcactv_ovrl_profile_multi_add\n};\n\nenum IPCKeyboardKeystateFlags : unsigned char\n{\n    kbd_keystate_flag_key_down         = 1 << 0,\n    kbd_keystate_flag_lshift_down      = 1 << 1,\n    kbd_keystate_flag_rshift_down      = 1 << 2,\n    kbd_keystate_flag_lctrl_down       = 1 << 3,\n    kbd_keystate_flag_rctrl_down       = 1 << 4,\n    kbd_keystate_flag_lalt_down        = 1 << 5,\n    kbd_keystate_flag_ralt_down        = 1 << 6,\n    kbd_keystate_flag_capslock_toggled = 1 << 7,\n    kbd_keystate_flag_MAX              = 1 << 7\n};\n\nenum IPCElevatedActionID\n{\n    ipceact_refresh,                   //Prompts to refresh possibly changed data, such as InputSimulator screen offsets. No data in lParam\n    ipceact_mouse_move,                //lParam = X & Y (in low/high word order, signed)\n    ipceact_mouse_hwheel,              //lParam = delta (float)\n    ipceact_mouse_vwheel,              //lParam = delta (float)\n    ipceact_pen_move,                  //lParam = X & Y (in low/high word order, signed)\n    ipceact_pen_button_down,           //lParam = Button ID (0/1)\n    ipceact_pen_button_up,             //lParam = Button ID (0/1)\n    ipceact_pen_leave,                 //No data in lParam\n    ipceact_key_down,                  //lParam = Keycodes (3 unsigned chars)\n    ipceact_key_up,                    //lParam = Keycodes (3 unsigned chars)\n    ipceact_key_toggle,                //lParam = Keycodes (3 unsigned chars)\n    ipceact_key_press_and_release,     //lParam = Keycode  (1 unsigned char)\n    ipceact_key_togglekey_set,         //lParam = Keycode & bool (low/high word order)\n    ipceact_keystate_w32_set,          //lParam = Win32 Keystate & bool (low/high word order)\n    ipceact_keystate_set,              //lParam = IPCKeyboardKeystateFlags & Keycode (low/high word order)\n    ipceact_keyboard_text_finish,      //Finishes the keyboard text queue. Keyboard text is queued by sending strings with ipcestrid_keyboard_text*. No data in lParam\n    ipceact_launch_application,        //Launches application previously defined by sending ipcestrid_launch_application_path and ipcestrid_launch_application_arg strings. No data in lParam\n    ipceact_window_topmost_set,        //Sets the temporary topmost window (WindowManager::SetTempTopMostWindow()). lParam = HWND (nullptr/0 to clear)\n    ipceact_MAX\n};\n\nenum IPCElevatedStringID\n{\n    ipcestrid_keyboard_text,\n    ipcestrid_keyboard_text_force_unicode,\n    ipcestrid_launch_application_path,            //This also resets ipcestrid_launch_application_arg so sending that can be avoided\n    ipcestrid_launch_application_arg\n};\n\nclass IPCManager\n{\n    private:\n        UINT m_RegisteredMessages[ipcmsg_MAX];\n\n    public:\n        IPCManager();\n        static IPCManager& Get();\n        void DisableUIPForRegisteredMessages(HWND window_handle) const;   //Disables User Interface Privilege Isolation in order to enable unelevated UI application to send messages to the overlay\n        UINT GetWin32MessageID(IPCMsgID IPC_id) const;\n        IPCMsgID GetIPCMessageID(UINT win32_id) const;\n        bool GetAllMesssagesRegistered() const;\t\t\t//Sanity check in case registering the messages fails, which should usually not happen\n        static bool IsDashboardAppRunning();\n        static bool IsUIAppRunning();\n        static bool IsElevatedModeProcessRunning();\n        static DWORD GetDashboardAppProcessID();\n        static DWORD GetUIAppProcessID();\n\n        void PostMessageToDashboardApp(IPCMsgID IPC_id, WPARAM w_param = 0, LPARAM l_param = 0) const;\n        void PostConfigMessageToDashboardApp(ConfigID_Bool   configid, LPARAM l_param = 0) const;\n        void PostConfigMessageToDashboardApp(ConfigID_Int    configid, LPARAM l_param = 0) const;\n        void PostConfigMessageToDashboardApp(ConfigID_Float  configid, float value = 0.0f) const;\n        void PostConfigMessageToDashboardApp(ConfigID_Handle configid, LPARAM l_param = 0) const;\n\n        void PostMessageToUIApp(IPCMsgID IPC_id, WPARAM w_param = 0, LPARAM l_param = 0) const;\n        void PostConfigMessageToUIApp(ConfigID_Bool   configid, LPARAM l_param = 0) const;\n        void PostConfigMessageToUIApp(ConfigID_Int    configid, LPARAM l_param = 0) const;\n        void PostConfigMessageToUIApp(ConfigID_Float  configid, float value = 0.0f) const;\n        void PostConfigMessageToUIApp(ConfigID_Handle configid, LPARAM l_param = 0) const;\n\n        void PostMessageToElevatedModeProcess(IPCMsgID IPC_id, WPARAM w_param = 0, LPARAM l_param = 0) const;\n\n        void SendStringToDashboardApp(ConfigID_String config_id, const std::string& str, HWND source_window) const;\n        void SendStringToUIApp(ConfigID_String config_id, const std::string& str, HWND source_window) const;\n        void SendStringToElevatedModeProcess(IPCElevatedStringID elevated_str_id, const std::string& str, HWND source_window) const;\n};"
  },
  {
    "path": "src/Shared/Logging.cpp",
    "content": "#include \"Logging.h\"\n\n#include <string>\n\n#include \"openvr.h\"\n#include \"Util.h\"\n\n#include \"DesktopPlusWinRT.h\"\n\nvoid DPLog_Init(const char* name)\n{\n    //Make file names from provided log name\n    const std::string  filename          = std::string(name) + \".log\";\n    const std::wstring filename_u16      = WStringConvertFromUTF8(name) + L\".log\";\n    const std::wstring filename_prev_u16 = WStringConvertFromUTF8(name) + L\"_prev.log\";\n\n    //We always append, but if we just rotated the log we set it to truncate further down to avoid the blank lines at the top\n    loguru::FileMode log_file_mode = loguru::Append;\n\n    //Check the creation time and rotate if it was created a week or longer ago\n    HANDLE file_handle = ::CreateFileW(filename_u16.c_str(), FILE_READ_ATTRIBUTES, FILE_SHARE_READ, nullptr, OPEN_EXISTING, 0, nullptr);\n\n    if (file_handle != INVALID_HANDLE_VALUE)\n    {\n        FILETIME ftime_log_create;\n        if (::GetFileTime(file_handle, &ftime_log_create, nullptr, nullptr))\n        {\n            ::CloseHandle(file_handle);\n\n            FILETIME ftime_current;\n            ::GetSystemTimeAsFileTime(&ftime_current);\n\n            ULARGE_INTEGER time_current{ftime_current.dwLowDateTime, ftime_current.dwHighDateTime};\n            ULARGE_INTEGER time_create{ftime_log_create.dwLowDateTime, ftime_log_create.dwHighDateTime};\n            const ULONGLONG ftime_week = 7ULL * 24 * 60 * 60 * 10000000;\n\n            if (time_create.QuadPart + ftime_week <= time_current.QuadPart)\n            {\n                ::DeleteFileW(filename_prev_u16.c_str());\n                ::MoveFileW(filename_u16.c_str(), filename_prev_u16.c_str());\n\n                //Windows' file system tunneling preserves the creation date between deleting and creating a new log file, so set it manually here\n                file_handle = ::CreateFileW(filename_u16.c_str(), FILE_WRITE_ATTRIBUTES, FILE_SHARE_READ, nullptr, OPEN_ALWAYS, 0, nullptr);\n                if (file_handle != INVALID_HANDLE_VALUE)\n                {\n                    ::SetFileTime(file_handle, &ftime_current, nullptr, nullptr);\n                    ::CloseHandle(file_handle);\n                }\n\n                log_file_mode = loguru::Truncate;\n            }\n        }\n        else\n        {\n            ::CloseHandle(file_handle);\n        }\n    }\n    else  //Reading the file attributes failed, so we assume the file didn't exist and truncate\n    {\n        log_file_mode = loguru::Truncate;\n    }\n\n    //Set some Loguru settings\n    loguru::g_internal_verbosity = 1;\n    loguru::g_colorlogtostderr   = false;\n    loguru::g_preamble_uptime    = false;\n    loguru::g_preamble_thread    = false;\n\n    //Init Loguru\n    loguru::init(__argc, __argv);\n    loguru::add_file(filename.c_str(), log_file_mode, loguru::g_stderr_verbosity);\n\n    LOG_F(INFO, \"Launching %s\", k_pch_DesktopPlusVersion);\n}\n\nvoid DPLog_SteamVR_SystemInfo()\n{\n    if (vr::VRSystem() == nullptr)\n    {\n        LOG_F(INFO, \"SteamVR is not loaded\");\n        return;\n    }\n    \n    //Get some info that might be useful for debugging...\n    LOG_F(INFO, \"SteamVR Runtime Version: %s\", vr::VRSystem()->GetRuntimeVersion());\n    LOG_F(INFO, \"OpenVR API Version: %u.%u.%u\", vr::k_nSteamVRVersionMajor, vr::k_nSteamVRVersionMinor, vr::k_nSteamVRVersionBuild);\n\n    {\n        char buffer[vr::k_unMaxPropertyStringSize];\n        LOG_SCOPE_F(INFO, \"HMD Info\");\n\n        vr::VRSystem()->GetStringTrackedDeviceProperty(vr::k_unTrackedDeviceIndex_Hmd, vr::Prop_ModelNumber_String, buffer, vr::k_unMaxPropertyStringSize);\n        LOG_F(INFO, \"Model: %s\", buffer);\n        vr::VRSystem()->GetStringTrackedDeviceProperty(vr::k_unTrackedDeviceIndex_Hmd, vr::Prop_ManufacturerName_String, buffer, vr::k_unMaxPropertyStringSize);\n        LOG_F(INFO, \"Manufacturer: %s\", buffer);\n        vr::VRSystem()->GetStringTrackedDeviceProperty(vr::k_unTrackedDeviceIndex_Hmd, vr::Prop_TrackingSystemName_String, buffer, vr::k_unMaxPropertyStringSize);\n        LOG_F(INFO, \"Tracking System: %s\", buffer);\n    }\n}\n\nvoid DPLog_DPWinRT_SupportInfo()\n{\n    //Loguru doesn't support taking logging from other libraries, but we just make do with this instead for now\n    if (DPWinRT_IsCaptureSupported())\n    {\n        LOG_SCOPE_F(INFO, \"Graphics Capture Support\");\n        LOG_F(INFO, \"Combined Desktop: %s\",          (DPWinRT_IsCaptureFromCombinedDesktopSupported())      ? \"Yes\" : \"No\");\n        LOG_F(INFO, \"Disabling Cursor: %s\",          (DPWinRT_IsCaptureCursorEnabledPropertySupported())    ? \"Yes\" : \"No\");\n        LOG_F(INFO, \"Disabling Border: %s\",          (DPWinRT_IsBorderRequiredPropertySupported())          ? \"Yes\" : \"No\");\n        LOG_F(INFO, \"Capture Secondary Windows: %s\", (DPWinRT_IsIncludeSecondaryWindowsPropertySupported()) ? \"Yes\" : \"No\");\n        LOG_F(INFO, \"Native Update Limiter: %s\",     (DPWinRT_IsMinUpdateIntervalPropertySupported())       ? \"Yes\" : \"No\");\n    }\n    else\n    {\n        LOG_F(INFO, \"Graphics Capture is not supported\");\n    }\n\n}\n"
  },
  {
    "path": "src/Shared/Logging.h",
    "content": "//Just log rotation/unified init plus some common info logs on top of Loguru\n\n#pragma once\n\n#include \"loguru.hpp\"\n\n//Version string logged and displayed in the UI\n//DPLUS_SHA is set externally for nightly builds to use the commit hash in version string\n//DPLUS_SHA is also always defined but usually empty... so we detect this with some constexpr trickery instead of messing with the build system even more\nconstexpr const char* const k_pch_DesktopPlusVersion = (sizeof(\"\" DPLUS_SHA) <= 1) ? \"Desktop+ 3.4\" : \"Desktop+ \" DPLUS_SHA;\n\n//Version written to config file\n//Only really increased when backward incompatible changes were made (we fall back to default values when things are missing usually)\nstatic const int k_nDesktopPlusConfigVersion = 2;\n\nvoid DPLog_Init(const char* name);\nvoid DPLog_SteamVR_SystemInfo();\nvoid DPLog_DPWinRT_SupportInfo();"
  },
  {
    "path": "src/Shared/Matrices.cpp",
    "content": "// Desktop+: Modified for OpenVR compatibility and more\n\n///////////////////////////////////////////////////////////////////////////////\n// Matrice.cpp\n// ===========\n// NxN Matrix Math classes\n//\n// The elements of the matrix are stored as column major order.\n// | 0 2 |    | 0 3 6 |    |  0  4  8 12 |\n// | 1 3 |    | 1 4 7 |    |  1  5  9 13 |\n//            | 2 5 8 |    |  2  6 10 14 |\n//                         |  3  7 11 15 |\n//\n//  AUTHOR: Song Ho Ahn (song.ahn@gmail.com)\n// CREATED: 2005-06-24\n// UPDATED: 2014-09-21\n//\n// Copyright (C) 2005 Song Ho Ahn\n///////////////////////////////////////////////////////////////////////////////\n\n#include <cmath>\n#include <algorithm>\n#include <sstream>\n#include \"Matrices.h\"\n\nconstexpr float DEG2RAD = 3.141593f / 180;\nconstexpr float RAD2DEG = 180 / 3.141593f;\nconstexpr float EPSILON = 0.00001f;\n\nfloat clampf(float value, float value_min, float value_max)\n{\n    return std::max(value_min, std::min(value, value_max));\n}\n\n///////////////////////////////////////////////////////////////////////////////\n// transpose 2x2 matrix\n///////////////////////////////////////////////////////////////////////////////\nMatrix2& Matrix2::transpose()\n{\n    std::swap(m[1],  m[2]);\n    return *this;\n}\n\n\n\n///////////////////////////////////////////////////////////////////////////////\n// return the determinant of 2x2 matrix\n///////////////////////////////////////////////////////////////////////////////\nfloat Matrix2::getDeterminant()\n{\n    return m[0] * m[3] - m[1] * m[2];\n}\n\n\n\n///////////////////////////////////////////////////////////////////////////////\n// inverse of 2x2 matrix\n// If cannot find inverse, set identity matrix\n///////////////////////////////////////////////////////////////////////////////\nMatrix2& Matrix2::invert()\n{\n    float determinant = getDeterminant();\n    if(fabs(determinant) <= EPSILON)\n    {\n        return identity();\n    }\n\n    float tmp = m[0];   // copy the first element\n    float invDeterminant = 1.0f / determinant;\n    m[0] =  invDeterminant * m[3];\n    m[1] = -invDeterminant * m[1];\n    m[2] = -invDeterminant * m[2];\n    m[3] =  invDeterminant * tmp;\n\n    return *this;\n}\n\n\n\n///////////////////////////////////////////////////////////////////////////////\n// transpose 3x3 matrix\n///////////////////////////////////////////////////////////////////////////////\nMatrix3& Matrix3::transpose()\n{\n    std::swap(m[1],  m[3]);\n    std::swap(m[2],  m[6]);\n    std::swap(m[5],  m[7]);\n\n    return *this;\n}\n\n\n\n///////////////////////////////////////////////////////////////////////////////\n// return determinant of 3x3 matrix\n///////////////////////////////////////////////////////////////////////////////\nfloat Matrix3::getDeterminant()\n{\n    return m[0] * (m[4] * m[8] - m[5] * m[7]) -\n           m[1] * (m[3] * m[8] - m[5] * m[6]) +\n           m[2] * (m[3] * m[7] - m[4] * m[6]);\n}\n\n\n\n///////////////////////////////////////////////////////////////////////////////\n// inverse 3x3 matrix\n// If cannot find inverse, set identity matrix\n///////////////////////////////////////////////////////////////////////////////\nMatrix3& Matrix3::invert()\n{\n    float determinant, invDeterminant;\n    float tmp[9];\n\n    tmp[0] = m[4] * m[8] - m[5] * m[7];\n    tmp[1] = m[2] * m[7] - m[1] * m[8];\n    tmp[2] = m[1] * m[5] - m[2] * m[4];\n    tmp[3] = m[5] * m[6] - m[3] * m[8];\n    tmp[4] = m[0] * m[8] - m[2] * m[6];\n    tmp[5] = m[2] * m[3] - m[0] * m[5];\n    tmp[6] = m[3] * m[7] - m[4] * m[6];\n    tmp[7] = m[1] * m[6] - m[0] * m[7];\n    tmp[8] = m[0] * m[4] - m[1] * m[3];\n\n    // check determinant if it is 0\n    determinant = m[0] * tmp[0] + m[1] * tmp[3] + m[2] * tmp[6];\n    if(fabs(determinant) <= EPSILON)\n    {\n        return identity(); // cannot inverse, make it idenety matrix\n    }\n\n    // divide by the determinant\n    invDeterminant = 1.0f / determinant;\n    m[0] = invDeterminant * tmp[0];\n    m[1] = invDeterminant * tmp[1];\n    m[2] = invDeterminant * tmp[2];\n    m[3] = invDeterminant * tmp[3];\n    m[4] = invDeterminant * tmp[4];\n    m[5] = invDeterminant * tmp[5];\n    m[6] = invDeterminant * tmp[6];\n    m[7] = invDeterminant * tmp[7];\n    m[8] = invDeterminant * tmp[8];\n\n    return *this;\n}\n\n\n\nMatrix4::Matrix4(const std::string str)\n{\n    float m_temp[16];\n    char c;\n    std::stringstream ss(str);\n\n    ss >> c;\n\n    for (size_t i = 0; i < 16; ++i)\n    {\n        ss >> m_temp[i];\n    }\n\n    if (!ss.fail())\n        set(m_temp);\n    else\n        identity();\n}\n\nvoid Matrix4::setRotation(float x, float y, float z)\n{\n    //Compute cosine and sine for each angle\n    float cx = cosf(x * DEG2RAD);\n    float sx = sinf(x * DEG2RAD);\n    float cy = cosf(y * DEG2RAD);\n    float sy = sinf(y * DEG2RAD);\n    float cz = cosf(z * DEG2RAD);\n    float sz = sinf(z * DEG2RAD);\n\n    //Refill the the matrix. Any scaling is lost here\n    m[0]  =  cy * cz + sy * sx * sz; m[4] = -cy * sz + sy * sx * cz; m[8]  =  sy * cx;\n    m[1]  =  cx * sz;                m[5] =  cx * cz;                m[9]  = -sx;\n    m[2]  =  cy * sx * sz - sy * cz; m[6] =  sy * sz + cy * sx * cz; m[10] =  cy * cx;\n}\n\nvoid Matrix4::setRotation(const Vector3& v)\n{\n    setRotation(v.x, v.y, v.z);\n}\n\n\n///////////////////////////////////////////////////////////////////////////////\n// transpose 4x4 matrix\n///////////////////////////////////////////////////////////////////////////////\nMatrix4& Matrix4::transpose()\n{\n    std::swap(m[1],  m[4]);\n    std::swap(m[2],  m[8]);\n    std::swap(m[3],  m[12]);\n    std::swap(m[6],  m[9]);\n    std::swap(m[7],  m[13]);\n    std::swap(m[11], m[14]);\n\n    return *this;\n}\n\n\n\n///////////////////////////////////////////////////////////////////////////////\n// inverse 4x4 matrix\n///////////////////////////////////////////////////////////////////////////////\nMatrix4& Matrix4::invert()\n{\n    // If the 4th row is [0,0,0,1] then it is affine matrix and\n    // it has no projective transformation.\n    if(m[3] == 0 && m[7] == 0 && m[11] == 0 && m[15] == 1)\n        this->invertAffine();\n    else\n    {\n        this->invertGeneral();\n        /*@@ invertProjective() is not optimized (slower than generic one)\n        if(fabs(m[0]*m[5] - m[1]*m[4]) > EPSILON)\n            this->invertProjective();   // inverse using matrix partition\n        else\n            this->invertGeneral();      // generalized inverse\n        */\n    }\n\n    return *this;\n}\n\n\n\n///////////////////////////////////////////////////////////////////////////////\n// compute the inverse of 4x4 Euclidean transformation matrix\n//\n// Euclidean transformation is translation, rotation, and reflection.\n// With Euclidean transform, only the position and orientation of the object\n// will be changed. Euclidean transform does not change the shape of an object\n// (no scaling). Length and angle are reserved.\n//\n// Use inverseAffine() if the matrix has scale and shear transformation.\n//\n// M = [ R | T ]\n//     [ --+-- ]    (R denotes 3x3 rotation/reflection matrix)\n//     [ 0 | 1 ]    (T denotes 1x3 translation matrix)\n//\n// y = M*x  ->  y = R*x + T  ->  x = R^-1*(y - T)  ->  x = R^T*y - R^T*T\n// (R is orthogonal,  R^-1 = R^T)\n//\n//  [ R | T ]-1    [ R^T | -R^T * T ]    (R denotes 3x3 rotation matrix)\n//  [ --+-- ]   =  [ ----+--------- ]    (T denotes 1x3 translation)\n//  [ 0 | 1 ]      [  0  |     1    ]    (R^T denotes R-transpose)\n///////////////////////////////////////////////////////////////////////////////\nMatrix4& Matrix4::invertEuclidean()\n{\n    // transpose 3x3 rotation matrix part\n    // | R^T | 0 |\n    // | ----+-- |\n    // |  0  | 1 |\n    float tmp;\n    tmp = m[1];  m[1] = m[4];  m[4] = tmp;\n    tmp = m[2];  m[2] = m[8];  m[8] = tmp;\n    tmp = m[6];  m[6] = m[9];  m[9] = tmp;\n\n    // compute translation part -R^T * T\n    // | 0 | -R^T x |\n    // | --+------- |\n    // | 0 |   0    |\n    float x = m[12];\n    float y = m[13];\n    float z = m[14];\n    m[12] = -(m[0] * x + m[4] * y + m[8] * z);\n    m[13] = -(m[1] * x + m[5] * y + m[9] * z);\n    m[14] = -(m[2] * x + m[6] * y + m[10]* z);\n\n    // last row should be unchanged (0,0,0,1)\n\n    return *this;\n}\n\n\n\n///////////////////////////////////////////////////////////////////////////////\n// compute the inverse of a 4x4 affine transformation matrix\n//\n// Affine transformations are generalizations of Euclidean transformations.\n// Affine transformation includes translation, rotation, reflection, scaling,\n// and shearing. Length and angle are NOT preserved.\n// M = [ R | T ]\n//     [ --+-- ]    (R denotes 3x3 rotation/scale/shear matrix)\n//     [ 0 | 1 ]    (T denotes 1x3 translation matrix)\n//\n// y = M*x  ->  y = R*x + T  ->  x = R^-1*(y - T)  ->  x = R^-1*y - R^-1*T\n//\n//  [ R | T ]-1   [ R^-1 | -R^-1 * T ]\n//  [ --+-- ]   = [ -----+---------- ]\n//  [ 0 | 1 ]     [  0   +     1     ]\n///////////////////////////////////////////////////////////////////////////////\nMatrix4& Matrix4::invertAffine()\n{\n    // R^-1\n    Matrix3 r(m[0],m[1],m[2], m[4],m[5],m[6], m[8],m[9],m[10]);\n    r.invert();\n    m[0] = r[0];  m[1] = r[1];  m[2] = r[2];\n    m[4] = r[3];  m[5] = r[4];  m[6] = r[5];\n    m[8] = r[6];  m[9] = r[7];  m[10]= r[8];\n\n    // -R^-1 * T\n    float x = m[12];\n    float y = m[13];\n    float z = m[14];\n    m[12] = -(r[0] * x + r[3] * y + r[6] * z);\n    m[13] = -(r[1] * x + r[4] * y + r[7] * z);\n    m[14] = -(r[2] * x + r[5] * y + r[8] * z);\n\n    // last row should be unchanged (0,0,0,1)\n    //m[3] = m[7] = m[11] = 0.0f;\n    //m[15] = 1.0f;\n\n    return * this;\n}\n\n\n\n///////////////////////////////////////////////////////////////////////////////\n// inverse matrix using matrix partitioning (blockwise inverse)\n// It devides a 4x4 matrix into 4 of 2x2 matrices. It works in case of where\n// det(A) != 0. If not, use the generic inverse method\n// inverse formula.\n// M = [ A | B ]    A, B, C, D are 2x2 matrix blocks\n//     [ --+-- ]    det(M) = |A| * |D - ((C * A^-1) * B)|\n//     [ C | D ]\n//\n// M^-1 = [ A' | B' ]   A' = A^-1 - (A^-1 * B) * C'\n//        [ ---+--- ]   B' = (A^-1 * B) * -D'\n//        [ C' | D' ]   C' = -D' * (C * A^-1)\n//                      D' = (D - ((C * A^-1) * B))^-1\n//\n// NOTE: I wrap with () if it it used more than once.\n//       The matrix is invertable even if det(A)=0, so must check det(A) before\n//       calling this function, and use invertGeneric() instead.\n///////////////////////////////////////////////////////////////////////////////\nMatrix4& Matrix4::invertProjective()\n{\n    // partition\n    Matrix2 a(m[0], m[1], m[4], m[5]);\n    Matrix2 b(m[8], m[9], m[12], m[13]);\n    Matrix2 c(m[2], m[3], m[6], m[7]);\n    Matrix2 d(m[10], m[11], m[14], m[15]);\n\n    // pre-compute repeated parts\n    a.invert();             // A^-1\n    Matrix2 ab = a * b;     // A^-1 * B\n    Matrix2 ca = c * a;     // C * A^-1\n    Matrix2 cab = ca * b;   // C * A^-1 * B\n    Matrix2 dcab = d - cab; // D - C * A^-1 * B\n\n    // check determinant if |D - C * A^-1 * B| = 0\n    //NOTE: this function assumes det(A) is already checked. if |A|=0 then,\n    //      cannot use this function.\n    float determinant = dcab[0] * dcab[3] - dcab[1] * dcab[2];\n    if(fabs(determinant) <= EPSILON)\n    {\n        return identity();\n    }\n\n    // compute D' and -D'\n    Matrix2 d1 = dcab;      //  (D - C * A^-1 * B)\n    d1.invert();            //  (D - C * A^-1 * B)^-1\n    Matrix2 d2 = -d1;       // -(D - C * A^-1 * B)^-1\n\n    // compute C'\n    Matrix2 c1 = d2 * ca;   // -D' * (C * A^-1)\n\n    // compute B'\n    Matrix2 b1 = ab * d2;   // (A^-1 * B) * -D'\n\n    // compute A'\n    Matrix2 a1 = a - (ab * c1); // A^-1 - (A^-1 * B) * C'\n\n    // assemble inverse matrix\n    m[0] = a1[0];  m[4] = a1[2]; /*|*/ m[8] = b1[0];  m[12]= b1[2];\n    m[1] = a1[1];  m[5] = a1[3]; /*|*/ m[9] = b1[1];  m[13]= b1[3];\n    /*-----------------------------+-----------------------------*/\n    m[2] = c1[0];  m[6] = c1[2]; /*|*/ m[10]= d1[0];  m[14]= d1[2];\n    m[3] = c1[1];  m[7] = c1[3]; /*|*/ m[11]= d1[1];  m[15]= d1[3];\n\n    return *this;\n}\n\n\n\n///////////////////////////////////////////////////////////////////////////////\n// compute the inverse of a general 4x4 matrix using Cramer's Rule\n// If cannot find inverse, return indentity matrix\n// M^-1 = adj(M) / det(M)\n///////////////////////////////////////////////////////////////////////////////\nMatrix4& Matrix4::invertGeneral()\n{\n    // get cofactors of minor matrices\n    float cofactor0 = getCofactor(m[5],m[6],m[7], m[9],m[10],m[11], m[13],m[14],m[15]);\n    float cofactor1 = getCofactor(m[4],m[6],m[7], m[8],m[10],m[11], m[12],m[14],m[15]);\n    float cofactor2 = getCofactor(m[4],m[5],m[7], m[8],m[9], m[11], m[12],m[13],m[15]);\n    float cofactor3 = getCofactor(m[4],m[5],m[6], m[8],m[9], m[10], m[12],m[13],m[14]);\n\n    // get determinant\n    float determinant = m[0] * cofactor0 - m[1] * cofactor1 + m[2] * cofactor2 - m[3] * cofactor3;\n    if(fabs(determinant) <= EPSILON)\n    {\n        return identity();\n    }\n\n    // get rest of cofactors for adj(M)\n    float cofactor4 = getCofactor(m[1],m[2],m[3], m[9],m[10],m[11], m[13],m[14],m[15]);\n    float cofactor5 = getCofactor(m[0],m[2],m[3], m[8],m[10],m[11], m[12],m[14],m[15]);\n    float cofactor6 = getCofactor(m[0],m[1],m[3], m[8],m[9], m[11], m[12],m[13],m[15]);\n    float cofactor7 = getCofactor(m[0],m[1],m[2], m[8],m[9], m[10], m[12],m[13],m[14]);\n\n    float cofactor8 = getCofactor(m[1],m[2],m[3], m[5],m[6], m[7],  m[13],m[14],m[15]);\n    float cofactor9 = getCofactor(m[0],m[2],m[3], m[4],m[6], m[7],  m[12],m[14],m[15]);\n    float cofactor10= getCofactor(m[0],m[1],m[3], m[4],m[5], m[7],  m[12],m[13],m[15]);\n    float cofactor11= getCofactor(m[0],m[1],m[2], m[4],m[5], m[6],  m[12],m[13],m[14]);\n\n    float cofactor12= getCofactor(m[1],m[2],m[3], m[5],m[6], m[7],  m[9], m[10],m[11]);\n    float cofactor13= getCofactor(m[0],m[2],m[3], m[4],m[6], m[7],  m[8], m[10],m[11]);\n    float cofactor14= getCofactor(m[0],m[1],m[3], m[4],m[5], m[7],  m[8], m[9], m[11]);\n    float cofactor15= getCofactor(m[0],m[1],m[2], m[4],m[5], m[6],  m[8], m[9], m[10]);\n\n    // build inverse matrix = adj(M) / det(M)\n    // adjugate of M is the transpose of the cofactor matrix of M\n    float invDeterminant = 1.0f / determinant;\n    m[0] =  invDeterminant * cofactor0;\n    m[1] = -invDeterminant * cofactor4;\n    m[2] =  invDeterminant * cofactor8;\n    m[3] = -invDeterminant * cofactor12;\n\n    m[4] = -invDeterminant * cofactor1;\n    m[5] =  invDeterminant * cofactor5;\n    m[6] = -invDeterminant * cofactor9;\n    m[7] =  invDeterminant * cofactor13;\n\n    m[8] =  invDeterminant * cofactor2;\n    m[9] = -invDeterminant * cofactor6;\n    m[10]=  invDeterminant * cofactor10;\n    m[11]= -invDeterminant * cofactor14;\n\n    m[12]= -invDeterminant * cofactor3;\n    m[13]=  invDeterminant * cofactor7;\n    m[14]= -invDeterminant * cofactor11;\n    m[15]=  invDeterminant * cofactor15;\n\n    return *this;\n}\n\n\n\n///////////////////////////////////////////////////////////////////////////////\n// return determinant of 4x4 matrix\n///////////////////////////////////////////////////////////////////////////////\nfloat Matrix4::getDeterminant()\n{\n    return m[0] * getCofactor(m[5],m[6],m[7], m[9],m[10],m[11], m[13],m[14],m[15]) -\n           m[1] * getCofactor(m[4],m[6],m[7], m[8],m[10],m[11], m[12],m[14],m[15]) +\n           m[2] * getCofactor(m[4],m[5],m[7], m[8],m[9], m[11], m[12],m[13],m[15]) -\n           m[3] * getCofactor(m[4],m[5],m[6], m[8],m[9], m[10], m[12],m[13],m[14]);\n}\n\nvr::HmdMatrix34_t Matrix4::toOpenVR34() const\n{\n    vr::HmdMatrix34_t matrixObj = {0};\n    matrixObj.m[0][0] = m[0];\n    matrixObj.m[1][0] = m[1];\n    matrixObj.m[2][0] = m[2];\n    matrixObj.m[0][1] = m[4];\n    matrixObj.m[1][1] = m[5];\n    matrixObj.m[2][1] = m[6];\n    matrixObj.m[0][2] = m[8];\n    matrixObj.m[1][2] = m[9];\n    matrixObj.m[2][2] = m[10];\n    matrixObj.m[0][3] = m[12];\n    matrixObj.m[1][3] = m[13];\n    matrixObj.m[2][3] = m[14];\n\n    return matrixObj;\n}\n\nstd::string Matrix4::toString() const\n{\n    std::stringstream ss;\n    ss << '[';\n\n    for (size_t i = 0; i < 15; ++i)\n    {\n        ss << m[i] << ' ';\n    }\n\n    ss << m[15] << ']';\n\n    return ss.str();\n}\n\nVector3 Matrix4::getRotation() const\n{\n    Vector3 rot;\n    rot.x = std::asin(-clampf(m[9], -1.0f, 1.0f));        //Pitch\n    rot.z = std::atan2(m[1], m[5]);                       //Roll\n    rot.y = std::atan2(clampf(m[8], -1.0f, 1.0f), m[10]); //Yaw\n\n    //Convert to degrees\n    rot.x *= RAD2DEG;\n    rot.y *= RAD2DEG;\n    rot.z *= RAD2DEG;\n\n    return rot;\n}\n\nbool Matrix4::isZero() const\n{\n    for (size_t i = 0; i < 15; ++i)\n    {\n        if (m[i] != 0)\n            return false;\n    }\n    return true;\n}\n\n\n\n///////////////////////////////////////////////////////////////////////////////\n// compute cofactor of 3x3 minor matrix without sign\n// input params are 9 elements of the minor matrix\n// NOTE: The caller must know its sign.\n///////////////////////////////////////////////////////////////////////////////\nfloat Matrix4::getCofactor(float m0, float m1, float m2,\n                           float m3, float m4, float m5,\n                           float m6, float m7, float m8)\n{\n    return m0 * (m4 * m8 - m5 * m7) -\n           m1 * (m3 * m8 - m5 * m6) +\n           m2 * (m3 * m7 - m4 * m6);\n}\n\n\n\n///////////////////////////////////////////////////////////////////////////////\n// translate this matrix by (x, y, z)\n///////////////////////////////////////////////////////////////////////////////\nMatrix4& Matrix4::translate(const Vector3& v)\n{\n    return translate(v.x, v.y, v.z);\n}\n\nMatrix4& Matrix4::translate(float x, float y, float z)\n{\n    m[0] += m[3] * x;   m[4] += m[7] * x;   m[8] += m[11]* x;   m[12]+= m[15]* x;\n    m[1] += m[3] * y;   m[5] += m[7] * y;   m[9] += m[11]* y;   m[13]+= m[15]* y;\n    m[2] += m[3] * z;   m[6] += m[7] * z;   m[10]+= m[11]* z;   m[14]+= m[15]* z;\n\n    return *this;\n}\n\n\n\n///////////////////////////////////////////////////////////////////////////////\n// uniform scale\n///////////////////////////////////////////////////////////////////////////////\nMatrix4& Matrix4::scale(float s)\n{\n    return scale(s, s, s);\n}\n\nMatrix4& Matrix4::scale(float x, float y, float z)\n{\n    m[0] *= x;   m[4] *= x;   m[8] *= x;   m[12] *= x;\n    m[1] *= y;   m[5] *= y;   m[9] *= y;   m[13] *= y;\n    m[2] *= z;   m[6] *= z;   m[10]*= z;   m[14] *= z;\n    return *this;\n}\n\n\n\n///////////////////////////////////////////////////////////////////////////////\n// build a rotation matrix with given angle(degree) and rotation axis, then\n// multiply it with this object\n///////////////////////////////////////////////////////////////////////////////\nMatrix4& Matrix4::rotate(float angle, const Vector3& axis)\n{\n    return rotate(angle, axis.x, axis.y, axis.z);\n}\n\nMatrix4& Matrix4::rotate(float angle, float x, float y, float z)\n{\n    float c = cosf(angle * DEG2RAD);    // cosine\n    float s = sinf(angle * DEG2RAD);    // sine\n    float c1 = 1.0f - c;                // 1 - c\n    float m0 = m[0],  m4 = m[4],  m8 = m[8],  m12= m[12],\n          m1 = m[1],  m5 = m[5],  m9 = m[9],  m13= m[13],\n          m2 = m[2],  m6 = m[6],  m10= m[10], m14= m[14];\n\n    // build rotation matrix\n    float r0 = x * x * c1 + c;\n    float r1 = x * y * c1 + z * s;\n    float r2 = x * z * c1 - y * s;\n    float r4 = x * y * c1 - z * s;\n    float r5 = y * y * c1 + c;\n    float r6 = y * z * c1 + x * s;\n    float r8 = x * z * c1 + y * s;\n    float r9 = y * z * c1 - x * s;\n    float r10= z * z * c1 + c;\n\n    // multiply rotation matrix\n    m[0] = r0 * m0 + r4 * m1 + r8 * m2;\n    m[1] = r1 * m0 + r5 * m1 + r9 * m2;\n    m[2] = r2 * m0 + r6 * m1 + r10* m2;\n    m[4] = r0 * m4 + r4 * m5 + r8 * m6;\n    m[5] = r1 * m4 + r5 * m5 + r9 * m6;\n    m[6] = r2 * m4 + r6 * m5 + r10* m6;\n    m[8] = r0 * m8 + r4 * m9 + r8 * m10;\n    m[9] = r1 * m8 + r5 * m9 + r9 * m10;\n    m[10]= r2 * m8 + r6 * m9 + r10* m10;\n    m[12]= r0 * m12+ r4 * m13+ r8 * m14;\n    m[13]= r1 * m12+ r5 * m13+ r9 * m14;\n    m[14]= r2 * m12+ r6 * m13+ r10* m14;\n\n    return *this;\n}\n\nMatrix4& Matrix4::rotateX(float angle)\n{\n    float c = cosf(angle * DEG2RAD);\n    float s = sinf(angle * DEG2RAD);\n    float m1 = m[1],  m2 = m[2],\n          m5 = m[5],  m6 = m[6],\n          m9 = m[9],  m10= m[10],\n          m13= m[13], m14= m[14];\n\n    m[1] = m1 * c + m2 *-s;\n    m[2] = m1 * s + m2 * c;\n    m[5] = m5 * c + m6 *-s;\n    m[6] = m5 * s + m6 * c;\n    m[9] = m9 * c + m10*-s;\n    m[10]= m9 * s + m10* c;\n    m[13]= m13* c + m14*-s;\n    m[14]= m13* s + m14* c;\n\n    return *this;\n}\n\nMatrix4& Matrix4::rotateY(float angle)\n{\n    float c = cosf(angle * DEG2RAD);\n    float s = sinf(angle * DEG2RAD);\n    float m0 = m[0],  m2 = m[2],\n          m4 = m[4],  m6 = m[6],\n          m8 = m[8],  m10= m[10],\n          m12= m[12], m14= m[14];\n\n    m[0] = m0 * c + m2 * s;\n    m[2] = m0 *-s + m2 * c;\n    m[4] = m4 * c + m6 * s;\n    m[6] = m4 *-s + m6 * c;\n    m[8] = m8 * c + m10* s;\n    m[10]= m8 *-s + m10* c;\n    m[12]= m12* c + m14* s;\n    m[14]= m12*-s + m14* c;\n\n    return *this;\n}\n\nMatrix4& Matrix4::rotateZ(float angle)\n{\n    float c = cosf(angle * DEG2RAD);\n    float s = sinf(angle * DEG2RAD);\n    float m0 = m[0],  m1 = m[1],\n          m4 = m[4],  m5 = m[5],\n          m8 = m[8],  m9 = m[9],\n          m12= m[12], m13= m[13];\n\n    m[0] = m0 * c + m1 *-s;\n    m[1] = m0 * s + m1 * c;\n    m[4] = m4 * c + m5 *-s;\n    m[5] = m4 * s + m5 * c;\n    m[8] = m8 * c + m9 *-s;\n    m[9] = m8 * s + m9 * c;\n    m[12]= m12* c + m13*-s;\n    m[13]= m12* s + m13* c;\n\n    return *this;\n}\n"
  },
  {
    "path": "src/Shared/Matrices.h",
    "content": "// Desktop+: Modified for OpenVR compatibility and more\n\n///////////////////////////////////////////////////////////////////////////////\n// Matrice.h\n// =========\n// NxN Matrix Math classes\n//\n// The elements of the matrix are stored as column major order.\n// | 0 2 |    | 0 3 6 |    |  0  4  8 12 |\n// | 1 3 |    | 1 4 7 |    |  1  5  9 13 |\n//            | 2 5 8 |    |  2  6 10 14 |\n//                         |  3  7 11 15 |\n//\n//  AUTHOR: Song Ho Ahn (song.ahn@gmail.com)\n// CREATED: 2005-06-24\n// UPDATED: 2013-09-30\n//\n// Copyright (C) 2005 Song Ho Ahn\n///////////////////////////////////////////////////////////////////////////////\n\n#ifndef MATH_MATRICES_H\n#define MATH_MATRICES_H\n\n#include <iostream>\n#include <iomanip>\n#include \"Vectors.h\"\n#include \"openvr.h\"\n\n///////////////////////////////////////////////////////////////////////////\n// 2x2 matrix\n///////////////////////////////////////////////////////////////////////////\nclass Matrix2\n{\npublic:\n    // constructors\n    Matrix2();  // init with identity\n    Matrix2(const float src[4]);\n    Matrix2(float m0, float m1, float m2, float m3);\n\n    void        set(const float src[4]);\n    void        set(float m0, float m1, float m2, float m3);\n    void        setRow(int index, const float row[2]);\n    void        setRow(int index, const Vector2& v);\n    void        setColumn(int index, const float col[2]);\n    void        setColumn(int index, const Vector2& v);\n\n    const float* get() const;\n    float       getDeterminant();\n\n    Matrix2&    identity();\n    Matrix2&    transpose();                            // transpose itself and return reference\n    Matrix2&    invert();\n\n    // operators\n    Matrix2     operator+(const Matrix2& rhs) const;    // add rhs\n    Matrix2     operator-(const Matrix2& rhs) const;    // subtract rhs\n    Matrix2&    operator+=(const Matrix2& rhs);         // add rhs and update this object\n    Matrix2&    operator-=(const Matrix2& rhs);         // subtract rhs and update this object\n    Vector2     operator*(const Vector2& rhs) const;    // multiplication: v' = M * v\n    Matrix2     operator*(const Matrix2& rhs) const;    // multiplication: M3 = M1 * M2\n    Matrix2&    operator*=(const Matrix2& rhs);         // multiplication: M1' = M1 * M2\n    bool        operator==(const Matrix2& rhs) const;   // exact compare, no epsilon\n    bool        operator!=(const Matrix2& rhs) const;   // exact compare, no epsilon\n    float       operator[](int index) const;            // subscript operator v[0], v[1]\n    float&      operator[](int index);                  // subscript operator v[0], v[1]\n\n    friend Matrix2 operator-(const Matrix2& m);                     // unary operator (-)\n    friend Matrix2 operator*(float scalar, const Matrix2& m);       // pre-multiplication\n    friend Vector2 operator*(const Vector2& vec, const Matrix2& m); // pre-multiplication\n    friend std::ostream& operator<<(std::ostream& os, const Matrix2& m);\n\nprotected:\n\nprivate:\n    float m[4];\n\n};\n\n\n\n///////////////////////////////////////////////////////////////////////////\n// 3x3 matrix\n///////////////////////////////////////////////////////////////////////////\nclass Matrix3\n{\npublic:\n    // constructors\n    Matrix3();  // init with identity\n    Matrix3(const float src[9]);\n    Matrix3(float m0, float m1, float m2,           // 1st column\n            float m3, float m4, float m5,           // 2nd column\n            float m6, float m7, float m8);          // 3rd column\n\n    void        set(const float src[9]);\n    void        set(float m0, float m1, float m2,   // 1st column\n                    float m3, float m4, float m5,   // 2nd column\n                    float m6, float m7, float m8);  // 3rd column\n    void        setRow(int index, const float row[3]);\n    void        setRow(int index, const Vector3& v);\n    void        setColumn(int index, const float col[3]);\n    void        setColumn(int index, const Vector3& v);\n\n    const float* get() const;\n    float       getDeterminant();\n\n    Matrix3&    identity();\n    Matrix3&    transpose();                            // transpose itself and return reference\n    Matrix3&    invert();\n\n    // operators\n    Matrix3     operator+(const Matrix3& rhs) const;    // add rhs\n    Matrix3     operator-(const Matrix3& rhs) const;    // subtract rhs\n    Matrix3&    operator+=(const Matrix3& rhs);         // add rhs and update this object\n    Matrix3&    operator-=(const Matrix3& rhs);         // subtract rhs and update this object\n    Vector3     operator*(const Vector3& rhs) const;    // multiplication: v' = M * v\n    Matrix3     operator*(const Matrix3& rhs) const;    // multiplication: M3 = M1 * M2\n    Matrix3&    operator*=(const Matrix3& rhs);         // multiplication: M1' = M1 * M2\n    bool        operator==(const Matrix3& rhs) const;   // exact compare, no epsilon\n    bool        operator!=(const Matrix3& rhs) const;   // exact compare, no epsilon\n    float       operator[](int index) const;            // subscript operator v[0], v[1]\n    float&      operator[](int index);                  // subscript operator v[0], v[1]\n\n    friend Matrix3 operator-(const Matrix3& m);                     // unary operator (-)\n    friend Matrix3 operator*(float scalar, const Matrix3& m);       // pre-multiplication\n    friend Vector3 operator*(const Vector3& vec, const Matrix3& m); // pre-multiplication\n    friend std::ostream& operator<<(std::ostream& os, const Matrix3& m);\n\nprotected:\n\nprivate:\n    float m[9];\n\n};\n\n\n\n///////////////////////////////////////////////////////////////////////////\n// 4x4 matrix\n///////////////////////////////////////////////////////////////////////////\nclass Matrix4\n{\npublic:\n    // constructors\n    Matrix4();  // init with identity\n    Matrix4(const float src[16]);\n    Matrix4(float m00, float m01, float m02, float m03, // 1st column\n            float m04, float m05, float m06, float m07, // 2nd column\n            float m08, float m09, float m10, float m11, // 3rd column\n            float m12, float m13, float m14, float m15);// 4th column\n    Matrix4(const vr::HmdMatrix34_t& src);\t//From OpenVR 3x4 matrix\n    Matrix4(const std::string str);         //From string generated by toString()\n    Matrix4(const Vector3& right, const Vector3& up, const Vector3& forward); //Construct matrix from right, up and forward vectors, resulting in the following matrix:\n                                                                              //Rx Ux Fx  0\n                                                                              //Ry Uy Fy  0\n                                                                              //Rz Uz Fz  0\n                                                                              //0   0  0  1\n\n    void        set(const float src[16]);\n    void        set(float m00, float m01, float m02, float m03, // 1st column\n                    float m04, float m05, float m06, float m07, // 2nd column\n                    float m08, float m09, float m10, float m11, // 3rd column\n                    float m12, float m13, float m14, float m15);// 4th column\n    void        setRow(int index, const float row[4]);\n    void        setRow(int index, const Vector4& v);\n    void        setRow(int index, const Vector3& v);\n    void        setColumn(int index, const float col[4]);\n    void        setColumn(int index, const Vector4& v);\n    void        setColumn(int index, const Vector3& v);\n\n    void setTranslation(const Vector3 & v);\n    void setRotation(float x, float y, float z);        //Set rotation with euler angles (ZYX rotation order). Resets scale to 1, if any\n    void setRotation(const Vector3& v);                 //^\n\n    const float*      get() const;\n    const float*      getTranspose();                   // return transposed matrix\n    float\t\t\t  getDeterminant();\n    vr::HmdMatrix34_t toOpenVR34() const;\n    std::string       toString() const;\n\n\n    Vector3 getTranslation() const;\n    Vector3 getRotation()    const;                     //Returns rotation in euler angles (ZYX rotation order)\n    bool    isZero() const;                             // return if the matrix is a zero matrix\n\n    Matrix4&    identity();\n    Matrix4&    zero();\n    Matrix4&    transpose();                            // transpose itself and return reference\n    Matrix4&    invert();                               // check best inverse method before inverse\n    Matrix4&    invertEuclidean();                      // inverse of Euclidean transform matrix\n    Matrix4&    invertAffine();                         // inverse of affine transform matrix\n    Matrix4&    invertProjective();                     // inverse of projective matrix using partitioning\n    Matrix4&    invertGeneral();                        // inverse of generic matrix\n\n    // transform matrix\n    Matrix4&    translate(float x, float y, float z);   // translation by (x,y,z)\n    Matrix4&    translate(const Vector3& v);            //\n    Matrix4&    translate_relative(float offset_right, float offset_up, float offset_forward); // translation relative to current rotation\n    Matrix4&    translate_relative(const Vector3& v);\n    Matrix4&    rotate(float angle, const Vector3& axis); // rotate angle(degree) along the given axix\n    Matrix4&    rotate(float angle, float x, float y, float z);\n    Matrix4&    rotateX(float angle);                   // rotate on X-axis with degree\n    Matrix4&    rotateY(float angle);                   // rotate on Y-axis with degree\n    Matrix4&    rotateZ(float angle);                   // rotate on Z-axis with degree\n    Matrix4&    scale(float scale);                     // uniform scale\n    Matrix4&    scale(float sx, float sy, float sz);    // scale by (sx, sy, sz) on each axis\n\n    // operators\n    Matrix4     operator+(const Matrix4& rhs) const;    // add rhs\n    Matrix4     operator-(const Matrix4& rhs) const;    // subtract rhs\n    Matrix4&    operator+=(const Matrix4& rhs);         // add rhs and update this object\n    Matrix4&    operator-=(const Matrix4& rhs);         // subtract rhs and update this object\n    Vector4     operator*(const Vector4& rhs) const;    // multiplication: v' = M * v\n    Vector3     operator*(const Vector3& rhs) const;    // multiplication: v' = M * v\n    Matrix4     operator*(const Matrix4& rhs) const;    // multiplication: M3 = M1 * M2\n    Matrix4&    operator*=(const Matrix4& rhs);         // multiplication: M1' = M1 * M2\n    bool        operator==(const Matrix4& rhs) const;   // exact compare, no epsilon\n    bool        operator!=(const Matrix4& rhs) const;   // exact compare, no epsilon\n    float       operator[](int index) const;            // subscript operator v[0], v[1]\n    float&      operator[](int index);                  // subscript operator v[0], v[1]\n\n    friend Matrix4 operator-(const Matrix4& m);                     // unary operator (-)\n    friend Matrix4 operator*(float scalar, const Matrix4& m);       // pre-multiplication\n    friend Vector3 operator*(const Vector3& vec, const Matrix4& m); // pre-multiplication\n    friend Vector4 operator*(const Vector4& vec, const Matrix4& m); // pre-multiplication\n    friend std::ostream& operator<<(std::ostream& os, const Matrix4& m);\n\nprotected:\n\nprivate:\n    float       getCofactor(float m0, float m1, float m2,\n                            float m3, float m4, float m5,\n                            float m6, float m7, float m8);\n\n    float m[16];\n\n};\n\n\n\n///////////////////////////////////////////////////////////////////////////\n// inline functions for Matrix2\n///////////////////////////////////////////////////////////////////////////\ninline Matrix2::Matrix2()\n{\n    // initially identity matrix\n    identity();\n}\n\n\n\ninline Matrix2::Matrix2(const float src[4])\n{\n    set(src);\n}\n\n\n\ninline Matrix2::Matrix2(float m0, float m1, float m2, float m3)\n{\n    set(m0, m1, m2, m3);\n}\n\n\n\ninline void Matrix2::set(const float src[4])\n{\n    m[0] = src[0];  m[1] = src[1];  m[2] = src[2];  m[3] = src[3];\n}\n\n\n\ninline void Matrix2::set(float m0, float m1, float m2, float m3)\n{\n    m[0]= m0;  m[1] = m1;  m[2] = m2;  m[3]= m3;\n}\n\n\n\ninline void Matrix2::setRow(int index, const float row[2])\n{\n    m[index] = row[0];  m[index + 2] = row[1];\n}\n\n\n\ninline void Matrix2::setRow(int index, const Vector2& v)\n{\n    m[index] = v.x;  m[index + 2] = v.y;\n}\n\n\n\ninline void Matrix2::setColumn(int index, const float col[2])\n{\n    m[index*2] = col[0];  m[index*2 + 1] = col[1];\n}\n\n\n\ninline void Matrix2::setColumn(int index, const Vector2& v)\n{\n    m[index*2] = v.x;  m[index*2 + 1] = v.y;\n}\n\n\n\ninline const float* Matrix2::get() const\n{\n    return m;\n}\n\n\n\ninline Matrix2& Matrix2::identity()\n{\n    m[0] = m[3] = 1.0f;\n    m[1] = m[2] = 0.0f;\n    return *this;\n}\n\n\n\ninline Matrix2 Matrix2::operator+(const Matrix2& rhs) const\n{\n    return Matrix2(m[0]+rhs[0], m[1]+rhs[1], m[2]+rhs[2], m[3]+rhs[3]);\n}\n\n\n\ninline Matrix2 Matrix2::operator-(const Matrix2& rhs) const\n{\n    return Matrix2(m[0]-rhs[0], m[1]-rhs[1], m[2]-rhs[2], m[3]-rhs[3]);\n}\n\n\n\ninline Matrix2& Matrix2::operator+=(const Matrix2& rhs)\n{\n    m[0] += rhs[0];  m[1] += rhs[1];  m[2] += rhs[2];  m[3] += rhs[3];\n    return *this;\n}\n\n\n\ninline Matrix2& Matrix2::operator-=(const Matrix2& rhs)\n{\n    m[0] -= rhs[0];  m[1] -= rhs[1];  m[2] -= rhs[2];  m[3] -= rhs[3];\n    return *this;\n}\n\n\n\ninline Vector2 Matrix2::operator*(const Vector2& rhs) const\n{\n    return Vector2(m[0]*rhs.x + m[2]*rhs.y,  m[1]*rhs.x + m[3]*rhs.y);\n}\n\n\n\ninline Matrix2 Matrix2::operator*(const Matrix2& rhs) const\n{\n    return Matrix2(m[0]*rhs[0] + m[2]*rhs[1],  m[1]*rhs[0] + m[3]*rhs[1],\n                   m[0]*rhs[2] + m[2]*rhs[3],  m[1]*rhs[2] + m[3]*rhs[3]);\n}\n\n\n\ninline Matrix2& Matrix2::operator*=(const Matrix2& rhs)\n{\n    *this = *this * rhs;\n    return *this;\n}\n\n\n\ninline bool Matrix2::operator==(const Matrix2& rhs) const\n{\n    return (m[0] == rhs[0]) && (m[1] == rhs[1]) && (m[2] == rhs[2]) && (m[3] == rhs[3]);\n}\n\n\n\ninline bool Matrix2::operator!=(const Matrix2& rhs) const\n{\n    return (m[0] != rhs[0]) || (m[1] != rhs[1]) || (m[2] != rhs[2]) || (m[3] != rhs[3]);\n}\n\n\n\ninline float Matrix2::operator[](int index) const\n{\n    return m[index];\n}\n\n\n\ninline float& Matrix2::operator[](int index)\n{\n    return m[index];\n}\n\n\n\ninline Matrix2 operator-(const Matrix2& rhs)\n{\n    return Matrix2(-rhs[0], -rhs[1], -rhs[2], -rhs[3]);\n}\n\n\n\ninline Matrix2 operator*(float s, const Matrix2& rhs)\n{\n    return Matrix2(s*rhs[0], s*rhs[1], s*rhs[2], s*rhs[3]);\n}\n\n\n\ninline Vector2 operator*(const Vector2& v, const Matrix2& rhs)\n{\n    return Vector2(v.x*rhs[0] + v.y*rhs[1],  v.x*rhs[2] + v.y*rhs[3]);\n}\n\n\n\ninline std::ostream& operator<<(std::ostream& os, const Matrix2& m)\n{\n    os << std::fixed << std::setprecision(5);\n    os << \"[\" << std::setw(10) << m[0] << \" \" << std::setw(10) << m[2] << \"]\\n\"\n       << \"[\" << std::setw(10) << m[1] << \" \" << std::setw(10) << m[3] << \"]\\n\";\n    os << std::resetiosflags(std::ios_base::fixed | std::ios_base::floatfield);\n    return os;\n}\n// END OF MATRIX2 INLINE //////////////////////////////////////////////////////\n\n\n\n\n///////////////////////////////////////////////////////////////////////////\n// inline functions for Matrix3\n///////////////////////////////////////////////////////////////////////////\ninline Matrix3::Matrix3()\n{\n    // initially identity matrix\n    identity();\n}\n\n\n\ninline Matrix3::Matrix3(const float src[9])\n{\n    set(src);\n}\n\n\n\ninline Matrix3::Matrix3(float m0, float m1, float m2,\n                        float m3, float m4, float m5,\n                        float m6, float m7, float m8)\n{\n    set(m0, m1, m2,  m3, m4, m5,  m6, m7, m8);\n}\n\n\n\ninline void Matrix3::set(const float src[9])\n{\n    m[0] = src[0];  m[1] = src[1];  m[2] = src[2];\n    m[3] = src[3];  m[4] = src[4];  m[5] = src[5];\n    m[6] = src[6];  m[7] = src[7];  m[8] = src[8];\n}\n\n\n\ninline void Matrix3::set(float m0, float m1, float m2,\n                         float m3, float m4, float m5,\n                         float m6, float m7, float m8)\n{\n    m[0] = m0;  m[1] = m1;  m[2] = m2;\n    m[3] = m3;  m[4] = m4;  m[5] = m5;\n    m[6] = m6;  m[7] = m7;  m[8] = m8;\n}\n\n\n\ninline void Matrix3::setRow(int index, const float row[3])\n{\n    m[index] = row[0];  m[index + 3] = row[1];  m[index + 6] = row[2];\n}\n\n\n\ninline void Matrix3::setRow(int index, const Vector3& v)\n{\n    m[index] = v.x;  m[index + 3] = v.y;  m[index + 6] = v.z;\n}\n\n\n\ninline void Matrix3::setColumn(int index, const float col[3])\n{\n    m[index*3] = col[0];  m[index*3 + 1] = col[1];  m[index*3 + 2] = col[2];\n}\n\n\n\ninline void Matrix3::setColumn(int index, const Vector3& v)\n{\n    m[index*3] = v.x;  m[index*3 + 1] = v.y;  m[index*3 + 2] = v.z;\n}\n\n\n\ninline const float* Matrix3::get() const\n{\n    return m;\n}\n\n\n\ninline Matrix3& Matrix3::identity()\n{\n    m[0] = m[4] = m[8] = 1.0f;\n    m[1] = m[2] = m[3] = m[5] = m[6] = m[7] = 0.0f;\n    return *this;\n}\n\n\n\ninline Matrix3 Matrix3::operator+(const Matrix3& rhs) const\n{\n    return Matrix3(m[0]+rhs[0], m[1]+rhs[1], m[2]+rhs[2],\n                   m[3]+rhs[3], m[4]+rhs[4], m[5]+rhs[5],\n                   m[6]+rhs[6], m[7]+rhs[7], m[8]+rhs[8]);\n}\n\n\n\ninline Matrix3 Matrix3::operator-(const Matrix3& rhs) const\n{\n    return Matrix3(m[0]-rhs[0], m[1]-rhs[1], m[2]-rhs[2],\n                   m[3]-rhs[3], m[4]-rhs[4], m[5]-rhs[5],\n                   m[6]-rhs[6], m[7]-rhs[7], m[8]-rhs[8]);\n}\n\n\n\ninline Matrix3& Matrix3::operator+=(const Matrix3& rhs)\n{\n    m[0] += rhs[0];  m[1] += rhs[1];  m[2] += rhs[2];\n    m[3] += rhs[3];  m[4] += rhs[4];  m[5] += rhs[5];\n    m[6] += rhs[6];  m[7] += rhs[7];  m[8] += rhs[8];\n    return *this;\n}\n\n\n\ninline Matrix3& Matrix3::operator-=(const Matrix3& rhs)\n{\n    m[0] -= rhs[0];  m[1] -= rhs[1];  m[2] -= rhs[2];\n    m[3] -= rhs[3];  m[4] -= rhs[4];  m[5] -= rhs[5];\n    m[6] -= rhs[6];  m[7] -= rhs[7];  m[8] -= rhs[8];\n    return *this;\n}\n\n\n\ninline Vector3 Matrix3::operator*(const Vector3& rhs) const\n{\n    return Vector3(m[0]*rhs.x + m[3]*rhs.y + m[6]*rhs.z,\n                   m[1]*rhs.x + m[4]*rhs.y + m[7]*rhs.z,\n                   m[2]*rhs.x + m[5]*rhs.y + m[8]*rhs.z);\n}\n\n\n\ninline Matrix3 Matrix3::operator*(const Matrix3& rhs) const\n{\n    return Matrix3(m[0]*rhs[0] + m[3]*rhs[1] + m[6]*rhs[2],  m[1]*rhs[0] + m[4]*rhs[1] + m[7]*rhs[2],  m[2]*rhs[0] + m[5]*rhs[1] + m[8]*rhs[2],\n                   m[0]*rhs[3] + m[3]*rhs[4] + m[6]*rhs[5],  m[1]*rhs[3] + m[4]*rhs[4] + m[7]*rhs[5],  m[2]*rhs[3] + m[5]*rhs[4] + m[8]*rhs[5],\n                   m[0]*rhs[6] + m[3]*rhs[7] + m[6]*rhs[8],  m[1]*rhs[6] + m[4]*rhs[7] + m[7]*rhs[8],  m[2]*rhs[6] + m[5]*rhs[7] + m[8]*rhs[8]);\n}\n\n\n\ninline Matrix3& Matrix3::operator*=(const Matrix3& rhs)\n{\n    *this = *this * rhs;\n    return *this;\n}\n\n\n\ninline bool Matrix3::operator==(const Matrix3& rhs) const\n{\n    return (m[0] == rhs[0]) && (m[1] == rhs[1]) && (m[2] == rhs[2]) &&\n           (m[3] == rhs[3]) && (m[4] == rhs[4]) && (m[5] == rhs[5]) &&\n           (m[6] == rhs[6]) && (m[7] == rhs[7]) && (m[8] == rhs[8]);\n}\n\n\n\ninline bool Matrix3::operator!=(const Matrix3& rhs) const\n{\n    return (m[0] != rhs[0]) || (m[1] != rhs[1]) || (m[2] != rhs[2]) ||\n           (m[3] != rhs[3]) || (m[4] != rhs[4]) || (m[5] != rhs[5]) ||\n           (m[6] != rhs[6]) || (m[7] != rhs[7]) || (m[8] != rhs[8]);\n}\n\n\n\ninline float Matrix3::operator[](int index) const\n{\n    return m[index];\n}\n\n\n\ninline float& Matrix3::operator[](int index)\n{\n    return m[index];\n}\n\n\n\ninline Matrix3 operator-(const Matrix3& rhs)\n{\n    return Matrix3(-rhs[0], -rhs[1], -rhs[2], -rhs[3], -rhs[4], -rhs[5], -rhs[6], -rhs[7], -rhs[8]);\n}\n\n\n\ninline Matrix3 operator*(float s, const Matrix3& rhs)\n{\n    return Matrix3(s*rhs[0], s*rhs[1], s*rhs[2], s*rhs[3], s*rhs[4], s*rhs[5], s*rhs[6], s*rhs[7], s*rhs[8]);\n}\n\n\n\ninline Vector3 operator*(const Vector3& v, const Matrix3& m)\n{\n    return Vector3(v.x*m[0] + v.y*m[1] + v.z*m[2],  v.x*m[3] + v.y*m[4] + v.z*m[5],  v.x*m[6] + v.y*m[7] + v.z*m[8]);\n}\n\n\n\ninline std::ostream& operator<<(std::ostream& os, const Matrix3& m)\n{\n    os << std::fixed << std::setprecision(5);\n    os << \"[\" << std::setw(10) << m[0] << \" \" << std::setw(10) << m[3] << \" \" << std::setw(10) << m[6] << \"]\\n\"\n       << \"[\" << std::setw(10) << m[1] << \" \" << std::setw(10) << m[4] << \" \" << std::setw(10) << m[7] << \"]\\n\"\n       << \"[\" << std::setw(10) << m[2] << \" \" << std::setw(10) << m[5] << \" \" << std::setw(10) << m[8] << \"]\\n\";\n    os << std::resetiosflags(std::ios_base::fixed | std::ios_base::floatfield);\n    return os;\n}\n// END OF MATRIX3 INLINE //////////////////////////////////////////////////////\n\n\n\n\n///////////////////////////////////////////////////////////////////////////\n// inline functions for Matrix4\n///////////////////////////////////////////////////////////////////////////\ninline Matrix4::Matrix4()\n{\n    // initially identity matrix\n    identity();\n}\n\n\n\ninline Matrix4::Matrix4(const float src[16])\n{\n    set(src);\n}\n\n\n\ninline Matrix4::Matrix4(float m00, float m01, float m02, float m03,\n                        float m04, float m05, float m06, float m07,\n                        float m08, float m09, float m10, float m11,\n                        float m12, float m13, float m14, float m15)\n{\n    set(m00, m01, m02, m03,  m04, m05, m06, m07,  m08, m09, m10, m11,  m12, m13, m14, m15);\n}\n\ninline Matrix4::Matrix4(const vr::HmdMatrix34_t& src)\n{\n    set(\n        src.m[0][0], src.m[1][0], src.m[2][0], 0.0f,\n        src.m[0][1], src.m[1][1], src.m[2][1], 0.0f,\n        src.m[0][2], src.m[1][2], src.m[2][2], 0.0f,\n        src.m[0][3], src.m[1][3], src.m[2][3], 1.0f\n        );\n}\n\ninline Matrix4::Matrix4(const Vector3& right, const Vector3& up, const Vector3& forward)\n{\n    set(\n        right.x,   right.y,   right.z,   0.0f,\n        up.x,      up.y,      up.z,      0.0f,\n        forward.x, forward.y, forward.z, 0.0f,\n        0.0f,      0.0f,      0.0f,      1.0f\n        );\n}\n\n\n\ninline void Matrix4::set(const float src[16])\n{\n    m[0] = src[0];  m[1] = src[1];  m[2] = src[2];  m[3] = src[3];\n    m[4] = src[4];  m[5] = src[5];  m[6] = src[6];  m[7] = src[7];\n    m[8] = src[8];  m[9] = src[9];  m[10]= src[10]; m[11]= src[11];\n    m[12]= src[12]; m[13]= src[13]; m[14]= src[14]; m[15]= src[15];\n}\n\n\n\ninline void Matrix4::set(float m00, float m01, float m02, float m03,\n                         float m04, float m05, float m06, float m07,\n                         float m08, float m09, float m10, float m11,\n                         float m12, float m13, float m14, float m15)\n{\n    m[0] = m00;  m[1] = m01;  m[2] = m02;  m[3] = m03;\n    m[4] = m04;  m[5] = m05;  m[6] = m06;  m[7] = m07;\n    m[8] = m08;  m[9] = m09;  m[10]= m10;  m[11]= m11;\n    m[12]= m12;  m[13]= m13;  m[14]= m14;  m[15]= m15;\n}\n\n\n\ninline void Matrix4::setRow(int index, const float row[4])\n{\n    m[index] = row[0];  m[index + 4] = row[1];  m[index + 8] = row[2];  m[index + 12] = row[3];\n}\n\n\n\ninline void Matrix4::setRow(int index, const Vector4& v)\n{\n    m[index] = v.x;  m[index + 4] = v.y;  m[index + 8] = v.z;  m[index + 12] = v.w;\n}\n\n\n\ninline void Matrix4::setRow(int index, const Vector3& v)\n{\n    m[index] = v.x;  m[index + 4] = v.y;  m[index + 8] = v.z;\n}\n\n\n\ninline void Matrix4::setColumn(int index, const float col[4])\n{\n    m[index*4] = col[0];  m[index*4 + 1] = col[1];  m[index*4 + 2] = col[2];  m[index*4 + 3] = col[3];\n}\n\n\n\ninline void Matrix4::setColumn(int index, const Vector4& v)\n{\n    m[index*4] = v.x;  m[index*4 + 1] = v.y;  m[index*4 + 2] = v.z;  m[index*4 + 3] = v.w;\n}\n\n\n\ninline void Matrix4::setColumn(int index, const Vector3& v)\n{\n    m[index*4] = v.x;  m[index*4 + 1] = v.y;  m[index*4 + 2] = v.z;\n}\n\n\n\ninline void Matrix4::setTranslation(const Vector3& v)\n{\n    m[12] = v.x;\n    m[13] = v.y;\n    m[14] = v.z;\n}\n\n\n\ninline const float* Matrix4::get() const\n{\n    return m;\n}\n\n\n\ninline const float* Matrix4::getTranspose()\n{\n    float tm[16];\n    tm[0] = m[0];   tm[1] = m[4];   tm[2] = m[8];   tm[3] = m[12];\n    tm[4] = m[1];   tm[5] = m[5];   tm[6] = m[9];   tm[7] = m[13];\n    tm[8] = m[2];   tm[9] = m[6];   tm[10]= m[10];  tm[11]= m[14];\n    tm[12]= m[3];   tm[13]= m[7];   tm[14]= m[11];  tm[15]= m[15];\n    return tm;\n}\n\n\n\ninline Vector3 Matrix4::getTranslation() const\n{\n    return Vector3(m[12], m[13], m[14]);\n}\n\ninline Matrix4& Matrix4::identity()\n{\n    m[0] = m[5] = m[10] = m[15] = 1.0f;\n    m[1] = m[2] = m[3] = m[4] = m[6] = m[7] = m[8] = m[9] = m[11] = m[12] = m[13] = m[14] = 0.0f;\n    return *this;\n}\n\ninline Matrix4& Matrix4::zero()\n{\n    m[0] = m[1] = m[2] = m[3] = m[4] = m[5] = m[6] = m[7] = m[8] = m[9] = m[10] = m[11] = m[12] = m[13] = m[14] = m[15] = 0.0f;\n    return *this;\n}\n\ninline Matrix4& Matrix4::translate_relative(float offset_right, float offset_up, float offset_forward)\n{\n    Vector3 offset(offset_right, offset_up, offset_forward);\n\n    return translate_relative(offset);\n}\n\ninline Matrix4& Matrix4::translate_relative(const Vector3& v)\n{\n    Vector3 offset = *this * v;\n\n    m[12] += offset.x;\n    m[13] += offset.y;\n    m[14] += offset.z;\n\n    return *this;\n}\n\ninline Matrix4 Matrix4::operator+(const Matrix4& rhs) const\n{\n    return Matrix4(m[0]+rhs[0],   m[1]+rhs[1],   m[2]+rhs[2],   m[3]+rhs[3],\n                   m[4]+rhs[4],   m[5]+rhs[5],   m[6]+rhs[6],   m[7]+rhs[7],\n                   m[8]+rhs[8],   m[9]+rhs[9],   m[10]+rhs[10], m[11]+rhs[11],\n                   m[12]+rhs[12], m[13]+rhs[13], m[14]+rhs[14], m[15]+rhs[15]);\n}\n\n\n\ninline Matrix4 Matrix4::operator-(const Matrix4& rhs) const\n{\n    return Matrix4(m[0]-rhs[0],   m[1]-rhs[1],   m[2]-rhs[2],   m[3]-rhs[3],\n                   m[4]-rhs[4],   m[5]-rhs[5],   m[6]-rhs[6],   m[7]-rhs[7],\n                   m[8]-rhs[8],   m[9]-rhs[9],   m[10]-rhs[10], m[11]-rhs[11],\n                   m[12]-rhs[12], m[13]-rhs[13], m[14]-rhs[14], m[15]-rhs[15]);\n}\n\n\n\ninline Matrix4& Matrix4::operator+=(const Matrix4& rhs)\n{\n    m[0] += rhs[0];   m[1] += rhs[1];   m[2] += rhs[2];   m[3] += rhs[3];\n    m[4] += rhs[4];   m[5] += rhs[5];   m[6] += rhs[6];   m[7] += rhs[7];\n    m[8] += rhs[8];   m[9] += rhs[9];   m[10]+= rhs[10];  m[11]+= rhs[11];\n    m[12]+= rhs[12];  m[13]+= rhs[13];  m[14]+= rhs[14];  m[15]+= rhs[15];\n    return *this;\n}\n\n\n\ninline Matrix4& Matrix4::operator-=(const Matrix4& rhs)\n{\n    m[0] -= rhs[0];   m[1] -= rhs[1];   m[2] -= rhs[2];   m[3] -= rhs[3];\n    m[4] -= rhs[4];   m[5] -= rhs[5];   m[6] -= rhs[6];   m[7] -= rhs[7];\n    m[8] -= rhs[8];   m[9] -= rhs[9];   m[10]-= rhs[10];  m[11]-= rhs[11];\n    m[12]-= rhs[12];  m[13]-= rhs[13];  m[14]-= rhs[14];  m[15]-= rhs[15];\n    return *this;\n}\n\n\n\ninline Vector4 Matrix4::operator*(const Vector4& rhs) const\n{\n    return Vector4(m[0]*rhs.x + m[4]*rhs.y + m[8]*rhs.z  + m[12]*rhs.w,\n                   m[1]*rhs.x + m[5]*rhs.y + m[9]*rhs.z  + m[13]*rhs.w,\n                   m[2]*rhs.x + m[6]*rhs.y + m[10]*rhs.z + m[14]*rhs.w,\n                   m[3]*rhs.x + m[7]*rhs.y + m[11]*rhs.z + m[15]*rhs.w);\n}\n\n\n\ninline Vector3 Matrix4::operator*(const Vector3& rhs) const\n{\n    return Vector3(m[0]*rhs.x + m[4]*rhs.y + m[8]*rhs.z,\n                   m[1]*rhs.x + m[5]*rhs.y + m[9]*rhs.z,\n                   m[2]*rhs.x + m[6]*rhs.y + m[10]*rhs.z);\n}\n\n\n\ninline Matrix4 Matrix4::operator*(const Matrix4& n) const\n{\n    return Matrix4(m[0]*n[0]  + m[4]*n[1]  + m[8]*n[2]  + m[12]*n[3],   m[1]*n[0]  + m[5]*n[1]  + m[9]*n[2]  + m[13]*n[3],   m[2]*n[0]  + m[6]*n[1]  + m[10]*n[2]  + m[14]*n[3],   m[3]*n[0]  + m[7]*n[1]  + m[11]*n[2]  + m[15]*n[3],\n                   m[0]*n[4]  + m[4]*n[5]  + m[8]*n[6]  + m[12]*n[7],   m[1]*n[4]  + m[5]*n[5]  + m[9]*n[6]  + m[13]*n[7],   m[2]*n[4]  + m[6]*n[5]  + m[10]*n[6]  + m[14]*n[7],   m[3]*n[4]  + m[7]*n[5]  + m[11]*n[6]  + m[15]*n[7],\n                   m[0]*n[8]  + m[4]*n[9]  + m[8]*n[10] + m[12]*n[11],  m[1]*n[8]  + m[5]*n[9]  + m[9]*n[10] + m[13]*n[11],  m[2]*n[8]  + m[6]*n[9]  + m[10]*n[10] + m[14]*n[11],  m[3]*n[8]  + m[7]*n[9]  + m[11]*n[10] + m[15]*n[11],\n                   m[0]*n[12] + m[4]*n[13] + m[8]*n[14] + m[12]*n[15],  m[1]*n[12] + m[5]*n[13] + m[9]*n[14] + m[13]*n[15],  m[2]*n[12] + m[6]*n[13] + m[10]*n[14] + m[14]*n[15],  m[3]*n[12] + m[7]*n[13] + m[11]*n[14] + m[15]*n[15]);\n}\n\n\n\ninline Matrix4& Matrix4::operator*=(const Matrix4& rhs)\n{\n    *this = *this * rhs;\n    return *this;\n}\n\n\n\ninline bool Matrix4::operator==(const Matrix4& n) const\n{\n    return (m[0] == n[0])  && (m[1] == n[1])  && (m[2] == n[2])  && (m[3] == n[3])  &&\n           (m[4] == n[4])  && (m[5] == n[5])  && (m[6] == n[6])  && (m[7] == n[7])  &&\n           (m[8] == n[8])  && (m[9] == n[9])  && (m[10]== n[10]) && (m[11]== n[11]) &&\n           (m[12]== n[12]) && (m[13]== n[13]) && (m[14]== n[14]) && (m[15]== n[15]);\n}\n\n\n\ninline bool Matrix4::operator!=(const Matrix4& n) const\n{\n    return (m[0] != n[0])  || (m[1] != n[1])  || (m[2] != n[2])  || (m[3] != n[3])  ||\n           (m[4] != n[4])  || (m[5] != n[5])  || (m[6] != n[6])  || (m[7] != n[7])  ||\n           (m[8] != n[8])  || (m[9] != n[9])  || (m[10]!= n[10]) || (m[11]!= n[11]) ||\n           (m[12]!= n[12]) || (m[13]!= n[13]) || (m[14]!= n[14]) || (m[15]!= n[15]);\n}\n\n\n\ninline float Matrix4::operator[](int index) const\n{\n    return m[index];\n}\n\n\n\ninline float& Matrix4::operator[](int index)\n{\n    return m[index];\n}\n\n\n\ninline Matrix4 operator-(const Matrix4& rhs)\n{\n    return Matrix4(-rhs[0], -rhs[1], -rhs[2], -rhs[3], -rhs[4], -rhs[5], -rhs[6], -rhs[7], -rhs[8], -rhs[9], -rhs[10], -rhs[11], -rhs[12], -rhs[13], -rhs[14], -rhs[15]);\n}\n\n\n\ninline Matrix4 operator*(float s, const Matrix4& rhs)\n{\n    return Matrix4(s*rhs[0], s*rhs[1], s*rhs[2], s*rhs[3], s*rhs[4], s*rhs[5], s*rhs[6], s*rhs[7], s*rhs[8], s*rhs[9], s*rhs[10], s*rhs[11], s*rhs[12], s*rhs[13], s*rhs[14], s*rhs[15]);\n}\n\n\n\ninline Vector4 operator*(const Vector4& v, const Matrix4& m)\n{\n    return Vector4(v.x*m[0] + v.y*m[1] + v.z*m[2] + v.w*m[3],  v.x*m[4] + v.y*m[5] + v.z*m[6] + v.w*m[7],  v.x*m[8] + v.y*m[9] + v.z*m[10] + v.w*m[11], v.x*m[12] + v.y*m[13] + v.z*m[14] + v.w*m[15]);\n}\n\n\n\ninline Vector3 operator*(const Vector3& v, const Matrix4& m)\n{\n    return Vector3(v.x*m[0] + v.y*m[1] + v.z*m[2],  v.x*m[4] + v.y*m[5] + v.z*m[6],  v.x*m[8] + v.y*m[9] + v.z*m[10]);\n}\n\n\n\ninline std::ostream& operator<<(std::ostream& os, const Matrix4& m)\n{\n    os << std::fixed << std::setprecision(5);\n    os << \"[\" << std::setw(10) << m[0] << \" \" << std::setw(10) << m[4] << \" \" << std::setw(10) << m[8]  <<  \" \" << std::setw(10) << m[12] << \"]\\n\"\n       << \"[\" << std::setw(10) << m[1] << \" \" << std::setw(10) << m[5] << \" \" << std::setw(10) << m[9]  <<  \" \" << std::setw(10) << m[13] << \"]\\n\"\n       << \"[\" << std::setw(10) << m[2] << \" \" << std::setw(10) << m[6] << \" \" << std::setw(10) << m[10] <<  \" \" << std::setw(10) << m[14] << \"]\\n\"\n       << \"[\" << std::setw(10) << m[3] << \" \" << std::setw(10) << m[7] << \" \" << std::setw(10) << m[11] <<  \" \" << std::setw(10) << m[15] << \"]\\n\";\n    os << std::resetiosflags(std::ios_base::fixed | std::ios_base::floatfield);\n    return os;\n}\n// END OF MATRIX4 INLINE //////////////////////////////////////////////////////\n#endif\n"
  },
  {
    "path": "src/Shared/OUtoSBSConverter.cpp",
    "content": "#include \"OUtoSBSConverter.h\"\n\n#include \"Util.h\"\n\nID3D11Texture2D* OUtoSBSConverter::GetTexture() const\n{\n    return (m_MultiGPUTexSBSTarget != nullptr) ? m_MultiGPUTexSBSTarget.Get() : m_TexSBS.Get();\n}\n\nVector2Int OUtoSBSConverter::GetTextureSizeSBS() const\n{\n    return m_TextSizeSBS;\n}\n\nHRESULT OUtoSBSConverter::Convert(ID3D11Device* device, ID3D11DeviceContext* device_context, ID3D11Device* multi_gpu_device, ID3D11DeviceContext* multi_gpu_device_context, \n                                  ID3D11Texture2D* tex_source, int tex_source_width, int tex_source_height, int crop_x, int crop_y, int crop_width, int crop_height)\n{\n    Vector2Int sbs_size(crop_width * 2, crop_height / 2);\n\n    //Resource setup on first time or when dimensions changed\n    if ( (m_TexSBS == nullptr) || (sbs_size != m_TextSizeSBS) )\n    {\n        m_TextSizeSBS = sbs_size;\n\n        //Delete old resources if they exist\n        CleanRefs();\n\n        //Get source desc to match format (we assume it doesn't change unexpectedly to avoid doing this every call)\n        D3D11_TEXTURE2D_DESC tex_source_desc;\n        tex_source->GetDesc(&tex_source_desc);\n\n        //Create texture\n        D3D11_TEXTURE2D_DESC TexD;\n        RtlZeroMemory(&TexD, sizeof(D3D11_TEXTURE2D_DESC));\n        TexD.Width  = m_TextSizeSBS.x;\n        TexD.Height = m_TextSizeSBS.y;\n        TexD.MipLevels = 1;\n        TexD.ArraySize = 1;\n        TexD.Format = tex_source_desc.Format;\n        TexD.SampleDesc.Count = 1;\n        TexD.Usage = D3D11_USAGE_DEFAULT;\n        TexD.BindFlags = D3D11_BIND_SHADER_RESOURCE;\n        TexD.CPUAccessFlags = 0;\n        TexD.MiscFlags = 0;\n\n        HRESULT hr = device->CreateTexture2D(&TexD, nullptr, &m_TexSBS);\n\n        if (FAILED(hr))\n            return hr;\n\n        //Create textures for multi-gpu processing if needed\n        if (multi_gpu_device != nullptr) \n        {\n            //Staging texture\n            TexD.Usage = D3D11_USAGE_STAGING;\n            TexD.BindFlags = 0;\n            TexD.CPUAccessFlags = D3D11_CPU_ACCESS_READ;\n            TexD.MiscFlags = 0;\n\n            hr = device->CreateTexture2D(&TexD, nullptr, &m_MultiGPUTexSBSStaging);\n\n            if (FAILED(hr))\n                return hr;\n\n            //Copy-target texture\n            TexD.Usage = D3D11_USAGE_DYNAMIC;\n            TexD.BindFlags = D3D11_BIND_SHADER_RESOURCE;\n            TexD.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE;\n            TexD.MiscFlags = 0;\n\n            hr = multi_gpu_device->CreateTexture2D(&TexD, nullptr, &m_MultiGPUTexSBSTarget);\n\n            if (FAILED(hr))\n                return hr;\n        }\n    }\n\n    //Copy top and bottom half of the cropped region into the left and right halves of SBS texture\n    D3D11_BOX source_region = {0};\n    source_region.left   = clamp(crop_x, 0, tex_source_width);\n    source_region.right  = clamp(crop_x + crop_width, 0, tex_source_width);\n    source_region.top    = clamp(crop_y, 0, tex_source_height);\n    source_region.bottom = clamp(crop_y + m_TextSizeSBS.y, 0, tex_source_height);\n    source_region.front  = 0;\n    source_region.back   = 1;\n\n    device_context->CopySubresourceRegion(m_TexSBS.Get(), 0, 0, 0, 0, tex_source, 0, &source_region);          //Top -> Left\n\n    source_region.top    = source_region.bottom;\n    source_region.bottom = clamp((int)source_region.top + m_TextSizeSBS.y, 0, tex_source_height);\n\n    device_context->CopySubresourceRegion(m_TexSBS.Get(), 0, crop_width, 0, 0, tex_source, 0, &source_region); //Bottom -> Right\n\n    //If set up for multi-gpu processing, copy the texture over\n    if (m_MultiGPUTexSBSTarget != nullptr)\n    {\n        //Same as in OutputManager::RefreshOpenVROverlayTexture\n        device_context->CopyResource(m_MultiGPUTexSBSStaging.Get(), m_TexSBS.Get());\n\n        D3D11_MAPPED_SUBRESOURCE mapped_resource_staging;\n        RtlZeroMemory(&mapped_resource_staging, sizeof(D3D11_MAPPED_SUBRESOURCE));\n        HRESULT hr = device_context->Map(m_MultiGPUTexSBSStaging.Get(), 0, D3D11_MAP_READ, 0, &mapped_resource_staging);\n\n        if (FAILED(hr))\n            return hr;\n\n        D3D11_MAPPED_SUBRESOURCE mapped_resource_target;\n        RtlZeroMemory(&mapped_resource_target, sizeof(D3D11_MAPPED_SUBRESOURCE));\n        hr = multi_gpu_device_context->Map(m_MultiGPUTexSBSTarget.Get(), 0, D3D11_MAP_WRITE_DISCARD, 0, &mapped_resource_target);\n\n        if (FAILED(hr))\n            return hr;\n\n        memcpy(mapped_resource_target.pData, mapped_resource_staging.pData, (size_t)m_TextSizeSBS.y * mapped_resource_staging.RowPitch);\n\n        device_context->Unmap(m_MultiGPUTexSBSStaging.Get(), 0);\n        multi_gpu_device_context->Unmap(m_MultiGPUTexSBSTarget.Get(), 0);\n    }\n\n    return S_OK;\n}\n\nvoid OUtoSBSConverter::CleanRefs()\n{\n    m_TexSBS.Reset();\n    m_MultiGPUTexSBSStaging.Reset();\n    m_MultiGPUTexSBSTarget.Reset();\n}\n"
  },
  {
    "path": "src/Shared/OUtoSBSConverter.h",
    "content": "#pragma once\n\n#ifndef NOMINMAX\n    #define NOMINMAX\n#endif\n#include <d3d11.h>\n#include <wrl/client.h>\n\n#include \"Vectors.h\"\n\n//This class rearranges an OU 3D texture to a SBS 3D texture\nclass OUtoSBSConverter\n{\n    private:\n        Microsoft::WRL::ComPtr<ID3D11Texture2D> m_TexSBS;                 //Owned by device\n        Microsoft::WRL::ComPtr<ID3D11Texture2D> m_MultiGPUTexSBSStaging;  //Staging texture, owned by device\n        Microsoft::WRL::ComPtr<ID3D11Texture2D> m_MultiGPUTexSBSTarget;   //Target texture to copy to, owned by multi_gpu_device\n        Vector2Int m_TextSizeSBS;\n\n    public:\n        ID3D11Texture2D* GetTexture() const; //Does not add a reference\n        Vector2Int GetTextureSizeSBS() const;\n        HRESULT Convert(ID3D11Device* device, ID3D11DeviceContext* device_context, ID3D11Device* multi_gpu_device, ID3D11DeviceContext* multi_gpu_device_context, \n                        ID3D11Texture2D* tex_source, int tex_source_width, int tex_source_height, int crop_x, int crop_y, int crop_width, int crop_height);\n        void CleanRefs();\n\n};"
  },
  {
    "path": "src/Shared/OpenVRExt.cpp",
    "content": "#include \"OpenVRExt.h\"\n\n#include <wrl/client.h>\n\n#include \"openvr.h\"\n#include \"Matrices.h\"\n\nnamespace vr\n{\n    ID3D11ShaderResourceView* IVROverlayEx::GetOverlayTextureExInternal(VROverlayHandle_t overlay_handle, ID3D11Resource* device_texture_ref)\n    {\n        //m_SharedOverlayTexuresMutex is assumed to be locked already\n        if (device_texture_ref == nullptr)\n            return nullptr;\n\n        auto it = m_SharedOverlayTextures.find(overlay_handle);\n\n        if (it == m_SharedOverlayTextures.end())\n            return nullptr;\n\n        SharedOverlayTexture& shared_tex = it->second;\n\n        //Get overlay texture from OpenVR if we don't have it cached yet\n        if (shared_tex.ShaderResourceView == nullptr)\n        {\n            uint32_t ovrl_width;\n            uint32_t ovrl_height;\n            uint32_t ovrl_native_format;\n            ETextureType ovrl_api_type;\n            EColorSpace ovrl_color_space;\n            VRTextureBounds_t ovrl_tex_bounds;\n\n            VROverlayError ovrl_error = vr::VROverlayError_None;\n            ovrl_error = VROverlay()->GetOverlayTexture(overlay_handle, (void**)&shared_tex.ShaderResourceView, device_texture_ref, &ovrl_width, &ovrl_height, &ovrl_native_format,\n                                                        &ovrl_api_type, &ovrl_color_space, &ovrl_tex_bounds);\n\n            //Shader Resource View set despite returning an error might not ever happen, but call release if it does\n            if ((ovrl_error != VROverlayError_None) && (shared_tex.ShaderResourceView != nullptr))\n            {\n                VROverlay()->ReleaseNativeOverlayHandle(overlay_handle, it->second.ShaderResourceView);\n                shared_tex.ShaderResourceView = nullptr;\n            }\n        }\n\n        return shared_tex.ShaderResourceView;\n    }\n\n    bool IVROverlayEx::GetOverlayIntersectionParamsForDevice(VROverlayIntersectionParams_t& params, TrackedDeviceIndex_t device_index, ETrackingUniverseOrigin tracking_origin, bool use_tip_offset)\n    {\n        if (device_index >= k_unMaxTrackedDeviceCount)\n            return false;\n\n        TrackedDevicePose_t poses[k_unMaxTrackedDeviceCount];\n        VRSystem()->GetDeviceToAbsoluteTrackingPose(tracking_origin, IVRSystemEx::GetTimeNowToPhotons(), poses, k_unMaxTrackedDeviceCount);\n\n        if (!poses[device_index].bPoseIsValid)\n            return false;\n\n        Matrix4 mat_device = poses[device_index].mDeviceToAbsoluteTracking;\n\n        if (use_tip_offset)\n        {\n            mat_device = mat_device * IVRSystemEx::GetControllerTipMatrix( VRSystem()->GetControllerRoleForTrackedDeviceIndex(device_index) );\n        } \n\n        //Set up intersection test\n        Vector3 v_pos = mat_device.getTranslation();\n        Vector3 forward = {mat_device[8], mat_device[9], mat_device[10]};\n        forward *= -1.0f;\n\n        params.eOrigin    = tracking_origin;\n        params.vSource    = {v_pos.x, v_pos.y, v_pos.z};\n        params.vDirection = {forward.x, forward.y, forward.z};\n\n        return true;\n    }\n\n    bool IVROverlayEx::ComputeOverlayIntersectionForDevice(VROverlayHandle_t overlay_handle, TrackedDeviceIndex_t device_index, ETrackingUniverseOrigin tracking_origin, \n                                                           VROverlayIntersectionResults_t* results, bool use_tip_offset, bool front_face_only)\n    {\n        VROverlayIntersectionParams_t params = {0};\n\n        if (GetOverlayIntersectionParamsForDevice(params, device_index, tracking_origin, use_tip_offset))\n        {\n            if (VROverlay()->ComputeOverlayIntersection(overlay_handle, &params, results))\n            {\n                return ( (!front_face_only) || (IsOverlayIntersectionHitFrontFacing(params, *results)) );\n            }\n        }\n\n        return false;\n    }\n\n    bool IVROverlayEx::IsOverlayIntersectionHitFrontFacing(const VROverlayIntersectionParams_t& params, const VROverlayIntersectionResults_t& results)\n    {\n        Vector3 intersect_src_pos       = params.vSource;\n        Vector3 intersect_target_pos    = results.vPoint;\n        Vector3 intersect_target_normal = results.vNormal;\n        intersect_target_normal.normalize();\n\n        return (intersect_target_normal.dot(intersect_src_pos - intersect_target_pos) >= 0.0f);\n    }\n\n    TrackedDeviceIndex_t IVROverlayEx::FindPointerDeviceForOverlay(VROverlayHandle_t overlay_handle)\n    {\n        TrackedDeviceIndex_t device_index = k_unTrackedDeviceIndexInvalid;\n\n        //Check left and right hand controller\n        for (int controller_role = TrackedControllerRole_LeftHand; controller_role <= TrackedControllerRole_RightHand; ++controller_role)\n        {\n            TrackedDeviceIndex_t device_index_intersection = VRSystem()->GetTrackedDeviceIndexForControllerRole((ETrackedControllerRole)controller_role);\n            VROverlayIntersectionResults_t results;\n\n            if (ComputeOverlayIntersectionForDevice(overlay_handle, device_index_intersection, TrackingUniverseStanding, &results))\n            {\n                device_index = device_index_intersection;\n            }\n        }\n\n        return device_index;\n    }\n\n    TrackedDeviceIndex_t IVROverlayEx::FindPointerDeviceForOverlay(VROverlayHandle_t overlay_handle, Vector2 pos_uv)\n    {\n        TrackedDeviceIndex_t device_index = k_unTrackedDeviceIndexInvalid;\n        float nearest_uv_distance = FLT_MAX;\n\n        //Check left and right hand controller\n        for (int controller_role = TrackedControllerRole_LeftHand; controller_role <= TrackedControllerRole_RightHand; ++controller_role)\n        {\n            TrackedDeviceIndex_t device_index_intersection = VRSystem()->GetTrackedDeviceIndexForControllerRole((ETrackedControllerRole)controller_role);\n            VROverlayIntersectionResults_t results;\n\n            if (ComputeOverlayIntersectionForDevice(overlay_handle, device_index_intersection, TrackingUniverseStanding, &results))\n            {\n                const Vector2 uv_intesection(results.vUVs.v[0], results.vUVs.v[1]);\n                const float distance = pos_uv.distance(uv_intesection);\n\n                if (distance < nearest_uv_distance)\n                {\n                    device_index = device_index_intersection;\n                    nearest_uv_distance = distance;\n                }\n            }\n        }\n\n        return device_index;\n    }\n\n    bool IVROverlayEx::IsSystemLaserPointerActive()\n    {\n        //IsSteamVRDrawingControllers() appears to only return true while the laser pointer is active even if SteamVR is drawing controllers from no scene app running or similar\n        return (VROverlay()->IsDashboardVisible() || VRSystem()->IsSteamVRDrawingControllers());\n    }\n\n    EVROverlayError IVROverlayEx::SetOverlayTextureEx(VROverlayHandle_t overlay_handle, const Texture_t* texture_ptr, Vector2Int texture_size, bool* out_shared_texture_invalidated_ptr)\n    {\n        if (texture_ptr == nullptr)\n            return VROverlayError_InvalidTexture;\n\n        bool shared_texture_invalidated = true;\n\n        {\n            const std::lock_guard<std::mutex> textures_lock(m_SharedOverlayTexuresMutex);\n\n            //Check if we already keep track of this overlay and update data & release shared handles if needed\n            auto it = m_SharedOverlayTextures.find(overlay_handle);\n            if (it != m_SharedOverlayTextures.end())\n            {\n                //Release shared texture handle if SetOverlayTexture will invalidate it\n                SharedOverlayTexture& shared_tex = it->second;\n                shared_texture_invalidated = (texture_size != shared_tex.TextureSize);\n\n                if ((shared_texture_invalidated) && (shared_tex.ShaderResourceView != nullptr))\n                {\n                    VROverlay()->ReleaseNativeOverlayHandle(overlay_handle, shared_tex.ShaderResourceView);\n                    shared_tex.ShaderResourceView = nullptr;\n                }\n\n                shared_tex.TextureSize = texture_size;\n                shared_tex.TextureColorSpace = texture_ptr->eColorSpace;\n            }\n            else    //Add new shared overlay texture data\n            {\n                SharedOverlayTexture shared_tex;\n                shared_tex.TextureSize = texture_size;\n                shared_tex.TextureColorSpace = texture_ptr->eColorSpace;\n\n                m_SharedOverlayTextures.emplace(overlay_handle, shared_tex);\n            }\n        }\n\n        if (out_shared_texture_invalidated_ptr != nullptr)\n        {\n            *out_shared_texture_invalidated_ptr = shared_texture_invalidated;\n        }\n\n        return VROverlay()->SetOverlayTexture(overlay_handle, texture_ptr);\n    }\n\n    EVROverlayError IVROverlayEx::SetOverlayFromFileEx(VROverlayHandle_t overlay_handle, const char* file_path)\n    {\n        {\n            const std::lock_guard<std::mutex> textures_lock(m_SharedOverlayTexuresMutex);\n\n            //Check if we already keep track of this overlay and update data & release shared handles if needed\n            auto it = m_SharedOverlayTextures.find(overlay_handle);\n            if (it != m_SharedOverlayTextures.end())\n            {\n                //Always release shared texture handle (we assume that file loads aren't used for frequent updates and usually don't know the dimensions head of time)\n                SharedOverlayTexture& shared_tex = it->second;\n\n                if (shared_tex.ShaderResourceView != nullptr)\n                {\n                    VROverlay()->ReleaseNativeOverlayHandle(overlay_handle, shared_tex.ShaderResourceView);\n                    shared_tex.ShaderResourceView = nullptr;\n                }\n\n                shared_tex.TextureSize = {-1, -1};\n                shared_tex.TextureColorSpace = ColorSpace_Auto;\n            }\n            else    //Add new shared overlay texture data\n            {\n                SharedOverlayTexture shared_tex;\n                shared_tex.TextureSize = {-1, -1};\n                shared_tex.TextureColorSpace = ColorSpace_Auto;\n\n                m_SharedOverlayTextures.emplace(overlay_handle, shared_tex);\n            }\n        }\n\n        return VROverlay()->SetOverlayFromFile(overlay_handle, file_path);\n    }\n\n    EVROverlayError IVROverlayEx::SetSharedOverlayTexture(VROverlayHandle_t overlay_handle_source, VROverlayHandle_t overlay_handle_target, ID3D11Resource* device_texture_ref)\n    {\n        const std::lock_guard<std::mutex> textures_lock(m_SharedOverlayTexuresMutex);\n\n        ID3D11ShaderResourceView* shader_resource_view = GetOverlayTextureExInternal(overlay_handle_source, device_texture_ref);\n\n        if (shader_resource_view != nullptr)\n        {\n            Microsoft::WRL::ComPtr<ID3D11Resource> ovrl_tex;\n            Microsoft::WRL::ComPtr<IDXGIResource> ovrl_dxgi_resource;\n            shader_resource_view->GetResource(&ovrl_tex);\n\n            HRESULT hr = ovrl_tex.As(&ovrl_dxgi_resource);\n\n            if (!FAILED(hr))\n            {\n                HANDLE ovrl_tex_handle = nullptr;\n                ovrl_dxgi_resource->GetSharedHandle(&ovrl_tex_handle);\n\n                Texture_t vrtex_target = {};\n                vrtex_target.eType = TextureType_DXGISharedHandle;\n                vrtex_target.eColorSpace = m_SharedOverlayTextures[overlay_handle_source].TextureColorSpace;\n                vrtex_target.handle = ovrl_tex_handle;\n\n                return VROverlay()->SetOverlayTexture(overlay_handle_target, &vrtex_target);\n            }\n        }\n\n        return VROverlayError_InvalidTexture;\n    }\n\n    EVROverlayError IVROverlayEx::ReleaseSharedOverlayTexture(VROverlayHandle_t overlay_handle)\n    {\n        const std::lock_guard<std::mutex> textures_lock(m_SharedOverlayTexuresMutex);\n\n        EVROverlayError overlay_error = VROverlayError_None;\n\n        auto it = m_SharedOverlayTextures.find(overlay_handle);\n        if (it != m_SharedOverlayTextures.end())\n        {\n            if (it->second.ShaderResourceView != nullptr)\n            {\n                overlay_error = VROverlay()->ReleaseNativeOverlayHandle(overlay_handle, it->second.ShaderResourceView);\n            }\n\n            m_SharedOverlayTextures.erase(it);\n        }\n\n        return overlay_error;\n    }\n\n    EVROverlayError IVROverlayEx::ClearOverlayTextureEx(VROverlayHandle_t overlay_handle)\n    {\n        {\n            const std::lock_guard<std::mutex> textures_lock(m_SharedOverlayTexuresMutex);\n\n            auto it = m_SharedOverlayTextures.find(overlay_handle);\n            if (it != m_SharedOverlayTextures.end())\n            {\n                if (it->second.ShaderResourceView != nullptr)\n                {\n                    VROverlay()->ReleaseNativeOverlayHandle(overlay_handle, it->second.ShaderResourceView);\n                }\n\n                m_SharedOverlayTextures.erase(it);\n            }\n        }\n\n        return VROverlay()->ClearOverlayTexture(overlay_handle);\n    }\n\n    ID3D11ShaderResourceView* IVROverlayEx::GetOverlayTextureEx(VROverlayHandle_t overlay_handle, ID3D11Resource* device_texture_ref)\n    {\n        if (device_texture_ref == nullptr)\n            return nullptr;\n\n        const std::lock_guard<std::mutex> textures_lock(m_SharedOverlayTexuresMutex);\n\n        return GetOverlayTextureExInternal(overlay_handle, device_texture_ref);\n    }\n\n    Vector2Int IVROverlayEx::GetOverlayTextureSizeEx(VROverlayHandle_t overlay_handle)\n    {\n        {\n            const std::lock_guard<std::mutex> textures_lock(m_SharedOverlayTexuresMutex);\n            auto it = m_SharedOverlayTextures.find(overlay_handle);\n\n            if (it != m_SharedOverlayTextures.end())\n            {\n                return it->second.TextureSize;\n            }\n        }\n\n        return {-1, -1};\n    }\n\n    EVROverlayError IVROverlayEx::DestroyOverlayEx(VROverlayHandle_t overlay_handle)\n    {\n        {\n            const std::lock_guard<std::mutex> textures_lock(m_SharedOverlayTexuresMutex);\n\n            auto it = m_SharedOverlayTextures.find(overlay_handle);\n\n            if (it != m_SharedOverlayTextures.end())\n            {\n                if (it->second.ShaderResourceView != nullptr)\n                {\n                    VROverlay()->ReleaseNativeOverlayHandle(overlay_handle, it->second.ShaderResourceView);\n                }\n\n                m_SharedOverlayTextures.erase(it);\n            }\n        }\n\n        return VROverlay()->DestroyOverlay(overlay_handle);\n    }\n\n\n    void IVRSystemEx::TransformOpenVR34TranslateRelative(HmdMatrix34_t& matrix, float offset_right, float offset_up, float offset_forward)\n    {\n        matrix.m[0][3] += offset_right * matrix.m[0][0];\n        matrix.m[1][3] += offset_right * matrix.m[1][0];\n        matrix.m[2][3] += offset_right * matrix.m[2][0];\n\n        matrix.m[0][3] += offset_up * matrix.m[0][1];\n        matrix.m[1][3] += offset_up * matrix.m[1][1];\n        matrix.m[2][3] += offset_up * matrix.m[2][1];\n\n        matrix.m[0][3] += offset_forward * matrix.m[0][2];\n        matrix.m[1][3] += offset_forward * matrix.m[1][2];\n        matrix.m[2][3] += offset_forward * matrix.m[2][2];\n    }\n\n    void IVRSystemEx::TransformLookAt(Matrix4& matrix, const Vector3 pos_target, const Vector3 up)\n    {\n        const Vector3 pos(matrix.getTranslation());\n\n        Vector3 z_axis = pos_target - pos;\n        z_axis.normalize();\n        Vector3 x_axis = up.cross(z_axis);\n        x_axis.normalize();\n        Vector3 y_axis = z_axis.cross(x_axis);\n\n        matrix = { x_axis.x, x_axis.y, x_axis.z, 0.0f,\n                   y_axis.x, y_axis.y, y_axis.z, 0.0f,\n                   z_axis.x, z_axis.y, z_axis.z, 0.0f,\n                   pos.x,    pos.y,    pos.z,    1.0f };\n    }\n\n    Matrix4 IVRSystemEx::ComputeHMDFacingTransform(float distance)\n    {\n        //This is based on dashboard positioning code posted by Valve on the OpenVR GitHub\n        static const Vector3 up = {0.0f, 1.0f, 0.0f};\n\n        TrackedDevicePose_t poses[k_unMaxTrackedDeviceCount];\n        VRSystem()->GetDeviceToAbsoluteTrackingPose(TrackingUniverseStanding, 0.0f /*don't predict anything here*/, poses, k_unMaxTrackedDeviceCount);\n\n        Matrix4 mat_hmd(poses[k_unTrackedDeviceIndex_Hmd].mDeviceToAbsoluteTracking);\n        mat_hmd.translate_relative(0.0f, 0.0f, 0.10f);\n        Matrix4 mat_hmd_temp = mat_hmd;\n\n        Vector3 dashboard_start = mat_hmd_temp.translate_relative(0.0f, 0.0f, -distance).getTranslation();\n        Vector3 forward_temp    = (dashboard_start - mat_hmd.getTranslation()).normalize();\n        Vector3 right           = forward_temp.cross(up).normalize();\n        Vector3 forward         = up.cross(right).normalize();\n\n        dashboard_start = mat_hmd.getTranslation() + (distance * forward);\n\n        Matrix4 mat_dashboard(right, up, forward * -1.0f);\n        mat_dashboard.setTranslation(dashboard_start);\n\n        return mat_dashboard;\n    }\n\n    float IVRSystemEx::GetTimeNowToPhotons()\n    {\n        float seconds_since_last_vsync;\n        VRSystem()->GetTimeSinceLastVsync(&seconds_since_last_vsync, nullptr);\n\n        const float vsync_to_photons  = VRSystem()->GetFloatTrackedDeviceProperty(k_unTrackedDeviceIndex_Hmd, Prop_SecondsFromVsyncToPhotons_Float);\n        const float display_frequency = VRSystem()->GetFloatTrackedDeviceProperty(k_unTrackedDeviceIndex_Hmd, Prop_DisplayFrequency_Float);\n\n        return (1.0f / display_frequency) - seconds_since_last_vsync + vsync_to_photons;\n    }\n\n    Matrix4 IVRSystemEx::GetControllerTipMatrix(ETrackedControllerRole controller_role)\n    {\n        if ( (controller_role != TrackedControllerRole_LeftHand) && (controller_role != TrackedControllerRole_RightHand) )\n            return Matrix4();\n\n        char buffer[k_unMaxPropertyStringSize];\n        VRInputValueHandle_t input_value = k_ulInvalidInputValueHandle;\n\n        VRSystem()->GetStringTrackedDeviceProperty(VRSystem()->GetTrackedDeviceIndexForControllerRole(controller_role), Prop_RenderModelName_String, buffer, k_unMaxPropertyStringSize);\n        VRInput()->GetInputSourceHandle((controller_role == TrackedControllerRole_RightHand) ? \"/user/hand/right\" : \"/user/hand/left\", &input_value);\n\n        RenderModel_ControllerMode_State_t controller_state = {0};\n        RenderModel_ComponentState_t component_state = {0};\n\n        if (VRRenderModels()->GetComponentStateForDevicePath(buffer, k_pch_Controller_Component_Tip, input_value, &controller_state, &component_state))\n        {\n            return component_state.mTrackingToComponentLocal;\n        }\n\n        return Matrix4();\n    }\n\n    TrackedDeviceIndex_t IVRSystemEx::GetFirstVRTracker()\n    {\n        for (int i = 0; i < k_unMaxTrackedDeviceCount; ++i)\n        {\n            if (VRSystem()->GetTrackedDeviceClass(i) == TrackedDeviceClass_GenericTracker)\n            {\n                return i;\n            }\n        }\n\n        return k_unTrackedDeviceIndexInvalid;\n    }\n\n    static IVRSystemEx  g_IVRSystemEx;\n    static IVROverlayEx g_IVROverlayEx;\n\n    IVRSystemEx* VRSystemEx()\n    {\n        return &g_IVRSystemEx;\n    }\n\n    IVROverlayEx* VROverlayEx()\n    {\n        return &g_IVROverlayEx;\n    }\n}\n"
  },
  {
    "path": "src/Shared/OpenVRExt.h",
    "content": "//Extra functions extending/wrapping OpenVR APIs\n//Interfaces are supposed to be thread-safe\n//Assumes OpenVR is initialized\n\n#pragma once\n\n#include <map>\n#include <mutex>\n\n#ifndef NOMINMAX\n    #define NOMINMAX\n#endif\n#include <d3d11.h>\n\n#include \"Matrices.h\"\n\nnamespace vr\n{\n    class IVRSystemEx\n    {\n        public:\n            //Translate the matrix relative to its own orientation\n            static void TransformOpenVR34TranslateRelative(HmdMatrix34_t& matrix, float offset_right, float offset_up, float offset_forward);\n\n            //Rotate the matrix towards the given target position\n            static void TransformLookAt(Matrix4& matrix, const Vector3 pos_target, const Vector3 up = {0.0f, 1.0f, 0.0f});\n\n            //Returns transform similar to the dashboard transform (not a perfect match, though)\n            static Matrix4 ComputeHMDFacingTransform(float distance);\n\n            //Returns the time to the next photon/physical display update, in seconds\n            static float GetTimeNowToPhotons();\n\n            //Returns tip matrix of the tracked device with the given controller role (handed roles only)\n            static Matrix4 GetControllerTipMatrix(ETrackedControllerRole controller_role = TrackedControllerRole_RightHand);\n\n            //Returns the first generic tracker device\n            static TrackedDeviceIndex_t GetFirstVRTracker();\n    };\n\n    class IVROverlayEx\n    {\n        private:\n            struct SharedOverlayTexture\n            {\n                ID3D11ShaderResourceView* ShaderResourceView = nullptr;\n                Vector2Int TextureSize = {-1, -1};\n                EColorSpace TextureColorSpace = ColorSpace_Auto;\n            };\n\n            std::mutex m_SharedOverlayTexuresMutex;\n            std::map<VROverlayHandle_t, SharedOverlayTexture> m_SharedOverlayTextures;\n\n            ID3D11ShaderResourceView* GetOverlayTextureExInternal(VROverlayHandle_t overlay_handle, ID3D11Resource* device_texture_ref);\n\n        public:\n            //Returns false if the device has no valid pose\n            static bool GetOverlayIntersectionParamsForDevice(VROverlayIntersectionParams_t& params, TrackedDeviceIndex_t device_index, ETrackingUniverseOrigin tracking_origin, \n                                                              bool use_tip_offset = true);\n\n            //Returns true if intersection happened\n            static bool ComputeOverlayIntersectionForDevice(VROverlayHandle_t overlay_handle, TrackedDeviceIndex_t device_index, ETrackingUniverseOrigin tracking_origin, \n                                                            VROverlayIntersectionResults_t* results, bool use_tip_offset = true, bool front_face_only = true);\n\n            //Returns true if intersection hit the front side of the overlay\n            static bool IsOverlayIntersectionHitFrontFacing(const VROverlayIntersectionParams_t& params, const VROverlayIntersectionResults_t& results);\n\n            //Returns which handed controller is currently pointing at the given overlay, if any\n            static TrackedDeviceIndex_t FindPointerDeviceForOverlay(VROverlayHandle_t overlay_handle);\n\n            //Returns the device pointing closest to the given position if there are multiple\n            static TrackedDeviceIndex_t FindPointerDeviceForOverlay(VROverlayHandle_t overlay_handle, Vector2 pos_uv);\n\n            //Returns true if the system laser pointer is likely to be active. There may be edge-cases with this depending on SteamVR behavior\n            static bool IsSystemLaserPointerActive();\n\n            //-OverlayTextureEx functions\n            //These functions wrap around base OpenVR functions to keep track texture sizes and shared texture handles (when used once)\n            //This is under the assumption that nothing except these functions invalidate existing shared texture handles\n            //Size difference is assumed to trigger backing texture change\n            //These functions should only be called on application-owned overlays with textures previously set with SetOverlayTextureEx()\n\n            //Calls IVROverlay::SetOverlayTextureEx() and adds overlay book-keeping data\n            //If out_shared_texture_invalidated_ptr is non-null, it is set to true if the shared texture would be invalidated (even if there never was any, so can be used as refresh flag)\n            EVROverlayError SetOverlayTextureEx(VROverlayHandle_t overlay_handle, const Texture_t* texture_ptr, Vector2Int texture_size, bool* out_shared_texture_invalidated_ptr = nullptr);\n\n            //Calls IVROverlay::SetOverlayFromFile() and adds overlay book-keeping data\n            //This always invalidates the cached shared texture if there is any\n            EVROverlayError SetOverlayFromFileEx(VROverlayHandle_t overlay_handle, const char* file_path);\n\n            //Takes the shared texture of the source overlay and sets it as texture on the target overlay. This only works as long as the backing texture exists\n            EVROverlayError SetSharedOverlayTexture(VROverlayHandle_t overlay_handle_source, VROverlayHandle_t overlay_handle_target, ID3D11Resource* device_texture_ref);\n\n            //Releases the shared texture of the overlay, but doesn't touch the overlay in any other way. Use when letting another process/module take over rendering to it\n            EVROverlayError ReleaseSharedOverlayTexture(VROverlayHandle_t overlay_handle);\n\n            //Calls IVROverlay::ClearOverlayTexture(), frees the shared texture if needed, and removes the overlay book-keeping data\n            EVROverlayError ClearOverlayTextureEx(VROverlayHandle_t overlay_handle);\n\n            //Returns the shared texture after requesting it from OpenVR if it's not already cached (or nullptr if that fails)\n            ID3D11ShaderResourceView* GetOverlayTextureEx(VROverlayHandle_t overlay_handle, ID3D11Resource* device_texture_ref);\n\n            //Returns stored texture size. Doesn't call into OpenVR. Returns {-1, -1} if overlay is unknown\n            Vector2Int GetOverlayTextureSizeEx(VROverlayHandle_t overlay_handle);\n\n            //Calls IVROverlay::DestroyOverlay(), frees the shared texture if needed, and removes the overlay book-keeping data\n            EVROverlayError DestroyOverlayEx(VROverlayHandle_t overlay_handle);\n\n    };\n\n    IVRSystemEx* VRSystemEx();\n    IVROverlayEx* VROverlayEx();\n}\n"
  },
  {
    "path": "src/Shared/OverlayDragger.cpp",
    "content": "#include \"OverlayDragger.h\"\n\n#ifdef DPLUS_UI\n    #include \"UIManager.h\"\n#else\n    #include \"OutputManager.h\"\n#endif\n\n#include \"OverlayManager.h\"\n#include \"InterprocessMessaging.h\"\n#include \"Util.h\"\n#include \"OpenVRExt.h\"\n\nOverlayDragger::OverlayDragger() : \n    m_DragModeDeviceID(-1),\n    m_DragModeOverlayID(k_ulOverlayID_None),\n    m_DragModeOverlayHandle(vr::k_ulOverlayHandleInvalid),\n    m_DragModeOverlayOrigin(ovrl_origin_room),\n    m_DragModeMaxWidth(FLT_MAX),\n    m_DragModeSnappedExtraWidth(0.0f),\n    m_DragGestureActive(false),\n    m_DragGestureScaleDistanceStart(0.0f),\n    m_DragGestureScaleWidthStart(0.0f),\n    m_DragGestureScaleDistanceLast(0.0f),\n    m_AbsoluteModeActive(false),\n    m_AbsoluteModeOffsetForward(0.0f),\n    m_DashboardHMD_Y(-100.0f)\n{\n    m_DashboardMatLast.zero();\n}\n\nvoid OverlayDragger::DragStartBase(bool is_gesture_drag)\n{\n    if ( (IsDragActive()) || (IsDragGestureActive()) )\n        return;\n\n    //This is also used by DragGestureStart() (with is_gesture_drag = true), but only to convert between overlay origins.\n    //Doesn't need calls to the other DragUpdate() or DragFinish() functions in that case\n    vr::TrackedDeviceIndex_t device_index = ConfigManager::Get().GetPrimaryLaserPointerDevice();\n\n    vr::TrackedDevicePose_t poses[vr::k_unMaxTrackedDeviceCount];\n    vr::VRSystem()->GetDeviceToAbsoluteTrackingPose(vr::TrackingUniverseStanding, vr::IVRSystemEx::GetTimeNowToPhotons(), poses, vr::k_unMaxTrackedDeviceCount);\n\n    //We have no dashboard device, but something still started a drag, eh? This happens when the dashboard is closed but the overlays are still interactive\n    //There doesn't seem to be a way to get around this, so we guess by checking which of the two hand controllers are currently pointing at the overlay\n    //Works for most cases at least\n    if (device_index == vr::k_unTrackedDeviceIndexInvalid)\n    {\n        device_index = vr::IVROverlayEx::FindPointerDeviceForOverlay(m_DragModeOverlayHandle);\n\n        //Still nothing, try the config hint\n        if (device_index == vr::k_unTrackedDeviceIndexInvalid)\n        {\n            device_index = (vr::TrackedDeviceIndex_t)ConfigManager::GetValue(configid_int_state_laser_pointer_device_hint);\n        }\n    }\n\n    //Use HMD as device when the tracked device will never have a valid pose (e.g. gamepads)\n    if ((device_index != vr::k_unTrackedDeviceIndexInvalid) && (vr::VRSystem()->GetBoolTrackedDeviceProperty(device_index, vr::Prop_NeverTracked_Bool)) )\n    {\n        device_index = vr::k_unTrackedDeviceIndex_Hmd;\n    }\n\n    if ( (device_index < vr::k_unMaxTrackedDeviceCount) && (poses[device_index].bPoseIsValid) )\n    {\n        if (!is_gesture_drag)\n        {\n            m_DragModeDeviceID = device_index;\n        }\n\n        m_DragModeMatrixSourceStart = poses[device_index].mDeviceToAbsoluteTracking;\n\n        switch (m_DragModeOverlayOrigin)\n        {\n            case ovrl_origin_hmd:\n            {\n                if (poses[vr::k_unTrackedDeviceIndex_Hmd].bPoseIsValid)\n                {\n                    vr::VROverlay()->SetOverlayTransformAbsolute(m_DragModeOverlayHandle, vr::TrackingUniverseStanding, &poses[vr::k_unTrackedDeviceIndex_Hmd].mDeviceToAbsoluteTracking);\n                }\n                break;\n            }\n            case ovrl_origin_right_hand:\n            {\n                vr::TrackedDeviceIndex_t index_right_hand = vr::VRSystem()->GetTrackedDeviceIndexForControllerRole(vr::TrackedControllerRole_RightHand);\n\n                if ( (index_right_hand != vr::k_unTrackedDeviceIndexInvalid) && (poses[index_right_hand].bPoseIsValid) )\n                {\n                    vr::VROverlay()->SetOverlayTransformAbsolute(m_DragModeOverlayHandle, vr::TrackingUniverseStanding, &poses[index_right_hand].mDeviceToAbsoluteTracking);\n                }\n                break;\n            }\n            case ovrl_origin_left_hand:\n            {\n                vr::TrackedDeviceIndex_t index_left_hand = vr::VRSystem()->GetTrackedDeviceIndexForControllerRole(vr::TrackedControllerRole_LeftHand);\n\n                if ( (index_left_hand != vr::k_unTrackedDeviceIndexInvalid) && (poses[index_left_hand].bPoseIsValid) )\n                {\n                    vr::VROverlay()->SetOverlayTransformAbsolute(m_DragModeOverlayHandle, vr::TrackingUniverseStanding, &poses[index_left_hand].mDeviceToAbsoluteTracking);\n                }\n                break;\n            }\n            case ovrl_origin_aux:\n            {\n                vr::TrackedDeviceIndex_t index_tracker = vr::IVRSystemEx::GetFirstVRTracker();\n\n                if ( (index_tracker != vr::k_unTrackedDeviceIndexInvalid) && (poses[index_tracker].bPoseIsValid) )\n                {\n                    vr::VROverlay()->SetOverlayTransformAbsolute(m_DragModeOverlayHandle, vr::TrackingUniverseStanding, &poses[index_tracker].mDeviceToAbsoluteTracking);\n                }\n                break;\n            }\n        }\n\n        vr::HmdMatrix34_t transform_target;\n        vr::TrackingUniverseOrigin origin;\n        vr::VROverlay()->GetOverlayTransformAbsolute(m_DragModeOverlayHandle, &origin, &transform_target);\n        m_DragModeMatrixTargetStart   = transform_target;\n        m_DragModeMatrixTargetCurrent = m_DragModeMatrixTargetStart;\n    }\n    else\n    {\n        //No drag started, reset state\n        m_DragModeOverlayID     = k_ulOverlayID_None;\n        m_DragModeOverlayHandle = vr::k_ulOverlayHandleInvalid;\n    }\n}\n\nvoid OverlayDragger::DragGestureStartBase()\n{\n    if ( (IsDragActive()) || (IsDragGestureActive()) )\n        return;\n\n    DragStartBase(true); //Call the other drag start function to convert the overlay transform to absolute. This doesn't actually start the normal drag\n\n    DragGestureUpdate();\n\n    m_DragGestureScaleDistanceStart = m_DragGestureScaleDistanceLast;\n\n    if (m_DragModeOverlayID != k_ulOverlayID_None)\n    {\n        m_DragGestureScaleWidthStart = OverlayManager::Get().GetConfigData(m_DragModeOverlayID).ConfigFloat[configid_float_overlay_width];\n    }\n    else\n    {\n        vr::VROverlay()->GetOverlayWidthInMeters(m_DragModeOverlayHandle, &m_DragGestureScaleWidthStart);\n    }\n\n    m_DragGestureActive = true;\n}\n\nvoid OverlayDragger::TransformForceUpright(Matrix4& transform) const\n{\n    //Based off of ComputeHMDFacingTransform()... might not be the best way to do it, but it works.\n    static const Vector3 up = {0.0f, 1.0f, 0.0f};\n\n    Matrix4 matrix_temp  = transform;\n    Vector3 ovrl_start   = matrix_temp.translate_relative(0.0f, 0.0f, -0.001f).getTranslation();\n    Vector3 forward_temp = (ovrl_start - transform.getTranslation()).normalize();\n    Vector3 right        = forward_temp.cross(up).normalize();\n    Vector3 forward      = up.cross(right).normalize();\n\n    Matrix4 mat_upright(right, up, forward * -1.0f);\n    mat_upright.setTranslation(ovrl_start);\n\n    transform = mat_upright;\n}\n\nvoid OverlayDragger::TransformForceDistance(Matrix4& transform, Vector3 reference_pos, float distance, bool use_cylinder_shape, bool auto_tilt) const\n{\n    //Match origin y-position to the overlay's to achieve cylindrical position (acts as sphere otherwise)\n    if (use_cylinder_shape)\n        reference_pos.y = transform.getTranslation().y;\n\n    Matrix4 matrix_lookat = transform;\n    float distance_to_reference = matrix_lookat.getTranslation().distance(reference_pos);\n\n    //Use up-vector multiplied by rotation matrix to avoid locking at near-up transforms\n    Vector3 up = matrix_lookat * Vector3(0.0f, 1.0f, 0.0f);\n    vr::IVRSystemEx::TransformLookAt(matrix_lookat, reference_pos, up);\n    matrix_lookat.translate_relative(0.0f, 0.0f, distance_to_reference - distance);\n\n    if (auto_tilt)\n    {\n        //Transfer scale from original transform before replacing it with the lookat one\n        Vector3 row_1(transform[0], transform[1], transform[2]);\n        float scale_x = row_1.length(); //Scaling is always uniform so we just check the x-axis\n\n        Vector3 pos = matrix_lookat.getTranslation();\n        matrix_lookat.setTranslation({0.0f, 0.0f, 0.0f});\n        matrix_lookat.scale(scale_x);\n        matrix_lookat.setTranslation(pos);\n\n        transform = matrix_lookat;\n    }\n    else\n    {\n        transform.setTranslation(matrix_lookat.getTranslation());\n    }\n}\n\nvoid OverlayDragger::TransformSnapRotation(Matrix4& transform, float degrees, bool snap_x, bool snap_y, bool snap_z) const\n{\n    //Don't touch the matrix if all snapping is off\n    if ((!snap_x) && (!snap_y) && (!snap_z))\n        return;\n\n    degrees = clamp(degrees, 0.001f, 360.0f);\n\n    const Vector3 translation = transform.getTranslation();\n\n    //Get scale from matrix, so we can remove and later re-apply it\n    const Vector3 row_1(transform[0], transform[1], transform[2]);\n    const float scale_x = row_1.length(); //Scaling is always uniform so we just check the x-axis\n\n    //Remove scale and translation\n    transform.setTranslation({0.0f, 0.0f, 0.0f});\n    transform.scale(1 / scale_x);\n\n    //Get rotation as euler angles\n    Vector3 rot = transform.getRotation();\n\n    if (snap_x)\n        rot.x = roundf(rot.x / degrees) * degrees;\n    if (snap_y)\n        rot.y = roundf(rot.y / degrees) * degrees;\n    if (snap_z) \n        rot.z = roundf(rot.z / degrees) * degrees;\n\n    transform.setRotation(rot.x, rot.y, rot.z);\n\n    //Restore scale and translation\n    transform.scale(scale_x);\n    transform.setTranslation(translation);\n}\n\nMatrix4 OverlayDragger::GetBaseOffsetMatrix()\n{\n    const OverlayConfigData& data = OverlayManager::Get().GetCurrentConfigData();\n\n    return GetBaseOffsetMatrix((OverlayOrigin)data.ConfigInt[configid_int_overlay_origin], OverlayManager::Get().GetOriginConfigFromData(data));\n}\n\nMatrix4 OverlayDragger::GetBaseOffsetMatrix(OverlayOrigin overlay_origin)\n{\n    return GetBaseOffsetMatrix(overlay_origin, OverlayOriginConfig());\n}\n\nMatrix4 OverlayDragger::GetBaseOffsetMatrix(OverlayOrigin overlay_origin, const OverlayOriginConfig& origin_config)\n{\n    Matrix4 matrix; //Identity\n\n    vr::TrackingUniverseOrigin universe_origin = vr::TrackingUniverseStanding;\n\n    switch (overlay_origin)\n    {\n        case ovrl_origin_room:\n        case ovrl_origin_theater_screen:\n        {\n            break;\n        }\n        case ovrl_origin_hmd_floor:\n        {\n            vr::TrackedDevicePose_t poses[vr::k_unTrackedDeviceIndex_Hmd + 1];\n            vr::VRSystem()->GetDeviceToAbsoluteTrackingPose(universe_origin, vr::IVRSystemEx::GetTimeNowToPhotons(), poses, vr::k_unTrackedDeviceIndex_Hmd + 1);\n\n            if (poses[vr::k_unTrackedDeviceIndex_Hmd].bPoseIsValid)\n            {\n                Matrix4 mat_pose = poses[vr::k_unTrackedDeviceIndex_Hmd].mDeviceToAbsoluteTracking;\n                Vector3 pos_offset = mat_pose.getTranslation();\n\n                //Force HMD pose upright to have it act as turning-only base position\n                if (origin_config.HMDFloorUseTurning)\n                {\n                    matrix = mat_pose;\n                    TransformForceUpright(matrix);\n                }\n\n                pos_offset.y = 0.0f;\n                matrix.setTranslation(pos_offset);\n            }\n            break;\n        }\n        case ovrl_origin_seated_universe:\n        {\n            matrix = vr::VRSystem()->GetSeatedZeroPoseToStandingAbsoluteTrackingPose();\n            break;\n        }\n        case ovrl_origin_dashboard:\n        {\n            //Adjust behavior if GamepadUI (SteamVR 2 dashboard) exists\n            vr::VROverlayHandle_t handle_gamepad_ui = vr::k_ulOverlayHandleInvalid;\n            vr::VROverlay()->FindOverlay(\"valve.steam.gamepadui.bar\", &handle_gamepad_ui);\n\n            //Update dashboard transform if it's visible or we never set the dashboard matrix before (IsDashboardVisible() can return false while visible)\n            if ( (vr::VROverlay()->IsDashboardVisible()) || (m_DashboardMatLast.isZero()) )\n            {\n                //This code is prone to break when Valve makes changes to the dashboard\n\n                //Use GamepadUI as a reference if available since it's more stable due to the systemui overlay changing size depending on the active dashboard overlay's flags\n                vr::VROverlayHandle_t handle_dashboard = handle_gamepad_ui;\n\n                if (handle_dashboard == vr::k_ulOverlayHandleInvalid)\n                {\n                    vr::VROverlay()->FindOverlay(\"system.systemui\", &handle_dashboard);\n                }\n\n                //Double-checking dashboard overlay visibility for the case when IsDashboardVisible() is false while it's actually visible\n                if ( (handle_dashboard != vr::k_ulOverlayHandleInvalid) && (vr::VROverlay()->IsOverlayVisible(handle_dashboard)) )\n                {\n                    vr::HmdMatrix34_t matrix_overlay_dashboard;\n\n                    vr::HmdVector2_t overlay_dashboard_size;\n                    vr::VROverlay()->GetOverlayMouseScale(handle_dashboard, &overlay_dashboard_size); //Coordinate size should be mouse scale\n\n                    vr::VROverlay()->GetTransformForOverlayCoordinates(handle_dashboard, universe_origin, { overlay_dashboard_size.v[0]/2.0f, 0.0f }, &matrix_overlay_dashboard);\n                    m_DashboardMatLast = matrix_overlay_dashboard;\n\n                    if (m_DashboardHMD_Y == -100.0f)    //If Desktop+ was started with the dashboard open, the value will still be default, so set it now\n                    {\n                        UpdateDashboardHMD_Y();\n                    }\n                }\n            }\n\n            matrix = m_DashboardMatLast;\n\n            if (handle_gamepad_ui != vr::k_ulOverlayHandleInvalid)\n            {\n                //Magic number, from taking the difference of both version's dashboard origins at the same HMD position\n                const Matrix4 matrix_to_old_dash( 1.14634132f,      3.725290300e-09f, -3.725290300e-09f, 0.00000000f, \n                                                  0.00000000f,      0.878148496f,      0.736854136f,     0.00000000f, \n                                                  7.45058060e-09f, -0.736854076f,      0.878148496f,     0.00000000f,\n                                                 -5.96046448e-08f,  2.174717430f,      0.123533726f,     1.00000000f);\n\n                //Move origin point roughly back to where it was in the old dashboard\n                matrix = matrix * matrix_to_old_dash;\n\n                //Move matrix towards normal dashboard overlay position\n                matrix.translate_relative(0.0f, -1.143f, 0.768f);\n            }\n            else\n            {\n                //Move matrix towards normal dashboard overlay position\n                matrix.translate_relative(0.0f, 0.90f, 0.0f);\n\n                //Correct the tilt we get when an overlay with control bar is active\n                //The rotation may be off for overlays that don't use one, but without being able to detect this cleanly there doesn't seem much that can be done\n                //For now it's being done when the Desktop+ dashboard tab isn't selected which is fine for most cases\n                //The legacy dashboard is rarely used anyhow\n                vr::VROverlayHandle_t ovrl_handle_dplus;\n                vr::VROverlay()->FindOverlay(\"elvissteinjr.DesktopPlusDashboard\", &ovrl_handle_dplus);\n\n                if ((ovrl_handle_dplus == vr::k_ulOverlayHandleInvalid) || (!vr::VROverlay()->IsActiveDashboardOverlay(ovrl_handle_dplus)))\n                {\n                    //SteamVR 2.15.1 made changes that make the rotation no longer necessary\n                    //While this change is only in the beta branch, we conditionally skip applying the rotation on 2.15 runtimes\n                    const bool use_simple_offset = (strstr(vr::VRSystem()->GetRuntimeVersion(), \"2.15\") != nullptr);\n                    if (use_simple_offset)\n                    {\n                        matrix.translate_relative(0.0f, 0.265f, 0.0f);\n                    }\n                    else\n                    {\n                        Matrix4 mat_rot;\n                        mat_rot.rotateX(40.0f);\n                        matrix = matrix * mat_rot;\n\n                        //Additional offset to make the position pretty much equal to D+ dashboard tab\n                        matrix.translate_relative(0.0f, -0.002f, 0.268f);\n                    }\n                }\n            }\n\n            break;\n        }\n        case ovrl_origin_hmd:\n        case ovrl_origin_right_hand:\n        case ovrl_origin_left_hand:\n        case ovrl_origin_aux:\n        {\n            //This is used for the dragging only. In other cases the origin is identity, as it's attached to the controller via OpenVR\n            vr::TrackedDeviceIndex_t device_index;\n\n            switch (overlay_origin)\n            {\n                case ovrl_origin_hmd:        device_index = vr::k_unTrackedDeviceIndex_Hmd;                                                              break;\n                case ovrl_origin_right_hand: device_index = vr::VRSystem()->GetTrackedDeviceIndexForControllerRole(vr::TrackedControllerRole_RightHand); break;\n                case ovrl_origin_left_hand:  device_index = vr::VRSystem()->GetTrackedDeviceIndexForControllerRole(vr::TrackedControllerRole_LeftHand);  break;\n                case ovrl_origin_aux:        device_index = vr::IVRSystemEx::GetFirstVRTracker();                                                        break;\n                default:                     device_index = vr::k_unTrackedDeviceIndexInvalid;\n            }\n\n            if (device_index != vr::k_unTrackedDeviceIndexInvalid)\n            {\n                vr::TrackedDevicePose_t poses[vr::k_unMaxTrackedDeviceCount];\n                vr::VRSystem()->GetDeviceToAbsoluteTrackingPose(universe_origin, vr::IVRSystemEx::GetTimeNowToPhotons(), poses, vr::k_unMaxTrackedDeviceCount);\n\n                if (poses[device_index].bPoseIsValid)\n                {\n                    matrix = poses[device_index].mDeviceToAbsoluteTracking;\n                }\n            }\n            break;\n        }\n        case ovrl_origin_dplus_tab:\n        {\n            vr::VROverlayHandle_t ovrl_handle_dplus;\n            vr::VROverlay()->FindOverlay(\"elvissteinjr.DesktopPlusDashboard\", &ovrl_handle_dplus);\n\n            if (ovrl_handle_dplus != vr::k_ulOverlayHandleInvalid)\n            {\n                vr::HmdMatrix34_t matrix_dplus_tab;\n                vr::TrackingUniverseOrigin origin = vr::TrackingUniverseStanding;\n\n                vr::VROverlay()->GetTransformForOverlayCoordinates(ovrl_handle_dplus, origin, {0.5f, 0.0f}, &matrix_dplus_tab);\n\n                matrix = matrix_dplus_tab;\n\n                //Additional offset if GamepadUI (SteamVR 2 dashboard) exists\n                vr::VROverlayHandle_t handle_gamepad_ui = vr::k_ulOverlayHandleInvalid;\n                vr::VROverlay()->FindOverlay(\"valve.steam.gamepadui.bar\", &handle_gamepad_ui);\n\n                if (handle_gamepad_ui != vr::k_ulOverlayHandleInvalid)\n                {\n                    matrix.translate(0.0f, -0.05f, 0.0f);\n                }\n            }\n            break;\n        }\n    }\n\n    return matrix;\n}\n\nvoid OverlayDragger::ApplyDashboardScale(Matrix4& matrix)\n{\n    Matrix4 mat_origin = GetBaseOffsetMatrix(ovrl_origin_dplus_tab);\n    Vector3 row_1(mat_origin[0], mat_origin[1], mat_origin[2]);\n    float dashboard_scale = 0.469f;\n\n    //If the dashboard scale cannot be determined yet (D+ tab hasn't been used yet), don't calculate and use the fallback value instead\n    if (row_1 != Vector3(1.0f, 0.0f, 0.0f))\n    {\n        dashboard_scale = row_1.length();\n    }\n\n    Vector3 translation = matrix.getTranslation();\n    matrix.setTranslation({0.0f, 0.0f, 0.0f});\n    matrix.scale(dashboard_scale);\n    matrix.setTranslation(translation);\n}\n\nvoid OverlayDragger::DragStart(unsigned int overlay_id)\n{\n    if ( (IsDragActive()) || (IsDragGestureActive()) )\n        return;\n\n    const OverlayConfigData& data = OverlayManager::Get().GetConfigData(overlay_id);\n\n    m_DragModeDeviceID            = -1;\n    m_DragModeOverlayID           = overlay_id;\n    m_DragModeOverlayOrigin       = (OverlayOrigin)data.ConfigInt[configid_int_overlay_origin];\n    m_DragModeOverlayOriginConfig = OverlayManager::Get().GetOriginConfigFromData(data);\n    m_DragModeOverlayHandle       = data.ConfigHandle[configid_handle_overlay_state_overlay_handle];\n    m_DragModeMaxWidth            = FLT_MAX;\n\n    DragStartBase(false);\n}\n\nvoid OverlayDragger::DragStart(vr::VROverlayHandle_t overlay_handle, OverlayOrigin overlay_origin)\n{\n    if ( (IsDragActive()) || (IsDragGestureActive()) )\n        return;\n\n    m_DragModeDeviceID            = -1;\n    m_DragModeOverlayID           = k_ulOverlayID_None;\n    m_DragModeOverlayHandle       = overlay_handle;\n    m_DragModeOverlayOrigin       = overlay_origin;\n    m_DragModeOverlayOriginConfig = OverlayOriginConfig();\n    m_DragModeMaxWidth            = FLT_MAX;\n\n    DragStartBase(false);\n}\n\nvoid OverlayDragger::DragUpdate()\n{\n    vr::TrackedDevicePose_t poses[vr::k_unMaxTrackedDeviceCount];\n    vr::VRSystem()->GetDeviceToAbsoluteTrackingPose(vr::TrackingUniverseStanding, vr::IVRSystemEx::GetTimeNowToPhotons(), poses, vr::k_unMaxTrackedDeviceCount);\n\n    if (poses[m_DragModeDeviceID].bPoseIsValid)\n    {\n        if (m_AbsoluteModeActive)\n        {\n            //Get matrices\n            Matrix4 mat_device = poses[m_DragModeDeviceID].mDeviceToAbsoluteTracking;\n\n            //Apply tip offset if controller\n            mat_device = mat_device * vr::IVRSystemEx::GetControllerTipMatrix( vr::VRSystem()->GetControllerRoleForTrackedDeviceIndex(m_DragModeDeviceID) );\n\n            //Apply forward offset\n            mat_device.translate_relative(0.0f, 0.0f, -m_AbsoluteModeOffsetForward);\n\n            m_DragModeMatrixTargetCurrent = mat_device;\n\n            //Set transform\n            vr::HmdMatrix34_t vrmat = m_DragModeMatrixTargetCurrent.toOpenVR34();\n            vr::VROverlay()->SetOverlayTransformAbsolute(m_DragModeOverlayHandle, vr::TrackingUniverseStanding, &vrmat);\n        }\n        else\n        {\n            Matrix4 matrix_source_current = poses[m_DragModeDeviceID].mDeviceToAbsoluteTracking;\n            Matrix4 matrix_target_new = m_DragModeMatrixTargetStart;\n\n            Matrix4 matrix_source_start_inverse = m_DragModeMatrixSourceStart;\n            matrix_source_start_inverse.invert();\n\n            matrix_source_current = matrix_source_current * matrix_source_start_inverse;\n\n            m_DragModeMatrixTargetCurrent = matrix_source_current * matrix_target_new;\n\n            //Apply drag settings if managed overlay (while most would work on UI overlays, they're more of a hindrance most of the time)\n            if (m_DragModeOverlayID != k_ulOverlayID_None)\n            {\n                //Snap rotation if enabled\n                if (ConfigManager::GetValue(configid_bool_input_drag_snap_rotation))\n                {\n                    TransformSnapRotation(m_DragModeMatrixTargetCurrent, (float)ConfigManager::GetValue(configid_int_input_drag_snap_rotation_angle), \n                                                                                ConfigManager::GetValue(configid_bool_input_drag_snap_rotation_x), \n                                                                                ConfigManager::GetValue(configid_bool_input_drag_snap_rotation_y), \n                                                                                ConfigManager::GetValue(configid_bool_input_drag_snap_rotation_z));\n                }\n\n                //Snap position if enabled\n                if (ConfigManager::GetValue(configid_bool_input_drag_snap_position))\n                {\n                    const float& snap_size = ConfigManager::GetRef(configid_float_input_drag_snap_position_size);\n\n                    //Transform position to be multiples of snap size\n                    Vector3 pos = m_DragModeMatrixTargetCurrent.getTranslation();\n\n                    //Use center bottom if managed overlay to allow matching alignment of differently sized overlays\n                    Vector3 pos_bottom_orig  = OverlayManager::Get().GetOverlayCenterBottomTransform(m_DragModeOverlayID, m_DragModeOverlayHandle).getTranslation();\n                    Vector3 pos_middle = OverlayManager::Get().GetOverlayMiddleTransform(m_DragModeOverlayID, m_DragModeOverlayHandle).getTranslation();\n                    Vector3 pos_bottom_offset = pos_bottom_orig - pos_middle;\n                    pos += pos_bottom_offset;\n\n                    //Snap to size\n                    pos /= snap_size;\n                    pos.x = roundf(pos.x);\n                    pos.y = roundf(pos.y);\n                    pos.z = roundf(pos.z);\n                    pos *= snap_size;\n\n                    //Get difference of bottom centered transform and apply it to the original middle-aligned position\n                    pos = (pos - pos_bottom_offset);\n\n                    m_DragModeMatrixTargetCurrent.setTranslation(pos);\n                }\n\n                //Force fixed distance if enabled\n                if (ConfigManager::GetValue(configid_bool_input_drag_fixed_distance))\n                {\n                    const float& fixed_distance = ConfigManager::GetValue(configid_float_input_drag_fixed_distance_m);\n\n                    TransformForceDistance(m_DragModeMatrixTargetCurrent, m_TempStandingPosition, fixed_distance, (ConfigManager::GetValue(configid_int_input_drag_fixed_distance_shape) == 1), \n                                           ConfigManager::GetValue(configid_bool_input_drag_fixed_distance_auto_tilt));\n\n                    //Calculate curvature based on overlay width and distance if auto-curving is enabled\n                    if (ConfigManager::GetValue(configid_bool_input_drag_fixed_distance_auto_curve))\n                    {\n                        float width  = OverlayManager::Get().GetConfigData(m_DragModeOverlayID).ConfigFloat[configid_float_overlay_width];\n                        float& curve = OverlayManager::Get().GetConfigData(m_DragModeOverlayID).ConfigFloat[configid_float_overlay_curvature];\n\n                        curve = clamp(width / (fixed_distance * 4.0f), 0.0f, 1.0f);\n\n                        vr::VROverlay()->SetOverlayCurvature(m_DragModeOverlayHandle, curve);\n\n                        //Sync adjusted curvature value\n                        #ifdef DPLUS_UI\n                            IPCManager::Get().PostConfigMessageToDashboardApp(configid_int_state_overlay_current_id_override, (int)m_DragModeOverlayID);\n                            IPCManager::Get().PostConfigMessageToDashboardApp(configid_float_overlay_curvature, curve);\n                            IPCManager::Get().PostConfigMessageToDashboardApp(configid_int_state_overlay_current_id_override, -1);\n                        #else\n                            IPCManager::Get().PostConfigMessageToUIApp(configid_int_state_overlay_current_id_override, (int)m_DragModeOverlayID);\n                            IPCManager::Get().PostConfigMessageToUIApp(configid_float_overlay_curvature, curve);\n                            IPCManager::Get().PostConfigMessageToUIApp(configid_int_state_overlay_current_id_override, -1);\n                        #endif\n                    }\n                }\n            }\n\n            vr::HmdMatrix34_t vrmat = m_DragModeMatrixTargetCurrent.toOpenVR34();\n            vr::VROverlay()->SetOverlayTransformAbsolute(m_DragModeOverlayHandle, vr::TrackingUniverseStanding, &vrmat);\n        }\n    }\n}\n\nvoid OverlayDragger::DragAddDistance(float distance)\n{\n    float overlay_width  =  1.0f;\n    float overlay_height = -1.0f;\n\n    if (m_DragModeOverlayID != k_ulOverlayID_None)\n    {\n        overlay_width = OverlayManager::Get().GetConfigData(m_DragModeOverlayID).ConfigFloat[configid_float_overlay_width];\n\n        #ifndef DPLUS_UI\n            if (OutputManager* outmgr = OutputManager::Get())\n            {\n                overlay_height = outmgr->GetOverlayHeight(m_DragModeOverlayID);\n            }\n        #endif\n    }\n    else\n    {\n        vr::VROverlay()->GetOverlayWidthInMeters(m_DragModeOverlayHandle, &overlay_width);\n\n        #ifdef DPLUS_UI\n            if (UIManager* uimgr = UIManager::Get())\n            {\n                overlay_height = uimgr->GetOverlayHeight(m_DragModeOverlayHandle);\n            }\n        #endif\n    }\n\n    if (overlay_height == -1.0f)\n    {\n        //Fallback method if above don't apply. This usually isn't used, however, as dashboard drags the managed overlays and UI the unmanaged ones.\n        Vector3 pos_middle = OverlayManager::Get().GetOverlayMiddleTransform(      m_DragModeOverlayID, m_DragModeOverlayHandle).getTranslation();\n        Vector3 pos_bottom = OverlayManager::Get().GetOverlayCenterBottomTransform(m_DragModeOverlayID, m_DragModeOverlayHandle).getTranslation();\n        overlay_height = pos_middle.distance(pos_bottom) * 2.0f;\n    }\n\n    //Scale distance to overlay size\n    distance = clamp(distance * std::max(std::min(overlay_width, overlay_height) * 0.5f, 0.1f), -0.35f, 0.35f);\n\n    if (m_AbsoluteModeActive)\n    {\n        m_AbsoluteModeOffsetForward += distance * 0.5f;\n        m_AbsoluteModeOffsetForward = std::max(0.01f, m_AbsoluteModeOffsetForward);\n    }\n    else if ( (m_DragModeOverlayID != k_ulOverlayID_None) && (ConfigManager::GetValue(configid_bool_input_drag_fixed_distance)) ) //Add to fixed distance setting instead if enabled and managed overlay\n    {\n        float& fixed_distance = ConfigManager::GetRef(configid_float_input_drag_fixed_distance_m);\n        fixed_distance += distance * 0.5f;\n        fixed_distance = std::max(0.5f, fixed_distance);\n\n        //Sync adjusted distance value\n        #ifdef DPLUS_UI\n            IPCManager::Get().PostConfigMessageToDashboardApp(configid_float_input_drag_fixed_distance_m, fixed_distance);\n        #else\n            IPCManager::Get().PostConfigMessageToUIApp(configid_float_input_drag_fixed_distance_m, fixed_distance);\n        #endif\n    }\n    else\n    {\n        vr::TrackedDevicePose_t poses[vr::k_unMaxTrackedDeviceCount];\n        vr::VRSystem()->GetDeviceToAbsoluteTrackingPose(vr::TrackingUniverseStanding, vr::IVRSystemEx::GetTimeNowToPhotons(), poses, vr::k_unMaxTrackedDeviceCount);\n\n        if (poses[m_DragModeDeviceID].bPoseIsValid)\n        {\n            Matrix4 mat_drag_device = m_DragModeMatrixSourceStart;\n\n            //Apply tip offset if possible (usually the case)\n            mat_drag_device = mat_drag_device * vr::IVRSystemEx::GetControllerTipMatrix( vr::VRSystem()->GetControllerRoleForTrackedDeviceIndex(m_DragModeDeviceID) );\n\n            //Take the drag device start orientation and the overlay's start translation and offset forward from there\n            mat_drag_device.setTranslation(m_DragModeMatrixTargetStart.getTranslation());\n            mat_drag_device.translate_relative(0.0f, 0.0f, distance * -0.5f);\n            m_DragModeMatrixTargetStart.setTranslation(mat_drag_device.getTranslation());\n        }\n    }\n}\n\nfloat OverlayDragger::DragAddWidth(float width)\n{\n    if (!IsDragActive())\n        return 0.0f;\n\n    const float width_orig = width;\n    width = clamp(width, -0.25f, 0.25f) + 1.0f + m_DragModeSnappedExtraWidth; //Expected range is smaller than for DragAddDistance()\n\n    float overlay_width = 1.0f;\n    float overlay_width_min = 0.05f;\n\n    if (m_DragModeOverlayID != k_ulOverlayID_None)\n    {\n        const OverlayConfigData& data = OverlayManager::Get().GetConfigData(m_DragModeOverlayID);\n\n        overlay_width = data.ConfigFloat[configid_float_overlay_width];\n    }\n    else\n    {\n        vr::VROverlay()->GetOverlayWidthInMeters(m_DragModeOverlayHandle, &overlay_width);\n\n        overlay_width_min = 0.50f; //Usually used with ImGui window UI overlays, so use higher minimum width\n    }\n\n    const float overlay_width_orig = overlay_width;\n    overlay_width *= width;\n\n    if (overlay_width < overlay_width_min)\n    {\n        overlay_width = overlay_width_min;\n    }\n\n    //Snap width if snapping is enabled and managed overlay\n    if ( (m_DragModeOverlayID != k_ulOverlayID_None) && (ConfigManager::GetValue(configid_bool_input_drag_snap_position)) )\n    {\n        const float& snap_size = ConfigManager::GetRef(configid_float_input_drag_snap_position_size);\n\n        //Use round up/down depending on scale direction\n        overlay_width  = (width > 1.0f) ? floorf(overlay_width / snap_size) : ceilf(overlay_width / snap_size);\n        overlay_width *= snap_size;\n\n        //If the snapped width had no effect, add some of it up for next time\n        if (fabs(overlay_width - overlay_width_orig) < snap_size)\n        {\n            //Reset the extra width if the sign doesn't match anymore (direction changed)\n            if (sgn(m_DragModeSnappedExtraWidth) != sgn(width_orig))\n            {\n                m_DragModeSnappedExtraWidth = 0.0f;\n            }\n\n            m_DragModeSnappedExtraWidth += clamp(width_orig, -0.05f, 0.05f);\n\n            //Also prevent being snapped to a value of the opposite direction as it looks like some weird flickering then\n            overlay_width = overlay_width_orig;\n        }\n        else\n        {\n            m_DragModeSnappedExtraWidth = 0.0f;\n        }\n    }\n\n    overlay_width = std::min(overlay_width, m_DragModeMaxWidth);\n    vr::VROverlay()->SetOverlayWidthInMeters(m_DragModeOverlayHandle, overlay_width);\n\n    if (m_DragModeOverlayID != k_ulOverlayID_None)\n    {\n        OverlayConfigData& data = OverlayManager::Get().GetConfigData(m_DragModeOverlayID);\n        data.ConfigFloat[configid_float_overlay_width] = overlay_width;\n\n        #ifndef DPLUS_UI\n            //Send adjusted width to the UI app\n            IPCManager::Get().PostConfigMessageToUIApp(configid_int_state_overlay_current_id_override, (int)m_DragModeOverlayID);\n            IPCManager::Get().PostConfigMessageToUIApp(configid_float_overlay_width, overlay_width);\n            IPCManager::Get().PostConfigMessageToUIApp(configid_int_state_overlay_current_id_override, -1);\n        #endif\n    }\n\n    return overlay_width;\n}\n\nvoid OverlayDragger::DragSetMaxWidth(float max_width)\n{\n    m_DragModeMaxWidth = max_width;\n}\n\nMatrix4 OverlayDragger::DragFinish()\n{\n    DragUpdate();\n\n    //Allow managed overlay origin to change after drag (used for auto-docking)\n    if (m_DragModeOverlayID != k_ulOverlayID_None)\n    {\n        OverlayConfigData& data = OverlayManager::Get().GetConfigData(m_DragModeOverlayID);\n        m_DragModeOverlayOrigin = (OverlayOrigin)data.ConfigInt[configid_int_overlay_origin];\n    }\n\n    vr::HmdMatrix34_t transform_target;\n    vr::TrackingUniverseOrigin origin;\n\n    vr::VROverlay()->GetOverlayTransformAbsolute(m_DragModeOverlayHandle, &origin, &transform_target);\n    Matrix4 matrix_target_finish = transform_target;\n\n    Matrix4 matrix_target_base = GetBaseOffsetMatrix(m_DragModeOverlayOrigin, m_DragModeOverlayOriginConfig);\n    matrix_target_base.invert();\n\n    Matrix4 matrix_target_relative = matrix_target_base * matrix_target_finish;\n\n    //Apply to managed overlay if drag was with ID\n    if (m_DragModeOverlayID != k_ulOverlayID_None)\n    {\n        OverlayConfigData& data = OverlayManager::Get().GetConfigData(m_DragModeOverlayID);\n\n        //Counteract additonal offset that might've been present on the transform\n        matrix_target_relative.translate_relative(-data.ConfigFloat[configid_float_overlay_offset_right],\n                                                  -data.ConfigFloat[configid_float_overlay_offset_up],\n                                                  -data.ConfigFloat[configid_float_overlay_offset_forward]);\n\n        //Counteract origin offset for dashboard origin overlays\n        #ifndef DPLUS_UI\n        if (m_DragModeOverlayOrigin == ovrl_origin_dashboard)\n        {\n            if (OutputManager* outmgr = OutputManager::Get())\n            {\n                float height = outmgr->GetOverlayHeight(m_DragModeOverlayID);\n                matrix_target_relative.translate_relative(0.0f, height / -2.0f, 0.0f);\n            }\n        }\n        #endif\n\n        data.ConfigTransform = matrix_target_relative;\n    }\n\n    //Reset state\n    m_DragModeDeviceID          = -1;\n    m_DragModeOverlayID         = k_ulOverlayID_None;\n    m_DragModeOverlayHandle     = vr::k_ulOverlayHandleInvalid;\n    m_DragModeSnappedExtraWidth = 0.0f;\n    m_AbsoluteModeActive        = false;\n\n    return matrix_target_relative;\n}\n\nvoid OverlayDragger::DragCancel()\n{\n    //Reset state\n    m_DragModeDeviceID          = -1;\n    m_DragModeOverlayID         = k_ulOverlayID_None;\n    m_DragModeOverlayHandle     = vr::k_ulOverlayHandleInvalid;\n    m_DragModeSnappedExtraWidth = 0.0f;\n    m_AbsoluteModeActive        = false;\n}\n\nvoid OverlayDragger::DragGestureStart(unsigned int overlay_id)\n{\n    if ( (IsDragActive()) || (IsDragGestureActive()) )\n        return;\n\n    const OverlayConfigData& data = OverlayManager::Get().GetConfigData(overlay_id);\n\n    m_DragModeDeviceID      = -1;\n    m_DragModeOverlayID     = overlay_id;\n    m_DragModeOverlayOrigin = (OverlayOrigin)data.ConfigInt[configid_int_overlay_origin];\n    m_DragModeOverlayHandle = data.ConfigHandle[configid_handle_overlay_state_overlay_handle];\n\n    DragGestureStartBase();\n}\n\nvoid OverlayDragger::DragGestureStart(vr::VROverlayHandle_t overlay_handle, OverlayOrigin overlay_origin)\n{\n    if ( (IsDragActive()) || (IsDragGestureActive()) )\n        return;\n\n    m_DragModeDeviceID      = -1;\n    m_DragModeOverlayID     = k_ulOverlayID_None;\n    m_DragModeOverlayHandle = overlay_handle;\n    m_DragModeOverlayOrigin = overlay_origin;\n\n    DragGestureStartBase();\n}\n\nvoid OverlayDragger::DragGestureUpdate()\n{\n    vr::TrackedDeviceIndex_t index_right = vr::VRSystem()->GetTrackedDeviceIndexForControllerRole(vr::TrackedControllerRole_RightHand);\n    vr::TrackedDeviceIndex_t index_left  = vr::VRSystem()->GetTrackedDeviceIndexForControllerRole(vr::TrackedControllerRole_LeftHand);\n\n    if ( (index_right != vr::k_unTrackedDeviceIndexInvalid) && (index_left != vr::k_unTrackedDeviceIndexInvalid) )\n    {\n        vr::TrackingUniverseOrigin universe_origin = vr::TrackingUniverseStanding;\n        vr::TrackedDevicePose_t poses[vr::k_unMaxTrackedDeviceCount];\n        vr::VRSystem()->GetDeviceToAbsoluteTrackingPose(universe_origin, vr::IVRSystemEx::GetTimeNowToPhotons(), poses, vr::k_unMaxTrackedDeviceCount);\n\n        if ( (poses[index_right].bPoseIsValid) && (poses[index_left].bPoseIsValid) )\n        {\n            Matrix4 mat_right = poses[index_right].mDeviceToAbsoluteTracking;\n            Matrix4 mat_left  = poses[index_left].mDeviceToAbsoluteTracking;\n\n            //Gesture Scale\n            m_DragGestureScaleDistanceLast = mat_right.getTranslation().distance(mat_left.getTranslation());\n\n            if (m_DragGestureActive)\n            {\n                //Scale is just the start scale multiplied by the factor of changed controller distance\n                float width = m_DragGestureScaleWidthStart * (m_DragGestureScaleDistanceLast / m_DragGestureScaleDistanceStart);\n                width = std::min(width, m_DragModeMaxWidth);\n                vr::VROverlay()->SetOverlayWidthInMeters(m_DragModeOverlayHandle, width);\n\n                if (m_DragModeOverlayID != k_ulOverlayID_None)\n                {\n                    OverlayManager::Get().GetConfigData(m_DragModeOverlayID).ConfigFloat[configid_float_overlay_width] = width;\n\n                    #ifndef DPLUS_UI\n                    //Send adjusted width to the UI app\n                    IPCManager::Get().PostConfigMessageToUIApp(configid_int_state_overlay_current_id_override, (int)m_DragModeOverlayID);\n                    IPCManager::Get().PostConfigMessageToUIApp(configid_float_overlay_width, width);\n                    IPCManager::Get().PostConfigMessageToUIApp(configid_int_state_overlay_current_id_override, -1);\n                    #endif\n                }\n            }\n\n            //Gesture Rotate\n            Matrix4 matrix_rotate_current = mat_left;\n            //Use up-vector multiplied by rotation matrix to avoid locking at near-up transforms\n            Vector3 up = m_DragGestureRotateMatLast * Vector3(0.0f, 1.0f, 0.0f);\n            up.normalize();\n            //Rotation motion is taken from the differences between left controller lookat(right controller) results\n            vr::IVRSystemEx::TransformLookAt(matrix_rotate_current, mat_right.getTranslation(), up);\n\n            if (m_DragGestureActive)\n            {\n                //Get difference of last drag frame\n                Matrix4 matrix_rotate_last_inverse = m_DragGestureRotateMatLast;\n                matrix_rotate_last_inverse.setTranslation({0.0f, 0.0f, 0.0f});\n                matrix_rotate_last_inverse.invert();\n\n                Matrix4 matrix_rotate_current_at_origin = matrix_rotate_current;\n                matrix_rotate_current_at_origin.setTranslation({0.0f, 0.0f, 0.0f});\n\n                Matrix4 matrix_rotate_diff = matrix_rotate_current_at_origin * matrix_rotate_last_inverse;\n\n                //Apply difference\n                Matrix4& mat_overlay = m_DragModeMatrixTargetStart;\n                Vector3 pos = mat_overlay.getTranslation();\n                mat_overlay.setTranslation({0.0f, 0.0f, 0.0f});\n                mat_overlay = matrix_rotate_diff * mat_overlay;\n                mat_overlay.setTranslation(pos);\n\n                vr::HmdMatrix34_t vrmat = mat_overlay.toOpenVR34();\n                vr::VROverlay()->SetOverlayTransformAbsolute(m_DragModeOverlayHandle, vr::TrackingUniverseStanding, &vrmat);\n            }\n\n            m_DragGestureRotateMatLast = matrix_rotate_current;\n        }\n    }\n}\n\nMatrix4 OverlayDragger::DragGestureFinish()\n{\n    Matrix4 matrix_target_base = GetBaseOffsetMatrix(m_DragModeOverlayOrigin, m_DragModeOverlayOriginConfig);\n    matrix_target_base.invert();\n\n    Matrix4 matrix_target_relative = matrix_target_base * m_DragModeMatrixTargetStart;\n\n    //Apply to managed overlay if drag was with ID\n    if (m_DragModeOverlayID != k_ulOverlayID_None)\n    {\n        OverlayConfigData& data = OverlayManager::Get().GetConfigData(m_DragModeOverlayID);\n\n        //Counteract additonal offset that might've been present on the transform\n        matrix_target_relative.translate_relative(-data.ConfigFloat[configid_float_overlay_offset_right],\n                                                  -data.ConfigFloat[configid_float_overlay_offset_up],\n                                                  -data.ConfigFloat[configid_float_overlay_offset_forward]);\n\n        //Counteract origin offset for dashboard origin overlays\n        #ifndef DPLUS_UI\n        if (m_DragModeOverlayOrigin == ovrl_origin_dashboard)\n        {\n            if (OutputManager* outmgr = OutputManager::Get())\n            {\n                float height = outmgr->GetOverlayHeight(m_DragModeOverlayID);\n                matrix_target_relative.translate_relative(0.0f, height / -2.0f, 0.0f);\n            }\n        }\n        #endif\n\n        data.ConfigTransform = matrix_target_relative;\n    }\n\n    //Reset state\n    m_DragGestureActive     = false;\n    m_DragModeOverlayID     = k_ulOverlayID_None;\n    m_DragModeOverlayHandle = vr::k_ulOverlayHandleInvalid;\n\n    return matrix_target_relative;\n}\n\nvoid OverlayDragger::AbsoluteModeSet(bool is_active, float offset_forward)\n{\n    m_AbsoluteModeActive = is_active;\n    m_AbsoluteModeOffsetForward = offset_forward;\n}\n\nvoid OverlayDragger::UpdateDashboardHMD_Y()\n{\n    vr::VROverlayHandle_t ovrl_handle_dplus;\n    vr::VROverlay()->FindOverlay(\"elvissteinjr.DesktopPlusDashboard\", &ovrl_handle_dplus);\n\n    //Use dashboard dummy if available and visible. It provides a way more reliable reference point\n    if ( (ovrl_handle_dplus != vr::k_ulOverlayHandleInvalid) && (vr::VROverlay()->IsOverlayVisible(ovrl_handle_dplus)) )\n    {\n        //Adjust offset if GamepadUI (SteamVR 2 dashboard) exists\n        vr::VROverlayHandle_t handle_gamepad_ui = vr::k_ulOverlayHandleInvalid;\n        vr::VROverlay()->FindOverlay(\"valve.steam.gamepadui.bar\", &handle_gamepad_ui);\n\n        vr::HmdMatrix34_t matrix_dplus_tab;\n        vr::TrackingUniverseOrigin origin = vr::TrackingUniverseStanding;\n        vr::VROverlay()->GetTransformForOverlayCoordinates(ovrl_handle_dplus, origin, {0.5f, 0.0f}, &matrix_dplus_tab);\n\n        //Rough height difference between dashboard dummy reference point and SystemUI reference point (slightly different with GamepadUI active)\n        const float height_diff = (handle_gamepad_ui != vr::k_ulOverlayHandleInvalid) ? 0.505283f : 0.575283f;\n        m_DashboardHMD_Y = matrix_dplus_tab.m[1][3] + height_diff;\n    }\n    else //Otherwise use current headset pose. This works decently when looking straight, but drifts sligthly when not\n    {\n        vr::TrackingUniverseOrigin universe_origin = vr::TrackingUniverseStanding;\n        vr::TrackedDevicePose_t poses[vr::k_unTrackedDeviceIndex_Hmd + 1];\n        vr::VRSystem()->GetDeviceToAbsoluteTrackingPose(universe_origin, 0 /*don't predict anything here*/, poses, vr::k_unTrackedDeviceIndex_Hmd + 1);\n\n        if (poses[vr::k_unTrackedDeviceIndex_Hmd].bPoseIsValid)\n        {\n            Matrix4 mat_pose = poses[vr::k_unTrackedDeviceIndex_Hmd].mDeviceToAbsoluteTracking;\n\n            //Offset pose 0.10 m forward to the actual center of the HMD pose. This is still pretty hacky, but minimizes deviation from not looking straight\n            mat_pose.translate_relative(0.0f, 0.0f, 0.10f);\n\n            m_DashboardHMD_Y = mat_pose.getTranslation().y;\n        }\n    }\n}\n\nvoid OverlayDragger::UpdateTempStandingPosition()\n{\n    vr::TrackedDevicePose_t poses[vr::k_unTrackedDeviceIndex_Hmd + 1];\n    vr::VRSystem()->GetDeviceToAbsoluteTrackingPose(vr::TrackingUniverseStanding, vr::IVRSystemEx::GetTimeNowToPhotons(), poses, vr::k_unTrackedDeviceIndex_Hmd + 1);\n\n    if (poses[vr::k_unTrackedDeviceIndex_Hmd].bPoseIsValid)\n    {\n        Matrix4 mat_pose = poses[vr::k_unTrackedDeviceIndex_Hmd].mDeviceToAbsoluteTracking;\n        Vector3 pos_hmd = mat_pose.getTranslation();\n\n        //Allow for slight tolerance in position changes from head movement for a more fixed reference point\n        if (pos_hmd.distance(m_TempStandingPosition) > 0.05f)\n        {\n            m_TempStandingPosition = pos_hmd;\n        }\n    }\n}\n\nbool OverlayDragger::IsDragActive() const\n{\n    return (m_DragModeDeviceID != -1);\n}\n\nbool OverlayDragger::IsDragGestureActive() const\n{\n    return m_DragGestureActive;\n}\n\nint OverlayDragger::GetDragDeviceID() const\n{\n    return m_DragModeDeviceID;\n}\n\nunsigned int OverlayDragger::GetDragOverlayID() const\n{\n    return m_DragModeOverlayID;\n}\n\nvr::VROverlayHandle_t OverlayDragger::GetDragOverlayHandle() const\n{\n    return m_DragModeOverlayHandle;\n}\n\nconst Matrix4& OverlayDragger::GetDragOverlayMatrix() const\n{\n    return m_DragModeMatrixTargetCurrent;\n}\n"
  },
  {
    "path": "src/Shared/OverlayDragger.h",
    "content": "#pragma once\n\n#include \"Matrices.h\"\n#include \"ConfigManager.h\"\n\n//Class handling dragging overlays with motion controllers, with support for all Desktop+ overlay origins\nclass OverlayDragger\n{\n    private:\n        int m_DragModeDeviceID;                 //-1 if not dragging\n        unsigned int m_DragModeOverlayID;\n        vr::VROverlayHandle_t m_DragModeOverlayHandle;\n        OverlayOrigin m_DragModeOverlayOrigin;\n        OverlayOriginConfig m_DragModeOverlayOriginConfig;\n        float m_DragModeMaxWidth;\n\n        Matrix4 m_DragModeMatrixTargetStart;\n        Matrix4 m_DragModeMatrixSourceStart;\n        Matrix4 m_DragModeMatrixTargetCurrent;\n        float m_DragModeSnappedExtraWidth;\n\n        bool  m_DragGestureActive;\n        float m_DragGestureScaleDistanceStart;\n        float m_DragGestureScaleWidthStart;\n        float m_DragGestureScaleDistanceLast;\n        Matrix4 m_DragGestureRotateMatLast;\n\n        bool m_AbsoluteModeActive;              //Absolute mode forces the overlay to stay centered on the controller tip + offset\n        float m_AbsoluteModeOffsetForward;\n\n        Matrix4 m_DashboardMatLast;\n        float m_DashboardHMD_Y;                 //The HMDs y-position when the dashboard was activated. Used for dashboard-relative positioning\n        Vector3 m_TempStandingPosition;         //Standing position updated when the dashboard tab is activated or drag-mode activated while dashboard tab closed. Used as semi-fixed reference point\n\n        void DragStartBase(bool is_gesture_drag = false);\n        void DragGestureStartBase();\n\n        void TransformForceUpright(Matrix4& transform) const;\n        void TransformForceDistance(Matrix4& transform, Vector3 reference_pos, float distance, bool use_cylinder_shape = false, bool auto_tilt = false) const;\n        void TransformSnapRotation(Matrix4& transform, float degrees, bool snap_x, bool snap_y, bool snap_z) const;\n\n    public:\n        OverlayDragger();\n\n        Matrix4 GetBaseOffsetMatrix();\n        Matrix4 GetBaseOffsetMatrix(OverlayOrigin overlay_origin);  //Not recommended to use with origins that have config values\n        Matrix4 GetBaseOffsetMatrix(OverlayOrigin overlay_origin, const OverlayOriginConfig& origin_config);\n        void ApplyDashboardScale(Matrix4& matrix);\n\n        void DragStart(unsigned int overlay_id);\n        void DragStart(vr::VROverlayHandle_t overlay_handle, OverlayOrigin overlay_origin = ovrl_origin_room); //Not recommended to use with origins that have config values\n        void DragUpdate();\n        void DragAddDistance(float distance);\n        float DragAddWidth(float width);                            //Returns new width\n        void DragSetMaxWidth(float max_width);                      //Maximum width applied whenever width would otherwise change. Resets on drag start, so set after\n        Matrix4 DragFinish();                                       //Returns new overlay origin-relative transform\n        void DragCancel();                                          //Stops drag without applying any changes\n\n        void DragGestureStart(unsigned int overlay_id);\n        void DragGestureStart(vr::VROverlayHandle_t overlay_handle, OverlayOrigin overlay_origin = ovrl_origin_room);\n        void DragGestureUpdate();\n        Matrix4 DragGestureFinish();                                //Returns new overlay origin-relative transform\n\n        void AbsoluteModeSet(bool is_active, float offset_forward); //Automatically reset on DragFinish()\n\n        void UpdateDashboardHMD_Y();\n        void UpdateTempStandingPosition();\n\n        bool IsDragActive() const;                                  //Doesn't include active gesture drag\n        bool IsDragGestureActive() const;\n        int GetDragDeviceID() const;\n        unsigned int GetDragOverlayID() const;\n        vr::VROverlayHandle_t GetDragOverlayHandle() const;\n        const Matrix4& GetDragOverlayMatrix() const;                //Only valid while IsDragActive() returns true\n};"
  },
  {
    "path": "src/Shared/OverlayManager.cpp",
    "content": "#include \"OverlayManager.h\"\n\n#ifndef DPLUS_UI\n    #include \"OutputManager.h\"\n    #include \"DesktopPlusWinRT.h\"\n    #include \"DPBrowserAPIClient.h\"\n#else\n    #include \"UIManager.h\"\n    #include \"TranslationManager.h\"\n#endif\n\n#include \"InterprocessMessaging.h\"\n#include \"WindowManager.h\"\n#include \"Util.h\"\n#include \"OpenVRExt.h\"\n#include \"Logging.h\"\n\n#include <sstream>\n#include <unordered_set>\n\nstatic OverlayManager g_OverlayManager;\n\nOverlayManager& OverlayManager::Get()\n{\n    return g_OverlayManager;\n}\n\n#ifndef DPLUS_UI\n    OverlayManager::OverlayManager() : m_CurrentOverlayID(0), m_OverlayNull(k_ulOverlayID_None), m_TheaterOverlayHandle(vr::k_ulOverlayHandleInvalid),\n                                       m_TheaterOverlayReferenceHandle(vr::k_ulOverlayHandleInvalid), m_CurrentTheaterOverlayOrigHandle(vr::k_ulOverlayHandleInvalid), \n                                       m_CurrentTheaterOverlayID(k_ulOverlayID_None)\n#else\n    OverlayManager::OverlayManager() : m_CurrentOverlayID(0)\n#endif\n{\n\n}\n\n\nMatrix4 OverlayManager::GetOverlayTransformBase(vr::VROverlayHandle_t ovrl_handle, unsigned int id, bool add_bottom_offset) const\n{\n    //The gist is that GetTransformForOverlayCoordinates() is fundamentally broken if the overlay has non-default properties\n    //UV min/max not 0.0/1.0? You still get coordinates as if they were\n    //Pixel aspect not 1.0? That function doesn't care\n    //Also doesn't care about curvature, but that's not a huge issue\n    const OverlayConfigData& data = GetConfigData(id);\n\n    int ovrl_pixel_width = 1, ovrl_pixel_height = 1;\n\n    vr::HmdVector2_t ovrl_mouse_scale;\n    //Use mouse scale of overlay if possible as it can sometimes differ from the config size (and GetOverlayTextureSize() currently leaks GPU memory, oops)\n    if (vr::VROverlay()->GetOverlayMouseScale(ovrl_handle, &ovrl_mouse_scale) == vr::VROverlayError_None)\n    {\n        ovrl_pixel_width  = (int)ovrl_mouse_scale.v[0];\n        ovrl_pixel_height = (int)ovrl_mouse_scale.v[1];\n    }\n    else\n    {\n        ovrl_pixel_width  = data.ConfigInt[configid_int_overlay_state_content_width];\n        ovrl_pixel_height = data.ConfigInt[configid_int_overlay_state_content_height];\n\n        //If we can't get mouse scale we still need to make the 3D adjustments ourselves here\n        if (data.ConfigBool[configid_bool_overlay_3D_enabled])\n        {\n            switch (data.ConfigInt[configid_int_overlay_3D_mode])\n            {\n                case ovrl_3Dmode_hsbs:\n                case ovrl_3Dmode_sbs:\n                {\n                    ovrl_pixel_width /= 2; \n                    break;\n                }\n                case ovrl_3Dmode_hou:\n                case ovrl_3Dmode_ou:\n                {\n                    //OU converted to SBS will have texture size based on crop rect\n                    ovrl_pixel_width  = data.ConfigInt[configid_int_overlay_crop_width];\n                    ovrl_pixel_height = data.ConfigInt[configid_int_overlay_crop_height] / 2;\n                }\n            }\n        }\n    }\n\n    //Y-coordinate from this function is pretty much unpredictable if not pixel_height / 2\n    vr::HmdMatrix34_t matrix = {0};\n    vr::VROverlay()->GetTransformForOverlayCoordinates(ovrl_handle, vr::TrackingUniverseStanding, { (float)ovrl_pixel_width/2.0f, (float)ovrl_pixel_height/2.0f }, &matrix);\n\n    if (add_bottom_offset)\n    {\n        //Get texture bounds\n        vr::VRTextureBounds_t bounds;\n        vr::VROverlay()->GetOverlayTextureBounds(ovrl_handle, &bounds);\n\n        //Get 3D height factor\n        float height_factor_3d = 1.0f;\n\n        if (data.ConfigBool[configid_bool_overlay_3D_enabled])\n        {\n            float texel_aspect = 1.0f;\n            vr::VROverlay()->GetOverlayTexelAspect(ovrl_handle, &texel_aspect);\n\n            height_factor_3d = 1.0f / texel_aspect;\n        }\n\n        //Attempt to calculate the correct offset to bottom, taking in account all the things GetTransformForOverlayCoordinates() does not\n        float width = data.ConfigFloat[configid_float_overlay_width];\n        float uv_width  = bounds.uMax - bounds.uMin;\n        float uv_height = bounds.vMax - bounds.vMin;\n        float cropped_width  = ovrl_pixel_width  * uv_width;\n        float cropped_height = ovrl_pixel_height * uv_height * height_factor_3d;\n        float aspect_ratio_orig = (float)ovrl_pixel_width / ovrl_pixel_height;\n        float aspect_ratio_new = cropped_height / cropped_width;\n        float height = (aspect_ratio_orig * width);\n        float offset_to_bottom =  -( (aspect_ratio_new * width) - (aspect_ratio_orig * width) ) / 2.0f;\n        offset_to_bottom -= height / 2.0f;\n\n        //Theater Screen's width can't be queried, so we use a cursor overlay and use it as reference point instead\n        if (data.ConfigInt[configid_int_overlay_origin] == ovrl_origin_theater_screen)\n        {\n            #ifdef DPLUS_UI\n                vr::VROverlayHandle_t ovrl_handle_reference = vr::k_ulOverlayHandleInvalid;\n                vr::VROverlay()->FindOverlay(\"elvissteinjr.DesktopPlusTheaterReference\", &ovrl_handle_reference);\n            #else\n                vr::VROverlayHandle_t ovrl_handle_reference = m_TheaterOverlayReferenceHandle;\n            #endif\n\n            if (ovrl_handle_reference != vr::k_ulOverlayHandleInvalid)\n            {\n                //Put reference cursor overlay to bottom center of theater overlay\n                vr::HmdVector2_t pos{ovrl_pixel_width/2.0f, 0.0f};\n                vr::VROverlay()->SetOverlayCursorPositionOverride(ovrl_handle, &pos);\n\n                vr::VROverlay()->SetOverlayAlpha(ovrl_handle_reference, 0.25f);\n\n                //Grab its middle spot and return that\n                vr::VROverlay()->GetTransformForOverlayCoordinates(ovrl_handle_reference, vr::TrackingUniverseStanding, {0.5f, 0.5f}, &matrix);\n\n                return matrix;\n            }\n        }\n\n        //When Performance Monitor, apply additional offset of the unused overlay space\n        #ifdef DPLUS_UI\n            if (data.ConfigInt[configid_int_overlay_capture_source] == ovrl_capsource_ui)\n            {\n                float height_new = aspect_ratio_new * width; //height uses aspect_ratio_orig, so calculate new height\n                offset_to_bottom += ( (cropped_height - UIManager::Get()->GetPerformanceWindow().GetSize().y) ) * ( (height_new / cropped_height) ) / 2.0f;\n            }\n        #endif\n\n        //Move to bottom\n        vr::IVRSystemEx::TransformOpenVR34TranslateRelative(matrix, 0.0f, offset_to_bottom, 0.0f);\n    }\n\n    return matrix;\n}\n\n#ifndef DPLUS_UI\n\nvoid OverlayManager::TheaterOverlayForwardCapture(const Overlay& ovrl_source)\n{\n    if (ovrl_source.GetTextureSource() == ovrl_texsource_winrt_capture)\n    {\n        DPWinRT_StartCaptureFromOverlay(m_TheaterOverlayHandle, m_CurrentTheaterOverlayOrigHandle);\n        DPWinRT_PauseCapture(m_TheaterOverlayHandle, !ovrl_source.IsVisible());\n        DPWinRT_PauseCapture(m_CurrentTheaterOverlayOrigHandle, true);\n    }\n    else if (ovrl_source.GetTextureSource() == ovrl_texsource_ui)\n    {\n        vr::VROverlay()->SetOverlayRenderingPid(m_TheaterOverlayHandle, IPCManager::GetUIAppProcessID());\n        IPCManager::Get().PostMessageToUIApp(ipcmsg_action, ipcact_overlays_ui_reset);\n    }\n    else if (ovrl_source.GetTextureSource() == ovrl_texsource_browser)\n    {\n        DPBrowserAPIClient::Get().DPBrowser_DuplicateBrowserOutput(m_CurrentTheaterOverlayOrigHandle, m_TheaterOverlayHandle);\n        DPBrowserAPIClient::Get().DPBrowser_PauseBrowser(m_TheaterOverlayHandle, !ovrl_source.IsVisible());\n        DPBrowserAPIClient::Get().DPBrowser_PauseBrowser(m_CurrentTheaterOverlayOrigHandle, true);\n\n        vr::VROverlay()->SetOverlayRenderingPid(m_TheaterOverlayHandle, DPBrowserAPIClient::Get().GetServerAppProcessID());\n    }\n}\n\nvoid OverlayManager::TheaterOverlayReturnCapture(const Overlay& ovrl_source)\n{\n    if (ovrl_source.GetTextureSource() == ovrl_texsource_winrt_capture)\n    {\n        DPWinRT_StopCapture(m_TheaterOverlayHandle);\n    }\n    else if (ovrl_source.GetTextureSource() == ovrl_texsource_ui)\n    {\n        vr::VROverlay()->SetOverlayRenderingPid(m_TheaterOverlayHandle, ::GetCurrentProcessId());\n        IPCManager::Get().PostMessageToUIApp(ipcmsg_action, ipcact_overlays_ui_reset);\n    }\n    else if (ovrl_source.GetTextureSource() == ovrl_texsource_browser)\n    {\n        DPBrowserAPIClient::Get().DPBrowser_StopBrowser(m_TheaterOverlayHandle);\n\n        vr::VROverlay()->SetOverlayRenderingPid(m_TheaterOverlayHandle, ::GetCurrentProcessId());\n    }\n}\n\n#endif //ifndef DPLUS_UI\n\nunsigned int OverlayManager::DuplicateOverlay(const OverlayConfigData& data, unsigned int source_id)\n{\n    unsigned int id = (unsigned int)m_OverlayConfigData.size();\n\n    #ifndef DPLUS_UI\n        m_Overlays.emplace_back(id);\n    #endif\n    m_OverlayConfigData.push_back(data);\n\n    OverlayConfigData& new_data = m_OverlayConfigData.back();\n\n    #ifdef DPLUS_UI\n        if (new_data.ConfigInt[configid_int_overlay_capture_source] == ovrl_capsource_ui)\n        {\n            UIManager::Get()->GetPerformanceWindow().ScheduleOverlaySharedTextureUpdate();\n        }\n    #endif\n\n    //Store duplication ID for browser overlays\n    if ( (source_id != k_ulOverlayID_None) && (new_data.ConfigInt[configid_int_overlay_capture_source] == ovrl_capsource_browser) )\n    {\n        //If this is duplicated from an already duplicating overlay, keep the ID\n        if (new_data.ConfigInt[configid_int_overlay_duplication_id] == -1)\n        {\n            new_data.ConfigInt[configid_int_overlay_duplication_id] = (int)source_id;\n        }\n\n        //Also clear strings which won't match after any navigation has been done on the source overlay but would hang around forever in profiles\n        new_data.ConfigStr[configid_str_overlay_browser_url] = \"\";\n        new_data.ConfigStr[configid_str_overlay_browser_url_user_last] = \"\";\n        new_data.ConfigStr[configid_str_overlay_browser_title] = \"\";\n    }\n\n    //Send handle over to UI\n    #ifndef DPLUS_UI\n        new_data.ConfigHandle[configid_handle_overlay_state_overlay_handle] = m_Overlays.back().GetHandle();\n\n        IPCManager::Get().PostConfigMessageToUIApp(configid_int_state_overlay_current_id_override, (int)id);\n        IPCManager::Get().PostConfigMessageToUIApp(configid_handle_overlay_state_overlay_handle, new_data.ConfigHandle[configid_handle_overlay_state_overlay_handle]);\n        IPCManager::Get().PostConfigMessageToUIApp(configid_int_state_overlay_current_id_override, -1);\n    #endif\n\n    return id;\n}\n\nunsigned int OverlayManager::AddOverlay(OverlayCaptureSource capture_source, int desktop_id, HWND window_handle)\n{\n    unsigned int id = (unsigned int)m_OverlayConfigData.size();\n\n    #ifndef DPLUS_UI\n    m_Overlays.emplace_back(id);\n    #endif\n    m_OverlayConfigData.push_back(OverlayConfigData());\n\n    OverlayConfigData& data = m_OverlayConfigData.back();\n\n    //Load general default values from the default profile\n    unsigned int current_id_old = m_CurrentOverlayID;\n    m_CurrentOverlayID = id;\n    ConfigManager::Get().LoadOverlayProfileDefault();\n    m_CurrentOverlayID = current_id_old;\n\n    //Apply additional defaults\n    data.ConfigBool[configid_bool_overlay_show_backside]                       = true;\n    data.ConfigInt[configid_int_overlay_capture_source]                        = capture_source;\n    data.ConfigFloat[configid_float_overlay_width]                             = 0.3f;\n    data.ConfigFloat[configid_float_overlay_curvature]                         = 0.0f;\n    data.ConfigFloat[configid_float_overlay_state_brightness_extra_multiplier] = 1.0f;\n\n    switch (capture_source)\n    {\n        case ovrl_capsource_desktop_duplication:\n        {\n            data.ConfigInt[configid_int_overlay_desktop_id] = desktop_id;\n            break;\n        }\n        case ovrl_capsource_winrt_capture:\n        {\n            data.ConfigInt[configid_int_overlay_winrt_desktop_id]       = desktop_id; //This just so happens to be -2 (unset) if called from ipcact_overlay_new_drag with HWND\n            data.ConfigHandle[configid_handle_overlay_state_winrt_hwnd] = (intptr_t)window_handle;\n           \n            #ifdef DPLUS_UI\n                const WindowInfo* window_info = WindowManager::Get().WindowListFindWindow(window_handle);\n\n                if (window_info != nullptr)\n                {\n                    data.ConfigStr[configid_str_overlay_winrt_last_window_title]    = StringConvertFromUTF16(window_info->GetTitle().c_str());\n                    data.ConfigStr[configid_str_overlay_winrt_last_window_exe_name] = window_info->GetExeName();\n                }\n            #endif\n            break;\n        }\n        case ovrl_capsource_ui:\n        {\n            #ifdef DPLUS_UI\n                if ((UIManager::Get()) && (UIManager::Get()->IsOpenVRLoaded()))\n                {\n                    UIManager::Get()->GetPerformanceWindow().ScheduleOverlaySharedTextureUpdate();\n                }\n            #endif\n            break;\n        }\n    }\n\n    #ifdef DPLUS_UI\n        SetOverlayNameAuto(id);\n    #endif\n\n    //Send handle over to UI\n    #ifndef DPLUS_UI\n        data.ConfigHandle[configid_handle_overlay_state_overlay_handle] = m_Overlays.back().GetHandle();\n\n        IPCManager::Get().PostConfigMessageToUIApp(configid_int_state_overlay_current_id_override, (int)id);\n        IPCManager::Get().PostConfigMessageToUIApp(configid_handle_overlay_state_overlay_handle, data.ConfigHandle[configid_handle_overlay_state_overlay_handle]);\n        IPCManager::Get().PostConfigMessageToUIApp(configid_int_state_overlay_current_id_override, -1);\n    #endif\n\n    return id;\n}\n\nunsigned int OverlayManager::AddUIOverlay()\n{\n    unsigned int id = DuplicateOverlay(OverlayConfigData());\n\n    //Load general default values from the default profile\n    unsigned int current_id_old = m_CurrentOverlayID;\n    m_CurrentOverlayID = id;\n    ConfigManager::Get().LoadOverlayProfileDefault();\n    m_CurrentOverlayID = current_id_old;\n\n    //Apply additional defaults\n    OverlayConfigData& data = m_OverlayConfigData.back();\n    data.ConfigInt[configid_int_overlay_origin]          = ovrl_origin_left_hand;\n    data.ConfigInt[configid_int_overlay_capture_source]  = ovrl_capsource_ui;\n    data.ConfigFloat[configid_float_overlay_width]       = 0.3f;\n    data.ConfigFloat[configid_float_overlay_curvature]   = 0.0f;\n\n    //Put the overlay on the left hand controller if possible, otherwise on right or just in the room\n    #ifdef DPLUS_UI\n    if ( (UIManager::Get()) && (UIManager::Get()->IsOpenVRLoaded()) )\n    {\n    #endif\n        if (vr::VRSystem()->GetTrackedDeviceIndexForControllerRole(vr::TrackedControllerRole_LeftHand) != vr::k_unTrackedDeviceIndexInvalid)\n        {\n            data.ConfigInt[configid_int_overlay_origin] = ovrl_origin_left_hand;\n        }\n        else if (vr::VRSystem()->GetTrackedDeviceIndexForControllerRole(vr::TrackedControllerRole_RightHand) != vr::k_unTrackedDeviceIndexInvalid)\n        {\n            data.ConfigInt[configid_int_overlay_origin] = ovrl_origin_right_hand;\n        }\n        else\n        {\n            data.ConfigInt[configid_int_overlay_origin] = ovrl_origin_room;\n            data.ConfigFloat[configid_float_overlay_width] = 0.7f;\n        }\n    #ifdef DPLUS_UI\n    }\n    #endif\n\n    return id;\n}\n\n#ifndef DPLUS_UI\n\nOverlay& OverlayManager::GetOverlay(unsigned int id)\n{\n    if (id < m_Overlays.size())\n        return m_Overlays[id];\n    else\n        return m_OverlayNull; //Return null overlay when out of range\n}\n\nconst Overlay& OverlayManager::GetOverlay(unsigned int id) const\n{\n    if (id < m_Overlays.size())\n        return m_Overlays[id];\n    else\n        return m_OverlayNull; //Return null overlay when out of range\n}\n\nOverlay& OverlayManager::GetCurrentOverlay()\n{\n    return GetOverlay(m_CurrentOverlayID);\n}\n\nOverlay& OverlayManager::GetPrimaryDashboardOverlay()\n{\n    auto it = std::find_if(m_Overlays.begin(), m_Overlays.end(), [&](auto& overlay)\n                           { return ( (GetConfigData(overlay.GetID()).ConfigInt[configid_int_overlay_origin] == ovrl_origin_dashboard) && (overlay.IsVisible()) ); });\n\n    return (it != m_Overlays.end()) ? *it : m_OverlayNull;\n}\n\nvr::VROverlayHandle_t OverlayManager::GetTheaterOverlayHandle() const\n{\n    return m_TheaterOverlayHandle;\n}\n\nunsigned int OverlayManager::GetTheaterOverlayID() const\n{\n    return m_CurrentTheaterOverlayID;\n}\n\nvoid OverlayManager::SetTheaterOverlayID(unsigned int id)\n{\n    if (id >= m_OverlayConfigData.size())\n    {\n        ClearTheaterOverlay();\n        return;\n    }\n\n    Overlay& ovrl_source = m_Overlays[id];\n\n    if (m_CurrentTheaterOverlayID == k_ulOverlayID_None)\n    {\n        vr::VROverlayError ovrl_error = vr::VROverlayError_None;\n        vr::VROverlayHandle_t ovrl_handle;\n        vr::VROverlayHandle_t icon_unused;\n\n        ovrl_error = vr::VROverlay()->CreateDashboardOverlay(g_AppKeyTheaterScreen, \"Desktop+ Theater Screen\", &ovrl_handle, &icon_unused);\n\n        if (ovrl_error == vr::VROverlayError_None)\n        {\n            vr::VROverlay()->SetOverlayFlag(ovrl_handle, vr::VROverlayFlags_NoDashboardTab,        true);\n            vr::VROverlay()->SetOverlayFlag(ovrl_handle, vr::VROverlayFlags_EnableControlBar,      true);\n            vr::VROverlay()->SetOverlayFlag(ovrl_handle, vr::VROverlayFlags_EnableControlBarClose, true);\n            vr::VROverlay()->SetOverlayFlag(ovrl_handle, vr::VROverlayFlags_MinimalControlBar,     true);\n\n            m_TheaterOverlayHandle = ovrl_handle;\n\n            //Since the Theater Screen's width can't be queried, getting anything except the middle transform is difficult\n            //We work around this restriction by using a cursor overlay as transform reference as it can be placed relative on the overlay and still be queried for its transform\n            ovrl_error = vr::VROverlay()->CreateOverlay(\"elvissteinjr.DesktopPlusTheaterReference\", \"Desktop+ Theater Screen Reference\", &ovrl_handle);\n\n            if (ovrl_error == vr::VROverlayError_None)\n            {\n                //Empty overlay is probably fine, but give it something to work with just to be sure\n                unsigned char bytes[2 * 2 * 4] = {0}; //2x2 transparent RGBA\n                vr::VROverlay()->SetOverlayRaw(ovrl_handle, bytes, 2, 2, 4);\n\n                //Apply initial cursor state, though position gets adjusted for every query as it needs to match overlay mouse scale\n                vr::HmdVector2_t hotspot{0.5f, 0.5f};\n                vr::HmdVector2_t pos{0.0f, 0.0f};\n                vr::VROverlay()->SetOverlayCursor(m_TheaterOverlayHandle, ovrl_handle);\n                vr::VROverlay()->SetOverlayTransformCursor(ovrl_handle, &hotspot);\n                vr::VROverlay()->SetOverlayCursorPositionOverride(m_TheaterOverlayHandle, &pos);\n\n                m_TheaterOverlayReferenceHandle = ovrl_handle;\n            }\n            else\n            {\n                LOG_F(WARNING, \"Failed to create Theater Mode reference overlay: %s\", vr::VROverlay()->GetOverlayErrorNameFromEnum(ovrl_error));\n            }\n        }\n        else\n        {\n            LOG_F(WARNING, \"Failed to create Theater Mode overlay: %s\", vr::VROverlay()->GetOverlayErrorNameFromEnum(ovrl_error));\n        }\n    }\n    else if (m_CurrentTheaterOverlayID < m_OverlayConfigData.size())\n    {\n        //Return previous source overlay to its own handle\n        Overlay& ovrl_source_prev = m_Overlays[m_CurrentTheaterOverlayID];\n        ovrl_source_prev.SetHandle(m_CurrentTheaterOverlayOrigHandle);\n        vr::VROverlay()->SetOverlayAlpha(m_CurrentTheaterOverlayOrigHandle, ovrl_source.GetOpacity());  //Match opacity as its only set on changes\n        ovrl_source_prev.SetVisible(false);                                                             //Mark it as invisible and have OutputManager reset it later\n\n        m_OverlayConfigData[m_CurrentTheaterOverlayID].ConfigHandle[configid_handle_overlay_state_overlay_handle] = m_CurrentTheaterOverlayOrigHandle;\n        ConfigManager::SetValue(configid_handle_state_theater_orig_overlay_handle, vr::k_ulOverlayHandleInvalid);\n\n        //Send handle change over to UI\n        IPCManager::Get().PostConfigMessageToUIApp(configid_int_state_overlay_current_id_override, (int)m_CurrentTheaterOverlayID);\n        IPCManager::Get().PostConfigMessageToUIApp(configid_handle_overlay_state_overlay_handle, m_CurrentTheaterOverlayOrigHandle);\n        IPCManager::Get().PostConfigMessageToUIApp(configid_int_state_overlay_current_id_override, -1);\n\n        IPCManager::Get().PostConfigMessageToUIApp(configid_handle_state_theater_orig_overlay_handle, vr::k_ulOverlayHandleInvalid);\n\n        TheaterOverlayReturnCapture(ovrl_source_prev);\n    }\n\n    m_CurrentTheaterOverlayOrigHandle = ovrl_source.GetHandle();\n    vr::VROverlay()->HideOverlay(m_CurrentTheaterOverlayOrigHandle);                     //Hide original overlay\n    vr::VROverlay()->SetOverlayAlpha(m_TheaterOverlayHandle, ovrl_source.GetOpacity());  //Match opacity as its only set on changes\n    ovrl_source.SetHandle(m_TheaterOverlayHandle);\n\n    m_CurrentTheaterOverlayID = id;\n\n    if (m_CurrentTheaterOverlayID < m_OverlayConfigData.size())\n    {\n        m_OverlayConfigData[m_CurrentTheaterOverlayID].ConfigHandle[configid_handle_overlay_state_overlay_handle] = m_TheaterOverlayHandle;\n        ConfigManager::SetValue(configid_handle_state_theater_orig_overlay_handle, m_CurrentTheaterOverlayOrigHandle);\n\n        //Send handle change over to UI\n        IPCManager::Get().PostConfigMessageToUIApp(configid_int_state_overlay_current_id_override, (int)m_CurrentTheaterOverlayID);\n        IPCManager::Get().PostConfigMessageToUIApp(configid_handle_overlay_state_overlay_handle, m_TheaterOverlayHandle);\n        IPCManager::Get().PostConfigMessageToUIApp(configid_int_state_overlay_current_id_override, -1);\n\n        IPCManager::Get().PostConfigMessageToUIApp(configid_handle_state_theater_orig_overlay_handle, m_CurrentTheaterOverlayOrigHandle);\n\n        TheaterOverlayForwardCapture(ovrl_source);\n    }\n\n    //Keep active overlay count correct\n    if (OutputManager* outmgr = OutputManager::Get())\n    {\n        outmgr->ResetOverlayActiveCount();\n    }\n}\n\nvoid OverlayManager::ClearTheaterOverlay(bool no_ui_update)\n{\n    if (m_CurrentTheaterOverlayID < m_OverlayConfigData.size())\n    {\n        //Return previous source overlay to its own handle\n        Overlay& ovrl_source_prev = m_Overlays[m_CurrentTheaterOverlayID];\n        ovrl_source_prev.SetHandle(m_CurrentTheaterOverlayOrigHandle);\n        vr::VROverlay()->SetOverlayAlpha(m_CurrentTheaterOverlayOrigHandle, ovrl_source_prev.GetOpacity());  //Match opacity as its only set on changes\n        ovrl_source_prev.SetVisible(false);                                                                  //Mark it as invisible and have OutputManager reset it later\n\n        m_OverlayConfigData[m_CurrentTheaterOverlayID].ConfigHandle[configid_handle_overlay_state_overlay_handle] = m_CurrentTheaterOverlayOrigHandle;\n        ConfigManager::SetValue(configid_handle_state_theater_orig_overlay_handle, vr::k_ulOverlayHandleInvalid);\n\n        //Send handle change over to UI (this should be skipped when the current theater overlay is in the process of being removed)\n        if (!no_ui_update)\n        {\n            IPCManager::Get().PostConfigMessageToUIApp(configid_int_state_overlay_current_id_override, (int)m_CurrentTheaterOverlayID);\n            IPCManager::Get().PostConfigMessageToUIApp(configid_handle_overlay_state_overlay_handle, m_CurrentTheaterOverlayOrigHandle);\n            IPCManager::Get().PostConfigMessageToUIApp(configid_int_state_overlay_current_id_override, -1);\n\n            IPCManager::Get().PostConfigMessageToUIApp(configid_handle_state_theater_orig_overlay_handle, vr::k_ulOverlayHandleInvalid);\n        }\n\n        TheaterOverlayReturnCapture(ovrl_source_prev);\n    }\n\n    if (m_TheaterOverlayHandle != vr::k_ulOverlayHandleInvalid)\n    {\n        vr::VROverlay()->DestroyOverlay(m_TheaterOverlayHandle);\n\n        if (m_TheaterOverlayReferenceHandle != vr::k_ulOverlayHandleInvalid)\n        {\n            vr::VROverlay()->DestroyOverlay(m_TheaterOverlayReferenceHandle);\n        }\n    }\n\n    m_CurrentTheaterOverlayID         = k_ulOverlayID_None;\n    m_CurrentTheaterOverlayOrigHandle = vr::k_ulOverlayHandleInvalid;\n    m_TheaterOverlayHandle            = vr::k_ulOverlayHandleInvalid;\n    m_TheaterOverlayReferenceHandle   = vr::k_ulOverlayHandleInvalid;\n\n    //Keep active overlay count correct\n    if (OutputManager* outmgr = OutputManager::Get())\n    {\n        outmgr->ResetOverlayActiveCount();\n    }\n}\n\n#endif\n\nOverlayConfigData& OverlayManager::GetConfigData(unsigned int id)\n{\n    if (id < m_OverlayConfigData.size())\n        return m_OverlayConfigData[id];\n    else\n        return m_OverlayConfigDataNull; //Return null overlay data when out of range\n}\n\nconst OverlayConfigData& OverlayManager::GetConfigData(unsigned int id) const\n{\n    if (id < m_OverlayConfigData.size())\n        return m_OverlayConfigData[id];\n    else\n        return m_OverlayConfigDataNull; //Return null overlay data when out of range\n}\n\nOverlayConfigData& OverlayManager::GetCurrentConfigData()\n{\n    return GetConfigData(m_CurrentOverlayID);\n}\n\nOverlayOriginConfig OverlayManager::GetOriginConfigFromData(const OverlayConfigData& data) const\n{\n    OverlayOriginConfig origin_config;\n    origin_config.HMDFloorUseTurning = data.ConfigBool[configid_bool_overlay_origin_hmd_floor_use_turning];\n\n    return origin_config;\n}\n\nunsigned int OverlayManager::GetCurrentOverlayID() const\n{\n    return m_CurrentOverlayID;\n}\n\nvoid OverlayManager::SetCurrentOverlayID(unsigned int id)\n{\n    m_CurrentOverlayID = id;\n}\n\n\n#ifndef DPLUS_UI\n\nunsigned int OverlayManager::FindOverlayID(vr::VROverlayHandle_t handle) const\n{\n    const auto it = std::find_if(m_Overlays.cbegin(), m_Overlays.cend(), [&](const auto& overlay){ return (overlay.GetHandle() == handle); });\n\n    if (it != m_Overlays.cend())\n    {\n        return it->GetID();\n    }\n    else if (handle == m_CurrentTheaterOverlayOrigHandle)\n    {\n        return m_CurrentTheaterOverlayID;\n    }\n\n    return k_ulOverlayID_None;\n}\n\nunsigned int OverlayManager::FindTheaterOverlayID() const\n{\n    return m_CurrentTheaterOverlayID;\n}\n\n#else\n\nunsigned int OverlayManager::FindOverlayID(vr::VROverlayHandle_t handle) const\n{\n    for (unsigned int i = 0; i < m_OverlayConfigData.size(); ++i)\n    {\n        if (m_OverlayConfigData[i].ConfigHandle[configid_handle_overlay_state_overlay_handle] == handle)\n        {\n            return i;\n        }\n    }\n\n    //Theater overlay handle switcheroo is mostly transparent to the UI but in some instances it still needs to find the overlay based on its original handle while used in theater screen\n    if (handle == ConfigManager::GetValue(configid_handle_state_theater_orig_overlay_handle))\n    {\n        return FindTheaterOverlayID();\n    }\n\n    return k_ulOverlayID_None;\n}\n\nunsigned int OverlayManager::FindTheaterOverlayID() const\n{\n    for (unsigned int i = 0; i < m_OverlayConfigData.size(); ++i)\n    {\n        const OverlayConfigData& data = m_OverlayConfigData[i];\n        if ((data.ConfigInt[configid_int_overlay_origin] == ovrl_origin_theater_screen) && (data.ConfigBool[configid_bool_overlay_enabled]))\n        {\n            return i;\n        }\n    }\n\n    return k_ulOverlayID_None;\n}\n\n#endif\n\nunsigned int OverlayManager::GetOverlayCount() const\n{\n    return (unsigned int)m_OverlayConfigData.size();\n}\n\nvoid OverlayManager::SwapOverlays(unsigned int id, unsigned int id2)\n{\n    if ( (id == id2) || (id >= m_OverlayConfigData.size()) || (id2 >= m_OverlayConfigData.size()) )\n        return;\n\n    //Swap config data\n    std::iter_swap(m_OverlayConfigData.begin() + id, m_OverlayConfigData.begin() + id2);\n\n    #ifndef DPLUS_UI\n        //Swap overlays and fix IDs\n        std::iter_swap(m_Overlays.begin() + id, m_Overlays.begin() + id2);\n        m_Overlays[id].SetID(id);\n        m_Overlays[id2].SetID(id2);\n\n        //Fixup theater overlay ID if needed\n        m_CurrentTheaterOverlayID = (m_CurrentTheaterOverlayID == id) ? id2 : (m_CurrentTheaterOverlayID == id2) ? id : m_CurrentTheaterOverlayID;\n    #endif\n\n    #ifdef DPLUS_UI\n        //Swap assigned keyboard overlay ID as well if it is one of the affected ones\n        WindowKeyboard& window_keyboard = UIManager::Get()->GetVRKeyboard().GetWindow();\n        int assigned_id = -1;\n\n        assigned_id = window_keyboard.GetAssignedOverlayID(floating_window_ovrl_state_dashboard_tab);\n        window_keyboard.SetAssignedOverlayID( (assigned_id == id) ? id2 : (assigned_id == id2) ? id : assigned_id, floating_window_ovrl_state_dashboard_tab);\n        assigned_id = window_keyboard.GetAssignedOverlayID(floating_window_ovrl_state_room);\n        window_keyboard.SetAssignedOverlayID( (assigned_id == id) ? id2 : (assigned_id == id2) ? id : assigned_id, floating_window_ovrl_state_room);\n    #endif\n\n    //Find and swap overlay duplication IDs\n    for (auto& data : m_OverlayConfigData)\n    {\n        if (data.ConfigInt[configid_int_overlay_duplication_id] == (int)id)\n            data.ConfigInt[configid_int_overlay_duplication_id] = (int)id2;\n        else if (data.ConfigInt[configid_int_overlay_duplication_id] == (int)id2)\n            data.ConfigInt[configid_int_overlay_duplication_id] = (int)id;\n    }\n\n    //Fixup focused overlay if needed\n    int& focused_overlay_id = ConfigManager::Get().GetRef(configid_int_state_overlay_focused_id);\n    focused_overlay_id = (focused_overlay_id == id) ? id2 : (focused_overlay_id == id2) ? id : focused_overlay_id;\n}\n\nvoid OverlayManager::RemoveOverlay(unsigned int id)\n{\n    if (id < m_OverlayConfigData.size())\n    {\n        //Find and fix overlays referencing the to-be-deleted overlay with their duplication ID first\n        unsigned int replacement_id = k_ulOverlayID_None;\n        for (unsigned int i = 0; i < m_OverlayConfigData.size(); ++i)\n        {\n            OverlayConfigData& data = m_OverlayConfigData[i];\n            int& duplication_id = data.ConfigInt[configid_int_overlay_duplication_id];\n\n            if (duplication_id == (int)id)\n            {\n                //Take the first of the duplicating overlays and convert it to standalone as the new source\n                if (replacement_id == k_ulOverlayID_None)\n                {\n                    ConvertDuplicatedOverlayToStandalone(i, true);\n                    replacement_id = i;\n                }\n                else    //We already have one, so just change the ID\n                {\n                    duplication_id = (int)replacement_id;\n                }\n            }\n\n            //Also fix overlay duplication IDs reference overlays that had an higher ID than the removed one\n            if ( (duplication_id != -1) && (duplication_id > (int)id) )\n            {\n                duplication_id--;\n            }\n        }\n\n        #ifndef DPLUS_UI\n            //Clear Theater overlay if it's currently used by this to ensure proper cleanup\n            if (m_CurrentTheaterOverlayID == id)\n            {\n                ClearTheaterOverlay(true);\n            }\n        #endif\n\n        //Then delete the config\n        m_OverlayConfigData.erase(m_OverlayConfigData.begin() + id);\n\n        #ifndef DPLUS_UI\n            m_Overlays.erase(m_Overlays.begin() + id);\n\n            //Fixup IDs for overlays past it if the overlay wasn't the last one\n            if (id != m_Overlays.size())\n            {\n                for (auto& overlay : m_Overlays)\n                {\n                    if (overlay.GetID() > id)\n                    {\n                        overlay.SetID(overlay.GetID() - 1);\n                    }\n                }\n            }\n\n            if (m_CurrentTheaterOverlayID > id)\n            {\n                m_CurrentTheaterOverlayID--;\n            }\n\n            //Keep active count correct\n            if (OutputManager* outmgr = OutputManager::Get())\n            {\n                outmgr->ResetOverlayActiveCount();\n            }\n        #else\n            //Remove assigned keyboard overlay ID or fix it up if it was higher than the removed one\n            WindowKeyboard& window_keyboard = UIManager::Get()->GetVRKeyboard().GetWindow();\n            int assigned_id = -1;\n\n            assigned_id = window_keyboard.GetAssignedOverlayID(floating_window_ovrl_state_dashboard_tab);\n            if (assigned_id == (int)id)\n            {\n                window_keyboard.SetAssignedOverlayID(-1, floating_window_ovrl_state_dashboard_tab);\n                window_keyboard.GetOverlayState(floating_window_ovrl_state_dashboard_tab).IsVisible = false;\n            }\n            else if (assigned_id > (int)id)\n            {\n                window_keyboard.SetAssignedOverlayID(assigned_id - 1, floating_window_ovrl_state_dashboard_tab);\n            }\n\n            assigned_id = window_keyboard.GetAssignedOverlayID(floating_window_ovrl_state_room);\n            if (assigned_id == (int)id)\n            {\n                window_keyboard.SetAssignedOverlayID(-1, floating_window_ovrl_state_room);\n                window_keyboard.GetOverlayState(floating_window_ovrl_state_room).IsVisible = false;\n            }\n            else if (assigned_id > (int)id)\n            {\n                window_keyboard.SetAssignedOverlayID(assigned_id - 1, floating_window_ovrl_state_room);\n            }\n        #endif\n\n        //Fixup current overlay if needed\n        if (m_CurrentOverlayID >= m_OverlayConfigData.size())\n        {\n            m_CurrentOverlayID--;\n        }\n\n        //Fixup focused overlay if needed\n        int& focused_overlay_id = ConfigManager::Get().GetRef(configid_int_state_overlay_focused_id);\n\n        if (focused_overlay_id == (int)id)\n        {\n            focused_overlay_id = -1;\n        }\n        else if (focused_overlay_id > (int)id)\n        {\n            focused_overlay_id--;\n        }\n    }\n}\n\nvoid OverlayManager::RemoveAllOverlays()\n{\n    #ifndef DPLUS_UI\n        ClearTheaterOverlay(true);\n    #endif\n\n    //Remove all overlays with minimal overhead and refreshes\n    while (m_OverlayConfigData.size() > 0)\n    {\n        m_OverlayConfigData.erase(m_OverlayConfigData.begin() + m_OverlayConfigData.size() - 1);\n\n        #ifndef DPLUS_UI\n            m_Overlays.erase(m_Overlays.begin() + m_Overlays.size() - 1);\n        #endif\n    }\n\n    m_CurrentOverlayID = k_ulOverlayID_None;\n\n    #ifndef DPLUS_UI\n        //Fixup active overlay counts after we just removed everything that might've been considered active\n        if (OutputManager* outmgr = OutputManager::Get())\n        {\n            outmgr->ResetOverlayActiveCount();\n        }\n    #endif\n}\n\n#ifndef DPLUS_UI\n\nOverlayIDList OverlayManager::FindInactiveOverlaysForWindow(const WindowInfo& window_info) const\n{\n    OverlayIDList matching_overlay_ids;\n    OverlayIDList candidate_overlay_ids;\n\n    //Check if there are any candidates before comparing anything\n    for (unsigned int i = 0; i < m_OverlayConfigData.size(); ++i)\n    {\n        const OverlayConfigData& data = m_OverlayConfigData[i];\n\n        if ( (data.ConfigInt[configid_int_overlay_capture_source] == ovrl_capsource_winrt_capture) && (data.ConfigInt[configid_int_overlay_winrt_desktop_id] == -2) &&\n             (m_Overlays[i].GetTextureSource() == ovrl_texsource_none) )\n        {\n            candidate_overlay_ids.push_back(i);\n        }\n    }\n\n    //If no candidates, stop here\n    if (candidate_overlay_ids.empty())\n        return matching_overlay_ids;\n\n    //Do the basically the same as in WindowManager::FindClosestWindowForTitle(), but matching overlays to one window instead\n    const std::string title_str = StringConvertFromUTF16(window_info.GetTitle().c_str());\n\n    //Precompute UTF-16 window class names of candidate overlays (using total m_OverlayConfigData array size for direct indexing), as we're using WindowInfo's matching function\n    std::vector<std::wstring> overlay_class_name_wstr;\n    overlay_class_name_wstr.resize(m_OverlayConfigData.size());\n\n    for (auto i : candidate_overlay_ids)\n    {\n        overlay_class_name_wstr[i] = WStringConvertFromUTF8(m_OverlayConfigData[i].ConfigStr[configid_str_overlay_winrt_last_window_class_name].c_str());\n    }\n\n    //Look for a complete match first \n    for (auto i : candidate_overlay_ids)\n    {\n        const OverlayConfigData& data = m_OverlayConfigData[i];\n\n        if ( (window_info.IsClassNameMatching(overlay_class_name_wstr[i])) && (data.ConfigStr[configid_str_overlay_winrt_last_window_exe_name] == window_info.GetExeName()) && \n             (data.ConfigStr[configid_str_overlay_winrt_last_window_title] == title_str) )\n        {\n            matching_overlay_ids.push_back(i);\n        }\n    }\n\n    //Stop here if we already had at least a single match\n    if (!matching_overlay_ids.empty())\n    {\n        return matching_overlay_ids;\n    }\n\n    //Cut off document part of title if it there is one\n    std::string title_search = title_str;\n    std::string app_name;\n    size_t search_pos = title_str.rfind(\" - \");\n\n    if (search_pos != std::string::npos)\n    {\n        app_name = title_str.substr(search_pos);\n    }\n\n    //Try to find a partial match by removing the last word from the title string and appending the application name\n    while ((search_pos != 0) && (search_pos != std::string::npos))\n    {\n        search_pos--;\n        search_pos = title_str.find_last_of(L' ', search_pos);\n\n        if (search_pos != std::string::npos)\n        {\n            title_search = title_str.substr(0, search_pos) + app_name;\n        }\n        else if (!app_name.empty()) //Last attempt, just the app-name\n        {\n            title_search = app_name;\n        }\n        else\n        {\n            break;\n        }\n\n        for (auto i : candidate_overlay_ids)\n        {\n            const OverlayConfigData& data = m_OverlayConfigData[i];\n\n            //Check if class/exe name matches and search title can be found in the last stored window title (but just skip if overlay uses strict matching)\n            if ( (!data.ConfigBool[configid_bool_overlay_winrt_window_matching_strict]) &&\n                 (window_info.IsClassNameMatching(overlay_class_name_wstr[i])) && (data.ConfigStr[configid_str_overlay_winrt_last_window_exe_name] == window_info.GetExeName()) &&\n                 (data.ConfigStr[configid_str_overlay_winrt_last_window_title].find(title_search) != std::string::npos) )\n            {\n                matching_overlay_ids.push_back(i);\n            }\n        }\n\n        //Don't reduce the title any further if we had at least a single match\n        if (!matching_overlay_ids.empty())\n        {\n            break;\n        }\n    }\n\n    //Nothing found, try to get a match with just the same class and exe name at least\n    if (matching_overlay_ids.empty())\n    {\n        for (auto i : candidate_overlay_ids)\n        {\n            const OverlayConfigData& data = m_OverlayConfigData[i];\n\n            if ( (!data.ConfigBool[configid_bool_overlay_winrt_window_matching_strict]) &&\n                 (window_info.IsClassNameMatching(overlay_class_name_wstr[i])) && (data.ConfigStr[configid_str_overlay_winrt_last_window_exe_name] == window_info.GetExeName()) )\n            {\n                matching_overlay_ids.push_back(i);\n            }\n        }\n    }\n\n    return matching_overlay_ids;\n}\n\n#endif //ifndef DPLUS_UI\n\nOverlayIDList OverlayManager::FindDuplicatedOverlaysForOverlay(unsigned int source_id) const\n{\n    OverlayIDList matching_overlay_ids;\n\n    for (unsigned int i = 0; i < m_OverlayConfigData.size(); ++i)\n    {\n        const OverlayConfigData& data = m_OverlayConfigData[i];\n\n        if (data.ConfigInt[configid_int_overlay_duplication_id] == source_id)\n        {\n            matching_overlay_ids.push_back(i);\n        }\n    }\n\n    return matching_overlay_ids;\n}\n\nOverlayIDList OverlayManager::FindOverlaysWithTags(const char* str_tags) const\n{\n    OverlayIDList matching_overlay_ids;\n\n    for (unsigned int i = 0; i < m_OverlayConfigData.size(); ++i)\n    {\n        const OverlayConfigData& data = m_OverlayConfigData[i];\n\n        //This isn't exactly the fastest way to do it, but this is called rarely and saves us from keeping a cache up to date and stuff\n        if (MatchOverlayTags(str_tags, data.ConfigStr[configid_str_overlay_tags].c_str(), &data))\n        {\n            matching_overlay_ids.push_back(i);\n        }\n    }\n\n    return matching_overlay_ids;\n}\n\nvoid OverlayManager::ConvertDuplicatedOverlayToStandalone(unsigned int id, bool no_reset)\n{\n    if (id < m_OverlayConfigData.size())\n    {\n        OverlayConfigData& data = m_OverlayConfigData[id];\n\n        //Nothing to do if there's no valid duplication source ID\n        if ( (data.ConfigInt[configid_int_overlay_duplication_id] == -1) && ((unsigned int)data.ConfigInt[configid_int_overlay_duplication_id] >= m_OverlayConfigData.size()) )\n            return;\n\n        OverlayConfigData& data_source = m_OverlayConfigData[data.ConfigInt[configid_int_overlay_duplication_id]];\n\n        //Currently only works and used on browser overlays\n        if (data.ConfigInt[configid_int_overlay_capture_source] == ovrl_capsource_browser)\n        {\n            //Copy relevant properties over\n            data.ConfigStr[configid_str_overlay_browser_url]           = data_source.ConfigStr[configid_str_overlay_browser_url];\n            data.ConfigStr[configid_str_overlay_browser_url_user_last] = data_source.ConfigStr[configid_str_overlay_browser_url_user_last];\n            data.ConfigInt[configid_int_overlay_user_width]            = data_source.ConfigInt[configid_int_overlay_user_width];\n            data.ConfigInt[configid_int_overlay_user_height]           = data_source.ConfigInt[configid_int_overlay_user_height];\n            data.ConfigFloat[configid_float_overlay_browser_zoom]      = data_source.ConfigFloat[configid_float_overlay_browser_zoom];\n        }\n\n        data.ConfigInt[configid_int_overlay_state_content_width]  = data_source.ConfigInt[configid_int_overlay_state_content_width];\n        data.ConfigInt[configid_int_overlay_state_content_height] = data_source.ConfigInt[configid_int_overlay_state_content_height];\n\n        //Remove duplication ID and reset the overlay\n        data.ConfigInt[configid_int_overlay_duplication_id] = -1;\n\n        #ifndef DPLUS_UI\n            if (!no_reset)\n            {\n                if (OutputManager* outmgr = OutputManager::Get())\n                {\n                    unsigned int current_overlay_old = m_CurrentOverlayID;\n                    m_CurrentOverlayID = id;\n\n                    GetOverlay(id).SetTextureSource(ovrl_texsource_invalid);    //Invalidate capture source so browser source is re-initialized with a new context\n\n                    outmgr->ResetCurrentOverlay();\n\n                    m_CurrentOverlayID = current_overlay_old;\n                }\n            }\n        #endif\n    }\n}\n\n#ifdef DPLUS_UI\n\nstd::vector<OverlayManager::TagListEntry> OverlayManager::GetKnownOverlayTagList()\n{\n    auto add_tags_from_tags_string = [](const std::string& str, std::vector<OverlayManager::TagListEntry>& list, std::unordered_set<std::string>& list_unique_tags)\n    {\n        std::string single_tag_str;\n        std::stringstream ss(str);\n        while (std::getline(ss, single_tag_str, ' '))\n        {\n            //Only add if not already in list\n            if ((!single_tag_str.empty()) && (list_unique_tags.find(single_tag_str) == list_unique_tags.end()))\n            {\n                list.push_back({single_tag_str, false});\n                list_unique_tags.insert(single_tag_str);\n            }\n        }\n    };\n\n    //List of built-in, automatically matched tags\n    const char* auto_tags[] = {\"Ovrl_All\", \"Ovrl_Visible\", \"Ovrl_Hidden\", \"Ovrl_Desktop\", \"Ovrl_Window\", \"Ovrl_Browser\", \"Ovrl_PerfMon\"};\n\n    std::vector<OverlayManager::TagListEntry> list;\n    std::unordered_set<std::string> list_unique_tags;\n\n    //Add auto tags\n    for (const auto& tag_str : auto_tags)\n    {\n        list.push_back({tag_str, true});\n        list_unique_tags.insert(tag_str);\n    }\n\n    //Add tags referenced by actions\n    ActionManager& action_manager = ConfigManager::Get().GetActionManager();\n    for (ActionUID uid : action_manager.GetActionOrderListUI())\n    {\n        const Action& action = action_manager.GetAction(uid);\n\n        add_tags_from_tags_string(action.TargetTags, list, list_unique_tags);\n\n        for (const auto& command : action.Commands)\n        {\n            //For Show Overlay, StrMain contains overlay tags\n            if (command.Type == ActionCommand::command_show_overlay)\n            {\n                add_tags_from_tags_string(command.StrMain, list, list_unique_tags);\n            }\n        }\n    }\n\n    //Add tags referenced by overlays\n    for (const auto& data : m_OverlayConfigData)\n    {\n        add_tags_from_tags_string(data.ConfigStr[configid_str_overlay_tags], list, list_unique_tags);\n    }\n\n    return list;\n}\n\nvoid OverlayManager::SetCurrentOverlayNameAuto(const WindowInfo* window_info)\n{\n    SetOverlayNameAuto(m_CurrentOverlayID, window_info);\n}\n\nvoid OverlayManager::SetOverlayNameAuto(unsigned int id, const WindowInfo* window_info)\n{\n    if (id < m_OverlayConfigData.size())\n    {\n        OverlayConfigData& data = m_OverlayConfigData[id];\n\n        //Call is just silently ignored when overlay name is set to custom/user set already\n        if (data.ConfigBool[configid_bool_overlay_name_custom])\n            return;\n\n        data.ConfigNameStr = \"\";\n\n        //If override window info passed, use that\n        if (window_info != nullptr)\n        {\n            data.ConfigNameStr += StringConvertFromUTF16(window_info->GetTitle().c_str());\n            return;\n        }\n\n        switch (data.ConfigInt[configid_int_overlay_capture_source])\n        {\n            case ovrl_capsource_desktop_duplication:\n            {\n                data.ConfigNameStr += TranslationManager::Get().GetDesktopIDString(data.ConfigInt[configid_int_overlay_desktop_id]);\n                break;\n            }\n            case ovrl_capsource_winrt_capture:\n            {\n                if (data.ConfigHandle[configid_handle_overlay_state_winrt_hwnd] != 0)\n                {\n                    data.ConfigNameStr += data.ConfigStr[configid_str_overlay_winrt_last_window_title];\n                }\n                else if (data.ConfigInt[configid_int_overlay_winrt_desktop_id] != -2)\n                {\n                    data.ConfigNameStr += TranslationManager::Get().GetDesktopIDString(data.ConfigInt[configid_int_overlay_winrt_desktop_id]);\n                }\n                else\n                {\n                    data.ConfigNameStr += TranslationManager::GetString(tstr_SourceWinRTNone);\n                }\n                break;\n            }\n            case ovrl_capsource_ui:\n            {\n                data.ConfigNameStr += TranslationManager::GetString(tstr_SourcePerformanceMonitor); //So far all UI overlays are just that\n                break;\n            }\n            case ovrl_capsource_browser:\n            {\n                //Use duplication IDs' title if any is set\n                const int duplication_id = data.ConfigInt[configid_int_overlay_duplication_id];\n                const std::string& title = (duplication_id == -1) ? data.ConfigStr[configid_str_overlay_browser_title] : \n                                                                    GetConfigData((unsigned int)duplication_id).ConfigStr[configid_str_overlay_browser_title];\n\n                if (!title.empty())\n                {\n                    data.ConfigNameStr += title;\n                }\n                else\n                {\n                    data.ConfigNameStr += TranslationManager::GetString(tstr_SourceBrowserNoPage);\n                }\n                break;\n            }\n        }\n    }\n}\n\nvoid OverlayManager::SetOverlayNamesAutoForWindow(const WindowInfo& window_info)\n{\n    for (unsigned int i = 0; i < m_OverlayConfigData.size(); ++i)\n    {\n        const OverlayConfigData& data = m_OverlayConfigData[i];\n\n        if ( (data.ConfigInt[configid_int_overlay_capture_source] == ovrl_capsource_winrt_capture) && ((HWND)data.ConfigHandle[configid_handle_overlay_state_winrt_hwnd] == window_info.GetWindowHandle()) )\n        {\n            SetOverlayNameAuto(i, &window_info);\n        }\n    }\n}\n\n#endif //ifdef DPLUS_UI\n\nMatrix4 OverlayManager::GetOverlayMiddleTransform(unsigned int id, vr::VROverlayHandle_t ovrl_handle) const\n{\n    //Get handle if none was given\n    if (ovrl_handle == vr::k_ulOverlayHandleInvalid)\n    {\n        #ifdef DPLUS_UI\n            ovrl_handle = GetConfigData(id).ConfigHandle[configid_handle_overlay_state_overlay_handle];\n        #else\n            ovrl_handle = GetOverlay(id).GetHandle();\n        #endif\n\n        //Couldn't find overlay, return identity matrix\n        if (ovrl_handle == vr::k_ulOverlayHandleInvalid)\n        {\n            return Matrix4();\n        }\n    }\n\n    return GetOverlayTransformBase(ovrl_handle, id, false);\n}\n\nMatrix4 OverlayManager::GetOverlayCenterBottomTransform(unsigned int id, vr::VROverlayHandle_t ovrl_handle) const\n{\n    //Get handle if none was given\n    if (ovrl_handle == vr::k_ulOverlayHandleInvalid)\n    {\n        #ifdef DPLUS_UI\n            ovrl_handle = GetConfigData(id).ConfigHandle[configid_handle_overlay_state_overlay_handle];\n        #else\n            ovrl_handle = GetOverlay(id).GetHandle();\n        #endif\n\n        //Couldn't find overlay, return identity matrix\n        if (ovrl_handle == vr::k_ulOverlayHandleInvalid)\n        {\n            return Matrix4();\n        }\n    }\n\n    return GetOverlayTransformBase(ovrl_handle, id, true);\n}\n\nbool OverlayManager::MatchOverlayTagSingle(const char* str_tags, const char* str_single_tag)\n{\n    return MatchOverlayTagSingle(str_tags, str_tags + strlen(str_tags), str_single_tag, strlen(str_single_tag));\n}\n\nbool OverlayManager::MatchOverlayTagSingle(const char* str_tags, const char* str_tags_end, const char* str_single_tag, size_t str_single_tag_length)\n{\n    //Split input string into individual tags and compare each to the single tag\n    const char* tag_start = str_tags;\n    const char* tag_end   = nullptr;\n\n    while (tag_start < str_tags_end)\n    {\n        tag_end = (const char*)memchr(tag_start, ' ', str_tags_end - tag_start);\n\n        if (tag_end == nullptr)\n            tag_end = str_tags_end;\n\n        size_t tag_length = tag_end - tag_start;\n\n        if ((tag_length == str_single_tag_length) && (memcmp(tag_start, str_single_tag, tag_length) == 0))\n        {\n            return true;\n        }\n\n        tag_start = tag_end + 1;\n    }\n\n    return false;\n}\n\nbool OverlayManager::MatchOverlayTags(const char* str_tags_a, const char* str_tags_b, const OverlayConfigData* data_b)\n{\n    //Split string a into individual tags and compare each to string b\n    const char* str_tags_a_end = str_tags_a + strlen(str_tags_a);\n    const char* tag_a_start    = str_tags_a;\n    const char* tag_a_end      = nullptr;\n    const char* str_tags_b_end = str_tags_b + strlen(str_tags_b);\n\n    while (tag_a_start < str_tags_a_end)\n    {\n        tag_a_end = (const char*)memchr(tag_a_start, ' ', str_tags_a_end - tag_a_start);\n\n        if (tag_a_end == nullptr)\n            tag_a_end = str_tags_a_end;\n\n        size_t tag_a_length = tag_a_end - tag_a_start;\n\n        //Handle built-in auto tags\n        if (data_b != nullptr)\n        {\n            //tag_a_length doesn't count the NUL terminator, but sizeof does, so we add 1 when comparing lengths but only compare actual tag length\n            if ((tag_a_length + 1 == sizeof(\"Ovrl_All\")) && (memcmp(tag_a_start, \"Ovrl_All\", tag_a_length) == 0))\n            {\n                return true;\n            }\n            else if ((tag_a_length + 1 == sizeof(\"Ovrl_Visible\")) && (memcmp(tag_a_start, \"Ovrl_Visible\", tag_a_length) == 0))\n            {\n                if (data_b->ConfigBool[configid_bool_overlay_enabled])\n                {\n                    return true;\n                }\n            }\n            else if ((tag_a_length + 1 == sizeof(\"Ovrl_Hidden\")) && (memcmp(tag_a_start, \"Ovrl_Hidden\", tag_a_length) == 0))\n            {\n                if (!data_b->ConfigBool[configid_bool_overlay_enabled])\n                {\n                    return true;\n                }\n            }\n            else if ((tag_a_length + 1 == sizeof(\"Ovrl_Desktop\")) && (memcmp(tag_a_start, \"Ovrl_Desktop\", tag_a_length) == 0))\n            {\n                if (  (data_b->ConfigInt[configid_int_overlay_capture_source] == ovrl_capsource_desktop_duplication) || \n                    ( (data_b->ConfigInt[configid_int_overlay_capture_source] == ovrl_capsource_winrt_capture) && (data_b->ConfigInt[configid_int_overlay_winrt_desktop_id] != -2)) )\n                {\n                    return true;\n                }\n            }\n            else if ((tag_a_length + 1 == sizeof(\"Ovrl_Window\")) && (memcmp(tag_a_start, \"Ovrl_Window\", tag_a_length) == 0))\n            {\n                if ( (data_b->ConfigInt[configid_int_overlay_capture_source] == ovrl_capsource_winrt_capture) && (data_b->ConfigHandle[configid_handle_overlay_state_winrt_hwnd] != 0) )\n                {\n                    return true;\n                }\n            }\n            else if ((tag_a_length + 1 == sizeof(\"Ovrl_Browser\")) && (memcmp(tag_a_start, \"Ovrl_Browser\", tag_a_length) == 0))\n            {\n                if (data_b->ConfigInt[configid_int_overlay_capture_source] == ovrl_capsource_browser)\n                {\n                    return true;\n                }\n            }\n            else if ((tag_a_length + 1 == sizeof(\"Ovrl_PerfMon\")) && (memcmp(tag_a_start, \"Ovrl_PerfMon\", tag_a_length) == 0))\n            {\n                if (data_b->ConfigInt[configid_int_overlay_capture_source] == ovrl_capsource_ui)\n                {\n                    return true;\n                }\n            }\n        }\n\n        if (MatchOverlayTagSingle(str_tags_b, str_tags_b_end, tag_a_start, tag_a_length))\n        {\n            return true;\n        }\n\n        tag_a_start = tag_a_end + 1;\n    }\n\n    return false;\n}\n"
  },
  {
    "path": "src/Shared/OverlayManager.h",
    "content": "#pragma once\n\n#include <algorithm>\n\n#include \"ConfigManager.h\"\n\n#ifndef DPLUS_UI\n    #include \"Overlays.h\"   //UI app only deals with overlay config data\n#endif\n\nstatic const unsigned int k_ulOverlayID_None = UINT_MAX;      //Most functions return this on error, which will fall back to m_OverlayNull when requested\nstatic const int k_lOverlayOutputErrorTextureWidth  = 960;    //Unfortunately the best option is to just hardcode the size in some places\nstatic const int k_lOverlayOutputErrorTextureHeight = 540;\ntypedef std::vector<unsigned int> OverlayIDList;\n\nclass WindowInfo;\n\nclass OverlayManager\n{\n    public:\n\n        struct TagListEntry\n        {\n            std::string Tag;\n            bool IsAutoTag = false;\n        };\n\n    private:\n        #ifndef DPLUS_UI\n            std::vector<Overlay> m_Overlays;\n            Overlay m_OverlayNull;\n\n            vr::VROverlayHandle_t m_TheaterOverlayHandle;               //Handle of the dashboard overlay used for Theater Mode display\n            vr::VROverlayHandle_t m_TheaterOverlayReferenceHandle;      //Handle of the cursor overlay used as theater overlay reference transform\n            vr::VROverlayHandle_t m_CurrentTheaterOverlayOrigHandle;    //Handle of the overlay originally held by current theater overlay\n            unsigned int m_CurrentTheaterOverlayID;                     //ID of overlay the theater overlay duplicates\n        #endif\n        std::vector<OverlayConfigData> m_OverlayConfigData;\n\n        unsigned int m_CurrentOverlayID;\n        OverlayConfigData m_OverlayConfigDataNull;\n\n        Matrix4 GetOverlayTransformBase(vr::VROverlayHandle_t ovrl_handle, unsigned int id, bool add_bottom_offset) const;\n\n        #ifndef DPLUS_UI\n            void TheaterOverlayForwardCapture(const Overlay& ovrl_source);\n            void TheaterOverlayReturnCapture(const Overlay& ovrl_source);\n        #endif\n\n    public:\n        static OverlayManager& Get();\n\n        OverlayManager();\n        unsigned int DuplicateOverlay(const OverlayConfigData& data, unsigned int source_id = k_ulOverlayID_None);\n        unsigned int AddOverlay(OverlayCaptureSource capture_source, int desktop_id = -2, HWND window_handle = nullptr);\n        unsigned int AddUIOverlay();                                    //Adds UI overlay without using any base data\n        #ifndef DPLUS_UI\n            Overlay& GetOverlay(unsigned int id);                       //Returns m_OverlayNull on error\n            const Overlay& GetOverlay(unsigned int id) const;           //Returns m_OverlayNull on error\n            Overlay& GetCurrentOverlay();\n            Overlay& GetPrimaryDashboardOverlay();                      //Returns first visible overlay with dashboard origin, or m_OverlayNull if none exists\n\n            vr::VROverlayHandle_t GetTheaterOverlayHandle() const;      //Returns theater overlay handle (may be invalid if no overlay ID is assinged)\n            unsigned int GetTheaterOverlayID() const;\n            void SetTheaterOverlayID(unsigned int id);\n            void ClearTheaterOverlay(bool no_ui_update = false);\n        #endif\n        OverlayConfigData& GetConfigData(unsigned int id);\n        const OverlayConfigData& GetConfigData(unsigned int id) const;\n        OverlayConfigData& GetCurrentConfigData();\n        OverlayOriginConfig GetOriginConfigFromData(const OverlayConfigData& data) const;\n\n        unsigned int GetCurrentOverlayID() const;\n        void SetCurrentOverlayID(unsigned int id);\n\n        unsigned int FindOverlayID(vr::VROverlayHandle_t handle) const; //Returns k_ulOverlayID_None on error\n        unsigned int FindTheaterOverlayID() const;                      //Returns k_ulOverlayID_None on error\n        unsigned int GetOverlayCount() const;\n        void SwapOverlays(unsigned int id, unsigned int id2);\n        void RemoveOverlay(unsigned int id);\n        void RemoveAllOverlays();\n\n        #ifndef DPLUS_UI\n            //Returns list of inactive (not currently capturing) overlay IDs with winrt_last_* strings matching the given window\n            OverlayIDList FindInactiveOverlaysForWindow(const WindowInfo& window_info) const;\n        #endif\n\n        //Returns list of overlay IDs using source_id as duplication ID\n        OverlayIDList FindDuplicatedOverlaysForOverlay(unsigned int source_id) const;\n        //Returns list of overlay IDs that contain the given tags\n        OverlayIDList FindOverlaysWithTags(const char* str_tags) const;\n\n        void ConvertDuplicatedOverlayToStandalone(unsigned int id, bool no_reset = false);\n\n        #ifdef DPLUS_UI\n            std::vector<TagListEntry> GetKnownOverlayTagList();\n\n            void SetCurrentOverlayNameAuto(const WindowInfo* window_info = nullptr);\n            void SetOverlayNameAuto(unsigned int id, const WindowInfo* window_info = nullptr); //window_info is optional, can be passed as override for when the handle isn't stored\n            void SetOverlayNamesAutoForWindow(const WindowInfo& window_info);                  //Calls SetOverlayNameAuto() for all overlays currently using window_handle as source\n        #endif\n\n        Matrix4 GetOverlayMiddleTransform(unsigned int id, vr::VROverlayHandle_t ovrl_handle = vr::k_ulOverlayHandleInvalid) const;\n        Matrix4 GetOverlayCenterBottomTransform(unsigned int id, vr::VROverlayHandle_t ovrl_handle = vr::k_ulOverlayHandleInvalid) const;\n\n        static bool MatchOverlayTagSingle(const char* str_tags, const char* str_single_tag);\n        static bool MatchOverlayTagSingle(const char* str_tags, const char* str_tags_end, const char* str_single_tag, size_t str_single_tag_length);\n        //Returns if any tags in str a match with any in str b, optionally uses data b to match built-in auto tags from str a\n        static bool MatchOverlayTags(const char* str_tags_a, const char* str_tags_b, const OverlayConfigData* data_b = nullptr);\n};"
  },
  {
    "path": "src/Shared/Util.cpp",
    "content": "#include \"Util.h\"\n\n#include <vector>\n#include <cassert>\n\n#include <d3d11.h>\n#include <wrl/client.h>\n#include <shldisp.h>\n#include <shlobj.h>\n#include <shellapi.h>\n\nstd::string StringConvertFromUTF16(LPCWSTR str)\n{\n    int length_utf8 = ::WideCharToMultiByte(CP_UTF8, 0, str, -1, nullptr, 0, nullptr, nullptr);\n    if (length_utf8 != 0)\n    {\n        std::string str_utf8(length_utf8 - 1, '\\0');\n        if (::WideCharToMultiByte(CP_UTF8, 0, str, -1, &str_utf8[0], length_utf8, nullptr, nullptr) != 0)\n        {\n            return str_utf8;\n        }\n    }\n\n    return \"\";\n}\n\nstd::wstring WStringConvertFromUTF8(const char * str)\n{\n    int length_utf16 = ::MultiByteToWideChar(CP_UTF8, 0, str, -1, nullptr, 0);\n    if (length_utf16 != 0)\n    {\n        std::wstring str_utf16(length_utf16 - 1, L'\\0');\n        if (::MultiByteToWideChar(CP_UTF8, 0, str, -1, &str_utf16[0], length_utf16) != 0)\n        {\n            return str_utf16;\n        }\n    }\n\n    return L\"\";\n}\n\n//This is only needed for std::error_code.message(), thanks to it being in the local ANSI codepage instead of UTF-8\nstd::wstring WStringConvertFromLocalEncoding(const char* str)\n{\n    int length_utf16 = ::MultiByteToWideChar(CP_ACP, 0, str, -1, nullptr, 0);\n    if (length_utf16 != 0)\n    {\n        std::wstring str_utf16(length_utf16 - 1, L'\\0');\n        if (::MultiByteToWideChar(CP_ACP, 0, str, -1, &str_utf16[0], length_utf16) != 0)\n        {\n            return str_utf16;\n        }\n    }\n\n    return L\"\";\n}\n\nstd::wstring WStringConvertToTitleCase(LPCWSTR str)\n{\n    //We basically only use this to turn all-caps into title-case, but LCMapStringEx() will keep all-caps words as-is\n    //To work around this we convert them to lower-case first, though that technically breaks acronyms and such... which we don't deal with\n    int length_lower = ::LCMapStringEx(LOCALE_NAME_USER_DEFAULT, LCMAP_LOWERCASE, str, -1, nullptr, 0, nullptr, nullptr, 0);\n    if (length_lower != 0)\n    {\n        std::wstring str_lower(length_lower - 1, L'\\0');\n        if (::LCMapStringEx(LOCALE_NAME_USER_DEFAULT, LCMAP_LOWERCASE, str, -1, &str_lower[0], length_lower, nullptr, nullptr, 0) != 0)\n        {\n            int length_title = ::LCMapStringEx(LOCALE_NAME_USER_DEFAULT, LCMAP_TITLECASE, str_lower.c_str(), (int)str_lower.size(), nullptr, 0, nullptr, nullptr, 0);\n            if (length_title != 0)\n            {\n                std::wstring str_title(length_title, L'\\0');    //Careful: By having passed the input string length this time, the returned length doesn't include NUL\n                if (::LCMapStringEx(LOCALE_NAME_USER_DEFAULT, LCMAP_TITLECASE, str_lower.c_str(), (int)str_lower.size(), &str_title[0], length_title, nullptr, nullptr, 0) != 0)\n                {\n                    return str_title;\n                }\n            }\n        }\n    }\n\n    return str;\n}\n\nDEVMODE GetDevmodeForDisplayID(int display_id, bool wmr_ignore_vscreens, HMONITOR* hmon)\n{\n    if (display_id == -1)\n        display_id = 0;\n\n    DEVMODE mode = {0};\n    Microsoft::WRL::ComPtr<IDXGIFactory1> factory_ptr;\n\n    //This needs to go through DXGI as EnumDisplayDevices()'s order can be different\n    HRESULT hr = CreateDXGIFactory1(__uuidof(IDXGIFactory1), (void**)&factory_ptr);\n    if (!FAILED(hr))\n    {\n        Microsoft::WRL::ComPtr<IDXGIAdapter> adapter_ptr;\n        UINT i = 0;\n        int output_count = 0;\n\n        while (factory_ptr->EnumAdapters(i, &adapter_ptr) != DXGI_ERROR_NOT_FOUND)\n        {\n            //Check if this a WMR virtual display adapter and skip it when the option is enabled\n            if (wmr_ignore_vscreens)\n            {\n                DXGI_ADAPTER_DESC adapter_desc;\n                adapter_ptr->GetDesc(&adapter_desc);\n\n                if (wcscmp(adapter_desc.Description, L\"Virtual Display Adapter\") == 0)\n                {\n                    ++i;\n                    continue;\n                }\n            }\n\n            //Enum the available outputs\n            Microsoft::WRL::ComPtr<IDXGIOutput> output_ptr;\n            UINT output_index = 0;\n            while (adapter_ptr->EnumOutputs(output_index, &output_ptr) != DXGI_ERROR_NOT_FOUND)\n            {\n                //Check if this happens to be the output we're looking for\n                if (display_id == output_count)\n                {\n                    //Get devmode\n                    DXGI_OUTPUT_DESC output_desc;\n                    output_ptr->GetDesc(&output_desc);\n\n                    mode.dmSize = sizeof(DEVMODE);\n\n                    if (EnumDisplaySettings(output_desc.DeviceName, ENUM_CURRENT_SETTINGS, &mode) != FALSE)\n                    {\n                        //Set hmon if requested\n                        if (hmon != nullptr)\n                        {\n                            *hmon = output_desc.Monitor;\n                        }\n\n                        //Get out early\n                        return mode;\n                    }\n\n                    mode.dmSize = 0;    //Reset dmSize to 0 if the call failed\n                }\n\n                ++output_index;\n                ++output_count;\n            }\n\n            ++i;\n        }\n    }\n\n    //Set hmon to nullptr\n    if (hmon != nullptr)\n    {\n        *hmon = nullptr;\n    }\n\n    return mode;\n}\n\nint GetDisplayIDFromHMonitor(HMONITOR monitor_handle, bool wmr_ignore_vscreens)\n{\n    Microsoft::WRL::ComPtr<IDXGIFactory1> factory_ptr;\n\n    //We want the display/desktop ID as enumerated by DXGI\n    HRESULT hr = CreateDXGIFactory1(__uuidof(IDXGIFactory1), (void**)&factory_ptr);\n    if (!FAILED(hr))\n    {\n        Microsoft::WRL::ComPtr<IDXGIAdapter> adapter_ptr;\n        UINT i = 0;\n        int output_count = 0;\n\n        while (factory_ptr->EnumAdapters(i, &adapter_ptr) != DXGI_ERROR_NOT_FOUND)\n        {\n            //Check if this a WMR virtual display adapter and skip it when the option is enabled\n            if (wmr_ignore_vscreens)\n            {\n                DXGI_ADAPTER_DESC adapter_desc;\n                adapter_ptr->GetDesc(&adapter_desc);\n\n                if (wcscmp(adapter_desc.Description, L\"Virtual Display Adapter\") == 0)\n                {\n                    ++i;\n                    continue;\n                }\n            }\n\n            //Enum the available outputs\n            Microsoft::WRL::ComPtr<IDXGIOutput> output_ptr;\n            UINT output_index = 0;\n            while (adapter_ptr->EnumOutputs(output_index, &output_ptr) != DXGI_ERROR_NOT_FOUND)\n            {\n                //Get hmonitor and compare\n                DXGI_OUTPUT_DESC output_desc;\n                output_ptr->GetDesc(&output_desc);\n\n                if (output_desc.Monitor == monitor_handle)\n                {\n                    return output_count;\n                }\n\n                ++output_index;\n                ++output_count;\n            }\n\n            ++i;\n        }\n    }\n\n    return -1;\n}\n\nint GetMonitorRefreshRate(int display_id, bool wmr_ignore_vscreens)\n{\n    DEVMODE mode = GetDevmodeForDisplayID(display_id, wmr_ignore_vscreens);\n\n    if ( (mode.dmSize != 0) && (mode.dmFields & DM_DISPLAYFREQUENCY) ) //Something would be wrong if that field isn't supported, but let's check anyways\n    {\n        return mode.dmDisplayFrequency;\n    }\n\n    return 60;\t//Fallback value\n}\n\nvoid CenterRectToMonitor(LPRECT prc)\n{\n    HMONITOR    hmonitor;\n    MONITORINFO mi;\n    RECT        rc;\n    int         w = prc->right  - prc->left;\n    int         h = prc->bottom - prc->top;\n\n    //Get the nearest monitor to the passed rect\n    hmonitor = ::MonitorFromRect(prc, MONITOR_DEFAULTTONEAREST);\n\n    //Get monitor rect\n    mi.cbSize = sizeof(mi);\n    ::GetMonitorInfo(hmonitor, &mi);\n\n    rc = mi.rcMonitor;\n\n    //Center the passed rect to the monitor rect \n    prc->left   = rc.left + (rc.right  - rc.left - w) / 2;\n    prc->top    = rc.top  + (rc.bottom - rc.top  - h) / 2;\n    prc->right  = prc->left + w;\n    prc->bottom = prc->top  + h;\n}\n\nvoid CenterWindowToMonitor(HWND hwnd, bool use_cursor_pos)\n{\n    RECT rc;\n    ::GetWindowRect(hwnd, &rc);\n\n    HMONITOR    hmonitor;\n    MONITORINFO mi;\n    RECT rcm;\n    int w = rc.right  - rc.left;\n    int h = rc.bottom - rc.top;\n\n    if (use_cursor_pos) //Cursor position is used to determine the screen to center on\n    {\n        POINT mouse_pos = {0};\n        ::GetCursorPos(&mouse_pos); \n        RECT mouse_rc;\n        mouse_rc.left   = mouse_pos.x;\n        mouse_rc.right  = mouse_pos.x;\n        mouse_rc.top    = mouse_pos.y;\n        mouse_rc.bottom = mouse_pos.y;\n\n        hmonitor = ::MonitorFromRect(&mouse_rc, MONITOR_DEFAULTTONEAREST);\n    }\n    else\n    {\n        //Get the nearest monitor to the passed rect\n        hmonitor = ::MonitorFromRect(&rc, MONITOR_DEFAULTTONEAREST);\n    }\n\n    //Get monitor rect\n    mi.cbSize = sizeof(mi);\n    ::GetMonitorInfo(hmonitor, &mi);\n\n    rcm = mi.rcMonitor;\n\n    //Center the passed rect to the monitor rect \n    rc.left   = rcm.left + (rcm.right  - rcm.left - w) / 2;\n    rc.top    = rcm.top  + (rcm.bottom - rcm.top  - h) / 2;\n    rc.right  = rc.left + w;\n    rc.bottom = rc.top  + h;\n\n    ::SetWindowPos(hwnd, nullptr, rc.left, rc.top, 0, 0, SWP_NOSIZE | SWP_NOZORDER | SWP_NOACTIVATE);\n}\n\nvoid ForceScreenRefresh()\n{\n    //This is a hacky workaround for occasionally not getting a full desktop image after resetting duplication until a screen change occurs\n    //For secondary screens that could possibly not happen until manual user interaction, so instead we force the desktop to redraw itself\n    //Unproblematic, but proper fix would be welcome too\n    if (HWND shell_window = ::GetShellWindow())\n        ::SendMessage(shell_window, WM_SETTINGCHANGE, 0, 0); \n}\n\nbool IsProcessElevated() \n{\n    TOKEN_ELEVATION elevation;\n    DWORD cb_size = sizeof(TOKEN_ELEVATION);\n\n    if (::GetTokenInformation(::GetCurrentProcessToken(), TokenElevation, &elevation, sizeof(elevation), &cb_size) ) \n    {\n        return elevation.TokenIsElevated;\n    }\n\n    return false;\n}\n\nbool IsProcessElevated(DWORD process_id) \n{\n    bool ret = false;\n    HANDLE handle_window_process = ::OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, FALSE, process_id);\n    if (handle_window_process != nullptr)\n    {\n        HANDLE handle_token = nullptr;\n        if (::OpenProcessToken(handle_window_process, TOKEN_QUERY, &handle_token))\n        {\n            TOKEN_ELEVATION elevation;\n            DWORD cb_size = sizeof(TOKEN_ELEVATION);\n\n            if (::GetTokenInformation(handle_token, TokenElevation, &elevation, sizeof(elevation), &cb_size))\n            {\n                ret = elevation.TokenIsElevated;\n            }\n        }\n\n        if (handle_token) \n        {\n            CloseHandle(handle_token);\n        }\n\n        CloseHandle(handle_window_process);\n    }\n\n    return ret;\n}\n\nbool ShellExecuteUnelevated(LPCWSTR lpFile, LPCWSTR lpParameters, LPCWSTR lpDirectory, LPCWSTR lpOperation, INT nShowCmd)\n{\n    //This function will fail if explorer.exe is not running, but it could be argued that this scenario is not exactly the sanest for a desktop viewing application\n    //Elevated mode should be avoided in the first place to be fair\n\n    //Use desktop automation to get the active shell view for the desktop\n    Microsoft::WRL::ComPtr<IShellView> shell_view;\n    Microsoft::WRL::ComPtr<IShellWindows> shell_windows;\n    HRESULT hr = ::CoCreateInstance(CLSID_ShellWindows, nullptr, CLSCTX_LOCAL_SERVER, IID_IShellWindows, &shell_windows);\n\n    if (SUCCEEDED(hr))\n    {\n        HWND hwnd = nullptr;\n        Microsoft::WRL::ComPtr<IDispatch> dispatch;\n        VARIANT v_empty = {};\n\n        if (shell_windows->FindWindowSW(&v_empty, &v_empty, SWC_DESKTOP, (long*)&hwnd, SWFO_NEEDDISPATCH, &dispatch) == S_OK)\n        {\n            Microsoft::WRL::ComPtr<IServiceProvider> service_provider;\n            hr = dispatch.As(&service_provider);\n\n            if (SUCCEEDED(hr))\n            {\n                Microsoft::WRL::ComPtr<IShellBrowser> shell_browser;\n                hr = service_provider->QueryService(SID_STopLevelBrowser, __uuidof(IShellBrowser), (void**)&shell_browser);\n\n                if (SUCCEEDED(hr))\n                {\n                    hr = shell_browser->QueryActiveShellView(&shell_view);\n                }\n            }\n        }\n        else\n        {\n            hr = E_FAIL;\n        }\n    }\n\n    if (FAILED(hr))\n        return false;\n\n    //Use the shell view to get the shell dispatch interface\n    Microsoft::WRL::ComPtr<IShellDispatch2> shell_dispatch2;\n    Microsoft::WRL::ComPtr<IDispatch> dispatch_background;\n    hr = shell_view->GetItemObject(SVGIO_BACKGROUND, __uuidof(IDispatch), (void**)&dispatch_background);\n    if (SUCCEEDED(hr))\n    {\n        Microsoft::WRL::ComPtr<IShellFolderViewDual> shell_folderview_dual;\n        hr = dispatch_background.As(&shell_folderview_dual);\n\n        if (SUCCEEDED(hr))\n        {\n            Microsoft::WRL::ComPtr<IDispatch> dispatch;\n            hr = shell_folderview_dual->get_Application(&dispatch);\n\n            if (SUCCEEDED(hr))\n            {\n                hr = dispatch.As(&shell_dispatch2);\n            }\n        }\n    }\n\n    if (FAILED(hr))\n        return false;\n\n    //Use the shell dispatch interface to call ShellExecuteW() in the explorer process, which is running unelevated in most cases\n    BSTR bstr_file = ::SysAllocString(lpFile);\n    hr = (bstr_file != nullptr) ? S_OK : E_OUTOFMEMORY;\n\n    if (SUCCEEDED(hr))\n    {\n        VARIANT v_args = {};\n        VARIANT v_dir = {};\n        VARIANT v_operation = {};\n        VARIANT v_show = {};\n        v_show.vt = VT_I4;\n        v_show.intVal = nShowCmd;\n\n        //Optional parameters (SysAllocString() returns nullptr on nullptr input)\n        v_args.bstrVal      = ::SysAllocString(lpParameters);\n        v_dir.bstrVal       = ::SysAllocString(lpDirectory);\n        v_operation.bstrVal = ::SysAllocString(lpOperation);\n        v_args.vt       = (v_args.bstrVal != nullptr)      ? VT_BSTR : VT_EMPTY;\n        v_dir.vt        = (v_dir.bstrVal != nullptr)       ? VT_BSTR : VT_EMPTY;\n        v_operation.vt  = (v_operation.bstrVal != nullptr) ? VT_BSTR : VT_EMPTY;\n\n        hr = shell_dispatch2->ShellExecuteW(bstr_file, v_args, v_dir, v_operation, v_show);\n\n        ::SysFreeString(bstr_file);\n        ::SysFreeString(v_args.bstrVal);\n        ::SysFreeString(v_dir.bstrVal);\n        ::SysFreeString(v_operation.bstrVal);\n\n        return true;\n    }\n\n    return false;\n}\n\nbool FileExists(LPCTSTR path)\n{\n    DWORD attrib = GetFileAttributes(path);\n\n    return ((attrib != INVALID_FILE_ATTRIBUTES) && !(attrib & FILE_ATTRIBUTE_DIRECTORY));\n}\n\nbool DirectoryExists(LPCTSTR path)\n{\n    DWORD attrib = GetFileAttributes(path);\n\n    return ((attrib != INVALID_FILE_ATTRIBUTES) && (attrib & FILE_ATTRIBUTE_DIRECTORY));\n}\n\nvoid StopProcessByWindowClass(LPCTSTR class_name)\n{\n    //Try to close it gracefully first so it can save the config\n    if (HWND window_handle = ::FindWindow(class_name, nullptr))\n    {\n        ::PostMessage(window_handle, WM_QUIT, 0, 0);\n    }\n\n    ULONGLONG start_tick = ::GetTickCount64();\n\n    while ( (::FindWindow(class_name, nullptr) != nullptr) && (::GetTickCount64() - start_tick < 3000) ) //Wait 3 seconds max\n    {\n        Sleep(5); //Should be usually quick though, so don't wait around too long\n    }\n\n    //Still running? Time to kill it\n    if (HWND window_handle = ::FindWindow(class_name, nullptr))\n    {\n        DWORD pid;\n        ::GetWindowThreadProcessId(window_handle, &pid);\n\n        HANDLE phandle;\n        phandle = ::OpenProcess(SYNCHRONIZE | PROCESS_TERMINATE, TRUE, pid);\n\n        if (phandle != nullptr)\n        {\n            ::TerminateProcess(phandle, 0);\n            ::WaitForSingleObject(phandle, INFINITE);\n            ::CloseHandle(phandle);\n        }\n    }\n}\n\nHWND FindMainWindow(DWORD pid)\n{\n    std::pair<HWND, DWORD> params = {nullptr, pid};\n\n    //Enumerate the windows using a lambda to process each window\n    BOOL bResult = ::EnumWindows([](HWND hwnd, LPARAM lParam) -> BOOL \n                                 {\n                                     auto pParams = (std::pair<HWND, DWORD>*)(lParam);\n\n                                     DWORD processId;\n                                     if ( (::GetWindowThreadProcessId(hwnd, &processId)) && (processId == pParams->second) )\n                                     {\n                                         //If it's an unowned top-level window and visible, it's assumed to be the main window\n                                         //Take the first match in the process, should be good enough for our use-case\n                                         if ( (::GetWindow(hwnd, GW_OWNER) == (HWND)0) && (::IsWindowVisible(hwnd)) )\n                                         {\n                                             //Stop enumerating\n                                             pParams->first = hwnd;\n                                             return FALSE;\n                                         }\n                                     }\n\n                                      //Continue enumerating\n                                      return TRUE;\n                                  },\n                                  (LPARAM)&params);\n\n    if ((!bResult) && (params.first != nullptr))\n    {\n        return params.first;\n    }\n\n    return 0;\n}\n\nunsigned int GetKeyboardModifierState()\n{\n    unsigned int modifiers = 0;\n\n    if (::GetAsyncKeyState(VK_SHIFT) < 0)\n        modifiers |= MOD_SHIFT;\n    if (::GetAsyncKeyState(VK_CONTROL) < 0)\n        modifiers |= MOD_CONTROL;\n    if (::GetAsyncKeyState(VK_MENU) < 0)\n        modifiers |= MOD_ALT;\n    if ((::GetAsyncKeyState(VK_LWIN) < 0) || (::GetAsyncKeyState(VK_RWIN) < 0))\n        modifiers |= MOD_WIN;\n\n    return modifiers;\n}\n\nvoid StringReplaceAll(std::string& source, const std::string& from, const std::string& to)\n{\n    std::string new_string;\n    new_string.reserve(source.length());\n\n    std::string::size_type last_pos = 0;\n    std::string::size_type find_pos;\n\n    while ((find_pos = source.find(from, last_pos)) != std::string::npos)\n    {\n        new_string.append(source, last_pos, find_pos - last_pos);\n        new_string += to;\n        last_pos = find_pos + from.length();\n    }\n\n    //Append the remaining string\n    new_string.append(source, last_pos, source.length() - last_pos);\n\n    source.swap(new_string);\n}\n\nvoid WStringReplaceAll(std::wstring& source, const std::wstring& from, const std::wstring& to)\n{\n    std::wstring new_string;\n    new_string.reserve(source.length());\n\n    std::wstring::size_type last_pos = 0;\n    std::wstring::size_type find_pos;\n\n    while ((find_pos = source.find(from, last_pos)) != std::wstring::npos)\n    {\n        new_string.append(source, last_pos, find_pos - last_pos);\n        new_string += to;\n        last_pos = find_pos + from.length();\n    }\n\n    //Append the remaining string\n    new_string.append(source, last_pos, source.length() - last_pos);\n\n    source.swap(new_string);\n}\n\nbool IsWCharInvalidForFileName(wchar_t wchar)\n{\n    if ( (wchar < 32) || ( (wchar < 256) && (strchr(\"<>:\\\"/\\\\|?*\", (char)wchar) != nullptr) ) )\n        return true;\n\n    return false;\n}\n\nvoid SanitizeFileNameWString(std::wstring& str)\n{\n    //This doesn't care about reserved names, but neither do we where this is used\n    str.erase( std::remove_if(str.begin(), str.end(), IsWCharInvalidForFileName), str.end() );\n}\n\nbool WStringCompareNatural(std::wstring& str1, std::wstring& str2)\n{\n    return (::CompareStringEx(LOCALE_NAME_USER_DEFAULT, LINGUISTIC_IGNORECASE | SORT_DIGITSASNUMBERS, \n                              str1.c_str(), (int)str1.size(), str2.c_str(), (int)str2.size(), nullptr, nullptr, 0) == CSTR_LESS_THAN);\n}\n\n//This ain't pretty, but GetKeyNameText() works with scancodes, which are not exactly the same and the output strings aren't that nice either (and always localized)\n//Should this be translatable?\nconst char* g_VK_name[256] = \n{\n    \"[None]\",\n    \"Left Mouse\",\n    \"Right Mouse\",\n    \"Control Break\",\n    \"Middle Mouse\",\n    \"X1 Mouse\",\n    \"X2 Mouse\",\n    \"[Undefined] (7)\",\n    \"Backspace\",\n    \"Tab\",\n    \"[Reserved] (10)\",\n    \"[Reserved] (11)\",\n    \"Clear\",\n    \"Enter\",\n    \"[Undefined] (14)\",\n    \"[Undefined] (15)\",\n    \"Shift\",\n    \"Ctrl\",\n    \"Alt\",\n    \"Pause\",\n    \"Caps Lock\",\n    \"IME Kana\",\n    \"[Undefined] (22)\",\n    \"IME Junja\",\n    \"IME Final\",\n    \"IME Kanji\",\n    \"[Undefined] (26)\",\n    \"Esc\",\n    \"IME Convert\",\n    \"IME Non Convert\",\n    \"IME Accept\",\n    \"IME Mode Change\",\n    \"Space\",\n    \"Page Up\",\n    \"Page Down\",\n    \"End\",\n    \"Home\",\n    \"Left Arrow\",\n    \"Up Arrow\",\n    \"Right Arrow\",\n    \"Down Arrow\",\n    \"Select\",\n    \"Print\",\n    \"Execute\",\n    \"Print-Screen\",\n    \"Insert\",\n    \"Delete\",\n    \"Help\",\n    \"0\",  //0x30 - 0x5A are ASCII equivalent, but we want iterate this array directly for listing too\n    \"1\",\n    \"2\",\n    \"3\",\n    \"4\",\n    \"5\",\n    \"6\",\n    \"7\",\n    \"8\",\n    \"9\",\n    \"[Undefined] (58)\",\n    \"[Undefined] (59)\",\n    \"[Undefined] (60)\",\n    \"[Undefined] (61)\",\n    \"[Undefined] (62)\",\n    \"[Undefined] (63)\",\n    \"[Undefined] (64)\",\n    \"A\",\n    \"B\",\n    \"C\",\n    \"D\",\n    \"E\",\n    \"F\",\n    \"G\",\n    \"H\",\n    \"I\",\n    \"J\",\n    \"K\",\n    \"L\",\n    \"M\",\n    \"N\",\n    \"O\",\n    \"P\",\n    \"Q\",\n    \"R\",\n    \"S\",\n    \"T\",\n    \"U\",\n    \"V\",\n    \"W\",\n    \"X\",\n    \"Y\",\n    \"Z\",\n    \"Left Windows\",\n    \"Right Windows\",\n    \"Context Menu\",\n    \"[Reserved] (94)\",\n    \"Sleep\",\n    \"Numpad 0\",\n    \"Numpad 1\",\n    \"Numpad 2\",\n    \"Numpad 3\",\n    \"Numpad 4\",\n    \"Numpad 5\",\n    \"Numpad 6\",\n    \"Numpad 7\",\n    \"Numpad 8\",\n    \"Numpad 9\",\n    \"Numpad Multiply\",\n    \"Numpad Add\",\n    \"Separator\",\n    \"Numpad Subtract\",\n    \"Numpad Decimal\",\n    \"Numpad Divide\",\n    \"F1\",\n    \"F2\",\n    \"F3\",\n    \"F4\",\n    \"F5\",\n    \"F6\",\n    \"F7\",\n    \"F8\",\n    \"F9\",\n    \"F10\",\n    \"F11\",\n    \"F12\",\n    \"F13\",\n    \"F14\",\n    \"F15\",\n    \"F16\",\n    \"F17\",\n    \"F18\",\n    \"F19\",\n    \"F20\",\n    \"F21\",\n    \"F22\",\n    \"F23\",\n    \"F24\",\n    \"[Unassigned] (136)\",\n    \"[Unassigned] (137)\",\n    \"[Unassigned] (138)\",\n    \"[Unassigned] (139)\",\n    \"[Unassigned] (140)\",\n    \"[Unassigned] (141)\",\n    \"[Unassigned] (142)\",\n    \"[Unassigned] (143)\",\n    \"Num Lock\",\n    \"Scroll Lock\",\n    \"OEM 1\",\n    \"OEM 2\",\n    \"OEM 3\",\n    \"OEM 4\",\n    \"OEM 5\",\n    \"[Unassigned] (151)\",\n    \"[Unassigned] (152)\",\n    \"[Unassigned] (153)\",\n    \"[Unassigned] (154)\",\n    \"[Unassigned] (155)\",\n    \"[Unassigned] (156)\",\n    \"[Unassigned] (157)\",\n    \"[Unassigned] (158)\",\n    \"[Unassigned] (159)\",\n    \"Left Shift\",\n    \"Right Shift\",\n    \"Left Ctrl\",\n    \"Right Ctrl\",\n    \"Left Alt\",\n    \"Right Alt\",\n    \"Browser Back\",\n    \"Browser Forward\",\n    \"Browser Refresh\",\n    \"Browser Stop\",\n    \"Browser Search\",\n    \"Browser Favorites\",\n    \"Browser Home\",\n    \"Volume Mute\",\n    \"Volume Down\",\n    \"Volume Up\",\n    \"Media Next\",\n    \"Media Previous\",\n    \"Media Stop\",\n    \"Media Play/Pause\",\n    \"Launch Mail\",\n    \"Select Media\",\n    \"Launch Application 1\",\n    \"Launch Application 2\",\n    \"[Reserved] (184)\",\n    \"[Reserved] (185)\",\n    \"[Layout-Specific 1] (186)\",\n    \"+\",\n    \",\",\n    \"-\",\n    \".\",\n    \"[Layout-Specific 2] (191)\",\n    \"[Layout-Specific 3] (192)\",\n    \"[Reserved] (193)\",\n    \"[Reserved] (194)\",\n    \"[Reserved] (195)\",\n    \"[Reserved] (196)\",\n    \"[Reserved] (197)\",\n    \"[Reserved] (198)\",\n    \"[Reserved] (199)\",\n    \"[Reserved] (200)\",\n    \"[Reserved] (201)\",\n    \"[Reserved] (202)\",\n    \"[Reserved] (203)\",\n    \"[Reserved] (204)\",\n    \"[Reserved] (205)\",\n    \"[Reserved] (206)\",\n    \"[Reserved] (207)\",\n    \"[Reserved] (208)\",\n    \"[Reserved] (209)\",\n    \"[Reserved] (210)\",\n    \"[Reserved] (211)\",\n    \"[Reserved] (212)\",\n    \"[Reserved] (213)\",\n    \"[Reserved] (214)\",\n    \"[Reserved] (215)\",\n    \"[Unassigned] (216)\",\n    \"[Unassigned] (217)\",\n    \"[Unassigned] (218)\",\n    \"[Layout-Specific 4] (219)\",\n    \"[Layout-Specific 5] (220)\",\n    \"[Layout-Specific 6] (221)\",\n    \"[Layout-Specific 7] (222)\",\n    \"[Layout-Specific 8] (223)\",\n    \"[Reserved] (224)\",\n    \"[Reserved] (225)\",\n    \"[Layout-Specific 102] (226)\", //Big jump, but that's VK_OEM_102, so dunno\n    \"OEM 6\",\n    \"OEM 7\",\n    \"IME Process\",\n    \"OEM 8\",\n    \"Unicode Packet\",\n    \"[Unassigned] (232)\",\n    \"OEM 9\",\n    \"OEM 10\",\n    \"OEM 11\",\n    \"OEM 12\",\n    \"OEM 13\",\n    \"OEM 14\",\n    \"OEM 15\",\n    \"OEM 16\",\n    \"OEM 17\",\n    \"OEM 18\",\n    \"OEM 19\",\n    \"OEM 20\",\n    \"OEM 21\",\n    \"Attn\",\n    \"CrSel\",\n    \"ExSel\",\n    \"Erase EOF\",\n    \"Play\",\n    \"Zoom\",\n    \"NoName\",\n    \"PA1\",\n    \"OEM Clear\",\n    \"[Unassigned] (255)\",\n};\n\n//Attempt at making a list of indicies to sort the key codes in a way an end-user would make expect them in, leaving the obscure stuff at the end.\nconst unsigned char g_VK_name_order_list[256] = \n{ 0, 1, 2, 4, 5, 6, 27, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 44, 145, 19, 8, 9, 13, 20, 16, 17, 18, 160, 161, 162, 163, 164, 165, \n91, 92, 93, 32, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, \n89, 90, 187, 189, 190, 188, 45, 46, 36, 35, 33, 34, 37, 38, 39, 40, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 144, 107, 109, 106, 111, 110, 167, 166,\n168, 169, 170, 171, 172, 173, 175, 174, 176, 177, 179, 178, 180, 181, 182, 183, 186, 191, 192, 219, 220, 221, 222, 223, 226, 146, 147, 148, 149, 150, 227,\n228, 230, 233, 234, 235, 236, 237, 238, 239, 240, 241, 242, 243, 244, 245, 124, 125, 126, 127, 128, 129, 130, 131, 132, 133, 134, 135, 21, 23, 25, 28, 29,\n30, 31, 229, 3, 95, 12, 41, 42, 43, 47, 108, 246, 247, 248, 249, 250, 251, 252, 253, 254, 231, 7, 14, 15, 22, 26, 58, 59, 60, 61, 62, 63, 64, 24, 136, 137,\n138, 139, 140, 141, 142, 143, 151, 152, 153, 154, 155, 156, 157, 158, 159, 216, 217, 218, 232, 255, 10, 11, 94, 184, 185, 193, 194, 195, 196, 197, 198, 199,\n200, 201, 202, 203, 204, 205, 206, 207, 208, 209, 210, 211, 212, 213, 214, 215, 224, 225 };\n\n//VKs that are layout specific and are attempted to get their name in their current layout set in the processed name list\nconst unsigned char g_VK_layout_specific_list[] = {186, 191, 192, 219, 220, 221, 222, 223, 226};\n\nstd::vector<std::string> g_VK_name_processed;\n\nstd::wstring GetCurrentLayoutKeyNameForKeyCode(unsigned char keycode)\n{\n    //GetKeyNameTextW() wants scancodes, so get that and fill out the lparam bits expected\n    UINT scancode = ::MapVirtualKey(keycode, MAPVK_VK_TO_VSC);\n    UINT lparam = (scancode << 16);\n\n    //Additional extended bit if the scancode is extended (why tho)\n    BYTE highbyte = HIBYTE(scancode);\n    bool is_extended = ((highbyte == 0xe0) || (highbyte == 0xe1));\n    if (is_extended) \n        lparam |= (1 << 24);\n\n    wchar_t buffer[128] = {0};\n    int written_length = ::GetKeyNameTextW((LONG)lparam, buffer, 128);\n    if (written_length != 0)\n    {\n        //GetKeyNameTextW() output ALL LOOKS LIKE THIS, which is kind of grating to look at, so convert to title case\n        //Though we don't do it for single character ones as capitalizing special characters can have unexpected results and wouldn't make a difference for normal letters anyways\n        if (written_length > 1)\n        {\n            return WStringConvertToTitleCase(buffer);\n        }\n        else\n        {\n            return std::wstring(buffer, written_length);\n        }\n    }\n\n    //This could go deeper in terms of fallbacks, but we only really care about a specific set of keys for this\n    return L\"\";\n}\n\nvoid ProcessKeyCodeNameList()\n{\n    g_VK_name_processed.clear();\n    g_VK_name_processed.reserve(256);\n    for (size_t keycode = 0; keycode < 256; ++keycode)\n    {\n        g_VK_name_processed.push_back(g_VK_name[keycode]);\n    }\n\n    WStringConvertToTitleCase(L\"\");\n\n    int specific_id = 1;\n    for (unsigned char keycode : g_VK_layout_specific_list)\n    {\n        std::wstring wstr = GetCurrentLayoutKeyNameForKeyCode(keycode);\n\n        if (!wstr.empty())\n        {\n            //Special case for VK_OEM_102, the others are encountered sequentially (but not sequential as keycodes...) and before this so this is okay\n            if (keycode == VK_OEM_102)\n            {\n                specific_id = 102;\n            }\n\n            //Replicate list entry but with the newly learned key name in it\n            g_VK_name_processed[keycode]  = \"[Layout Specific \" + std::to_string(specific_id) + \"] \" + StringConvertFromUTF16(wstr.c_str()) + \" (\" + std::to_string((int)keycode) + \")\";\n        }\n\n        ++specific_id;\n    }\n}\n\nconst char* GetStringForKeyCode(unsigned char keycode)\n{\n    if (g_VK_name_processed.empty())\n        ProcessKeyCodeNameList();\n\n    assert(g_VK_name_processed.size() == 256);\n\n    return g_VK_name_processed[keycode].c_str();\n}\n\nunsigned char GetKeyCodeForListID(unsigned char list_id)\n{\n    return g_VK_name_order_list[list_id];\n}"
  },
  {
    "path": "src/Shared/Util.h",
    "content": "//Some misc utility functions shared between both applications\n\n#pragma once\n\n#include <algorithm>\n#include <string>\n\n#ifndef NOMINMAX\n    #define NOMINMAX\n#endif\n#include <windows.h>\n#include <d3d11.h>\n\n#include \"Matrices.h\"\n\n//String conversion functions using win32's conversion. Return empty string on failure\nstd::string StringConvertFromUTF16(LPCWSTR str);\nstd::wstring WStringConvertFromUTF8(const char* str);\nstd::wstring WStringConvertFromLocalEncoding(const char* str);\nstd::wstring WStringConvertToTitleCase(LPCWSTR str);\n\n//Algorithm helpers\ntemplate <typename T> T clamp(const T& value, const T& value_min, const T& value_max) \n{\n    return std::max(value_min, std::min(value, value_max));\n}\n\ntemplate <typename T> int sgn(T val)\n{ \n    return (T(0) < val) - (val < T(0));\n}\n\ntemplate <typename T_out, typename T_in> inline typename std::enable_if_t<std::is_trivially_copyable_v<T_out> && std::is_trivially_copyable_v<T_in>, T_out> pun_cast(const T_in& value)\n{\n    //Do type punning, but actually legal\n    T_out value_out = T_out(0);\n    std::memcpy(&value_out, &value, (sizeof(value_out) < sizeof(value)) ? sizeof(value_out) : sizeof(value) );\n    return value_out;\n}\n\ninline float smoothstep(float step, float value_min, float value_max)\n{\n    return ((step) * (step) * (3 - 2 * (step))) * (value_max - value_min) + value_min;\n}\n\ninline float lin2log(float value_normalized)\n{\n    return value_normalized * (logf(value_normalized + 1.0f) / logf(2.0f));\n}\n\n//64-bit packed value helpers for window messages (we don't support building for 32-bit so this is fine)\n#define MAKEQWORD(a, b)     ((DWORD64)(((DWORD)(((DWORD64)(a)) & 0xffffffff)) | ((DWORD64)((DWORD)(((DWORD64)(b)) & 0xffffffff))) << 32))\n#define LODWORD(qw)         ((DWORD)(qw))\n#define HIDWORD(qw)         ((DWORD)(((qw) >> 32) & 0xffffffff))\n\n//Display stuff\nDEVMODE GetDevmodeForDisplayID(int display_id, bool wmr_ignore_vscreens, HMONITOR* hmon = nullptr); //DEVMODE.dmSize != 0 on success\nint GetDisplayIDFromHMonitor(HMONITOR monitor_handle, bool wmr_ignore_vscreens);                    //Returns -1 on failure\nint GetMonitorRefreshRate(int display_id, bool wmr_ignore_vscreens);\nvoid CenterRectToMonitor(LPRECT prc);\nvoid CenterWindowToMonitor(HWND hwnd, bool use_cursor_pos = false);\nvoid ForceScreenRefresh();\n\n//Misc\nbool IsProcessElevated();\nbool IsProcessElevated(DWORD process_id);\nbool ShellExecuteUnelevated(LPCWSTR lpFile, LPCWSTR lpParameters = nullptr, LPCWSTR lpDirectory = nullptr, LPCWSTR lpOperation = nullptr, INT nShowCmd = SW_SHOWNORMAL);\nbool FileExists(LPCTSTR path);\nbool DirectoryExists(LPCTSTR path);\nvoid StopProcessByWindowClass(LPCTSTR class_name); //Used to stop the previous instance of the application\nHWND FindMainWindow(DWORD pid);\nunsigned int GetKeyboardModifierState();\nvoid StringReplaceAll(std::string& source, const std::string& from, const std::string& to);\nvoid WStringReplaceAll(std::wstring& source, const std::wstring& from, const std::wstring& to);\nbool IsWCharInvalidForFileName(wchar_t wchar);\nvoid SanitizeFileNameWString(std::wstring& str);\nbool WStringCompareNatural(std::wstring& str1, std::wstring& str2);\n\n//Virtual Keycode string mapping\nconst char* GetStringForKeyCode(unsigned char keycode);\nunsigned char GetKeyCodeForListID(unsigned char list_id);  //Used for a more natural sort when doing direct listing"
  },
  {
    "path": "src/Shared/Vectors.h",
    "content": "// Desktop+: Extended with Vector2Int and for OpenVR compatibility \n\n///////////////////////////////////////////////////////////////////////////////\n// Vectors.h\n// =========\n// 2D/3D/4D vectors\n//\n//  AUTHOR: Song Ho Ahn (song.ahn@gmail.com)\n// CREATED: 2007-02-14\n// UPDATED: 2013-01-20\n//\n// Copyright (C) 2007-2013 Song Ho Ahn\n///////////////////////////////////////////////////////////////////////////////\n\n\n#ifndef VECTORS_H_DEF\n#define VECTORS_H_DEF\n\n#include <cmath>\n#include <iostream>\n#include \"openvr.h\"\n\n///////////////////////////////////////////////////////////////////////////////\n// 2D vector\n///////////////////////////////////////////////////////////////////////////////\nstruct Vector2\n{\n    float x;\n    float y;\n\n    // ctors\n    Vector2() : x(0), y(0) {};\n    Vector2(float x, float y) : x(x), y(y) {};\n\n    // utils functions\n    void        set(float x, float y);\n    float       length() const;                         //\n    float       distance(const Vector2& vec) const;     // distance between two vectors\n    Vector2&    normalize();                            //\n    float       dot(const Vector2& vec) const;          // dot product\n    bool        equal(const Vector2& vec, float e) const; // compare with epsilon\n\n    // operators\n    Vector2     operator-() const;                      // unary operator (negate)\n    Vector2     operator+(const Vector2& rhs) const;    // add rhs\n    Vector2     operator-(const Vector2& rhs) const;    // subtract rhs\n    Vector2&    operator+=(const Vector2& rhs);         // add rhs and update this object\n    Vector2&    operator-=(const Vector2& rhs);         // subtract rhs and update this object\n    Vector2     operator*(const float scale) const;     // scale\n    Vector2     operator*(const Vector2& rhs) const;    // multiply each element\n    Vector2&    operator*=(const float scale);          // scale and update this object\n    Vector2&    operator*=(const Vector2& rhs);         // multiply each element and update this object\n    Vector2     operator/(const float scale) const;     // inverse scale\n    Vector2&    operator/=(const float scale);          // scale and update this object\n    bool        operator==(const Vector2& rhs) const;   // exact compare, no epsilon\n    bool        operator!=(const Vector2& rhs) const;   // exact compare, no epsilon\n    bool        operator<(const Vector2& rhs) const;    // comparison for sort\n    float       operator[](int index) const;            // subscript operator v[0], v[1]\n    float&      operator[](int index);                  // subscript operator v[0], v[1]\n\n    friend Vector2 operator*(const float a, const Vector2 vec);\n    friend std::ostream& operator<<(std::ostream& os, const Vector2& vec);\n};\n\n\n\n///////////////////////////////////////////////////////////////////////////////\n// 3D vector\n///////////////////////////////////////////////////////////////////////////////\nstruct Vector3\n{\n    float x;\n    float y;\n    float z;\n\n    // ctors\n    Vector3() : x(0), y(0), z(0) {};\n    Vector3(float x, float y, float z) : x(x), y(y), z(z) {};\n    Vector3(vr::HmdVector3_t hmd_v) : x(hmd_v.v[0]), y(hmd_v.v[1]), z(hmd_v.v[2]) {};\n\n    // utils functions\n    void        set(float x, float y, float z);\n    float       length() const;                         //\n    float       distance(const Vector3& vec) const;     // distance between two vectors\n    Vector3&    normalize();                            //\n    float       dot(const Vector3& vec) const;          // dot product\n    Vector3     cross(const Vector3& vec) const;        // cross product\n    bool        equal(const Vector3& vec, float e) const; // compare with epsilon\n\n    // operators\n    Vector3     operator-() const;                      // unary operator (negate)\n    Vector3     operator+(const Vector3& rhs) const;    // add rhs\n    Vector3     operator-(const Vector3& rhs) const;    // subtract rhs\n    Vector3&    operator+=(const Vector3& rhs);         // add rhs and update this object\n    Vector3&    operator-=(const Vector3& rhs);         // subtract rhs and update this object\n    Vector3     operator*(const float scale) const;     // scale\n    Vector3     operator*(const Vector3& rhs) const;    // multiplay each element\n    Vector3&    operator*=(const float scale);          // scale and update this object\n    Vector3&    operator*=(const Vector3& rhs);         // product each element and update this object\n    Vector3     operator/(const float scale) const;     // inverse scale\n    Vector3&    operator/=(const float scale);          // scale and update this object\n    bool        operator==(const Vector3& rhs) const;   // exact compare, no epsilon\n    bool        operator!=(const Vector3& rhs) const;   // exact compare, no epsilon\n    bool        operator<(const Vector3& rhs) const;    // comparison for sort\n    float       operator[](int index) const;            // subscript operator v[0], v[1]\n    float&      operator[](int index);                  // subscript operator v[0], v[1]\n\n    friend Vector3 operator*(const float a, const Vector3 vec);\n    friend std::ostream& operator<<(std::ostream& os, const Vector3& vec);\n};\n\n\n\n///////////////////////////////////////////////////////////////////////////////\n// 4D vector\n///////////////////////////////////////////////////////////////////////////////\nstruct Vector4\n{\n    float x;\n    float y;\n    float z;\n    float w;\n\n    // ctors\n    Vector4() : x(0), y(0), z(0), w(0) {};\n    Vector4(float x, float y, float z, float w) : x(x), y(y), z(z), w(w) {};\n\n    // utils functions\n    void        set(float x, float y, float z, float w);\n    float       length() const;                         //\n    float       distance(const Vector4& vec) const;     // distance between two vectors\n    Vector4&    normalize();                            //\n    float       dot(const Vector4& vec) const;          // dot product\n    bool        equal(const Vector4& vec, float e) const; // compare with epsilon\n\n    // operators\n    Vector4     operator-() const;                      // unary operator (negate)\n    Vector4     operator+(const Vector4& rhs) const;    // add rhs\n    Vector4     operator-(const Vector4& rhs) const;    // subtract rhs\n    Vector4&    operator+=(const Vector4& rhs);         // add rhs and update this object\n    Vector4&    operator-=(const Vector4& rhs);         // subtract rhs and update this object\n    Vector4     operator*(const float scale) const;     // scale\n    Vector4     operator*(const Vector4& rhs) const;    // multiply each element\n    Vector4&    operator*=(const float scale);          // scale and update this object\n    Vector4&    operator*=(const Vector4& rhs);         // multiply each element and update this object\n    Vector4     operator/(const float scale) const;     // inverse scale\n    Vector4&    operator/=(const float scale);          // scale and update this object\n    bool        operator==(const Vector4& rhs) const;   // exact compare, no epsilon\n    bool        operator!=(const Vector4& rhs) const;   // exact compare, no epsilon\n    bool        operator<(const Vector4& rhs) const;    // comparison for sort\n    float       operator[](int index) const;            // subscript operator v[0], v[1]\n    float&      operator[](int index);                  // subscript operator v[0], v[1]\n\n    friend Vector4 operator*(const float a, const Vector4 vec);\n    friend std::ostream& operator<<(std::ostream& os, const Vector4& vec);\n};\n\n\n\n// fast math routines from Doom3 SDK\ninline float invSqrt(float x)\n{\n    float xhalf = 0.5f * x;\n    int i = 0;\n    memcpy(&i, &x, sizeof(float)); // get bits for floating value\n    i = 0x5f3759df - (i>>1);       // gives initial guess\n    memcpy(&x, &i, sizeof(float)); // convert bits back to float\n    x = x * (1.5f - xhalf*x*x);    // Newton step\n    return x;\n}\n\n\n\n///////////////////////////////////////////////////////////////////////////////\n// inline functions for Vector2\n///////////////////////////////////////////////////////////////////////////////\ninline Vector2 Vector2::operator-() const {\n    return Vector2(-x, -y);\n}\n\ninline Vector2 Vector2::operator+(const Vector2& rhs) const {\n    return Vector2(x+rhs.x, y+rhs.y);\n}\n\ninline Vector2 Vector2::operator-(const Vector2& rhs) const {\n    return Vector2(x-rhs.x, y-rhs.y);\n}\n\ninline Vector2& Vector2::operator+=(const Vector2& rhs) {\n    x += rhs.x; y += rhs.y; return *this;\n}\n\ninline Vector2& Vector2::operator-=(const Vector2& rhs) {\n    x -= rhs.x; y -= rhs.y; return *this;\n}\n\ninline Vector2 Vector2::operator*(const float a) const {\n    return Vector2(x*a, y*a);\n}\n\ninline Vector2 Vector2::operator*(const Vector2& rhs) const {\n    return Vector2(x*rhs.x, y*rhs.y);\n}\n\ninline Vector2& Vector2::operator*=(const float a) {\n    x *= a; y *= a; return *this;\n}\n\ninline Vector2& Vector2::operator*=(const Vector2& rhs) {\n    x *= rhs.x; y *= rhs.y; return *this;\n}\n\ninline Vector2 Vector2::operator/(const float a) const {\n    return Vector2(x/a, y/a);\n}\n\ninline Vector2& Vector2::operator/=(const float a) {\n    x /= a; y /= a; return *this;\n}\n\ninline bool Vector2::operator==(const Vector2& rhs) const {\n    return (x == rhs.x) && (y == rhs.y);\n}\n\ninline bool Vector2::operator!=(const Vector2& rhs) const {\n    return (x != rhs.x) || (y != rhs.y);\n}\n\ninline bool Vector2::operator<(const Vector2& rhs) const {\n    if(x < rhs.x) return true;\n    if(x > rhs.x) return false;\n    if(y < rhs.y) return true;\n    if(y > rhs.y) return false;\n    return false;\n}\n\ninline float Vector2::operator[](int index) const {\n    return (&x)[index];\n}\n\ninline float& Vector2::operator[](int index) {\n    return (&x)[index];\n}\n\ninline void Vector2::set(float x_, float y_) {\n    this->x = x_; this->y = y_;\n}\n\ninline float Vector2::length() const {\n    return sqrtf(x*x + y*y);\n}\n\ninline float Vector2::distance(const Vector2& vec) const {\n    return sqrtf((vec.x-x)*(vec.x-x) + (vec.y-y)*(vec.y-y));\n}\n\ninline Vector2& Vector2::normalize() {\n    //@@const float EPSILON = 0.000001f;\n    float xxyy = x*x + y*y;\n    //@@if(xxyy < EPSILON)\n    //@@    return *this;\n\n    //float invLength = invSqrt(xxyy);\n    float invLength = 1.0f / sqrtf(xxyy);\n    x *= invLength;\n    y *= invLength;\n    return *this;\n}\n\ninline float Vector2::dot(const Vector2& rhs) const {\n    return (x*rhs.x + y*rhs.y);\n}\n\ninline bool Vector2::equal(const Vector2& rhs, float epsilon) const {\n    return fabs(x - rhs.x) < epsilon && fabs(y - rhs.y) < epsilon;\n}\n\ninline Vector2 operator*(const float a, const Vector2 vec) {\n    return Vector2(a*vec.x, a*vec.y);\n}\n\ninline std::ostream& operator<<(std::ostream& os, const Vector2& vec) {\n    os << \"(\" << vec.x << \", \" << vec.y << \")\";\n    return os;\n}\n// END OF VECTOR2 /////////////////////////////////////////////////////////////\n\n\n\n\n///////////////////////////////////////////////////////////////////////////////\n// inline functions for Vector3\n///////////////////////////////////////////////////////////////////////////////\ninline Vector3 Vector3::operator-() const {\n    return Vector3(-x, -y, -z);\n}\n\ninline Vector3 Vector3::operator+(const Vector3& rhs) const {\n    return Vector3(x+rhs.x, y+rhs.y, z+rhs.z);\n}\n\ninline Vector3 Vector3::operator-(const Vector3& rhs) const {\n    return Vector3(x-rhs.x, y-rhs.y, z-rhs.z);\n}\n\ninline Vector3& Vector3::operator+=(const Vector3& rhs) {\n    x += rhs.x; y += rhs.y; z += rhs.z; return *this;\n}\n\ninline Vector3& Vector3::operator-=(const Vector3& rhs) {\n    x -= rhs.x; y -= rhs.y; z -= rhs.z; return *this;\n}\n\ninline Vector3 Vector3::operator*(const float a) const {\n    return Vector3(x*a, y*a, z*a);\n}\n\ninline Vector3 Vector3::operator*(const Vector3& rhs) const {\n    return Vector3(x*rhs.x, y*rhs.y, z*rhs.z);\n}\n\ninline Vector3& Vector3::operator*=(const float a) {\n    x *= a; y *= a; z *= a; return *this;\n}\n\ninline Vector3& Vector3::operator*=(const Vector3& rhs) {\n    x *= rhs.x; y *= rhs.y; z *= rhs.z; return *this;\n}\n\ninline Vector3 Vector3::operator/(const float a) const {\n    return Vector3(x/a, y/a, z/a);\n}\n\ninline Vector3& Vector3::operator/=(const float a) {\n    x /= a; y /= a; z /= a; return *this;\n}\n\ninline bool Vector3::operator==(const Vector3& rhs) const {\n    return (x == rhs.x) && (y == rhs.y) && (z == rhs.z);\n}\n\ninline bool Vector3::operator!=(const Vector3& rhs) const {\n    return (x != rhs.x) || (y != rhs.y) || (z != rhs.z);\n}\n\ninline bool Vector3::operator<(const Vector3& rhs) const {\n    if(x < rhs.x) return true;\n    if(x > rhs.x) return false;\n    if(y < rhs.y) return true;\n    if(y > rhs.y) return false;\n    if(z < rhs.z) return true;\n    if(z > rhs.z) return false;\n    return false;\n}\n\ninline float Vector3::operator[](int index) const {\n    return (&x)[index];\n}\n\ninline float& Vector3::operator[](int index) {\n    return (&x)[index];\n}\n\ninline void Vector3::set(float x_, float y_, float z_) {\n    this->x = x_; this->y = y_; this->z = z_;\n}\n\ninline float Vector3::length() const {\n    return sqrtf(x*x + y*y + z*z);\n}\n\ninline float Vector3::distance(const Vector3& vec) const {\n    return sqrtf((vec.x-x)*(vec.x-x) + (vec.y-y)*(vec.y-y) + (vec.z-z)*(vec.z-z));\n}\n\ninline Vector3& Vector3::normalize() {\n    //@@const float EPSILON = 0.000001f;\n    float xxyyzz = x*x + y*y + z*z;\n    //@@if(xxyyzz < EPSILON)\n    //@@    return *this; // do nothing if it is ~zero vector\n\n    //float invLength = invSqrt(xxyyzz);\n    float invLength = 1.0f / sqrtf(xxyyzz);\n    x *= invLength;\n    y *= invLength;\n    z *= invLength;\n    return *this;\n}\n\ninline float Vector3::dot(const Vector3& rhs) const {\n    return (x*rhs.x + y*rhs.y + z*rhs.z);\n}\n\ninline Vector3 Vector3::cross(const Vector3& rhs) const {\n    return Vector3(y*rhs.z - z*rhs.y, z*rhs.x - x*rhs.z, x*rhs.y - y*rhs.x);\n}\n\ninline bool Vector3::equal(const Vector3& rhs, float epsilon) const {\n    return fabs(x - rhs.x) < epsilon && fabs(y - rhs.y) < epsilon && fabs(z - rhs.z) < epsilon;\n}\n\ninline Vector3 operator*(const float a, const Vector3 vec) {\n    return Vector3(a*vec.x, a*vec.y, a*vec.z);\n}\n\ninline std::ostream& operator<<(std::ostream& os, const Vector3& vec) {\n    os << \"(\" << vec.x << \", \" << vec.y << \", \" << vec.z << \")\";\n    return os;\n}\n// END OF VECTOR3 /////////////////////////////////////////////////////////////\n\n\n\n///////////////////////////////////////////////////////////////////////////////\n// inline functions for Vector4\n///////////////////////////////////////////////////////////////////////////////\ninline Vector4 Vector4::operator-() const {\n    return Vector4(-x, -y, -z, -w);\n}\n\ninline Vector4 Vector4::operator+(const Vector4& rhs) const {\n    return Vector4(x+rhs.x, y+rhs.y, z+rhs.z, w+rhs.w);\n}\n\ninline Vector4 Vector4::operator-(const Vector4& rhs) const {\n    return Vector4(x-rhs.x, y-rhs.y, z-rhs.z, w-rhs.w);\n}\n\ninline Vector4& Vector4::operator+=(const Vector4& rhs) {\n    x += rhs.x; y += rhs.y; z += rhs.z; w += rhs.w; return *this;\n}\n\ninline Vector4& Vector4::operator-=(const Vector4& rhs) {\n    x -= rhs.x; y -= rhs.y; z -= rhs.z; w -= rhs.w; return *this;\n}\n\ninline Vector4 Vector4::operator*(const float a) const {\n    return Vector4(x*a, y*a, z*a, w*a);\n}\n\ninline Vector4 Vector4::operator*(const Vector4& rhs) const {\n    return Vector4(x*rhs.x, y*rhs.y, z*rhs.z, w*rhs.w);\n}\n\ninline Vector4& Vector4::operator*=(const float a) {\n    x *= a; y *= a; z *= a; w *= a; return *this;\n}\n\ninline Vector4& Vector4::operator*=(const Vector4& rhs) {\n    x *= rhs.x; y *= rhs.y; z *= rhs.z; w *= rhs.w; return *this;\n}\n\ninline Vector4 Vector4::operator/(const float a) const {\n    return Vector4(x/a, y/a, z/a, w/a);\n}\n\ninline Vector4& Vector4::operator/=(const float a) {\n    x /= a; y /= a; z /= a; w /= a; return *this;\n}\n\ninline bool Vector4::operator==(const Vector4& rhs) const {\n    return (x == rhs.x) && (y == rhs.y) && (z == rhs.z) && (w == rhs.w);\n}\n\ninline bool Vector4::operator!=(const Vector4& rhs) const {\n    return (x != rhs.x) || (y != rhs.y) || (z != rhs.z) || (w != rhs.w);\n}\n\ninline bool Vector4::operator<(const Vector4& rhs) const {\n    if(x < rhs.x) return true;\n    if(x > rhs.x) return false;\n    if(y < rhs.y) return true;\n    if(y > rhs.y) return false;\n    if(z < rhs.z) return true;\n    if(z > rhs.z) return false;\n    if(w < rhs.w) return true;\n    if(w > rhs.w) return false;\n    return false;\n}\n\ninline float Vector4::operator[](int index) const {\n    return (&x)[index];\n}\n\ninline float& Vector4::operator[](int index) {\n    return (&x)[index];\n}\n\ninline void Vector4::set(float x_, float y_, float z_, float w_) {\n    this->x = x_; this->y = y_; this->z = z_; this->w = w_;\n}\n\ninline float Vector4::length() const {\n    return sqrtf(x*x + y*y + z*z + w*w);\n}\n\ninline float Vector4::distance(const Vector4& vec) const {\n    return sqrtf((vec.x-x)*(vec.x-x) + (vec.y-y)*(vec.y-y) + (vec.z-z)*(vec.z-z) + (vec.w-w)*(vec.w-w));\n}\n\ninline Vector4& Vector4::normalize() {\n    //NOTE: leave w-component untouched\n    //@@const float EPSILON = 0.000001f;\n    float xxyyzz = x*x + y*y + z*z;\n    //@@if(xxyyzz < EPSILON)\n    //@@    return *this; // do nothing if it is zero vector\n\n    //float invLength = invSqrt(xxyyzz);\n    float invLength = 1.0f / sqrtf(xxyyzz);\n    x *= invLength;\n    y *= invLength;\n    z *= invLength;\n    return *this;\n}\n\ninline float Vector4::dot(const Vector4& rhs) const {\n    return (x*rhs.x + y*rhs.y + z*rhs.z + w*rhs.w);\n}\n\ninline bool Vector4::equal(const Vector4& rhs, float epsilon) const {\n    return fabs(x - rhs.x) < epsilon && fabs(y - rhs.y) < epsilon &&\n           fabs(z - rhs.z) < epsilon && fabs(w - rhs.w) < epsilon;\n}\n\ninline Vector4 operator*(const float a, const Vector4 vec) {\n    return Vector4(a*vec.x, a*vec.y, a*vec.z, a*vec.w);\n}\n\ninline std::ostream& operator<<(std::ostream& os, const Vector4& vec) {\n    os << \"(\" << vec.x << \", \" << vec.y << \", \" << vec.z << \", \" << vec.w << \")\";\n    return os;\n}\n// END OF VECTOR4 /////////////////////////////////////////////////////////////\n\n///////////////////////////////////////////////////////////////////////////////\n// 2D vector int\n///////////////////////////////////////////////////////////////////////////////\nstruct Vector2Int\n{\n    int x;\n    int y;\n\n    // ctors\n    Vector2Int() : x(0), y(0) {};\n    Vector2Int(int x, int y) : x(x), y(y) {};\n\n    // utils functions\n    void              set(int x, int y);\n    float             length() const;                          //\n    float             distance(const Vector2Int& vec) const;   // distance between two vectors\n    static Vector2Int vec_min(const Vector2Int& lhs, const Vector2Int& rhs);\n    static Vector2Int vec_max(const Vector2Int& lhs, const Vector2Int& rhs);\n    static Vector2Int vec_clamp(const Vector2Int& vec, const Vector2Int& vec_min, Vector2Int vec_max);\n\n\n                                                            // operators\n    Vector2Int     operator-() const;                       // unary operator (negate)\n    Vector2Int     operator+(const Vector2Int& rhs) const;  // add rhs\n    Vector2Int     operator-(const Vector2Int& rhs) const;  // subtract rhs\n    Vector2Int&    operator+=(const Vector2Int& rhs);       // add rhs and update this object\n    Vector2Int&    operator-=(const Vector2Int& rhs);       // subtract rhs and update this object\n    Vector2Int     operator*(const int scale) const;        // scale\n    Vector2Int     operator*(const float scale) const;      // scale\n    Vector2Int     operator*(const Vector2Int& rhs) const;  // multiply each element\n    Vector2Int&    operator*=(const int scale);             // scale and update this object\n    Vector2Int&    operator*=(const float scale);           // scale and update this object\n    Vector2Int&    operator*=(const Vector2Int& rhs);       // multiply each element and update this object\n    Vector2Int     operator/(const int scale) const;        // inverse scale\n    Vector2Int     operator/(const float scale) const;      // inverse scale\n    Vector2Int&    operator/=(const int scale);             // scale and update this object\n    Vector2Int&    operator/=(const float scale);           // scale and update this object\n    bool           operator==(const Vector2Int& rhs) const; // exact compare, no epsilon\n    bool           operator!=(const Vector2Int& rhs) const; // exact compare, no epsilon\n    bool           operator<(const Vector2Int& rhs) const;  // comparison for sort\n    int            operator[](int index) const;             // subscript operator v[0], v[1]\n    int&           operator[](int index);                   // subscript operator v[0], v[1]\n\n    friend Vector2Int operator*(const int a, const Vector2Int vec);\n    friend std::ostream& operator<<(std::ostream& os, const Vector2Int& vec);\n};\n\n///////////////////////////////////////////////////////////////////////////////\n// inline functions for Vector2Int\n///////////////////////////////////////////////////////////////////////////////\ninline Vector2Int Vector2Int::operator-() const {\n    return Vector2Int(-x, -y);\n}\n\ninline Vector2Int Vector2Int::operator+(const Vector2Int& rhs) const {\n    return Vector2Int(x+rhs.x, y+rhs.y);\n}\n\ninline Vector2Int Vector2Int::operator-(const Vector2Int& rhs) const {\n    return Vector2Int(x-rhs.x, y-rhs.y);\n}\n\ninline Vector2Int& Vector2Int::operator+=(const Vector2Int& rhs) {\n    x += rhs.x; y += rhs.y; return *this;\n}\n\ninline Vector2Int& Vector2Int::operator-=(const Vector2Int& rhs) {\n    x -= rhs.x; y -= rhs.y; return *this;\n}\n\ninline Vector2Int Vector2Int::operator*(const int a) const {\n    return Vector2Int(x*a, y*a);\n}\n\ninline Vector2Int Vector2Int::operator*(const float a) const {\n    return Vector2Int(int(x*a), int(y*a));\n}\n\ninline Vector2Int Vector2Int::operator*(const Vector2Int& rhs) const {\n    return Vector2Int(x*rhs.x, y*rhs.y);\n}\n\ninline Vector2Int& Vector2Int::operator*=(const int a) {\n    x *= a; y *= a; return *this;\n}\n\ninline Vector2Int& Vector2Int::operator*=(const float a) {\n    x = int(x*a); y = int(y*a); return *this;\n}\n\ninline Vector2Int& Vector2Int::operator*=(const Vector2Int& rhs) {\n    x *= rhs.x; y *= rhs.y; return *this;\n}\n\ninline Vector2Int Vector2Int::operator/(const int a) const {\n    return Vector2Int(x/a, y/a);\n}\n\ninline Vector2Int Vector2Int::operator/(const float a) const {\n    return Vector2Int(int(x/a), int(y/a));\n}\n\ninline Vector2Int& Vector2Int::operator/=(const int a) {\n    x /= a; y /= a; return *this;\n}\n\ninline Vector2Int& Vector2Int::operator/=(const float a) {\n    x = int(x/a); y = int(y/a); return *this;\n}\n\ninline bool Vector2Int::operator==(const Vector2Int& rhs) const {\n    return (x == rhs.x) && (y == rhs.y);\n}\n\ninline bool Vector2Int::operator!=(const Vector2Int& rhs) const {\n    return (x != rhs.x) || (y != rhs.y);\n}\n\ninline bool Vector2Int::operator<(const Vector2Int& rhs) const {\n    if(x < rhs.x) return true;\n    if(x > rhs.x) return false;\n    if(y < rhs.y) return true;\n    if(y > rhs.y) return false;\n    return false;\n}\n\ninline int Vector2Int::operator[](int index) const {\n    return (&x)[index];\n}\n\ninline int& Vector2Int::operator[](int index) {\n    return (&x)[index];\n}\n\ninline void Vector2Int::set(int x_, int y_) {\n    this->x = x_; this->y = y_;\n}\n\ninline float Vector2Int::length() const {\n    return sqrtf( float(x*x + y*y) );\n}\n\ninline float Vector2Int::distance(const Vector2Int& vec) const {\n    return sqrtf( float((vec.x-x)*(vec.x-x) + (vec.y-y)*(vec.y-y)) );\n}\n\ninline Vector2Int Vector2Int::vec_min(const Vector2Int& lhs, const Vector2Int& rhs) {\n    return Vector2Int(lhs.x < rhs.x ? lhs.x : rhs.x, lhs.y < rhs.y ? lhs.y : rhs.y);\n}\n\ninline Vector2Int Vector2Int::vec_max(const Vector2Int& lhs, const Vector2Int& rhs) {\n    return Vector2Int(lhs.x >= rhs.x ? lhs.x : rhs.x, lhs.y >= rhs.y ? lhs.y : rhs.y);\n}\n\ninline Vector2Int Vector2Int::vec_clamp(const Vector2Int& vec, const Vector2Int& vec_min, Vector2Int vec_max) {\n    return Vector2Int((vec.x < vec_min.x) ? vec_min.x : (vec.x > vec_max.x) ? vec_max.x : vec.x, (vec.y < vec_min.y) ? vec_min.y : (vec.y > vec_max.y) ? vec_max.y : vec.y);\n}\n\ninline Vector2Int operator*(const int a, const Vector2Int vec) {\n    return Vector2Int(a*vec.x, a*vec.y);\n}\n\ninline std::ostream& operator<<(std::ostream& os, const Vector2Int& vec) {\n    os << \"(\" << vec.x << \", \" << vec.y << \")\";\n    return os;\n}\n// END OF VECTOR2INT /////////////////////////////////////////////////////////////\n\n#endif\n"
  },
  {
    "path": "src/Shared/WindowManager.cpp",
    "content": "\n#include \"WindowManager.h\"\n\n#include <algorithm>\n#include <iostream>\n\n#include <dwmapi.h>\n#include <Psapi.h>\n\n#include \"ConfigManager.h\"\n#include \"InterprocessMessaging.h\"\n#include \"Util.h\"\n\n#ifndef DPLUS_UI\n    #include \"InputSimulator.h\"\n#endif\n\nWindowManager g_WindowManager;\n\nbool inline MatchTitleAndClassName(WindowInfo const& window, std::wstring const& title, std::wstring const& className)\n{\n    return ( (window.GetTitle() == title) && (window.GetWindowClassName() == className) );\n}\n\nbool IsKnownBlockedWindow(WindowInfo const& window)\n{\n    bool is_blocked = ( // Task View\n                       (MatchTitleAndClassName(window, L\"Task View\", L\"Windows.UI.Core.CoreWindow\")) ||\n                        // XAML Islands\n                       (MatchTitleAndClassName(window, L\"DesktopWindowXamlSource\", L\"Windows.UI.Core.CoreWindow\")) ||\n                        // XAML Popups\n                       (MatchTitleAndClassName(window, L\"PopupHost\", L\"Xaml_WindowedPopupClass\")) );\n\n    // Capture Picker Window (child of ApplicationFrameWindow)\n    if ( (!is_blocked) && (window.GetWindowClassName() == L\"ApplicationFrameWindow\") )\n    {\n        is_blocked = (::FindWindowExW(window.GetWindowHandle(), nullptr, L\"Windows.UI.Core.CoreWindow\", L\"CapturePicker\") != nullptr);\n    }\n\n    // Jumplists and Action Center (and maybe others)\n    if ( (!is_blocked) && (window.GetExeName() == \"ShellExperienceHost.exe\") )\n    {\n        is_blocked = true;\n    }\n\n    return is_blocked;\n}\n\nbool IsCapturableWindow(WindowInfo const& window)\n{\n    HWND window_handle = window.GetWindowHandle();\n    if ((window.GetTitle().empty()) || (window_handle == ::GetShellWindow()) || (!::IsWindowVisible(window_handle)) || (::GetAncestor(window_handle, GA_ROOT) != window_handle))\n    {\n        return false;\n    }\n\n    LONG style = ::GetWindowLongW(window_handle, GWL_STYLE);\n    if (style & WS_DISABLED)\n    {\n        return false;\n    }\n\n    LONG exStyle = ::GetWindowLongW(window_handle, GWL_EXSTYLE);\n    if (exStyle & WS_EX_TOOLWINDOW)    // No tooltips\n    {\n        return false;\n    }\n\n    if ((exStyle & WS_EX_NOACTIVATE) && !(exStyle & WS_EX_APPWINDOW))   //WS_EX_NOACTIVATE is not capturable unless WS_EX_APPWINDOW is also set\n    {\n        return false;\n    }\n\n    // Check to see if the window is cloaked if it's a UWP\n    if ((wcscmp(window.GetWindowClassName().c_str(), L\"Windows.UI.Core.CoreWindow\") == 0) || (wcscmp(window.GetWindowClassName().c_str(), L\"ApplicationFrameWindow\") == 0))\n    {\n        DWORD cloaked = FALSE;\n        if (SUCCEEDED(::DwmGetWindowAttribute(window_handle, DWMWA_CLOAKED, &cloaked, sizeof(cloaked))) && (cloaked == DWM_CLOAKED_SHELL))\n        {\n            return false;\n        }\n    }\n\n    // Unfortunate work-around. Not sure how to avoid this.\n    if (IsKnownBlockedWindow(window))\n    {\n        return false;\n    }\n\n    return true;\n}\n\nWindowInfo::WindowInfo(HWND window_handle)\n{\n    m_WindowHandle = window_handle;\n    m_Icon = nullptr;\n\n    if (m_WindowHandle == nullptr)\n    {\n        m_Title     = L\"[No Window]\";\n        m_ListTitle =  \"[No Window]\";\n\n        return;\n    }\n\n    UpdateWindowTitle();\n\n    WCHAR class_buffer[256];\n\n    if (::GetClassNameW(m_WindowHandle, class_buffer, 256) != 0)\n    {\n        m_ClassName = class_buffer;\n\n        //WPF heuristics:\n        //WPF apps randomize parts their class names in the pattern of \"HwndWrapper[{appName};{threadName};{randomName}]\"\n        //This is bad for our purposes since we expect the class names to be a static property\n        //Here we try to detect these cases and cut them off at the first semicolon (keeping thread name might work, but windows might switch threads perhaps)\n        //This is not ideal, but better than never matching the window again and the class name didn't offer any unique info in the first place\n        if (m_ClassName.find(L\"HwndWrapper[\") != std::wstring::npos)\n        {\n            m_ClassName = m_ClassName.substr(0, m_ClassName.find(L';'));\n        }\n    }\n}\n\nHICON WindowInfo::GetIcon() const\n{\n    return (m_Icon == nullptr) ? m_Icon = GetIcon(m_WindowHandle) : m_Icon;\n}\n\nconst std::wstring& WindowInfo::GetTitle() const\n{\n    return m_Title;\n}\n\nconst std::wstring& WindowInfo::GetWindowClassName() const\n{\n    return m_ClassName;\n}\n\nconst std::string& WindowInfo::GetExeName() const\n{\n    return (m_ExeName.empty()) ? m_ExeName = GetExeName(m_WindowHandle) : m_ExeName;\n}\n\nconst std::string& WindowInfo::GetListTitle() const\n{\n    if (m_ListTitle.empty())\n    {\n        m_ListTitle = \"[\" + GetExeName() + \"]: \" + StringConvertFromUTF16(GetTitle().c_str());\n    }\n\n    return m_ListTitle;\n}\n\nconst bool WindowInfo::IsClassNameMatching(const std::wstring& class_name) const\n{\n    return ((m_ClassName.empty()) || (class_name.empty()) || (m_ClassName == class_name));\n}\n\nbool WindowInfo::UpdateWindowTitle()\n{\n    bool ret = false;\n    int title_length = ::GetWindowTextLengthW(m_WindowHandle);\n    if (title_length > 0)\n    {\n        title_length++;\n\n        auto title_buffer = std::unique_ptr<WCHAR[]>{new WCHAR[title_length]};\n\n        if (::GetWindowTextW(m_WindowHandle, title_buffer.get(), title_length) != 0)\n        {\n            std::wstring title_new = title_buffer.get();\n\n            //Some applications send a lot of title updates without actually changing the title (notepad does it on every mouse move!), so check if the title is different and pass that info along\n            if (m_Title == title_new)\n            {\n                return false;\n            }\n\n            m_Title = title_new;\n            ret = true;\n        }\n\n        //Clear list title so it gets generated again on next GetListTitle() call\n        m_ListTitle = \"\";\n    }\n\n    return ret;\n}\n\nstd::string WindowInfo::GetExeName(HWND window_handle)\n{\n    std::string exe_name;\n\n    if (window_handle == nullptr)\n        return exe_name;\n\n    DWORD proc_id;\n    ::GetWindowThreadProcessId(window_handle, &proc_id);\n    HANDLE proc_handle = ::OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, FALSE, proc_id);\n    if (proc_handle)\n    {\n        WCHAR exe_buffer[MAX_PATH];\n        //GetProcessImageFileNameW has more info than we need, but takes the least access permissions so we can get elevated process names through it\n        if (::GetProcessImageFileNameW(proc_handle, exe_buffer, MAX_PATH) != 0)\n        {\n            exe_name = StringConvertFromUTF16(exe_buffer);\n\n            //Cut off the path we're not interested in\n            std::size_t pos = exe_name.find_last_of(\"\\\\\");\n            if ((pos != std::string::npos) && (pos + 1 < exe_name.length())) //String should be well-formed, but let's be careful\n            {\n                exe_name = exe_name.substr(pos + 1);\n            }\n        }\n\n        ::CloseHandle(proc_handle);\n    }\n\n    return exe_name;\n}\n\nHWND WindowInfo::GetWindowHandle() const\n{\n    return m_WindowHandle;\n}\n\nHICON WindowInfo::GetIcon(HWND window_handle)\n{\n    HICON icon_handle = nullptr;\n    icon_handle = (HICON)::SendMessage(window_handle, WM_GETICON, ICON_BIG, 0);\n\n    if (icon_handle == nullptr)\n    {\n        icon_handle = (HICON)::GetClassLongPtr(window_handle, GCLP_HICON);\n    }\n\n    if (icon_handle == nullptr)\n    {\n        icon_handle = ::LoadIcon(nullptr, IDI_APPLICATION);\n    }\n\n    return icon_handle;\n}\n\nHWND WindowInfo::FindClosestWindowForTitle(const std::string& title_str, const std::string& class_str, const std::string& exe_str, const std::vector<WindowInfo>& window_list,\n                                           bool use_strict_matching)\n{\n    //The idea is that most applications with changing titles keep their name at the end after a dash, so we separate that part if we can find it\n    //Apart from that, we try to find matches by removing one space-separated chunk in each iteration (first one is 1:1 match, last just the app-name)\n    //This still creates matches for apps that don't have their name after a dash but appended something to the previously known name\n    //As a last resort we just match up the first window with the same executable and class name\n\n    std::wstring title_wstr = WStringConvertFromUTF8(title_str.c_str());\n    std::wstring class_wstr = WStringConvertFromUTF8(class_str.c_str());\n\n    //Look for a complete match first\n    auto it = std::find_if(window_list.begin(), window_list.end(), \n                           [&](const auto& info){ return ( (info.IsClassNameMatching(class_wstr)) && (info.GetExeName() == exe_str) && (info.GetTitle() == title_wstr) ); });\n\n    if (it != window_list.end())\n    {\n        return it->GetWindowHandle();\n    }\n    else if (use_strict_matching)   //Stop here if strict matching is enabled\n    {\n        return nullptr;\n    }\n\n    //Cut off document part of title if it there is one\n    std::wstring title_search = title_wstr;\n    std::wstring app_name;\n    size_t search_pos = title_wstr.rfind(L\" - \");\n\n    if (search_pos != std::wstring::npos)\n    {\n        app_name = title_wstr.substr(search_pos);\n    }\n\n    //Try to find a partial match by removing the last word from the title string and appending the application name\n    while ((search_pos != 0) && (search_pos != std::wstring::npos))\n    {\n        search_pos--;\n        search_pos = title_wstr.find_last_of(L' ', search_pos);\n\n        if (search_pos != std::wstring::npos)\n        {\n            title_search = title_wstr.substr(0, search_pos) + app_name;\n        }\n        else if (!app_name.empty()) //Last attempt, just the app-name\n        {\n            title_search = app_name;\n        }\n        else\n        {\n            break;\n        }\n\n        auto it = std::find_if(window_list.begin(), window_list.end(), \n                               [&](const auto& info){ return ( (info.IsClassNameMatching(class_wstr)) && (info.GetExeName() == exe_str) && (info.GetTitle().find(title_search) != std::wstring::npos) ); });\n\n        if (it != window_list.end())\n        {\n            return it->GetWindowHandle();\n        }\n    }\n\n    //Nothing found, try to get a window from the same class and exe name at least\n    it = std::find_if(window_list.begin(), window_list.end(), [&](const auto& info){ return (info.IsClassNameMatching(class_wstr)) && (info.GetExeName() == exe_str); });\n\n    if (it != window_list.end())\n    {\n        return it->GetWindowHandle();\n    }\n\n    return nullptr; //We tried\n}\n\n\n#define WM_WINDOWMANAGER_UPDATE_DATA            WM_APP   //Sent to WindowManager thread to update the local thread data\n#define WM_WINDOWMANAGER_TEXT_INPUT_MOUSE_CLICK WM_APP+1 //Sent to WindowManager thread to indicate a left mouse click happening\n\nWindowManager& WindowManager::Get()\n{\n    return g_WindowManager;\n}\n\nvoid WindowManager::UpdateConfigState()\n{\n    WindowManagerThreadData thread_data_new;\n    thread_data_new.BlockDrag       = ((m_IsOverlayActive) && (ConfigManager::GetValue(configid_int_windows_winrt_dragging_mode) != window_dragging_none));\n    thread_data_new.DoOverlayDrag   = ((m_IsOverlayActive) && (ConfigManager::GetValue(configid_int_windows_winrt_dragging_mode) == window_dragging_overlay));\n    thread_data_new.KeepOnScreen    = ((m_IsOverlayActive) && (ConfigManager::GetValue(configid_bool_windows_winrt_keep_on_screen)));\n    thread_data_new.TargetWindow    = m_TargetWindow;\n    thread_data_new.TargetOverlayID = m_TargetOverlayID;\n\n    if (m_IsActive)\n    {\n        //Create WindowManager thread if there is none\n        if (m_ThreadHandle == nullptr)\n        {\n            WindowListInit();\n\n            m_ThreadData = thread_data_new; //No need to lock since no other thread exists to race with\n            m_ThreadHandle = ::CreateThread(nullptr, 0, WindowManagerThreadEntry, nullptr, 0, &m_ThreadID);\n        }\n        else if (m_ThreadData != thread_data_new) //If just data has changed, update existing thread\n        {\n            {\n                std::lock_guard<std::mutex> lock(m_ThreadMutex);\n                m_ThreadData = thread_data_new;\n            }\n\n            //Reset done flag before sending update\n            {\n                std::lock_guard<std::mutex> lock(m_UpdateDoneMutex);\n                m_UpdateDoneFlag = false;\n            }\n\n            ::PostThreadMessage(m_ThreadID, WM_WINDOWMANAGER_UPDATE_DATA, 0, 0);\n\n            //Wait for thread to be done with update before continuing (for 500ms in case the window message goes poof... shouldn't happen though)\n            {\n                std::unique_lock<std::mutex> lock(m_UpdateDoneMutex);\n                m_UpdateDoneCV.wait_for(lock, std::chrono::milliseconds(500), [&]{ return m_UpdateDoneFlag; });\n            }\n        }\n    }\n    else //If thread is no longer needed, remove it\n    {\n        if (m_ThreadHandle != nullptr)\n        {\n            ::PostThreadMessage(m_ThreadID, WM_QUIT, 0, 0);\n\n            ::CloseHandle(m_ThreadHandle);\n            m_ThreadHandle = nullptr;\n            m_ThreadID     = 0;\n        }\n    }\n}\n\nvoid WindowManager::SetTargetWindow(HWND window, unsigned int overlay_id)\n{\n    if (window != m_TargetWindow)\n        m_DragOverlayMsgSent = false;\n\n    m_TargetWindow    = window;\n    m_TargetOverlayID = overlay_id;\n    UpdateConfigState();\n}\n\nHWND WindowManager::GetTargetWindow() const\n{\n    return m_TargetWindow;\n}\n\nvoid WindowManager::SetActive(bool is_active)\n{\n    m_IsActive = is_active;\n    UpdateConfigState();\n}\n\nbool WindowManager::IsActive() const\n{\n    return m_IsActive;\n}\n\nvoid WindowManager::SetOverlayActive(bool is_active)\n{\n    m_IsOverlayActive = is_active;\n    UpdateConfigState();\n}\n\nbool WindowManager::IsOverlayActive() const\n{\n    return m_IsOverlayActive;\n}\n\nconst WindowInfo& WindowManager::WindowListAdd(HWND window)\n{\n    auto it = std::find_if(m_WindowList.begin(), m_WindowList.end(), [&](const auto& info){ return (info.GetWindowHandle() == window); });\n\n    if (it == m_WindowList.end())\n    {\n        m_WindowList.emplace_back(window);\n        return m_WindowList.back();\n    }\n\n    return *it;\n}\n\nstd::wstring WindowManager::WindowListRemove(HWND window)\n{\n    std::wstring last_title;\n\n    auto it = std::find_if(m_WindowList.begin(), m_WindowList.end(), [&](const auto& info){ return (info.GetWindowHandle() == window); });\n\n    if (it != m_WindowList.end())\n    {\n        last_title = it->GetTitle();\n        m_WindowList.erase(it);\n    }\n\n    return last_title;\n}\n\nWindowInfo const* WindowManager::WindowListUpdateTitle(HWND window, bool* has_title_changed)\n{\n    auto it = std::find_if(m_WindowList.begin(), m_WindowList.end(), [&](const auto& info){ return (info.GetWindowHandle() == window); });\n\n    if (it != m_WindowList.end())\n    {\n        bool title_changed = it->UpdateWindowTitle();\n\n        if (has_title_changed != nullptr)\n            *has_title_changed = title_changed;\n\n        return &*it;\n    }\n    else if (IsCapturableWindow(window)) //Window not in the list, check if it's capturable and add it then. This is for windows that are not created with a title and are skipped without this\n    {\n        if (has_title_changed != nullptr)\n            *has_title_changed = true;\n\n        return &WindowListAdd(window);\n    }\n\n    if (has_title_changed != nullptr)\n        *has_title_changed = false;\n\n    return nullptr;\n}\n\nconst std::vector<WindowInfo>& WindowManager::WindowListGet() const\n{\n    return m_WindowList;\n}\n\nWindowInfo const* WindowManager::WindowListFindWindow(HWND window) const\n{\n    auto it = std::find_if(m_WindowList.begin(), m_WindowList.end(), [&](const auto& info){ return (info.GetWindowHandle() == window); });\n\n    if (it != m_WindowList.end())\n    {\n        return &*it;\n    }\n\n    return nullptr;\n}\n\nbool WindowManager::IsTextInputFocused()\n{\n    //Wait for the text input focused state to be stable for before reporting any changes\n    //We are more eager to show than hide to keep responsiveness while accommodating for applications that flicker the state back and forth in certain cases\n    if (m_IsTextInputFocusedUpdateTick + ((m_IsTextInputFocusedPending) ? 100 : 200) < ::GetTickCount64())\n    {\n        m_IsTextInputFocused = m_IsTextInputFocusedPending;\n    }\n\n    return m_IsTextInputFocused;\n}\n\nvoid WindowManager::UpdateTextInputFocusedState(bool new_state)\n{\n    m_IsTextInputFocusedPending    = new_state;\n    m_IsTextInputFocusedUpdateTick = ::GetTickCount64();\n}\n\nvoid WindowManager::OnTextInputLeftMouseClick()\n{\n    if (m_ThreadID != 0)\n        ::PostThreadMessage(m_ThreadID, WM_WINDOWMANAGER_TEXT_INPUT_MOUSE_CLICK, 0, 0);\n}\n\nbool WindowManager::WouldDragMaximizedTitleBar(HWND window, int prev_cursor_x, int prev_cursor_y, int new_cursor_x, int new_cursor_y)\n{\n    //If the target window matches (simulated left mouse down), the window is maximized and the cursor position changed\n    if ( (window == m_TargetWindow) && (::IsZoomed(window)) && ( (prev_cursor_x != new_cursor_x) || (prev_cursor_y != new_cursor_y) ) )\n    {\n        //Return true if previous cursor position was on the title bar\n        return (::SendMessage(window, WM_NCHITTEST, 0, MAKELPARAM(prev_cursor_x, prev_cursor_y)) == HTCAPTION);\n    }\n\n    return false;\n}\n\nvoid WindowManager::RaiseAndFocusWindow(HWND window, InputSimulator* input_sim_ptr)\n{\n    const bool timeout_passed = (m_LastFocusFailedTick + 3000 <= ::GetTickCount64());\n    bool focus_success = true;\n    if ( ( (m_LastFocusFailedWindow != window) || (timeout_passed) ) && (::GetForegroundWindow() != window) )\n    {\n        focus_success = (::IsIconic(window)) ? ::OpenIcon(window) /*Also focuses*/: ::SetForegroundWindow(window);\n\n        if (focus_success)\n        {\n            m_LastFocusFailedWindow = nullptr;\n        }\n        else if (input_sim_ptr != nullptr)\n        {\n            #ifndef DPLUS_UI\n\n            //We failed, let's try again with a hack: Pressing the ALT key before trying to switch. This releases most locks that block SetForegroundWindow()\n            bool alt_was_down = (::GetAsyncKeyState(VK_MENU) < 0);\n\n            if (alt_was_down) //Release ALT first if it's already down so it can actually be pressed again\n            {\n                input_sim_ptr->KeyboardSetUp(VK_MENU);\n            }\n\n            input_sim_ptr->KeyboardSetDown(VK_MENU);\n            ::Sleep(100); //Allow for a little bit of time for the system to register the key press\n\n            //Try again\n            focus_success = (::IsIconic(window)) ? ::OpenIcon(window) : ::SetForegroundWindow(window);\n\n            if (!alt_was_down) //Leave the key down if it was previously\n            {\n                input_sim_ptr->KeyboardSetUp(VK_MENU);\n            }\n\n            if (focus_success)\n            {\n                m_LastFocusFailedWindow = nullptr;\n            }\n\n            #endif\n        }\n    }\n\n    if ( ((!focus_success) || (!timeout_passed)) && (::GetForegroundWindow() != window) )    //Do this when focusing failed or the timeout hasn't passed yet\n    {\n        //Above didn't work, try to make the window topmost at least (we won't count this as a focus success, however)\n        SetTempTopMostWindow(window);\n    }\n\n    if (!focus_success)\n    {\n        //If a focusing attempt failed we either wait until it succeeded on another window or 3 seconds in order to not spam attempts when it's blocked for some reason\n        m_LastFocusFailedWindow = window;\n        m_LastFocusFailedTick = ::GetTickCount64();\n    }\n}\n\nbool WindowManager::SetTempTopMostWindow(HWND window)\n{\n    if (ConfigManager::GetValue(configid_bool_state_misc_elevated_mode_active))\n    {\n        IPCManager::Get().PostMessageToElevatedModeProcess(ipcmsg_elevated_action, ipceact_window_topmost_set, (LPARAM)window);\n        return true;    //No failure report, but elevated will work in most cases anyways\n    }\n\n    if (window == m_TempTopMostWindow)\n        return true;\n\n    //Don't attempt to change the temp topmost window if clearing the old one failed to avoid getting stuck with permanent style changes\n    if (!ClearTempTopMostWindow())\n        return false;\n\n    //Don't do anything if the window is already topmost on its own\n    LONG exstyle = ::GetWindowLongW(window, GWL_EXSTYLE);\n    if (exstyle & WS_EX_TOPMOST)\n        return true;\n\n    bool ret = ::SetWindowPos(window, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOACTIVATE | SWP_NOSIZE | SWP_NOMOVE);\n\n    if (ret)\n        m_TempTopMostWindow = window;\n\n    return ret;\n}\n\nbool WindowManager::ClearTempTopMostWindow()\n{\n    if (ConfigManager::GetValue(configid_bool_state_misc_elevated_mode_active))\n    {\n        IPCManager::Get().PostMessageToElevatedModeProcess(ipcmsg_elevated_action, ipceact_window_topmost_set, 0);\n        return true;\n    }\n\n    bool ret = true;\n\n    if ( (m_TempTopMostWindow != nullptr) && (::IsWindow(m_TempTopMostWindow)) )\n    {\n        ret = ::SetWindowPos(m_TempTopMostWindow, HWND_NOTOPMOST, 0, 0, 0, 0, SWP_NOACTIVATE | SWP_NOSIZE | SWP_NOMOVE);\n\n        //Also bring the currently focused window back to the top\n        ::SetWindowPos(::GetForegroundWindow(), HWND_TOP, 0, 0, 0, 0, SWP_NOACTIVATE | SWP_NOSIZE | SWP_NOMOVE);\n    }\n\n    if (ret)\n    {\n        m_TempTopMostWindow = nullptr;\n    }\n\n    return ret;\n}\n\nvoid WindowManager::FocusActiveVRSceneApp(InputSimulator* input_sim_ptr)\n{\n    //Try finding and focusing the window of the current scene application\n    uint32_t pid = vr::VRApplications()->GetCurrentSceneProcessId();\n\n    if (pid != 0)\n    {\n        HWND scene_process_window = FindMainWindow(pid);\n\n        if (scene_process_window != nullptr)\n        {\n            RaiseAndFocusWindow(scene_process_window, input_sim_ptr);\n        }\n    }\n}\n\nvoid WindowManager::MoveWindowIntoWorkArea(HWND window)\n{\n    if ( (::IsIconic(window)) || ((::IsZoomed(window))) )\n        return;\n\n    //Get position of the window and DWM frame bounds\n    int offset_x = 0, offset_y = 0;\n    RECT window_rect, dwm_rect;\n    ::GetWindowRect(window, &window_rect);\n    \n    if (::DwmGetWindowAttribute(window, DWMWA_EXTENDED_FRAME_BOUNDS, &dwm_rect, sizeof(dwm_rect)) == S_OK)\n    {\n        //We take the frame bounds as well since the window rect contains the window shadows, but we don't want those to to take up any space in the calculations\n        offset_x = window_rect.left - dwm_rect.left;\n        offset_y = window_rect.top  - dwm_rect.top;\n\n        //Replace the window rect with the DWM one. We apply the stored offset later when setting the window position\n        window_rect = dwm_rect; \n    }\n\n    //Get the nearest monitor to the window\n    HMONITOR hmon = ::MonitorFromWindow(window, MONITOR_DEFAULTTONEAREST);\n\n    //Get monitor info to access the work area rect\n    MONITORINFO monitor_info = {0};\n    monitor_info.cbSize = sizeof(monitor_info);\n    ::GetMonitorInfo(hmon, &monitor_info);\n\n    int width  = window_rect.right  - window_rect.left;\n    int height = window_rect.bottom - window_rect.top;\n\n    //Clamp position to the work area. This does move windows even if they'd be fully accessible across multiple screens, but whatever\n    window_rect.left   = std::max(monitor_info.rcWork.left, std::min(monitor_info.rcWork.right  - width,  window_rect.left));\n    window_rect.top    = std::max(monitor_info.rcWork.top,  std::min(monitor_info.rcWork.bottom - height, window_rect.top));\n    window_rect.right  = window_rect.left + width;\n    window_rect.bottom = window_rect.top  + height;\n\n    ::SetWindowPos(window, nullptr,  window_rect.left + offset_x, window_rect.top + offset_y, 0, 0, SWP_NOZORDER | SWP_NOSIZE | SWP_NOACTIVATE);\n}\n\nbool WindowManager::IsHoveringCapturableTitleBar(HWND window, int cursor_x, int cursor_y)\n{\n    if (WindowListFindWindow(window) != nullptr)\n    {\n        LRESULT result = ::SendMessage(window, WM_NCHITTEST, 0, MAKELPARAM(cursor_x, cursor_y));\n\n        if (result == HTCAPTION)\n        {\n            return true;\n        }\n        else if ( (result == HTNOWHERE) || (result == HTCLIENT) ) //Fallback for windows that don't handle NCHITTEST correctly\n        {\n            //This is not DPI-aware at all and making it be that would require calling Windows 10 and up functions. Not worth to handle just for a small fallback.\n            //It gives us a somewhat reasonable title bar region to work with either way\n            int title_bar_height = (GetSystemMetrics(SM_CYFRAME) + GetSystemMetrics(SM_CYCAPTION) + GetSystemMetrics(SM_CXPADDEDBORDER));\n\n            //Get position of the window\n            RECT window_rect = {0};\n\n            if (::DwmGetWindowAttribute(window, DWMWA_EXTENDED_FRAME_BOUNDS, &window_rect, sizeof(window_rect)) == S_OK)\n            {\n                //Check if the cursor is in the region where an ill-implemented custom drawn title bar could be\n                if ( (cursor_x >= window_rect.left) && (cursor_x < window_rect.right) && (cursor_y >= window_rect.top) && (cursor_y < window_rect.top + title_bar_height) )\n                {\n                    return true;\n                }\n            }\n        }\n    }\n\n    return false;\n}\n\nvoid WindowManager::WindowListInit()\n{\n    m_WindowList.clear();\n\n    EnumWindows([](HWND hwnd, LPARAM lParam)\n                {\n                    if (::GetWindowTextLengthW(hwnd) > 0)\n                    {\n                        WindowInfo window = WindowInfo(hwnd);\n\n                        if (!IsCapturableWindow(window))\n                        {\n                            return TRUE;\n                        }\n\n                        ((std::vector<WindowInfo>*)lParam)->push_back(window);\n                    }\n\n                    return TRUE;\n                },\n                (LPARAM)&m_WindowList);\n}\n\nvoid WindowManager::HandleWinEvent(DWORD win_event, HWND hwnd, LONG id_object, LONG id_child, DWORD event_thread, DWORD event_time)\n{\n    #ifndef DPLUS_UI\n        if (id_object == OBJID_CARET)\n        {\n            HandleCaretWinEvent(win_event, hwnd);\n            return;\n        }\n    #endif\n\n    switch (win_event)\n    {\n        case EVENT_OBJECT_DESTROY:\n        case EVENT_OBJECT_HIDE:\n        case EVENT_OBJECT_CLOAKED:\n        {\n            if ( (id_object == OBJID_WINDOW) && (id_child == CHILDID_SELF) && (hwnd != nullptr) )\n            {\n                #ifdef DPLUS_UI\n                    IPCManager::Get().PostMessageToUIApp(ipcmsg_action, ipcact_winmanager_winlist_remove, (LPARAM)hwnd);\n                #else\n                    IPCManager::Get().PostMessageToDashboardApp(ipcmsg_action, ipcact_winmanager_winlist_remove, (LPARAM)hwnd);\n                #endif\n            }\n            return;\n        }\n        case EVENT_OBJECT_SHOW:\n        case EVENT_OBJECT_UNCLOAKED:\n        {\n            if ( (id_object == OBJID_WINDOW) && (id_child == CHILDID_SELF) && (hwnd != nullptr) && (GetAncestor(hwnd, GA_ROOT) == hwnd) && (GetWindowTextLengthW(hwnd) > 0) )\n            {\n                WindowInfo info(hwnd);\n                \n                if (IsCapturableWindow(info))\n                {\n                    #ifdef DPLUS_UI\n                        IPCManager::Get().PostMessageToUIApp(ipcmsg_action, ipcact_winmanager_winlist_add, (LPARAM)hwnd);\n                    #else\n                        IPCManager::Get().PostMessageToDashboardApp(ipcmsg_action, ipcact_winmanager_winlist_add, (LPARAM)hwnd);\n                    #endif\n                }\n            }\n            return;\n        }\n        case EVENT_OBJECT_NAMECHANGE:\n        {\n            if ( (id_object == OBJID_WINDOW) && (id_child == CHILDID_SELF) && (hwnd != nullptr) && (::GetAncestor(hwnd, GA_ROOT) == hwnd) && (::GetWindowTextLengthW(hwnd) > 0) )\n            {\n                //We don't bother checking if the window is capturable here. Windows that were created with an empty title may get late added from this (which then checks).\n                #ifdef DPLUS_UI\n                    IPCManager::Get().PostMessageToUIApp(ipcmsg_action, ipcact_winmanager_winlist_update, (LPARAM)hwnd);\n                #else\n                    IPCManager::Get().PostMessageToDashboardApp(ipcmsg_action, ipcact_winmanager_winlist_update, (LPARAM)hwnd);\n                #endif\n            }\n            return;\n        }\n\n        #ifndef DPLUS_UI\n\n        case EVENT_SYSTEM_FOREGROUND:\n        {\n            //Check if focus is from a different process than last time\n            DWORD process_id;\n            ::GetWindowThreadProcessId(hwnd, &process_id);\n            if (process_id != m_FocusLastProcess)\n            {\n                m_FocusLastProcess = process_id;\n\n                //Send updated process elevation state to UI\n                IPCManager::Get().PostConfigMessageToUIApp(configid_bool_state_window_focused_process_elevated, IsProcessElevated(process_id));\n            }\n\n            //Send focus update to UI\n            IPCManager::Get().PostMessageToUIApp(ipcmsg_action, ipcact_winmanager_focus_changed);\n\n            //Not a caret event, but trigger handling on focus change for text input focus tracking too\n            HandleCaretWinEvent(EVENT_SYSTEM_FOREGROUND, nullptr);\n\n            return;\n        }\n        case EVENT_SYSTEM_MOVESIZESTART:\n        case EVENT_OBJECT_LOCATIONCHANGE:\n        case EVENT_SYSTEM_MOVESIZEEND:\n        {\n            //Limit to visible top-level windows\n            if ( (hwnd == nullptr) || (id_object != OBJID_WINDOW) || (id_child != CHILDID_SELF) || (GetWindowTextLength(hwnd) == 0) || \n                 (!::IsWindowVisible(hwnd)) || (::GetAncestor(hwnd, GA_ROOT) != hwnd) )\n            {\n                return;\n            }\n\n            if ( (m_ThreadLocalData.BlockDrag) || (m_ThreadLocalData.KeepOnScreen) )\n            {\n                if (win_event == EVENT_SYSTEM_MOVESIZESTART)\n                {\n                    if (hwnd == m_ThreadLocalData.TargetWindow)\n                    {\n                        m_DragWindow = hwnd;\n                        m_DragOverlayMsgSent = false;\n                    }\n                    return;\n                }\n                else if (win_event == EVENT_OBJECT_LOCATIONCHANGE)\n                {\n                    if (hwnd == m_DragWindow)\n                    {\n                        if (m_ThreadLocalData.BlockDrag)\n                        {\n                            RECT window_rect;\n                            ::GetWindowRect(hwnd, &window_rect);\n                            SIZE start_size = {m_DragStartWindowRect.right - m_DragStartWindowRect.left, m_DragStartWindowRect.bottom - m_DragStartWindowRect.top};\n                            SIZE current_size = {window_rect.right - window_rect.left,window_rect.bottom - window_rect.top};\n\n                            //Only block/start overlay drag if the position changed, but not the size (allows in-place resizing)\n                            if ( ((window_rect.left != m_DragStartWindowRect.left) || (window_rect.top != m_DragStartWindowRect.top)) && (start_size.cx == current_size.cx) && \n                                 (start_size.cy == current_size.cy) )\n                            {\n                                ::SetCursorPos(m_DragStartMousePos.x, m_DragStartMousePos.y);\n                                ::SetWindowPos(hwnd, nullptr, m_DragStartWindowRect.left, m_DragStartWindowRect.top, 0, 0, SWP_NOZORDER | SWP_NOSIZE | SWP_NOACTIVATE);\n\n                                if (!m_DragOverlayMsgSent)\n                                {\n                                    //Start the overlay drag or send overlay ID 0 to have the mouse button be released (so the desktop drag stops)\n                                    IPCManager::Get().PostMessageToDashboardApp(ipcmsg_action, ipcact_winmanager_drag_start, (m_ThreadLocalData.DoOverlayDrag) ? m_ThreadLocalData.TargetOverlayID : UINT_MAX);\n                                    m_DragOverlayMsgSent = true;\n                                }\n                            }\n                            else if ((start_size.cx != current_size.cx) || (start_size.cy != current_size.cy))\n                            {\n                                //Adapt to size change in case this happened from a normal drag (when dragging from a maximized window for example)\n                                ::GetWindowRect(hwnd, &m_DragStartWindowRect);\n                            }\n                        }\n                        else if (m_ThreadLocalData.KeepOnScreen)\n                        {\n                            MoveWindowIntoWorkArea(hwnd);\n                        }\n                    }\n                    else if (hwnd == m_ThreadLocalData.TargetWindow)\n                    {\n                        //Fallback for windows that do their own dragging logic.\n                        //This has the chance of picking up a programmatic position change, \n                        //but the time window for that is very small since the target window is only set while the simulated mouse is down\n\n                        //Check if the current cursor is a sizing cursor, though. There are edge cases we want to avoid if that's the case\n                        bool is_sizing_cursor = false;\n                        CURSORINFO cinfo;\n                        cinfo.cbSize = sizeof(CURSORINFO);\n\n                        if (::GetCursorInfo(&cinfo))\n                        {\n                            is_sizing_cursor = ( (cinfo.hCursor == ::LoadCursor(nullptr, IDC_SIZENESW)) ||\n                                                 (cinfo.hCursor == ::LoadCursor(nullptr, IDC_SIZENS))   ||\n                                                 (cinfo.hCursor == ::LoadCursor(nullptr, IDC_SIZENWSE)) ||\n                                                 (cinfo.hCursor == ::LoadCursor(nullptr, IDC_SIZEWE)) );\n                        }\n\n\n                        if (!is_sizing_cursor)\n                        {\n                            m_DragWindow = hwnd;\n                        }\n\n                        m_DragOverlayMsgSent = false;\n                    }\n                }\n                else if (win_event == EVENT_SYSTEM_MOVESIZEEND)\n                {\n                    if (m_DragWindow != nullptr)\n                    {\n                        ::SetCursorPos(m_DragStartMousePos.x, m_DragStartMousePos.y);\n                        ::SetWindowPos(hwnd, nullptr, m_DragStartWindowRect.left, m_DragStartWindowRect.top, 0, 0, SWP_NOZORDER | SWP_NOSIZE | SWP_NOACTIVATE);\n\n                        if (m_ThreadLocalData.KeepOnScreen)\n                        {\n                            MoveWindowIntoWorkArea(hwnd);\n                        }\n                    }\n\n                    m_DragWindow = nullptr;\n                }\n            }\n        }\n\n        #endif\n    }\n}\n\nvoid WindowManager::HandleCaretWinEvent(DWORD win_event, HWND hwnd)\n{\n    //Text input focus tracking is limited to tracking applications sending caret win events.\n    //This is only an approximation in lack of accessible on-screen-keyboard request events (which still wouldn't cover every custom control)\n    //and makes assumptions about typical application behavior to avoid false positives.\n    //It generally works reasonably well for applications that use native controls or send assistive events for their custom ones, however.\n\n    //Get the root window if possible so different controls don't count as separate windows\n    hwnd = ::GetAncestor(hwnd, GA_ROOT);\n\n    switch (win_event)\n    {\n        case EVENT_SYSTEM_FOREGROUND:\n        {\n            if (m_ThreadTextInputFocusCaretWindow != nullptr)\n            {\n                m_ThreadTextInputFocusCaretWindow = nullptr;\n                IPCManager::Get().PostMessageToDashboardApp(ipcmsg_action, ipcact_winmanager_text_input_focus, false);\n            }\n\n            break;\n        }\n        case EVENT_OBJECT_CREATE:\n        case EVENT_OBJECT_SHOW:\n        case EVENT_OBJECT_LOCATIONCHANGE:   //Some applications don't send any caret events except for location change, so we accept those too in order to support them at least partially\n        {\n            if (m_ThreadTextInputFocusCaretWindow == nullptr)\n            {\n                CURSORINFO cinfo = {0};\n                cinfo.cbSize = sizeof(CURSORINFO);\n\n                if (::GetCursorInfo(&cinfo))\n                {\n                    if (hwnd == ::GetForegroundWindow())\n                    {\n                        //Only trigger when the mouse cursor is visible to get rid of false positives from games and such\n                        if (cinfo.flags & CURSOR_SHOWING)\n                        {\n                            //If the event is EVENT_OBJECT_LOCATIONCHANGE, ignore the event if the cursor isn't caret/ibeam or it's been longer than 200 ms since the last click\n                            //Some applications send spurious location change events for the caret even multiple seconds after anything changed, so we need to filter those out\n                            if ( (win_event == EVENT_OBJECT_LOCATIONCHANGE) && ((cinfo.hCursor != ::LoadCursor(nullptr, IDC_IBEAM)) || (m_ThreadTextInputFocusClickTick + 200 < ::GetTickCount64())) )\n                            {\n                                break;\n                            }\n\n                            m_ThreadTextInputFocusCaretWindow = hwnd;\n\n                            IPCManager::Get().PostMessageToDashboardApp(ipcmsg_action, ipcact_winmanager_text_input_focus, true);\n                        }\n                    }\n                }\n            }\n            break;\n        }\n        case EVENT_OBJECT_DESTROY:\n        case EVENT_OBJECT_HIDE:\n        {\n            //Either be the last caret-changing window or be focused\n            if ( (m_ThreadTextInputFocusCaretWindow == hwnd) || (::GetForegroundWindow() == hwnd) )\n            {\n                m_ThreadTextInputFocusCaretWindow = nullptr;\n\n                IPCManager::Get().PostMessageToDashboardApp(ipcmsg_action, ipcact_winmanager_text_input_focus, false);\n            }\n            break;\n        }\n    }\n}\n\nvoid WindowManager::HandleTextInputMouseClick()\n{\n    if (m_ThreadTextInputFocusCaretWindow != nullptr)\n    {\n        //If a mouse click happened while the cursor isn't the caret/ibeam cursor, we assume it unfocused any active text input even if there's no win event sent for it\n        CURSORINFO cinfo = {0};\n        cinfo.cbSize = sizeof(CURSORINFO);\n        if ( (!::GetCursorInfo(&cinfo)) || (cinfo.hCursor != ::LoadCursor(nullptr, IDC_IBEAM)) )\t//If cursor access fails or cursor isn't ibeam\n        {\n            m_ThreadTextInputFocusCaretWindow = nullptr;\n\n            IPCManager::Get().PostMessageToDashboardApp(ipcmsg_action, ipcact_winmanager_text_input_focus, false);\n        }\n    }\n\n    //Track the last click time\n    m_ThreadTextInputFocusClickTick = ::GetTickCount64();\n}\n\nvoid WindowManager::WindowManager::WinEventProc(HWINEVENTHOOK /*event_hook_handle*/, DWORD win_event, HWND hwnd, LONG id_object, LONG id_child, DWORD event_thread, DWORD event_time)\n{\n    Get().HandleWinEvent(win_event, hwnd, id_object, id_child, event_thread, event_time);\n}\n\nvoid WindowManager::ManageEventHooks(HWINEVENTHOOK& hook_handle_move_size, HWINEVENTHOOK& hook_handle_location_change, HWINEVENTHOOK& hook_handle_foreground, HWINEVENTHOOK& hook_handle_destroy_show,\n                                     HWINEVENTHOOK& hook_handle_caret)\n{\n    #ifndef DPLUS_UI\n\n    if ( (m_ThreadLocalData.BlockDrag) && (hook_handle_move_size == nullptr) )\n    {\n        hook_handle_move_size       = ::SetWinEventHook(EVENT_SYSTEM_MOVESIZESTART, EVENT_SYSTEM_MOVESIZEEND, nullptr, WindowManager::WinEventProc, 0, 0, \n                                                        WINEVENT_OUTOFCONTEXT | WINEVENT_SKIPOWNPROCESS);\n        hook_handle_location_change = ::SetWinEventHook(EVENT_OBJECT_LOCATIONCHANGE, EVENT_OBJECT_LOCATIONCHANGE, nullptr, WindowManager::WinEventProc, 0, 0,\n                                                        WINEVENT_OUTOFCONTEXT | WINEVENT_SKIPOWNPROCESS);\n    }\n    else if ( (!m_ThreadLocalData.BlockDrag) && (hook_handle_move_size != nullptr) )\n    {\n        UnhookWinEvent(hook_handle_move_size);\n        UnhookWinEvent(hook_handle_location_change);\n\n        hook_handle_move_size       = nullptr;\n        hook_handle_location_change = nullptr;\n    }\n\n    if (hook_handle_foreground == nullptr)\n    {\n        //Set initial elevated process focus state beforehand\n        DWORD process_id;\n        ::GetWindowThreadProcessId(::GetForegroundWindow(), &process_id);\n        m_FocusLastProcess = process_id;\n\n        //Send process elevation state to UI\n        IPCManager::Get().PostConfigMessageToUIApp(configid_bool_state_window_focused_process_elevated, IsProcessElevated(process_id));\n\n        hook_handle_foreground = ::SetWinEventHook(EVENT_SYSTEM_FOREGROUND, EVENT_SYSTEM_FOREGROUND, nullptr, WindowManager::WinEventProc, 0, 0, WINEVENT_OUTOFCONTEXT | WINEVENT_SKIPOWNPROCESS);\n    }\n\n    if (hook_handle_caret == nullptr)\n    {\n        hook_handle_caret = ::SetWinEventHook(EVENT_OBJECT_CREATE, EVENT_OBJECT_LOCATIONCHANGE, nullptr, WindowManager::WinEventProc, 0, 0, WINEVENT_OUTOFCONTEXT | WINEVENT_SKIPOWNPROCESS);\n    }\n\n    #endif\n\n    if (hook_handle_destroy_show == nullptr)\n    {\n        hook_handle_destroy_show = ::SetWinEventHook(EVENT_OBJECT_DESTROY, /*EVENT_OBJECT_SHOW*/EVENT_OBJECT_UNCLOAKED, nullptr, WindowManager::WinEventProc, 0, 0,\n                                                     WINEVENT_OUTOFCONTEXT | WINEVENT_SKIPOWNPROCESS);\n    }\n\n    //Reset drag window state when target window is nullptr\n    if (m_ThreadLocalData.TargetWindow == nullptr)\n    {\n        m_DragWindow = nullptr;\n        m_DragOverlayMsgSent = false;\n    }\n    else //Store drag start mouse position and window rect now, since doing on actual drag start will be too late\n    {\n        ::GetCursorPos(&m_DragStartMousePos);\n        ::GetWindowRect(m_ThreadLocalData.TargetWindow, &m_DragStartWindowRect);\n    }\n}\n\nDWORD WindowManager::WindowManagerThreadEntry(void* /*param*/)\n{\n    //Copy thread data for lock-free reads later\n    {\n        WindowManager& wman = Get();\n        std::lock_guard<std::mutex> lock(wman.m_ThreadMutex);\n\n        wman.m_ThreadLocalData = wman.m_ThreadData;\n    }\n\n    //Create event hooks\n    HWINEVENTHOOK hook_handle_move_size       = nullptr;\n    HWINEVENTHOOK hook_handle_location_change = nullptr;\n    HWINEVENTHOOK hook_handle_foreground      = nullptr;\n    HWINEVENTHOOK hook_handle_destroy_show    = nullptr;\n    HWINEVENTHOOK hook_handle_caret           = nullptr;\n\n    Get().ManageEventHooks(hook_handle_move_size, hook_handle_location_change, hook_handle_foreground, hook_handle_destroy_show, hook_handle_caret);\n\n    //Wait for callbacks, update or quit message\n    MSG msg;\n    while (::GetMessage(&msg, 0, 0, 0))\n    {\n        if (msg.message == WM_WINDOWMANAGER_UPDATE_DATA)\n        {\n            WindowManager& wman = Get();\n\n            //Copy new thread data\n            {\n                std::lock_guard<std::mutex> lock(wman.m_ThreadMutex);\n\n                if (wman.m_ThreadData.TargetWindow == nullptr)\n                {\n                    //Give a potentially dragged window's process a little bit of time to realize the mouse release\n                    ::Sleep(20);\n                }\n\n                //Process all pending messages/callbacks before continuing\n                while (::PeekMessage(&msg, 0, 0, WM_APP, PM_REMOVE));\n\n                wman.m_ThreadLocalData = wman.m_ThreadData;\n            }\n\n            wman.ManageEventHooks(hook_handle_move_size, hook_handle_location_change, hook_handle_foreground, hook_handle_destroy_show, hook_handle_caret);\n\n            //Notify main thread we're done\n            {\n                std::lock_guard<std::mutex> lock(wman.m_UpdateDoneMutex);\n                wman.m_UpdateDoneFlag = true;\n            }\n            wman.m_UpdateDoneCV.notify_one();\n        }\n        else if (msg.message == WM_WINDOWMANAGER_TEXT_INPUT_MOUSE_CLICK)\n        {\n            Get().HandleTextInputMouseClick();\n        }\n    }\n\n    ::UnhookWinEvent(hook_handle_move_size);\n    ::UnhookWinEvent(hook_handle_location_change);\n    ::UnhookWinEvent(hook_handle_foreground);\n    ::UnhookWinEvent(hook_handle_destroy_show);\n    ::UnhookWinEvent(hook_handle_caret);\n\n    return 0;\n}\n"
  },
  {
    "path": "src/Shared/WindowManager.h",
    "content": "#pragma once\n\n#define NOMINMAX\n#include <windows.h>\n\n#include <vector>\n#include <mutex>\n\nclass WindowInfo\n{\n    private:\n        HWND m_WindowHandle;\n        mutable HICON m_Icon;              //Is nullptr until requested by calling GetIcon() for the first time\n        std::wstring m_Title;\n        std::wstring m_ClassName;\n        mutable std::string m_ExeName;     //Is empty until requested by calling GetExeName() for the first time\n        mutable std::string m_ListTitle;   //Is empty until requested by calling GetListTitle() for the first time\n\n    public: \n        WindowInfo(HWND window_handle);\n        bool operator==(const WindowInfo& info) { return (m_WindowHandle == info.m_WindowHandle); }\n        bool operator!=(const WindowInfo& info) { return !(*this == info); }\n\n        HWND GetWindowHandle() const;\n        HICON GetIcon() const;\n        const std::wstring& GetTitle() const;\n        const std::wstring& GetWindowClassName() const;\n        const std::string& GetExeName() const;\n        const std::string& GetListTitle() const;\n        \n        const bool IsClassNameMatching(const std::wstring& class_name) const;    //Returns true if names match or either is empty\n\n        bool UpdateWindowTitle();          //Returns if the title has changed\n\n        static std::string GetExeName(HWND window_handle);\n        static HICON GetIcon(HWND window_handle);\n        static HWND FindClosestWindowForTitle(const std::string& title_str, const std::string& class_str, const std::string& exe_str, const std::vector<WindowInfo>& window_list, \n                                              bool use_strict_matching = false);\n};\n\nstruct WindowManagerThreadData\n{\n    bool BlockDrag = false;\n    bool DoOverlayDrag = false;\n    bool KeepOnScreen = false;\n    HWND TargetWindow = nullptr;\n    unsigned int TargetOverlayID = UINT_MAX;\n\n    bool operator==(const WindowManagerThreadData b)\n    {\n        return ( (BlockDrag       == b.BlockDrag)     &&\n                 (DoOverlayDrag   == b.DoOverlayDrag) &&\n                 (KeepOnScreen    == b.KeepOnScreen)  &&\n                 (TargetWindow    == b.TargetWindow)  &&\n                 (TargetOverlayID == b.TargetOverlayID) );\n    }\n    bool operator!=(const WindowManagerThreadData b)\n    {\n        return !(*this == b);\n    }\n};\n\nclass InputSimulator;\n\n//WindowManager uses a separate thread for win event hook callbacks in order to be able to react as soon as possible (needed for window drag blocking)\n//For the UI process it only really tracks the window list\nclass WindowManager\n{\n    public:\n        static WindowManager& Get();\n\n        //- Only called by main thread\n        void UpdateConfigState();                                                                //Updates config state from ConfigManager for the WindowManager thread\n        void SetTargetWindow(HWND window, unsigned int overlay_id = UINT_MAX);                   //Sets target window and overlay id for the WindowManager thread\n        HWND GetTargetWindow() const;\n        void SetActive(bool is_active);                                                          //Set active state for the window manager. Threads are destroyed when it's inactive\n        bool IsActive() const;\n        void SetOverlayActive(bool is_active);                                                   //Set overlay active state. Overlay interactive config states are forced to false when inactive\n        bool IsOverlayActive() const;\n\n        const WindowInfo& WindowListAdd(HWND window);                                            //Returns reference to new or already existing window\n        std::wstring WindowListRemove(HWND window);                                              //Returns title of removed window or blank wstring\n        WindowInfo const* WindowListUpdateTitle(HWND window, bool* has_title_changed = nullptr); //Returns pointer to updated window (may be nullptr)\n        const std::vector<WindowInfo>& WindowListGet() const;\n        WindowInfo const* WindowListFindWindow(HWND window) const;\n\n        bool IsTextInputFocused();\n        void UpdateTextInputFocusedState(bool new_state);                                        //Update main thread accessible state in response to message sent by WindowManager thread\n        void OnTextInputLeftMouseClick();                                                        //Call for text input focus tracking purposes when left mouse was clicked\n\n        bool WouldDragMaximizedTitleBar(HWND window, int prev_cursor_x, int prev_cursor_y, int new_cursor_x, int new_cursor_y);\n        bool IsHoveringCapturableTitleBar(HWND window, int cursor_x, int cursor_y);\n        void RaiseAndFocusWindow(HWND window, InputSimulator* input_sim_ptr = nullptr);          //input_sim_ptr is optional but passing it increases chance of success\n        bool SetTempTopMostWindow(HWND window);\n        bool ClearTempTopMostWindow();\n        void FocusActiveVRSceneApp(InputSimulator* input_sim_ptr = nullptr);\n        static void MoveWindowIntoWorkArea(HWND window);\n\n    private:\n        //- Only accessed in main thread\n        HANDLE m_ThreadHandle          = nullptr;\n        DWORD m_ThreadID               = 0;\n        HWND m_TargetWindow            = nullptr;\n        unsigned int m_TargetOverlayID = UINT_MAX;\n\n        bool m_IsActive        = false;\n        bool m_IsOverlayActive = false;\n\n        HWND m_LastFocusFailedWindow    = nullptr;\n        ULONGLONG m_LastFocusFailedTick = 0;\n        HWND m_TempTopMostWindow        = nullptr;\n\n        std::vector<WindowInfo> m_WindowList;\n\n        bool m_IsTextInputFocused                = false;\n        bool m_IsTextInputFocusedPending         = false;\n        ULONGLONG m_IsTextInputFocusedUpdateTick = 0;\n\n        //- Protected by m_ThreadMutex\n        std::mutex m_ThreadMutex;\n        WindowManagerThreadData m_ThreadData;\n\n        //- Synchronization variables for UpdateConfigState(). m_TargetWindow is not something we can afford to have updated slightly delayed, so that function blocks\n        std::condition_variable m_UpdateDoneCV;\n        std::mutex m_UpdateDoneMutex;\n        bool m_UpdateDoneFlag = false;\n\n        //- Only accessed in WindowManager thread\n        WindowManagerThreadData m_ThreadLocalData;\n        POINT m_DragStartMousePos   {0, 0};\n        RECT m_DragStartWindowRect  {0, 0, 0, 0};\n        HWND m_DragWindow         = nullptr;\n        bool m_DragOverlayMsgSent = false;\n        DWORD m_FocusLastProcess  = 0;\n\n        //    Local state for text input focus tracking\n        HWND m_ThreadTextInputFocusCaretWindow    = nullptr;\n        ULONGLONG m_ThreadTextInputFocusClickTick = 0;\n\n        //- Only called by main thread\n        void WindowListInit();\n\n        //- Only called by WindowManager thread\n        void HandleWinEvent(DWORD win_event, HWND hwnd, LONG id_object, LONG id_child, DWORD event_thread, DWORD event_time);\n        void HandleCaretWinEvent(DWORD win_event, HWND hwnd);\n        void HandleTextInputMouseClick();\n\n        static void CALLBACK WinEventProc(HWINEVENTHOOK event_hook_handle, DWORD win_event, HWND hwnd, LONG id_object, LONG id_child, DWORD event_thread, DWORD event_time);\n        void ManageEventHooks(HWINEVENTHOOK& hook_handle_move_size, HWINEVENTHOOK& hook_handle_location_change, HWINEVENTHOOK& hook_handle_foreground, HWINEVENTHOOK& hook_handle_destroy_show,\n                              HWINEVENTHOOK& hook_handle_caret);\n\n        static DWORD WindowManagerThreadEntry(void* param);\n};"
  },
  {
    "path": "src/Shared/loguru.cpp",
    "content": "#if defined(__GNUC__) || defined(__clang__)\n// Disable all warnings from gcc/clang:\n#pragma GCC diagnostic push\n#pragma GCC diagnostic ignored \"-Wpragmas\"\n\n#pragma GCC diagnostic ignored \"-Wc++98-compat\"\n#pragma GCC diagnostic ignored \"-Wc++98-compat-pedantic\"\n#pragma GCC diagnostic ignored \"-Wexit-time-destructors\"\n#pragma GCC diagnostic ignored \"-Wformat-nonliteral\"\n#pragma GCC diagnostic ignored \"-Wglobal-constructors\"\n#pragma GCC diagnostic ignored \"-Wgnu-zero-variadic-macro-arguments\"\n#pragma GCC diagnostic ignored \"-Wmissing-prototypes\"\n#pragma GCC diagnostic ignored \"-Wpadded\"\n#pragma GCC diagnostic ignored \"-Wsign-conversion\"\n#pragma GCC diagnostic ignored \"-Wunknown-pragmas\"\n#pragma GCC diagnostic ignored \"-Wunused-macros\"\n#pragma GCC diagnostic ignored \"-Wzero-as-null-pointer-constant\"\n#elif defined(_MSC_VER)\n#pragma warning(push)\n#pragma warning(disable:4365) // conversion from 'X' to 'Y', signed/unsigned mismatch\n#endif\n\n#include \"loguru.hpp\"\n\n#ifndef LOGURU_HAS_BEEN_IMPLEMENTED\n#define LOGURU_HAS_BEEN_IMPLEMENTED\n\n#define LOGURU_PREAMBLE_WIDTH (53 + LOGURU_THREADNAME_WIDTH + LOGURU_FILENAME_WIDTH)\n\n#undef min\n#undef max\n\n#include <algorithm>\n#include <atomic>\n#include <cctype>\n#include <chrono>\n#include <cstdarg>\n#include <cstdio>\n#include <cstdlib>\n#include <cstring>\n#include <mutex>\n#include <regex>\n#include <string>\n#include <thread>\n#include <vector>\n\n#if LOGURU_SYSLOG\n#include <syslog.h>\n#else\n#define LOG_USER 0\n#endif\n\n#ifdef _WIN32\n\t#include <direct.h>\n\n\t#define localtime_r(a, b) localtime_s(b, a) // No localtime_r with MSVC, but arguments are swapped for localtime_s\n#else\n\t#include <signal.h>\n\t#include <sys/stat.h> // mkdir\n\t#include <unistd.h>   // STDERR_FILENO\n#endif\n\n#ifdef __linux__\n\t#include <linux/limits.h> // PATH_MAX\n#elif !defined(_WIN32)\n\t#include <limits.h> // PATH_MAX\n#endif\n\n#ifndef PATH_MAX\n\t#define PATH_MAX 1024\n#endif\n\n#ifdef __APPLE__\n\t#include \"TargetConditionals.h\"\n#endif\n\n// TODO: use defined(_POSIX_VERSION) for some of these things?\n\n#if defined(_WIN32) || defined(__CYGWIN__)\n\t#define LOGURU_PTHREADS    0\n\t#define LOGURU_WINTHREADS  1\n\t#ifndef LOGURU_STACKTRACES\n\t\t#define LOGURU_STACKTRACES 0\n\t#endif\n#else\n\t#define LOGURU_PTHREADS    1\n\t#define LOGURU_WINTHREADS  0\n\t#ifdef __GLIBC__\n\t\t#ifndef LOGURU_STACKTRACES\n\t\t\t#define LOGURU_STACKTRACES 1\n\t\t#endif\n\t#else\n\t\t#ifndef LOGURU_STACKTRACES\n\t\t\t#define LOGURU_STACKTRACES 0\n\t\t#endif\n\t#endif\n#endif\n\n#if LOGURU_STACKTRACES\n\t#include <cxxabi.h>    // for __cxa_demangle\n\t#include <dlfcn.h>     // for dladdr\n\t#include <execinfo.h>  // for backtrace\n#endif // LOGURU_STACKTRACES\n\n#if LOGURU_PTHREADS\n\t#include <pthread.h>\n\t#if defined(__FreeBSD__)\n\t\t#include <pthread_np.h>\n\t\t#include <sys/thr.h>\n\t#elif defined(__OpenBSD__)\n\t\t#include <pthread_np.h>\n\t#endif\n\n\t#ifdef __linux__\n\t\t/* On Linux, the default thread name is the same as the name of the binary.\n\t\t   Additionally, all new threads inherit the name of the thread it got forked from.\n\t\t   For this reason, Loguru use the pthread Thread Local Storage\n\t\t   for storing thread names on Linux. */\n\t\t#ifndef LOGURU_PTLS_NAMES\n\t\t\t#define LOGURU_PTLS_NAMES 1\n\t\t#endif\n\t#endif\n#endif\n\n#if LOGURU_WINTHREADS\n\t#ifndef _WIN32_WINNT\n\t\t#define _WIN32_WINNT 0x0502\n\t#endif\n\t#define WIN32_LEAN_AND_MEAN\n\t#define NOMINMAX\n\t#include <windows.h>\n#endif\n\n#ifndef LOGURU_PTLS_NAMES\n   #define LOGURU_PTLS_NAMES 0\n#endif\n\nLOGURU_ANONYMOUS_NAMESPACE_BEGIN\n\nnamespace loguru\n{\n\tusing namespace std::chrono;\n\n#if LOGURU_WITH_FILEABS\n\tstruct FileAbs\n\t{\n\t\tchar path[PATH_MAX];\n\t\tchar mode_str[4];\n\t\tVerbosity verbosity;\n\t\tstruct stat st;\n\t\tFILE* fp;\n\t\tbool is_reopening = false; // to prevent recursive call in file_reopen.\n\t\tdecltype(steady_clock::now()) last_check_time = steady_clock::now();\n\t};\n#else\n\ttypedef FILE* FileAbs;\n#endif\n\n\tstruct Callback\n\t{\n\t\tstd::string     id;\n\t\tlog_handler_t   callback;\n\t\tvoid*           user_data;\n\t\tVerbosity       verbosity; // Does not change!\n\t\tclose_handler_t close;\n\t\tflush_handler_t flush;\n\t\tunsigned        indentation;\n\t};\n\n\tusing CallbackVec = std::vector<Callback>;\n\n\tusing StringPair     = std::pair<std::string, std::string>;\n\tusing StringPairList = std::vector<StringPair>;\n\n\tconst auto s_start_time = steady_clock::now();\n\n\tVerbosity g_stderr_verbosity  = Verbosity_0;\n\tbool      g_colorlogtostderr  = true;\n\tunsigned  g_flush_interval_ms = 0;\n\tbool      g_preamble_header   = true;\n\tbool      g_preamble          = true;\n\n\tVerbosity g_internal_verbosity = Verbosity_0;\n\n\t// Preamble details\n\tbool      g_preamble_date     = true;\n\tbool      g_preamble_time     = true;\n\tbool      g_preamble_uptime   = true;\n\tbool      g_preamble_thread   = true;\n\tbool      g_preamble_file     = true;\n\tbool      g_preamble_verbose  = true;\n\tbool      g_preamble_pipe     = true;\n\n\tstatic std::recursive_mutex  s_mutex;\n\tstatic Verbosity             s_max_out_verbosity = Verbosity_OFF;\n\tstatic std::string           s_argv0_filename;\n\tstatic std::string           s_arguments;\n\tstatic char                  s_current_dir[PATH_MAX];\n\tstatic CallbackVec           s_callbacks;\n\tstatic fatal_handler_t       s_fatal_handler   = nullptr;\n\tstatic verbosity_to_name_t   s_verbosity_to_name_callback = nullptr;\n\tstatic name_to_verbosity_t   s_name_to_verbosity_callback = nullptr;\n\tstatic StringPairList        s_user_stack_cleanups;\n\tstatic bool                  s_strip_file_path = true;\n\tstatic std::atomic<unsigned> s_stderr_indentation { 0 };\n\n\t// For periodic flushing:\n\tstatic std::thread* s_flush_thread   = nullptr;\n\tstatic bool         s_needs_flushing = false;\n\n\tstatic SignalOptions s_signal_options = SignalOptions::none();\n\n\tstatic const bool s_terminal_has_color = [](){\n\t\t#ifdef _WIN32\n\t\t\t#ifndef ENABLE_VIRTUAL_TERMINAL_PROCESSING\n\t\t\t#define ENABLE_VIRTUAL_TERMINAL_PROCESSING  0x0004\n\t\t\t#endif\n\n\t\t\tHANDLE hOut = GetStdHandle(STD_OUTPUT_HANDLE);\n\t\t\tif (hOut != INVALID_HANDLE_VALUE) {\n\t\t\t\tDWORD dwMode = 0;\n\t\t\t\tGetConsoleMode(hOut, &dwMode);\n\t\t\t\tdwMode |= ENABLE_VIRTUAL_TERMINAL_PROCESSING;\n\t\t\t\treturn SetConsoleMode(hOut, dwMode) != 0;\n\t\t\t}\n\t\t\treturn false;\n\t\t#else\n\t\t\tif (!isatty(STDERR_FILENO)) {\n\t\t\t\treturn false;\n\t\t\t}\n\t\t\tif (const char* term = getenv(\"TERM\")) {\n\t\t\t\treturn 0 == strcmp(term, \"cygwin\")\n\t\t\t\t\t|| 0 == strcmp(term, \"linux\")\n\t\t\t\t\t|| 0 == strcmp(term, \"rxvt-unicode-256color\")\n\t\t\t\t\t|| 0 == strcmp(term, \"screen\")\n\t\t\t\t\t|| 0 == strcmp(term, \"screen-256color\")\n\t\t\t\t\t|| 0 == strcmp(term, \"screen.xterm-256color\")\n\t\t\t\t\t|| 0 == strcmp(term, \"tmux-256color\")\n\t\t\t\t\t|| 0 == strcmp(term, \"xterm\")\n\t\t\t\t\t|| 0 == strcmp(term, \"xterm-256color\")\n\t\t\t\t\t|| 0 == strcmp(term, \"xterm-termite\")\n\t\t\t\t\t|| 0 == strcmp(term, \"xterm-color\");\n\t\t\t} else {\n\t\t\t\treturn false;\n\t\t\t}\n\t\t#endif\n\t}();\n\n\tstatic void print_preamble_header(char* out_buff, size_t out_buff_size);\n\n\t// ------------------------------------------------------------------------------\n\t// Colors\n\n\tbool terminal_has_color() { return s_terminal_has_color; }\n\n\t// Colors\n\n#ifdef _WIN32\n#define VTSEQ(ID) (\"\\x1b[1;\" #ID \"m\")\n#else\n#define VTSEQ(ID) (\"\\x1b[\" #ID \"m\")\n#endif\n\n\tconst char* terminal_black()      { return s_terminal_has_color ? VTSEQ(30) : \"\"; }\n\tconst char* terminal_red()        { return s_terminal_has_color ? VTSEQ(31) : \"\"; }\n\tconst char* terminal_green()      { return s_terminal_has_color ? VTSEQ(32) : \"\"; }\n\tconst char* terminal_yellow()     { return s_terminal_has_color ? VTSEQ(33) : \"\"; }\n\tconst char* terminal_blue()       { return s_terminal_has_color ? VTSEQ(34) : \"\"; }\n\tconst char* terminal_purple()     { return s_terminal_has_color ? VTSEQ(35) : \"\"; }\n\tconst char* terminal_cyan()       { return s_terminal_has_color ? VTSEQ(36) : \"\"; }\n\tconst char* terminal_light_gray() { return s_terminal_has_color ? VTSEQ(37) : \"\"; }\n\tconst char* terminal_white()      { return s_terminal_has_color ? VTSEQ(37) : \"\"; }\n\tconst char* terminal_light_red()  { return s_terminal_has_color ? VTSEQ(91) : \"\"; }\n\tconst char* terminal_dim()        { return s_terminal_has_color ? VTSEQ(2)  : \"\"; }\n\n\t// Formating\n\tconst char* terminal_bold()       { return s_terminal_has_color ? VTSEQ(1) : \"\"; }\n\tconst char* terminal_underline()  { return s_terminal_has_color ? VTSEQ(4) : \"\"; }\n\n\t// You should end each line with this!\n\tconst char* terminal_reset()      { return s_terminal_has_color ? VTSEQ(0) : \"\"; }\n\n\t// ------------------------------------------------------------------------------\n#if LOGURU_WITH_FILEABS\n\tvoid file_reopen(void* user_data);\n\tinline FILE* to_file(void* user_data) { return reinterpret_cast<FileAbs*>(user_data)->fp; }\n#else\n\tinline FILE* to_file(void* user_data) { return reinterpret_cast<FILE*>(user_data); }\n#endif\n\n\tvoid file_log(void* user_data, const Message& message)\n\t{\n#if LOGURU_WITH_FILEABS\n\t\tFileAbs* file_abs = reinterpret_cast<FileAbs*>(user_data);\n\t\tif (file_abs->is_reopening) {\n\t\t\treturn;\n\t\t}\n\t\t// It is better checking file change every minute/hour/day,\n\t\t// instead of doing this every time we log.\n\t\t// Here check_interval is set to zero to enable checking every time;\n\t\tconst auto check_interval = seconds(0);\n\t\tif (duration_cast<seconds>(steady_clock::now() - file_abs->last_check_time) > check_interval) {\n\t\t\tfile_abs->last_check_time = steady_clock::now();\n\t\t\tfile_reopen(user_data);\n\t\t}\n\t\tFILE* file = to_file(user_data);\n\t\tif (!file) {\n\t\t\treturn;\n\t\t}\n#else\n\t\tFILE* file = to_file(user_data);\n#endif\n\t\tfprintf(file, \"%s%s%s%s\\n\",\n\t\t\tmessage.preamble, message.indentation, message.prefix, message.message);\n\t\tif (g_flush_interval_ms == 0) {\n\t\t\tfflush(file);\n\t\t}\n\t}\n\n\tvoid file_close(void* user_data)\n\t{\n\t\tFILE* file = to_file(user_data);\n\t\tif (file) {\n\t\t\tfclose(file);\n\t\t}\n#if LOGURU_WITH_FILEABS\n\t\tdelete reinterpret_cast<FileAbs*>(user_data);\n#endif\n\t}\n\n\tvoid file_flush(void* user_data)\n\t{\n\t\tFILE* file = to_file(user_data);\n\t\tfflush(file);\n\t}\n\n#if LOGURU_WITH_FILEABS\n\tvoid file_reopen(void* user_data)\n\t{\n\t\tFileAbs * file_abs = reinterpret_cast<FileAbs*>(user_data);\n\t\tstruct stat st;\n\t\tint ret;\n\t\tif (!file_abs->fp || (ret = stat(file_abs->path, &st)) == -1 || (st.st_ino != file_abs->st.st_ino)) {\n\t\t\tfile_abs->is_reopening = true;\n\t\t\tif (file_abs->fp) {\n\t\t\t\tfclose(file_abs->fp);\n\t\t\t}\n\t\t\tif (!file_abs->fp) {\n\t\t\t\tVLOG_F(g_internal_verbosity, \"Reopening file '\" LOGURU_FMT(s) \"' due to previous error\", file_abs->path);\n\t\t\t}\n\t\t\telse if (ret < 0) {\n\t\t\t\tconst auto why = errno_as_text();\n\t\t\t\tVLOG_F(g_internal_verbosity, \"Reopening file '\" LOGURU_FMT(s) \"' due to '\" LOGURU_FMT(s) \"'\", file_abs->path, why.c_str());\n\t\t\t} else {\n\t\t\t\tVLOG_F(g_internal_verbosity, \"Reopening file '\" LOGURU_FMT(s) \"' due to file changed\", file_abs->path);\n\t\t\t}\n\t\t\t// try reopen current file.\n\t\t\tif (!create_directories(file_abs->path)) {\n\t\t\t\tLOG_F(ERROR, \"Failed to create directories to '\" LOGURU_FMT(s) \"'\", file_abs->path);\n\t\t\t}\n\t\t\tfile_abs->fp = fopen(file_abs->path, file_abs->mode_str);\n\t\t\tif (!file_abs->fp) {\n\t\t\t\tLOG_F(ERROR, \"Failed to open '\" LOGURU_FMT(s) \"'\", file_abs->path);\n\t\t\t} else {\n\t\t\t\tstat(file_abs->path, &file_abs->st);\n\t\t\t}\n\t\t\tfile_abs->is_reopening = false;\n\t\t}\n\t}\n#endif\n\t// ------------------------------------------------------------------------------\n\t// ------------------------------------------------------------------------------\n#if LOGURU_SYSLOG\n\tvoid syslog_log(void* /*user_data*/, const Message& message)\n\t{\n\t\t/*\n\t\t\tLevel 0: Is reserved for kernel panic type situations.\n\t\t\tLevel 1: Is for Major resource failure.\n\t\t\tLevel 2->7 Application level failures\n\t\t*/\n\t\tint level;\n\t\tif (message.verbosity < Verbosity_FATAL) {\n\t\t\tlevel = 1; // System Alert\n\t\t} else {\n\t\t\tswitch(message.verbosity) {\n\t\t\t\tcase Verbosity_FATAL:   level = 2; break;\t// System Critical\n\t\t\t\tcase Verbosity_ERROR:   level = 3; break;\t// System Error\n\t\t\t\tcase Verbosity_WARNING: level = 4; break;\t// System Warning\n\t\t\t\tcase Verbosity_INFO:    level = 5; break;\t// System Notice\n\t\t\t\tcase Verbosity_1:       level = 6; break;\t// System Info\n\t\t\t\tdefault:                level = 7; break;\t// System Debug\n\t\t\t}\n\t\t}\n\n\t\t// Note: We don't add the time info.\n\t\t// This is done automatically by the syslog deamon.\n\t\t// Otherwise log all information that the file log does.\n\t\tsyslog(level, \"%s%s%s\", message.indentation, message.prefix, message.message);\n\t}\n\n\tvoid syslog_close(void* /*user_data*/)\n\t{\n\t\tcloselog();\n\t}\n\n\tvoid syslog_flush(void* /*user_data*/)\n\t{}\n#endif\n// ------------------------------------------------------------------------------\n\t// Helpers:\n\n\tText::~Text() { free(_str); }\n\n#if LOGURU_USE_FMTLIB\n\tText vtextprintf(const char* format, fmt::format_args args)\n\t{\n\t\treturn Text(STRDUP(fmt::vformat(format, args).c_str()));\n\t}\n#else\n\tLOGURU_PRINTF_LIKE(1, 0)\n\tstatic Text vtextprintf(const char* format, va_list vlist)\n\t{\n#ifdef _WIN32\n\t\tint bytes_needed = _vscprintf(format, vlist);\n\t\tCHECK_F(bytes_needed >= 0, \"Bad string format: '%s'\", format);\n\t\tchar* buff = (char*)malloc(bytes_needed+1);\n\t\tvsnprintf(buff, bytes_needed+1, format, vlist);\n\t\treturn Text(buff);\n#else\n\t\tchar* buff = nullptr;\n\t\tint result = vasprintf(&buff, format, vlist);\n\t\tCHECK_F(result >= 0, \"Bad string format: '\" LOGURU_FMT(s) \"'\", format);\n\t\treturn Text(buff);\n#endif\n\t}\n\n\tText textprintf(const char* format, ...)\n\t{\n\t\tva_list vlist;\n\t\tva_start(vlist, format);\n\t\tauto result = vtextprintf(format, vlist);\n\t\tva_end(vlist);\n\t\treturn result;\n\t}\n#endif\n\n\t// Overloaded for variadic template matching.\n\tText textprintf()\n\t{\n\t\treturn Text(static_cast<char*>(calloc(1, 1)));\n\t}\n\n\tstatic const char* indentation(unsigned depth)\n\t{\n\t\tstatic const char buff[] =\n\t\t\".   .   .   .   .   .   .   .   .   .   \" \".   .   .   .   .   .   .   .   .   .   \"\n\t\t\".   .   .   .   .   .   .   .   .   .   \" \".   .   .   .   .   .   .   .   .   .   \"\n\t\t\".   .   .   .   .   .   .   .   .   .   \" \".   .   .   .   .   .   .   .   .   .   \"\n\t\t\".   .   .   .   .   .   .   .   .   .   \" \".   .   .   .   .   .   .   .   .   .   \"\n\t\t\".   .   .   .   .   .   .   .   .   .   \" \".   .   .   .   .   .   .   .   .   .   \";\n\t\tstatic const size_t INDENTATION_WIDTH = 4;\n\t\tstatic const size_t NUM_INDENTATIONS = (sizeof(buff) - 1) / INDENTATION_WIDTH;\n\t\tdepth = std::min<unsigned>(depth, NUM_INDENTATIONS);\n\t\treturn buff + INDENTATION_WIDTH * (NUM_INDENTATIONS - depth);\n\t}\n\n\tstatic void parse_args(int& argc, char* argv[], const char* verbosity_flag)\n\t{\n\t\tint arg_dest = 1;\n\t\tint out_argc = argc;\n\n\t\tauto verbosity_len = strlen(verbosity_flag);\n\n\t\tfor (int arg_it = 1; arg_it < argc; ++arg_it) {\n\t\t\tauto arg = argv[arg_it];\n\t\t\tauto arg_len = strlen(arg);\n\n\t\t\tbool last_is_alpha = false;\n\t\t\t#if LOGURU_USE_LOCALE\n\t\t\ttry {  // locale variant of isalpha will throw on error\n\t\t\t\tlast_is_alpha = std::isalpha(cmd[arg_len], std::locale(\"\"));\n\t\t\t}\n\t\t\tcatch (...) {\n\t\t\t\tlast_is_alpha = std::isalpha(static_cast<int>(arg[arg_len]));\n\t\t\t}\n\t\t\t#else\n\t\t\tlast_is_alpha = std::isalpha(static_cast<int>(arg[arg_len]));\n\t\t\t#endif\n\n\t\t\tif (strncmp(arg, verbosity_flag, verbosity_len) == 0 && !last_is_alpha) {\n\t\t\t\tout_argc -= 1;\n\t\t\t\tauto value_str = arg + arg_len;\n\t\t\t\tif (value_str[0] == '\\0') {\n\t\t\t\t\t// Value in separate argument\n\t\t\t\t\targ_it += 1;\n\t\t\t\t\tCHECK_LT_F(arg_it, argc, \"Missing verbosity level after \" LOGURU_FMT(s) \"\", verbosity_flag);\n\t\t\t\t\tvalue_str = argv[arg_it];\n\t\t\t\t\tout_argc -= 1;\n\t\t\t\t}\n\t\t\t\tif (*value_str == '=') { value_str += 1; }\n\n\t\t\t\tauto req_verbosity = get_verbosity_from_name(value_str);\n\t\t\t\tif (req_verbosity != Verbosity_INVALID) {\n\t\t\t\t\tg_stderr_verbosity = req_verbosity;\n\t\t\t\t} else {\n\t\t\t\t\tchar* end = 0;\n\t\t\t\t\tg_stderr_verbosity = static_cast<int>(strtol(value_str, &end, 10));\n\t\t\t\t\tCHECK_F(end && *end == '\\0',\n\t\t\t\t\t\t\"Invalid verbosity. Expected integer, INFO, WARNING, ERROR or OFF, got '\" LOGURU_FMT(s) \"'\", value_str);\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\targv[arg_dest++] = argv[arg_it];\n\t\t\t}\n\t\t}\n\n\t\targc = out_argc;\n\t\targv[argc] = nullptr;\n\t}\n\n\tstatic long long now_ns()\n\t{\n\t\treturn duration_cast<nanoseconds>(high_resolution_clock::now().time_since_epoch()).count();\n\t}\n\n\t// Returns the part of the path after the last / or \\ (if any).\n\tconst char* filename(const char* path)\n\t{\n\t\tfor (auto ptr = path; *ptr; ++ptr) {\n\t\t\tif (*ptr == '/' || *ptr == '\\\\') {\n\t\t\t\tpath = ptr + 1;\n\t\t\t}\n\t\t}\n\t\treturn path;\n\t}\n\n\t// ------------------------------------------------------------------------------\n\n\tstatic void on_atexit()\n\t{\n\t\tVLOG_F(g_internal_verbosity, \"atexit\");\n\t\tflush();\n\t}\n\n\tstatic void install_signal_handlers(const SignalOptions& signal_options);\n\n\tstatic void write_hex_digit(std::string& out, unsigned num)\n\t{\n\t\tDCHECK_LT_F(num, 16u);\n\t\tif (num < 10u) { out.push_back(char('0' + num)); }\n\t\telse { out.push_back(char('A' + num - 10)); }\n\t}\n\n\tstatic void write_hex_byte(std::string& out, uint8_t n)\n\t{\n\t\twrite_hex_digit(out, n >> 4u);\n\t\twrite_hex_digit(out, n & 0x0f);\n\t}\n\n\tstatic void escape(std::string& out, const std::string& str)\n\t{\n\t\tfor (char c : str) {\n\t\t\t/**/ if (c == '\\a') { out += \"\\\\a\";  }\n\t\t\telse if (c == '\\b') { out += \"\\\\b\";  }\n\t\t\telse if (c == '\\f') { out += \"\\\\f\";  }\n\t\t\telse if (c == '\\n') { out += \"\\\\n\";  }\n\t\t\telse if (c == '\\r') { out += \"\\\\r\";  }\n\t\t\telse if (c == '\\t') { out += \"\\\\t\";  }\n\t\t\telse if (c == '\\v') { out += \"\\\\v\";  }\n\t\t\telse if (c == '\\\\') { out += \"\\\\\\\\\"; }\n\t\t\telse if (c == '\\'') { out += \"\\\\\\'\"; }\n\t\t\telse if (c == '\\\"') { out += \"\\\\\\\"\"; }\n\t\t\telse if (c == ' ')  { out += \"\\\\ \";  }\n\t\t\telse if (0 <= c && c < 0x20) { // ASCI control character:\n\t\t\t// else if (c < 0x20 || c != (c & 127)) { // ASCII control character or UTF-8:\n\t\t\t\tout += \"\\\\x\";\n\t\t\t\twrite_hex_byte(out, static_cast<uint8_t>(c));\n\t\t\t} else { out += c; }\n\t\t}\n\t}\n\n\tText errno_as_text()\n\t{\n\t\tchar buff[256];\n\t#if defined(__GLIBC__) && defined(_GNU_SOURCE)\n\t\t// GNU Version\n\t\treturn Text(STRDUP(strerror_r(errno, buff, sizeof(buff))));\n\t#elif defined(__APPLE__) || _POSIX_C_SOURCE >= 200112L\n\t\t// XSI Version\n\t\tstrerror_r(errno, buff, sizeof(buff));\n\t\treturn Text(strdup(buff));\n\t#elif defined(_WIN32)\n\t\tstrerror_s(buff, sizeof(buff), errno);\n\t\treturn Text(STRDUP(buff));\n\t#else\n\t\t// Not thread-safe.\n\t\treturn Text(STRDUP(strerror(errno)));\n\t#endif\n\t}\n\n\tvoid init(int& argc, char* argv[], const Options& options)\n\t{\n\t\tCHECK_GT_F(argc,       0,       \"Expected proper argc/argv\");\n\t\tCHECK_EQ_F(argv[argc], nullptr, \"Expected proper argc/argv\");\n\n\t\ts_argv0_filename = filename(argv[0]);\n\n\t\t#ifdef _WIN32\n\t\t\t#define getcwd _getcwd\n\t\t#endif\n\n\t\tif (!getcwd(s_current_dir, sizeof(s_current_dir))) {\n\t\t\tconst auto error_text = errno_as_text();\n\t\t\tLOG_F(WARNING, \"Failed to get current working directory: \" LOGURU_FMT(s) \"\", error_text.c_str());\n\t\t}\n\n\t\ts_arguments = \"\";\n\t\tfor (int i = 0; i < argc; ++i) {\n\t\t\tescape(s_arguments, argv[i]);\n\t\t\tif (i + 1 < argc) {\n\t\t\t\ts_arguments += \" \";\n\t\t\t}\n\t\t}\n\n\t\tif (options.verbosity_flag) {\n\t\t\tparse_args(argc, argv, options.verbosity_flag);\n\t\t}\n\n\t\tif (const auto main_thread_name = options.main_thread_name) {\n\t\t\t#if LOGURU_PTLS_NAMES || LOGURU_WINTHREADS\n\t\t\t\tset_thread_name(main_thread_name);\n\t\t\t#elif LOGURU_PTHREADS\n\t\t\t\tchar old_thread_name[16] = {0};\n\t\t\t\tauto this_thread = pthread_self();\n\t\t\t\t#if defined(__APPLE__) || defined(__linux__) || defined(__sun)\n\t\t\t\t\tpthread_getname_np(this_thread, old_thread_name, sizeof(old_thread_name));\n\t\t\t\t#endif\n\t\t\t\tif (old_thread_name[0] == 0) {\n\t\t\t\t\t#ifdef __APPLE__\n\t\t\t\t\t\tpthread_setname_np(main_thread_name);\n\t\t\t\t\t#elif defined(__FreeBSD__) || defined(__OpenBSD__)\n\t\t\t\t\t\tpthread_set_name_np(this_thread, main_thread_name);\n\t\t\t\t\t#elif defined(__linux__) || defined(__sun)\n\t\t\t\t\t\tpthread_setname_np(this_thread, main_thread_name);\n\t\t\t\t\t#endif\n\t\t\t\t}\n\t\t\t#endif // LOGURU_PTHREADS\n\t\t}\n\n\t\tif (g_stderr_verbosity >= Verbosity_INFO) {\n\t\t\tif (g_preamble_header) {\n\t\t\t\tchar preamble_explain[LOGURU_PREAMBLE_WIDTH];\n\t\t\t\tprint_preamble_header(preamble_explain, sizeof(preamble_explain));\n\t\t\t\tif (g_colorlogtostderr && s_terminal_has_color) {\n\t\t\t\t\tfprintf(stderr, \"%s%s%s\\n\", terminal_reset(), terminal_dim(), preamble_explain);\n\t\t\t\t} else {\n\t\t\t\t\tfprintf(stderr, \"%s\\n\", preamble_explain);\n\t\t\t\t}\n\t\t\t}\n\t\t\tfflush(stderr);\n\t\t}\n\t\tVLOG_F(g_internal_verbosity, \"Arguments: \" LOGURU_FMT(s) \"\", s_arguments.c_str());\n\t\tif (strlen(s_current_dir) != 0)\n\t\t{\n\t\t\tVLOG_F(g_internal_verbosity, \"Current dir: \" LOGURU_FMT(s) \"\", s_current_dir);\n\t\t}\n\t\tVLOG_F(g_internal_verbosity, \"stderr verbosity: \" LOGURU_FMT(d) \"\", g_stderr_verbosity);\n\t\tVLOG_F(g_internal_verbosity, \"-----------------------------------\");\n\n\t\tinstall_signal_handlers(options.signal_options);\n\n\t\tatexit(on_atexit);\n\t}\n\n\tvoid shutdown()\n\t{\n\t\tVLOG_F(g_internal_verbosity, \"loguru::shutdown()\");\n\t\tremove_all_callbacks();\n\t\tset_fatal_handler(nullptr);\n\t\tset_verbosity_to_name_callback(nullptr);\n\t\tset_name_to_verbosity_callback(nullptr);\n\t}\n\n\tvoid write_date_time(char* buff, unsigned long long buff_size)\n\t{\n\t\tauto now = system_clock::now();\n\t\tlong long ms_since_epoch = duration_cast<milliseconds>(now.time_since_epoch()).count();\n\t\ttime_t sec_since_epoch = time_t(ms_since_epoch / 1000);\n\t\ttm time_info;\n\t\tlocaltime_r(&sec_since_epoch, &time_info);\n\t\tsnprintf(buff, buff_size, \"%04d%02d%02d_%02d%02d%02d.%03lld\",\n\t\t\t1900 + time_info.tm_year, 1 + time_info.tm_mon, time_info.tm_mday,\n\t\t\ttime_info.tm_hour, time_info.tm_min, time_info.tm_sec, ms_since_epoch % 1000);\n\t}\n\n\tconst char* argv0_filename()\n\t{\n\t\treturn s_argv0_filename.c_str();\n\t}\n\n\tconst char* arguments()\n\t{\n\t\treturn s_arguments.c_str();\n\t}\n\n\tconst char* current_dir()\n\t{\n\t\treturn s_current_dir;\n\t}\n\n\tconst char* home_dir()\n\t{\n\t\t#ifdef __MINGW32__\n\t\t\tauto home = getenv(\"USERPROFILE\");\n\t\t\tCHECK_F(home != nullptr, \"Missing USERPROFILE\");\n\t\t\treturn home;\n\t\t#elif defined(_WIN32)\n\t\t\tchar* user_profile;\n\t\t\tsize_t len;\n\t\t\terrno_t err = _dupenv_s(&user_profile, &len, \"USERPROFILE\");\n\t\t\tCHECK_F(err == 0, \"Missing USERPROFILE\");\n\t\t\treturn user_profile;\n\t\t#else // _WIN32\n\t\t\tauto home = getenv(\"HOME\");\n\t\t\tCHECK_F(home != nullptr, \"Missing HOME\");\n\t\t\treturn home;\n\t\t#endif // _WIN32\n\t}\n\n\tvoid suggest_log_path(const char* prefix, char* buff, unsigned long long buff_size)\n\t{\n\t\tif (prefix[0] == '~') {\n\t\t\tsnprintf(buff, buff_size - 1, \"%s%s\", home_dir(), prefix + 1);\n\t\t} else {\n\t\t\tsnprintf(buff, buff_size - 1, \"%s\", prefix);\n\t\t}\n\n\t\t// Check for terminating /\n\t\tsize_t n = strlen(buff);\n\t\tif (n != 0) {\n\t\t\tif (buff[n - 1] != '/') {\n\t\t\t\tCHECK_F(n + 2 < buff_size, \"Filename buffer too small\");\n\t\t\t\tbuff[n] = '/';\n\t\t\t\tbuff[n + 1] = '\\0';\n\t\t\t}\n\t\t}\n\n\t#ifdef _WIN32\n\t\tstrncat_s(buff, buff_size - strlen(buff) - 1, s_argv0_filename.c_str(), buff_size - strlen(buff) - 1);\n\t\tstrncat_s(buff, buff_size - strlen(buff) - 1, \"/\",                      buff_size - strlen(buff) - 1);\n\t\twrite_date_time(buff + strlen(buff),    buff_size - strlen(buff));\n\t\tstrncat_s(buff, buff_size - strlen(buff) - 1, \".log\",                   buff_size - strlen(buff) - 1);\n\t#else\n\t\tstrncat(buff, s_argv0_filename.c_str(), buff_size - strlen(buff) - 1);\n\t\tstrncat(buff, \"/\", buff_size - strlen(buff) - 1);\n\t\twrite_date_time(buff + strlen(buff), buff_size - strlen(buff));\n\t\tstrncat(buff, \".log\", buff_size - strlen(buff) - 1);\n\t#endif\n\t}\n\n\tbool create_directories(const char* file_path_const)\n\t{\n\t\tCHECK_F(file_path_const && *file_path_const);\n\t\tchar* file_path = STRDUP(file_path_const);\n\t\tfor (char* p = strchr(file_path + 1, '/'); p; p = strchr(p + 1, '/')) {\n\t\t\t*p = '\\0';\n\n\t#ifdef _WIN32\n\t\t\tif (_mkdir(file_path) == -1) {\n\t#else\n\t\t\tif (mkdir(file_path, 0755) == -1) {\n\t#endif\n\t\t\t\tif (errno != EEXIST) {\n\t\t\t\t\tLOG_F(ERROR, \"Failed to create directory '\" LOGURU_FMT(s) \"'\", file_path);\n\t\t\t\t\tLOG_IF_F(ERROR, errno == EACCES,       \"EACCES\");\n\t\t\t\t\tLOG_IF_F(ERROR, errno == ENAMETOOLONG, \"ENAMETOOLONG\");\n\t\t\t\t\tLOG_IF_F(ERROR, errno == ENOENT,       \"ENOENT\");\n\t\t\t\t\tLOG_IF_F(ERROR, errno == ENOTDIR,      \"ENOTDIR\");\n\t\t\t\t\tLOG_IF_F(ERROR, errno == ELOOP,        \"ELOOP\");\n\n\t\t\t\t\t*p = '/';\n\t\t\t\t\tfree(file_path);\n\t\t\t\t\treturn false;\n\t\t\t\t}\n\t\t\t}\n\t\t\t*p = '/';\n\t\t}\n\t\tfree(file_path);\n\t\treturn true;\n\t}\n\tbool add_file(const char* path_in, FileMode mode, Verbosity verbosity)\n\t{\n\t\tchar path[PATH_MAX];\n\t\tif (path_in[0] == '~') {\n\t\t\tsnprintf(path, sizeof(path) - 1, \"%s%s\", home_dir(), path_in + 1);\n\t\t} else {\n\t\t\tsnprintf(path, sizeof(path) - 1, \"%s\", path_in);\n\t\t}\n\n\t\tif (!create_directories(path)) {\n\t\t\tLOG_F(ERROR, \"Failed to create directories to '\" LOGURU_FMT(s) \"'\", path);\n\t\t}\n\n\t\tconst char* mode_str = (mode == FileMode::Truncate ? \"w\" : \"a\");\n\t\tFILE* file;\n\t#ifdef _WIN32\n\t\tfile = _fsopen(path, mode_str, _SH_DENYNO);\n\t#else\n\t\tfile = fopen(path, mode_str);\n\t#endif\n\t\tif (!file) {\n\t\t\tLOG_F(ERROR, \"Failed to open '\" LOGURU_FMT(s) \"'\", path);\n\t\t\treturn false;\n\t\t}\n#if LOGURU_WITH_FILEABS\n\t\tFileAbs* file_abs = new FileAbs(); // this is deleted in file_close;\n\t\tsnprintf(file_abs->path, sizeof(file_abs->path) - 1, \"%s\", path);\n\t\tsnprintf(file_abs->mode_str, sizeof(file_abs->mode_str) - 1, \"%s\", mode_str);\n\t\tstat(file_abs->path, &file_abs->st);\n\t\tfile_abs->fp = file;\n\t\tfile_abs->verbosity = verbosity;\n\t\tadd_callback(path_in, file_log, file_abs, verbosity, file_close, file_flush);\n#else\n\t\tadd_callback(path_in, file_log, file, verbosity, file_close, file_flush);\n#endif\n\n\t\tif (mode == FileMode::Append) {\n\t\t\tfprintf(file, \"\\n\\n\\n\\n\\n\");\n\t\t}\n\t\tif (!s_arguments.empty()) {\n\t\t\tfprintf(file, \"Arguments: %s\\n\", s_arguments.c_str());\n\t\t}\n\t\tif (strlen(s_current_dir) != 0) {\n\t\t\tfprintf(file, \"Current dir: %s\\n\", s_current_dir);\n\t\t}\n\t\tfprintf(file, \"File verbosity level: %d\\n\", verbosity);\n\t\tif (g_preamble_header) {\n\t\t\tchar preamble_explain[LOGURU_PREAMBLE_WIDTH];\n\t\t\tprint_preamble_header(preamble_explain, sizeof(preamble_explain));\n\t\t\tfprintf(file, \"%s\\n\", preamble_explain);\n\t\t}\n\t\tfflush(file);\n\n\t\tVLOG_F(g_internal_verbosity, \"Logging to '\" LOGURU_FMT(s) \"', mode: '\" LOGURU_FMT(s) \"', verbosity: \" LOGURU_FMT(d) \"\", path, mode_str, verbosity);\n\t\treturn true;\n\t}\n\n\t/*\n\t\tWill add syslog as a standard sink for log messages\n\t\tAny logging message with a verbosity lower or equal to\n\t\tthe given verbosity will be included.\n\n\t\tThis works for Unix like systems (i.e. Linux/Mac)\n\t\tThere is no current implementation for Windows (as I don't know the\n\t\tequivalent calls or have a way to test them). If you know please\n\t\tadd and send a pull request.\n\n\t\tThe code should still compile under windows but will only generate\n\t\ta warning message that syslog is unavailable.\n\n\t\tSearch for LOGURU_SYSLOG to find and fix.\n\t*/\n\tbool add_syslog(const char* app_name, Verbosity verbosity)\n\t{\n\t\treturn add_syslog(app_name, verbosity, LOG_USER);\n\t}\n\tbool add_syslog(const char* app_name, Verbosity verbosity, int facility)\n\t{\n#if LOGURU_SYSLOG\n\t\tif (app_name == nullptr) {\n\t\t\tapp_name = argv0_filename();\n\t\t}\n\t\topenlog(app_name, 0, facility);\n\t\tadd_callback(\"'syslog'\", syslog_log, nullptr, verbosity, syslog_close, syslog_flush);\n\n\t\tVLOG_F(g_internal_verbosity, \"Logging to 'syslog' , verbosity: \" LOGURU_FMT(d) \"\", verbosity);\n\t\treturn true;\n#else\n\t\t(void)app_name;\n\t\t(void)verbosity;\n\t\t(void)facility;\n\t\tVLOG_F(g_internal_verbosity, \"syslog not implemented on this system. Request to install syslog logging ignored.\");\n\t\treturn false;\n#endif\n\t}\n\t// Will be called right before abort().\n\tvoid set_fatal_handler(fatal_handler_t handler)\n\t{\n\t\ts_fatal_handler = handler;\n\t}\n\n\tfatal_handler_t get_fatal_handler()\n\t{\n\t\treturn s_fatal_handler;\n\t}\n\n\tvoid set_verbosity_to_name_callback(verbosity_to_name_t callback)\n\t{\n\t\ts_verbosity_to_name_callback = callback;\n\t}\n\n\tvoid set_name_to_verbosity_callback(name_to_verbosity_t callback)\n\t{\n\t\ts_name_to_verbosity_callback = callback;\n\t}\n\n\tvoid add_stack_cleanup(const char* find_this, const char* replace_with_this)\n\t{\n\t\tif (strlen(find_this) <= strlen(replace_with_this)) {\n\t\t\tLOG_F(WARNING, \"add_stack_cleanup: the replacement should be shorter than the pattern!\");\n\t\t\treturn;\n\t\t}\n\n\t\ts_user_stack_cleanups.push_back(StringPair(find_this, replace_with_this));\n\t}\n\n\tstatic void on_callback_change()\n\t{\n\t\ts_max_out_verbosity = Verbosity_OFF;\n\t\tfor (const auto& callback : s_callbacks) {\n\t\t\ts_max_out_verbosity = std::max(s_max_out_verbosity, callback.verbosity);\n\t\t}\n\t}\n\n\tvoid add_callback(\n\t\tconst char*     id,\n\t\tlog_handler_t   callback,\n\t\tvoid*           user_data,\n\t\tVerbosity       verbosity,\n\t\tclose_handler_t on_close,\n\t\tflush_handler_t on_flush)\n\t{\n\t\tstd::lock_guard<std::recursive_mutex> lock(s_mutex);\n\t\ts_callbacks.push_back(Callback{id, callback, user_data, verbosity, on_close, on_flush, 0});\n\t\ton_callback_change();\n\t}\n\n\t// Returns a custom verbosity name if one is available, or nullptr.\n\t// See also set_verbosity_to_name_callback.\n\tconst char* get_verbosity_name(Verbosity verbosity)\n\t{\n\t\tauto name = s_verbosity_to_name_callback\n\t\t\t\t? (*s_verbosity_to_name_callback)(verbosity)\n\t\t\t\t: nullptr;\n\n\t\t// Use standard replacements if callback fails:\n\t\tif (!name)\n\t\t{\n\t\t\tif (verbosity <= Verbosity_FATAL) {\n\t\t\t\tname = \"FATL\";\n\t\t\t} else if (verbosity == Verbosity_ERROR) {\n\t\t\t\tname = \"ERR\";\n\t\t\t} else if (verbosity == Verbosity_WARNING) {\n\t\t\t\tname = \"WARN\";\n\t\t\t} else if (verbosity == Verbosity_INFO) {\n\t\t\t\tname = \"INFO\";\n\t\t\t}\n\t\t}\n\n\t\treturn name;\n\t}\n\n\t// Returns Verbosity_INVALID if the name is not found.\n\t// See also set_name_to_verbosity_callback.\n\tVerbosity get_verbosity_from_name(const char* name)\n\t{\n\t\tauto verbosity = s_name_to_verbosity_callback\n\t\t\t\t? (*s_name_to_verbosity_callback)(name)\n\t\t\t\t: Verbosity_INVALID;\n\n\t\t// Use standard replacements if callback fails:\n\t\tif (verbosity == Verbosity_INVALID) {\n\t\t\tif (strcmp(name, \"OFF\") == 0) {\n\t\t\t\tverbosity = Verbosity_OFF;\n\t\t\t} else if (strcmp(name, \"INFO\") == 0) {\n\t\t\t\tverbosity = Verbosity_INFO;\n\t\t\t} else if (strcmp(name, \"WARNING\") == 0) {\n\t\t\t\tverbosity = Verbosity_WARNING;\n\t\t\t} else if (strcmp(name, \"ERROR\") == 0) {\n\t\t\t\tverbosity = Verbosity_ERROR;\n\t\t\t} else if (strcmp(name, \"FATAL\") == 0) {\n\t\t\t\tverbosity = Verbosity_FATAL;\n\t\t\t}\n\t\t}\n\n\t\treturn verbosity;\n\t}\n\n\tbool remove_callback(const char* id)\n\t{\n\t\tstd::lock_guard<std::recursive_mutex> lock(s_mutex);\n\t\tauto it = std::find_if(begin(s_callbacks), end(s_callbacks), [&](const Callback& c) { return c.id == id; });\n\t\tif (it != s_callbacks.end()) {\n\t\t\tif (it->close) { it->close(it->user_data); }\n\t\t\ts_callbacks.erase(it);\n\t\t\ton_callback_change();\n\t\t\treturn true;\n\t\t} else {\n\t\t\tLOG_F(ERROR, \"Failed to locate callback with id '\" LOGURU_FMT(s) \"'\", id);\n\t\t\treturn false;\n\t\t}\n\t}\n\n\tvoid remove_all_callbacks()\n\t{\n\t\tstd::lock_guard<std::recursive_mutex> lock(s_mutex);\n\t\tfor (auto& callback : s_callbacks) {\n\t\t\tif (callback.close) {\n\t\t\t\tcallback.close(callback.user_data);\n\t\t\t}\n\t\t}\n\t\ts_callbacks.clear();\n\t\ton_callback_change();\n\t}\n\n\t// Returns the maximum of g_stderr_verbosity and all file/custom outputs.\n\tVerbosity current_verbosity_cutoff()\n\t{\n\t\treturn g_stderr_verbosity > s_max_out_verbosity ?\n\t\t\t   g_stderr_verbosity : s_max_out_verbosity;\n\t}\n\n\t// ------------------------------------------------------------------------\n\t// Threads names\n\n#if LOGURU_PTLS_NAMES\n\tstatic pthread_once_t s_pthread_key_once = PTHREAD_ONCE_INIT;\n\tstatic pthread_key_t  s_pthread_key_name;\n\n\tvoid make_pthread_key_name()\n\t{\n\t\t(void)pthread_key_create(&s_pthread_key_name, free);\n\t}\n#endif\n\n#if LOGURU_WINTHREADS\n\t// Where we store the custom thread name set by `set_thread_name`\n\tchar* thread_name_buffer()\n\t{\n\t\t__declspec( thread ) static char thread_name[LOGURU_THREADNAME_WIDTH + 1] = {0};\n\t\treturn &thread_name[0];\n\t}\n#endif // LOGURU_WINTHREADS\n\n\tvoid set_thread_name(const char* name)\n\t{\n\t\t#if LOGURU_PTLS_NAMES\n\t\t\t// Store thread name in thread-local storage at `s_pthread_key_name`\n\t\t\t(void)pthread_once(&s_pthread_key_once, make_pthread_key_name);\n\t\t\t(void)pthread_setspecific(s_pthread_key_name, STRDUP(name));\n\t\t#elif LOGURU_PTHREADS\n\t\t\t// Tell the OS the thread name\n\t\t\t#ifdef __APPLE__\n\t\t\t\tpthread_setname_np(name);\n\t\t\t#elif defined(__FreeBSD__) || defined(__OpenBSD__)\n\t\t\t\tpthread_set_name_np(pthread_self(), name);\n\t\t\t#elif defined(__linux__) || defined(__sun)\n\t\t\t\tpthread_setname_np(pthread_self(), name);\n\t\t\t#endif\n\t\t#elif LOGURU_WINTHREADS\n\t\t\t// Store thread name in a thread-local storage:\n\t\t\tstrncpy_s(thread_name_buffer(), LOGURU_THREADNAME_WIDTH + 1, name, _TRUNCATE);\n\t\t#else // LOGURU_PTHREADS\n\t\t\t// TODO: on these weird platforms we should also store the thread name\n\t\t\t// in a generic thread-local storage.\n\t\t\t(void)name;\n\t\t#endif // LOGURU_PTHREADS\n\t}\n\n\tvoid get_thread_name(char* buffer, unsigned long long length, bool right_align_hex_id)\n\t{\n\t\tCHECK_NE_F(length, 0u, \"Zero length buffer in get_thread_name\");\n\t\tCHECK_NOTNULL_F(buffer, \"nullptr in get_thread_name\");\n\n\t\t#if LOGURU_PTLS_NAMES\n\t\t\t(void)pthread_once(&s_pthread_key_once, make_pthread_key_name);\n\t\t\tif (const char* name = static_cast<const char*>(pthread_getspecific(s_pthread_key_name))) {\n\t\t\t\tsnprintf(buffer, static_cast<size_t>(length), \"%s\", name);\n\t\t\t} else {\n\t\t\t\tbuffer[0] = 0;\n\t\t\t}\n\t\t#elif LOGURU_PTHREADS\n\t\t\t// Ask the OS about the thread name.\n\t\t\t// This is what we *want* to do on all platforms, but\n\t\t\t// only some platforms support it (currently).\n\t\t\tpthread_getname_np(pthread_self(), buffer, length);\n\t\t#elif LOGURU_WINTHREADS\n\t\t\tsnprintf(buffer, static_cast<size_t>(length), \"%s\", thread_name_buffer());\n\t\t#else\n\t\t\t// Thread names unsupported\n\t\t\tbuffer[0] = 0;\n\t\t#endif\n\n\t\tif (buffer[0] == 0) {\n\t\t\t// We failed to get a readable thread name.\n\t\t\t// Write a HEX thread ID instead.\n\t\t\t// We try to get an ID that is the same as the ID you could\n\t\t\t// read in your debugger, system monitor etc.\n\n\t\t\t#ifdef __APPLE__\n\t\t\t\tuint64_t thread_id;\n\t\t\t\tpthread_threadid_np(pthread_self(), &thread_id);\n\t\t\t#elif defined(__FreeBSD__)\n\t\t\t\tlong thread_id;\n\t\t\t\t(void)thr_self(&thread_id);\n\t\t\t#elif LOGURU_PTHREADS\n\t\t\t\tuint64_t thread_id = pthread_self();\n\t\t\t#else\n\t\t\t\t// This ID does not correllate to anything we can get from the OS,\n\t\t\t\t// so this is the worst way to get the ID.\n\t\t\t\tconst auto thread_id = std::hash<std::thread::id>{}(std::this_thread::get_id());\n\t\t\t#endif\n\n\t\t\tif (right_align_hex_id) {\n\t\t\t\tsnprintf(buffer, static_cast<size_t>(length), \"%*X\", static_cast<int>(length - 1), static_cast<unsigned>(thread_id));\n\t\t\t} else {\n\t\t\t\tsnprintf(buffer, static_cast<size_t>(length), \"%X\", static_cast<unsigned>(thread_id));\n\t\t\t}\n\t\t}\n\t}\n\n\t// ------------------------------------------------------------------------\n\t// Stack traces\n\n#if LOGURU_STACKTRACES\n\tText demangle(const char* name)\n\t{\n\t\tint status = -1;\n\t\tchar* demangled = abi::__cxa_demangle(name, 0, 0, &status);\n\t\tText result{status == 0 ? demangled : STRDUP(name)};\n\t\treturn result;\n\t}\n\n\t#if LOGURU_RTTI\n\t\ttemplate <class T>\n\t\tstd::string type_name()\n\t\t{\n\t\t\tauto demangled = demangle(typeid(T).name());\n\t\t\treturn demangled.c_str();\n\t\t}\n\t#endif // LOGURU_RTTI\n\n\tstatic const StringPairList REPLACE_LIST = {\n\t\t#if LOGURU_RTTI\n\t\t\t{ type_name<std::string>(),    \"std::string\"    },\n\t\t\t{ type_name<std::wstring>(),   \"std::wstring\"   },\n\t\t\t{ type_name<std::u16string>(), \"std::u16string\" },\n\t\t\t{ type_name<std::u32string>(), \"std::u32string\" },\n\t\t#endif // LOGURU_RTTI\n\t\t{ \"std::__1::\",                \"std::\"          },\n\t\t{ \"__thiscall \",               \"\"               },\n\t\t{ \"__cdecl \",                  \"\"               },\n\t};\n\n\tvoid do_replacements(const StringPairList& replacements, std::string& str)\n\t{\n\t\tfor (auto&& p : replacements) {\n\t\t\tif (p.first.size() <= p.second.size()) {\n\t\t\t\t// On gcc, \"type_name<std::string>()\" is \"std::string\"\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tsize_t it;\n\t\t\twhile ((it=str.find(p.first)) != std::string::npos) {\n\t\t\t\tstr.replace(it, p.first.size(), p.second);\n\t\t\t}\n\t\t}\n\t}\n\n\tstd::string prettify_stacktrace(const std::string& input)\n\t{\n\t\tstd::string output = input;\n\n\t\tdo_replacements(s_user_stack_cleanups, output);\n\t\tdo_replacements(REPLACE_LIST, output);\n\n\t\ttry {\n\t\t\tstd::regex std_allocator_re(R\"(,\\s*std::allocator<[^<>]+>)\");\n\t\t\toutput = std::regex_replace(output, std_allocator_re, std::string(\"\"));\n\n\t\t\tstd::regex template_spaces_re(R\"(<\\s*([^<> ]+)\\s*>)\");\n\t\t\toutput = std::regex_replace(output, template_spaces_re, std::string(\"<$1>\"));\n\t\t} catch (std::regex_error&) {\n\t\t\t// Probably old GCC.\n\t\t}\n\n\t\treturn output;\n\t}\n\n\tstd::string stacktrace_as_stdstring(int skip)\n\t{\n\t\t// From https://gist.github.com/fmela/591333\n\t\tvoid* callstack[128];\n\t\tconst auto max_frames = sizeof(callstack) / sizeof(callstack[0]);\n\t\tint num_frames = backtrace(callstack, max_frames);\n\t\tchar** symbols = backtrace_symbols(callstack, num_frames);\n\n\t\tstd::string result;\n\t\t// Print stack traces so the most relevant ones are written last\n\t\t// Rationale: http://yellerapp.com/posts/2015-01-22-upside-down-stacktraces.html\n\t\tfor (int i = num_frames - 1; i >= skip; --i) {\n\t\t\tchar buf[1024];\n\t\t\tDl_info info;\n\t\t\tif (dladdr(callstack[i], &info) && info.dli_sname) {\n\t\t\t\tchar* demangled = NULL;\n\t\t\t\tint status = -1;\n\t\t\t\tif (info.dli_sname[0] == '_') {\n\t\t\t\t\tdemangled = abi::__cxa_demangle(info.dli_sname, 0, 0, &status);\n\t\t\t\t}\n\t\t\t\tsnprintf(buf, sizeof(buf), \"%-3d %*p %s + %zd\\n\",\n\t\t\t\t\t\t i - skip, int(2 + sizeof(void*) * 2), callstack[i],\n\t\t\t\t\t\t status == 0 ? demangled :\n\t\t\t\t\t\t info.dli_sname == 0 ? symbols[i] : info.dli_sname,\n\t\t\t\t\t\t static_cast<char*>(callstack[i]) - static_cast<char*>(info.dli_saddr));\n\t\t\t\tfree(demangled);\n\t\t\t} else {\n\t\t\t\tsnprintf(buf, sizeof(buf), \"%-3d %*p %s\\n\",\n\t\t\t\t\t\t i - skip, int(2 + sizeof(void*) * 2), callstack[i], symbols[i]);\n\t\t\t}\n\t\t\tresult += buf;\n\t\t}\n\t\tfree(symbols);\n\n\t\tif (num_frames == max_frames) {\n\t\t\tresult = \"[truncated]\\n\" + result;\n\t\t}\n\n\t\tif (!result.empty() && result[result.size() - 1] == '\\n') {\n\t\t\tresult.resize(result.size() - 1);\n\t\t}\n\n\t\treturn prettify_stacktrace(result);\n\t}\n\n#else // LOGURU_STACKTRACES\n\tText demangle(const char* name)\n\t{\n\t\treturn Text(STRDUP(name));\n\t}\n\n\tstd::string stacktrace_as_stdstring(int)\n\t{\n\t\t// No stacktraces available on this platform\"\n\t\treturn \"\";\n\t}\n\n#endif // LOGURU_STACKTRACES\n\n\tText stacktrace(int skip)\n\t{\n\t\tauto str = stacktrace_as_stdstring(skip + 1);\n\t\treturn Text(STRDUP(str.c_str()));\n\t}\n\n\t// ------------------------------------------------------------------------\n\n\tstatic void print_preamble_header(char* out_buff, size_t out_buff_size)\n\t{\n\t\tif (out_buff_size == 0) { return; }\n\t\tout_buff[0] = '\\0';\n\t\tsize_t pos = 0;\n\t\tif (g_preamble_date && pos < out_buff_size) {\n\t\t\tint bytes = snprintf(out_buff + pos, out_buff_size - pos, \"date       \");\n\t\t\tif (bytes > 0) {\n\t\t\t\tpos += bytes;\n\t\t\t}\n\t\t}\n\t\tif (g_preamble_time && pos < out_buff_size) {\n\t\t\tint bytes = snprintf(out_buff + pos, out_buff_size - pos, \"time         \");\n\t\t\tif (bytes > 0) {\n\t\t\t\tpos += bytes;\n\t\t\t}\n\t\t}\n\t\tif (g_preamble_uptime && pos < out_buff_size) {\n\t\t\tint bytes = snprintf(out_buff + pos, out_buff_size - pos, \"( uptime  ) \");\n\t\t\tif (bytes > 0) {\n\t\t\t\tpos += bytes;\n\t\t\t}\n\t\t}\n\t\tif (g_preamble_thread && pos < out_buff_size) {\n\t\t\tint bytes = snprintf(out_buff + pos, out_buff_size - pos, \"[%-*s]\", LOGURU_THREADNAME_WIDTH, \" thread name/id\");\n\t\t\tif (bytes > 0) {\n\t\t\t\tpos += bytes;\n\t\t\t}\n\t\t}\n\t\tif (g_preamble_file && pos < out_buff_size) {\n\t\t\tint bytes = snprintf(out_buff + pos, out_buff_size - pos, \"%*s:line  \", LOGURU_FILENAME_WIDTH, \"file\");\n\t\t\tif (bytes > 0) {\n\t\t\t\tpos += bytes;\n\t\t\t}\n\t\t}\n\t\tif (g_preamble_verbose && pos < out_buff_size) {\n\t\t\tint bytes = snprintf(out_buff + pos, out_buff_size - pos, \"   v\");\n\t\t\tif (bytes > 0) {\n\t\t\t\tpos += bytes;\n\t\t\t}\n\t\t}\n\t\tif (g_preamble_pipe && pos < out_buff_size) {\n\t\t\tint bytes = snprintf(out_buff + pos, out_buff_size - pos, \"| \");\n\t\t\tif (bytes > 0) {\n\t\t\t\tpos += bytes;\n\t\t\t}\n\t\t}\n\t}\n\n\tstatic void print_preamble(char* out_buff, size_t out_buff_size, Verbosity verbosity, const char* file, unsigned line)\n\t{\n\t\tif (out_buff_size == 0) { return; }\n\t\tout_buff[0] = '\\0';\n\t\tif (!g_preamble) { return; }\n\t\tlong long ms_since_epoch = duration_cast<milliseconds>(system_clock::now().time_since_epoch()).count();\n\t\ttime_t sec_since_epoch = time_t(ms_since_epoch / 1000);\n\t\ttm time_info;\n\t\tlocaltime_r(&sec_since_epoch, &time_info);\n\n\t\tauto uptime_ms = duration_cast<milliseconds>(steady_clock::now() - s_start_time).count();\n\t\tauto uptime_sec = static_cast<double> (uptime_ms) / 1000.0;\n\n\t\tchar thread_name[LOGURU_THREADNAME_WIDTH + 1] = {0};\n\t\tget_thread_name(thread_name, LOGURU_THREADNAME_WIDTH + 1, true);\n\n\t\tif (s_strip_file_path) {\n\t\t\tfile = filename(file);\n\t\t}\n\n\t\tchar level_buff[6];\n\t\tconst char* custom_level_name = get_verbosity_name(verbosity);\n\t\tif (custom_level_name) {\n\t\t\tsnprintf(level_buff, sizeof(level_buff) - 1, \"%s\", custom_level_name);\n\t\t} else {\n\t\t\tsnprintf(level_buff, sizeof(level_buff) - 1, \"% 4d\", static_cast<int8_t>(verbosity));\n\t\t}\n\n\t\tsize_t pos = 0;\n\n\t\tif (g_preamble_date && pos < out_buff_size) {\n\t\t\tint bytes = snprintf(out_buff + pos, out_buff_size - pos, \"%04d-%02d-%02d \",\n\t\t\t\t                 1900 + time_info.tm_year, 1 + time_info.tm_mon, time_info.tm_mday);\n\t\t\tif (bytes > 0) {\n\t\t\t\tpos += bytes;\n\t\t\t}\n\t\t}\n\t\tif (g_preamble_time && pos < out_buff_size) {\n\t\t\tint bytes = snprintf(out_buff + pos, out_buff_size - pos, \"%02d:%02d:%02d.%03lld \",\n\t\t\t                     time_info.tm_hour, time_info.tm_min, time_info.tm_sec, ms_since_epoch % 1000);\n\t\t\tif (bytes > 0) {\n\t\t\t\tpos += bytes;\n\t\t\t}\n\t\t}\n\t\tif (g_preamble_uptime && pos < out_buff_size) {\n\t\t\tint bytes = snprintf(out_buff + pos, out_buff_size - pos, \"(%8.3fs) \",\n\t\t\t                     uptime_sec);\n\t\t\tif (bytes > 0) {\n\t\t\t\tpos += bytes;\n\t\t\t}\n\t\t}\n\t\tif (g_preamble_thread && pos < out_buff_size) {\n\t\t\tint bytes = snprintf(out_buff + pos, out_buff_size - pos, \"[%-*s]\",\n\t\t\t                     LOGURU_THREADNAME_WIDTH, thread_name);\n\t\t\tif (bytes > 0) {\n\t\t\t\tpos += bytes;\n\t\t\t}\n\t\t}\n\t\tif (g_preamble_file && pos < out_buff_size) {\n\t\t\tchar shortened_filename[LOGURU_FILENAME_WIDTH + 1];\n\t\t\tsnprintf(shortened_filename, LOGURU_FILENAME_WIDTH + 1, \"%s\", file);\n\t\t\tint bytes = snprintf(out_buff + pos, out_buff_size - pos, \"%*s:%-5u \",\n\t\t\t                     LOGURU_FILENAME_WIDTH, shortened_filename, line);\n\t\t\tif (bytes > 0) {\n\t\t\t\tpos += bytes;\n\t\t\t}\n\t\t}\n\t\tif (g_preamble_verbose && pos < out_buff_size) {\n\t\t\tint bytes = snprintf(out_buff + pos, out_buff_size - pos, \"%4s\",\n\t\t\t                     level_buff);\n\t\t\tif (bytes > 0) {\n\t\t\t\tpos += bytes;\n\t\t\t}\n\t\t}\n\t\tif (g_preamble_pipe && pos < out_buff_size) {\n\t\t\tint bytes = snprintf(out_buff + pos, out_buff_size - pos, \"| \");\n\t\t\tif (bytes > 0) {\n\t\t\t\tpos += bytes;\n\t\t\t}\n\t\t}\n\t}\n\n\t// stack_trace_skip is just if verbosity == FATAL.\n\tstatic void log_message(int stack_trace_skip, Message& message, bool with_indentation, bool abort_if_fatal)\n\t{\n\t\tconst auto verbosity = message.verbosity;\n\t\tstd::lock_guard<std::recursive_mutex> lock(s_mutex);\n\n\t\tif (message.verbosity == Verbosity_FATAL) {\n\t\t\tauto st = loguru::stacktrace(stack_trace_skip + 2);\n\t\t\tif (!st.empty()) {\n\t\t\t\tRAW_LOG_F(ERROR, \"Stack trace:\\n\" LOGURU_FMT(s) \"\", st.c_str());\n\t\t\t}\n\n\t\t\tauto ec = loguru::get_error_context();\n\t\t\tif (!ec.empty()) {\n\t\t\t\tRAW_LOG_F(ERROR, \"\" LOGURU_FMT(s) \"\", ec.c_str());\n\t\t\t}\n\t\t}\n\n\t\tif (with_indentation) {\n\t\t\tmessage.indentation = indentation(s_stderr_indentation);\n\t\t}\n\n\t\tif (verbosity <= g_stderr_verbosity) {\n\t\t\tif (g_colorlogtostderr && s_terminal_has_color) {\n\t\t\t\tif (verbosity > Verbosity_WARNING) {\n\t\t\t\t\tfprintf(stderr, \"%s%s%s%s%s%s%s%s\\n\",\n\t\t\t\t\t\tterminal_reset(),\n\t\t\t\t\t\tterminal_dim(),\n\t\t\t\t\t\tmessage.preamble,\n\t\t\t\t\t\tmessage.indentation,\n\t\t\t\t\t\tverbosity == Verbosity_INFO ? terminal_reset() : \"\", // un-dim for info\n\t\t\t\t\t\tmessage.prefix,\n\t\t\t\t\t\tmessage.message,\n\t\t\t\t\t\tterminal_reset());\n\t\t\t\t} else {\n\t\t\t\t\tfprintf(stderr, \"%s%s%s%s%s%s%s\\n\",\n\t\t\t\t\t\tterminal_reset(),\n\t\t\t\t\t\tverbosity == Verbosity_WARNING ? terminal_yellow() : terminal_red(),\n\t\t\t\t\t\tmessage.preamble,\n\t\t\t\t\t\tmessage.indentation,\n\t\t\t\t\t\tmessage.prefix,\n\t\t\t\t\t\tmessage.message,\n\t\t\t\t\t\tterminal_reset());\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tfprintf(stderr, \"%s%s%s%s\\n\",\n\t\t\t\t\tmessage.preamble, message.indentation, message.prefix, message.message);\n\t\t\t}\n\n\t\t\tif (g_flush_interval_ms == 0) {\n\t\t\t\tfflush(stderr);\n\t\t\t} else {\n\t\t\t\ts_needs_flushing = true;\n\t\t\t}\n\t\t}\n\n\t\tfor (auto& p : s_callbacks) {\n\t\t\tif (verbosity <= p.verbosity) {\n\t\t\t\tif (with_indentation) {\n\t\t\t\t\tmessage.indentation = indentation(p.indentation);\n\t\t\t\t}\n\t\t\t\tp.callback(p.user_data, message);\n\t\t\t\tif (g_flush_interval_ms == 0) {\n\t\t\t\t\tif (p.flush) { p.flush(p.user_data); }\n\t\t\t\t} else {\n\t\t\t\t\ts_needs_flushing = true;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tif (g_flush_interval_ms > 0 && !s_flush_thread) {\n\t\t\ts_flush_thread = new std::thread([](){\n\t\t\t\tfor (;;) {\n\t\t\t\t\tif (s_needs_flushing) {\n\t\t\t\t\t\tflush();\n\t\t\t\t\t}\n\t\t\t\t\tstd::this_thread::sleep_for(std::chrono::milliseconds(g_flush_interval_ms));\n\t\t\t\t}\n\t\t\t});\n\t\t}\n\n\t\tif (message.verbosity == Verbosity_FATAL) {\n\t\t\tflush();\n\n\t\t\tif (s_fatal_handler) {\n\t\t\t\ts_fatal_handler(message);\n\t\t\t\tflush();\n\t\t\t}\n\n\t\t\tif (abort_if_fatal) {\n#if !defined(_WIN32)\n\t\t\t\tif (s_signal_options.sigabrt) {\n\t\t\t\t\t// Make sure we don't catch our own abort:\n\t\t\t\t\tsignal(SIGABRT, SIG_DFL);\n\t\t\t\t}\n#endif\n\t\t\t\tabort();\n\t\t\t}\n\t\t}\n\t}\n\n\t// stack_trace_skip is just if verbosity == FATAL.\n\tvoid log_to_everywhere(int stack_trace_skip, Verbosity verbosity,\n\t                       const char* file, unsigned line,\n\t                       const char* prefix, const char* buff)\n\t{\n\t\tchar preamble_buff[LOGURU_PREAMBLE_WIDTH];\n\t\tprint_preamble(preamble_buff, sizeof(preamble_buff), verbosity, file, line);\n\t\tauto message = Message{verbosity, file, line, preamble_buff, \"\", prefix, buff};\n\t\tlog_message(stack_trace_skip + 1, message, true, true);\n\t}\n\n#if LOGURU_USE_FMTLIB\n\tvoid vlog(Verbosity verbosity, const char* file, unsigned line, const char* format, fmt::format_args args)\n\t{\n\t\tauto formatted = fmt::vformat(format, args);\n\t\tlog_to_everywhere(1, verbosity, file, line, \"\", formatted.c_str());\n\t}\n\n\tvoid raw_vlog(Verbosity verbosity, const char* file, unsigned line, const char* format, fmt::format_args args)\n\t{\n\t\tauto formatted = fmt::vformat(format, args);\n\t\tauto message = Message{verbosity, file, line, \"\", \"\", \"\", formatted.c_str()};\n\t\tlog_message(1, message, false, true);\n\t}\n#else\n\tvoid log(Verbosity verbosity, const char* file, unsigned line, const char* format, ...)\n\t{\n\t\tva_list vlist;\n\t\tva_start(vlist, format);\n\t\tvlog(verbosity, file, line, format, vlist);\n\t\tva_end(vlist);\n\t}\n\n\tvoid vlog(Verbosity verbosity, const char* file, unsigned line, const char* format, va_list vlist)\n\t{\n\t\tauto buff = vtextprintf(format, vlist);\n\t\tlog_to_everywhere(1, verbosity, file, line, \"\", buff.c_str());\n\t}\n\n\tvoid raw_log(Verbosity verbosity, const char* file, unsigned line, const char* format, ...)\n\t{\n\t\tva_list vlist;\n\t\tva_start(vlist, format);\n\t\tauto buff = vtextprintf(format, vlist);\n\t\tauto message = Message{verbosity, file, line, \"\", \"\", \"\", buff.c_str()};\n\t\tlog_message(1, message, false, true);\n\t\tva_end(vlist);\n\t}\n#endif\n\n\tvoid flush()\n\t{\n\t\tstd::lock_guard<std::recursive_mutex> lock(s_mutex);\n\t\tfflush(stderr);\n\t\tfor (const auto& callback : s_callbacks)\n\t\t{\n\t\t\tif (callback.flush) {\n\t\t\t\tcallback.flush(callback.user_data);\n\t\t\t}\n\t\t}\n\t\ts_needs_flushing = false;\n\t}\n\n\tLogScopeRAII::LogScopeRAII(Verbosity verbosity, const char* file, unsigned line, const char* format, va_list vlist) :\n\t\t_verbosity(verbosity), _file(file), _line(line)\n\t{\n\t\tthis->Init(format, vlist);\n\t}\n\n\tLogScopeRAII::LogScopeRAII(Verbosity verbosity, const char* file, unsigned line, const char* format, ...) :\n\t\t_verbosity(verbosity), _file(file), _line(line)\n\t{\n\t\tva_list vlist;\n\t\tva_start(vlist, format);\n\t\tthis->Init(format, vlist);\n\t\tva_end(vlist);\n\t}\n\n\tLogScopeRAII::~LogScopeRAII()\n\t{\n\t\tif (_file) {\n\t\t\tstd::lock_guard<std::recursive_mutex> lock(s_mutex);\n\t\t\tif (_indent_stderr && s_stderr_indentation > 0) {\n\t\t\t\t--s_stderr_indentation;\n\t\t\t}\n\t\t\tfor (auto& p : s_callbacks) {\n\t\t\t\t// Note: Callback indentation cannot change!\n\t\t\t\tif (_verbosity <= p.verbosity) {\n\t\t\t\t\t// in unlikely case this callback is new\n\t\t\t\t\tif (p.indentation > 0) {\n\t\t\t\t\t\t--p.indentation;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n#if LOGURU_VERBOSE_SCOPE_ENDINGS\n\t\t\tauto duration_sec = static_cast<double>(now_ns() - _start_time_ns) / 1e9;\n#if LOGURU_USE_FMTLIB\n\t\t\tauto buff = textprintf(\"{:.{}f} s: {:s}\", duration_sec, LOGURU_SCOPE_TIME_PRECISION, _name);\n#else\n\t\t\tauto buff = textprintf(\"%.*f s: %s\", LOGURU_SCOPE_TIME_PRECISION, duration_sec, _name);\n#endif\n\t\t\tlog_to_everywhere(1, _verbosity, _file, _line, \"} \", buff.c_str());\n#else\n\t\t\tlog_to_everywhere(1, _verbosity, _file, _line, \"}\", \"\");\n#endif\n\t\t}\n\t}\n\n\tvoid LogScopeRAII::Init(const char* format, va_list vlist)\n\t{\n\t\tif (_verbosity <= current_verbosity_cutoff()) {\n\t\t\tstd::lock_guard<std::recursive_mutex> lock(s_mutex);\n\t\t\t_indent_stderr = (_verbosity <= g_stderr_verbosity);\n\t\t\t_start_time_ns = now_ns();\n\t\t\tvsnprintf(_name, sizeof(_name), format, vlist);\n\t\t\tlog_to_everywhere(1, _verbosity, _file, _line, \"{ \", _name);\n\n\t\t\tif (_indent_stderr) {\n\t\t\t\t++s_stderr_indentation;\n\t\t\t}\n\n\t\t\tfor (auto& p : s_callbacks) {\n\t\t\t\tif (_verbosity <= p.verbosity) {\n\t\t\t\t\t++p.indentation;\n\t\t\t\t}\n\t\t\t}\n\t\t} else {\n\t\t\t_file = nullptr;\n\t\t}\n\t}\n\n#if LOGURU_USE_FMTLIB\n\tvoid vlog_and_abort(int stack_trace_skip, const char* expr, const char* file, unsigned line, const char* format, fmt::format_args args)\n\t{\n\t\tauto formatted = fmt::vformat(format, args);\n\t\tlog_to_everywhere(stack_trace_skip + 1, Verbosity_FATAL, file, line, expr, formatted.c_str());\n\t\tabort(); // log_to_everywhere already does this, but this makes the analyzer happy.\n\t}\n#else\n\tvoid log_and_abort(int stack_trace_skip, const char* expr, const char* file, unsigned line, const char* format, ...)\n\t{\n\t\tva_list vlist;\n\t\tva_start(vlist, format);\n\t\tauto buff = vtextprintf(format, vlist);\n\t\tlog_to_everywhere(stack_trace_skip + 1, Verbosity_FATAL, file, line, expr, buff.c_str());\n\t\tva_end(vlist);\n\t\tabort(); // log_to_everywhere already does this, but this makes the analyzer happy.\n\t}\n#endif\n\n\tvoid log_and_abort(int stack_trace_skip, const char* expr, const char* file, unsigned line)\n\t{\n\t\tlog_and_abort(stack_trace_skip + 1, expr, file, line, \" \");\n\t}\n\n\t// ----------------------------------------------------------------------------\n\t// Streams:\n\n#if LOGURU_USE_FMTLIB\n\ttemplate<typename... Args>\n\tstd::string vstrprintf(const char* format, const Args&... args)\n\t{\n\t\tauto text = textprintf(format, args...);\n\t\tstd::string result = text.c_str();\n\t\treturn result;\n\t}\n\n\ttemplate<typename... Args>\n\tstd::string strprintf(const char* format, const Args&... args)\n\t{\n\t\treturn vstrprintf(format, args...);\n\t}\n#else\n\tstd::string vstrprintf(const char* format, va_list vlist)\n\t{\n\t\tauto text = vtextprintf(format, vlist);\n\t\tstd::string result = text.c_str();\n\t\treturn result;\n\t}\n\n\tstd::string strprintf(const char* format, ...)\n\t{\n\t\tva_list vlist;\n\t\tva_start(vlist, format);\n\t\tauto result = vstrprintf(format, vlist);\n\t\tva_end(vlist);\n\t\treturn result;\n\t}\n#endif\n\n\t#if LOGURU_WITH_STREAMS\n\n\tStreamLogger::~StreamLogger() noexcept(false)\n\t{\n\t\tauto message = _ss.str();\n\t\tlog(_verbosity, _file, _line, LOGURU_FMT(s), message.c_str());\n\t}\n\n\tAbortLogger::~AbortLogger() noexcept(false)\n\t{\n\t\tauto message = _ss.str();\n\t\tloguru::log_and_abort(1, _expr, _file, _line, LOGURU_FMT(s), message.c_str());\n\t}\n\n\t#endif // LOGURU_WITH_STREAMS\n\n\t// ----------------------------------------------------------------------------\n\t// 888888 88\"\"Yb 88\"\"Yb  dP\"Yb  88\"\"Yb      dP\"\"b8  dP\"Yb  88b 88 888888 888888 Yb  dP 888888\n\t// 88__   88__dP 88__dP dP   Yb 88__dP     dP   `\" dP   Yb 88Yb88   88   88__    YbdP    88\n\t// 88\"\"   88\"Yb  88\"Yb  Yb   dP 88\"Yb      Yb      Yb   dP 88 Y88   88   88\"\"    dPYb    88\n\t// 888888 88  Yb 88  Yb  YbodP  88  Yb      YboodP  YbodP  88  Y8   88   888888 dP  Yb   88\n\t// ----------------------------------------------------------------------------\n\n\tstruct StringStream\n\t{\n\t\tstd::string str;\n\t};\n\n\t// Use this in your EcPrinter implementations.\n\tvoid stream_print(StringStream& out_string_stream, const char* text)\n\t{\n\t\tout_string_stream.str += text;\n\t}\n\n\t// ----------------------------------------------------------------------------\n\n\tusing ECPtr = EcEntryBase*;\n\n#if defined(_WIN32) || (defined(__APPLE__) && !TARGET_OS_IPHONE)\n\t#ifdef __APPLE__\n\t\t#define LOGURU_THREAD_LOCAL __thread\n\t#else\n\t\t#define LOGURU_THREAD_LOCAL thread_local\n\t#endif\n\tstatic LOGURU_THREAD_LOCAL ECPtr thread_ec_ptr = nullptr;\n\n\tECPtr& get_thread_ec_head_ref()\n\t{\n\t\treturn thread_ec_ptr;\n\t}\n#else // !thread_local\n\tstatic pthread_once_t s_ec_pthread_once = PTHREAD_ONCE_INIT;\n\tstatic pthread_key_t  s_ec_pthread_key;\n\n\tvoid free_ec_head_ref(void* io_error_context)\n\t{\n\t\tdelete reinterpret_cast<ECPtr*>(io_error_context);\n\t}\n\n\tvoid ec_make_pthread_key()\n\t{\n\t\t(void)pthread_key_create(&s_ec_pthread_key, free_ec_head_ref);\n\t}\n\n\tECPtr& get_thread_ec_head_ref()\n\t{\n\t\t(void)pthread_once(&s_ec_pthread_once, ec_make_pthread_key);\n\t\tauto ec = reinterpret_cast<ECPtr*>(pthread_getspecific(s_ec_pthread_key));\n\t\tif (ec == nullptr) {\n\t\t\tec = new ECPtr(nullptr);\n\t\t\t(void)pthread_setspecific(s_ec_pthread_key, ec);\n\t\t}\n\t\treturn *ec;\n\t}\n#endif // !thread_local\n\n\t// ----------------------------------------------------------------------------\n\n\tEcHandle get_thread_ec_handle()\n\t{\n\t\treturn get_thread_ec_head_ref();\n\t}\n\n\tText get_error_context()\n\t{\n\t\treturn get_error_context_for(get_thread_ec_head_ref());\n\t}\n\n\tText get_error_context_for(const EcEntryBase* ec_head)\n\t{\n\t\tstd::vector<const EcEntryBase*> stack;\n\t\twhile (ec_head) {\n\t\t\tstack.push_back(ec_head);\n\t\t\tec_head = ec_head->_previous;\n\t\t}\n\t\tstd::reverse(stack.begin(), stack.end());\n\n\t\tStringStream result;\n\t\tif (!stack.empty()) {\n\t\t\tresult.str += \"------------------------------------------------\\n\";\n\t\t\tfor (auto entry : stack) {\n\t\t\t\tconst auto description = std::string(entry->_descr) + \":\";\n#if LOGURU_USE_FMTLIB\n\t\t\t\tauto prefix = textprintf(\"[ErrorContext] {.{}s}:{:-5u} {:-20s} \",\n\t\t\t\t\tfilename(entry->_file), LOGURU_FILENAME_WIDTH, entry->_line, description.c_str());\n#else\n\t\t\t\tauto prefix = textprintf(\"[ErrorContext] %*s:%-5u %-20s \",\n\t\t\t\t\tLOGURU_FILENAME_WIDTH, filename(entry->_file), entry->_line, description.c_str());\n#endif\n\t\t\t\tresult.str += prefix.c_str();\n\t\t\t\tentry->print_value(result);\n\t\t\t\tresult.str += \"\\n\";\n\t\t\t}\n\t\t\tresult.str += \"------------------------------------------------\";\n\t\t}\n\t\treturn Text(STRDUP(result.str.c_str()));\n\t}\n\n\tEcEntryBase::EcEntryBase(const char* file, unsigned line, const char* descr)\n\t\t: _file(file), _line(line), _descr(descr)\n\t{\n\t\tEcEntryBase*& ec_head = get_thread_ec_head_ref();\n\t\t_previous = ec_head;\n\t\tec_head = this;\n\t}\n\n\tEcEntryBase::~EcEntryBase()\n\t{\n\t\tget_thread_ec_head_ref() = _previous;\n\t}\n\n\t// ------------------------------------------------------------------------\n\n\tText ec_to_text(const char* value)\n\t{\n\t\t// Add quotes around the string to make it obvious where it begin and ends.\n\t\t// This is great for detecting erroneous leading or trailing spaces in e.g. an identifier.\n\t\tauto str = \"\\\"\" + std::string(value) + \"\\\"\";\n\t\treturn Text{STRDUP(str.c_str())};\n\t}\n\n\tText ec_to_text(char c)\n\t{\n\t\t// Add quotes around the character to make it obvious where it begin and ends.\n\t\tstd::string str = \"'\";\n\n\t\tauto write_hex_digit = [&](unsigned num)\n\t\t{\n\t\t\tif (num < 10u) { str += char('0' + num); }\n\t\t\telse           { str += char('a' + num - 10); }\n\t\t};\n\n\t\tauto write_hex_16 = [&](uint16_t n)\n\t\t{\n\t\t\twrite_hex_digit((n >> 12u) & 0x0f);\n\t\t\twrite_hex_digit((n >>  8u) & 0x0f);\n\t\t\twrite_hex_digit((n >>  4u) & 0x0f);\n\t\t\twrite_hex_digit((n >>  0u) & 0x0f);\n\t\t};\n\n\t\tif      (c == '\\\\') { str += \"\\\\\\\\\"; }\n\t\telse if (c == '\\\"') { str += \"\\\\\\\"\"; }\n\t\telse if (c == '\\'') { str += \"\\\\\\'\"; }\n\t\telse if (c == '\\0') { str += \"\\\\0\";  }\n\t\telse if (c == '\\b') { str += \"\\\\b\";  }\n\t\telse if (c == '\\f') { str += \"\\\\f\";  }\n\t\telse if (c == '\\n') { str += \"\\\\n\";  }\n\t\telse if (c == '\\r') { str += \"\\\\r\";  }\n\t\telse if (c == '\\t') { str += \"\\\\t\";  }\n\t\telse if (0 <= c && c < 0x20) {\n\t\t\tstr += \"\\\\u\";\n\t\t\twrite_hex_16(static_cast<uint16_t>(c));\n\t\t} else { str += c; }\n\n\t\tstr += \"'\";\n\n\t\treturn Text{STRDUP(str.c_str())};\n\t}\n\n\t#define DEFINE_EC(Type)                        \\\n\t\tText ec_to_text(Type value)                \\\n\t\t{                                          \\\n\t\t\tauto str = std::to_string(value);      \\\n\t\t\treturn Text{STRDUP(str.c_str())};      \\\n\t\t}\n\n\tDEFINE_EC(int)\n\tDEFINE_EC(unsigned int)\n\tDEFINE_EC(long)\n\tDEFINE_EC(unsigned long)\n\tDEFINE_EC(long long)\n\tDEFINE_EC(unsigned long long)\n\tDEFINE_EC(float)\n\tDEFINE_EC(double)\n\tDEFINE_EC(long double)\n\n\t#undef DEFINE_EC\n\n\tText ec_to_text(EcHandle ec_handle)\n\t{\n\t\tText parent_ec = get_error_context_for(ec_handle);\n\t\tsize_t buffer_size = strlen(parent_ec.c_str()) + 2;\n\t\tchar* with_newline = reinterpret_cast<char*>(malloc(buffer_size));\n\t\twith_newline[0] = '\\n';\n\t#ifdef _WIN32\n\t\tstrncpy_s(with_newline + 1, buffer_size, parent_ec.c_str(), buffer_size - 2);\n\t#else\n\t\tstrcpy(with_newline + 1, parent_ec.c_str());\n\t#endif\n\t\treturn Text(with_newline);\n\t}\n\n\t// ----------------------------------------------------------------------------\n\n} // namespace loguru\n\n// ----------------------------------------------------------------------------\n// .dP\"Y8 88  dP\"\"b8 88b 88    db    88     .dP\"Y8\n// `Ybo.\" 88 dP   `\" 88Yb88   dPYb   88     `Ybo.\"\n// o.`Y8b 88 Yb  \"88 88 Y88  dP__Yb  88  .o o.`Y8b\n// 8bodP' 88  YboodP 88  Y8 dP\"\"\"\"Yb 88ood8 8bodP'\n// ----------------------------------------------------------------------------\n\n#ifdef _WIN32\nnamespace loguru {\n\tvoid install_signal_handlers(const SignalOptions& signal_options)\n\t{\n\t\t(void)signal_options;\n\t\t// TODO: implement signal handlers on windows\n\t}\n} // namespace loguru\n\n#else // _WIN32\n\nnamespace loguru\n{\n\tvoid write_to_stderr(const char* data, size_t size)\n\t{\n\t\tauto result = write(STDERR_FILENO, data, size);\n\t\t(void)result; // Ignore errors.\n\t}\n\n\tvoid write_to_stderr(const char* data)\n\t{\n\t\twrite_to_stderr(data, strlen(data));\n\t}\n\n\tvoid call_default_signal_handler(int signal_number)\n\t{\n\t\tstruct sigaction sig_action;\n\t\tmemset(&sig_action, 0, sizeof(sig_action));\n\t\tsigemptyset(&sig_action.sa_mask);\n\t\tsig_action.sa_handler = SIG_DFL;\n\t\tsigaction(signal_number, &sig_action, NULL);\n\t\tkill(getpid(), signal_number);\n\t}\n\n\tvoid signal_handler(int signal_number, siginfo_t*, void*)\n\t{\n\t\tconst char* signal_name = \"UNKNOWN SIGNAL\";\n\n\t\tif (signal_number == SIGABRT) { signal_name = \"SIGABRT\"; }\n\t\tif (signal_number == SIGBUS)  { signal_name = \"SIGBUS\";  }\n\t\tif (signal_number == SIGFPE)  { signal_name = \"SIGFPE\";  }\n\t\tif (signal_number == SIGILL)  { signal_name = \"SIGILL\";  }\n\t\tif (signal_number == SIGINT)  { signal_name = \"SIGINT\";  }\n\t\tif (signal_number == SIGSEGV) { signal_name = \"SIGSEGV\"; }\n\t\tif (signal_number == SIGTERM) { signal_name = \"SIGTERM\"; }\n\n\t\t// --------------------------------------------------------------------\n\t\t/* There are few things that are safe to do in a signal handler,\n\t\t   but writing to stderr is one of them.\n\t\t   So we first print out what happened to stderr so we're sure that gets out,\n\t\t   then we do the unsafe things, like logging the stack trace.\n\t\t*/\n\n\t\tif (g_colorlogtostderr && s_terminal_has_color) {\n\t\t\twrite_to_stderr(terminal_reset());\n\t\t\twrite_to_stderr(terminal_bold());\n\t\t\twrite_to_stderr(terminal_light_red());\n\t\t}\n\t\twrite_to_stderr(\"\\n\");\n\t\twrite_to_stderr(\"Loguru caught a signal: \");\n\t\twrite_to_stderr(signal_name);\n\t\twrite_to_stderr(\"\\n\");\n\t\tif (g_colorlogtostderr && s_terminal_has_color) {\n\t\t\twrite_to_stderr(terminal_reset());\n\t\t}\n\n\t\t// --------------------------------------------------------------------\n\n\t\tif (s_signal_options.unsafe_signal_handler) {\n\t\t\t// --------------------------------------------------------------------\n\t\t\t/* Now we do unsafe things. This can for example lead to deadlocks if\n\t\t\t   the signal was triggered from the system's memory management functions\n\t\t\t   and the code below tries to do allocations.\n\t\t\t*/\n\n\t\t\tflush();\n\t\t\tchar preamble_buff[LOGURU_PREAMBLE_WIDTH];\n\t\t\tprint_preamble(preamble_buff, sizeof(preamble_buff), Verbosity_FATAL, \"\", 0);\n\t\t\tauto message = Message{Verbosity_FATAL, \"\", 0, preamble_buff, \"\", \"Signal: \", signal_name};\n\t\t\ttry {\n\t\t\t\tlog_message(1, message, false, false);\n\t\t\t} catch (...) {\n\t\t\t\t// This can happed due to s_fatal_handler.\n\t\t\t\twrite_to_stderr(\"Exception caught and ignored by Loguru signal handler.\\n\");\n\t\t\t}\n\t\t\tflush();\n\n\t\t\t// --------------------------------------------------------------------\n\t\t}\n\n\t\tcall_default_signal_handler(signal_number);\n\t}\n\n\tvoid install_signal_handlers(const SignalOptions& signal_options)\n\t{\n\t\ts_signal_options = signal_options;\n\n\t\tstruct sigaction sig_action;\n\t\tmemset(&sig_action, 0, sizeof(sig_action));\n\t\tsigemptyset(&sig_action.sa_mask);\n\t\tsig_action.sa_flags |= SA_SIGINFO;\n\t\tsig_action.sa_sigaction = &signal_handler;\n\n\t\tif (signal_options.sigabrt) {\n\t\t\tCHECK_F(sigaction(SIGABRT, &sig_action, NULL) != -1, \"Failed to install handler for SIGABRT\");\n\t\t}\n\t\tif (signal_options.sigbus) {\n\t\t\tCHECK_F(sigaction(SIGBUS, &sig_action, NULL) != -1, \"Failed to install handler for SIGBUS\");\n\t\t}\n\t\tif (signal_options.sigfpe) {\n\t\t\tCHECK_F(sigaction(SIGFPE, &sig_action, NULL) != -1, \"Failed to install handler for SIGFPE\");\n\t\t}\n\t\tif (signal_options.sigill) {\n\t\t\tCHECK_F(sigaction(SIGILL, &sig_action, NULL) != -1, \"Failed to install handler for SIGILL\");\n\t\t}\n\t\tif (signal_options.sigint) {\n\t\t\tCHECK_F(sigaction(SIGINT, &sig_action, NULL) != -1, \"Failed to install handler for SIGINT\");\n\t\t}\n\t\tif (signal_options.sigsegv) {\n\t\t\tCHECK_F(sigaction(SIGSEGV, &sig_action, NULL) != -1, \"Failed to install handler for SIGSEGV\");\n\t\t}\n\t\tif (signal_options.sigterm) {\n\t\t\tCHECK_F(sigaction(SIGTERM, &sig_action, NULL) != -1, \"Failed to install handler for SIGTERM\");\n\t\t}\n\t}\n} // namespace loguru\n\n#endif // _WIN32\n\n\n#if defined(__GNUC__) || defined(__clang__)\n#pragma GCC diagnostic pop\n#elif defined(_MSC_VER)\n#pragma warning(pop)\n#endif\n\nLOGURU_ANONYMOUS_NAMESPACE_END\n\n#endif // LOGURU_IMPLEMENTATION\n"
  },
  {
    "path": "src/Shared/loguru.hpp",
    "content": "/*\nLoguru logging library for C++, by Emil Ernerfeldt.\nwww.github.com/emilk/loguru\nIf you find Loguru useful, please let me know on twitter or in a mail!\nTwitter: @ernerfeldt\nMail:    emil.ernerfeldt@gmail.com\nWebsite: www.ilikebigbits.com\n\n# License\n\tThis software is in the public domain. Where that dedication is not\n\trecognized, you are granted a perpetual, irrevocable license to\n\tcopy, modify and distribute it as you see fit.\n\n# Inspiration\n\tMuch of Loguru was inspired by GLOG, https://code.google.com/p/google-glog/.\n\tThe choice of public domain is fully due Sean T. Barrett\n\tand his wonderful stb libraries at https://github.com/nothings/stb.\n\n# Version history\n\t* Version 0.1.0 - 2015-03-22 - Works great on Mac.\n\t* Version 0.2.0 - 2015-09-17 - Removed the only dependency.\n\t* Version 0.3.0 - 2015-10-02 - Drop-in replacement for most of GLOG\n\t* Version 0.4.0 - 2015-10-07 - Single-file!\n\t* Version 0.5.0 - 2015-10-17 - Improved file logging\n\t* Version 0.6.0 - 2015-10-24 - Add stack traces\n\t* Version 0.7.0 - 2015-10-27 - Signals\n\t* Version 0.8.0 - 2015-10-30 - Color logging.\n\t* Version 0.9.0 - 2015-11-26 - ABORT_S and proper handling of FATAL\n\t* Version 1.0.0 - 2016-02-14 - ERROR_CONTEXT\n\t* Version 1.1.0 - 2016-02-19 - -v OFF, -v INFO etc\n\t* Version 1.1.1 - 2016-02-20 - textprintf vs strprintf\n\t* Version 1.1.2 - 2016-02-22 - Remove g_alsologtostderr\n\t* Version 1.1.3 - 2016-02-29 - ERROR_CONTEXT as linked list\n\t* Version 1.2.0 - 2016-03-19 - Add get_thread_name()\n\t* Version 1.2.1 - 2016-03-20 - Minor fixes\n\t* Version 1.2.2 - 2016-03-29 - Fix issues with set_fatal_handler throwing an exception\n\t* Version 1.2.3 - 2016-05-16 - Log current working directory in loguru::init().\n\t* Version 1.2.4 - 2016-05-18 - Custom replacement for -v in loguru::init() by bjoernpollex\n\t* Version 1.2.5 - 2016-05-18 - Add ability to print ERROR_CONTEXT of parent thread.\n\t* Version 1.2.6 - 2016-05-19 - Bug fix regarding VLOG verbosity argument lacking ().\n\t* Version 1.2.7 - 2016-05-23 - Fix PATH_MAX problem.\n\t* Version 1.2.8 - 2016-05-26 - Add shutdown() and remove_all_callbacks()\n\t* Version 1.2.9 - 2016-06-09 - Use a monotonic clock for uptime.\n\t* Version 1.3.0 - 2016-07-20 - Fix issues with callback flush/close not being called.\n\t* Version 1.3.1 - 2016-07-20 - Add LOGURU_UNSAFE_SIGNAL_HANDLER to toggle stacktrace on signals.\n\t* Version 1.3.2 - 2016-07-20 - Add loguru::arguments()\n\t* Version 1.4.0 - 2016-09-15 - Semantic versioning + add loguru::create_directories\n\t* Version 1.4.1 - 2016-09-29 - Customize formating with LOGURU_FILENAME_WIDTH\n\t* Version 1.5.0 - 2016-12-22 - LOGURU_USE_FMTLIB by kolis and LOGURU_WITH_FILEABS by scinart\n\t* Version 1.5.1 - 2017-08-08 - Terminal colors on Windows 10 thanks to looki\n\t* Version 1.6.0 - 2018-01-03 - Add LOGURU_RTTI and LOGURU_STACKTRACES settings\n\t* Version 1.7.0 - 2018-01-03 - Add ability to turn off the preamble with loguru::g_preamble\n\t* Version 1.7.1 - 2018-04-05 - Add function get_fatal_handler\n\t* Version 1.7.2 - 2018-04-22 - Fix a bug where large file names could cause stack corruption (thanks @ccamporesi)\n\t* Version 1.8.0 - 2018-04-23 - Shorten long file names to keep preamble fixed width\n\t* Version 1.9.0 - 2018-09-22 - Adjust terminal colors, add LOGURU_VERBOSE_SCOPE_ENDINGS, add LOGURU_SCOPE_TIME_PRECISION, add named log levels\n\t* Version 2.0.0 - 2018-09-22 - Split loguru.hpp into loguru.hpp and loguru.cpp\n\t* Version 2.1.0 - 2019-09-23 - Update fmtlib + add option to loguru::init to NOT set main thread name.\n\t* Version 2.2.0 - 2020-07-31 - Replace LOGURU_CATCH_SIGABRT with struct SignalOptions\n\n# Compiling\n\tJust include <loguru.hpp> where you want to use Loguru.\n\tThen, in one .cpp file #include <loguru.cpp>\n\tMake sure you compile with -std=c++11 -lstdc++ -lpthread -ldl\n\n# Usage\n\tFor details, please see the official documentation at emilk.github.io/loguru\n\n\t#include <loguru.hpp>\n\n\tint main(int argc, char* argv[]) {\n\t\tloguru::init(argc, argv);\n\n\t\t// Put every log message in \"everything.log\":\n\t\tloguru::add_file(\"everything.log\", loguru::Append, loguru::Verbosity_MAX);\n\n\t\tLOG_F(INFO, \"The magic number is %d\", 42);\n\t}\n\n*/\n\n#if defined(LOGURU_IMPLEMENTATION)\n\t#error \"You are defining LOGURU_IMPLEMENTATION. This is for older versions of Loguru. You should now instead include loguru.cpp (or build it and link with it)\"\n#endif\n\n// Disable all warnings from gcc/clang:\n#if defined(__clang__)\n\t#pragma clang system_header\n#elif defined(__GNUC__)\n\t#pragma GCC system_header\n#endif\n\n#ifndef LOGURU_HAS_DECLARED_FORMAT_HEADER\n#define LOGURU_HAS_DECLARED_FORMAT_HEADER\n\n// Semantic versioning. Loguru version can be printed with printf(\"%d.%d.%d\", LOGURU_VERSION_MAJOR, LOGURU_VERSION_MINOR, LOGURU_VERSION_PATCH);\n#define LOGURU_VERSION_MAJOR 2\n#define LOGURU_VERSION_MINOR 1\n#define LOGURU_VERSION_PATCH 0\n\n#if defined(_MSC_VER)\n#include <sal.h>\t// Needed for _In_z_ etc annotations\n#endif\n\n#if defined(__linux__) || defined(__APPLE__)\n#define LOGURU_SYSLOG 1\n#else\n#define LOGURU_SYSLOG 0\n#endif\n\n// ----------------------------------------------------------------------------\n\n#ifndef LOGURU_EXPORT\n\t// Define to your project's export declaration if needed for use in a shared library.\n\t#define LOGURU_EXPORT\n#endif\n\n#ifndef LOGURU_SCOPE_TEXT_SIZE\n\t// Maximum length of text that can be printed by a LOG_SCOPE.\n\t// This should be long enough to get most things, but short enough not to clutter the stack.\n\t#define LOGURU_SCOPE_TEXT_SIZE 196\n#endif\n\n#ifndef LOGURU_FILENAME_WIDTH\n\t// Width of the column containing the file name\n\t#define LOGURU_FILENAME_WIDTH 23\n#endif\n\n#ifndef LOGURU_THREADNAME_WIDTH\n\t// Width of the column containing the thread name\n\t#define LOGURU_THREADNAME_WIDTH 16\n#endif\n\n#ifndef LOGURU_SCOPE_TIME_PRECISION\n\t// Resolution of scope timers. 3=ms, 6=us, 9=ns\n\t#define LOGURU_SCOPE_TIME_PRECISION 3\n#endif\n\n#ifdef LOGURU_CATCH_SIGABRT\n\t#error \"You are defining LOGURU_CATCH_SIGABRT. This is for older versions of Loguru. You should now instead set the options passed to loguru::init\"\n#endif\n\n#ifndef LOGURU_VERBOSE_SCOPE_ENDINGS\n\t// Show milliseconds and scope name at end of scope.\n\t#define LOGURU_VERBOSE_SCOPE_ENDINGS 1\n#endif\n\n#ifndef LOGURU_REDEFINE_ASSERT\n\t#define LOGURU_REDEFINE_ASSERT 0\n#endif\n\n#ifndef LOGURU_WITH_STREAMS\n\t#define LOGURU_WITH_STREAMS 0\n#endif\n\n#ifndef LOGURU_REPLACE_GLOG\n\t#define LOGURU_REPLACE_GLOG 0\n#endif\n\n#if LOGURU_REPLACE_GLOG\n\t#undef LOGURU_WITH_STREAMS\n\t#define LOGURU_WITH_STREAMS 1\n#endif\n\n#if defined(LOGURU_UNSAFE_SIGNAL_HANDLER)\n\t#error \"You are defining LOGURU_UNSAFE_SIGNAL_HANDLER. This is for older versions of Loguru. You should now instead set the unsafe_signal_handler option when you call loguru::init.\"\n#endif\n\n#if LOGURU_IMPLEMENTATION\n\t#undef LOGURU_WITH_STREAMS\n\t#define LOGURU_WITH_STREAMS 1\n#endif\n\n#ifndef LOGURU_USE_FMTLIB\n\t#define LOGURU_USE_FMTLIB 0\n#endif\n\n#ifndef LOGURU_USE_LOCALE\n        #define LOGURU_USE_LOCALE 0\n#endif\n\n#ifndef LOGURU_WITH_FILEABS\n\t#define LOGURU_WITH_FILEABS 0\n#endif\n\n#ifndef LOGURU_RTTI\n#if defined(__clang__)\n\t#if __has_feature(cxx_rtti)\n\t\t#define LOGURU_RTTI 1\n\t#endif\n#elif defined(__GNUG__)\n\t#if defined(__GXX_RTTI)\n\t\t#define LOGURU_RTTI 1\n\t#endif\n#elif defined(_MSC_VER)\n\t#if defined(_CPPRTTI)\n\t\t#define LOGURU_RTTI 1\n\t#endif\n#endif\n#endif\n\n#ifdef LOGURU_USE_ANONYMOUS_NAMESPACE\n\t#define LOGURU_ANONYMOUS_NAMESPACE_BEGIN namespace {\n\t#define LOGURU_ANONYMOUS_NAMESPACE_END }\n#else\n\t#define LOGURU_ANONYMOUS_NAMESPACE_BEGIN\n\t#define LOGURU_ANONYMOUS_NAMESPACE_END\n#endif\n\n// --------------------------------------------------------------------\n// Utility macros\n\n#define LOGURU_CONCATENATE_IMPL(s1, s2) s1 ## s2\n#define LOGURU_CONCATENATE(s1, s2) LOGURU_CONCATENATE_IMPL(s1, s2)\n\n#ifdef __COUNTER__\n#   define LOGURU_ANONYMOUS_VARIABLE(str) LOGURU_CONCATENATE(str, __COUNTER__)\n#else\n#   define LOGURU_ANONYMOUS_VARIABLE(str) LOGURU_CONCATENATE(str, __LINE__)\n#endif\n\n#if defined(__clang__) || defined(__GNUC__)\n\t// Helper macro for declaring functions as having similar signature to printf.\n\t// This allows the compiler to catch format errors at compile-time.\n\t#define LOGURU_PRINTF_LIKE(fmtarg, firstvararg) __attribute__((__format__ (__printf__, fmtarg, firstvararg)))\n\t#define LOGURU_FORMAT_STRING_TYPE const char*\n#elif defined(_MSC_VER)\n\t#define LOGURU_PRINTF_LIKE(fmtarg, firstvararg)\n\t#define LOGURU_FORMAT_STRING_TYPE _In_z_ _Printf_format_string_ const char*\n#else\n\t#define LOGURU_PRINTF_LIKE(fmtarg, firstvararg)\n\t#define LOGURU_FORMAT_STRING_TYPE const char*\n#endif\n\n// Used to mark log_and_abort for the benefit of the static analyzer and optimizer.\n#if defined(_MSC_VER)\n#define LOGURU_NORETURN __declspec(noreturn)\n#else\n#define LOGURU_NORETURN __attribute__((noreturn))\n#endif\n\n#if defined(_MSC_VER)\n#define LOGURU_PREDICT_FALSE(x) (x)\n#define LOGURU_PREDICT_TRUE(x)  (x)\n#else\n#define LOGURU_PREDICT_FALSE(x) (__builtin_expect(x,     0))\n#define LOGURU_PREDICT_TRUE(x)  (__builtin_expect(!!(x), 1))\n#endif\n\n#if LOGURU_USE_FMTLIB\n\t#include <fmt/format.h>\n\t#define LOGURU_FMT(x) \"{:\" #x \"}\"\n#else\n\t#define LOGURU_FMT(x) \"%\" #x\n#endif\n\n#ifdef _WIN32\n\t#define STRDUP(str) _strdup(str)\n#else\n\t#define STRDUP(str) strdup(str)\n#endif\n\n#include <stdarg.h>\n\n// --------------------------------------------------------------------\nLOGURU_ANONYMOUS_NAMESPACE_BEGIN\n\nnamespace loguru\n{\n\t// Simple RAII ownership of a char*.\n\tclass LOGURU_EXPORT Text\n\t{\n\tpublic:\n\t\texplicit Text(char* owned_str) : _str(owned_str) {}\n\t\t~Text();\n\t\tText(Text&& t)\n\t\t{\n\t\t\t_str = t._str;\n\t\t\tt._str = nullptr;\n\t\t}\n\t\tText(Text& t) = delete;\n\t\tText& operator=(Text& t) = delete;\n\t\tvoid operator=(Text&& t) = delete;\n\n\t\tconst char* c_str() const { return _str; }\n\t\tbool empty() const { return _str == nullptr || *_str == '\\0'; }\n\n\t\tchar* release()\n\t\t{\n\t\t\tauto result = _str;\n\t\t\t_str = nullptr;\n\t\t\treturn result;\n\t\t}\n\n\tprivate:\n\t\tchar* _str;\n\t};\n\n\t// Like printf, but returns the formated text.\n#if LOGURU_USE_FMTLIB\n\tLOGURU_EXPORT\n\tText vtextprintf(const char* format, fmt::format_args args);\n\n\ttemplate<typename... Args>\n\tLOGURU_EXPORT\n\tText textprintf(LOGURU_FORMAT_STRING_TYPE format, const Args&... args) {\n\t\treturn vtextprintf(format, fmt::make_format_args(args...));\n\t}\n#else\n\tLOGURU_EXPORT\n\tText textprintf(LOGURU_FORMAT_STRING_TYPE format, ...) LOGURU_PRINTF_LIKE(1, 2);\n#endif\n\n\t// Overloaded for variadic template matching.\n\tLOGURU_EXPORT\n\tText textprintf();\n\n\tusing Verbosity = int;\n\n#undef FATAL\n#undef ERROR\n#undef WARNING\n#undef INFO\n#undef MAX\n\n\tenum NamedVerbosity : Verbosity\n\t{\n\t\t// Used to mark an invalid verbosity. Do not log to this level.\n\t\tVerbosity_INVALID = -10, // Never do LOG_F(INVALID)\n\n\t\t// You may use Verbosity_OFF on g_stderr_verbosity, but for nothing else!\n\t\tVerbosity_OFF     = -9, // Never do LOG_F(OFF)\n\n\t\t// Prefer to use ABORT_F or ABORT_S over LOG_F(FATAL) or LOG_S(FATAL).\n\t\tVerbosity_FATAL   = -3,\n\t\tVerbosity_ERROR   = -2,\n\t\tVerbosity_WARNING = -1,\n\n\t\t// Normal messages. By default written to stderr.\n\t\tVerbosity_INFO    =  0,\n\n\t\t// Same as Verbosity_INFO in every way.\n\t\tVerbosity_0       =  0,\n\n\t\t// Verbosity levels 1-9 are generally not written to stderr, but are written to file.\n\t\tVerbosity_1       = +1,\n\t\tVerbosity_2       = +2,\n\t\tVerbosity_3       = +3,\n\t\tVerbosity_4       = +4,\n\t\tVerbosity_5       = +5,\n\t\tVerbosity_6       = +6,\n\t\tVerbosity_7       = +7,\n\t\tVerbosity_8       = +8,\n\t\tVerbosity_9       = +9,\n\n\t\t// Do not use higher verbosity levels, as that will make grepping log files harder.\n\t\tVerbosity_MAX     = +9,\n\t};\n\n\tstruct Message\n\t{\n\t\t// You would generally print a Message by just concatenating the buffers without spacing.\n\t\t// Optionally, ignore preamble and indentation.\n\t\tVerbosity   verbosity;   // Already part of preamble\n\t\tconst char* filename;    // Already part of preamble\n\t\tunsigned    line;        // Already part of preamble\n\t\tconst char* preamble;    // Date, time, uptime, thread, file:line, verbosity.\n\t\tconst char* indentation; // Just a bunch of spacing.\n\t\tconst char* prefix;      // Assertion failure info goes here (or \"\").\n\t\tconst char* message;     // User message goes here.\n\t};\n\n\t/* Everything with a verbosity equal or greater than g_stderr_verbosity will be\n\twritten to stderr. You can set this in code or via the -v argument.\n\tSet to loguru::Verbosity_OFF to write nothing to stderr.\n\tDefault is 0, i.e. only log ERROR, WARNING and INFO are written to stderr.\n\t*/\n\tLOGURU_EXPORT extern Verbosity g_stderr_verbosity;\n\tLOGURU_EXPORT extern bool      g_colorlogtostderr; // True by default.\n\tLOGURU_EXPORT extern unsigned  g_flush_interval_ms; // 0 (unbuffered) by default.\n\tLOGURU_EXPORT extern bool      g_preamble_header; // Prepend each log start by a descriptions line with all columns name? True by default.\n\tLOGURU_EXPORT extern bool      g_preamble; // Prefix each log line with date, time etc? True by default.\n\n\t/* Specify the verbosity used by loguru to log its info messages including the header\n\tlogged when logged::init() is called or on exit. Default is 0 (INFO).\n\t*/\n\tLOGURU_EXPORT extern Verbosity g_internal_verbosity;\n\n\t// Turn off individual parts of the preamble\n\tLOGURU_EXPORT extern bool      g_preamble_date; // The date field\n\tLOGURU_EXPORT extern bool      g_preamble_time; // The time of the current day\n\tLOGURU_EXPORT extern bool      g_preamble_uptime; // The time since init call\n\tLOGURU_EXPORT extern bool      g_preamble_thread; // The logging thread\n\tLOGURU_EXPORT extern bool      g_preamble_file; // The file from which the log originates from\n\tLOGURU_EXPORT extern bool      g_preamble_verbose; // The verbosity field\n\tLOGURU_EXPORT extern bool      g_preamble_pipe; // The pipe symbol right before the message\n\n\t// May not throw!\n\ttypedef void (*log_handler_t)(void* user_data, const Message& message);\n\ttypedef void (*close_handler_t)(void* user_data);\n\ttypedef void (*flush_handler_t)(void* user_data);\n\n\t// May throw if that's how you'd like to handle your errors.\n\ttypedef void (*fatal_handler_t)(const Message& message);\n\n\t// Given a verbosity level, return the level's name or nullptr.\n\ttypedef const char* (*verbosity_to_name_t)(Verbosity verbosity);\n\n\t// Given a verbosity level name, return the verbosity level or\n\t// Verbosity_INVALID if name is not recognized.\n\ttypedef Verbosity (*name_to_verbosity_t)(const char* name);\n\n\tstruct SignalOptions\n\t{\n\t\t/// Make Loguru try to do unsafe but useful things,\n\t\t/// like printing a stack trace, when catching signals.\n\t\t/// This may lead to bad things like deadlocks in certain situations.\n\t\tbool unsafe_signal_handler = true;\n\n\t\t/// Should Loguru catch SIGABRT ?\n\t\tbool sigabrt = true;\n\n\t\t/// Should Loguru catch SIGBUS ?\n\t\tbool sigbus = true;\n\n\t\t/// Should Loguru catch SIGFPE ?\n\t\tbool sigfpe = true;\n\n\t\t/// Should Loguru catch SIGILL ?\n\t\tbool sigill = true;\n\n\t\t/// Should Loguru catch SIGINT ?\n\t\tbool sigint = true;\n\n\t\t/// Should Loguru catch SIGSEGV ?\n\t\tbool sigsegv = true;\n\n\t\t/// Should Loguru catch SIGTERM ?\n\t\tbool sigterm = true;\n\n\t\tstatic SignalOptions none()\n\t\t{\n\t\t\tSignalOptions options;\n\t\t\toptions.unsafe_signal_handler = false;\n\t\t\toptions.sigabrt = false;\n\t\t\toptions.sigbus = false;\n\t\t\toptions.sigfpe = false;\n\t\t\toptions.sigill = false;\n\t\t\toptions.sigint = false;\n\t\t\toptions.sigsegv = false;\n\t\t\toptions.sigterm = false;\n\t\t\treturn options;\n\t\t}\n\t};\n\n\t// Runtime options passed to loguru::init\n\tstruct Options\n\t{\n\t\t// This allows you to use something else instead of \"-v\" via verbosity_flag.\n\t\t// Set to nullptr if you don't want Loguru to parse verbosity from the args.\n\t\tconst char* verbosity_flag = \"-v\";\n\n\t\t// loguru::init will set the name of the calling thread to this.\n\t\t// If you don't want Loguru to set the name of the main thread,\n\t\t// set this to nullptr.\n\t\t// NOTE: on SOME platforms loguru::init will only overwrite the thread name\n\t\t// if a thread name has not already been set.\n\t\t// To always set a thread name, use loguru::set_thread_name instead.\n\t\tconst char* main_thread_name = \"main thread\";\n\n\t\tSignalOptions signal_options;\n\t};\n\n\t/*  Should be called from the main thread.\n\t\tYou don't *need* to call this, but if you do you get:\n\t\t\t* Signal handlers installed\n\t\t\t* Program arguments logged\n\t\t\t* Working dir logged\n\t\t\t* Optional -v verbosity flag parsed\n\t\t\t* Main thread name set to \"main thread\"\n\t\t\t* Explanation of the preamble (date, thread name, etc) logged\n\n\t\tloguru::init() will look for arguments meant for loguru and remove them.\n\t\tArguments meant for loguru are:\n\t\t\t-v n   Set loguru::g_stderr_verbosity level. Examples:\n\t\t\t\t-v 3        Show verbosity level 3 and lower.\n\t\t\t\t-v 0        Only show INFO, WARNING, ERROR, FATAL (default).\n\t\t\t\t-v INFO     Only show INFO, WARNING, ERROR, FATAL (default).\n\t\t\t\t-v WARNING  Only show WARNING, ERROR, FATAL.\n\t\t\t\t-v ERROR    Only show ERROR, FATAL.\n\t\t\t\t-v FATAL    Only show FATAL.\n\t\t\t\t-v OFF      Turn off logging to stderr.\n\n\t\tTip: You can set g_stderr_verbosity before calling loguru::init.\n\t\tThat way you can set the default but have the user override it with the -v flag.\n\t\tNote that -v does not affect file logging (see loguru::add_file).\n\n\t\tYou can you something other than the -v flag by setting the verbosity_flag option.\n\t*/\n\tLOGURU_EXPORT\n\tvoid init(int& argc, char* argv[], const Options& options = {});\n\n\t// Will call remove_all_callbacks(). After calling this, logging will still go to stderr.\n\t// You generally don't need to call this.\n\tLOGURU_EXPORT\n\tvoid shutdown();\n\n\t// What ~ will be replaced with, e.g. \"/home/your_user_name/\"\n\tLOGURU_EXPORT\n\tconst char* home_dir();\n\n\t/* Returns the name of the app as given in argv[0] but without leading path.\n\t   That is, if argv[0] is \"../foo/app\" this will return \"app\".\n\t*/\n\tLOGURU_EXPORT\n\tconst char* argv0_filename();\n\n\t// Returns all arguments given to loguru::init(), but escaped with a single space as separator.\n\tLOGURU_EXPORT\n\tconst char* arguments();\n\n\t// Returns the path to the current working dir when loguru::init() was called.\n\tLOGURU_EXPORT\n\tconst char* current_dir();\n\n\t// Returns the part of the path after the last / or \\ (if any).\n\tLOGURU_EXPORT\n\tconst char* filename(const char* path);\n\n\t// e.g. \"foo/bar/baz.ext\" will create the directories \"foo/\" and \"foo/bar/\"\n\tLOGURU_EXPORT\n\tbool create_directories(const char* file_path_const);\n\n\t// Writes date and time with millisecond precision, e.g. \"20151017_161503.123\"\n\tLOGURU_EXPORT\n\tvoid write_date_time(char* buff, unsigned long long buff_size);\n\n\t// Helper: thread-safe version strerror\n\tLOGURU_EXPORT\n\tText errno_as_text();\n\n\t/* Given a prefix of e.g. \"~/loguru/\" this might return\n\t   \"/home/your_username/loguru/app_name/20151017_161503.123.log\"\n\n\t   where \"app_name\" is a sanitized version of argv[0].\n\t*/\n\tLOGURU_EXPORT\n\tvoid suggest_log_path(const char* prefix, char* buff, unsigned long long buff_size);\n\n\tenum FileMode { Truncate, Append };\n\n\t/*  Will log to a file at the given path.\n\t\tAny logging message with a verbosity lower or equal to\n\t\tthe given verbosity will be included.\n\t\tThe function will create all directories in 'path' if needed.\n\t\tIf path starts with a ~, it will be replaced with loguru::home_dir()\n\t\tTo stop the file logging, just call loguru::remove_callback(path) with the same path.\n\t*/\n\tLOGURU_EXPORT\n\tbool add_file(const char* path, FileMode mode, Verbosity verbosity);\n\n\tLOGURU_EXPORT\n\t// Send logs to syslog with LOG_USER facility (see next call)\n\tbool add_syslog(const char* app_name, Verbosity verbosity);\n\tLOGURU_EXPORT\n\t// Send logs to syslog with your own choice of facility (LOG_USER, LOG_AUTH, ...)\n\t// see loguru.cpp: syslog_log() for more details.\n\tbool add_syslog(const char* app_name, Verbosity verbosity, int facility);\n\n\t/*  Will be called right before abort().\n\t\tYou can for instance use this to print custom error messages, or throw an exception.\n\t\tFeel free to call LOG:ing function from this, but not FATAL ones! */\n\tLOGURU_EXPORT\n\tvoid set_fatal_handler(fatal_handler_t handler);\n\n\t// Get the current fatal handler, if any. Default value is nullptr.\n\tLOGURU_EXPORT\n\tfatal_handler_t get_fatal_handler();\n\n\t/*  Will be called on each log messages with a verbosity less or equal to the given one.\n\t\tUseful for displaying messages on-screen in a game, for example.\n\t\tThe given on_close is also expected to flush (if desired).\n\t*/\n\tLOGURU_EXPORT\n\tvoid add_callback(\n\t\tconst char*     id,\n\t\tlog_handler_t   callback,\n\t\tvoid*           user_data,\n\t\tVerbosity       verbosity,\n\t\tclose_handler_t on_close = nullptr,\n\t\tflush_handler_t on_flush = nullptr);\n\n\t/*  Set a callback that returns custom verbosity level names. If callback\n\t\tis nullptr or returns nullptr, default log names will be used.\n\t*/\n\tLOGURU_EXPORT\n\tvoid set_verbosity_to_name_callback(verbosity_to_name_t callback);\n\n\t/*  Set a callback that returns the verbosity level matching a name. The\n\t\tcallback should return Verbosity_INVALID if the name is not\n\t\trecognized.\n\t*/\n\tLOGURU_EXPORT\n\tvoid set_name_to_verbosity_callback(name_to_verbosity_t callback);\n\n\t/*  Get a custom name for a specific verbosity, if one exists, or nullptr. */\n\tLOGURU_EXPORT\n\tconst char* get_verbosity_name(Verbosity verbosity);\n\n\t/*  Get the verbosity enum value from a custom 4-character level name, if one exists.\n\t\tIf the name does not match a custom level name, Verbosity_INVALID is returned.\n\t*/\n\tLOGURU_EXPORT\n\tVerbosity get_verbosity_from_name(const char* name);\n\n\t// Returns true iff the callback was found (and removed).\n\tLOGURU_EXPORT\n\tbool remove_callback(const char* id);\n\n\t// Shut down all file logging and any other callback hooks installed.\n\tLOGURU_EXPORT\n\tvoid remove_all_callbacks();\n\n\t// Returns the maximum of g_stderr_verbosity and all file/custom outputs.\n\tLOGURU_EXPORT\n\tVerbosity current_verbosity_cutoff();\n\n#if LOGURU_USE_FMTLIB\n\t// Internal functions\n    LOGURU_EXPORT\n\tvoid vlog(Verbosity verbosity, const char* file, unsigned line, LOGURU_FORMAT_STRING_TYPE format, fmt::format_args args);\n    LOGURU_EXPORT\n\tvoid raw_vlog(Verbosity verbosity, const char* file, unsigned line, LOGURU_FORMAT_STRING_TYPE format, fmt::format_args args);\n\n\t// Actual logging function. Use the LOG macro instead of calling this directly.\n\ttemplate <typename... Args>\n\tLOGURU_EXPORT\n\tvoid log(Verbosity verbosity, const char* file, unsigned line, LOGURU_FORMAT_STRING_TYPE format, const Args &... args) {\n\t    vlog(verbosity, file, line, format, fmt::make_format_args(args...));\n\t}\n\n\t// Log without any preamble or indentation.\n\ttemplate <typename... Args>\n\tLOGURU_EXPORT\n\tvoid raw_log(Verbosity verbosity, const char* file, unsigned line, LOGURU_FORMAT_STRING_TYPE format, const Args &... args) {\n\t    raw_vlog(verbosity, file, line, format, fmt::make_format_args(args...));\n\t}\n#else // LOGURU_USE_FMTLIB?\n\t// Actual logging function. Use the LOG macro instead of calling this directly.\n\tLOGURU_EXPORT\n\tvoid log(Verbosity verbosity, const char* file, unsigned line, LOGURU_FORMAT_STRING_TYPE format, ...) LOGURU_PRINTF_LIKE(4, 5);\n\n\t// Actual logging function.\n\tLOGURU_EXPORT\n\tvoid vlog(Verbosity verbosity, const char* file, unsigned line, LOGURU_FORMAT_STRING_TYPE format, va_list) LOGURU_PRINTF_LIKE(4, 0);\n\n\t// Log without any preamble or indentation.\n\tLOGURU_EXPORT\n\tvoid raw_log(Verbosity verbosity, const char* file, unsigned line, LOGURU_FORMAT_STRING_TYPE format, ...) LOGURU_PRINTF_LIKE(4, 5);\n#endif // !LOGURU_USE_FMTLIB\n\n\t// Helper class for LOG_SCOPE_F\n\tclass LOGURU_EXPORT LogScopeRAII\n\t{\n\tpublic:\n\t\tLogScopeRAII() : _file(nullptr) {} // No logging\n\t\tLogScopeRAII(Verbosity verbosity, const char* file, unsigned line, LOGURU_FORMAT_STRING_TYPE format, va_list vlist) LOGURU_PRINTF_LIKE(5, 0);\n\t\tLogScopeRAII(Verbosity verbosity, const char* file, unsigned line, LOGURU_FORMAT_STRING_TYPE format, ...) LOGURU_PRINTF_LIKE(5, 6);\n\t\t~LogScopeRAII();\n\n\t\tvoid Init(LOGURU_FORMAT_STRING_TYPE format, va_list vlist) LOGURU_PRINTF_LIKE(2, 0);\n\n#if defined(_MSC_VER) && _MSC_VER > 1800\n\t\t// older MSVC default move ctors close the scope on move. See\n\t\t// issue #43\n\t\tLogScopeRAII(LogScopeRAII&& other)\n\t\t\t: _verbosity(other._verbosity)\n\t\t\t, _file(other._file)\n\t\t\t, _line(other._line)\n\t\t\t, _indent_stderr(other._indent_stderr)\n\t\t\t, _start_time_ns(other._start_time_ns)\n\t\t{\n\t\t\t// Make sure the tmp object's destruction doesn't close the scope:\n\t\t\tother._file = nullptr;\n\n\t\t\tfor (unsigned int i = 0; i < LOGURU_SCOPE_TEXT_SIZE; ++i) {\n\t\t\t\t_name[i] = other._name[i];\n\t\t\t}\n\t\t}\n#else\n\t\tLogScopeRAII(LogScopeRAII&&) = default;\n#endif\n\n\tprivate:\n\t\tLogScopeRAII(const LogScopeRAII&) = delete;\n\t\tLogScopeRAII& operator=(const LogScopeRAII&) = delete;\n\t\tvoid operator=(LogScopeRAII&&) = delete;\n\n\t\tVerbosity   _verbosity;\n\t\tconst char* _file; // Set to null if we are disabled due to verbosity\n\t\tunsigned    _line;\n\t\tbool        _indent_stderr; // Did we?\n\t\tlong long   _start_time_ns;\n\t\tchar        _name[LOGURU_SCOPE_TEXT_SIZE];\n\t};\n\n\t// Marked as 'noreturn' for the benefit of the static analyzer and optimizer.\n\t// stack_trace_skip is the number of extrace stack frames to skip above log_and_abort.\n#if LOGURU_USE_FMTLIB\n\tLOGURU_EXPORT\n\tLOGURU_NORETURN void vlog_and_abort(int stack_trace_skip, const char* expr, const char* file, unsigned line, LOGURU_FORMAT_STRING_TYPE format, fmt::format_args);\n\ttemplate <typename... Args>\n\tLOGURU_EXPORT\n\tLOGURU_NORETURN void log_and_abort(int stack_trace_skip, const char* expr, const char* file, unsigned line, LOGURU_FORMAT_STRING_TYPE format, const Args&... args) {\n\t    vlog_and_abort(stack_trace_skip, expr, file, line, format, fmt::make_format_args(args...));\n\t}\n#else\n\tLOGURU_EXPORT\n\tLOGURU_NORETURN void log_and_abort(int stack_trace_skip, const char* expr, const char* file, unsigned line, LOGURU_FORMAT_STRING_TYPE format, ...) LOGURU_PRINTF_LIKE(5, 6);\n#endif\n\tLOGURU_EXPORT\n\tLOGURU_NORETURN void log_and_abort(int stack_trace_skip, const char* expr, const char* file, unsigned line);\n\n\t// Flush output to stderr and files.\n\t// If g_flush_interval_ms is set to non-zero, this will be called automatically this often.\n\t// If not set, you do not need to call this at all.\n\tLOGURU_EXPORT\n\tvoid flush();\n\n\ttemplate<class T> inline Text format_value(const T&)                    { return textprintf(\"N/A\");     }\n\ttemplate<>        inline Text format_value(const char& v)               { return textprintf(LOGURU_FMT(c),   v); }\n\ttemplate<>        inline Text format_value(const int& v)                { return textprintf(LOGURU_FMT(d),   v); }\n\ttemplate<>        inline Text format_value(const float& v)              { return textprintf(LOGURU_FMT(f),   v); }\n\ttemplate<>        inline Text format_value(const double& v)             { return textprintf(LOGURU_FMT(f),   v); }\n\n#if LOGURU_USE_FMTLIB\n\ttemplate<>        inline Text format_value(const unsigned int& v)       { return textprintf(LOGURU_FMT(d), v); }\n\ttemplate<>        inline Text format_value(const long& v)               { return textprintf(LOGURU_FMT(d), v); }\n\ttemplate<>        inline Text format_value(const unsigned long& v)      { return textprintf(LOGURU_FMT(d), v); }\n\ttemplate<>        inline Text format_value(const long long& v)          { return textprintf(LOGURU_FMT(d), v); }\n\ttemplate<>        inline Text format_value(const unsigned long long& v) { return textprintf(LOGURU_FMT(d), v); }\n#else\n\ttemplate<>        inline Text format_value(const unsigned int& v)       { return textprintf(LOGURU_FMT(u),   v); }\n\ttemplate<>        inline Text format_value(const long& v)               { return textprintf(LOGURU_FMT(lu),  v); }\n\ttemplate<>        inline Text format_value(const unsigned long& v)      { return textprintf(LOGURU_FMT(ld),  v); }\n\ttemplate<>        inline Text format_value(const long long& v)          { return textprintf(LOGURU_FMT(llu), v); }\n\ttemplate<>        inline Text format_value(const unsigned long long& v) { return textprintf(LOGURU_FMT(lld), v); }\n#endif\n\n\t/* Thread names can be set for the benefit of readable logs.\n\t   If you do not set the thread name, a hex id will be shown instead.\n\t   These thread names may or may not be the same as the system thread names,\n\t   depending on the system.\n\t   Try to limit the thread name to 15 characters or less. */\n\tLOGURU_EXPORT\n\tvoid set_thread_name(const char* name);\n\n\t/* Returns the thread name for this thread.\n\t   On most *nix systems this will return the system thread name (settable from both within and without Loguru).\n\t   On other systems it will return whatever you set in `set_thread_name()`;\n\t   If no thread name is set, this will return a hexadecimal thread id.\n\t   `length` should be the number of bytes available in the buffer.\n\t   17 is a good number for length.\n\t   `right_align_hex_id` means any hexadecimal thread id will be written to the end of buffer.\n\t*/\n\tLOGURU_EXPORT\n\tvoid get_thread_name(char* buffer, unsigned long long length, bool right_align_hex_id);\n\n\t/* Generates a readable stacktrace as a string.\n\t   'skip' specifies how many stack frames to skip.\n\t   For instance, the default skip (1) means:\n\t   don't include the call to loguru::stacktrace in the stack trace. */\n\tLOGURU_EXPORT\n\tText stacktrace(int skip = 1);\n\n\t/*  Add a string to be replaced with something else in the stack output.\n\n\t\tFor instance, instead of having a stack trace look like this:\n\t\t\t0x41f541 some_function(std::basic_ofstream<char, std::char_traits<char> >&)\n\t\tYou can clean it up with:\n\t\t\tauto verbose_type_name = loguru::demangle(typeid(std::ofstream).name());\n\t\t\tloguru::add_stack_cleanup(verbose_type_name.c_str(); \"std::ofstream\");\n\t\tSo the next time you will instead see:\n\t\t\t0x41f541 some_function(std::ofstream&)\n\n\t\t`replace_with_this` must be shorter than `find_this`.\n\t*/\n\tLOGURU_EXPORT\n\tvoid add_stack_cleanup(const char* find_this, const char* replace_with_this);\n\n\t// Example: demangle(typeid(std::ofstream).name()) -> \"std::basic_ofstream<char, std::char_traits<char> >\"\n\tLOGURU_EXPORT\n\tText demangle(const char* name);\n\n\t// ------------------------------------------------------------------------\n\t/*\n\tNot all terminals support colors, but if they do, and g_colorlogtostderr\n\tis set, Loguru will write them to stderr to make errors in red, etc.\n\n\tYou also have the option to manually use them, via the function below.\n\n\tNote, however, that if you do, the color codes could end up in your logfile!\n\n\tThis means if you intend to use them functions you should either:\n\t\t* Use them on the stderr/stdout directly (bypass Loguru).\n\t\t* Don't add file outputs to Loguru.\n\t\t* Expect some \\e[1m things in your logfile.\n\n\tUsage:\n\t\tprintf(\"%sRed%sGreen%sBold green%sClear again\\n\",\n\t\t\t   loguru::terminal_red(), loguru::terminal_green(),\n\t\t\t   loguru::terminal_bold(), loguru::terminal_reset());\n\n\tIf the terminal at hand does not support colors the above output\n\twill just not have funky \\e[1m things showing.\n\t*/\n\n\t// Do the output terminal support colors?\n\tLOGURU_EXPORT\n\tbool terminal_has_color();\n\n\t// Colors\n\tLOGURU_EXPORT const char* terminal_black();\n\tLOGURU_EXPORT const char* terminal_red();\n\tLOGURU_EXPORT const char* terminal_green();\n\tLOGURU_EXPORT const char* terminal_yellow();\n\tLOGURU_EXPORT const char* terminal_blue();\n\tLOGURU_EXPORT const char* terminal_purple();\n\tLOGURU_EXPORT const char* terminal_cyan();\n\tLOGURU_EXPORT const char* terminal_light_gray();\n\tLOGURU_EXPORT const char* terminal_light_red();\n\tLOGURU_EXPORT const char* terminal_white();\n\n\t// Formating\n\tLOGURU_EXPORT const char* terminal_bold();\n\tLOGURU_EXPORT const char* terminal_underline();\n\n\t// You should end each line with this!\n\tLOGURU_EXPORT const char* terminal_reset();\n\n\t// --------------------------------------------------------------------\n\t// Error context related:\n\n\tstruct StringStream;\n\n\t// Use this in your EcEntryBase::print_value overload.\n\tLOGURU_EXPORT\n\tvoid stream_print(StringStream& out_string_stream, const char* text);\n\n\tclass LOGURU_EXPORT EcEntryBase\n\t{\n\tpublic:\n\t\tEcEntryBase(const char* file, unsigned line, const char* descr);\n\t\t~EcEntryBase();\n\t\tEcEntryBase(const EcEntryBase&) = delete;\n\t\tEcEntryBase(EcEntryBase&&) = delete;\n\t\tEcEntryBase& operator=(const EcEntryBase&) = delete;\n\t\tEcEntryBase& operator=(EcEntryBase&&) = delete;\n\n\t\tvirtual void print_value(StringStream& out_string_stream) const = 0;\n\n\t\tEcEntryBase* previous() const { return _previous; }\n\n\t// private:\n\t\tconst char*  _file;\n\t\tunsigned     _line;\n\t\tconst char*  _descr;\n\t\tEcEntryBase* _previous;\n\t};\n\n\ttemplate<typename T>\n\tclass EcEntryData : public EcEntryBase\n\t{\n\tpublic:\n\t\tusing Printer = Text(*)(T data);\n\n\t\tEcEntryData(const char* file, unsigned line, const char* descr, T data, Printer&& printer)\n\t\t\t: EcEntryBase(file, line, descr), _data(data), _printer(printer) {}\n\n\t\tvirtual void print_value(StringStream& out_string_stream) const override\n\t\t{\n\t\t\tconst auto str = _printer(_data);\n\t\t\tstream_print(out_string_stream, str.c_str());\n\t\t}\n\n\tprivate:\n\t\tT       _data;\n\t\tPrinter _printer;\n\t};\n\n\t// template<typename Printer>\n\t// class EcEntryLambda : public EcEntryBase\n\t// {\n\t// public:\n\t// \tEcEntryLambda(const char* file, unsigned line, const char* descr, Printer&& printer)\n\t// \t\t: EcEntryBase(file, line, descr), _printer(std::move(printer)) {}\n\n\t// \tvirtual void print_value(StringStream& out_string_stream) const override\n\t// \t{\n\t// \t\tconst auto str = _printer();\n\t// \t\tstream_print(out_string_stream, str.c_str());\n\t// \t}\n\n\t// private:\n\t// \tPrinter _printer;\n\t// };\n\n\t// template<typename Printer>\n\t// EcEntryLambda<Printer> make_ec_entry_lambda(const char* file, unsigned line, const char* descr, Printer&& printer)\n\t// {\n\t// \treturn {file, line, descr, std::move(printer)};\n\t// }\n\n\ttemplate <class T>\n\tstruct decay_char_array { using type = T; };\n\n\ttemplate <unsigned long long  N>\n\tstruct decay_char_array<const char(&)[N]> { using type = const char*; };\n\n\ttemplate <class T>\n\tstruct make_const_ptr { using type = T; };\n\n\ttemplate <class T>\n\tstruct make_const_ptr<T*> { using type = const T*; };\n\n\ttemplate <class T>\n\tstruct make_ec_type { using type = typename make_const_ptr<typename decay_char_array<T>::type>::type; };\n\n\t/* \tA stack trace gives you the names of the function at the point of a crash.\n\t\tWith ERROR_CONTEXT, you can also get the values of select local variables.\n\t\tUsage:\n\n\t\tvoid process_customers(const std::string& filename)\n\t\t{\n\t\t\tERROR_CONTEXT(\"Processing file\", filename.c_str());\n\t\t\tfor (int customer_index : ...)\n\t\t\t{\n\t\t\t\tERROR_CONTEXT(\"Customer index\", customer_index);\n\t\t\t\t...\n\t\t\t}\n\t\t}\n\n\t\tThe context is in effect during the scope of the ERROR_CONTEXT.\n\t\tUse loguru::get_error_context() to get the contents of the active error contexts.\n\n\t\tExample result:\n\n\t\t------------------------------------------------\n\t\t[ErrorContext]                main.cpp:416   Processing file:    \"customers.json\"\n\t\t[ErrorContext]                main.cpp:417   Customer index:     42\n\t\t------------------------------------------------\n\n\t\tError contexts are printed automatically on crashes, and only on crashes.\n\t\tThis makes them much faster than logging the value of a variable.\n\t*/\n\t#define ERROR_CONTEXT(descr, data)                                             \\\n\t\tconst loguru::EcEntryData<loguru::make_ec_type<decltype(data)>::type>      \\\n\t\t\tLOGURU_ANONYMOUS_VARIABLE(error_context_scope_)(                       \\\n\t\t\t\t__FILE__, __LINE__, descr, data,                                   \\\n\t\t\t\tstatic_cast<loguru::EcEntryData<loguru::make_ec_type<decltype(data)>::type>::Printer>(loguru::ec_to_text) ) // For better error messages\n\n/*\n\t#define ERROR_CONTEXT(descr, data)                                 \\\n\t\tconst auto LOGURU_ANONYMOUS_VARIABLE(error_context_scope_)(    \\\n\t\t\tloguru::make_ec_entry_lambda(__FILE__, __LINE__, descr,    \\\n\t\t\t\t[=](){ return loguru::ec_to_text(data); }))\n*/\n\n\tusing EcHandle = const EcEntryBase*;\n\n\t/*\n\t\tGet a light-weight handle to the error context stack on this thread.\n\t\tThe handle is valid as long as the current thread has no changes to its error context stack.\n\t\tYou can pass the handle to loguru::get_error_context on another thread.\n\t\tThis can be very useful for when you have a parent thread spawning several working threads,\n\t\tand you want the error context of the parent thread to get printed (too) when there is an\n\t\terror on the child thread. You can accomplish this thusly:\n\n\t\tvoid foo(const char* parameter)\n\t\t{\n\t\t\tERROR_CONTEXT(\"parameter\", parameter)\n\t\t\tconst auto parent_ec_handle = loguru::get_thread_ec_handle();\n\n\t\t\tstd::thread([=]{\n\t\t\t\tloguru::set_thread_name(\"child thread\");\n\t\t\t\tERROR_CONTEXT(\"parent context\", parent_ec_handle);\n\t\t\t\tdangerous_code();\n\t\t\t}.join();\n\t\t}\n\n\t*/\n\tLOGURU_EXPORT\n\tEcHandle get_thread_ec_handle();\n\n\t// Get a string describing the current stack of error context. Empty string if there is none.\n\tLOGURU_EXPORT\n\tText get_error_context();\n\n\t// Get a string describing the error context of the given thread handle.\n\tLOGURU_EXPORT\n\tText get_error_context_for(EcHandle ec_handle);\n\n\t// ------------------------------------------------------------------------\n\n\tLOGURU_EXPORT Text ec_to_text(const char* data);\n\tLOGURU_EXPORT Text ec_to_text(char data);\n\tLOGURU_EXPORT Text ec_to_text(int data);\n\tLOGURU_EXPORT Text ec_to_text(unsigned int data);\n\tLOGURU_EXPORT Text ec_to_text(long data);\n\tLOGURU_EXPORT Text ec_to_text(unsigned long data);\n\tLOGURU_EXPORT Text ec_to_text(long long data);\n\tLOGURU_EXPORT Text ec_to_text(unsigned long long data);\n\tLOGURU_EXPORT Text ec_to_text(float data);\n\tLOGURU_EXPORT Text ec_to_text(double data);\n\tLOGURU_EXPORT Text ec_to_text(long double data);\n\tLOGURU_EXPORT Text ec_to_text(EcHandle);\n\n\t/*\n\tYou can add ERROR_CONTEXT support for your own types by overloading ec_to_text. Here's how:\n\n\tsome.hpp:\n\t\tnamespace loguru {\n\t\t\tText ec_to_text(MySmallType data)\n\t\t\tText ec_to_text(const MyBigType* data)\n\t\t} // namespace loguru\n\n\tsome.cpp:\n\t\tnamespace loguru {\n\t\t\tText ec_to_text(MySmallType small_value)\n\t\t\t{\n\t\t\t\t// Called only when needed, i.e. on a crash.\n\t\t\t\tstd::string str = small_value.as_string(); // Format 'small_value' here somehow.\n\t\t\t\treturn Text{STRDUP(str.c_str())};\n\t\t\t}\n\n\t\t\tText ec_to_text(const MyBigType* big_value)\n\t\t\t{\n\t\t\t\t// Called only when needed, i.e. on a crash.\n\t\t\t\tstd::string str = big_value->as_string(); // Format 'big_value' here somehow.\n\t\t\t\treturn Text{STRDUP(str.c_str())};\n\t\t\t}\n\t\t} // namespace loguru\n\n\tAny file that include some.hpp:\n\t\tvoid foo(MySmallType small, const MyBigType& big)\n\t\t{\n\t\t\tERROR_CONTEXT(\"Small\", small); // Copy ´small` by value.\n\t\t\tERROR_CONTEXT(\"Big\",   &big);  // `big` should not change during this scope!\n\t\t\t....\n\t\t}\n\t*/\n} // namespace loguru\n\nLOGURU_ANONYMOUS_NAMESPACE_END\n\n// --------------------------------------------------------------------\n// Logging macros\n\n// LOG_F(2, \"Only logged if verbosity is 2 or higher: %d\", some_number);\n#define VLOG_F(verbosity, ...)                                                                     \\\n\t((verbosity) > loguru::current_verbosity_cutoff()) ? (void)0                                   \\\n\t\t\t\t\t\t\t\t\t  : loguru::log(verbosity, __FILE__, __LINE__, __VA_ARGS__)\n\n// LOG_F(INFO, \"Foo: %d\", some_number);\n#define LOG_F(verbosity_name, ...) VLOG_F(loguru::Verbosity_ ## verbosity_name, __VA_ARGS__)\n\n#define VLOG_IF_F(verbosity, cond, ...)                                                            \\\n\t((verbosity) > loguru::current_verbosity_cutoff() || (cond) == false)                          \\\n\t\t? (void)0                                                                                  \\\n\t\t: loguru::log(verbosity, __FILE__, __LINE__, __VA_ARGS__)\n\n#define LOG_IF_F(verbosity_name, cond, ...)                                                        \\\n\tVLOG_IF_F(loguru::Verbosity_ ## verbosity_name, cond, __VA_ARGS__)\n\n#define VLOG_SCOPE_F(verbosity, ...)                                                               \\\n\tloguru::LogScopeRAII LOGURU_ANONYMOUS_VARIABLE(error_context_RAII_) =                          \\\n\t((verbosity) > loguru::current_verbosity_cutoff()) ? loguru::LogScopeRAII() :                  \\\n\tloguru::LogScopeRAII(verbosity, __FILE__, __LINE__, __VA_ARGS__)\n\n// Raw logging - no preamble, no indentation. Slightly faster than full logging.\n#define RAW_VLOG_F(verbosity, ...)                                                                 \\\n\t((verbosity) > loguru::current_verbosity_cutoff()) ? (void)0                                   \\\n\t\t\t\t\t\t\t\t\t  : loguru::raw_log(verbosity, __FILE__, __LINE__, __VA_ARGS__)\n\n#define RAW_LOG_F(verbosity_name, ...) RAW_VLOG_F(loguru::Verbosity_ ## verbosity_name, __VA_ARGS__)\n\n// Use to book-end a scope. Affects logging on all threads.\n#define LOG_SCOPE_F(verbosity_name, ...)                                                           \\\n\tVLOG_SCOPE_F(loguru::Verbosity_ ## verbosity_name, __VA_ARGS__)\n\n#define LOG_SCOPE_FUNCTION(verbosity_name) LOG_SCOPE_F(verbosity_name, __func__)\n\n// -----------------------------------------------\n// ABORT_F macro. Usage:  ABORT_F(\"Cause of error: %s\", error_str);\n\n// Message is optional\n#define ABORT_F(...) loguru::log_and_abort(0, \"ABORT: \", __FILE__, __LINE__, __VA_ARGS__)\n\n// --------------------------------------------------------------------\n// CHECK_F macros:\n\n#define CHECK_WITH_INFO_F(test, info, ...)                                                         \\\n\tLOGURU_PREDICT_TRUE((test) == true) ? (void)0 : loguru::log_and_abort(0, \"CHECK FAILED:  \" info \"  \", __FILE__,      \\\n\t\t\t\t\t\t\t\t\t\t\t\t\t   __LINE__, ##__VA_ARGS__)\n\n/* Checked at runtime too. Will print error, then call fatal_handler (if any), then 'abort'.\n   Note that the test must be boolean.\n   CHECK_F(ptr); will not compile, but CHECK_F(ptr != nullptr); will. */\n#define CHECK_F(test, ...) CHECK_WITH_INFO_F(test, #test, ##__VA_ARGS__)\n\n#define CHECK_NOTNULL_F(x, ...) CHECK_WITH_INFO_F((x) != nullptr, #x \" != nullptr\", ##__VA_ARGS__)\n\n#define CHECK_OP_F(expr_left, expr_right, op, ...)                                                 \\\n\tdo                                                                                             \\\n\t{                                                                                              \\\n\t\tauto val_left = expr_left;                                                                 \\\n\t\tauto val_right = expr_right;                                                               \\\n\t\tif (! LOGURU_PREDICT_TRUE(val_left op val_right))                                          \\\n\t\t{                                                                                          \\\n\t\t\tauto str_left = loguru::format_value(val_left);                                        \\\n\t\t\tauto str_right = loguru::format_value(val_right);                                      \\\n\t\t\tauto fail_info = loguru::textprintf(\"CHECK FAILED:  \" LOGURU_FMT(s) \" \" LOGURU_FMT(s) \" \" LOGURU_FMT(s) \"  (\" LOGURU_FMT(s) \" \" LOGURU_FMT(s) \" \" LOGURU_FMT(s) \")  \",           \\\n\t\t\t\t#expr_left, #op, #expr_right, str_left.c_str(), #op, str_right.c_str());           \\\n\t\t\tauto user_msg = loguru::textprintf(__VA_ARGS__);                                       \\\n\t\t\tloguru::log_and_abort(0, fail_info.c_str(), __FILE__, __LINE__,                        \\\n\t\t\t                      LOGURU_FMT(s), user_msg.c_str());                                         \\\n\t\t}                                                                                          \\\n\t} while (false)\n\n#ifndef LOGURU_DEBUG_LOGGING\n\t#ifndef NDEBUG\n\t\t#define LOGURU_DEBUG_LOGGING 1\n\t#else\n\t\t#define LOGURU_DEBUG_LOGGING 0\n\t#endif\n#endif\n\n#if LOGURU_DEBUG_LOGGING\n\t// Debug logging enabled:\n\t#define DLOG_F(verbosity_name, ...)     LOG_F(verbosity_name, __VA_ARGS__)\n\t#define DVLOG_F(verbosity, ...)         VLOG_F(verbosity, __VA_ARGS__)\n\t#define DLOG_IF_F(verbosity_name, ...)  LOG_IF_F(verbosity_name, __VA_ARGS__)\n\t#define DVLOG_IF_F(verbosity, ...)      VLOG_IF_F(verbosity, __VA_ARGS__)\n\t#define DRAW_LOG_F(verbosity_name, ...) RAW_LOG_F(verbosity_name, __VA_ARGS__)\n\t#define DRAW_VLOG_F(verbosity, ...)     RAW_VLOG_F(verbosity, __VA_ARGS__)\n#else\n\t// Debug logging disabled:\n\t#define DLOG_F(verbosity_name, ...)\n\t#define DVLOG_F(verbosity, ...)\n\t#define DLOG_IF_F(verbosity_name, ...)\n\t#define DVLOG_IF_F(verbosity, ...)\n\t#define DRAW_LOG_F(verbosity_name, ...)\n\t#define DRAW_VLOG_F(verbosity, ...)\n#endif\n\n#define CHECK_EQ_F(a, b, ...) CHECK_OP_F(a, b, ==, ##__VA_ARGS__)\n#define CHECK_NE_F(a, b, ...) CHECK_OP_F(a, b, !=, ##__VA_ARGS__)\n#define CHECK_LT_F(a, b, ...) CHECK_OP_F(a, b, < , ##__VA_ARGS__)\n#define CHECK_GT_F(a, b, ...) CHECK_OP_F(a, b, > , ##__VA_ARGS__)\n#define CHECK_LE_F(a, b, ...) CHECK_OP_F(a, b, <=, ##__VA_ARGS__)\n#define CHECK_GE_F(a, b, ...) CHECK_OP_F(a, b, >=, ##__VA_ARGS__)\n\n#ifndef LOGURU_DEBUG_CHECKS\n\t#ifndef NDEBUG\n\t\t#define LOGURU_DEBUG_CHECKS 1\n\t#else\n\t\t#define LOGURU_DEBUG_CHECKS 0\n\t#endif\n#endif\n\n#if LOGURU_DEBUG_CHECKS\n\t// Debug checks enabled:\n\t#define DCHECK_F(test, ...)             CHECK_F(test, ##__VA_ARGS__)\n\t#define DCHECK_NOTNULL_F(x, ...)        CHECK_NOTNULL_F(x, ##__VA_ARGS__)\n\t#define DCHECK_EQ_F(a, b, ...)          CHECK_EQ_F(a, b, ##__VA_ARGS__)\n\t#define DCHECK_NE_F(a, b, ...)          CHECK_NE_F(a, b, ##__VA_ARGS__)\n\t#define DCHECK_LT_F(a, b, ...)          CHECK_LT_F(a, b, ##__VA_ARGS__)\n\t#define DCHECK_LE_F(a, b, ...)          CHECK_LE_F(a, b, ##__VA_ARGS__)\n\t#define DCHECK_GT_F(a, b, ...)          CHECK_GT_F(a, b, ##__VA_ARGS__)\n\t#define DCHECK_GE_F(a, b, ...)          CHECK_GE_F(a, b, ##__VA_ARGS__)\n#else\n\t// Debug checks disabled:\n\t#define DCHECK_F(test, ...)\n\t#define DCHECK_NOTNULL_F(x, ...)\n\t#define DCHECK_EQ_F(a, b, ...)\n\t#define DCHECK_NE_F(a, b, ...)\n\t#define DCHECK_LT_F(a, b, ...)\n\t#define DCHECK_LE_F(a, b, ...)\n\t#define DCHECK_GT_F(a, b, ...)\n\t#define DCHECK_GE_F(a, b, ...)\n#endif // NDEBUG\n\n\n#if LOGURU_REDEFINE_ASSERT\n\t#undef assert\n\t#ifndef NDEBUG\n\t\t// Debug:\n\t\t#define assert(test) CHECK_WITH_INFO_F(!!(test), #test) // HACK\n\t#else\n\t\t#define assert(test)\n\t#endif\n#endif // LOGURU_REDEFINE_ASSERT\n\n#endif // LOGURU_HAS_DECLARED_FORMAT_HEADER\n\n// ----------------------------------------------------------------------------\n// .dP\"Y8 888888 88\"\"Yb 888888    db    8b    d8 .dP\"Y8\n// `Ybo.\"   88   88__dP 88__     dPYb   88b  d88 `Ybo.\"\n// o.`Y8b   88   88\"Yb  88\"\"    dP__Yb  88YbdP88 o.`Y8b\n// 8bodP'   88   88  Yb 888888 dP\"\"\"\"Yb 88 YY 88 8bodP'\n\n#if LOGURU_WITH_STREAMS\n#ifndef LOGURU_HAS_DECLARED_STREAMS_HEADER\n#define LOGURU_HAS_DECLARED_STREAMS_HEADER\n\n/* This file extends loguru to enable std::stream-style logging, a la Glog.\n   It's an optional feature behind the LOGURU_WITH_STREAMS settings\n   because including it everywhere will slow down compilation times.\n*/\n\n#include <cstdarg>\n#include <sstream> // Adds about 38 kLoC on clang.\n#include <string>\n\nLOGURU_ANONYMOUS_NAMESPACE_BEGIN\n\nnamespace loguru\n{\n\t// Like sprintf, but returns the formated text.\n\tLOGURU_EXPORT\n\tstd::string strprintf(LOGURU_FORMAT_STRING_TYPE format, ...) LOGURU_PRINTF_LIKE(1, 2);\n\n\t// Like vsprintf, but returns the formated text.\n\tLOGURU_EXPORT\n\tstd::string vstrprintf(LOGURU_FORMAT_STRING_TYPE format, va_list) LOGURU_PRINTF_LIKE(1, 0);\n\n\tclass LOGURU_EXPORT StreamLogger\n\t{\n\tpublic:\n\t\tStreamLogger(Verbosity verbosity, const char* file, unsigned line) : _verbosity(verbosity), _file(file), _line(line) {}\n\t\t~StreamLogger() noexcept(false);\n\n\t\ttemplate<typename T>\n\t\tStreamLogger& operator<<(const T& t)\n\t\t{\n\t\t\t_ss << t;\n\t\t\treturn *this;\n\t\t}\n\n\t\t// std::endl and other iomanip:s.\n\t\tStreamLogger& operator<<(std::ostream&(*f)(std::ostream&))\n\t\t{\n\t\t\tf(_ss);\n\t\t\treturn *this;\n\t\t}\n\n\tprivate:\n\t\tVerbosity   _verbosity;\n\t\tconst char* _file;\n\t\tunsigned    _line;\n\t\tstd::ostringstream _ss;\n\t};\n\n\tclass LOGURU_EXPORT AbortLogger\n\t{\n\tpublic:\n\t\tAbortLogger(const char* expr, const char* file, unsigned line) : _expr(expr), _file(file), _line(line) { }\n\t\tLOGURU_NORETURN ~AbortLogger() noexcept(false);\n\n\t\ttemplate<typename T>\n\t\tAbortLogger& operator<<(const T& t)\n\t\t{\n\t\t\t_ss << t;\n\t\t\treturn *this;\n\t\t}\n\n\t\t// std::endl and other iomanip:s.\n\t\tAbortLogger& operator<<(std::ostream&(*f)(std::ostream&))\n\t\t{\n\t\t\tf(_ss);\n\t\t\treturn *this;\n\t\t}\n\n\tprivate:\n\t\tconst char*        _expr;\n\t\tconst char*        _file;\n\t\tunsigned           _line;\n\t\tstd::ostringstream _ss;\n\t};\n\n\tclass LOGURU_EXPORT Voidify\n\t{\n\tpublic:\n\t\tVoidify() {}\n\t\t// This has to be an operator with a precedence lower than << but higher than ?:\n\t\tvoid operator&(const StreamLogger&) { }\n\t\tvoid operator&(const AbortLogger&)  { }\n\t};\n\n\t/*  Helper functions for CHECK_OP_S macro.\n\t\tGLOG trick: The (int, int) specialization works around the issue that the compiler\n\t\twill not instantiate the template version of the function on values of unnamed enum type. */\n\t#define DEFINE_CHECK_OP_IMPL(name, op)                                                             \\\n\t\ttemplate <typename T1, typename T2>                                                            \\\n\t\tinline std::string* name(const char* expr, const T1& v1, const char* op_str, const T2& v2)     \\\n\t\t{                                                                                              \\\n\t\t\tif (LOGURU_PREDICT_TRUE(v1 op v2)) { return NULL; }                                        \\\n\t\t\tstd::ostringstream ss;                                                                     \\\n\t\t\tss << \"CHECK FAILED:  \" << expr << \"  (\" << v1 << \" \" << op_str << \" \" << v2 << \")  \";     \\\n\t\t\treturn new std::string(ss.str());                                                          \\\n\t\t}                                                                                              \\\n\t\tinline std::string* name(const char* expr, int v1, const char* op_str, int v2)                 \\\n\t\t{                                                                                              \\\n\t\t\treturn name<int, int>(expr, v1, op_str, v2);                                               \\\n\t\t}\n\n\tDEFINE_CHECK_OP_IMPL(check_EQ_impl, ==)\n\tDEFINE_CHECK_OP_IMPL(check_NE_impl, !=)\n\tDEFINE_CHECK_OP_IMPL(check_LE_impl, <=)\n\tDEFINE_CHECK_OP_IMPL(check_LT_impl, < )\n\tDEFINE_CHECK_OP_IMPL(check_GE_impl, >=)\n\tDEFINE_CHECK_OP_IMPL(check_GT_impl, > )\n\t#undef DEFINE_CHECK_OP_IMPL\n\n\t/*  GLOG trick: Function is overloaded for integral types to allow static const integrals\n\t\tdeclared in classes and not defined to be used as arguments to CHECK* macros. */\n\ttemplate <class T>\n\tinline const T&           referenceable_value(const T&           t) { return t; }\n\tinline char               referenceable_value(char               t) { return t; }\n\tinline unsigned char      referenceable_value(unsigned char      t) { return t; }\n\tinline signed char        referenceable_value(signed char        t) { return t; }\n\tinline short              referenceable_value(short              t) { return t; }\n\tinline unsigned short     referenceable_value(unsigned short     t) { return t; }\n\tinline int                referenceable_value(int                t) { return t; }\n\tinline unsigned int       referenceable_value(unsigned int       t) { return t; }\n\tinline long               referenceable_value(long               t) { return t; }\n\tinline unsigned long      referenceable_value(unsigned long      t) { return t; }\n\tinline long long          referenceable_value(long long          t) { return t; }\n\tinline unsigned long long referenceable_value(unsigned long long t) { return t; }\n} // namespace loguru\n\nLOGURU_ANONYMOUS_NAMESPACE_END\n\n// -----------------------------------------------\n// Logging macros:\n\n// usage:  LOG_STREAM(INFO) << \"Foo \" << std::setprecision(10) << some_value;\n#define VLOG_IF_S(verbosity, cond)                                                                 \\\n\t((verbosity) > loguru::current_verbosity_cutoff() || (cond) == false)                          \\\n\t\t? (void)0                                                                                  \\\n\t\t: loguru::Voidify() & loguru::StreamLogger(verbosity, __FILE__, __LINE__)\n#define LOG_IF_S(verbosity_name, cond) VLOG_IF_S(loguru::Verbosity_ ## verbosity_name, cond)\n#define VLOG_S(verbosity)              VLOG_IF_S(verbosity, true)\n#define LOG_S(verbosity_name)          VLOG_S(loguru::Verbosity_ ## verbosity_name)\n\n// -----------------------------------------------\n// ABORT_S macro. Usage:  ABORT_S() << \"Causo of error: \" << details;\n\n#define ABORT_S() loguru::Voidify() & loguru::AbortLogger(\"ABORT: \", __FILE__, __LINE__)\n\n// -----------------------------------------------\n// CHECK_S macros:\n\n#define CHECK_WITH_INFO_S(cond, info)                                                              \\\n\tLOGURU_PREDICT_TRUE((cond) == true)                                                            \\\n\t\t? (void)0                                                                                  \\\n\t\t: loguru::Voidify() & loguru::AbortLogger(\"CHECK FAILED:  \" info \"  \", __FILE__, __LINE__)\n\n#define CHECK_S(cond) CHECK_WITH_INFO_S(cond, #cond)\n#define CHECK_NOTNULL_S(x) CHECK_WITH_INFO_S((x) != nullptr, #x \" != nullptr\")\n\n#define CHECK_OP_S(function_name, expr1, op, expr2)                                                \\\n\twhile (auto error_string = loguru::function_name(#expr1 \" \" #op \" \" #expr2,                    \\\n\t\t\t\t\t\t\t\t\t\t\t\t\t loguru::referenceable_value(expr1), #op,      \\\n\t\t\t\t\t\t\t\t\t\t\t\t\t loguru::referenceable_value(expr2)))          \\\n\t\tloguru::AbortLogger(error_string->c_str(), __FILE__, __LINE__)\n\n#define CHECK_EQ_S(expr1, expr2) CHECK_OP_S(check_EQ_impl, expr1, ==, expr2)\n#define CHECK_NE_S(expr1, expr2) CHECK_OP_S(check_NE_impl, expr1, !=, expr2)\n#define CHECK_LE_S(expr1, expr2) CHECK_OP_S(check_LE_impl, expr1, <=, expr2)\n#define CHECK_LT_S(expr1, expr2) CHECK_OP_S(check_LT_impl, expr1, < , expr2)\n#define CHECK_GE_S(expr1, expr2) CHECK_OP_S(check_GE_impl, expr1, >=, expr2)\n#define CHECK_GT_S(expr1, expr2) CHECK_OP_S(check_GT_impl, expr1, > , expr2)\n\n#if LOGURU_DEBUG_LOGGING\n\t// Debug logging enabled:\n\t#define DVLOG_IF_S(verbosity, cond)     VLOG_IF_S(verbosity, cond)\n\t#define DLOG_IF_S(verbosity_name, cond) LOG_IF_S(verbosity_name, cond)\n\t#define DVLOG_S(verbosity)              VLOG_S(verbosity)\n\t#define DLOG_S(verbosity_name)          LOG_S(verbosity_name)\n#else\n\t// Debug logging disabled:\n\t#define DVLOG_IF_S(verbosity, cond)                                                     \\\n\t\t(true || (verbosity) > loguru::current_verbosity_cutoff() || (cond) == false)       \\\n\t\t\t? (void)0                                                                       \\\n\t\t\t: loguru::Voidify() & loguru::StreamLogger(verbosity, __FILE__, __LINE__)\n\n\t#define DLOG_IF_S(verbosity_name, cond) DVLOG_IF_S(loguru::Verbosity_ ## verbosity_name, cond)\n\t#define DVLOG_S(verbosity)              DVLOG_IF_S(verbosity, true)\n\t#define DLOG_S(verbosity_name)          DVLOG_S(loguru::Verbosity_ ## verbosity_name)\n#endif\n\n#if LOGURU_DEBUG_CHECKS\n\t// Debug checks enabled:\n\t#define DCHECK_S(cond)                  CHECK_S(cond)\n\t#define DCHECK_NOTNULL_S(x)             CHECK_NOTNULL_S(x)\n\t#define DCHECK_EQ_S(a, b)               CHECK_EQ_S(a, b)\n\t#define DCHECK_NE_S(a, b)               CHECK_NE_S(a, b)\n\t#define DCHECK_LT_S(a, b)               CHECK_LT_S(a, b)\n\t#define DCHECK_LE_S(a, b)               CHECK_LE_S(a, b)\n\t#define DCHECK_GT_S(a, b)               CHECK_GT_S(a, b)\n\t#define DCHECK_GE_S(a, b)               CHECK_GE_S(a, b)\n#else\n// Debug checks disabled:\n\t#define DCHECK_S(cond)                  CHECK_S(true || (cond))\n\t#define DCHECK_NOTNULL_S(x)             CHECK_S(true || (x) != nullptr)\n\t#define DCHECK_EQ_S(a, b)               CHECK_S(true || (a) == (b))\n\t#define DCHECK_NE_S(a, b)               CHECK_S(true || (a) != (b))\n\t#define DCHECK_LT_S(a, b)               CHECK_S(true || (a) <  (b))\n\t#define DCHECK_LE_S(a, b)               CHECK_S(true || (a) <= (b))\n\t#define DCHECK_GT_S(a, b)               CHECK_S(true || (a) >  (b))\n\t#define DCHECK_GE_S(a, b)               CHECK_S(true || (a) >= (b))\n#endif\n\n#if LOGURU_REPLACE_GLOG\n\t#undef LOG\n\t#undef VLOG\n\t#undef LOG_IF\n\t#undef VLOG_IF\n\t#undef CHECK\n\t#undef CHECK_NOTNULL\n\t#undef CHECK_EQ\n\t#undef CHECK_NE\n\t#undef CHECK_LT\n\t#undef CHECK_LE\n\t#undef CHECK_GT\n\t#undef CHECK_GE\n\t#undef DLOG\n\t#undef DVLOG\n\t#undef DLOG_IF\n\t#undef DVLOG_IF\n\t#undef DCHECK\n\t#undef DCHECK_NOTNULL\n\t#undef DCHECK_EQ\n\t#undef DCHECK_NE\n\t#undef DCHECK_LT\n\t#undef DCHECK_LE\n\t#undef DCHECK_GT\n\t#undef DCHECK_GE\n\t#undef VLOG_IS_ON\n\n\t#define LOG            LOG_S\n\t#define VLOG           VLOG_S\n\t#define LOG_IF         LOG_IF_S\n\t#define VLOG_IF        VLOG_IF_S\n\t#define CHECK(cond)    CHECK_S(!!(cond))\n\t#define CHECK_NOTNULL  CHECK_NOTNULL_S\n\t#define CHECK_EQ       CHECK_EQ_S\n\t#define CHECK_NE       CHECK_NE_S\n\t#define CHECK_LT       CHECK_LT_S\n\t#define CHECK_LE       CHECK_LE_S\n\t#define CHECK_GT       CHECK_GT_S\n\t#define CHECK_GE       CHECK_GE_S\n\t#define DLOG           DLOG_S\n\t#define DVLOG          DVLOG_S\n\t#define DLOG_IF        DLOG_IF_S\n\t#define DVLOG_IF       DVLOG_IF_S\n\t#define DCHECK         DCHECK_S\n\t#define DCHECK_NOTNULL DCHECK_NOTNULL_S\n\t#define DCHECK_EQ      DCHECK_EQ_S\n\t#define DCHECK_NE      DCHECK_NE_S\n\t#define DCHECK_LT      DCHECK_LT_S\n\t#define DCHECK_LE      DCHECK_LE_S\n\t#define DCHECK_GT      DCHECK_GT_S\n\t#define DCHECK_GE      DCHECK_GE_S\n\t#define VLOG_IS_ON(verbosity) ((verbosity) <= loguru::current_verbosity_cutoff())\n\n#endif // LOGURU_REPLACE_GLOG\n\n#endif // LOGURU_WITH_STREAMS\n\n#endif // LOGURU_HAS_DECLARED_STREAMS_HEADER\n"
  },
  {
    "path": "src/Shared/openvr.h",
    "content": "#pragma once\n\n// openvr.h\n//========= Copyright Valve Corporation ============//\n// Dynamically generated file. Do not modify this file directly.\n\n#ifndef _OPENVR_API\n#define _OPENVR_API\n\n#include <stdint.h>\n\n\n\n// version.h\n\nnamespace vr\n{\n\tstatic const uint32_t k_nSteamVRVersionMajor = 2;\n\tstatic const uint32_t k_nSteamVRVersionMinor = 15;\n\tstatic const uint32_t k_nSteamVRVersionBuild = 6;\n} // namespace vr\n\n// public_vrtypes.h\n\n#ifndef _INCLUDE_CORE_VRTYPES_PUBLIC_H\n#define _INCLUDE_CORE_VRTYPES_PUBLIC_H\n\nnamespace vr\n{\n#pragma pack( push, 8 )\n\ntypedef uint32_t PropertyTypeTag_t;\n\n// right-handed system\n// +y is up\n// +x is to the right\n// -z is forward\n// Distance unit is  meters\nstruct HmdMatrix34_t\n{\n\tfloat m[3][4];\n};\n\nstruct HmdMatrix33_t\n{\n\tfloat m[3][3];\n};\n\nstruct HmdMatrix44_t\n{\n\tfloat m[4][4];\n};\n\nstruct HmdVector3_t\n{\n\tfloat v[3];\n};\n\nstruct HmdVector4_t\n{\n\tfloat v[4];\n};\n\nstruct HmdVector3d_t\n{\n\tdouble v[3];\n};\n\nstruct HmdVector2_t\n{\n\tfloat v[2];\n};\n\nstruct HmdQuaternion_t\n{\n\tdouble w, x, y, z;\n};\n\nstruct HmdQuaternionf_t\n{\n\tfloat w, x, y, z;\n};\n\nstruct HmdColor_t\n{\n\tfloat r, g, b, a;\n};\n\nstruct HmdQuad_t\n{\n\tHmdVector3_t vCorners[ 4 ];\n};\n\nstruct HmdRect2_t\n{\n\tHmdVector2_t vTopLeft;\n\tHmdVector2_t vBottomRight;\n};\n\n/** Holds the transform for a single bone */\nstruct VRBoneTransform_t\n{\n\tHmdVector4_t position;\n\tHmdQuaternionf_t orientation;\n};\n\nstruct VREyeTrackingData_t\n{\n\tbool bActive;\n\tbool bValid;\n\tbool bTracked;\n\n\tvr::HmdVector3_t vGazeOrigin;  // Ray origin\n\tvr::HmdVector3_t vGazeTarget;  // Gaze target (fixation point)\n};\n\n/** Used to return the post-distortion UVs for each color channel.\n* UVs range from 0 to 1 with 0,0 in the upper left corner of the\n* source render target. The 0,0 to 1,1 range covers a single eye. */\nstruct DistortionCoordinates_t\n{\n\tfloat rfRed[2];\n\tfloat rfGreen[2];\n\tfloat rfBlue[2];\n};\n\nenum EVREye\n{\n\tEye_Left = 0,\n\tEye_Right = 1\n};\n\nenum ETextureType\n{\n\tTextureType_Invalid = -1, // Handle has been invalidated\n\tTextureType_DirectX = 0, // Handle is an ID3D11Texture\n\tTextureType_OpenGL = 1,  // Handle is an OpenGL texture name or an OpenGL render buffer name, depending on submit flags\n\tTextureType_Vulkan = 2, // Handle is a pointer to a VRVulkanTextureData_t structure\n\tTextureType_IOSurface = 3, // Handle is a macOS cross-process-sharable IOSurfaceRef, deprecated in favor of TextureType_Metal on supported platforms\n\tTextureType_DirectX12 = 4, // Handle is a pointer to a D3D12TextureData_t structure\n\tTextureType_DXGISharedHandle = 5, // Handle is a HANDLE DXGI share handle, only supported for Overlay render targets.\n\t\t\t\t\t\t\t\t\t  // this texture is used directly by our renderer, so only perform atomic (copyresource or resolve) on it\n\tTextureType_Metal = 6,\t// Handle is a MTLTexture conforming to the MTLSharedTexture protocol. Textures submitted to IVRCompositor::Submit which\n\t\t\t\t\t\t\t// are of type MTLTextureType2DArray assume layer 0 is the left eye texture (vr::EVREye::Eye_left), layer 1 is the right\n\t\t\t\t\t\t\t// eye texture (vr::EVREye::Eye_Right)\n\n\tTextureType_Reserved = 7,\n\tTextureType_SharedTextureHandle = 8, // A pointer to a vr::SharedTextureHandle_t that was imported via, eg. ImportDmabuf.\n};\n\nenum EColorSpace\n{\n\tColorSpace_Auto = 0,\t// Assumes 'gamma' for 8-bit per component formats, otherwise 'linear'.  This mirrors the DXGI formats which have _SRGB variants.\n\tColorSpace_Gamma = 1,\t// Texture data can be displayed directly on the display without any conversion (a.k.a. display native format).\n\tColorSpace_Linear = 2,\t// Same as gamma but has been converted to a linear representation using DXGI's sRGB conversion algorithm.\n};\n\nstruct Texture_t\n{\n\tvoid* handle; // See ETextureType definition above\n\tETextureType eType;\n\tEColorSpace eColorSpace;\n};\n\n/** Allows the application to control what part of the provided texture will be used in the\n* frame buffer. */\nstruct VRTextureBounds_t\n{\n\tfloat uMin, vMin;\n\tfloat uMax, vMax;\n};\n\n/** Allows specifying pose used to render provided scene texture (if different from value returned by WaitGetPoses). */\nstruct VRTextureWithPose_t : public Texture_t\n{\n\tHmdMatrix34_t mDeviceToAbsoluteTracking; // Actual pose used to render scene textures.\n};\n\nstruct VRTextureDepthInfo_t\n{\n\tvoid* handle; // See ETextureType definition above\n\tHmdMatrix44_t mProjection;\n\tHmdVector2_t vRange; // 0..1\n};\n\nstruct VRTextureWithDepth_t : public Texture_t\n{\n\tVRTextureDepthInfo_t depth;\n};\n\nstruct VRTextureWithPoseAndDepth_t : public VRTextureWithPose_t\n{\n\tVRTextureDepthInfo_t depth;\n};\n\nstruct VRTextureMotionInfo_t\n{\n\tvoid *handle; // See ETextureType definition above\n\tHmdMatrix44_t mDeltaPose; // Incremental application-applied transform, if any, since the previous frame that affects the view.\n};\n\nstruct VRTextureWithMotion_t : VRTextureWithPoseAndDepth_t\n{\n\tVRTextureMotionInfo_t motion;\n};\n\n// 64-bit types that are part of public structures\n// that are replicated in shared memory.\n#if defined(__linux__) || defined(__APPLE__)\ntypedef uint64_t vrshared_uint64_t __attribute__ ((aligned(8)));\ntypedef double vrshared_double __attribute__ ((aligned(8)));\n#else\ntypedef uint64_t vrshared_uint64_t;\ntypedef double vrshared_double;\n#endif\n\nstatic const uint32_t MaxDmabufPlaneCount = 4;\n\nstruct DmabufPlane_t\n{\n\tuint32_t unOffset;\n\tuint32_t unStride;\n\tint32_t nFd; // This is not consumed, it is dup'ed.\n};\n\nstruct DmabufAttributes_t\n{\n\tvoid *pNext; // MUST be NULL. Unused right now, but could be used to extend this structure in the future.\n\n\tuint32_t unWidth;\n\tuint32_t unHeight;\n\tuint32_t unDepth;\n\tuint32_t unMipLevels;\n\tuint32_t unArrayLayers;\n\tuint32_t unSampleCount;\n\tuint32_t unFormat;   // DRM_FORMAT_\n\tuint64_t ulModifier; // DRM_FORMAT_MOD_\n\n\tuint32_t unPlaneCount;\n\tDmabufPlane_t plane[MaxDmabufPlaneCount];\n};\n\n#pragma pack( pop )\n\n} // namespace vr\n\n#endif\n\n// vrtypes.h\n\n#ifndef _INCLUDE_VRTYPES_H\n#define _INCLUDE_VRTYPES_H\n\n// Forward declarations to avoid requiring vulkan.h\nstruct VkDevice_T;\nstruct VkPhysicalDevice_T;\nstruct VkInstance_T;\nstruct VkQueue_T;\n\n// Forward declarations to avoid requiring d3d12.h\nstruct ID3D12Resource;\nstruct ID3D12CommandQueue;\n\nnamespace vr\n{\n#pragma pack( push, 8 )\n\n/** A handle for a spatial anchor.  This handle is only valid during the session it was created in.\n* Anchors that live beyond one session should be saved by their string descriptors. */\ntypedef uint32_t SpatialAnchorHandle_t;\n\ntypedef void* glSharedTextureHandle_t;\ntypedef int32_t glInt_t;\ntypedef uint32_t glUInt_t;\n\n\n// Handle to a shared texture (HANDLE on Windows obtained using OpenSharedResource).\ntypedef uint64_t SharedTextureHandle_t;\n#define INVALID_SHARED_TEXTURE_HANDLE\t((vr::SharedTextureHandle_t)0)\n\nenum ETrackingResult\n{\n\tTrackingResult_Uninitialized\t\t\t= 1,\n\n\tTrackingResult_Calibrating_InProgress\t= 100,\n\tTrackingResult_Calibrating_OutOfRange\t= 101,\n\n\tTrackingResult_Running_OK\t\t\t\t= 200,\n\tTrackingResult_Running_OutOfRange\t\t= 201,\n\n\tTrackingResult_Fallback_RotationOnly\t= 300,\n};\n\ntypedef uint32_t DriverId_t;\nstatic const uint32_t k_nDriverNone = 0xFFFFFFFF;\n\nstatic const uint32_t k_unMaxDriverDebugResponseSize = 32768;\n\n/** Used to pass device IDs to API calls */\ntypedef uint32_t TrackedDeviceIndex_t;\nstatic const uint32_t k_unTrackedDeviceIndex_Hmd = 0;\nstatic const uint32_t k_unMaxTrackedDeviceCount = 64;\nstatic const uint32_t k_unTrackedDeviceIndexOther = 0xFFFFFFFE;\nstatic const uint32_t k_unTrackedDeviceIndexInvalid = 0xFFFFFFFF;\n\n/** Describes what kind of object is being tracked at a given ID */\nenum ETrackedDeviceClass\n{\n\tTrackedDeviceClass_Invalid = 0,\t\t\t\t// the ID was not valid.\n\tTrackedDeviceClass_HMD = 1,\t\t\t\t\t// Head-Mounted Displays\n\tTrackedDeviceClass_Controller = 2,\t\t\t// Tracked controllers\n\tTrackedDeviceClass_GenericTracker = 3,\t\t// Generic trackers, similar to controllers\n\tTrackedDeviceClass_TrackingReference = 4,\t// Camera and base stations that serve as tracking reference points\n\tTrackedDeviceClass_DisplayRedirect = 5,\t\t// Accessories that aren't necessarily tracked themselves, but may redirect video output from other tracked devices\n\n\tTrackedDeviceClass_Max\n};\n\n\n/** Describes what specific role associated with a tracked device */\nenum ETrackedControllerRole\n{\n\tTrackedControllerRole_Invalid = 0,\t\t\t\t\t// Invalid value for controller type\n\tTrackedControllerRole_LeftHand = 1,\t\t\t\t\t// Tracked device associated with the left hand\n\tTrackedControllerRole_RightHand = 2,\t\t\t\t// Tracked device associated with the right hand\n\tTrackedControllerRole_OptOut = 3,\t\t\t\t\t// Tracked device is opting out of left/right hand selection\n\tTrackedControllerRole_Treadmill = 4,\t\t\t\t// Tracked device is a treadmill or other locomotion device\n\tTrackedControllerRole_Stylus = 5,\t\t\t\t\t// Tracked device is a stylus\n\tTrackedControllerRole_Max = 5\n};\n\n\n/** Returns true if the tracked controller role is allowed to be a hand */\ninline bool IsRoleAllowedAsHand( ETrackedControllerRole eRole )\n{\n\tswitch ( eRole )\n\t{\n\tcase TrackedControllerRole_Invalid:\n\tcase TrackedControllerRole_LeftHand:\n\tcase TrackedControllerRole_RightHand:\n\t\treturn true;\n\tdefault:\n\t\treturn false;\n\t}\n}\n\n\n/** describes a single pose for a tracked object */\nstruct TrackedDevicePose_t\n{\n\tHmdMatrix34_t mDeviceToAbsoluteTracking;\n\tHmdVector3_t vVelocity;\t\t\t\t// velocity in tracker space in m/s\n\tHmdVector3_t vAngularVelocity;\t\t// angular velocity in radians/s (?)\n\tETrackingResult eTrackingResult;\n\tbool bPoseIsValid;\n\n\t// This indicates that there is a device connected for this spot in the pose array.\n\t// It could go from true to false if the user unplugs the device.\n\tbool bDeviceIsConnected;\n};\n\n/** Identifies which style of tracking origin the application wants to use\n* for the poses it is requesting */\nenum ETrackingUniverseOrigin\n{\n\tTrackingUniverseSeated = 0,\t\t// Poses are provided relative to the seated zero pose\n\tTrackingUniverseStanding = 1,\t// Poses are provided relative to the safe bounds configured by the user\n\tTrackingUniverseRawAndUncalibrated = 2,\t// Poses are provided in the coordinate system defined by the driver.  It has Y up and is unified for devices of the same driver. You usually don't want this one.\n};\n\nenum EAdditionalRadioFeatures\n{\n\tAdditionalRadioFeatures_None           = 0x00000000,\n\tAdditionalRadioFeatures_HTCLinkBox     = 0x00000001,\n\tAdditionalRadioFeatures_InternalDongle = 0x00000002,\n\tAdditionalRadioFeatures_ExternalDongle = 0x00000004,\n};\n\ntypedef uint64_t WebConsoleHandle_t;\n#define INVALID_WEB_CONSOLE_HANDLE\t((vr::WebConsoleHandle_t)0)\n\n// Refers to a single container of properties\ntypedef uint64_t PropertyContainerHandle_t;\ntypedef uint32_t PropertyTypeTag_t;\n\nstatic const PropertyContainerHandle_t k_ulInvalidPropertyContainer = 0;\nstatic const PropertyTypeTag_t k_unInvalidPropertyTag = 0;\n\ntypedef PropertyContainerHandle_t DriverHandle_t;\nstatic const PropertyContainerHandle_t k_ulInvalidDriverHandle = 0;\n\n// Use these tags to set/get common types as struct properties\nstatic const PropertyTypeTag_t k_unFloatPropertyTag = 1;\nstatic const PropertyTypeTag_t k_unInt32PropertyTag = 2;\nstatic const PropertyTypeTag_t k_unUint64PropertyTag = 3;\nstatic const PropertyTypeTag_t k_unBoolPropertyTag = 4;\nstatic const PropertyTypeTag_t k_unStringPropertyTag = 5;\nstatic const PropertyTypeTag_t k_unErrorPropertyTag = 6;\nstatic const PropertyTypeTag_t k_unDoublePropertyTag = 7;\n\nstatic const PropertyTypeTag_t k_unHmdMatrix34PropertyTag = 20;\nstatic const PropertyTypeTag_t k_unHmdMatrix44PropertyTag = 21;\nstatic const PropertyTypeTag_t k_unHmdVector3PropertyTag = 22;\nstatic const PropertyTypeTag_t k_unHmdVector4PropertyTag = 23;\nstatic const PropertyTypeTag_t k_unHmdVector2PropertyTag = 24;\nstatic const PropertyTypeTag_t k_unHmdQuadPropertyTag = 25;\n\nstatic const PropertyTypeTag_t k_unHiddenAreaPropertyTag = 30;\nstatic const PropertyTypeTag_t k_unPathHandleInfoTag = 31;\nstatic const PropertyTypeTag_t k_unActionPropertyTag = 32;\nstatic const PropertyTypeTag_t k_unInputValuePropertyTag = 33;\nstatic const PropertyTypeTag_t k_unWildcardPropertyTag = 34;\nstatic const PropertyTypeTag_t k_unHapticVibrationPropertyTag = 35;\nstatic const PropertyTypeTag_t k_unSkeletonPropertyTag = 36;\n\nstatic const PropertyTypeTag_t k_unSpatialAnchorPosePropertyTag = 40;\nstatic const PropertyTypeTag_t k_unJsonPropertyTag = 41;\nstatic const PropertyTypeTag_t k_unActiveActionSetPropertyTag = 42;\n\nstatic const PropertyTypeTag_t k_unOpenVRInternalReserved_Start = 1000;\nstatic const PropertyTypeTag_t k_unOpenVRInternalReserved_End = 10000;\n\n\n/** Each entry in this enum represents a property that can be retrieved about a\n* tracked device. Many fields are only valid for one ETrackedDeviceClass. */\nenum ETrackedDeviceProperty\n{\n\tProp_Invalid\t\t\t\t\t\t\t\t= 0,\n\n\t// general properties that apply to all device classes\n\tProp_TrackingSystemName_String\t\t\t\t= 1000,\n\tProp_ModelNumber_String\t\t\t\t\t\t= 1001,\n\tProp_SerialNumber_String\t\t\t\t\t= 1002,\n\tProp_RenderModelName_String\t\t\t\t\t= 1003,\n\tProp_WillDriftInYaw_Bool\t\t\t\t\t= 1004,\n\tProp_ManufacturerName_String\t\t\t\t= 1005,\n\tProp_TrackingFirmwareVersion_String\t\t\t= 1006,\n\tProp_HardwareRevision_String\t\t\t\t= 1007,\n\tProp_AllWirelessDongleDescriptions_String\t= 1008,\n\tProp_ConnectedWirelessDongle_String\t\t\t= 1009,\n\tProp_DeviceIsWireless_Bool\t\t\t\t\t= 1010,\n\tProp_DeviceIsCharging_Bool\t\t\t\t\t= 1011,\n\tProp_DeviceBatteryPercentage_Float\t\t\t= 1012, // 0 is empty, 1 is full\n\tProp_StatusDisplayTransform_Matrix34\t\t= 1013,\n\tProp_Firmware_UpdateAvailable_Bool\t\t\t= 1014,\n\tProp_Firmware_ManualUpdate_Bool\t\t\t\t= 1015,\n\tProp_Firmware_ManualUpdateURL_String\t\t= 1016,\n\tProp_HardwareRevision_Uint64\t\t\t\t= 1017,\n\tProp_FirmwareVersion_Uint64\t\t\t\t\t= 1018,\n\tProp_FPGAVersion_Uint64\t\t\t\t\t\t= 1019,\n\tProp_VRCVersion_Uint64\t\t\t\t\t\t= 1020,\n\tProp_RadioVersion_Uint64\t\t\t\t\t= 1021,\n\tProp_DongleVersion_Uint64\t\t\t\t\t= 1022,\n\tProp_BlockServerShutdown_Bool\t\t\t\t= 1023,\n\tProp_CanUnifyCoordinateSystemWithHmd_Bool\t= 1024,\n\tProp_ContainsProximitySensor_Bool\t\t\t= 1025,\n\tProp_DeviceProvidesBatteryStatus_Bool\t\t= 1026,\n\tProp_DeviceCanPowerOff_Bool\t\t\t\t\t= 1027,\n\tProp_Firmware_ProgrammingTarget_String\t\t= 1028,\n\tProp_DeviceClass_Int32\t\t\t\t\t\t= 1029,\n\tProp_HasCamera_Bool\t\t\t\t\t\t\t= 1030,\n\tProp_DriverVersion_String                   = 1031,\n\tProp_Firmware_ForceUpdateRequired_Bool      = 1032,\n\tProp_ViveSystemButtonFixRequired_Bool\t\t= 1033,\n\tProp_ParentDriver_Uint64\t\t\t\t\t= 1034,\n\tProp_ResourceRoot_String\t\t\t\t\t= 1035,\n\tProp_RegisteredDeviceType_String\t\t\t= 1036,\n\tProp_InputProfilePath_String\t\t\t\t= 1037, // input profile to use for this device in the input system. Will default to tracking system name if this isn't provided\n\tProp_NeverTracked_Bool\t\t\t\t\t\t= 1038, // Used for devices that will never have a valid pose by design\n\tProp_NumCameras_Int32\t\t\t\t\t\t= 1039,\n\tProp_CameraFrameLayout_Int32\t\t\t\t= 1040, // EVRTrackedCameraFrameLayout value\n\tProp_CameraStreamFormat_Int32\t\t\t\t= 1041, // ECameraVideoStreamFormat value\n\tProp_AdditionalDeviceSettingsPath_String\t= 1042, // driver-relative path to additional device and global configuration settings\n\tProp_Identifiable_Bool\t\t\t\t\t\t= 1043, // Whether device supports being identified from vrmonitor (e.g. blink LED, vibrate haptics, etc)\n\tProp_BootloaderVersion_Uint64\t\t\t    = 1044,\n\tProp_AdditionalSystemReportData_String\t\t= 1045, // additional string to include in system reports about a tracked device\n\tProp_CompositeFirmwareVersion_String        = 1046, // additional FW components from a device that gets propagated into reports\n\tProp_Firmware_RemindUpdate_Bool             = 1047,\n\tProp_PeripheralApplicationVersion_Uint64\t= 1048,\n\tProp_ManufacturerSerialNumber_String\t\t= 1049,\n\tProp_ComputedSerialNumber_String\t\t\t= 1050,\n\tProp_EstimatedDeviceFirstUseTime_Int32\t\t= 1051,\n\tProp_DevicePowerUsage_Float\t\t\t\t\t= 1052,\n\tProp_IgnoreMotionForStandby_Bool\t\t\t= 1053,\n\tProp_ActualTrackingSystemName_String\t\t= 1054, // the literal local driver name in case someone is playing games with prop 1000\n\tProp_AllowCameraToggle_Bool\t\t\t\t\t= 1055, // Shows the Enable/Disable camera option. Hide this for certain headsets if they have the camera tracking (since it's always on)\n\tProp_AllowLightSourceFrequency_Bool\t\t\t= 1056, // Shows the Anti-Flicker option in camera settings.\n\tProp_SteamRemoteClientID_Uint64\t\t\t\t= 1057, // For vrlink\n\tProp_Reserved_1058\t\t\t\t\t\t\t= 1058,\n\tProp_Reserved_1059\t\t\t\t\t\t\t= 1059,\n\tProp_Reserved_1060\t\t\t\t\t\t\t= 1060,\n\n\t// Properties that are unique to TrackedDeviceClass_HMD\n\tProp_ReportsTimeSinceVSync_Bool\t\t\t\t= 2000,\n\tProp_SecondsFromVsyncToPhotons_Float\t\t= 2001,\n\tProp_DisplayFrequency_Float\t\t\t\t\t= 2002,\n\tProp_UserIpdMeters_Float\t\t\t\t\t= 2003,\n\tProp_CurrentUniverseId_Uint64\t\t\t\t= 2004,\n\tProp_PreviousUniverseId_Uint64_deprecated\t= Prop_Invalid,\n\tProp_DisplayFirmwareVersion_Uint64\t\t\t= 2006,\n\tProp_IsOnDesktop_Bool\t\t\t\t\t\t= 2007,\n\tProp_DisplayMCType_Int32\t\t\t\t\t= 2008,\n\tProp_DisplayMCOffset_Float\t\t\t\t\t= 2009,\n\tProp_DisplayMCScale_Float\t\t\t\t\t= 2010,\n\tProp_EdidVendorID_Int32\t\t\t\t\t\t= 2011,\n\tProp_DisplayMCImageLeft_String              = 2012,\n\tProp_DisplayMCImageRight_String             = 2013,\n\tProp_DisplayGCBlackClamp_Float\t\t\t\t= 2014,\n\tProp_EdidProductID_Int32\t\t\t\t\t= 2015,\n\tProp_CameraToHeadTransform_Matrix34\t\t\t= 2016,\n\tProp_DisplayGCType_Int32\t\t\t\t\t= 2017,\n\tProp_DisplayGCOffset_Float\t\t\t\t\t= 2018,\n\tProp_DisplayGCScale_Float\t\t\t\t\t= 2019,\n\tProp_DisplayGCPrescale_Float\t\t\t\t= 2020,\n\tProp_DisplayGCImage_String\t\t\t\t\t= 2021,\n\tProp_LensCenterLeftU_Float\t\t\t\t\t= 2022,\n\tProp_LensCenterLeftV_Float\t\t\t\t\t= 2023,\n\tProp_LensCenterRightU_Float\t\t\t\t\t= 2024,\n\tProp_LensCenterRightV_Float\t\t\t\t\t= 2025,\n\tProp_UserHeadToEyeDepthMeters_Float\t\t\t= 2026,\n\tProp_CameraFirmwareVersion_Uint64\t\t\t= 2027,\n\tProp_CameraFirmwareDescription_String\t\t= 2028,\n\tProp_DisplayFPGAVersion_Uint64\t\t\t\t= 2029,\n\tProp_DisplayBootloaderVersion_Uint64\t\t= 2030,\n\tProp_DisplayHardwareVersion_Uint64\t\t\t= 2031,\n\tProp_AudioFirmwareVersion_Uint64\t\t\t= 2032,\n\tProp_CameraCompatibilityMode_Int32\t\t\t= 2033,\n\tProp_ScreenshotHorizontalFieldOfViewDegrees_Float = 2034,\n\tProp_ScreenshotVerticalFieldOfViewDegrees_Float = 2035,\n\tProp_DisplaySuppressed_Bool\t\t\t\t\t= 2036,\n\tProp_DisplayAllowNightMode_Bool\t\t\t\t= 2037,\n\tProp_DisplayMCImageWidth_Int32\t\t\t\t= 2038,\n\tProp_DisplayMCImageHeight_Int32\t\t\t\t= 2039,\n\tProp_DisplayMCImageNumChannels_Int32\t\t= 2040,\n\tProp_DisplayMCImageData_Binary\t\t\t\t= 2041,\n\tProp_SecondsFromPhotonsToVblank_Float\t\t= 2042,\n\tProp_DriverDirectModeSendsVsyncEvents_Bool\t= 2043,\n\tProp_DisplayDebugMode_Bool\t\t\t\t\t= 2044,\n\tProp_GraphicsAdapterLuid_Uint64\t\t\t\t= 2045,\n\tProp_DriverProvidedChaperonePath_String\t\t= 2048,\n\tProp_ExpectedTrackingReferenceCount_Int32\t= 2049, // expected number of sensors or basestations to reserve UI space for\n\tProp_ExpectedControllerCount_Int32\t\t\t= 2050, // expected number of tracked controllers to reserve UI space for\n\tProp_NamedIconPathControllerLeftDeviceOff_String\t= 2051, // placeholder icon for \"left\" controller if not yet detected/loaded\n\tProp_NamedIconPathControllerRightDeviceOff_String\t= 2052, // placeholder icon for \"right\" controller if not yet detected/loaded\n\tProp_NamedIconPathTrackingReferenceDeviceOff_String\t= 2053, // placeholder icon for sensor/base if not yet detected/loaded\n\tProp_DoNotApplyPrediction_Bool\t\t\t\t= 2054, // currently no effect. was used to disable HMD pose prediction on MR, which is now done by MR driver setting velocity=0\n\tProp_CameraToHeadTransforms_Matrix34_Array\t= 2055,\n\tProp_DistortionMeshResolution_Int32\t\t\t= 2056, // custom resolution of compositor calls to IVRSystem::ComputeDistortion\n\tProp_DriverIsDrawingControllers_Bool\t\t= 2057,\n\tProp_DriverRequestsApplicationPause_Bool\t= 2058,\n\tProp_DriverRequestsReducedRendering_Bool\t= 2059,\n\tProp_MinimumIpdStepMeters_Float\t\t\t\t= 2060,\n\tProp_AudioBridgeFirmwareVersion_Uint64\t\t= 2061,\n\tProp_ImageBridgeFirmwareVersion_Uint64\t\t= 2062,\n\tProp_ImuToHeadTransform_Matrix34\t\t\t= 2063,\n\tProp_ImuFactoryGyroBias_Vector3\t\t\t\t= 2064,\n\tProp_ImuFactoryGyroScale_Vector3\t\t\t= 2065,\n\tProp_ImuFactoryAccelerometerBias_Vector3\t= 2066,\n\tProp_ImuFactoryAccelerometerScale_Vector3\t= 2067,\n\t// reserved 2068\n\tProp_ConfigurationIncludesLighthouse20Features_Bool = 2069,\n\tProp_AdditionalRadioFeatures_Uint64         = 2070,\n\tProp_CameraWhiteBalance_Vector4_Array\t\t= 2071, // Prop_NumCameras_Int32-sized array of float[4] RGBG white balance calibration data (max size is vr::k_unMaxCameras)\n\tProp_CameraDistortionFunction_Int32_Array\t= 2072, // Prop_NumCameras_Int32-sized array of vr::EVRDistortionFunctionType values (max size is vr::k_unMaxCameras)\n\tProp_CameraDistortionCoefficients_Float_Array = 2073, // Prop_NumCameras_Int32-sized array of double[vr::k_unMaxDistortionFunctionParameters] (max size is vr::k_unMaxCameras)\n\tProp_ExpectedControllerType_String\t\t\t= 2074,\n\tProp_HmdTrackingStyle_Int32\t\t\t\t\t= 2075, // one of EHmdTrackingStyle\n\tProp_DriverProvidedChaperoneVisibility_Bool = 2076,\n\tProp_HmdColumnCorrectionSettingPrefix_String = 2077,\n\tProp_CameraSupportsCompatibilityModes_Bool\t= 2078,\n\tProp_SupportsRoomViewDepthProjection_Bool\t= 2079,\n\tProp_DisplayAvailableFrameRates_Float_Array = 2080, // populated by compositor from actual EDID list when available from GPU driver\n\tProp_DisplaySupportsMultipleFramerates_Bool = 2081, // if this is true but Prop_DisplayAvailableFrameRates_Float_Array is empty, explain to user\n\tProp_DisplayColorMultLeft_Vector3\t\t\t= 2082,\n\tProp_DisplayColorMultRight_Vector3\t\t\t= 2083,\n\tProp_DisplaySupportsRuntimeFramerateChange_Bool = 2084,\n\tProp_DisplaySupportsAnalogGain_Bool \t\t= 2085,\n\tProp_DisplayMinAnalogGain_Float \t\t\t= 2086,\n\tProp_DisplayMaxAnalogGain_Float \t\t\t= 2087,\n    Prop_CameraExposureTime_Float               = 2088,\n    Prop_CameraGlobalGain_Float                 = 2089,\n\t// Prop_DashboardLayoutPathName_String \t\t= 2090, // DELETED\n\tProp_DashboardScale_Float \t\t\t\t\t= 2091,\n\t// Prop_PeerButtonInfo_String\t\t\t\t\t= 2092, // DELETED\n\tProp_Hmd_SupportsHDR10_Bool\t\t\t\t\t= 2093,\n\tProp_Hmd_EnableParallelRenderCameras_Bool\t= 2094,\n\tProp_DriverProvidedChaperoneJson_String\t\t= 2095, // higher priority than Prop_DriverProvidedChaperonePath_String\n\tProp_ForceSystemLayerUseAppPoses_Bool\t\t= 2096,\n\tProp_DashboardLinkSupport_Int32\t\t\t\t= 2097,\n\tProp_DisplayMinUIAnalogGain_Float \t\t\t= 2098,\n\n\tProp_IpdUIRangeMinMeters_Float \t\t\t\t= 2100,\n\tProp_IpdUIRangeMaxMeters_Float \t\t\t\t= 2101,\n\tProp_Hmd_SupportsHDCP14LegacyCompat_Bool\t= 2102,\n\tProp_Hmd_SupportsMicMonitoring_Bool \t\t= 2103,\n\tProp_Hmd_SupportsDisplayPortTrainingMode_Bool\t= 2104,\n\tProp_Hmd_SupportsRoomViewDirect_Bool \t\t= 2105,\n\tProp_Hmd_SupportsAppThrottling_Bool\t\t\t= 2106,\n\tProp_Hmd_SupportsGpuBusMonitoring_Bool\t\t= 2107,\n\tProp_DriverDisplaysIPDChanges_Bool\t\t\t= 2108,\n\t// Prop_Driver_RecenterSupport_Int32\t\t\t= 2109, // DELETED\n\tProp_Reserved_2110\t\t\t\t\t\t\t= 2110,\n\tProp_Reserved_2111\t\t\t\t\t\t\t= 2111,\n\tProp_Reserved_2112\t\t\t\t\t\t\t= 2112,\n\n\tProp_Hmd_MaxDistortedTextureWidth_Int32\t\t= 2113,\n\tProp_Hmd_MaxDistortedTextureHeight_Int32\t= 2114,\n\tProp_Hmd_AllowSupersampleFiltering_Bool\t\t= 2115,\n\n\tProp_Hmd_AllowsClientToControlTextureIndex  = 2116,\n\tProp_Reserved_2117\t\t\t\t\t\t\t= 2117,\n\n\t// Driver requested mura correction properties\n\tProp_DriverRequestedMuraCorrectionMode_Int32\t\t= 2200,\n\tProp_DriverRequestedMuraFeather_InnerLeft_Int32\t\t= 2201,\n\tProp_DriverRequestedMuraFeather_InnerRight_Int32\t= 2202,\n\tProp_DriverRequestedMuraFeather_InnerTop_Int32\t\t= 2203,\n\tProp_DriverRequestedMuraFeather_InnerBottom_Int32\t= 2204,\n\tProp_DriverRequestedMuraFeather_OuterLeft_Int32\t\t= 2205,\n\tProp_DriverRequestedMuraFeather_OuterRight_Int32\t= 2206,\n\tProp_DriverRequestedMuraFeather_OuterTop_Int32\t\t= 2207,\n\tProp_DriverRequestedMuraFeather_OuterBottom_Int32\t= 2208,\n\n\tProp_Audio_DefaultPlaybackDeviceId_String\t\t\t\t= 2300,\n\tProp_Audio_DefaultRecordingDeviceId_String\t\t\t\t= 2301,\n\tProp_Audio_DefaultPlaybackDeviceVolume_Float\t\t\t= 2302,\n\tProp_Audio_SupportsDualSpeakerAndJackOutput_Bool\t\t= 2303,\n\tProp_Audio_DriverManagesPlaybackVolumeControl_Bool\t\t= 2304,\n\tProp_Audio_DriverPlaybackVolume_Float\t\t\t\t\t= 2305,\n\tProp_Audio_DriverPlaybackMute_Bool\t\t\t\t\t\t= 2306,\n\tProp_Audio_DriverManagesRecordingVolumeControl_Bool\t\t= 2307,\n\tProp_Audio_DriverRecordingVolume_Float\t\t\t\t\t= 2308,\n\tProp_Audio_DriverRecordingMute_Bool\t\t\t\t\t\t= 2309,\n\n\t// Pipewire Audio Stuff\n\tProp_Audio_PipewirePlaybackNode_Int32\t\t\t\t\t= 2400,\n\tProp_Audio_PipewireRecordingNode_Int32\t\t\t\t\t= 2401,\n\n\t// Properties that are unique to TrackedDeviceClass_Controller\n\tProp_AttachedDeviceId_String\t\t\t\t= 3000,\n\tProp_SupportedButtons_Uint64\t\t\t\t= 3001,\n\tProp_Axis0Type_Int32\t\t\t\t\t\t= 3002, // Return value is of type EVRControllerAxisType\n\tProp_Axis1Type_Int32\t\t\t\t\t\t= 3003, // Return value is of type EVRControllerAxisType\n\tProp_Axis2Type_Int32\t\t\t\t\t\t= 3004, // Return value is of type EVRControllerAxisType\n\tProp_Axis3Type_Int32\t\t\t\t\t\t= 3005, // Return value is of type EVRControllerAxisType\n\tProp_Axis4Type_Int32\t\t\t\t\t\t= 3006, // Return value is of type EVRControllerAxisType\n\tProp_ControllerRoleHint_Int32\t\t\t\t= 3007, // Return value is of type ETrackedControllerRole\n\n\t// Properties that are unique to TrackedDeviceClass_TrackingReference\n\tProp_FieldOfViewLeftDegrees_Float\t\t\t= 4000,\n\tProp_FieldOfViewRightDegrees_Float\t\t\t= 4001,\n\tProp_FieldOfViewTopDegrees_Float\t\t\t= 4002,\n\tProp_FieldOfViewBottomDegrees_Float\t\t\t= 4003,\n\tProp_TrackingRangeMinimumMeters_Float\t\t= 4004,\n\tProp_TrackingRangeMaximumMeters_Float\t\t= 4005,\n\tProp_ModeLabel_String\t\t\t\t\t\t= 4006,\n\tProp_CanWirelessIdentify_Bool               = 4007, // volatile, based on radio presence and fw discovery\n\tProp_Nonce_Int32                            = 4008,\n\n\t// Properties that are used for user interface like icons names\n\tProp_IconPathName_String\t\t\t\t\t\t= 5000, // DEPRECATED. Value not referenced. Now expected to be part of icon path properties.\n\tProp_NamedIconPathDeviceOff_String\t\t\t\t= 5001, // {driver}/icons/icon_filename - PNG for static icon, or GIF for animation, 50x32 for headsets and 32x32 for others\n\tProp_NamedIconPathDeviceSearching_String\t\t= 5002, // {driver}/icons/icon_filename - PNG for static icon, or GIF for animation, 50x32 for headsets and 32x32 for others\n\tProp_NamedIconPathDeviceSearchingAlert_String\t= 5003, // {driver}/icons/icon_filename - PNG for static icon, or GIF for animation, 50x32 for headsets and 32x32 for others\n\tProp_NamedIconPathDeviceReady_String\t\t\t= 5004, // {driver}/icons/icon_filename - PNG for static icon, or GIF for animation, 50x32 for headsets and 32x32 for others\n\tProp_NamedIconPathDeviceReadyAlert_String\t\t= 5005, // {driver}/icons/icon_filename - PNG for static icon, or GIF for animation, 50x32 for headsets and 32x32 for others\n\tProp_NamedIconPathDeviceNotReady_String\t\t\t= 5006, // {driver}/icons/icon_filename - PNG for static icon, or GIF for animation, 50x32 for headsets and 32x32 for others\n\tProp_NamedIconPathDeviceStandby_String\t\t\t= 5007, // {driver}/icons/icon_filename - PNG for static icon, or GIF for animation, 50x32 for headsets and 32x32 for others\n\tProp_NamedIconPathDeviceAlertLow_String\t\t\t= 5008, // {driver}/icons/icon_filename - PNG for static icon, or GIF for animation, 50x32 for headsets and 32x32 for others\n\tProp_NamedIconPathDeviceStandbyAlert_String\t\t= 5009, // {driver}/icons/icon_filename - PNG for static icon, or GIF for animation, 50x32 for headsets and 32x32 for others\n\n\t// Properties that are used by helpers, but are opaque to applications\n\tProp_DisplayHiddenArea_Binary_Start\t\t\t\t= 5100,\n\tProp_DisplayHiddenArea_Binary_End\t\t\t\t= 5150,\n\tProp_ParentContainer\t\t\t\t\t\t\t= 5151,\n\tProp_OverrideContainer_Uint64\t\t\t\t\t= 5152,\n\n\t// Properties that are unique to drivers\n\tProp_UserConfigPath_String\t\t\t\t\t= 6000,\n\tProp_InstallPath_String\t\t\t\t\t\t= 6001,\n\tProp_HasDisplayComponent_Bool\t\t\t\t= 6002,\n\tProp_HasControllerComponent_Bool\t\t\t= 6003,\n\tProp_HasCameraComponent_Bool\t\t\t\t= 6004,\n\tProp_HasDriverDirectModeComponent_Bool\t\t= 6005,\n\tProp_HasVirtualDisplayComponent_Bool\t\t= 6006,\n\tProp_HasSpatialAnchorsSupport_Bool\t\t\t= 6007,\n\tProp_SupportsXrTextureSets_Bool\t\t\t\t= 6008,\n\tProp_SupportsXrEyeGazeInteraction_Bool\t\t= 6009,\n\tProp_DeviceHasNoIMU_Bool\t\t\t\t\t= 6010,\n\tProp_UseAdvancedPrediction_Bool\t\t\t\t= 6011,\n\n\t// Properties that are set internally based on other information provided by drivers\n\tProp_ControllerType_String\t\t\t\t\t= 7000,\n\t//Prop_LegacyInputProfile_String\t\t\t\t= 7001, // This is no longer used. See \"legacy_binding\" in the input profile instead.\n\tProp_ControllerHandSelectionPriority_Int32\t= 7002, // Allows hand assignments to prefer some controllers over others. High numbers are selected over low numbers\n\n\t// Vendors are free to expose private debug data in this reserved region\n\tProp_VendorSpecific_Reserved_Start\t\t\t= 10000,\n\tProp_VendorSpecific_Reserved_End\t\t\t= 10999,\n\n\t// Addl SteamVR Reserved Space\n\tProp_Reserved_11000\t\t\t\t\t\t\t= 11000,\n\tProp_Reserved_11001\t\t\t\t\t\t\t= 11001,\n\tProp_Reserved_11002\t\t\t\t\t\t\t= 11002,\n\tProp_Reserved_11003\t\t\t\t\t\t\t= 11003,\n\tProp_Reserved_11004\t\t\t\t\t\t\t= 11004,\n\n\tProp_TrackedDeviceProperty_Max\t\t\t\t= 1000000,\n};\n\n/** No string property will ever be longer than this length */\nstatic const uint32_t k_unMaxPropertyStringSize = 32 * 1024;\n\n/** Used to return errors that occur when reading properties. */\nenum ETrackedPropertyError\n{\n\tTrackedProp_Success\t\t\t\t\t\t= 0,\n\tTrackedProp_WrongDataType\t\t\t\t= 1,\n\tTrackedProp_WrongDeviceClass\t\t\t= 2,\n\tTrackedProp_BufferTooSmall\t\t\t\t= 3,\n\tTrackedProp_UnknownProperty\t\t\t\t= 4, // Driver has not set the property (and may not ever).\n\tTrackedProp_InvalidDevice\t\t\t\t= 5,\n\tTrackedProp_CouldNotContactServer\t\t= 6,\n\tTrackedProp_ValueNotProvidedByDevice\t= 7,\n\tTrackedProp_StringExceedsMaximumLength\t= 8,\n\tTrackedProp_NotYetAvailable\t\t\t\t= 9, // The property value isn't known yet, but is expected soon. Call again later.\n\tTrackedProp_PermissionDenied\t\t\t= 10,\n\tTrackedProp_InvalidOperation\t\t\t= 11,\n\tTrackedProp_CannotWriteToWildcards\t\t= 12,\n\tTrackedProp_IPCReadFailure\t\t\t\t= 13,\n\tTrackedProp_OutOfMemory\t\t\t\t\t= 14,\n\tTrackedProp_InvalidContainer\t\t\t= 15,\n};\n\n/** Used to drive certain text in the UI when talking about the tracking system for the HMD */\nenum EHmdTrackingStyle\n{\n\tHmdTrackingStyle_Unknown\t\t\t\t= 0,\n\n\tHmdTrackingStyle_Lighthouse\t\t\t\t= 1, // base stations and lasers\n\tHmdTrackingStyle_OutsideInCameras\t\t= 2, // Cameras and LED, Rift 1 style\n\tHmdTrackingStyle_InsideOutCameras\t\t= 3, // Cameras on HMD looking at the world\n};\n\ntypedef uint64_t VRActionHandle_t;\ntypedef uint64_t VRActionSetHandle_t;\ntypedef uint64_t VRInputValueHandle_t;\ntypedef uint64_t VRInputComponentHandle_t;\n\nstatic const VRActionHandle_t k_ulInvalidActionHandle = 0;\nstatic const VRActionSetHandle_t k_ulInvalidActionSetHandle = 0;\nstatic const VRInputValueHandle_t k_ulInvalidInputValueHandle = 0;\nstatic const VRInputComponentHandle_t k_ulInvalidInputComponentHandle = 0;\n\n\n/** Allows the application to control how scene textures are used by the compositor when calling Submit. */\nenum EVRSubmitFlags\n{\n\t// Simple render path. App submits rendered left and right eye images with no lens distortion correction applied.\n\tSubmit_Default = 0x00,\n\n\t// App submits final left and right eye images with lens distortion already applied (lens distortion makes the images appear\n\t// barrel distorted with chromatic aberration correction applied). The app would have used the data returned by\n\t// vr::IVRSystem::ComputeDistortion() to apply the correct distortion to the rendered images before calling Submit().\n\tSubmit_LensDistortionAlreadyApplied = 0x01,\n\n\t// If the texture pointer passed in is actually a renderbuffer (e.g. for MSAA in OpenGL) then set this flag.\n\tSubmit_GlRenderBuffer = 0x02,\n\n\t// Do not use\n\tSubmit_Reserved = 0x04,\n\n\t// Set to indicate that pTexture is a pointer to a VRTextureWithPose_t.\n\t// This flag can be combined with Submit_TextureWithDepth to pass a VRTextureWithPoseAndDepth_t.\n\tSubmit_TextureWithPose = 0x08,\n\n\t// Set to indicate that pTexture is a pointer to a VRTextureWithDepth_t.\n\t// This flag can be combined with Submit_TextureWithPose to pass a VRTextureWithPoseAndDepth_t.\n\tSubmit_TextureWithDepth = 0x10,\n\n\t// Set to indicate a discontinuity between this and the last frame.\n\t// This will prevent motion smoothing from attempting to extrapolate using the pair.\n\tSubmit_FrameDiscontinuity = 0x20,\n\n\t// Set to indicate that pTexture->handle is a contains VRVulkanTextureArrayData_t\n\tSubmit_VulkanTextureWithArrayData = 0x40,\n\n\t// If the texture pointer passed in is an OpenGL Array texture, set this flag\n\tSubmit_GlArrayTexture = 0x80,\n\n\t// If the texture is an EGL texture and not an glX/wGL texture (Linux only, currently)\n\tSubmit_IsEgl = 0x100,\n\n\t// Set to indicate that pTexture is a pointer to a VRTextureWithMotion_t.\n\tSubmit_TextureWithMotion = 0x200 | Submit_TextureWithPose | Submit_TextureWithDepth,\n\n\t// Do not use\n\tSubmit_Reserved2 = 0x08000,\n\tSubmit_Reserved3 = 0x10000,\n\tSubmit_Reserved4 = 0x20000,\n\tSubmit_Reserved5 = 0x40000,\n\tSubmit_Reserved6 = 0x80000,\n};\n\n/** Data required for passing Vulkan textures to IVRCompositor::Submit.\n* Be sure to call OpenVR_Shutdown before destroying these resources.\n* Please see https://github.com/ValveSoftware/openvr/wiki/Vulkan for Vulkan-specific documentation */\nstruct VRVulkanTextureData_t\n{\n\tuint64_t m_nImage; // VkImage\n\tVkDevice_T *m_pDevice;\n\tVkPhysicalDevice_T *m_pPhysicalDevice;\n\tVkInstance_T *m_pInstance;\n\tVkQueue_T *m_pQueue;\n\tuint32_t m_nQueueFamilyIndex;\n\tuint32_t m_nWidth, m_nHeight, m_nFormat, m_nSampleCount;\n};\n\n/** Data required for passing Vulkan texture arrays to IVRCompositor::Submit.\n* Be sure to call OpenVR_Shutdown before destroying these resources.\n* Please see https://github.com/ValveSoftware/openvr/wiki/Vulkan for Vulkan-specific documentation */\nstruct VRVulkanTextureArrayData_t : public VRVulkanTextureData_t\n{\n\tuint32_t m_unArrayIndex;\n\tuint32_t m_unArraySize;\n};\n\n/** Data required for passing D3D12 textures to IVRCompositor::Submit.\n* Be sure to call OpenVR_Shutdown before destroying these resources. */\nstruct D3D12TextureData_t\n{\n\tID3D12Resource *m_pResource;\n\tID3D12CommandQueue *m_pCommandQueue;\n\tuint32_t m_nNodeMask;\n};\n\n/** Status of the overall system or tracked objects */\nenum EVRState\n{\n\tVRState_Undefined = -1,\n\tVRState_Off = 0,\n\tVRState_Searching = 1,\n\tVRState_Searching_Alert = 2,\n\tVRState_Ready = 3,\n\tVRState_Ready_Alert = 4,\n\tVRState_NotReady = 5,\n\tVRState_Standby = 6,\n\tVRState_Ready_Alert_Low = 7,\n};\n\n/** The types of events that could be posted (and what the parameters mean for each event type) */\nenum EVREventType\n{\n\tVREvent_None = 0,\n\n\tVREvent_TrackedDeviceActivated\t\t= 100,\n\tVREvent_TrackedDeviceDeactivated\t= 101,\n\tVREvent_TrackedDeviceUpdated\t\t= 102,\n\tVREvent_TrackedDeviceUserInteractionStarted\t= 103,\n\tVREvent_TrackedDeviceUserInteractionEnded\t= 104,\n\tVREvent_IpdChanged\t\t\t\t\t= 105,\n\tVREvent_EnterStandbyMode\t\t\t= 106,\n\tVREvent_LeaveStandbyMode\t\t\t= 107,\n\tVREvent_TrackedDeviceRoleChanged\t= 108,\n\tVREvent_WatchdogWakeUpRequested\t\t= 109,\n\tVREvent_LensDistortionChanged\t\t= 110,\n\tVREvent_PropertyChanged\t\t\t\t= 111,\n\tVREvent_WirelessDisconnect\t\t\t= 112,\n\tVREvent_WirelessReconnect\t\t\t= 113,\n\tVREvent_Reserved_0114\t\t\t\t= 114,\n\tVREvent_Reserved_0115\t\t\t\t= 115,\n\n\tVREvent_ButtonPress\t\t\t\t\t= 200, // data is controller\n\tVREvent_ButtonUnpress\t\t\t\t= 201, // data is controller\n\tVREvent_ButtonTouch\t\t\t\t\t= 202, // data is controller\n\tVREvent_ButtonUntouch\t\t\t\t= 203, // data is controller\n\n\t// VREvent_DualAnalog_Press\t\t\t= 250, // No longer sent\n\t// VREvent_DualAnalog_Unpress\t\t= 251, // No longer sent\n\t// VREvent_DualAnalog_Touch\t\t\t= 252, // No longer sent\n\t// VREvent_DualAnalog_Untouch\t\t= 253, // No longer sent\n\t// VREvent_DualAnalog_Move\t\t\t= 254, // No longer sent\n\t// VREvent_DualAnalog_ModeSwitch1\t= 255, // No longer sent\n\t// VREvent_DualAnalog_ModeSwitch2\t= 256, // No longer sent\n\tVREvent_Modal_Cancel\t\t\t\t= 257, // Sent to overlays with the\n\n\tVREvent_MouseMove\t\t\t\t\t= 300, // data is mouse\n\tVREvent_MouseButtonDown\t\t\t\t= 301, // data is mouse\n\tVREvent_MouseButtonUp\t\t\t\t= 302, // data is mouse\n\tVREvent_FocusEnter\t\t\t\t\t= 303, // data is overlay\n\tVREvent_FocusLeave\t\t\t\t\t= 304, // data is overlay\n\tVREvent_ScrollDiscrete\t\t\t\t= 305, // data is scroll\n\tVREvent_TouchPadMove\t\t\t\t= 306, // data is mouse\n\tVREvent_OverlayFocusChanged\t\t\t= 307, // data is overlay, global event\n\tVREvent_ReloadOverlays\t\t\t\t= 308,\n\tVREvent_ScrollSmooth\t\t\t\t= 309, // data is scroll\n\tVREvent_LockMousePosition\t\t\t= 310, // data is mouse\n\tVREvent_UnlockMousePosition\t\t\t= 311, // data is mouse\n\n\tVREvent_InputFocusCaptured\t\t\t= 400, // data is process DEPRECATED\n\tVREvent_InputFocusReleased\t\t\t= 401, // data is process DEPRECATED\n\t// VREvent_SceneFocusLost\t\t\t= 402, // data is process\n\t// VREvent_SceneFocusGained\t\t\t= 403, // data is process\n\tVREvent_SceneApplicationChanged\t\t= 404, // data is process - The App actually drawing the scene changed (usually to or from the compositor)\n\t// VREvent_SceneFocusChanged\t\t= 405, // data is process - This is defunct and will not be called.\n\tVREvent_InputFocusChanged\t\t\t= 406, // data is process\n\t// VREvent_SceneApplicationSecondaryRenderingStarted = 407,\n\tVREvent_SceneApplicationUsingWrongGraphicsAdapter = 408, // data is process\n\tVREvent_ActionBindingReloaded\t\t = 409, // data is process - The App that action binds reloaded for\n\n\tVREvent_HideRenderModels\t\t\t= 410, // Sent to the scene application to request hiding render models temporarily\n\tVREvent_ShowRenderModels\t\t\t= 411, // Sent to the scene application to request restoring render model visibility\n\n\tVREvent_SceneApplicationStateChanged = 412, // No data; but query VRApplications()->GetSceneApplicationState();\n\n\tVREvent_SceneAppPipeDisconnected    = 413, // data is process - Called when the scene app's pipe has been closed.\n\n\tVREvent_ConsoleOpened               = 420,\n\tVREvent_ConsoleClosed               = 421,\n\n\tVREvent_OverlayShown\t\t\t\t= 500, // Indicates that an overlay is now visible to someone and should be rendering normally. Reflects IVROverlay::IsOverlayVisible() becoming true.\n\tVREvent_OverlayHidden\t\t\t\t= 501, // Indicates that an overlay is no longer visible to someone and doesn't need to render frames. Reflects IVROverlay::IsOverlayVisible() becoming false.\n\tVREvent_DashboardActivated\t\t\t= 502,\n\tVREvent_DashboardDeactivated\t\t= 503,\n\t//VREvent_DashboardThumbSelected\t\t= 504, // Sent to the overlay manager - data is overlay - No longer sent\n\t//VREvent_DashboardRequested\t\t\t= 505, // Sent to the overlay manager - data is overlay\n\tVREvent_ResetDashboard\t\t\t\t= 506, // Send to the overlay manager\n\t//VREvent_RenderToast\t\t\t\t\t= 507, // Send to the dashboard to render a toast - data is the notification ID -- no longer sent\n\tVREvent_ImageLoaded\t\t\t\t\t= 508, // Sent to overlays when a SetOverlayRaw or SetOverlayFromFile call finishes loading\n\tVREvent_ShowKeyboard\t\t\t\t= 509, // Sent to keyboard renderer in the dashboard to invoke it\n\tVREvent_HideKeyboard\t\t\t\t= 510, // Sent to keyboard renderer in the dashboard to hide it\n\tVREvent_OverlayGamepadFocusGained\t= 511, // Sent to an overlay when IVROverlay::SetFocusOverlay is called on it\n\tVREvent_OverlayGamepadFocusLost\t\t= 512, // Send to an overlay when it previously had focus and IVROverlay::SetFocusOverlay is called on something else\n\tVREvent_OverlaySharedTextureChanged = 513,\n\t//VREvent_DashboardGuideButtonDown\t= 514, // These are no longer sent\n\t//VREvent_DashboardGuideButtonUp\t\t= 515,\n\tVREvent_ScreenshotTriggered\t\t\t= 516, // Screenshot button combo was pressed, Dashboard should request a screenshot\n\tVREvent_ImageFailed\t\t\t\t\t= 517, // Sent to overlays when a SetOverlayRaw or SetOverlayfromFail fails to load\n\tVREvent_DashboardOverlayCreated\t\t= 518,\n\tVREvent_SwitchGamepadFocus\t\t\t= 519,\n\n\t// Screenshot API\n\tVREvent_RequestScreenshot\t\t\t\t= 520, // Sent by vrclient application to compositor to take a screenshot\n\tVREvent_ScreenshotTaken\t\t\t\t\t= 521, // Sent by compositor to the application that the screenshot has been taken\n\tVREvent_ScreenshotFailed\t\t\t\t= 522, // Sent by compositor to the application that the screenshot failed to be taken\n\tVREvent_SubmitScreenshotToDashboard\t\t= 523, // Sent by compositor to the dashboard that a completed screenshot was submitted\n\tVREvent_ScreenshotProgressToDashboard\t= 524, // Sent by compositor to the dashboard that a completed screenshot was submitted\n\n\tVREvent_PrimaryDashboardDeviceChanged\t= 525,\n\tVREvent_RoomViewShown\t\t\t\t\t= 526, // Sent by compositor whenever room-view is enabled (for scene apps only - not for construct or transient bounds)\n\tVREvent_RoomViewHidden\t\t\t\t\t= 527, // Sent by compositor whenever room-view is disabled (for scene apps only - not for construct or transient bounds)\n\tVREvent_ShowUI\t\t\t\t\t\t\t= 528, // data is showUi\n\tVREvent_ShowDevTools\t\t\t\t\t= 529, // data is showDevTools\n\tVREvent_DesktopViewUpdating\t\t\t\t= 530,\n\tVREvent_DesktopViewReady\t\t\t\t= 531,\n\n\tVREvent_StartDashboard\t\t\t\t\t= 532,\n\tVREvent_ElevatePrism\t\t\t\t\t= 533,\n\tVREvent_OverlayClosed\t\t\t\t\t= 534, // The overlay's close button is pressed.\n\tVREvent_DashboardThumbChanged\t\t\t= 535, // Sent when a dashboard thumbnail image changes\n\tVREvent_DesktopMightBeVisible\t\t\t= 536, // Sent when any known desktop related overlay is visible\n\tVREvent_DesktopMightBeHidden\t\t\t= 537, // Sent when all known desktop related overlays are hidden\n\tVREvent_MutualSteamCapabilitiesChanged\t= 538, // Sent when the set of capabilities common between both Steam and SteamVR have changed.\n\tVREvent_OverlayCreated\t\t\t\t\t= 539, // An OpenVR overlay of any sort was created. Data is overlay.\n\tVREvent_OverlayDestroyed\t\t\t\t= 540, // An OpenVR overlay of any sort was destroyed. Data is overlay.\n\tVREvent_OverlayNameChanged\t\t\t\t= 544, // An OpenVR overlay's name changed. Data is overlay.\n\n\tVREvent_TrackingRecordingStarted\t\t= 541,\n\tVREvent_TrackingRecordingStopped\t\t= 542,\n\tVREvent_SetTrackingRecordingPath\t\t= 543,\n\n\tVREvent_Reserved_0560  \t\t\t\t\t= 560, // No data\n\tVREvent_Reserved_0561  \t\t\t\t\t= 561, // No data\n\tVREvent_Reserved_0562\t\t\t\t\t= 562, // No data\n\tVREvent_Reserved_0563\t\t\t\t\t= 563, // No data\n\n\tVREvent_Notification_Shown\t\t\t\t= 600,\n\tVREvent_Notification_Hidden\t\t\t\t= 601,\n\tVREvent_Notification_BeginInteraction\t= 602,\n\tVREvent_Notification_Destroyed\t\t\t= 603,\n\n\tVREvent_Quit\t\t\t\t\t\t\t= 700, // data is process\n\tVREvent_ProcessQuit\t\t\t\t\t\t= 701, // data is process\n\t//VREvent_QuitAborted_UserPrompt\t\t\t= 702, // data is process\n\tVREvent_QuitAcknowledged\t\t\t\t= 703, // data is process\n\tVREvent_DriverRequestedQuit\t\t\t\t= 704, // The driver has requested that SteamVR shut down\n\tVREvent_RestartRequested\t\t\t\t= 705, // A driver or other component wants the user to restart SteamVR\n\tVREvent_InvalidateSwapTextureSets\t\t= 706,\n\tVREvent_RequestDisconnectWirelessHMD\t= 707, // vrserver asks vrlink to disconnect\n\n\tVREvent_ChaperoneDataHasChanged\t\t\t= 800, // this will never happen with the new chaperone system\n\tVREvent_ChaperoneUniverseHasChanged\t\t= 801,\n\tVREvent_ChaperoneTempDataHasChanged\t\t= 802, // this will never happen with the new chaperone system\n\tVREvent_ChaperoneSettingsHaveChanged\t= 803,\n\tVREvent_SeatedZeroPoseReset\t\t\t\t= 804,\n\tVREvent_ChaperoneFlushCache\t\t\t\t= 805, // Sent when the process needs to reload any cached data it retrieved from VRChaperone()\n\tVREvent_ChaperoneRoomSetupStarting\t    = 806, // Triggered by CVRChaperoneClient::RoomSetupStarting\n\tVREvent_ChaperoneRoomSetupCommitted\t    = 807, // Triggered by CVRChaperoneClient::CommitWorkingCopy (formerly VREvent_ChaperoneRoomSetupFinished)\n\tVREvent_StandingZeroPoseReset\t\t\t= 808,\n\tVREvent_Reserved_0809  \t\t\t\t\t= 809,\n\tVREvent_Reserved_0810  \t\t\t\t\t= 810,\n\tVREvent_Reserved_0811  \t\t\t\t\t= 811,\n\tVREvent_Reserved_0812  \t\t\t\t\t= 812,\n\tVREvent_Reserved_0813  \t\t\t\t\t= 813,\n\tVREvent_Reserved_0814  \t\t\t\t\t= 814,\n\n\tVREvent_AudioSettingsHaveChanged\t\t= 820,\n\n\tVREvent_BackgroundSettingHasChanged\t\t\t\t= 850,\n\tVREvent_CameraSettingsHaveChanged\t\t\t\t= 851,\n\tVREvent_ReprojectionSettingHasChanged\t\t\t= 852,\n\tVREvent_ModelSkinSettingsHaveChanged\t\t\t= 853,\n\tVREvent_EnvironmentSettingsHaveChanged\t\t\t= 854,\n\tVREvent_PowerSettingsHaveChanged\t\t\t\t= 855,\n\tVREvent_EnableHomeAppSettingsHaveChanged\t\t= 856,\n\tVREvent_SteamVRSectionSettingChanged\t\t\t= 857,\n\tVREvent_LighthouseSectionSettingChanged\t\t\t= 858,\n\tVREvent_NullSectionSettingChanged\t\t\t\t= 859,\n\tVREvent_UserInterfaceSectionSettingChanged\t\t= 860,\n\tVREvent_NotificationsSectionSettingChanged\t\t= 861,\n\tVREvent_KeyboardSectionSettingChanged\t\t\t= 862,\n\tVREvent_PerfSectionSettingChanged\t\t\t\t= 863,\n\tVREvent_DashboardSectionSettingChanged\t\t\t= 864,\n\tVREvent_WebInterfaceSectionSettingChanged\t\t= 865,\n\tVREvent_TrackersSectionSettingChanged\t\t\t= 866,\n\tVREvent_LastKnownSectionSettingChanged\t\t\t= 867,\n\tVREvent_DismissedWarningsSectionSettingChanged\t= 868,\n\tVREvent_GpuSpeedSectionSettingChanged\t\t\t= 869,\n\tVREvent_WindowsMRSectionSettingChanged\t\t\t= 870,\n\tVREvent_OtherSectionSettingChanged\t\t\t\t= 871,\n\tVREvent_AnyDriverSettingsChanged\t\t\t\t= 872,\n\tVREvent_Reserved_0873\t\t\t\t\t\t\t= 873,\n\n\tVREvent_StatusUpdate\t\t\t\t\t= 900,\n\n\tVREvent_WebInterface_InstallDriverCompleted = 950,\n\n\tVREvent_MCImageUpdated\t\t\t\t\t= 1000,\n\n\tVREvent_FirmwareUpdateStarted\t\t\t= 1100,\n\tVREvent_FirmwareUpdateFinished\t\t\t= 1101,\n\n\tVREvent_KeyboardClosed\t\t\t\t\t= 1200, // DEPRECATED: Sent only to the overlay it closed for, or globally if it was closed for a scene app\n\tVREvent_KeyboardCharInput\t\t\t\t= 1201, // Sent on keyboard input. Warning: event type appears as both global event and overlay event\n\tVREvent_KeyboardDone\t\t\t\t\t= 1202, // Sent when DONE button clicked on keyboard. Warning: event type appears as both global event and overlay event\n\tVREvent_KeyboardOpened_Global\t\t\t= 1203, // Sent globally when the keyboard is opened. data.keyboard.overlayHandle is who it was opened for (scene app if k_ulOverlayHandleInvalid)\n\tVREvent_KeyboardClosed_Global\t\t\t= 1204, // Sent globally when the keyboard is closed. data.keyboard.overlayHandle is who it was opened for (scene app if k_ulOverlayHandleInvalid)\n\n\t//VREvent_ApplicationTransitionStarted\t\t= 1300,\n\t//VREvent_ApplicationTransitionAborted\t\t= 1301,\n\t//VREvent_ApplicationTransitionNewAppStarted\t= 1302,\n\tVREvent_ApplicationListUpdated\t\t\t\t= 1303,\n\tVREvent_ApplicationMimeTypeLoad\t\t\t\t= 1304,\n\t// VREvent_ApplicationTransitionNewAppLaunchComplete = 1305,\n\tVREvent_ProcessConnected\t\t\t\t\t= 1306,\n\tVREvent_ProcessDisconnected\t\t\t\t\t= 1307,\n\n\t//VREvent_Compositor_MirrorWindowShown\t\t= 1400, // DEPRECATED\n\t//VREvent_Compositor_MirrorWindowHidden\t\t= 1401, // DEPRECATED\n\tVREvent_Compositor_ChaperoneBoundsShown\t\t= 1410,\n\tVREvent_Compositor_ChaperoneBoundsHidden\t= 1411,\n\tVREvent_Compositor_DisplayDisconnected\t\t= 1412,\n\tVREvent_Compositor_DisplayReconnected\t\t= 1413,\n\tVREvent_Compositor_HDCPError\t\t\t\t= 1414, // data is hdcpError\n\tVREvent_Compositor_ApplicationNotResponding\t= 1415,\n\tVREvent_Compositor_ApplicationResumed\t\t= 1416,\n\tVREvent_Compositor_OutOfVideoMemory\t\t\t= 1417,\n\tVREvent_Compositor_DisplayModeNotSupported\t= 1418, // k_pch_SteamVR_PreferredRefreshRate\n\tVREvent_Compositor_StageOverrideReady\t\t= 1419,\n\tVREvent_Compositor_RequestDisconnectReconnect = 1420,\n\n\tVREvent_TrackedCamera_StartVideoStream  = 1500,\n\tVREvent_TrackedCamera_StopVideoStream   = 1501,\n\tVREvent_TrackedCamera_PauseVideoStream  = 1502,\n\tVREvent_TrackedCamera_ResumeVideoStream = 1503,\n\tVREvent_TrackedCamera_EditingSurface    = 1550,\n\n\tVREvent_PerformanceTest_EnableCapture\t= 1600,\n\tVREvent_PerformanceTest_DisableCapture\t= 1601,\n\tVREvent_PerformanceTest_FidelityLevel\t= 1602,\n\n\tVREvent_MessageOverlay_Closed\t\t\t= 1650,\n\tVREvent_MessageOverlayCloseRequested\t= 1651,\n\n\tVREvent_Input_HapticVibration\t\t\t= 1700, // data is hapticVibration\n\tVREvent_Input_BindingLoadFailed\t\t\t= 1701, // data is inputBinding\n\tVREvent_Input_BindingLoadSuccessful\t\t= 1702, // data is inputBinding\n\tVREvent_Input_ActionManifestReloaded\t= 1703, // no data\n\tVREvent_Input_ActionManifestLoadFailed\t= 1704, // data is actionManifest\n\tVREvent_Input_ProgressUpdate\t\t\t= 1705, // data is progressUpdate\n\tVREvent_Input_TrackerActivated\t\t\t= 1706,\n\tVREvent_Input_BindingsUpdated\t\t\t= 1707,\n\tVREvent_Input_BindingSubscriptionChanged = 1708,\n\n\tVREvent_SpatialAnchors_PoseUpdated\t\t= 1800,        // data is spatialAnchor. broadcast\n\tVREvent_SpatialAnchors_DescriptorUpdated = 1801,       // data is spatialAnchor. broadcast\n\tVREvent_SpatialAnchors_RequestPoseUpdate = 1802,       // data is spatialAnchor. sent to specific driver\n\tVREvent_SpatialAnchors_RequestDescriptorUpdate = 1803, // data is spatialAnchor. sent to specific driver\n\n\tVREvent_SystemReport_Started\t\t\t= 1900, // user or system initiated generation of a system report. broadcast\n\n\tVREvent_Monitor_ShowHeadsetView\t\t\t= 2000, // data is process\n\tVREvent_Monitor_HideHeadsetView\t\t\t= 2001, // data is process\n\n\tVREvent_Audio_SetSpeakersVolume\t\t\t= 2100,\n\tVREvent_Audio_SetSpeakersMute\t\t\t= 2101,\n\tVREvent_Audio_SetMicrophoneVolume\t\t= 2102,\n\tVREvent_Audio_SetMicrophoneMute\t\t\t= 2103,\n\n\tVREvent_RenderModel_CountChanged       = 2200, //Number of RenderModels in the system has changed\n\n\t// Vendors are free to expose private events in this reserved region\n\tVREvent_VendorSpecific_Reserved_Start\t= 10000,\n\tVREvent_VendorSpecific_Reserved_End\t\t= 19999,\n};\n\n\n/** Level of Hmd activity */\n// UserInteraction_Timeout means the device is in the process of timing out.\n// InUse = ( k_EDeviceActivityLevel_UserInteraction || k_EDeviceActivityLevel_UserInteraction_Timeout )\n// VREvent_TrackedDeviceUserInteractionStarted fires when the devices transitions from Standby -> UserInteraction or Idle -> UserInteraction.\n// VREvent_TrackedDeviceUserInteractionEnded fires when the devices transitions from UserInteraction_Timeout -> Idle\nenum EDeviceActivityLevel\n{\n\tk_EDeviceActivityLevel_Unknown = -1,\n\tk_EDeviceActivityLevel_Idle = 0,\t\t\t\t\t\t// No activity for the last 10 seconds\n\tk_EDeviceActivityLevel_UserInteraction = 1,\t\t\t\t// Activity (movement or prox sensor) is happening now\n\tk_EDeviceActivityLevel_UserInteraction_Timeout = 2,\t\t// No activity for the last 0.5 seconds\n\tk_EDeviceActivityLevel_Standby = 3,\t\t\t\t\t\t// Idle for at least 5 seconds (configurable in Settings -> Power Management)\n\tk_EDeviceActivityLevel_Idle_Timeout = 4,\n};\n\n\n/** VR controller button and axis IDs */\nenum EVRButtonId\n{\n\tk_EButton_System\t\t\t= 0,\n\tk_EButton_ApplicationMenu\t= 1,\n\tk_EButton_Grip\t\t\t\t= 2,\n\tk_EButton_DPad_Left\t\t\t= 3,\n\tk_EButton_DPad_Up\t\t\t= 4,\n\tk_EButton_DPad_Right\t\t= 5,\n\tk_EButton_DPad_Down\t\t\t= 6,\n\tk_EButton_A\t\t\t\t\t= 7,\n\n\tk_EButton_ProximitySensor   = 31,\n\n\tk_EButton_Axis0\t\t\t\t= 32,\n\tk_EButton_Axis1\t\t\t\t= 33,\n\tk_EButton_Axis2\t\t\t\t= 34,\n\tk_EButton_Axis3\t\t\t\t= 35,\n\tk_EButton_Axis4\t\t\t\t= 36,\n\n\t// aliases for well known controllers\n\tk_EButton_SteamVR_Touchpad\t= k_EButton_Axis0,\n\tk_EButton_SteamVR_Trigger\t= k_EButton_Axis1,\n\n\tk_EButton_Dashboard_Back\t= k_EButton_Grip,\n\n\tk_EButton_IndexController_A\t\t= k_EButton_Grip,\n\tk_EButton_IndexController_B\t\t= k_EButton_ApplicationMenu,\n\tk_EButton_IndexController_JoyStick\t= k_EButton_Axis3,\n\n\tk_EButton_Reserved0\t\t\t= 50,\n\tk_EButton_Reserved1\t\t\t= 51,\n\n\tk_EButton_Max\t\t\t\t= 64\n};\n\ninline uint64_t ButtonMaskFromId( EVRButtonId id ) { return 1ull << id; }\n\n/** used for controller button events */\nstruct VREvent_Controller_t\n{\n\tuint32_t button; // EVRButtonId enum\n};\n\n\n/** used for simulated mouse events in overlay space */\nenum EVRMouseButton\n{\n\tVRMouseButton_Left\t\t\t\t\t= 0x0001,\n\tVRMouseButton_Right\t\t\t\t\t= 0x0002,\n\tVRMouseButton_Middle\t\t\t\t= 0x0004,\n};\n\n\n/** used for simulated mouse events in overlay space */\nstruct VREvent_Mouse_t\n{\n\tfloat x, y; // co-ords are in GL space, bottom left of the texture is 0,0\n\tuint32_t button; // EVRMouseButton enum\n\n\t// if from an event triggered by cursor input on an overlay that supports multiple cursors, this is the index of\n\t// which tracked cursor the event is for\n\tuint32_t cursorIndex;\n};\n\n/** used for simulated mouse wheel scroll */\nstruct VREvent_Scroll_t\n{\n\tfloat xdelta, ydelta;\n\tuint32_t unused;\n\tfloat viewportscale; // For scrolling on an overlay with laser mouse, this is the overlay's vertical size relative to the overlay height. Range: [0,1]\n\n\t// if from an event triggered by cursor input on an overlay that supports multiple cursors, this is the index of\n\t// which tracked cursor the event is for\n\tuint32_t cursorIndex;\n};\n\n/** when in mouse input mode you can receive data from the touchpad, these events are only sent if the users finger\n   is on the touchpad (or just released from it). These events are sent to overlays with the VROverlayFlags_SendVRTouchpadEvents\n   flag set.\n**/\nstruct VREvent_TouchPadMove_t\n{\n\t// true if the users finger is detected on the touch pad\n\tbool bFingerDown;\n\n\t// How long the finger has been down in seconds\n\tfloat flSecondsFingerDown;\n\n\t// These values indicate the starting finger position (so you can do some basic swipe stuff)\n\tfloat fValueXFirst;\n\tfloat fValueYFirst;\n\n\t// This is the raw sampled coordinate without deadzoning\n\tfloat fValueXRaw;\n\tfloat fValueYRaw;\n};\n\n/** notification related events. Details will still change at this point */\nstruct VREvent_Notification_t\n{\n\tuint64_t ulUserValue;\n\tuint32_t notificationId;\n};\n\n/** Used for events about processes */\nstruct VREvent_Process_t\n{\n\tuint32_t pid;\n\tuint32_t oldPid;\n\tbool bForced;\n\t// If the associated event was triggered by a connection loss\n\tbool bConnectionLost;\n};\n\n\n/** Used for a few events about overlays */\nstruct VREvent_Overlay_t\n{\n\tuint64_t overlayHandle; // VROverlayHandle_t\n\tuint64_t devicePath;\n\tuint64_t memoryBlockId;\n\n\t// if from an event triggered by cursor input on an overlay that supports multiple cursors, this is the index of\n\t// which tracked cursor the event is for\n\tuint32_t cursorIndex;\n};\n\n\n/** Used for a few events about overlays */\nstruct VREvent_Status_t\n{\n\tuint32_t statusState; // EVRState enum\n};\n\n/** Used for keyboard events */\nstruct VREvent_Keyboard_t\n{\n\tchar cNewInput[8]; // 7 bytes of utf8 + null\n\tuint64_t uUserValue; // caller specified opaque token\n\tuint64_t overlayHandle; // VROverlayHandle_t\n};\n\nstruct VREvent_Ipd_t\n{\n\tfloat ipdMeters;\n};\n\nstruct VREvent_Chaperone_t\n{\n\tuint64_t m_nPreviousUniverse_deprecated;\n\tuint64_t m_nCurrentUniverse;\n};\n\n/** Not actually used for any events */\nstruct VREvent_Reserved_t\n{\n\tuint64_t reserved0;\n\tuint64_t reserved1;\n\tuint64_t reserved2;\n\tuint64_t reserved3;\n\tuint64_t reserved4;\n\tuint64_t reserved5;\n};\n\nstruct VREvent_PerformanceTest_t\n{\n\tuint32_t m_nFidelityLevel;\n};\n\nstruct VREvent_SeatedZeroPoseReset_t\n{\n\tbool bResetBySystemMenu;\n};\n\nstruct VREvent_Screenshot_t\n{\n\tuint32_t handle;\n\tuint32_t type;\n};\n\nstruct VREvent_ScreenshotProgress_t\n{\n\tfloat progress;\n};\n\nstruct VREvent_ApplicationLaunch_t\n{\n\tuint32_t pid;\n\tuint32_t unArgsHandle;\n};\n\nstruct VREvent_EditingCameraSurface_t\n{\n\tuint64_t overlayHandle;\n\tuint32_t nVisualMode;\n};\n\nstruct VREvent_MessageOverlay_t\n{\n\tuint32_t unVRMessageOverlayResponse; // vr::VRMessageOverlayResponse enum\n};\n\nstruct VREvent_Property_t\n{\n\tPropertyContainerHandle_t container;\n\tETrackedDeviceProperty prop;\n};\n\nstruct VREvent_HapticVibration_t\n{\n\tuint64_t containerHandle; // property container handle of the device with the haptic component\n\tuint64_t componentHandle; // Which haptic component needs to vibrate\n\tfloat fDurationSeconds;\n\tfloat fFrequency;\n\tfloat fAmplitude;\n};\n\nstruct VREvent_WebConsole_t\n{\n\tWebConsoleHandle_t webConsoleHandle;\n};\n\nstruct VREvent_InputBindingLoad_t\n{\n\tvr::PropertyContainerHandle_t ulAppContainer;\n\tuint64_t pathMessage;\n\tuint64_t pathUrl;\n\tuint64_t pathControllerType;\n};\n\nstruct VREvent_InputActionManifestLoad_t\n{\n\tuint64_t pathAppKey;\n\tuint64_t pathMessage;\n\tuint64_t pathMessageParam;\n\tuint64_t pathManifestPath;\n};\n\nstruct VREvent_SpatialAnchor_t\n{\n\tSpatialAnchorHandle_t unHandle;\n};\n\nstruct VREvent_ProgressUpdate_t\n{\n\tuint64_t ulApplicationPropertyContainer;\n\tuint64_t pathDevice;\n\tuint64_t pathInputSource;\n\tuint64_t pathProgressAction;\n\tuint64_t pathIcon;\n\tfloat fProgress;\n};\n\nenum EShowUIType\n{\n\tShowUI_ControllerBinding = 0,\n\tShowUI_ManageTrackers = 1,\n\t// ShowUI_QuickStart = 2, // Deprecated\n\tShowUI_Pairing = 3,\n\tShowUI_Settings = 4,\n\tShowUI_DebugCommands = 5,\n\tShowUI_FullControllerBinding = 6,\n\tShowUI_ManageDrivers = 7,\n};\n\nstruct VREvent_ShowUI_t\n{\n\tEShowUIType eType;\n};\n\nstruct VREvent_ShowDevTools_t\n{\n\tint32_t nBrowserIdentifier;\n};\n\nenum EHDCPError\n{\n\tHDCPError_None = 0,\n\tHDCPError_LinkLost = 1,\n\tHDCPError_Tampered = 2,\n\tHDCPError_DeviceRevoked = 3,\n\tHDCPError_Unknown = 4\n};\n\nstruct VREvent_HDCPError_t\n{\n\tEHDCPError eCode;\n};\n\nstruct VREvent_AudioVolumeControl_t\n{\n\tfloat fVolumeLevel;\n};\n\nstruct VREvent_AudioMuteControl_t\n{\n\tbool bMute;\n};\n\ntypedef union\n{\n\tVREvent_Reserved_t reserved;\n\tVREvent_Controller_t controller;\n\tVREvent_Mouse_t mouse;\n\tVREvent_Scroll_t scroll;\n\tVREvent_Process_t process;\n\tVREvent_Notification_t notification;\n\tVREvent_Overlay_t overlay;\n\tVREvent_Status_t status;\n\tVREvent_Keyboard_t keyboard;\n\tVREvent_Ipd_t ipd;\n\tVREvent_Chaperone_t chaperone;\n\tVREvent_PerformanceTest_t performanceTest;\n\tVREvent_TouchPadMove_t touchPadMove;\n\tVREvent_SeatedZeroPoseReset_t seatedZeroPoseReset;\n\tVREvent_Screenshot_t screenshot;\n\tVREvent_ScreenshotProgress_t screenshotProgress;\n\tVREvent_ApplicationLaunch_t applicationLaunch;\n\tVREvent_EditingCameraSurface_t cameraSurface;\n\tVREvent_MessageOverlay_t messageOverlay;\n\tVREvent_Property_t property;\n\tVREvent_HapticVibration_t hapticVibration;\n\tVREvent_WebConsole_t webConsole;\n\tVREvent_InputBindingLoad_t inputBinding;\n\tVREvent_InputActionManifestLoad_t actionManifest;\n\tVREvent_SpatialAnchor_t spatialAnchor;\n\tVREvent_ProgressUpdate_t progressUpdate;\n\tVREvent_ShowUI_t showUi;\n\tVREvent_ShowDevTools_t showDevTools;\n\tVREvent_HDCPError_t hdcpError;\n\tVREvent_AudioVolumeControl_t audioVolumeControl;\n\tVREvent_AudioMuteControl_t audioMuteControl;\n\t/** NOTE!!! If you change this you MUST manually update openvr_interop.cs.py and openvr_api_flat.h.py */\n} VREvent_Data_t;\n\n\n#if defined(__linux__) || defined(__APPLE__)\n// This structure was originally defined mis-packed on Linux, preserved for\n// compatibility.\n#pragma pack( push, 4 )\n#endif\n\n/** An event posted by the server to all running applications */\nstruct VREvent_t\n{\n\tuint32_t eventType; // EVREventType enum\n\tTrackedDeviceIndex_t trackedDeviceIndex;\n\tfloat eventAgeSeconds;\n\t// event data must be the end of the struct as its size is variable\n\tVREvent_Data_t data;\n};\n\n#if defined(__linux__) || defined(__APPLE__)\n#pragma pack( pop )\n#endif\n\ntypedef uint32_t VRComponentProperties;\n\nenum EVRComponentProperty\n{\n\tVRComponentProperty_IsStatic = (1 << 0),\n\tVRComponentProperty_IsVisible = (1 << 1),\n\tVRComponentProperty_IsTouched = (1 << 2),\n\tVRComponentProperty_IsPressed = (1 << 3),\n\tVRComponentProperty_IsScrolled = (1 << 4),\n\tVRComponentProperty_IsHighlighted = (1 << 5),\n};\n\n\n/** Describes state information about a render-model component, including transforms and other dynamic properties */\nstruct RenderModel_ComponentState_t\n{\n\tHmdMatrix34_t mTrackingToComponentRenderModel;  // Transform required when drawing the component render model\n\tHmdMatrix34_t mTrackingToComponentLocal;        // Transform available for attaching to a local component coordinate system (-Z out from surface )\n\tVRComponentProperties uProperties;\n};\n\n\nenum EVRInputError\n{\n\tVRInputError_None = 0,\n\tVRInputError_NameNotFound = 1,\n\tVRInputError_WrongType = 2,\n\tVRInputError_InvalidHandle = 3,\n\tVRInputError_InvalidParam = 4,\n\tVRInputError_NoSteam = 5,\n\tVRInputError_MaxCapacityReached = 6,\n\tVRInputError_IPCError = 7,\n\tVRInputError_NoActiveActionSet = 8,\n\tVRInputError_InvalidDevice = 9,\n\tVRInputError_InvalidSkeleton = 10,\n\tVRInputError_InvalidBoneCount = 11,\n\tVRInputError_InvalidCompressedData = 12,\n\tVRInputError_NoData = 13,\n\tVRInputError_BufferTooSmall = 14,\n\tVRInputError_MismatchedActionManifest = 15,\n\tVRInputError_MissingSkeletonData = 16,\n\tVRInputError_InvalidBoneIndex = 17,\n\tVRInputError_InvalidPriority = 18,\n\tVRInputError_PermissionDenied = 19,\n\tVRInputError_InvalidRenderModel = 20,\n};\n\nenum EVRSpatialAnchorError\n{\n\tVRSpatialAnchorError_Success = 0,\n\tVRSpatialAnchorError_Internal = 1,\n\tVRSpatialAnchorError_UnknownHandle = 2,\n\tVRSpatialAnchorError_ArrayTooSmall = 3,\n\tVRSpatialAnchorError_InvalidDescriptorChar = 4,\n\tVRSpatialAnchorError_NotYetAvailable = 5,\n\tVRSpatialAnchorError_NotAvailableInThisUniverse = 6,\n\tVRSpatialAnchorError_PermanentlyUnavailable = 7,\n\tVRSpatialAnchorError_WrongDriver = 8,\n\tVRSpatialAnchorError_DescriptorTooLong = 9,\n\tVRSpatialAnchorError_Unknown = 10,\n\tVRSpatialAnchorError_NoRoomCalibration = 11,\n\tVRSpatialAnchorError_InvalidArgument = 12,\n\tVRSpatialAnchorError_UnknownDriver = 13,\n};\n\n/** The mesh to draw into the stencil (or depth) buffer to perform\n* early stencil (or depth) kills of pixels that will never appear on the HMD.\n* This mesh draws on all the pixels that will be hidden after distortion.\n*\n* If the HMD does not provide a visible area mesh pVertexData will be\n* NULL and unTriangleCount will be 0. */\nstruct HiddenAreaMesh_t\n{\n\tconst HmdVector2_t *pVertexData;\n\tuint32_t unTriangleCount;\n};\n\n\nenum EHiddenAreaMeshType\n{\n\tk_eHiddenAreaMesh_Standard = 0,\n\tk_eHiddenAreaMesh_Inverse = 1,\n\tk_eHiddenAreaMesh_LineLoop = 2,\n\n\tk_eHiddenAreaMesh_Max = 3,\n};\n\n\n/** Identifies what kind of axis is on the controller at index n. Read this type\n* with pVRSystem->Get( nControllerDeviceIndex, Prop_Axis0Type_Int32 + n );\n*/\nenum EVRControllerAxisType\n{\n\tk_eControllerAxis_None = 0,\n\tk_eControllerAxis_TrackPad = 1,\n\tk_eControllerAxis_Joystick = 2,\n\tk_eControllerAxis_Trigger = 3, // Analog trigger data is in the X axis\n};\n\n\n/** contains information about one axis on the controller */\nstruct VRControllerAxis_t\n{\n\tfloat x; // Ranges from -1.0 to 1.0 for joysticks and track pads. Ranges from 0.0 to 1.0 for triggers were 0 is fully released.\n\tfloat y; // Ranges from -1.0 to 1.0 for joysticks and track pads. Is always 0.0 for triggers.\n};\n\n\n/** the number of axes in the controller state */\nstatic const uint32_t k_unControllerStateAxisCount = 5;\n\n\n#if defined(__linux__) || defined(__APPLE__)\n// This structure was originally defined mis-packed on Linux, preserved for\n// compatibility.\n#pragma pack( push, 4 )\n#endif\n\n/** Holds all the state of a controller at one moment in time. */\nstruct VRControllerState001_t\n{\n\t// If packet num matches that on your prior call, then the controller state hasn't been changed since\n\t// your last call and there is no need to process it\n\tuint32_t unPacketNum;\n\n\t// bit flags for each of the buttons. Use ButtonMaskFromId to turn an ID into a mask\n\tuint64_t ulButtonPressed;\n\tuint64_t ulButtonTouched;\n\n\t// Axis data for the controller's analog inputs\n\tVRControllerAxis_t rAxis[ k_unControllerStateAxisCount ];\n};\n#if defined(__linux__) || defined(__APPLE__)\n#pragma pack( pop )\n#endif\n\n\ntypedef VRControllerState001_t VRControllerState_t;\n\n\n/** determines how to provide output to the application of various event processing functions. */\nenum EVRControllerEventOutputType\n{\n\tControllerEventOutput_OSEvents = 0,\n\tControllerEventOutput_VREvents = 1,\n};\n\n\n\n/** Collision Bounds Style */\nenum ECollisionBoundsStyle\n{\n\tCOLLISION_BOUNDS_STYLE_BEGINNER = 0,\n\tCOLLISION_BOUNDS_STYLE_INTERMEDIATE,\n\tCOLLISION_BOUNDS_STYLE_SQUARES,\n\tCOLLISION_BOUNDS_STYLE_ADVANCED,\n\tCOLLISION_BOUNDS_STYLE_NONE,\n\n\tCOLLISION_BOUNDS_STYLE_COUNT\n};\n\n/** used to refer to a single VR overlay */\ntypedef uint64_t VROverlayHandle_t;\n\nstatic const VROverlayHandle_t k_ulOverlayHandleInvalid = 0;\n\n/** Errors that can occur around VR overlays */\nenum EVROverlayError\n{\n\tVROverlayError_None\t\t\t\t\t\t= 0,\n\n\tVROverlayError_UnknownOverlay\t\t\t= 10,\n\tVROverlayError_InvalidHandle\t\t\t= 11,\n\tVROverlayError_PermissionDenied\t\t\t= 12,\n\tVROverlayError_OverlayLimitExceeded\t\t= 13, // No more overlays could be created because the maximum number already exist\n\tVROverlayError_WrongVisibilityType\t\t= 14,\n\tVROverlayError_KeyTooLong\t\t\t\t= 15,\n\tVROverlayError_NameTooLong\t\t\t\t= 16,\n\tVROverlayError_KeyInUse\t\t\t\t\t= 17,\n\tVROverlayError_WrongTransformType\t\t= 18,\n\tVROverlayError_InvalidTrackedDevice\t\t= 19,\n\tVROverlayError_InvalidParameter\t\t\t= 20,\n\tVROverlayError_ThumbnailCantBeDestroyed\t= 21,\n\tVROverlayError_ArrayTooSmall\t\t\t= 22,\n\tVROverlayError_RequestFailed\t\t\t= 23,\n\tVROverlayError_InvalidTexture\t\t\t= 24,\n\tVROverlayError_UnableToLoadFile\t\t\t= 25,\n\tVROverlayError_KeyboardAlreadyInUse\t\t= 26,\n\tVROverlayError_NoNeighbor\t\t\t\t= 27,\n\tVROverlayError_TooManyMaskPrimitives\t= 29,\n\tVROverlayError_BadMaskPrimitive\t\t\t= 30,\n\tVROverlayError_TextureAlreadyLocked\t\t= 31,\n\tVROverlayError_TextureLockCapacityReached = 32,\n\tVROverlayError_TextureNotLocked\t\t\t= 33,\n\tVROverlayError_TimedOut                 = 34,\n};\n\n/** enum values to pass in to VR_Init to identify whether the application will\n* draw a 3D scene. */\nenum EVRApplicationType\n{\n\tVRApplication_Other = 0,\t\t// Some other kind of application that isn't covered by the other entries\n\tVRApplication_Scene\t= 1,\t\t// Application will submit 3D frames\n\tVRApplication_Overlay = 2,\t\t// Application only interacts with overlays\n\tVRApplication_Background = 3,\t// Application should not start SteamVR if it's not already running, and should not\n\t\t\t\t\t\t\t\t\t// keep it running if everything else quits.\n\tVRApplication_Utility = 4,\t\t// Init should not try to load any drivers. The application needs access to utility\n\t\t\t\t\t\t\t\t\t// interfaces (like IVRSettings and IVRApplications) but not hardware.\n\tVRApplication_VRMonitor = 5,\t// Reserved for vrmonitor\n\tVRApplication_SteamWatchdog = 6,// Reserved for Steam\n\tVRApplication_Bootstrapper = 7, // reserved for vrstartup\n\tVRApplication_WebHelper = 8,\t// reserved for vrwebhelper\n\tVRApplication_OpenXRInstance = 9, // reserved for openxr (created instance, but not session yet)\n\tVRApplication_OpenXRScene = 10,\t  // reserved for openxr (started session)\n\tVRApplication_OpenXROverlay = 11, // reserved for openxr (started overlay session)\n\tVRApplication_Prism = 12,\t\t// reserved for the vrprismhost process\n\tVRApplication_RoomView = 13,\t// reserved for the RoomView process\n\n\tVRApplication_Max\n};\n\n\n/** returns true if the specified application type is one of the\n* OpenXR types */\ninline bool IsOpenXRAppType( EVRApplicationType eType )\n{\n\treturn eType == VRApplication_OpenXRInstance\n\t\t|| eType == VRApplication_OpenXRScene\n\t\t|| eType == VRApplication_OpenXROverlay;\n}\n\n\n/** returns true if the specified application type submits eye buffers */\ninline bool BAppTypeSubmitsEyeBuffers( EVRApplicationType eType )\n{\n\treturn eType == VRApplication_Scene\n\t\t|| eType == VRApplication_OpenXRScene\n\t\t|| eType == VRApplication_RoomView;\n}\n\n\n/** error codes for firmware */\nenum EVRFirmwareError\n{\n\tVRFirmwareError_None = 0,\n\tVRFirmwareError_Success = 1,\n\tVRFirmwareError_Fail = 2,\n};\n\n\n/** error codes for notifications */\nenum EVRNotificationError\n{\n\tVRNotificationError_OK = 0,\n\tVRNotificationError_InvalidNotificationId = 100,\n\tVRNotificationError_NotificationQueueFull = 101,\n\tVRNotificationError_InvalidOverlayHandle = 102,\n\tVRNotificationError_SystemWithUserValueAlreadyExists = 103,\n\tVRNotificationError_ServiceUnavailable = 104,\n};\n\n\nenum EVRSkeletalMotionRange\n{\n\t// The range of motion of the skeleton takes into account any physical limits imposed by\n\t// the controller itself.  This will tend to be the most accurate pose compared to the user's\n\t// actual hand pose, but might not allow a closed fist for example\n\tVRSkeletalMotionRange_WithController = 0,\n\n\t// Retarget the range of motion provided by the input device to make the hand appear to move\n\t// as if it was not holding a controller.  eg: map \"hand grasping controller\" to \"closed fist\"\n\tVRSkeletalMotionRange_WithoutController = 1,\n};\n\nenum EVRSkeletalTrackingLevel\n{\n\t// body part location can't be directly determined by the device. Any skeletal pose provided by\n\t// the device is estimated by assuming the position required to active buttons, triggers, joysticks,\n\t// or other input sensors.\n\t// E.g. Vive Controller, Gamepad\n\tVRSkeletalTracking_Estimated = 0,\n\n\t// body part location can be measured directly but with fewer degrees of freedom than the actual body\n\t// part. Certain body part positions may be unmeasured by the device and estimated from other input data.\n\t// E.g. Index Controllers, gloves that only measure finger curl\n\tVRSkeletalTracking_Partial = 1,\n\n\t// Body part location can be measured directly throughout the entire range of motion of the body part.\n\t// E.g. Mocap suit for the full body, gloves that measure rotation of each finger segment\n\tVRSkeletalTracking_Full = 2,\n\n\tVRSkeletalTrackingLevel_Count,\n\tVRSkeletalTrackingLevel_Max = VRSkeletalTrackingLevel_Count - 1\n};\n\n\n/** Type used for referring to bones by their index */\ntypedef int32_t BoneIndex_t;\nconst BoneIndex_t k_unInvalidBoneIndex = -1;\n\n\n/** error codes returned by Vr_Init */\n\n// Please add adequate error description to https://developer.valvesoftware.com/w/index.php?title=Category:SteamVRHelp\nenum EVRInitError\n{\n\tVRInitError_None\t= 0,\n\tVRInitError_Unknown = 1,\n\n\tVRInitError_Init_InstallationNotFound\t\t\t= 100,\n\tVRInitError_Init_InstallationCorrupt\t\t\t= 101,\n\tVRInitError_Init_VRClientDLLNotFound\t\t\t= 102,\n\tVRInitError_Init_FileNotFound\t\t\t\t\t= 103,\n\tVRInitError_Init_FactoryNotFound\t\t\t\t= 104,\n\tVRInitError_Init_InterfaceNotFound\t\t\t\t= 105,\n\tVRInitError_Init_InvalidInterface\t\t\t\t= 106,\n\tVRInitError_Init_UserConfigDirectoryInvalid\t\t= 107,\n\tVRInitError_Init_HmdNotFound\t\t\t\t\t= 108,\n\tVRInitError_Init_NotInitialized\t\t\t\t\t= 109,\n\tVRInitError_Init_PathRegistryNotFound\t\t\t= 110,\n\tVRInitError_Init_NoConfigPath\t\t\t\t\t= 111,\n\tVRInitError_Init_NoLogPath\t\t\t\t\t\t= 112,\n\tVRInitError_Init_PathRegistryNotWritable\t\t= 113,\n\tVRInitError_Init_AppInfoInitFailed\t\t\t\t= 114,\n\tVRInitError_Init_Retry\t\t\t\t\t\t\t= 115, // Used internally to cause retries to vrserver\n\tVRInitError_Init_InitCanceledByUser\t\t\t\t= 116, // The calling application should silently exit. The user canceled app startup\n\tVRInitError_Init_AnotherAppLaunching\t\t\t= 117,\n\tVRInitError_Init_SettingsInitFailed\t\t\t\t= 118,\n\tVRInitError_Init_ShuttingDown\t\t\t\t\t= 119,\n\tVRInitError_Init_TooManyObjects\t\t\t\t\t= 120,\n\tVRInitError_Init_NoServerForBackgroundApp\t\t= 121,\n\tVRInitError_Init_NotSupportedWithCompositor\t\t= 122,\n\tVRInitError_Init_NotAvailableToUtilityApps\t\t= 123,\n\tVRInitError_Init_Internal\t\t\t\t \t\t= 124,\n\tVRInitError_Init_HmdDriverIdIsNone\t\t \t\t= 125,\n\tVRInitError_Init_HmdNotFoundPresenceFailed \t\t= 126,\n\tVRInitError_Init_VRMonitorNotFound\t\t\t\t= 127,\n\tVRInitError_Init_VRMonitorStartupFailed\t\t\t= 128,\n\tVRInitError_Init_LowPowerWatchdogNotSupported\t= 129,\n\tVRInitError_Init_InvalidApplicationType\t\t\t= 130,\n\tVRInitError_Init_NotAvailableToWatchdogApps\t\t= 131,\n\tVRInitError_Init_WatchdogDisabledInSettings\t\t= 132,\n\tVRInitError_Init_VRDashboardNotFound\t\t\t= 133,\n\tVRInitError_Init_VRDashboardStartupFailed\t\t= 134,\n\tVRInitError_Init_VRHomeNotFound\t\t\t\t\t= 135,\n\tVRInitError_Init_VRHomeStartupFailed\t\t\t= 136,\n\tVRInitError_Init_RebootingBusy\t\t\t\t\t= 137,\n\tVRInitError_Init_FirmwareUpdateBusy\t\t\t\t= 138,\n\tVRInitError_Init_FirmwareRecoveryBusy\t\t\t= 139,\n\tVRInitError_Init_USBServiceBusy\t\t\t\t\t= 140,\n\tVRInitError_Init_VRWebHelperStartupFailed\t\t= 141,\n\tVRInitError_Init_TrackerManagerInitFailed\t\t= 142,\n\tVRInitError_Init_AlreadyRunning\t\t\t\t\t= 143,\n\tVRInitError_Init_FailedForVrMonitor\t\t\t\t= 144,\n\tVRInitError_Init_PropertyManagerInitFailed\t\t= 145,\n\tVRInitError_Init_WebServerFailed\t\t\t\t= 146,\n\tVRInitError_Init_IllegalTypeTransition\t\t\t= 147,\n\tVRInitError_Init_MismatchedRuntimes\t\t\t\t= 148,\n\tVRInitError_Init_InvalidProcessId\t\t\t\t= 149,\n\tVRInitError_Init_VRServiceStartupFailed\t\t\t= 150,\n\tVRInitError_Init_PrismNeedsNewDrivers\t\t\t= 151,\n\tVRInitError_Init_PrismStartupTimedOut\t\t\t= 152,\n\tVRInitError_Init_CouldNotStartPrism\t\t\t\t= 153,\n\tVRInitError_Init_PrismClientInitFailed\t\t\t= 154,\n\tVRInitError_Init_PrismClientStartFailed\t\t\t= 155,\n\tVRInitError_Init_PrismExitedUnexpectedly\t\t= 156,\n\tVRInitError_Init_BadLuid\t\t\t\t\t\t= 157,\n\tVRInitError_Init_NoServerForAppContainer\t\t= 158,\n\tVRInitError_Init_DuplicateBootstrapper\t\t\t= 159,\n\tVRInitError_Init_VRDashboardServicePending\t\t= 160,\n\tVRInitError_Init_VRDashboardServiceTimeout\t\t= 161,\n\tVRInitError_Init_VRDashboardServiceStopped\t\t= 162,\n\tVRInitError_Init_VRDashboardAlreadyStarted\t\t= 163,\n\tVRInitError_Init_VRDashboardCopyFailed\t\t\t= 164,\n\tVRInitError_Init_VRDashboardTokenFailure\t\t= 165,\n\tVRInitError_Init_VRDashboardEnvironmentFailure\t= 166,\n\tVRInitError_Init_VRDashboardPathFailure\t\t\t= 167,\n\tVRInitError_Init_InstallationTooOld\t\t\t\t= 168,\n\tVRInitError_Init_ClientVersionAlreadyProvided\t= 169,\n\n\tVRInitError_Driver_Failed\t\t\t\t\t\t= 200,\n\tVRInitError_Driver_Unknown\t\t\t\t\t\t= 201,\n\tVRInitError_Driver_HmdUnknown\t\t\t\t\t= 202,\n\tVRInitError_Driver_NotLoaded\t\t\t\t\t= 203,\n\tVRInitError_Driver_RuntimeOutOfDate\t\t\t\t= 204,\n\tVRInitError_Driver_HmdInUse\t\t\t\t\t\t= 205,\n\tVRInitError_Driver_NotCalibrated\t\t\t\t= 206,\n\tVRInitError_Driver_CalibrationInvalid\t\t\t= 207,\n\tVRInitError_Driver_HmdDisplayNotFound\t\t\t= 208,\n\tVRInitError_Driver_TrackedDeviceInterfaceUnknown = 209,\n\t// VRInitError_Driver_HmdDisplayNotFoundAfterFix = 210, // not needed: here for historic reasons\n\tVRInitError_Driver_HmdDriverIdOutOfBounds\t\t= 211,\n\tVRInitError_Driver_HmdDisplayMirrored\t\t\t= 212,\n\tVRInitError_Driver_HmdDisplayNotFoundLaptop\t\t= 213,\n\tVRInitError_Driver_PeerDriverNotInstalled\t\t= 214,\n\tVRInitError_Driver_WirelessHmdNotConnected\t\t= 215,\n\n\t// Never make error 259 because we return it from main and it would conflict with STILL_ACTIVE\n\n\tVRInitError_IPC_ServerInitFailed\t\t\t\t= 300,\n\tVRInitError_IPC_ConnectFailed\t\t\t\t\t= 301,\n\tVRInitError_IPC_SharedStateInitFailed\t\t\t= 302,\n\tVRInitError_IPC_CompositorInitFailed\t\t\t= 303,\n\tVRInitError_IPC_MutexInitFailed\t\t\t\t\t= 304,\n\tVRInitError_IPC_Failed\t\t\t\t\t\t\t= 305,\n\tVRInitError_IPC_CompositorConnectFailed\t\t\t= 306,\n\tVRInitError_IPC_CompositorInvalidConnectResponse = 307,\n\tVRInitError_IPC_ConnectFailedAfterMultipleAttempts = 308,\n\tVRInitError_IPC_ConnectFailedAfterTargetExited = 309,\n\tVRInitError_IPC_NamespaceUnavailable\t\t\t = 310,\n\n\tVRInitError_Compositor_Failed\t\t\t\t\t\t\t\t\t\t\t\t= 400,\n\tVRInitError_Compositor_D3D11HardwareRequired\t\t\t\t\t\t\t\t= 401,\n\tVRInitError_Compositor_FirmwareRequiresUpdate\t\t\t\t\t\t\t\t= 402,\n\tVRInitError_Compositor_OverlayInitFailed\t\t\t\t\t\t\t\t\t= 403,\n\tVRInitError_Compositor_ScreenshotsInitFailed\t\t\t\t\t\t\t\t= 404,\n\tVRInitError_Compositor_UnableToCreateDevice\t\t\t\t\t\t\t\t\t= 405,\n\tVRInitError_Compositor_SharedStateIsNull\t\t\t\t\t\t\t\t\t= 406,\n\tVRInitError_Compositor_NotificationManagerIsNull\t\t\t\t\t\t\t= 407,\n\tVRInitError_Compositor_ResourceManagerClientIsNull\t\t\t\t\t\t\t= 408,\n\tVRInitError_Compositor_MessageOverlaySharedStateInitFailure\t\t\t\t\t= 409,\n\tVRInitError_Compositor_PropertiesInterfaceIsNull\t\t\t\t\t\t\t= 410,\n\tVRInitError_Compositor_CreateFullscreenWindowFailed\t\t\t\t\t\t\t= 411,\n\tVRInitError_Compositor_SettingsInterfaceIsNull\t\t\t\t\t\t\t\t= 412,\n\tVRInitError_Compositor_FailedToShowWindow\t\t\t\t\t\t\t\t\t= 413,\n\tVRInitError_Compositor_DistortInterfaceIsNull\t\t\t\t\t\t\t\t= 414,\n\tVRInitError_Compositor_DisplayFrequencyFailure\t\t\t\t\t\t\t\t= 415,\n\tVRInitError_Compositor_RendererInitializationFailed\t\t\t\t\t\t\t= 416,\n\tVRInitError_Compositor_DXGIFactoryInterfaceIsNull\t\t\t\t\t\t\t= 417,\n\tVRInitError_Compositor_DXGIFactoryCreateFailed\t\t\t\t\t\t\t\t= 418,\n\tVRInitError_Compositor_DXGIFactoryQueryFailed\t\t\t\t\t\t\t\t= 419,\n\tVRInitError_Compositor_InvalidAdapterDesktop\t\t\t\t\t\t\t\t= 420,\n\tVRInitError_Compositor_InvalidHmdAttachment\t\t\t\t\t\t\t\t\t= 421,\n\tVRInitError_Compositor_InvalidOutputDesktop\t\t\t\t\t\t\t\t\t= 422,\n\tVRInitError_Compositor_InvalidDeviceProvided\t\t\t\t\t\t\t\t= 423,\n\tVRInitError_Compositor_D3D11RendererInitializationFailed\t\t\t\t\t= 424,\n\tVRInitError_Compositor_FailedToFindDisplayMode\t\t\t\t\t\t\t\t= 425,\n\tVRInitError_Compositor_FailedToCreateSwapChain\t\t\t\t\t\t\t\t= 426,\n\tVRInitError_Compositor_FailedToGetBackBuffer\t\t\t\t\t\t\t\t= 427,\n\tVRInitError_Compositor_FailedToCreateRenderTarget\t\t\t\t\t\t\t= 428,\n\tVRInitError_Compositor_FailedToCreateDXGI2SwapChain\t\t\t\t\t\t\t= 429,\n\tVRInitError_Compositor_FailedtoGetDXGI2BackBuffer\t\t\t\t\t\t\t= 430,\n\tVRInitError_Compositor_FailedToCreateDXGI2RenderTarget\t\t\t\t\t\t= 431,\n\tVRInitError_Compositor_FailedToGetDXGIDeviceInterface\t\t\t\t\t\t= 432,\n\tVRInitError_Compositor_SelectDisplayMode\t\t\t\t\t\t\t\t\t= 433,\n\tVRInitError_Compositor_FailedToCreateNvAPIRenderTargets\t\t\t\t\t\t= 434,\n\tVRInitError_Compositor_NvAPISetDisplayMode\t\t\t\t\t\t\t\t\t= 435,\n\tVRInitError_Compositor_FailedToCreateDirectModeDisplay\t\t\t\t\t\t= 436,\n\tVRInitError_Compositor_InvalidHmdPropertyContainer\t\t\t\t\t\t\t= 437,\n\tVRInitError_Compositor_UpdateDisplayFrequency\t\t\t\t\t\t\t\t= 438,\n\tVRInitError_Compositor_CreateRasterizerState\t\t\t\t\t\t\t\t= 439,\n\tVRInitError_Compositor_CreateWireframeRasterizerState\t\t\t\t\t\t= 440,\n\tVRInitError_Compositor_CreateSamplerState\t\t\t\t\t\t\t\t\t= 441,\n\tVRInitError_Compositor_CreateClampToBorderSamplerState\t\t\t\t\t\t= 442,\n\tVRInitError_Compositor_CreateAnisoSamplerState\t\t\t\t\t\t\t\t= 443,\n\tVRInitError_Compositor_CreateOverlaySamplerState\t\t\t\t\t\t\t= 444,\n\tVRInitError_Compositor_CreatePanoramaSamplerState\t\t\t\t\t\t\t= 445,\n\tVRInitError_Compositor_CreateFontSamplerState\t\t\t\t\t\t\t\t= 446,\n\tVRInitError_Compositor_CreateNoBlendState\t\t\t\t\t\t\t\t\t= 447,\n\tVRInitError_Compositor_CreateBlendState\t\t\t\t\t\t\t\t\t\t= 448,\n\tVRInitError_Compositor_CreateAlphaBlendState\t\t\t\t\t\t\t\t= 449,\n\tVRInitError_Compositor_CreateBlendStateMaskR\t\t\t\t\t\t\t\t= 450,\n\tVRInitError_Compositor_CreateBlendStateMaskG\t\t\t\t\t\t\t\t= 451,\n\tVRInitError_Compositor_CreateBlendStateMaskB\t\t\t\t\t\t\t\t= 452,\n\tVRInitError_Compositor_CreateDepthStencilState\t\t\t\t\t\t\t\t= 453,\n\tVRInitError_Compositor_CreateDepthStencilStateNoWrite\t\t\t\t\t\t= 454,\n\tVRInitError_Compositor_CreateDepthStencilStateNoDepth\t\t\t\t\t\t= 455,\n\tVRInitError_Compositor_CreateFlushTexture\t\t\t\t\t\t\t\t\t= 456,\n\tVRInitError_Compositor_CreateDistortionSurfaces\t\t\t\t\t\t\t\t= 457,\n\tVRInitError_Compositor_CreateConstantBuffer\t\t\t\t\t\t\t\t\t= 458,\n\tVRInitError_Compositor_CreateHmdPoseConstantBuffer\t\t\t\t\t\t\t= 459,\n\tVRInitError_Compositor_CreateHmdPoseStagingConstantBuffer\t\t\t\t\t= 460,\n\tVRInitError_Compositor_CreateSharedFrameInfoConstantBuffer\t\t\t\t\t= 461,\n\tVRInitError_Compositor_CreateOverlayConstantBuffer\t\t\t\t\t\t\t= 462,\n\tVRInitError_Compositor_CreateSceneTextureIndexConstantBuffer\t\t\t\t= 463,\n\tVRInitError_Compositor_CreateReadableSceneTextureIndexConstantBuffer\t\t= 464,\n\tVRInitError_Compositor_CreateLayerGraphicsTextureIndexConstantBuffer\t\t= 465,\n\tVRInitError_Compositor_CreateLayerComputeTextureIndexConstantBuffer\t\t\t= 466,\n\tVRInitError_Compositor_CreateLayerComputeSceneTextureIndexConstantBuffer\t= 467,\n\tVRInitError_Compositor_CreateComputeHmdPoseConstantBuffer\t\t\t\t\t= 468,\n\tVRInitError_Compositor_CreateGeomConstantBuffer\t\t\t\t\t\t\t\t= 469,\n\tVRInitError_Compositor_CreatePanelMaskConstantBuffer\t\t\t\t\t\t= 470,\n\tVRInitError_Compositor_CreatePixelSimUBO\t\t\t\t\t\t\t\t\t= 471,\n\tVRInitError_Compositor_CreateMSAARenderTextures\t\t\t\t\t\t\t\t= 472,\n\tVRInitError_Compositor_CreateResolveRenderTextures\t\t\t\t\t\t\t= 473,\n\tVRInitError_Compositor_CreateComputeResolveRenderTextures\t\t\t\t\t= 474,\n\tVRInitError_Compositor_CreateDriverDirectModeResolveTextures\t\t\t\t= 475,\n\tVRInitError_Compositor_OpenDriverDirectModeResolveTextures\t\t\t\t\t= 476,\n\tVRInitError_Compositor_CreateFallbackSyncTexture\t\t\t\t\t\t\t= 477,\n\tVRInitError_Compositor_ShareFallbackSyncTexture\t\t\t\t\t\t\t\t= 478,\n\tVRInitError_Compositor_CreateOverlayIndexBuffer\t\t\t\t\t\t\t\t= 479,\n\tVRInitError_Compositor_CreateOverlayVertexBuffer\t\t\t\t\t\t\t= 480,\n\tVRInitError_Compositor_CreateTextVertexBuffer\t\t\t\t\t\t\t\t= 481,\n\tVRInitError_Compositor_CreateTextIndexBuffer\t\t\t\t\t\t\t\t= 482,\n\tVRInitError_Compositor_CreateMirrorTextures\t\t\t\t\t\t\t\t\t= 483,\n\tVRInitError_Compositor_CreateLastFrameRenderTexture\t\t\t\t\t\t\t= 484,\n\tVRInitError_Compositor_CreateMirrorOverlay\t\t\t\t\t\t\t\t\t= 485,\n\tVRInitError_Compositor_FailedToCreateVirtualDisplayBackbuffer\t\t\t\t= 486,\n\tVRInitError_Compositor_DisplayModeNotSupported\t\t\t\t\t\t\t\t= 487,\n\tVRInitError_Compositor_CreateOverlayInvalidCall\t\t\t\t\t\t\t\t= 488,\n\tVRInitError_Compositor_CreateOverlayAlreadyInitialized\t\t\t\t\t\t= 489,\n\tVRInitError_Compositor_FailedToCreateMailbox\t\t\t\t\t\t\t\t= 490,\n\tVRInitError_Compositor_WindowInterfaceIsNull\t\t\t\t\t\t\t\t= 491,\n\tVRInitError_Compositor_SystemLayerCreateInstance\t\t\t\t\t\t\t= 492,\n\tVRInitError_Compositor_SystemLayerCreateSession\t\t\t\t\t\t\t\t= 493,\n\tVRInitError_Compositor_CreateInverseDistortUVs\t\t\t\t\t\t\t\t= 494,\n\tVRInitError_Compositor_CreateBackbufferDepth\t\t\t\t\t\t\t\t= 495,\n\tVRInitError_Compositor_CannotDRMLeaseDisplay\t\t\t\t\t\t\t\t= 496,\n\tVRInitError_Compositor_CannotConnectToDisplayServer\t\t\t\t\t\t\t= 497,\n\tVRInitError_Compositor_GnomeNoDRMLeasing\t\t\t\t\t\t\t\t\t= 498,\n\tVRInitError_Compositor_FailedToInitializeEncoder\t\t\t\t\t\t\t= 499,\n\tVRInitError_Compositor_CreateBlurTexture\t\t\t\t\t\t\t\t\t= 500,\n\n\tVRInitError_VendorSpecific_UnableToConnectToOculusRuntime\t\t= 1000,\n\tVRInitError_VendorSpecific_WindowsNotInDevMode\t\t\t\t\t= 1001,\n\tVRInitError_VendorSpecific_OculusLinkNotEnabled\t\t\t\t\t= 1002,\n\n\tVRInitError_VendorSpecific_HmdFound_CantOpenDevice \t\t\t\t= 1101,\n\tVRInitError_VendorSpecific_HmdFound_UnableToRequestConfigStart\t= 1102,\n\tVRInitError_VendorSpecific_HmdFound_NoStoredConfig \t\t\t\t= 1103,\n\tVRInitError_VendorSpecific_HmdFound_ConfigTooBig \t\t\t\t= 1104,\n\tVRInitError_VendorSpecific_HmdFound_ConfigTooSmall \t\t\t\t= 1105,\n\tVRInitError_VendorSpecific_HmdFound_UnableToInitZLib \t\t\t= 1106,\n\tVRInitError_VendorSpecific_HmdFound_CantReadFirmwareVersion \t= 1107,\n\tVRInitError_VendorSpecific_HmdFound_UnableToSendUserDataStart\t= 1108,\n\tVRInitError_VendorSpecific_HmdFound_UnableToGetUserDataStart\t= 1109,\n\tVRInitError_VendorSpecific_HmdFound_UnableToGetUserDataNext\t\t= 1110,\n\tVRInitError_VendorSpecific_HmdFound_UserDataAddressRange\t\t= 1111,\n\tVRInitError_VendorSpecific_HmdFound_UserDataError\t\t\t\t= 1112,\n\tVRInitError_VendorSpecific_HmdFound_ConfigFailedSanityCheck\t\t= 1113,\n\tVRInitError_VendorSpecific_OculusRuntimeBadInstall\t\t\t\t= 1114,\n\tVRInitError_VendorSpecific_HmdFound_UnexpectedConfiguration_1\t= 1115,\n\n\tVRInitError_VendorSpecific_Oasis_UnlockRequired\t\t\t\t\t= 1150,\n\n\tVRInitError_VendorSpecific_VRLink_OutdatedDriverMESA\t\t\t= 1200,\n\tVRInitError_VendorSpecific_VRLink_OutdatedDriverNVIDIA\t\t\t= 1201,\n\tVRInitError_VendorSpecific_VRLink_NoVideoSupport\t\t\t\t= 1202,\n\n\tVRInitError_Steam_SteamInstallationNotFound = 2000,\n\n\t// Strictly a placeholder\n\tVRInitError_LastError\n};\n\nenum EVRScreenshotType\n{\n\tVRScreenshotType_None = 0,\n\tVRScreenshotType_Mono = 1, // left eye only\n\tVRScreenshotType_Stereo = 2,\n\tVRScreenshotType_Cubemap = 3,\n\tVRScreenshotType_MonoPanorama = 4,\n\tVRScreenshotType_StereoPanorama = 5\n};\n\nenum EVRScreenshotPropertyFilenames\n{\n\tVRScreenshotPropertyFilenames_Preview = 0,\n\tVRScreenshotPropertyFilenames_VR = 1,\n};\n\nenum EVRTrackedCameraError\n{\n\tVRTrackedCameraError_None                       = 0,\n\tVRTrackedCameraError_OperationFailed            = 100,\n\tVRTrackedCameraError_InvalidHandle              = 101,\n\tVRTrackedCameraError_InvalidFrameHeaderVersion  = 102,\n\tVRTrackedCameraError_OutOfHandles               = 103,\n\tVRTrackedCameraError_IPCFailure                 = 104,\n\tVRTrackedCameraError_NotSupportedForThisDevice  = 105,\n\tVRTrackedCameraError_SharedMemoryFailure        = 106,\n\tVRTrackedCameraError_FrameBufferingFailure      = 107,\n\tVRTrackedCameraError_StreamSetupFailure         = 108,\n\tVRTrackedCameraError_InvalidGLTextureId         = 109,\n\tVRTrackedCameraError_InvalidSharedTextureHandle = 110,\n\tVRTrackedCameraError_FailedToGetGLTextureId     = 111,\n\tVRTrackedCameraError_SharedTextureFailure       = 112,\n\tVRTrackedCameraError_NoFrameAvailable           = 113,\n\tVRTrackedCameraError_InvalidArgument            = 114,\n\tVRTrackedCameraError_InvalidFrameBufferSize     = 115,\n};\n\nenum EVRTrackedCameraFrameLayout\n{\n\tEVRTrackedCameraFrameLayout_Mono\t\t\t\t= 0x0001,\n\tEVRTrackedCameraFrameLayout_Stereo\t\t\t\t= 0x0002,\n\tEVRTrackedCameraFrameLayout_VerticalLayout\t\t= 0x0010,\t// Stereo frames are Top/Bottom (left/right)\n\tEVRTrackedCameraFrameLayout_HorizontalLayout\t= 0x0020,\t// Stereo frames are Left/Right\n};\n\nenum EVRTrackedCameraFrameType\n{\n\tVRTrackedCameraFrameType_Distorted = 0,\t\t\t// This is the camera video frame size in pixels, still distorted.\n\tVRTrackedCameraFrameType_Undistorted,\t\t\t// In pixels, an undistorted inscribed rectangle region without invalid regions. This size is subject to changes shortly.\n\tVRTrackedCameraFrameType_MaximumUndistorted,\t// In pixels, maximum undistorted with invalid regions. Non zero alpha component identifies valid regions.\n\tMAX_CAMERA_FRAME_TYPES\n};\n\nenum EVRDistortionFunctionType\n{\n\tVRDistortionFunctionType_None,\n\tVRDistortionFunctionType_FTheta,\n\tVRDistortionFunctionType_Extended_FTheta,\n\tMAX_DISTORTION_FUNCTION_TYPES,\n};\n\nstatic const uint32_t k_unMaxDistortionFunctionParameters = 8;\n\ntypedef uint64_t TrackedCameraHandle_t;\n#define INVALID_TRACKED_CAMERA_HANDLE\t((vr::TrackedCameraHandle_t)0)\n\nstruct CameraVideoStreamFrameHeader_t\n{\n\tEVRTrackedCameraFrameType eFrameType;\n\n\tuint32_t nWidth;\n\tuint32_t nHeight;\n\tuint32_t nBytesPerPixel;\n\n\tuint32_t nFrameSequence;\n\n\tTrackedDevicePose_t trackedDevicePose;\n\n\tuint64_t ulFrameExposureTime;\t\t\t\t\t\t// mid-point of the exposure of the image in host system ticks\n};\n\n// Screenshot types\ntypedef uint32_t ScreenshotHandle_t;\n\nstatic const uint32_t k_unScreenshotHandleInvalid = 0;\n\n/** Compositor frame timing reprojection flags. */\nconst uint32_t VRCompositor_ReprojectionReason_Cpu = 0x01;\nconst uint32_t VRCompositor_ReprojectionReason_Gpu = 0x02;\nconst uint32_t VRCompositor_ReprojectionAsync = 0x04;\t\t// This flag indicates the async reprojection mode is active,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t// but does not indicate if reprojection actually happened or not.\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t// Use the ReprojectionReason flags above to check if reprojection\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t// was actually applied (i.e. scene texture was reused).\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t// NumFramePresents > 1 also indicates the scene texture was reused,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t// and also the number of times that it was presented in total.\n\nconst uint32_t VRCompositor_ReprojectionMotion = 0x08;\t\t// This flag indicates whether or not motion smoothing was triggered for this frame\n\nconst uint32_t VRCompositor_PredictionMask = 0xF0;\t\t\t// The runtime may predict more than one frame ahead if\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t// it detects the application is taking too long to render. These\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t// bits will contain the count of additional frames (normally zero).\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t// Use the VR_COMPOSITOR_ADDITIONAL_PREDICTED_FRAMES macro to read from\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t// the latest frame timing entry.\n\nconst uint32_t VRCompositor_ThrottleMask = 0xF00;\t\t\t// Number of frames the compositor is throttling the application.\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t// Use the VR_COMPOSITOR_NUMBER_OF_THROTTLED_FRAMES macro to read from\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t// the latest frame timing entry.\n\n#define VR_COMPOSITOR_ADDITIONAL_PREDICTED_FRAMES( timing ) ( ( ( timing ).m_nReprojectionFlags & vr::VRCompositor_PredictionMask ) >> 4 )\n#define VR_COMPOSITOR_NUMBER_OF_THROTTLED_FRAMES( timing ) ( ( ( timing ).m_nReprojectionFlags & vr::VRCompositor_ThrottleMask ) >> 8 )\n\n#if defined(__linux__) || defined(__APPLE__)\n#pragma pack( push, 4 )\n#endif\n/** Provides a single frame's timing information to the app */\nstruct Compositor_FrameTiming\n{\n\tuint32_t m_nSize; // Set to sizeof( Compositor_FrameTiming )\n\tuint32_t m_nFrameIndex;\n\tuint32_t m_nNumFramePresents; // number of times this frame was presented\n\tuint32_t m_nNumMisPresented; // number of times this frame was presented on a vsync other than it was originally predicted to\n\tuint32_t m_nNumDroppedFrames; // number of additional times previous frame was scanned out\n\tuint32_t m_nReprojectionFlags;\n\n\t/** Absolute time reference for comparing frames.  This aligns with the vsync that running start is relative to. */\n\tdouble m_flSystemTimeInSeconds;\n\n\t/** These times may include work from other processes due to OS scheduling.\n\t* The fewer packets of work these are broken up into, the less likely this will happen.\n\t* GPU work can be broken up by calling Flush.  This can sometimes be useful to get the GPU started\n\t* processing that work earlier in the frame. */\n\tfloat m_flPreSubmitGpuMs; // time spent rendering the scene (gpu work submitted between WaitGetPoses and second Submit)\n\tfloat m_flPostSubmitGpuMs; // additional time spent rendering by application (e.g. companion window)\n\tfloat m_flTotalRenderGpuMs; // time between work submitted immediately after present (ideally vsync) until the end of compositor submitted work\n\tfloat m_flCompositorRenderGpuMs; // time spend performing distortion correction, rendering chaperone, overlays, etc.\n\tfloat m_flCompositorRenderCpuMs; // time spent on cpu submitting the above work for this frame\n\tfloat m_flCompositorIdleCpuMs; // time spent waiting for running start (application could have used this much more time)\n\n\t/** Miscellaneous measured intervals. */\n\tfloat m_flClientFrameIntervalMs; // time between calls to WaitGetPoses\n\tfloat m_flPresentCallCpuMs; // time blocked on call to present (usually 0.0, but can go long)\n\tfloat m_flWaitForPresentCpuMs; // time spent spin-waiting for frame index to change (not near-zero indicates wait object failure)\n\tfloat m_flSubmitFrameMs; // time spent in IVRCompositor::Submit (not near-zero indicates driver issue)\n\n\t/** The following are all relative to this frame's SystemTimeInSeconds */\n\tfloat m_flWaitGetPosesCalledMs;\n\tfloat m_flNewPosesReadyMs;\n\tfloat m_flNewFrameReadyMs; // second call to IVRCompositor::Submit\n\tfloat m_flCompositorUpdateStartMs;\n\tfloat m_flCompositorUpdateEndMs;\n\tfloat m_flCompositorRenderStartMs;\n\n\tvr::TrackedDevicePose_t m_HmdPose; // pose used by app to render this frame\n\n\tuint32_t m_nNumVSyncsReadyForUse;\n\tuint32_t m_nNumVSyncsToFirstView;\n\n\tfloat m_flTransferLatencyMs;\n};\n#if defined(__linux__) || defined(__APPLE__)\n#pragma pack( pop )\n#endif\n\n/** Provides compositor benchmark results to the app */\nstruct Compositor_BenchmarkResults\n{\n\tfloat m_flMegaPixelsPerSecond; // Measurement of GPU MP/s performed by compositor benchmark\n\tfloat m_flHmdRecommendedMegaPixelsPerSecond; // Recommended default MP/s given the HMD resolution, refresh, and panel mask.\n};\n\n/** Frame timing data provided by direct mode drivers. */\nstruct DriverDirectMode_FrameTiming\n{\n\tuint32_t m_nSize; // Set to sizeof( DriverDirectMode_FrameTiming )\n\tuint32_t m_nNumFramePresents; // number of times frame was presented\n\tuint32_t m_nNumMisPresented; // number of times frame was presented on a vsync other than it was originally predicted to\n\tuint32_t m_nNumDroppedFrames; // number of additional times previous frame was scanned out (i.e. compositor missed vsync)\n\tuint32_t m_nReprojectionFlags;\n};\n\n/** These flags will be set on DriverDirectMode_FrameTiming::m_nReprojectionFlags when IVRDriverDirectModeComponent::GetFrameTiming is called for drivers to optionally respond to. */\nconst uint32_t VRCompositor_ReprojectionMotion_Enabled = 0x100; // Motion Smoothing is enabled in the UI for the currently running application\nconst uint32_t VRCompositor_ReprojectionMotion_ForcedOn = 0x200; // Motion Smoothing is forced on in the UI for the currently running application\nconst uint32_t VRCompositor_ReprojectionMotion_AppThrottled = 0x400; // Application is requesting throttling via ForceInterleavedReprojectionOn\n\n\nenum EVSync\n{\n\tVSync_None,\n\tVSync_WaitRender,\t// block following render work until vsync\n\tVSync_NoWaitRender,\t// do not block following render work (allow to get started early)\n};\n\nenum EVRMuraCorrectionMode\n{\n\tEVRMuraCorrectionMode_Default = 0,\n\tEVRMuraCorrectionMode_NoCorrection\n};\n\n/** raw IMU data provided by IVRIOBuffer from paths to tracked devices with IMUs */\nenum Imu_OffScaleFlags\n{\n\tOffScale_AccelX\t= 0x01,\n\tOffScale_AccelY\t= 0x02,\n\tOffScale_AccelZ\t= 0x04,\n\tOffScale_GyroX\t= 0x08,\n\tOffScale_GyroY\t= 0x10,\n\tOffScale_GyroZ\t= 0x20,\n};\n\nstruct ImuSample_t\n{\n\tdouble fSampleTime;\n\tHmdVector3d_t vAccel;\n\tHmdVector3d_t vGyro;\n\tuint32_t unOffScaleFlags;\n};\n\nenum class EVRDistortionChannel : uint32_t\n{\n\tRed = 0,\t\t// given a coordinate in distorted panel space, returns the coordinate to sample in rectilinear render space for the red channel\n\tGreen,\t\t\t// given a coordinate in distorted panel space, returns the coordinate to sample in rectilinear render space for the green channel\n\tBlue,\t\t\t// given a coordinate in distorted panel space, returns the coordinate to sample in rectilinear render space for the blue channel\n\tInverseRed,\t\t// given a coordinate in rectilinear render space, returns the corresponding coordinate in distorted panel space for the red channel\n\tInverseGreen,\t// given a coordinate in rectilinear render space, returns the corresponding coordinate in distorted panel space for the green channel\n\tInverseBlue,\t// given a coordinate in rectilinear render space, returns the corresponding coordinate in distorted panel space for the blue channel\n\tCount\n};\n\nstruct DistortionCoordinate_t\n{\n\tfloat u, v; // 0..1\n};\n\n#pragma pack( pop )\n\n// figure out how to import from the VR API dll\n#if defined(_WIN32)\n\n  #if !defined(OPENVR_BUILD_STATIC)\n    #ifdef VR_API_EXPORT\n      #define VR_INTERFACE extern \"C\" __declspec( dllexport )\n    #else\n      #define VR_INTERFACE extern \"C\" __declspec( dllimport )\n    #endif\n  #else\n    #define VR_INTERFACE extern \"C\"\n  #endif\n\n#elif defined(__GNUC__) || defined(COMPILER_GCC) || defined(__APPLE__)\n\n#ifdef VR_API_EXPORT\n  #define VR_INTERFACE extern \"C\" __attribute__((visibility(\"default\")))\n#else\n  #define VR_INTERFACE extern \"C\"\n#endif\n\n#else\n  #error \"Unsupported Platform.\"\n#endif\n\n\n#if defined( _WIN32 )\n  #define VR_CALLTYPE __cdecl\n#else\n  #define VR_CALLTYPE\n#endif\n\n} // namespace vr\n#endif // _INCLUDE_VRTYPES_H\n\n// vrannotation.h\n\n#ifdef API_GEN\n# define VR_CLANG_ATTR(ATTR) __attribute__((annotate( ATTR )))\n#else\n# define VR_CLANG_ATTR(ATTR)\n#endif\n\n#define VR_METHOD_DESC(DESC) VR_CLANG_ATTR( \"desc:\" #DESC \";\" )\n#define VR_IGNOREATTR() VR_CLANG_ATTR( \"ignore\" )\n#define VR_OUT_STRUCT() VR_CLANG_ATTR( \"out_struct: ;\" )\n#define VR_OUT_STRING() VR_CLANG_ATTR( \"out_string: ;\" )\n#define VR_OUT_ARRAY_CALL(COUNTER,FUNCTION,PARAMS) VR_CLANG_ATTR( \"out_array_call:\" #COUNTER \",\" #FUNCTION \",\" #PARAMS \";\" )\n#define VR_OUT_ARRAY_COUNT(COUNTER) VR_CLANG_ATTR( \"out_array_count:\" #COUNTER \";\" )\n#define VR_ARRAY_COUNT(COUNTER) VR_CLANG_ATTR( \"array_count:\" #COUNTER \";\" )\n#define VR_ARRAY_COUNT_D(COUNTER, DESC) VR_CLANG_ATTR( \"array_count:\" #COUNTER \";desc:\" #DESC )\n#define VR_BUFFER_COUNT(COUNTER) VR_CLANG_ATTR( \"buffer_count:\" #COUNTER \";\" )\n#define VR_OUT_BUFFER_COUNT(COUNTER) VR_CLANG_ATTR( \"out_buffer_count:\" #COUNTER \";\" )\n#define VR_OUT_STRING_COUNT(COUNTER) VR_CLANG_ATTR( \"out_string_count:\" #COUNTER \";\" )\n\n// ivrsystem.h\n\nnamespace vr\n{\n\nclass IVRSystem\n{\npublic:\n\n\n\t// ------------------------------------\n\t// Display Methods\n\t// ------------------------------------\n\n\t/** Suggested size for the intermediate render target that the distortion pulls from. */\n\tvirtual void GetRecommendedRenderTargetSize( uint32_t *pnWidth, uint32_t *pnHeight ) = 0;\n\n\t/** The projection matrix for the specified eye */\n\tvirtual HmdMatrix44_t GetProjectionMatrix( EVREye eEye, float fNearZ, float fFarZ ) = 0;\n\n\t/** The components necessary to build your own projection matrix in case your\n\t* application is doing something fancy like infinite Z */\n\tvirtual void GetProjectionRaw( EVREye eEye, float *pfLeft, float *pfRight, float *pfTop, float *pfBottom ) = 0;\n\n\t/** Gets the result of the distortion function for the specified eye and input UVs. UVs go from 0,0 in\n\t* the upper left of that eye's viewport and 1,1 in the lower right of that eye's viewport.\n\t* Returns true for success. Otherwise, returns false, and distortion coordinates are not suitable. */\n\tvirtual bool ComputeDistortion( EVREye eEye, float fU, float fV, DistortionCoordinates_t *pDistortionCoordinates ) = 0;\n\n\t/** Gets the result of the distortion functions for the specified eye and set of input UVs.\n\t * nNumCoordinates must be the number of elements of pInput and pOutput.\n\t * Returns true for success. Otherwise, returns false, and distortion coordinates are not suitable. */\n\tvirtual bool ComputeDistortionSet( EVREye eEye, EVRDistortionChannel eChannel, bool bAsNormalizedDeviceCoordinates,\n\t\tuint32_t nNumCoordinates, const DistortionCoordinate_t *pInput, DistortionCoordinate_t *pOutput ) = 0;\n\n\t/** Returns the transform from eye space to the head space. Eye space is the per-eye flavor of head\n\t* space that provides stereo disparity. Instead of Model * View * Projection the sequence is Model * View * Eye^-1 * Projection.\n\t* Normally View and Eye^-1 will be multiplied together and treated as View in your application.\n\t*/\n\tvirtual HmdMatrix34_t GetEyeToHeadTransform( EVREye eEye ) = 0;\n\n\t/** Returns the number of elapsed seconds since the last recorded vsync event. This\n\t*\twill come from a vsync timer event in the timer if possible or from the application-reported\n\t*   time if that is not available. If no vsync times are available the function will\n\t*   return zero for vsync time and frame counter and return false from the method. */\n\tvirtual bool GetTimeSinceLastVsync( float *pfSecondsSinceLastVsync, uint64_t *pulFrameCounter ) = 0;\n\n\t/** [D3D9 Only]\n\t* Returns the adapter index that the user should pass into CreateDevice to set up D3D9 in such\n\t* a way that it can go full screen exclusive on the HMD. Returns -1 if there was an error.\n\t*/\n\tvirtual int32_t GetD3D9AdapterIndex() = 0;\n\n\t/** [D3D10/11 Only]\n\t* Returns the adapter index that the user should pass into EnumAdapters to create the device\n\t* and swap chain in DX10 and DX11. If an error occurs the index will be set to -1.\n\t*/\n\tvirtual void GetDXGIOutputInfo( int32_t *pnAdapterIndex ) = 0;\n\n\t/**\n\t * Returns platform- and texture-type specific adapter identification so that applications and the\n\t * compositor are creating textures and swap chains on the same GPU. If an error occurs the device\n\t * will be set to 0.\n\t * pInstance is an optional parameter that is required only when textureType is TextureType_Vulkan.\n\t * [D3D10/11/12 Only (D3D9 Not Supported)]\n\t *  Returns the adapter LUID that identifies the GPU attached to the HMD. The user should\n\t *  enumerate all adapters using IDXGIFactory::EnumAdapters and IDXGIAdapter::GetDesc to find\n\t *  the adapter with the matching LUID, or use IDXGIFactory4::EnumAdapterByLuid.\n\t *  The discovered IDXGIAdapter should be used to create the device and swap chain.\n\t * [Vulkan Only]\n\t *  Returns the VkPhysicalDevice that should be used by the application.\n\t *  pInstance must be the instance the application will use to query for the VkPhysicalDevice.  The application\n\t *  must create the VkInstance with extensions returned by IVRCompositor::GetVulkanInstanceExtensionsRequired enabled.\n\t * [macOS Only]\n\t *  For TextureType_IOSurface returns the id<MTLDevice> that should be used by the application.\n\t *  On 10.13+ for TextureType_OpenGL returns the 'registryId' of the renderer which should be used\n\t *   by the application. See Apple Technical Q&A QA1168 for information on enumerating GL Renderers, and the\n\t *   new kCGLRPRegistryIDLow and kCGLRPRegistryIDHigh CGLRendererProperty values in the 10.13 SDK.\n\t *  Pre 10.13 for TextureType_OpenGL returns 0, as there is no dependable way to correlate the HMDs MTLDevice\n\t *   with a GL Renderer.\n\t */\n\tvirtual void GetOutputDevice( uint64_t *pnDevice, ETextureType textureType, VkInstance_T *pInstance = nullptr ) = 0;\n\n\t// ------------------------------------\n\t// Display Mode methods\n\t// ------------------------------------\n\n\t/** Use to determine if the headset display is part of the desktop (i.e. extended) or hidden (i.e. direct mode). */\n\tvirtual bool IsDisplayOnDesktop() = 0;\n\n\t/** Set the display visibility (true = extended, false = direct mode).  Return value of true indicates that the change was successful. */\n\tvirtual bool SetDisplayVisibility( bool bIsVisibleOnDesktop ) = 0;\n\n\t// ------------------------------------\n\t// Tracking Methods\n\t// ------------------------------------\n\n\t/** The pose that the tracker thinks that the HMD will be in at the specified number of seconds into the\n\t* future. Pass 0 to get the state at the instant the method is called. Most of the time the application should\n\t* calculate the time until the photons will be emitted from the display and pass that time into the method.\n\t*\n\t* This is roughly analogous to the inverse of the view matrix in most applications, though\n\t* many games will need to do some additional rotation or translation on top of the rotation\n\t* and translation provided by the head pose.\n\t*\n\t* For devices where bPoseIsValid is true the application can use the pose to position the device\n\t* in question. The provided array can be any size up to k_unMaxTrackedDeviceCount.\n\t*\n\t* Seated experiences should call this method with TrackingUniverseSeated and receive poses relative\n\t* to the seated zero pose. Standing experiences should call this method with TrackingUniverseStanding\n\t* and receive poses relative to the Chaperone Play Area. TrackingUniverseRawAndUncalibrated should\n\t* probably not be used unless the application is the Chaperone calibration tool itself, but will provide\n\t* poses relative to the hardware-specific coordinate system in the driver.\n\t*/\n\tvirtual void GetDeviceToAbsoluteTrackingPose( ETrackingUniverseOrigin eOrigin, float fPredictedSecondsToPhotonsFromNow, VR_ARRAY_COUNT(unTrackedDevicePoseArrayCount) TrackedDevicePose_t *pTrackedDevicePoseArray, uint32_t unTrackedDevicePoseArrayCount ) = 0;\n\n\t/** Returns the transform from the seated zero pose to the standing absolute tracking system. This allows\n\t* applications to represent the seated origin to used or transform object positions from one coordinate\n\t* system to the other.\n\t*\n\t* The seated origin may or may not be inside the Play Area or Collision Bounds returned by IVRChaperone. Its position\n\t* depends on what the user has set from the Dashboard settings and previous calls to ResetSeatedZeroPose. */\n\tvirtual HmdMatrix34_t GetSeatedZeroPoseToStandingAbsoluteTrackingPose() = 0;\n\n\t/** Returns the transform from the tracking origin to the standing absolute tracking system. This allows\n\t* applications to convert from raw tracking space to the calibrated standing coordinate system. */\n\tvirtual HmdMatrix34_t GetRawZeroPoseToStandingAbsoluteTrackingPose() = 0;\n\n\t/** Get a sorted array of device indices of a given class of tracked devices (e.g. controllers).  Devices are sorted right to left\n\t* relative to the specified tracked device (default: hmd -- pass in -1 for absolute tracking space).  Returns the number of devices\n\t* in the list, or the size of the array needed if not large enough. */\n\tvirtual uint32_t GetSortedTrackedDeviceIndicesOfClass( ETrackedDeviceClass eTrackedDeviceClass, VR_ARRAY_COUNT(unTrackedDeviceIndexArrayCount) vr::TrackedDeviceIndex_t *punTrackedDeviceIndexArray, uint32_t unTrackedDeviceIndexArrayCount, vr::TrackedDeviceIndex_t unRelativeToTrackedDeviceIndex = k_unTrackedDeviceIndex_Hmd ) = 0;\n\n\t/** Returns the level of activity on the device. */\n\tvirtual EDeviceActivityLevel GetTrackedDeviceActivityLevel( vr::TrackedDeviceIndex_t unDeviceId ) = 0;\n\n\t/** Convenience utility to apply the specified transform to the specified pose.\n\t*   This properly transforms all pose components, including velocity and angular velocity\n\t*/\n\tvirtual void ApplyTransform( TrackedDevicePose_t *pOutputPose, const TrackedDevicePose_t *pTrackedDevicePose, const HmdMatrix34_t *pTransform ) = 0;\n\n\t/** Returns the device index associated with a specific role, for example the left hand or the right hand. This function is deprecated in favor of the new IVRInput system. */\n\tvirtual vr::TrackedDeviceIndex_t GetTrackedDeviceIndexForControllerRole( vr::ETrackedControllerRole unDeviceType ) = 0;\n\n\t/** Returns the controller type associated with a device index. This function is deprecated in favor of the new IVRInput system. */\n\tvirtual vr::ETrackedControllerRole GetControllerRoleForTrackedDeviceIndex( vr::TrackedDeviceIndex_t unDeviceIndex ) = 0;\n\n\t// ------------------------------------\n\t// Property methods\n\t// ------------------------------------\n\n\t/** Returns the device class of a tracked device. If there has not been a device connected in this slot\n\t* since the application started this function will return TrackedDevice_Invalid. For previous detected\n\t* devices the function will return the previously observed device class.\n\t*\n\t* To determine which devices exist on the system, just loop from 0 to k_unMaxTrackedDeviceCount and check\n\t* the device class. Every device with something other than TrackedDevice_Invalid is associated with an\n\t* actual tracked device. */\n\tvirtual ETrackedDeviceClass GetTrackedDeviceClass( vr::TrackedDeviceIndex_t unDeviceIndex ) = 0;\n\n\t/** Returns true if there is a device connected in this slot. */\n\tvirtual bool IsTrackedDeviceConnected( vr::TrackedDeviceIndex_t unDeviceIndex ) = 0;\n\n\t/** Returns a bool property. If the device index is not valid or the property is not a bool type this function will return false. */\n\tvirtual bool GetBoolTrackedDeviceProperty( vr::TrackedDeviceIndex_t unDeviceIndex, ETrackedDeviceProperty prop, ETrackedPropertyError *pError = 0L ) = 0;\n\n\t/** Returns a float property. If the device index is not valid or the property is not a float type this function will return 0. */\n\tvirtual float GetFloatTrackedDeviceProperty( vr::TrackedDeviceIndex_t unDeviceIndex, ETrackedDeviceProperty prop, ETrackedPropertyError *pError = 0L ) = 0;\n\n\t/** Returns an int property. If the device index is not valid or the property is not a int type this function will return 0. */\n\tvirtual int32_t GetInt32TrackedDeviceProperty( vr::TrackedDeviceIndex_t unDeviceIndex, ETrackedDeviceProperty prop, ETrackedPropertyError *pError = 0L ) = 0;\n\n\t/** Returns a uint64 property. If the device index is not valid or the property is not a uint64 type this function will return 0. */\n\tvirtual uint64_t GetUint64TrackedDeviceProperty( vr::TrackedDeviceIndex_t unDeviceIndex, ETrackedDeviceProperty prop, ETrackedPropertyError *pError = 0L ) = 0;\n\n\t/** Returns a matrix property. If the device index is not valid or the property is not a matrix type, this function will return identity. */\n\tvirtual HmdMatrix34_t GetMatrix34TrackedDeviceProperty( vr::TrackedDeviceIndex_t unDeviceIndex, ETrackedDeviceProperty prop, ETrackedPropertyError *pError = 0L ) = 0;\n\n\t/** Returns an array of one type of property. If the device index is not valid or the property is not a single value or an array of the specified type,\n\t* this function will return 0. Otherwise it returns the number of bytes necessary to hold the array of properties. If unBufferSize is\n\t* greater than the returned size and pBuffer is non-NULL, pBuffer is filled with the contents of array of properties. */\n\tvirtual uint32_t GetArrayTrackedDeviceProperty( vr::TrackedDeviceIndex_t unDeviceIndex, ETrackedDeviceProperty prop, PropertyTypeTag_t propType, void *pBuffer, uint32_t unBufferSize, ETrackedPropertyError *pError = 0L ) = 0;\n\n\t/** Returns a string property. If the device index is not valid or the property is not a string type this function will\n\t* return 0. Otherwise it returns the length of the number of bytes necessary to hold this string including the trailing\n\t* null. Strings will always fit in buffers of k_unMaxPropertyStringSize characters. */\n\tvirtual uint32_t GetStringTrackedDeviceProperty( vr::TrackedDeviceIndex_t unDeviceIndex, ETrackedDeviceProperty prop, VR_OUT_STRING() char *pchValue, uint32_t unBufferSize, ETrackedPropertyError *pError = 0L ) = 0;\n\n\t/** returns a string that corresponds with the specified property error. The string will be the name\n\t* of the error enum value for all valid error codes */\n\tvirtual const char *GetPropErrorNameFromEnum( ETrackedPropertyError error ) = 0;\n\n\t// ------------------------------------\n\t// Event methods\n\t// ------------------------------------\n\n\t/** Returns true and fills the event with the next event on the queue if there is one. If there are no events\n\t* this method returns false. uncbVREvent should be the size in bytes of the VREvent_t struct */\n\tvirtual bool PollNextEvent( VREvent_t *pEvent, uint32_t uncbVREvent ) = 0;\n\n\t/** Returns true and fills the event with the next event on the queue if there is one. If there are no events\n\t* this method returns false. Fills in the pose of the associated tracked device in the provided pose struct.\n\t* This pose will always be older than the call to this function and should not be used to render the device.\n\tuncbVREvent should be the size in bytes of the VREvent_t struct */\n\tvirtual bool PollNextEventWithPose( ETrackingUniverseOrigin eOrigin, VREvent_t *pEvent, uint32_t uncbVREvent, vr::TrackedDevicePose_t *pTrackedDevicePose ) = 0;\n\n\t/** Returns true and fills the event with the next event on the queue, including any of this user's overlay event queues,\n\t* if there are any. If there are no events this method returns false. uncbVREvent should be the size in bytes of the VREvent_t struct.\n\t* If the event is targeted at a specific overlay, *pulOverlayHandle will be set to the handle, else k_ulOverlayHandleInvalid.\n\t* This method is equivalent to calling both PollNextEventWithPose, and IVROverlay::PollNextOverlayEvent for every overlay you create,\n\t* but is more efficient. You may pass NULL for pTrackedDevicePose if you don't care about poses. You must pass a valid pointer for\n\t* pulOverlayHandle, because otherwise the target for some events (like ButtonPress) would be ambiguous even with one overlay.\n\t* If you call this, you should not call PollNextEvent/WithPose(), since they all share the same read pointer. */\n\tvirtual bool PollNextEventWithPoseAndOverlays( vr::ETrackingUniverseOrigin eOrigin, VREvent_t *pEvent, uint32_t uncbVREvent, TrackedDevicePose_t *pTrackedDevicePose, VROverlayHandle_t *pulOverlayHandle ) = 0;\n\n\t/** returns the name of an EVREvent enum value */\n\tvirtual const char *GetEventTypeNameFromEnum( EVREventType eType ) = 0;\n\n\t// ------------------------------------\n\t// Rendering helper methods\n\t// ------------------------------------\n\n\t/** Returns the hidden area mesh for the current HMD. The pixels covered by this mesh will never be seen by the user after the lens distortion is\n\t* applied based on visibility to the panels. If this HMD does not have a hidden area mesh, the vertex data and count will be NULL and 0 respectively.\n\t* This mesh is meant to be rendered into the stencil buffer (or into the depth buffer setting nearz) before rendering each eye's view.\n\t* This will improve performance by letting the GPU early-reject pixels the user will never see before running the pixel shader.\n\t* NOTE: Render this mesh with backface culling disabled since the winding order of the vertices can be different per-HMD or per-eye.\n\t* Setting the bInverse argument to true will produce the visible area mesh that is commonly used in place of full-screen quads. The visible area mesh covers all of the pixels the hidden area mesh does not cover.\n\t* Setting the bLineLoop argument will return a line loop of vertices in HiddenAreaMesh_t->pVertexData with HiddenAreaMesh_t->unTriangleCount set to the number of vertices.\n\t*/\n\tvirtual HiddenAreaMesh_t GetHiddenAreaMesh( EVREye eEye, EHiddenAreaMeshType type = k_eHiddenAreaMesh_Standard ) = 0;\n\n\t/** Provides per-eye NDC foveation centers based on eye tracking, using the projection matrices accessible\n\t* from IVRSystem::GetProjectionMatrix(...). Returns true if these NDC points are valid; false otherwise.\n\t* This API will return false on systems that do not have an eye tracker, or may transiently return false if a\n\t* system's eye tracker reports that eye tracking is invalid.\n\t*/\n\tvirtual bool GetEyeTrackedFoveationCenter( HmdVector2_t *pNdcLeft, HmdVector2_t *pNdcRight ) = 0;\n\n\t/** A variant of GetEyeTrackedFoveationCenter(...), where the caller specifies the projection matrix to use.\n\t* Use this if you need a foveation center for a projection matrix other than what is returned from IVRSystem::GetProjectionMatrix(...).\n\t*/\n\tvirtual bool GetEyeTrackedFoveationCenterForProjection( const HmdMatrix44_t *pProjMat, HmdVector2_t *pNdc ) = 0;\n\n\t// ------------------------------------\n\t// Controller methods\n\t// ------------------------------------\n\n\t/** Fills the supplied struct with the current state of the controller. Returns false if the controller index\n\t* is invalid. This function is deprecated in favor of the new IVRInput system. */\n\tvirtual bool GetControllerState( vr::TrackedDeviceIndex_t unControllerDeviceIndex, vr::VRControllerState_t *pControllerState, uint32_t unControllerStateSize ) = 0;\n\n\t/** fills the supplied struct with the current state of the controller and the provided pose with the pose of\n\t* the controller when the controller state was updated most recently. Use this form if you need a precise controller\n\t* pose as input to your application when the user presses or releases a button. This function is deprecated in favor of the new IVRInput system. */\n\tvirtual bool GetControllerStateWithPose( ETrackingUniverseOrigin eOrigin, vr::TrackedDeviceIndex_t unControllerDeviceIndex, vr::VRControllerState_t *pControllerState, uint32_t unControllerStateSize, TrackedDevicePose_t *pTrackedDevicePose ) = 0;\n\n\t/** Trigger a single haptic pulse on a controller. After this call the application may not trigger another haptic pulse on this controller\n\t* and axis combination for 5ms. This function is deprecated in favor of the new IVRInput system. */\n\tvirtual void TriggerHapticPulse( vr::TrackedDeviceIndex_t unControllerDeviceIndex, uint32_t unAxisId, unsigned short usDurationMicroSec ) = 0;\n\n\t/** returns the name of an EVRButtonId enum value. This function is deprecated in favor of the new IVRInput system.  */\n\tvirtual const char *GetButtonIdNameFromEnum( EVRButtonId eButtonId ) = 0;\n\n\t/** returns the name of an EVRControllerAxisType enum value. This function is deprecated in favor of the new IVRInput system. */\n\tvirtual const char *GetControllerAxisTypeNameFromEnum( EVRControllerAxisType eAxisType ) = 0;\n\n\t/** Returns true if this application is receiving input from the system. This would return false if\n\t* system-related functionality is consuming the input stream. */\n\tvirtual bool IsInputAvailable() = 0;\n\n\t/** Returns true SteamVR is drawing controllers on top of the application. Applications should consider\n\t* not drawing anything attached to the user's hands in this case. */\n\tvirtual bool IsSteamVRDrawingControllers() = 0;\n\n\t/** Returns true if the user has put SteamVR into a mode that is distracting them from the application.\n\t* For applications where this is appropriate, the application should pause ongoing activity. */\n\tvirtual bool ShouldApplicationPause() = 0;\n\n\t/** Returns true if SteamVR is doing significant rendering work and the game should do what it can to reduce\n\t* its own workload. One common way to do this is to reduce the size of the render target provided for each eye. */\n\tvirtual bool ShouldApplicationReduceRenderingWork() = 0;\n\n\t// ------------------------------------\n\t// Firmware methods\n\t// ------------------------------------\n\n\t/** Performs the actual firmware update if applicable.\n\t * The following events will be sent, if VRFirmwareError_None was returned: VREvent_FirmwareUpdateStarted, VREvent_FirmwareUpdateFinished\n\t * Use the properties Prop_Firmware_UpdateAvailable_Bool, Prop_Firmware_ManualUpdate_Bool, and Prop_Firmware_ManualUpdateURL_String\n\t * to figure our whether a firmware update is available, and to figure out whether its a manual update\n\t * Prop_Firmware_ManualUpdateURL_String should point to an URL describing the manual update process */\n\tvirtual vr::EVRFirmwareError PerformFirmwareUpdate( vr::TrackedDeviceIndex_t unDeviceIndex ) = 0;\n\n\t// ------------------------------------\n\t// Application life cycle methods\n\t// ------------------------------------\n\n\t/** Call this to acknowledge to the system that VREvent_Quit has been received and that the process is exiting.\n\t* This extends the timeout until the process is killed. */\n\tvirtual void AcknowledgeQuit_Exiting() = 0;\n\n\t// -------------------------------------\n\t// App container sandbox methods\n\t// -------------------------------------\n\n\t/** Retrieves a null-terminated, semicolon-delimited list of UTF8 file paths that an application\n\t* must have read access to when running inside of an app container. Returns the number of bytes\n\t* needed to hold the list. */\n\tvirtual uint32_t GetAppContainerFilePaths( VR_OUT_STRING() char *pchBuffer, uint32_t unBufferSize ) = 0;\n\n\t// -------------------------------------\n\t// System methods\n\t// -------------------------------------\n\n\t/** Returns the current version of the SteamVR runtime. The returned string will remain valid until VR_Shutdown is called.\n\t*\n\t* NOTE: Is it not appropriate to use this version to test for the presence of any SteamVR feature. Only use this version\n\t* number for logging or showing to a user, and not to try to detect anything at runtime. When appropriate, feature-specific\n\t* presence information is provided by other APIs. */\n\tvirtual const char *GetRuntimeVersion() = 0;\n\n\t/** Tells the SteamVR runtime which version of the SDK our client was compiled against. */\n\tvirtual vr::EVRInitError SetSDKVersion( uint32_t nVersionMajor, uint32_t nVersionMinor, uint32_t nVersionBuild ) = 0;\n};\n\nstatic const char * const IVRSystem_Version = \"IVRSystem_026\";\n\n}\n\n\n// ivrapplications.h\n\nnamespace vr\n{\n\n\t/** Used for all errors reported by the IVRApplications interface */\n\tenum EVRApplicationError\n\t{\n\t\tVRApplicationError_None = 0,\n\n\t\tVRApplicationError_AppKeyAlreadyExists = 100,\t// Only one application can use any given key\n\t\tVRApplicationError_NoManifest = 101,\t\t\t// the running application does not have a manifest\n\t\tVRApplicationError_NoApplication = 102,\t\t\t// No application is running\n\t\tVRApplicationError_InvalidIndex = 103,\n\t\tVRApplicationError_UnknownApplication = 104,\t// the application could not be found\n\t\tVRApplicationError_IPCFailed = 105,\t\t\t\t// An IPC failure caused the request to fail\n\t\tVRApplicationError_ApplicationAlreadyRunning = 106,\n\t\tVRApplicationError_InvalidManifest = 107,\n\t\tVRApplicationError_InvalidApplication = 108,\n\t\tVRApplicationError_LaunchFailed = 109,\t\t\t// the process didn't start\n\t\tVRApplicationError_ApplicationAlreadyStarting = 110, // the system was already starting the same application\n\t\tVRApplicationError_LaunchInProgress = 111,\t\t// The system was already starting a different application\n\t\tVRApplicationError_OldApplicationQuitting = 112, // Caller should retry while PerformApplicationPrelaunchCheck is returing this\n\t\tVRApplicationError_TransitionAborted = 113,\n\t\tVRApplicationError_IsTemplate = 114, // error when you try to call LaunchApplication() on a template type app (use LaunchTemplateApplication)\n\t\tVRApplicationError_SteamVRIsExiting = 115,\n\t\tVRApplicationError_WaitingForChaperone = 116,   // Caller should retry while PerformApplicationPrelaunchCheck is returing this\n\n\t\tVRApplicationError_BufferTooSmall = 200,\t\t// The provided buffer was too small to fit the requested data\n\t\tVRApplicationError_PropertyNotSet = 201,\t\t// The requested property was not set\n\t\tVRApplicationError_UnknownProperty = 202,\n\t\tVRApplicationError_InvalidParameter = 203,\n\n\t\tVRApplicationError_NotImplemented = 300,  // Fcn is not implemented in current interface\n\t};\n\n\t/** The maximum length of an application key */\n\tstatic const uint32_t k_unMaxApplicationKeyLength = 128;\n\n\t/** these are the properties available on applications. */\n\tenum EVRApplicationProperty\n\t{\n\t\tVRApplicationProperty_Name_String\t\t\t\t= 0,\n\n\t\tVRApplicationProperty_LaunchType_String\t\t\t= 11,\n\t\tVRApplicationProperty_WorkingDirectory_String\t= 12,\n\t\tVRApplicationProperty_BinaryPath_String\t\t\t= 13,\n\t\tVRApplicationProperty_Arguments_String\t\t\t= 14,\n\t\tVRApplicationProperty_URL_String\t\t\t\t= 15,\n\n\t\tVRApplicationProperty_Description_String\t\t= 50,\n\t\tVRApplicationProperty_NewsURL_String\t\t\t= 51,\n\t\tVRApplicationProperty_ImagePath_String\t\t\t= 52,\n\t\tVRApplicationProperty_ImagePathCapsule_String\t= 55,\n\t\tVRApplicationProperty_Source_String\t\t\t\t= 53,\n\t\tVRApplicationProperty_ActionManifestURL_String\t= 54,\n\n\t\tVRApplicationProperty_IsDashboardOverlay_Bool\t= 60,\n\t\tVRApplicationProperty_IsTemplate_Bool\t\t\t= 61,\n\t\tVRApplicationProperty_IsInstanced_Bool\t\t\t= 62,\n\t\tVRApplicationProperty_IsInternal_Bool\t\t\t= 63,\n\t\tVRApplicationProperty_WantsCompositorPauseInStandby_Bool = 64,\n\t\tVRApplicationProperty_IsHidden_Bool\t\t\t\t= 65,\n\n\t\tVRApplicationProperty_LastLaunchTime_Uint64\t\t= 70,\n\t};\n\n\tenum EVRSceneApplicationState\n\t{\n\t\tEVRSceneApplicationState_None\t\t\t\t\t\t      = 0, // Scene Application is not running\n\t\tEVRSceneApplicationState_Starting\t\t\t\t\t      = 1, // Scene Application is starting\n\t\tEVRSceneApplicationState_Quitting\t\t\t\t\t      = 2, // Scene Application is quitting\n\t\tEVRSceneApplicationState_Running\t\t\t\t\t\t  = 3, // Scene Application is running, and submitting frames, a custom skybox, or a visible overlay\n\t\tEVRSceneApplicationState_Waiting\t\t\t\t\t\t  = 4, // Scene Application is running, but not drawing anything\n\t};\n\n\tstruct AppOverrideKeys_t\n\t{\n\t\tconst char *pchKey;\n\t\tconst char *pchValue;\n\t};\n\n\t/** Currently recognized mime types */\n\tstatic const char * const k_pch_MimeType_HomeApp\t\t= \"vr/home\";\n\tstatic const char * const k_pch_MimeType_GameTheater\t= \"vr/game_theater\";\n\n\tclass IVRApplications\n\t{\n\tpublic:\n\n\t\t// ---------------  Application management  --------------- //\n\n\t\t/** Adds an application manifest to the list to load when building the list of installed applications.\n\t\t* Temporary manifests are not automatically loaded */\n\t\tvirtual EVRApplicationError AddApplicationManifest( const char *pchApplicationManifestFullPath, bool bTemporary = false ) = 0;\n\n\t\t/** Removes an application manifest from the list to load when building the list of installed applications. */\n\t\tvirtual EVRApplicationError RemoveApplicationManifest( const char *pchApplicationManifestFullPath ) = 0;\n\n\t\t/** Returns true if an application is installed */\n\t\tvirtual bool IsApplicationInstalled( const char *pchAppKey ) = 0;\n\n\t\t/** Returns the number of applications available in the list */\n\t\tvirtual uint32_t GetApplicationCount() = 0;\n\n\t\t/** Returns the key of the specified application. The index is at least 0 and is less than the return\n\t\t* value of GetApplicationCount(). The buffer should be at least k_unMaxApplicationKeyLength in order to\n\t\t* fit the key. */\n\t\tvirtual EVRApplicationError GetApplicationKeyByIndex( uint32_t unApplicationIndex, VR_OUT_STRING() char *pchAppKeyBuffer, uint32_t unAppKeyBufferLen ) = 0;\n\n\t\t/** Returns the key of the application for the specified Process Id. The buffer should be at least\n\t\t* k_unMaxApplicationKeyLength in order to fit the key. */\n\t\tvirtual EVRApplicationError GetApplicationKeyByProcessId( uint32_t unProcessId, VR_OUT_STRING() char *pchAppKeyBuffer, uint32_t unAppKeyBufferLen ) = 0;\n\n\t\t/** Launches the application. The existing scene application will exit and then the new application will start.\n\t\t* This call is not valid for dashboard overlay applications. */\n\t\tvirtual EVRApplicationError LaunchApplication( const char *pchAppKey ) = 0;\n\n\t\t/** Launches an instance of an application of type template, with its app key being pchNewAppKey (which must be unique) and optionally override sections\n\t\t* from the manifest file via AppOverrideKeys_t\n\t\t*/\n\t\tvirtual EVRApplicationError LaunchTemplateApplication( const char *pchTemplateAppKey, const char *pchNewAppKey, VR_ARRAY_COUNT( unKeys ) const AppOverrideKeys_t *pKeys, uint32_t unKeys ) = 0;\n\n\t\t/** launches the application currently associated with this mime type and passes it the option args, typically the filename or object name of the item being launched */\n\t\tvirtual vr::EVRApplicationError LaunchApplicationFromMimeType( const char *pchMimeType, const char *pchArgs ) = 0;\n\n\t\t/** Launches the dashboard overlay application if it is not already running. This call is only valid for\n\t\t* dashboard overlay applications. */\n\t\tvirtual EVRApplicationError LaunchDashboardOverlay( const char *pchAppKey ) = 0;\n\n\t\t/** Cancel a pending launch for an application */\n\t\tvirtual bool CancelApplicationLaunch( const char *pchAppKey ) = 0;\n\n\t\t/** Identifies a running application. OpenVR can't always tell which process started in response\n\t\t* to a URL. This function allows a URL handler (or the process itself) to identify the app key\n\t\t* for the now running application. Passing a process ID of 0 identifies the calling process.\n\t\t* The application must be one that's known to the system via a call to AddApplicationManifest. */\n\t\tvirtual EVRApplicationError IdentifyApplication( uint32_t unProcessId, const char *pchAppKey ) = 0;\n\n\t\t/** Returns the process ID for an application. Return 0 if the application was not found or is not running. */\n\t\tvirtual uint32_t GetApplicationProcessId( const char *pchAppKey ) = 0;\n\n\t\t/** Returns a string for an applications error */\n\t\tvirtual const char *GetApplicationsErrorNameFromEnum( EVRApplicationError error ) = 0;\n\n\t\t// ---------------  Application properties  --------------- //\n\n\t\t/** Returns a value for an application property. The required buffer size to fit this value will be returned. */\n\t\tvirtual uint32_t GetApplicationPropertyString( const char *pchAppKey, EVRApplicationProperty eProperty, VR_OUT_STRING() char *pchPropertyValueBuffer, uint32_t unPropertyValueBufferLen, EVRApplicationError *peError = nullptr ) = 0;\n\n\t\t/** Returns a bool value for an application property. Returns false in all error cases. */\n\t\tvirtual bool GetApplicationPropertyBool( const char *pchAppKey, EVRApplicationProperty eProperty, EVRApplicationError *peError = nullptr ) = 0;\n\n\t\t/** Returns a uint64 value for an application property. Returns 0 in all error cases. */\n\t\tvirtual uint64_t GetApplicationPropertyUint64( const char *pchAppKey, EVRApplicationProperty eProperty, EVRApplicationError *peError = nullptr ) = 0;\n\n\t\t/** Sets the application auto-launch flag. This is only valid for applications which return true for VRApplicationProperty_IsDashboardOverlay_Bool. */\n\t\tvirtual EVRApplicationError SetApplicationAutoLaunch( const char *pchAppKey, bool bAutoLaunch ) = 0;\n\n\t\t/** Gets the application auto-launch flag. This is only valid for applications which return true for VRApplicationProperty_IsDashboardOverlay_Bool. */\n\t\tvirtual bool GetApplicationAutoLaunch( const char *pchAppKey ) = 0;\n\n\t\t/** Adds this mime-type to the list of supported mime types for this application*/\n\t\tvirtual EVRApplicationError SetDefaultApplicationForMimeType( const char *pchAppKey, const char *pchMimeType ) = 0;\n\n\t\t/** return the app key that will open this mime type */\n\t\tvirtual bool GetDefaultApplicationForMimeType( const char *pchMimeType, VR_OUT_STRING() char *pchAppKeyBuffer, uint32_t unAppKeyBufferLen ) = 0;\n\n\t\t/** Get the list of supported mime types for this application, comma-delimited */\n\t\tvirtual bool GetApplicationSupportedMimeTypes( const char *pchAppKey, VR_OUT_STRING() char *pchMimeTypesBuffer, uint32_t unMimeTypesBuffer ) = 0;\n\n\t\t/** Get the list of app-keys that support this mime type, comma-delimited, the return value is number of bytes you need to return the full string */\n\t\tvirtual uint32_t GetApplicationsThatSupportMimeType( const char *pchMimeType, VR_OUT_STRING() char *pchAppKeysThatSupportBuffer, uint32_t unAppKeysThatSupportBuffer ) = 0;\n\n\t\t/** Get the args list from an app launch that had the process already running, you call this when you get a VREvent_ApplicationMimeTypeLoad */\n\t\tvirtual uint32_t GetApplicationLaunchArguments( uint32_t unHandle, VR_OUT_STRING() char *pchArgs, uint32_t unArgs ) = 0;\n\n\t\t// ---------------  Transition methods --------------- //\n\n\t\t/** Returns the app key for the application that is starting up */\n\t\tvirtual EVRApplicationError GetStartingApplication( VR_OUT_STRING() char *pchAppKeyBuffer, uint32_t unAppKeyBufferLen ) = 0;\n\n\t\t/** Returns the application transition state */\n\t\tvirtual EVRSceneApplicationState GetSceneApplicationState() = 0;\n\n\t\t/** Returns errors that would prevent the specified application from launching immediately. Calling this function will\n\t\t* cause the current scene application to quit, so only call it when you are actually about to launch something else.\n\t\t* What the caller should do about these failures depends on the failure:\n\t\t*   VRApplicationError_OldApplicationQuitting - An existing application has been told to quit. Wait for a VREvent_ProcessQuit\n\t\t*                                               and try again.\n\t\t*   VRApplicationError_WaitingForChaperone    - Room setup is in progress. Wait for VREvent_ChaperoneRoomSetupCommitted and try again.\n\t\t*   VRApplicationError_ApplicationAlreadyStarting - This application is already starting. This is a permanent failure.\n\t\t*   VRApplicationError_LaunchInProgress\t      - A different application is already starting. This is a permanent failure.\n\t\t*   VRApplicationError_None                   - Go ahead and launch. Everything is clear.\n\t\t*/\n\t\tvirtual EVRApplicationError PerformApplicationPrelaunchCheck( const char *pchAppKey ) = 0;\n\n\t\t/** Returns a string for an application transition state */\n\t\tvirtual const char *GetSceneApplicationStateNameFromEnum( EVRSceneApplicationState state ) = 0;\n\n\t\t/** Starts a subprocess within the calling application. This\n\t\t* suppresses all application transition UI and automatically identifies the new executable\n\t\t* as part of the same application. On success the calling process should exit immediately.\n\t\t* If working directory is NULL or \"\" the directory portion of the binary path will be\n\t\t* the working directory. */\n\t\tvirtual EVRApplicationError LaunchInternalProcess( const char *pchBinaryPath, const char *pchArguments, const char *pchWorkingDirectory ) = 0;\n\n\t\t/** Registers a new subprocess launched by the calling application. This\n\t\t * suppresses all application transition UI and automatically identifies the new process\n\t\t * as part of the same application. On success the calling process should exit immediately. */\n\t\tvirtual EVRApplicationError RegisterSubprocess( uint32_t nPid ) = 0;\n\n\t\t/** Returns the current scene process ID according to the application system. A scene process will get scene\n\t\t* focus once it starts rendering, but it will appear here once it calls VR_Init with the Scene application\n\t\t* type. */\n\t\tvirtual uint32_t GetCurrentSceneProcessId() = 0;\n\t};\n\n\tstatic const char * const IVRApplications_Version = \"IVRApplications_008\";\n\n} // namespace vr\n\n// ivrsettings.h\n\n#ifndef OPENVR_NO_STL\n#include <string>\n#endif\n\nnamespace vr\n{\n\tenum EVRSettingsError\n\t{\n\t\tVRSettingsError_None = 0,\n\t\tVRSettingsError_IPCFailed = 1,\n\t\tVRSettingsError_WriteFailed = 2,\n\t\tVRSettingsError_ReadFailed = 3,\n\t\tVRSettingsError_JsonParseFailed = 4,\n\t\tVRSettingsError_UnsetSettingHasNoDefault = 5, // This will be returned if the setting does not appear in the appropriate default file and has not been set\n\t\tVRSettingsError_AccessDenied = 6,\n\t};\n\n\t// The maximum length of a settings key\n\tstatic const uint32_t k_unMaxSettingsKeyLength = 128;\n\n\tclass IVRSettings\n\t{\n\tpublic:\n\t\tvirtual const char *GetSettingsErrorNameFromEnum( EVRSettingsError eError ) = 0;\n\n\t\tvirtual void SetBool( const char *pchSection, const char *pchSettingsKey, bool bValue, EVRSettingsError *peError = nullptr ) = 0;\n\t\tvirtual void SetInt32( const char *pchSection, const char *pchSettingsKey, int32_t nValue, EVRSettingsError *peError = nullptr ) = 0;\n\t\tvirtual void SetFloat( const char *pchSection, const char *pchSettingsKey, float flValue, EVRSettingsError *peError = nullptr ) = 0;\n\t\tvirtual void SetString( const char *pchSection, const char *pchSettingsKey, const char *pchValue, EVRSettingsError *peError = nullptr ) = 0;\n\n\t\t// Users of the system need to provide a proper default in default.vrsettings in the resources/settings/ directory\n\t\t// of either the runtime or the driver_xxx directory. Otherwise the default will be false, 0, 0.0 or \"\"\n\t\tvirtual bool GetBool( const char *pchSection, const char *pchSettingsKey, EVRSettingsError *peError = nullptr ) = 0;\n\t\tvirtual int32_t GetInt32( const char *pchSection, const char *pchSettingsKey, EVRSettingsError *peError = nullptr ) = 0;\n\t\tvirtual float GetFloat( const char *pchSection, const char *pchSettingsKey, EVRSettingsError *peError = nullptr ) = 0;\n\t\tvirtual void GetString( const char *pchSection, const char *pchSettingsKey, VR_OUT_STRING() char *pchValue, uint32_t unValueLen, EVRSettingsError *peError = nullptr ) = 0;\n\n\t\tvirtual void RemoveSection( const char *pchSection, EVRSettingsError *peError = nullptr ) = 0;\n\t\tvirtual void RemoveKeyInSection( const char *pchSection, const char *pchSettingsKey, EVRSettingsError *peError = nullptr ) = 0;\n\t};\n\n\t//-----------------------------------------------------------------------------\n\tstatic const char * const IVRSettings_Version = \"IVRSettings_003\";\n\n\tclass CVRSettingHelper\n\t{\n\t\tIVRSettings *m_pSettings;\n\tpublic:\n\t\tCVRSettingHelper( IVRSettings *pSettings )\n\t\t{\n\t\t\tm_pSettings = pSettings;\n\t\t}\n\n\t\tconst char *GetSettingsErrorNameFromEnum( EVRSettingsError eError )\n\t\t{\n\t\t\treturn m_pSettings->GetSettingsErrorNameFromEnum( eError );\n\t\t}\n\n\t\tvoid SetBool( const char *pchSection, const char *pchSettingsKey, bool bValue, EVRSettingsError *peError = nullptr )\n\t\t{\n\t\t\tm_pSettings->SetBool( pchSection, pchSettingsKey, bValue, peError );\n\t\t}\n\n\t\tvoid SetInt32( const char *pchSection, const char *pchSettingsKey, int32_t nValue, EVRSettingsError *peError = nullptr )\n\t\t{\n\t\t\tm_pSettings->SetInt32( pchSection, pchSettingsKey, nValue, peError );\n\t\t}\n\t\tvoid SetFloat( const char *pchSection, const char *pchSettingsKey, float flValue, EVRSettingsError *peError = nullptr )\n\t\t{\n\t\t\tm_pSettings->SetFloat( pchSection, pchSettingsKey, flValue, peError );\n\t\t}\n\t\tvoid SetString( const char *pchSection, const char *pchSettingsKey, const char *pchValue, EVRSettingsError *peError = nullptr )\n\t\t{\n\t\t\tm_pSettings->SetString( pchSection, pchSettingsKey, pchValue, peError );\n\t\t}\n#ifndef OPENVR_NO_STL\n\t\tvoid SetString( const std::string & sSection, const std::string &  sSettingsKey, const std::string & sValue, EVRSettingsError *peError = nullptr )\n\t\t{\n\t\t\tm_pSettings->SetString( sSection.c_str(), sSettingsKey.c_str(), sValue.c_str(), peError );\n\t\t}\n#endif\n\n\t\tbool GetBool( const char *pchSection, const char *pchSettingsKey, EVRSettingsError *peError = nullptr )\n\t\t{\n\t\t\treturn m_pSettings->GetBool( pchSection, pchSettingsKey, peError );\n\t\t}\n\t\tint32_t GetInt32( const char *pchSection, const char *pchSettingsKey, EVRSettingsError *peError = nullptr )\n\t\t{\n\t\t\treturn m_pSettings->GetInt32( pchSection, pchSettingsKey, peError );\n\t\t}\n\t\tfloat GetFloat( const char *pchSection, const char *pchSettingsKey, EVRSettingsError *peError = nullptr )\n\t\t{\n\t\t\treturn m_pSettings->GetFloat( pchSection, pchSettingsKey, peError );\n\t\t}\n\t\tvoid GetString( const char *pchSection, const char *pchSettingsKey, VR_OUT_STRING() char *pchValue, uint32_t unValueLen, EVRSettingsError *peError = nullptr )\n\t\t{\n\t\t\tm_pSettings->GetString( pchSection, pchSettingsKey, pchValue, unValueLen, peError );\n\t\t}\n#ifndef OPENVR_NO_STL\n\t\tstd::string GetString( const std::string & sSection, const std::string & sSettingsKey, EVRSettingsError *peError = nullptr )\n\t\t{\n\t\t\tchar buf[4096];\n\t\t\tvr::EVRSettingsError eError;\n\t\t\tm_pSettings->GetString( sSection.c_str(), sSettingsKey.c_str(), buf, sizeof( buf ), &eError );\n\t\t\tif ( peError )\n\t\t\t\t*peError = eError;\n\t\t\tif ( eError == vr::VRSettingsError_None )\n\t\t\t\treturn buf;\n\t\t\telse\n\t\t\t\treturn \"\";\n\t\t}\n#endif\n\n\t\tvoid RemoveSection( const char *pchSection, EVRSettingsError *peError = nullptr )\n\t\t{\n\t\t\tm_pSettings->RemoveSection( pchSection, peError );\n\t\t}\n\t\tvoid RemoveKeyInSection( const char *pchSection, const char *pchSettingsKey, EVRSettingsError *peError = nullptr )\n\t\t{\n\t\t\tm_pSettings->RemoveKeyInSection( pchSection, pchSettingsKey, peError );\n\t\t}\n\t};\n\n\n\t//-----------------------------------------------------------------------------\n\t// steamvr keys\n\tstatic const char * const k_pch_SteamVR_Section = \"steamvr\";\n\tstatic const char * const k_pch_SteamVR_Contrast_Float = \"contrast\";\n\tstatic const char * const k_pch_SteamVR_RequireHmd_String = \"requireHmd\";\n\tstatic const char * const k_pch_SteamVR_ForcedDriverKey_String = \"forcedDriver\";\n\tstatic const char * const k_pch_SteamVR_ForcedHmdKey_String = \"forcedHmd\";\n\tstatic const char * const k_pch_SteamVR_DisplayDebug_Bool = \"displayDebug\";\n\tstatic const char * const k_pch_SteamVR_DebugProcessPipe_String = \"debugProcessPipe\";\n\tstatic const char * const k_pch_SteamVR_DisplayDebugX_Int32 = \"displayDebugX\";\n\tstatic const char * const k_pch_SteamVR_DisplayDebugY_Int32 = \"displayDebugY\";\n\tstatic const char * const k_pch_SteamVR_SendSystemButtonToAllApps_Bool= \"sendSystemButtonToAllApps\";\n\tstatic const char * const k_pch_SteamVR_LogLevel_Int32 = \"loglevel\";\n\tstatic const char * const k_pch_SteamVR_IPD_Float = \"ipd\";\n\tstatic const char * const k_pch_SteamVR_Background_String = \"background\";\n\tstatic const char * const k_pch_SteamVR_BackgroundUseDomeProjection_Bool = \"backgroundUseDomeProjection\";\n\tstatic const char * const k_pch_SteamVR_BackgroundCameraHeight_Float = \"backgroundCameraHeight\";\n\tstatic const char * const k_pch_SteamVR_BackgroundDomeRadius_Float = \"backgroundDomeRadius\";\n\tstatic const char * const k_pch_SteamVR_GridColor_String = \"gridColor\";\n\tstatic const char * const k_pch_SteamVR_PlayAreaColor_String = \"playAreaColor\";\n\tstatic const char * const k_pch_SteamVR_TrackingLossColor_String = \"trackingLossColor\";\n\tstatic const char * const k_pch_SteamVR_StartColor_String = \"startColor\";\n\tstatic const char * const k_pch_SteamVR_ShowStage_Bool = \"showStage\";\n\tstatic const char * const k_pch_SteamVR_DrawTrackingReferences_Bool = \"drawTrackingReferences\";\n\tstatic const char * const k_pch_SteamVR_ActivateMultipleDrivers_Bool = \"activateMultipleDrivers\";\n\tstatic const char * const k_pch_SteamVR_UsingSpeakers_Bool = \"usingSpeakers\";\n\tstatic const char * const k_pch_SteamVR_SpeakersForwardYawOffsetDegrees_Float = \"speakersForwardYawOffsetDegrees\";\n\tstatic const char * const k_pch_SteamVR_BaseStationPowerManagement_Int32 = \"basestationPowerManagement\";\n\tstatic const char * const k_pch_SteamVR_ShowBaseStationPowerManagementTip_Int32 = \"ShowBaseStationPowerManagementTip\";\n\tstatic const char * const k_pch_SteamVR_NeverKillProcesses_Bool = \"neverKillProcesses\";\n\tstatic const char * const k_pch_SteamVR_SupersampleScale_Float = \"supersampleScale\";\n\tstatic const char * const k_pch_SteamVR_MaxRecommendedResolution_Int32 = \"maxRecommendedResolution\";\n\tstatic const char * const k_pch_SteamVR_MotionSmoothing_Bool = \"motionSmoothing\";\n\tstatic const char * const k_pch_SteamVR_MotionSmoothingOverride_Int32 = \"motionSmoothingOverride\";\n\tstatic const char * const k_pch_SteamVR_FoveatedSharpening_Bool = \"sharpening\";\n\tstatic const char * const k_pch_SteamVR_FoveatedSharpeningOverride_Int32 = \"sharpeningOverride\";\n\tstatic const char * const k_pch_SteamVR_FramesToThrottle_Int32 = \"framesToThrottle\";\n\tstatic const char * const k_pch_SteamVR_AdditionalFramesToPredict_Int32 = \"additionalFramesToPredict\";\n\tstatic const char * const k_pch_SteamVR_WorldScale_Float = \"worldScale\";\n\tstatic const char * const k_pch_SteamVR_FovScale_Int32 = \"fovScale\";\n\tstatic const char * const k_pch_SteamVR_FovScaleInner_Int32 = \"fovScaleInner\";\n\tstatic const char * const k_pch_SteamVR_FovScaleUpper_Int32 = \"fovScaleUpper\";\n\tstatic const char * const k_pch_SteamVR_FovScaleLower_Int32 = \"fovScaleLower\";\n\tstatic const char * const k_pch_SteamVR_FovScaleFormat_Int32 = \"fovScaleFormat\";\n\tstatic const char * const k_pch_SteamVR_FovScaleLetterboxed_Bool = \"fovScaleLetterboxed\";\n\tstatic const char * const k_pch_SteamVR_DisableAsyncReprojection_Bool = \"disableAsync\";\n\tstatic const char * const k_pch_SteamVR_ForceFadeOnBadTracking_Bool = \"forceFadeOnBadTracking\";\n\tstatic const char * const k_pch_SteamVR_DefaultMirrorView_Int32 = \"mirrorView\";\n\tstatic const char * const k_pch_SteamVR_ShowLegacyMirrorView_Bool = \"showLegacyMirrorView\";\n\tstatic const char * const k_pch_SteamVR_MirrorViewVisibility_Bool = \"showMirrorView\";\n\tstatic const char * const k_pch_SteamVR_MirrorViewDisplayMode_Int32 = \"mirrorViewDisplayMode\";\n\tstatic const char * const k_pch_SteamVR_MirrorViewEye_Int32 = \"mirrorViewEye\";\n\tstatic const char * const k_pch_SteamVR_MirrorViewGeometry_String = \"mirrorViewGeometry\";\n\tstatic const char * const k_pch_SteamVR_MirrorViewGeometryMaximized_String = \"mirrorViewGeometryMaximized\";\n\tstatic const char * const k_pch_SteamVR_PerfGraphVisibility_Bool = \"showPerfGraph\";\n\tstatic const char * const k_pch_SteamVR_StartMonitorFromAppLaunch = \"startMonitorFromAppLaunch\";\n\tstatic const char * const k_pch_SteamVR_StartCompositorFromAppLaunch_Bool = \"startCompositorFromAppLaunch\";\n\tstatic const char * const k_pch_SteamVR_StartDashboardFromAppLaunch_Bool = \"startDashboardFromAppLaunch\";\n\tstatic const char * const k_pch_SteamVR_StartOverlayAppsFromDashboard_Bool = \"startOverlayAppsFromDashboard\";\n\tstatic const char * const k_pch_SteamVR_EnableHomeApp = \"enableHomeApp\";\n\tstatic const char * const k_pch_SteamVR_CycleBackgroundImageTimeSec_Int32 = \"CycleBackgroundImageTimeSec\";\n\tstatic const char * const k_pch_SteamVR_RetailDemo_Bool = \"retailDemo\";\n\tstatic const char * const k_pch_SteamVR_IpdOffset_Float = \"ipdOffset\";\n\tstatic const char * const k_pch_SteamVR_AllowSupersampleFiltering_Bool = \"allowSupersampleFiltering\";\n\tstatic const char * const k_pch_SteamVR_SupersampleManualOverride_Bool = \"supersampleManualOverride\";\n\tstatic const char * const k_pch_SteamVR_EnableLinuxVulkanAsync_Bool = \"enableLinuxVulkanAsync\";\n\tstatic const char * const k_pch_SteamVR_AllowDisplayLockedMode_Bool = \"allowDisplayLockedMode\";\n\tstatic const char * const k_pch_SteamVR_HaveStartedTutorialForNativeChaperoneDriver_Bool = \"haveStartedTutorialForNativeChaperoneDriver\";\n\tstatic const char * const k_pch_SteamVR_DebugInputBinding = \"debugInputBinding\";\n\tstatic const char * const k_pch_SteamVR_DoNotFadeToGrid = \"doNotFadeToGrid\";\n\tstatic const char * const k_pch_SteamVR_EnableSharedResourceJournaling = \"enableSharedResourceJournaling\";\n\tstatic const char * const k_pch_SteamVR_EnableSafeMode = \"enableSafeMode\";\n\tstatic const char * const k_pch_SteamVR_PreferredRefreshRate = \"preferredRefreshRate\";\n\tstatic const char * const k_pch_SteamVR_LastVersionNotice = \"lastVersionNotice\";\n\tstatic const char * const k_pch_SteamVR_LastVersionNoticeDate = \"lastVersionNoticeDate\";\n\tstatic const char * const k_pch_SteamVR_HmdDisplayColorGainR_Float = \"hmdDisplayColorGainR\";\n\tstatic const char * const k_pch_SteamVR_HmdDisplayColorGainG_Float = \"hmdDisplayColorGainG\";\n\tstatic const char * const k_pch_SteamVR_HmdDisplayColorGainB_Float = \"hmdDisplayColorGainB\";\n\tstatic const char * const k_pch_SteamVR_CustomIconStyle_String = \"customIconStyle\";\n\tstatic const char * const k_pch_SteamVR_CustomOffIconStyle_String = \"customOffIconStyle\";\n\tstatic const char * const k_pch_SteamVR_CustomIconForceUpdate_String = \"customIconForceUpdate\";\n\tstatic const char * const k_pch_SteamVR_AllowGlobalActionSetPriority = \"globalActionSetPriority\";\n\tstatic const char * const k_pch_SteamVR_OverlayRenderQuality = \"overlayRenderQuality_2\";\n\tstatic const char * const k_pch_SteamVR_BlockOculusSDKOnOpenVRLaunchOption_Bool = \"blockOculusSDKOnOpenVRLaunchOption\";\n\tstatic const char * const k_pch_SteamVR_BlockOculusSDKOnAllLaunches_Bool = \"blockOculusSDKOnAllLaunches\";\n\tstatic const char * const k_pch_SteamVR_HDCPLegacyCompatibility_Bool = \"hdcp14legacyCompatibility\";\n\tstatic const char * const k_pch_SteamVR_DisplayPortTrainingMode_Int = \"displayPortTrainingMode\";\n\tstatic const char * const k_pch_SteamVR_UsePrism_Bool = \"usePrism\";\n\tstatic const char * const k_pch_SteamVR_AllowFallbackMirrorWindowLinux_Bool = \"allowFallbackMirrorWindowLinux\";\n\tstatic const char * const k_pch_SteamVR_DisableKeyboardPrivacy_Bool = \"disableKeyboardPrivacy\";\n\n\t//-----------------------------------------------------------------------------\n\t// openxr keys\n\tstatic const char * const k_pch_OpenXR_Section = \"openxr\";\n\tstatic const char * const k_pch_OpenXR_MetaUnityPluginCompatibility_Int32 = \"metaUnityPluginCompatibility\";\n\n\t//-----------------------------------------------------------------------------\n\t// direct mode keys\n\tstatic const char * const k_pch_DirectMode_Section = \"direct_mode\";\n\tstatic const char * const k_pch_DirectMode_Enable_Bool = \"enable\";\n\tstatic const char * const k_pch_DirectMode_Count_Int32 = \"count\";\n\tstatic const char * const k_pch_DirectMode_EdidVid_Int32 = \"edidVid\";\n\tstatic const char * const k_pch_DirectMode_EdidPid_Int32 = \"edidPid\";\n\n\t//-----------------------------------------------------------------------------\n\t// lighthouse keys\n\tstatic const char * const k_pch_Lighthouse_Section = \"driver_lighthouse\";\n\tstatic const char * const k_pch_Lighthouse_DisableIMU_Bool = \"disableimu\";\n\tstatic const char * const k_pch_Lighthouse_DisableIMUExceptHMD_Bool = \"disableimuexcepthmd\";\n\tstatic const char * const k_pch_Lighthouse_UseDisambiguation_String = \"usedisambiguation\";\n\tstatic const char * const k_pch_Lighthouse_DisambiguationDebug_Int32 = \"disambiguationdebug\";\n\tstatic const char * const k_pch_Lighthouse_PrimaryBasestation_Int32 = \"primarybasestation\";\n\tstatic const char * const k_pch_Lighthouse_DBHistory_Bool = \"dbhistory\";\n\tstatic const char * const k_pch_Lighthouse_EnableBluetooth_Bool = \"enableBluetooth\";\n\tstatic const char * const k_pch_Lighthouse_PowerManagedBaseStations_String = \"PowerManagedBaseStations\";\n\tstatic const char * const k_pch_Lighthouse_PowerManagedBaseStations2_String = \"PowerManagedBaseStations2\";\n\tstatic const char * const k_pch_Lighthouse_InactivityTimeoutForBaseStations_Int32 = \"InactivityTimeoutForBaseStations\";\n\tstatic const char * const k_pch_Lighthouse_EnableImuFallback_Bool = \"enableImuFallback\";\n\n\t//-----------------------------------------------------------------------------\n\t// null keys\n\tstatic const char * const k_pch_Null_Section = \"driver_null\";\n\tstatic const char * const k_pch_Null_SerialNumber_String = \"serialNumber\";\n\tstatic const char * const k_pch_Null_ModelNumber_String = \"modelNumber\";\n\tstatic const char * const k_pch_Null_WindowX_Int32 = \"windowX\";\n\tstatic const char * const k_pch_Null_WindowY_Int32 = \"windowY\";\n\tstatic const char * const k_pch_Null_WindowWidth_Int32 = \"windowWidth\";\n\tstatic const char * const k_pch_Null_WindowHeight_Int32 = \"windowHeight\";\n\tstatic const char * const k_pch_Null_RenderWidth_Int32 = \"renderWidth\";\n\tstatic const char * const k_pch_Null_RenderHeight_Int32 = \"renderHeight\";\n\tstatic const char * const k_pch_Null_SecondsFromVsyncToPhotons_Float = \"secondsFromVsyncToPhotons\";\n\tstatic const char * const k_pch_Null_DisplayFrequency_Float = \"displayFrequency\";\n\n\t//-----------------------------------------------------------------------------\n\t// Windows MR keys\n\tstatic const char * const k_pch_WindowsMR_Section = \"driver_holographic\";\n\n\t//-----------------------------------------------------------------------------\n\t// user interface keys\n\tstatic const char * const k_pch_UserInterface_Section = \"userinterface\";\n\tstatic const char * const k_pch_UserInterface_StatusAlwaysOnTop_Bool = \"StatusAlwaysOnTop\";\n\tstatic const char * const k_pch_UserInterface_MinimizeToTray_Bool = \"MinimizeToTray\";\n\tstatic const char * const k_pch_UserInterface_HidePopupsWhenStatusMinimized_Bool = \"HidePopupsWhenStatusMinimized\";\n\tstatic const char * const k_pch_UserInterface_Screenshots_Bool = \"screenshots\";\n\tstatic const char * const k_pch_UserInterface_ScreenshotType_Int = \"screenshotType\";\n\tstatic const char * const k_pch_UserInterface_CheckStatusInterval_Int = \"vrmStatusCheckInterval\";\n\tstatic const char * const k_pch_UserInterface_CheckForSteam_Bool = \"vrmCheckForSteam\";\n\n\t//-----------------------------------------------------------------------------\n\t// notification keys\n\tstatic const char * const k_pch_Notifications_Section = \"notifications\";\n\tstatic const char * const k_pch_Notifications_DoNotDisturb_Bool = \"DoNotDisturb\";\n\n\t//-----------------------------------------------------------------------------\n\t// keyboard keys\n\tstatic const char * const k_pch_Keyboard_Section = \"keyboard\";\n\tstatic const char * const k_pch_Keyboard_TutorialCompletions = \"TutorialCompletions\";\n\tstatic const char * const k_pch_Keyboard_ScaleX = \"ScaleX\";\n\tstatic const char * const k_pch_Keyboard_ScaleY = \"ScaleY\";\n\tstatic const char * const k_pch_Keyboard_OffsetLeftX = \"OffsetLeftX\";\n\tstatic const char * const k_pch_Keyboard_OffsetRightX = \"OffsetRightX\";\n\tstatic const char * const k_pch_Keyboard_OffsetY = \"OffsetY\";\n\tstatic const char * const k_pch_Keyboard_Smoothing = \"Smoothing\";\n\n\t//-----------------------------------------------------------------------------\n\t// perf keys\n\tstatic const char * const k_pch_Perf_Section = \"perfcheck\";\n\tstatic const char * const k_pch_Perf_PerfGraphInHMD_Bool = \"perfGraphInHMD\";\n\tstatic const char * const k_pch_Perf_AllowTimingStore_Bool = \"allowTimingStore\";\n\tstatic const char * const k_pch_Perf_SaveTimingsOnExit_Bool = \"saveTimingsOnExit\";\n\tstatic const char * const k_pch_Perf_TestData_Float = \"perfTestData\";\n\tstatic const char * const k_pch_Perf_GPUProfiling_Bool = \"GPUProfiling\";\n\tstatic const char * const k_pch_Perf_GpuBusMonitoring_Bool = \"gpuBusMonitoring\";\n\n\t//-----------------------------------------------------------------------------\n\t// collision bounds keys\n\tstatic const char * const k_pch_CollisionBounds_Section = \"collisionBounds\";\n\tstatic const char * const k_pch_CollisionBounds_Style_Int32 = \"CollisionBoundsStyle\";\n\tstatic const char * const k_pch_CollisionBounds_GroundPerimeterOn_Bool = \"CollisionBoundsGroundPerimeterOn\";\n\tstatic const char * const k_pch_CollisionBounds_CenterMarkerOn_Bool = \"CollisionBoundsCenterMarkerOn\";\n\tstatic const char * const k_pch_CollisionBounds_PlaySpaceOn_Bool = \"CollisionBoundsPlaySpaceOn\";\n\tstatic const char * const k_pch_CollisionBounds_FadeDistance_Float = \"CollisionBoundsFadeDistance\";\n\tstatic const char * const k_pch_CollisionBounds_WallHeight_Float = \"CollisionBoundsWallHeight\";\n\tstatic const char * const k_pch_CollisionBounds_ColorGammaR_Int32 = \"CollisionBoundsColorGammaR\";\n\tstatic const char * const k_pch_CollisionBounds_ColorGammaG_Int32 = \"CollisionBoundsColorGammaG\";\n\tstatic const char * const k_pch_CollisionBounds_ColorGammaB_Int32 = \"CollisionBoundsColorGammaB\";\n\tstatic const char * const k_pch_CollisionBounds_ColorGammaA_Int32 = \"CollisionBoundsColorGammaA\";\n\tstatic const char * const k_pch_CollisionBounds_EnableDriverImport = \"enableDriverBoundsImport\";\n\n\t//-----------------------------------------------------------------------------\n\t// camera keys\n\tstatic const char * const k_pch_Camera_Section = \"camera\";\n\tstatic const char * const k_pch_Camera_EnableCamera_Bool = \"enableCamera\";\n\tstatic const char * const k_pch_Camera_ShowOnController_Bool = \"showOnController\";\n\tstatic const char * const k_pch_Camera_EnableCameraForCollisionBounds_Bool = \"enableCameraForCollisionBounds\";\n\tstatic const char * const k_pch_Camera_RoomView_Int32 = \"roomView\";\n\tstatic const char * const k_pch_Camera_BoundsColorGammaR_Int32 = \"cameraBoundsColorGammaR\";\n\tstatic const char * const k_pch_Camera_BoundsColorGammaG_Int32 = \"cameraBoundsColorGammaG\";\n\tstatic const char * const k_pch_Camera_BoundsColorGammaB_Int32 = \"cameraBoundsColorGammaB\";\n\tstatic const char * const k_pch_Camera_BoundsColorGammaA_Int32 = \"cameraBoundsColorGammaA\";\n\tstatic const char * const k_pch_Camera_BoundsStrength_Int32 = \"cameraBoundsStrength\";\n\tstatic const char * const k_pch_Camera_RoomViewStyle_Int32 = \"roomViewStyle\";\n\n\t//-----------------------------------------------------------------------------\n\t// audio keys\n\tstatic const char * const k_pch_audio_Section = \"audio\";\n\tstatic const char * const k_pch_audio_SetOsDefaultPlaybackDevice_Bool = \"setOsDefaultPlaybackDevice\";\n\tstatic const char * const k_pch_audio_EnablePlaybackDeviceOverride_Bool = \"enablePlaybackDeviceOverride\";\n\tstatic const char * const k_pch_audio_PlaybackDeviceOverride_String = \"playbackDeviceOverride\";\n\tstatic const char * const k_pch_audio_PlaybackDeviceOverrideName_String = \"playbackDeviceOverrideName\";\n\tstatic const char * const k_pch_audio_SetOsDefaultRecordingDevice_Bool = \"setOsDefaultRecordingDevice\";\n\tstatic const char * const k_pch_audio_EnableRecordingDeviceOverride_Bool = \"enableRecordingDeviceOverride\";\n\tstatic const char * const k_pch_audio_RecordingDeviceOverride_String = \"recordingDeviceOverride\";\n\tstatic const char * const k_pch_audio_RecordingDeviceOverrideName_String = \"recordingDeviceOverrideName\";\n\tstatic const char * const k_pch_audio_EnablePlaybackMirror_Bool = \"enablePlaybackMirror\";\n\tstatic const char * const k_pch_audio_PlaybackMirrorDevice_String = \"playbackMirrorDevice\";\n\tstatic const char * const k_pch_audio_PlaybackMirrorDeviceName_String = \"playbackMirrorDeviceName\";\n\tstatic const char * const k_pch_audio_OldPlaybackMirrorDevice_String = \"onPlaybackMirrorDevice\";\n\tstatic const char * const k_pch_audio_ActiveMirrorDevice_String = \"activePlaybackMirrorDevice\";\n\tstatic const char * const k_pch_audio_EnablePlaybackMirrorIndependentVolume_Bool = \"enablePlaybackMirrorIndependentVolume\";\n\tstatic const char * const k_pch_audio_LastHmdPlaybackDeviceId_String = \"lastHmdPlaybackDeviceId\";\n\tstatic const char * const k_pch_audio_VIVEHDMIGain = \"viveHDMIGain\";\n\tstatic const char * const k_pch_audio_DualSpeakerAndJackOutput_Bool = \"dualSpeakerAndJackOutput\";\n\tstatic const char * const k_pch_audio_MuteMicMonitor_Bool = \"muteMicMonitor\";\n\n\t//-----------------------------------------------------------------------------\n\t// power management keys\n\tstatic const char * const k_pch_Power_Section = \"power\";\n\tstatic const char * const k_pch_Power_PowerOffOnExit_Bool = \"powerOffOnExit\";\n\tstatic const char * const k_pch_Power_TurnOffScreensTimeout_Float = \"turnOffScreensTimeout\";\n\tstatic const char * const k_pch_Power_TurnOffControllersTimeout_Float = \"turnOffControllersTimeout\";\n\tstatic const char * const k_pch_Power_ReturnToWatchdogTimeout_Float = \"returnToWatchdogTimeout\";\n\tstatic const char * const k_pch_Power_AutoLaunchSteamVROnButtonPress = \"autoLaunchSteamVROnButtonPress\";\n\tstatic const char * const k_pch_Power_PauseCompositorOnStandby_Bool = \"pauseCompositorOnStandby\";\n\tstatic const char * const k_pch_Power_OverrideWindowsPowerScheme_Bool = \"overrideWindowsPowerScheme\";\n\n\t//-----------------------------------------------------------------------------\n\t// dashboard keys\n\tstatic const char * const k_pch_Dashboard_Section = \"dashboard\";\n\tstatic const char * const k_pch_Dashboard_EnableDashboard_Bool = \"enableDashboard\";\n\tstatic const char * const k_pch_Dashboard_ArcadeMode_Bool = \"arcadeMode\";\n\tstatic const char * const k_pch_Dashboard_Position = \"position\";\n\tstatic const char * const k_pch_Dashboard_DashboardScale = \"dashboardScale\";\n\tstatic const char * const k_pch_Dashboard_UseStandaloneSystemLayer = \"standaloneSystemLayer\";\n\tstatic const char * const k_pch_Dashboard_AllowSteamOverlays_Bool = \"allowSteamOverlays\";\n\tstatic const char * const k_pch_Dashboard_AllowVRGamepadUI_Bool = \"allowVRGamepadUI\";\n\tstatic const char * const k_pch_Dashboard_SteamMatchesHMDFramerate = \"steamMatchesHMDFramerate\";\n\tstatic const char * const k_pch_Dashboard_GrabHandleAcceleration = \"grabHandleAcceleration\";\n\tstatic const char * const k_pch_Dashboard_OverlayBacksideColor_String = \"overlayBacksideColor\";\n\n\t//-----------------------------------------------------------------------------\n\t// model skin keys\n\tstatic const char * const k_pch_modelskin_Section = \"modelskins\";\n\n\t//-----------------------------------------------------------------------------\n\t// driver keys - These could be checked in any driver_<name> section\n\tstatic const char * const k_pch_Driver_Enable_Bool = \"enable\";\n\tstatic const char * const k_pch_Driver_BlockedBySafemode_Bool = \"blocked_by_safe_mode\";\n\tstatic const char * const k_pch_Driver_LoadPriority_Int32 = \"loadPriority\";\n\tstatic const char * const k_pch_Driver_Hmd_AllowsClientToControlTextureIndex_Bool = \"hmdAllowsClientToControlTextureIndex\";\n\tstatic const char * const k_pch_Driver_ForceSystemLayerUseAppPoses_Bool = \"forceSystemLayerUseAppPoses\";\n\n\t//-----------------------------------------------------------------------------\n\t// web interface keys\n\tstatic const char* const k_pch_WebInterface_Section = \"WebInterface\";\n\n\t//-----------------------------------------------------------------------------\n\t// vrwebhelper keys\n\tstatic const char* const k_pch_VRWebHelper_Section = \"VRWebHelper\";\n\tstatic const char* const k_pch_VRWebHelper_DebuggerEnabled_Bool = \"DebuggerEnabled\";\n\tstatic const char* const k_pch_VRWebHelper_DebuggerPort_Int32 = \"DebuggerPort\";\n\n\t//-----------------------------------------------------------------------------\n\t// tracking overrides - keys are device paths, values are the device paths their\n\t//  tracking/pose information overrides\n\tstatic const char* const k_pch_TrackingOverride_Section = \"TrackingOverrides\";\n\n\t//-----------------------------------------------------------------------------\n\t// per-app keys - the section name for these is the app key itself. Some of these are prefixed by the controller type\n\tstatic const char* const k_pch_App_BindingAutosaveURLSuffix_String = \"AutosaveURL\";\n\tstatic const char* const k_pch_App_BindingLegacyAPISuffix_String = \"_legacy\";\n\tstatic const char *const k_pch_App_BindingSteamVRInputAPISuffix_String = \"_steamvrinput\";\n\tstatic const char *const k_pch_App_BindingOpenXRAPISuffix_String = \"_openxr\";\n\tstatic const char* const k_pch_App_BindingCurrentURLSuffix_String = \"CurrentURL\";\n\tstatic const char* const k_pch_App_BindingPreviousURLSuffix_String = \"PreviousURL\";\n\tstatic const char* const k_pch_App_NeedToUpdateAutosaveSuffix_Bool = \"NeedToUpdateAutosave\";\n\tstatic const char* const k_pch_App_DominantHand_Int32 = \"DominantHand\";\n\tstatic const char* const k_pch_App_BlockOculusSDK_Bool = \"blockOculusSDK\";\n\n\t//-----------------------------------------------------------------------------\n\t// configuration for trackers\n\tstatic const char * const k_pch_Trackers_Section = \"trackers\";\n\n\t//-----------------------------------------------------------------------------\n\t// configuration for desktop UI windows\n\tstatic const char * const k_pch_DesktopUI_Section = \"DesktopUI\";\n\n\t//-----------------------------------------------------------------------------\n\t// Last known keys for righting recovery\n\tstatic const char * const k_pch_LastKnown_Section = \"LastKnown\";\n\tstatic const char* const k_pch_LastKnown_HMDManufacturer_String = \"HMDManufacturer\";\n\tstatic const char *const k_pch_LastKnown_HMDModel_String = \"HMDModel\";\n\tstatic const char* const k_pch_LastKnown_ActualHMDDriver_String = \"ActualHMDDriver\";\n\tstatic const char* const k_pch_LastKnown_HMDSerialNumber_String = \"HMDSerialNumber\";\n\tstatic const char* const k_pch_LastKnown_HMDRemoteClientID_String = \"RemoteClientID\"; // uint64 in string\n\n\t//-----------------------------------------------------------------------------\n\t// Dismissed warnings\n\tstatic const char * const k_pch_DismissedWarnings_Section = \"DismissedWarnings\";\n\n\t//-----------------------------------------------------------------------------\n\t// Input Settings\n\tstatic const char * const k_pch_Input_Section = \"input\";\n\tstatic const char* const k_pch_Input_LeftThumbstickRotation_Float = \"leftThumbstickRotation\";\n\tstatic const char* const k_pch_Input_RightThumbstickRotation_Float = \"rightThumbstickRotation\";\n\tstatic const char* const k_pch_Input_ThumbstickDeadzone_Float = \"thumbstickDeadzone\";\n\n\t//-----------------------------------------------------------------------------\n\t// Log of GPU performance\n\tstatic const char * const k_pch_GpuSpeed_Section = \"GpuSpeed\";\n\n\t//-----------------------------------------------------------------------------\n\t// OpenXR Render Model Extension keys\n\tstatic const char *const k_pch_XRRenderModelCache_Section = \"XRRenderModelUuidCache\";\n\n} // namespace vr\n\n// ivrchaperone.h\n\nnamespace vr\n{\n\n#pragma pack( push, 8 )\n\nenum ChaperoneCalibrationState\n{\n\t// OK!\n\tChaperoneCalibrationState_OK = 1,\t\t\t\t\t\t\t\t\t// Chaperone is fully calibrated and working correctly\n\n\t// Warnings\n\tChaperoneCalibrationState_Warning = 100,\n\tChaperoneCalibrationState_Warning_BaseStationMayHaveMoved = 101,\t// A base station thinks that it might have moved\n\tChaperoneCalibrationState_Warning_BaseStationRemoved = 102,\t\t\t// There are less base stations than when calibrated\n\tChaperoneCalibrationState_Warning_SeatedBoundsInvalid = 103,\t\t// Seated bounds haven't been calibrated for the current tracking center\n\n\t// Errors\n\tChaperoneCalibrationState_Error = 200,\t\t\t\t\t\t\t\t// The UniverseID is invalid\n\tChaperoneCalibrationState_Error_BaseStationUninitialized = 201,\t\t// Tracking center hasn't be calibrated for at least one of the base stations\n\tChaperoneCalibrationState_Error_BaseStationConflict = 202,\t\t\t// Tracking center is calibrated, but base stations disagree on the tracking space\n\tChaperoneCalibrationState_Error_PlayAreaInvalid = 203,\t\t\t\t// Play Area hasn't been calibrated for the current tracking center\n\tChaperoneCalibrationState_Error_CollisionBoundsInvalid = 204,\t\t// Collision Bounds haven't been calibrated for the current tracking center\n};\n\n\n/** HIGH LEVEL TRACKING SPACE ASSUMPTIONS:\n* 0,0,0 is the preferred standing area center.\n* 0Y is the floor height.\n* -Z is the preferred forward facing direction. */\nclass IVRChaperone\n{\npublic:\n\n\t/** Get the current state of Chaperone calibration. This state can change at any time during a session due to physical base station changes. **/\n\tvirtual ChaperoneCalibrationState GetCalibrationState() = 0;\n\n\t/** Returns the width and depth of the Play Area (formerly named Soft Bounds) in X and Z.\n\t* Tracking space center (0,0,0) is the center of the Play Area. **/\n\tvirtual bool GetPlayAreaSize( float *pSizeX, float *pSizeZ ) = 0;\n\n\t/** Returns a quad describing the Play Area (formerly named Soft Bounds).\n\t * The corners form a rectangle.\n\t * Corners are in counter-clockwise order, starting at the front-right.\n\t * The positions are given relative to the standing origin.\n\t * The center of the rectangle is the center of the user's calibrated play space, not necessarily the standing\n\t * origin.\n\t * The Play Area's forward direction goes from its center through the mid-point of a line drawn between the\n\t * first and second corner.\n\t * The quad lies on the XZ plane (height = 0y), with 2 sides parallel to the X-axis and two sides parallel\n\t * to the Z-axis of the user's calibrated Play Area. **/\n\tvirtual bool GetPlayAreaRect( HmdQuad_t *rect ) = 0;\n\n\t/** Reload Chaperone data from the .vrchap file on disk. */\n\tvirtual void ReloadInfo( void ) = 0;\n\n\t/** Optionally give the chaperone system a hit about the color and brightness in the scene **/\n\tvirtual void SetSceneColor( HmdColor_t color ) = 0;\n\n\t/** Get the current chaperone bounds draw color and brightness **/\n\tvirtual void GetBoundsColor( HmdColor_t *pOutputColorArray, int nNumOutputColors, float flCollisionBoundsFadeDistance, HmdColor_t *pOutputCameraColor ) = 0;\n\n\t/** Determine whether the bounds are showing right now **/\n\tvirtual bool AreBoundsVisible() = 0;\n\n\t/** Force the bounds to show, mostly for utilities **/\n\tvirtual void ForceBoundsVisible( bool bForce ) = 0;\n\n\t/** Sets the zero pose for the given tracker coordinate system to the current position and yaw of the HMD. After\n\t* ResetZeroPose all GetDeviceToAbsoluteTrackingPose calls as the origin will be relative to this new zero pose.\n\t* The new zero coordinate system will not change the fact that the Y axis is up in the real world, so the next\n\t* pose returned from GetDeviceToAbsoluteTrackingPose after a call to ResetZeroPose may not be exactly an\n\t* identity matrix.\n\t*\n\t* NOTE: This function overrides the user's previously saved zero pose and should only be called as the result of a user action.\n\t* Users are also able to set their zero pose via the OpenVR Dashboard.\n\t**/\n\tvirtual void ResetZeroPose( ETrackingUniverseOrigin eTrackingUniverseOrigin ) = 0;\n};\n\nstatic const char * const IVRChaperone_Version = \"IVRChaperone_004\";\n\n#pragma pack( pop )\n\n}\n\n// ivrchaperonesetup.h\n\nnamespace vr\n{\n\nenum EChaperoneConfigFile\n{\n\tEChaperoneConfigFile_Live = 1,\t\t// The live chaperone config, used by most applications and games\n\tEChaperoneConfigFile_Temp = 2,\t\t// The temporary chaperone config, used to live-preview collision bounds in room setup\n};\n\nenum EChaperoneImportFlags\n{\n\tEChaperoneImport_BoundsOnly = 0x0001,\n};\n\n/** Manages the working copy of the chaperone info. By default this will be the same as the\n* live copy. Any changes made with this interface will stay in the working copy until\n* CommitWorkingCopy() is called, at which point the working copy and the live copy will be\n* the same again. */\nclass IVRChaperoneSetup\n{\npublic:\n\n\t/** Saves the current working copy to disk */\n\tvirtual bool CommitWorkingCopy( EChaperoneConfigFile configFile ) = 0;\n\n\t/** Reverts the working copy to match the live chaperone calibration.\n\t* To modify existing data this MUST be do WHILE getting a non-error ChaperoneCalibrationStatus.\n\t* Only after this should you do gets and sets on the existing data. */\n\tvirtual void RevertWorkingCopy() = 0;\n\n\t/** Returns the width and depth of the Play Area (formerly named Soft Bounds) in X and Z from the working copy.\n\t* Tracking space center (0,0,0) is the center of the Play Area. */\n\tvirtual bool GetWorkingPlayAreaSize( float *pSizeX, float *pSizeZ ) = 0;\n\n\t/** Returns the 4 corner positions of the Play Area (formerly named Soft Bounds) from the working copy.\n\t* Corners are in clockwise order.\n\t* Tracking space center (0,0,0) is the center of the Play Area.\n\t* It's a rectangle.\n\t* 2 sides are parallel to the X axis and 2 sides are parallel to the Z axis.\n\t* Height of every corner is 0Y (on the floor). **/\n\tvirtual bool GetWorkingPlayAreaRect( HmdQuad_t *rect ) = 0;\n\n\t/** Returns the number of Quads if the buffer points to null. Otherwise it returns Quads\n\t* into the buffer up to the max specified from the working copy. */\n\tvirtual bool GetWorkingCollisionBoundsInfo( VR_OUT_ARRAY_COUNT(punQuadsCount) HmdQuad_t *pQuadsBuffer, uint32_t* punQuadsCount ) = 0;\n\n\t/** Returns the number of Quads if the buffer points to null. Otherwise it returns Quads\n\t* into the buffer up to the max specified. */\n\tvirtual bool GetLiveCollisionBoundsInfo( VR_OUT_ARRAY_COUNT(punQuadsCount) HmdQuad_t *pQuadsBuffer, uint32_t* punQuadsCount ) = 0;\n\n\t/** Returns the preferred seated position from the working copy. */\n\tvirtual bool GetWorkingSeatedZeroPoseToRawTrackingPose( HmdMatrix34_t *pmatSeatedZeroPoseToRawTrackingPose ) = 0;\n\n\t/** Returns the standing origin from the working copy. */\n\tvirtual bool GetWorkingStandingZeroPoseToRawTrackingPose( HmdMatrix34_t *pmatStandingZeroPoseToRawTrackingPose ) = 0;\n\n\t/** Sets the Play Area in the working copy. */\n\tvirtual void SetWorkingPlayAreaSize( float sizeX, float sizeZ ) = 0;\n\n\t/** Sets the Collision Bounds in the working copy. Note: ceiling height is ignored. */\n\tvirtual void SetWorkingCollisionBoundsInfo( VR_ARRAY_COUNT(unQuadsCount) HmdQuad_t *pQuadsBuffer, uint32_t unQuadsCount ) = 0;\n\n\t/** Sets the Collision Bounds in the working copy. */\n\tvirtual void SetWorkingPerimeter( VR_ARRAY_COUNT( unPointCount ) const HmdVector2_t *pPointBuffer, uint32_t unPointCount ) = 0;\n\n\t/** Sets the preferred seated position in the working copy. */\n\tvirtual void SetWorkingSeatedZeroPoseToRawTrackingPose( const HmdMatrix34_t *pMatSeatedZeroPoseToRawTrackingPose ) = 0;\n\n\t/** Sets the preferred standing position in the working copy. */\n\tvirtual void SetWorkingStandingZeroPoseToRawTrackingPose( const HmdMatrix34_t *pMatStandingZeroPoseToRawTrackingPose ) = 0;\n\n\t/** Tear everything down and reload it from the file on disk */\n\tvirtual void ReloadFromDisk( EChaperoneConfigFile configFile ) = 0;\n\n\t/** Returns the preferred seated position. */\n\tvirtual bool GetLiveSeatedZeroPoseToRawTrackingPose( HmdMatrix34_t *pmatSeatedZeroPoseToRawTrackingPose ) = 0;\n\n\tvirtual bool ExportLiveToBuffer( VR_OUT_STRING() char *pBuffer, uint32_t *pnBufferLength ) = 0;\n\tvirtual bool ImportFromBufferToWorking( const char *pBuffer, uint32_t nImportFlags ) = 0;\n\n\t/** Shows the chaperone data in the working set to preview in the compositor.*/\n\tvirtual void ShowWorkingSetPreview() = 0;\n\n\t/** Hides the chaperone data in the working set to preview in the compositor (if it was visible).*/\n\tvirtual void HideWorkingSetPreview() = 0;\n\n\t/** Fire an event that the tracking system can use to know room setup is about to begin. This lets the tracking\n\t * system make any last minute adjustments that should be incorporated into the new setup.  If the user is adjusting\n\t * live in HMD using a tweak tool, keep in mind that calling this might cause the user to see the room jump. */\n\tvirtual void RoomSetupStarting() = 0;\n};\n\nstatic const char * const IVRChaperoneSetup_Version = \"IVRChaperoneSetup_006\";\n\n\n}\n\n// ivrcompositor.h\n\nnamespace vr\n{\n\n#pragma pack( push, 8 )\n\n/** Errors that can occur with the VR compositor */\nenum EVRCompositorError\n{\n\tVRCompositorError_None\t\t\t\t\t\t= 0,\n\tVRCompositorError_RequestFailed\t\t\t\t= 1,\n\tVRCompositorError_IncompatibleVersion\t\t= 100,\n\tVRCompositorError_DoNotHaveFocus\t\t\t= 101,\n\tVRCompositorError_InvalidTexture\t\t\t= 102,\n\tVRCompositorError_IsNotSceneApplication\t\t= 103,\n\tVRCompositorError_TextureIsOnWrongDevice\t= 104,\n\tVRCompositorError_TextureUsesUnsupportedFormat = 105,\n\tVRCompositorError_SharedTexturesNotSupported = 106,\n\tVRCompositorError_IndexOutOfRange\t\t\t= 107,\n\tVRCompositorError_AlreadySubmitted\t\t\t= 108,\n\tVRCompositorError_InvalidBounds\t\t\t\t= 109,\n\tVRCompositorError_AlreadySet\t\t\t\t= 110,\n};\n\n/** Usage types for retreiving shared textures */\nenum EVRCompositorTextureUsage\n{\n\tVRCompositorTextureUsage_Left = Eye_Left,\n\tVRCompositorTextureUsage_Right = Eye_Right,\n\tVRCompositorTextureUsage_Both,\n};\n\n/** Timing mode passed to SetExplicitTimingMode(); see that function for documentation */\nenum EVRCompositorTimingMode\n{\n\tVRCompositorTimingMode_Implicit\t\t\t\t\t\t\t\t\t\t\t= 0,\n\tVRCompositorTimingMode_Explicit_RuntimePerformsPostPresentHandoff\t\t= 1,\n\tVRCompositorTimingMode_Explicit_ApplicationPerformsPostPresentHandoff\t= 2,\n};\n\n/** Cumulative stats for current application.  These are not cleared until a new app connects,\n* but they do stop accumulating once the associated app disconnects. */\nstruct Compositor_CumulativeStats\n{\n\tuint32_t m_nPid; // Process id associated with these stats (may no longer be running).\n\tuint32_t m_nNumFramePresents; // total number of times we called present (includes reprojected frames)\n\tuint32_t m_nNumDroppedFrames; // total number of times an old frame was re-scanned out (without reprojection)\n\tuint32_t m_nNumReprojectedFrames; // total number of times a frame was scanned out a second time (with reprojection)\n\n\t/** Values recorded at startup before application has fully faded in the first time. */\n\tuint32_t m_nNumFramePresentsOnStartup;\n\tuint32_t m_nNumDroppedFramesOnStartup;\n\tuint32_t m_nNumReprojectedFramesOnStartup;\n\n\t/** Applications may explicitly fade to the compositor.  This is usually to handle level transitions, and loading often causes\n\t* system wide hitches.  The following stats are collected during this period.  Does not include values recorded during startup. */\n\tuint32_t m_nNumLoading;\n\tuint32_t m_nNumFramePresentsLoading;\n\tuint32_t m_nNumDroppedFramesLoading;\n\tuint32_t m_nNumReprojectedFramesLoading;\n\n\t/** If we don't get a new frame from the app in less than 2.5 frames, then we assume the app has hung and start\n\t* fading back to the compositor.  The following stats are a result of this, and are a subset of those recorded above.\n\t* Does not include values recorded during start up or loading. */\n\tuint32_t m_nNumTimedOut;\n\tuint32_t m_nNumFramePresentsTimedOut;\n\tuint32_t m_nNumDroppedFramesTimedOut;\n\tuint32_t m_nNumReprojectedFramesTimedOut;\n\n\t/** For items in this section, divide all the values by m_nNumFrameSubmits. */\n\tuint32_t m_nNumFrameSubmits;\n\tvrshared_double m_flSumCompositorCPUTimeMS;\n\tvrshared_double m_flSumCompositorGPUTimeMS;\n\tvrshared_double m_flSumTargetFrameTimes;\n\tvrshared_double m_flSumApplicationCPUTimeMS;\n\tvrshared_double m_flSumApplicationGPUTimeMS;\n\n\tuint32_t m_nNumFramesWithDepth; // total frames submitted with depth by the current application\n};\n\nstruct Compositor_StageRenderSettings\n{\n\t/** Primary color is applied as a tint to (i.e. multiplied with) the model's texture */\n\tHmdColor_t m_PrimaryColor;\n\tHmdColor_t m_SecondaryColor;\n\n\t/** Vignette radius is in meters and is used to fade to the specified secondary solid color over\n\t* that 3D distance from the origin of the playspace. */\n\tfloat m_flVignetteInnerRadius;\n\tfloat m_flVignetteOuterRadius;\n\n\t/** Fades to the secondary color based on view incidence.  This variable controls the linearity\n\t* of the effect.  It is mutually exclusive with vignette.  Additionally, it treats the mesh as faceted. */\n\tfloat m_flFresnelStrength;\n\n\t/** Controls backface culling. */\n\tbool m_bBackfaceCulling;\n\n\t/** Converts the render model's texture to luma and applies to rgb equally.  This is useful to\n\t* combat compression artifacts that can occur on desaturated source material. */\n\tbool m_bGreyscale;\n\n\t/** Renders mesh as a wireframe. */\n\tbool m_bWireframe;\n};\n\nstatic inline Compositor_StageRenderSettings DefaultStageRenderSettings()\n{\n\tCompositor_StageRenderSettings settings;\n\tsettings.m_PrimaryColor.r = 1.0f;\n\tsettings.m_PrimaryColor.g = 1.0f;\n\tsettings.m_PrimaryColor.b = 1.0f;\n\tsettings.m_PrimaryColor.a = 1.0f;\n\tsettings.m_SecondaryColor.r = 1.0f;\n\tsettings.m_SecondaryColor.g = 1.0f;\n\tsettings.m_SecondaryColor.b = 1.0f;\n\tsettings.m_SecondaryColor.a = 1.0f;\n\tsettings.m_flVignetteInnerRadius = 0.0f;\n\tsettings.m_flVignetteOuterRadius = 0.0f;\n\tsettings.m_flFresnelStrength = 0.0f;\n\tsettings.m_bBackfaceCulling = false;\n\tsettings.m_bGreyscale = false;\n\tsettings.m_bWireframe = false;\n\treturn settings;\n}\n\n#pragma pack( pop )\n\n/** Allows the application to interact with the compositor */\nclass IVRCompositor\n{\npublic:\n\t/** Sets tracking space returned by WaitGetPoses */\n\tvirtual void SetTrackingSpace( ETrackingUniverseOrigin eOrigin ) = 0;\n\n\t/** Gets current tracking space returned by WaitGetPoses */\n\tvirtual ETrackingUniverseOrigin GetTrackingSpace() = 0;\n\n\t/** Scene applications should call this function to get poses to render with (and optionally poses predicted an additional frame out to use for gameplay).\n\t* This function will block until \"running start\" milliseconds before the start of the frame, and should be called at the last moment before needing to\n\t* start rendering.\n\t*\n\t* Return codes:\n\t*\t- IsNotSceneApplication (make sure to call VR_Init with VRApplicaiton_Scene)\n\t*\t- DoNotHaveFocus (some other app has taken focus - this will throttle the call to 10hz to reduce the impact on that app)\n\t*/\n\tvirtual EVRCompositorError WaitGetPoses( VR_ARRAY_COUNT( unRenderPoseArrayCount ) TrackedDevicePose_t* pRenderPoseArray, uint32_t unRenderPoseArrayCount,\n\t\tVR_ARRAY_COUNT( unGamePoseArrayCount ) TrackedDevicePose_t* pGamePoseArray, uint32_t unGamePoseArrayCount ) = 0;\n\n\t/** Get the last set of poses returned by WaitGetPoses. */\n\tvirtual EVRCompositorError GetLastPoses( VR_ARRAY_COUNT( unRenderPoseArrayCount ) TrackedDevicePose_t* pRenderPoseArray, uint32_t unRenderPoseArrayCount,\n\t\tVR_ARRAY_COUNT( unGamePoseArrayCount ) TrackedDevicePose_t* pGamePoseArray, uint32_t unGamePoseArrayCount ) = 0;\n\n\t/** Interface for accessing last set of poses returned by WaitGetPoses one at a time.\n\t* Returns VRCompositorError_IndexOutOfRange if unDeviceIndex not less than k_unMaxTrackedDeviceCount otherwise VRCompositorError_None.\n\t* It is okay to pass NULL for either pose if you only want one of the values. */\n\tvirtual EVRCompositorError GetLastPoseForTrackedDeviceIndex( TrackedDeviceIndex_t unDeviceIndex, TrackedDevicePose_t *pOutputPose, TrackedDevicePose_t *pOutputGamePose ) = 0;\n\n\t/** Get the shared texture to copy into for submitting frames. */\n\tvirtual EVRCompositorError GetSubmitTexture( Texture_t *pOutTexture, bool *pNeedsFlush, EVRCompositorTextureUsage eUsage,\n\t\tconst Texture_t *pTexture, const VRTextureBounds_t *pBounds = 0, EVRSubmitFlags nSubmitFlags = Submit_Default ) = 0;\n\n\t/** Updated scene texture to display. If pBounds is NULL the entire texture will be used.  If called from an OpenGL app, consider adding a glFlush after\n\t* Submitting both frames to signal the driver to start processing, otherwise it may wait until the command buffer fills up, causing the app to miss frames.\n\t*\n\t* OpenGL dirty state:\n\t*\tglBindTexture\n\t*\n\t* Return codes:\n\t*\t- IsNotSceneApplication (make sure to call VR_Init with VRApplicaiton_Scene)\n\t*\t- DoNotHaveFocus (some other app has taken focus)\n\t*\t- TextureIsOnWrongDevice (application did not use proper AdapterIndex - see IVRSystem.GetDXGIOutputInfo)\n\t*\t- SharedTexturesNotSupported (application needs to call CreateDXGIFactory1 or later before creating DX device)\n\t*\t- TextureUsesUnsupportedFormat (scene textures must be compatible with DXGI sharing rules - e.g. uncompressed, no mips, etc.)\n\t*\t- InvalidTexture (usually means bad arguments passed in)\n\t*\t- AlreadySubmitted (app has submitted two left textures or two right textures in a single frame - i.e. before calling WaitGetPoses again)\n\t*/\n\tvirtual EVRCompositorError Submit( EVREye eEye, const Texture_t *pTexture, const VRTextureBounds_t* pBounds = 0, EVRSubmitFlags nSubmitFlags = Submit_Default ) = 0;\n\tvirtual EVRCompositorError SubmitWithArrayIndex( EVREye eEye, const Texture_t *pTexture, uint32_t unTextureArrayIndex,\n\t\tconst VRTextureBounds_t *pBounds = 0, EVRSubmitFlags nSubmitFlags = Submit_Default ) = 0;\n\n\t/** Clears the frame that was sent with the last call to Submit. This will cause the\n\t* compositor to show the grid until Submit is called again. */\n\tvirtual void ClearLastSubmittedFrame() = 0;\n\n\t/** Call immediately after presenting your app's window (i.e. companion window) to unblock the compositor.\n\t* This is an optional call, which only needs to be used if you can't instead call WaitGetPoses immediately after Present.\n\t* For example, if your engine's render and game loop are not on separate threads, or blocking the render thread until 3ms before the next vsync would\n\t* introduce a deadlock of some sort.  This function tells the compositor that you have finished all rendering after having Submitted buffers for both\n\t* eyes, and it is free to start its rendering work.  This should only be called from the same thread you are rendering on. */\n\tvirtual void PostPresentHandoff() = 0;\n\n\t/** Returns true if timing data is filled it.  Sets oldest timing info if nFramesAgo is larger than the stored history.\n\t* Be sure to set timing.size = sizeof(Compositor_FrameTiming) on struct passed in before calling this function. */\n\tvirtual bool GetFrameTiming( Compositor_FrameTiming *pTiming, uint32_t unFramesAgo = 0 ) = 0;\n\n\t/** Interface for copying a range of timing data.  Frames are returned in ascending order (oldest to newest) with the last being the most recent frame.\n\t* Only the first entry's m_nSize needs to be set, as the rest will be inferred from that.  Returns total number of entries filled out. */\n\tvirtual uint32_t GetFrameTimings( VR_ARRAY_COUNT( nFrames ) Compositor_FrameTiming *pTiming, uint32_t nFrames ) = 0;\n\n\t/** Returns the time in seconds left in the current (as identified by FrameTiming's frameIndex) frame.\n\t* Due to \"running start\", this value may roll over to the next frame before ever reaching 0.0. */\n\tvirtual float GetFrameTimeRemaining() = 0;\n\n\t/** Fills out stats accumulated for the last connected application.  Pass in sizeof( Compositor_CumulativeStats ) as second parameter. */\n\tvirtual void GetCumulativeStats( Compositor_CumulativeStats *pStats, uint32_t nStatsSizeInBytes ) = 0;\n\n\t/** Fades the view on the HMD to the specified color. The fade will take fSeconds, and the color values are between\n\t* 0.0 and 1.0. This color is faded on top of the scene based on the alpha parameter. Removing the fade color instantly\n\t* would be FadeToColor( 0.0, 0.0, 0.0, 0.0, 0.0 ).  Values are in un-premultiplied alpha space. */\n\tvirtual void FadeToColor( float fSeconds, float fRed, float fGreen, float fBlue, float fAlpha, bool bBackground = false ) = 0;\n\n\t/** Get current fade color value. */\n\tvirtual HmdColor_t GetCurrentFadeColor( bool bBackground = false ) = 0;\n\n\t/** Fading the Grid in or out in fSeconds */\n\tvirtual void FadeGrid( float fSeconds, bool bFadeGridIn ) = 0;\n\n\t/** Get current alpha value of grid. */\n\tvirtual float GetCurrentGridAlpha() = 0;\n\n\t/** Override the skybox used in the compositor (e.g. for during level loads when the app can't feed scene images fast enough)\n\t* Order is Front, Back, Left, Right, Top, Bottom.  If only a single texture is passed, it is assumed in lat-long format.\n\t* If two are passed, it is assumed a lat-long stereo pair. */\n\tvirtual EVRCompositorError SetSkyboxOverride( VR_ARRAY_COUNT( unTextureCount ) const Texture_t *pTextures, uint32_t unTextureCount ) = 0;\n\n\t/** Resets compositor skybox back to defaults. */\n\tvirtual void ClearSkyboxOverride() = 0;\n\n\t/** Brings the compositor window to the front. This is useful for covering any other window that may be on the HMD\n\t* and is obscuring the compositor window. */\n\tvirtual void CompositorBringToFront() = 0;\n\n\t/** Pushes the compositor window to the back. This is useful for allowing other applications to draw directly to the HMD. */\n\tvirtual void CompositorGoToBack() = 0;\n\n\t/** DEPRECATED: Tells the compositor process to clean up and exit. You do not need to call this function at shutdown.\n\t* Under normal circumstances the compositor will manage its own life cycle based on what applications are running. */\n\tvirtual void CompositorQuit() = 0;\n\n\t/** Return whether the compositor is fullscreen */\n\tvirtual bool IsFullscreen() = 0;\n\n\t/** Returns the process ID of the process that is currently rendering the scene */\n\tvirtual uint32_t GetCurrentSceneFocusProcess() = 0;\n\n\t/** Returns the process ID of the process that rendered the last frame (or 0 if the compositor itself rendered the frame.)\n\t* Returns 0 when fading out from an app and the app's process Id when fading into an app. */\n\tvirtual uint32_t GetLastFrameRenderer() = 0;\n\n\t/** Returns true if the current process has the scene focus */\n\tvirtual bool CanRenderScene() = 0;\n\n\t/** DEPRECATED: Opens the headset view (as either a window or docked widget depending on user's preferences) that displays what the user\n\t* sees in the headset. */\n\tvirtual void ShowMirrorWindow() = 0;\n\n\t/** DEPRECATED: Closes the headset view, either as a window or docked widget. */\n\tvirtual void HideMirrorWindow() = 0;\n\n\t/** DEPRECATED: Returns true if the headset view (either as a window or docked widget) is shown. */\n\tvirtual bool IsMirrorWindowVisible() = 0;\n\n\t/** Writes back buffer and stereo left/right pair from the application to a 'screenshots' folder in the SteamVR runtime root. */\n\tvirtual void CompositorDumpImages() = 0;\n\n\t/** Let an app know it should be rendering with low resources. */\n\tvirtual bool ShouldAppRenderWithLowResources() = 0;\n\n\t/** Override interleaved reprojection logic to force on. */\n\tvirtual void ForceInterleavedReprojectionOn( bool bOverride ) = 0;\n\n\t/** Force reconnecting to the compositor process. */\n\tvirtual void ForceReconnectProcess() = 0;\n\n\t/** Temporarily suspends rendering (useful for finer control over scene transitions). */\n\tvirtual void SuspendRendering( bool bSuspend ) = 0;\n\n\t/** Opens a shared D3D11 texture with the undistorted composited image for each eye.  Use ReleaseMirrorTextureD3D11 when finished\n\t* instead of calling Release on the resource itself. */\n\tvirtual vr::EVRCompositorError GetMirrorTextureD3D11( vr::EVREye eEye, void *pD3D11DeviceOrResource, void **ppD3D11ShaderResourceView ) = 0;\n\tvirtual void ReleaseMirrorTextureD3D11( void *pD3D11ShaderResourceView ) = 0;\n\n\t/** Access to mirror textures from OpenGL. */\n\tvirtual vr::EVRCompositorError GetMirrorTextureGL( vr::EVREye eEye, vr::glUInt_t *pglTextureId, vr::glSharedTextureHandle_t *pglSharedTextureHandle ) = 0;\n\tvirtual bool ReleaseSharedGLTexture( vr::glUInt_t glTextureId, vr::glSharedTextureHandle_t glSharedTextureHandle ) = 0;\n\tvirtual void LockGLSharedTextureForAccess( vr::glSharedTextureHandle_t glSharedTextureHandle ) = 0;\n\tvirtual void UnlockGLSharedTextureForAccess( vr::glSharedTextureHandle_t glSharedTextureHandle ) = 0;\n\n\t/** [Vulkan Only]\n\t* return 0. Otherwise it returns the length of the number of bytes necessary to hold this string including the trailing\n\t* null.  The string will be a space separated list of-required instance extensions to enable in VkCreateInstance */\n\tvirtual uint32_t GetVulkanInstanceExtensionsRequired( VR_OUT_STRING() char *pchValue, uint32_t unBufferSize ) = 0;\n\n\t/** [Vulkan only]\n\t* return 0. Otherwise it returns the length of the number of bytes necessary to hold this string including the trailing\n\t* null.  The string will be a space separated list of required device extensions to enable in VkCreateDevice */\n\tvirtual uint32_t GetVulkanDeviceExtensionsRequired( VkPhysicalDevice_T *pPhysicalDevice, VR_OUT_STRING() char *pchValue, uint32_t unBufferSize ) = 0;\n\n\t/** [ Vulkan/D3D12 Only ]\n\t* There are two purposes for SetExplicitTimingMode:\n\t*\t1. To get a more accurate GPU timestamp for when the frame begins in Vulkan/D3D12 applications.\n\t*\t2. (Optional) To avoid having WaitGetPoses access the Vulkan queue so that the queue can be accessed from\n\t*\tanother thread while WaitGetPoses is executing.\n\t*\n\t* More accurate GPU timestamp for the start of the frame is achieved by the application calling\n\t* SubmitExplicitTimingData immediately before its first submission to the Vulkan/D3D12 queue.\n\t* This is more accurate because normally this GPU timestamp is recorded during WaitGetPoses.  In D3D11,\n\t* WaitGetPoses queues a GPU timestamp write, but it does not actually get submitted to the GPU until the\n\t* application flushes.  By using SubmitExplicitTimingData, the timestamp is recorded at the same place for\n\t* Vulkan/D3D12 as it is for D3D11, resulting in a more accurate GPU time measurement for the frame.\n\t*\n\t* Avoiding WaitGetPoses accessing the Vulkan queue can be achieved using SetExplicitTimingMode as well.  If this is desired,\n\t* the application should set the timing mode to Explicit_ApplicationPerformsPostPresentHandoff and *MUST* call PostPresentHandoff\n\t* itself. If these conditions are met, then WaitGetPoses is guaranteed not to access the queue.  Note that PostPresentHandoff\n\t* and SubmitExplicitTimingData will access the queue, so only WaitGetPoses becomes safe for accessing the queue from another\n\t* thread. */\n\tvirtual void SetExplicitTimingMode( EVRCompositorTimingMode eTimingMode ) = 0;\n\n\t/** [ Vulkan/D3D12 Only ]\n\t* Submit explicit timing data.  When SetExplicitTimingMode is true, this must be called immediately before\n\t* the application's first vkQueueSubmit (Vulkan) or ID3D12CommandQueue::ExecuteCommandLists (D3D12) of each frame.\n\t* This function will insert a GPU timestamp write just before the application starts its rendering.  This function\n\t* will perform a vkQueueSubmit on Vulkan so must not be done simultaneously with VkQueue operations on another thread.\n\t* Returns VRCompositorError_RequestFailed if SetExplicitTimingMode is not enabled. */\n\tvirtual EVRCompositorError SubmitExplicitTimingData() = 0;\n\n\t/** Indicates whether or not motion smoothing is enabled by the user settings.\n\t* If you want to know if motion smoothing actually triggered due to a late frame, check Compositor_FrameTiming\n\t* m_nReprojectionFlags & VRCompositor_ReprojectionMotion instead. */\n\tvirtual bool IsMotionSmoothingEnabled() = 0;\n\n\t/** Indicates whether or not motion smoothing is supported by the current hardware. */\n\tvirtual bool IsMotionSmoothingSupported() = 0;\n\n\t/** Indicates whether or not the current scene focus app is currently loading.  This is inferred from its use of FadeGrid to\n\t* explicitly fade to the compositor to cover up the fact that it cannot render at a sustained full framerate during this time. */\n\tvirtual bool IsCurrentSceneFocusAppLoading() = 0;\n\n\t/** Override the stage model used in the compositor to replace the grid.  RenderModelPath is a full path the an OBJ file to load.\n\t* This file will be loaded asynchronously from disk and uploaded to the gpu by the runtime.  Once ready for rendering, the\n\t* VREvent StageOverrideReady will be sent.  Use FadeToGrid to reveal.  Call ClearStageOverride to free the associated resources when finished. */\n\tvirtual EVRCompositorError SetStageOverride_Async( const char *pchRenderModelPath, const HmdMatrix34_t *pTransform = 0,\n\t\tconst Compositor_StageRenderSettings *pRenderSettings = 0, uint32_t nSizeOfRenderSettings = 0 ) = 0;\n\n\t/** Resets the stage to its default user specified setting. */\n\tvirtual void ClearStageOverride() = 0;\n\n\t/** Returns true if pBenchmarkResults is filled it.  Sets pBenchmarkResults with the result of the compositor benchmark.\n\t* nSizeOfBenchmarkResults should be set to sizeof(Compositor_BenchmarkResults) */\n\tvirtual bool GetCompositorBenchmarkResults( Compositor_BenchmarkResults *pBenchmarkResults, uint32_t nSizeOfBenchmarkResults ) = 0;\n\n\t/** Returns the frame id associated with the poses last returned by WaitGetPoses.  Deltas between IDs correspond to number of headset vsync intervals. */\n\tvirtual EVRCompositorError GetLastPosePredictionIDs( uint32_t *pRenderPosePredictionID, uint32_t *pGamePosePredictionID ) = 0;\n\n\t/** Get the most up-to-date predicted (or recorded - up to 100ms old) set of poses for a given frame id. */\n\tvirtual EVRCompositorError GetPosesForFrame( uint32_t unPosePredictionID, VR_ARRAY_COUNT( unPoseArrayCount ) TrackedDevicePose_t* pPoseArray, uint32_t unPoseArrayCount ) = 0;\n};\n\nstatic const char * const IVRCompositor_Version = \"IVRCompositor_029\";\n\n} // namespace vr\n\n\n\n// ivrheadsetview.h\n\nnamespace vr\n{\n\tenum HeadsetViewMode_t\n\t{\n\t\tHeadsetViewMode_Left = 0,\n\t\tHeadsetViewMode_Right,\n\t\tHeadsetViewMode_Both\n\t};\n\n\tclass IVRHeadsetView\n\t{\n\tpublic:\n\t\t/** Sets the resolution in pixels to render the headset view. These values are clamped to k_unHeadsetViewMaxWidth\n\t\t* and k_unHeadsetViewMaxHeight respectively. For cropped views, the rendered output will be fit to aspect ratio\n\t\t* defined by the the specified dimensions. For uncropped views, the caller should use GetHeadsetViewAspectRation\n\t\t* to adjust the requested render size to avoid squashing or stretching, and then apply letterboxing to compensate\n\t\t* when displaying the results. */\n\t\tvirtual void SetHeadsetViewSize( uint32_t nWidth, uint32_t nHeight ) = 0;\n\n\t\t/** Gets the current resolution used to render the headset view. */\n\t\tvirtual void GetHeadsetViewSize( uint32_t *pnWidth, uint32_t *pnHeight ) = 0;\n\n\t\t/** Set the mode used to render the headset view. */\n\t\tvirtual void SetHeadsetViewMode( HeadsetViewMode_t eHeadsetViewMode ) = 0;\n\n\t\t/** Get the current mode used to render the headset view. */\n\t\tvirtual HeadsetViewMode_t GetHeadsetViewMode() = 0;\n\n\t\t/** Set whether or not the headset view should be rendered cropped to hide the hidden area mesh or not. */\n\t\tvirtual void SetHeadsetViewCropped( bool bCropped ) = 0;\n\n\t\t/** Get the current cropping status of the headset view. */\n\t\tvirtual bool GetHeadsetViewCropped() = 0;\n\n\t\t/** Get the aspect ratio (width:height) of the uncropped headset view (accounting for the current set mode). */\n\t\tvirtual float GetHeadsetViewAspectRatio() = 0;\n\n\t\t/** Set the range [0..1] that the headset view blends across the stereo overlapped area in cropped both mode. */\n\t\tvirtual void SetHeadsetViewBlendRange( float flStartPct, float flEndPct ) = 0;\n\n\t\t/** Get the current range [0..1] that the headset view blends across the stereo overlapped area in cropped both mode. */\n\t\tvirtual void GetHeadsetViewBlendRange( float *pStartPct, float *pEndPct ) = 0;\n\t};\n\n\tstatic const uint32_t k_unHeadsetViewMaxWidth = 3840;\n\tstatic const uint32_t k_unHeadsetViewMaxHeight = 2160;\n\tstatic const char * const k_pchHeadsetViewOverlayKey = \"system.HeadsetView\";\n\n\tstatic const char * const IVRHeadsetView_Version = \"IVRHeadsetView_001\";\n\n\t/** Returns the current IVRHeadsetView pointer or NULL the interface could not be found. */\n\tVR_INTERFACE vr::IVRHeadsetView *VR_CALLTYPE VRHeadsetView();\n\n} // namespace vr\n\n\n// ivrnotifications.h\n\nnamespace vr\n{\n\n#pragma pack( push, 8 )\n\n// Used for passing graphic data\nstruct NotificationBitmap_t\n{\n\tNotificationBitmap_t()\n\t\t: m_pImageData( nullptr )\n\t\t, m_nWidth( 0 )\n\t\t, m_nHeight( 0 )\n\t\t, m_nBytesPerPixel( 0 )\n\t{\n\t}\n\n\tvoid *m_pImageData;\n\tint32_t m_nWidth;\n\tint32_t m_nHeight;\n\tint32_t m_nBytesPerPixel;\n};\n\n\n/** Be aware that the notification type is used as 'priority' to pick the next notification */\nenum EVRNotificationType\n{\n\t/** Transient notifications are automatically hidden after a period of time set by the user.\n\t* They are used for things like information and chat messages that do not require user interaction. */\n\tEVRNotificationType_Transient = 0,\n\n\t/** Persistent notifications are shown to the user until they are hidden by calling RemoveNotification().\n\t* They are used for things like phone calls and alarms that require user interaction. */\n\tEVRNotificationType_Persistent = 1,\n\n\t/** System notifications are shown no matter what. It is expected, that the ulUserValue is used as ID.\n\t * If there is already a system notification in the queue with that ID it is not accepted into the queue\n\t * to prevent spamming with system notification */\n\tEVRNotificationType_Transient_SystemWithUserValue = 2,\n};\n\nenum EVRNotificationStyle\n{\n\t/** Creates a notification with minimal external styling. */\n\tEVRNotificationStyle_None = 0,\n\n\t/** Used for notifications about overlay-level status. In Steam this is used for events like downloads completing. */\n\tEVRNotificationStyle_Application = 100,\n\n\t/** Used for notifications about contacts that are unknown or not available. In Steam this is used for friend invitations and offline friends. */\n\tEVRNotificationStyle_Contact_Disabled = 200,\n\n\t/** Used for notifications about contacts that are available but inactive. In Steam this is used for friends that are online but not playing a game. */\n\tEVRNotificationStyle_Contact_Enabled = 201,\n\n\t/** Used for notifications about contacts that are available and active. In Steam this is used for friends that are online and currently running a game. */\n\tEVRNotificationStyle_Contact_Active = 202,\n};\n\nstatic const uint32_t k_unNotificationTextMaxSize = 256;\n\ntypedef uint32_t VRNotificationId;\n\n\n\n#pragma pack( pop )\n\n/** Allows notification sources to interact with the VR system. */\nclass IVRNotifications\n{\npublic:\n\t/** Create a notification and enqueue it to be shown to the user.\n\t* An overlay handle is required to create a notification, as otherwise it would be impossible for a user to act on it.\n\t* To create a two-line notification, use a line break ('\\n') to split the text into two lines.\n\t* The pImage argument may be NULL, in which case the specified overlay's icon will be used instead. */\n\tvirtual EVRNotificationError CreateNotification( VROverlayHandle_t ulOverlayHandle, uint64_t ulUserValue, EVRNotificationType type, const char *pchText, EVRNotificationStyle style, const NotificationBitmap_t *pImage, /* out */ VRNotificationId *pNotificationId ) = 0;\n\n\t/** Destroy a notification, hiding it first if it currently shown to the user. */\n\tvirtual EVRNotificationError RemoveNotification( VRNotificationId notificationId ) = 0;\n\n};\n\nstatic const char * const IVRNotifications_Version = \"IVRNotifications_002\";\n\n} // namespace vr\n\n\n\n// ivroverlay.h\n\nnamespace vr\n{\n\n\t/** The maximum length of an overlay key in bytes, counting the terminating null character. */\n\tstatic const uint32_t k_unVROverlayMaxKeyLength = 128;\n\n\t/** The maximum length of an overlay name in bytes, counting the terminating null character. */\n\tstatic const uint32_t k_unVROverlayMaxNameLength = 128;\n\n\t/** The maximum number of overlays that can exist in the system at one time. */\n\tstatic const uint32_t k_unMaxOverlayCount = 128;\n\n\t/** The maximum number of overlay intersection mask primitives per overlay */\n\tstatic const uint32_t k_unMaxOverlayIntersectionMaskPrimitivesCount = 32;\n\n\t/** Types of input supported by VR Overlays */\n\tenum VROverlayInputMethod\n\t{\n\t\tVROverlayInputMethod_None\t\t= 0, // No input events will be generated automatically for this overlay\n\t\tVROverlayInputMethod_Mouse\t\t= 1, // Tracked controllers will get mouse events automatically\n\t\t// VROverlayInputMethod_DualAnalog = 2, // No longer supported\n\t};\n\n\t/** Allows the caller to figure out which overlay transform getter to call. */\n\tenum VROverlayTransformType\n\t{\n\t\tVROverlayTransform_Invalid\t\t\t\t\t= -1,\n\t\tVROverlayTransform_Absolute\t\t\t\t\t= 0,\n\t\tVROverlayTransform_TrackedDeviceRelative\t= 1,\n\t\t//VROverlayTransform_SystemOverlay\t\t\t= 2, // Deleted from the SDK.\n\t\tVROverlayTransform_TrackedComponent \t\t= 3,\n\t\tVROverlayTransform_Cursor\t\t\t\t\t= 4,\n\t\tVROverlayTransform_DashboardTab\t\t\t\t= 5,\n\t\tVROverlayTransform_DashboardThumb\t\t\t= 6,\n\t\tVROverlayTransform_Mountable\t\t\t\t= 7,\n\t\tVROverlayTransform_Projection\t\t\t\t= 8,\n\t\tVROverlayTransform_Subview\t\t\t\t\t= 9,\n\t};\n\n\t/** Overlay control settings */\n\tenum VROverlayFlags\n\t{\n\t\t// Set this flag on a dashboard overlay to prevent a tab from showing up for that overlay\n\t\tVROverlayFlags_NoDashboardTab = 1 << 3,\n\n\t\t// When this is set the overlay will receive VREvent_ScrollDiscrete events like a mouse wheel.\n\t\t// Requires mouse input mode.\n\t\tVROverlayFlags_SendVRDiscreteScrollEvents = 1 << 6,\n\n\t\t// Indicates that the overlay would like to receive\n\t\tVROverlayFlags_SendVRTouchpadEvents = 1 << 7,\n\n\t\t// If set this will render a vertical scroll wheel on the primary controller,\n\t\t//  only needed if not using VROverlayFlags_SendVRScrollEvents but you still want to represent a scroll wheel\n\t\tVROverlayFlags_ShowTouchPadScrollWheel = 1 << 8,\n\n\t\t// If this is set ownership and render access to the overlay are transferred\n\t\t// to the new scene process on a call to IVRApplications::LaunchInternalProcess\n\t\tVROverlayFlags_TransferOwnershipToInternalProcess = 1 << 9,\n\n\t\t// If set, renders 50% of the texture in each eye, side by side\n\t\tVROverlayFlags_SideBySide_Parallel = 1 << 10, // Texture is left/right\n\t\tVROverlayFlags_SideBySide_Crossed = 1 << 11, // Texture is crossed and right/left\n\n\t\tVROverlayFlags_Panorama = 1 << 12, // Texture is a panorama\n\t\tVROverlayFlags_StereoPanorama = 1 << 13, // Texture is a stereo panorama\n\n\t\t// If this is set on an overlay owned by the scene application that overlay\n\t\t// will be sorted with the \"Other\" overlays on top of all other scene overlays\n\t\tVROverlayFlags_SortWithNonSceneOverlays = 1 << 14,\n\n\t\t// If set, the overlay will be shown in the dashboard, otherwise it will be hidden.\n\t\tVROverlayFlags_VisibleInDashboard = 1 << 15,\n\n\t\t// If this is set and the overlay's input method is not none, the system-wide laser mouse\n\t\t// mode will be activated whenever this overlay is visible.\n\t\tVROverlayFlags_MakeOverlaysInteractiveIfVisible = 1 << 16,\n\n\t\t// If this is set the overlay will receive smooth VREvent_ScrollSmooth that emulate trackpad scrolling.\n\t\t// Requires mouse input mode.\n\t\tVROverlayFlags_SendVRSmoothScrollEvents = 1 << 17,\n\n\t\t// If this is set, the overlay texture will be protected content, preventing unauthorized reads.\n\t\tVROverlayFlags_ProtectedContent = 1 << 18,\n\n\t\t// If this is set, the laser mouse splat will not be drawn over this overlay. The overlay will\n\t\t// be responsible for drawing its own \"cursor\".\n\t\tVROverlayFlags_HideLaserIntersection = 1 << 19,\n\n\t\t// If this is set, clicking away from the overlay will cause it to receive a VREvent_Modal_Cancel event.\n\t\t// This is ignored for dashboard overlays.\n\t\tVROverlayFlags_WantsModalBehavior = 1 << 20,\n\n\t\t// If this is set, alpha composition assumes the texture is pre-multiplied\n\t\tVROverlayFlags_IsPremultiplied = 1 << 21,\n\n\t\t// If this is set, the alpha values of the overlay texture will be ignored\n\t\tVROverlayFlags_IgnoreTextureAlpha = 1 << 22,\n\n\t\t// If this is set, this overlay will have a control bar drawn underneath of it in the dashboard.\n\t\tVROverlayFlags_EnableControlBar = 1 << 23, // DEPRECATED\n\n\t\t// If this is set, the overlay control bar will provide a button to toggle the keyboard.\n\t\tVROverlayFlags_EnableControlBarKeyboard = 1 << 24,\n\n\t\t// If this is set, the overlay control bar will provide a \"close\" button which will send a\n\t\t// VREvent_OverlayClosed event to the overlay when pressed. Applications that use this flag are responsible\n\t\t// for responding to the event with something that approximates \"closing\" behavior, such as destroying their\n\t\t// overlay and/or shutting down their application.\n\t\tVROverlayFlags_EnableControlBarClose = 1 << 25,\n\n\t\t// When set, use a minimal control bar on the overlay. This is the successor to VROverlayFlags_EnableControlBar\n\t\tVROverlayFlags_MinimalControlBar = 1 << 26,\n\n\t\t// If this is set, click stabilization will be applied to the laser interaction so that clicks more reliably\n\t\t// trigger on the user's intended target\n\t\tVROverlayFlags_EnableClickStabilization = 1 << 27,\n\n\t\t// If this is set, laser mouse pointer events may be sent for the secondary laser. These events will have\n\t\t// cursorIndex set to 0 for the primary laser and 1 for the secondary.\n\t\tVROverlayFlags_MultiCursor = 1 << 28,\n\n\t\t// If this is set, the compositor won't draw any stylized backing when viewing the overlay from behind.\n\t\t// NOTE: Overlays will only have a backside in the first place if build with OpenVR SDK 2.15.x and higher.\n\t\t// NOTE: DashboardOverlays ignore this flag and the SDK version; they always have a backside unless the user\n\t\t//       globally disables that.\n\t\tVROverlayFlags_NoBackside = 1 << 29,\n\t};\n\n\tenum VRMessageOverlayResponse\n\t{\n\t\tVRMessageOverlayResponse_ButtonPress_0 = 0,\n\t\tVRMessageOverlayResponse_ButtonPress_1 = 1,\n\t\tVRMessageOverlayResponse_ButtonPress_2 = 2,\n\t\tVRMessageOverlayResponse_ButtonPress_3 = 3,\n\t\tVRMessageOverlayResponse_CouldntFindSystemOverlay = 4,\n\t\tVRMessageOverlayResponse_CouldntFindOrCreateClientOverlay= 5,\n\t\tVRMessageOverlayResponse_ApplicationQuit = 6\n\t};\n\n\tstruct VROverlayIntersectionParams_t\n\t{\n\t\tHmdVector3_t vSource;\n\t\tHmdVector3_t vDirection;\n\t\tETrackingUniverseOrigin eOrigin;\n\t};\n\n\tstruct VROverlayIntersectionResults_t\n\t{\n\t\tHmdVector3_t vPoint;\n\t\tHmdVector3_t vNormal;\n\t\tHmdVector2_t vUVs;\n\t\tfloat fDistance;\n\t};\n\n\t// Input modes for the Big Picture gamepad text entry\n\tenum EGamepadTextInputMode\n\t{\n\t\tk_EGamepadTextInputModeNormal = 0,\n\t\tk_EGamepadTextInputModePassword = 1,\n\t\tk_EGamepadTextInputModeSubmit = 2,\n\t};\n\n\t// Controls number of allowed lines for the Big Picture gamepad text entry\n\tenum EGamepadTextInputLineMode\n\t{\n\t\tk_EGamepadTextInputLineModeSingleLine = 0,\n\t\tk_EGamepadTextInputLineModeMultipleLines = 1\n\t};\n\n\tenum EVROverlayIntersectionMaskPrimitiveType\n\t{\n\t\tOverlayIntersectionPrimitiveType_Rectangle,\n\t\tOverlayIntersectionPrimitiveType_Circle,\n\t};\n\n\tstruct IntersectionMaskRectangle_t\n\t{\n\t\tfloat m_flTopLeftX;\n\t\tfloat m_flTopLeftY;\n\t\tfloat m_flWidth;\n\t\tfloat m_flHeight;\n\t};\n\n\tstruct IntersectionMaskCircle_t\n\t{\n\t\tfloat m_flCenterX;\n\t\tfloat m_flCenterY;\n\t\tfloat m_flRadius;\n\t};\n\n\t/** NOTE!!! If you change this you MUST manually update openvr_interop.cs.py and openvr_api_flat.h.py */\n\ttypedef union\n\t{\n\t\tIntersectionMaskRectangle_t m_Rectangle;\n\t\tIntersectionMaskCircle_t m_Circle;\n\t} VROverlayIntersectionMaskPrimitive_Data_t;\n\n\tstruct VROverlayIntersectionMaskPrimitive_t\n\t{\n\t\tEVROverlayIntersectionMaskPrimitiveType m_nPrimitiveType;\n\t\tVROverlayIntersectionMaskPrimitive_Data_t m_Primitive;\n\t};\n\n\tenum EKeyboardFlags\n\t{\n\t\t/** Makes the keyboard send key events immediately instead of accumulating a buffer */\n\t\tKeyboardFlag_Minimal = 1 << 0,\n\t\t/** Makes the keyboard take all focus and dismiss when clicking off the panel */\n\t\tKeyboardFlag_Modal = 1 << 1,\n\t\t/** Shows arrow keys on the keyboard when in minimal mode. Buffered (non-minimal) mode always has them. In minimal\n\t\t * mode, when arrow keys are pressed, they send ANSI escape sequences (e.g. \"\\x1b[D\" for left arrow). */\n\t\tKeyboardFlag_ShowArrowKeys = 1 << 2,\n\t\t/** Shows the hide keyboard button instead of a Done button. The Done key sends a VREvent_KeyboardDone when\n\t\t * clicked. Hide only sends the Closed event. */\n\t\tKeyboardFlag_HideDoneKey = 1 << 3,\n\t};\n\n\t/** Defines the project used in an overlay that is using SetOverlayTransformProjection */\n\tstruct VROverlayProjection_t\n\t{\n\t\t/** Tangent of the sides of the frustum */\n\t\tfloat fLeft;\n\t\tfloat fRight;\n\t\tfloat fTop;\n\t\tfloat fBottom;\n\t};\n\n\tclass IVROverlay\n\t{\n\tpublic:\n\n\t\t// ---------------------------------------------\n\t\t// Overlay management methods\n\t\t// ---------------------------------------------\n\n\t\t/** Finds an existing overlay with the specified key. */\n\t\tvirtual EVROverlayError FindOverlay( const char *pchOverlayKey, VROverlayHandle_t * pOverlayHandle ) = 0;\n\n\t\t/** Creates a new named overlay. All overlays start hidden and with default settings. */\n\t\tvirtual EVROverlayError CreateOverlay( const char *pchOverlayKey, const char *pchOverlayName, VROverlayHandle_t * pOverlayHandle ) = 0;\n\n\t\t/** Creates a Subview Overlay, which is a separate image that gets composited onto an existing parent overlay based on a 2D transform.\n\t\t* Subview overlays may only be created for parent overlays of the same process. */\n\t\tvirtual EVROverlayError CreateSubviewOverlay( VROverlayHandle_t parentOverlayHandle, const char *pchSubviewOverlayKey, const char *pchSubviewOverlayName, VROverlayHandle_t *pSubviewOverlayHandle ) = 0;\n\n\t\t/** Destroys the specified overlay. When an application calls VR_Shutdown all overlays created by that app are\n\t\t* automatically destroyed. */\n\t\tvirtual EVROverlayError DestroyOverlay( VROverlayHandle_t ulOverlayHandle ) = 0;\n\n\t\t/** Fills the provided buffer with the string key of the overlay. Returns the size of buffer required to store the key, including\n\t\t* the terminating null character. k_unVROverlayMaxKeyLength will be enough bytes to fit the string. */\n\t\tvirtual uint32_t GetOverlayKey( VROverlayHandle_t ulOverlayHandle, VR_OUT_STRING() char *pchValue, uint32_t unBufferSize, EVROverlayError *pError = 0L ) = 0;\n\n\t\t/** Fills the provided buffer with the friendly name of the overlay. Returns the size of buffer required to store the key, including\n\t\t* the terminating null character. k_unVROverlayMaxNameLength will be enough bytes to fit the string. */\n\t\tvirtual uint32_t GetOverlayName( VROverlayHandle_t ulOverlayHandle, VR_OUT_STRING() char *pchValue, uint32_t unBufferSize, EVROverlayError *pError = 0L ) = 0;\n\n\t\t/** set the name to use for this overlay */\n\t\tvirtual EVROverlayError SetOverlayName( VROverlayHandle_t ulOverlayHandle, const char *pchName ) = 0;\n\n\t\t/** Gets the raw image data from an overlay. Overlay image data is always returned as RGBA data, 4 bytes per pixel. If the buffer is not large enough, width and height\n\t\t* will be set and VROverlayError_ArrayTooSmall is returned. */\n\t\tvirtual EVROverlayError GetOverlayImageData( VROverlayHandle_t ulOverlayHandle, void *pvBuffer, uint32_t unBufferSize, uint32_t *punWidth, uint32_t *punHeight ) = 0;\n\n\t\t/** returns a string that corresponds with the specified overlay error. The string will be the name\n\t\t* of the error enum value for all valid error codes */\n\t\tvirtual const char *GetOverlayErrorNameFromEnum( EVROverlayError error ) = 0;\n\n\t\t// ---------------------------------------------\n\t\t// Overlay rendering methods\n\t\t// ---------------------------------------------\n\n\t\t/** Sets the pid that is allowed to render to this overlay (the creator pid is always allow to render),\n\t\t*\tby default this is the pid of the process that made the overlay */\n\t\tvirtual EVROverlayError SetOverlayRenderingPid( VROverlayHandle_t ulOverlayHandle, uint32_t unPID ) = 0;\n\n\t\t/** Gets the pid that is allowed to render to this overlay */\n\t\tvirtual uint32_t GetOverlayRenderingPid( VROverlayHandle_t ulOverlayHandle ) = 0;\n\n\t\t/** Specify flag setting for a given overlay */\n\t\tvirtual EVROverlayError SetOverlayFlag( VROverlayHandle_t ulOverlayHandle, VROverlayFlags eOverlayFlag, bool bEnabled ) = 0;\n\n\t\t/** Sets flag setting for a given overlay */\n\t\tvirtual EVROverlayError GetOverlayFlag( VROverlayHandle_t ulOverlayHandle, VROverlayFlags eOverlayFlag, bool *pbEnabled ) = 0;\n\n\t\t/** Gets all the flags for a given overlay */\n\t\tvirtual EVROverlayError GetOverlayFlags( VROverlayHandle_t ulOverlayHandle, uint32_t *pFlags ) = 0;\n\n\t\t/** Sets the color tint of the overlay quad. Use 0.0 to 1.0 per channel. */\n\t\tvirtual EVROverlayError SetOverlayColor( VROverlayHandle_t ulOverlayHandle, float fRed, float fGreen, float fBlue ) = 0;\n\n\t\t/** Gets the color tint of the overlay quad. */\n\t\tvirtual EVROverlayError GetOverlayColor( VROverlayHandle_t ulOverlayHandle, float *pfRed, float *pfGreen, float *pfBlue ) = 0;\n\n\t\t/** Sets the alpha of the overlay quad. Use 1.0 for 100 percent opacity to 0.0 for 0 percent opacity. */\n\t\tvirtual EVROverlayError SetOverlayAlpha( VROverlayHandle_t ulOverlayHandle, float fAlpha ) = 0;\n\n\t\t/** Gets the alpha of the overlay quad. By default overlays are rendering at 100 percent alpha (1.0). */\n\t\tvirtual EVROverlayError GetOverlayAlpha( VROverlayHandle_t ulOverlayHandle, float *pfAlpha ) = 0;\n\n\t\t/** Sets the aspect ratio of the texels in the overlay. 1.0 means the texels are square. 2.0 means the texels\n\t\t* are twice as wide as they are tall. Defaults to 1.0. */\n\t\tvirtual EVROverlayError SetOverlayTexelAspect( VROverlayHandle_t ulOverlayHandle, float fTexelAspect ) = 0;\n\n\t\t/** Gets the aspect ratio of the texels in the overlay. Defaults to 1.0 */\n\t\tvirtual EVROverlayError GetOverlayTexelAspect( VROverlayHandle_t ulOverlayHandle, float *pfTexelAspect ) = 0;\n\n\t\t/** Sets the rendering sort order for the overlay. Overlays are rendered this order:\n\t\t*      Overlays owned by the scene application\n\t\t*      Overlays owned by some other application\n\t\t*\n\t\t*\tWithin a category overlays are rendered lowest sort order to highest sort order. Overlays with the same\n\t\t*\tsort order are rendered back to front base on distance from the HMD.\n\t\t*\n\t\t*\tSubview overlays are always drawn immediately on top of their parent overlay, and the sort order is\n\t\t*\tonly relative to their peer subviews for that overlay.\n\t\t*\n\t\t*\tSort order defaults to 0. */\n\t\tvirtual EVROverlayError SetOverlaySortOrder( VROverlayHandle_t ulOverlayHandle, uint32_t unSortOrder ) = 0;\n\n\t\t/** Gets the sort order of the overlay. See SetOverlaySortOrder for how this works. */\n\t\tvirtual EVROverlayError GetOverlaySortOrder( VROverlayHandle_t ulOverlayHandle, uint32_t *punSortOrder ) = 0;\n\n\t\t/** Sets the width of the overlay quad in meters. By default overlays are rendered on a quad that is 1 meter across */\n\t\tvirtual EVROverlayError SetOverlayWidthInMeters( VROverlayHandle_t ulOverlayHandle, float fWidthInMeters ) = 0;\n\n\t\t/** Returns the width of the overlay quad in meters. By default overlays are rendered on a quad that is 1 meter across */\n\t\tvirtual EVROverlayError GetOverlayWidthInMeters( VROverlayHandle_t ulOverlayHandle, float *pfWidthInMeters ) = 0;\n\n\t\t/** Use to draw overlay as a curved surface. Curvature is a percentage from (0..1] where 1 is a fully closed cylinder.\n\t\t* For a specific radius, curvature can be computed as: overlay.width / (2 PI r). */\n\t\tvirtual EVROverlayError SetOverlayCurvature( VROverlayHandle_t ulOverlayHandle, float fCurvature ) = 0;\n\n\t\t/** Returns the curvature of the overlay as a percentage from (0..1] where 1 is a fully closed cylinder. */\n\t\tvirtual EVROverlayError GetOverlayCurvature( VROverlayHandle_t ulOverlayHandle, float *pfCurvature ) = 0;\n\n\t\t/** Sets the pitch angle (in radians) of the overlay before curvature is applied -- to form a fan or disk. */\n\t\tvirtual EVROverlayError SetOverlayPreCurvePitch( VROverlayHandle_t ulOverlayHandle, float fRadians ) = 0;\n\n\t\t/** Returns the overlay's set pre-curve pitch angle (in radians). */\n\t\tvirtual EVROverlayError GetOverlayPreCurvePitch( VROverlayHandle_t ulOverlayHandle, float *pfRadians ) = 0;\n\n\t\t/** Sets the colorspace the overlay texture's data is in.  Defaults to 'auto'.\n\t\t* If the texture needs to be resolved, you should call SetOverlayTexture with the appropriate colorspace instead. */\n\t\tvirtual EVROverlayError SetOverlayTextureColorSpace( VROverlayHandle_t ulOverlayHandle, EColorSpace eTextureColorSpace ) = 0;\n\n\t\t/** Gets the overlay's current colorspace setting. */\n\t\tvirtual EVROverlayError GetOverlayTextureColorSpace( VROverlayHandle_t ulOverlayHandle, EColorSpace *peTextureColorSpace ) = 0;\n\n\t\t/** Sets the part of the texture to use for the overlay. UV Min is the upper left corner and UV Max is the lower right corner. */\n\t\tvirtual EVROverlayError SetOverlayTextureBounds( VROverlayHandle_t ulOverlayHandle, const VRTextureBounds_t *pOverlayTextureBounds ) = 0;\n\n\t\t/** Gets the part of the texture to use for the overlay. UV Min is the upper left corner and UV Max is the lower right corner. */\n\t\tvirtual EVROverlayError GetOverlayTextureBounds( VROverlayHandle_t ulOverlayHandle, VRTextureBounds_t *pOverlayTextureBounds ) = 0;\n\n\t\t/** Returns the transform type of this overlay. */\n\t\tvirtual EVROverlayError GetOverlayTransformType( VROverlayHandle_t ulOverlayHandle, VROverlayTransformType *peTransformType ) = 0;\n\n\t\t/** Sets the transform to absolute tracking origin. */\n\t\tvirtual EVROverlayError SetOverlayTransformAbsolute( VROverlayHandle_t ulOverlayHandle, ETrackingUniverseOrigin eTrackingOrigin, const HmdMatrix34_t *pmatTrackingOriginToOverlayTransform ) = 0;\n\n\t\t/** Gets the transform if it is absolute. Returns an error if the transform is some other type. */\n\t\tvirtual EVROverlayError GetOverlayTransformAbsolute( VROverlayHandle_t ulOverlayHandle, ETrackingUniverseOrigin *peTrackingOrigin, HmdMatrix34_t *pmatTrackingOriginToOverlayTransform ) = 0;\n\n\t\t/** Sets the transform to relative to the transform of the specified tracked device. */\n\t\tvirtual EVROverlayError SetOverlayTransformTrackedDeviceRelative( VROverlayHandle_t ulOverlayHandle, TrackedDeviceIndex_t unTrackedDevice, const HmdMatrix34_t *pmatTrackedDeviceToOverlayTransform ) = 0;\n\n\t\t/** Gets the transform if it is relative to a tracked device. Returns an error if the transform is some other type. */\n\t\tvirtual EVROverlayError GetOverlayTransformTrackedDeviceRelative( VROverlayHandle_t ulOverlayHandle, TrackedDeviceIndex_t *punTrackedDevice, HmdMatrix34_t *pmatTrackedDeviceToOverlayTransform ) = 0;\n\n\t\t/** Sets the transform to draw the overlay on a rendermodel component mesh instead of a quad. This will only draw when the system is\n\t\t* drawing the device. Overlays with this transform type cannot receive mouse events. */\n\t\tvirtual EVROverlayError SetOverlayTransformTrackedDeviceComponent( VROverlayHandle_t ulOverlayHandle, TrackedDeviceIndex_t unDeviceIndex, const char *pchComponentName ) = 0;\n\n\t\t/** Gets the transform information when the overlay is rendering on a component. */\n\t\tvirtual EVROverlayError GetOverlayTransformTrackedDeviceComponent( VROverlayHandle_t ulOverlayHandle, TrackedDeviceIndex_t *punDeviceIndex, VR_OUT_STRING() char *pchComponentName, uint32_t unComponentNameSize ) = 0;\n\n\t\t/** Sets the hotspot for the specified overlay when that overlay is used as a cursor. These are in texture space with 0,0 in the upper left corner of\n\t\t* the texture and 1,1 in the lower right corner of the texture. */\n\t\tvirtual EVROverlayError SetOverlayTransformCursor( VROverlayHandle_t ulCursorOverlayHandle, const HmdVector2_t *pvHotspot ) = 0;\n\n\t\t/** Gets cursor hotspot/transform for the specified overlay */\n\t\tvirtual vr::EVROverlayError GetOverlayTransformCursor( VROverlayHandle_t ulOverlayHandle, HmdVector2_t *pvHotspot ) = 0;\n\n\t\t/** Sets the overlay as a projection overlay */\n\t\tvirtual vr::EVROverlayError SetOverlayTransformProjection( VROverlayHandle_t ulOverlayHandle,\n\t\t\tETrackingUniverseOrigin eTrackingOrigin, const HmdMatrix34_t* pmatTrackingOriginToOverlayTransform,\n\t\t\tconst VROverlayProjection_t *pProjection, vr::EVREye eEye ) = 0;\n\n\t\t/** Positions a subview overlay to a position within the parent overlay, from the top-left corners of each overlay, in the pixel coordinate space of the parent standalone overlay. */\n\t\tvirtual EVROverlayError SetSubviewPosition( VROverlayHandle_t ulOverlayHandle, float fX, float fY ) = 0;\n\n\t\t/** Shows the VR overlay. Not applicable for Dashboard Overlays. */\n\t\tvirtual EVROverlayError ShowOverlay( VROverlayHandle_t ulOverlayHandle ) = 0;\n\n\t\t/** Hides the VR overlay. Not applicable for Dashboard Overlays. */\n\t\tvirtual EVROverlayError HideOverlay( VROverlayHandle_t ulOverlayHandle ) = 0;\n\n\t\t/** Returns true if the overlay is currently visible, applicable for all overlay types except Dashboard Thumbnail overlays. VREvent_OverlayShown and VREvent_OverlayHidden reflect changes to this value. */\n\t\tvirtual bool IsOverlayVisible( VROverlayHandle_t ulOverlayHandle ) = 0;\n\n\t\t/** Get the transform in 3d space associated with a specific 2d point in the overlay's coordinate space (where 0,0 is the lower left). -Z points out of the overlay */\n\t\tvirtual EVROverlayError GetTransformForOverlayCoordinates( VROverlayHandle_t ulOverlayHandle, ETrackingUniverseOrigin eTrackingOrigin, HmdVector2_t coordinatesInOverlay, HmdMatrix34_t *pmatTransform ) = 0;\n\n\t\t/** This function will block until the top of each frame, and can therefore be used to synchronize with the runtime's update rate.\n\t\t* Note: In non-async mode, some signals may be dropped due to scene app performance, so passing a timeout of 1000/refresh rate\n\t\t* may be useful depending on the overlay app's desired behavior. */\n\t\tvirtual EVROverlayError WaitFrameSync( uint32_t nTimeoutMs ) = 0;\n\n\t\t// ---------------------------------------------\n\t\t// Overlay input methods\n\t\t// ---------------------------------------------\n\n\t\t/** Returns true and fills the event with the next event on the overlay's event queue, if there is one.\n\t\t* If there are no events this method returns false. uncbVREvent should be the size in bytes of the VREvent_t struct */\n\t\tvirtual bool PollNextOverlayEvent( VROverlayHandle_t ulOverlayHandle, VREvent_t *pEvent, uint32_t uncbVREvent ) = 0;\n\n\t\t/** Returns the current input settings for the specified overlay. */\n\t\tvirtual EVROverlayError GetOverlayInputMethod( VROverlayHandle_t ulOverlayHandle, VROverlayInputMethod *peInputMethod ) = 0;\n\n\t\t/** Sets the input settings for the specified overlay. */\n\t\tvirtual EVROverlayError SetOverlayInputMethod( VROverlayHandle_t ulOverlayHandle, VROverlayInputMethod eInputMethod ) = 0;\n\n\t\t/** Gets the mouse scaling factor that is used for mouse events. The actual texture may be a different size, but this is\n\t\t* typically the size of the underlying UI in pixels. */\n\t\tvirtual EVROverlayError GetOverlayMouseScale( VROverlayHandle_t ulOverlayHandle, HmdVector2_t *pvecMouseScale ) = 0;\n\n\t\t/** Sets the mouse scaling factor that is used for mouse events. The actual texture may be a different size, but this is\n\t\t* typically the size of the underlying UI in pixels (not in world space). */\n\t\tvirtual EVROverlayError SetOverlayMouseScale( VROverlayHandle_t ulOverlayHandle, const HmdVector2_t *pvecMouseScale ) = 0;\n\n\t\t/** Computes the overlay-space pixel coordinates of where the ray intersects the overlay with the\n\t\t* specified settings. Returns false if there is no intersection. */\n\t\tvirtual bool ComputeOverlayIntersection( VROverlayHandle_t ulOverlayHandle, const VROverlayIntersectionParams_t *pParams, VROverlayIntersectionResults_t *pResults ) = 0;\n\n\t\t/** Returns true if the specified overlay is the hover target. An overlay is the hover target when it is the last overlay \"moused over\"\n\t\t* by the virtual mouse pointer */\n\t\tvirtual bool IsHoverTargetOverlay( VROverlayHandle_t ulOverlayHandle ) = 0;\n\n\t\t/** Sets a list of primitives to be used for controller ray intersection\n\t\t* typically the size of the underlying UI in pixels (not in world space). */\n\t\tvirtual EVROverlayError SetOverlayIntersectionMask( VROverlayHandle_t ulOverlayHandle, VROverlayIntersectionMaskPrimitive_t *pMaskPrimitives, uint32_t unNumMaskPrimitives, uint32_t unPrimitiveSize = sizeof( VROverlayIntersectionMaskPrimitive_t ) ) = 0;\n\n\t\t/** Triggers a haptic event on the laser mouse controller for the specified overlay */\n\t\tvirtual EVROverlayError TriggerLaserMouseHapticVibration( VROverlayHandle_t ulOverlayHandle, float fDurationSeconds, float fFrequency, float fAmplitude ) = 0;\n\n\t\t/** Sets the cursor to use for the specified overlay. This will be drawn instead of the generic blob when the laser mouse is pointed at the specified overlay */\n\t\tvirtual EVROverlayError SetOverlayCursor( VROverlayHandle_t ulOverlayHandle, VROverlayHandle_t ulCursorHandle ) = 0;\n\n\t\t/** Sets the override cursor position to use for this overlay in overlay mouse coordinates. This position will be used to draw the cursor\n\t\t* instead of whatever the laser mouse cursor position is. */\n\t\tvirtual EVROverlayError SetOverlayCursorPositionOverride( VROverlayHandle_t ulOverlayHandle, const HmdVector2_t *pvCursor ) = 0;\n\n\t\t/** Clears the override cursor position for this overlay */\n\t\tvirtual EVROverlayError ClearOverlayCursorPositionOverride( VROverlayHandle_t ulOverlayHandle ) = 0;\n\n\t\t// ---------------------------------------------\n\t\t// Overlay texture methods\n\t\t// ---------------------------------------------\n\n\t\t/** Texture to draw for the overlay. This function can only be called by the overlay's creator or renderer process (see SetOverlayRenderingPid) .\n\t\t*\n\t\t* OpenGL dirty state:\n\t\t*\tglBindTexture\n\t\t*/\n\t\tvirtual EVROverlayError SetOverlayTexture( VROverlayHandle_t ulOverlayHandle, const Texture_t *pTexture ) = 0;\n\n\t\t/** Use this to tell the overlay system to release the texture set for this overlay. */\n\t\tvirtual EVROverlayError ClearOverlayTexture( VROverlayHandle_t ulOverlayHandle ) = 0;\n\n\t\t/** Separate interface for providing the data as a stream of bytes, but there is an upper bound on data\n\t\t* that can be sent. This function can only be called by the overlay's renderer process. */\n\t\tvirtual EVROverlayError SetOverlayRaw( VROverlayHandle_t ulOverlayHandle, void *pvBuffer, uint32_t unWidth, uint32_t unHeight, uint32_t unBytesPerPixel ) = 0;\n\n\t\t/** Separate interface for providing the image through a filename: can be png or jpg, and should not be bigger than 1920x1080.\n\t\t* This function can only be called by the overlay's renderer process */\n\t\tvirtual EVROverlayError SetOverlayFromFile( VROverlayHandle_t ulOverlayHandle, const char *pchFilePath ) = 0;\n\n\t\t/** Get the native texture handle/device for an overlay you have created.\n\t\t* On windows this handle will be a ID3D11ShaderResourceView with a ID3D11Texture2D bound.\n\t\t*\n\t\t* The texture will always be sized to match the backing texture you supplied in SetOverlayTexture above.\n\t\t*\n\t\t* You MUST call ReleaseNativeOverlayHandle() with pNativeTextureHandle once you are done with this texture.\n\t\t*\n\t\t* pNativeTextureHandle is an OUTPUT, it will be a pointer to a ID3D11ShaderResourceView *.\n\t\t* pNativeTextureRef is an INPUT and should be a ID3D11Resource *. The device used by pNativeTextureRef will be used to bind pNativeTextureHandle.\n\t\t*/\n\t\tvirtual EVROverlayError GetOverlayTexture( VROverlayHandle_t ulOverlayHandle, void **pNativeTextureHandle, void *pNativeTextureRef, uint32_t *pWidth, uint32_t *pHeight, uint32_t *pNativeFormat, ETextureType *pAPIType, EColorSpace *pColorSpace, VRTextureBounds_t *pTextureBounds ) = 0;\n\n\t\t/** Release the pNativeTextureHandle provided from the GetOverlayTexture call, this allows the system to free the underlying GPU resources for this object,\n\t\t* so only do it once you stop rendering this texture.\n\t\t*/\n\t\tvirtual EVROverlayError ReleaseNativeOverlayHandle( VROverlayHandle_t ulOverlayHandle, void *pNativeTextureHandle ) = 0;\n\n\t\t/** Get the size of the overlay texture */\n\t\tvirtual EVROverlayError GetOverlayTextureSize( VROverlayHandle_t ulOverlayHandle, uint32_t *pWidth, uint32_t *pHeight ) = 0;\n\n\t\t// ----------------------------------------------\n\t\t// Dashboard Overlay Methods\n\t\t// ----------------------------------------------\n\n\t\t/** Creates a dashboard overlay and returns its handle */\n\t\tvirtual EVROverlayError CreateDashboardOverlay( const char *pchOverlayKey, const char *pchOverlayFriendlyName, VROverlayHandle_t * pMainHandle, VROverlayHandle_t *pThumbnailHandle ) = 0;\n\n\t\t/** Returns true if the dashboard is visible */\n\t\tvirtual bool IsDashboardVisible() = 0;\n\n\t\t/** returns true if the dashboard is visible and the specified overlay is the active system Overlay */\n\t\tvirtual bool IsActiveDashboardOverlay( VROverlayHandle_t ulOverlayHandle ) = 0;\n\n\t\t/** Sets the dashboard overlay to only appear when the specified process ID has scene focus */\n\t\tvirtual EVROverlayError SetDashboardOverlaySceneProcess( VROverlayHandle_t ulOverlayHandle, uint32_t unProcessId ) = 0;\n\n\t\t/** Gets the process ID that this dashboard overlay requires to have scene focus */\n\t\tvirtual EVROverlayError GetDashboardOverlaySceneProcess( VROverlayHandle_t ulOverlayHandle, uint32_t *punProcessId ) = 0;\n\n\t\t/** Shows the dashboard. */\n\t\tvirtual void ShowDashboard( const char *pchOverlayToShow ) = 0;\n\n\t\t/** Returns the tracked device index that has the laser pointer in the dashboard, or the last one that was used. */\n\t\tvirtual vr::TrackedDeviceIndex_t GetPrimaryDashboardDevice() = 0;\n\n\t\t// ---------------------------------------------\n\t\t// Keyboard methods\n\t\t// ---------------------------------------------\n\n\t\t/** Show the virtual keyboard to accept input. In most cases, you should pass KeyboardFlag_Modal to enable modal overlay\n\t\t* behavior on the keyboard itself. See EKeyboardFlags for more. */\n\t\tvirtual EVROverlayError ShowKeyboard( EGamepadTextInputMode eInputMode, EGamepadTextInputLineMode eLineInputMode, uint32_t unFlags,\n\t\t\tconst char *pchDescription, uint32_t unCharMax, const char *pchExistingText, uint64_t uUserValue ) = 0;\n\n\t\t/** Show the virtual keyboard to accept input for an overlay. In most cases, you should pass KeyboardFlag_Modal to enable modal\n\t\t* overlay behavior on the keyboard itself. See EKeyboardFlags for more. */\n\t\tvirtual EVROverlayError ShowKeyboardForOverlay( VROverlayHandle_t ulOverlayHandle, EGamepadTextInputMode eInputMode,\n\t\t\tEGamepadTextInputLineMode eLineInputMode, uint32_t unFlags, const char *pchDescription, uint32_t unCharMax,\n\t\t\tconst char *pchExistingText, uint64_t uUserValue ) = 0;\n\n\t\t/** Get the text that was entered into the text input **/\n\t\tvirtual uint32_t GetKeyboardText( VR_OUT_STRING() char *pchText, uint32_t cchText ) = 0;\n\n\t\t/** Hide the virtual keyboard **/\n\t\tvirtual void HideKeyboard() = 0;\n\n\t\t/** Set the position of the keyboard in world space **/\n\t\tvirtual void SetKeyboardTransformAbsolute( ETrackingUniverseOrigin eTrackingOrigin, const HmdMatrix34_t *pmatTrackingOriginToKeyboardTransform ) = 0;\n\n\t\t/** Set the position of the keyboard in overlay space by telling it to avoid a rectangle in the overlay. Rectangle coords have (0,0) in the bottom left **/\n\t\tvirtual void SetKeyboardPositionForOverlay( VROverlayHandle_t ulOverlayHandle, HmdRect2_t avoidRect ) = 0;\n\n\t\t// ---------------------------------------------\n\t\t// Message box methods\n\t\t// ---------------------------------------------\n\n\t\t/** Show the message overlay. This will block and return you a result. **/\n\t\tvirtual VRMessageOverlayResponse ShowMessageOverlay( const char* pchText, const char* pchCaption, const char* pchButton0Text, const char* pchButton1Text = nullptr, const char* pchButton2Text = nullptr, const char* pchButton3Text = nullptr ) = 0;\n\n\t\t/** If the calling process owns the overlay and it's open, this will close it. **/\n\t\tvirtual void CloseMessageOverlay() = 0;\n\t};\n\n\tstatic const char * const IVROverlay_Version = \"IVROverlay_028\";\n\n} // namespace vr\n\n// ivroverlayview.h\n\nnamespace vr\n{\n\tstruct VROverlayView_t\n\t{\n\t\tVROverlayHandle_t overlayHandle;\n\t\tTexture_t texture;\n\t\tVRTextureBounds_t textureBounds;\n\t};\n\n\tenum EDeviceType\n\t{\n\t\tDeviceType_Invalid           = -1, // Invalid handle\n\t\tDeviceType_DirectX11         = 0, // Handle is an ID3D11Device\n\t\tDeviceType_Vulkan            = 1, // Handle is a pointer to a VRVulkanDevice_t structure\n\t};\n\n\tstruct VRVulkanDevice_t\n\t{\n\t\tVkInstance_T *m_pInstance;\n\t\tVkDevice_T *m_pDevice;\n\t\tVkPhysicalDevice_T *m_pPhysicalDevice;\n\t\tVkQueue_T *m_pQueue;\n\t\tuint32_t m_uQueueFamilyIndex;\n\t};\n\n\tstruct VRNativeDevice_t\n\t{\n\t\tvoid *handle; // See EDeviceType definition above\n\t\tEDeviceType eType;\n\t};\n\n\tclass IVROverlayView\n\t{\n\tpublic:\n\t\t/** Acquire an OverlayView_t from an overlay handle\n\t\t*\n\t\t* The overlay view can be used to sample the contents directly by a native API. The\n\t\t* contents of the OverlayView_t will remain unchanged through the lifetime of the\n\t\t* OverlayView_t.\n\t\t*\n\t\t* The caller acquires read privileges over the OverlayView_t, but should not\n\t\t* write to it.\n\t\t*\n\t\t* AcquireOverlayView() may be called on the same ulOverlayHandle multiple times to\n\t\t* refresh the image contents. In this case the caller is strongly encouraged to re-use\n\t\t* the same pOverlayView for all re-acquisition calls.\n\t\t*\n\t\t* If the producer has not yet queued an image, AcquireOverlayView will return success,\n\t\t* and the Texture_t will have the expected ETextureType. However, the Texture_t->handle\n\t\t* will be nullptr. Once the producer generates the first overlay frame, Texture_t->handle\n\t\t* will become a valid handle.\n\t\t*/\n\t\tvirtual EVROverlayError AcquireOverlayView(VROverlayHandle_t ulOverlayHandle, VRNativeDevice_t *pNativeDevice, VROverlayView_t *pOverlayView, uint32_t unOverlayViewSize ) = 0;\n\n\t\t/** Release an acquired OverlayView_t\n\t\t*\n\t\t* Denotes that pOverlayView will no longer require access to the resources it acquired in\n\t\t* all previous calls to AcquireOverlayView().\n\t\t*\n\t\t* All OverlayView_t*'s provided to AcquireOverlayView() as pOverlayViews must be\n\t\t* passed into ReleaseOverlayView() in order for the underlying GPU resources to be freed.\n\t\t*/\n\t\tvirtual EVROverlayError ReleaseOverlayView(VROverlayView_t *pOverlayView) = 0;\n\n\t\t/** Posts an overlay event */\n\t\tvirtual void PostOverlayEvent(VROverlayHandle_t ulOverlayHandle, const VREvent_t *pvrEvent) = 0;\n\n\t\t/** Determines whether this process is permitted to view an overlay's content. */\n\t\tvirtual bool IsViewingPermitted( VROverlayHandle_t ulOverlayHandle ) = 0;\n\n\t};\n\n\tstatic const char * const IVROverlayView_Version = \"IVROverlayView_003\";\n\n}\n\n// ivrrendermodels.h\n\nnamespace vr\n{\n\nstatic const char * const k_pch_Controller_Component_GDC2015 = \"gdc2015\";\t\t\t// Canonical coordinate system of the gdc 2015 wired controller, provided for backwards compatibility\nstatic const char * const k_pch_Controller_Component_Base = \"base\";\t\t\t\t\t// For controllers with an unambiguous 'base'.\nstatic const char * const k_pch_Controller_Component_Tip = \"tip\";\t\t\t\t\t// OpenVR: For controllers with an unambiguous 'tip' (used for 'laser-pointing')\nstatic const char * const k_pch_Controller_Component_OpenXR_Aim= \"openxr_aim\";      // OpenXR: For controllers with an unambiguous 'tip' (used for 'laser-pointing')\nstatic const char * const k_pch_Controller_Component_HandGrip = \"handgrip\";\t\t\t// OpenVR: Neutral, ambidextrous hand-pose when holding controller. On plane between neutrally posed index finger and thumb\nstatic const char * const k_pch_Controller_Component_OpenXR_Grip = \"openxr_grip\";\t\t\t// OpenXR: Neutral, ambidextrous hand-pose when holding controller. On plane between neutrally posed index finger and thumb\nstatic const char * const k_pch_Controller_Component_OpenXR_HandModel = \"openxr_handmodel\";\t// OpenXR: Pose that can be used to place hand models & visuals that aren't reliant on the physical shape of a controller\nstatic const char * const k_pch_Controller_Component_Status = \"status\";\t\t\t\t\t\t// 1:1 aspect ratio status area, with canonical [0,1] uv mapping\n\n#pragma pack( push, 8 )\n\n/** Errors that can occur with the VR compositor */\nenum EVRRenderModelError\n{\n\tVRRenderModelError_None = 0,\n\tVRRenderModelError_Loading = 100,\n\tVRRenderModelError_NotSupported = 200,\n\tVRRenderModelError_InvalidArg = 300,\n\tVRRenderModelError_InvalidModel = 301,\n\tVRRenderModelError_NoShapes = 302,\n\tVRRenderModelError_MultipleShapes = 303,\n\tVRRenderModelError_TooManyVertices = 304,\n\tVRRenderModelError_MultipleTextures = 305,\n\tVRRenderModelError_BufferTooSmall = 306,\n\tVRRenderModelError_NotEnoughNormals = 307,\n\tVRRenderModelError_NotEnoughTexCoords = 308,\n\n\tVRRenderModelError_InvalidTexture = 400,\n};\n\nenum EVRRenderModelTextureFormat\n{\n\tVRRenderModelTextureFormat_RGBA8_SRGB = 0, // RGBA with 8 bits per channel per pixel. Data size is width * height * 4ub\n\tVRRenderModelTextureFormat_BC2,\n\tVRRenderModelTextureFormat_BC4,\n\tVRRenderModelTextureFormat_BC7,\n\tVRRenderModelTextureFormat_BC7_SRGB,\n\tVRRenderModelTextureFormat_RGBA16_FLOAT,\n};\n\n/** A single vertex in a render model */\nstruct RenderModel_Vertex_t\n{\n\tHmdVector3_t vPosition;\t\t// position in meters in device space\n\tHmdVector3_t vNormal;\n\tfloat rfTextureCoord[2];\n};\n\n/** A texture map for use on a render model */\n#if defined(__linux__) || defined(__APPLE__)\n// This structure was originally defined mis-packed on Linux, preserved for\n// compatibility.\n#pragma pack( push, 4 )\n#endif\n\nstruct RenderModel_TextureMap_t\n{\n\tuint16_t unWidth, unHeight; // width and height of the texture map in pixels\n\tconst uint8_t *rubTextureMapData;\t// Map texture data.\n\tEVRRenderModelTextureFormat format; // Refer to EVRRenderModelTextureFormat\n\tuint16_t unMipLevels;\n};\n#if defined(__linux__) || defined(__APPLE__)\n#pragma pack( pop )\n#endif\n\n/**  Session unique texture identifier. Rendermodels which share the same texture will have the same id.\nIDs <0 denote the texture is not present */\n\ntypedef int32_t TextureID_t;\n\nconst TextureID_t INVALID_TEXTURE_ID = -1;\n\n#if defined(__linux__) || defined(__APPLE__)\n// This structure was originally defined mis-packed on Linux, preserved for\n// compatibility.\n#pragma pack( push, 4 )\n#endif\n\nstruct RenderModel_t\n{\n\tconst RenderModel_Vertex_t *rVertexData;\t// Vertex data for the mesh\n\tuint32_t unVertexCount;\t\t\t\t\t\t// Number of vertices in the vertex data\n\tconst uint16_t *rIndexData;\t\t\t\t\t// Indices into the vertex data for each triangle\n\tuint32_t unTriangleCount;\t\t\t\t\t// Number of triangles in the mesh. Index count is 3 * TriangleCount\n\tTextureID_t diffuseTextureId;\t\t\t\t// Session unique texture identifier. Rendermodels which share the same texture will have the same id. <0 == texture not present\n};\n#if defined(__linux__) || defined(__APPLE__)\n#pragma pack( pop )\n#endif\n\n\nstruct RenderModel_ControllerMode_State_t\n{\n\tbool bScrollWheelVisible; // is this controller currently set to be in a scroll wheel mode\n};\n\n#pragma pack( pop )\n\nclass IVRRenderModels\n{\npublic:\n\n\t/** Loads and returns a render model for use in the application. pchRenderModelName should be a render model name\n\t* from the Prop_RenderModelName_String property or an absolute path name to a render model on disk.\n\t*\n\t* The resulting render model is valid until VR_Shutdown() is called or until FreeRenderModel() is called. When the\n\t* application is finished with the render model it should call FreeRenderModel() to free the memory associated\n\t* with the model.\n\t*\n\t* The method returns VRRenderModelError_Loading while the render model is still being loaded.\n\t* The method returns VRRenderModelError_None once loaded successfully, otherwise will return an error. */\n\tvirtual EVRRenderModelError LoadRenderModel_Async( const char *pchRenderModelName, RenderModel_t **ppRenderModel ) = 0;\n\n\t/** Frees a previously returned render model\n\t*   It is safe to call this on a null ptr. */\n\tvirtual void FreeRenderModel( RenderModel_t *pRenderModel ) = 0;\n\n\t/** Loads and returns a texture for use in the application. */\n\tvirtual EVRRenderModelError LoadTexture_Async( TextureID_t textureId, RenderModel_TextureMap_t **ppTexture ) = 0;\n\n\t/** Frees a previously returned texture\n\t*   It is safe to call this on a null ptr. */\n\tvirtual void FreeTexture( RenderModel_TextureMap_t *pTexture ) = 0;\n\n\t/** Creates a D3D11 texture and loads data into it. */\n\tvirtual EVRRenderModelError LoadTextureD3D11_Async( TextureID_t textureId, void *pD3D11Device, void **ppD3D11Texture2D ) = 0;\n\n\t/** Helper function to copy the bits into an existing texture. */\n\tvirtual EVRRenderModelError LoadIntoTextureD3D11_Async( TextureID_t textureId, void *pDstTexture ) = 0;\n\n\t/** Use this to free textures created with LoadTextureD3D11_Async instead of calling Release on them. */\n\tvirtual void FreeTextureD3D11( void *pD3D11Texture2D ) = 0;\n\n\t/** Use this to get the names of available render models.  Index does not correlate to a tracked device index, but\n\t* is only used for iterating over all available render models.  If the index is out of range, this function will return 0.\n\t* Otherwise, it will return the size of the buffer required for the name. */\n\tvirtual uint32_t GetRenderModelName( uint32_t unRenderModelIndex, VR_OUT_STRING() char *pchRenderModelName, uint32_t unRenderModelNameLen ) = 0;\n\n\t/** Returns the number of available render models. */\n\tvirtual uint32_t GetRenderModelCount() = 0;\n\n\n\t/** Returns the number of components of the specified render model.\n\t*  Components are useful when client application wish to draw, label, or otherwise interact with components of tracked objects.\n\t*  Examples controller components:\n\t*   renderable things such as triggers, buttons\n\t*   non-renderable things which include coordinate systems such as 'tip', 'base', a neutral controller agnostic hand-pose\n\t*   If all controller components are enumerated and rendered, it will be equivalent to drawing the traditional render model\n\t*   Returns 0 if components not supported, >0 otherwise */\n\tvirtual uint32_t GetComponentCount( const char *pchRenderModelName ) = 0;\n\n\t/** Use this to get the names of available components.  Index does not correlate to a tracked device index, but\n\t* is only used for iterating over all available components.  If the index is out of range, this function will return 0.\n\t* Otherwise, it will return the size of the buffer required for the name. */\n\tvirtual uint32_t GetComponentName( const char *pchRenderModelName, uint32_t unComponentIndex, VR_OUT_STRING( ) char *pchComponentName, uint32_t unComponentNameLen ) = 0;\n\n\t/** Get the button mask for all buttons associated with this component\n\t*   If no buttons (or axes) are associated with this component, return 0\n\t*   Note: multiple components may be associated with the same button. Ex: two grip buttons on a single controller.\n\t*   Note: A single component may be associated with multiple buttons. Ex: A trackpad which also provides \"D-pad\" functionality */\n\tvirtual uint64_t GetComponentButtonMask( const char *pchRenderModelName, const char *pchComponentName ) = 0;\n\n\t/** Use this to get the render model name for the specified rendermode/component combination, to be passed to LoadRenderModel.\n\t* If the component name is out of range, this function will return 0.\n\t* Otherwise, it will return the size of the buffer required for the name. */\n\tvirtual uint32_t GetComponentRenderModelName( const char *pchRenderModelName, const char *pchComponentName, VR_OUT_STRING( ) char *pchComponentRenderModelName, uint32_t unComponentRenderModelNameLen ) = 0;\n\n\t/** Use this to query information about the component, as a function of the controller state.\n\t*\n\t* For dynamic controller components (ex: trigger) values will reflect component motions\n\t* For static components this will return a consistent value independent of the VRControllerState_t\n\t*\n\t* If the pchRenderModelName or pchComponentName is invalid, this will return false (and transforms will be set to identity).\n\t* Otherwise, return true\n\t* Note: For dynamic objects, visibility may be dynamic. (I.e., true/false will be returned based on controller state and controller mode state ) */\n\tvirtual bool GetComponentStateForDevicePath( const char *pchRenderModelName, const char *pchComponentName, vr::VRInputValueHandle_t devicePath, const vr::RenderModel_ControllerMode_State_t *pState, vr::RenderModel_ComponentState_t *pComponentState ) = 0;\n\n\t/** This version of GetComponentState takes a controller state block instead of an action origin. This function is deprecated. You should use the new input system and GetComponentStateForDevicePath instead. */\n\tvirtual bool GetComponentState( const char *pchRenderModelName, const char *pchComponentName, const vr::VRControllerState_t *pControllerState, const RenderModel_ControllerMode_State_t *pState, RenderModel_ComponentState_t *pComponentState ) = 0;\n\n\t/** Returns true if the render model has a component with the specified name */\n\tvirtual bool RenderModelHasComponent( const char *pchRenderModelName, const char *pchComponentName ) = 0;\n\n\t/** Returns the URL of the thumbnail image for this rendermodel */\n\tvirtual uint32_t GetRenderModelThumbnailURL( const char *pchRenderModelName, VR_OUT_STRING() char *pchThumbnailURL, uint32_t unThumbnailURLLen, vr::EVRRenderModelError *peError ) = 0;\n\n\t/** Provides a render model path that will load the unskinned model if the model name provided has been replace by the user. If the model\n\t* hasn't been replaced the path value will still be a valid path to load the model. Pass this to LoadRenderModel_Async, etc. to load the\n\t* model. */\n\tvirtual uint32_t GetRenderModelOriginalPath( const char *pchRenderModelName, VR_OUT_STRING() char *pchOriginalPath, uint32_t unOriginalPathLen, vr::EVRRenderModelError *peError ) = 0;\n\n\t/** Returns a string for a render model error */\n\tvirtual const char *GetRenderModelErrorNameFromEnum( vr::EVRRenderModelError error ) = 0;\n};\n\nstatic const char * const IVRRenderModels_Version = \"IVRRenderModels_006\";\n\n}\n\n\n// ivrextendeddisplay.h\n\nnamespace vr\n{\n\n\t/** NOTE: Use of this interface is not recommended in production applications. It will not work for displays which use\n\t* direct-to-display mode. Creating our own window is also incompatible with the VR compositor and is not available when the compositor is running. */\n\tclass IVRExtendedDisplay\n\t{\n\tpublic:\n\n\t\t/** Size and position that the window needs to be on the VR display. */\n\t\tvirtual void GetWindowBounds( int32_t *pnX, int32_t *pnY, uint32_t *pnWidth, uint32_t *pnHeight ) = 0;\n\n\t\t/** Gets the viewport in the frame buffer to draw the output of the distortion into */\n\t\tvirtual void GetEyeOutputViewport( EVREye eEye, uint32_t *pnX, uint32_t *pnY, uint32_t *pnWidth, uint32_t *pnHeight ) = 0;\n\n\t\t/** [D3D10/11 Only]\n\t\t* Returns the adapter index and output index that the user should pass into EnumAdapters and EnumOutputs\n\t\t* to create the device and swap chain in DX10 and DX11. If an error occurs both indices will be set to -1.\n\t\t*/\n\t\tvirtual void GetDXGIOutputInfo( int32_t *pnAdapterIndex, int32_t *pnAdapterOutputIndex ) = 0;\n\n\t};\n\n\tstatic const char * const IVRExtendedDisplay_Version = \"IVRExtendedDisplay_001\";\n\n}\n\n\n// ivrtrackedcamera.h\n\nnamespace vr\n{\n\nclass IVRTrackedCamera\n{\npublic:\n\n\t// ------------------------------------\n\t// IVRTrackedCamera is used by client applications to poll for camera frames, when available.\n\t// This API has no relevance to driver writers adding camera support to a particular HMD.\n\t// ------------------------------------\n\n\t/** Returns a string for an error */\n\tvirtual const char *GetCameraErrorNameFromEnum( vr::EVRTrackedCameraError eCameraError ) = 0;\n\n\t/** For convenience, same as tracked property request Prop_HasCamera_Bool */\n\tvirtual vr::EVRTrackedCameraError HasCamera( vr::TrackedDeviceIndex_t nDeviceIndex, bool *pHasCamera ) = 0;\n\n\t/** Gets size of the image frame. */\n\tvirtual vr::EVRTrackedCameraError GetCameraFrameSize( vr::TrackedDeviceIndex_t nDeviceIndex, vr::EVRTrackedCameraFrameType eFrameType, uint32_t *pnWidth, uint32_t *pnHeight, uint32_t *pnFrameBufferSize ) = 0;\n\n\tvirtual vr::EVRTrackedCameraError GetCameraIntrinsics( vr::TrackedDeviceIndex_t nDeviceIndex, uint32_t nCameraIndex, vr::EVRTrackedCameraFrameType eFrameType, vr::HmdVector2_t *pFocalLength, vr::HmdVector2_t *pCenter ) = 0;\n\n\tvirtual vr::EVRTrackedCameraError GetCameraProjection( vr::TrackedDeviceIndex_t nDeviceIndex, uint32_t nCameraIndex, vr::EVRTrackedCameraFrameType eFrameType, float flZNear, float flZFar, vr::HmdMatrix44_t *pProjection ) = 0;\n\n\t/** Acquiring streaming service permits video streaming for the caller. Releasing hints the system that video services do not need to be maintained for this client.\n\t* If the camera has not already been activated, a one time spin up may incur some auto exposure as well as initial streaming frame delays.\n\t* The camera should be considered a global resource accessible for shared consumption but not exclusive to any caller.\n\t* The camera may go inactive due to lack of active consumers or headset idleness. */\n\tvirtual vr::EVRTrackedCameraError AcquireVideoStreamingService( vr::TrackedDeviceIndex_t nDeviceIndex, vr::TrackedCameraHandle_t *pHandle ) = 0;\n\tvirtual vr::EVRTrackedCameraError ReleaseVideoStreamingService( vr::TrackedCameraHandle_t hTrackedCamera ) = 0;\n\n\t/** Copies the image frame into a caller's provided buffer. The image data is currently provided as RGBA data, 4 bytes per pixel.\n\t* A caller can provide null for the framebuffer or frameheader if not desired. Requesting the frame header first, followed by the frame buffer allows\n\t* the caller to determine if the frame as advanced per the frame header sequence.\n\t* If there is no frame available yet, due to initial camera spinup or re-activation, the error will be VRTrackedCameraError_NoFrameAvailable.\n\t* Ideally a caller should be polling at ~16ms intervals */\n\tvirtual vr::EVRTrackedCameraError GetVideoStreamFrameBuffer( vr::TrackedCameraHandle_t hTrackedCamera, vr::EVRTrackedCameraFrameType eFrameType, void *pFrameBuffer, uint32_t nFrameBufferSize, vr::CameraVideoStreamFrameHeader_t *pFrameHeader, uint32_t nFrameHeaderSize ) = 0;\n\n\t/** Gets size of the image frame. */\n\tvirtual vr::EVRTrackedCameraError GetVideoStreamTextureSize( vr::TrackedDeviceIndex_t nDeviceIndex, vr::EVRTrackedCameraFrameType eFrameType, vr::VRTextureBounds_t *pTextureBounds, uint32_t *pnWidth, uint32_t *pnHeight ) = 0;\n\n\t/** Access a shared D3D11 texture for the specified tracked camera stream.\n\t* The camera frame type VRTrackedCameraFrameType_Undistorted is not supported directly as a shared texture. It is an interior subregion of the shared texture VRTrackedCameraFrameType_MaximumUndistorted.\n\t* Instead, use GetVideoStreamTextureSize() with VRTrackedCameraFrameType_Undistorted to determine the proper interior subregion bounds along with GetVideoStreamTextureD3D11() with\n\t* VRTrackedCameraFrameType_MaximumUndistorted to provide the texture. The VRTrackedCameraFrameType_MaximumUndistorted will yield an image where the invalid regions are decoded\n\t* by the alpha channel having a zero component. The valid regions all have a non-zero alpha component. The subregion as described by VRTrackedCameraFrameType_Undistorted\n\t* guarantees a rectangle where all pixels are valid. */\n\tvirtual vr::EVRTrackedCameraError GetVideoStreamTextureD3D11( vr::TrackedCameraHandle_t hTrackedCamera, vr::EVRTrackedCameraFrameType eFrameType, void *pD3D11DeviceOrResource, void **ppD3D11ShaderResourceView, vr::CameraVideoStreamFrameHeader_t *pFrameHeader, uint32_t nFrameHeaderSize ) = 0;\n\n\t/** Access a shared GL texture for the specified tracked camera stream */\n\tvirtual vr::EVRTrackedCameraError GetVideoStreamTextureGL( vr::TrackedCameraHandle_t hTrackedCamera, vr::EVRTrackedCameraFrameType eFrameType, vr::glUInt_t *pglTextureId, vr::CameraVideoStreamFrameHeader_t *pFrameHeader, uint32_t nFrameHeaderSize ) = 0;\n\tvirtual vr::EVRTrackedCameraError ReleaseVideoStreamTextureGL( vr::TrackedCameraHandle_t hTrackedCamera, vr::glUInt_t glTextureId ) = 0;\n\tvirtual void SetCameraTrackingSpace( vr::ETrackingUniverseOrigin eUniverse ) = 0;\n\tvirtual vr::ETrackingUniverseOrigin GetCameraTrackingSpace( ) = 0;\n};\n\nstatic const char * const IVRTrackedCamera_Version = \"IVRTrackedCamera_006\";\n\n} // namespace vr\n\n\n// ivrscreenshots.h\n\nnamespace vr\n{\n\n/** Errors that can occur with the VR compositor */\nenum EVRScreenshotError\n{\n\tVRScreenshotError_None\t\t\t\t\t\t\t= 0,\n\tVRScreenshotError_RequestFailed\t\t\t\t\t= 1,\n\tVRScreenshotError_IncompatibleVersion\t\t\t= 100,\n\tVRScreenshotError_NotFound\t\t\t\t\t\t= 101,\n\tVRScreenshotError_BufferTooSmall\t\t\t\t= 102,\n\tVRScreenshotError_ScreenshotAlreadyInProgress\t= 108,\n};\n\n/** Allows the application to generate screenshots */\nclass IVRScreenshots\n{\npublic:\n\t/** Request a screenshot of the requested type.\n\t *  A request of the VRScreenshotType_Stereo type will always\n\t *  work. Other types will depend on the underlying application\n\t *  support.\n\t *  The first file name is for the preview image and should be a\n\t *  regular screenshot (ideally from the left eye). The second\n\t *  is the VR screenshot in the correct format. They should be\n\t *  in the same aspect ratio.  Formats per type:\n\t *  VRScreenshotType_Mono: the VR filename is ignored (can be\n\t *  nullptr), this is a normal flat single shot.\n\t *  VRScreenshotType_Stereo:  The VR image should be a\n\t *  side-by-side with the left eye image on the left.\n\t *  VRScreenshotType_Cubemap: The VR image should be six square\n\t *  images composited horizontally.\n\t *  VRScreenshotType_StereoPanorama: above/below with left eye\n\t *  panorama being the above image.  Image is typically square\n\t *  with the panorama being 2x horizontal.\n\t *\n\t *  Note that the VR dashboard will call this function when\n\t *  the user presses the screenshot binding (currently System\n\t *  Button + Trigger).  If Steam is running, the destination\n\t *  file names will be in %TEMP% and will be copied into\n\t *  Steam's screenshot library for the running application\n\t *  once SubmitScreenshot() is called.\n\t *  If Steam is not running, the paths will be in the user's\n\t *  documents folder under Documents\\SteamVR\\Screenshots.\n\t *  Other VR applications can call this to initiate a\n\t *  screenshot outside of user control.\n\t *  The destination file names do not need an extension,\n\t *  will be replaced with the correct one for the format\n\t *  which is currently .png. */\n\tvirtual vr::EVRScreenshotError RequestScreenshot( vr::ScreenshotHandle_t *pOutScreenshotHandle, vr::EVRScreenshotType type, const char *pchPreviewFilename, const char *pchVRFilename ) = 0;\n\n\t/** Called by the running VR application to indicate that it\n\t *  wishes to be in charge of screenshots.  If the\n\t *  application does not call this, the Compositor will only\n\t *  support VRScreenshotType_Stereo screenshots that will be\n\t *  captured without notification to the running app.\n\t *  Once hooked your application will receive a\n\t *  VREvent_RequestScreenshot event when the user presses the\n\t *  buttons to take a screenshot. */\n\tvirtual vr::EVRScreenshotError HookScreenshot( VR_ARRAY_COUNT( numTypes ) const vr::EVRScreenshotType *pSupportedTypes, int numTypes ) = 0;\n\n\t/** When your application receives a\n\t *  VREvent_RequestScreenshot event, call these functions to get\n\t *  the details of the screenshot request. */\n\tvirtual vr::EVRScreenshotType GetScreenshotPropertyType( vr::ScreenshotHandle_t screenshotHandle, vr::EVRScreenshotError *pError ) = 0;\n\n\t/** Get the filename for the preview or vr image (see\n\t *  vr::EScreenshotPropertyFilenames).  The return value is\n\t *  the size of the string.   */\n \tvirtual uint32_t GetScreenshotPropertyFilename( vr::ScreenshotHandle_t screenshotHandle, vr::EVRScreenshotPropertyFilenames filenameType, VR_OUT_STRING() char *pchFilename, uint32_t cchFilename, vr::EVRScreenshotError *pError ) = 0;\n\n\t/** Call this if the application is taking the screen shot\n\t *  will take more than a few ms processing. This will result\n\t *  in an overlay being presented that shows a completion\n\t *  bar. */\n\tvirtual vr::EVRScreenshotError UpdateScreenshotProgress( vr::ScreenshotHandle_t screenshotHandle, float flProgress ) = 0;\n\n\t/** Tells the compositor to take an internal screenshot of\n\t *  type VRScreenshotType_Stereo. It will take the current\n\t *  submitted scene textures of the running application and\n\t *  write them into the preview image and a side-by-side file\n\t *  for the VR image.\n\t *  This is similar to request screenshot, but doesn't ever\n\t *  talk to the application, just takes the shot and submits. */\n\tvirtual vr::EVRScreenshotError TakeStereoScreenshot( vr::ScreenshotHandle_t *pOutScreenshotHandle, const char *pchPreviewFilename, const char *pchVRFilename ) = 0;\n\n\t/** Submit the completed screenshot.  If Steam is running\n\t *  this will call into the Steam client and upload the\n\t *  screenshot to the screenshots section of the library for\n\t *  the running application.  If Steam is not running, this\n\t *  function will display a notification to the user that the\n\t *  screenshot was taken. The paths should be full paths with\n\t *  extensions.\n\t *  File paths should be absolute including extensions.\n\t *  screenshotHandle can be k_unScreenshotHandleInvalid if this\n\t *  was a new shot taking by the app to be saved and not\n\t *  initiated by a user (achievement earned or something) */\n\tvirtual vr::EVRScreenshotError SubmitScreenshot( vr::ScreenshotHandle_t screenshotHandle, vr::EVRScreenshotType type, const char *pchSourcePreviewFilename, const char *pchSourceVRFilename ) = 0;\n};\n\nstatic const char * const IVRScreenshots_Version = \"IVRScreenshots_001\";\n\n} // namespace vr\n\n\n\n// ivrresources.h\n\nnamespace vr\n{\n\nclass IVRResources\n{\npublic:\n\n\t// ------------------------------------\n\t// Shared Resource Methods\n\t// ------------------------------------\n\n\t/** Loads the specified resource into the provided buffer if large enough.\n\t* Returns the size in bytes of the buffer required to hold the specified resource. */\n\tvirtual uint32_t LoadSharedResource( const char *pchResourceName, char *pchBuffer, uint32_t unBufferLen ) = 0;\n\n\t/** Provides the full path to the specified resource. Resource names can include named directories for\n\t* drivers and other things, and this resolves all of those and returns the actual physical path.\n\t* pchResourceTypeDirectory is the subdirectory of resources to look in. */\n\tvirtual uint32_t GetResourceFullPath( const char *pchResourceName, const char *pchResourceTypeDirectory, VR_OUT_STRING() char *pchPathBuffer, uint32_t unBufferLen ) = 0;\n};\n\nstatic const char * const IVRResources_Version = \"IVRResources_001\";\n\n\n}\n\n// ivrdrivermanager.h\n\nnamespace vr\n{\n\nclass IVRDriverManager\n{\npublic:\n\tvirtual uint32_t GetDriverCount() const = 0;\n\n\t/** Returns the length of the number of bytes necessary to hold this string including the trailing null. */\n\tvirtual uint32_t GetDriverName( vr::DriverId_t nDriver, VR_OUT_STRING() char *pchValue, uint32_t unBufferSize ) = 0;\n\n\tvirtual DriverHandle_t GetDriverHandle( const char *pchDriverName ) = 0;\n\n\tvirtual bool IsEnabled( vr::DriverId_t nDriver ) const = 0;\n};\n\nstatic const char * const IVRDriverManager_Version = \"IVRDriverManager_001\";\n\n} // namespace vr\n\n\n\n// ivrinput.h\n\nnamespace vr\n{\n\t// Maximum number of characters in an action name, including the trailing null\n\tstatic const uint32_t k_unMaxActionNameLength = 64;\n\n\t// Maximum number of characters in an action set name, including the trailing null\n\tstatic const uint32_t k_unMaxActionSetNameLength = 64;\n\n\t// Maximum number of origins for an action\n\tstatic const uint32_t k_unMaxActionOriginCount = 16;\n\n\t// Maximum number of characters in a bone name, including the trailing null\n\tstatic const uint32_t k_unMaxBoneNameLength = 32;\n\n\tenum EVRSkeletalTransformSpace\n\t{\n\t\tVRSkeletalTransformSpace_Model = 0,\n\t\tVRSkeletalTransformSpace_Parent = 1\n\t};\n\n\tenum EVRSkeletalReferencePose\n\t{\n\t\tVRSkeletalReferencePose_BindPose = 0,\n\t\tVRSkeletalReferencePose_OpenHand,\n\t\tVRSkeletalReferencePose_Fist,\n\t\tVRSkeletalReferencePose_GripLimit\n\t};\n\n\tenum EVRFinger\n\t{\n\t\tVRFinger_Thumb = 0,\n\t\tVRFinger_Index,\n\t\tVRFinger_Middle,\n\t\tVRFinger_Ring,\n\t\tVRFinger_Pinky,\n\t\tVRFinger_Count\n\t};\n\n\tenum EVRFingerSplay\n\t{\n\t\tVRFingerSplay_Thumb_Index = 0,\n\t\tVRFingerSplay_Index_Middle,\n\t\tVRFingerSplay_Middle_Ring,\n\t\tVRFingerSplay_Ring_Pinky,\n\t\tVRFingerSplay_Count\n\t};\n\n\tenum EVRSummaryType\n\t{\n\t\t// The skeletal summary data will match the animated bone transforms for the action.\n\t\tVRSummaryType_FromAnimation = 0,\n\n\t\t// The skeletal summary data will include unprocessed data directly from the device when available.\n\t\t// This data is generally less latent than the data that is computed from the animations.\n\t\tVRSummaryType_FromDevice = 1,\n\t};\n\n\tenum EVRInputFilterCancelType\n\t{\n\t\tVRInputFilterCancel_Timers = 0,\n\t\tVRInputFilterCancel_Momentum = 1,\n\t};\n\n\tenum EVRInputStringBits\n\t{\n\t\tVRInputString_Hand = 0x01,\n\t\tVRInputString_ControllerType = 0x02,\n\t\tVRInputString_InputSource = 0x04,\n\n\t\tVRInputString_All = 0xFFFFFFFF\n\t};\n\n\tstruct InputAnalogActionData_t\n\t{\n\t\t/** Whether or not this action is currently available to be bound in the active action set */\n\t\tbool bActive;\n\n\t\t/** The origin that caused this action's current state */\n\t\tVRInputValueHandle_t activeOrigin;\n\n\t\t/** The current state of this action; will be delta updates for mouse actions */\n\t\tfloat x, y, z;\n\n\t\t/** Deltas since the previous call to UpdateActionState() */\n\t\tfloat deltaX, deltaY, deltaZ;\n\n\t\t/** Time relative to now when this event happened. Will be negative to indicate a past time. */\n\t\tfloat fUpdateTime;\n\t};\n\n\tstruct InputDigitalActionData_t\n\t{\n\t\t/** Whether or not this action is currently available to be bound in the active action set */\n\t\tbool bActive;\n\n\t\t/** The origin that caused this action's current state */\n\t\tVRInputValueHandle_t activeOrigin;\n\n\t\t/** The current state of this action; will be true if currently pressed */\n\t\tbool bState;\n\n\t\t/** This is true if the state has changed since the last frame */\n\t\tbool bChanged;\n\n\t\t/** Time relative to now when this event happened. Will be negative to indicate a past time. */\n\t\tfloat fUpdateTime;\n\t};\n\n\tstruct InputPoseActionData_t\n\t{\n\t\t/** Whether or not this action is currently available to be bound in the active action set */\n\t\tbool bActive;\n\n\t\t/** The origin that caused this action's current state */\n\t\tVRInputValueHandle_t activeOrigin;\n\n\t\t/** The current state of this action */\n\t\tTrackedDevicePose_t pose;\n\t};\n\n\tstruct InputSkeletalActionData_t\n\t{\n\t\t/** Whether or not this action is currently available to be bound in the active action set */\n\t\tbool bActive;\n\n\t\t/** The origin that caused this action's current state */\n\t\tVRInputValueHandle_t activeOrigin;\n\t};\n\n\tstruct InputOriginInfo_t\n\t{\n\t\tVRInputValueHandle_t devicePath;\n\t\tTrackedDeviceIndex_t trackedDeviceIndex;\n\t\tchar rchRenderModelComponentName[128];\n\t};\n\n\tstruct InputBindingInfo_t\n\t{\n\t\tchar rchDevicePathName[128];\n\t\tchar rchInputPathName[128];\n\t\tchar rchModeName[128];\n\t\tchar rchSlotName[128];\n\t\tchar rchInputSourceType[ 32 ];\n\t};\n\n\t// * Experimental global action set priority *\n\t// These constants are part of the experimental support in SteamVR for overlay\n\t// apps selectively overriding input in the base scene application. This may be\n\t// useful for overlay applications that need to use part or all of a controller\n\t// without taking away all input to the game. This system must be enabled by the\n\t// \"Experimental overlay input overrides\" setting in the developer section of\n\t// SteamVR settings.\n\t//\n\t// To use this system, set the nPriority field of an action set to any number in\n\t// this range.\n\tstatic const int32_t k_nActionSetOverlayGlobalPriorityMin\t= 0x01000000;\n\tstatic const int32_t k_nActionSetOverlayGlobalPriorityMax\t= 0x01FFFFFF;\n\n\tstatic const int32_t k_nActionSetPriorityReservedMin\t\t= 0x02000000;\n\n\tstruct VRActiveActionSet_t\n\t{\n\t\t/** This is the handle of the action set to activate for this frame. */\n\t\tVRActionSetHandle_t ulActionSet;\n\n\t\t/** This is the handle of a device path that this action set should be active for. To\n\t\t* activate for all devices, set this to k_ulInvalidInputValueHandle. */\n\t\tVRInputValueHandle_t ulRestrictedToDevice;\n\n\t\t/** The action set to activate for all devices other than ulRestrictedDevice. If\n\t\t* ulRestrictedToDevice is set to k_ulInvalidInputValueHandle, this parameter is\n\t\t* ignored. */\n\t\tVRActionSetHandle_t ulSecondaryActionSet;\n\n\t\t// This field is ignored\n\t\tuint32_t unPadding;\n\n\t\t/** The priority of this action set relative to other action sets. Any inputs\n\t\t* bound to a source (e.g. trackpad, joystick, trigger) will disable bindings in\n\t\t* other active action sets with a smaller priority.\n\t\t*\n\t\t* Overlay applications (i.e. ApplicationType_Overlay) may set their action set priority\n\t\t* to a value between k_nActionSetOverlayGlobalPriorityMin and k_nActionSetOverlayGlobalPriorityMax\n\t\t* to cause any inputs bound to a source used by that action set to be disabled in scene applications.\n\t\t*\n\t\t* No action set priority may value may be larger than k_nActionSetPriorityReservedMin\n\t\t*/\n\t\tint32_t nPriority;\n\t};\n\n\t/** Contains summary information about the current skeletal pose */\n\tstruct VRSkeletalSummaryData_t\n\t{\n\t\t/** The amount that each finger is 'curled' inwards towards the palm.  In the case of the thumb,\n\t\t* this represents how much the thumb is wrapped around the fist.\n\t\t* 0 means straight, 1 means fully curled */\n\t\tfloat\tflFingerCurl[ VRFinger_Count ];\n\n\t\t/** The amount that each pair of adjacent fingers are separated.\n\t\t* 0 means the digits are touching, 1 means they are fully separated.\n\t\t*/\n\t\tfloat\tflFingerSplay[ VRFingerSplay_Count ];\n\t};\n\n\n\tclass IVRInput\n\t{\n\tpublic:\n\n\t\t// ---------------  Handle management   --------------- //\n\n\t\t/** Sets the path to the action manifest JSON file that is used by this application. If this information\n\t\t* was set on the Steam partner site, calls to this function are ignored. If the Steam partner site\n\t\t* setting and the path provided by this call are different, VRInputError_MismatchedActionManifest is returned.\n\t\t* This call must be made before the first call to UpdateActionState or IVRSystem::PollNextEvent. */\n\t\tvirtual EVRInputError SetActionManifestPath( const char *pchActionManifestPath ) = 0;\n\n\t\t/** Returns a handle for an action set. This handle is used for all performance-sensitive calls. */\n\t\tvirtual EVRInputError GetActionSetHandle( const char *pchActionSetName, VRActionSetHandle_t *pHandle ) = 0;\n\n\t\t/** Returns a handle for an action. This handle is used for all performance-sensitive calls. */\n\t\tvirtual EVRInputError GetActionHandle( const char *pchActionName, VRActionHandle_t *pHandle ) = 0;\n\n\t\t/** Returns a handle for any path in the input system. E.g. /user/hand/right */\n\t\tvirtual EVRInputError GetInputSourceHandle( const char *pchInputSourcePath, VRInputValueHandle_t *pHandle ) = 0;\n\n\n\n\t\t// --------------- Reading action state ------------------- //\n\n\t\t/** Reads the current state into all actions. After this call, the results of Get*Action calls\n\t\t* will be the same until the next call to UpdateActionState. */\n\t\tvirtual EVRInputError UpdateActionState( VR_ARRAY_COUNT( unSetCount ) VRActiveActionSet_t *pSets, uint32_t unSizeOfVRSelectedActionSet_t, uint32_t unSetCount ) = 0;\n\n\t\t/** Reads the state of a digital action given its handle. This will return VRInputError_WrongType if the type of\n\t\t* action is something other than digital */\n\t\tvirtual EVRInputError GetDigitalActionData( VRActionHandle_t action, InputDigitalActionData_t *pActionData, uint32_t unActionDataSize, VRInputValueHandle_t ulRestrictToDevice ) = 0;\n\n\t\t/** Reads the state of an analog action given its handle. This will return VRInputError_WrongType if the type of\n\t\t* action is something other than analog */\n\t\tvirtual EVRInputError GetAnalogActionData( VRActionHandle_t action, InputAnalogActionData_t *pActionData, uint32_t unActionDataSize, VRInputValueHandle_t ulRestrictToDevice ) = 0;\n\n\t\t/** Reads the state of a pose action given its handle for the number of seconds relative to now. This\n\t\t* will generally be called with negative times from the fUpdateTime fields in other actions. */\n\t\tvirtual EVRInputError GetPoseActionDataRelativeToNow( VRActionHandle_t action, ETrackingUniverseOrigin eOrigin, float fPredictedSecondsFromNow, InputPoseActionData_t *pActionData, uint32_t unActionDataSize, VRInputValueHandle_t ulRestrictToDevice ) = 0;\n\n\t\t/** Reads the state of a pose action given its handle. The returned values will match the values returned\n\t\t* by the last call to IVRCompositor::WaitGetPoses(). */\n\t\tvirtual EVRInputError GetPoseActionDataForNextFrame( VRActionHandle_t action, ETrackingUniverseOrigin eOrigin, InputPoseActionData_t *pActionData, uint32_t unActionDataSize, VRInputValueHandle_t ulRestrictToDevice ) = 0;\n\n\t\t/** Reads the state of a skeletal action given its handle. */\n\t\tvirtual EVRInputError GetSkeletalActionData( VRActionHandle_t action, InputSkeletalActionData_t *pActionData, uint32_t unActionDataSize ) = 0;\n\n\t\t/** Returns the current dominant hand for the user for this application. This function will only return success for applications\n\t\t* which include \"supports_dominant_hand_setting\": true in their action manifests. The dominant hand will only change after\n\t\t* a call to UpdateActionState, and the action data returned after that point will use the new dominant hand. */\n\t\tvirtual EVRInputError GetDominantHand( ETrackedControllerRole *peDominantHand ) = 0;\n\n\t\t/** Sets the dominant hand for the user for this application. */\n\t\tvirtual EVRInputError SetDominantHand( ETrackedControllerRole eDominantHand ) = 0;\n\n\t\t/** Reads the state of an eye tracking action given its handle for the number of seconds relative to now.\n\t\t * This will generally be called with negative times from the fUpdateTime fields in other actions. */\n\t\tvirtual EVRInputError GetEyeTrackingDataRelativeToNow( VRActionHandle_t action, vr::ETrackingUniverseOrigin eOrigin, float fPredictedSecondsFromNow, vr::VREyeTrackingData_t *pEyeTrackingData, uint32_t ulEyeTrackingDataSize ) = 0;\n\n\t\t/** Reads the state of an eye tracking action given its handle. The returned data will be for the frame\n\t\t * predicted by last call to IVRCompositor::WaitGetPoses(). */\n\t\tvirtual EVRInputError GetEyeTrackingDataForNextFrame( VRActionHandle_t action, vr::ETrackingUniverseOrigin eOrigin, vr::VREyeTrackingData_t *pEyeTrackingData, uint32_t ulEyeTrackingDataSize ) = 0;\n\n\t\t// ---------------  Static Skeletal Data ------------------- //\n\n\t\t/** Reads the number of bones in skeleton associated with the given action */\n\t\tvirtual EVRInputError GetBoneCount( VRActionHandle_t action, uint32_t* pBoneCount ) = 0;\n\n\t\t/** Fills the given array with the index of each bone's parent in the skeleton associated with the given action */\n\t\tvirtual EVRInputError GetBoneHierarchy( VRActionHandle_t action, VR_ARRAY_COUNT( unIndexArayCount ) BoneIndex_t* pParentIndices, uint32_t unIndexArayCount ) = 0;\n\n\t\t/** Fills the given buffer with the name of the bone at the given index in the skeleton associated with the given action */\n\t\tvirtual EVRInputError GetBoneName( VRActionHandle_t action, BoneIndex_t nBoneIndex, VR_OUT_STRING() char* pchBoneName, uint32_t unNameBufferSize ) = 0;\n\n\t\t/** Fills the given buffer with the transforms for a specific static skeletal reference pose */\n\t\tvirtual EVRInputError GetSkeletalReferenceTransforms( VRActionHandle_t action, EVRSkeletalTransformSpace eTransformSpace, EVRSkeletalReferencePose eReferencePose, VR_ARRAY_COUNT( unTransformArrayCount ) VRBoneTransform_t *pTransformArray, uint32_t unTransformArrayCount ) = 0;\n\n\t\t/** Reads the level of accuracy to which the controller is able to track the user to recreate a skeletal pose */\n\t\tvirtual EVRInputError GetSkeletalTrackingLevel( VRActionHandle_t action, EVRSkeletalTrackingLevel* pSkeletalTrackingLevel ) = 0;\n\n\t\t// ---------------  Dynamic Skeletal Data ------------------- //\n\n\t\t/** Reads the state of the skeletal bone data associated with this action and copies it into the given buffer. */\n\t\tvirtual EVRInputError GetSkeletalBoneData( VRActionHandle_t action, EVRSkeletalTransformSpace eTransformSpace, EVRSkeletalMotionRange eMotionRange, VR_ARRAY_COUNT( unTransformArrayCount ) VRBoneTransform_t *pTransformArray, uint32_t unTransformArrayCount ) = 0;\n\n\t\t/** Reads summary information about the current pose of the skeleton associated with the given action.   */\n\t\tvirtual EVRInputError GetSkeletalSummaryData( VRActionHandle_t action, EVRSummaryType eSummaryType, VRSkeletalSummaryData_t * pSkeletalSummaryData ) = 0;\n\n\t\t/** Reads the state of the skeletal bone data in a compressed form that is suitable for\n\t\t* sending over the network. The required buffer size will never exceed ( sizeof(VR_BoneTransform_t)*boneCount + 2).\n\t\t* Usually the size will be much smaller. */\n\t\tvirtual EVRInputError GetSkeletalBoneDataCompressed( VRActionHandle_t action, EVRSkeletalMotionRange eMotionRange, VR_OUT_BUFFER_COUNT( unCompressedSize ) void *pvCompressedData, uint32_t unCompressedSize, uint32_t *punRequiredCompressedSize ) = 0;\n\n\t\t/** Turns a compressed buffer from GetSkeletalBoneDataCompressed and turns it back into a bone transform array. */\n\t\tvirtual EVRInputError DecompressSkeletalBoneData( const void *pvCompressedBuffer, uint32_t unCompressedBufferSize, EVRSkeletalTransformSpace eTransformSpace, VR_ARRAY_COUNT( unTransformArrayCount ) VRBoneTransform_t *pTransformArray, uint32_t unTransformArrayCount ) = 0;\n\n\t\t// --------------- Haptics ------------------- //\n\n\t\t/** Triggers a haptic event as described by the specified action */\n\t\tvirtual EVRInputError TriggerHapticVibrationAction( VRActionHandle_t action, float fStartSecondsFromNow, float fDurationSeconds, float fFrequency, float fAmplitude, VRInputValueHandle_t ulRestrictToDevice ) = 0;\n\n\t\t// --------------- Action Origins ---------------- //\n\n\t\t/** Retrieve origin handles for an action */\n\t\tvirtual EVRInputError GetActionOrigins( VRActionSetHandle_t actionSetHandle, VRActionHandle_t digitalActionHandle, VR_ARRAY_COUNT( originOutCount ) VRInputValueHandle_t *originsOut, uint32_t originOutCount ) = 0;\n\n\t\t/** Retrieves the name of the origin in the current language. unStringSectionsToInclude is a bitfield of values in EVRInputStringBits that allows the\n\t\t\tapplication to specify which parts of the origin's information it wants a string for. */\n\t\tvirtual EVRInputError GetOriginLocalizedName( VRInputValueHandle_t origin, VR_OUT_STRING() char *pchNameArray, uint32_t unNameArraySize, int32_t unStringSectionsToInclude ) = 0;\n\n\t\t/** Retrieves useful information for the origin of this action */\n\t\tvirtual EVRInputError GetOriginTrackedDeviceInfo( VRInputValueHandle_t origin, InputOriginInfo_t *pOriginInfo, uint32_t unOriginInfoSize ) = 0;\n\n\t\t/** Retrieves useful information about the bindings for an action */\n\t\tvirtual EVRInputError GetActionBindingInfo( VRActionHandle_t action, VR_ARRAY_COUNT( unBindingInfoCount ) InputBindingInfo_t *pOriginInfo, uint32_t unBindingInfoSize, uint32_t unBindingInfoCount, uint32_t *punReturnedBindingInfoCount ) = 0;\n\n\t\t/** Shows the current binding for the action in-headset */\n\t\tvirtual EVRInputError ShowActionOrigins( VRActionSetHandle_t actionSetHandle, VRActionHandle_t ulActionHandle ) = 0;\n\n\t\t/** Shows the current binding all the actions in the specified action sets */\n\t\tvirtual EVRInputError ShowBindingsForActionSet( VR_ARRAY_COUNT( unSetCount ) VRActiveActionSet_t *pSets, uint32_t unSizeOfVRSelectedActionSet_t, uint32_t unSetCount, VRInputValueHandle_t originToHighlight ) = 0;\n\n\t\t/** Use this to query what action on the component returned by GetOriginTrackedDeviceInfo would trigger this binding. */\n\t\tvirtual EVRInputError GetComponentStateForBinding( const char *pchRenderModelName, const char *pchComponentName,\n\t\t\tconst InputBindingInfo_t *pOriginInfo, uint32_t unBindingInfoSize, uint32_t unBindingInfoCount,\n\t\t\tvr::RenderModel_ComponentState_t *pComponentState ) = 0;\n\n\n\t\t// --------------- Legacy Input ------------------- //\n\t\tvirtual bool IsUsingLegacyInput() = 0;\n\n\n\t\t// --------------- Utilities ------------------- //\n\n\t\t/** Opens the binding user interface. If no app key is provided it will use the key from the calling process.\n\t\t* If no set is provided it will open to the root of the app binding page. */\n\t\tvirtual EVRInputError OpenBindingUI( const char* pchAppKey, VRActionSetHandle_t ulActionSetHandle, VRInputValueHandle_t ulDeviceHandle, bool bShowOnDesktop ) = 0;\n\n\t\t/** Returns the variant set in the current bindings. If the binding doesn't include a variant setting, this function\n\t\t* will return an empty string */\n\t\tvirtual EVRInputError GetBindingVariant( vr::VRInputValueHandle_t ulDevicePath,\n\t\t\tVR_OUT_STRING() char *pchVariantArray, uint32_t unVariantArraySize ) = 0;\n\n\t};\n\n\tstatic const char * const IVRInput_Version = \"IVRInput_011\";\n\n} // namespace vr\n\n// ivriobuffer.h\n\nnamespace vr\n{\n\ntypedef uint64_t IOBufferHandle_t;\nstatic const uint64_t k_ulInvalidIOBufferHandle = 0;\n\n\tenum EIOBufferError\n\t{\n\t\tIOBuffer_Success                  = 0,\n\t\tIOBuffer_OperationFailed          = 100,\n\t\tIOBuffer_InvalidHandle            = 101,\n\t\tIOBuffer_InvalidArgument          = 102,\n\t\tIOBuffer_PathExists               = 103,\n\t\tIOBuffer_PathDoesNotExist         = 104,\n\t\tIOBuffer_Permission               = 105,\n\t};\n\n\tenum EIOBufferMode\n\t{\n\t\tIOBufferMode_Read                 = 0x0001,\n\t\tIOBufferMode_Write                = 0x0002,\n\t\tIOBufferMode_Create               = 0x0200,\n\t};\n\n\t// ----------------------------------------------------------------------------------------------\n\t// Purpose:\n\t// ----------------------------------------------------------------------------------------------\n\tclass IVRIOBuffer\n\t{\n\tpublic:\n\t\t/** opens an existing or creates a new IOBuffer of unSize bytes */\n\t\tvirtual vr::EIOBufferError Open( const char *pchPath, vr::EIOBufferMode mode, uint32_t unElementSize, uint32_t unElements, vr::IOBufferHandle_t *pulBuffer ) = 0;\n\n\t\t/** closes a previously opened or created buffer */\n\t\tvirtual vr::EIOBufferError Close( vr::IOBufferHandle_t ulBuffer ) = 0;\n\n\t\t/** reads up to unBytes from buffer into *pDst, returning number of bytes read in *punRead */\n\t\tvirtual vr::EIOBufferError Read( vr::IOBufferHandle_t ulBuffer, void *pDst, uint32_t unBytes, uint32_t *punRead ) = 0;\n\n\t\t/** writes unBytes of data from *pSrc into a buffer. */\n\t\tvirtual vr::EIOBufferError Write( vr::IOBufferHandle_t ulBuffer, void *pSrc, uint32_t unBytes ) = 0;\n\n\t\t/** retrieves the property container of an buffer. */\n\t\tvirtual vr::PropertyContainerHandle_t PropertyContainer( vr::IOBufferHandle_t ulBuffer ) = 0;\n\n\t\t/** inexpensively checks for readers to allow writers to fast-fail potentially expensive copies and writes. */\n\t\tvirtual bool HasReaders( vr::IOBufferHandle_t ulBuffer ) = 0;\n\t};\n\n\tstatic const char * const IVRIOBuffer_Version = \"IVRIOBuffer_002\";\n}\n\n// ivrspatialanchors.h\n\nnamespace vr\n{\n\tstatic const SpatialAnchorHandle_t k_ulInvalidSpatialAnchorHandle = 0;\n\n\tstruct SpatialAnchorPose_t\n\t{\n\t\tHmdMatrix34_t mAnchorToAbsoluteTracking;\n\t};\n\n\tclass IVRSpatialAnchors\n\t{\n\tpublic:\n\n\t\t/** Returns a handle for an spatial anchor described by \"descriptor\".  On success, pHandle\n\t\t* will contain a handle valid for this session.  Caller can wait for an event or occasionally\n\t\t* poll GetSpatialAnchorPose() to find the virtual coordinate associated with this anchor. */\n\t\tvirtual EVRSpatialAnchorError CreateSpatialAnchorFromDescriptor( const char *pchDescriptor, SpatialAnchorHandle_t *pHandleOut ) = 0;\n\n\t\t/** Returns a handle for an new spatial anchor at pPose.  On success, pHandle\n\t\t* will contain a handle valid for this session.  Caller can wait for an event or occasionally\n\t\t* poll GetSpatialAnchorDescriptor() to find the permanent descriptor for this pose.\n\t\t* The result of GetSpatialAnchorPose() may evolve from this initial position if the driver chooses\n\t\t* to update it.\n\t\t* The anchor will be associated with the driver that provides unDeviceIndex, and the driver may use that specific\n\t\t* device as a hint for how to best create the anchor.\n\t\t* The eOrigin must match whatever tracking origin you are working in (seated/standing/raw).\n\t\t* This should be called when the user is close to (and ideally looking at/interacting with) the target physical\n\t\t* location.  At that moment, the driver will have the most information about how to recover that physical point\n\t\t* in the future, and the quality of the anchor (when the descriptor is re-used) will be highest.\n\t\t* The caller may decide to apply offsets from this initial pose, but is advised to stay relatively close to the\n\t\t* original pose location for highest fidelity. */\n\t\tvirtual EVRSpatialAnchorError CreateSpatialAnchorFromPose( TrackedDeviceIndex_t unDeviceIndex, ETrackingUniverseOrigin eOrigin, SpatialAnchorPose_t *pPose, SpatialAnchorHandle_t *pHandleOut ) = 0;\n\n\t\t/** Get the pose for a given handle.  This is intended to be cheap enough to call every frame (or fairly often)\n\t\t* so that the driver can refine this position when it has more information available. */\n\t\tvirtual EVRSpatialAnchorError GetSpatialAnchorPose( SpatialAnchorHandle_t unHandle, ETrackingUniverseOrigin eOrigin, SpatialAnchorPose_t *pPoseOut ) = 0;\n\n\t\t/** Get the descriptor for a given handle.  This will be empty for handles where the driver has not\n\t\t* yet built a descriptor.  It will be the application-supplied descriptor for previously saved anchors\n\t\t* that the application is requesting poses for.  If the driver has called UpdateSpatialAnchorDescriptor()\n\t\t* already in this session, it will be the descriptor provided by the driver.\n\t\t* Returns true if the descriptor fits into the buffer, else false.  Buffer size should be at least\n\t\t* k_unMaxSpatialAnchorDescriptorSize. */\n\t\tvirtual EVRSpatialAnchorError GetSpatialAnchorDescriptor( SpatialAnchorHandle_t unHandle, VR_OUT_STRING() char *pchDescriptorOut, uint32_t *punDescriptorBufferLenInOut ) = 0;\n\n\t};\n\n\tstatic const char * const IVRSpatialAnchors_Version = \"IVRSpatialAnchors_001\";\n\n} // namespace vr\n\n// ivrdebug.h\n\nnamespace vr\n{\n\tenum EVRDebugError\n\t{\n\t\tVRDebugError_Success = 0,\n\t\tVRDebugError_BadParameter\n\t};\n\n\t/** Handle for vr profiler events */\n\ttypedef uint64_t VrProfilerEventHandle_t;\n\n\tclass IVRDebug\n\t{\n\tpublic:\n\n\t\t/** Create a vr profiler discrete event (point)\n\t\t* The event will be associated with the message provided in pchMessage, and the current\n\t\t* time will be used as the event timestamp. */\n\t\tvirtual EVRDebugError EmitVrProfilerEvent( const char *pchMessage ) = 0;\n\n\t\t/** Create an vr profiler duration event (line)\n\t\t* The current time will be used as the timestamp for the start of the line.\n\t\t* On success, pHandleOut will contain a handle valid for terminating this event. */\n\t\tvirtual EVRDebugError BeginVrProfilerEvent( VrProfilerEventHandle_t *pHandleOut ) = 0;\n\n\t\t/** Terminate a vr profiler event\n\t\t* The event associated with hHandle will be considered completed when this method is called.\n\t\t* The current time will be used assocaited to the termination time of the event, and\n\t\t* pchMessage will be used as the event title. */\n\t\tvirtual EVRDebugError FinishVrProfilerEvent( VrProfilerEventHandle_t hHandle, const char *pchMessage ) = 0;\n\n\t\t/** Sends a request to the driver for the specified device and returns the response. The maximum response size is 32k,\n\t\t* but this method can be called with a smaller buffer. If the response exceeds the size of the buffer, it is truncated.\n\t\t* The size of the response including its terminating null is returned. */\n\t\tvirtual uint32_t DriverDebugRequest( vr::TrackedDeviceIndex_t unDeviceIndex, const char *pchRequest, VR_OUT_STRING() char *pchResponseBuffer, uint32_t unResponseBufferSize ) = 0;\n\n\t};\n\n\tstatic const char * const IVRDebug_Version = \"IVRDebug_001\";\n\n} // namespace vr\n\n// ivripcresourcemanagerclient.h\n\nnamespace vr\n{\n\n// -----------------------------------------------------------------------------\n// Purpose: Interact with the IPCResourceManager\n// -----------------------------------------------------------------------------\nclass IVRIPCResourceManagerClient\n{\npublic:\n\t/** Create a new tracked Vulkan Image\n\t *\n\t * nImageFormat: in VkFormat\n\t */\n\tvirtual bool NewSharedVulkanImage( uint32_t nImageFormat, uint32_t nWidth, uint32_t nHeight, bool bRenderable, bool bMappable, bool bComputeAccess, uint32_t unMipLevels, uint32_t unArrayLayerCount, uint32_t unAdditionalVkCreateFlags, uint32_t unAdditionalVkUsageFlags, vr::SharedTextureHandle_t *pSharedHandle ) = 0;\n\n\t/** Create a new tracked Vulkan Buffer */\n\tvirtual bool NewSharedVulkanBuffer( uint32_t nSize, uint32_t nUsageFlags, vr::SharedTextureHandle_t *pSharedHandle ) = 0;\n\n\t/** Create a new tracked Vulkan Semaphore */\n\tvirtual bool NewSharedVulkanSemaphore( bool bCounting, vr::SharedTextureHandle_t *pSharedHandle ) = 0;\n\n\t/** Grab a reference to hSharedHandle, and optionally generate a new IPC handle if pNewIpcHandle is not nullptr  */\n\tvirtual bool RefResource( vr::SharedTextureHandle_t hSharedHandle, uint64_t *pNewIpcHandle ) = 0;\n\n\t/** Drop a reference to hSharedHandle */\n\tvirtual bool UnrefResource( vr::SharedTextureHandle_t hSharedHandle ) = 0;\n\n\t/* Get all the DRM formats we support using DMA-BUF images for.\n\t *\n\t * pOutFormatCount and pOutFormats function like Vulkan:\n\t *   - If pOutFormats is NULL, then pOutFormatCount will be overwritten with the format count.\n\t *   - If pOutFormats is not NULL, then pOutFormatCount specifies the size of the pOutFormats array,\n\t *       and will be overwritten with the number of formats written to the array.\n\t *\n\t * If the function fails, false is returned, and pOutFormatCount will be 0.\n\t * Supported on Linux only.\n\t */\n\tvirtual bool GetDmabufFormats( uint32_t *pOutFormatCount, uint32_t *pOutFormats ) = 0;\n\n\t/** Get dmabuf modifiers we are allowed to use.\n\t *\n\t * pOutModifierCount and pOutModifiers function like Vulkan:\n\t *   - If pOutModifiers is NULL, then pOutModifierCount will be overwritten with the modifier count.\n\t *   - If pOutModifiers is not NULL, then pOutModifierCount specifies the size of the pOutModifiers array,\n\t *       and will be overwritten with the number of modifiers written to the array.\n\t *\n\t * If modifiers are not supported, a single DRM_FORMAT_MOD_INVALID entry will be returned.\n\t *\n\t * If the function fails, false is returned, and pOutModifierCount will be 0.\n\t * Supported on Linux only.\n\t */\n\tvirtual bool GetDmabufModifiers( vr::EVRApplicationType eApplicationType, uint32_t unDRMFormat, uint32_t *pOutModifierCount, uint64_t *pOutModifiers ) = 0;\n\n\t/** Import a dmabuf directly.\n\t * Note: the FD you pass in will be dup'ed, so you must close it yourself.\n\t * This function does NOT take ownership of the fd you pass in.\n\t * Supported on Linux only.\n\t */\n\tvirtual bool ImportDmabuf( vr::EVRApplicationType eApplicationType, vr::DmabufAttributes_t *pDmabufAttributes, vr::SharedTextureHandle_t *pSharedHandle ) = 0;\n\n\t/** Consumes an IPC handle (eg. from RefResource) and returns a file-descriptor.\n\t * Caller acquires ownership of fd and is responsible for closing it.\n\t * Supported on Linux only.\n\t */\n\tvirtual bool ReceiveSharedFd( uint64_t ulIpcHandle, int *pOutFd ) = 0;\n\nprotected:\n\t/** Non-deletable */\n\tvirtual ~IVRIPCResourceManagerClient() {};\n};\n\nstatic const char *IVRIPCResourceManagerClient_Version = \"IVRIPCResourceManagerClient_003\";\n\n}\n// End\n\n#endif // _OPENVR_API\n\n\n\nnamespace vr\n{\n#if !defined( OPENVR_INTERFACE_INTERNAL )\n\n\t/** Finds the active installation of the VR API and initializes it. The provided path must be absolute\n\t* or relative to the current working directory. These are the local install versions of the equivalent\n\t* functions in steamvr.h and will work without a local Steam install.\n\t*\n\t* This path is to the \"root\" of the VR API install. That's the directory with\n\t* the \"drivers\" directory and a platform (i.e. \"win32\") directory in it, not the directory with the DLL itself.\n\t*\n\t* pStartupInfo is reserved for future use.\n\t*/\n\tinline IVRSystem *VR_Init( EVRInitError *peError, EVRApplicationType eApplicationType, const char *pStartupInfo = nullptr );\n\n\t/** unloads vrclient.dll. Any interface pointers from the interface are\n\t* invalid after this point */\n\tinline void VR_Shutdown();\n\n\t/** Returns true if there is an HMD attached. This check is as lightweight as possible and\n\t* can be called outside of VR_Init/VR_Shutdown. It should be used when an application wants\n\t* to know if initializing VR is a possibility but isn't ready to take that step yet.\n\t*/\n\tVR_INTERFACE bool VR_CALLTYPE VR_IsHmdPresent();\n\n\t/** Returns true if the OpenVR runtime is installed. */\n\tVR_INTERFACE bool VR_CALLTYPE VR_IsRuntimeInstalled();\n\n\t/** Returns where the OpenVR runtime is installed. */\n\tVR_INTERFACE bool VR_GetRuntimePath( VR_OUT_STRING() char *pchPathBuffer, uint32_t unBufferSize, uint32_t *punRequiredBufferSize );\n\t\n\t/** Returns the name of the enum value for an EVRInitError. This function may be called outside of VR_Init()/VR_Shutdown(). */\n\tVR_INTERFACE const char *VR_CALLTYPE VR_GetVRInitErrorAsSymbol( EVRInitError error );\n\n\t/** Returns an English string for an EVRInitError. Applications should call VR_GetVRInitErrorAsSymbol instead and\n\t* use that as a key to look up their own localized error message. This function may be called outside of VR_Init()/VR_Shutdown(). */\n\tVR_INTERFACE const char *VR_CALLTYPE VR_GetVRInitErrorAsEnglishDescription( EVRInitError error );\n\n\t/** Returns the interface of the specified version. This method must be called after VR_Init. The\n\t* pointer returned is valid until VR_Shutdown is called.\n\t*/\n\tVR_INTERFACE void *VR_CALLTYPE VR_GetGenericInterface( const char *pchInterfaceVersion, EVRInitError *peError );\n\n\t/** Returns whether the interface of the specified version exists.\n\t*/\n\tVR_INTERFACE bool VR_CALLTYPE VR_IsInterfaceVersionValid( const char *pchInterfaceVersion );\n\n\t/** Returns a token that represents whether the VR interface handles need to be reloaded */\n\tVR_INTERFACE uint32_t VR_CALLTYPE VR_GetInitToken();\n\n\t// These typedefs allow old enum names from SDK 0.9.11 to be used in applications.\n\t// They will go away in the future.\n\ttypedef EVRInitError HmdError;\n\ttypedef EVREye Hmd_Eye;\n\ttypedef EColorSpace ColorSpace;\n\ttypedef ETrackingResult HmdTrackingResult;\n\ttypedef ETrackedDeviceClass TrackedDeviceClass;\n\ttypedef ETrackingUniverseOrigin TrackingUniverseOrigin;\n\ttypedef ETrackedDeviceProperty TrackedDeviceProperty;\n\ttypedef ETrackedPropertyError TrackedPropertyError;\n\ttypedef EVRSubmitFlags VRSubmitFlags_t;\n\ttypedef EVRState VRState_t;\n\ttypedef ECollisionBoundsStyle CollisionBoundsStyle_t;\n\ttypedef EVROverlayError VROverlayError;\n\ttypedef EVRFirmwareError VRFirmwareError;\n\ttypedef EVRCompositorError VRCompositorError;\n\ttypedef EVRScreenshotError VRScreenshotsError;\n\n\tinline uint32_t &VRToken()\n\t{\n\t\tstatic uint32_t token;\n\t\treturn token;\n\t}\n\n\tclass COpenVRContext\n\t{\n\tpublic:\n\t\tCOpenVRContext() { Clear(); }\n\t\tvoid Clear();\n\n\t\tinline void CheckClear()\n\t\t{\n\t\t\tif ( VRToken() != VR_GetInitToken() )\n\t\t\t{\n\t\t\t\tClear();\n\t\t\t\tVRToken() = VR_GetInitToken();\n\t\t\t}\n\t\t}\n\n\t\tIVRSystem *VRSystem()\n\t\t{\n\t\t\tCheckClear();\n\t\t\tif ( m_pVRSystem == nullptr )\n\t\t\t{\n\t\t\t\tEVRInitError eError;\n\t\t\t\tm_pVRSystem = ( IVRSystem * )VR_GetGenericInterface( IVRSystem_Version, &eError );\n\t\t\t}\n\t\t\treturn m_pVRSystem;\n\t\t}\n\t\tIVRChaperone *VRChaperone()\n\t\t{\n\t\t\tCheckClear();\n\t\t\tif ( m_pVRChaperone == nullptr )\n\t\t\t{\n\t\t\t\tEVRInitError eError;\n\t\t\t\tm_pVRChaperone = ( IVRChaperone * )VR_GetGenericInterface( IVRChaperone_Version, &eError );\n\t\t\t}\n\t\t\treturn m_pVRChaperone;\n\t\t}\n\n\t\tIVRChaperoneSetup *VRChaperoneSetup()\n\t\t{\n\t\t\tCheckClear();\n\t\t\tif ( m_pVRChaperoneSetup == nullptr )\n\t\t\t{\n\t\t\t\tEVRInitError eError;\n\t\t\t\tm_pVRChaperoneSetup = ( IVRChaperoneSetup * )VR_GetGenericInterface( IVRChaperoneSetup_Version, &eError );\n\t\t\t}\n\t\t\treturn m_pVRChaperoneSetup;\n\t\t}\n\n\t\tIVRCompositor *VRCompositor()\n\t\t{\n\t\t\tCheckClear();\n\t\t\tif ( m_pVRCompositor == nullptr )\n\t\t\t{\n\t\t\t\tEVRInitError eError;\n\t\t\t\tm_pVRCompositor = ( IVRCompositor * )VR_GetGenericInterface( IVRCompositor_Version, &eError );\n\t\t\t}\n\t\t\treturn m_pVRCompositor;\n\t\t}\n\n\t\tIVROverlay *VROverlay()\n\t\t{\n\t\t\tCheckClear();\n\t\t\tif ( m_pVROverlay == nullptr )\n\t\t\t{\n\t\t\t\tEVRInitError eError;\n\t\t\t\tm_pVROverlay = ( IVROverlay * )VR_GetGenericInterface( IVROverlay_Version, &eError );\n\t\t\t}\n\t\t\treturn m_pVROverlay;\n\t\t}\n\n\t\tIVROverlayView *VROverlayView()\n\t\t{\n\t\t\tCheckClear();\n\t\t\tif ( m_pVROverlayView == nullptr )\n\t\t\t{\n\t\t\t\tEVRInitError eError;\n\t\t\t\tm_pVROverlayView = ( IVROverlayView * ) VR_GetGenericInterface( IVROverlayView_Version, &eError );\n\t\t\t}\n\t\t\treturn m_pVROverlayView;\n\t\t}\n\n\t\tIVRHeadsetView *VRHeadsetView()\n\t\t{\n\t\t\tCheckClear();\n\t\t\tif ( m_pVRHeadsetView == nullptr )\n\t\t\t{\n\t\t\t\tEVRInitError eError;\n\t\t\t\tm_pVRHeadsetView = ( IVRHeadsetView * ) VR_GetGenericInterface( IVRHeadsetView_Version, &eError );\n\t\t\t}\n\t\t\treturn m_pVRHeadsetView;\n\t\t}\n\n\t\tIVRResources *VRResources()\n\t\t{\n\t\t\tCheckClear();\n\t\t\tif ( m_pVRResources == nullptr )\n\t\t\t{\n\t\t\t\tEVRInitError eError;\n\t\t\t\tm_pVRResources = (IVRResources *)VR_GetGenericInterface( IVRResources_Version, &eError );\n\t\t\t}\n\t\t\treturn m_pVRResources;\n\t\t}\n\n\t\tIVRScreenshots *VRScreenshots()\n\t\t{\n\t\t\tCheckClear();\n\t\t\tif ( m_pVRScreenshots == nullptr )\n\t\t\t{\n\t\t\t\tEVRInitError eError;\n\t\t\t\tm_pVRScreenshots = ( IVRScreenshots * )VR_GetGenericInterface( IVRScreenshots_Version, &eError );\n\t\t\t}\n\t\t\treturn m_pVRScreenshots;\n\t\t}\n\n\t\tIVRRenderModels *VRRenderModels()\n\t\t{\n\t\t\tCheckClear();\n\t\t\tif ( m_pVRRenderModels == nullptr )\n\t\t\t{\n\t\t\t\tEVRInitError eError;\n\t\t\t\tm_pVRRenderModels = ( IVRRenderModels * )VR_GetGenericInterface( IVRRenderModels_Version, &eError );\n\t\t\t}\n\t\t\treturn m_pVRRenderModels;\n\t\t}\n\n\t\tIVRExtendedDisplay *VRExtendedDisplay()\n\t\t{\n\t\t\tCheckClear();\n\t\t\tif ( m_pVRExtendedDisplay == nullptr )\n\t\t\t{\n\t\t\t\tEVRInitError eError;\n\t\t\t\tm_pVRExtendedDisplay = ( IVRExtendedDisplay * )VR_GetGenericInterface( IVRExtendedDisplay_Version, &eError );\n\t\t\t}\n\t\t\treturn m_pVRExtendedDisplay;\n\t\t}\n\n\t\tIVRSettings *VRSettings()\n\t\t{\n\t\t\tCheckClear();\n\t\t\tif ( m_pVRSettings == nullptr )\n\t\t\t{\n\t\t\t\tEVRInitError eError;\n\t\t\t\tm_pVRSettings = ( IVRSettings * )VR_GetGenericInterface( IVRSettings_Version, &eError );\n\t\t\t}\n\t\t\treturn m_pVRSettings;\n\t\t}\n\n\t\tIVRApplications *VRApplications()\n\t\t{\n\t\t\tCheckClear();\n\t\t\tif ( m_pVRApplications == nullptr )\n\t\t\t{\n\t\t\t\tEVRInitError eError;\n\t\t\t\tm_pVRApplications = ( IVRApplications * )VR_GetGenericInterface( IVRApplications_Version, &eError );\n\t\t\t}\n\t\t\treturn m_pVRApplications;\n\t\t}\n\n\t\tIVRTrackedCamera *VRTrackedCamera()\n\t\t{\n\t\t\tCheckClear();\n\t\t\tif ( m_pVRTrackedCamera == nullptr )\n\t\t\t{\n\t\t\t\tEVRInitError eError;\n\t\t\t\tm_pVRTrackedCamera = ( IVRTrackedCamera * )VR_GetGenericInterface( IVRTrackedCamera_Version, &eError );\n\t\t\t}\n\t\t\treturn m_pVRTrackedCamera;\n\t\t}\n\n\t\tIVRDriverManager *VRDriverManager()\n\t\t{\n\t\t\tCheckClear();\n\t\t\tif ( !m_pVRDriverManager )\n\t\t\t{\n\t\t\t\tEVRInitError eError;\n\t\t\t\tm_pVRDriverManager = ( IVRDriverManager * )VR_GetGenericInterface( IVRDriverManager_Version, &eError );\n\t\t\t}\n\t\t\treturn m_pVRDriverManager;\n\t\t}\n\n\t\tIVRInput *VRInput()\n\t\t{\n\t\t\tCheckClear();\n\t\t\tif ( !m_pVRInput )\n\t\t\t{\n\t\t\t\tEVRInitError eError;\n\t\t\t\tm_pVRInput = (IVRInput *)VR_GetGenericInterface( IVRInput_Version, &eError );\n\t\t\t}\n\t\t\treturn m_pVRInput;\n\t\t}\n\n\t\tIVRIOBuffer *VRIOBuffer()\n\t\t{\n\t\t\tif ( !m_pVRIOBuffer )\n\t\t\t{\n\t\t\t\tEVRInitError eError;\n\t\t\t\tm_pVRIOBuffer = ( IVRIOBuffer * )VR_GetGenericInterface( IVRIOBuffer_Version, &eError );\n\t\t\t}\n\t\t\treturn m_pVRIOBuffer;\n\t\t}\n\n\t\tIVRSpatialAnchors *VRSpatialAnchors()\n\t\t{\n\t\t\tCheckClear();\n\t\t\tif ( !m_pVRSpatialAnchors )\n\t\t\t{\n\t\t\t\tEVRInitError eError;\n\t\t\t\tm_pVRSpatialAnchors = (IVRSpatialAnchors *)VR_GetGenericInterface( IVRSpatialAnchors_Version, &eError );\n\t\t\t}\n\t\t\treturn m_pVRSpatialAnchors;\n\t\t}\n\n\t\tIVRDebug *VRDebug()\n\t\t{\n\t\t\tCheckClear();\n\t\t\tif ( !m_pVRDebug )\n\t\t\t{\n\t\t\t\tEVRInitError eError;\n\t\t\t\tm_pVRDebug = (IVRDebug *)VR_GetGenericInterface( IVRDebug_Version, &eError );\n\t\t\t}\n\t\t\treturn m_pVRDebug;\n\t\t}\n\n\t\tIVRNotifications *VRNotifications()\n\t\t{\n\t\t\tCheckClear();\n\t\t\tif ( !m_pVRNotifications )\n\t\t\t{\n\t\t\t\tEVRInitError eError;\n\t\t\t\tm_pVRNotifications = ( IVRNotifications * )VR_GetGenericInterface( IVRNotifications_Version, &eError );\n\t\t\t}\n\t\t\treturn m_pVRNotifications;\n\t\t}\n\n\t\tIVRIPCResourceManagerClient *VRIPCResourceManager()\n\t\t{\n\t\t\tCheckClear();\n\t\t\tif ( !m_pVRIPCResourceManagerClient )\n\t\t\t{\n\t\t\t\tEVRInitError eError;\n\t\t\t\tm_pVRIPCResourceManagerClient = ( IVRIPCResourceManagerClient * )VR_GetGenericInterface( IVRIPCResourceManagerClient_Version, &eError );\n\t\t\t}\n\t\t\treturn m_pVRIPCResourceManagerClient;\n\t\t}\n\t\t\n\tprivate:\n\t\tIVRSystem\t\t\t*m_pVRSystem;\n\t\tIVRChaperone\t\t*m_pVRChaperone;\n\t\tIVRChaperoneSetup\t*m_pVRChaperoneSetup;\n\t\tIVRCompositor\t\t*m_pVRCompositor;\n\t\tIVRHeadsetView\t\t*m_pVRHeadsetView;\n\t\tIVROverlay\t\t\t*m_pVROverlay;\n\t\tIVROverlayView\t\t*m_pVROverlayView;\n\t\tIVRResources\t\t*m_pVRResources;\n\t\tIVRRenderModels\t\t*m_pVRRenderModels;\n\t\tIVRExtendedDisplay\t*m_pVRExtendedDisplay;\n\t\tIVRSettings\t\t\t*m_pVRSettings;\n\t\tIVRApplications\t\t*m_pVRApplications;\n\t\tIVRTrackedCamera\t*m_pVRTrackedCamera;\n\t\tIVRScreenshots\t\t*m_pVRScreenshots;\n\t\tIVRDriverManager\t*m_pVRDriverManager;\n\t\tIVRInput\t\t\t*m_pVRInput;\n\t\tIVRIOBuffer\t\t\t*m_pVRIOBuffer;\n\t\tIVRSpatialAnchors   *m_pVRSpatialAnchors;\n\t\tIVRDebug\t\t\t*m_pVRDebug;\n\t\tIVRNotifications\t*m_pVRNotifications;\n\t\tIVRIPCResourceManagerClient *m_pVRIPCResourceManagerClient;\n\t};\n\n\tinline COpenVRContext &OpenVRInternal_ModuleContext()\n\t{\n\t\tstatic void *ctx[ sizeof( COpenVRContext ) / sizeof( void * ) ];\n\t\treturn *( COpenVRContext * )ctx; // bypass zero-init constructor\n\t}\n\n\tinline IVRSystem *VR_CALLTYPE VRSystem() { return OpenVRInternal_ModuleContext().VRSystem(); }\n\tinline IVRChaperone *VR_CALLTYPE VRChaperone() { return OpenVRInternal_ModuleContext().VRChaperone(); }\n\tinline IVRChaperoneSetup *VR_CALLTYPE VRChaperoneSetup() { return OpenVRInternal_ModuleContext().VRChaperoneSetup(); }\n\tinline IVRCompositor *VR_CALLTYPE VRCompositor() { return OpenVRInternal_ModuleContext().VRCompositor(); }\n\tinline IVROverlay *VR_CALLTYPE VROverlay() { return OpenVRInternal_ModuleContext().VROverlay(); }\n\tinline IVROverlayView *VR_CALLTYPE VROverlayView() { return OpenVRInternal_ModuleContext().VROverlayView(); }\n\tinline IVRHeadsetView *VR_CALLTYPE VRHeadsetView() { return OpenVRInternal_ModuleContext().VRHeadsetView(); }\n\tinline IVRScreenshots *VR_CALLTYPE VRScreenshots() { return OpenVRInternal_ModuleContext().VRScreenshots(); }\n\tinline IVRRenderModels *VR_CALLTYPE VRRenderModels() { return OpenVRInternal_ModuleContext().VRRenderModels(); }\n\tinline IVRApplications *VR_CALLTYPE VRApplications() { return OpenVRInternal_ModuleContext().VRApplications(); }\n\tinline IVRSettings *VR_CALLTYPE VRSettings() { return OpenVRInternal_ModuleContext().VRSettings(); }\n\tinline IVRResources *VR_CALLTYPE VRResources() { return OpenVRInternal_ModuleContext().VRResources(); }\n\tinline IVRExtendedDisplay *VR_CALLTYPE VRExtendedDisplay() { return OpenVRInternal_ModuleContext().VRExtendedDisplay(); }\n\tinline IVRTrackedCamera *VR_CALLTYPE VRTrackedCamera() { return OpenVRInternal_ModuleContext().VRTrackedCamera(); }\n\tinline IVRDriverManager *VR_CALLTYPE VRDriverManager() { return OpenVRInternal_ModuleContext().VRDriverManager(); }\n\tinline IVRInput *VR_CALLTYPE VRInput() { return OpenVRInternal_ModuleContext().VRInput(); }\n\tinline IVRIOBuffer *VR_CALLTYPE VRIOBuffer() { return OpenVRInternal_ModuleContext().VRIOBuffer(); }\n\tinline IVRSpatialAnchors *VR_CALLTYPE VRSpatialAnchors() { return OpenVRInternal_ModuleContext().VRSpatialAnchors(); }\n\tinline IVRNotifications *VR_CALLTYPE VRNotifications() { return OpenVRInternal_ModuleContext().VRNotifications(); }\n\tinline IVRDebug *VR_CALLTYPE VRDebug() { return OpenVRInternal_ModuleContext().VRDebug(); }\n\tinline IVRIPCResourceManagerClient *VR_CALLTYPE VRIPCResourceManager() { return OpenVRInternal_ModuleContext().VRIPCResourceManager(); }\n\n\tinline void COpenVRContext::Clear()\n\t{\n\t\tm_pVRSystem = nullptr;\n\t\tm_pVRChaperone = nullptr;\n\t\tm_pVRChaperoneSetup = nullptr;\n\t\tm_pVRCompositor = nullptr;\n\t\tm_pVROverlay = nullptr;\n\t\tm_pVROverlayView = nullptr;\n\t\tm_pVRHeadsetView = nullptr;\n\t\tm_pVRRenderModels = nullptr;\n\t\tm_pVRExtendedDisplay = nullptr;\n\t\tm_pVRSettings = nullptr;\n\t\tm_pVRApplications = nullptr;\n\t\tm_pVRTrackedCamera = nullptr;\n\t\tm_pVRResources = nullptr;\n\t\tm_pVRScreenshots = nullptr;\n\t\tm_pVRDriverManager = nullptr;\n\t\tm_pVRInput = nullptr;\n\t\tm_pVRIOBuffer = nullptr;\n\t\tm_pVRSpatialAnchors = nullptr;\n\t\tm_pVRNotifications = nullptr;\n\t\tm_pVRDebug = nullptr;\n\t\tm_pVRIPCResourceManagerClient = nullptr;\n\t}\n\t\n\tVR_INTERFACE uint32_t VR_CALLTYPE VR_InitInternal2( EVRInitError *peError, EVRApplicationType eApplicationType, const char *pStartupInfo );\n\tVR_INTERFACE void VR_CALLTYPE VR_ShutdownInternal();\n\n\t/** Finds the active installation of vrclient.dll and initializes it */\n\tinline IVRSystem *VR_Init( EVRInitError *peError, EVRApplicationType eApplicationType, const char *pStartupInfo )\n\t{\n\t\tIVRSystem *pVRSystem = nullptr;\n\n\t\tEVRInitError eError;\n\t\tVRToken() = VR_InitInternal2( &eError, eApplicationType, pStartupInfo );\n\t\tCOpenVRContext &ctx = OpenVRInternal_ModuleContext();\n\t\tctx.Clear();\n\n\t\tif ( eError == VRInitError_None && !VR_IsInterfaceVersionValid( IVRSystem_Version ) )\n\t\t{\n\t\t\teError = VRInitError_Init_InterfaceNotFound;\n\t\t}\n\n\t\tif ( eError == VRInitError_None )\n\t\t{\n\t\t\tpVRSystem = VRSystem();\n\t\t\teError = pVRSystem->SetSDKVersion( k_nSteamVRVersionMajor, k_nSteamVRVersionMinor, k_nSteamVRVersionBuild );\n\t\t}\n\n\t\tif ( eError != VRInitError_None )\n\t\t{\n\t\t\tpVRSystem = nullptr;\n\t\t\tVR_ShutdownInternal();\n\t\t}\n\n\t\tif ( peError )\n\t\t\t*peError = eError;\n\t\treturn pVRSystem;\n\t}\n\n\t/** unloads vrclient.dll. Any interface pointers from the interface are\n\t* invalid after this point */\n\tinline void VR_Shutdown()\n\t{\n\t\tVR_ShutdownInternal();\n\t}\n\n#endif // OPENVR_INTERFACE_INTERNAL\n}\n"
  }
]